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.

120 lines (105 loc) 5.1 kB
import { LinearSRGBColorSpace, SRGBColorSpace, Texture, TextureLoader } from "three"; import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js"; import { type GLTF, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js"; import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; import { isDevEnvironment } from "../debug/index.js"; import { type ILightDataRegistry } from "../engine_lightdata.js"; import { type SourceIdentifier } from "../engine_types.js"; import { getParam, PromiseAllWithErrors, resolveUrl } from "../engine_utils.js"; import { resolveReferences } from "./extension_utils.js"; // the lightmap extension is aimed to also export export skyboxes and custom reflection maps // should we rename it? // should we split it into multiple extensions? export const EXTENSION_NAME = "NEEDLE_lightmaps"; const debug = getParam("debuglightmapsextension") || getParam("debuglightmaps") export enum LightmapType { Lightmap = 0, Skybox = 1, Reflection = 2, } declare type LightmapExtension = { textures: Array<LightmapInfo>; } declare type LightmapInfo = { type: LightmapType, pointer?: string, index?: number; } export class NEEDLE_lightmaps implements GLTFLoaderPlugin { get name(): string { return EXTENSION_NAME; } private parser: GLTFParser; private registry: ILightDataRegistry; private source: SourceIdentifier; constructor(parser: GLTFParser, reg: ILightDataRegistry, source: SourceIdentifier) { this.parser = parser; this.registry = reg; this.source = source; } afterRoot(_result: GLTF): Promise<void> | null { const extensions = this.parser.json.extensions; if (extensions) { const ext: LightmapExtension = extensions[EXTENSION_NAME]; if (ext) { const arr = ext.textures; if (!arr?.length) { return null; } if (debug) console.log(ext); return new Promise(async (resolve, _reject) => { const dependencies: Array<Promise<any>> = []; for (const entry of arr) { if (entry.pointer) { if (debug) console.log(entry); let res: Promise<any> | null = null; // Check if the pointer is a json pointer: if (entry.pointer.startsWith("/textures/")) { if (debug) console.log("Load texture from gltf", entry.pointer); res = resolveReferences(this.parser, entry.pointer).then(res => this.resolveTexture(entry, res)); } // The pointer can be a string to a file on disc else if (typeof entry.pointer === "string") { if (debug) console.log("Load texture from path", entry.pointer); const path = resolveUrl(this.source, entry.pointer); let loader: TextureLoader | EXRLoader | RGBELoader; if (path.endsWith(".exr")) loader = new EXRLoader(this.parser.options.manager); else if (path.endsWith(".hdr")) loader = new RGBELoader(this.parser.options.manager); else loader = new TextureLoader(this.parser.options.manager); res = loader.loadAsync(path, undefined).then(res => this.resolveTexture(entry, res)); } else if (entry.pointer === undefined) { // data is missing? } if (res) dependencies.push(res); } } const results = await PromiseAllWithErrors(dependencies); if (results?.anyFailed) { if (isDevEnvironment()) console.error("Failed to load lightmap extension", results); } resolve(); }); } } return null; } private resolveTexture(entry: LightmapInfo, res: any) { const tex: Texture = res as unknown as Texture; if (debug) console.log("Lightmap loaded:", tex); if (tex?.isTexture) { if (!this.registry) console.log(LightmapType[entry.type], entry.pointer, tex); else { tex.colorSpace = LinearSRGBColorSpace; this.registry.registerTexture(this.source, entry.type, tex, entry.index); } } } }