@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.
137 lines (117 loc) • 5.56 kB
text/typescript
import { BufferGeometry, Object3D } from "three";
import { Context } from "./engine_setup.js";
import { CustomModel } from "./engine_types.js";
import { type NeedleMimetype } from "./engine_utils_format.js";
export type ValidLoaderReturnType = CustomModel | Object3D | BufferGeometry;
/** @internal */
export type CustomLoader = {
/** The name of the loader. This is used for debugging purposes. */
name?: string,
/** Load the model from the given URL. This method should return a promise that resolves to the loaded model. */
loadAsync: (url: string, onProgress?: ((event: ProgressEvent<EventTarget>) => void) | undefined) => Promise<ValidLoaderReturnType>,
/** Load the model given a buffer. This method should return the loaded model. */
parse: (buffer: ArrayBuffer | string, path: string) => ValidLoaderReturnType,
};
type CustomLoaderCallback = (args: { context: Context, url: string, mimetype: NeedleMimetype }) => CustomLoader | Promise<CustomLoader> | null | undefined | void;
/** @internal */
export const registeredModelLoaderCallbacks: Array<{ name?: string, priority: number, callback: CustomLoaderCallback }> = [];
type MimetypeCallback = (args: {
/** The URL of the file to load */
url: string,
/** The response of the range request with the first few bytes of the file (bytes are available in the 'args.bytes' property of this callback) */
response: Response,
/** The mimetype of the file as provided by the request header */
contentType: string | null,
/** The first few bytes of the file as a Uint8Array */
bytes: Uint8Array
}) => NeedleMimetype | null;
/** @internal */
export const registeredMimetypeCallbacks: Array<MimetypeCallback> = [];
/**
* NeedleEngineModelLoader is a namespace that provides functions to register custom model loaders and mimetype callbacks.
* It allows you to create custom loaders for specific file types and determine the mimetype of files based on their content.
* @example
* ```ts
* import { NeedleEngineModelLoader } from "@needle-tools/engine";
* import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
*
* NeedleEngineModelLoader.onCreateCustomModelLoader(args => {
* if (args.mimetype === "model/stl") {
* return new STLLoader();
* }
* });
*
* NeedleEngineModelLoader.onDetermineModelMimetype((args) => {
* // detect stl mimetype
* const bytes = args.bytes;
* if (bytes[0] === 0x73 && bytes[1] === 0x74 && bytes[2] === 0x6c) {
* return "model/stl";
* }
* return null;
* });
* ```
*/
export namespace NeedleEngineModelLoader {
// export type Plugin = {
// readonly name: string;
// canLoad: (mimetype: string, url: string) => boolean,
// createLoader: (url: string, mimetype: string) => Promise<CustomLoader>,
// }
// export function registerLoaderPlugin(plugin: Plugin) {
// }
type CustomLoaderOptions = {
/** The name of the loader. This is used for debugging purposes. */
name?: string,
/**
* The priority of the loader. Higher priority loaders will be called first.
* @default 0
*/
priority?: number,
}
/**
* Register a custom loader callback. For every file that is requested this callback is called with the url and mimetype. It should return a custom loader or null if it does not want to handle the file.
* @param callback The callback to register
* @param opts Optional options for the loader (e.g. name, priority)
* @returns A function to unregister the callback
* @example
* ```ts
* import { onCreateModelLoader } from "@needle-tools/engine";
* const unregister = onCreateModelLoader((url, mimetype) => {
* if (mimetype === "application/vnd.usdz+zip") {
* return new CustomLoader();
* }
* return null;
* });
*/
export function onCreateCustomModelLoader(callback: CustomLoaderCallback, opts?: CustomLoaderOptions) {
const entry = { name: opts?.name, priority: opts?.priority ?? 0, callback };
registeredModelLoaderCallbacks.push(entry);
// sort plugins by priority. Higher priority first
registeredModelLoaderCallbacks.sort((a, b) => {
if (a.priority === b.priority) return 0;
if (a.priority > b.priority) return -1;
return 1;
});
return () => {
const index = registeredModelLoaderCallbacks.indexOf(entry);
if (index >= 0) {
registeredModelLoaderCallbacks.splice(index, 1);
}
}
}
/**
* Register a callback to determine the mimetype of a file. This is to support custom loaders. The callback will provide the URL of the file to load + a range request response with the first few bytes of the file. The callback should return a mimetype or null if it does not want to handle the file.
* @param callback The callback to register
* @returns A function to unregister the callback
*
*/
export function onDetermineModelMimetype(callback: MimetypeCallback): (() => void) {
registeredMimetypeCallbacks.push(callback);
return () => {
const index = registeredMimetypeCallbacks.indexOf(callback);
if (index >= 0) {
registeredMimetypeCallbacks.splice(index, 1);
}
}
}
}