UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

211 lines (210 loc) 6.16 kB
import { AnimBinder } from "./anim-binder.js"; import { AnimTarget } from "../evaluator/anim-target.js"; import { Entity } from "../../entity.js"; class DefaultAnimBinder { constructor(graph) { this.graph = graph; if (!graph) return; this._mask = null; const nodes = {}; const flatten = function(node) { nodes[node.name] = node; for (let i = 0; i < node.children.length; ++i) { flatten(node.children[i]); } }; flatten(graph); this.nodes = nodes; this.targetCache = {}; const findMeshInstances = function(node) { let object = node; while (object && !(object instanceof Entity)) { object = object.parent; } let meshInstances; if (object) { if (object.render) { meshInstances = object.render.meshInstances; } else if (object.model) { meshInstances = object.model.meshInstances; } } return meshInstances; }; this.nodeCounts = {}; this.activeNodes = []; this.handlers = { "localPosition": function(node) { const object = node.localPosition; const func = function(value) { object.set(...value); }; return DefaultAnimBinder.createAnimTarget(func, "vector", 3, node, "localPosition"); }, "localRotation": function(node) { const object = node.localRotation; const func = function(value) { object.set(...value); }; return DefaultAnimBinder.createAnimTarget(func, "quaternion", 4, node, "localRotation"); }, "localScale": function(node) { const object = node.localScale; const func = function(value) { object.set(...value); }; return DefaultAnimBinder.createAnimTarget(func, "vector", 3, node, "localScale"); }, "weight": function(node, weightName) { if (weightName.indexOf("name.") === 0) { weightName = weightName.replace("name.", ""); } else { weightName = Number(weightName); } const meshInstances = findMeshInstances(node); const instances = []; if (meshInstances) { for (let i = 0; i < meshInstances.length; ++i) { if (meshInstances[i].node.name === node.name && meshInstances[i].morphInstance) { instances.push(meshInstances[i].morphInstance); } } } if (instances.length > 0) { const func = { set: (value) => { for (let i = 0; i < instances.length; ++i) { instances[i].setWeight(weightName, value[0]); } }, get: () => { return [instances[0].getWeight(weightName)]; } }; return DefaultAnimBinder.createAnimTarget(func, "number", 1, node, `weight.${weightName}`); } return null; }, "materialTexture": (node, textureName) => { const meshInstances = findMeshInstances(node); if (meshInstances) { let meshInstance; for (let i = 0; i < meshInstances.length; ++i) { if (meshInstances[i].node.name === node.name) { meshInstance = meshInstances[i]; break; } } if (meshInstance) { const func = (value) => { const textureAsset = this.animComponent.system.app.assets.get(value[0]); if (textureAsset && textureAsset.resource && textureAsset.type === "texture") { meshInstance.material[textureName] = textureAsset.resource; meshInstance.material.update(); } }; return DefaultAnimBinder.createAnimTarget(func, "vector", 1, node, "materialTexture", "material"); } } return null; } }; } _isPathInMask = (path, checkMaskValue) => { const maskItem = this._mask[path]; if (!maskItem) return false; else if (maskItem.children || checkMaskValue && maskItem.value !== false) return true; return false; }; _isPathActive(path) { if (!this._mask) return true; const rootNodeNames = [path.entityPath[0], this.graph.name]; for (let j = 0; j < rootNodeNames.length; ++j) { let currEntityPath = rootNodeNames[j]; if (this._isPathInMask(currEntityPath, path.entityPath.length === 1)) return true; for (let i = 1; i < path.entityPath.length; i++) { currEntityPath += `/${path.entityPath[i]}`; if (this._isPathInMask(currEntityPath, i === path.entityPath.length - 1)) return true; } } return false; } findNode(path) { if (!this._isPathActive(path)) { return null; } let node; if (this.graph) { node = this.graph.findByPath(path.entityPath); if (!node) { node = this.graph.findByPath(path.entityPath.slice(1)); } } if (!node) { node = this.nodes[path.entityPath[path.entityPath.length - 1] || ""]; } return node; } static createAnimTarget(func, type, valueCount, node, propertyPath, componentType) { const targetPath = AnimBinder.encode(node.path, componentType ? componentType : "entity", propertyPath); return new AnimTarget(func, type, valueCount, targetPath); } resolve(path) { const encodedPath = AnimBinder.encode(path.entityPath, path.component, path.propertyPath); let target = this.targetCache[encodedPath]; if (target) return target; const node = this.findNode(path); if (!node) { return null; } const handler = this.handlers[path.propertyPath]; if (!handler) { return null; } target = handler(node); if (!target) { return null; } this.targetCache[encodedPath] = target; if (!this.nodeCounts[node.path]) { this.activeNodes.push(node); this.nodeCounts[node.path] = 1; } else { this.nodeCounts[node.path]++; } return target; } unresolve(path) { if (path.component !== "graph") { return; } const node = this.nodes[path.entityPath[path.entityPath.length - 1] || ""]; this.nodeCounts[node.path]--; if (this.nodeCounts[node.path] === 0) { const activeNodes = this.activeNodes; const i = activeNodes.indexOf(node.node); const len = activeNodes.length; if (i < len - 1) { activeNodes[i] = activeNodes[len - 1]; } activeNodes.pop(); } } // flag animating nodes as dirty update(deltaTime) { const activeNodes = this.activeNodes; for (let i = 0; i < activeNodes.length; ++i) { activeNodes[i]._dirtifyLocal(); } } assignMask(mask) { if (mask !== this._mask) { this._mask = mask; return true; } return false; } } export { DefaultAnimBinder };