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.

173 lines (151 loc) • 7.21 kB
import { Loader } from "three"; import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { isDevEnvironment } from "../debug/index.js"; import { isResourceTrackingEnabled } from "../engine_assetdatabase.js"; import { Context } from "../engine_setup.js"; import { type ConstructorConcrete, GLTF, type SourceIdentifier } from "../engine_types.js"; import { getParam } from "../engine_utils.js"; import { NEEDLE_lightmaps } from "../extensions/NEEDLE_lightmaps.js"; import { EXT_texture_exr } from "./EXT_texture_exr.js"; import { NEEDLE_components } from "./NEEDLE_components.js"; import { NEEDLE_gameobject_data } from "./NEEDLE_gameobject_data.js"; import { NEEDLE_lighting_settings } from "./NEEDLE_lighting_settings.js"; import { NEEDLE_materialx } from "./NEEDLE_materialx.js"; import { NEEDLE_persistent_assets } from "./NEEDLE_persistent_assets.js"; import { NEEDLE_progressive } from "./NEEDLE_progressive.js"; import { NEEDLE_render_objects } from "./NEEDLE_render_objects.js"; import { NEEDLE_techniques_webgl } from "./NEEDLE_techniques_webgl.js"; import { InternalUsageTrackerPlugin } from "./usage_tracker.js"; const debug = getParam("debugextensions"); // lazily import the GLTFAnimationPointerExtension in case it doesnt exist (e.g. using vanilla three) let GLTFAnimationPointerExtension: any; const KHR_ANIMATIONPOINTER_IMPORT = import("@needle-tools/three-animation-pointer").then(async mod => { GLTFAnimationPointerExtension = mod.GLTFAnimationPointerExtension; return GLTFAnimationPointerExtension; }).catch(e => { console.warn("Failed to import GLTFLoaderAnimationPointer. Please use @needle-tools/three-animationpointer for full KHR_animation support", e); }); /** * Callback type for glTF import plugins. See {@link INeedleGLTFExtensionPlugin.onImport} */ export type OnImportCallback = (loader: GLTFLoader, url: string, context: Context) => void; /** * Callback type for glTF export plugins. See {@link INeedleGLTFExtensionPlugin.onExport} */ export type OnExportCallback = (exporter: GLTFExporter, context: Context) => void; /** * Interface for registering custom glTF extensions to the Needle Engine GLTFLoaders. * Register your plugin using the {@link addCustomExtensionPlugin} method */ export interface INeedleGLTFExtensionPlugin { /** The Name of your plugin */ name: string; /** Called before starting to load a glTF file. This callback can be used to add custom extensions to the [GLTFLoader](https://threejs.org/docs/#GLTFLoader.register) * * @example Add a custom extension to the GLTFloader * ```ts * onImport: (loader, url, context) => { * loader.register((parser) => new MyCustomExtension(parser)); * } * ``` */ onImport?: OnImportCallback; /** Called after a glTF file has been loaded */ onLoaded?: (url: string, gltf: GLTF, context: Context) => void; /** Called before starting to export a glTF file. This callback can be used to add custom extensions to the [GLTFExporter](https://threejs.org/docs/#examples/en/exporters/GLTFExporter.register) * * @example Add a custom extension to the GLTFExporter * ```ts * onExport: (exporter, context) => { * exporter.register((writer) => new MyCustomExportExtension(writer)); * } * */ onExport?: OnExportCallback; } /** * Registered custom glTF extension plugins */ const _plugins = new Array<INeedleGLTFExtensionPlugin>(); /** Register callbacks for registering custom gltf importer or exporter plugins */ export function addCustomExtensionPlugin(ext: INeedleGLTFExtensionPlugin) { if (!_plugins.includes(ext)) { _plugins.push(ext); } } /** Unregister callbacks for registering custom gltf importer or exporter plugins */ export function removeCustomImportExtensionType(ext: INeedleGLTFExtensionPlugin) { const index = _plugins.indexOf(ext); if (index >= 0) _plugins.splice(index, 1); } /** Registers the Needle Engine components extension */ export function registerComponentExtension(loader: GLTFLoader | Loader | object): NEEDLE_components | null { if (loader instanceof GLTFLoader) { const ext = new NEEDLE_components(); loader.register(p => { ext.parser = p; return ext; }); return ext; } return null; } class PointerResolver { resolvePath(path: string) { if (path.includes('/extensions/builtin_components/')) return path.replace('/extensions/builtin_components/', '/userData/components/'); if (path.includes('extensions/builtin_components/')) return path.replace('extensions/builtin_components/', '/userData/components/'); return path; } } export async function registerExtensions(loader: GLTFLoader, context: Context, url: string, sourceId: SourceIdentifier) { // Make sure to remove any url parameters from the sourceId (because the source id in the renderer does not have a ?v=xxx so it will not be able to register the resolved lightmap otherwise) const idEnd = url.indexOf("?"); if (idEnd >= 0) url = url.substring(0, idEnd); if (!sourceId) { sourceId = url; } if (sourceId.startsWith("blob:") || sourceId.startsWith("data:")) { console.debug("[GLTFLoader] Suspicious sourceId detected"); } loader.register(p => new NEEDLE_gameobject_data(p)); loader.register(p => new NEEDLE_persistent_assets(p)); loader.register(p => new NEEDLE_lightmaps(p, context.lightmaps, sourceId)); loader.register(p => new NEEDLE_lighting_settings(p, sourceId, context)); loader.register(p => new NEEDLE_techniques_webgl(p, sourceId)); loader.register(p => new NEEDLE_render_objects(p, sourceId)); loader.register(p => new NEEDLE_progressive(p)); loader.register(p => new EXT_texture_exr(p)); loader.register(p => new NEEDLE_materialx(context, loader, url, p)); if (isResourceTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p)) await KHR_ANIMATIONPOINTER_IMPORT.catch(_ => { }) loader.register(p => { if (GLTFAnimationPointerExtension) { const ext = new GLTFAnimationPointerExtension(p); const setPointerResolverFunction = ext.setAnimationPointerResolver; setPointerResolverFunction.bind(ext)(new PointerResolver()); return ext; } else { if (debug || isDevEnvironment()) console.error("Missing KHR_animation_pointer extension...") return { name: "KHR_animation_pointer_NOT_AVAILABLE" }; } }); for (const plugin of _plugins) { if (plugin.onImport) plugin.onImport(loader, url, context); } } export function registerExportExtensions(exp: GLTFExporter, context: Context) { for (const ext of _plugins) if (ext.onExport) ext.onExport(exp, context); } /** @internal */ export function invokeLoadedImportPluginHooks(url: string, gltf: GLTF, context: Context) { for (const ext of _plugins) if (ext.onLoaded) ext.onLoaded(url, gltf, context); }