UNPKG

gpu-curtains

Version:

gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.

194 lines (193 loc) 7.34 kB
import { throwWarning } from "../../utils/utils.mjs"; //#region src/extras/loaders/GLTFLoader.ts const GL = typeof window !== "undefined" && WebGLRenderingContext || { REPEAT: 10497 }; const GLB_MAGIC = 1179937895; const CHUNK_TYPE = { JSON: 1313821514, BIN: 5130562 }; const DEFAULT_TRANSLATION = [ 0, 0, 0 ]; const DEFAULT_ROTATION = [ 0, 0, 0, 1 ]; const DEFAULT_SCALE = [ 1, 1, 1 ]; const absUriRegEx = typeof window !== "undefined" && new RegExp(`^${window.location.protocol}`, "i") || RegExp(`^(http|https):`, "i"); const dataUriRegEx = /^data:/; /** * Basic glTF loader class. * * Allow to load a glTF from an URI and create the associated {@link ArrayBuffer} and {@link ImageBitmap}. * * @example * ```javascript * const gltfLoader = new GLTFLoader() * const gltf = await gltfLoader.loadFromUrl('path/to/model.gltf') * * // create a gltfScenesManager from the resulting 'gltf' object * // assuming 'renderer' is a valid camera or curtains renderer * const gltfScenesManager = new GLTFScenesManager({ renderer, gltf }) * gltfScenesManager.addMeshes() * ``` */ var GLTFLoader = class GLTFLoader { /** * {@link GLTFLoader} constructor. */ constructor() { this.gltf = null; } /** * Build the absolute uri of the resource * @param uri - uri of the resource * @param baseUrl - base url from which to get all the other assets. * @returns - absolute uri of the resource */ static resolveUri(uri, baseUrl) { if (!!uri.match(absUriRegEx) || !!uri.match(dataUriRegEx)) return uri; return baseUrl + uri; } /** * Load a glTF from the given url. * @param url - url of the glTF. * @returns - the {@link GPUCurtainsGLTF} created. */ async loadFromUrl(url) { const i = url.lastIndexOf("/"); const baseUrl = i !== 0 ? url.substring(0, i + 1) : ""; const response = await fetch(url); if (url.endsWith(".gltf")) return this.loadFromJson(await response.json(), baseUrl); else if (url.endsWith(".glb")) return this.loadFromBinary(await response.arrayBuffer(), baseUrl); else throw new Error("Unrecognized file extension"); } /** * Parse a {@link GLTF.IGLTF | glTF json} and create our {@link gltf} base object. * @param json - already parsed JSON content. * @param baseUrl - base url from which to get all the other assets. * @param binaryChunk - optional binary chunks. * @returns - {@link gltf} base object. */ async loadFromJsonBase(json, baseUrl, binaryChunk = null) { if (baseUrl === void 0) throw new Error("baseUrl must be specified."); if (!json.asset) throw new Error("Missing asset description."); if (json.asset.minVersion !== "2.0" && json.asset.version !== "2.0") throw new Error("Incompatible asset version."); for (const accessor of json.accessors) { accessor.byteOffset = accessor.byteOffset ?? 0; accessor.normalized = accessor.normalized ?? false; } for (const bufferView of json.bufferViews) bufferView.byteOffset = bufferView.byteOffset ?? 0; for (const node of json.nodes) if (!node.matrix) { node.rotation = node.rotation ?? DEFAULT_ROTATION; node.scale = node.scale ?? DEFAULT_SCALE; node.translation = node.translation ?? DEFAULT_TRANSLATION; } if (json.samplers) for (const sampler of json.samplers) { sampler.wrapS = sampler.wrapS ?? GL.REPEAT; sampler.wrapT = sampler.wrapT ?? GL.REPEAT; } const pendingBuffers = []; if (binaryChunk) pendingBuffers.push(Promise.resolve(binaryChunk)); else for (const index in json.buffers) { const buffer = json.buffers[index]; const uri = GLTFLoader.resolveUri(buffer.uri, baseUrl); pendingBuffers[index] = fetch(uri).then((response) => response.arrayBuffer()); } const pendingImages = []; const bitmapOptions = { colorSpaceConversion: "none", premultiplyAlpha: "none" }; for (let index = 0; index < json.images?.length || 0; ++index) { const image = json.images[index]; if (image.uri) if (image.uri.includes(".ktx2") || image.uri.includes(".basis")) throwWarning(`GLTFLoader: Basis/compressed textures not supported. This file could not be loaded: ${image.uri}`); else if (image.uri.includes(".webp")) pendingImages[index] = new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { createImageBitmap(img, bitmapOptions).then(resolve).catch(reject); }; img.onerror = reject; img.src = GLTFLoader.resolveUri(image.uri, baseUrl); }); else pendingImages[index] = fetch(GLTFLoader.resolveUri(image.uri, baseUrl)).then(async (response) => { return createImageBitmap(await response.blob(), bitmapOptions); }); else { const bufferView = json.bufferViews[image.bufferView]; pendingImages[index] = pendingBuffers[bufferView.buffer].then((buffer) => { const blob = new Blob([new Uint8Array(buffer, bufferView.byteOffset, bufferView.byteLength)], { type: image.mimeType }); if (image.mimeType === "image/webp") return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.src = URL.createObjectURL(blob); img.onload = () => { createImageBitmap(img, bitmapOptions).then((bitmap) => { URL.revokeObjectURL(img.src); resolve(bitmap); }).catch(reject); }; img.onerror = (err) => { URL.revokeObjectURL(img.src); reject(err); }; }); else return createImageBitmap(blob, bitmapOptions); }); } } return { ...json, arrayBuffers: await Promise.all(pendingBuffers), imagesBitmaps: await Promise.all(pendingImages) }; } /** * Load a glTF from a .glb file. * @param arrayBuffer - {@link ArrayBuffer} containing the data. * @param baseUrl - base url from which to get all the other assets. * @returns - the {@link GPUCurtainsGLTF} created. */ async loadFromBinary(arrayBuffer, baseUrl) { const headerView = new DataView(arrayBuffer, 0, 12); const magic = headerView.getUint32(0, true); const version = headerView.getUint32(4, true); const length = headerView.getUint32(8, true); if (magic !== GLB_MAGIC) throw new Error("Invalid magic string in binary header."); if (version !== 2) throw new Error("Incompatible version in binary header."); const chunks = {}; let chunkOffset = 12; while (chunkOffset < length) { const chunkHeaderView = new DataView(arrayBuffer, chunkOffset, 8); const chunkLength = chunkHeaderView.getUint32(0, true); const chunkType = chunkHeaderView.getUint32(4, true); chunks[chunkType] = arrayBuffer.slice(chunkOffset + 8, chunkOffset + 8 + chunkLength); chunkOffset += chunkLength + 8; } if (!chunks[CHUNK_TYPE.JSON]) throw new Error("File contained no json chunk."); const jsonString = new TextDecoder("utf-8").decode(chunks[CHUNK_TYPE.JSON]); return this.loadFromJson(JSON.parse(jsonString), baseUrl, chunks[CHUNK_TYPE.BIN]); } /** * Load the glTF json, parse the data and create our {@link GPUCurtainsGLTF} object. * @param json - already parsed JSON content. * @param baseUrl - base url from which to get all the other assets. * @param binaryChunk - optional binary chunks. * @returns - the {@link GPUCurtainsGLTF} created. */ async loadFromJson(json, baseUrl, binaryChunk = null) { this.gltf = await this.loadFromJsonBase(json, baseUrl, binaryChunk); return this.gltf; } }; //#endregion export { GLTFLoader };