'use strict'; const { updateElementReadonly, updateElementInvalid, setPropValue, getPropValue } = require('../../utils/assets'); exports.template = /* html */`
Time Frame
Time: (s)
FPS
Start:
Frames:
End:
WrapMode Speed
`; exports.style = /* css */` .container { padding: 4px; } .container[multiple-invalid] > *:not(.multiple-warn-tip) { display: none!important; } .container[multiple-invalid] > .multiple-warn-tip { display: block; } .container .multiple-warn-tip { display: none; text-align: center; color: var(--color-focus-contrast-weakest); margin-top: 8px; } .container > .show-type-wrap { text-align: center; margin-top: 8px; } .container > .clips { padding: 4px; border-radius: calc(var(--size-normal-radius) * 1px); overflow-y: auto; max-height: 250px; background: var(--color-normal-fill-emphasis); margin-bottom: 8px; } .container > .clips > .clip {} .container > .clips > .clip > .table { border-radius: 4px; border-bottom-right-radius: 0; background: var(--color-normal-fill-emphasis); } .container > .clips > .clip > .table > .header { display: flex; padding: 0 4px; margin-bottom: 4px; border-bottom: 1px solid var(--color-default-border); color: var(--color-default-fill-weakest); } .container > .clips > .clip > .table > .line { display: flex; padding: 0 4px; cursor: pointer; } .container > .clips > .clip > .table > .line[active] { background-color: var(--color-info-fill-important); } .container > .clips > .clip > .table > .line > .name, .container > .clips > .clip > .table > .header > .name { flex: 1; } .container > .clips > .clip > .table > .line > .time, .container > .clips > .clip > .table > .header > .time { width: 40px; text-align: right; } .container > .clips > .clip > .add-clip { display: flex; justify-content: flex-end; } .container > .clips > .clip > .add-clip > .button > ui-icon { padding: 0 4px; border-radius: 2px; line-height: 16px; margin-left: 10px; margin-right: 2px; cursor: pointer; } .container > .clips > .clip > .add-clip > .button > ui-icon:hover { background: var(--color-normal-fill); } .container > .clips > .clip > .add-clip > .button > ui-icon[disabled] { opacity: 0.55; pointer-events: none; } .container > .editor[disabled] { opacity: 0.55; pointer-events: none; } .container > .editor > .anim-name { display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 5px; } .container > .editor > .anim-name { display: flex; justify-content: space-between; margin-top: 10px; margin-bottom: 5px; } .container > .editor > .anim-name > ui-icon { font-size: 18px; margin: auto 10px; margin-left: 0; } .container > .editor > .anim-name > ui-input { flex: 1; } .container > .editor > .clip-info { font-size: 11px; color: var(--color-normal-fill-weakest); display: flex; justify-content: space-between; margin-bottom: 5px; } .container > .editor ui-num-input { color: var(--color-normal-contrast); width: 50px; } .container > .editor > .cut-info { margin: 5px 0 10px; line-height: 20px; display: flex; justify-content: space-between; } .container > .editor > .cut-info > .frames-info { font-size: 11px; color: var(--color-normal-fill-weakest); } .container > .editor > .edit-ruler { border: 1px solid var(--color-normal-contrast-important); border-top: transparent; padding: 0 20px; display: flex; flex-direction: column; position: relative; user-select: none; } .container > .editor > .edit-ruler > .grid { position: relative; display: flex; font-size: 10px; box-sizing: border-box; height: 18px; } .container > .editor > .edit-ruler > .grid > .label-item { position: absolute; } .container > .editor > .edit-ruler > .ruler-gear > .start { position: absolute; top: 3px; } .container > .editor > .edit-ruler > .ruler-gear > .grid-item { display: flex; justify-content: space-between; align-items: flex-end; } .container > .editor > .edit-ruler > .grid .sm-grid { display: inline-block; width: 20%; height: 4px; border-left: 1px solid var(--color-normal-contrast-important); } .container > .editor > .edit-ruler > .grid .mid-grid { display: inline-block; width: 20%; height: 8px; border-left: 1px solid var(--color-normal-contrast-important); } .container > .editor > .edit-ruler > .control-wrap { position: relative; width: 100%; left: 0; top: -16px; } .container > .editor > .edit-ruler > .control-wrap > .control-duration { background: var(--color-focus-fill); position: absolute; height: 16px; opacity: 0.2; } .container > .editor > .edit-ruler > .control-wrap > .control { user-select: none; width: 6px; position: absolute; cursor: pointer; opacity: 0.7; } .container > .editor > .edit-ruler > .control-wrap > .control:hover { opacity: 1; } .container > .editor > .edit-ruler > .control-wrap > .control > .box { background: var(--color-focus-fill); width: 100%; height: 10px; } .container > .editor > .edit-ruler > .control-wrap > .control > .direction { border: 3px solid var(--color-focus-fill); width: 0; height: 0; border-bottom-color: transparent; } .container > .editor > .edit-ruler > .control-wrap > .control-left > .direction { border-left-color: transparent; } .container > .editor > .edit-ruler > .control-wrap > .control-right > .direction { border-right-color: transparent; } .container > .editor > .edit-ruler > .control-wrap > .control-virtual { --color-focus-fill: var(--color-success-fill); display: none; } .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .box { background: var(--color-focus-fill); width: 100%; height: 10px; } .container > .editor > .edit-ruler > .control-wrap > .control-virtual[direction="right"] > .direction { border-right-color: transparent; border-left-color: var(--color-focus-fill); } .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .direction { border-left-color: transparent; } .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .number { margin-top: -47px; font-size: 10px; color: var(--color-success-fill-weaker); } `; exports.$ = { container: '.container', importAllAnimationsCheckbox: '.animator-config-import-all-animator ui-checkbox', importAllAnimatorWrap: '.animator-config-import-all-animator', clips: '.clips', editor: '.editor', clipName: '.clip-name', clipDuration: '.clip-duration', clipFPS: '.clip-fps', clipFrom: '.clip-from', clipTo: '.clip-to', clipFrames: '.clip-frames', wrapMode: '.wrap-mode', speed: '.speed', additiveEnabled: '.additive-enabled', refClip: '.ref-clip', rulerMaking: '.ruler-making', rulerGear: '.ruler-gear', controlWrap: '.control-wrap', controlDuration: '.control-duration', controlLeft: '.control-left', controlRight: '.control-right', controlVirtual: '.control-virtual', controlVirtualNumber: '.control-virtual-number', showTypeWrap: '.show-type-wrap', showType: '.show-type', }; /** * attribute corresponds to the edit element */ const Elements = { // infos put first infos: { ready() { const panel = this; Object.assign(panel, { animationInfos: null, }); }, update() { const panel = this; if (panel.meta && panel.meta.userData.animationImportSettings) { panel.animationInfos = panel.meta.userData.animationImportSettings; // Support multiple selection when the list display, limit the number of display clip name collection for renaming and new to determine whether the same name panel.clipNames = new Set(); for (const animationInfo of panel.animationInfos) { panel.clipNames.add(animationInfo.name); for (const subAnimInfo of animationInfo.splits) { panel.clipNames.add(subAnimInfo.name); } } } else { panel.animationInfos = null; } }, }, showType: { ready() { const panel = this; panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame'; panel.$.showType.addEventListener('change', (event) => { panel.animationTimeShowType = event.target.value === 0 ? 'time' : 'frame'; Elements.clips.update.call(panel); }); }, update() { const panel = this; if (!panel.animationInfos) { panel.$.showTypeWrap.style.display = 'none'; return; } else { panel.$.showTypeWrap.style.display = 'block'; } panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame'; }, }, clips: { ready() { const panel = this; Object.assign(panel, { splitClipIndex: 0, rawClipIndex: 0, currentClipInfo: null, }); }, update() { const panel = this; panel.$.clips.innerText = ''; if (!panel.animationInfos) { panel.$.clips.style.display = 'none'; return; } else { panel.$.clips.style.display = 'block'; } panel.updateRawClipInfo(); panel.updateCurrentClipInfo(); panel.animationInfos.forEach((animInfo, rawClipIndex) => { const clip = document.createElement('div'); clip.setAttribute('class', 'clip'); panel.$.clips.appendChild(clip); if (!animInfo.duration) { clip.setAttribute('disabled', 'true'); } const table = document.createElement('div'); table.setAttribute('class', 'table'); clip.appendChild(table); const header = document.createElement('div'); header.setAttribute('class', 'header'); table.appendChild(header); const name = document.createElement('div'); name.setAttribute('class', 'name'); name.innerHTML = `Clips ( ${animInfo.name} ) `; header.appendChild(name); const time = document.createElement('div'); time.setAttribute('class', 'time'); time.innerHTML = 'Start'; header.appendChild(time); const timeEnd = document.createElement('div'); timeEnd.setAttribute('class', 'time end'); timeEnd.innerHTML = 'End'; header.appendChild(timeEnd); animInfo.splits.forEach((subAnim, splitClipIndex) => { const line = document.createElement('div'); line.setAttribute('class', 'line'); if (panel.rawClipIndex === rawClipIndex && panel.splitClipIndex === splitClipIndex) { line.setAttribute('active', true); } table.appendChild(line); line.setAttribute('rawCLipIndex', rawClipIndex); line.setAttribute('splitClipIndex', splitClipIndex); line.addEventListener('click', () => { panel.onSelect(rawClipIndex, splitClipIndex); }); const name = document.createElement('div'); name.setAttribute('class', 'name'); name.innerHTML = subAnim.name; line.appendChild(name); const time = document.createElement('div'); time.setAttribute('class', 'time'); time.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.from.toFixed(3) : Math.round(subAnim.from * (subAnim.fps || panel.rawClipInfo.fps)); line.appendChild(time); const timeEnd = document.createElement('div'); timeEnd.setAttribute('class', 'time end'); timeEnd.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.to.toFixed(3) : Math.round(subAnim.to * (subAnim.fps || panel.rawClipInfo.fps)); line.appendChild(timeEnd); }); // Button area const addClip = document.createElement('div'); addClip.setAttribute('class', 'add-clip'); clip.appendChild(addClip); const button = document.createElement('div'); button.setAttribute('class', 'button'); addClip.appendChild(button); const addIcon = document.createElement('ui-icon'); addIcon.setAttribute('value', 'add'); addIcon.setAttribute('tooltip', 'Duplicate Selected'); updateElementReadonly.call(panel, addIcon); button.appendChild(addIcon); addIcon.addEventListener('click', () => { const newInfo = panel.newClipTemplate(); panel.clipNames.add(newInfo.name); panel.animationInfos[panel.rawClipIndex].splits.push(newInfo); Elements.clips.update.call(panel); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }); const miniIcon = document.createElement('ui-icon'); miniIcon.setAttribute('value', 'mini'); miniIcon.setAttribute('tooltip', 'Remove Selected'); updateElementReadonly.call(panel, miniIcon); button.prepend(miniIcon); miniIcon.addEventListener('click', () => { panel.updateCurrentClipInfo(); if (!panel.currentClipInfo) { return; } panel.clipNames.delete(panel.currentClipInfo.name); panel.animationInfos[panel.rawClipIndex].splits.splice(panel.splitClipIndex, 1); const length = panel.animationInfos[panel.rawClipIndex].splits.length; if (length > 0 && panel.splitClipIndex > 0 && panel.splitClipIndex >= length) { panel.splitClipIndex = length - 1; } Elements.clips.update.call(panel); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }); }); }, }, editor: { ready() { const panel = this; Object.assign(panel, { gridTableWith: panel.$.container.getBoundingClientRect().width, virtualControl: null, clipNames: [], }); panel.onClipNameBind = panel.onClipName.bind(panel); panel.$.clipName.addEventListener('confirm', panel.onClipNameBind); panel.onMouseDownBindLeft = panel.onMouseDown.bind(panel, 'left'); panel.onMouseDownBindRight = panel.onMouseDown.bind(panel, 'right'); panel.$.controlLeft.addEventListener('mousedown', panel.onMouseDownBindLeft); panel.$.controlRight.addEventListener('mousedown', panel.onMouseDownBindRight); panel.onCutClipBind = panel.onCutClip.bind(panel); panel.$.clipFrom.addEventListener('confirm', panel.onCutClipBind); panel.$.clipTo.addEventListener('confirm', panel.onCutClipBind); panel.onFpsChangeBind = panel.onFpsChange.bind(panel); panel.$.clipFPS.addEventListener('confirm', panel.onFpsChangeBind); panel.onWrapModeChangeBind = panel.onWrapModeChange.bind(panel); panel.$.wrapMode.addEventListener('confirm', panel.onWrapModeChangeBind); panel.onSpeedChangeBind = panel.onSpeedChange.bind(panel); panel.$.speed.addEventListener('confirm', panel.onSpeedChangeBind); panel.onAdditiveEnabledChangedBind = panel.onAdditiveEnabledChanged.bind(panel); panel.$.additiveEnabled.addEventListener('confirm', panel.onAdditiveEnabledChangedBind); panel.onRefClipChangedBind = panel.onRefClipChanged.bind(panel); panel.$.refClip.addEventListener('confirm', panel.onRefClipChangedBind); function observer() { const rect = panel.$.editor.getBoundingClientRect(); panel.gridTableWith = rect.width - 60; if (panel.gridTableWith < 0) { panel.gridTableWith = panel.$.container.getBoundingClientRect().width; } Elements.clips.update.call(panel); Elements.editor.update.call(panel); } panel.resizeObserver = new window.ResizeObserver(observer); panel.resizeObserver.observe(panel.$.editor); observer(); }, close() { const panel = this; panel.resizeObserver.unobserve(panel.$.editor); panel.$.clipName.removeEventListener('confirm', panel.onClipNameBind); panel.$.controlLeft.removeEventListener('mousedown', panel.onMouseDownBindLeft); panel.$.controlRight.removeEventListener('mousedown', panel.onMouseDownBindRight); panel.$.clipFrom.removeEventListener('confirm', panel.onCutClipBind); panel.$.clipTo.removeEventListener('confirm', panel.onCutClipBind); panel.$.clipFPS.removeEventListener('confirm', panel.onFpsChangeBind); panel.$.wrapMode.removeEventListener('confirm', panel.onWrapModeChangeBind); panel.$.speed.removeEventListener('confirm', panel.onSpeedChangeBind); panel.$.additiveEnabled.removeEventListener('confirm', panel.onAdditiveEnabledChangedBind); panel.$.refClip.removeEventListener('confirm', panel.onRefClipChangedBind); }, update() { const panel = this; panel.updateRawClipInfo(); panel.updateCurrentClipInfo(); panel.updateGridConfig(); if (!panel.currentClipInfo) { panel.$.editor.style.display = 'none'; return; } else { panel.$.editor.style.display = 'block'; } updateElementReadonly.call(panel, panel.$.editor); panel.$.clipName.value = panel.currentClipInfo.name; // ruler making panel.$.rulerMaking.innerText = ''; const maxNum = panel.gridConfig.mod + 1; for (let minNum = 1; minNum <= maxNum; minNum++) { // If the remaining cells are less than 1.5, hide the label if (((panel.gridConfig.width / panel.gridConfig.spacing) % 5) >= 1.5 || minNum !== maxNum) { const label = document.createElement('div'); label.setAttribute('class', 'label-item'); label.style.left = `${panel.gridConfig.spacing * 5 * (minNum - 1) - 6}px`; panel.$.rulerMaking.appendChild(label); const span = document.createElement('span'); span.setAttribute('class', 'mid-label'); span.innerText = (panel.gridConfig.labelStep * (minNum - 1)).toFixed(3); label.appendChild(span); } } const lastMakingLabel = document.createElement('div'); lastMakingLabel.setAttribute('class', 'label-item'); lastMakingLabel.style.left = `${panel.gridConfig.width}px`; panel.$.rulerMaking.appendChild(lastMakingLabel); lastMakingLabel.innerText = panel.rawClipInfo.duration.toFixed(3); // ruler gear panel.$.rulerGear.innerText = ''; Object.assign(panel.$.rulerGear.style, { 'margin-left': `${panel.gridConfig.spacing}px`, }); const firstRulerGear = document.createElement('div'); firstRulerGear.setAttribute('class', 'start'); firstRulerGear.style.left = `${0 - panel.gridConfig.spacing}px`; firstRulerGear.innerHTML = ''; panel.$.rulerGear.appendChild(firstRulerGear); for (let minNum = 0; minNum < panel.gridConfig.mod; minNum++) { const item = document.createElement('div'); item.setAttribute('class', 'grid-item'); item.style.width = `${panel.gridConfig.spacing * 5}px`; const lines = Array(4).fill(''); lines.push(''); item.innerHTML = lines.join(''); panel.$.rulerGear.appendChild(item); } const lastGearItem = document.createElement('div'); lastGearItem.setAttribute('class', 'grid-item'); lastGearItem.style.width = `${panel.gridConfig.rest * panel.gridConfig.spacing}px`; lastGearItem.innerHTML = Array(panel.gridConfig.rest).fill('').join(''); panel.$.rulerGear.appendChild(lastGearItem); // control-wrap Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle); Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle); Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle); }, }, mountAllAnimationsOnPrefab: { ready() { const panel = this; panel.$.importAllAnimationsCheckbox.addEventListener('change', panel.setProp.bind(panel, 'mountAllAnimationsOnPrefab', 'boolean')); panel.$.importAllAnimationsCheckbox.addEventListener('confirm', () => { panel.dispatch('snapshot'); }); }, update() { const panel = this; if (!panel.animationInfos) { panel.$.importAllAnimatorWrap.style.display = 'none'; return; } else { panel.$.importAllAnimatorWrap.style.display = 'block'; } panel.$.importAllAnimationsCheckbox.value = getPropValue.call(panel, panel.meta.userData.mountAllAnimationsOnPrefab, true); updateElementInvalid.call(panel, panel.$.importAllAnimationsCheckbox, 'mountAllAnimationsOnPrefab'); updateElementReadonly.call(panel, panel.$.importAllAnimationsCheckbox); }, }, }; async function callModelPreviewFunction(funcName, ...args) { return await Editor.Message.request('scene', 'call-preview-function', 'scene:model-preview', funcName, ...args); } exports.methods = { /** animation name -> uuid */ initAnimationNameToUUIDMap() { if (this.meta && this.meta.subMetas) { const animationNameToUUIDMap = new Map(); Object.keys(this.meta.subMetas).forEach((id) => { const subMeta = this.meta.subMetas[id]; if (subMeta.importer === 'gltf-animation') { const sourceName = subMeta.name; const animName = sourceName.slice(0, sourceName.lastIndexOf('.')); animationNameToUUIDMap.set(animName, subMeta.uuid); } }); this.animationNameToUUIDMap = animationNameToUUIDMap; } }, initAnimationInfos() { if (this.meta && this.meta.userData.animationImportSettings) { this.animationInfos = this.meta.userData.animationImportSettings; // Collect clip names for renaming and creating to determine whether the name is repeated this.clipNames = new Set(); for (const animationInfo of this.animationInfos) { this.clipNames.add(animationInfo.name); for (const subAnimInfo of animationInfo.splits) { this.clipNames.add(subAnimInfo.name); } } } else { this.animationInfos = null; } }, onSelect(rawClipIndex, splitClipIndex) { this.rawClipIndex = rawClipIndex; this.splitClipIndex = splitClipIndex; const isElementSelect = (element) => element.getAttribute('rawClipIndex') == rawClipIndex && element.getAttribute('splitClipIndex') == splitClipIndex; Elements.editor.update.call(this); this.$.clips.querySelectorAll('.line').forEach((child) => { if (isElementSelect(child)) { child.setAttribute('active', true); } else { child.removeAttribute('active'); } }); const curClipInfo = this.getCurClipInfo(); Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo); }, getCurClipInfo() { if (!this.animationInfos) { return null; } const animInfo = this.animationInfos[this.rawClipIndex]; const splitInfo = animInfo.splits[this.splitClipIndex]; if (!animInfo) { return null; } if (!splitInfo) { return null; } const rawClipUUID = this.animationNameToUUIDMap.get(animInfo.name); const clipUUID = this.animationNameToUUIDMap.get(splitInfo.name); let duration = animInfo.duration; let fps = animInfo.fps; let from = 0; let to = duration; if (splitInfo) { from = splitInfo.from; to = splitInfo.to; duration = to - from; if (splitInfo.fps !== undefined) { fps = splitInfo.fps; } // if (this.animationNameToUUIDMap.has(splitInfo.name)) { // clipUUID = this.animationNameToUUIDMap.get(splitInfo.name); // } } return { rawClipUUID, rawClipIndex: this.rawClipIndex, clipUUID, duration, fps, from, to, wrapMode: splitInfo.wrapMode, speed: splitInfo.speed || 1, additiveEnabled: splitInfo.additive?.enabled ?? false, refClip: splitInfo.additive?.refClip || '', }; }, getRightName(name) { if (!name) { return null; } const panel = this; do { const result = name.match(/(.*)_(\d{0,3})/); if (result) { name = `${result[1]}_${Number(result[2]) + 1}`; } else { name += '_1'; } } while (panel.clipNames.has(name)); return name; }, newClipTemplate() { const panel = this; // Verify the name return { name: panel.getRightName(panel.rawClipInfo.name), from: 0, to: panel.rawClipInfo.duration, wrapMode: 2 /* Loop */, speed: 1, }; }, updateCurrentClipInfo() { const panel = this; if (!panel.animationInfos) { panel.currentClipInfo = null; return; } const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex]; if (!info || !panel.gridTableWith) { panel.currentClipInfo = null; return; } const duration = info.to - info.from; const ctrlStart = (info.from / panel.rawClipInfo.duration) * panel.gridTableWith; const ctrlEnd = (info.to / panel.rawClipInfo.duration) * panel.gridTableWith; const durationWidth = (duration / panel.rawClipInfo.duration) * panel.gridTableWith; const fps = info.fps !== undefined ? info.fps : panel.rawClipInfo.fps; const wrapMode = info.wrapMode ?? panel.rawClipInfo.wrapMode; const speed = info.speed ?? panel.rawClipInfo.speed; const additiveEnabled = (info.additive?.enabled) ?? panel.rawClipInfo.additiveEnabled; const refClip = (info.additive?.refClip) ?? panel.rawClipInfo.refClip; panel.currentClipInfo = { name: info.name, from: info.from * fps, to: info.to * fps, ctrlStart, ctrlEnd, ctrlStartStyle: { transform: `translateX(${ctrlStart - 6}px)`, }, ctrlEndStyle: { transform: `translateX(${ctrlEnd}px)`, }, durationStyle: { width: `${durationWidth}px`, transform: `translateX(${ctrlStart}px)`, }, duration, fps, wrapMode, speed, additiveEnabled, refClip, }; const maxFrames = (panel.rawClipInfo.duration * panel.currentClipInfo.fps).toFixed(0); const startFrames = panel.currentClipInfo.from.toFixed(0); const endFrames = panel.currentClipInfo.to.toFixed(0); panel.$.clipFrames.innerText = maxFrames; panel.$.clipFPS.value = fps; // TODO: hack for bug at 3d-tasks#10113. Because the new value would be limited in min and max, should firstly remove min and max. panel.$.clipFrom.max = null; panel.$.clipTo.min = null; panel.$.clipTo.max = null; panel.$.clipFrom.value = startFrames; panel.$.clipFrom.setAttribute('max', endFrames); panel.$.clipTo.value = endFrames; panel.$.clipTo.setAttribute('min', startFrames); panel.$.clipTo.setAttribute('max', maxFrames); panel.$.wrapMode.value = panel.currentClipInfo.wrapMode; panel.$.speed.value = panel.currentClipInfo.speed || 1; panel.$.additiveEnabled.value = panel.currentClipInfo.additiveEnabled || false; panel.$.refClip.value = panel.currentClipInfo.refClip || ''; }, updateRawClipInfo() { const panel = this; if (!panel.animationInfos) { panel.rawClipInfo = null; return; } if (!panel.animationInfos[panel.rawClipIndex]) { panel.rawClipInfo = null; return; } const { name, duration, fps } = panel.animationInfos[panel.rawClipIndex]; panel.rawClipInfo = { name, duration, fps }; panel.$.clipDuration.innerText = duration.toFixed(3); }, updateGridConfig() { const panel = this; if (!panel.currentClipInfo) { return null; } let width = panel.gridTableWith; const info = panel.rawClipInfo; const { step, spacing } = panel.getStepAndSpacing(width, panel.currentClipInfo.fps * info.duration); width = (panel.currentClipInfo.fps * info.duration * spacing) / step; const mod = Math.floor(panel.gridTableWith / (spacing * 5)); const rest = Math.floor((panel.gridTableWith % (spacing * 5)) / spacing); const labelStep = info.duration * ((5 * step) / (panel.currentClipInfo.fps * info.duration)); panel.gridConfig = { step, spacing, mod, rest, width, labelStep, }; }, getStepAndSpacing(width, frames) { const config = { minSpacing: 10, maxSpacing: 20, }; const rawMinSpacing = width / frames; let spacing = rawMinSpacing; let step = 1; if (rawMinSpacing < config.minSpacing) { // Calculates a minimum spacing value that is a multiple of maxSpacing step = Math.ceil(config.minSpacing / rawMinSpacing); spacing = rawMinSpacing * step; } return { step, spacing, }; }, onMouseDown(type) { const panel = this; const info = panel.currentClipInfo; if (!info) { return; } panel.virtualControl = { type }; if (type === 'right') { panel.virtualControl.style = info.ctrlEndStyle; panel.virtualControl.value = info.to / panel.currentClipInfo.fps; } else { panel.virtualControl.style = info.ctrlStartStyle; panel.virtualControl.value = info.from / panel.currentClipInfo.fps; } panel.$.controlVirtual.setAttribute('direction', panel.virtualControl.type); panel.$.controlVirtual.style.display = 'block'; panel.updateVirtualControl(); panel.onMouseMoveBind = panel.onMouseMove.bind(panel); panel.onMouseUpBind = panel.onMouseUp.bind(panel); document.addEventListener('mousemove', panel.onMouseMoveBind); document.addEventListener('mouseup', panel.onMouseUpBind); }, onMouseMove(event) { const panel = this; event.preventDefault(); if (!panel.virtualControl) { return; } // beyond border const { type } = panel.virtualControl; let x = event.x - panel.$.rulerMaking.getBoundingClientRect().x; if ( x > panel.gridConfig.width || x < 0 || (type === 'left' && x > panel.currentClipInfo.ctrlEnd) || (type === 'right' && x < panel.currentClipInfo.ctrlStart) ) { return; } const { duration } = panel.rawClipInfo; const value = (x / panel.gridConfig.width) * duration; const currentTime = Editor.Utils.Math.clamp(value, 0, duration); const currentFrame = parseInt((currentTime * panel.currentClipInfo.fps).toFixed(0)); panel.virtualControl.value = currentTime; if (type === 'left') { panel.virtualControl.startFrame = currentFrame; x -= 6; } else { panel.virtualControl.endFrame = currentFrame; } panel.virtualControl.style = { transform: `translateX(${x}px)`, }; cancelAnimationFrame(panel.animationId); panel.animationId = requestAnimationFrame(() => { panel.updateVirtualControl(); }); }, onMouseUp() { const panel = this; if (!panel.virtualControl) { return; } document.removeEventListener('mousemove', panel.onMouseMoveBind); document.removeEventListener('mouseup', panel.onMouseUpBind); const { value } = panel.virtualControl; let { type } = panel.virtualControl; type = type === 'right' ? 'to' : 'from'; // refresh data const splitInfo = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex]; if (splitInfo[type] !== value) { const { duration } = panel.rawClipInfo; splitInfo[type] = Editor.Utils.Math.clamp(value, 0, duration); } Elements.clips.update.call(panel); Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle); Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle); Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle); panel.$.controlVirtual.style.display = 'none'; const curClipInfo = panel.getCurClipInfo(); Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo); panel.dispatch('change'); panel.dispatch('snapshot'); }, updateVirtualControl() { const panel = this; Object.assign(panel.$.controlVirtual.style, panel.virtualControl.style); panel.$.controlVirtualNumber.innerText = panel.virtualControl.value.toFixed(3); if (panel.virtualControl.startFrame || panel.virtualControl.endFrame) { if (panel.virtualControl.type === 'left') { panel.$.clipFrom.value = panel.virtualControl.startFrame; } else { panel.$.clipTo.value = panel.virtualControl.endFrame; } } }, onClipName(event) { const panel = this; if (!panel.currentClipInfo) { return; } let name = event.target.value; if (!name) { return; } if (panel.clipNames.has(name)) { name = panel.getRightName(name); } const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex]; if (info.name === name) { return; } panel.clipNames.delete(info.name); info.name = name; panel.clipNames.add(name); panel.dispatch('change'); panel.dispatch('snapshot'); Elements.clips.update.call(panel); }, onCutClip(event) { const panel = this; const path = event.target.getAttribute('path'); panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex][path] = event.target.value / panel.currentClipInfo.fps; Elements.clips.update.call(panel); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, onFpsChange(event) { const panel = this; panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].fps = Number(event.target.value); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, onWrapModeChange(event) { const panel = this; const wrapMode = Number(event.target.value); panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].wrapMode = wrapMode; callModelPreviewFunction( 'setClipConfig', { wrapMode, } ); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, onSpeedChange(event) { const panel = this; const speed = Number(event.target.value); panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].speed = speed; callModelPreviewFunction( 'setClipConfig', { speed, } ); Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, onAdditiveEnabledChanged(event) { const panel = this; const enabled = Boolean(event.target.value); (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).enabled = enabled; Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, onRefClipChanged(event) { const panel = this; const refClipUUID = String(event.target.value); (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).refClip = refClipUUID; Elements.editor.update.call(panel); panel.dispatch('change'); panel.dispatch('snapshot'); }, setProp(prop, type, event) { setPropValue.call(this, prop, type, event); this.dispatch('change'); this.dispatch('track', { tab: 'animation', prop, value: event.target.value }); }, }; exports.ready = function() { for (const prop in Elements) { const element = Elements[prop]; if (element.ready) { element.ready.call(this); } } }; exports.update = function(assetList, metaList) { this.assetList = assetList; this.metaList = metaList; this.asset = assetList[0]; this.meta = metaList[0]; if (assetList.length > 1) { this.$.container.setAttribute('multiple-invalid', ''); return; } else { this.$.container.removeAttribute('multiple-invalid'); } for (const prop in Elements) { const element = Elements[prop]; if (element.update) { element.update.call(this); } } this.initAnimationNameToUUIDMap(); this.initAnimationInfos(); if (this.animationInfos) { this.onSelect(this.rawClipIndex, this.splitClipIndex); } }; exports.close = function() { for (const prop in Elements) { const element = Elements[prop]; if (element.close) { element.close.call(this); } } };