@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
text/typescript
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);
}
}
}
}