@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 • 5.03 kB
JavaScript
import { createLoaders } from "@needle-tools/gltf-progressive";
import { CubeUVReflectionMapping, EquirectangularRefractionMapping, SRGBColorSpace, TextureLoader } from "three";
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
const running = new Map();
// #region api
/**
* Loads a PMREM texture from the given URL. This also supports the ultra-fast preprocessed environment maps (PMREM) format.
* @param url The URL of the PMREM texture to load.
* @param renderer The WebGLRenderer to use for loading the texture.
* @returns A promise that resolves to the loaded texture or null if loading failed.
*/
export function loadPMREM(url, renderer) {
if (running.has(url)) {
return running.get(url);
}
const actualUrl = new URL(url, window.location.href);
const promise = internalLoadPMREM(actualUrl, renderer);
running.set(url, promise);
promise.finally(() => {
running.delete(url);
});
return promise;
}
// #region loading
async function internalLoadPMREM(url, renderer) {
if (!url)
return Promise.resolve(null);
const pathname = url.pathname;
const isPMREM_URL = url.toString().toLowerCase().includes("pmrem") || url.searchParams.get("pmrem") != null;
let isEXR = pathname.endsWith(".exr");
let isHdr = pathname.endsWith(".hdr");
let isKtx2 = pathname.endsWith(".ktx2");
// For URLs that don't carry a recognizable extension (e.g. `blob:` URLs
// from dropped local files, or signed CDN URLs that elide the filename),
// sniff Content-Type and a few magic bytes to pick the right loader.
// EXRLoader / RGBELoader / KTX2Loader vs TextureLoader is not interchangeable —
// routing EXR binary data through TextureLoader fails outright.
if (!isEXR && !isHdr && !isKtx2) {
try {
const probe = await fetch(url.toString(), {
method: "GET",
headers: { range: "bytes=0-15" },
});
const contentType = probe.headers.get("content-type")?.toLowerCase() ?? "";
// Content-Type wins when it's specific — skip reading the body.
if (contentType === "image/x-exr" || contentType === "image/aces") {
isEXR = true;
}
else if (contentType === "image/vnd.radiance" || contentType === "image/x-hdr") {
isHdr = true;
}
else if (contentType === "image/ktx2") {
isKtx2 = true;
}
// Otherwise fall back to magic-byte sniffing.
else {
const bytes = new Uint8Array(await probe.arrayBuffer());
if (bytes.length >= 4 && bytes[0] === 0x76 && bytes[1] === 0x2f && bytes[2] === 0x31 && bytes[3] === 0x01) {
// EXR magic: 0x76 0x2f 0x31 0x01
isEXR = true;
}
else if (bytes.length >= 12
&& bytes[0] === 0xAB && bytes[1] === 0x4B && bytes[2] === 0x54 && bytes[3] === 0x58
&& bytes[4] === 0x20 && bytes[5] === 0x32 && bytes[6] === 0x30 && bytes[7] === 0xBB) {
// KTX2 magic: «KTX 20»\r\n\x1A\n
isKtx2 = true;
}
else if (bytes.length >= 10
&& bytes[0] === 0x23 && bytes[1] === 0x3F // "#?"
&& bytes[2] === 0x52 && bytes[3] === 0x41 && bytes[4] === 0x44 && bytes[5] === 0x49 // "RADI"
&& bytes[6] === 0x41 && bytes[7] === 0x4E && bytes[8] === 0x43 && bytes[9] === 0x45) { // "ANCE"
// Radiance HDR header: "#?RADIANCE"
isHdr = true;
}
}
}
catch (err) {
console.warn("loadPMREM: failed to probe URL for format detection", url, err);
}
}
let loader;
if (isEXR) {
loader = new EXRLoader();
}
else if (isHdr) {
loader = new RGBELoader();
}
else if (isKtx2) {
const { ktx2Loader } = createLoaders(renderer);
loader = ktx2Loader;
}
else {
loader = new TextureLoader();
}
const str = url.toString();
const promise = loader.loadAsync(str)
.then(tex => {
if (tex) {
const nameIndex = pathname.lastIndexOf("/");
tex.name = pathname.substring(nameIndex >= 0 ? nameIndex + 1 : 0);
if (isPMREM_URL) {
tex.mapping = CubeUVReflectionMapping;
}
else {
tex.mapping = EquirectangularRefractionMapping;
}
if (loader instanceof TextureLoader) {
tex.colorSpace = SRGBColorSpace;
}
}
return tex;
});
const texture = await promise.catch(_err => {
console.warn("Failed to load texture from url:", url);
return null;
});
return texture;
}
//# sourceMappingURL=engine_pmrem.js.map