@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
JavaScript
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