/* eslint-disable @typescript-eslint/no-unsafe-return */ const propUtils = require('../utils/prop'); exports.template = /* html*/`
Regenerate bounding box
`; const excludeList = [ 'duration', 'capacity', 'loop', 'playOnAwake', 'prewarm', 'simulationSpace', 'simulationSpeed', 'startDelay', 'startLifetime', 'startColor', 'scaleSpace', 'startSize3D', 'startSizeX', 'startSizeY', 'startSizeZ', 'startSpeed', 'startRotation3D', 'startRotationX', 'startRotationY', 'startRotationZ', 'gravityModifier', 'rateOverTime', 'rateOverDistance', 'bursts', 'shapeModule', 'velocityOvertimeModule', 'forceOvertimeModule', 'sizeOvertimeModule', 'rotationOvertimeModule', 'colorOverLifetimeModule', 'textureAnimationModule', 'trailModule', 'renderer', 'renderCulling', 'limitVelocityOvertimeModule', 'cullingMode', 'aabbHalfX', 'aabbHalfY', 'aabbHalfZ', 'noiseModule', ]; exports.methods = { getObjectByKey(target, key) { let params = []; if (typeof key === 'string') { params = key.split('.'); } else if (key instanceof Array) { params = key; } if (params.length > 0) { const value = params.shift(); return this.getObjectByKey(target[value], params); } else { return target; } }, getEnumName(type, value) { for (const opt of type.enumList) { if (opt.value === value) { return opt.name; } } return String(); }, getEnumObjFromName(type, ...name) { const enumMap = {}; for (const opt of type.enumList) { enumMap[opt.name] = { name: opt.name, value: opt.value, }; } return name.map((value) => enumMap[value]); }, getShapeTypeEmitFrom(shapeType) { const shapeTypeName = this.getEnumName(this.dump.value.shapeModule.value.shapeType, shapeType); let emitEnum = null; switch (shapeTypeName) { case 'Box': emitEnum = this.getEnumObjFromName(this.dump.value.shapeModule.value.emitFrom, 'Volume', 'Shell', 'Edge'); break; case 'Cone': emitEnum = this.getEnumObjFromName(this.dump.value.shapeModule.value.emitFrom, 'Base', 'Shell', 'Volume'); break; case 'Sphere': emitEnum = this.getEnumObjFromName(this.dump.value.shapeModule.value.emitFrom, 'Volume', 'Shell'); break; case 'Hemisphere': emitEnum = this.getEnumObjFromName(this.dump.value.shapeModule.value.emitFrom, 'Volume', 'Shell'); break; default: emitEnum = []; } return emitEnum; }, }; const uiElements = { resetBounds: { async ready() { this.$.resetBounds.addEventListener('confirm', async () => { const nodeDumps = this.dump.value.node.values || [this.dump.value.node.value]; const componentUUIDs = this.dump.value.uuid.values || [this.dump.value.uuid.value]; await Promise.all(componentUUIDs.map(uuid => { return new Promise((res) => { Editor.Message.request(propUtils.getMessageProtocolScene(this.$this), 'execute-component-method', { uuid, name: '_calculateBounding', args: [true], }).then(() => { Editor.Message.request(propUtils.getMessageProtocolScene(this.$this), 'execute-component-method', { uuid, name: 'gizmo.onNodeChanged', args: [], }); res(); }); }); })); nodeDumps.forEach(dump => { Editor.Message.broadcast('scene:change-node', dump.uuid); }); }); }, update() { const isInvalid = propUtils.isMultipleInvalid(this.dump.value.renderCulling); if (isInvalid || !this.dump.value.renderCulling.value) { this.$.resetBounds.setAttribute('disabled', true); } else { if (this.$.resetBounds.hasAttribute('disabled')) { this.$.resetBounds.removeAttribute('disabled'); } } }, }, uiSections: { ready() { this.$.uiSections = this.$this.shadowRoot.querySelectorAll('ui-section'); this.$.uiSections.forEach((element) => { // expand when checkbox enable if (element.hasAttribute('autoExpand')) { element.addEventListener('checkbox-enable', () => { element.setAttribute('expand', 'expand'); }); } }); }, update() { this.$.uiSections.forEach((element) => { const key = element.getAttribute('key'); const showflag = element.getAttribute('showflag'); const autoflag = element.getAttribute('autoflag'); if (showflag) { if (typeof showflag === 'string') { if (showflag.startsWith('!')) { const dump = this.getObjectByKey(this.dump.value, showflag.slice(1)); const isInvalid = propUtils.isMultipleInvalid(dump); if (dump.value || isInvalid) { // continue when don't show element.style = 'display: none;'; return true; } } else { const dump = this.getObjectByKey(this.dump.value, showflag); const isInvalid = propUtils.isMultipleInvalid(dump); if (!dump.value || isInvalid) { // continue when don't show element.style = 'display: none;'; return true; } } } } element.style = ''; if (autoflag) { const oldChildren = Array.from(element.children); const children = []; const oldCheckbox = element.querySelector('[slot="header"] > ui-checkbox'); if (oldCheckbox) { oldCheckbox.removeEventListener('change', oldCheckbox.changeEvent); oldCheckbox.changeEvent = undefined; } const header = document.createElement('ui-prop'); header.setAttribute('slot', 'header'); header.setAttribute('no-label', ''); header.setAttribute('type', 'dump'); header.setAttribute('empty', 'true'); header.className = 'header'; const dump = this.getObjectByKey(this.dump.value, key); header.dump = dump; const checkbox = document.createElement('ui-checkbox'); checkbox.changeEvent = (event) => { dump.value.enable.value = event.target.value; header.dispatch('change-dump'); }; checkbox.addEventListener('change', checkbox.changeEvent); checkbox.setAttribute('value', dump.value.enable.value); const label = document.createElement('ui-label'); label.setAttribute('value', propUtils.getName(dump)); label.setAttribute('tooltip', dump.tooltip); header.replaceChildren(...[checkbox, label]); children.push(header); const propMap = dump.value; for (const propKey in propMap) { const propDump = propMap[propKey]; if (propKey === 'enable') { continue; } const oldProp = oldChildren.find((child) => child.getAttribute('key') === propKey); const uiProp = oldProp || document.createElement('ui-prop'); uiProp.setAttribute('type', 'dump'); uiProp.setAttribute('key', propKey); const isShow = propDump.visible; if (isShow) { uiProp.render(propDump); children.push(uiProp); } } children.sort((a, b) => (a.dump.displayOrder ? a.dump.displayOrder : 0 - b.dump.displayOrder ? b.dump.displayOrder : 0)); children.forEach((newChild, index) => { const oldChild = oldChildren[index]; if (oldChild === newChild) { return; } if (oldChild) { oldChild.replaceWith(newChild); } else { element.appendChild(newChild); } }); while (oldChildren.length > children.length) { const oldChild = oldChildren.pop(); oldChild.remove(); } } }); }, }, showBounds: { ready() { this.$.showBounds.addEventListener('change', (event) => { const componentUUIDs = this.dump.value.uuid.values || [this.dump.value.uuid.value]; componentUUIDs.forEach(uuid => { Editor.Message.send(propUtils.getMessageProtocolScene(this.$this), 'execute-component-method', { uuid, name: 'gizmo.showBoundingBox', args: [event.target.value], }); }); }); }, async update() { if (!this.dump.value.renderCulling.value) { this.$.showBounds.setAttribute('disabled', true); } else if (this.$.showBounds.hasAttribute('disabled')) { this.$.showBounds.removeAttribute('disabled'); } const componentUUIDs = this.dump.value.uuid.values || [this.dump.value.uuid.value]; const values = await Promise.all( componentUUIDs.map( uuid => Editor.Message.request(propUtils.getMessageProtocolScene(this.$this), 'execute-component-method', { uuid, name: 'gizmo.isShowBoundingBox', args: [], }))); const invalid = values.some(v => v !== values[0]); this.$.showBounds.invalid = invalid; this.$.showBounds.value = values[0]; }, }, emitFromSelect: { ready() { this.$.emitFromSelect.addEventListener('change', (event) => { this.dump.value.shapeModule.value.emitFrom.value = event.target.value; this.$.emitFromSelect.parentNode.dispatch('change-dump'); }); }, update() { this.$.emitFromSelect.setAttribute('value', this.dump.value.shapeModule.value.emitFrom.value); const datas = this.getShapeTypeEmitFrom(this.dump.value.shapeModule.value.shapeType.value); const children = datas.map((data) => { const child = document.createElement('option'); child.innerHTML = data.name; child.setAttribute('value', data.value); return child; }); this.$.emitFromSelect.replaceChildren(...children); }, }, baseProps: { ready() { this.$.baseProps = this.$this.shadowRoot.querySelectorAll('ui-prop:not(.customProp)'); this.$.baseProps.forEach((element) => { const key = element.getAttribute('key'); const isEmpty = element.getAttribute('empty'); const isHeader = element.getAttribute('slot') === 'header'; element.addEventListener('change-dump', () => { uiElements.baseProps.update.call(this, key); }); if (isEmpty) { if (isHeader) { /** * @type {HTMLInputElement} */ const checkbox = element.querySelector('ui-checkbox'); if (checkbox) { checkbox.addEventListener('change', (event) => { const dump = this.getObjectByKey(this.dump.value, key); const value = event.target.value; if (dump.values) { dump.values = dump.values.map(v => value); } dump.value = value; element.dispatch('change-dump'); if (value) { // bubbles the event when value is true const event = new Event('checkbox-enable', { bubbles: true, cancelable: true }); checkbox.dispatchEvent(event); } }); } } } }); }, /** * * @param {string} [eventInstigatorKey] */ update(eventInstigatorKey) { this.$.baseProps.forEach((element) => { const key = element.getAttribute('key'); const isEmpty = element.getAttribute('empty'); let isShow = !key || this.getObjectByKey(this.dump.value, key).visible; const isHeader = element.getAttribute('slot') === 'header'; const displayName = element.getAttribute('displayName'); const dump = this.getObjectByKey(this.dump.value, key); const showflag = element.getAttribute('showflag'); const disableflag = element.getAttribute('disableflag'); let isDisable = false; if (typeof showflag === 'string') { // only update the elements relate to eventInstigator if (eventInstigatorKey) { if (showflag.startsWith(`!${eventInstigatorKey}`)) { const dump = this.getObjectByKey(this.dump.value, showflag.slice(1)); const isInvalid = propUtils.isMultipleInvalid(dump); isShow = isShow && !isInvalid && !dump.value; } else if (showflag.startsWith(eventInstigatorKey)) { const dump = this.getObjectByKey(this.dump.value, showflag); const isInvalid = propUtils.isMultipleInvalid(dump); isShow = isShow && !isInvalid && dump.value; } else { return; } } else { if (showflag.startsWith('!')) { const dump = this.getObjectByKey(this.dump.value, showflag.slice(1)); const isInvalid = propUtils.isMultipleInvalid(dump); isShow = isShow && !isInvalid && !dump.value; } else { const dump = this.getObjectByKey(this.dump.value, showflag); const isInvalid = propUtils.isMultipleInvalid(dump); isShow = isShow && !isInvalid && dump.value; } } } else if (typeof disableflag === 'string') { // only update the elements relate to eventInstigator if (eventInstigatorKey) { const contentSlot = element.querySelector('[slot=content]'); if (!contentSlot) { return; } if (disableflag.startsWith(`!${eventInstigatorKey}`)) { const dump = this.getObjectByKey(this.dump.value, disableflag.slice(1)); const isInvalid = propUtils.isMultipleInvalid(dump) || !dump.value; if (isInvalid) { contentSlot.setAttribute('disabled', true); } else if (contentSlot.hasAttribute('disabled')) { contentSlot.removeAttribute('disabled'); } } else if (disableflag.startsWith(eventInstigatorKey)) { const dump = this.getObjectByKey(this.dump.value, disableflag); const isInvalid = propUtils.isMultipleInvalid(dump) || !!dump.value; if (isInvalid) { contentSlot.setAttribute('disabled', true); } else if (contentSlot.hasAttribute('disabled')) { contentSlot.removeAttribute('disabled'); } } else { return; } } else { if (disableflag.startsWith('!')) { const dump = this.getObjectByKey(this.dump.value, disableflag.slice(1)); const isInvalid = propUtils.isMultipleInvalid(dump); isDisable = isInvalid || !dump.value; } else { const dump = this.getObjectByKey(this.dump.value, disableflag); const isInvalid = propUtils.isMultipleInvalid(dump); isDisable = isInvalid || !!dump.value; } } } else if (eventInstigatorKey) { // skip all element without showflag return; } dump.displayName = displayName; if (!isEmpty) { if (isShow) { element.render(dump); } if (typeof disableflag === 'string') { const contentSlot = element.querySelector('[slot=content]'); if (contentSlot) { if (isDisable) { contentSlot.setAttribute('disabled', true); } else if (contentSlot.hasAttribute('disabled')) { contentSlot.removeAttribute('disabled'); } } } } else { const label = element.querySelector('ui-label'); if (label) { const labelflag = element.getAttribute('labelflag'); if (labelflag) { const dump = this.getObjectByKey(this.dump.value, labelflag); label.setAttribute('value', propUtils.getName(dump)); label.setAttribute('tooltip', dump.tooltip); } } if (isHeader) { const checkbox = element.querySelector('ui-checkbox'); if (checkbox) { checkbox.setAttribute('value', dump.value); checkbox.invalid = propUtils.isMultipleInvalid(dump); } } element.dump = dump; } element.style = isShow ? '' : 'display: none;'; }); }, }, customProps: { update() { propUtils.updateCustomPropElements(this.$.customProps, excludeList, this.dump, (element, prop) => { element.className = 'customProp'; if (prop.dump.visible) { element.render(prop.dump); } element.hidden = !prop.dump.visible; }); }, }, noisePreview: { async update() { if (!this.dump?.value?.uuid?.values && !this.dump?.value?.uuid?.value) { return; } let uuid = this.dump.value.uuid.values ? this.dump.value.uuid.values[0] : this.dump.value.uuid.value; if (!uuid) { return; } let data = await Editor.Message.request(propUtils.getMessageProtocolScene(this.$this), 'execute-component-method', { uuid, name: 'getNoisePreview', args: [100, 100], }); if (data.length === 0) { return; } data = data.reduce((result, item) => { const value = item * 255; const rgba = [value, value, value, 255]; result.push(...rgba); return result; }, []); const imageData = new ImageData(new Uint8ClampedArray(data), 100, 100); const context = this.$.noisePreview.getContext('2d'); context.putImageData(imageData, 0, 0); }, }, useGPU: { changeState(useGPU) { if (useGPU) { this.$.cpuMaterial.setAttribute('state-disabled', ''); this.$.trailMaterial.setAttribute('state-disabled', ''); this.$.gpuMaterial.removeAttribute('state-disabled'); } else { this.$.cpuMaterial.removeAttribute('state-disabled'); this.$.trailMaterial.removeAttribute('state-disabled'); this.$.gpuMaterial.setAttribute('state-disabled', ''); } }, ready() { this.$.useGPU.addEventListener('change', (event) => { uiElements.useGPU.changeState(event.target.value); }); }, update() { uiElements.useGPU.changeState.call(this, this.dump.value.renderer.value.useGPU.value); }, }, }; exports.$ = { customProps: '#customProps', emitFromSelect: '#emitFromSelect', showBounds: '#showBounds', resetBounds: '#resetBounds', noisePreview: '#noisePreview', useGPU: '#use-gpu', cpuMaterial: '.cpu-material', gpuMaterial: '.gpu-material', trailMaterial: '.trail-material', }; exports.ready = function() { for (const key in uiElements) { const element = uiElements[key]; if (typeof element.ready === 'function') { element.ready.call(this); } } }; exports.update = function(dump) { this.dump = dump; for (const key in uiElements) { const element = uiElements[key]; if (typeof element.update === 'function') { element.update.call(this); } } }; exports.style = /* css */` .particle-system-component > .content > .indent { margin-left: calc(var(--ui-prop-margin-left) + 8px); } .particle-system-component ui-section .header ui-checkbox { margin-right: 4px; } .trail-material[state-disabled] { opacity: 0.5; } .gpu-material[state-disabled] { opacity: 0.5; } .cpu-material[state-disabled] { opacity: 0.5; } `; exports.listeners = { async 'change-dump'(event) { const target = event.target; if (!target) { return; } const dump = event.target.dump; if (!dump) { return; } // renderMode选择mesh次数 if (dump.path.endsWith('renderer.renderMode') && dump.value === 4) { Editor.Metrics._trackEventWithTimer({ category: 'particleSystem', id: 'A100011', value: 1, }); } // 粒子系统其他模块埋点 const trackMap = { 'noiseModule.enable': 'A100000', 'shapeModule.enable': 'A100001', velocityOvertimeModule: 'A100002', forceOvertimeModule: 'A100003', 'sizeOvertimeModule.enable': 'A100004', 'rotationOvertimeModule.enable': 'A100005', colorOverLifetimeModule: 'A100006', textureAnimationModule: 'A100007', 'limitVelocityOvertimeModule.enable':'A100008', 'trailModule.enable': 'A100009', }; const dumpKey = Object.keys(trackMap).find(key => dump.path.endsWith(key)); if (!dumpKey) { return; } const value = dump.type === 'Boolean' ? dump.value : dump.value.enable.value; if (!value) { return; } Editor.Metrics._trackEventWithTimer({ category: 'particleSystem', id: trackMap[dumpKey], value: 1, }); }, };