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.

167 lines (152 loc) • 6.67 kB
import { Material, Object3D, Texture } from "three"; import { GLTFParser, GLTFReference } from "three/examples/jsm/loaders/GLTFLoader.js"; import { debugExtension } from "../engine_default_parameters.js"; import { getParam } from "../engine_utils.js"; import { type IExtensionReferenceResolver } from "./extension_resolver.js"; const debug = getParam("debugresolvedependencies"); declare type GLTFParserWithCache = GLTFParser & { cache: Map<string, any> }; declare type DependencyInfo = { prefix: string, dependencyName: string, } const rootExtensionPrefix = ["/extensions/", "extensions/"]; const defaultDependencies = [ { prefix: "/nodes/", dependencyName: "node" }, { prefix: "/meshes/", dependencyName: "mesh" }, { prefix: "/materials/", dependencyName: "material" }, { prefix: "/textures/", dependencyName: "texture" }, { prefix: "/animations/", dependencyName: "animation" }, // legacy support { prefix: "nodes/", dependencyName: "node" }, { prefix: "meshes/", dependencyName: "mesh" }, { prefix: "materials/", dependencyName: "material" }, { prefix: "textures/", dependencyName: "texture" }, { prefix: "animations/", dependencyName: "animation" }, ] export async function resolveReferences(parser: GLTFParser, obj: object | string) { if (debug) console.log(parser, obj); const arr: Promise<any>[] = []; internalResolve(defaultDependencies, parser as GLTFParserWithCache, obj, arr); const res = await Promise.all(arr); if (typeof obj === "string" && res.length === 1) { return res[0]; } return res; } /** * Utility method to check if two materials were created from the same glTF material */ export function compareAssociation<T extends Material>(obj1: T, obj2: T): boolean { if (!obj1 || !obj2) return false; if (obj1["needle:identifier"] != undefined && obj2["needle:identifier"] != undefined) return obj1["needle:identifier"] === obj2["needle:identifier"]; return false; } /** * Setting * @hidden */ export function maskGltfAssociation(obj: Object3D | Material | Texture | GLTFReference, identifier: string) { // Mark an object with an identifier to check if two objects were created from the same source obj["needle:identifier"] = identifier; } function internalResolve(paths: DependencyInfo[], parser: GLTFParserWithCache, obj: object | string, promises: Promise<any>[]) { if (typeof obj === "object" && obj !== undefined && obj !== null) { for (const key of Object.keys(obj)) { const val = obj[key]; // handle json pointer in string variable if (typeof val === "string") { const ext = resolveExtension(parser, val); if (ext !== null && ext !== undefined) { if (typeof ext.then === "function") promises.push(ext.then(res => obj[key] = res)); else obj[key] = ext; } else { // e.g. prefix = "/materials/"; const res = tryResolveDependency(paths, parser, val); if (res) { promises.push(res.then(res => { obj[key] = res; return res; })); continue; } } } // handle json pointers in arrays else if (Array.isArray(val)) { for (let i = 0; i < val.length; i++) { const entry = val[i]; const ext = resolveExtension(parser, entry); if (ext !== null) { if (typeof ext.then === "function") promises.push(ext.then(res => val[i] = res)); else val[i] = ext; continue; } for (const dep of paths) { const index = tryGetIndex(dep.prefix, entry); if (index >= 0) { if (debug) console.log(dep, index, dep.dependencyName); promises.push(parser.getDependency(dep.dependencyName, index).then(res => val[i] = res)); break; } } // recurse if (typeof entry === "object") { internalResolve(paths, parser, entry, promises); } } } // recurse else if (typeof val === "object") { internalResolve(paths, parser, val, promises); } } } else if (typeof obj === "string") { const res = tryResolveDependency(paths, parser, obj); if (res) promises.push(res); } } function resolveExtension(parser: GLTFParser, str): Promise<void> | null { if (parser && parser.plugins && typeof str === "string") { for (const prefix of rootExtensionPrefix) { if (str.startsWith(prefix)) { let name = str.substring(prefix.length); const endIndex = name.indexOf("/"); if (endIndex >= 0) name = name.substring(0, endIndex); const ext = parser.plugins[name] as any as IExtensionReferenceResolver; if (debugExtension) console.log(name, ext); if (typeof ext?.resolve === "function") { const path = str.substring(prefix.length + name.length + 1); return ext.resolve(parser, path); } break; } } } return null; } function tryResolveDependency(paths: DependencyInfo[], parser: GLTFParser & { cache: Map<string, any> }, str: string): Promise<any> | null { for (const dep of paths) { const index = tryGetIndex(dep.prefix, str); if (index >= 0) { if (debug) console.warn("GET DEPENDENCY", dep, index, dep.dependencyName); return parser.getDependency(dep.dependencyName, index); } } return null; } function tryGetIndex(prefix: string, str: string): number { if (typeof str === "string" && str.startsWith(prefix)) { const part = str.substring(prefix.length); const index = Number.parseInt(part); if (index >= 0) { return index; } } return -1; }