UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

221 lines • 8.89 kB
import { builtinComponentKeyName } from "../engine_constants.js"; import { debugExtension } from "../engine_default_parameters.js"; import { getLoader } from "../engine_gltf.js"; import { apply } from "../js-extensions/index.js"; import { maskGltfAssociation, resolveReferences } from "./extension_utils.js"; export const debug = debugExtension; const componentsArrayExportKey = "$___Export_Components"; export const EXTENSION_NAME = "NEEDLE_components"; class ExtensionData { [builtinComponentKeyName]; } class ExportData { node; nodeIndex; nodeDef; constructor(node, nodeIndex, nodeDef) { this.node = node; this.nodeIndex = nodeIndex; this.nodeDef = nodeDef; } } export class NEEDLE_components { get name() { return EXTENSION_NAME; } // import parser; nodeToObjectMap = {}; /** The loaded gltf */ gltf = null; // export exportContext; objectToNodeMap = {}; context; writer; registerExport(exp) { //@ts-ignore exp.register(writer => { // we want to hook into BEFORE user data is written // because we want to remove the components list (circular references) // and replace them with the serialized data // the write node callback is called after user data is serialized // we could also traverse everything before export and remove components // but doing it like that we avoid traversing multiple times if ("serializeUserData" in writer) { //@ts-ignore const originalFunction = writer.serializeUserData.bind(writer); this.writer = writer; //@ts-ignore writer.serializeUserData = (o, def) => { try { const hadUserData = this.serializeUserData(o, def); if (hadUserData) //@ts-ignore writer.extensionsUsed[this.name] = true; originalFunction(o, def); } finally { this.afterSerializeUserData(o, def); } }; } return this; }); } beforeParse() { this.exportContext = {}; this.objectToNodeMap = {}; } // https://github.com/mrdoob/three.js/blob/efbfc67edc7f65cfcc61a389ffc5fd43ea702bc6/examples/jsm/exporters/GLTFExporter.js#L532 serializeUserData(node, _nodeDef) { const components = node.userData?.components; if (!components || components.length <= 0) return false; // delete components before serializing user data to avoid circular references delete node.userData.components; node[componentsArrayExportKey] = components; return true; } afterSerializeUserData(node, _nodeDef) { if (node.type === "Scene") { if (debug) console.log("DONE", JSON.stringify(_nodeDef)); } // reset userdata if (node[componentsArrayExportKey] === undefined) return; const components = node[componentsArrayExportKey]; delete node[componentsArrayExportKey]; if (components !== null) { node.userData.components = components; } // console.log(_nodeDef, _nodeDef.mesh); } writeNode(node, nodeDef) { const nodeIndex = this.writer.json.nodes.length; if (debug) console.log(node.name, nodeIndex, node.uuid); const context = new ExportData(node, nodeIndex, nodeDef); this.exportContext[nodeIndex] = context; this.objectToNodeMap[node.uuid] = nodeIndex; } ; afterParse(input) { if (debug) console.log("AFTER", input); for (const i in this.exportContext) { const context = this.exportContext[i]; const node = context.node; const nodeDef = context.nodeDef; const nodeIndex = context.nodeIndex; const components = node.userData?.components; if (!components || components.length <= 0) continue; // create data container const data = new ExtensionData(); nodeDef.extensions = nodeDef.extensions || {}; nodeDef.extensions[this.name] = data; this.context.object = node; this.context.nodeId = nodeIndex; this.context.objectToNode = this.objectToNodeMap; const serializedComponentData = []; for (const comp of components) { this.context.target = comp; const res = getLoader().writeBuiltinComponentData(comp, this.context); if (res !== null) { serializedComponentData.push(res); // (comp as unknown as ISerializationCallbackReceiver)?.onAfterSerialize?.call(comp); } } if (serializedComponentData.length > 0) { data[builtinComponentKeyName] = serializedComponentData; if (debug) console.log("DID WRITE", node, "nodeIndex", nodeIndex, serializedComponentData); } } } // ------------------------------------- // LOADING // called by GLTFLoader beforeRoot() { if (debug) console.log("BEGIN LOAD"); this.nodeToObjectMap = {}; return null; } // called by GLTFLoader async afterRoot(result) { this.gltf = result; const parser = result.parser; const ext = parser?.extensions; if (!ext) return; const hasExtension = ext[this.name]; if (debug) console.log("After root", result, this.parser, ext); const loadComponents = []; if (hasExtension === true) { const nodes = parser.json.nodes; if (nodes) { for (let i = 0; i < nodes.length; i++) { const obj = await parser.getDependency('node', i); this.nodeToObjectMap[i] = obj; } for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; const index = i; // node.mesh; const ext = node.extensions; if (!ext) continue; const data = ext[this.name]; if (!data) continue; if (debug) console.log("NODE", node); const obj = this.nodeToObjectMap[index]; if (!obj) { console.error("Could not find object for node index: " + index, node, parser); continue; } apply(obj); loadComponents.push(this.createComponents(obj, data)); } } } await Promise.all(loadComponents); for (const instance of parser.associations.keys()) { const value = parser.associations.get(instance); if (value?.materials != undefined) { const key = "/materials/" + value.materials; maskGltfAssociation(instance, key); } } } async createComponents(obj, data) { if (!data) return; const componentData = data[builtinComponentKeyName]; if (componentData) { const tasks = new Array(); if (debug) console.log(obj.name, componentData); for (const i in componentData) { const serializedData = componentData[i]; if (debug) console.log("Serialized data", JSON.parse(JSON.stringify(serializedData))); if (serializedData && this.parser) { tasks.push(resolveReferences(this.parser, serializedData) .catch(e => console.error(`Error while resolving references (see console for details)\n`, e, obj, serializedData))); } obj.userData = obj.userData || {}; obj.userData[builtinComponentKeyName] = obj.userData[builtinComponentKeyName] || []; obj.userData[builtinComponentKeyName].push(serializedData); } await Promise.all(tasks).catch((e) => { console.error("Error while loading components", e); }); } } } //# sourceMappingURL=NEEDLE_components.js.map