diff --git a/README.md b/README.md index c4033c108..d20e7c88d 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ ### DOM 2 WebGPU rendering engine -gpu-curtains is a small, lightweight WebGPU rendering engine library. +gpu-curtains is a small, lightweight 3D WebGPU rendering engine library. -Although it can theoretically be used as a genuine 3D engine, its main purpose is to turn HTML elements into textured planes, allowing you to animate them via WGSL shaders. +It can be used as a standalone 3D engine, but also includes extra classes designed to turn HTML elements into textured planes or meshes, allowing you to animate them via WGSL shaders. The project was initially conceived as a WebGPU port of [curtains.js](https://github.com/martinlaxenaire/curtainsjs). It turned out to be a complete rewrite of the library instead, but with a very similar API. @@ -168,9 +168,9 @@ window.addEventListener('load', async () => { ## Limitations -gpu-curtains is mostly made to create quads based on HTML elements, it may lack some common 3D engines features (even tho it is slowly evolving towards a real 3D engine). +gpu-curtains is a slowly evolving 3D engine and may lack some common features. -If you need a more robust 3D engine that could handle complex geometries or advanced rendering mechanics, then you should probably go with another library like [three.js](https://github.com/mrdoob/three.js) or [Babylon.js](https://github.com/BabylonJS). +If you need a more robust 3D engine that could handle complex glTF or advanced lighting, shading or rendering mechanics, then you should probably go with another library like [three.js](https://github.com/mrdoob/three.js) or [Babylon.js](https://github.com/BabylonJS). ## Contributing diff --git a/ROADMAP.md b/ROADMAP.md index 3d3c7b455..7570d6166 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,9 +8,9 @@ - Camera - Geometries - Materials (Material, RenderMaterial, ComputeMaterial) -- Buffer & Bindings & BindGroups +- Buffers & Bindings & BindGroups - Render + Compute Pipelines (async by default) -- PipelineManager to cache RenderPipeline + set only needed BindGroup +- PipelineManager to cache RenderPipelineEntry and ComputePipelineEntry + set only needed BindGroup - Meshes (Mesh, DOMMesh, FullscreenPlane, Plane) - Compute passes - Texture (including storage & depth) & DOMTexture classes @@ -20,12 +20,14 @@ - Lights & Shadows - Shader passes - PingPongPlane -- Plane raycasting +- RenderBundle - Basic CacheManager - Scroll + resize, frustum culling check - GPUCurtains - OrbitControls +- Raycaster - GLTFLoader + GLTFScenesManager +- HDRLoader + EnvironmentMap ## Work in progress @@ -34,9 +36,10 @@ ## TODO / possible improvements - Add/improve GLTFScenesManager features (sparse accessors, animations, morphing, skinning...) -- Mesh raycasting - Add more lights (SpotLight...) +- MSDF fonts +- Better shader chunks system and/or custom preprocessor +- Implement different lit extras RenderMaterial (i.e. RenderLambertMaterial, RenderPhongMaterial, RenderPBRMaterial, etc.)? +- Implement indirect draw calls? - Improve typedoc documentation? -- Use render bundles? Probably not suited to the library tho -- Use indirect draw calls? - More examples & tests? \ No newline at end of file diff --git a/dist/esm/core/DOM/DOMFrustum.mjs b/dist/esm/core/DOM/DOMFrustum.mjs index e0b0ad43a..69759c8f3 100644 --- a/dist/esm/core/DOM/DOMFrustum.mjs +++ b/dist/esm/core/DOM/DOMFrustum.mjs @@ -37,6 +37,12 @@ class DOMFrustum { this.modelViewProjectionMatrix = modelViewProjectionMatrix; this.containerBoundingRect = containerBoundingRect; this.DOMFrustumMargins = { ...defaultDOMFrustumMargins, ...DOMFrustumMargins }; + this.clipSpaceBoundingRect = { + top: 0, + left: 0, + width: 0, + height: 0 + }; this.projectedBoundingRect = { top: 0, right: 0, @@ -82,6 +88,12 @@ class DOMFrustum { */ setDocumentCoordsFromClipSpaceOBB() { this.computeClipSpaceOBB(); + this.clipSpaceBoundingRect = { + top: this.clipSpaceOBB.max.y, + left: this.clipSpaceOBB.min.x, + width: this.clipSpaceOBB.max.x - this.clipSpaceOBB.min.x, + height: this.clipSpaceOBB.max.y - this.clipSpaceOBB.min.y + }; const minX = (this.clipSpaceOBB.min.x + 1) * 0.5; const maxX = (this.clipSpaceOBB.max.x + 1) * 0.5; const minY = 1 - (this.clipSpaceOBB.min.y + 1) * 0.5; @@ -103,15 +115,21 @@ class DOMFrustum { * @param boundingSphere - bounding sphere in clip space. */ setDocumentCoordsFromClipSpaceSphere(boundingSphere = { center: new Vec3(), radius: 0 }) { + this.clipSpaceBoundingRect = { + top: boundingSphere.center.y + boundingSphere.radius, + left: boundingSphere.center.x - boundingSphere.radius, + width: boundingSphere.radius * 2, + height: boundingSphere.radius * 2 + }; const centerX = (boundingSphere.center.x + 1) * 0.5; const centerY = 1 - (boundingSphere.center.y + 1) * 0.5; const { width, height, top, left } = this.containerBoundingRect; - this.projectedBoundingRect.width = boundingSphere.radius * height * 0.5; - this.projectedBoundingRect.height = boundingSphere.radius * height * 0.5; + this.projectedBoundingRect.width = boundingSphere.radius * height; + this.projectedBoundingRect.height = boundingSphere.radius * height; this.projectedBoundingRect.left = centerX * width + left - this.projectedBoundingRect.width * 0.5; - this.projectedBoundingRect.x = centerX * width + left - this.projectedBoundingRect.width * 0.5; + this.projectedBoundingRect.x = this.projectedBoundingRect.left; this.projectedBoundingRect.top = centerY * height + top - this.projectedBoundingRect.height * 0.5; - this.projectedBoundingRect.y = centerY * height + top - this.projectedBoundingRect.height * 0.5; + this.projectedBoundingRect.y = this.projectedBoundingRect.top; this.projectedBoundingRect.right = this.projectedBoundingRect.left + this.projectedBoundingRect.width; this.projectedBoundingRect.bottom = this.projectedBoundingRect.top + this.projectedBoundingRect.height; } diff --git a/dist/esm/core/bindGroups/BindGroup.mjs b/dist/esm/core/bindGroups/BindGroup.mjs index 76580d3a2..3f83f8457 100644 --- a/dist/esm/core/bindGroups/BindGroup.mjs +++ b/dist/esm/core/bindGroups/BindGroup.mjs @@ -35,7 +35,11 @@ class BindGroup { this.consumers = /* @__PURE__ */ new Set(); for (const binding of this.bufferBindings) { if ("buffer" in binding) { - binding.buffer.consumers.add(this.uuid); + if (binding.parent) { + binding.parent.buffer.consumers.add(this.uuid); + } else { + binding.buffer.consumers.add(this.uuid); + } } if ("resultBuffer" in binding) { binding.resultBuffer.consumers.add(this.uuid); @@ -57,8 +61,13 @@ class BindGroup { addBindings(bindings = []) { bindings.forEach((binding) => { if ("buffer" in binding) { - this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding); - binding.buffer.consumers.add(this.uuid); + if (binding.parent) { + this.renderer.deviceManager.bufferBindings.set(binding.parent.cacheKey, binding.parent); + binding.parent.buffer.consumers.add(this.uuid); + } else { + this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding); + binding.buffer.consumers.add(this.uuid); + } } }); this.bindings = [...this.bindings, ...bindings]; @@ -81,6 +90,13 @@ class BindGroup { if (!binding.buffer.consumers.size) { binding.buffer.destroy(); } + if (binding.parent) { + binding.parent.buffer.consumers.delete(this.uuid); + if (!binding.parent.buffer.consumers.size) { + this.renderer.removeBuffer(binding.parent.buffer); + binding.parent.buffer.destroy(); + } + } } if ("resultBuffer" in binding) { this.renderer.removeBuffer(binding.resultBuffer); @@ -230,6 +246,9 @@ class BindGroup { this.resetEntries(); for (const binding of this.bufferBindings) { binding.buffer.reset(); + if (binding.parent) { + binding.parent.buffer.reset(); + } if ("resultBuffer" in binding) { binding.resultBuffer.reset(); } @@ -258,12 +277,13 @@ class BindGroup { ); } /** - * Creates binding GPUBuffer with correct params - * @param binding - the binding element + * Creates binding GPUBuffer with correct params. + * @param binding - The binding element. + * @param optionalLabel - Optional label to use for the {@link GPUBuffer}. */ - createBindingBuffer(binding) { + createBindingBuffer(binding, optionalLabel = null) { binding.buffer.createBuffer(this.renderer, { - label: this.options.label + ": " + binding.bindingType + " buffer from: " + binding.label, + label: optionalLabel || this.options.label + ": " + binding.bindingType + " buffer from: " + binding.label, usage: [...["copySrc", "copyDst", binding.bindingType], ...binding.options.usage] }); if ("resultBuffer" in binding) { @@ -284,7 +304,9 @@ class BindGroup { binding.visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE; } if ("buffer" in binding) { - if (!binding.buffer.GPUBuffer) { + if (binding.parent && !binding.parent.buffer.GPUBuffer) { + this.createBindingBuffer(binding.parent, binding.parent.options.label); + } else if (!binding.buffer.GPUBuffer && !binding.parent) { this.createBindingBuffer(binding); } } @@ -393,10 +415,12 @@ class BindGroup { for (const binding of bindingsRef) { bindGroupCopy.addBinding(binding); if ("buffer" in binding) { - if (!binding.buffer.GPUBuffer) { + if (binding.parent && !binding.parent.buffer.GPUBuffer) { + this.createBindingBuffer(binding.parent, binding.parent.options.label); + binding.parent.buffer.consumers.add(bindGroupCopy.uuid); + } else if (!binding.buffer.GPUBuffer && !binding.parent) { this.createBindingBuffer(binding); } - binding.buffer.consumers.add(bindGroupCopy.uuid); if ("resultBuffer" in binding) { binding.resultBuffer.consumers.add(bindGroupCopy.uuid); } diff --git a/dist/esm/core/bindings/BufferBinding.mjs b/dist/esm/core/bindings/BufferBinding.mjs index 848d7a384..bb5303c81 100644 --- a/dist/esm/core/bindings/BufferBinding.mjs +++ b/dist/esm/core/bindings/BufferBinding.mjs @@ -1,6 +1,6 @@ import { Binding } from './Binding.mjs'; import { getBindGroupLayoutBindingType, getBufferLayout, getBindingWGSLVarType } from './utils.mjs'; -import { toCamelCase, throwWarning, toKebabCase } from '../../utils/utils.mjs'; +import { throwWarning, toCamelCase, toKebabCase } from '../../utils/utils.mjs'; import { Vec2 } from '../../math/Vec2.mjs'; import { Vec3 } from '../../math/Vec3.mjs'; import { BufferElement, bytesPerRow } from './bufferElements/BufferElement.mjs'; @@ -8,7 +8,26 @@ import { BufferArrayElement } from './bufferElements/BufferArrayElement.mjs'; import { BufferInterleavedArrayElement } from './bufferElements/BufferInterleavedArrayElement.mjs'; import { Buffer } from '../buffers/Buffer.mjs'; -class BufferBinding extends Binding { +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + member.set(obj, value); + return value; +}; +var _parent; +const _BufferBinding = class _BufferBinding extends Binding { /** * BufferBinding constructor * @param parameters - {@link BufferBindingParams | parameters} used to create our BufferBindings @@ -22,17 +41,25 @@ class BufferBinding extends Binding { access = "read", usage = [], struct = {}, - bindings = [] + childrenBindings = [], + parent = null, + minOffset = 256, + offset = 0 }) { bindingType = bindingType ?? "uniform"; super({ label, name, bindingType, visibility }); + /** @ignore */ + __privateAdd(this, _parent, void 0); this.options = { ...this.options, useStruct, access, usage, struct, - bindings + childrenBindings, + parent, + minOffset, + offset }; this.cacheKey += `${useStruct},${access},`; this.arrayBufferSize = 0; @@ -45,20 +72,121 @@ class BufferBinding extends Binding { this.setBindings(struct); this.setInputsAlignment(); } - if (Object.keys(struct).length || this.options.bindings.length) { + this.setChildrenBindings(childrenBindings); + if (Object.keys(struct).length || this.childrenBindings.length) { this.setBufferAttributes(); this.setWGSLFragment(); } + this.parent = parent; } /** - * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource} + * Clone a {@link BufferBindingParams#struct | struct object} width new default values. + * @param struct - New cloned struct object. + */ + static cloneStruct(struct) { + return Object.keys(struct).reduce((acc, bindingKey) => { + const binding = struct[bindingKey]; + let value; + if (Array.isArray(binding.value) || ArrayBuffer.isView(binding.value)) { + value = new binding.value.constructor(binding.value.length); + } else if (typeof binding.value === "number") { + value = 0; + } else { + value = new binding.value.constructor(); + } + return { + ...acc, + [bindingKey]: { + type: binding.type, + value + } + }; + }, {}); + } + /** + * Get the {@link BufferBinding} parent if any. + * @readonly + * @returns - The {@link BufferBinding} parent if any. + */ + get parent() { + return __privateGet(this, _parent); + } + /** + * Set the new {@link BufferBinding} parent. + * @param value - New {@link BufferBinding} parent to set if any. + */ + set parent(value) { + if (!!value) { + this.parentView = new DataView(value.arrayBuffer, this.offset, this.getMinOffsetSize(this.arrayBufferSize)); + const getAllBufferElements = (binding) => { + const getBufferElements = (binding2) => { + return binding2.bufferElements; + }; + return [ + ...getBufferElements(binding), + binding.childrenBindings.map((child) => getAllBufferElements(child)).flat() + ].flat(); + }; + const bufferElements = getAllBufferElements(this); + this.parentViewSetBufferEls = bufferElements.map((bufferElement) => { + switch (bufferElement.bufferLayout.View) { + case Int32Array: + return { + bufferElement, + viewSetFunction: this.parentView.setInt32.bind(this.parentView) + }; + case Uint16Array: + return { + bufferElement, + viewSetFunction: this.parentView.setUint16.bind(this.parentView) + }; + case Uint32Array: + return { + bufferElement, + viewSetFunction: this.parentView.setUint32.bind(this.parentView) + }; + case Float32Array: + default: + return { + bufferElement, + viewSetFunction: this.parentView.setFloat32.bind(this.parentView) + }; + } + }); + if (!this.parent && this.buffer.GPUBuffer) { + this.buffer.destroy(); + } + } else { + this.parentView = null; + this.parentViewSetBufferEls = null; + } + __privateSet(this, _parent, value); + } + /** + * Round the given size value to the nearest minimum {@link GPUDevice} buffer offset alignment. + * @param value - Size to round. + */ + getMinOffsetSize(value) { + return Math.ceil(value / this.options.minOffset) * this.options.minOffset; + } + /** + * Get this {@link BufferBinding} offset in bytes inside the {@link arrayBuffer | parent arrayBuffer}. + * @readonly + * @returns - The offset in bytes inside the {@link arrayBuffer | parent arrayBuffer} + */ + get offset() { + return this.getMinOffsetSize(this.options.offset * this.getMinOffsetSize(this.arrayBufferSize)); + } + /** + * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource}. * @readonly */ get resourceLayout() { return { buffer: { type: getBindGroupLayoutBindingType(this) - } + }, + ...this.parent && { offset: this.offset, size: this.arrayBufferSize } }; } /** @@ -69,21 +197,27 @@ class BufferBinding extends Binding { return `buffer,${getBindGroupLayoutBindingType(this)},${this.visibility},`; } /** - * Get {@link GPUBindGroupEntry#resource | bind group resource} + * Get {@link GPUBindGroupEntry#resource | bind group resource}. * @readonly */ get resource() { - return { buffer: this.buffer.GPUBuffer }; + return { + buffer: this.parent ? this.parent.buffer.GPUBuffer : this.buffer.GPUBuffer, + ...this.parent && { offset: this.offset, size: this.arrayBufferSize } + }; } /** * Clone this {@link BufferBinding} into a new one. Allows to skip buffer layout alignment computations. * @param params - params to use for cloning */ - clone(params) { - const { struct, ...defaultParams } = params; + clone(params = {}) { + let { struct, childrenBindings, parent, ...defaultParams } = params; + const { label, name, bindingType, visibility, useStruct, access, usage } = this.options; + defaultParams = { ...{ label, name, bindingType, visibility, useStruct, access, usage }, ...defaultParams }; const bufferBindingCopy = new this.constructor(defaultParams); - struct && bufferBindingCopy.setBindings(struct); + struct = struct || _BufferBinding.cloneStruct(this.options.struct); bufferBindingCopy.options.struct = struct; + bufferBindingCopy.setBindings(struct); bufferBindingCopy.arrayBufferSize = this.arrayBufferSize; bufferBindingCopy.arrayBuffer = new ArrayBuffer(bufferBindingCopy.arrayBufferSize); bufferBindingCopy.arrayView = new DataView( @@ -108,12 +242,48 @@ class BufferBinding extends Binding { newBufferElement.setView(bufferBindingCopy.arrayBuffer, bufferBindingCopy.arrayView); bufferBindingCopy.bufferElements.push(newBufferElement); }); + if (this.options.childrenBindings) { + bufferBindingCopy.options.childrenBindings = this.options.childrenBindings; + bufferBindingCopy.options.childrenBindings.forEach((child) => { + const count = child.count ? Math.max(1, child.count) : 1; + bufferBindingCopy.cacheKey += `child(count:${count}):${child.binding.cacheKey}`; + }); + bufferBindingCopy.options.childrenBindings.forEach((child) => { + bufferBindingCopy.childrenBindings = [ + ...bufferBindingCopy.childrenBindings, + Array.from(Array(Math.max(1, child.count || 1)).keys()).map((i) => { + return child.binding.clone({ + ...child.binding.options, + // clone struct with new arrays + struct: _BufferBinding.cloneStruct(child.binding.options.struct) + }); + }) + ].flat(); + }); + bufferBindingCopy.childrenBindings.forEach((binding, index) => { + let offset = this.arrayView.byteLength; + for (let i = 0; i < index; i++) { + offset += this.childrenBindings[i].arrayBuffer.byteLength; + } + binding.bufferElements.forEach((bufferElement, i) => { + bufferElement.alignment.start.row = this.childrenBindings[index].bufferElements[i].alignment.start.row; + bufferElement.alignment.end.row = this.childrenBindings[index].bufferElements[i].alignment.end.row; + }); + binding.arrayView = new DataView(bufferBindingCopy.arrayBuffer, offset, binding.arrayBuffer.byteLength); + for (const bufferElement of binding.bufferElements) { + bufferElement.setView(bufferBindingCopy.arrayBuffer, binding.arrayView); + } + }); + } if (this.name === bufferBindingCopy.name && this.label === bufferBindingCopy.label) { bufferBindingCopy.wgslStructFragment = this.wgslStructFragment; bufferBindingCopy.wgslGroupFragment = this.wgslGroupFragment; } else { bufferBindingCopy.setWGSLFragment(); } + if (parent) { + bufferBindingCopy.parent = parent; + } bufferBindingCopy.shouldUpdate = bufferBindingCopy.arrayBufferSize > 0; return bufferBindingCopy; } @@ -153,6 +323,49 @@ class BufferBinding extends Binding { this.cacheKey += `${bindingKey},${bindings[bindingKey].type},`; } } + /** + * Set this {@link BufferBinding} optional {@link childrenBindings}. + * @param childrenBindings - Array of {@link BufferBindingChildrenBinding} to use as {@link childrenBindings}. + */ + setChildrenBindings(childrenBindings) { + this.childrenBindings = []; + if (childrenBindings && childrenBindings.length) { + const childrenArray = []; + childrenBindings.sort((a, b) => { + const countA = a.count ? Math.max(a.count) : a.forceArray ? 1 : 0; + const countB = b.count ? Math.max(b.count) : b.forceArray ? 1 : 0; + return countA - countB; + }).forEach((child) => { + if (child.count && child.count > 1 || child.forceArray) { + childrenArray.push(child.binding); + } + }); + if (childrenArray.length > 1) { + childrenArray.shift(); + throwWarning( + `BufferBinding: "${this.label}" contains multiple children bindings arrays. These children bindings cannot be added to the BufferBinding: "${childrenArray.map((child) => child.label).join(", ")}"` + ); + childrenArray.forEach((removedChildBinding) => { + childrenBindings = childrenBindings.filter((child) => child.binding.name !== removedChildBinding.name); + }); + } + this.options.childrenBindings = childrenBindings; + childrenBindings.forEach((child) => { + const count = child.count ? Math.max(1, child.count) : 1; + this.cacheKey += `child(count:${count}):${child.binding.cacheKey}`; + this.childrenBindings = [ + ...this.childrenBindings, + Array.from(Array(count).keys()).map((i) => { + return child.binding.clone({ + ...child.binding.options, + // clone struct with new arrays + struct: _BufferBinding.cloneStruct(child.binding.options.struct) + }); + }) + ].flat(); + }); + } + } /** * Set the buffer alignments from {@link inputs}. */ @@ -253,21 +466,22 @@ class BufferBinding extends Binding { setBufferAttributes() { const bufferElementsArrayBufferSize = this.bufferElements.length ? this.bufferElements[this.bufferElements.length - 1].paddedByteCount : 0; this.arrayBufferSize = bufferElementsArrayBufferSize; - this.options.bindings.forEach((binding) => { + this.childrenBindings.forEach((binding) => { this.arrayBufferSize += binding.arrayBufferSize; }); this.arrayBuffer = new ArrayBuffer(this.arrayBufferSize); this.arrayView = new DataView(this.arrayBuffer, 0, bufferElementsArrayBufferSize); - this.options.bindings.forEach((binding, index) => { + this.childrenBindings.forEach((binding, index) => { let offset = bufferElementsArrayBufferSize; for (let i = 0; i < index; i++) { - offset += this.options.bindings[i].arrayBuffer.byteLength; + offset += this.childrenBindings[i].arrayBuffer.byteLength; } const bufferElLastRow = this.bufferElements.length ? this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1 : 0; - const bindingLastRow = index > 0 ? this.options.bindings[index - 1].bufferElements.length ? this.options.bindings[index - 1].bufferElements[this.options.bindings[index - 1].bufferElements.length - 1].alignment.end.row + 1 : 0 : 0; + const bindingLastRow = index > 0 ? this.childrenBindings[index - 1].bufferElements.length ? this.childrenBindings[index - 1].bufferElements[this.childrenBindings[index - 1].bufferElements.length - 1].alignment.end.row + 1 : 0 : 0; binding.bufferElements.forEach((bufferElement) => { - bufferElement.alignment.start.row += bufferElLastRow + bindingLastRow; - bufferElement.alignment.end.row += bufferElLastRow + bindingLastRow; + const rowOffset = index === 0 ? bufferElLastRow + bindingLastRow : bindingLastRow; + bufferElement.alignment.start.row += rowOffset; + bufferElement.alignment.end.row += rowOffset; }); binding.arrayView = new DataView(this.arrayBuffer, offset, binding.arrayBuffer.byteLength); for (const bufferElement of binding.bufferElements) { @@ -284,22 +498,8 @@ class BufferBinding extends Binding { * Set the WGSL code snippet to append to the shaders code. It consists of variable (and Struct structures if needed) declarations. */ setWGSLFragment() { - if (!this.bufferElements.length && !this.options.bindings.length) + if (!this.bufferElements.length && !this.childrenBindings.length) return; - const uniqueBindings = []; - this.options.bindings.forEach((binding) => { - const bindingExists = uniqueBindings.find((b) => b.name === binding.name); - if (!bindingExists) { - uniqueBindings.push({ - name: binding.name, - label: binding.label, - count: 1, - wgslStructFragment: binding.wgslStructFragment - }); - } else { - bindingExists.count++; - } - }); const kebabCaseLabel = toKebabCase(this.label); if (this.useStruct) { const structs = {}; @@ -339,12 +539,12 @@ class BufferBinding extends Binding { const varType = getBindingWGSLVarType(this); this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]; } - if (uniqueBindings.length) { - uniqueBindings.forEach((binding) => { - structs[kebabCaseLabel][binding.name] = binding.count > 1 ? `array<${toKebabCase(binding.label)}>` : toKebabCase(binding.label); + if (this.childrenBindings.length) { + this.options.childrenBindings.forEach((child) => { + structs[kebabCaseLabel][child.binding.name] = child.count && child.count > 1 || child.forceArray ? `array<${toKebabCase(child.binding.label)}>` : toKebabCase(child.binding.label); }); } - const additionalBindings = uniqueBindings.length ? uniqueBindings.map((binding) => binding.wgslStructFragment).join("\n\n") + "\n\n" : ""; + const additionalBindings = this.childrenBindings.length ? this.options.childrenBindings.map((child) => child.binding.wgslStructFragment).join("\n\n") + "\n\n" : ""; this.wgslStructFragment = additionalBindings + Object.keys(structs).reverse().map((struct) => { return `struct ${struct} { ${Object.keys(structs[struct]).map((binding) => `${binding}: ${structs[struct][binding]}`).join(",\n ")} @@ -369,7 +569,7 @@ class BufferBinding extends Binding { } /** * Executed at the beginning of a Material render call. - * If any of the {@link inputs} has changed, run its onBeforeUpdate callback then updates our {@link arrayBuffer} array. + * If any of the {@link inputs} has changed, run its `onBeforeUpdate` callback then updates our {@link arrayBuffer} array. * Also sets the {@link shouldUpdate} property to true so the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} knows it will need to update the {@link GPUBuffer}. */ update() { @@ -383,12 +583,25 @@ class BufferBinding extends Binding { binding.shouldUpdate = false; } } - this.options.bindings.forEach((binding) => { + this.childrenBindings.forEach((binding) => { binding.update(); if (binding.shouldUpdate) { this.shouldUpdate = true; } + binding.shouldUpdate = false; }); + if (this.shouldUpdate && this.parent && this.parentViewSetBufferEls) { + let index = 0; + this.parentViewSetBufferEls.forEach((viewSetBuffer, i) => { + const { bufferElement, viewSetFunction } = viewSetBuffer; + bufferElement.view.forEach((value) => { + viewSetFunction(index * bufferElement.view.BYTES_PER_ELEMENT, value, true); + index++; + }); + }); + this.parent.shouldUpdate = true; + this.shouldUpdate = false; + } } /** * Extract the data corresponding to a specific {@link BufferElement} from a {@link Float32Array} holding the {@link BufferBinding#buffer | GPU buffer} data of this {@link BufferBinding} @@ -408,6 +621,8 @@ class BufferBinding extends Binding { return result; } } -} +}; +_parent = new WeakMap(); +let BufferBinding = _BufferBinding; export { BufferBinding }; diff --git a/dist/esm/core/bindings/WritableBufferBinding.mjs b/dist/esm/core/bindings/WritableBufferBinding.mjs index 26ee53ad2..05b2c47ea 100644 --- a/dist/esm/core/bindings/WritableBufferBinding.mjs +++ b/dist/esm/core/bindings/WritableBufferBinding.mjs @@ -15,11 +15,28 @@ class WritableBufferBinding extends BufferBinding { access = "read_write", usage = [], struct = {}, + childrenBindings = [], + parent = null, + minOffset = 256, + offset = 0, shouldCopyResult = false }) { bindingType = "storage"; visibility = ["compute"]; - super({ label, name, bindingType, visibility, useStruct, access, usage, struct }); + super({ + label, + name, + bindingType, + visibility, + useStruct, + access, + usage, + struct, + childrenBindings, + parent, + minOffset, + offset + }); this.options = { ...this.options, shouldCopyResult diff --git a/dist/esm/core/bindings/utils.mjs b/dist/esm/core/bindings/utils.mjs index b68c2dde1..a617d5033 100644 --- a/dist/esm/core/bindings/utils.mjs +++ b/dist/esm/core/bindings/utils.mjs @@ -1,7 +1,9 @@ +import { WebGPUShaderStageConstants } from '../../utils/webgpu-constants.mjs'; + const bindingVisibilities = /* @__PURE__ */ new Map([ - ["vertex", GPUShaderStage.VERTEX], - ["fragment", GPUShaderStage.FRAGMENT], - ["compute", GPUShaderStage.COMPUTE] + ["vertex", WebGPUShaderStageConstants.VERTEX], + ["fragment", WebGPUShaderStageConstants.FRAGMENT], + ["compute", WebGPUShaderStageConstants.COMPUTE] ]); const getBindingVisibility = (visibilities = []) => { return visibilities.reduce((acc, v) => { diff --git a/dist/esm/core/buffers/utils.mjs b/dist/esm/core/buffers/utils.mjs index d371851db..1e4e31c0c 100644 --- a/dist/esm/core/buffers/utils.mjs +++ b/dist/esm/core/buffers/utils.mjs @@ -1,14 +1,16 @@ +import { WebGPUBufferUsageConstants } from '../../utils/webgpu-constants.mjs'; + const bufferUsages = /* @__PURE__ */ new Map([ - ["copySrc", GPUBufferUsage.COPY_SRC], - ["copyDst", GPUBufferUsage.COPY_DST], - ["index", GPUBufferUsage.INDEX], - ["indirect", GPUBufferUsage.INDIRECT], - ["mapRead", GPUBufferUsage.MAP_READ], - ["mapWrite", GPUBufferUsage.MAP_WRITE], - ["queryResolve", GPUBufferUsage.QUERY_RESOLVE], - ["storage", GPUBufferUsage.STORAGE], - ["uniform", GPUBufferUsage.UNIFORM], - ["vertex", GPUBufferUsage.VERTEX] + ["copySrc", WebGPUBufferUsageConstants.COPY_SRC], + ["copyDst", WebGPUBufferUsageConstants.COPY_DST], + ["index", WebGPUBufferUsageConstants.INDEX], + ["indirect", WebGPUBufferUsageConstants.INDIRECT], + ["mapRead", WebGPUBufferUsageConstants.MAP_READ], + ["mapWrite", WebGPUBufferUsageConstants.MAP_WRITE], + ["queryResolve", WebGPUBufferUsageConstants.QUERY_RESOLVE], + ["storage", WebGPUBufferUsageConstants.STORAGE], + ["uniform", WebGPUBufferUsageConstants.UNIFORM], + ["vertex", WebGPUBufferUsageConstants.VERTEX] ]); const getBufferUsages = (usages = []) => { return usages.reduce((acc, v) => { diff --git a/dist/esm/core/camera/Camera.mjs b/dist/esm/core/camera/Camera.mjs index 267bd27f6..11c3635e7 100644 --- a/dist/esm/core/camera/Camera.mjs +++ b/dist/esm/core/camera/Camera.mjs @@ -18,7 +18,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _fov, _near, _far, _pixelRatio; diff --git a/dist/esm/core/computePasses/ComputePass.mjs b/dist/esm/core/computePasses/ComputePass.mjs index f653bcaab..a1264d1cf 100644 --- a/dist/esm/core/computePasses/ComputePass.mjs +++ b/dist/esm/core/computePasses/ComputePass.mjs @@ -1,5 +1,5 @@ import { isRenderer } from '../renderers/utils.mjs'; -import { generateUUID } from '../../utils/utils.mjs'; +import { generateUUID, throwWarning } from '../../utils/utils.mjs'; import { ComputeMaterial } from '../materials/ComputeMaterial.mjs'; import { Texture } from '../textures/Texture.mjs'; import { DOMTexture } from '../textures/DOMTexture.mjs'; @@ -10,7 +10,7 @@ var __accessCheck = (obj, member, msg) => { }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + return member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) @@ -19,7 +19,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _autoRender; @@ -102,7 +102,7 @@ class ComputePass { useAsyncPipeline, dispatchSize }); - this.addToScene(); + this.addToScene(true); } /** * Get or set whether the compute pass is ready to render (the material has been successfully compiled) @@ -118,22 +118,45 @@ class ComputePass { this._ready = value; } /** - * Add our compute pass to the scene and the renderer + * Add our {@link ComputePass} to the scene and optionally to the renderer. + * @param addToRenderer - whether to add this {@link ComputePass} to the {@link Renderer#computePasses | Renderer computePasses array} */ - addToScene() { - this.renderer.computePasses.push(this); + addToScene(addToRenderer = false) { + if (addToRenderer) { + this.renderer.computePasses.push(this); + } if (__privateGet(this, _autoRender)) { this.renderer.scene.addComputePass(this); } } /** - * Remove our compute pass from the scene and the renderer + * Remove our {@link ComputePass} from the scene and optionally from the renderer as well. + * @param removeFromRenderer - whether to remove this {@link ComputePass} from the {@link Renderer#computePasses | Renderer computePasses array}. */ - removeFromScene() { + removeFromScene(removeFromRenderer = false) { if (__privateGet(this, _autoRender)) { this.renderer.scene.removeComputePass(this); } - this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); + if (removeFromRenderer) { + this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); + } + } + /** + * Set a new {@link Renderer} for this {@link ComputePass}. + * @param renderer - new {@link Renderer} to set. + */ + setRenderer(renderer) { + renderer = renderer && renderer.renderer || renderer; + if (!renderer || !(renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer")) { + throwWarning( + `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.` + ); + return; + } + this.material?.setRenderer(renderer); + this.removeFromScene(true); + this.renderer = renderer; + this.addToScene(true); } /** * Create the compute pass material @@ -360,7 +383,7 @@ class ComputePass { * Remove the ComputePass from the scene and destroy it */ remove() { - this.removeFromScene(); + this.removeFromScene(true); this.destroy(); } /** diff --git a/dist/esm/core/geometries/Geometry.mjs b/dist/esm/core/geometries/Geometry.mjs index 5bfd487b7..d41aa36a1 100644 --- a/dist/esm/core/geometries/Geometry.mjs +++ b/dist/esm/core/geometries/Geometry.mjs @@ -203,9 +203,11 @@ class Geometry { */ getAttributeByName(name) { let attribute; - this.vertexBuffers.forEach((vertexBuffer) => { + for (const vertexBuffer of this.vertexBuffers) { attribute = vertexBuffer.attributes.find((attribute2) => attribute2.name === name); - }); + if (attribute) + break; + } return attribute; } /** diff --git a/dist/esm/core/lights/AmbientLight.mjs b/dist/esm/core/lights/AmbientLight.mjs index c4b95e472..14d7832dc 100644 --- a/dist/esm/core/lights/AmbientLight.mjs +++ b/dist/esm/core/lights/AmbientLight.mjs @@ -9,13 +9,7 @@ class AmbientLight extends Light { */ constructor(renderer, { color = new Vec3(1), intensity = 0.1 } = {}) { const type = "ambientLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; + super(renderer, { color, intensity, type }); } // explicitly disable all kinds of transformations /** @ignore */ diff --git a/dist/esm/core/lights/DirectionalLight.mjs b/dist/esm/core/lights/DirectionalLight.mjs index 2c2e2a40e..2bc774294 100644 --- a/dist/esm/core/lights/DirectionalLight.mjs +++ b/dist/esm/core/lights/DirectionalLight.mjs @@ -17,7 +17,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _actualPosition, _direction; @@ -35,8 +35,7 @@ class DirectionalLight extends Light { shadow = null } = {}) { const type = "directionalLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); + super(renderer, { color, intensity, type }); /** @ignore */ __privateAdd(this, _actualPosition, void 0); /** @@ -56,11 +55,6 @@ class DirectionalLight extends Light { this.target.onChange(() => this.setDirection()); this.position.copy(position); this.parent = this.renderer.scene; - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; this.shadow = new DirectionalShadow(this.renderer, { autoRender: false, // will be set by calling cast() @@ -70,6 +64,14 @@ class DirectionalLight extends Light { this.shadow.cast(shadow); } } + /** + * Set or reset this {@link DirectionalLight} {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + this.shadow?.setRenderer(renderer); + super.setRenderer(renderer); + } /** * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link DirectionalLight} has been overflowed. */ diff --git a/dist/esm/core/lights/Light.mjs b/dist/esm/core/lights/Light.mjs index 27d23fb9a..04646137f 100644 --- a/dist/esm/core/lights/Light.mjs +++ b/dist/esm/core/lights/Light.mjs @@ -18,7 +18,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _intensity, _intensityColor; @@ -28,7 +28,7 @@ class Light extends Object3D { * @param renderer - {@link CameraRenderer} used to create this {@link Light}. * @param parameters - {@link LightParams | parameters} used to create this {@link Light}. */ - constructor(renderer, { color = new Vec3(1), intensity = 1, index = 0, type = "lights" } = {}) { + constructor(renderer, { color = new Vec3(1), intensity = 1, type = "lights" } = {}) { super(); /** @ignore */ __privateAdd(this, _intensity, void 0); @@ -38,10 +38,7 @@ class Light extends Object3D { */ __privateAdd(this, _intensityColor, void 0); this.type = type; - Object.defineProperty(this, "index", { value: index }); - renderer = isCameraRenderer(renderer, this.constructor.name); - this.renderer = renderer; - this.setRendererBinding(); + this.setRenderer(renderer); this.uuid = generateUUID(); this.options = { color, @@ -53,7 +50,27 @@ class Light extends Object3D { () => this.onPropertyChanged("color", __privateGet(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)) ); this.intensity = intensity; + } + /** + * Set or reset this light {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + const hasRenderer = !!this.renderer; + if (this.renderer) { + this.renderer.removeLight(this); + } + renderer = isCameraRenderer(renderer, this.constructor.name); + this.renderer = renderer; + this.index = this.renderer.lights.filter((light) => light.type === this.type).length; + if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { + this.onMaxLightOverflow(this.type); + } this.renderer.addLight(this); + this.setRendererBinding(); + if (hasRenderer) { + this.reset(); + } } /** * Set or reset this {@link Light} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. @@ -108,22 +125,23 @@ class Light extends Object3D { * @param lightsType - {@link type} of light. */ onMaxLightOverflow(lightsType) { + this.renderer.onMaxLightOverflow(lightsType); if (this.rendererBinding) { - this.renderer.onMaxLightOverflow(lightsType); this.rendererBinding = this.renderer.bindings[lightsType]; } } /** - * Remove this {@link Light} from the {@link renderer}. + * Remove this {@link Light} from the {@link renderer} and destroy it. */ remove() { this.renderer.removeLight(this); + this.destroy(); } /** * Destroy this {@link Light}. */ destroy() { - this.parent = null; + super.destroy(); } } _intensity = new WeakMap(); diff --git a/dist/esm/core/lights/PointLight.mjs b/dist/esm/core/lights/PointLight.mjs index a1a0d5704..36b55cbc9 100644 --- a/dist/esm/core/lights/PointLight.mjs +++ b/dist/esm/core/lights/PointLight.mjs @@ -17,7 +17,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _range, _actualPosition; @@ -29,8 +29,7 @@ class PointLight extends Light { */ constructor(renderer, { color = new Vec3(1), intensity = 1, position = new Vec3(), range = 0, shadow = null } = {}) { const type = "pointLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); + super(renderer, { color, intensity, type }); /** @ignore */ __privateAdd(this, _range, void 0); /** @ignore */ @@ -45,11 +44,6 @@ class PointLight extends Light { this.position.copy(position); this.range = range; this.parent = this.renderer.scene; - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; this.shadow = new PointShadow(this.renderer, { autoRender: false, // will be set by calling cast() @@ -59,6 +53,16 @@ class PointLight extends Light { this.shadow.cast(shadow); } } + /** + * Set or reset this {@link PointLight} {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + if (this.shadow) { + this.shadow.setRenderer(renderer); + } + super.setRenderer(renderer); + } /** * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link PointLight} has been overflowed. */ diff --git a/dist/esm/core/materials/ComputeMaterial.mjs b/dist/esm/core/materials/ComputeMaterial.mjs index 9523c4d57..ff05f68b8 100644 --- a/dist/esm/core/materials/ComputeMaterial.mjs +++ b/dist/esm/core/materials/ComputeMaterial.mjs @@ -44,18 +44,16 @@ class ComputeMaterial extends Material { dispatchSize = [Math.ceil(dispatchSize), 1, 1]; } this.dispatchSize = dispatchSize; + } + /** + * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link ComputePipelineEntry} from cache or if we should create a new one. + */ + setPipelineEntry() { this.pipelineEntry = this.renderer.pipelineManager.createComputePipeline({ renderer: this.renderer, label: this.options.label + " compute pipeline", shaders: this.options.shaders, - useAsync: this.options.useAsyncPipeline - }); - } - /** - * When all bind groups are created, add them to the {@link ComputePipelineEntry} - */ - setPipelineEntryProperties() { - this.pipelineEntry.setPipelineEntryProperties({ + useAsync: this.options.useAsyncPipeline, bindGroups: this.bindGroups }); } @@ -74,8 +72,10 @@ class ComputeMaterial extends Material { if (this.ready) return; super.compileMaterial(); + if (!this.pipelineEntry) { + this.setPipelineEntry(); + } if (this.pipelineEntry && this.pipelineEntry.canCompile) { - this.setPipelineEntryProperties(); await this.compilePipelineEntry(); } } diff --git a/dist/esm/core/materials/Material.mjs b/dist/esm/core/materials/Material.mjs index a0757b930..8d275da93 100644 --- a/dist/esm/core/materials/Material.mjs +++ b/dist/esm/core/materials/Material.mjs @@ -48,6 +48,14 @@ class Material { this.setTextures(); this.setSamplers(); } + /** + * Set or reset this {@link Material} {@link renderer}. + * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + renderer = isRenderer(renderer, this.type); + this.renderer = renderer; + } /** * Check if all bind groups are ready, and create them if needed */ diff --git a/dist/esm/core/materials/RenderMaterial.mjs b/dist/esm/core/materials/RenderMaterial.mjs index 63321b10a..e26dd33ea 100644 --- a/dist/esm/core/materials/RenderMaterial.mjs +++ b/dist/esm/core/materials/RenderMaterial.mjs @@ -1,5 +1,6 @@ import { Material } from './Material.mjs'; import { isRenderer } from '../renderers/utils.mjs'; +import { RenderPipelineEntry } from '../pipelines/RenderPipelineEntry.mjs'; import { throwWarning } from '../../utils/utils.mjs'; import { compareRenderingOptions } from './utils.mjs'; import default_projected_vsWgsl from '../shaders/chunks/default/default_projected_vs.wgsl.mjs'; @@ -53,12 +54,12 @@ class RenderMaterial extends Material { if (targets === void 0) { targets = [ { - format: this.renderer.options.preferredFormat + format: this.renderer.options.context.format } ]; } if (targets && targets.length && !targets[0].format) { - targets[0].format = this.renderer.options.preferredFormat; + targets[0].format = this.renderer.options.context.format; } this.options = { ...this.options, @@ -80,6 +81,20 @@ class RenderMaterial extends Material { this.attributes = null; this.pipelineEntry = null; } + /** + * Set or reset this {@link RenderMaterial} {@link renderer}. Will also update the renderer camera bind group if needed. + * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + if (this.useCameraBindGroup && this.renderer) { + this.renderer.cameraLightsBindGroup.consumers.delete(this.uuid); + } + super.setRenderer(renderer); + if (this.useCameraBindGroup) { + this.bindGroups[0] = this.renderer.cameraLightsBindGroup; + this.renderer.cameraLightsBindGroup.consumers.add(this.uuid); + } + } /** * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link RenderPipelineEntry} from cache or if we should create a new one. */ @@ -121,6 +136,9 @@ class RenderMaterial extends Material { * @param renderingOptions - new {@link RenderMaterialRenderingOptions | rendering options} properties to be set */ setRenderingOptions(renderingOptions = {}) { + if (renderingOptions.transparent && renderingOptions.targets.length && !renderingOptions.targets[0].blend) { + renderingOptions.targets[0].blend = RenderPipelineEntry.getDefaultTransparentBlending(); + } const newProperties = compareRenderingOptions(renderingOptions, this.options.rendering); const oldRenderingOptions = { ...this.options.rendering }; this.options.rendering = { ...this.options.rendering, ...renderingOptions }; @@ -200,6 +218,11 @@ New rendering options: ${JSON.stringify( */ updateBindGroups() { const startBindGroupIndex = this.useCameraBindGroup ? 1 : 0; + if (this.useCameraBindGroup) { + if (this.bindGroups[0].needsPipelineFlush && this.pipelineEntry.ready) { + this.pipelineEntry.flushPipelineEntry(this.bindGroups); + } + } for (let i = startBindGroupIndex; i < this.bindGroups.length; i++) { this.updateBindGroup(this.bindGroups[i]); } diff --git a/dist/esm/core/meshes/FullscreenPlane.mjs b/dist/esm/core/meshes/FullscreenPlane.mjs index 561eb737f..eff8ff8b4 100644 --- a/dist/esm/core/meshes/FullscreenPlane.mjs +++ b/dist/esm/core/meshes/FullscreenPlane.mjs @@ -8,7 +8,7 @@ class FullscreenPlane extends MeshBaseMixin(class { }) { /** * FullscreenPlane constructor - * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link FullscreenPlane} + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link FullscreenPlane} * @param parameters - {@link MeshBaseRenderParams | parameters} use to create this {@link FullscreenPlane} */ constructor(renderer, parameters = {}) { diff --git a/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs index c83dab4e2..34fe1542b 100644 --- a/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs +++ b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs @@ -21,7 +21,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; let meshIndex = 0; @@ -40,7 +40,8 @@ const defaultMeshBaseParams = { visible: true, renderOrder: 0, // textures - texturesOptions: {} + texturesOptions: {}, + renderBundle: null }; function MeshBaseMixin(Base) { var _autoRender, _a; @@ -94,11 +95,13 @@ function MeshBaseMixin(Base) { visible, renderOrder, outputTarget, + renderBundle, texturesOptions, autoRender, ...meshParameters } = parameters; this.outputTarget = outputTarget ?? null; + this.renderBundle = renderBundle ?? null; meshParameters.sampleCount = !!meshParameters.sampleCount ? meshParameters.sampleCount : this.outputTarget ? this.outputTarget.renderPass.options.sampleCount : this.renderer && this.renderer.renderPass ? this.renderer.renderPass.options.sampleCount : 1; this.options = { ...this.options ?? {}, @@ -106,6 +109,7 @@ function MeshBaseMixin(Base) { label: label ?? "Mesh " + this.renderer.meshes.length, ...shaders !== void 0 ? { shaders } : {}, ...outputTarget !== void 0 && { outputTarget }, + ...renderBundle !== void 0 && { renderBundle }, texturesOptions, ...autoRender !== void 0 && { autoRender }, ...meshParameters @@ -184,6 +188,7 @@ function MeshBaseMixin(Base) { ); return; } + this.material?.setRenderer(renderer); const oldRenderer = this.renderer; this.removeFromScene(true); this.renderer = renderer; @@ -198,19 +203,33 @@ function MeshBaseMixin(Base) { } } /** - * Assign or remove a {@link RenderTarget} to this Mesh - * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well. - * @param outputTarget - the RenderTarget to assign or null if we want to remove the current RenderTarget + * Assign or remove a {@link RenderTarget} to this Mesh. + * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a {@link RenderTarget} as well. + * @param outputTarget - the {@link RenderTarget} to assign or null if we want to remove the current {@link RenderTarget}. */ setOutputTarget(outputTarget) { if (outputTarget && outputTarget.type !== "RenderTarget") { - throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget}`); + throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget.type}`); return; } this.removeFromScene(); this.outputTarget = outputTarget; this.addToScene(); } + /** + * Assign or remove a {@link RenderBundle} to this Mesh. + * @param renderBundle - the {@link RenderBundle} to assign or null if we want to remove the current {@link RenderBundle}. + * @param updateScene - Whether to remove and then re-add the Mesh from the {@link core/scenes/Scene.Scene | Scene} or not. + */ + setRenderBundle(renderBundle, updateScene = true) { + if (updateScene) { + this.removeFromScene(); + this.renderBundle = renderBundle; + this.addToScene(); + } else { + this.renderBundle = renderBundle; + } + } /** * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh @@ -451,7 +470,11 @@ ${geometry.wgslStructFragment}` if (!options.label) { options.label = this.options.label + " " + options.name; } - const domTexture = new DOMTexture(this.renderer, { ...options, ...this.options.texturesOptions }); + const texturesOptions = { ...options, ...this.options.texturesOptions }; + if (this.renderBundle) { + texturesOptions.useExternalTextures = false; + } + const domTexture = new DOMTexture(this.renderer, texturesOptions); this.addDOMTexture(domTexture); return domTexture; } @@ -460,6 +483,9 @@ ${geometry.wgslStructFragment}` * @param domTexture - {@link DOMTexture} to add */ addDOMTexture(domTexture) { + if (this.renderBundle) { + this.renderBundle.ready = false; + } this.material.addTexture(domTexture); this.onDOMTextureAdded(domTexture); } @@ -488,6 +514,9 @@ ${geometry.wgslStructFragment}` * @param texture - {@link Texture} to add */ addTexture(texture) { + if (this.renderBundle) { + this.renderBundle.ready = false; + } this.material.addTexture(texture); } /* BINDINGS */ @@ -597,9 +626,12 @@ ${geometry.wgslStructFragment}` onBeforeRenderPass() { if (!this.renderer.ready) return; - this.ready = this.material && this.material.ready && this.geometry && this.geometry.ready; this.setGeometry(); + if (this.visible) { + this._onRenderCallback && this._onRenderCallback(); + } this.material.onBeforeRender(); + this.ready = this.material && this.material.ready && this.geometry && this.geometry.ready; } /** * Render our {@link MeshBase} if the {@link RenderMaterial} is ready @@ -608,7 +640,6 @@ ${geometry.wgslStructFragment}` onRenderPass(pass) { if (!this.ready) return; - this._onRenderCallback && this._onRenderCallback(); this.material.render(pass); this.geometry.render(pass); } @@ -631,9 +662,6 @@ ${geometry.wgslStructFragment}` this.onBeforeRenderPass(); if (!this.renderer.ready || !this.visible) return; - if (super.render) { - super.render(); - } !this.renderer.production && pass.pushDebugGroup(this.options.label); this.onRenderPass(pass); !this.renderer.production && pass.popDebugGroup(); diff --git a/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs index b27532783..f342475f2 100644 --- a/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs +++ b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs @@ -4,6 +4,7 @@ import { MeshBaseMixin } from './MeshBaseMixin.mjs'; import default_projected_vsWgsl from '../../shaders/chunks/default/default_projected_vs.wgsl.mjs'; import default_normal_fsWgsl from '../../shaders/chunks/default/default_normal_fs.wgsl.mjs'; import { getPCFDirectionalShadows, getPCFShadowContribution, getPCFPointShadows, getPCFPointShadowContribution } from '../../shaders/chunks/shading/shadows.mjs'; +import { BufferBinding } from '../../bindings/BufferBinding.mjs'; const defaultProjectedMeshParams = { // frustum culling and visibility @@ -70,9 +71,61 @@ function ProjectedMeshBaseMixin(Base) { } this.setDOMFrustum(); } + /** + * Set or reset this Mesh {@link renderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + super.setRenderer(renderer); + this.camera = this.renderer.camera; + if (this.options.castShadows) { + this.renderer.shadowCastingLights.forEach((light) => { + if (light.shadow.isActive) { + light.shadow.addShadowCastingMesh(this); + } + }); + } + } + /** + * Assign or remove a {@link RenderBundle} to this Mesh. + * @param renderBundle - The {@link RenderBundle} to assign or null if we want to remove the current {@link RenderBundle}. + * @param updateScene - Whether to remove and then re-add the Mesh from the {@link core/scenes/Scene.Scene | Scene} or not. + */ + setRenderBundle(renderBundle, updateScene = true) { + if (this.renderBundle && renderBundle && this.renderBundle.uuid === renderBundle.uuid) + return; + const hasRenderBundle = !!this.renderBundle; + const bindGroup = this.material.getBindGroupByBindingName("matrices"); + const matrices = this.material.getBufferBindingByName("matrices"); + if (this.renderBundle && !renderBundle && matrices.parent) { + matrices.parent = null; + matrices.shouldResetBindGroup = true; + bindGroup.createBindingBuffer(matrices); + } + super.setRenderBundle(renderBundle, updateScene); + if (this.renderBundle && this.renderBundle.binding) { + if (hasRenderBundle) { + bindGroup.destroyBufferBinding(matrices); + } + matrices.options.offset = this.renderBundle.meshes.size - 1; + matrices.parent = this.renderBundle.binding; + matrices.shouldResetBindGroup = true; + } + } + /** + * Reset the {@link BufferBinding | matrices buffer binding} parent and offset and tell its bind group to update. + * @param offset - New offset to use in the parent {@link RenderBundle#binding | RenderBundle binding}. + */ + patchRenderBundleBinding(offset = 0) { + const matrices = this.material.getBufferBindingByName("matrices"); + matrices.options.offset = offset; + matrices.parent = this.renderBundle.binding; + matrices.shouldResetBindGroup = true; + } /* SHADERS */ /** - * Set default shaders if one or both of them are missing + * Set default shaders if one or both of them are missing. + * Can also patch the fragment shader if the mesh should receive shadows. */ setShaders() { const { shaders } = this.options; @@ -101,6 +154,13 @@ function ProjectedMeshBaseMixin(Base) { }; } } + if (this.options.receiveShadows) { + const hasActiveShadows = this.renderer.shadowCastingLights.find((light) => light.shadow.isActive); + if (hasActiveShadows && shaders.fragment && typeof shaders.fragment === "object") { + shaders.fragment.code = getPCFDirectionalShadows(this.renderer) + getPCFShadowContribution + getPCFPointShadows(this.renderer) + getPCFPointShadowContribution + shaders.fragment.code; + } + } + return shaders; } /* GEOMETRY */ /** @@ -151,10 +211,6 @@ function ProjectedMeshBaseMixin(Base) { depthSamplers.push(light.shadow.depthComparisonSampler); } }); - const hasActiveShadows = this.renderer.shadowCastingLights.find((light) => light.shadow.isActive); - if (hasActiveShadows && parameters.shaders.fragment && typeof parameters.shaders.fragment === "object") { - parameters.shaders.fragment.code = getPCFDirectionalShadows(this.renderer) + getPCFShadowContribution + getPCFPointShadows(this.renderer) + getPCFPointShadowContribution + parameters.shaders.fragment.code; - } depthSamplers = depthSamplers.filter( (sampler, i, array) => array.findIndex((s) => s.uuid === sampler.uuid) === i ); @@ -178,7 +234,9 @@ function ProjectedMeshBaseMixin(Base) { setMaterial(meshParameters) { const matricesUniforms = { label: "Matrices", + name: "matrices", visibility: ["vertex"], + minOffset: this.renderer.device.limits.minUniformBufferOffsetAlignment, struct: { model: { type: "mat4x4f", @@ -194,15 +252,16 @@ function ProjectedMeshBaseMixin(Base) { type: "mat3x3f", value: this.normalMatrix } - // modelViewProjection: { - // type: 'mat4x4f', - // value: this.modelViewProjectionMatrix, - // }, } }; - if (!meshParameters.uniforms) - meshParameters.uniforms = {}; - meshParameters.uniforms = { matrices: matricesUniforms, ...meshParameters.uniforms }; + if (this.options.renderBundle && this.options.renderBundle.binding) { + matricesUniforms.parent = this.options.renderBundle.binding; + matricesUniforms.offset = this.options.renderBundle.meshes.size; + } + const meshTransformationBinding = new BufferBinding(matricesUniforms); + if (!meshParameters.bindings) + meshParameters.bindings = []; + meshParameters.bindings.unshift(meshTransformationBinding); super.setMaterial(meshParameters); } /** @@ -312,7 +371,7 @@ function ProjectedMeshBaseMixin(Base) { const sphereCenter = cMax.add(cMin).multiplyScalar(0.5).clone(); sphereCenter.x = (rect.xMax + rect.xMin) / 2; sphereCenter.y = (rect.yMax + rect.yMin) / 2; - const sphereRadius = Math.max(rect.xMax - rect.xMin, rect.yMax - rect.yMin); + const sphereRadius = Math.max(rect.xMax - rect.xMin, rect.yMax - rect.yMin) * 0.5; return { center: sphereCenter, radius: sphereRadius diff --git a/dist/esm/core/pipelines/ComputePipelineEntry.mjs b/dist/esm/core/pipelines/ComputePipelineEntry.mjs index b90812e9c..c7a4f18c3 100644 --- a/dist/esm/core/pipelines/ComputePipelineEntry.mjs +++ b/dist/esm/core/pipelines/ComputePipelineEntry.mjs @@ -8,8 +8,7 @@ class ComputePipelineEntry extends PipelineEntry { * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link ComputePipelineEntry} */ constructor(parameters) { - const { renderer } = parameters; - const { label } = parameters; + const { label, renderer, bindGroups } = parameters; const type = "ComputePipelineEntry"; isRenderer(renderer, label ? label + " " + type : type); super(parameters); @@ -22,14 +21,7 @@ class ComputePipelineEntry extends PipelineEntry { } }; this.descriptor = null; - } - /** - * Set {@link ComputePipelineEntry} properties (in this case the {@link bindGroups | bind groups}) - * @param parameters - the {@link core/materials/ComputeMaterial.ComputeMaterial#bindGroups | bind groups} to use - */ - setPipelineEntryProperties(parameters) { - const { bindGroups } = parameters; - this.setPipelineEntryBindGroups(bindGroups); + this.setPipelineEntryProperties({ bindGroups }); } /* SHADERS */ /** diff --git a/dist/esm/core/pipelines/PipelineEntry.mjs b/dist/esm/core/pipelines/PipelineEntry.mjs index e4b93b1bc..bcd74b218 100644 --- a/dist/esm/core/pipelines/PipelineEntry.mjs +++ b/dist/esm/core/pipelines/PipelineEntry.mjs @@ -9,7 +9,7 @@ class PipelineEntry { constructor(parameters) { this.type = "PipelineEntry"; let { renderer } = parameters; - const { label, shaders, useAsync } = parameters; + const { label, shaders, useAsync, bindGroups, cacheKey } = parameters; renderer = isRenderer(renderer, label ? label + " " + this.type : this.type); this.renderer = renderer; Object.defineProperty(this, "index", { value: pipelineId++ }); @@ -23,7 +23,9 @@ class PipelineEntry { this.options = { label, shaders, - useAsync: useAsync !== void 0 ? useAsync : true + useAsync: useAsync !== void 0 ? useAsync : true, + bindGroups, + cacheKey }; } /** @@ -40,6 +42,14 @@ class PipelineEntry { get canCompile() { return !this.status.compiling && !this.status.compiled && !this.status.error; } + /** + * Set {@link PipelineEntry} properties (in this case the {@link bindGroups | bind groups}) + * @param parameters - the {@link bindGroups | bind groups} to use + */ + setPipelineEntryProperties(parameters) { + const { bindGroups } = parameters; + this.setPipelineEntryBindGroups(bindGroups); + } /** * Set our {@link PipelineEntry#bindGroups | pipeline entry bind groups} * @param bindGroups - {@link core/materials/Material.Material#bindGroups | bind groups} to use with this {@link PipelineEntry} diff --git a/dist/esm/core/pipelines/PipelineManager.mjs b/dist/esm/core/pipelines/PipelineManager.mjs index 9eccdc75c..3d0fbf148 100644 --- a/dist/esm/core/pipelines/PipelineManager.mjs +++ b/dist/esm/core/pipelines/PipelineManager.mjs @@ -59,15 +59,41 @@ class PipelineManager { } } /** - * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineEntryParams | parameters}. - * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * Checks if the provided {@link PipelineEntryParams | PipelineEntry parameters} belongs to an already created {@link ComputePipelineEntry}. * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters} + * @returns - the found {@link ComputePipelineEntry}, or null if not found + */ + isSameComputePipeline(parameters) { + return this.pipelineEntries.filter((pipelineEntry) => pipelineEntry instanceof ComputePipelineEntry).find((pipelineEntry) => { + const { options } = pipelineEntry; + const { shaders, cacheKey } = parameters; + const sameCacheKey = cacheKey === options.cacheKey; + const sameComputeShader = this.compareShaders(shaders.compute, options.shaders.compute); + return sameCacheKey && sameComputeShader; + }); + } + /** + * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineManagerPipelineEntryParams | parameters}. + * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * @param parameters - {@link PipelineManagerPipelineEntryParams | PipelineEntry parameters} * @returns - newly created {@link ComputePipelineEntry} */ createComputePipeline(parameters) { - const pipelineEntry = new ComputePipelineEntry(parameters); - this.pipelineEntries.push(pipelineEntry); - return pipelineEntry; + let cacheKey = ""; + parameters.bindGroups.forEach((bindGroup) => { + bindGroup.bindings.forEach((binding) => { + cacheKey += binding.name + ","; + }); + cacheKey += bindGroup.pipelineCacheKey; + }); + const existingPipelineEntry = this.isSameComputePipeline({ ...parameters, cacheKey }); + if (existingPipelineEntry) { + return existingPipelineEntry; + } else { + const pipelineEntry = new ComputePipelineEntry({ ...parameters, cacheKey }); + this.pipelineEntries.push(pipelineEntry); + return pipelineEntry; + } } /** * Check if the given {@link AllowedPipelineEntries | PipelineEntry} is already set, if not set it diff --git a/dist/esm/core/pipelines/RenderPipelineEntry.mjs b/dist/esm/core/pipelines/RenderPipelineEntry.mjs index 6a99eb2b6..80c8465bd 100644 --- a/dist/esm/core/pipelines/RenderPipelineEntry.mjs +++ b/dist/esm/core/pipelines/RenderPipelineEntry.mjs @@ -36,8 +36,6 @@ class RenderPipelineEntry extends PipelineEntry { this.options = { ...this.options, attributes, - bindGroups, - cacheKey, ...renderingOptions }; this.setPipelineEntryProperties({ attributes, bindGroups }); @@ -195,6 +193,22 @@ ${this.shaders.full.head}`; }); } } + /** + * Get default transparency blend state. + * @returns - The default transparency blend state. + */ + static getDefaultTransparentBlending() { + return { + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha" + }, + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha" + } + }; + } /** * Create the render pipeline {@link descriptor} */ @@ -204,16 +218,7 @@ ${this.shaders.full.head}`; let vertexLocationIndex = -1; if (this.options.rendering.targets.length) { if (this.options.rendering.transparent) { - this.options.rendering.targets[0].blend = this.options.rendering.targets[0].blend ? this.options.rendering.targets[0].blend : { - color: { - srcFactor: "src-alpha", - dstFactor: "one-minus-src-alpha" - }, - alpha: { - srcFactor: "one", - dstFactor: "one-minus-src-alpha" - } - }; + this.options.rendering.targets[0].blend = this.options.rendering.targets[0].blend ? this.options.rendering.targets[0].blend : RenderPipelineEntry.getDefaultTransparentBlending(); } } else { this.options.rendering.targets = []; diff --git a/dist/esm/core/renderPasses/RenderBundle.mjs b/dist/esm/core/renderPasses/RenderBundle.mjs new file mode 100644 index 000000000..ec88e3d0d --- /dev/null +++ b/dist/esm/core/renderPasses/RenderBundle.mjs @@ -0,0 +1,403 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { generateUUID, throwWarning } from '../../utils/utils.mjs'; +import { BufferBinding } from '../bindings/BufferBinding.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + member.set(obj, value); + return value; +}; +var __privateMethod = (obj, member, method) => { + __accessCheck(obj, member, "access private method"); + return method; +}; +var _useProjection, _ready, _setBinding, setBinding_fn, _patchBindingOffset, patchBindingOffset_fn, _onSizeChanged, onSizeChanged_fn, _setDescriptor, setDescriptor_fn, _encodeRenderCommands, encodeRenderCommands_fn, _cleanUp, cleanUp_fn; +let bundleIndex = 0; +class RenderBundle { + /** + * RenderBundle constructor + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link RenderBundle}. + * @param parameters - {@link RenderBundleParams | parameters} use to create this {@link RenderBundle}. + */ + constructor(renderer, { + label = "", + renderPass = null, + renderOrder = 0, + transparent = null, + visible = true, + size = 0, + useBuffer = false + } = {}) { + /** + * Set the {@link binding} and patches its array and buffer size if needed. + * @private + */ + __privateAdd(this, _setBinding); + /** + * Path the {@link binding} array and buffer size with the minimum {@link Renderer#device | device} buffer offset alignment. + * @param size - new {@link binding} size to use. + * @private + */ + __privateAdd(this, _patchBindingOffset); + /** + * Called each time the {@link RenderBundle} size has actually changed. + * @param newSize - new {@link RenderBundle} size to set. + * @private + */ + __privateAdd(this, _onSizeChanged); + /** + * Set the {@link descriptor} based on the {@link RenderBundleOptions#renderPass | render pass}. + * @private + */ + __privateAdd(this, _setDescriptor); + /** + * Create the {@link descriptor}, {@link encoder} and {@link bundle} used by this {@link RenderBundle}. + * @private + */ + __privateAdd(this, _encodeRenderCommands); + /** + * Destroy the {@link binding} buffer if needed and remove the {@link RenderBundle} from the {@link Renderer}. + * @private + */ + __privateAdd(this, _cleanUp); + /** @ignore */ + // whether this render bundle should be added to the 'projected' or 'unProjected' Scene stacks. + __privateAdd(this, _useProjection, void 0); + /** @ignore */ + __privateAdd(this, _ready, void 0); + this.type = "RenderBundle"; + renderer = isRenderer(renderer, this.type); + this.renderer = renderer; + this.uuid = generateUUID(); + Object.defineProperty(this, "index", { value: bundleIndex++ }); + this.renderOrder = renderOrder; + this.renderer.renderBundles.push(this); + this.transparent = transparent; + this.visible = visible; + this.options = { + label, + renderPass, + useBuffer, + size + }; + this.meshes = /* @__PURE__ */ new Map(); + this.encoder = null; + this.bundle = null; + __privateSet(this, _ready, false); + this.binding = null; + if (this.options.useBuffer) { + __privateSet(this, _useProjection, true); + if (this.options.size !== 0) { + __privateMethod(this, _setBinding, setBinding_fn).call(this); + } else { + this.options.useBuffer = false; + if (!this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): Cannot use a single transformation buffer if the size parameter has not been set upon creation.` + ); + } + } + } + } + /** + * Get whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not (useful to know in which {@link core/scenes/Scene.Scene | Scene} stack it has been added. + * @readonly + * @returns - Whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not. + */ + get useProjection() { + return __privateGet(this, _useProjection); + } + /** + * Set whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not. + * @param value - New projection value. + */ + set useProjection(value) { + __privateSet(this, _useProjection, value); + } + /** + * Set the new {@link RenderBundle} size. Should be used before adding or removing {@link meshes} to the {@link RenderBundle} if the {@link bundle} has already been created (especially if it's using a {@link binding}). + * @param value - New size to set. + */ + set size(value) { + if (value !== this.options.size) { + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not change its size after it has been created.` + ); + } + this.ready = false; + __privateMethod(this, _onSizeChanged, onSizeChanged_fn).call(this, value); + this.options.size = value; + } + } + /** + * Get whether our {@link RenderBundle} is ready. + * @readonly + * @returns - Whether our {@link RenderBundle} is ready. + */ + get ready() { + return __privateGet(this, _ready); + } + /** + * Set whether our {@link RenderBundle} is ready and encode it if needed. + * @param value - New ready state. + */ + set ready(value) { + if (value && !this.ready) { + this.size = this.meshes.size; + __privateMethod(this, _encodeRenderCommands, encodeRenderCommands_fn).call(this); + } else if (!value && this.ready) { + this.bundle = null; + } + __privateSet(this, _ready, value); + } + /** + * Called by the {@link core/scenes/Scene.Scene | Scene} to eventually add a {@link RenderedMesh | mesh} to this {@link RenderBundle}. Can set the {@link RenderBundleOptions#renderPass | render pass} if needed. If the {@link RenderBundleOptions#renderPass | render pass} is already set and the {@link mesh} output {@link RenderPass} does not match, it won't be added. + * @param mesh - {@link RenderedMesh | Mesh} to eventually add. + * @param outputPass - The mesh output {@link RenderPass}. + */ + addMesh(mesh, outputPass) { + if (!this.options.renderPass) { + this.options.renderPass = outputPass; + } else if (outputPass.uuid !== this.options.renderPass.uuid) { + throwWarning( + `${this.options.label} (${this.type}): Cannot add Mesh ${mesh.options.label} to this render bundle because the output render passes do not match.` + ); + mesh.renderBundle = null; + return; + } + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not add meshes to it after it has been created (mesh added: ${mesh.options.label}).` + ); + } + this.ready = false; + this.meshes.set(mesh.uuid, mesh); + } + /** + * Remove any {@link RenderedMesh | rendered mesh} from this {@link RenderBundle}. + * @param mesh - {@link RenderedMesh | Mesh} to remove. + */ + removeSceneObject(mesh) { + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not remove meshes from it after it has been created (mesh removed: ${mesh.options.label}).` + ); + } + this.ready = false; + this.meshes.delete(mesh.uuid); + mesh.setRenderBundle(null, false); + } + /** + * Remove a {@link SceneStackedMesh | scene stacked mesh} from this {@link RenderBundle}. + * @param mesh - {@link SceneStackedMesh | Scene stacked mesh} to remove. + * @param keepMesh - Whether to preserve the {@link mesh} in order to render it normally again. Default to `true`. + */ + removeMesh(mesh, keepMesh = true) { + this.removeSceneObject(mesh); + if (keepMesh && mesh.type !== "ShaderPass" && mesh.type !== "PingPongPlane") { + this.renderer.scene.addMesh(mesh); + } + if (this.meshes.size === 0) { + this.renderer.scene.removeRenderBundle(this); + } + } + /** + * Update the {@link binding} buffer if needed. + */ + updateBinding() { + if (this.binding && this.binding.shouldUpdate && this.binding.buffer.GPUBuffer) { + this.renderer.queueWriteBuffer(this.binding.buffer.GPUBuffer, 0, this.binding.arrayBuffer); + this.binding.shouldUpdate = false; + } + } + /** + * Render the {@link RenderBundle}. + * + * If it is ready, execute each {@link RenderedMesh#onBeforeRenderPass | mesh onBeforeRenderPass method}, {@link updateBinding | update the binding} if needed, execute the {@link bundle} and finally execute each {@link RenderedMesh#onAfterRenderPass | mesh onAfterRenderPass method}. + * + * If not, just render its {@link meshes} as usual and check whether they are all ready and if we can therefore encode our {@link RenderBundle}. + * @param pass - {@link GPURenderPassEncoder} to use. + */ + render(pass) { + if (this.ready && this.bundle && this.visible) { + this.meshes.forEach((mesh) => { + mesh.onBeforeRenderPass(); + }); + this.updateBinding(); + this.renderer.pipelineManager.resetCurrentPipeline(); + if (!this.renderer.production) { + pass.pushDebugGroup(`${this.options.label}: execute bundle`); + } + pass.executeBundles([this.bundle]); + if (!this.renderer.production) { + pass.popDebugGroup(); + } + this.renderer.pipelineManager.resetCurrentPipeline(); + this.meshes.forEach((mesh) => { + mesh.onAfterRenderPass(); + }); + } + if (!this.ready) { + let isReady = true; + for (const [_key, mesh] of this.meshes) { + mesh.render(pass); + if (!mesh.ready) { + isReady = false; + } + if ("sourcesReady" in mesh && !mesh.sourcesReady) { + isReady = false; + } + } + this.ready = isReady; + } + } + /** + * Called when the {@link Renderer#device | WebGPU device} has been lost. + * Just set the {@link ready} flag to `false` to eventually invalidate the {@link bundle}. + */ + loseContext() { + this.ready = false; + } + /** + * Empty the {@link RenderBundle}. Can eventually re-add the {@link SceneStackedMesh | scene stacked meshes} to the {@link core/scenes/Scene.Scene | Scene} in order to render them normally again. + * @param keepMeshes - Whether to preserve the {@link meshes} in order to render them normally again. Default to `true`. + */ + empty(keepMeshes = true) { + this.ready = false; + this.meshes.forEach((mesh) => { + this.removeMesh(mesh, keepMeshes); + }); + this.size = 0; + } + /** + * Remove the {@link RenderBundle}, i.e. destroy it while preserving the {@link SceneStackedMesh | scene stacked meshes} by re-adding them to the {@link core/scenes/Scene.Scene | Scene}. + */ + remove() { + this.empty(true); + __privateMethod(this, _cleanUp, cleanUp_fn).call(this); + } + /** + * Remove the {@link RenderBundle} from our {@link core/scenes/Scene.Scene | Scene}, {@link RenderedMesh#remove | remove the meshes}, eventually destroy the {@link binding} and remove the {@link RenderBundle} from the {@link Renderer}. + */ + destroy() { + this.ready = false; + this.meshes.forEach((mesh) => { + mesh.remove(); + }); + this.size = 0; + __privateMethod(this, _cleanUp, cleanUp_fn).call(this); + } +} +_useProjection = new WeakMap(); +_ready = new WeakMap(); +_setBinding = new WeakSet(); +setBinding_fn = function() { + this.binding = new BufferBinding({ + label: this.options.label + " matrices", + name: "matrices", + struct: { + model: { + type: "array", + value: new Float32Array(16 * this.options.size) + }, + modelView: { + type: "array", + value: new Float32Array(16 * this.options.size) + }, + normal: { + type: "array", + value: new Float32Array(12 * this.options.size) + } + } + }); + __privateMethod(this, _patchBindingOffset, patchBindingOffset_fn).call(this, this.options.size); +}; +_patchBindingOffset = new WeakSet(); +patchBindingOffset_fn = function(size) { + const minOffset = this.renderer.device.limits.minUniformBufferOffsetAlignment; + if (this.binding.arrayBufferSize < size * minOffset) { + this.binding.arrayBufferSize = size * minOffset; + this.binding.arrayBuffer = new ArrayBuffer(this.binding.arrayBufferSize); + this.binding.buffer.size = this.binding.arrayBuffer.byteLength; + } +}; +_onSizeChanged = new WeakSet(); +onSizeChanged_fn = function(newSize) { + if (newSize > this.options.size && this.binding) { + __privateMethod(this, _patchBindingOffset, patchBindingOffset_fn).call(this, newSize); + if (this.binding.buffer.GPUBuffer) { + this.binding.buffer.GPUBuffer.destroy(); + this.binding.buffer.createBuffer(this.renderer, { + label: this.binding.options.label, + usage: [ + ...["copySrc", "copyDst", this.binding.bindingType], + ...this.binding.options.usage + ] + }); + let offset = 0; + this.meshes.forEach((mesh) => { + mesh.patchRenderBundleBinding(offset); + offset++; + }); + this.binding.shouldUpdate = true; + } + } +}; +_setDescriptor = new WeakSet(); +setDescriptor_fn = function() { + this.descriptor = { + ...this.options.renderPass.options.colorAttachments && { + colorFormats: this.options.renderPass.options.colorAttachments.map( + (colorAttachment) => colorAttachment.targetFormat + ) + }, + ...this.options.renderPass.options.useDepth && { + depthStencilFormat: this.options.renderPass.options.depthFormat + }, + sampleCount: this.options.renderPass.options.sampleCount + }; +}; +_encodeRenderCommands = new WeakSet(); +encodeRenderCommands_fn = function() { + __privateMethod(this, _setDescriptor, setDescriptor_fn).call(this); + this.renderer.pipelineManager.resetCurrentPipeline(); + this.encoder = this.renderer.device.createRenderBundleEncoder({ + ...this.descriptor, + label: this.options.label + " (encoder)" + }); + if (!this.renderer.production) { + this.encoder.pushDebugGroup(`${this.options.label}: create encoder`); + } + this.meshes.forEach((mesh) => { + mesh.material.render(this.encoder); + mesh.geometry.render(this.encoder); + }); + if (!this.renderer.production) { + this.encoder.popDebugGroup(); + } + this.bundle = this.encoder.finish({ label: this.options.label + " (bundle)" }); + this.renderer.pipelineManager.resetCurrentPipeline(); +}; +_cleanUp = new WeakSet(); +cleanUp_fn = function() { + if (this.binding) { + this.binding.buffer.destroy(); + } + this.renderer.renderBundles = this.renderer.renderBundles.filter((bundle) => bundle.uuid !== this.uuid); +}; + +export { RenderBundle }; diff --git a/dist/esm/core/renderPasses/RenderPass.mjs b/dist/esm/core/renderPasses/RenderPass.mjs index 6fdbb0ba0..f2a854551 100644 --- a/dist/esm/core/renderPasses/RenderPass.mjs +++ b/dist/esm/core/renderPasses/RenderPass.mjs @@ -33,7 +33,7 @@ class RenderPass { loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0], - targetFormat: this.renderer.options.preferredFormat + targetFormat: this.renderer.options.context.format }; if (!colorAttachments.length) { colorAttachments = [defaultColorAttachment]; @@ -228,13 +228,13 @@ class RenderPass { } /** * Set our {@link GPUColor | clear colors value}.
- * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURenderer#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel. + * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURendererContextOptions#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel. * @param clearValue - new {@link GPUColor | clear colors value} to use * @param colorAttachmentIndex - index of the color attachment for which to use this clear value */ setClearValue(clearValue = [0, 0, 0, 0], colorAttachmentIndex = 0) { if (this.options.useColorAttachments) { - if (this.renderer.alphaMode === "premultiplied") { + if (this.renderer.options.context.alphaMode === "premultiplied") { const alpha = clearValue[3]; clearValue[0] = Math.min(clearValue[0], alpha); clearValue[1] = Math.min(clearValue[1], alpha); diff --git a/dist/esm/core/renderPasses/RenderTarget.mjs b/dist/esm/core/renderPasses/RenderTarget.mjs index ea6f73fea..2c1e5e7fa 100644 --- a/dist/esm/core/renderPasses/RenderTarget.mjs +++ b/dist/esm/core/renderPasses/RenderTarget.mjs @@ -9,7 +9,7 @@ var __accessCheck = (obj, member, msg) => { }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + return member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) @@ -18,7 +18,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _autoRender; @@ -57,7 +57,7 @@ class RenderTarget { this.renderTexture = new Texture(this.renderer, { label: this.options.label ? `${this.options.label} Render Texture` : "Render Target render texture", name: "renderTexture", - format: colorAttachments && colorAttachments.length && colorAttachments[0].targetFormat ? colorAttachments[0].targetFormat : this.renderer.options.preferredFormat, + format: colorAttachments && colorAttachments.length && colorAttachments[0].targetFormat ? colorAttachments[0].targetFormat : this.renderer.options.context.format, ...this.options.qualityRatio !== void 0 && { qualityRatio: this.options.qualityRatio }, usage: ["copySrc", "renderAttachment", "textureBinding"] }); diff --git a/dist/esm/core/renderers/GPUCameraRenderer.mjs b/dist/esm/core/renderers/GPUCameraRenderer.mjs index 73afa3408..19f26c1ce 100644 --- a/dist/esm/core/renderers/GPUCameraRenderer.mjs +++ b/dist/esm/core/renderers/GPUCameraRenderer.mjs @@ -13,7 +13,7 @@ var __accessCheck = (obj, member, msg) => { }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + return member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) @@ -22,7 +22,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _shouldUpdateCameraLightsBindGroup; @@ -37,8 +37,7 @@ class GPUCameraRenderer extends GPURenderer { container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass, camera = {}, lights = {} @@ -49,15 +48,16 @@ class GPUCameraRenderer extends GPURenderer { container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context, renderPass }); /** @ignore */ __privateAdd(this, _shouldUpdateCameraLightsBindGroup, void 0); this.type = "GPUCameraRenderer"; camera = { ...{ fov: 50, near: 0.1, far: 1e3 }, ...camera }; - lights = { ...{ maxAmbientLights: 2, maxDirectionalLights: 5, maxPointLights: 5 }, ...lights }; + if (lights !== false) { + lights = { ...{ maxAmbientLights: 2, maxDirectionalLights: 5, maxPointLights: 5 }, ...lights }; + } this.options = { ...this.options, camera, @@ -68,8 +68,10 @@ class GPUCameraRenderer extends GPURenderer { this.lights = []; this.setCamera(camera); this.setCameraBinding(); - this.setLightsBinding(); - this.setShadowsBinding(); + if (this.options.lights) { + this.setLightsBinding(); + this.setShadowsBinding(); + } this.setCameraLightsBindGroup(); } /** @@ -184,19 +186,24 @@ class GPUCameraRenderer extends GPURenderer { */ addLight(light) { this.lights.push(light); + this.bindings[light.type].inputs.count.value++; + this.bindings[light.type].inputs.count.shouldUpdate = true; } /** - * Remove a {@link Light} from the {@link lights} array and destroy it. + * Remove a {@link Light} from the {@link lights} array. * @param light - {@link Light} to remove. */ removeLight(light) { this.lights = this.lights.filter((l) => l.uuid !== light.uuid); - light.destroy(); + this.bindings[light.type].inputs.count.value--; + this.bindings[light.type].inputs.count.shouldUpdate = true; } /** * Set the lights {@link BufferBinding} based on the {@link lightsBindingParams}. */ setLightsBinding() { + if (!this.options.lights) + return; this.lightsBindingParams = { ambientLights: { max: this.options.lights.maxAmbientLights, @@ -264,7 +271,7 @@ class GPUCameraRenderer extends GPURenderer { }).reduce((acc, binding) => { acc[binding.key] = { type: binding.type, - value: new Float32Array(this.lightsBindingParams[lightsType].max * binding.size) + value: new Float32Array(Math.max(this.lightsBindingParams[lightsType].max, 1) * binding.size) }; return acc; }, {}); @@ -290,24 +297,42 @@ class GPUCameraRenderer extends GPURenderer { onMaxLightOverflow(lightsType) { if (!this.production) { throwWarning( - `${this.type}: You are overflowing the current max lights count of ${this.lightsBindingParams[lightsType].max} for this type of lights: ${lightsType}. This should be avoided by setting a larger ${"max" + lightsType.charAt(0).toUpperCase() + lightsType.slice(1)} when instancing your ${this.type}.` + `${this.options.label} (${this.type}): You are overflowing the current max lights count of '${this.lightsBindingParams[lightsType].max}' for this type of lights: ${lightsType}. This should be avoided by setting a larger ${"max" + lightsType.charAt(0).toUpperCase() + lightsType.slice(1)} when instancing your ${this.type}.` ); } this.lightsBindingParams[lightsType].max++; const oldLightBinding = this.cameraLightsBindGroup.getBindingByName(lightsType); - this.cameraLightsBindGroup.destroyBufferBinding(oldLightBinding); + if (oldLightBinding) { + this.cameraLightsBindGroup.destroyBufferBinding(oldLightBinding); + } this.setLightsTypeBinding(lightsType); const lightBindingIndex = this.cameraLightsBindGroup.bindings.findIndex((binding) => binding.name === lightsType); - this.cameraLightsBindGroup.bindings[lightBindingIndex] = this.bindings[lightsType]; + if (lightBindingIndex !== -1) { + this.cameraLightsBindGroup.bindings[lightBindingIndex] = this.bindings[lightsType]; + } else { + this.bindings[lightsType].shouldResetBindGroup = true; + this.bindings[lightsType].shouldResetBindGroupLayout = true; + this.cameraLightsBindGroup.addBinding(this.bindings[lightsType]); + this.shouldUpdateCameraLightsBindGroup(); + } if (lightsType === "directionalLights" || lightsType === "pointLights") { const shadowsType = lightsType.replace("Lights", "") + "Shadows"; const oldShadowsBinding = this.cameraLightsBindGroup.getBindingByName(shadowsType); - this.cameraLightsBindGroup.destroyBufferBinding(oldShadowsBinding); + if (oldShadowsBinding) { + this.cameraLightsBindGroup.destroyBufferBinding(oldShadowsBinding); + } this.setShadowsTypeBinding(lightsType); const shadowsBindingIndex = this.cameraLightsBindGroup.bindings.findIndex( (binding) => binding.name === shadowsType ); - this.cameraLightsBindGroup.bindings[shadowsBindingIndex] = this.bindings[shadowsType]; + if (shadowsBindingIndex !== -1) { + this.cameraLightsBindGroup.bindings[shadowsBindingIndex] = this.bindings[shadowsType]; + } else { + this.bindings[shadowsType].shouldResetBindGroup = true; + this.bindings[shadowsType].shouldResetBindGroupLayout = true; + this.cameraLightsBindGroup.addBinding(this.bindings[shadowsType]); + this.shouldUpdateCameraLightsBindGroup(); + } } this.cameraLightsBindGroup.resetEntries(); this.cameraLightsBindGroup.createBindGroup(); @@ -347,35 +372,26 @@ class GPUCameraRenderer extends GPURenderer { const shadowsType = type + "Shadows"; const struct = this.shadowsBindingsStruct[type]; const label = type.charAt(0).toUpperCase() + type.slice(1) + " shadows"; - const binding = new BufferBinding({ - label: label + " element", - name: shadowsType + "Elements", - bindingType: "uniform", - visibility: ["vertex", "fragment"], - struct - }); this.bindings[shadowsType] = new BufferBinding({ label, name: shadowsType, bindingType: "storage", visibility: ["vertex", "fragment", "compute"], // TODO needed in compute? - bindings: Array.from(Array(this.lightsBindingParams[lightsType].max).keys()).map((i) => { - return binding.clone({ - ...binding.options, - // clone struct with new arrays - struct: Object.keys(struct).reduce((acc, bindingKey) => { - const binding2 = struct[bindingKey]; - return { - ...acc, - [bindingKey]: { - type: binding2.type, - value: Array.isArray(binding2.value) || ArrayBuffer.isView(binding2.value) ? new binding2.value.constructor(binding2.value.length) : binding2.value - } - }; - }, {}) - }); - }) + childrenBindings: [ + { + binding: new BufferBinding({ + label: label + " element", + name: shadowsType + "Elements", + bindingType: "uniform", + visibility: ["vertex", "fragment"], + struct + }), + count: Math.max(1, this.lightsBindingParams[lightsType].max), + forceArray: true + // needs to be iterable anyway! + } + ] }); } /* CAMERA, LIGHTS & SHADOWS BIND GROUP */ @@ -384,7 +400,7 @@ class GPUCameraRenderer extends GPURenderer { */ setCameraLightsBindGroup() { this.cameraLightsBindGroup = new BindGroup(this, { - label: "Camera and lights uniform bind group", + label: this.options.label + ": Camera and lights uniform bind group", bindings: Object.keys(this.bindings).map((bindingName) => this.bindings[bindingName]).flat() }); this.cameraLightsBindGroup.consumers.add(this.uuid); @@ -413,6 +429,15 @@ class GPUCameraRenderer extends GPURenderer { this.bindings.camera?.shouldUpdateBinding("position"); this.shouldUpdateCameraLightsBindGroup(); } + /** + * Update the {@link cameraLightsBindGroup | camera and lights BindGroup}. + */ + updateCameraLightsBindGroup() { + if (this.cameraLightsBindGroup && __privateGet(this, _shouldUpdateCameraLightsBindGroup)) { + this.cameraLightsBindGroup.update(); + __privateSet(this, _shouldUpdateCameraLightsBindGroup, false); + } + } /** * Get all objects ({@link core/renderers/GPURenderer.RenderedMesh | rendered meshes} or {@link core/computePasses/ComputePass.ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}, including {@link cameraLightsBindGroup | camera and lights bind group}. * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not. @@ -469,18 +494,18 @@ class GPUCameraRenderer extends GPURenderer { if (!this.ready) return; this.setCameraBindGroup(); - if (this.cameraLightsBindGroup && __privateGet(this, _shouldUpdateCameraLightsBindGroup)) { - this.cameraLightsBindGroup.update(); - __privateSet(this, _shouldUpdateCameraLightsBindGroup, false); - } + this.updateCameraLightsBindGroup(); super.render(commandEncoder); + if (this.cameraLightsBindGroup) { + this.cameraLightsBindGroup.needsPipelineFlush = false; + } } /** * Destroy our {@link GPUCameraRenderer} */ destroy() { this.cameraLightsBindGroup?.destroy(); - this.lights.forEach((light) => this.removeLight(light)); + this.lights.forEach((light) => light.remove()); super.destroy(); } } diff --git a/dist/esm/core/renderers/GPURenderer.mjs b/dist/esm/core/renderers/GPURenderer.mjs index d04ca73a2..c2d5ee285 100644 --- a/dist/esm/core/renderers/GPURenderer.mjs +++ b/dist/esm/core/renderers/GPURenderer.mjs @@ -12,12 +12,11 @@ class GPURenderer { */ constructor({ deviceManager, - label = "Main renderer", + label, container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass }) { // callbacks / events @@ -35,27 +34,36 @@ class GPURenderer { }; this.type = "GPURenderer"; this.uuid = generateUUID(); - if (!deviceManager) { - throwError(`GPURenderer (${label}): no device manager provided: ${deviceManager}`); + if (!deviceManager || deviceManager.constructor.name !== "GPUDeviceManager") { + throwError( + label ? `${label} (${this.type}): no device manager or wrong device manager provided: ${deviceManager}` : `${this.type}: no device manager or wrong device manager provided: ${deviceManager}` + ); + } + if (!label) { + label = `${this.constructor.name}${deviceManager.renderers.length}`; } this.deviceManager = deviceManager; this.deviceManager.addRenderer(this); this.shouldRender = true; this.shouldRenderScene = true; + const contextOptions = { + ...{ + alphaMode: "premultiplied", + format: this.deviceManager.gpu?.getPreferredCanvasFormat() + }, + ...context + }; renderPass = { ...{ useDepth: true, sampleCount: 4, clearValue: [0, 0, 0, 0] }, ...renderPass }; - preferredFormat = preferredFormat ?? this.deviceManager.gpu?.getPreferredCanvasFormat(); this.options = { deviceManager, label, container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context: contextOptions, renderPass }; this.pixelRatio = pixelRatio ?? window.devicePixelRatio ?? 1; - this.alphaMode = alphaMode; const isOffscreenCanvas = container instanceof OffscreenCanvas; const isContainerCanvas = isOffscreenCanvas || container instanceof HTMLCanvasElement; this.canvas = isContainerCanvas ? container : document.createElement("canvas"); @@ -255,8 +263,7 @@ class GPURenderer { configureContext() { this.context.configure({ device: this.device, - format: this.options.preferredFormat, - alphaMode: this.alphaMode, + ...this.options.context, // needed so we can copy textures for post processing usage usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST //viewFormats: [] @@ -277,6 +284,7 @@ class GPURenderer { * Force all our scene objects to lose context. */ loseContext() { + this.renderBundles.forEach((bundle) => bundle.loseContext()); this.renderedObjects.forEach((sceneObject) => sceneObject.loseContext()); } /** @@ -359,7 +367,7 @@ class GPURenderer { }) { if (!srcBuffer || !srcBuffer.GPUBuffer) { throwWarning( - `${this.type} (${this.options.label}): cannot copy to buffer because the source buffer has not been provided` + `${this.options.label} (${this.type}): cannot copy to buffer because the source buffer has not been provided` ); return null; } @@ -376,13 +384,13 @@ class GPURenderer { } if (srcBuffer.GPUBuffer.mapState !== "unmapped") { throwWarning( - `${this.type} (${this.options.label}): Cannot copy from ${srcBuffer.GPUBuffer} because it is currently mapped` + `${this.options.label} (${this.type}): Cannot copy from ${srcBuffer.GPUBuffer} because it is currently mapped` ); return; } if (dstBuffer.GPUBuffer.mapState !== "unmapped") { throwWarning( - `${this.type} (${this.options.label}): Cannot copy from ${dstBuffer.GPUBuffer} because it is currently mapped` + `${this.options.label} (${this.type}): Cannot copy from ${dstBuffer.GPUBuffer} because it is currently mapped` ); return; } @@ -602,6 +610,7 @@ class GPURenderer { this.renderTargets = []; this.meshes = []; this.textures = []; + this.renderBundles = []; } /** * Get all this {@link GPURenderer} rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) @@ -782,6 +791,7 @@ class GPURenderer { destroy() { this.deviceManager.renderers = this.deviceManager.renderers.filter((renderer) => renderer.uuid !== this.uuid); this.domElement?.destroy(); + this.renderBundles.forEach((bundle) => bundle.destroy()); this.renderPass?.destroy(); this.postProcessingPass?.destroy(); this.renderTargets.forEach((renderTarget) => renderTarget.destroy()); diff --git a/dist/esm/core/renderers/utils.mjs b/dist/esm/core/renderers/utils.mjs index 50f8f8783..cb2b01c87 100644 --- a/dist/esm/core/renderers/utils.mjs +++ b/dist/esm/core/renderers/utils.mjs @@ -28,6 +28,9 @@ const isCurtainsRenderer = (renderer, type) => { } return renderer; }; +const isProjectedMesh = (object) => { + return object.constructor.name === "Mesh" || object.constructor.name === "DOMMesh" || object.constructor.name === "Plane" ? object : false; +}; const generateMips = /* @__PURE__ */ (() => { let sampler; let module; @@ -146,4 +149,4 @@ const generateMips = /* @__PURE__ */ (() => { }; })(); -export { generateMips, isCameraRenderer, isCurtainsRenderer, isRenderer }; +export { generateMips, isCameraRenderer, isCurtainsRenderer, isProjectedMesh, isRenderer }; diff --git a/dist/esm/core/scenes/Scene.mjs b/dist/esm/core/scenes/Scene.mjs index 86c9aec95..2e279c30a 100644 --- a/dist/esm/core/scenes/Scene.mjs +++ b/dist/esm/core/scenes/Scene.mjs @@ -1,6 +1,7 @@ import { isRenderer } from '../renderers/utils.mjs'; import { Object3D } from '../objects3D/Object3D.mjs'; import { Vec3 } from '../../math/Vec3.mjs'; +import { throwWarning } from '../../utils/utils.mjs'; const camPosA = new Vec3(); const camPosB = new Vec3(); @@ -127,6 +128,23 @@ class Scene extends Object3D { const { stack } = renderPassEntry; return mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected; } + /** + * Order a {@link SceneStackedObject} array by using the {@link SceneStackedObject#renderOrder | renderOrder} or {@link SceneStackedObject#index | index} properties. + * @param stack - {@link SceneStackedObject} to sort, filled with {@link RenderedMesh} or {@link RenderBundle}. + */ + orderStack(stack) { + stack.sort((a, b) => { + return a.renderOrder - b.renderOrder || a.index - b.index; + }); + } + /** + * Test whether a {@link SceneStackedObject} is a {@link RenderBundle} or not. + * @param object - Object to test. + * @returns - Whether the {@link object} is a {@link RenderBundle} or not. + */ + isStackObjectRenderBundle(object) { + return object.type === "RenderBundle"; + } /** * Add a Mesh to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. * Meshes are then ordered by their {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#index | indexes (order of creation]}, {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#index | pipeline entry indexes} and then {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#renderOrder | renderOrder} @@ -134,31 +152,80 @@ class Scene extends Object3D { */ addMesh(mesh) { const projectionStack = this.getMeshProjectionStack(mesh); - const similarMeshes = mesh.transparent ? projectionStack.transparent : projectionStack.opaque; - similarMeshes.push(mesh); - similarMeshes.sort((a, b) => { - return a.renderOrder - b.renderOrder || //a.material.pipelineEntry.index - b.material.pipelineEntry.index || - a.index - b.index; - }); - if ("parent" in mesh && !mesh.parent && mesh.material.options.rendering.useProjection) { + const isTransparent = !!mesh.transparent; + const { useProjection } = mesh.material.options.rendering; + if (mesh.renderBundle) { + const { renderBundle } = mesh; + renderBundle.addMesh(mesh, mesh.outputTarget ? mesh.outputTarget.renderPass : this.renderer.renderPass); + if (mesh.renderBundle) { + if (renderBundle.meshes.size === 1) { + if (renderBundle.transparent === null) { + renderBundle.transparent = isTransparent; + } + if (renderBundle.useProjection === null) { + renderBundle.useProjection = useProjection; + } + this.addRenderBundle(renderBundle, projectionStack); + } + } + } + if (!mesh.renderBundle) { + const similarMeshes = isTransparent ? projectionStack.transparent : projectionStack.opaque; + similarMeshes.push(mesh); + this.orderStack(similarMeshes); + } + if ("parent" in mesh && !mesh.parent && useProjection) { mesh.parent = this; } } /** - * Remove a Mesh from our {@link Scene} - * @param mesh - Mesh to remove + * Remove a Mesh from our {@link Scene}. + * @param mesh - Mesh to remove. */ removeMesh(mesh) { const projectionStack = this.getMeshProjectionStack(mesh); - if (mesh.transparent) { - projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid); + const isTransparent = !!mesh.transparent; + if (mesh.renderBundle) { + mesh.renderBundle.removeMesh(mesh, false); } else { - projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid); + if (isTransparent) { + projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid); + } else { + projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid); + } } if ("parent" in mesh && mesh.parent && mesh.parent.object3DIndex === this.object3DIndex) { mesh.parent = null; } } + /** + * Add a {@link RenderBundle} to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. + * @param renderBundle - {@link RenderBundle} to add. + * @param projectionStack - {@link ProjectionStack} onto which to add the {@link RenderBundle}. + */ + addRenderBundle(renderBundle, projectionStack) { + const similarObjects = !!renderBundle.transparent ? projectionStack.transparent : projectionStack.opaque; + similarObjects.push(renderBundle); + this.orderStack(similarObjects); + } + /** + * Remove a {@link RenderBundle} from our {@link Scene}. + * @param renderBundle - {@link RenderBundle} to remove. + */ + removeRenderBundle(renderBundle) { + const renderPassEntry = this.renderPassEntries.renderTarget.find( + (passEntry) => passEntry.renderPass.uuid === renderBundle.options.renderPass?.uuid + ); + const { stack } = renderPassEntry || this.renderPassEntries.screen[0]; + const isProjected = !!renderBundle.useProjection; + const projectionStack = isProjected ? stack.projected : stack.unProjected; + const isTransparent = !!renderBundle.transparent; + if (isTransparent) { + projectionStack.transparent = projectionStack.transparent.filter((bundle) => bundle.uuid !== renderBundle.uuid); + } else { + projectionStack.opaque = projectionStack.opaque.filter((bundle) => bundle.uuid !== renderBundle.uuid); + } + } /** * Add a {@link ShaderPass} to our scene {@link renderPassEntries} screen array. * Before rendering the {@link ShaderPass}, we will copy the correct input texture into its {@link ShaderPass#renderTexture | renderTexture} @@ -194,9 +261,10 @@ class Scene extends Object3D { ); } } : null; + const outputPass = shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass; const shaderPassEntry = { // use output target or postprocessing render pass - renderPass: shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass, + renderPass: outputPass, // render to output target renderTexture or directly to screen renderTexture: shaderPass.outputTarget ? shaderPass.outputTarget.renderTexture : null, onBeforeRenderPass, @@ -205,6 +273,24 @@ class Scene extends Object3D { stack: null // explicitly set to null }; + if (shaderPass.renderBundle) { + const isTransparent = !!shaderPass.transparent; + const { renderBundle } = shaderPass; + if (renderBundle.meshes.size < 1) { + renderBundle.addMesh(shaderPass, outputPass); + renderBundle.size = 1; + } else { + throwWarning( + `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 ShaderPass to a render bundle. This ShaderPass will not be added: ${shaderPass.options.label}` + ); + shaderPass.renderBundle = null; + } + if (shaderPass.renderBundle) { + shaderPass.renderBundle.renderOrder = shaderPass.renderOrder; + renderBundle.transparent = isTransparent; + renderBundle.useProjection = false; + } + } this.renderPassEntries.screen.push(shaderPassEntry); this.renderPassEntries.screen.sort((a, b) => { const isPostProA = a.element && !a.element.outputTarget; @@ -229,6 +315,9 @@ class Scene extends Object3D { * @param shaderPass - {@link ShaderPass} to remove */ removeShaderPass(shaderPass) { + if (shaderPass.renderBundle) { + shaderPass.renderBundle.empty(); + } this.renderPassEntries.screen = this.renderPassEntries.screen.filter( (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid ); @@ -259,6 +348,24 @@ class Scene extends Object3D { stack: null // explicitly set to null }); + if (pingPongPlane.renderBundle) { + const isTransparent = !!pingPongPlane.transparent; + const { renderBundle } = pingPongPlane; + if (renderBundle.meshes.size < 1) { + renderBundle.addMesh(pingPongPlane, pingPongPlane.outputTarget.renderPass); + renderBundle.size = 1; + } else { + throwWarning( + `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 PingPongPlane to a render bundle. This PingPongPlane will not be added: ${pingPongPlane.options.label}` + ); + pingPongPlane.renderBundle = null; + } + if (pingPongPlane.renderBundle) { + pingPongPlane.renderBundle.renderOrder = pingPongPlane.renderOrder; + renderBundle.transparent = isTransparent; + renderBundle.useProjection = false; + } + } this.renderPassEntries.pingPong.sort((a, b) => a.element.renderOrder - b.element.renderOrder); } /** @@ -266,6 +373,9 @@ class Scene extends Object3D { * @param pingPongPlane - {@link PingPongPlane} to remove */ removePingPongPlane(pingPongPlane) { + if (pingPongPlane.renderBundle) { + pingPongPlane.renderBundle.empty(); + } this.renderPassEntries.pingPong = this.renderPassEntries.pingPong.filter( (entry) => entry.element.uuid !== pingPongPlane.uuid ); @@ -286,14 +396,27 @@ class Scene extends Object3D { return this.renderPassEntries.screen.find((entry) => entry.element?.uuid === object.uuid); } else { const entryType = object.outputTarget ? "renderTarget" : "screen"; - return this.renderPassEntries[entryType].find((entry) => { - return [ - ...entry.stack.unProjected.opaque, - ...entry.stack.unProjected.transparent, - ...entry.stack.projected.opaque, - ...entry.stack.projected.transparent - ].some((mesh) => mesh.uuid === object.uuid); - }); + if (object.renderBundle) { + return this.renderPassEntries[entryType].find((entry) => { + return [ + ...entry.stack.unProjected.opaque, + ...entry.stack.unProjected.transparent, + ...entry.stack.projected.opaque, + ...entry.stack.projected.transparent + ].filter((object2) => object2.type === "RenderBundle").some((bundle) => { + return bundle.meshes.get(object.uuid); + }); + }); + } else { + return this.renderPassEntries[entryType].find((entry) => { + return [ + ...entry.stack.unProjected.opaque, + ...entry.stack.unProjected.transparent, + ...entry.stack.projected.opaque, + ...entry.stack.projected.transparent + ].some((mesh) => mesh.uuid === object.uuid); + }); + } } } /** @@ -305,6 +428,9 @@ class Scene extends Object3D { if (meshA.renderOrder !== meshB.renderOrder) { return meshA.renderOrder - meshB.renderOrder; } + if (this.isStackObjectRenderBundle(meshA) || this.isStackObjectRenderBundle(meshB)) { + return meshA.renderOrder - meshB.renderOrder; + } meshA.geometry ? posA.copy(meshA.geometry.boundingBox.center).applyMat4(meshA.worldMatrix) : meshA.worldMatrix.getTranslation(posA); meshB.geometry ? posB.copy(meshB.geometry.boundingBox.center).applyMat4(meshB.worldMatrix) : meshB.worldMatrix.getTranslation(posB); const radiusA = meshA.geometry ? meshA.geometry.boundingBox.radius * meshA.worldMatrix.getMaxScaleOnAxis() : 0; @@ -328,11 +454,17 @@ class Scene extends Object3D { const swapChainTexture = renderPassEntry.renderPass.updateView(renderPassEntry.renderTexture?.texture); renderPassEntry.onBeforeRenderPass && renderPassEntry.onBeforeRenderPass(commandEncoder, swapChainTexture); const pass = commandEncoder.beginRenderPass(renderPassEntry.renderPass.descriptor); - !this.renderer.production && pass.pushDebugGroup( - renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` - ); + if (!this.renderer.production) { + pass.pushDebugGroup( + renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` + ); + } if (renderPassEntry.element) { - renderPassEntry.element.render(pass); + if (renderPassEntry.element.renderBundle) { + renderPassEntry.element.renderBundle.render(pass); + } else { + renderPassEntry.element.render(pass); + } } else if (renderPassEntry.stack) { for (const mesh of renderPassEntry.stack.unProjected.opaque) { mesh.render(pass); @@ -350,7 +482,8 @@ class Scene extends Object3D { } } } - !this.renderer.production && pass.popDebugGroup(); + if (!this.renderer.production) + pass.popDebugGroup(); pass.end(); renderPassEntry.onAfterRenderPass && renderPassEntry.onAfterRenderPass(commandEncoder, swapChainTexture); this.renderer.pipelineManager.resetCurrentPipeline(); @@ -379,7 +512,11 @@ class Scene extends Object3D { render(commandEncoder) { for (const computePass of this.computePassEntries) { const pass = commandEncoder.beginComputePass(); + if (!this.renderer.production) + pass.pushDebugGroup(`${computePass.options.label}: begin compute pass`); computePass.render(pass); + if (!this.renderer.production) + pass.popDebugGroup(); pass.end(); computePass.copyBufferToResult(commandEncoder); this.renderer.pipelineManager.resetCurrentPipeline(); diff --git a/dist/esm/core/shaders/chunks/shading/ibl-shading.mjs b/dist/esm/core/shaders/chunks/shading/ibl-shading.mjs index b129a9f8c..9890ad627 100644 --- a/dist/esm/core/shaders/chunks/shading/ibl-shading.mjs +++ b/dist/esm/core/shaders/chunks/shading/ibl-shading.mjs @@ -17,12 +17,10 @@ fn getIBLIndirect( metallic: f32, diffuseColor: vec3f, f0: vec3f, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ptr_reflectedLight: ptr, // ptr_iblIndirect: ptr ) { @@ -38,7 +36,7 @@ fn getIBLIndirect( let brdf: vec3f = textureSample( lutTexture, - lutSampler, + clampSampler, brdfSamplePoint ).rgb; @@ -51,16 +49,16 @@ fn getIBLIndirect( let specularLight: vec4f = textureSampleLevel( envSpecularTexture, - envSpecularSampler, - reflection, + clampSampler, + reflection * ibl.envRotation, lod ); // IBL diffuse (irradiance) let diffuseLight: vec4f = textureSample( envDiffuseTexture, - envDiffuseSampler, - normal + clampSampler, + normal * ibl.envRotation ); // product of specularFactor and specularTexture.a @@ -97,12 +95,10 @@ fn getIBL( f0: vec3f, metallic: f32, roughness: f32, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ${useOcclusion ? "occlusion: f32," : ""} ) -> vec3f { var directLight: DirectLight; @@ -137,12 +133,10 @@ fn getIBL( metallic, diffuseColor, f0, + clampSampler, lutTexture, - lutSampler, envSpecularTexture, - envSpecularSampler, envDiffuseTexture, - envDiffuseSampler, &reflectedLight, // &iblIndirect ); diff --git a/dist/esm/core/shaders/chunks/shading/shadows.mjs b/dist/esm/core/shaders/chunks/shading/shadows.mjs index 7e71d7aa7..b044478ad 100644 --- a/dist/esm/core/shaders/chunks/shading/shadows.mjs +++ b/dist/esm/core/shaders/chunks/shading/shadows.mjs @@ -53,40 +53,45 @@ fn getPCFShadowContribution(index: i32, worldPosition: vec3f, depthTexture: text shadowCoords.z ); - // Percentage-closer filtering. Sample texels in the region - // to smooth the result. var visibility = 0.0; - let size: vec2f = vec2f(textureDimensions(depthTexture).xy); + let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; + let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; - let texelSize: vec2f = 1.0 / size; + if(frustumTest) { + // Percentage-closer filtering. Sample texels in the region + // to smooth the result. + let size: vec2f = vec2f(textureDimensions(depthTexture).xy); - let sampleCount: i32 = directionalShadow.pcfSamples; - let maxSamples: f32 = f32(sampleCount) - 1.0; + let texelSize: vec2f = 1.0 / size; + + let sampleCount: i32 = directionalShadow.pcfSamples; + let maxSamples: f32 = f32(sampleCount) - 1.0; - for (var x = 0; x < sampleCount; x++) { - for (var y = 0; y < sampleCount; y++) { - let offset = texelSize * vec2( - f32(x) - maxSamples * 0.5, - f32(y) - maxSamples * 0.5 - ); - - visibility += textureSampleCompare( - depthTexture, - depthComparisonSampler, - shadowCoords.xy + offset, - shadowCoords.z - directionalShadow.bias - ); + for (var x = 0; x < sampleCount; x++) { + for (var y = 0; y < sampleCount; y++) { + let offset = texelSize * vec2( + f32(x) - maxSamples * 0.5, + f32(y) - maxSamples * 0.5 + ); + + visibility += textureSampleCompareLevel( + depthTexture, + depthComparisonSampler, + shadowCoords.xy + offset, + shadowCoords.z - directionalShadow.bias + ); + } } + visibility /= f32(sampleCount * sampleCount); + + visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); + } + else { + visibility = 1.0; } - visibility /= f32(sampleCount * sampleCount); - - visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); - - let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; - let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; - return select(1.0, visibility, frustumTest); + return visibility; } ` ); @@ -94,21 +99,29 @@ const getPCFDirectionalShadows = (renderer) => { const directionalLights = renderer.shadowCastingLights.filter( (light) => light.type === "directionalLights" ); + const minDirectionalLights = Math.max(renderer.lightsBindingParams.directionalLights.max, 1); return ( /* wgsl */ ` -fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { - var directionalShadowContribution: array; +fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { + var directionalShadowContribution: array; var lightDirection: vec3f; ${directionalLights.map((light, index) => { return `lightDirection = worldPosition - directionalLights.elements[${index}].direction; - ${light.shadow.isActive ? `directionalShadowContribution[${index}] = select( 1.0, getPCFShadowContribution(${index}, worldPosition, shadowDepthTexture${index}), directionalShadows.directionalShadowsElements[${index}].isActive > 0);` : ""}`; + ${light.shadow.isActive ? ` + if(directionalShadows.directionalShadowsElements[${index}].isActive > 0) { + directionalShadowContribution[${index}] = getPCFShadowContribution( + ${index}, + worldPosition, + shadowDepthTexture${index} + ); + } else { + directionalShadowContribution[${index}] = 1.0; + } + ` : `directionalShadowContribution[${index}] = 1.0;`}`; }).join("\n")} return directionalShadowContribution; @@ -198,7 +211,7 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex f32(z) - maxSamples * 0.5 ); - closestDepth = textureSampleCompare( + closestDepth = textureSampleCompareLevel( depthCubeTexture, depthComparisonSampler, shadowPosition.xyz + offset, @@ -206,9 +219,8 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex ); closestDepth *= cameraRange; - if(currentDepth <= closestDepth) { - visibility += 1.0; - } + + visibility += select(0.0, 1.0, currentDepth <= closestDepth); } } } @@ -222,21 +234,34 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex ); const getPCFPointShadows = (renderer) => { const pointLights = renderer.shadowCastingLights.filter((light) => light.type === "pointLights"); + const minPointLights = Math.max(renderer.lightsBindingParams.pointLights.max, 1); return ( /* wgsl */ ` -fn getPCFPointShadows(worldPosition: vec3f) -> array { - var pointShadowContribution: array; +fn getPCFPointShadows(worldPosition: vec3f) -> array { + var pointShadowContribution: array; var lightDirection: vec3f; + var lightDistance: f32; + var lightColor: vec3f; ${pointLights.map((light, index) => { return `lightDirection = pointLights.elements[${index}].position - worldPosition; - ${light.shadow.isActive ? `pointShadowContribution[${index}] = select( 1.0, getPCFPointShadowContribution(${index}, vec4(lightDirection, length(lightDirection)), pointShadowCubeDepthTexture${index}), pointShadows.pointShadowsElements[${index}].isActive > 0);` : ""}`; + lightDistance = length(lightDirection); + lightColor = pointLights.elements[${index}].color * rangeAttenuation(pointLights.elements[${index}].range, lightDistance); + + ${light.shadow.isActive ? ` + if(pointShadows.pointShadowsElements[${index}].isActive > 0 && length(lightColor) > 0.0001) { + pointShadowContribution[${index}] = getPCFPointShadowContribution( + ${index}, + vec4(lightDirection, length(lightDirection)), + pointShadowCubeDepthTexture${index} + ); + } else { + pointShadowContribution[${index}] = 1.0; + } + ` : `pointShadowContribution[${index}] = 1.0;`}`; }).join("\n")} return pointShadowContribution; @@ -260,9 +285,7 @@ const applyDirectionalShadows = ( const applyPointShadows = ( /* wgsl */ ` - if(directLight.visible) { - directLight.color *= pointShadows[i]; - } + directLight.color *= pointShadows[i]; ` ); diff --git a/dist/esm/core/shaders/compute/compute-brdf-lut.wgsl.mjs b/dist/esm/core/shaders/compute/compute-brdf-lut.wgsl.mjs new file mode 100644 index 000000000..47cc24af6 --- /dev/null +++ b/dist/esm/core/shaders/compute/compute-brdf-lut.wgsl.mjs @@ -0,0 +1,199 @@ +var computeBrdfLutWgsl = ( + /* wgsl */ + ` +fn radicalInverse_VdC(inputBits: u32) -> f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// GGX microfacet distribution +struct MicrofacetDistributionSample { + pdf: f32, + cosTheta: f32, + sinTheta: f32, + phi: f32 +}; + +fn D_GGX(NdotH: f32, roughness: f32) -> f32 { + let a: f32 = NdotH * roughness; + let k: f32 = roughness / (1.0 - NdotH * NdotH + a * a); + return k * k * (1.0 / ${Math.PI}); +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.html +// This implementation is based on https://bruop.github.io/ibl/, +// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html +// and https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +fn GGX(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var ggx: MicrofacetDistributionSample; + + // evaluate sampling equations + let alpha: f32 = roughness * roughness; + ggx.cosTheta = clamp(sqrt((1.0 - xi.y) / (1.0 + (alpha * alpha - 1.0) * xi.y)), 0.0, 1.0); + ggx.sinTheta = sqrt(1.0 - ggx.cosTheta * ggx.cosTheta); + ggx.phi = 2.0 * ${Math.PI} * xi.x; + + // evaluate GGX pdf (for half vector) + ggx.pdf = D_GGX(ggx.cosTheta, alpha); + + // Apply the Jacobian to obtain a pdf that is parameterized by l + // see https://bruop.github.io/ibl/ + // Typically you'd have the following: + // float pdf = D_GGX(NoH, roughness) * NoH / (4.0 * VoH); + // but since V = N => VoH == NoH + ggx.pdf /= 4.0; + + return ggx; +} + +fn Lambertian(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var lambertian: MicrofacetDistributionSample; + + // Cosine weighted hemisphere sampling + // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Cosine-WeightedHemisphereSampling + lambertian.cosTheta = sqrt(1.0 - xi.y); + lambertian.sinTheta = sqrt(xi.y); // equivalent to \`sqrt(1.0 - cosTheta*cosTheta)\`; + lambertian.phi = 2.0 * ${Math.PI} * xi.x; + + lambertian.pdf = lambertian.cosTheta / ${Math.PI}; // evaluation for solid angle, therefore drop the sinTheta + + return lambertian; +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); + } + else { + bitangent = vec3(0.0, 0.0, -1.0); + } + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// getImportanceSample returns an importance sample direction with pdf in the .w component +fn getImportanceSample(Xi: vec2, N: vec3f, roughness: f32) -> vec4f { + var importanceSample: MicrofacetDistributionSample; + + importanceSample = GGX(Xi, roughness); + + // transform the hemisphere sample to the normal coordinate frame + // i.e. rotate the hemisphere to the normal direction + let localSpaceDirection: vec3f = normalize(vec3( + importanceSample.sinTheta * cos(importanceSample.phi), + importanceSample.sinTheta * sin(importanceSample.phi), + importanceSample.cosTheta + )); + + let TBN: mat3x3f = generateTBN(N); + let direction: vec3f = TBN * localSpaceDirection; + + return vec4(direction, importanceSample.pdf); +} + +// From the filament docs. Geometric Shadowing function +// https://google.github.io/filament/Filament.html#toc4.4.2 +fn V_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 { + let a2: f32 = pow(roughness, 4.0); + let GGXV: f32 = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2); + let GGXL: f32 = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2); + return 0.5 / (GGXV + GGXL); +} + +@compute @workgroup_size(16, 16, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let texelSize: vec2 = textureDimensions(lutStorageTexture); + + let x: u32 = global_id.x; + let y: u32 = global_id.y; + + // Check bounds + if (x >= texelSize.x || y >= texelSize.y) { + return; + } + + let epsilon: f32 = 1e-6; + + // Compute roughness and N\xB7V from texture coordinates + let NdotV: f32 = max(f32(x) / f32(texelSize.x - 1), epsilon); // Maps x-axis to N\xB7V (0.0 to 1.0) + let roughness: f32 = max(f32(y) / f32(texelSize.y - 1), epsilon); // Maps y-axis to roughness (0.0 to 1.0) + + // Calculate view vector and normal vector + let V: vec3 = vec3(sqrt(1.0 - NdotV * NdotV), 0.0, NdotV); // Normalized view vector + let N: vec3 = vec3(0.0, 0.0, 1.0); // Normal is along z-axis + + // Initialize integration variables + var A: f32 = 0.0; + var B: f32 = 0.0; + var C: f32 = 0.0; + + // Monte Carlo integration to calculate A and B factors + let sampleCount: u32 = params.sampleCount; + for (var i: u32 = 0; i < sampleCount; i++) { + let Xi: vec2 = hammersley2d(i, sampleCount); // Importance sampling (Hammersley sequence) + + //let H: vec3 = importanceSampleGGX(Xi, N, roughness); + let importanceSample: vec4f = getImportanceSample(Xi, N, roughness); + let H: vec3f = importanceSample.xyz; + // let pdf: f32 = importanceSample.w; + + let L: vec3 = normalize(reflect(-V, H)); + + let NdotL: f32 = clamp(L.z, 0.0, 1.0); + let NdotH: f32 = clamp(H.z, 0.0, 1.0); + let VdotH: f32 = clamp(dot(V, H), 0.0, 1.0); + + // Ensure valid light direction + if (NdotL > 0.0) { + // LUT for GGX distribution. + + // Taken from: https://bruop.github.io/ibl + // Shadertoy: https://www.shadertoy.com/view/3lXXDB + // Terms besides V are from the GGX PDF we're dividing by. + let V_pdf: f32 = V_SmithGGXCorrelated(NdotV, NdotL, roughness) * VdotH * NdotL / max(NdotH, epsilon); + let Fc: f32 = pow(1.0 - VdotH, 5.0); + A += (1.0 - Fc) * V_pdf; + B += Fc * V_pdf; + C += 0.0; + } + } + + // Average the integration result + // The PDF is simply pdf(v, h) -> NDF * . + // To parametrize the PDF over l, use the Jacobian transform, yielding to: pdf(v, l) -> NDF * / 4 + // Since the BRDF divide through the PDF to be normalized, the 4 can be pulled out of the integral. + A = A * 4.0 / f32(sampleCount); + B = B * 4.0 / f32(sampleCount); + C = C * 4.0 * 2.0 * ${Math.PI} / f32(sampleCount); + + // Store the result in the LUT texture + textureStore(lutStorageTexture, vec2(x, y), vec4(A, B, C, 1.0)); +} +` +); + +export { computeBrdfLutWgsl as default }; diff --git a/dist/esm/core/shaders/compute/compute-diffuse-from-specular-cubemap.wgsl.mjs b/dist/esm/core/shaders/compute/compute-diffuse-from-specular-cubemap.wgsl.mjs new file mode 100644 index 000000000..00a97232e --- /dev/null +++ b/dist/esm/core/shaders/compute/compute-diffuse-from-specular-cubemap.wgsl.mjs @@ -0,0 +1,157 @@ +const computeDiffuseFromSpecularCubemap = (specularTexture) => ( + /* wgsl */ + ` +fn radicalInverse_VdC(inputBits: u32) -> f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); + } + else { + bitangent = vec3(0.0, 0.0, -1.0); + } + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// Mipmap Filtered Samples (GPU Gems 3, 20.4) +// https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling +// https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf +fn computeLod(pdf: f32) -> f32 { + // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf + return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); +} + +fn transformDirection(face: u32, uv: vec2f) -> vec3f { + // Transform the direction based on the cubemap face + switch (face) { + case 0u { + // +X + return vec3f( 1.0, uv.y, -uv.x); + } + case 1u { + // -X + return vec3f(-1.0, uv.y, uv.x); + } + case 2u { + // +Y + return vec3f( uv.x, -1.0, uv.y); + } + case 3u { + // -Y + return vec3f( uv.x, 1.0, -uv.y); + } + case 4u { + // +Z + return vec3f( uv.x, uv.y, 1.0); + } + case 5u { + // -Z + return vec3f(-uv.x, uv.y, -1.0); + } + default { + return vec3f(0.0, 0.0, 0.0); + } + } +} + +const PI = ${Math.PI}; + +@compute @workgroup_size(8, 8, 1) fn main( + @builtin(global_invocation_id) GlobalInvocationID: vec3u, +) { + let faceSize: u32 = params.faceSize; + let sampleCount: u32 = params.sampleCount; + + let face: u32 = GlobalInvocationID.z; + let x: u32 = GlobalInvocationID.x; + let y: u32 = GlobalInvocationID.y; + + if (x >= faceSize || y >= faceSize) { + return; + } + + let texelSize: f32 = 1.0 / f32(faceSize); + let halfTexel: f32 = texelSize * 0.5; + + var uv: vec2f = vec2( + (f32(x) + halfTexel) * texelSize, + (f32(y) + halfTexel) * texelSize + ); + + uv = uv * 2.0 - 1.0; + + let normal: vec3 = transformDirection(face, uv); + + var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); + + for (var i: u32 = 0; i < sampleCount; i++) { + // generate a quasi monte carlo point in the unit square [0.1)^2 + let xi: vec2f = hammersley2d(i, sampleCount); + + let cosTheta: f32 = sqrt(1.0 - xi.y); + let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); + let phi: f32 = 2.0 * PI * xi.x; + let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta + + let sampleVec: vec3f = vec3f( + sinTheta * cos(phi), + sinTheta * sin(phi), + cosTheta + ); + + let TBN: mat3x3f = generateTBN(normalize(normal)); + + var direction: vec3f = TBN * sampleVec; + + // invert along Y axis + direction.y *= -1.0; + + let lod: f32 = computeLod(pdf); + + let sampleLevel = min(lod, f32(params.maxMipLevel)); + + // Convert sampleVec to texture coordinates of the specular env map + irradiance += textureSampleLevel( + ${specularTexture.options.name}, + clampSampler, + direction, + sampleLevel + ).rgb; + } + + irradiance /= f32(sampleCount); + + textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); +} +` +); + +export { computeDiffuseFromSpecularCubemap }; diff --git a/dist/esm/core/shaders/compute/compute-specular-cubemap-from-hdr.wgsl.mjs b/dist/esm/core/shaders/compute/compute-specular-cubemap-from-hdr.wgsl.mjs new file mode 100644 index 000000000..21f0565dc --- /dev/null +++ b/dist/esm/core/shaders/compute/compute-specular-cubemap-from-hdr.wgsl.mjs @@ -0,0 +1,74 @@ +var computeSpecularCubemapFromHdr = ( + /* wgsl */ + ` +// Cube face lookup vectors +// positive and negative Y need to be inverted +const faceVectors = array, 2>, 6>( + array, 2>(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // +X + array, 2>(vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // -X + array, 2>(vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0)), // -Y + array, 2>(vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, -1.0)), // +Y + array, 2>(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0)), // +Z + array, 2>(vec3(0.0, 0.0, -1.0), vec3(0.0, 1.0, 0.0)) // -Z +); + +// Utility to calculate 3D direction for a given cube face pixel +fn texelDirection(faceIndex : u32, u : f32, v : f32) -> vec3 { + let forward = faceVectors[faceIndex][0]; + let up = faceVectors[faceIndex][1]; + let right = normalize(cross(up, forward)); + return normalize(forward + (2.0 * u - 1.0) * right + (2.0 * v - 1.0) * up); +} + +// Map 3D direction to equirectangular coordinates +fn dirToEquirect(dir : vec3) -> vec2 { + let phi = atan2(dir.z, dir.x); + let theta = asin(dir.y); + let u = 0.5 + 0.5 * phi / ${Math.PI}; + let v = 0.5 - theta / ${Math.PI}; + return vec2(u, v); +} + +@compute @workgroup_size(8, 8, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let faceSize = params.faceSize; + let cubeFaceIndex = global_id.z; + let x = global_id.x; + let y = global_id.y; + + if (x >= faceSize || y >= faceSize || cubeFaceIndex >= 6u) { + return; + } + + let u = f32(x) / f32(faceSize); + let v = f32(y) / f32(faceSize); + + // Get the 3D direction for this cube face texel + let dir = texelDirection(cubeFaceIndex, u, v); + + // Map to equirectangular coordinates + let uv = dirToEquirect(dir); + + let hdrWidth = params.imageSize.x; + let hdrHeight = params.imageSize.y; + + let texX = u32(clamp(uv.x * hdrWidth, 0.0, hdrWidth - 1.0)); + let texY = u32(clamp(uv.y * hdrHeight, 0.0, hdrHeight - 1.0)); + + let hdrTexelIndex = texY * u32(hdrWidth) + texX; + + // Sample the equirectangular texture + let sampledColor = params.hdrImageData[hdrTexelIndex]; + + // Correct cube face order in texture store (fix for reversed face indices) + textureStore( + specularStorageCubemap, + vec2(x, y), + cubeFaceIndex, + sampledColor + ); +} +` +); + +export { computeSpecularCubemapFromHdr as default }; diff --git a/dist/esm/core/shadows/DirectionalShadow.mjs b/dist/esm/core/shadows/DirectionalShadow.mjs index bb9b88180..e1704a351 100644 --- a/dist/esm/core/shadows/DirectionalShadow.mjs +++ b/dist/esm/core/shadows/DirectionalShadow.mjs @@ -51,7 +51,6 @@ class DirectionalShadow extends Shadow { ...this.options, camera }; - this.setRendererBinding(); this.camera = { projectionMatrix: new Mat4(), viewMatrix: new Mat4(), diff --git a/dist/esm/core/shadows/PointShadow.mjs b/dist/esm/core/shadows/PointShadow.mjs index e2c5b6630..9a0e56ccf 100644 --- a/dist/esm/core/shadows/PointShadow.mjs +++ b/dist/esm/core/shadows/PointShadow.mjs @@ -19,7 +19,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _tempCubeDirection; @@ -85,7 +85,6 @@ class PointShadow extends Shadow { ...this.options, camera }; - this.setRendererBinding(); this.cubeDirections = [ new Vec3(-1, 0, 0), new Vec3(1, 0, 0), @@ -160,10 +159,7 @@ class PointShadow extends Shadow { reset() { this.setRendererBinding(); super.reset(); - this.onPropertyChanged("cameraNear", this.camera.near); - this.onPropertyChanged("cameraFar", this.camera.far); - this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); - this.updateViewMatrices(); + this.updateProjectionMatrix(); } /** * Update the {@link PointShadow#camera.projectionMatrix | camera perspective projection matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. @@ -188,10 +184,10 @@ class PointShadow extends Shadow { __privateGet(this, _tempCubeDirection).copy(this.cubeDirections[i]).add(position); this.camera.viewMatrices[i].makeView(position, __privateGet(this, _tempCubeDirection), this.cubeUps[i]); for (let j = 0; j < 16; j++) { - this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.value[i * 16 + j] = this.camera.viewMatrices[i].elements[j]; + this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.value[i * 16 + j] = this.camera.viewMatrices[i].elements[j]; } } - this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.shouldUpdate = true; + this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.shouldUpdate = true; } /** * Set or resize the {@link depthTexture} and eventually resize the {@link depthPassTarget} as well. @@ -217,7 +213,7 @@ class PointShadow extends Shadow { createDepthTexture() { const maxSize = Math.max(this.depthTextureSize.x, this.depthTextureSize.y); this.depthTexture = new Texture(this.renderer, { - label: "Point shadow cube depth texture " + this.index, + label: `${this.constructor.name} (index: ${this.index}) depth texture`, name: "pointShadowCubeDepthTexture" + this.index, type: "depth", format: this.depthTextureFormat, @@ -255,9 +251,14 @@ class PointShadow extends Shadow { () => { if (!this.meshes.size) return; + this.renderer.setCameraBindGroup(); this.useDepthMaterials(); for (let i = 0; i < 6; i++) { const commandEncoder = this.renderer.device.createCommandEncoder(); + if (!this.renderer.production) + commandEncoder.pushDebugGroup( + `${this.constructor.name} (index: ${this.index}): depth pass command encoder for face ${i}` + ); this.depthPassTarget.renderPass.setRenderPassDescriptor( this.depthTexture.texture.createView({ label: this.depthTexture.texture.label + " cube face view " + i, @@ -266,9 +267,12 @@ class PointShadow extends Shadow { baseArrayLayer: i }) ); - this.rendererBinding.options.bindings[this.index].inputs.face.value = i; - this.renderer.cameraLightsBindGroup.update(); + this.rendererBinding.childrenBindings[this.index].inputs.face.value = i; + this.renderer.shouldUpdateCameraLightsBindGroup(); + this.renderer.updateCameraLightsBindGroup(); this.renderDepthPass(commandEncoder); + if (!this.renderer.production) + commandEncoder.popDebugGroup(); const commandBuffer = commandEncoder.finish(); this.renderer.device.queue.submit([commandBuffer]); } diff --git a/dist/esm/core/shadows/Shadow.mjs b/dist/esm/core/shadows/Shadow.mjs index 85eb8c545..5b119637f 100644 --- a/dist/esm/core/shadows/Shadow.mjs +++ b/dist/esm/core/shadows/Shadow.mjs @@ -22,7 +22,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { @@ -98,9 +98,7 @@ class Shadow { __privateAdd(this, _depthMaterials, void 0); /** @ignore */ __privateAdd(this, _depthPassTaskID, void 0); - renderer = isCameraRenderer(renderer, this.constructor.name); - this.renderer = renderer; - this.rendererBinding = null; + this.setRenderer(renderer); this.light = light; this.index = this.light.index; this.options = { @@ -120,6 +118,22 @@ class Shadow { __privateMethod(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); this.isActive = false; } + /** + * Set or reset this shadow {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + renderer = isCameraRenderer(renderer, this.constructor.name); + this.renderer = renderer; + this.setRendererBinding(); + __privateGet(this, _depthMaterials)?.forEach((depthMaterial) => { + depthMaterial.setRenderer(this.renderer); + }); + } + /** @ignore */ + setRendererBinding() { + this.rendererBinding = null; + } /** * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
* Called internally by the associated {@link core/lights/Light.Light | Light} if any shadow parameters are specified when creating it. Can also be called directly. @@ -129,20 +143,15 @@ class Shadow { __privateMethod(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); this.isActive = true; } - /** @ignore */ - setRendererBinding() { - } /** * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link core/lights/Light.Light | lights} has been overflowed. */ reset() { - if (this.isActive) { - this.onPropertyChanged("isActive", 1); - this.onPropertyChanged("intensity", this.intensity); - this.onPropertyChanged("bias", this.bias); - this.onPropertyChanged("normalBias", this.normalBias); - this.onPropertyChanged("pcfSamples", this.pcfSamples); - } + this.onPropertyChanged("isActive", this.isActive ? 1 : 0); + this.onPropertyChanged("intensity", this.intensity); + this.onPropertyChanged("bias", this.bias); + this.onPropertyChanged("normalBias", this.normalBias); + this.onPropertyChanged("pcfSamples", this.pcfSamples); } /** * Get whether this {@link Shadow} is actually casting shadows. @@ -153,7 +162,7 @@ class Shadow { } /** * Start or stop casting shadows. - * @param value + * @param value - New active state. */ set isActive(value) { if (!value && this.isActive) { @@ -278,7 +287,7 @@ class Shadow { */ createDepthTexture() { this.depthTexture = new Texture(this.renderer, { - label: "Shadow depth texture " + this.index, + label: `${this.constructor.name} (index: ${this.light.index}) depth texture`, name: "shadowDepthTexture" + this.index, type: "depth", format: this.depthTextureFormat, @@ -311,11 +320,11 @@ class Shadow { if (this.rendererBinding) { if (value instanceof Mat4) { for (let i = 0; i < value.elements.length; i++) { - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].value[i] = value.elements[i]; + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].value[i] = value.elements[i]; } - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].shouldUpdate = true; + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].shouldUpdate = true; } else { - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].value = value; + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].value = value; } this.renderer.shouldUpdateCameraLightsBindGroup(); } @@ -379,11 +388,25 @@ class Shadow { * @param commandEncoder - {@link GPUCommandEncoder} to use. */ renderDepthPass(commandEncoder) { + const renderBundles = /* @__PURE__ */ new Map(); + this.meshes.forEach((mesh) => { + if (mesh.options.renderBundle) { + renderBundles.set(mesh.options.renderBundle.uuid, mesh.options.renderBundle); + } + }); + renderBundles.forEach((bundle) => { + bundle.updateBinding(); + }); + renderBundles.clear(); this.renderer.pipelineManager.resetCurrentPipeline(); const depthPass = commandEncoder.beginRenderPass(this.depthPassTarget.renderPass.descriptor); + if (!this.renderer.production) + depthPass.pushDebugGroup(`${this.constructor.name} (index: ${this.index}): depth pass`); this.meshes.forEach((mesh) => { mesh.render(depthPass); }); + if (!this.renderer.production) + depthPass.popDebugGroup(); depthPass.end(); } /** @@ -438,6 +461,8 @@ class Shadow { * @param parameters - Optional {@link RenderMaterialParams | parameters} to use for the depth material. */ addShadowCastingMesh(mesh, parameters = {}) { + if (this.meshes.get(mesh.uuid)) + return; mesh.options.castShadows = true; __privateGet(this, _materials).set(mesh.uuid, mesh.material); parameters = this.patchShadowCastingMeshParams(mesh, parameters); @@ -448,7 +473,7 @@ class Shadow { __privateGet(this, _depthMaterials).set( mesh.uuid, new RenderMaterial(this.renderer, { - label: mesh.options.label + " depth render material", + label: `${this.constructor.name} (index: ${this.index}) ${mesh.options.label} depth render material`, ...parameters }) ); diff --git a/dist/esm/core/textures/Texture.mjs b/dist/esm/core/textures/Texture.mjs index 8255f2906..584b1de03 100644 --- a/dist/esm/core/textures/Texture.mjs +++ b/dist/esm/core/textures/Texture.mjs @@ -9,7 +9,7 @@ var __accessCheck = (obj, member, msg) => { }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + return member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) @@ -18,7 +18,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _autoResize; @@ -61,7 +61,7 @@ class Texture { this.options.viewDimension = parameters.fromTexture.options.viewDimension; } if (!this.options.format) { - this.options.format = this.renderer.options.preferredFormat; + this.options.format = this.renderer.options.context.format; } this.size = this.options.fixedSize ? { width: this.options.fixedSize.width * this.options.qualityRatio, diff --git a/dist/esm/core/textures/utils.mjs b/dist/esm/core/textures/utils.mjs index 80575e9b5..20d0614f6 100644 --- a/dist/esm/core/textures/utils.mjs +++ b/dist/esm/core/textures/utils.mjs @@ -1,9 +1,11 @@ +import { WebGPUTextureUsageConstants } from '../../utils/webgpu-constants.mjs'; + const textureUsages = /* @__PURE__ */ new Map([ - ["copySrc", GPUTextureUsage.COPY_SRC], - ["copyDst", GPUTextureUsage.COPY_DST], - ["renderAttachment", GPUTextureUsage.RENDER_ATTACHMENT], - ["storageBinding", GPUTextureUsage.STORAGE_BINDING], - ["textureBinding", GPUTextureUsage.TEXTURE_BINDING] + ["copySrc", WebGPUTextureUsageConstants.COPY_SRC], + ["copyDst", WebGPUTextureUsageConstants.COPY_DST], + ["renderAttachment", WebGPUTextureUsageConstants.RENDER_ATTACHMENT], + ["storageBinding", WebGPUTextureUsageConstants.STORAGE_BINDING], + ["textureBinding", WebGPUTextureUsageConstants.TEXTURE_BINDING] ]); const getTextureUsages = (usages = []) => { return usages.reduce((acc, v) => { diff --git a/dist/esm/curtains/GPUCurtains.mjs b/dist/esm/curtains/GPUCurtains.mjs index 5c93057cc..25a52911c 100644 --- a/dist/esm/curtains/GPUCurtains.mjs +++ b/dist/esm/curtains/GPUCurtains.mjs @@ -15,8 +15,7 @@ class GPUCurtains { container, label, pixelRatio = window.devicePixelRatio ?? 1, - preferredFormat, - alphaMode = "premultiplied", + context = {}, production = false, adapterOptions = {}, renderPass, @@ -48,8 +47,7 @@ class GPUCurtains { lights, production, adapterOptions, - preferredFormat, - alphaMode, + context, renderPass, autoRender, autoResize, @@ -99,12 +97,11 @@ class GPUCurtains { this.createCurtainsRenderer({ deviceManager: this.deviceManager, // TODO ...this.options? - label: this.options.label, + label: this.options.label || "GPUCurtains main GPUCurtainsRenderer", container: this.options.container, pixelRatio: this.options.pixelRatio, autoResize: this.options.autoResize, - preferredFormat: this.options.preferredFormat, - alphaMode: this.options.alphaMode, + context: this.options.context, renderPass: this.options.renderPass, camera: this.options.camera, lights: this.options.lights diff --git a/dist/esm/curtains/meshes/Plane.mjs b/dist/esm/curtains/meshes/Plane.mjs index f1486439f..f6bea50b8 100644 --- a/dist/esm/curtains/meshes/Plane.mjs +++ b/dist/esm/curtains/meshes/Plane.mjs @@ -1,8 +1,6 @@ import { isCurtainsRenderer } from '../../core/renderers/utils.mjs'; import { PlaneGeometry } from '../../core/geometries/PlaneGeometry.mjs'; import { DOMMesh } from './DOMMesh.mjs'; -import { Vec3 } from '../../math/Vec3.mjs'; -import { Vec2 } from '../../math/Vec2.mjs'; import { cacheManager } from '../../utils/CacheManager.mjs'; const defaultPlaneParams = { @@ -40,43 +38,6 @@ class Plane extends DOMMesh { super(renderer, element, { geometry, ...materialParams }); this.type = "Plane"; } - /** - * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link Plane} - * It ranges from -1 to 1 on both axis - * @param mouseCoords - pointer {@link Vec2 | vector} coordinates - * @returns - raycasted {@link Vec2 | vector} coordinates relative to the {@link Plane} - */ - mouseToPlaneCoords(mouseCoords = new Vec2()) { - const worldMouse = { - x: 2 * (mouseCoords.x / this.renderer.boundingRect.width) - 1, - y: 2 * (1 - mouseCoords.y / this.renderer.boundingRect.height) - 1 - }; - const rayOrigin = this.camera.position.clone(); - const rayDirection = new Vec3(worldMouse.x, worldMouse.y, -0.5); - rayDirection.unproject(this.camera); - rayDirection.sub(rayOrigin).normalize(); - const planeNormals = new Vec3(0, 0, 1); - planeNormals.applyQuat(this.quaternion).normalize(); - const result = new Vec3(0, 0, 0); - const denominator = planeNormals.dot(rayDirection); - if (Math.abs(denominator) >= 1e-4) { - const inverseViewMatrix = this.worldMatrix.getInverse().premultiply(this.camera.viewMatrix); - const planeOrigin = this.worldTransformOrigin.clone().add(this.worldPosition); - const rotatedOrigin = new Vec3( - this.worldPosition.x - planeOrigin.x, - this.worldPosition.y - planeOrigin.y, - this.worldPosition.z - planeOrigin.z - ); - rotatedOrigin.applyQuat(this.quaternion); - planeOrigin.add(rotatedOrigin); - const distance = planeNormals.dot(planeOrigin.clone().sub(rayOrigin)) / denominator; - result.copy(rayOrigin.add(rayDirection.multiplyScalar(distance))); - result.applyMat4(inverseViewMatrix); - } else { - result.set(Infinity, Infinity, Infinity); - } - return new Vec2(result.x, result.y); - } } export { Plane }; diff --git a/dist/esm/curtains/objects3D/DOMObject3D.mjs b/dist/esm/curtains/objects3D/DOMObject3D.mjs index 24b455411..60fcaefb4 100644 --- a/dist/esm/curtains/objects3D/DOMObject3D.mjs +++ b/dist/esm/curtains/objects3D/DOMObject3D.mjs @@ -20,7 +20,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _DOMObjectWorldPosition, _DOMObjectWorldScale, _DOMObjectDepthScaleRatio; diff --git a/dist/esm/curtains/renderers/GPUCurtainsRenderer.mjs b/dist/esm/curtains/renderers/GPUCurtainsRenderer.mjs index 003c30d63..0e39a1cd8 100644 --- a/dist/esm/curtains/renderers/GPUCurtainsRenderer.mjs +++ b/dist/esm/curtains/renderers/GPUCurtainsRenderer.mjs @@ -11,8 +11,7 @@ class GPUCurtainsRenderer extends GPUCameraRenderer { container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass, camera, lights @@ -23,8 +22,7 @@ class GPUCurtainsRenderer extends GPUCameraRenderer { container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context, renderPass, camera, lights diff --git a/dist/esm/extras/controls/OrbitControls.mjs b/dist/esm/extras/controls/OrbitControls.mjs index 51f75f0ee..b8844771f 100644 --- a/dist/esm/extras/controls/OrbitControls.mjs +++ b/dist/esm/extras/controls/OrbitControls.mjs @@ -17,7 +17,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { @@ -163,7 +163,6 @@ class OrbitControls { throwWarning("OrbitControls: cannot initialize without a camera."); return; } - this.camera = camera; __privateMethod(this, _setBaseParams, setBaseParams_fn).call(this, { target, enableZoom, @@ -179,14 +178,22 @@ class OrbitControls { enablePan, panSpeed }); + this.element = element ?? (typeof window !== "undefined" ? window : null); + this.useCamera(camera); + } + /** + * Allow to set or reset this {@link OrbitControls#camera | OrbitControls camera}. + * @param camera - New {@link camera} to use. + */ + useCamera(camera) { + this.camera = camera; + this.camera.position.onChange(() => { + this.camera.lookAt(this.target); + }); __privateGet(this, _offset).copy(this.camera.position).sub(this.target); __privateGet(this, _spherical).radius = __privateGet(this, _offset).length(); __privateGet(this, _spherical).theta = Math.atan2(__privateGet(this, _offset).x, __privateGet(this, _offset).z); __privateGet(this, _spherical).phi = Math.acos(Math.min(Math.max(__privateGet(this, _offset).y / __privateGet(this, _spherical).radius, -1), 1)); - this.camera.position.onChange(() => { - this.camera.lookAt(this.target); - }); - this.element = element ?? (typeof window !== "undefined" ? window : null); __privateMethod(this, _update, update_fn).call(this); } /** diff --git a/dist/esm/extras/environment-map/EnvironmentMap.mjs b/dist/esm/extras/environment-map/EnvironmentMap.mjs new file mode 100644 index 000000000..8095f5926 --- /dev/null +++ b/dist/esm/extras/environment-map/EnvironmentMap.mjs @@ -0,0 +1,375 @@ +import { isRenderer, generateMips } from '../../core/renderers/utils.mjs'; +import { HDRLoader } from '../loaders/HDRLoader.mjs'; +import { Texture } from '../../core/textures/Texture.mjs'; +import { ComputePass } from '../../core/computePasses/ComputePass.mjs'; +import { Vec2 } from '../../math/Vec2.mjs'; +import { throwWarning } from '../../utils/utils.mjs'; +import { Sampler } from '../../core/samplers/Sampler.mjs'; +import { Mat3 } from '../../math/Mat3.mjs'; +import computeBrdfLutWgsl from '../../core/shaders/compute/compute-brdf-lut.wgsl.mjs'; +import computeSpecularCubemapFromHdr from '../../core/shaders/compute/compute-specular-cubemap-from-hdr.wgsl.mjs'; +import { computeDiffuseFromSpecularCubemap } from '../../core/shaders/compute/compute-diffuse-from-specular-cubemap.wgsl.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateMethod = (obj, member, method) => { + __accessCheck(obj, member, "access private method"); + return method; +}; +var _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn; +class EnvironmentMap { + /** + * {@link EnvironmentMap} constructor. + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link EnvironmentMap}. + * @param params - {@link EnvironmentMapParams | parameters} use to create this {@link EnvironmentMap}. Defines the various textures options. + */ + constructor(renderer, params = { + lutTextureParams: { + size: 256, + computeSampleCount: 1024, + label: "Environment LUT texture", + name: "lutTexture", + format: "rgba32float" + }, + diffuseTextureParams: { + size: 128, + computeSampleCount: 2048, + label: "Environment diffuse texture", + name: "diffuseTexture", + format: "rgba16float" + }, + specularTextureParams: { + label: "Environment specular texture", + name: "specularTexture", + format: "rgba16float", + generateMips: true + } + }) { + /** + * Once the given {@link ComputePass} has written to a temporary storage {@link Texture}, copy it into our permanent {@link Texture}. + * @param commandEncoder - The GPU command encoder to use. + * @param storageTexture - Temporary storage {@link Texture} used in the {@link ComputePass}. + * @param texture - Permanent {@link Texture} (either the {@link lutTexture}, {@link specularTexture} or {@link diffuseTexture}) to copy onto. + * @private + */ + __privateAdd(this, _copyComputeStorageTextureToTexture); + renderer = isRenderer(renderer, "EnvironmentMap"); + this.renderer = renderer; + this.options = params; + this.sampler = new Sampler(this.renderer, { + label: "Clamp sampler", + name: "clampSampler", + magFilter: "linear", + minFilter: "linear", + mipmapFilter: "linear", + addressModeU: "clamp-to-edge", + addressModeV: "clamp-to-edge" + }); + this.rotation = new Mat3(new Float32Array([0, 0, 1, 0, 1, 0, -1, 0, 0])); + this.hdrLoader = new HDRLoader(); + this.computeBRDFLUTTexture(); + } + /** + * Create the {@link lutTexture | BRDF GGX LUT texture} using the provided {@link LUTTextureParams | LUT texture options} and a {@link ComputePass} that runs once. + * @async + */ + async computeBRDFLUTTexture() { + const { size, computeSampleCount, ...lutTextureParams } = this.options.lutTextureParams; + this.lutTexture = new Texture(this.renderer, { + ...lutTextureParams, + visibility: ["fragment"], + fixedSize: { + width: size, + height: size + }, + autoDestroy: false + }); + let lutStorageTexture = new Texture(this.renderer, { + label: "LUT storage texture", + name: "lutStorageTexture", + format: this.lutTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.lutTexture.size.width, + height: this.lutTexture.size.height + } + }); + let computeLUTPass = new ComputePass(this.renderer, { + label: "Compute LUT texture", + autoRender: false, + // we're going to render only on demand + dispatchSize: [Math.ceil(lutStorageTexture.size.width / 16), Math.ceil(lutStorageTexture.size.height / 16), 1], + shaders: { + compute: { + code: computeBrdfLutWgsl + } + }, + uniforms: { + params: { + struct: { + sampleCount: { + type: "u32", + value: computeSampleCount + } + } + } + }, + textures: [lutStorageTexture] + }); + await computeLUTPass.material.compileMaterial(); + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.renderSingleComputePass(commandEncoder, computeLUTPass); + __privateMethod(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, lutStorageTexture, this.lutTexture); + }, + { once: true } + ); + this.renderer.onAfterCommandEncoderSubmission.add( + () => { + computeLUTPass.destroy(); + lutStorageTexture.destroy(); + lutStorageTexture = null; + computeLUTPass = null; + }, + { once: true } + ); + } + /** + * Create the {@link specularTexture | specular cube map texture} from a loaded {@link HDRImageData} using the provided {@link SpecularTextureParams | specular texture options} and a {@link ComputePass} that runs once. + * @params parsedHdr - parsed {@link HDRImageData} loaded by the {@link hdrLoader}. + * @async + */ + async computeSpecularCubemapFromHDRData(parsedHdr) { + let cubeStorageTexture = new Texture(this.renderer, { + label: "Specular storage cubemap", + name: "specularStorageCubemap", + format: this.specularTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.specularTexture.size.width, + height: this.specularTexture.size.height, + depth: 6 + }, + viewDimension: "2d-array" + }); + let computeCubeMapPass = new ComputePass(this.renderer, { + label: "Compute specular cubemap from equirectangular", + autoRender: false, + // we're going to render only on demand + dispatchSize: [ + Math.ceil(this.specularTexture.size.width / 8), + Math.ceil(this.specularTexture.size.height / 8), + 6 + ], + shaders: { + compute: { + code: computeSpecularCubemapFromHdr + } + }, + storages: { + params: { + struct: { + hdrImageData: { + type: "array", + value: parsedHdr.data + }, + imageSize: { + type: "vec2f", + value: new Vec2(parsedHdr.width, parsedHdr.height) + }, + faceSize: { + type: "u32", + value: this.specularTexture.size.width + } + } + } + }, + textures: [cubeStorageTexture] + }); + await computeCubeMapPass.material.compileMaterial(); + const commandEncoder = this.renderer.device?.createCommandEncoder({ + label: "Render once command encoder" + }); + if (!this.renderer.production) + commandEncoder.pushDebugGroup("Render once command encoder"); + this.renderer.renderSingleComputePass(commandEncoder, computeCubeMapPass); + __privateMethod(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, cubeStorageTexture, this.specularTexture); + if (!this.renderer.production) + commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.renderer.device?.queue.submit([commandBuffer]); + if (this.specularTexture.texture.mipLevelCount > 1) { + generateMips(this.renderer.device, this.specularTexture.texture); + } + computeCubeMapPass.destroy(); + cubeStorageTexture.destroy(); + cubeStorageTexture = null; + computeCubeMapPass = null; + } + /** + * Compute the {@link diffuseTexture | diffuse cube map texture} from the {@link specularTexture | specular cube map texture } using the provided {@link DiffuseTextureParams | diffuse texture options} and a {@link ComputePass} that runs once. + */ + async computeDiffuseFromSpecular() { + if (this.specularTexture.options.viewDimension !== "cube") { + throwWarning( + "Could not compute the diffuse texture because the specular texture is not a cube map:" + this.specularTexture.options.viewDimension + ); + return; + } + let diffuseStorageTexture = new Texture(this.renderer, { + label: "Diffuse storage cubemap", + name: "diffuseEnvMap", + format: this.diffuseTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.diffuseTexture.size.width, + height: this.diffuseTexture.size.height, + depth: 6 + }, + viewDimension: "2d-array" + }); + let computeDiffusePass = new ComputePass(this.renderer, { + label: "Compute diffuse map from specular map", + autoRender: false, + // we're going to render only on demand + dispatchSize: [Math.ceil(this.diffuseTexture.size.width / 8), Math.ceil(this.diffuseTexture.size.height / 8), 6], + shaders: { + compute: { + code: computeDiffuseFromSpecularCubemap(this.specularTexture) + } + }, + uniforms: { + params: { + struct: { + faceSize: { + type: "u32", + value: this.diffuseTexture.size.width + }, + maxMipLevel: { + type: "u32", + value: this.specularTexture.texture.mipLevelCount + }, + sampleCount: { + type: "u32", + value: this.options.diffuseTextureParams.computeSampleCount + } + } + } + }, + samplers: [this.sampler], + textures: [this.specularTexture, diffuseStorageTexture] + }); + await computeDiffusePass.material.compileMaterial(); + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.renderSingleComputePass(commandEncoder, computeDiffusePass); + __privateMethod(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, diffuseStorageTexture, this.diffuseTexture); + }, + { once: true } + ); + this.renderer.onAfterCommandEncoderSubmission.add( + () => { + computeDiffusePass.destroy(); + diffuseStorageTexture.destroy(); + diffuseStorageTexture = null; + computeDiffusePass = null; + }, + { once: true } + ); + } + /** + * Load an HDR environment map and then generates the {@link specularTexture} and {@link diffuseTexture} using two separate {@link ComputePass}. + * @param url - The url of the .hdr file to load. + * @async + */ + async loadAndComputeFromHDR(url) { + const parsedHdr = await this.hdrLoader.loadFromUrl(url); + const { width, height } = parsedHdr ? parsedHdr : { width: 1024, height: 512 }; + const faceSize = Math.max(width / 4, height / 2); + const textureDefaultOptions = { + viewDimension: "cube", + autoDestroy: false + // keep alive when changing glTF + }; + if (!this.specularTexture) { + this.specularTexture = new Texture(this.renderer, { + ...this.options.specularTextureParams, + ...{ + visibility: ["fragment", "compute"], + fixedSize: { + width: faceSize, + height: faceSize + } + }, + ...textureDefaultOptions + }); + } else if (this.specularTexture.size.width !== faceSize || this.specularTexture.size.height !== faceSize) { + this.specularTexture.options.fixedSize.width = faceSize; + this.specularTexture.options.fixedSize.height = faceSize; + this.specularTexture.size.width = faceSize; + this.specularTexture.size.height = faceSize; + this.specularTexture.createTexture(); + } + const { size, computeSampleCount, ...diffuseTextureParams } = this.options.diffuseTextureParams; + const diffuseSize = Math.min(size, faceSize); + if (!this.diffuseTexture) { + this.diffuseTexture = new Texture(this.renderer, { + ...diffuseTextureParams, + ...{ + visibility: ["fragment"], + fixedSize: { + width: diffuseSize, + height: diffuseSize + } + }, + ...textureDefaultOptions + }); + } else if (this.diffuseTexture.size.width !== diffuseSize || this.diffuseTexture.size.height !== diffuseSize) { + this.diffuseTexture.options.fixedSize.width = diffuseSize; + this.diffuseTexture.options.fixedSize.height = diffuseSize; + this.diffuseTexture.size.width = diffuseSize; + this.diffuseTexture.size.height = diffuseSize; + this.diffuseTexture.createTexture(); + } + if (parsedHdr) { + this.computeSpecularCubemapFromHDRData(parsedHdr).then(() => { + this.computeDiffuseFromSpecular(); + }); + } + } + /** + * Destroy the {@link EnvironmentMap} and its associated textures. + */ + destroy() { + this.lutTexture?.destroy(); + this.diffuseTexture?.destroy(); + this.specularTexture?.destroy(); + } +} +_copyComputeStorageTextureToTexture = new WeakSet(); +copyComputeStorageTextureToTexture_fn = function(commandEncoder, storageTexture, texture) { + commandEncoder.copyTextureToTexture( + { + texture: storageTexture.texture + }, + { + texture: texture.texture + }, + [texture.texture.width, texture.texture.height, texture.texture.depthOrArrayLayers] + ); +}; + +export { EnvironmentMap }; diff --git a/dist/esm/extras/gltf/GLTFScenesManager.mjs b/dist/esm/extras/gltf/GLTFScenesManager.mjs index 69758d363..347b72d0a 100644 --- a/dist/esm/extras/gltf/GLTFScenesManager.mjs +++ b/dist/esm/extras/gltf/GLTFScenesManager.mjs @@ -24,7 +24,7 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var _primitiveInstances; diff --git a/dist/esm/extras/gltf/utils.mjs b/dist/esm/extras/gltf/utils.mjs index e26ec5c19..e12d3e2fc 100644 --- a/dist/esm/extras/gltf/utils.mjs +++ b/dist/esm/extras/gltf/utils.mjs @@ -1,6 +1,3 @@ -import { Texture } from '../../core/textures/Texture.mjs'; -import { Sampler } from '../../core/samplers/Sampler.mjs'; -import { ComputePass } from '../../core/computePasses/ComputePass.mjs'; import { throwWarning } from '../../utils/utils.mjs'; import { getLambert } from '../../core/shaders/chunks/shading/lambert-shading.mjs'; import { getPhong } from '../../core/shaders/chunks/shading/phong-shading.mjs'; @@ -198,14 +195,17 @@ const buildShaders = (meshDescriptor, shaderParameters = {}) => { } let { chunks } = shaderParameters || {}; const { iblParameters } = shaderParameters || {}; - const { lutTexture, envDiffuseTexture, envSpecularTexture } = iblParameters || {}; - const useIBLContribution = envDiffuseTexture && envDiffuseTexture.texture && envSpecularTexture && envSpecularTexture.texture && lutTexture && lutTexture.texture; - if (useIBLContribution && shadingModel === "IBL") { + const { environmentMap } = iblParameters || {}; + if (environmentMap && shadingModel === "IBL") { meshDescriptor.parameters.uniforms = { ...meshDescriptor.parameters.uniforms, ...{ ibl: { struct: { + envRotation: { + type: "mat3x3f", + value: environmentMap.rotation + }, diffuseStrength: { type: "f32", value: iblParameters?.diffuseStrength ?? 0.5 @@ -220,17 +220,13 @@ const buildShaders = (meshDescriptor, shaderParameters = {}) => { }; meshDescriptor.parameters.textures = [ ...meshDescriptor.parameters.textures, - lutTexture.texture, - envDiffuseTexture.texture, - envSpecularTexture.texture + environmentMap.lutTexture, + environmentMap.diffuseTexture, + environmentMap.specularTexture ]; - lutTexture.samplerName = lutTexture.samplerName || "defaultSampler"; - envDiffuseTexture.samplerName = envDiffuseTexture.samplerName || "defaultSampler"; - envSpecularTexture.samplerName = envSpecularTexture.samplerName || "defaultSampler"; + meshDescriptor.parameters.samplers = [...meshDescriptor.parameters.samplers, environmentMap.sampler]; } else if (shadingModel === "IBL") { - throwWarning( - "IBL shading requested but one of the LUT, environment specular or diffuse texture is missing. Defaulting to PBR shading." - ); + throwWarning("IBL shading requested but the environment map missing. Defaulting to PBR shading."); shadingModel = "PBR"; } const shadingOptions = { @@ -342,12 +338,10 @@ const buildShaders = (meshDescriptor, shaderParameters = {}) => { f0, metallic, roughness, - ${lutTexture.texture.options.name}, - ${lutTexture.samplerName}, - ${envSpecularTexture.texture.options.name}, - ${envSpecularTexture.samplerName}, - ${envDiffuseTexture.texture.options.name}, - ${envDiffuseTexture.samplerName}, + ${environmentMap.sampler.name}, + ${environmentMap.lutTexture.options.name}, + ${environmentMap.specularTexture.options.name}, + ${environmentMap.diffuseTexture.options.name}, occlusion ), color.a @@ -405,240 +399,5 @@ const buildShaders = (meshDescriptor, shaderParameters = {}) => { } }; }; -const computeDiffuseFromSpecular = async (renderer, diffuseTexture, specularTexture) => { - if (specularTexture.options.viewDimension !== "cube") { - throwWarning( - "Could not compute the diffuse texture because the specular texture is not a cube map:" + specularTexture.options.viewDimension - ); - return; - } - const computeDiffuseShader = ` - fn radicalInverse_VdC(inputBits: u32) -> f32 { - var bits: u32 = inputBits; - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 - } - - // hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 - // that can be used for quasi Monte Carlo integration - fn hammersley2d(i: u32, N: u32) -> vec2f { - return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); - } - - // TBN generates a tangent bitangent normal coordinate frame from the normal - // (the normal must be normalized) - fn generateTBN(normal: vec3f) -> mat3x3f { - var bitangent: vec3f = vec3(0.0, 1.0, 0.0); - - let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); - let epsilon: f32 = 0.0000001; - - if (1.0 - abs(NdotUp) <= epsilon) { - // Sampling +Y or -Y, so we need a more robust bitangent. - if (NdotUp > 0.0) { - bitangent = vec3(0.0, 0.0, 1.0); - } - else { - bitangent = vec3(0.0, 0.0, -1.0); - } - } - - let tangent: vec3f = normalize(cross(bitangent, normal)); - bitangent = cross(normal, tangent); - - return mat3x3f(tangent, bitangent, normal); - } - - // Mipmap Filtered Samples (GPU Gems 3, 20.4) - // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - fn computeLod(pdf: f32) -> f32 { - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); - } - - fn transformDirection(face: u32, uv: vec2f) -> vec3f { - // Transform the direction based on the cubemap face - switch (face) { - case 0u { - // +X - return vec3f( 1.0, uv.y, -uv.x); - } - case 1u { - // -X - return vec3f(-1.0, uv.y, uv.x); - } - case 2u { - // +Y - return vec3f( uv.x, -1.0, uv.y); - } - case 3u { - // -Y - return vec3f( uv.x, 1.0, -uv.y); - } - case 4u { - // +Z - return vec3f( uv.x, uv.y, 1.0); - } - case 5u { - // -Z - return vec3f(-uv.x, uv.y, -1.0); - } - default { - return vec3f(0.0, 0.0, 0.0); - } - } - } - - const PI = ${Math.PI}; - - @compute @workgroup_size(8, 8, 1) fn main( - @builtin(global_invocation_id) GlobalInvocationID: vec3u, - ) { - let faceSize: u32 = params.faceSize; - let sampleCount: u32 = params.sampleCount; - - let face: u32 = GlobalInvocationID.z; - let x: u32 = GlobalInvocationID.x; - let y: u32 = GlobalInvocationID.y; - - if (x >= faceSize || y >= faceSize) { - return; - } - - let texelSize: f32 = 1.0 / f32(faceSize); - let halfTexel: f32 = texelSize * 0.5; - - var uv: vec2f = vec2( - (f32(x) + halfTexel) * texelSize, - (f32(y) + halfTexel) * texelSize - ); - - uv = uv * 2.0 - 1.0; - - let normal: vec3 = transformDirection(face, uv); - - var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); - - for (var i: u32 = 0; i < sampleCount; i++) { - // generate a quasi monte carlo point in the unit square [0.1)^2 - let xi: vec2f = hammersley2d(i, sampleCount); - - let cosTheta: f32 = sqrt(1.0 - xi.y); - let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); - let phi: f32 = 2.0 * PI * xi.x; - let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta - - let sampleVec: vec3f = vec3f( - sinTheta * cos(phi), - sinTheta * sin(phi), - cosTheta - ); - - let TBN: mat3x3f = generateTBN(normalize(normal)); - - var direction: vec3f = TBN * sampleVec; - - // invert along Y axis - direction.y *= -1.0; - - let lod: f32 = computeLod(pdf); - - // Convert sampleVec to texture coordinates of the specular env map - irradiance += textureSampleLevel( - envSpecularTexture, - specularSampler, - direction, - min(lod, f32(params.maxMipLevel)) - ).rgb; - } - - irradiance /= f32(sampleCount); - - textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); - } - `; - let diffuseStorageTexture = new Texture(renderer, { - label: "Diffuse storage cubemap", - name: "diffuseEnvMap", - format: "rgba32float", - visibility: ["compute"], - usage: ["copySrc", "storageBinding"], - type: "storage", - fixedSize: { - width: specularTexture.size.width, - height: specularTexture.size.height, - depth: 6 - }, - viewDimension: "2d-array" - }); - const sampler = new Sampler(renderer, { - label: "Compute diffuse sampler", - name: "specularSampler", - addressModeU: "clamp-to-edge", - addressModeV: "clamp-to-edge", - minFilter: "linear", - magFilter: "linear" - }); - let computeDiffusePass = new ComputePass(renderer, { - autoRender: false, - // we're going to render only on demand - dispatchSize: [Math.ceil(specularTexture.size.width / 8), Math.ceil(specularTexture.size.height / 8), 6], - shaders: { - compute: { - code: computeDiffuseShader - } - }, - uniforms: { - params: { - struct: { - faceSize: { - type: "u32", - value: specularTexture.size.width - }, - maxMipLevel: { - type: "u32", - value: specularTexture.texture.mipLevelCount - }, - sampleCount: { - type: "u32", - value: 2048 - } - } - } - }, - samplers: [sampler], - textures: [specularTexture, diffuseStorageTexture] - }); - await computeDiffusePass.material.compileMaterial(); - renderer.onBeforeRenderScene.add( - (commandEncoder) => { - renderer.renderSingleComputePass(commandEncoder, computeDiffusePass); - commandEncoder.copyTextureToTexture( - { - texture: diffuseStorageTexture.texture - }, - { - texture: diffuseTexture.texture - }, - [diffuseTexture.texture.width, diffuseTexture.texture.height, diffuseTexture.texture.depthOrArrayLayers] - ); - }, - { once: true } - ); - renderer.onAfterCommandEncoderSubmission.add( - () => { - computeDiffusePass.destroy(); - diffuseStorageTexture.destroy(); - diffuseStorageTexture = null; - computeDiffusePass = null; - }, - { once: true } - ); -}; -export { buildShaders, computeDiffuseFromSpecular }; +export { buildShaders }; diff --git a/dist/esm/extras/loaders/HDRLoader.mjs b/dist/esm/extras/loaders/HDRLoader.mjs index 175676df1..41feefd03 100644 --- a/dist/esm/extras/loaders/HDRLoader.mjs +++ b/dist/esm/extras/loaders/HDRLoader.mjs @@ -53,89 +53,13 @@ class HDRLoader { } /** * Load and decode RGBE-encoded data to a flat list of floating point pixel data (RGBA). - * @param url - The url of the .hdr file to load - * @returns - The {@link HDRImageData} + * @param url - The url of the .hdr file to load. + * @returns - The {@link HDRImageData}. */ async loadFromUrl(url) { const buffer = await (await fetch(url)).arrayBuffer(); return __privateMethod(this, _decodeRGBE, decodeRGBE_fn).call(this, new DataView(buffer)); } - /** - * Convert an equirectangular {@link HDRImageData} to 6 {@link HDRImageData} cube map faces. Works but can display artifacts at the poles. - * @param parsedHdr - equirectangular {@link HDRImageData} to use. - * @returns - 6 {@link HDRImageData} cube map faces - */ - equirectangularToCubeMap(parsedHdr) { - const faceSize = Math.max(parsedHdr.width / 4, parsedHdr.height / 2); - const faces = { - posX: new Float32Array(faceSize * faceSize * 4), - negX: new Float32Array(faceSize * faceSize * 4), - posY: new Float32Array(faceSize * faceSize * 4), - negY: new Float32Array(faceSize * faceSize * 4), - posZ: new Float32Array(faceSize * faceSize * 4), - negZ: new Float32Array(faceSize * faceSize * 4) - }; - function getPixel(u, v) { - const x = Math.floor(u * parsedHdr.width); - const y = Math.floor(v * parsedHdr.height); - const index = (y * parsedHdr.width + x) * 4; - return [parsedHdr.data[index], parsedHdr.data[index + 1], parsedHdr.data[index + 2], parsedHdr.data[index + 3]]; - } - function setPixel(face, x, y, pixel) { - const index = (y * faceSize + x) * 4; - faces[face][index] = pixel[0]; - faces[face][index + 1] = pixel[1]; - faces[face][index + 2] = pixel[2]; - faces[face][index + 3] = pixel[3]; - } - function mapDirection(face, x, y) { - const a = 2 * (x + 0.5) / faceSize - 1; - const b = 2 * (y + 0.5) / faceSize - 1; - switch (face) { - case "posX": - return [a, -1, -b]; - case "negX": - return [-a, 1, -b]; - case "posY": - return [-b, -a, 1]; - case "negY": - return [b, -a, -1]; - case "posZ": - return [-1, -a, -b]; - case "negZ": - return [1, a, -b]; - } - } - function directionToUV(direction) { - const [x, y, z] = direction; - const r = Math.sqrt(x * x + y * y); - const theta = Math.atan2(y, x); - const phi = Math.atan2(z, r); - const u = (theta + Math.PI) / (2 * Math.PI); - const v = (phi + Math.PI / 2) / Math.PI; - return [u, v]; - } - for (const face in faces) { - for (let y = 0; y < faceSize; y++) { - for (let x = 0; x < faceSize; x++) { - const direction = mapDirection(face, x, y); - const [u, v] = directionToUV(direction); - const pixel = getPixel(u, v); - setPixel(face, x, y, pixel); - } - } - } - const facesData = [faces.posX, faces.negX, faces.posY, faces.negY, faces.posZ, faces.negZ]; - return facesData.map((faceData) => { - return { - data: faceData, - width: faceSize, - height: faceSize, - exposure: parsedHdr.exposure, - gamma: parsedHdr.gamma - }; - }); - } } _decodeRGBE = new WeakSet(); decodeRGBE_fn = function(data) { diff --git a/dist/esm/extras/raycaster/Raycaster.mjs b/dist/esm/extras/raycaster/Raycaster.mjs new file mode 100644 index 000000000..feeedab25 --- /dev/null +++ b/dist/esm/extras/raycaster/Raycaster.mjs @@ -0,0 +1,336 @@ +import { Vec2 } from '../../math/Vec2.mjs'; +import { Vec3 } from '../../math/Vec3.mjs'; +import { isCameraRenderer, isProjectedMesh } from '../../core/renderers/utils.mjs'; +import { throwWarning } from '../../utils/utils.mjs'; +import { Object3D } from '../../core/objects3D/Object3D.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + member.set(obj, value); + return value; +}; +var __privateMethod = (obj, member, method) => { + __accessCheck(obj, member, "access private method"); + return method; +}; +var _localRay, _v0, _v1, _v2, _edge1, _edge2, _uv0, _uv1, _uv2, _n0, _n1, _n2, _intersectMesh, intersectMesh_fn; +class Raycaster { + /** + * Raycaster constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Raycaster} + */ + constructor(renderer) { + /** + * Test whether the {@link ray} is intersecting a given {@link ProjectedMesh | projected mesh} and if so, returns the given {@link Intersection | intersection} information. + * Uses various early exits to optimize the process: + * - if the mesh is frustum culled + * - if the pointer is currently outside the mesh clip space bounding rectangle. + * - based on the face culling. + * @param mesh - {@link ProjectedMesh | Projected mesh} to test against. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + * @private + */ + __privateAdd(this, _intersectMesh); + /** @ignore */ + __privateAdd(this, _localRay, void 0); + /** @ignore */ + __privateAdd(this, _v0, void 0); + /** @ignore */ + __privateAdd(this, _v1, void 0); + /** @ignore */ + __privateAdd(this, _v2, void 0); + /** @ignore */ + __privateAdd(this, _edge1, void 0); + /** @ignore */ + __privateAdd(this, _edge2, void 0); + /** @ignore */ + __privateAdd(this, _uv0, void 0); + /** @ignore */ + __privateAdd(this, _uv1, void 0); + /** @ignore */ + __privateAdd(this, _uv2, void 0); + /** @ignore */ + __privateAdd(this, _n0, void 0); + /** @ignore */ + __privateAdd(this, _n1, void 0); + /** @ignore */ + __privateAdd(this, _n2, void 0); + this.type = "Raycaster"; + renderer = isCameraRenderer(renderer, this.type); + this.renderer = renderer; + this.camera = this.renderer.camera; + this.pointer = new Vec2(Infinity); + this.ray = { + origin: new Vec3(), + direction: new Vec3() + }; + __privateSet(this, _localRay, { + origin: this.ray.origin.clone(), + direction: this.ray.direction.clone() + }); + __privateSet(this, _v0, new Vec3()); + __privateSet(this, _v1, new Vec3()); + __privateSet(this, _v2, new Vec3()); + __privateSet(this, _edge1, new Vec3()); + __privateSet(this, _edge2, new Vec3()); + __privateSet(this, _uv0, new Vec2()); + __privateSet(this, _uv1, new Vec2()); + __privateSet(this, _uv2, new Vec2()); + __privateSet(this, _n0, new Vec3()); + __privateSet(this, _n1, new Vec3()); + __privateSet(this, _n2, new Vec3()); + } + /** + * Set the {@link pointer} normalized device coordinates values (in the [-1, 1] range) based on a mouse/pointer/touch event and the {@link CameraRenderer#boundingRect | renderer bounding rectangle}. Useful if the canvas has a fixed position for example, but you might need to directly use {@link setFromNDCCoords} if not. + * @param e - Mouse, pointer or touch event. + */ + setFromMouse(e) { + const { clientX, clientY } = e.targetTouches && e.targetTouches.length ? e.targetTouches[0] : e; + this.setFromNDCCoords( + (clientX - this.renderer.boundingRect.left) / this.renderer.boundingRect.width * 2 - 1, + -((clientY - this.renderer.boundingRect.top) / this.renderer.boundingRect.height) * 2 + 1 + ); + } + /** + * Set the {@link pointer} normalized device coordinates (in the [-1, 1] range). + * @param x - input position along the X axis in the [-1, 1] range where `-1` represents the left edge and `1` the right edge. + * @param y - input position along the Y axis in the [-1, 1] range where `-1` represents the bottom edge and `1` the top edge. + */ + setFromNDCCoords(x = 0, y = 0) { + this.pointer.set(x, y); + this.setRay(); + } + /** + * Sets the {@link ray} origin and direction based on the {@link camera} and the normalized device coordinates of the {@link pointer}. + */ + setRay() { + this.camera.worldMatrix.getTranslation(this.ray.origin); + this.ray.direction.set(this.pointer.x, this.pointer.y, -1).unproject(this.camera).sub(this.ray.origin).normalize(); + } + // INTERSECTIONS + /** + * Ray-Triangle Intersection with Möller–Trumbore Algorithm. + * @param intersectionPoint - {@link Vec3} to store the intersection point if any. + * @returns - Whether an intersection point has been found or not. + */ + rayIntersectsTriangle(intersectionPoint) { + const EPSILON = 1e-6; + const h = new Vec3(); + const q = new Vec3(); + h.crossVectors(__privateGet(this, _localRay).direction, __privateGet(this, _edge2)); + const a = __privateGet(this, _edge1).dot(h); + if (Math.abs(a) < EPSILON) + return false; + const f = 1 / a; + const s = __privateGet(this, _localRay).origin.clone().sub(__privateGet(this, _v0)); + const u = f * s.dot(h); + if (u < 0 || u > 1) + return false; + q.crossVectors(s, __privateGet(this, _edge1)); + const v = f * __privateGet(this, _localRay).direction.dot(q); + if (v < 0 || u + v > 1) + return false; + const t = f * __privateGet(this, _edge2).dot(q); + if (t > EPSILON) { + intersectionPoint.copy(__privateGet(this, _localRay).origin).add(__privateGet(this, _localRay).direction.clone().multiplyScalar(t)); + return true; + } + return false; + } + /** + * Find the barycentric contributions of a given intersection point lying inside our current triangle. + * @param intersectionPoint - Given {@link Vec3 | intersection point}. + * @returns - {@link Vec3} barycentric contributions. + */ + getBarycentricCoordinates(intersectionPoint) { + const v0p = intersectionPoint.clone().sub(__privateGet(this, _v0)); + const d00 = __privateGet(this, _edge1).dot(__privateGet(this, _edge1)); + const d01 = __privateGet(this, _edge1).dot(__privateGet(this, _edge2)); + const d11 = __privateGet(this, _edge2).dot(__privateGet(this, _edge2)); + const d20 = v0p.dot(__privateGet(this, _edge1)); + const d21 = v0p.dot(__privateGet(this, _edge2)); + const denom = d00 * d11 - d01 * d01; + const barycentric = new Vec3(0, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); + barycentric.x = 1 - barycentric.y - barycentric.z; + return barycentric; + } + /** + * Get a rough estimation of the current normal of our current triangle, in local space. + * @returns - {@link Vec3} normal. + */ + getTriangleNormal() { + return new Vec3().crossVectors(__privateGet(this, _edge1), __privateGet(this, _edge2)).normalize(); + } + /** + * Set our input vector with the desired attribute value at the given offset defined by our triangleIndex, offset and whether we're using and indexed geometry or not. + * @param triangleIndex - Index of the triangle for which to look our attribute value. + * @param offset - Index of the point inside our triangle (`0`, `1` or `2`). + * @param indices - Indexed geometry array if defined or `null`. + * @param attribute - {@link VertexBufferAttribute | Vertex buffer attribute} to get the value from. + * @param vector - Input vector to set (can either be a {@link Vec2} or {@link Vec3}). + */ + setAttributeVectorAtIndex(triangleIndex, offset, indices, attribute, vector) { + const index = indices ? indices[triangleIndex * 3 + offset] : triangleIndex * 3 + offset; + vector.x = attribute.array[index * attribute.size]; + vector.y = attribute.array[index * attribute.size + 1]; + if ("z" in vector) { + vector.z = attribute.array[index * attribute.size + 2]; + } + } + /** + * Test whether the {@link ray} is intersecting a given object, if the is object is actually a {@link ProjectedMesh | projected mesh}. + * Then, if the recursive flag is set to `true`, test if the {@link Object3D#children | object's children} are intersecting as well. + * @param object - {@link Object3D | object} to test against. + * @param recursive - Whether we should also test against the {@link Object3D#children | object's children}. Default to `true`. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + */ + intersectObject(object, recursive = true, intersections = []) { + if (!(object instanceof Object3D)) { + if (!this.renderer.production) { + throwWarning(`${this.type}: object to test intersection again is not of type Object3D`); + } + return intersections; + } + const mesh = isProjectedMesh(object); + if (mesh) { + __privateMethod(this, _intersectMesh, intersectMesh_fn).call(this, mesh, intersections); + } + if (recursive) { + object.children.forEach((child) => { + this.intersectObject(child, recursive, intersections); + }); + } + if (intersections.length) { + intersections.sort((a, b) => { + return this.ray.origin.distance(a.point) - this.ray.origin.distance(b.point); + }); + } + return intersections; + } + /** + * Test whether the {@link ray} is intersecting a given array of objects. + * If the recursive flag is set to `true`, test if each {@link Object3D#children | object's children} are intersecting as well. + * @param objects - Array of {@link Object3D | objects} to test against. + * @param recursive - Whether we should also test against each {@link Object3D#children | object's children}. Default to `true`. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + */ + intersectObjects(objects, recursive = true, intersections = []) { + objects.forEach((object) => { + this.intersectObject(object, recursive, intersections); + }); + if (intersections.length) { + intersections.sort((a, b) => { + return this.ray.origin.distance(a.point) - this.ray.origin.distance(b.point); + }); + } + return intersections; + } +} +_localRay = new WeakMap(); +_v0 = new WeakMap(); +_v1 = new WeakMap(); +_v2 = new WeakMap(); +_edge1 = new WeakMap(); +_edge2 = new WeakMap(); +_uv0 = new WeakMap(); +_uv1 = new WeakMap(); +_uv2 = new WeakMap(); +_n0 = new WeakMap(); +_n1 = new WeakMap(); +_n2 = new WeakMap(); +_intersectMesh = new WeakSet(); +intersectMesh_fn = function(mesh, intersections = []) { + if (!mesh.geometry) + return intersections; + const position = mesh.geometry.getAttributeByName("position"); + if (!position) { + if (!this.renderer.production) { + throwWarning(`Raycaster: can't raycast on a mesh that has no position attribute: ${mesh.options.label}`); + } + return intersections; + } + if (!position.array) { + if (!this.renderer.production) { + throwWarning(`Raycaster: can't raycast on a mesh that has no position attribute array: ${mesh.options.label}`); + } + return intersections; + } + if (mesh.frustumCulling && mesh.domFrustum) { + const { clipSpaceBoundingRect } = mesh.domFrustum; + if (!mesh.domFrustum.isIntersecting) { + return intersections; + } else if (this.pointer.x > clipSpaceBoundingRect.left + clipSpaceBoundingRect.width || this.pointer.x < clipSpaceBoundingRect.left || this.pointer.y > clipSpaceBoundingRect.top || this.pointer.y < clipSpaceBoundingRect.top - clipSpaceBoundingRect.height) { + return intersections; + } + } + const inverseModelMatrix = mesh.worldMatrix.getInverse(); + __privateGet(this, _localRay).origin.copy(this.ray.origin).applyMat4(inverseModelMatrix); + __privateGet(this, _localRay).direction.copy(this.ray.direction).transformDirection(inverseModelMatrix); + const uv = mesh.geometry.getAttributeByName("uv"); + const normal = mesh.geometry.getAttributeByName("normal"); + const indices = mesh.geometry.indexBuffer?.array; + const triangleCount = indices ? indices.length / 3 : position.array.length / 9; + for (let i = 0; i < triangleCount; i++) { + this.setAttributeVectorAtIndex(i, 0, indices, position, __privateGet(this, _v0)); + this.setAttributeVectorAtIndex(i, 1, indices, position, __privateGet(this, _v1)); + this.setAttributeVectorAtIndex(i, 2, indices, position, __privateGet(this, _v2)); + __privateGet(this, _edge1).copy(__privateGet(this, _v1)).sub(__privateGet(this, _v0)); + __privateGet(this, _edge2).copy(__privateGet(this, _v2)).sub(__privateGet(this, _v0)); + if (mesh.material.options.rendering.cullMode !== "none") { + const computedNormal = this.getTriangleNormal(); + const faceDirection = computedNormal.dot(__privateGet(this, _localRay).direction); + if (faceDirection > 0 && mesh.material.options.rendering.cullMode === "back") { + continue; + } else if (faceDirection < 0 && mesh.material.options.rendering.cullMode === "front") { + continue; + } + } + const intersectionPoint = new Vec3(); + const isIntersected = this.rayIntersectsTriangle(intersectionPoint); + if (isIntersected) { + const barycentric = this.getBarycentricCoordinates(intersectionPoint); + const point = intersectionPoint.clone().applyMat4(mesh.worldMatrix); + const distance = this.ray.origin.distance(point); + const intersection = { + object: mesh, + distance, + localPoint: intersectionPoint, + point, + triangle: [__privateGet(this, _v0).clone(), __privateGet(this, _v1).clone(), __privateGet(this, _v2).clone()], + triangleIndex: i + }; + if (uv && uv.array && uv.array.length) { + this.setAttributeVectorAtIndex(i, 0, indices, uv, __privateGet(this, _uv0)); + this.setAttributeVectorAtIndex(i, 1, indices, uv, __privateGet(this, _uv1)); + this.setAttributeVectorAtIndex(i, 2, indices, uv, __privateGet(this, _uv2)); + intersection.uv = __privateGet(this, _uv0).clone().multiplyScalar(barycentric.x).add(__privateGet(this, _uv1).clone().multiplyScalar(barycentric.y)).add(__privateGet(this, _uv2).clone().multiplyScalar(barycentric.z)); + } + if (normal && normal.array && normal.array.length) { + this.setAttributeVectorAtIndex(i, 0, indices, normal, __privateGet(this, _n0)); + this.setAttributeVectorAtIndex(i, 1, indices, normal, __privateGet(this, _n1)); + this.setAttributeVectorAtIndex(i, 2, indices, normal, __privateGet(this, _n2)); + intersection.normal = __privateGet(this, _n0).clone().multiplyScalar(barycentric.x).add(__privateGet(this, _n1).clone().multiplyScalar(barycentric.y)).add(__privateGet(this, _n2).clone().multiplyScalar(barycentric.z)); + } + intersections.push(intersection); + } + } + return intersections; +}; + +export { Raycaster }; diff --git a/dist/esm/index.mjs b/dist/esm/index.mjs index c1bb6353a..256f440f1 100644 --- a/dist/esm/index.mjs +++ b/dist/esm/index.mjs @@ -29,6 +29,7 @@ export { PipelineManager } from './core/pipelines/PipelineManager.mjs'; export { GPUCameraRenderer } from './core/renderers/GPUCameraRenderer.mjs'; export { GPUDeviceManager } from './core/renderers/GPUDeviceManager.mjs'; export { GPURenderer } from './core/renderers/GPURenderer.mjs'; +export { RenderBundle } from './core/renderPasses/RenderBundle.mjs'; export { RenderPass } from './core/renderPasses/RenderPass.mjs'; export { RenderTarget } from './core/renderPasses/RenderTarget.mjs'; export { ShaderPass } from './core/renderPasses/ShaderPass.mjs'; @@ -55,11 +56,13 @@ export { Quat } from './math/Quat.mjs'; export { Vec2 } from './math/Vec2.mjs'; export { Vec3 } from './math/Vec3.mjs'; export { OrbitControls } from './extras/controls/OrbitControls.mjs'; +export { EnvironmentMap } from './extras/environment-map/EnvironmentMap.mjs'; export { BoxGeometry } from './extras/geometries/BoxGeometry.mjs'; export { SphereGeometry } from './extras/geometries/SphereGeometry.mjs'; export { PingPongPlane } from './extras/meshes/PingPongPlane.mjs'; +export { Raycaster } from './extras/raycaster/Raycaster.mjs'; export { GLTFScenesManager } from './extras/gltf/GLTFScenesManager.mjs'; -export { buildShaders, computeDiffuseFromSpecular } from './extras/gltf/utils.mjs'; +export { buildShaders } from './extras/gltf/utils.mjs'; export { GLTFLoader } from './extras/loaders/GLTFLoader.mjs'; export { HDRLoader } from './extras/loaders/HDRLoader.mjs'; export { logSceneCommands } from './utils/debug.mjs'; diff --git a/dist/esm/math/Vec3.mjs b/dist/esm/math/Vec3.mjs index 02da56232..20ede251c 100644 --- a/dist/esm/math/Vec3.mjs +++ b/dist/esm/math/Vec3.mjs @@ -367,6 +367,19 @@ class Vec3 { applyAxisAngle(axis = new Vec3(), angle = 0, quaternion = new Quat()) { return this.applyQuat(quaternion.setFromAxisAngle(axis, angle)); } + /** + * Transforms the direction of this vector by a {@link Mat4} (the upper left 3 x 3 subset) and then normalizes the result. + * @param matrix - {@link Mat4} to use for transformation. + * @returns - this {@link Vec3} with the transformation applied. + */ + transformDirection(matrix) { + const x = this.x, y = this.y, z = this.z; + const e = matrix.elements; + this.x = e[0] * x + e[4] * y + e[8] * z; + this.y = e[1] * x + e[5] * y + e[9] * z; + this.z = e[2] * x + e[6] * y + e[10] * z; + return this.normalize(); + } /** * Project a 3D coordinate {@link Vec3} to a 2D coordinate {@link Vec3} * @param camera - {@link Camera} to use for projection diff --git a/dist/esm/utils/TasksQueueManager.mjs b/dist/esm/utils/TasksQueueManager.mjs index e3ab9f3ce..25a156889 100644 --- a/dist/esm/utils/TasksQueueManager.mjs +++ b/dist/esm/utils/TasksQueueManager.mjs @@ -13,12 +13,12 @@ var __privateAdd = (obj, member, value) => { }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); return value; }; var __privateWrapper = (obj, member, setter, getter) => ({ set _(value) { - __privateSet(obj, member, value, setter); + __privateSet(obj, member, value); }, get _() { return __privateGet(obj, member, getter); diff --git a/dist/esm/utils/webgpu-constants.mjs b/dist/esm/utils/webgpu-constants.mjs new file mode 100644 index 000000000..1dfa258ee --- /dev/null +++ b/dist/esm/utils/webgpu-constants.mjs @@ -0,0 +1,26 @@ +const WebGPUShaderStageConstants = typeof GPUShaderStage !== "undefined" ? GPUShaderStage : { + VERTEX: 1, + FRAGMENT: 2, + COMPUTE: 4 +}; +const WebGPUBufferUsageConstants = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage : { + MAP_READ: 1, + MAP_WRITE: 2, + COPY_SRC: 4, + COPY_DST: 8, + INDEX: 16, + VERTEX: 32, + UNIFORM: 64, + STORAGE: 128, + INDIRECT: 256, + QUERY_RESOLVE: 512 +}; +const WebGPUTextureUsageConstants = typeof GPUTextureUsage !== "undefined" ? GPUTextureUsage : { + COPY_SRC: 1, + COPY_DST: 2, + TEXTURE_BINDING: 4, + STORAGE_BINDING: 8, + RENDER_ATTACHMENT: 16 +}; + +export { WebGPUBufferUsageConstants, WebGPUShaderStageConstants, WebGPUTextureUsageConstants }; diff --git a/dist/gpu-curtains.umd.js b/dist/gpu-curtains.umd.js index 49d63ffe7..6d30798d8 100644 --- a/dist/gpu-curtains.umd.js +++ b/dist/gpu-curtains.umd.js @@ -60,6 +60,9 @@ } return renderer; }; + const isProjectedMesh = (object) => { + return object.constructor.name === "Mesh" || object.constructor.name === "DOMMesh" || object.constructor.name === "Plane" ? object : false; + }; const generateMips = /* @__PURE__ */ (() => { let sampler; let module; @@ -178,10 +181,35 @@ }; })(); + const WebGPUShaderStageConstants = typeof GPUShaderStage !== "undefined" ? GPUShaderStage : { + VERTEX: 1, + FRAGMENT: 2, + COMPUTE: 4 + }; + const WebGPUBufferUsageConstants = typeof GPUBufferUsage !== "undefined" ? GPUBufferUsage : { + MAP_READ: 1, + MAP_WRITE: 2, + COPY_SRC: 4, + COPY_DST: 8, + INDEX: 16, + VERTEX: 32, + UNIFORM: 64, + STORAGE: 128, + INDIRECT: 256, + QUERY_RESOLVE: 512 + }; + const WebGPUTextureUsageConstants = typeof GPUTextureUsage !== "undefined" ? GPUTextureUsage : { + COPY_SRC: 1, + COPY_DST: 2, + TEXTURE_BINDING: 4, + STORAGE_BINDING: 8, + RENDER_ATTACHMENT: 16 + }; + const bindingVisibilities = /* @__PURE__ */ new Map([ - ["vertex", GPUShaderStage.VERTEX], - ["fragment", GPUShaderStage.FRAGMENT], - ["compute", GPUShaderStage.COMPUTE] + ["vertex", WebGPUShaderStageConstants.VERTEX], + ["fragment", WebGPUShaderStageConstants.FRAGMENT], + ["compute", WebGPUShaderStageConstants.COMPUTE] ]); const getBindingVisibility = (visibilities = []) => { return visibilities.reduce((acc, v) => { @@ -1114,6 +1142,19 @@ applyAxisAngle(axis = new Vec3(), angle = 0, quaternion = new Quat()) { return this.applyQuat(quaternion.setFromAxisAngle(axis, angle)); } + /** + * Transforms the direction of this vector by a {@link Mat4} (the upper left 3 x 3 subset) and then normalizes the result. + * @param matrix - {@link Mat4} to use for transformation. + * @returns - this {@link Vec3} with the transformation applied. + */ + transformDirection(matrix) { + const x = this.x, y = this.y, z = this.z; + const e = matrix.elements; + this.x = e[0] * x + e[4] * y + e[8] * z; + this.y = e[1] * x + e[5] * y + e[9] * z; + this.z = e[2] * x + e[6] * y + e[10] * z; + return this.normalize(); + } /** * Project a 3D coordinate {@link Vec3} to a 2D coordinate {@link Vec3} * @param camera - {@link Camera} to use for projection @@ -1528,16 +1569,16 @@ } const bufferUsages = /* @__PURE__ */ new Map([ - ["copySrc", GPUBufferUsage.COPY_SRC], - ["copyDst", GPUBufferUsage.COPY_DST], - ["index", GPUBufferUsage.INDEX], - ["indirect", GPUBufferUsage.INDIRECT], - ["mapRead", GPUBufferUsage.MAP_READ], - ["mapWrite", GPUBufferUsage.MAP_WRITE], - ["queryResolve", GPUBufferUsage.QUERY_RESOLVE], - ["storage", GPUBufferUsage.STORAGE], - ["uniform", GPUBufferUsage.UNIFORM], - ["vertex", GPUBufferUsage.VERTEX] + ["copySrc", WebGPUBufferUsageConstants.COPY_SRC], + ["copyDst", WebGPUBufferUsageConstants.COPY_DST], + ["index", WebGPUBufferUsageConstants.INDEX], + ["indirect", WebGPUBufferUsageConstants.INDIRECT], + ["mapRead", WebGPUBufferUsageConstants.MAP_READ], + ["mapWrite", WebGPUBufferUsageConstants.MAP_WRITE], + ["queryResolve", WebGPUBufferUsageConstants.QUERY_RESOLVE], + ["storage", WebGPUBufferUsageConstants.STORAGE], + ["uniform", WebGPUBufferUsageConstants.UNIFORM], + ["vertex", WebGPUBufferUsageConstants.VERTEX] ]); const getBufferUsages = (usages = []) => { return usages.reduce((acc, v) => { @@ -1632,7 +1673,26 @@ } } - class BufferBinding extends Binding { + var __accessCheck$k = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$i = (obj, member, getter) => { + __accessCheck$k(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$k = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$h = (obj, member, value, setter) => { + __accessCheck$k(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var _parent; + const _BufferBinding = class _BufferBinding extends Binding { /** * BufferBinding constructor * @param parameters - {@link BufferBindingParams | parameters} used to create our BufferBindings @@ -1646,17 +1706,25 @@ access = "read", usage = [], struct = {}, - bindings = [] + childrenBindings = [], + parent = null, + minOffset = 256, + offset = 0 }) { bindingType = bindingType ?? "uniform"; super({ label, name, bindingType, visibility }); + /** @ignore */ + __privateAdd$k(this, _parent, void 0); this.options = { ...this.options, useStruct, access, usage, struct, - bindings + childrenBindings, + parent, + minOffset, + offset }; this.cacheKey += `${useStruct},${access},`; this.arrayBufferSize = 0; @@ -1669,20 +1737,121 @@ this.setBindings(struct); this.setInputsAlignment(); } - if (Object.keys(struct).length || this.options.bindings.length) { + this.setChildrenBindings(childrenBindings); + if (Object.keys(struct).length || this.childrenBindings.length) { this.setBufferAttributes(); this.setWGSLFragment(); } + this.parent = parent; + } + /** + * Clone a {@link BufferBindingParams#struct | struct object} width new default values. + * @param struct - New cloned struct object. + */ + static cloneStruct(struct) { + return Object.keys(struct).reduce((acc, bindingKey) => { + const binding = struct[bindingKey]; + let value; + if (Array.isArray(binding.value) || ArrayBuffer.isView(binding.value)) { + value = new binding.value.constructor(binding.value.length); + } else if (typeof binding.value === "number") { + value = 0; + } else { + value = new binding.value.constructor(); + } + return { + ...acc, + [bindingKey]: { + type: binding.type, + value + } + }; + }, {}); + } + /** + * Get the {@link BufferBinding} parent if any. + * @readonly + * @returns - The {@link BufferBinding} parent if any. + */ + get parent() { + return __privateGet$i(this, _parent); + } + /** + * Set the new {@link BufferBinding} parent. + * @param value - New {@link BufferBinding} parent to set if any. + */ + set parent(value) { + if (!!value) { + this.parentView = new DataView(value.arrayBuffer, this.offset, this.getMinOffsetSize(this.arrayBufferSize)); + const getAllBufferElements = (binding) => { + const getBufferElements = (binding2) => { + return binding2.bufferElements; + }; + return [ + ...getBufferElements(binding), + binding.childrenBindings.map((child) => getAllBufferElements(child)).flat() + ].flat(); + }; + const bufferElements = getAllBufferElements(this); + this.parentViewSetBufferEls = bufferElements.map((bufferElement) => { + switch (bufferElement.bufferLayout.View) { + case Int32Array: + return { + bufferElement, + viewSetFunction: this.parentView.setInt32.bind(this.parentView) + }; + case Uint16Array: + return { + bufferElement, + viewSetFunction: this.parentView.setUint16.bind(this.parentView) + }; + case Uint32Array: + return { + bufferElement, + viewSetFunction: this.parentView.setUint32.bind(this.parentView) + }; + case Float32Array: + default: + return { + bufferElement, + viewSetFunction: this.parentView.setFloat32.bind(this.parentView) + }; + } + }); + if (!this.parent && this.buffer.GPUBuffer) { + this.buffer.destroy(); + } + } else { + this.parentView = null; + this.parentViewSetBufferEls = null; + } + __privateSet$h(this, _parent, value); + } + /** + * Round the given size value to the nearest minimum {@link GPUDevice} buffer offset alignment. + * @param value - Size to round. + */ + getMinOffsetSize(value) { + return Math.ceil(value / this.options.minOffset) * this.options.minOffset; + } + /** + * Get this {@link BufferBinding} offset in bytes inside the {@link arrayBuffer | parent arrayBuffer}. + * @readonly + * @returns - The offset in bytes inside the {@link arrayBuffer | parent arrayBuffer} + */ + get offset() { + return this.getMinOffsetSize(this.options.offset * this.getMinOffsetSize(this.arrayBufferSize)); } /** - * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource} + * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource}. * @readonly */ get resourceLayout() { return { buffer: { type: getBindGroupLayoutBindingType(this) - } + }, + ...this.parent && { offset: this.offset, size: this.arrayBufferSize } }; } /** @@ -1693,21 +1862,27 @@ return `buffer,${getBindGroupLayoutBindingType(this)},${this.visibility},`; } /** - * Get {@link GPUBindGroupEntry#resource | bind group resource} + * Get {@link GPUBindGroupEntry#resource | bind group resource}. * @readonly */ get resource() { - return { buffer: this.buffer.GPUBuffer }; + return { + buffer: this.parent ? this.parent.buffer.GPUBuffer : this.buffer.GPUBuffer, + ...this.parent && { offset: this.offset, size: this.arrayBufferSize } + }; } /** * Clone this {@link BufferBinding} into a new one. Allows to skip buffer layout alignment computations. * @param params - params to use for cloning */ - clone(params) { - const { struct, ...defaultParams } = params; + clone(params = {}) { + let { struct, childrenBindings, parent, ...defaultParams } = params; + const { label, name, bindingType, visibility, useStruct, access, usage } = this.options; + defaultParams = { ...{ label, name, bindingType, visibility, useStruct, access, usage }, ...defaultParams }; const bufferBindingCopy = new this.constructor(defaultParams); - struct && bufferBindingCopy.setBindings(struct); + struct = struct || _BufferBinding.cloneStruct(this.options.struct); bufferBindingCopy.options.struct = struct; + bufferBindingCopy.setBindings(struct); bufferBindingCopy.arrayBufferSize = this.arrayBufferSize; bufferBindingCopy.arrayBuffer = new ArrayBuffer(bufferBindingCopy.arrayBufferSize); bufferBindingCopy.arrayView = new DataView( @@ -1732,12 +1907,48 @@ newBufferElement.setView(bufferBindingCopy.arrayBuffer, bufferBindingCopy.arrayView); bufferBindingCopy.bufferElements.push(newBufferElement); }); + if (this.options.childrenBindings) { + bufferBindingCopy.options.childrenBindings = this.options.childrenBindings; + bufferBindingCopy.options.childrenBindings.forEach((child) => { + const count = child.count ? Math.max(1, child.count) : 1; + bufferBindingCopy.cacheKey += `child(count:${count}):${child.binding.cacheKey}`; + }); + bufferBindingCopy.options.childrenBindings.forEach((child) => { + bufferBindingCopy.childrenBindings = [ + ...bufferBindingCopy.childrenBindings, + Array.from(Array(Math.max(1, child.count || 1)).keys()).map((i) => { + return child.binding.clone({ + ...child.binding.options, + // clone struct with new arrays + struct: _BufferBinding.cloneStruct(child.binding.options.struct) + }); + }) + ].flat(); + }); + bufferBindingCopy.childrenBindings.forEach((binding, index) => { + let offset = this.arrayView.byteLength; + for (let i = 0; i < index; i++) { + offset += this.childrenBindings[i].arrayBuffer.byteLength; + } + binding.bufferElements.forEach((bufferElement, i) => { + bufferElement.alignment.start.row = this.childrenBindings[index].bufferElements[i].alignment.start.row; + bufferElement.alignment.end.row = this.childrenBindings[index].bufferElements[i].alignment.end.row; + }); + binding.arrayView = new DataView(bufferBindingCopy.arrayBuffer, offset, binding.arrayBuffer.byteLength); + for (const bufferElement of binding.bufferElements) { + bufferElement.setView(bufferBindingCopy.arrayBuffer, binding.arrayView); + } + }); + } if (this.name === bufferBindingCopy.name && this.label === bufferBindingCopy.label) { bufferBindingCopy.wgslStructFragment = this.wgslStructFragment; bufferBindingCopy.wgslGroupFragment = this.wgslGroupFragment; } else { bufferBindingCopy.setWGSLFragment(); } + if (parent) { + bufferBindingCopy.parent = parent; + } bufferBindingCopy.shouldUpdate = bufferBindingCopy.arrayBufferSize > 0; return bufferBindingCopy; } @@ -1777,6 +1988,49 @@ this.cacheKey += `${bindingKey},${bindings[bindingKey].type},`; } } + /** + * Set this {@link BufferBinding} optional {@link childrenBindings}. + * @param childrenBindings - Array of {@link BufferBindingChildrenBinding} to use as {@link childrenBindings}. + */ + setChildrenBindings(childrenBindings) { + this.childrenBindings = []; + if (childrenBindings && childrenBindings.length) { + const childrenArray = []; + childrenBindings.sort((a, b) => { + const countA = a.count ? Math.max(a.count) : a.forceArray ? 1 : 0; + const countB = b.count ? Math.max(b.count) : b.forceArray ? 1 : 0; + return countA - countB; + }).forEach((child) => { + if (child.count && child.count > 1 || child.forceArray) { + childrenArray.push(child.binding); + } + }); + if (childrenArray.length > 1) { + childrenArray.shift(); + throwWarning( + `BufferBinding: "${this.label}" contains multiple children bindings arrays. These children bindings cannot be added to the BufferBinding: "${childrenArray.map((child) => child.label).join(", ")}"` + ); + childrenArray.forEach((removedChildBinding) => { + childrenBindings = childrenBindings.filter((child) => child.binding.name !== removedChildBinding.name); + }); + } + this.options.childrenBindings = childrenBindings; + childrenBindings.forEach((child) => { + const count = child.count ? Math.max(1, child.count) : 1; + this.cacheKey += `child(count:${count}):${child.binding.cacheKey}`; + this.childrenBindings = [ + ...this.childrenBindings, + Array.from(Array(count).keys()).map((i) => { + return child.binding.clone({ + ...child.binding.options, + // clone struct with new arrays + struct: _BufferBinding.cloneStruct(child.binding.options.struct) + }); + }) + ].flat(); + }); + } + } /** * Set the buffer alignments from {@link inputs}. */ @@ -1877,21 +2131,22 @@ setBufferAttributes() { const bufferElementsArrayBufferSize = this.bufferElements.length ? this.bufferElements[this.bufferElements.length - 1].paddedByteCount : 0; this.arrayBufferSize = bufferElementsArrayBufferSize; - this.options.bindings.forEach((binding) => { + this.childrenBindings.forEach((binding) => { this.arrayBufferSize += binding.arrayBufferSize; }); this.arrayBuffer = new ArrayBuffer(this.arrayBufferSize); this.arrayView = new DataView(this.arrayBuffer, 0, bufferElementsArrayBufferSize); - this.options.bindings.forEach((binding, index) => { + this.childrenBindings.forEach((binding, index) => { let offset = bufferElementsArrayBufferSize; for (let i = 0; i < index; i++) { - offset += this.options.bindings[i].arrayBuffer.byteLength; + offset += this.childrenBindings[i].arrayBuffer.byteLength; } const bufferElLastRow = this.bufferElements.length ? this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1 : 0; - const bindingLastRow = index > 0 ? this.options.bindings[index - 1].bufferElements.length ? this.options.bindings[index - 1].bufferElements[this.options.bindings[index - 1].bufferElements.length - 1].alignment.end.row + 1 : 0 : 0; + const bindingLastRow = index > 0 ? this.childrenBindings[index - 1].bufferElements.length ? this.childrenBindings[index - 1].bufferElements[this.childrenBindings[index - 1].bufferElements.length - 1].alignment.end.row + 1 : 0 : 0; binding.bufferElements.forEach((bufferElement) => { - bufferElement.alignment.start.row += bufferElLastRow + bindingLastRow; - bufferElement.alignment.end.row += bufferElLastRow + bindingLastRow; + const rowOffset = index === 0 ? bufferElLastRow + bindingLastRow : bindingLastRow; + bufferElement.alignment.start.row += rowOffset; + bufferElement.alignment.end.row += rowOffset; }); binding.arrayView = new DataView(this.arrayBuffer, offset, binding.arrayBuffer.byteLength); for (const bufferElement of binding.bufferElements) { @@ -1908,22 +2163,8 @@ * Set the WGSL code snippet to append to the shaders code. It consists of variable (and Struct structures if needed) declarations. */ setWGSLFragment() { - if (!this.bufferElements.length && !this.options.bindings.length) + if (!this.bufferElements.length && !this.childrenBindings.length) return; - const uniqueBindings = []; - this.options.bindings.forEach((binding) => { - const bindingExists = uniqueBindings.find((b) => b.name === binding.name); - if (!bindingExists) { - uniqueBindings.push({ - name: binding.name, - label: binding.label, - count: 1, - wgslStructFragment: binding.wgslStructFragment - }); - } else { - bindingExists.count++; - } - }); const kebabCaseLabel = toKebabCase(this.label); if (this.useStruct) { const structs = {}; @@ -1963,12 +2204,12 @@ const varType = getBindingWGSLVarType(this); this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]; } - if (uniqueBindings.length) { - uniqueBindings.forEach((binding) => { - structs[kebabCaseLabel][binding.name] = binding.count > 1 ? `array<${toKebabCase(binding.label)}>` : toKebabCase(binding.label); + if (this.childrenBindings.length) { + this.options.childrenBindings.forEach((child) => { + structs[kebabCaseLabel][child.binding.name] = child.count && child.count > 1 || child.forceArray ? `array<${toKebabCase(child.binding.label)}>` : toKebabCase(child.binding.label); }); } - const additionalBindings = uniqueBindings.length ? uniqueBindings.map((binding) => binding.wgslStructFragment).join("\n\n") + "\n\n" : ""; + const additionalBindings = this.childrenBindings.length ? this.options.childrenBindings.map((child) => child.binding.wgslStructFragment).join("\n\n") + "\n\n" : ""; this.wgslStructFragment = additionalBindings + Object.keys(structs).reverse().map((struct) => { return `struct ${struct} { ${Object.keys(structs[struct]).map((binding) => `${binding}: ${structs[struct][binding]}`).join(",\n ")} @@ -1993,7 +2234,7 @@ } /** * Executed at the beginning of a Material render call. - * If any of the {@link inputs} has changed, run its onBeforeUpdate callback then updates our {@link arrayBuffer} array. + * If any of the {@link inputs} has changed, run its `onBeforeUpdate` callback then updates our {@link arrayBuffer} array. * Also sets the {@link shouldUpdate} property to true so the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} knows it will need to update the {@link GPUBuffer}. */ update() { @@ -2007,12 +2248,25 @@ binding.shouldUpdate = false; } } - this.options.bindings.forEach((binding) => { + this.childrenBindings.forEach((binding) => { binding.update(); if (binding.shouldUpdate) { this.shouldUpdate = true; } + binding.shouldUpdate = false; }); + if (this.shouldUpdate && this.parent && this.parentViewSetBufferEls) { + let index = 0; + this.parentViewSetBufferEls.forEach((viewSetBuffer, i) => { + const { bufferElement, viewSetFunction } = viewSetBuffer; + bufferElement.view.forEach((value) => { + viewSetFunction(index * bufferElement.view.BYTES_PER_ELEMENT, value, true); + index++; + }); + }); + this.parent.shouldUpdate = true; + this.shouldUpdate = false; + } } /** * Extract the data corresponding to a specific {@link BufferElement} from a {@link Float32Array} holding the {@link BufferBinding#buffer | GPU buffer} data of this {@link BufferBinding} @@ -2032,7 +2286,9 @@ return result; } } - } + }; + _parent = new WeakMap(); + let BufferBinding = _BufferBinding; class WritableBufferBinding extends BufferBinding { /** @@ -2048,11 +2304,28 @@ access = "read_write", usage = [], struct = {}, + childrenBindings = [], + parent = null, + minOffset = 256, + offset = 0, shouldCopyResult = false }) { bindingType = "storage"; visibility = ["compute"]; - super({ label, name, bindingType, visibility, useStruct, access, usage, struct }); + super({ + label, + name, + bindingType, + visibility, + useStruct, + access, + usage, + struct, + childrenBindings, + parent, + minOffset, + offset + }); this.options = { ...this.options, shouldCopyResult @@ -2095,7 +2368,11 @@ this.consumers = /* @__PURE__ */ new Set(); for (const binding of this.bufferBindings) { if ("buffer" in binding) { - binding.buffer.consumers.add(this.uuid); + if (binding.parent) { + binding.parent.buffer.consumers.add(this.uuid); + } else { + binding.buffer.consumers.add(this.uuid); + } } if ("resultBuffer" in binding) { binding.resultBuffer.consumers.add(this.uuid); @@ -2117,8 +2394,13 @@ addBindings(bindings = []) { bindings.forEach((binding) => { if ("buffer" in binding) { - this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding); - binding.buffer.consumers.add(this.uuid); + if (binding.parent) { + this.renderer.deviceManager.bufferBindings.set(binding.parent.cacheKey, binding.parent); + binding.parent.buffer.consumers.add(this.uuid); + } else { + this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding); + binding.buffer.consumers.add(this.uuid); + } } }); this.bindings = [...this.bindings, ...bindings]; @@ -2141,6 +2423,13 @@ if (!binding.buffer.consumers.size) { binding.buffer.destroy(); } + if (binding.parent) { + binding.parent.buffer.consumers.delete(this.uuid); + if (!binding.parent.buffer.consumers.size) { + this.renderer.removeBuffer(binding.parent.buffer); + binding.parent.buffer.destroy(); + } + } } if ("resultBuffer" in binding) { this.renderer.removeBuffer(binding.resultBuffer); @@ -2290,6 +2579,9 @@ this.resetEntries(); for (const binding of this.bufferBindings) { binding.buffer.reset(); + if (binding.parent) { + binding.parent.buffer.reset(); + } if ("resultBuffer" in binding) { binding.resultBuffer.reset(); } @@ -2318,12 +2610,13 @@ ); } /** - * Creates binding GPUBuffer with correct params - * @param binding - the binding element + * Creates binding GPUBuffer with correct params. + * @param binding - The binding element. + * @param optionalLabel - Optional label to use for the {@link GPUBuffer}. */ - createBindingBuffer(binding) { + createBindingBuffer(binding, optionalLabel = null) { binding.buffer.createBuffer(this.renderer, { - label: this.options.label + ": " + binding.bindingType + " buffer from: " + binding.label, + label: optionalLabel || this.options.label + ": " + binding.bindingType + " buffer from: " + binding.label, usage: [...["copySrc", "copyDst", binding.bindingType], ...binding.options.usage] }); if ("resultBuffer" in binding) { @@ -2344,7 +2637,9 @@ binding.visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE; } if ("buffer" in binding) { - if (!binding.buffer.GPUBuffer) { + if (binding.parent && !binding.parent.buffer.GPUBuffer) { + this.createBindingBuffer(binding.parent, binding.parent.options.label); + } else if (!binding.buffer.GPUBuffer && !binding.parent) { this.createBindingBuffer(binding); } } @@ -2453,10 +2748,12 @@ for (const binding of bindingsRef) { bindGroupCopy.addBinding(binding); if ("buffer" in binding) { - if (!binding.buffer.GPUBuffer) { + if (binding.parent && !binding.parent.buffer.GPUBuffer) { + this.createBindingBuffer(binding.parent, binding.parent.options.label); + binding.parent.buffer.consumers.add(bindGroupCopy.uuid); + } else if (!binding.buffer.GPUBuffer && !binding.parent) { this.createBindingBuffer(binding); } - binding.buffer.consumers.add(bindGroupCopy.uuid); if ("resultBuffer" in binding) { binding.resultBuffer.consumers.add(bindGroupCopy.uuid); } @@ -3564,11 +3861,11 @@ } const textureUsages = /* @__PURE__ */ new Map([ - ["copySrc", GPUTextureUsage.COPY_SRC], - ["copyDst", GPUTextureUsage.COPY_DST], - ["renderAttachment", GPUTextureUsage.RENDER_ATTACHMENT], - ["storageBinding", GPUTextureUsage.STORAGE_BINDING], - ["textureBinding", GPUTextureUsage.TEXTURE_BINDING] + ["copySrc", WebGPUTextureUsageConstants.COPY_SRC], + ["copyDst", WebGPUTextureUsageConstants.COPY_DST], + ["renderAttachment", WebGPUTextureUsageConstants.RENDER_ATTACHMENT], + ["storageBinding", WebGPUTextureUsageConstants.STORAGE_BINDING], + ["textureBinding", WebGPUTextureUsageConstants.TEXTURE_BINDING] ]); const getTextureUsages = (usages = []) => { return usages.reduce((acc, v) => { @@ -3586,15 +3883,15 @@ return 1 + Math.log2(maxSize) | 0; }; - var __accessCheck$g = (obj, member, msg) => { + var __accessCheck$j = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$f = (obj, member, getter) => { - __accessCheck$g(obj, member, "read from private field"); + var __privateGet$h = (obj, member, getter) => { + __accessCheck$j(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; - var __privateAdd$g = (obj, member, value) => { + var __privateAdd$j = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); @@ -3623,13 +3920,13 @@ constructor(renderer, parameters = defaultDOMTextureParams) { super(); /** Private {@link Vec3 | vector} used for {@link#modelMatrix} calculations, based on {@link parentMesh} {@link core/DOM/DOMElement.RectSize | size} */ - __privateAdd$g(this, _parentRatio, new Vec3(1)); + __privateAdd$j(this, _parentRatio, new Vec3(1)); /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on {@link size | source size} */ - __privateAdd$g(this, _sourceRatio, new Vec3(1)); + __privateAdd$j(this, _sourceRatio, new Vec3(1)); /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on #parentRatio and #sourceRatio */ - __privateAdd$g(this, _coverScale, new Vec3(1)); + __privateAdd$j(this, _coverScale, new Vec3(1)); /** Private rotation {@link Mat4 | matrix} based on texture {@link quaternion} */ - __privateAdd$g(this, _rotationMatrix, new Mat4()); + __privateAdd$j(this, _rotationMatrix, new Mat4()); // callbacks / events /** function assigned to the {@link onSourceLoaded} callback */ this._onSourceLoadedCallback = () => { @@ -3766,16 +4063,16 @@ const parentRatio = parentWidth / parentHeight; const sourceRatio = this.size.width / this.size.height; if (parentWidth > parentHeight) { - __privateGet$f(this, _parentRatio).set(parentRatio, 1, 1); - __privateGet$f(this, _sourceRatio).set(1 / sourceRatio, 1, 1); + __privateGet$h(this, _parentRatio).set(parentRatio, 1, 1); + __privateGet$h(this, _sourceRatio).set(1 / sourceRatio, 1, 1); } else { - __privateGet$f(this, _parentRatio).set(1, 1 / parentRatio, 1); - __privateGet$f(this, _sourceRatio).set(1, sourceRatio, 1); + __privateGet$h(this, _parentRatio).set(1, 1 / parentRatio, 1); + __privateGet$h(this, _sourceRatio).set(1, sourceRatio, 1); } - const coverRatio = parentRatio > sourceRatio !== parentWidth > parentHeight ? 1 : parentWidth > parentHeight ? __privateGet$f(this, _parentRatio).x * __privateGet$f(this, _sourceRatio).x : __privateGet$f(this, _sourceRatio).y * __privateGet$f(this, _parentRatio).y; - __privateGet$f(this, _coverScale).set(1 / (coverRatio * this.scale.x), 1 / (coverRatio * this.scale.y), 1); - __privateGet$f(this, _rotationMatrix).rotateFromQuaternion(this.quaternion); - this.modelMatrix.identity().premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1)).premultiplyScale(__privateGet$f(this, _coverScale)).premultiplyScale(__privateGet$f(this, _parentRatio)).premultiply(__privateGet$f(this, _rotationMatrix)).premultiplyScale(__privateGet$f(this, _sourceRatio)).premultiplyTranslate(this.transformOrigin).translate(this.position); + const coverRatio = parentRatio > sourceRatio !== parentWidth > parentHeight ? 1 : parentWidth > parentHeight ? __privateGet$h(this, _parentRatio).x * __privateGet$h(this, _sourceRatio).x : __privateGet$h(this, _sourceRatio).y * __privateGet$h(this, _parentRatio).y; + __privateGet$h(this, _coverScale).set(1 / (coverRatio * this.scale.x), 1 / (coverRatio * this.scale.y), 1); + __privateGet$h(this, _rotationMatrix).rotateFromQuaternion(this.quaternion); + this.modelMatrix.identity().premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1)).premultiplyScale(__privateGet$h(this, _coverScale)).premultiplyScale(__privateGet$h(this, _parentRatio)).premultiply(__privateGet$h(this, _rotationMatrix)).premultiplyScale(__privateGet$h(this, _sourceRatio)).premultiplyTranslate(this.transformOrigin).translate(this.position); } /** * If our {@link modelMatrix} has been updated, tell the {@link textureMatrix | texture matrix binding} to update as well @@ -4245,22 +4542,22 @@ } } - var __accessCheck$f = (obj, member, msg) => { + var __accessCheck$i = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$e = (obj, member, getter) => { - __accessCheck$f(obj, member, "read from private field"); + var __privateGet$g = (obj, member, getter) => { + __accessCheck$i(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; - var __privateAdd$f = (obj, member, value) => { + var __privateAdd$i = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$e = (obj, member, value, setter) => { - __accessCheck$f(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$g = (obj, member, value, setter) => { + __accessCheck$i(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _fov, _near, _far, _pixelRatio; @@ -4281,13 +4578,13 @@ } = {}) { super(); /** @ignore */ - __privateAdd$f(this, _fov, void 0); + __privateAdd$i(this, _fov, void 0); /** @ignore */ - __privateAdd$f(this, _near, void 0); + __privateAdd$i(this, _near, void 0); /** @ignore */ - __privateAdd$f(this, _far, void 0); + __privateAdd$i(this, _far, void 0); /** @ignore */ - __privateAdd$f(this, _pixelRatio, void 0); + __privateAdd$i(this, _pixelRatio, void 0); this.uuid = generateUUID(); this.position.set(0, 0, 10); this.up = new Vec3(0, 1, 0); @@ -4395,7 +4692,7 @@ * Get the {@link Camera} {@link fov | field of view} */ get fov() { - return __privateGet$e(this, _fov); + return __privateGet$g(this, _fov); } /** * Set the {@link Camera} {@link fov | field of view}. Update the {@link projectionMatrix} only if the field of view actually changed @@ -4404,7 +4701,7 @@ set fov(fov) { fov = Math.max(1, Math.min(fov ?? this.fov, 179)); if (fov !== this.fov) { - __privateSet$e(this, _fov, fov); + __privateSet$g(this, _fov, fov); this.shouldUpdateProjectionMatrices(); } this.setVisibleSize(); @@ -4414,7 +4711,7 @@ * Get the {@link Camera} {@link near} plane value. */ get near() { - return __privateGet$e(this, _near); + return __privateGet$g(this, _near); } /** * Set the {@link Camera} {@link near} plane value. Update the {@link projectionMatrix} only if the near plane actually changed @@ -4423,7 +4720,7 @@ set near(near) { near = Math.max(near ?? this.near, 0.01); if (near !== this.near) { - __privateSet$e(this, _near, near); + __privateSet$g(this, _near, near); this.shouldUpdateProjectionMatrices(); } } @@ -4431,7 +4728,7 @@ * Get / set the {@link Camera} {@link far} plane value. */ get far() { - return __privateGet$e(this, _far); + return __privateGet$g(this, _far); } /** * Set the {@link Camera} {@link far} plane value. Update {@link projectionMatrix} only if the far plane actually changed @@ -4440,7 +4737,7 @@ set far(far) { far = Math.max(far ?? this.far, this.near + 1); if (far !== this.far) { - __privateSet$e(this, _far, far); + __privateSet$g(this, _far, far); this.shouldUpdateProjectionMatrices(); } } @@ -4448,14 +4745,14 @@ * Get the {@link Camera} {@link pixelRatio} value. */ get pixelRatio() { - return __privateGet$e(this, _pixelRatio); + return __privateGet$g(this, _pixelRatio); } /** * Set the {@link Camera} {@link pixelRatio} value. Update the {@link CSSPerspective} only if the pixel ratio actually changed * @param pixelRatio - new pixel ratio value */ set pixelRatio(pixelRatio) { - __privateSet$e(this, _pixelRatio, pixelRatio ?? this.pixelRatio); + __privateSet$g(this, _pixelRatio, pixelRatio ?? this.pixelRatio); this.setCSSPerspective(); } /** @@ -4614,22 +4911,22 @@ } } - var __accessCheck$e = (obj, member, msg) => { + var __accessCheck$h = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$d = (obj, member, getter) => { - __accessCheck$e(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + var __privateGet$f = (obj, member, getter) => { + __accessCheck$h(obj, member, "read from private field"); + return member.get(obj); }; - var __privateAdd$e = (obj, member, value) => { + var __privateAdd$h = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$d = (obj, member, value, setter) => { - __accessCheck$e(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$f = (obj, member, value, setter) => { + __accessCheck$h(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _autoResize; @@ -4657,7 +4954,7 @@ */ constructor(renderer, parameters = defaultTextureParams) { /** Whether this texture should be automatically resized when the {@link Renderer renderer} size changes. Default to true. */ - __privateAdd$e(this, _autoResize, true); + __privateAdd$h(this, _autoResize, true); renderer = isRenderer(renderer, parameters.label ? parameters.label + " Texture" : "Texture"); this.type = "Texture"; this.renderer = renderer; @@ -4672,7 +4969,7 @@ this.options.viewDimension = parameters.fromTexture.options.viewDimension; } if (!this.options.format) { - this.options.format = this.renderer.options.preferredFormat; + this.options.format = this.renderer.options.context.format; } this.size = this.options.fixedSize ? { width: this.options.fixedSize.width * this.options.qualityRatio, @@ -4684,7 +4981,7 @@ depth: this.options.viewDimension.indexOf("cube") !== -1 ? 6 : 1 }; if (this.options.fixedSize) { - __privateSet$d(this, _autoResize, false); + __privateSet$f(this, _autoResize, false); } this.setBindings(); this.renderer.addTexture(this); @@ -4818,7 +5115,7 @@ * @param size - the optional new {@link TextureSize | size} to set */ resize(size = null) { - if (!__privateGet$d(this, _autoResize)) + if (!__privateGet$f(this, _autoResize)) return; if (!size) { size = { @@ -4888,6 +5185,14 @@ this.setTextures(); this.setSamplers(); } + /** + * Set or reset this {@link Material} {@link renderer}. + * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + renderer = isRenderer(renderer, this.type); + this.renderer = renderer; + } /** * Check if all bind groups are ready, and create them if needed */ @@ -5402,18 +5707,16 @@ dispatchSize = [Math.ceil(dispatchSize), 1, 1]; } this.dispatchSize = dispatchSize; + } + /** + * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link ComputePipelineEntry} from cache or if we should create a new one. + */ + setPipelineEntry() { this.pipelineEntry = this.renderer.pipelineManager.createComputePipeline({ renderer: this.renderer, label: this.options.label + " compute pipeline", shaders: this.options.shaders, - useAsync: this.options.useAsyncPipeline - }); - } - /** - * When all bind groups are created, add them to the {@link ComputePipelineEntry} - */ - setPipelineEntryProperties() { - this.pipelineEntry.setPipelineEntryProperties({ + useAsync: this.options.useAsyncPipeline, bindGroups: this.bindGroups }); } @@ -5432,8 +5735,10 @@ if (this.ready) return; super.compileMaterial(); + if (!this.pipelineEntry) { + this.setPipelineEntry(); + } if (this.pipelineEntry && this.pipelineEntry.canCompile) { - this.setPipelineEntryProperties(); await this.compilePipelineEntry(); } } @@ -5525,22 +5830,22 @@ } } - var __accessCheck$d = (obj, member, msg) => { + var __accessCheck$g = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$c = (obj, member, getter) => { - __accessCheck$d(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + var __privateGet$e = (obj, member, getter) => { + __accessCheck$g(obj, member, "read from private field"); + return member.get(obj); }; - var __privateAdd$d = (obj, member, value) => { + var __privateAdd$g = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$c = (obj, member, value, setter) => { - __accessCheck$d(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$e = (obj, member, value, setter) => { + __accessCheck$g(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _autoRender$2; @@ -5556,7 +5861,7 @@ * Whether this {@link ComputePass} should be added to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically * @private */ - __privateAdd$d(this, _autoRender$2, true); + __privateAdd$g(this, _autoRender$2, true); // callbacks / events /** function assigned to the {@link onReady} callback */ this._onReadyCallback = () => { @@ -5607,7 +5912,7 @@ }; this.renderOrder = renderOrder ?? 0; if (autoRender !== void 0) { - __privateSet$c(this, _autoRender$2, autoRender); + __privateSet$e(this, _autoRender$2, autoRender); } this.userData = {}; this.ready = false; @@ -5623,7 +5928,7 @@ useAsyncPipeline, dispatchSize }); - this.addToScene(); + this.addToScene(true); } /** * Get or set whether the compute pass is ready to render (the material has been successfully compiled) @@ -5639,22 +5944,45 @@ this._ready = value; } /** - * Add our compute pass to the scene and the renderer + * Add our {@link ComputePass} to the scene and optionally to the renderer. + * @param addToRenderer - whether to add this {@link ComputePass} to the {@link Renderer#computePasses | Renderer computePasses array} */ - addToScene() { - this.renderer.computePasses.push(this); - if (__privateGet$c(this, _autoRender$2)) { + addToScene(addToRenderer = false) { + if (addToRenderer) { + this.renderer.computePasses.push(this); + } + if (__privateGet$e(this, _autoRender$2)) { this.renderer.scene.addComputePass(this); } } /** - * Remove our compute pass from the scene and the renderer + * Remove our {@link ComputePass} from the scene and optionally from the renderer as well. + * @param removeFromRenderer - whether to remove this {@link ComputePass} from the {@link Renderer#computePasses | Renderer computePasses array}. */ - removeFromScene() { - if (__privateGet$c(this, _autoRender$2)) { + removeFromScene(removeFromRenderer = false) { + if (__privateGet$e(this, _autoRender$2)) { this.renderer.scene.removeComputePass(this); } - this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); + if (removeFromRenderer) { + this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); + } + } + /** + * Set a new {@link Renderer} for this {@link ComputePass}. + * @param renderer - new {@link Renderer} to set. + */ + setRenderer(renderer) { + renderer = renderer && renderer.renderer || renderer; + if (!renderer || !(renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer")) { + throwWarning( + `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.` + ); + return; + } + this.material?.setRenderer(renderer); + this.removeFromScene(true); + this.renderer = renderer; + this.addToScene(true); } /** * Create the compute pass material @@ -5881,7 +6209,7 @@ * Remove the ComputePass from the scene and destroy it */ remove() { - this.removeFromScene(); + this.removeFromScene(true); this.destroy(); } /** @@ -6028,6 +6356,12 @@ this.modelViewProjectionMatrix = modelViewProjectionMatrix; this.containerBoundingRect = containerBoundingRect; this.DOMFrustumMargins = { ...defaultDOMFrustumMargins, ...DOMFrustumMargins }; + this.clipSpaceBoundingRect = { + top: 0, + left: 0, + width: 0, + height: 0 + }; this.projectedBoundingRect = { top: 0, right: 0, @@ -6073,6 +6407,12 @@ */ setDocumentCoordsFromClipSpaceOBB() { this.computeClipSpaceOBB(); + this.clipSpaceBoundingRect = { + top: this.clipSpaceOBB.max.y, + left: this.clipSpaceOBB.min.x, + width: this.clipSpaceOBB.max.x - this.clipSpaceOBB.min.x, + height: this.clipSpaceOBB.max.y - this.clipSpaceOBB.min.y + }; const minX = (this.clipSpaceOBB.min.x + 1) * 0.5; const maxX = (this.clipSpaceOBB.max.x + 1) * 0.5; const minY = 1 - (this.clipSpaceOBB.min.y + 1) * 0.5; @@ -6094,15 +6434,21 @@ * @param boundingSphere - bounding sphere in clip space. */ setDocumentCoordsFromClipSpaceSphere(boundingSphere = { center: new Vec3(), radius: 0 }) { + this.clipSpaceBoundingRect = { + top: boundingSphere.center.y + boundingSphere.radius, + left: boundingSphere.center.x - boundingSphere.radius, + width: boundingSphere.radius * 2, + height: boundingSphere.radius * 2 + }; const centerX = (boundingSphere.center.x + 1) * 0.5; const centerY = 1 - (boundingSphere.center.y + 1) * 0.5; const { width, height, top, left } = this.containerBoundingRect; - this.projectedBoundingRect.width = boundingSphere.radius * height * 0.5; - this.projectedBoundingRect.height = boundingSphere.radius * height * 0.5; + this.projectedBoundingRect.width = boundingSphere.radius * height; + this.projectedBoundingRect.height = boundingSphere.radius * height; this.projectedBoundingRect.left = centerX * width + left - this.projectedBoundingRect.width * 0.5; - this.projectedBoundingRect.x = centerX * width + left - this.projectedBoundingRect.width * 0.5; + this.projectedBoundingRect.x = this.projectedBoundingRect.left; this.projectedBoundingRect.top = centerY * height + top - this.projectedBoundingRect.height * 0.5; - this.projectedBoundingRect.y = centerY * height + top - this.projectedBoundingRect.height * 0.5; + this.projectedBoundingRect.y = this.projectedBoundingRect.top; this.projectedBoundingRect.right = this.projectedBoundingRect.left + this.projectedBoundingRect.width; this.projectedBoundingRect.bottom = this.projectedBoundingRect.top + this.projectedBoundingRect.height; } @@ -6325,9 +6671,11 @@ */ getAttributeByName(name) { let attribute; - this.vertexBuffers.forEach((vertexBuffer) => { + for (const vertexBuffer of this.vertexBuffers) { attribute = vertexBuffer.attributes.find((attribute2) => attribute2.name === name); - }); + if (attribute) + break; + } return attribute; } /** @@ -6720,22 +7068,22 @@ } } - var __accessCheck$c = (obj, member, msg) => { + var __accessCheck$f = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$b = (obj, member, getter) => { - __accessCheck$c(obj, member, "read from private field"); + var __privateGet$d = (obj, member, getter) => { + __accessCheck$f(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; - var __privateAdd$c = (obj, member, value) => { + var __privateAdd$f = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$b = (obj, member, value, setter) => { - __accessCheck$c(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$d = (obj, member, value, setter) => { + __accessCheck$f(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _intensity$1, _intensityColor; @@ -6745,32 +7093,49 @@ * @param renderer - {@link CameraRenderer} used to create this {@link Light}. * @param parameters - {@link LightParams | parameters} used to create this {@link Light}. */ - constructor(renderer, { color = new Vec3(1), intensity = 1, index = 0, type = "lights" } = {}) { + constructor(renderer, { color = new Vec3(1), intensity = 1, type = "lights" } = {}) { super(); /** @ignore */ - __privateAdd$c(this, _intensity$1, void 0); + __privateAdd$f(this, _intensity$1, void 0); /** * A {@link Vec3} holding the {@link Light} {@link color} multiplied by its {@link intensity}. * @private */ - __privateAdd$c(this, _intensityColor, void 0); + __privateAdd$f(this, _intensityColor, void 0); this.type = type; - Object.defineProperty(this, "index", { value: index }); - renderer = isCameraRenderer(renderer, this.constructor.name); - this.renderer = renderer; - this.setRendererBinding(); + this.setRenderer(renderer); this.uuid = generateUUID(); this.options = { color, intensity }; this.color = color; - __privateSet$b(this, _intensityColor, this.color.clone()); + __privateSet$d(this, _intensityColor, this.color.clone()); this.color.onChange( - () => this.onPropertyChanged("color", __privateGet$b(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)) + () => this.onPropertyChanged("color", __privateGet$d(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)) ); this.intensity = intensity; + } + /** + * Set or reset this light {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + const hasRenderer = !!this.renderer; + if (this.renderer) { + this.renderer.removeLight(this); + } + renderer = isCameraRenderer(renderer, this.constructor.name); + this.renderer = renderer; + this.index = this.renderer.lights.filter((light) => light.type === this.type).length; + if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { + this.onMaxLightOverflow(this.type); + } this.renderer.addLight(this); + this.setRendererBinding(); + if (hasRenderer) { + this.reset(); + } } /** * Set or reset this {@link Light} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. @@ -6785,22 +7150,22 @@ */ reset() { this.setRendererBinding(); - this.onPropertyChanged("color", __privateGet$b(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)); + this.onPropertyChanged("color", __privateGet$d(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)); } /** * Get this {@link Light} intensity. * @returns - The {@link Light} intensity. */ get intensity() { - return __privateGet$b(this, _intensity$1); + return __privateGet$d(this, _intensity$1); } /** * Set this {@link Light} intensity and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. * @param value - The new {@link Light} intensity. */ set intensity(value) { - __privateSet$b(this, _intensity$1, value); - this.onPropertyChanged("color", __privateGet$b(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)); + __privateSet$d(this, _intensity$1, value); + this.onPropertyChanged("color", __privateGet$d(this, _intensityColor).copy(this.color).multiplyScalar(this.intensity)); } /** * Update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} input value and tell the {@link CameraRenderer#cameraLightsBindGroup | renderer camera, lights and shadows} bind group to update. @@ -6825,22 +7190,23 @@ * @param lightsType - {@link type} of light. */ onMaxLightOverflow(lightsType) { + this.renderer.onMaxLightOverflow(lightsType); if (this.rendererBinding) { - this.renderer.onMaxLightOverflow(lightsType); this.rendererBinding = this.renderer.bindings[lightsType]; } } /** - * Remove this {@link Light} from the {@link renderer}. + * Remove this {@link Light} from the {@link renderer} and destroy it. */ remove() { this.renderer.removeLight(this); + this.destroy(); } /** * Destroy this {@link Light}. */ destroy() { - this.parent = null; + super.destroy(); } } _intensity$1 = new WeakMap(); @@ -6854,13 +7220,7 @@ */ constructor(renderer, { color = new Vec3(1), intensity = 0.1 } = {}) { const type = "ambientLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; + super(renderer, { color, intensity, type }); } // explicitly disable all kinds of transformations /** @ignore */ @@ -6908,7 +7268,7 @@ loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0], - targetFormat: this.renderer.options.preferredFormat + targetFormat: this.renderer.options.context.format }; if (!colorAttachments.length) { colorAttachments = [defaultColorAttachment]; @@ -7103,13 +7463,13 @@ } /** * Set our {@link GPUColor | clear colors value}.
- * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURenderer#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel. + * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURendererContextOptions#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel. * @param clearValue - new {@link GPUColor | clear colors value} to use * @param colorAttachmentIndex - index of the color attachment for which to use this clear value */ setClearValue(clearValue = [0, 0, 0, 0], colorAttachmentIndex = 0) { if (this.options.useColorAttachments) { - if (this.renderer.alphaMode === "premultiplied") { + if (this.renderer.options.context.alphaMode === "premultiplied") { const alpha = clearValue[3]; clearValue[0] = Math.min(clearValue[0], alpha); clearValue[1] = Math.min(clearValue[1], alpha); @@ -7164,22 +7524,22 @@ } } - var __accessCheck$b = (obj, member, msg) => { + var __accessCheck$e = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$a = (obj, member, getter) => { - __accessCheck$b(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + var __privateGet$c = (obj, member, getter) => { + __accessCheck$e(obj, member, "read from private field"); + return member.get(obj); }; - var __privateAdd$b = (obj, member, value) => { + var __privateAdd$e = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$a = (obj, member, value, setter) => { - __accessCheck$b(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$c = (obj, member, value, setter) => { + __accessCheck$e(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _autoRender$1; @@ -7191,7 +7551,7 @@ */ constructor(renderer, parameters = {}) { /** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */ - __privateAdd$b(this, _autoRender$1, true); + __privateAdd$e(this, _autoRender$1, true); renderer = isRenderer(renderer, "RenderTarget"); this.type = "RenderTarget"; this.renderer = renderer; @@ -7206,7 +7566,7 @@ autoRender: autoRender === void 0 ? true : autoRender }; if (autoRender !== void 0) { - __privateSet$a(this, _autoRender$1, autoRender); + __privateSet$c(this, _autoRender$1, autoRender); } this.renderPass = new RenderPass(this.renderer, { label: this.options.label ? `${this.options.label} Render Pass` : "Render Target Render Pass", @@ -7218,7 +7578,7 @@ this.renderTexture = new Texture(this.renderer, { label: this.options.label ? `${this.options.label} Render Texture` : "Render Target render texture", name: "renderTexture", - format: colorAttachments && colorAttachments.length && colorAttachments[0].targetFormat ? colorAttachments[0].targetFormat : this.renderer.options.preferredFormat, + format: colorAttachments && colorAttachments.length && colorAttachments[0].targetFormat ? colorAttachments[0].targetFormat : this.renderer.options.context.format, ...this.options.qualityRatio !== void 0 && { qualityRatio: this.options.qualityRatio }, usage: ["copySrc", "renderAttachment", "textureBinding"] }); @@ -7242,7 +7602,7 @@ */ addToScene() { this.renderer.renderTargets.push(this); - if (__privateGet$a(this, _autoRender$1)) { + if (__privateGet$c(this, _autoRender$1)) { this.renderer.scene.addRenderTarget(this); } } @@ -7250,7 +7610,7 @@ * Remove the {@link RenderTarget} from the renderer and the {@link core/scenes/Scene.Scene | Scene} */ removeFromScene() { - if (__privateGet$a(this, _autoRender$1)) { + if (__privateGet$c(this, _autoRender$1)) { this.renderer.scene.removeRenderTarget(this); } this.renderer.renderTargets = this.renderer.renderTargets.filter((renderTarget) => renderTarget.uuid !== this.uuid); @@ -7292,2894 +7652,2406 @@ } _autoRender$1 = new WeakMap(); - const compareRenderingOptions = (newOptions = {}, baseOptions = {}) => { - return Object.keys(newOptions).filter((key) => { - if (Array.isArray(newOptions[key])) { - return JSON.stringify(newOptions[key]) !== JSON.stringify(baseOptions[key]); - } else { - return newOptions[key] !== baseOptions[key]; - } - }); - }; - - var default_projected_vsWgsl = ( - /* wgsl */ - ` -struct VSOutput { - @builtin(position) position: vec4f, - @location(0) uv: vec2f, - @location(1) normal: vec3f, - @location(2) worldPosition: vec3f, - @location(3) viewDirection: vec3f, -}; - -@vertex fn main( - attributes: Attributes, -) -> VSOutput { - var vsOutput: VSOutput; - - vsOutput.position = getOutputPosition(attributes.position); - vsOutput.uv = attributes.uv; - vsOutput.normal = getWorldNormal(attributes.normal); - vsOutput.worldPosition = getWorldPosition(attributes.position).xyz; - vsOutput.viewDirection = camera.position - vsOutput.worldPosition; - - return vsOutput; -}` - ); - - var default_vsWgsl = ( - /* wgsl */ - ` -struct VSOutput { - @builtin(position) position: vec4f, - @location(0) uv: vec2f, -}; - -@vertex fn main( - attributes: Attributes, -) -> VSOutput { - var vsOutput: VSOutput; - - vsOutput.position = vec4f(attributes.position, 1.0); - vsOutput.uv = attributes.uv; - - return vsOutput; -}` - ); - - var default_fsWgsl = ( - /* wgsl */ - ` -@fragment fn main() -> @location(0) vec4f { - return vec4(0.0, 0.0, 0.0, 1.0); -}` - ); - - class RenderMaterial extends Material { + let pipelineId = 0; + class PipelineEntry { /** - * RenderMaterial constructor - * @param renderer - our renderer class object - * @param parameters - {@link RenderMaterialParams | parameters} used to create our RenderMaterial + * PipelineEntry constructor + * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link PipelineEntry} */ - constructor(renderer, parameters) { - const type = "RenderMaterial"; - renderer = isRenderer(renderer, type); - if (!parameters.shaders) { - parameters.shaders = {}; - } - if (!parameters.shaders?.vertex) { - parameters.shaders.vertex = { - code: parameters.useProjection ? default_projected_vsWgsl : default_vsWgsl, - entryPoint: "main" - }; - } - if (!parameters.shaders.vertex.entryPoint) { - parameters.shaders.vertex.entryPoint = "main"; - } - if (parameters.shaders.fragment === void 0) { - parameters.shaders.fragment = { - entryPoint: "main", - code: default_fsWgsl - }; - } - super(renderer, parameters); - this.type = type; + constructor(parameters) { + this.type = "PipelineEntry"; + let { renderer } = parameters; + const { label, shaders, useAsync, bindGroups, cacheKey } = parameters; + renderer = isRenderer(renderer, label ? label + " " + this.type : this.type); this.renderer = renderer; - const { shaders } = parameters; - const { - useProjection, - transparent, - depth, - depthWriteEnabled, - depthCompare, - depthFormat, - cullMode, - sampleCount, - verticesOrder, - topology - } = parameters; - let { targets } = parameters; - if (targets === void 0) { - targets = [ - { - format: this.renderer.options.preferredFormat - } - ]; - } - if (targets && targets.length && !targets[0].format) { - targets[0].format = this.renderer.options.preferredFormat; - } + Object.defineProperty(this, "index", { value: pipelineId++ }); + this.layout = null; + this.pipeline = null; + this.status = { + compiling: false, + compiled: false, + error: null + }; this.options = { - ...this.options, + label, shaders, - rendering: { - useProjection, - transparent, - depth, - depthWriteEnabled, - depthCompare, - depthFormat, - cullMode, - sampleCount, - targets, - verticesOrder, - topology - } + useAsync: useAsync !== void 0 ? useAsync : true, + bindGroups, + cacheKey }; - this.attributes = null; - this.pipelineEntry = null; } /** - * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link RenderPipelineEntry} from cache or if we should create a new one. + * Get whether the {@link pipeline} is ready, i.e. successfully compiled + * @readonly */ - setPipelineEntry() { - this.pipelineEntry = this.renderer.pipelineManager.createRenderPipeline({ - renderer: this.renderer, - label: this.options.label + " render pipeline", - shaders: this.options.shaders, - useAsync: this.options.useAsyncPipeline, - rendering: this.options.rendering, - attributes: this.attributes, - bindGroups: this.bindGroups - }); + get ready() { + return !this.status.compiling && this.status.compiled && !this.status.error; } /** - * Compile the {@link RenderPipelineEntry} - * @async + * Get whether the {@link pipeline} is ready to be compiled, i.e. we have not already tried to compile it, and it's not currently compiling neither + * @readonly */ - async compilePipelineEntry() { - await this.pipelineEntry.compilePipelineEntry(); + get canCompile() { + return !this.status.compiling && !this.status.compiled && !this.status.error; } /** - * Check if attributes and all bind groups are ready, create them if needed, set {@link RenderPipelineEntry} bind group buffers and compile the pipeline. - * @async + * Set {@link PipelineEntry} properties (in this case the {@link bindGroups | bind groups}) + * @param parameters - the {@link bindGroups | bind groups} to use */ - async compileMaterial() { - if (this.ready) - return; - super.compileMaterial(); - if (this.attributes && !this.pipelineEntry) { - this.setPipelineEntry(); - } - if (this.pipelineEntry && this.pipelineEntry.canCompile) { - await this.compilePipelineEntry(); - } + setPipelineEntryProperties(parameters) { + const { bindGroups } = parameters; + this.setPipelineEntryBindGroups(bindGroups); } /** - * Set or reset one of the {@link RenderMaterialRenderingOptions | rendering options}. Should be use with great caution, because if the {@link RenderPipelineEntry#pipeline | render pipeline} has already been compiled, it can cause a pipeline flush. - * @param renderingOptions - new {@link RenderMaterialRenderingOptions | rendering options} properties to be set + * Set our {@link PipelineEntry#bindGroups | pipeline entry bind groups} + * @param bindGroups - {@link core/materials/Material.Material#bindGroups | bind groups} to use with this {@link PipelineEntry} */ - setRenderingOptions(renderingOptions = {}) { - const newProperties = compareRenderingOptions(renderingOptions, this.options.rendering); - const oldRenderingOptions = { ...this.options.rendering }; - this.options.rendering = { ...this.options.rendering, ...renderingOptions }; - if (this.pipelineEntry) { - if (this.pipelineEntry.ready && newProperties.length) { - if (!this.renderer.production) { - const oldProps = newProperties.map((key) => { - return { - [key]: Array.isArray(oldRenderingOptions[key]) ? oldRenderingOptions[key].map((optKey) => optKey) : oldRenderingOptions[key] - }; - }); - const newProps = newProperties.map((key) => { - return { - [key]: Array.isArray(renderingOptions[key]) ? renderingOptions[key].map((optKey) => optKey) : renderingOptions[key] - }; - }); - throwWarning( - `${this.options.label}: the change of rendering options is causing this RenderMaterial pipeline to be recompiled. This should be avoided. - -Old rendering options: ${JSON.stringify( - oldProps.reduce((acc, v) => { - return { ...acc, ...v }; - }, {}), - null, - 4 + setPipelineEntryBindGroups(bindGroups) { + this.bindGroups = bindGroups; + } + /* SHADERS */ + /** + * Create a {@link GPUShaderModule} + * @param parameters - Parameters used + * @param parameters.code - patched WGSL code string + * @param parameters.type - {@link MaterialShadersType | shader type} + * @returns - compiled {@link GPUShaderModule} if successful + */ + createShaderModule({ code = "", type = "vertex" }) { + const shaderModule = this.renderer.createShaderModule({ + label: this.options.label + ": " + type + " shader module", + code + }); + if ("getCompilationInfo" in shaderModule && !this.renderer.production) { + shaderModule.getCompilationInfo().then((compilationInfo) => { + for (const message of compilationInfo.messages) { + let formattedMessage = ""; + if (message.lineNum) { + formattedMessage += `Line ${message.lineNum}:${message.linePos} - ${code.substring( + message.offset, + message.offset + message.length )} - --------- - -New rendering options: ${JSON.stringify( - newProps.reduce((acc, v) => { - return { ...acc, ...v }; - }, {}), - null, - 4 - )}` - ); +`; + } + formattedMessage += message.message; + switch (message.type) { + case "error": + console.error(`${this.options.label} compilation error: +${formattedMessage}`); + break; + case "warning": + console.warn(`${this.options.label} compilation warning: +${formattedMessage}`); + break; + case "info": + console.log(`${this.options.label} compilation information: +${formattedMessage}`); + break; + } } - this.setPipelineEntry(); - } else { - this.pipelineEntry.options.rendering = { ...this.pipelineEntry.options.rendering, ...this.options.rendering }; - } + }); } + return shaderModule; } - /* ATTRIBUTES */ + /* SETUP */ /** - * Compute geometry if needed and get all useful geometry properties needed to create attributes buffers - * @param geometry - the geometry to draw + * Create the {@link PipelineEntry} shaders */ - setAttributesFromGeometry(geometry) { - this.attributes = { - wgslStructFragment: geometry.wgslStructFragment, - vertexBuffers: geometry.vertexBuffers, - layoutCacheKey: geometry.layoutCacheKey - }; + createShaders() { } - /* BIND GROUPS */ /** - * Get whether this {@link RenderMaterial} uses the renderer camera and lights bind group. - * @readonly - * */ - get useCameraBindGroup() { - return "cameraLightsBindGroup" in this.renderer && this.options.rendering.useProjection; + * Create the pipeline entry {@link layout} + */ + createPipelineLayout() { + this.layout = this.renderer.createPipelineLayout({ + label: this.options.label + " layout", + bindGroupLayouts: this.bindGroups.map((bindGroup) => bindGroup.bindGroupLayout) + }); } /** - * Create the bind groups if they need to be created, but first add camera and lights bind group if needed. + * Create the {@link PipelineEntry} descriptor */ - createBindGroups() { - if (this.useCameraBindGroup) { - this.bindGroups.push(this.renderer.cameraLightsBindGroup); - this.renderer.cameraLightsBindGroup.consumers.add(this.uuid); - } - super.createBindGroups(); + createPipelineDescriptor() { } /** - * Update all bind groups, except for the camera and light bind groups if present, as it is already updated by the renderer itself. + * Flush a {@link PipelineEntry}, i.e. reset its {@link bindGroups | bind groups}, {@link layout} and descriptor and recompile the {@link pipeline} + * Used when one of the bind group or rendering property has changed + * @param newBindGroups - new {@link bindGroups | bind groups} in case they have changed */ - updateBindGroups() { - const startBindGroupIndex = this.useCameraBindGroup ? 1 : 0; - for (let i = startBindGroupIndex; i < this.bindGroups.length; i++) { - this.updateBindGroup(this.bindGroups[i]); - } + flushPipelineEntry(newBindGroups = []) { + this.status.compiling = false; + this.status.compiled = false; + this.status.error = null; + this.setPipelineEntryBindGroups(newBindGroups); + this.compilePipelineEntry(); + } + /** + * Set up a {@link pipeline} by creating the shaders, the {@link layout} and the descriptor + */ + compilePipelineEntry() { + this.status.compiling = true; + this.createShaders(); + this.createPipelineLayout(); + this.createPipelineDescriptor(); } } - const getPositionAndNormal = (hasInstances = false) => { - if (hasInstances) { - return ( - /* wgsl */ - ` - var worldPosition: vec4f = instances[attributes.instanceIndex].modelMatrix * vec4f(attributes.position, 1.0); - let normal = (instances[attributes.instanceIndex].normalMatrix * vec4(attributes.normal, 0.0)).xyz; - ` - ); - } else { - return ( - /* wgsl */ - ` - var worldPosition: vec4f = matrices.model * vec4(attributes.position, 1.0); - let normal = getWorldNormal(attributes.normal); - ` - ); - } - }; - const getDefaultShadowDepthVs = (lightIndex = 0, hasInstances = false) => ( + var get_output_position = ( /* wgsl */ ` -@vertex fn main( - attributes: Attributes, -) -> @builtin(position) vec4f { - let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[${lightIndex}]; - - ${getPositionAndNormal(hasInstances)} - - let lightDirection: vec3f = normalize(worldPosition.xyz - directionalLights.elements[${lightIndex}].direction); - let NdotL: f32 = dot(normalize(normal), lightDirection); - let sinNdotL = sqrt(1.0 - NdotL * NdotL); - let normalBias: f32 = directionalShadow.normalBias * sinNdotL; - - worldPosition = vec4(worldPosition.xyz - normal * normalBias, worldPosition.w); - - return directionalShadow.projectionMatrix * directionalShadow.viewMatrix * worldPosition; +fn getWorldPosition(position: vec3f) -> vec4f { + return matrices.model * vec4f(position, 1.0); +} + +fn getOutputPosition(position: vec3f) -> vec4f { + return camera.projection * matrices.modelView * vec4f(position, 1.0); }` ); - const getPCFShadowContribution = ( + + var get_normals = ( /* wgsl */ ` -fn getPCFShadowContribution(index: i32, worldPosition: vec3f, depthTexture: texture_depth_2d) -> f32 { - let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[index]; - - // get shadow coords - var shadowCoords: vec3f = vec3((directionalShadow.projectionMatrix * directionalShadow.viewMatrix * vec4(worldPosition, 1.0)).xyz); - - // Convert XY to (0, 1) - // Y is flipped because texture coords are Y-down. - shadowCoords = vec3( - shadowCoords.xy * vec2(0.5, -0.5) + vec2(0.5), - shadowCoords.z - ); - - // Percentage-closer filtering. Sample texels in the region - // to smooth the result. - var visibility = 0.0; - - let size: vec2f = vec2f(textureDimensions(depthTexture).xy); - - let texelSize: vec2f = 1.0 / size; - - let sampleCount: i32 = directionalShadow.pcfSamples; - let maxSamples: f32 = f32(sampleCount) - 1.0; - - for (var x = 0; x < sampleCount; x++) { - for (var y = 0; y < sampleCount; y++) { - let offset = texelSize * vec2( - f32(x) - maxSamples * 0.5, - f32(y) - maxSamples * 0.5 - ); - - visibility += textureSampleCompare( - depthTexture, - depthComparisonSampler, - shadowCoords.xy + offset, - shadowCoords.z - directionalShadow.bias - ); - } - } - visibility /= f32(sampleCount * sampleCount); - - visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); - - let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; - let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; - - return select(1.0, visibility, frustumTest); +fn getWorldNormal(normal: vec3f) -> vec3f { + return normalize(matrices.normal * normal); } -` + +fn getViewNormal(normal: vec3f) -> vec3f { + return normalize((camera.view * vec4(matrices.normal * normal, 0.0)).xyz); +}` ); - const getPCFDirectionalShadows = (renderer) => { - const directionalLights = renderer.shadowCastingLights.filter( - (light) => light.type === "directionalLights" - ); - return ( - /* wgsl */ - ` -fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { - var directionalShadowContribution: array; - - var lightDirection: vec3f; - - ${directionalLights.map((light, index) => { - return `lightDirection = worldPosition - directionalLights.elements[${index}].direction; - - ${light.shadow.isActive ? `directionalShadowContribution[${index}] = select( 1.0, getPCFShadowContribution(${index}, worldPosition, shadowDepthTexture${index}), directionalShadows.directionalShadowsElements[${index}].isActive > 0);` : ""}`; - }).join("\n")} - - return directionalShadowContribution; -} -` - ); - }; - const getDefaultPointShadowDepthVs = (lightIndex = 0, hasInstances = false) => ( + + var get_uv_cover = ( /* wgsl */ ` -struct PointShadowVSOutput { - @builtin(position) position: vec4f, - @location(0) worldPosition: vec3f, -} - -@vertex fn main( - attributes: Attributes, -) -> PointShadowVSOutput { - var pointShadowVSOutput: PointShadowVSOutput; - - ${getPositionAndNormal(hasInstances)} - - let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[${lightIndex}]; - - let lightDirection: vec3f = normalize(pointLights.elements[${lightIndex}].position - worldPosition.xyz); - let NdotL: f32 = dot(normalize(normal), lightDirection); - let sinNdotL = sqrt(1.0 - NdotL * NdotL); - let normalBias: f32 = pointShadow.normalBias * sinNdotL; - - worldPosition = vec4(worldPosition.xyz - normal * normalBias, worldPosition.w); - - var position: vec4f = pointShadow.projectionMatrix * pointShadow.viewMatrices[pointShadow.face] * worldPosition; - - pointShadowVSOutput.position = position; - pointShadowVSOutput.worldPosition = worldPosition.xyz; - - return pointShadowVSOutput; +fn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f { + return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy; }` ); - const getDefaultPointShadowDepthFs = (lightIndex = 0) => ( + + var get_vertex_to_uv_coords = ( /* wgsl */ ` -struct PointShadowVSOutput { - @builtin(position) position: vec4f, - @location(0) worldPosition: vec3f, +fn getVertex2DToUVCoords(vertex: vec2f) -> vec2f { + return vec2( + vertex.x * 0.5 + 0.5, + 0.5 - vertex.y * 0.5 + ); } -@fragment fn main(fsInput: PointShadowVSOutput) -> @builtin(frag_depth) f32 { - // get distance between fragment and light source - var lightDistance: f32 = length(fsInput.worldPosition - pointLights.elements[${lightIndex}].position); - - let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[${lightIndex}]; - - // map to [0, 1] range by dividing by far plane - near plane - lightDistance = (lightDistance - pointShadow.cameraNear) / (pointShadow.cameraFar - pointShadow.cameraNear); - - // write this as modified depth - return clamp(lightDistance, 0.0, 1.0); -}` +fn getVertex3DToUVCoords(vertex: vec3f) -> vec2f { + return getVertex2DToUVCoords( vec2(vertex.x, vertex.y) ); +} +` ); - const getPCFPointShadowContribution = ( - /* wgsl */ - ` -fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTexture: texture_depth_cube) -> f32 { - let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[index]; - // Percentage-closer filtering. Sample texels in the region - // to smooth the result. - var visibility = 0.0; - var closestDepth = 0.0; - let currentDepth: f32 = shadowPosition.w; - let cameraRange: f32 = pointShadow.cameraFar - pointShadow.cameraNear; - let normalizedDepth: f32 = (shadowPosition.w - pointShadow.cameraNear) / cameraRange; + const ShaderChunks = { + /** WGSL code chunks added to the vertex shader */ + vertex: { + /** Applies given texture matrix to given uv coordinates */ + get_uv_cover + }, + /** WGSL code chunks added to the fragment shader */ + fragment: { + /** Applies given texture matrix to given uv coordinates */ + get_uv_cover, + /** Convert vertex position to uv coordinates */ + get_vertex_to_uv_coords + } + }; + const ProjectedShaderChunks = { + /** WGSL code chunks added to the vertex shader */ + vertex: { + /** Get output vec4f position vector by applying model view projection matrix to vec3f attribute position vector */ + get_output_position, + /** Get vec3f normals in world or view space */ + get_normals + }, + /** WGSL code chunks added to the fragment shader */ + fragment: {} + }; - let maxSize: f32 = f32(max(textureDimensions(depthCubeTexture).x, textureDimensions(depthCubeTexture).y)); - - let texelSize: vec3f = vec3(1.0 / maxSize); - let sampleCount: i32 = pointShadow.pcfSamples; - let maxSamples: f32 = f32(sampleCount) - 1.0; - - for (var x = 0; x < sampleCount; x++) { - for (var y = 0; y < sampleCount; y++) { - for (var z = 0; z < sampleCount; z++) { - let offset = texelSize * vec3( - f32(x) - maxSamples * 0.5, - f32(y) - maxSamples * 0.5, - f32(z) - maxSamples * 0.5 - ); - - closestDepth = textureSampleCompare( - depthCubeTexture, - depthComparisonSampler, - shadowPosition.xyz + offset, - normalizedDepth - pointShadow.bias - ); - - closestDepth *= cameraRange; - if(currentDepth <= closestDepth) { - visibility += 1.0; - } - } - } - } - - visibility /= f32(sampleCount * sampleCount * sampleCount); - - visibility = clamp(visibility, 1.0 - clamp(pointShadow.intensity, 0.0, 1.0), 1.0); - - return visibility; -}` - ); - const getPCFPointShadows = (renderer) => { - const pointLights = renderer.shadowCastingLights.filter((light) => light.type === "pointLights"); - return ( - /* wgsl */ - ` -fn getPCFPointShadows(worldPosition: vec3f) -> array { - var pointShadowContribution: array; - - var lightDirection: vec3f; - - ${pointLights.map((light, index) => { - return `lightDirection = pointLights.elements[${index}].position - worldPosition; - - ${light.shadow.isActive ? `pointShadowContribution[${index}] = select( 1.0, getPCFPointShadowContribution(${index}, vec4(lightDirection, length(lightDirection)), pointShadowCubeDepthTexture${index}), pointShadows.pointShadowsElements[${index}].isActive > 0);` : ""}`; - }).join("\n")} - - return pointShadowContribution; -} -` - ); - }; - const getPCFShadows = ( - /* wgsl */ - ` - let pointShadows = getPCFPointShadows(worldPosition); - let directionalShadows = getPCFDirectionalShadows(worldPosition); -` - ); - const applyDirectionalShadows = ( - /* wgsl */ - ` - directLight.color *= directionalShadows[i]; -` - ); - const applyPointShadows = ( - /* wgsl */ - ` - if(directLight.visible) { - directLight.color *= pointShadows[i]; - } -` - ); - - var __accessCheck$a = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$9 = (obj, member, getter) => { - __accessCheck$a(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$a = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$9 = (obj, member, value, setter) => { - __accessCheck$a(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; - }; - var __privateMethod$2 = (obj, member, method) => { - __accessCheck$a(obj, member, "access private method"); - return method; - }; - var _intensity, _bias, _normalBias, _pcfSamples, _isActive, _autoRender, _materials, _depthMaterials, _depthPassTaskID, _setParameters, setParameters_fn; - const shadowStruct = { - isActive: { - type: "i32", - value: 0 - }, - pcfSamples: { - type: "i32", - value: 0 - }, - bias: { - type: "f32", - value: 0 - }, - normalBias: { - type: "f32", - value: 0 - }, - intensity: { - type: "f32", - value: 0 - } - }; - class Shadow { + class RenderPipelineEntry extends PipelineEntry { /** - * Shadow constructor - * @param renderer - {@link CameraRenderer} used to create this {@link Shadow}. - * @param parameters - {@link ShadowBaseParams | parameters} used to create this {@link Shadow}. + * RenderPipelineEntry constructor + * @param parameters - {@link RenderPipelineEntryParams | parameters} used to create this {@link RenderPipelineEntry} */ - constructor(renderer, { - light, - intensity = 1, - bias = 0, - normalBias = 0, - pcfSamples = 1, - depthTextureSize = new Vec2(512), - depthTextureFormat = "depth24plus", - autoRender = true - } = {}) { - /** - * Set the {@link Shadow} parameters. - * @param parameters - parameters to use for this {@link Shadow}. - * @private - */ - __privateAdd$a(this, _setParameters); - /** @ignore */ - __privateAdd$a(this, _intensity, void 0); - /** @ignore */ - __privateAdd$a(this, _bias, void 0); - /** @ignore */ - __privateAdd$a(this, _normalBias, void 0); - /** @ignore */ - __privateAdd$a(this, _pcfSamples, void 0); - /** @ignore */ - __privateAdd$a(this, _isActive, void 0); - /** @ignore */ - __privateAdd$a(this, _autoRender, void 0); - /** - * Original {@link meshes} {@link RenderMaterial | materials}. - * @private - */ - __privateAdd$a(this, _materials, void 0); - /** - * Corresponding depth {@link meshes} {@link RenderMaterial | materials}. - * @private - */ - __privateAdd$a(this, _depthMaterials, void 0); - /** @ignore */ - __privateAdd$a(this, _depthPassTaskID, void 0); - renderer = isCameraRenderer(renderer, this.constructor.name); - this.renderer = renderer; - this.rendererBinding = null; - this.light = light; - this.index = this.light.index; + constructor(parameters) { + let { renderer, ...pipelineParams } = parameters; + const { label, attributes, bindGroups, cacheKey, ...renderingOptions } = pipelineParams; + const type = "RenderPipelineEntry"; + isRenderer(renderer, label ? label + " " + type : type); + super(parameters); + this.type = type; + this.shaders = { + vertex: { + head: "", + code: "", + module: null + }, + fragment: { + head: "", + code: "", + module: null + }, + full: { + head: "", + code: "", + module: null + } + }; + this.descriptor = null; this.options = { - light, - intensity, - bias, - normalBias, - pcfSamples, - depthTextureSize, - depthTextureFormat + ...this.options, + attributes, + ...renderingOptions }; - this.sampleCount = 1; - this.meshes = /* @__PURE__ */ new Map(); - __privateSet$9(this, _materials, /* @__PURE__ */ new Map()); - __privateSet$9(this, _depthMaterials, /* @__PURE__ */ new Map()); - __privateSet$9(this, _depthPassTaskID, null); - __privateMethod$2(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); - this.isActive = false; + this.setPipelineEntryProperties({ attributes, bindGroups }); } /** - * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
- * Called internally by the associated {@link core/lights/Light.Light | Light} if any shadow parameters are specified when creating it. Can also be called directly. - * @param parameters - parameters to use for this {@link Shadow}. + * Set {@link RenderPipelineEntry} properties (in this case the {@link bindGroups | bind groups} and {@link attributes}) + * @param parameters - the {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} and {@link core/materials/RenderMaterial.RenderMaterial#attributes | attributes} to use */ - cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender } = {}) { - __privateMethod$2(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); - this.isActive = true; - } - /** @ignore */ - setRendererBinding() { + setPipelineEntryProperties(parameters) { + const { attributes, bindGroups } = parameters; + this.attributes = attributes; + this.setPipelineEntryBindGroups(bindGroups); } + /* SHADERS */ /** - * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link core/lights/Light.Light | lights} has been overflowed. + * Patch the shaders by appending all the necessary shader chunks, {@link bindGroups | bind groups}) and {@link attributes} WGSL code fragments to the given {@link types/PipelineEntries.PipelineEntryParams#shaders | parameter shader code} */ - reset() { - if (this.isActive) { - this.onPropertyChanged("isActive", 1); - this.onPropertyChanged("intensity", this.intensity); - this.onPropertyChanged("bias", this.bias); - this.onPropertyChanged("normalBias", this.normalBias); - this.onPropertyChanged("pcfSamples", this.pcfSamples); + patchShaders() { + this.shaders.vertex.head = ""; + this.shaders.vertex.code = ""; + this.shaders.fragment.head = ""; + this.shaders.fragment.code = ""; + this.shaders.full.head = ""; + this.shaders.full.code = ""; + for (const chunk in ShaderChunks.vertex) { + this.shaders.vertex.head = `${ShaderChunks.vertex[chunk]} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${ShaderChunks.vertex[chunk]} +${this.shaders.full.head}`; } - } - /** - * Get whether this {@link Shadow} is actually casting shadows. - * @returns - Whether this {@link Shadow} is actually casting shadows. - */ - get isActive() { - return __privateGet$9(this, _isActive); - } - /** - * Start or stop casting shadows. - * @param value - */ - set isActive(value) { - if (!value && this.isActive) { - this.destroy(); - } else if (value && !this.isActive) { - this.init(); + if (this.options.shaders.fragment) { + for (const chunk in ShaderChunks.fragment) { + this.shaders.fragment.head = `${ShaderChunks.fragment[chunk]} +${this.shaders.fragment.head}`; + if (this.shaders.full.head.indexOf(ShaderChunks.fragment[chunk]) === -1) { + this.shaders.full.head = `${ShaderChunks.fragment[chunk]} +${this.shaders.full.head}`; + } + } + } + if (this.options.rendering.useProjection) { + for (const chunk in ProjectedShaderChunks.vertex) { + this.shaders.vertex.head = `${ProjectedShaderChunks.vertex[chunk]} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${ProjectedShaderChunks.vertex[chunk]} +${this.shaders.full.head}`; + } + if (this.options.shaders.fragment) { + for (const chunk in ProjectedShaderChunks.fragment) { + this.shaders.fragment.head = `${ProjectedShaderChunks.fragment[chunk]} +${this.shaders.fragment.head}`; + if (this.shaders.full.head.indexOf(ProjectedShaderChunks.fragment[chunk]) === -1) { + this.shaders.full.head = `${ProjectedShaderChunks.fragment[chunk]} +${this.shaders.full.head}`; + } + } + } + } + const groupsBindings = []; + for (const bindGroup of this.bindGroups) { + let bindIndex = 0; + bindGroup.bindings.forEach((binding, bindingIndex) => { + binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => { + groupsBindings.push({ + groupIndex: bindGroup.index, + visibility: binding.options.visibility, + bindIndex, + wgslStructFragment: binding.wgslStructFragment, + wgslGroupFragment: groupFragment, + newLine: bindingIndex === bindGroup.bindings.length - 1 && groupFragmentIndex === binding.wgslGroupFragment.length - 1 + }); + bindIndex++; + }); + }); + } + for (const groupBinding of groupsBindings) { + if (groupBinding.visibility.includes("vertex")) { + if (groupBinding.wgslStructFragment && this.shaders.vertex.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.vertex.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.vertex.head}`; + } + if (this.shaders.vertex.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.vertex.head = `${this.shaders.vertex.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.vertex.head += ` +`; + } + } + if (this.options.shaders.fragment && groupBinding.visibility.includes("fragment")) { + if (groupBinding.wgslStructFragment && this.shaders.fragment.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.fragment.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.fragment.head}`; + } + if (this.shaders.fragment.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.fragment.head = `${this.shaders.fragment.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.fragment.head += ` +`; + } + } + if (groupBinding.wgslStructFragment && this.shaders.full.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.full.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.full.head}`; + } + if (this.shaders.full.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.full.head = `${this.shaders.full.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.full.head += ` +`; + } + } + this.shaders.vertex.head = `${this.attributes.wgslStructFragment} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${this.attributes.wgslStructFragment} +${this.shaders.full.head}`; + this.shaders.vertex.code = this.shaders.vertex.head + this.options.shaders.vertex.code; + if (typeof this.options.shaders.fragment === "object") + this.shaders.fragment.code = this.shaders.fragment.head + this.options.shaders.fragment.code; + if (typeof this.options.shaders.fragment === "object") { + if (this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0) { + this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code; + } else { + this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code + this.options.shaders.fragment.code; + } } - __privateSet$9(this, _isActive, value); - } - /** - * Get this {@link Shadow} intensity. - * @returns - The {@link Shadow} intensity. - */ - get intensity() { - return __privateGet$9(this, _intensity); } + /* SETUP */ /** - * Set this {@link Shadow} intensity and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param value - The new {@link Shadow} intensity. + * Get whether the shaders modules have been created + * @readonly */ - set intensity(value) { - __privateSet$9(this, _intensity, value); - this.onPropertyChanged("intensity", this.intensity); + get shadersModulesReady() { + return !(!this.shaders.vertex.module || this.options.shaders.fragment && !this.shaders.fragment.module); } /** - * Get this {@link Shadow} bias. - * @returns - The {@link Shadow} bias. + * Create the {@link shaders}: patch them and create the {@link GPUShaderModule} */ - get bias() { - return __privateGet$9(this, _bias); + createShaders() { + this.patchShaders(); + const isSameShader = typeof this.options.shaders.fragment === "object" && this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0; + this.shaders.vertex.module = this.createShaderModule({ + code: this.shaders[isSameShader ? "full" : "vertex"].code, + type: "vertex" + }); + if (this.options.shaders.fragment) { + this.shaders.fragment.module = this.createShaderModule({ + code: this.shaders[isSameShader ? "full" : "fragment"].code, + type: "fragment" + }); + } } /** - * Set this {@link Shadow} bias and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param value - The new {@link Shadow} bias. + * Get default transparency blend state. + * @returns - The default transparency blend state. */ - set bias(value) { - __privateSet$9(this, _bias, value); - this.onPropertyChanged("bias", this.bias); + static getDefaultTransparentBlending() { + return { + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha" + }, + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha" + } + }; } /** - * Get this {@link Shadow} normal bias. - * @returns - The {@link Shadow} normal bias. + * Create the render pipeline {@link descriptor} */ - get normalBias() { - return __privateGet$9(this, _normalBias); + createPipelineDescriptor() { + if (!this.shadersModulesReady) + return; + let vertexLocationIndex = -1; + if (this.options.rendering.targets.length) { + if (this.options.rendering.transparent) { + this.options.rendering.targets[0].blend = this.options.rendering.targets[0].blend ? this.options.rendering.targets[0].blend : RenderPipelineEntry.getDefaultTransparentBlending(); + } + } else { + this.options.rendering.targets = []; + } + this.descriptor = { + label: this.options.label, + layout: this.layout, + vertex: { + module: this.shaders.vertex.module, + entryPoint: this.options.shaders.vertex.entryPoint, + buffers: this.attributes.vertexBuffers.map((vertexBuffer) => { + return { + stepMode: vertexBuffer.stepMode, + arrayStride: vertexBuffer.arrayStride * 4, + // 4 bytes each + attributes: vertexBuffer.attributes.map((attribute) => { + vertexLocationIndex++; + return { + shaderLocation: vertexLocationIndex, + offset: attribute.bufferOffset, + // previous attribute size * 4 + format: attribute.bufferFormat + }; + }) + }; + }) + }, + ...this.options.shaders.fragment && { + fragment: { + module: this.shaders.fragment.module, + entryPoint: this.options.shaders.fragment.entryPoint, + targets: this.options.rendering.targets + } + }, + primitive: { + topology: this.options.rendering.topology, + frontFace: this.options.rendering.verticesOrder, + cullMode: this.options.rendering.cullMode + }, + ...this.options.rendering.depth && { + depthStencil: { + depthWriteEnabled: this.options.rendering.depthWriteEnabled, + depthCompare: this.options.rendering.depthCompare, + format: this.options.rendering.depthFormat + } + }, + ...this.options.rendering.sampleCount > 1 && { + multisample: { + count: this.options.rendering.sampleCount + } + } + }; } /** - * Set this {@link Shadow} normal bias and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param value - The new {@link Shadow} normal bias. + * Create the render {@link pipeline} */ - set normalBias(value) { - __privateSet$9(this, _normalBias, value); - this.onPropertyChanged("normalBias", this.normalBias); + createRenderPipeline() { + if (!this.shadersModulesReady) + return; + try { + this.pipeline = this.renderer.createRenderPipeline(this.descriptor); + } catch (error) { + this.status.error = error; + throwError(error); + } } /** - * Get this {@link Shadow} PCF samples count. - * @returns - The {@link Shadow} PCF samples count. + * Asynchronously create the render {@link pipeline} + * @async + * @returns - void promise result */ - get pcfSamples() { - return __privateGet$9(this, _pcfSamples); + async createRenderPipelineAsync() { + if (!this.shadersModulesReady) + return; + try { + this.pipeline = await this.renderer.createRenderPipelineAsync(this.descriptor); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } catch (error) { + this.status.error = error; + throwError(error); + } } /** - * Set this {@link Shadow} PCF samples count and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param value - The new {@link Shadow} PCF samples count. + * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our render {@link pipeline} + * @async */ - set pcfSamples(value) { - __privateSet$9(this, _pcfSamples, Math.max(1, Math.ceil(value))); - this.onPropertyChanged("pcfSamples", this.pcfSamples); + async compilePipelineEntry() { + super.compilePipelineEntry(); + if (this.options.useAsync) { + await this.createRenderPipelineAsync(); + } else { + this.createRenderPipeline(); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } } - /** - * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget} and start rendering to the shadow map. - */ - init() { - if (!this.depthComparisonSampler) { - const samplerExists = this.renderer.samplers.find((sampler) => sampler.name === "depthComparisonSampler"); - this.depthComparisonSampler = samplerExists || new Sampler(this.renderer, { - label: "Depth comparison sampler", - name: "depthComparisonSampler", - // we do not want to repeat the shadows - addressModeU: "clamp-to-edge", - addressModeV: "clamp-to-edge", - compare: "less", - minFilter: "linear", - magFilter: "linear", - type: "comparison" - }); - } - this.setDepthTexture(); - if (!this.depthPassTarget) { - this.createDepthPassTarget(); - } - if (__privateGet$9(this, _depthPassTaskID) === null && __privateGet$9(this, _autoRender)) { - this.setDepthPass(); - this.onPropertyChanged("isActive", 1); + } + + const compareRenderingOptions = (newOptions = {}, baseOptions = {}) => { + return Object.keys(newOptions).filter((key) => { + if (Array.isArray(newOptions[key])) { + return JSON.stringify(newOptions[key]) !== JSON.stringify(baseOptions[key]); + } else { + return newOptions[key] !== baseOptions[key]; } - } + }); + }; + + var default_projected_vsWgsl = ( + /* wgsl */ + ` +struct VSOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normal: vec3f, + @location(2) worldPosition: vec3f, + @location(3) viewDirection: vec3f, +}; + +@vertex fn main( + attributes: Attributes, +) -> VSOutput { + var vsOutput: VSOutput; + + vsOutput.position = getOutputPosition(attributes.position); + vsOutput.uv = attributes.uv; + vsOutput.normal = getWorldNormal(attributes.normal); + vsOutput.worldPosition = getWorldPosition(attributes.position).xyz; + vsOutput.viewDirection = camera.position - vsOutput.worldPosition; + + return vsOutput; +}` + ); + + var default_vsWgsl = ( + /* wgsl */ + ` +struct VSOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, +}; + +@vertex fn main( + attributes: Attributes, +) -> VSOutput { + var vsOutput: VSOutput; + + vsOutput.position = vec4f(attributes.position, 1.0); + vsOutput.uv = attributes.uv; + + return vsOutput; +}` + ); + + var default_fsWgsl = ( + /* wgsl */ + ` +@fragment fn main() -> @location(0) vec4f { + return vec4(0.0, 0.0, 0.0, 1.0); +}` + ); + + class RenderMaterial extends Material { /** - * Reset the {@link depthTexture} when the {@link depthTextureSize} changes. + * RenderMaterial constructor + * @param renderer - our renderer class object + * @param parameters - {@link RenderMaterialParams | parameters} used to create our RenderMaterial */ - onDepthTextureSizeChanged() { - this.setDepthTexture(); + constructor(renderer, parameters) { + const type = "RenderMaterial"; + renderer = isRenderer(renderer, type); + if (!parameters.shaders) { + parameters.shaders = {}; + } + if (!parameters.shaders?.vertex) { + parameters.shaders.vertex = { + code: parameters.useProjection ? default_projected_vsWgsl : default_vsWgsl, + entryPoint: "main" + }; + } + if (!parameters.shaders.vertex.entryPoint) { + parameters.shaders.vertex.entryPoint = "main"; + } + if (parameters.shaders.fragment === void 0) { + parameters.shaders.fragment = { + entryPoint: "main", + code: default_fsWgsl + }; + } + super(renderer, parameters); + this.type = type; + this.renderer = renderer; + const { shaders } = parameters; + const { + useProjection, + transparent, + depth, + depthWriteEnabled, + depthCompare, + depthFormat, + cullMode, + sampleCount, + verticesOrder, + topology + } = parameters; + let { targets } = parameters; + if (targets === void 0) { + targets = [ + { + format: this.renderer.options.context.format + } + ]; + } + if (targets && targets.length && !targets[0].format) { + targets[0].format = this.renderer.options.context.format; + } + this.options = { + ...this.options, + shaders, + rendering: { + useProjection, + transparent, + depth, + depthWriteEnabled, + depthCompare, + depthFormat, + cullMode, + sampleCount, + targets, + verticesOrder, + topology + } + }; + this.attributes = null; + this.pipelineEntry = null; } /** - * Set or resize the {@link depthTexture} and eventually resize the {@link depthPassTarget} as well. + * Set or reset this {@link RenderMaterial} {@link renderer}. Will also update the renderer camera bind group if needed. + * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. */ - setDepthTexture() { - if (this.depthTexture && (this.depthTexture.size.width !== this.depthTextureSize.x || this.depthTexture.size.height !== this.depthTextureSize.y)) { - this.depthTexture.options.fixedSize.width = this.depthTextureSize.x; - this.depthTexture.options.fixedSize.height = this.depthTextureSize.y; - this.depthTexture.size.width = this.depthTextureSize.x; - this.depthTexture.size.height = this.depthTextureSize.y; - this.depthTexture.createTexture(); - if (this.depthPassTarget) { - this.depthPassTarget.resize(); - } - } else if (!this.depthTexture) { - this.createDepthTexture(); + setRenderer(renderer) { + if (this.useCameraBindGroup && this.renderer) { + this.renderer.cameraLightsBindGroup.consumers.delete(this.uuid); + } + super.setRenderer(renderer); + if (this.useCameraBindGroup) { + this.bindGroups[0] = this.renderer.cameraLightsBindGroup; + this.renderer.cameraLightsBindGroup.consumers.add(this.uuid); } } /** - * Create the {@link depthTexture}. + * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link RenderPipelineEntry} from cache or if we should create a new one. */ - createDepthTexture() { - this.depthTexture = new Texture(this.renderer, { - label: "Shadow depth texture " + this.index, - name: "shadowDepthTexture" + this.index, - type: "depth", - format: this.depthTextureFormat, - sampleCount: this.sampleCount, - fixedSize: { - width: this.depthTextureSize.x, - height: this.depthTextureSize.y - }, - autoDestroy: false - // do not destroy when removing a mesh + setPipelineEntry() { + this.pipelineEntry = this.renderer.pipelineManager.createRenderPipeline({ + renderer: this.renderer, + label: this.options.label + " render pipeline", + shaders: this.options.shaders, + useAsync: this.options.useAsyncPipeline, + rendering: this.options.rendering, + attributes: this.attributes, + bindGroups: this.bindGroups }); } /** - * Create the {@link depthPassTarget}. + * Compile the {@link RenderPipelineEntry} + * @async */ - createDepthPassTarget() { - this.depthPassTarget = new RenderTarget(this.renderer, { - label: "Depth pass render target for " + this.constructor.name + " " + this.index, - useColorAttachments: false, - depthTexture: this.depthTexture, - sampleCount: this.sampleCount - }); + async compilePipelineEntry() { + await this.pipelineEntry.compilePipelineEntry(); } /** - * Update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} input value and tell the {@link CameraRenderer#cameraLightsBindGroup | renderer camera, lights and shadows} bind group to update. - * @param propertyKey - name of the property to update. - * @param value - new value of the property. + * Check if attributes and all bind groups are ready, create them if needed, set {@link RenderPipelineEntry} bind group buffers and compile the pipeline. + * @async */ - onPropertyChanged(propertyKey, value) { - if (this.rendererBinding) { - if (value instanceof Mat4) { - for (let i = 0; i < value.elements.length; i++) { - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].value[i] = value.elements[i]; - } - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].shouldUpdate = true; - } else { - this.rendererBinding.options.bindings[this.index].inputs[propertyKey].value = value; - } - this.renderer.shouldUpdateCameraLightsBindGroup(); + async compileMaterial() { + if (this.ready) + return; + super.compileMaterial(); + if (this.attributes && !this.pipelineEntry) { + this.setPipelineEntry(); + } + if (this.pipelineEntry && this.pipelineEntry.canCompile) { + await this.compilePipelineEntry(); } } /** - * Start the depth pass. - */ - setDepthPass() { - __privateSet$9(this, _depthPassTaskID, this.render()); - } - /** - * Remove the depth pass from its {@link utils/TasksQueueManager.TasksQueueManager | task queue manager}. - * @param depthPassTaskID - Task queue manager ID to use for removal. - */ - removeDepthPass(depthPassTaskID) { - this.renderer.onBeforeRenderScene.remove(depthPassTaskID); - } - /** - * Render the depth pass. This happens before rendering the {@link CameraRenderer#scene | scene}.
- * - Force all the {@link meshes} to use their depth materials - * - Render all the {@link meshes} - * - Reset all the {@link meshes} materials to their original one. - * @param once - Whether to render it only once or not. + * Set or reset one of the {@link RenderMaterialRenderingOptions | rendering options}. Should be use with great caution, because if the {@link RenderPipelineEntry#pipeline | render pipeline} has already been compiled, it can cause a pipeline flush. + * @param renderingOptions - new {@link RenderMaterialRenderingOptions | rendering options} properties to be set */ - render(once = false) { - return this.renderer.onBeforeRenderScene.add( - (commandEncoder) => { - if (!this.meshes.size) - return; - this.useDepthMaterials(); - this.renderDepthPass(commandEncoder); - this.useOriginalMaterials(); - this.renderer.pipelineManager.resetCurrentPipeline(); - }, - { - once, - order: this.index - } - ); - } - /** - * Render the shadow map only once. Useful with static scenes if autoRender has been set to `false` to only take one snapshot of the shadow map. - */ - async renderOnce() { - if (!__privateGet$9(this, _autoRender)) { - this.onPropertyChanged("isActive", 1); - this.useDepthMaterials(); - this.meshes.forEach((mesh) => { - mesh.setGeometry(); - }); - await Promise.all( - [...__privateGet$9(this, _depthMaterials).values()].map(async (depthMaterial) => { - await depthMaterial.compileMaterial(); - }) - ); - this.render(true); + setRenderingOptions(renderingOptions = {}) { + if (renderingOptions.transparent && renderingOptions.targets.length && !renderingOptions.targets[0].blend) { + renderingOptions.targets[0].blend = RenderPipelineEntry.getDefaultTransparentBlending(); + } + const newProperties = compareRenderingOptions(renderingOptions, this.options.rendering); + const oldRenderingOptions = { ...this.options.rendering }; + this.options.rendering = { ...this.options.rendering, ...renderingOptions }; + if (this.pipelineEntry) { + if (this.pipelineEntry.ready && newProperties.length) { + if (!this.renderer.production) { + const oldProps = newProperties.map((key) => { + return { + [key]: Array.isArray(oldRenderingOptions[key]) ? oldRenderingOptions[key].map((optKey) => optKey) : oldRenderingOptions[key] + }; + }); + const newProps = newProperties.map((key) => { + return { + [key]: Array.isArray(renderingOptions[key]) ? renderingOptions[key].map((optKey) => optKey) : renderingOptions[key] + }; + }); + throwWarning( + `${this.options.label}: the change of rendering options is causing this RenderMaterial pipeline to be recompiled. This should be avoided. + +Old rendering options: ${JSON.stringify( + oldProps.reduce((acc, v) => { + return { ...acc, ...v }; + }, {}), + null, + 4 + )} + +-------- + +New rendering options: ${JSON.stringify( + newProps.reduce((acc, v) => { + return { ...acc, ...v }; + }, {}), + null, + 4 + )}` + ); + } + this.setPipelineEntry(); + } else { + this.pipelineEntry.options.rendering = { ...this.pipelineEntry.options.rendering, ...this.options.rendering }; + } } } + /* ATTRIBUTES */ /** - * Render all the {@link meshes} into the {@link depthPassTarget}. - * @param commandEncoder - {@link GPUCommandEncoder} to use. - */ - renderDepthPass(commandEncoder) { - this.renderer.pipelineManager.resetCurrentPipeline(); - const depthPass = commandEncoder.beginRenderPass(this.depthPassTarget.renderPass.descriptor); - this.meshes.forEach((mesh) => { - mesh.render(depthPass); - }); - depthPass.end(); - } - /** - * Get the default depth pass vertex shader for this {@link Shadow}. - * @returns - Depth pass vertex shader. + * Compute geometry if needed and get all useful geometry properties needed to create attributes buffers + * @param geometry - the geometry to draw */ - getDefaultShadowDepthVs(hasInstances = false) { - return { - /** Returned code. */ - code: getDefaultShadowDepthVs(this.index, hasInstances) + setAttributesFromGeometry(geometry) { + this.attributes = { + wgslStructFragment: geometry.wgslStructFragment, + vertexBuffers: geometry.vertexBuffers, + layoutCacheKey: geometry.layoutCacheKey }; } + /* BIND GROUPS */ /** - * Get the default depth pass fragment shader for this {@link Shadow}. - * @returns - A {@link ShaderOptions} if a depth pass fragment shader is needed, `false` otherwise. - */ - getDefaultShadowDepthFs() { - return false; - } - /** - * Patch the given {@link ProjectedMesh | mesh} material parameters to create the depth material. - * @param mesh - original {@link ProjectedMesh | mesh} to use. - * @param parameters - Optional additional parameters to use for the depth material. - * @returns - Patched parameters. - */ - patchShadowCastingMeshParams(mesh, parameters = {}) { - parameters = { ...mesh.material.options.rendering, ...parameters }; - parameters.targets = []; - parameters.sampleCount = this.sampleCount; - parameters.depthFormat = this.depthTextureFormat; - if (parameters.bindings) { - parameters.bindings = [mesh.material.getBufferBindingByName("matrices"), ...parameters.bindings]; - } else { - parameters.bindings = [mesh.material.getBufferBindingByName("matrices")]; - } - const hasInstances = mesh.material.inputsBindings.get("instances") && mesh.geometry.instancesCount > 1; - if (!parameters.shaders) { - parameters.shaders = { - vertex: this.getDefaultShadowDepthVs(hasInstances), - fragment: this.getDefaultShadowDepthFs() - }; - } - return parameters; + * Get whether this {@link RenderMaterial} uses the renderer camera and lights bind group. + * @readonly + * */ + get useCameraBindGroup() { + return "cameraLightsBindGroup" in this.renderer && this.options.rendering.useProjection; } /** - * Add a {@link ProjectedMesh | mesh} to the shadow map. Internally called by the {@link ProjectedMesh | mesh} if its `castShadows` parameters has been set to `true`, but can also be called externally to selectively cast shadows or to add specific parameters (such as custom depth pass shaders). - * - Save the original {@link ProjectedMesh | mesh} material. - * - {@link patchShadowCastingMeshParams | Patch} the parameters. - * - Create a new depth {@link RenderMaterial} with the patched parameters. - * - Add the {@link ProjectedMesh | mesh} to the {@link meshes} Map. - * @param mesh - {@link ProjectedMesh | mesh} to add to the shadow map. - * @param parameters - Optional {@link RenderMaterialParams | parameters} to use for the depth material. + * Create the bind groups if they need to be created, but first add camera and lights bind group if needed. */ - addShadowCastingMesh(mesh, parameters = {}) { - mesh.options.castShadows = true; - __privateGet$9(this, _materials).set(mesh.uuid, mesh.material); - parameters = this.patchShadowCastingMeshParams(mesh, parameters); - if (__privateGet$9(this, _depthMaterials).get(mesh.uuid)) { - __privateGet$9(this, _depthMaterials).get(mesh.uuid).destroy(); - __privateGet$9(this, _depthMaterials).delete(mesh.uuid); + createBindGroups() { + if (this.useCameraBindGroup) { + this.bindGroups.push(this.renderer.cameraLightsBindGroup); + this.renderer.cameraLightsBindGroup.consumers.add(this.uuid); } - __privateGet$9(this, _depthMaterials).set( - mesh.uuid, - new RenderMaterial(this.renderer, { - label: mesh.options.label + " depth render material", - ...parameters - }) - ); - this.meshes.set(mesh.uuid, mesh); - } - /** - * Force all the {@link meshes} to use the depth material. - */ - useDepthMaterials() { - this.meshes.forEach((mesh) => { - mesh.useMaterial(__privateGet$9(this, _depthMaterials).get(mesh.uuid)); - }); - } - /** - * Force all the {@link meshes} to use their original material. - */ - useOriginalMaterials() { - this.meshes.forEach((mesh) => { - mesh.useMaterial(__privateGet$9(this, _materials).get(mesh.uuid)); - }); + super.createBindGroups(); } /** - * Remove a {@link ProjectedMesh | mesh} from the shadow map and destroy its depth material. - * @param mesh - {@link ProjectedMesh | mesh} to remove. + * Update all bind groups, except for the camera and light bind groups if present, as it is already updated by the renderer itself. */ - removeMesh(mesh) { - const depthMaterial = __privateGet$9(this, _depthMaterials).get(mesh.uuid); - if (depthMaterial) { - depthMaterial.destroy(); - __privateGet$9(this, _depthMaterials).delete(mesh.uuid); + updateBindGroups() { + const startBindGroupIndex = this.useCameraBindGroup ? 1 : 0; + if (this.useCameraBindGroup) { + if (this.bindGroups[0].needsPipelineFlush && this.pipelineEntry.ready) { + this.pipelineEntry.flushPipelineEntry(this.bindGroups); + } } - this.meshes.delete(mesh.uuid); - } - /** - * Destroy the {@link Shadow}. - */ - destroy() { - this.onPropertyChanged("isActive", 0); - if (__privateGet$9(this, _depthPassTaskID) !== null) { - this.removeDepthPass(__privateGet$9(this, _depthPassTaskID)); - __privateSet$9(this, _depthPassTaskID, null); + for (let i = startBindGroupIndex; i < this.bindGroups.length; i++) { + this.updateBindGroup(this.bindGroups[i]); } - this.meshes.forEach((mesh) => this.removeMesh(mesh)); - __privateSet$9(this, _materials, /* @__PURE__ */ new Map()); - __privateSet$9(this, _depthMaterials, /* @__PURE__ */ new Map()); - this.meshes = /* @__PURE__ */ new Map(); - this.depthPassTarget?.destroy(); - this.depthTexture?.destroy(); } } - _intensity = new WeakMap(); - _bias = new WeakMap(); - _normalBias = new WeakMap(); - _pcfSamples = new WeakMap(); - _isActive = new WeakMap(); - _autoRender = new WeakMap(); - _materials = new WeakMap(); - _depthMaterials = new WeakMap(); - _depthPassTaskID = new WeakMap(); - _setParameters = new WeakSet(); - setParameters_fn = function({ - intensity = 1, - bias = 0, - normalBias = 0, - pcfSamples = 1, - depthTextureSize = new Vec2(512), - depthTextureFormat = "depth24plus", - autoRender = true - } = {}) { - this.intensity = intensity; - this.bias = bias; - this.normalBias = normalBias; - this.pcfSamples = pcfSamples; - this.depthTextureSize = depthTextureSize; - this.depthTextureSize.onChange(() => this.onDepthTextureSizeChanged()); - this.depthTextureFormat = depthTextureFormat; - __privateSet$9(this, _autoRender, autoRender); - }; - const directionalShadowStruct = { - ...shadowStruct, - viewMatrix: { - type: "mat4x4f", - value: new Float32Array(16) - }, - projectionMatrix: { - type: "mat4x4f", - value: new Float32Array(16) + const getPositionAndNormal = (hasInstances = false) => { + if (hasInstances) { + return ( + /* wgsl */ + ` + var worldPosition: vec4f = instances[attributes.instanceIndex].modelMatrix * vec4f(attributes.position, 1.0); + let normal = (instances[attributes.instanceIndex].normalMatrix * vec4(attributes.normal, 0.0)).xyz; + ` + ); + } else { + return ( + /* wgsl */ + ` + var worldPosition: vec4f = matrices.model * vec4(attributes.position, 1.0); + let normal = getWorldNormal(attributes.normal); + ` + ); } }; - class DirectionalShadow extends Shadow { - /** - * DirectionalShadow constructor - * @param renderer - {@link CameraRenderer} used to create this {@link DirectionalShadow}. - * @param parameters - {@link DirectionalShadowParams | parameters} used to create this {@link DirectionalShadow}. - */ - constructor(renderer, { - light, - intensity, - bias, - normalBias, - pcfSamples, - depthTextureSize, - depthTextureFormat, - autoRender, - camera = { - left: -10, - right: 10, - bottom: -10, - top: 10, - near: 0.1, - far: 50 - } - } = {}) { - super(renderer, { - light, - intensity, - bias, - normalBias, - pcfSamples, - depthTextureSize, - depthTextureFormat, - autoRender - }); - this.options = { - ...this.options, - camera - }; - this.setRendererBinding(); - this.camera = { - projectionMatrix: new Mat4(), - viewMatrix: new Mat4(), - up: new Vec3(0, 1, 0), - _left: camera.left, - _right: camera.right, - _bottom: camera.bottom, - _top: camera.top, - _near: camera.near, - _far: camera.far - }; - const _self = this; - const cameraProps = ["left", "right", "bottom", "top", "near", "far"]; - cameraProps.forEach((prop) => { - Object.defineProperty(_self.camera, prop, { - get() { - return _self.camera["_" + prop]; - }, - set(v) { - _self.camera["_" + prop] = v; - _self.updateProjectionMatrix(); - } - }); - }); - } - /** - * Set or reset this {@link DirectionalShadow} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - */ - setRendererBinding() { - this.rendererBinding = this.renderer.bindings.directionalShadows; - } - /** - * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
- * Called internally by the associated {@link DirectionalLight} if any shadow parameters are specified when creating it. Can also be called directly. - * @param parameters - parameters to use for this {@link DirectionalShadow}. - */ - cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender, camera } = {}) { - if (camera) { - this.camera.left = camera.left ?? -10; - this.camera.right = camera.right ?? 10; - this.camera.bottom = camera.bottom ?? -10; - this.camera.top = camera.right ?? 10; - this.camera.near = camera.near ?? 0.1; - this.camera.far = camera.far ?? 50; + const getDefaultShadowDepthVs = (lightIndex = 0, hasInstances = false) => ( + /* wgsl */ + ` +@vertex fn main( + attributes: Attributes, +) -> @builtin(position) vec4f { + let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[${lightIndex}]; + + ${getPositionAndNormal(hasInstances)} + + let lightDirection: vec3f = normalize(worldPosition.xyz - directionalLights.elements[${lightIndex}].direction); + let NdotL: f32 = dot(normalize(normal), lightDirection); + let sinNdotL = sqrt(1.0 - NdotL * NdotL); + let normalBias: f32 = directionalShadow.normalBias * sinNdotL; + + worldPosition = vec4(worldPosition.xyz - normal * normalBias, worldPosition.w); + + return directionalShadow.projectionMatrix * directionalShadow.viewMatrix * worldPosition; +}` + ); + const getPCFShadowContribution = ( + /* wgsl */ + ` +fn getPCFShadowContribution(index: i32, worldPosition: vec3f, depthTexture: texture_depth_2d) -> f32 { + let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[index]; + + // get shadow coords + var shadowCoords: vec3f = vec3((directionalShadow.projectionMatrix * directionalShadow.viewMatrix * vec4(worldPosition, 1.0)).xyz); + + // Convert XY to (0, 1) + // Y is flipped because texture coords are Y-down. + shadowCoords = vec3( + shadowCoords.xy * vec2(0.5, -0.5) + vec2(0.5), + shadowCoords.z + ); + + var visibility = 0.0; + + let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; + let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; + + if(frustumTest) { + // Percentage-closer filtering. Sample texels in the region + // to smooth the result. + let size: vec2f = vec2f(textureDimensions(depthTexture).xy); + + let texelSize: vec2f = 1.0 / size; + + let sampleCount: i32 = directionalShadow.pcfSamples; + let maxSamples: f32 = f32(sampleCount) - 1.0; + + for (var x = 0; x < sampleCount; x++) { + for (var y = 0; y < sampleCount; y++) { + let offset = texelSize * vec2( + f32(x) - maxSamples * 0.5, + f32(y) - maxSamples * 0.5 + ); + + visibility += textureSampleCompareLevel( + depthTexture, + depthComparisonSampler, + shadowCoords.xy + offset, + shadowCoords.z - directionalShadow.bias + ); } - super.cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); - } - /** - * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget}, compute the {@link DirectionalShadow#camera.projectionMatrix | camera projection matrix} and start rendering to the shadow map. - */ - init() { - super.init(); - this.updateProjectionMatrix(); - } - /** - * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link DirectionalLight} has been overflowed. - */ - reset() { - this.setRendererBinding(); - super.reset(); - this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); - this.onPropertyChanged("viewMatrix", this.camera.viewMatrix); - } - /** - * Update the {@link DirectionalShadow#camera.projectionMatrix | camera orthographic projection matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - */ - updateProjectionMatrix() { - this.camera.projectionMatrix.identity().makeOrthographic({ - left: this.camera.left, - right: this.camera.right, - bottom: this.camera.bottom, - top: this.camera.top, - near: this.camera.near, - far: this.camera.far - }); - this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); } - /** - * Update the {@link DirectionalShadow#camera.viewMatrix | camera view matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param position - {@link Vec3} to use as position for the {@link DirectionalShadow#camera.viewMatrix | camera view matrix}, based on the {@link light} position. - * @param target - {@link Vec3} to use as target for the {@link DirectionalShadow#camera.viewMatrix | camera view matrix}, based on the {@link light} target. - */ - updateViewMatrix(position = new Vec3(), target = new Vec3()) { - if (position.x === 0 && position.z === 0) { - this.camera.up.set(0, 0, 1); + visibility /= f32(sampleCount * sampleCount); + + visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); + } + else { + visibility = 1.0; + } + + return visibility; +} +` + ); + const getPCFDirectionalShadows = (renderer) => { + const directionalLights = renderer.shadowCastingLights.filter( + (light) => light.type === "directionalLights" + ); + const minDirectionalLights = Math.max(renderer.lightsBindingParams.directionalLights.max, 1); + return ( + /* wgsl */ + ` +fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { + var directionalShadowContribution: array; + + var lightDirection: vec3f; + + ${directionalLights.map((light, index) => { + return `lightDirection = worldPosition - directionalLights.elements[${index}].direction; + + ${light.shadow.isActive ? ` + if(directionalShadows.directionalShadowsElements[${index}].isActive > 0) { + directionalShadowContribution[${index}] = getPCFShadowContribution( + ${index}, + worldPosition, + shadowDepthTexture${index} + ); } else { - this.camera.up.set(0, 1, 0); + directionalShadowContribution[${index}] = 1.0; } - this.camera.viewMatrix.makeView(position, target, this.camera.up); - this.onPropertyChanged("viewMatrix", this.camera.viewMatrix); - } - } - - var __accessCheck$9 = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$8 = (obj, member, getter) => { - __accessCheck$9(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$9 = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$8 = (obj, member, value, setter) => { - __accessCheck$9(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; + ` : `directionalShadowContribution[${index}] = 1.0;`}`; + }).join("\n")} + + return directionalShadowContribution; +} +` + ); }; - var _actualPosition$1, _direction; - class DirectionalLight extends Light { - /** - * DirectionalLight constructor - * @param renderer - {@link CameraRenderer} used to create this {@link DirectionalLight}. - * @param parameters - {@link DirectionalLightBaseParams | parameters} used to create this {@link DirectionalLight}. - */ - constructor(renderer, { - color = new Vec3(1), - intensity = 1, - position = new Vec3(1), - target = new Vec3(), - shadow = null - } = {}) { - const type = "directionalLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); - /** @ignore */ - __privateAdd$9(this, _actualPosition$1, void 0); - /** - * The {@link Vec3 | direction} of the {@link DirectionalLight} is the {@link target} minus the actual {@link position}. - * @private - */ - __privateAdd$9(this, _direction, void 0); - this.options = { - ...this.options, - position, - target, - shadow - }; - __privateSet$8(this, _direction, new Vec3()); - __privateSet$8(this, _actualPosition$1, new Vec3()); - this.target = target; - this.target.onChange(() => this.setDirection()); - this.position.copy(position); - this.parent = this.renderer.scene; - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; - this.shadow = new DirectionalShadow(this.renderer, { - autoRender: false, - // will be set by calling cast() - light: this - }); - if (shadow) { - this.shadow.cast(shadow); - } - } - /** - * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link DirectionalLight} has been overflowed. - */ - reset() { - super.reset(); - this.setDirection(); - this.shadow?.reset(); - } - /** - * Set the {@link DirectionalLight} direction based on the {@link target} and the {@link worldMatrix} translation and update the {@link DirectionalShadow} view matrix. - */ - setDirection() { - __privateGet$8(this, _direction).copy(this.target).sub(this.worldMatrix.getTranslation(__privateGet$8(this, _actualPosition$1))); - this.onPropertyChanged("direction", __privateGet$8(this, _direction)); - this.shadow?.updateViewMatrix(__privateGet$8(this, _actualPosition$1), this.target); - } - // explicitly disable scale and transform origin transformations - /** @ignore */ - applyScale() { - } - /** @ignore */ - applyTransformOrigin() { - } - /** - * If the {@link modelMatrix | model matrix} has been updated, set the new direction from the {@link worldMatrix} translation. - */ - updateMatrixStack() { - super.updateMatrixStack(); - if (this.matricesNeedUpdate) { - this.setDirection(); - } - } - /** - * Tell the {@link renderer} that the maximum number of {@link DirectionalLight} has been overflown. - * @param lightsType - {@link type} of this light. - */ - onMaxLightOverflow(lightsType) { - super.onMaxLightOverflow(lightsType); - this.shadow?.setRendererBinding(); - } - /** - * Destroy this {@link DirectionalLight} and associated {@link DirectionalShadow}. - */ - destroy() { - super.destroy(); - this.shadow.destroy(); - } - } - _actualPosition$1 = new WeakMap(); - _direction = new WeakMap(); + const getDefaultPointShadowDepthVs = (lightIndex = 0, hasInstances = false) => ( + /* wgsl */ + ` +struct PointShadowVSOutput { + @builtin(position) position: vec4f, + @location(0) worldPosition: vec3f, +} - var __accessCheck$8 = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$7 = (obj, member, getter) => { - __accessCheck$8(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$8 = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$7 = (obj, member, value, setter) => { - __accessCheck$8(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; - }; - var _tempCubeDirection; - const pointShadowStruct = { - face: { - type: "i32", - value: 0 - }, - ...shadowStruct, - cameraNear: { - type: "f32", - value: 0 - }, - cameraFar: { - type: "f32", - value: 0 - }, - projectionMatrix: { - type: "mat4x4f", - value: new Float32Array(16) - }, - viewMatrices: { - type: "array", - value: new Float32Array(16 * 6) - } - }; - class PointShadow extends Shadow { - /** - * PointShadow constructor - * @param renderer - {@link CameraRenderer} used to create this {@link PointShadow}. - * @param parameters - {@link PointShadowParams | parameters} used to create this {@link PointShadow}. - */ - constructor(renderer, { - light, - intensity, - bias, - normalBias, - pcfSamples, - depthTextureSize, - depthTextureFormat, - autoRender, - camera = { - near: 0.1, - far: 150 - } - } = {}) { - super(renderer, { - light, - intensity, - bias, - normalBias, - pcfSamples, - depthTextureSize, - depthTextureFormat, - autoRender - }); - /** - * {@link Vec3} used to calculate the actual current direction based on the {@link PointLight} position. - * @private - */ - __privateAdd$8(this, _tempCubeDirection, void 0); - this.options = { - ...this.options, - camera - }; - this.setRendererBinding(); - this.cubeDirections = [ - new Vec3(-1, 0, 0), - new Vec3(1, 0, 0), - new Vec3(0, -1, 0), - new Vec3(0, 1, 0), - new Vec3(0, 0, -1), - new Vec3(0, 0, 1) - ]; - __privateSet$7(this, _tempCubeDirection, new Vec3()); - this.cubeUps = [ - new Vec3(0, -1, 0), - new Vec3(0, -1, 0), - new Vec3(0, 0, 1), - new Vec3(0, 0, -1), - new Vec3(0, -1, 0), - new Vec3(0, -1, 0) - ]; - if (camera.far <= 0) { - camera.far = 150; - } - this.camera = { - projectionMatrix: new Mat4(), - viewMatrices: [], - _near: camera.near, - _far: camera.far - }; - for (let i = 0; i < 6; i++) { - this.camera.viewMatrices.push(new Mat4()); - } - const _self = this; - const cameraProps = ["near", "far"]; - cameraProps.forEach((prop) => { - Object.defineProperty(_self.camera, prop, { - get() { - return _self.camera["_" + prop]; - }, - set(v) { - _self.camera["_" + prop] = v; - _self.updateProjectionMatrix(); - } - }); - }); - } - /** - * Set or reset this {@link PointShadow} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - */ - setRendererBinding() { - this.rendererBinding = this.renderer.bindings.pointShadows; - } - /** - * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
- * Called internally by the associated {@link PointLight} if any shadow parameters are specified when creating it. Can also be called directly. - * @param parameters - parameters to use for this {@link PointShadow}. - */ - cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender, camera } = {}) { - if (camera) { - this.camera.near = camera.near ?? 0.1; - this.camera.far = camera.far !== void 0 ? camera.far : this.light.range > 0 ? this.light.range : 150; - } - super.cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); - } - /** - * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget}, compute the {@link PointShadow#camera.projectionMatrix | camera projection matrix} and start rendering to the shadow map. - */ - init() { - super.init(); - this.updateProjectionMatrix(); - } - /** - * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link PointLight} has been overflowed. - */ - reset() { - this.setRendererBinding(); - super.reset(); - this.onPropertyChanged("cameraNear", this.camera.near); - this.onPropertyChanged("cameraFar", this.camera.far); - this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); - this.updateViewMatrices(); - } - /** - * Update the {@link PointShadow#camera.projectionMatrix | camera perspective projection matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - */ - updateProjectionMatrix() { - this.camera.projectionMatrix.identity().makePerspective({ - near: this.camera.near, - far: this.camera.far, - fov: 90, - aspect: 1 - }); - this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); - this.onPropertyChanged("cameraNear", this.camera.near); - this.onPropertyChanged("cameraFar", this.camera.far); - } - /** - * Update the {@link PointShadow#camera.viewMatrices | camera view matrices} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param position - {@link Vec3} to use as position for the {@link PointShadow#camera.viewMatrices | camera view matrices}, based on the {@link light} position. - */ - updateViewMatrices(position = new Vec3()) { - for (let i = 0; i < 6; i++) { - __privateGet$7(this, _tempCubeDirection).copy(this.cubeDirections[i]).add(position); - this.camera.viewMatrices[i].makeView(position, __privateGet$7(this, _tempCubeDirection), this.cubeUps[i]); - for (let j = 0; j < 16; j++) { - this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.value[i * 16 + j] = this.camera.viewMatrices[i].elements[j]; - } - } - this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.shouldUpdate = true; - } - /** - * Set or resize the {@link depthTexture} and eventually resize the {@link depthPassTarget} as well. - */ - setDepthTexture() { - if (this.depthTexture && (this.depthTexture.size.width !== this.depthTextureSize.x || this.depthTexture.size.height !== this.depthTextureSize.y)) { - const maxSize = Math.max(this.depthTextureSize.x, this.depthTextureSize.y); - this.depthTexture.options.fixedSize.width = maxSize; - this.depthTexture.options.fixedSize.height = maxSize; - this.depthTexture.size.width = maxSize; - this.depthTexture.size.height = maxSize; - this.depthTexture.createTexture(); - if (this.depthPassTarget) { - this.depthPassTarget.resize(); - } - } else if (!this.depthTexture) { - this.createDepthTexture(); - } - } - /** - * Create the cube {@link depthTexture}. - */ - createDepthTexture() { - const maxSize = Math.max(this.depthTextureSize.x, this.depthTextureSize.y); - this.depthTexture = new Texture(this.renderer, { - label: "Point shadow cube depth texture " + this.index, - name: "pointShadowCubeDepthTexture" + this.index, - type: "depth", - format: this.depthTextureFormat, - viewDimension: "cube", - sampleCount: this.sampleCount, - fixedSize: { - width: maxSize, - height: maxSize - }, - autoDestroy: false - // do not destroy when removing a mesh - }); - } - /** - * Remove the depth pass from its {@link utils/TasksQueueManager.TasksQueueManager | task queue manager}. - * @param depthPassTaskID - Task queue manager ID to use for removal. - */ - removeDepthPass(depthPassTaskID) { - this.renderer.onBeforeCommandEncoderCreation.remove(depthPassTaskID); - } - /** - * Render the depth pass. This happens before creating the {@link CameraRenderer} command encoder.
- * - Force all the {@link meshes} to use their depth materials - * - For each face of the depth cube texture: - * - Create a command encoder. - * - Set the {@link depthPassTarget} descriptor depth texture view to our depth cube texture current face. - * - Update the face index - * - Render all the {@link meshes} - * - Submit the command encoder - * - Reset all the {@link meshes} materials to their original one. - * @param once - Whether to render it only once or not. - */ - render(once = false) { - return this.renderer.onBeforeCommandEncoderCreation.add( - () => { - if (!this.meshes.size) - return; - this.useDepthMaterials(); - for (let i = 0; i < 6; i++) { - const commandEncoder = this.renderer.device.createCommandEncoder(); - this.depthPassTarget.renderPass.setRenderPassDescriptor( - this.depthTexture.texture.createView({ - label: this.depthTexture.texture.label + " cube face view " + i, - dimension: "2d", - arrayLayerCount: 1, - baseArrayLayer: i - }) - ); - this.rendererBinding.options.bindings[this.index].inputs.face.value = i; - this.renderer.cameraLightsBindGroup.update(); - this.renderDepthPass(commandEncoder); - const commandBuffer = commandEncoder.finish(); - this.renderer.device.queue.submit([commandBuffer]); - } - this.useOriginalMaterials(); - this.renderer.pipelineManager.resetCurrentPipeline(); - }, - { - once, - order: this.index - } - ); - } - /** - * Get the default depth pass vertex shader for this {@link PointShadow}. - * @returns - Depth pass vertex shader. - */ - getDefaultShadowDepthVs(hasInstances = false) { - return { - /** Returned code. */ - code: getDefaultPointShadowDepthVs(this.index, hasInstances) - }; - } - /** - * Get the default depth pass {@link types/Materials.ShaderOptions | fragment shader options} for this {@link PointShadow}. - * @returns - A {@link types/Materials.ShaderOptions | ShaderOptions} with the depth pass fragment shader. - */ - getDefaultShadowDepthFs() { - return { - /** Returned code. */ - code: getDefaultPointShadowDepthFs(this.index) - }; - } - } - _tempCubeDirection = new WeakMap(); +@vertex fn main( + attributes: Attributes, +) -> PointShadowVSOutput { + var pointShadowVSOutput: PointShadowVSOutput; + + ${getPositionAndNormal(hasInstances)} + + let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[${lightIndex}]; + + let lightDirection: vec3f = normalize(pointLights.elements[${lightIndex}].position - worldPosition.xyz); + let NdotL: f32 = dot(normalize(normal), lightDirection); + let sinNdotL = sqrt(1.0 - NdotL * NdotL); + let normalBias: f32 = pointShadow.normalBias * sinNdotL; + + worldPosition = vec4(worldPosition.xyz - normal * normalBias, worldPosition.w); + + var position: vec4f = pointShadow.projectionMatrix * pointShadow.viewMatrices[pointShadow.face] * worldPosition; - var __accessCheck$7 = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$6 = (obj, member, getter) => { - __accessCheck$7(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$7 = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$6 = (obj, member, value, setter) => { - __accessCheck$7(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; - }; - var _range, _actualPosition; - class PointLight extends Light { - /** - * PointLight constructor - * @param renderer - {@link CameraRenderer | CameraRenderer} used to create this {@link PointLight}. - * @param parameters - {@link PointLightBaseParams | parameters} used to create this {@link PointLight}. - */ - constructor(renderer, { color = new Vec3(1), intensity = 1, position = new Vec3(), range = 0, shadow = null } = {}) { - const type = "pointLights"; - const index = renderer.lights.filter((light) => light.type === type).length; - super(renderer, { color, intensity, index, type }); - /** @ignore */ - __privateAdd$7(this, _range, void 0); - /** @ignore */ - __privateAdd$7(this, _actualPosition, void 0); - this.options = { - ...this.options, - position, - range, - shadow - }; - __privateSet$6(this, _actualPosition, new Vec3()); - this.position.copy(position); - this.range = range; - this.parent = this.renderer.scene; - if (this.index + 1 > this.renderer.lightsBindingParams[this.type].max) { - this.onMaxLightOverflow(this.type); - } - this.rendererBinding.inputs.count.value = this.index + 1; - this.rendererBinding.inputs.count.shouldUpdate = true; - this.shadow = new PointShadow(this.renderer, { - autoRender: false, - // will be set by calling cast() - light: this - }); - if (shadow) { - this.shadow.cast(shadow); - } - } - /** - * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link PointLight} has been overflowed. - */ - reset() { - super.reset(); - this.onPropertyChanged("range", this.range); - this.setPosition(); - this.shadow?.reset(); - } - /** - * Get this {@link PointLight} range. - * @returns - The {@link PointLight} range. - */ - get range() { - return __privateGet$6(this, _range); - } - /** - * Set this {@link PointLight} range and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. - * @param value - The new {@link PointLight} range. - */ - set range(value) { - __privateSet$6(this, _range, value); - this.onPropertyChanged("range", this.range); - } - /** - * Set the {@link PointLight} position based on the {@link worldMatrix} translation and update the {@link PointShadow} view matrices. - */ - setPosition() { - this.onPropertyChanged("position", this.worldMatrix.getTranslation(__privateGet$6(this, _actualPosition))); - this.shadow?.updateViewMatrices(__privateGet$6(this, _actualPosition)); - } - // explicitly disable scale and transform origin transformations - /** @ignore */ - applyScale() { - } - /** @ignore */ - applyTransformOrigin() { - } - /** - * If the {@link modelMatrix | model matrix} has been updated, set the new position from the {@link worldMatrix} translation. - */ - updateMatrixStack() { - super.updateMatrixStack(); - if (this.matricesNeedUpdate) { - this.setPosition(); - } - } - /** - * Tell the {@link renderer} that the maximum number of {@link PointLight} has been overflown. - * @param lightsType - {@link type} of this light. - */ - onMaxLightOverflow(lightsType) { - super.onMaxLightOverflow(lightsType); - this.shadow?.setRendererBinding(); - } - /** - * Destroy this {@link PointLight} and associated {@link PointShadow}. - */ - destroy() { - super.destroy(); - this.shadow.destroy(); - } - } - _range = new WeakMap(); - _actualPosition = new WeakMap(); + pointShadowVSOutput.position = position; + pointShadowVSOutput.worldPosition = worldPosition.xyz; + + return pointShadowVSOutput; +}` + ); + const getDefaultPointShadowDepthFs = (lightIndex = 0) => ( + /* wgsl */ + ` +struct PointShadowVSOutput { + @builtin(position) position: vec4f, + @location(0) worldPosition: vec3f, +} + +@fragment fn main(fsInput: PointShadowVSOutput) -> @builtin(frag_depth) f32 { + // get distance between fragment and light source + var lightDistance: f32 = length(fsInput.worldPosition - pointLights.elements[${lightIndex}].position); + + let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[${lightIndex}]; + + // map to [0, 1] range by dividing by far plane - near plane + lightDistance = (lightDistance - pointShadow.cameraNear) / (pointShadow.cameraFar - pointShadow.cameraNear); + + // write this as modified depth + return clamp(lightDistance, 0.0, 1.0); +}` + ); + const getPCFPointShadowContribution = ( + /* wgsl */ + ` +fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTexture: texture_depth_cube) -> f32 { + let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[index]; - var __accessCheck$6 = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$5 = (obj, member, getter) => { - __accessCheck$6(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$6 = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$5 = (obj, member, value, setter) => { - __accessCheck$6(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; - }; - let meshIndex = 0; - const defaultMeshBaseParams = { - // material - autoRender: true, - useProjection: false, - useAsyncPipeline: true, - // rendering - cullMode: "back", - depth: true, - depthWriteEnabled: true, - depthCompare: "less", - depthFormat: "depth24plus", - transparent: false, - visible: true, - renderOrder: 0, - // textures - texturesOptions: {} - }; - function MeshBaseMixin(Base) { - var _autoRender, _a; - return _a = class extends Base { - /** - * MeshBase constructor - * - * @typedef MeshBaseArrayParams - * @type {array} - * @property {(Renderer|GPUCurtains)} 0 - our {@link Renderer} class object - * @property {(string|HTMLElement|null)} 1 - a DOM HTML Element that can be bound to a Mesh - * @property {MeshBaseParams} 2 - {@link MeshBaseParams | Mesh base parameters} - * - * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters - */ - constructor(...params) { - super( - params[0], - params[1], - { ...defaultMeshBaseParams, ...params[2] } - ); - /** Whether we should add this {@link MeshBase} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */ - __privateAdd$6(this, _autoRender, true); - // callbacks / events - /** function assigned to the {@link onReady} callback */ - this._onReadyCallback = () => { - }; - /** function assigned to the {@link onBeforeRender} callback */ - this._onBeforeRenderCallback = () => { - }; - /** function assigned to the {@link onRender} callback */ - this._onRenderCallback = () => { - }; - /** function assigned to the {@link onAfterRender} callback */ - this._onAfterRenderCallback = () => { - }; - /** function assigned to the {@link onAfterResize} callback */ - this._onAfterResizeCallback = () => { - }; - let renderer = params[0]; - const parameters = { ...defaultMeshBaseParams, ...params[2] }; - this.type = "MeshBase"; - this.uuid = generateUUID(); - Object.defineProperty(this, "index", { value: meshIndex++ }); - renderer = isRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); - this.renderer = renderer; - const { - label, - shaders, - geometry, - visible, - renderOrder, - outputTarget, - texturesOptions, - autoRender, - ...meshParameters - } = parameters; - this.outputTarget = outputTarget ?? null; - meshParameters.sampleCount = !!meshParameters.sampleCount ? meshParameters.sampleCount : this.outputTarget ? this.outputTarget.renderPass.options.sampleCount : this.renderer && this.renderer.renderPass ? this.renderer.renderPass.options.sampleCount : 1; - this.options = { - ...this.options ?? {}, - // merge possible lower options? - label: label ?? "Mesh " + this.renderer.meshes.length, - ...shaders !== void 0 ? { shaders } : {}, - ...outputTarget !== void 0 && { outputTarget }, - texturesOptions, - ...autoRender !== void 0 && { autoRender }, - ...meshParameters - }; - if (autoRender !== void 0) { - __privateSet$5(this, _autoRender, autoRender); - } - this.visible = visible; - this.renderOrder = renderOrder; - this.ready = false; - this.userData = {}; - if (geometry) { - this.useGeometry(geometry); - } - this.setMaterial({ - ...this.cleanupRenderMaterialParameters({ ...this.options }), - ...geometry && { verticesOrder: geometry.verticesOrder, topology: geometry.topology } - }); - this.addToScene(true); - } - /** - * Get private #autoRender value - * @readonly - */ - get autoRender() { - return __privateGet$5(this, _autoRender); - } - /** - * Get/set whether a Mesh is ready or not - * @readonly - */ - get ready() { - return this._ready; - } - set ready(value) { - if (value && !this._ready) { - this._onReadyCallback && this._onReadyCallback(); - } - this._ready = value; - } - /* SCENE */ - /** - * Add a Mesh to the {@link core/scenes/Scene.Scene | Scene} and optionally to the renderer. Can patch the {@link RenderMaterial} render options to match the {@link RenderPass} used to draw this Mesh. - * @param addToRenderer - whether to add this Mesh to the {@link Renderer#meshes | Renderer meshes array} - */ - addToScene(addToRenderer = false) { - if (addToRenderer) { - this.renderer.meshes.push(this); - } - this.setRenderingOptionsForRenderPass(this.outputTarget ? this.outputTarget.renderPass : this.renderer.renderPass); - if (__privateGet$5(this, _autoRender)) { - this.renderer.scene.addMesh(this); - } - } - /** - * Remove a Mesh from the {@link core/scenes/Scene.Scene | Scene} and optionally from the renderer as well. - * @param removeFromRenderer - whether to remove this Mesh from the {@link Renderer#meshes | Renderer meshes array} - */ - removeFromScene(removeFromRenderer = false) { - if (__privateGet$5(this, _autoRender)) { - this.renderer.scene.removeMesh(this); - } - if (removeFromRenderer) { - this.renderer.meshes = this.renderer.meshes.filter((m) => m.uuid !== this.uuid); - } - } - /** - * Set a new {@link Renderer} for this Mesh - * @param renderer - new {@link Renderer} to set - */ - setRenderer(renderer) { - renderer = renderer && renderer.renderer || renderer; - if (!renderer || !(renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer")) { - throwWarning( - `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.` - ); - return; - } - const oldRenderer = this.renderer; - this.removeFromScene(true); - this.renderer = renderer; - this.addToScene(true); - if (!oldRenderer.meshes.length) { - oldRenderer.onBeforeRenderScene.add( - (commandEncoder) => { - oldRenderer.forceClear(commandEncoder); - }, - { once: true } - ); - } - } - /** - * Assign or remove a {@link RenderTarget} to this Mesh - * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well. - * @param outputTarget - the RenderTarget to assign or null if we want to remove the current RenderTarget - */ - setOutputTarget(outputTarget) { - if (outputTarget && outputTarget.type !== "RenderTarget") { - throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget}`); - return; - } - this.removeFromScene(); - this.outputTarget = outputTarget; - this.addToScene(); - } - /** - * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. - * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh - */ - loseContext() { - this.ready = false; - this.geometry.loseContext(); - this.material.loseContext(); - } - /** - * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored - */ - restoreContext() { - this.geometry.restoreContext(this.renderer); - this.material.restoreContext(); - } - /* SHADERS */ - /** - * Set default shaders if one or both of them are missing - */ - setShaders() { - const { shaders } = this.options; - if (!shaders) { - this.options.shaders = { - vertex: { - code: default_vsWgsl, - entryPoint: "main" - }, - fragment: { - code: default_fsWgsl, - entryPoint: "main" - } - }; - } else { - if (!shaders.vertex || !shaders.vertex.code) { - shaders.vertex = { - code: default_vsWgsl, - entryPoint: "main" - }; - } - if (shaders.fragment === void 0 || shaders.fragment && !shaders.fragment.code) { - shaders.fragment = { - code: default_fsWgsl, - entryPoint: "main" - }; - } - } - } - /* GEOMETRY */ - /** - * Set or update the Mesh {@link Geometry} - * @param geometry - new {@link Geometry} to use - */ - useGeometry(geometry) { - if (this.geometry) { - if (geometry.shouldCompute) { - geometry.computeGeometry(); - } - if (this.geometry.layoutCacheKey !== geometry.layoutCacheKey) { - throwWarning( - `${this.options.label} (${this.type}): the current and new geometries do not have the same vertexBuffers layout, causing a probable pipeline recompilation. This should be avoided. + // Percentage-closer filtering. Sample texels in the region + // to smooth the result. + var visibility = 0.0; + var closestDepth = 0.0; + let currentDepth: f32 = shadowPosition.w; + let cameraRange: f32 = pointShadow.cameraFar - pointShadow.cameraNear; + let normalizedDepth: f32 = (shadowPosition.w - pointShadow.cameraNear) / cameraRange; -Current geometry layout: + let maxSize: f32 = f32(max(textureDimensions(depthCubeTexture).x, textureDimensions(depthCubeTexture).y)); -${this.geometry.wgslStructFragment} + let texelSize: vec3f = vec3(1.0 / maxSize); + let sampleCount: i32 = pointShadow.pcfSamples; + let maxSamples: f32 = f32(sampleCount) - 1.0; + + for (var x = 0; x < sampleCount; x++) { + for (var y = 0; y < sampleCount; y++) { + for (var z = 0; z < sampleCount; z++) { + let offset = texelSize * vec3( + f32(x) - maxSamples * 0.5, + f32(y) - maxSamples * 0.5, + f32(z) - maxSamples * 0.5 + ); --------- + closestDepth = textureSampleCompareLevel( + depthCubeTexture, + depthComparisonSampler, + shadowPosition.xyz + offset, + normalizedDepth - pointShadow.bias + ); -New geometry layout: + closestDepth *= cameraRange; -${geometry.wgslStructFragment}` - ); - this.material.setAttributesFromGeometry(geometry); - this.material.setPipelineEntry(); - } - this.geometry.consumers.delete(this.uuid); - } - this.geometry = geometry; - this.geometry.consumers.add(this.uuid); - this.computeGeometry(); - if (this.material) { - const renderingOptions = { - ...this.material.options.rendering, - ...{ verticesOrder: geometry.verticesOrder, topology: geometry.topology } - }; - this.material.setRenderingOptions(renderingOptions); - } - } - /** - * Compute the Mesh geometry if needed - */ - computeGeometry() { - if (this.geometry.shouldCompute) { - this.geometry.computeGeometry(); - } - } - /** - * Set our Mesh geometry: create buffers and add attributes to material - */ - setGeometry() { - if (this.geometry) { - if (!this.geometry.ready) { - this.geometry.createBuffers({ - renderer: this.renderer, - label: this.options.label + " geometry" - }); - } - this.setMaterialGeometryAttributes(); - } - } - /* MATERIAL */ - /** - * Set or update the {@link RenderMaterial} {@link types/Materials.RenderMaterialRenderingOptions | rendering options} to match the {@link RenderPass#descriptor | RenderPass descriptor} used to draw this Mesh. - * @param renderPass - {@link RenderPass | RenderPass} used to draw this Mesh, default to the {@link core/renderers/GPURenderer.GPURenderer#renderPass | renderer renderPass}. - */ - setRenderingOptionsForRenderPass(renderPass) { - const renderingOptions = { - // transparency (blend) - transparent: this.transparent, - // sample count - sampleCount: renderPass.options.sampleCount, - // color attachments - ...renderPass.options.colorAttachments.length && { - targets: renderPass.options.colorAttachments.map((colorAttachment, index) => { - return { - // patch format... - format: colorAttachment.targetFormat, - // ...but keep original blend values if any - ...this.options.targets?.length && this.options.targets[index] && this.options.targets[index].blend && { - blend: this.options.targets[index].blend - } - }; - }) - }, - // depth - depth: renderPass.options.useDepth, - ...renderPass.options.useDepth && { - depthFormat: renderPass.options.depthFormat - } - }; - this.material?.setRenderingOptions(renderingOptions); - } - /** - * Hook used to clean up parameters before sending them to the {@link RenderMaterial}. - * @param parameters - parameters to clean before sending them to the {@link RenderMaterial} - * @returns - cleaned parameters - */ - cleanupRenderMaterialParameters(parameters) { - delete parameters.texturesOptions; - delete parameters.outputTarget; - delete parameters.autoRender; - return parameters; - } - /** - * Set or update the Mesh {@link RenderMaterial} - * @param material - new {@link RenderMaterial} to use - */ - useMaterial(material) { - this.material = material; - this.transparent = this.material.options.rendering.transparent; - this.material.options.domTextures?.filter((texture) => texture instanceof DOMTexture).forEach((texture) => this.onDOMTextureAdded(texture)); - } - /** - * Patch the shaders if needed, then set the Mesh material - * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters} - */ - setMaterial(meshParameters) { - this.setShaders(); - meshParameters.shaders = this.options.shaders; - meshParameters.label = meshParameters.label + " material"; - this.useMaterial(new RenderMaterial(this.renderer, meshParameters)); - } - /** - * Set Mesh material attributes - */ - setMaterialGeometryAttributes() { - if (this.material && !this.material.attributes) { - this.material.setAttributesFromGeometry(this.geometry); - } - } - /** - * Get the transparent property value - */ - get transparent() { - return this._transparent; - } - /** - * Set the transparent property value. Update the {@link RenderMaterial} rendering options and {@link core/scenes/Scene.Scene | Scene} stack if needed. - * @param value - */ - set transparent(value) { - const switchTransparency = this.transparent !== void 0 && value !== this.transparent; - if (switchTransparency) { - this.removeFromScene(); - } - this._transparent = value; - if (switchTransparency) { - this.addToScene(); - } - } - /** - * Get the visible property value - */ - get visible() { - return this._visible; - } - /** - * Set the visible property value - * @param value - new visibility value - */ - set visible(value) { - this._visible = value; - } - /* TEXTURES */ - /** - * Get our {@link RenderMaterial#domTextures | RenderMaterial domTextures array} - * @readonly - */ - get domTextures() { - return this.material?.domTextures || []; - } - /** - * Get our {@link RenderMaterial#textures | RenderMaterial textures array} - * @readonly - */ - get textures() { - return this.material?.textures || []; - } - /** - * Create a new {@link DOMTexture} - * @param options - {@link DOMTextureParams | DOMTexture parameters} - * @returns - newly created {@link DOMTexture} - */ - createDOMTexture(options) { - if (!options.name) { - options.name = "texture" + (this.textures.length + this.domTextures.length); - } - if (!options.label) { - options.label = this.options.label + " " + options.name; - } - const domTexture = new DOMTexture(this.renderer, { ...options, ...this.options.texturesOptions }); - this.addDOMTexture(domTexture); - return domTexture; - } - /** - * Add a {@link DOMTexture} - * @param domTexture - {@link DOMTexture} to add - */ - addDOMTexture(domTexture) { - this.material.addTexture(domTexture); - this.onDOMTextureAdded(domTexture); - } - /** - * Callback run when a new {@link DOMTexture} has been added - * @param domTexture - newly created DOMTexture - */ - onDOMTextureAdded(domTexture) { - domTexture.parentMesh = this; - } - /** - * Create a new {@link Texture} - * @param options - {@link TextureParams | Texture parameters} - * @returns - newly created {@link Texture} - */ - createTexture(options) { - if (!options.name) { - options.name = "texture" + (this.textures.length + this.domTextures.length); - } - const texture = new Texture(this.renderer, options); - this.addTexture(texture); - return texture; + visibility += select(0.0, 1.0, currentDepth <= closestDepth); } - /** - * Add a {@link Texture} - * @param texture - {@link Texture} to add - */ - addTexture(texture) { - this.material.addTexture(texture); + } + } + + visibility /= f32(sampleCount * sampleCount * sampleCount); + + visibility = clamp(visibility, 1.0 - clamp(pointShadow.intensity, 0.0, 1.0), 1.0); + + return visibility; +}` + ); + const getPCFPointShadows = (renderer) => { + const pointLights = renderer.shadowCastingLights.filter((light) => light.type === "pointLights"); + const minPointLights = Math.max(renderer.lightsBindingParams.pointLights.max, 1); + return ( + /* wgsl */ + ` +fn getPCFPointShadows(worldPosition: vec3f) -> array { + var pointShadowContribution: array; + + var lightDirection: vec3f; + var lightDistance: f32; + var lightColor: vec3f; + + ${pointLights.map((light, index) => { + return `lightDirection = pointLights.elements[${index}].position - worldPosition; + + lightDistance = length(lightDirection); + lightColor = pointLights.elements[${index}].color * rangeAttenuation(pointLights.elements[${index}].range, lightDistance); + + ${light.shadow.isActive ? ` + if(pointShadows.pointShadowsElements[${index}].isActive > 0 && length(lightColor) > 0.0001) { + pointShadowContribution[${index}] = getPCFPointShadowContribution( + ${index}, + vec4(lightDirection, length(lightDirection)), + pointShadowCubeDepthTexture${index} + ); + } else { + pointShadowContribution[${index}] = 1.0; } - /* BINDINGS */ + ` : `pointShadowContribution[${index}] = 1.0;`}`; + }).join("\n")} + + return pointShadowContribution; +} +` + ); + }; + const getPCFShadows = ( + /* wgsl */ + ` + let pointShadows = getPCFPointShadows(worldPosition); + let directionalShadows = getPCFDirectionalShadows(worldPosition); +` + ); + const applyDirectionalShadows = ( + /* wgsl */ + ` + directLight.color *= directionalShadows[i]; +` + ); + const applyPointShadows = ( + /* wgsl */ + ` + directLight.color *= pointShadows[i]; +` + ); + + var __accessCheck$d = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$b = (obj, member, getter) => { + __accessCheck$d(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$d = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$b = (obj, member, value, setter) => { + __accessCheck$d(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var __privateMethod$5 = (obj, member, method) => { + __accessCheck$d(obj, member, "access private method"); + return method; + }; + var _intensity, _bias, _normalBias, _pcfSamples, _isActive, _autoRender, _materials, _depthMaterials, _depthPassTaskID, _setParameters, setParameters_fn; + const shadowStruct = { + isActive: { + type: "i32", + value: 0 + }, + pcfSamples: { + type: "i32", + value: 0 + }, + bias: { + type: "f32", + value: 0 + }, + normalBias: { + type: "f32", + value: 0 + }, + intensity: { + type: "f32", + value: 0 + } + }; + class Shadow { + /** + * Shadow constructor + * @param renderer - {@link CameraRenderer} used to create this {@link Shadow}. + * @param parameters - {@link ShadowBaseParams | parameters} used to create this {@link Shadow}. + */ + constructor(renderer, { + light, + intensity = 1, + bias = 0, + normalBias = 0, + pcfSamples = 1, + depthTextureSize = new Vec2(512), + depthTextureFormat = "depth24plus", + autoRender = true + } = {}) { /** - * Get the current {@link RenderMaterial} uniforms - * @readonly + * Set the {@link Shadow} parameters. + * @param parameters - parameters to use for this {@link Shadow}. + * @private */ - get uniforms() { - return this.material?.uniforms; - } + __privateAdd$d(this, _setParameters); + /** @ignore */ + __privateAdd$d(this, _intensity, void 0); + /** @ignore */ + __privateAdd$d(this, _bias, void 0); + /** @ignore */ + __privateAdd$d(this, _normalBias, void 0); + /** @ignore */ + __privateAdd$d(this, _pcfSamples, void 0); + /** @ignore */ + __privateAdd$d(this, _isActive, void 0); + /** @ignore */ + __privateAdd$d(this, _autoRender, void 0); /** - * Get the current {@link RenderMaterial} storages - * @readonly + * Original {@link meshes} {@link RenderMaterial | materials}. + * @private */ - get storages() { - return this.material?.storages; - } - /* RESIZE */ + __privateAdd$d(this, _materials, void 0); /** - * Resize the Mesh's textures - * @param boundingRect + * Corresponding depth {@link meshes} {@link RenderMaterial | materials}. + * @private */ - resize(boundingRect) { - if (super.resize) { - super.resize(boundingRect); - } - this.textures?.forEach((texture) => { - if (texture.options.fromTexture) { - texture.copy(texture.options.fromTexture); - } - }); - this.domTextures?.forEach((texture) => { - texture.resize(); - }); - this._onAfterResizeCallback && this._onAfterResizeCallback(); + __privateAdd$d(this, _depthMaterials, void 0); + /** @ignore */ + __privateAdd$d(this, _depthPassTaskID, void 0); + this.setRenderer(renderer); + this.light = light; + this.index = this.light.index; + this.options = { + light, + intensity, + bias, + normalBias, + pcfSamples, + depthTextureSize, + depthTextureFormat + }; + this.sampleCount = 1; + this.meshes = /* @__PURE__ */ new Map(); + __privateSet$b(this, _materials, /* @__PURE__ */ new Map()); + __privateSet$b(this, _depthMaterials, /* @__PURE__ */ new Map()); + __privateSet$b(this, _depthPassTaskID, null); + __privateMethod$5(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); + this.isActive = false; + } + /** + * Set or reset this shadow {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + renderer = isCameraRenderer(renderer, this.constructor.name); + this.renderer = renderer; + this.setRendererBinding(); + __privateGet$b(this, _depthMaterials)?.forEach((depthMaterial) => { + depthMaterial.setRenderer(this.renderer); + }); + } + /** @ignore */ + setRendererBinding() { + this.rendererBinding = null; + } + /** + * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
+ * Called internally by the associated {@link core/lights/Light.Light | Light} if any shadow parameters are specified when creating it. Can also be called directly. + * @param parameters - parameters to use for this {@link Shadow}. + */ + cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender } = {}) { + __privateMethod$5(this, _setParameters, setParameters_fn).call(this, { intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); + this.isActive = true; + } + /** + * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link core/lights/Light.Light | lights} has been overflowed. + */ + reset() { + this.onPropertyChanged("isActive", this.isActive ? 1 : 0); + this.onPropertyChanged("intensity", this.intensity); + this.onPropertyChanged("bias", this.bias); + this.onPropertyChanged("normalBias", this.normalBias); + this.onPropertyChanged("pcfSamples", this.pcfSamples); + } + /** + * Get whether this {@link Shadow} is actually casting shadows. + * @returns - Whether this {@link Shadow} is actually casting shadows. + */ + get isActive() { + return __privateGet$b(this, _isActive); + } + /** + * Start or stop casting shadows. + * @param value - New active state. + */ + set isActive(value) { + if (!value && this.isActive) { + this.destroy(); + } else if (value && !this.isActive) { + this.init(); } - /* EVENTS */ - /** - * Callback to execute when a Mesh is ready - i.e. its {@link material} and {@link geometry} are ready. - * @param callback - callback to run when {@link MeshBase} is ready - * @returns - our Mesh - */ - onReady(callback) { - if (callback) { - this._onReadyCallback = callback; - } - return this; + __privateSet$b(this, _isActive, value); + } + /** + * Get this {@link Shadow} intensity. + * @returns - The {@link Shadow} intensity. + */ + get intensity() { + return __privateGet$b(this, _intensity); + } + /** + * Set this {@link Shadow} intensity and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param value - The new {@link Shadow} intensity. + */ + set intensity(value) { + __privateSet$b(this, _intensity, value); + this.onPropertyChanged("intensity", this.intensity); + } + /** + * Get this {@link Shadow} bias. + * @returns - The {@link Shadow} bias. + */ + get bias() { + return __privateGet$b(this, _bias); + } + /** + * Set this {@link Shadow} bias and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param value - The new {@link Shadow} bias. + */ + set bias(value) { + __privateSet$b(this, _bias, value); + this.onPropertyChanged("bias", this.bias); + } + /** + * Get this {@link Shadow} normal bias. + * @returns - The {@link Shadow} normal bias. + */ + get normalBias() { + return __privateGet$b(this, _normalBias); + } + /** + * Set this {@link Shadow} normal bias and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param value - The new {@link Shadow} normal bias. + */ + set normalBias(value) { + __privateSet$b(this, _normalBias, value); + this.onPropertyChanged("normalBias", this.normalBias); + } + /** + * Get this {@link Shadow} PCF samples count. + * @returns - The {@link Shadow} PCF samples count. + */ + get pcfSamples() { + return __privateGet$b(this, _pcfSamples); + } + /** + * Set this {@link Shadow} PCF samples count and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param value - The new {@link Shadow} PCF samples count. + */ + set pcfSamples(value) { + __privateSet$b(this, _pcfSamples, Math.max(1, Math.ceil(value))); + this.onPropertyChanged("pcfSamples", this.pcfSamples); + } + /** + * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget} and start rendering to the shadow map. + */ + init() { + if (!this.depthComparisonSampler) { + const samplerExists = this.renderer.samplers.find((sampler) => sampler.name === "depthComparisonSampler"); + this.depthComparisonSampler = samplerExists || new Sampler(this.renderer, { + label: "Depth comparison sampler", + name: "depthComparisonSampler", + // we do not want to repeat the shadows + addressModeU: "clamp-to-edge", + addressModeV: "clamp-to-edge", + compare: "less", + minFilter: "linear", + magFilter: "linear", + type: "comparison" + }); } - /** - * Callback to execute before updating the {@link core/scenes/Scene.Scene | Scene} matrix stack. This means it is called early and allows to update transformations values before actually setting the Mesh matrices (if any). This also means it won't be called if the Mesh has not been added to the {@link core/scenes/Scene.Scene | Scene}. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. - * @param callback - callback to run just before updating the {@link core/scenes/Scene.Scene | Scene} matrix stack. - * @returns - our Mesh - */ - onBeforeRender(callback) { - if (callback) { - this._onBeforeRenderCallback = callback; - } - return this; + this.setDepthTexture(); + if (!this.depthPassTarget) { + this.createDepthPassTarget(); } - /** - * Callback to execute right before actually rendering the Mesh. Useful to update uniforms for example. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. - * @param callback - callback to run just before rendering the {@link MeshBase} - * @returns - our Mesh - */ - onRender(callback) { - if (callback) { - this._onRenderCallback = callback; - } - return this; + if (__privateGet$b(this, _depthPassTaskID) === null && __privateGet$b(this, _autoRender)) { + this.setDepthPass(); + this.onPropertyChanged("isActive", 1); } - /** - * Callback to execute just after a Mesh has been rendered. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. - * @param callback - callback to run just after {@link MeshBase} has been rendered - * @returns - our Mesh - */ - onAfterRender(callback) { - if (callback) { - this._onAfterRenderCallback = callback; + } + /** + * Reset the {@link depthTexture} when the {@link depthTextureSize} changes. + */ + onDepthTextureSizeChanged() { + this.setDepthTexture(); + } + /** + * Set or resize the {@link depthTexture} and eventually resize the {@link depthPassTarget} as well. + */ + setDepthTexture() { + if (this.depthTexture && (this.depthTexture.size.width !== this.depthTextureSize.x || this.depthTexture.size.height !== this.depthTextureSize.y)) { + this.depthTexture.options.fixedSize.width = this.depthTextureSize.x; + this.depthTexture.options.fixedSize.height = this.depthTextureSize.y; + this.depthTexture.size.width = this.depthTextureSize.x; + this.depthTexture.size.height = this.depthTextureSize.y; + this.depthTexture.createTexture(); + if (this.depthPassTarget) { + this.depthPassTarget.resize(); } - return this; + } else if (!this.depthTexture) { + this.createDepthTexture(); } - /** - * Callback to execute just after a Mesh has been resized. - * @param callback - callback to run just after {@link MeshBase} has been resized - * @returns - our Mesh - */ - onAfterResize(callback) { - if (callback) { - this._onAfterResizeCallback = callback; + } + /** + * Create the {@link depthTexture}. + */ + createDepthTexture() { + this.depthTexture = new Texture(this.renderer, { + label: `${this.constructor.name} (index: ${this.light.index}) depth texture`, + name: "shadowDepthTexture" + this.index, + type: "depth", + format: this.depthTextureFormat, + sampleCount: this.sampleCount, + fixedSize: { + width: this.depthTextureSize.x, + height: this.depthTextureSize.y + }, + autoDestroy: false + // do not destroy when removing a mesh + }); + } + /** + * Create the {@link depthPassTarget}. + */ + createDepthPassTarget() { + this.depthPassTarget = new RenderTarget(this.renderer, { + label: "Depth pass render target for " + this.constructor.name + " " + this.index, + useColorAttachments: false, + depthTexture: this.depthTexture, + sampleCount: this.sampleCount + }); + } + /** + * Update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} input value and tell the {@link CameraRenderer#cameraLightsBindGroup | renderer camera, lights and shadows} bind group to update. + * @param propertyKey - name of the property to update. + * @param value - new value of the property. + */ + onPropertyChanged(propertyKey, value) { + if (this.rendererBinding) { + if (value instanceof Mat4) { + for (let i = 0; i < value.elements.length; i++) { + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].value[i] = value.elements[i]; + } + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].shouldUpdate = true; + } else { + this.rendererBinding.childrenBindings[this.index].inputs[propertyKey].value = value; } - return this; - } - /* RENDER */ - /** - * Execute {@link onBeforeRender} callback if needed. Called by the {@link core/scenes/Scene.Scene | Scene} before updating the matrix stack. - */ - onBeforeRenderScene() { - if (!this.renderer.ready || !this.ready || !this.visible) - return; - this._onBeforeRenderCallback && this._onBeforeRenderCallback(); - } - /** - * Called before rendering the Mesh - * Set the geometry if needed (create buffers and add attributes to the {@link RenderMaterial}) - * Then executes {@link RenderMaterial#onBeforeRender}: create its bind groups and pipeline if needed and eventually update its bindings - */ - onBeforeRenderPass() { - if (!this.renderer.ready) - return; - this.ready = this.material && this.material.ready && this.geometry && this.geometry.ready; - this.setGeometry(); - this.material.onBeforeRender(); - } - /** - * Render our {@link MeshBase} if the {@link RenderMaterial} is ready - * @param pass - current render pass encoder - */ - onRenderPass(pass) { - if (!this.ready) - return; - this._onRenderCallback && this._onRenderCallback(); - this.material.render(pass); - this.geometry.render(pass); - } - /** - * Called after having rendered the Mesh - */ - onAfterRenderPass() { - this._onAfterRenderCallback && this._onAfterRenderCallback(); + this.renderer.shouldUpdateCameraLightsBindGroup(); } - /** - * Render our Mesh - * - Execute {@link onBeforeRenderPass} - * - Stop here if {@link Renderer} is not ready or Mesh is not {@link visible} - * - Execute super render call if it exists - * - {@link onRenderPass | render} our {@link material} and {@link geometry} - * - Execute {@link onAfterRenderPass} - * @param pass - current render pass encoder - */ - render(pass) { - this.onBeforeRenderPass(); - if (!this.renderer.ready || !this.visible) - return; - if (super.render) { - super.render(); + } + /** + * Start the depth pass. + */ + setDepthPass() { + __privateSet$b(this, _depthPassTaskID, this.render()); + } + /** + * Remove the depth pass from its {@link utils/TasksQueueManager.TasksQueueManager | task queue manager}. + * @param depthPassTaskID - Task queue manager ID to use for removal. + */ + removeDepthPass(depthPassTaskID) { + this.renderer.onBeforeRenderScene.remove(depthPassTaskID); + } + /** + * Render the depth pass. This happens before rendering the {@link CameraRenderer#scene | scene}.
+ * - Force all the {@link meshes} to use their depth materials + * - Render all the {@link meshes} + * - Reset all the {@link meshes} materials to their original one. + * @param once - Whether to render it only once or not. + */ + render(once = false) { + return this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + if (!this.meshes.size) + return; + this.useDepthMaterials(); + this.renderDepthPass(commandEncoder); + this.useOriginalMaterials(); + this.renderer.pipelineManager.resetCurrentPipeline(); + }, + { + once, + order: this.index } - !this.renderer.production && pass.pushDebugGroup(this.options.label); - this.onRenderPass(pass); - !this.renderer.production && pass.popDebugGroup(); - this.onAfterRenderPass(); + ); + } + /** + * Render the shadow map only once. Useful with static scenes if autoRender has been set to `false` to only take one snapshot of the shadow map. + */ + async renderOnce() { + if (!__privateGet$b(this, _autoRender)) { + this.onPropertyChanged("isActive", 1); + this.useDepthMaterials(); + this.meshes.forEach((mesh) => { + mesh.setGeometry(); + }); + await Promise.all( + [...__privateGet$b(this, _depthMaterials).values()].map(async (depthMaterial) => { + await depthMaterial.compileMaterial(); + }) + ); + this.render(true); } - /* DESTROY */ - /** - * Remove the Mesh from the {@link core/scenes/Scene.Scene | Scene} and destroy it - */ - remove() { - this.removeFromScene(true); - this.destroy(); - if (!this.renderer.meshes.length) { - this.renderer.onBeforeRenderScene.add( - (commandEncoder) => { - this.renderer.forceClear(commandEncoder); - }, - { once: true } - ); + } + /** + * Render all the {@link meshes} into the {@link depthPassTarget}. + * @param commandEncoder - {@link GPUCommandEncoder} to use. + */ + renderDepthPass(commandEncoder) { + const renderBundles = /* @__PURE__ */ new Map(); + this.meshes.forEach((mesh) => { + if (mesh.options.renderBundle) { + renderBundles.set(mesh.options.renderBundle.uuid, mesh.options.renderBundle); } + }); + renderBundles.forEach((bundle) => { + bundle.updateBinding(); + }); + renderBundles.clear(); + this.renderer.pipelineManager.resetCurrentPipeline(); + const depthPass = commandEncoder.beginRenderPass(this.depthPassTarget.renderPass.descriptor); + if (!this.renderer.production) + depthPass.pushDebugGroup(`${this.constructor.name} (index: ${this.index}): depth pass`); + this.meshes.forEach((mesh) => { + mesh.render(depthPass); + }); + if (!this.renderer.production) + depthPass.popDebugGroup(); + depthPass.end(); + } + /** + * Get the default depth pass vertex shader for this {@link Shadow}. + * @returns - Depth pass vertex shader. + */ + getDefaultShadowDepthVs(hasInstances = false) { + return { + /** Returned code. */ + code: getDefaultShadowDepthVs(this.index, hasInstances) + }; + } + /** + * Get the default depth pass fragment shader for this {@link Shadow}. + * @returns - A {@link ShaderOptions} if a depth pass fragment shader is needed, `false` otherwise. + */ + getDefaultShadowDepthFs() { + return false; + } + /** + * Patch the given {@link ProjectedMesh | mesh} material parameters to create the depth material. + * @param mesh - original {@link ProjectedMesh | mesh} to use. + * @param parameters - Optional additional parameters to use for the depth material. + * @returns - Patched parameters. + */ + patchShadowCastingMeshParams(mesh, parameters = {}) { + parameters = { ...mesh.material.options.rendering, ...parameters }; + parameters.targets = []; + parameters.sampleCount = this.sampleCount; + parameters.depthFormat = this.depthTextureFormat; + if (parameters.bindings) { + parameters.bindings = [mesh.material.getBufferBindingByName("matrices"), ...parameters.bindings]; + } else { + parameters.bindings = [mesh.material.getBufferBindingByName("matrices")]; } - /** - * Destroy the Mesh - */ - destroy() { - if (super.destroy) { - super.destroy(); - } - this.material?.destroy(); - this.geometry.consumers.delete(this.uuid); - if (!this.geometry.consumers.size) { - this.geometry?.destroy(this.renderer); - } + const hasInstances = mesh.material.inputsBindings.get("instances") && mesh.geometry.instancesCount > 1; + if (!parameters.shaders) { + parameters.shaders = { + vertex: this.getDefaultShadowDepthVs(hasInstances), + fragment: this.getDefaultShadowDepthFs() + }; } - }, _autoRender = new WeakMap(), _a; - } - - class CacheManager { + return parameters; + } /** - * CacheManager constructor + * Add a {@link ProjectedMesh | mesh} to the shadow map. Internally called by the {@link ProjectedMesh | mesh} if its `castShadows` parameters has been set to `true`, but can also be called externally to selectively cast shadows or to add specific parameters (such as custom depth pass shaders). + * - Save the original {@link ProjectedMesh | mesh} material. + * - {@link patchShadowCastingMeshParams | Patch} the parameters. + * - Create a new depth {@link RenderMaterial} with the patched parameters. + * - Add the {@link ProjectedMesh | mesh} to the {@link meshes} Map. + * @param mesh - {@link ProjectedMesh | mesh} to add to the shadow map. + * @param parameters - Optional {@link RenderMaterialParams | parameters} to use for the depth material. */ - constructor() { - this.planeGeometries = []; + addShadowCastingMesh(mesh, parameters = {}) { + if (this.meshes.get(mesh.uuid)) + return; + mesh.options.castShadows = true; + __privateGet$b(this, _materials).set(mesh.uuid, mesh.material); + parameters = this.patchShadowCastingMeshParams(mesh, parameters); + if (__privateGet$b(this, _depthMaterials).get(mesh.uuid)) { + __privateGet$b(this, _depthMaterials).get(mesh.uuid).destroy(); + __privateGet$b(this, _depthMaterials).delete(mesh.uuid); + } + __privateGet$b(this, _depthMaterials).set( + mesh.uuid, + new RenderMaterial(this.renderer, { + label: `${this.constructor.name} (index: ${this.index}) ${mesh.options.label} depth render material`, + ...parameters + }) + ); + this.meshes.set(mesh.uuid, mesh); } /** - * Check if a given {@link PlaneGeometry} is already cached based on its {@link PlaneGeometry#definition.id | definition id} - * @param planeGeometry - {@link PlaneGeometry} to check - * @returns - {@link PlaneGeometry} found or null if not found + * Force all the {@link meshes} to use the depth material. */ - getPlaneGeometry(planeGeometry) { - return this.planeGeometries.find((element) => element.definition.id === planeGeometry.definition.id); + useDepthMaterials() { + this.meshes.forEach((mesh) => { + mesh.useMaterial(__privateGet$b(this, _depthMaterials).get(mesh.uuid)); + }); } /** - * Check if a given {@link PlaneGeometry} is already cached based on its {@link PlaneGeometry#definition | definition id} - * @param planeGeometryID - {@link PlaneGeometry#definition.id | PlaneGeometry definition id} - * @returns - {@link PlaneGeometry} found or null if not found + * Force all the {@link meshes} to use their original material. */ - getPlaneGeometryByID(planeGeometryID) { - return this.planeGeometries.find((element) => element.definition.id === planeGeometryID); + useOriginalMaterials() { + this.meshes.forEach((mesh) => { + mesh.useMaterial(__privateGet$b(this, _materials).get(mesh.uuid)); + }); } /** - * Add a {@link PlaneGeometry} to our cache {@link planeGeometries} array - * @param planeGeometry + * Remove a {@link ProjectedMesh | mesh} from the shadow map and destroy its depth material. + * @param mesh - {@link ProjectedMesh | mesh} to remove. */ - addPlaneGeometry(planeGeometry) { - this.planeGeometries.push(planeGeometry); + removeMesh(mesh) { + const depthMaterial = __privateGet$b(this, _depthMaterials).get(mesh.uuid); + if (depthMaterial) { + depthMaterial.destroy(); + __privateGet$b(this, _depthMaterials).delete(mesh.uuid); + } + this.meshes.delete(mesh.uuid); } /** - * Destroy our {@link CacheManager} + * Destroy the {@link Shadow}. */ destroy() { - this.planeGeometries = []; + this.onPropertyChanged("isActive", 0); + if (__privateGet$b(this, _depthPassTaskID) !== null) { + this.removeDepthPass(__privateGet$b(this, _depthPassTaskID)); + __privateSet$b(this, _depthPassTaskID, null); + } + this.meshes.forEach((mesh) => this.removeMesh(mesh)); + __privateSet$b(this, _materials, /* @__PURE__ */ new Map()); + __privateSet$b(this, _depthMaterials, /* @__PURE__ */ new Map()); + this.meshes = /* @__PURE__ */ new Map(); + this.depthPassTarget?.destroy(); + this.depthTexture?.destroy(); } } - const cacheManager = new CacheManager(); + _intensity = new WeakMap(); + _bias = new WeakMap(); + _normalBias = new WeakMap(); + _pcfSamples = new WeakMap(); + _isActive = new WeakMap(); + _autoRender = new WeakMap(); + _materials = new WeakMap(); + _depthMaterials = new WeakMap(); + _depthPassTaskID = new WeakMap(); + _setParameters = new WeakSet(); + setParameters_fn = function({ + intensity = 1, + bias = 0, + normalBias = 0, + pcfSamples = 1, + depthTextureSize = new Vec2(512), + depthTextureFormat = "depth24plus", + autoRender = true + } = {}) { + this.intensity = intensity; + this.bias = bias; + this.normalBias = normalBias; + this.pcfSamples = pcfSamples; + this.depthTextureSize = depthTextureSize; + this.depthTextureSize.onChange(() => this.onDepthTextureSizeChanged()); + this.depthTextureFormat = depthTextureFormat; + __privateSet$b(this, _autoRender, autoRender); + }; - class FullscreenPlane extends MeshBaseMixin(class { - }) { + const directionalShadowStruct = { + ...shadowStruct, + viewMatrix: { + type: "mat4x4f", + value: new Float32Array(16) + }, + projectionMatrix: { + type: "mat4x4f", + value: new Float32Array(16) + } + }; + class DirectionalShadow extends Shadow { /** - * FullscreenPlane constructor - * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link FullscreenPlane} - * @param parameters - {@link MeshBaseRenderParams | parameters} use to create this {@link FullscreenPlane} + * DirectionalShadow constructor + * @param renderer - {@link CameraRenderer} used to create this {@link DirectionalShadow}. + * @param parameters - {@link DirectionalShadowParams | parameters} used to create this {@link DirectionalShadow}. */ - constructor(renderer, parameters = {}) { - renderer = isRenderer(renderer, parameters.label ? parameters.label + " FullscreenQuadMesh" : "FullscreenQuadMesh"); - let geometry = cacheManager.getPlaneGeometryByID(2); - if (!geometry) { - geometry = new PlaneGeometry({ widthSegments: 1, heightSegments: 1 }); - cacheManager.addPlaneGeometry(geometry); - } - if (!parameters.shaders || !parameters.shaders.vertex) { - ["uniforms", "storages"].forEach((bindingType) => { - Object.values(parameters[bindingType] ?? {}).forEach( - (binding) => binding.visibility = ["fragment"] - ); - }); - } - parameters.depthWriteEnabled = false; - if (!parameters.label) { - parameters.label = "FullscreenQuadMesh"; + constructor(renderer, { + light, + intensity, + bias, + normalBias, + pcfSamples, + depthTextureSize, + depthTextureFormat, + autoRender, + camera = { + left: -10, + right: 10, + bottom: -10, + top: 10, + near: 0.1, + far: 50 } - super(renderer, null, { geometry, ...parameters }); - this.size = { - document: { - width: this.renderer.boundingRect.width, - height: this.renderer.boundingRect.height, - top: this.renderer.boundingRect.top, - left: this.renderer.boundingRect.left - } + } = {}) { + super(renderer, { + light, + intensity, + bias, + normalBias, + pcfSamples, + depthTextureSize, + depthTextureFormat, + autoRender + }); + this.options = { + ...this.options, + camera }; - this.type = "FullscreenQuadMesh"; + this.camera = { + projectionMatrix: new Mat4(), + viewMatrix: new Mat4(), + up: new Vec3(0, 1, 0), + _left: camera.left, + _right: camera.right, + _bottom: camera.bottom, + _top: camera.top, + _near: camera.near, + _far: camera.far + }; + const _self = this; + const cameraProps = ["left", "right", "bottom", "top", "near", "far"]; + cameraProps.forEach((prop) => { + Object.defineProperty(_self.camera, prop, { + get() { + return _self.camera["_" + prop]; + }, + set(v) { + _self.camera["_" + prop] = v; + _self.updateProjectionMatrix(); + } + }); + }); + } + /** + * Set or reset this {@link DirectionalShadow} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + */ + setRendererBinding() { + this.rendererBinding = this.renderer.bindings.directionalShadows; + } + /** + * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
+ * Called internally by the associated {@link DirectionalLight} if any shadow parameters are specified when creating it. Can also be called directly. + * @param parameters - parameters to use for this {@link DirectionalShadow}. + */ + cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender, camera } = {}) { + if (camera) { + this.camera.left = camera.left ?? -10; + this.camera.right = camera.right ?? 10; + this.camera.bottom = camera.bottom ?? -10; + this.camera.top = camera.right ?? 10; + this.camera.near = camera.near ?? 0.1; + this.camera.far = camera.far ?? 50; + } + super.cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); } /** - * Resize our {@link FullscreenPlane} - * @param boundingRect - the new bounding rectangle + * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget}, compute the {@link DirectionalShadow#camera.projectionMatrix | camera projection matrix} and start rendering to the shadow map. */ - resize(boundingRect = null) { - this.size.document = boundingRect ?? this.renderer.boundingRect; - super.resize(boundingRect); + init() { + super.init(); + this.updateProjectionMatrix(); } /** - * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link FullscreenPlane} - * It ranges from -1 to 1 on both axis - * @param mouseCoords - pointer {@link Vec2 | vector} coordinates - * @returns - the mapped {@link Vec2 | vector} coordinates in the [-1, 1] range + * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link DirectionalLight} has been overflowed. */ - mouseToPlaneCoords(mouseCoords = new Vec2()) { - return new Vec2( - (mouseCoords.x - this.size.document.left) / this.size.document.width * 2 - 1, - 1 - (mouseCoords.y - this.size.document.top) / this.size.document.height * 2 - ); + reset() { + this.setRendererBinding(); + super.reset(); + this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); + this.onPropertyChanged("viewMatrix", this.camera.viewMatrix); } - } - - class Mat3 { - // prettier-ignore /** - * Mat3 constructor - * @param elements - initial array to use, default to identity matrix + * Update the {@link DirectionalShadow#camera.projectionMatrix | camera orthographic projection matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. */ - constructor(elements = new Float32Array([ - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 1 - ])) { - this.type = "Mat3"; - this.elements = elements; + updateProjectionMatrix() { + this.camera.projectionMatrix.identity().makeOrthographic({ + left: this.camera.left, + right: this.camera.right, + bottom: this.camera.bottom, + top: this.camera.top, + near: this.camera.near, + far: this.camera.far + }); + this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); } /** - * Sets the matrix from 9 numbers - * - * @param n11 - number - * @param n12 - number - * @param n13 - number - * @param n21 - number - * @param n22 - number - * @param n23 - number - * @param n31 - number - * @param n32 - number - * @param n33 - number - * @returns - this {@link Mat3} after being set + * Update the {@link DirectionalShadow#camera.viewMatrix | camera view matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param position - {@link Vec3} to use as position for the {@link DirectionalShadow#camera.viewMatrix | camera view matrix}, based on the {@link light} position. + * @param target - {@link Vec3} to use as target for the {@link DirectionalShadow#camera.viewMatrix | camera view matrix}, based on the {@link light} target. */ - set(n11, n12, n13, n21, n22, n23, n31, n32, n33) { - const te = this.elements; - te[0] = n11; - te[1] = n21; - te[2] = n31; - te[3] = n12; - te[4] = n22; - te[5] = n32; - te[6] = n13; - te[7] = n23; - te[8] = n33; - return this; + updateViewMatrix(position = new Vec3(), target = new Vec3()) { + if (position.x === 0 && position.z === 0) { + this.camera.up.set(0, 0, 1); + } else { + this.camera.up.set(0, 1, 0); + } + this.camera.viewMatrix.makeView(position, target, this.camera.up); + this.onPropertyChanged("viewMatrix", this.camera.viewMatrix); } + } + + var __accessCheck$c = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$a = (obj, member, getter) => { + __accessCheck$c(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$c = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$a = (obj, member, value, setter) => { + __accessCheck$c(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var _actualPosition$1, _direction; + class DirectionalLight extends Light { /** - * Sets the {@link Mat3} to an identity matrix - * @returns - this {@link Mat3} after being set + * DirectionalLight constructor + * @param renderer - {@link CameraRenderer} used to create this {@link DirectionalLight}. + * @param parameters - {@link DirectionalLightBaseParams | parameters} used to create this {@link DirectionalLight}. */ - identity() { - this.set(1, 0, 0, 0, 1, 0, 0, 0, 1); - return this; + constructor(renderer, { + color = new Vec3(1), + intensity = 1, + position = new Vec3(1), + target = new Vec3(), + shadow = null + } = {}) { + const type = "directionalLights"; + super(renderer, { color, intensity, type }); + /** @ignore */ + __privateAdd$c(this, _actualPosition$1, void 0); + /** + * The {@link Vec3 | direction} of the {@link DirectionalLight} is the {@link target} minus the actual {@link position}. + * @private + */ + __privateAdd$c(this, _direction, void 0); + this.options = { + ...this.options, + position, + target, + shadow + }; + __privateSet$a(this, _direction, new Vec3()); + __privateSet$a(this, _actualPosition$1, new Vec3()); + this.target = target; + this.target.onChange(() => this.setDirection()); + this.position.copy(position); + this.parent = this.renderer.scene; + this.shadow = new DirectionalShadow(this.renderer, { + autoRender: false, + // will be set by calling cast() + light: this + }); + if (shadow) { + this.shadow.cast(shadow); + } } /** - * Sets the {@link Mat3} values from an array - * @param array - array to use - * @returns - this {@link Mat3} after being set + * Set or reset this {@link DirectionalLight} {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. */ - // prettier-ignore - setFromArray(array = new Float32Array([ - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 1 - ])) { - for (let i = 0; i < this.elements.length; i++) { - this.elements[i] = array[i]; - } - return this; + setRenderer(renderer) { + this.shadow?.setRenderer(renderer); + super.setRenderer(renderer); } /** - * Copy another {@link Mat3} - * @param matrix - matrix to copy - * @returns - this {@link Mat3} after being set + * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link DirectionalLight} has been overflowed. */ - copy(matrix = new Mat3()) { - const array = matrix.elements; - this.elements[0] = array[0]; - this.elements[1] = array[1]; - this.elements[2] = array[2]; - this.elements[3] = array[3]; - this.elements[4] = array[4]; - this.elements[5] = array[5]; - this.elements[6] = array[6]; - this.elements[7] = array[7]; - this.elements[8] = array[8]; - return this; + reset() { + super.reset(); + this.setDirection(); + this.shadow?.reset(); } /** - * Clone a {@link Mat3} - * @returns - cloned {@link Mat3} + * Set the {@link DirectionalLight} direction based on the {@link target} and the {@link worldMatrix} translation and update the {@link DirectionalShadow} view matrix. */ - clone() { - return new Mat3().copy(this); + setDirection() { + __privateGet$a(this, _direction).copy(this.target).sub(this.worldMatrix.getTranslation(__privateGet$a(this, _actualPosition$1))); + this.onPropertyChanged("direction", __privateGet$a(this, _direction)); + this.shadow?.updateViewMatrix(__privateGet$a(this, _actualPosition$1), this.target); + } + // explicitly disable scale and transform origin transformations + /** @ignore */ + applyScale() { + } + /** @ignore */ + applyTransformOrigin() { } /** - * Set a {@link Mat3} from a {@link Mat4}. - * @param matrix - {@link Mat4} to use. - * @returns - this {@link Mat3} after being set. + * If the {@link modelMatrix | model matrix} has been updated, set the new direction from the {@link worldMatrix} translation. */ - setFromMat4(matrix = new Mat4()) { - const me = matrix.elements; - this.set(me[0], me[4], me[8], me[1], me[5], me[9], me[2], me[6], me[10]); - return this; + updateMatrixStack() { + super.updateMatrixStack(); + if (this.matricesNeedUpdate) { + this.setDirection(); + } } /** - * Multiply this {@link Mat3} with another {@link Mat3} - * @param matrix - {@link Mat3} to multiply with - * @returns - this {@link Mat3} after multiplication + * Tell the {@link renderer} that the maximum number of {@link DirectionalLight} has been overflown. + * @param lightsType - {@link type} of this light. */ - multiply(matrix = new Mat3()) { - return this.multiplyMatrices(this, matrix); + onMaxLightOverflow(lightsType) { + super.onMaxLightOverflow(lightsType); + this.shadow?.setRendererBinding(); + } + /** + * Destroy this {@link DirectionalLight} and associated {@link DirectionalShadow}. + */ + destroy() { + super.destroy(); + this.shadow.destroy(); + } + } + _actualPosition$1 = new WeakMap(); + _direction = new WeakMap(); + + var __accessCheck$b = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$9 = (obj, member, getter) => { + __accessCheck$b(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$b = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$9 = (obj, member, value, setter) => { + __accessCheck$b(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var _tempCubeDirection; + const pointShadowStruct = { + face: { + type: "i32", + value: 0 + }, + ...shadowStruct, + cameraNear: { + type: "f32", + value: 0 + }, + cameraFar: { + type: "f32", + value: 0 + }, + projectionMatrix: { + type: "mat4x4f", + value: new Float32Array(16) + }, + viewMatrices: { + type: "array", + value: new Float32Array(16 * 6) } + }; + class PointShadow extends Shadow { /** - * Multiply another {@link Mat3} with this {@link Mat3} - * @param matrix - {@link Mat3} to multiply with - * @returns - this {@link Mat3} after multiplication + * PointShadow constructor + * @param renderer - {@link CameraRenderer} used to create this {@link PointShadow}. + * @param parameters - {@link PointShadowParams | parameters} used to create this {@link PointShadow}. */ - premultiply(matrix = new Mat3()) { - return this.multiplyMatrices(matrix, this); + constructor(renderer, { + light, + intensity, + bias, + normalBias, + pcfSamples, + depthTextureSize, + depthTextureFormat, + autoRender, + camera = { + near: 0.1, + far: 150 + } + } = {}) { + super(renderer, { + light, + intensity, + bias, + normalBias, + pcfSamples, + depthTextureSize, + depthTextureFormat, + autoRender + }); + /** + * {@link Vec3} used to calculate the actual current direction based on the {@link PointLight} position. + * @private + */ + __privateAdd$b(this, _tempCubeDirection, void 0); + this.options = { + ...this.options, + camera + }; + this.cubeDirections = [ + new Vec3(-1, 0, 0), + new Vec3(1, 0, 0), + new Vec3(0, -1, 0), + new Vec3(0, 1, 0), + new Vec3(0, 0, -1), + new Vec3(0, 0, 1) + ]; + __privateSet$9(this, _tempCubeDirection, new Vec3()); + this.cubeUps = [ + new Vec3(0, -1, 0), + new Vec3(0, -1, 0), + new Vec3(0, 0, 1), + new Vec3(0, 0, -1), + new Vec3(0, -1, 0), + new Vec3(0, -1, 0) + ]; + if (camera.far <= 0) { + camera.far = 150; + } + this.camera = { + projectionMatrix: new Mat4(), + viewMatrices: [], + _near: camera.near, + _far: camera.far + }; + for (let i = 0; i < 6; i++) { + this.camera.viewMatrices.push(new Mat4()); + } + const _self = this; + const cameraProps = ["near", "far"]; + cameraProps.forEach((prop) => { + Object.defineProperty(_self.camera, prop, { + get() { + return _self.camera["_" + prop]; + }, + set(v) { + _self.camera["_" + prop] = v; + _self.updateProjectionMatrix(); + } + }); + }); } /** - * Multiply two {@link Mat3} - * @param a - first {@link Mat3} - * @param b - second {@link Mat3} - * @returns - {@link Mat3} resulting from the multiplication + * Set or reset this {@link PointShadow} {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. */ - multiplyMatrices(a = new Mat3(), b = new Mat3()) { - const ae = a.elements; - const be = b.elements; - const te = this.elements; - const a11 = ae[0], a12 = ae[3], a13 = ae[6]; - const a21 = ae[1], a22 = ae[4], a23 = ae[7]; - const a31 = ae[2], a32 = ae[5], a33 = ae[8]; - const b11 = be[0], b12 = be[3], b13 = be[6]; - const b21 = be[1], b22 = be[4], b23 = be[7]; - const b31 = be[2], b32 = be[5], b33 = be[8]; - te[0] = a11 * b11 + a12 * b21 + a13 * b31; - te[3] = a11 * b12 + a12 * b22 + a13 * b32; - te[6] = a11 * b13 + a12 * b23 + a13 * b33; - te[1] = a21 * b11 + a22 * b21 + a23 * b31; - te[4] = a21 * b12 + a22 * b22 + a23 * b32; - te[7] = a21 * b13 + a22 * b23 + a23 * b33; - te[2] = a31 * b11 + a32 * b21 + a33 * b31; - te[5] = a31 * b12 + a32 * b22 + a33 * b32; - te[8] = a31 * b13 + a32 * b23 + a33 * b33; - return this; + setRendererBinding() { + this.rendererBinding = this.renderer.bindings.pointShadows; } /** - * Invert this {@link Mat3}. - * @returns - this {@link Mat3} after being inverted + * Set the parameters and start casting shadows by setting the {@link isActive} setter to `true`.
+ * Called internally by the associated {@link PointLight} if any shadow parameters are specified when creating it. Can also be called directly. + * @param parameters - parameters to use for this {@link PointShadow}. */ - invert() { - const te = this.elements, n11 = te[0], n21 = te[1], n31 = te[2], n12 = te[3], n22 = te[4], n32 = te[5], n13 = te[6], n23 = te[7], n33 = te[8], t11 = n33 * n22 - n32 * n23, t12 = n32 * n13 - n33 * n12, t13 = n23 * n12 - n22 * n13, det = n11 * t11 + n21 * t12 + n31 * t13; - if (det === 0) - return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0); - const detInv = 1 / det; - te[0] = t11 * detInv; - te[1] = (n31 * n23 - n33 * n21) * detInv; - te[2] = (n32 * n21 - n31 * n22) * detInv; - te[3] = t12 * detInv; - te[4] = (n33 * n11 - n31 * n13) * detInv; - te[5] = (n31 * n12 - n32 * n11) * detInv; - te[6] = t13 * detInv; - te[7] = (n21 * n13 - n23 * n11) * detInv; - te[8] = (n22 * n11 - n21 * n12) * detInv; - return this; + cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender, camera } = {}) { + if (camera) { + this.camera.near = camera.near ?? 0.1; + this.camera.far = camera.far !== void 0 ? camera.far : this.light.range > 0 ? this.light.range : 150; + } + super.cast({ intensity, bias, normalBias, pcfSamples, depthTextureSize, depthTextureFormat, autoRender }); } /** - * Transpose this {@link Mat3}. - * @returns - this {@link Mat3} after being transposed + * Set the {@link depthComparisonSampler}, {@link depthTexture}, {@link depthPassTarget}, compute the {@link PointShadow#camera.projectionMatrix | camera projection matrix} and start rendering to the shadow map. */ - transpose() { - let tmp; - const m = this.elements; - tmp = m[1]; - m[1] = m[3]; - m[3] = tmp; - tmp = m[2]; - m[2] = m[6]; - m[6] = tmp; - tmp = m[5]; - m[5] = m[7]; - m[7] = tmp; - return this; + init() { + super.init(); + this.updateProjectionMatrix(); } /** - * Compute a normal {@link Mat3} matrix from a {@link Mat4} transformation matrix. - * @param matrix - {@link Mat4} transformation matrix - * @returns - this {@link Mat3} after being inverted and transposed + * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of corresponding {@link PointLight} has been overflowed. */ - getNormalMatrix(matrix = new Mat4()) { - return this.setFromMat4(matrix).invert().transpose(); + reset() { + this.setRendererBinding(); + super.reset(); + this.updateProjectionMatrix(); } - } - - class ProjectedObject3D extends Object3D { /** - * ProjectedObject3D constructor - * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link ProjectedObject3D} + * Update the {@link PointShadow#camera.projectionMatrix | camera perspective projection matrix} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. */ - constructor(renderer) { - super(); - renderer = isCameraRenderer(renderer, "ProjectedObject3D"); - this.camera = renderer.camera; + updateProjectionMatrix() { + this.camera.projectionMatrix.identity().makePerspective({ + near: this.camera.near, + far: this.camera.far, + fov: 90, + aspect: 1 + }); + this.onPropertyChanged("projectionMatrix", this.camera.projectionMatrix); + this.onPropertyChanged("cameraNear", this.camera.near); + this.onPropertyChanged("cameraFar", this.camera.far); } /** - * Tell our projection matrix stack to update + * Update the {@link PointShadow#camera.viewMatrices | camera view matrices} and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param position - {@link Vec3} to use as position for the {@link PointShadow#camera.viewMatrices | camera view matrices}, based on the {@link light} position. */ - applyPosition() { - super.applyPosition(); - this.shouldUpdateProjectionMatrixStack(); + updateViewMatrices(position = new Vec3()) { + for (let i = 0; i < 6; i++) { + __privateGet$9(this, _tempCubeDirection).copy(this.cubeDirections[i]).add(position); + this.camera.viewMatrices[i].makeView(position, __privateGet$9(this, _tempCubeDirection), this.cubeUps[i]); + for (let j = 0; j < 16; j++) { + this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.value[i * 16 + j] = this.camera.viewMatrices[i].elements[j]; + } + } + this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.shouldUpdate = true; } /** - * Tell our projection matrix stack to update + * Set or resize the {@link depthTexture} and eventually resize the {@link depthPassTarget} as well. */ - applyRotation() { - super.applyRotation(); - this.shouldUpdateProjectionMatrixStack(); + setDepthTexture() { + if (this.depthTexture && (this.depthTexture.size.width !== this.depthTextureSize.x || this.depthTexture.size.height !== this.depthTextureSize.y)) { + const maxSize = Math.max(this.depthTextureSize.x, this.depthTextureSize.y); + this.depthTexture.options.fixedSize.width = maxSize; + this.depthTexture.options.fixedSize.height = maxSize; + this.depthTexture.size.width = maxSize; + this.depthTexture.size.height = maxSize; + this.depthTexture.createTexture(); + if (this.depthPassTarget) { + this.depthPassTarget.resize(); + } + } else if (!this.depthTexture) { + this.createDepthTexture(); + } } /** - * Tell our projection matrix stack to update + * Create the cube {@link depthTexture}. */ - applyScale() { - super.applyScale(); - this.shouldUpdateProjectionMatrixStack(); + createDepthTexture() { + const maxSize = Math.max(this.depthTextureSize.x, this.depthTextureSize.y); + this.depthTexture = new Texture(this.renderer, { + label: `${this.constructor.name} (index: ${this.index}) depth texture`, + name: "pointShadowCubeDepthTexture" + this.index, + type: "depth", + format: this.depthTextureFormat, + viewDimension: "cube", + sampleCount: this.sampleCount, + fixedSize: { + width: maxSize, + height: maxSize + }, + autoDestroy: false + // do not destroy when removing a mesh + }); } /** - * Tell our projection matrix stack to update + * Remove the depth pass from its {@link utils/TasksQueueManager.TasksQueueManager | task queue manager}. + * @param depthPassTaskID - Task queue manager ID to use for removal. */ - applyTransformOrigin() { - super.applyTransformOrigin(); - this.shouldUpdateProjectionMatrixStack(); + removeDepthPass(depthPassTaskID) { + this.renderer.onBeforeCommandEncoderCreation.remove(depthPassTaskID); } /** - * Set our transform and projection matrices + * Render the depth pass. This happens before creating the {@link CameraRenderer} command encoder.
+ * - Force all the {@link meshes} to use their depth materials + * - For each face of the depth cube texture: + * - Create a command encoder. + * - Set the {@link depthPassTarget} descriptor depth texture view to our depth cube texture current face. + * - Update the face index + * - Render all the {@link meshes} + * - Submit the command encoder + * - Reset all the {@link meshes} materials to their original one. + * @param once - Whether to render it only once or not. */ - setMatrices() { - super.setMatrices(); - this.matrices = { - ...this.matrices, - modelView: { - matrix: new Mat4(), - shouldUpdate: true, - onUpdate: () => { - this.modelViewMatrix.multiplyMatrices(this.viewMatrix, this.worldMatrix); - } - }, - modelViewProjection: { - matrix: new Mat4(), - shouldUpdate: true, - onUpdate: () => { - this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix, this.modelViewMatrix); + render(once = false) { + return this.renderer.onBeforeCommandEncoderCreation.add( + () => { + if (!this.meshes.size) + return; + this.renderer.setCameraBindGroup(); + this.useDepthMaterials(); + for (let i = 0; i < 6; i++) { + const commandEncoder = this.renderer.device.createCommandEncoder(); + if (!this.renderer.production) + commandEncoder.pushDebugGroup( + `${this.constructor.name} (index: ${this.index}): depth pass command encoder for face ${i}` + ); + this.depthPassTarget.renderPass.setRenderPassDescriptor( + this.depthTexture.texture.createView({ + label: this.depthTexture.texture.label + " cube face view " + i, + dimension: "2d", + arrayLayerCount: 1, + baseArrayLayer: i + }) + ); + this.rendererBinding.childrenBindings[this.index].inputs.face.value = i; + this.renderer.shouldUpdateCameraLightsBindGroup(); + this.renderer.updateCameraLightsBindGroup(); + this.renderDepthPass(commandEncoder); + if (!this.renderer.production) + commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.renderer.device.queue.submit([commandBuffer]); } + this.useOriginalMaterials(); + this.renderer.pipelineManager.resetCurrentPipeline(); }, - normal: { - matrix: new Mat3(), - shouldUpdate: true, - onUpdate: () => { - this.normalMatrix.getNormalMatrix(this.worldMatrix); - } + { + once, + order: this.index } - }; + ); } /** - * Get our {@link modelViewMatrix | model view matrix} + * Get the default depth pass vertex shader for this {@link PointShadow}. + * @returns - Depth pass vertex shader. */ - get modelViewMatrix() { - return this.matrices.modelView.matrix; + getDefaultShadowDepthVs(hasInstances = false) { + return { + /** Returned code. */ + code: getDefaultPointShadowDepthVs(this.index, hasInstances) + }; } /** - * Set our {@link modelViewMatrix | model view matrix} - * @param value - new {@link modelViewMatrix | model view matrix} + * Get the default depth pass {@link types/Materials.ShaderOptions | fragment shader options} for this {@link PointShadow}. + * @returns - A {@link types/Materials.ShaderOptions | ShaderOptions} with the depth pass fragment shader. */ - set modelViewMatrix(value) { - this.matrices.modelView.matrix = value; - this.matrices.modelView.shouldUpdate = true; + getDefaultShadowDepthFs() { + return { + /** Returned code. */ + code: getDefaultPointShadowDepthFs(this.index) + }; } + } + _tempCubeDirection = new WeakMap(); + + var __accessCheck$a = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$8 = (obj, member, getter) => { + __accessCheck$a(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$a = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$8 = (obj, member, value, setter) => { + __accessCheck$a(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var _range, _actualPosition; + class PointLight extends Light { /** - * Get our {@link Camera#viewMatrix | camera view matrix} - * @readonly + * PointLight constructor + * @param renderer - {@link CameraRenderer | CameraRenderer} used to create this {@link PointLight}. + * @param parameters - {@link PointLightBaseParams | parameters} used to create this {@link PointLight}. */ - get viewMatrix() { - return this.camera.viewMatrix; + constructor(renderer, { color = new Vec3(1), intensity = 1, position = new Vec3(), range = 0, shadow = null } = {}) { + const type = "pointLights"; + super(renderer, { color, intensity, type }); + /** @ignore */ + __privateAdd$a(this, _range, void 0); + /** @ignore */ + __privateAdd$a(this, _actualPosition, void 0); + this.options = { + ...this.options, + position, + range, + shadow + }; + __privateSet$8(this, _actualPosition, new Vec3()); + this.position.copy(position); + this.range = range; + this.parent = this.renderer.scene; + this.shadow = new PointShadow(this.renderer, { + autoRender: false, + // will be set by calling cast() + light: this + }); + if (shadow) { + this.shadow.cast(shadow); + } } /** - * Get our {@link Camera#projectionMatrix | camera projection matrix} - * @readonly + * Set or reset this {@link PointLight} {@link CameraRenderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. */ - get projectionMatrix() { - return this.camera.projectionMatrix; + setRenderer(renderer) { + if (this.shadow) { + this.shadow.setRenderer(renderer); + } + super.setRenderer(renderer); } /** - * Get our {@link modelViewProjectionMatrix | model view projection matrix} + * Resend all properties to the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. Called when the maximum number of {@link PointLight} has been overflowed. */ - get modelViewProjectionMatrix() { - return this.matrices.modelViewProjection.matrix; + reset() { + super.reset(); + this.onPropertyChanged("range", this.range); + this.setPosition(); + this.shadow?.reset(); } /** - * Set our {@link modelViewProjectionMatrix | model view projection matrix} - * @param value - new {@link modelViewProjectionMatrix | model view projection matrix}s + * Get this {@link PointLight} range. + * @returns - The {@link PointLight} range. */ - set modelViewProjectionMatrix(value) { - this.matrices.modelViewProjection.matrix = value; - this.matrices.modelViewProjection.shouldUpdate = true; + get range() { + return __privateGet$8(this, _range); } /** - * Get our {@link normalMatrix | normal matrix} + * Set this {@link PointLight} range and update the {@link CameraRenderer} corresponding {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}. + * @param value - The new {@link PointLight} range. */ - get normalMatrix() { - return this.matrices.normal.matrix; + set range(value) { + __privateSet$8(this, _range, value); + this.onPropertyChanged("range", this.range); } /** - * Set our {@link normalMatrix | normal matrix} - * @param value - new {@link normalMatrix | normal matrix} + * Set the {@link PointLight} position based on the {@link worldMatrix} translation and update the {@link PointShadow} view matrices. */ - set normalMatrix(value) { - this.matrices.normal.matrix = value; - this.matrices.normal.shouldUpdate = true; + setPosition() { + this.onPropertyChanged("position", this.worldMatrix.getTranslation(__privateGet$8(this, _actualPosition))); + this.shadow?.updateViewMatrices(__privateGet$8(this, _actualPosition)); + } + // explicitly disable scale and transform origin transformations + /** @ignore */ + applyScale() { + } + /** @ignore */ + applyTransformOrigin() { } /** - * Set our projection matrices shouldUpdate flags to true (tell them to update) + * If the {@link modelMatrix | model matrix} has been updated, set the new position from the {@link worldMatrix} translation. */ - shouldUpdateProjectionMatrixStack() { - this.matrices.modelView.shouldUpdate = true; - this.matrices.modelViewProjection.shouldUpdate = true; + updateMatrixStack() { + super.updateMatrixStack(); + if (this.matricesNeedUpdate) { + this.setPosition(); + } } /** - * When the world matrix update, tell our projection matrix to update as well + * Tell the {@link renderer} that the maximum number of {@link PointLight} has been overflown. + * @param lightsType - {@link type} of this light. */ - shouldUpdateWorldMatrix() { - super.shouldUpdateWorldMatrix(); - this.shouldUpdateProjectionMatrixStack(); - this.matrices.normal.shouldUpdate = true; + onMaxLightOverflow(lightsType) { + super.onMaxLightOverflow(lightsType); + this.shadow?.setRendererBinding(); } /** - * Tell all our matrices to update + * Destroy this {@link PointLight} and associated {@link PointShadow}. */ - shouldUpdateMatrixStack() { - this.shouldUpdateModelMatrix(); - this.shouldUpdateProjectionMatrixStack(); + destroy() { + super.destroy(); + this.shadow.destroy(); } } + _range = new WeakMap(); + _actualPosition = new WeakMap(); - var default_normal_fsWgsl = ( - /* wgsl */ - ` -struct VSOutput { - @builtin(position) position: vec4f, - @location(0) uv: vec2f, - @location(1) normal: vec3f, -}; - -@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { - // normals - return vec4(normalize(fsInput.normal) * 0.5 + 0.5, 1.0); -}` - ); - - const defaultProjectedMeshParams = { - // frustum culling and visibility - frustumCulling: "OBB", - DOMFrustumMargins: { - top: 0, - right: 0, - bottom: 0, - left: 0 - }, - receiveShadows: false, - castShadows: false + var __accessCheck$9 = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$7 = (obj, member, getter) => { + __accessCheck$9(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$9 = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$7 = (obj, member, value, setter) => { + __accessCheck$9(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + let meshIndex = 0; + const defaultMeshBaseParams = { + // material + autoRender: true, + useProjection: false, + useAsyncPipeline: true, + // rendering + cullMode: "back", + depth: true, + depthWriteEnabled: true, + depthCompare: "less", + depthFormat: "depth24plus", + transparent: false, + visible: true, + renderOrder: 0, + // textures + texturesOptions: {}, + renderBundle: null }; - function ProjectedMeshBaseMixin(Base) { - return class ProjectedMeshBase extends MeshBaseMixin(Base) { + function MeshBaseMixin(Base) { + var _autoRender, _a; + return _a = class extends Base { /** - * ProjectedMeshBase constructor + * MeshBase constructor * * @typedef MeshBaseArrayParams * @type {array} - * @property {(CameraRenderer|GPUCurtains)} 0 - our renderer class object - * @property {(string|HTMLElement|null)} 1 - the DOM HTML Element that can be bound to a Mesh - * @property {ProjectedMeshParameters} 2 - Projected Mesh parameters + * @property {(Renderer|GPUCurtains)} 0 - our {@link Renderer} class object + * @property {(string|HTMLElement|null)} 1 - a DOM HTML Element that can be bound to a Mesh + * @property {MeshBaseParams} 2 - {@link MeshBaseParams | Mesh base parameters} * * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters */ @@ -10187,41 +10059,190 @@ struct VSOutput { super( params[0], params[1], - { ...defaultProjectedMeshParams, ...params[2], ...{ useProjection: true } } + { ...defaultMeshBaseParams, ...params[2] } ); + /** Whether we should add this {@link MeshBase} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */ + __privateAdd$9(this, _autoRender, true); // callbacks / events - /** function assigned to the {@link onReEnterView} callback */ - this._onReEnterViewCallback = () => { + /** function assigned to the {@link onReady} callback */ + this._onReadyCallback = () => { }; - /** function assigned to the {@link onLeaveView} callback */ - this._onLeaveViewCallback = () => { + /** function assigned to the {@link onBeforeRender} callback */ + this._onBeforeRenderCallback = () => { }; - let renderer = params[0]; - const parameters = { - ...defaultProjectedMeshParams, - ...params[2], - ...{ useProjection: true } + /** function assigned to the {@link onRender} callback */ + this._onRenderCallback = () => { }; - this.type = "MeshTransformed"; - renderer = isCameraRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); + /** function assigned to the {@link onAfterRender} callback */ + this._onAfterRenderCallback = () => { + }; + /** function assigned to the {@link onAfterResize} callback */ + this._onAfterResizeCallback = () => { + }; + let renderer = params[0]; + const parameters = { ...defaultMeshBaseParams, ...params[2] }; + this.type = "MeshBase"; + this.uuid = generateUUID(); + Object.defineProperty(this, "index", { value: meshIndex++ }); + renderer = isRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); this.renderer = renderer; - const { frustumCulling, DOMFrustumMargins, receiveShadows, castShadows } = parameters; + const { + label, + shaders, + geometry, + visible, + renderOrder, + outputTarget, + renderBundle, + texturesOptions, + autoRender, + ...meshParameters + } = parameters; + this.outputTarget = outputTarget ?? null; + this.renderBundle = renderBundle ?? null; + meshParameters.sampleCount = !!meshParameters.sampleCount ? meshParameters.sampleCount : this.outputTarget ? this.outputTarget.renderPass.options.sampleCount : this.renderer && this.renderer.renderPass ? this.renderer.renderPass.options.sampleCount : 1; this.options = { ...this.options ?? {}, // merge possible lower options? - frustumCulling, - DOMFrustumMargins, - receiveShadows, - castShadows + label: label ?? "Mesh " + this.renderer.meshes.length, + ...shaders !== void 0 ? { shaders } : {}, + ...outputTarget !== void 0 && { outputTarget }, + ...renderBundle !== void 0 && { renderBundle }, + texturesOptions, + ...autoRender !== void 0 && { autoRender }, + ...meshParameters }; - if (this.options.castShadows) { - this.renderer.shadowCastingLights.forEach((light) => { - if (light.shadow.isActive) { - light.shadow.addShadowCastingMesh(this); - } - }); + if (autoRender !== void 0) { + __privateSet$7(this, _autoRender, autoRender); } - this.setDOMFrustum(); + this.visible = visible; + this.renderOrder = renderOrder; + this.ready = false; + this.userData = {}; + if (geometry) { + this.useGeometry(geometry); + } + this.setMaterial({ + ...this.cleanupRenderMaterialParameters({ ...this.options }), + ...geometry && { verticesOrder: geometry.verticesOrder, topology: geometry.topology } + }); + this.addToScene(true); + } + /** + * Get private #autoRender value + * @readonly + */ + get autoRender() { + return __privateGet$7(this, _autoRender); + } + /** + * Get/set whether a Mesh is ready or not + * @readonly + */ + get ready() { + return this._ready; + } + set ready(value) { + if (value && !this._ready) { + this._onReadyCallback && this._onReadyCallback(); + } + this._ready = value; + } + /* SCENE */ + /** + * Add a Mesh to the {@link core/scenes/Scene.Scene | Scene} and optionally to the renderer. Can patch the {@link RenderMaterial} render options to match the {@link RenderPass} used to draw this Mesh. + * @param addToRenderer - whether to add this Mesh to the {@link Renderer#meshes | Renderer meshes array} + */ + addToScene(addToRenderer = false) { + if (addToRenderer) { + this.renderer.meshes.push(this); + } + this.setRenderingOptionsForRenderPass(this.outputTarget ? this.outputTarget.renderPass : this.renderer.renderPass); + if (__privateGet$7(this, _autoRender)) { + this.renderer.scene.addMesh(this); + } + } + /** + * Remove a Mesh from the {@link core/scenes/Scene.Scene | Scene} and optionally from the renderer as well. + * @param removeFromRenderer - whether to remove this Mesh from the {@link Renderer#meshes | Renderer meshes array} + */ + removeFromScene(removeFromRenderer = false) { + if (__privateGet$7(this, _autoRender)) { + this.renderer.scene.removeMesh(this); + } + if (removeFromRenderer) { + this.renderer.meshes = this.renderer.meshes.filter((m) => m.uuid !== this.uuid); + } + } + /** + * Set a new {@link Renderer} for this Mesh + * @param renderer - new {@link Renderer} to set + */ + setRenderer(renderer) { + renderer = renderer && renderer.renderer || renderer; + if (!renderer || !(renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer")) { + throwWarning( + `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.` + ); + return; + } + this.material?.setRenderer(renderer); + const oldRenderer = this.renderer; + this.removeFromScene(true); + this.renderer = renderer; + this.addToScene(true); + if (!oldRenderer.meshes.length) { + oldRenderer.onBeforeRenderScene.add( + (commandEncoder) => { + oldRenderer.forceClear(commandEncoder); + }, + { once: true } + ); + } + } + /** + * Assign or remove a {@link RenderTarget} to this Mesh. + * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a {@link RenderTarget} as well. + * @param outputTarget - the {@link RenderTarget} to assign or null if we want to remove the current {@link RenderTarget}. + */ + setOutputTarget(outputTarget) { + if (outputTarget && outputTarget.type !== "RenderTarget") { + throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget.type}`); + return; + } + this.removeFromScene(); + this.outputTarget = outputTarget; + this.addToScene(); + } + /** + * Assign or remove a {@link RenderBundle} to this Mesh. + * @param renderBundle - the {@link RenderBundle} to assign or null if we want to remove the current {@link RenderBundle}. + * @param updateScene - Whether to remove and then re-add the Mesh from the {@link core/scenes/Scene.Scene | Scene} or not. + */ + setRenderBundle(renderBundle, updateScene = true) { + if (updateScene) { + this.removeFromScene(); + this.renderBundle = renderBundle; + this.addToScene(); + } else { + this.renderBundle = renderBundle; + } + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. + * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh + */ + loseContext() { + this.ready = false; + this.geometry.loseContext(); + this.material.loseContext(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored + */ + restoreContext() { + this.geometry.restoreContext(this.renderer); + this.material.restoreContext(); } /* SHADERS */ /** @@ -10232,131 +10253,180 @@ struct VSOutput { if (!shaders) { this.options.shaders = { vertex: { - code: default_projected_vsWgsl, + code: default_vsWgsl, entryPoint: "main" }, fragment: { - code: default_normal_fsWgsl, + code: default_fsWgsl, entryPoint: "main" } }; } else { if (!shaders.vertex || !shaders.vertex.code) { shaders.vertex = { - code: default_projected_vsWgsl, + code: default_vsWgsl, entryPoint: "main" }; } if (shaders.fragment === void 0 || shaders.fragment && !shaders.fragment.code) { shaders.fragment = { - code: default_normal_fsWgsl, + code: default_fsWgsl, entryPoint: "main" }; } } } - /* GEOMETRY */ + /* GEOMETRY */ + /** + * Set or update the Mesh {@link Geometry} + * @param geometry - new {@link Geometry} to use + */ + useGeometry(geometry) { + if (this.geometry) { + if (geometry.shouldCompute) { + geometry.computeGeometry(); + } + if (this.geometry.layoutCacheKey !== geometry.layoutCacheKey) { + throwWarning( + `${this.options.label} (${this.type}): the current and new geometries do not have the same vertexBuffers layout, causing a probable pipeline recompilation. This should be avoided. + +Current geometry layout: + +${this.geometry.wgslStructFragment} + +-------- + +New geometry layout: + +${geometry.wgslStructFragment}` + ); + this.material.setAttributesFromGeometry(geometry); + this.material.setPipelineEntry(); + } + this.geometry.consumers.delete(this.uuid); + } + this.geometry = geometry; + this.geometry.consumers.add(this.uuid); + this.computeGeometry(); + if (this.material) { + const renderingOptions = { + ...this.material.options.rendering, + ...{ verticesOrder: geometry.verticesOrder, topology: geometry.topology } + }; + this.material.setRenderingOptions(renderingOptions); + } + } + /** + * Compute the Mesh geometry if needed + */ + computeGeometry() { + if (this.geometry.shouldCompute) { + this.geometry.computeGeometry(); + } + } /** - * Set or update the Projected Mesh {@link Geometry} - * @param geometry - new {@link Geometry} to use + * Set our Mesh geometry: create buffers and add attributes to material */ - useGeometry(geometry) { - super.useGeometry(geometry); - if (this.domFrustum) { - this.domFrustum.boundingBox = this.geometry.boundingBox; + setGeometry() { + if (this.geometry) { + if (!this.geometry.ready) { + this.geometry.createBuffers({ + renderer: this.renderer, + label: this.options.label + " geometry" + }); + } + this.setMaterialGeometryAttributes(); } - this.shouldUpdateMatrixStack(); } + /* MATERIAL */ /** - * Set the Mesh frustum culling + * Set or update the {@link RenderMaterial} {@link types/Materials.RenderMaterialRenderingOptions | rendering options} to match the {@link RenderPass#descriptor | RenderPass descriptor} used to draw this Mesh. + * @param renderPass - {@link RenderPass | RenderPass} used to draw this Mesh, default to the {@link core/renderers/GPURenderer.GPURenderer#renderPass | renderer renderPass}. */ - setDOMFrustum() { - this.domFrustum = new DOMFrustum({ - boundingBox: this.geometry?.boundingBox, - modelViewProjectionMatrix: this.modelViewProjectionMatrix, - containerBoundingRect: this.renderer.boundingRect, - DOMFrustumMargins: this.options.DOMFrustumMargins, - onReEnterView: () => { - this._onReEnterViewCallback && this._onReEnterViewCallback(); + setRenderingOptionsForRenderPass(renderPass) { + const renderingOptions = { + // transparency (blend) + transparent: this.transparent, + // sample count + sampleCount: renderPass.options.sampleCount, + // color attachments + ...renderPass.options.colorAttachments.length && { + targets: renderPass.options.colorAttachments.map((colorAttachment, index) => { + return { + // patch format... + format: colorAttachment.targetFormat, + // ...but keep original blend values if any + ...this.options.targets?.length && this.options.targets[index] && this.options.targets[index].blend && { + blend: this.options.targets[index].blend + } + }; + }) }, - onLeaveView: () => { - this._onLeaveViewCallback && this._onLeaveViewCallback(); + // depth + depth: renderPass.options.useDepth, + ...renderPass.options.useDepth && { + depthFormat: renderPass.options.depthFormat } - }); - this.DOMFrustumMargins = this.domFrustum.DOMFrustumMargins; - this.frustumCulling = this.options.frustumCulling; + }; + this.material?.setRenderingOptions(renderingOptions); } - /* MATERIAL */ /** - * Hook used to clean up parameters before sending them to the material. - * @param parameters - parameters to clean before sending them to the {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial} + * Hook used to clean up parameters before sending them to the {@link RenderMaterial}. + * @param parameters - parameters to clean before sending them to the {@link RenderMaterial} * @returns - cleaned parameters */ cleanupRenderMaterialParameters(parameters) { - delete parameters.frustumCulling; - delete parameters.DOMFrustumMargins; - if (this.options.receiveShadows) { - const depthTextures = []; - let depthSamplers = []; - this.renderer.shadowCastingLights.forEach((light) => { - if (light.shadow.isActive) { - depthTextures.push(light.shadow.depthTexture); - depthSamplers.push(light.shadow.depthComparisonSampler); - } - }); - const hasActiveShadows = this.renderer.shadowCastingLights.find((light) => light.shadow.isActive); - if (hasActiveShadows && parameters.shaders.fragment && typeof parameters.shaders.fragment === "object") { - parameters.shaders.fragment.code = getPCFDirectionalShadows(this.renderer) + getPCFShadowContribution + getPCFPointShadows(this.renderer) + getPCFPointShadowContribution + parameters.shaders.fragment.code; - } - depthSamplers = depthSamplers.filter( - (sampler, i, array) => array.findIndex((s) => s.uuid === sampler.uuid) === i - ); - if (parameters.textures) { - parameters.textures = [...parameters.textures, ...depthTextures]; - } else { - parameters.textures = depthTextures; - } - if (parameters.samplers) { - parameters.samplers = [...parameters.samplers, ...depthSamplers]; - } else { - parameters.samplers = depthSamplers; - } - } - return super.cleanupRenderMaterialParameters(parameters); + delete parameters.texturesOptions; + delete parameters.outputTarget; + delete parameters.autoRender; + return parameters; } /** - * Set a Mesh matrices uniforms inputs then call {@link MeshBaseClass} super method + * Set or update the Mesh {@link RenderMaterial} + * @param material - new {@link RenderMaterial} to use + */ + useMaterial(material) { + this.material = material; + this.transparent = this.material.options.rendering.transparent; + this.material.options.domTextures?.filter((texture) => texture instanceof DOMTexture).forEach((texture) => this.onDOMTextureAdded(texture)); + } + /** + * Patch the shaders if needed, then set the Mesh material * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters} */ setMaterial(meshParameters) { - const matricesUniforms = { - label: "Matrices", - visibility: ["vertex"], - struct: { - model: { - type: "mat4x4f", - value: this.worldMatrix - }, - modelView: { - // model view matrix (world matrix multiplied by camera view matrix) - type: "mat4x4f", - value: this.modelViewMatrix - }, - normal: { - // normal matrix - type: "mat3x3f", - value: this.normalMatrix - } - // modelViewProjection: { - // type: 'mat4x4f', - // value: this.modelViewProjectionMatrix, - // }, - } - }; - if (!meshParameters.uniforms) - meshParameters.uniforms = {}; - meshParameters.uniforms = { matrices: matricesUniforms, ...meshParameters.uniforms }; - super.setMaterial(meshParameters); + this.setShaders(); + meshParameters.shaders = this.options.shaders; + meshParameters.label = meshParameters.label + " material"; + this.useMaterial(new RenderMaterial(this.renderer, meshParameters)); + } + /** + * Set Mesh material attributes + */ + setMaterialGeometryAttributes() { + if (this.material && !this.material.attributes) { + this.material.setAttributesFromGeometry(this.geometry); + } + } + /** + * Get the transparent property value + */ + get transparent() { + return this._transparent; + } + /** + * Set the transparent property value. Update the {@link RenderMaterial} rendering options and {@link core/scenes/Scene.Scene | Scene} stack if needed. + * @param value + */ + set transparent(value) { + const switchTransparency = this.transparent !== void 0 && value !== this.transparent; + if (switchTransparency) { + this.removeFromScene(); + } + this._transparent = value; + if (switchTransparency) { + this.addToScene(); + } } /** * Get the visible property value @@ -10369,688 +10439,1158 @@ struct VSOutput { * @param value - new visibility value */ set visible(value) { - this.shouldUpdateMatrixStack(); this._visible = value; } - /* SIZE & TRANSFORMS */ + /* TEXTURES */ /** - * Resize our {@link ProjectedMeshBaseClass} - * @param boundingRect - the new bounding rectangle + * Get our {@link RenderMaterial#domTextures | RenderMaterial domTextures array} + * @readonly + */ + get domTextures() { + return this.material?.domTextures || []; + } + /** + * Get our {@link RenderMaterial#textures | RenderMaterial textures array} + * @readonly + */ + get textures() { + return this.material?.textures || []; + } + /** + * Create a new {@link DOMTexture} + * @param options - {@link DOMTextureParams | DOMTexture parameters} + * @returns - newly created {@link DOMTexture} + */ + createDOMTexture(options) { + if (!options.name) { + options.name = "texture" + (this.textures.length + this.domTextures.length); + } + if (!options.label) { + options.label = this.options.label + " " + options.name; + } + const texturesOptions = { ...options, ...this.options.texturesOptions }; + if (this.renderBundle) { + texturesOptions.useExternalTextures = false; + } + const domTexture = new DOMTexture(this.renderer, texturesOptions); + this.addDOMTexture(domTexture); + return domTexture; + } + /** + * Add a {@link DOMTexture} + * @param domTexture - {@link DOMTexture} to add + */ + addDOMTexture(domTexture) { + if (this.renderBundle) { + this.renderBundle.ready = false; + } + this.material.addTexture(domTexture); + this.onDOMTextureAdded(domTexture); + } + /** + * Callback run when a new {@link DOMTexture} has been added + * @param domTexture - newly created DOMTexture + */ + onDOMTextureAdded(domTexture) { + domTexture.parentMesh = this; + } + /** + * Create a new {@link Texture} + * @param options - {@link TextureParams | Texture parameters} + * @returns - newly created {@link Texture} + */ + createTexture(options) { + if (!options.name) { + options.name = "texture" + (this.textures.length + this.domTextures.length); + } + const texture = new Texture(this.renderer, options); + this.addTexture(texture); + return texture; + } + /** + * Add a {@link Texture} + * @param texture - {@link Texture} to add + */ + addTexture(texture) { + if (this.renderBundle) { + this.renderBundle.ready = false; + } + this.material.addTexture(texture); + } + /* BINDINGS */ + /** + * Get the current {@link RenderMaterial} uniforms + * @readonly + */ + get uniforms() { + return this.material?.uniforms; + } + /** + * Get the current {@link RenderMaterial} storages + * @readonly + */ + get storages() { + return this.material?.storages; + } + /* RESIZE */ + /** + * Resize the Mesh's textures + * @param boundingRect */ resize(boundingRect) { - if (this.domFrustum) - this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect); - super.resize(boundingRect); + if (super.resize) { + super.resize(boundingRect); + } + this.textures?.forEach((texture) => { + if (texture.options.fromTexture) { + texture.copy(texture.options.fromTexture); + } + }); + this.domTextures?.forEach((texture) => { + texture.resize(); + }); + this._onAfterResizeCallback && this._onAfterResizeCallback(); } + /* EVENTS */ /** - * Apply scale and resize textures + * Callback to execute when a Mesh is ready - i.e. its {@link material} and {@link geometry} are ready. + * @param callback - callback to run when {@link MeshBase} is ready + * @returns - our Mesh */ - applyScale() { - super.applyScale(); - for (const texture of this.domTextures) { - texture.resize(); + onReady(callback) { + if (callback) { + this._onReadyCallback = callback; } + return this; } /** - * Get our {@link DOMFrustum} projected bounding rectangle - * @readonly + * Callback to execute before updating the {@link core/scenes/Scene.Scene | Scene} matrix stack. This means it is called early and allows to update transformations values before actually setting the Mesh matrices (if any). This also means it won't be called if the Mesh has not been added to the {@link core/scenes/Scene.Scene | Scene}. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. + * @param callback - callback to run just before updating the {@link core/scenes/Scene.Scene | Scene} matrix stack. + * @returns - our Mesh */ - get projectedBoundingRect() { - return this.domFrustum?.projectedBoundingRect; + onBeforeRender(callback) { + if (callback) { + this._onBeforeRenderCallback = callback; + } + return this; } - /* EVENTS */ /** - * Assign a callback function to _onReEnterViewCallback - * @param callback - callback to run when {@link ProjectedMeshBaseClass} is reentering the view frustum + * Callback to execute right before actually rendering the Mesh. Useful to update uniforms for example. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. + * @param callback - callback to run just before rendering the {@link MeshBase} * @returns - our Mesh */ - onReEnterView(callback) { + onRender(callback) { if (callback) { - this._onReEnterViewCallback = callback; + this._onRenderCallback = callback; } return this; } /** - * Assign a callback function to _onLeaveViewCallback - * @param callback - callback to run when {@link ProjectedMeshBaseClass} is leaving the view frustum + * Callback to execute just after a Mesh has been rendered. The callback won't be called if the {@link Renderer} is not ready or the Mesh itself is neither {@link ready} nor {@link visible}. + * @param callback - callback to run just after {@link MeshBase} has been rendered * @returns - our Mesh */ - onLeaveView(callback) { + onAfterRender(callback) { if (callback) { - this._onLeaveViewCallback = callback; + this._onAfterRenderCallback = callback; } return this; } - /* RENDER */ /** - * Get the geometry bounding sphere in clip space. - * @readonly + * Callback to execute just after a Mesh has been resized. + * @param callback - callback to run just after {@link MeshBase} has been resized + * @returns - our Mesh */ - get clipSpaceBoundingSphere() { - const { center, radius, min, max } = this.geometry.boundingBox; - const translation = this.worldMatrix.getTranslation(); - const maxWorldRadius = radius * this.worldMatrix.getMaxScaleOnAxis(); - const cMin = center.clone().add(translation); - cMin.z += min.z; - const cMax = center.clone().add(translation); - cMax.z += max.z; - const sMin = cMin.clone(); - sMin.y += maxWorldRadius; - const sMax = cMax.clone(); - sMax.y += maxWorldRadius; - cMin.applyMat4(this.camera.viewProjectionMatrix); - cMax.applyMat4(this.camera.viewProjectionMatrix); - sMin.applyMat4(this.camera.viewProjectionMatrix); - sMax.applyMat4(this.camera.viewProjectionMatrix); - const rMin = cMin.distance(sMin); - const rMax = cMax.distance(sMax); - const rectMin = { - xMin: cMin.x - rMin, - xMax: cMin.x + rMin, - yMin: cMin.y - rMin, - yMax: cMin.y + rMin - }; - const rectMax = { - xMin: cMax.x - rMax, - xMax: cMax.x + rMax, - yMin: cMax.y - rMax, - yMax: cMax.y + rMax - }; - const rect = { - xMin: Math.min(rectMin.xMin, rectMax.xMin), - yMin: Math.min(rectMin.yMin, rectMax.yMin), - xMax: Math.max(rectMin.xMax, rectMax.xMax), - yMax: Math.max(rectMin.yMax, rectMax.yMax) - }; - const sphereCenter = cMax.add(cMin).multiplyScalar(0.5).clone(); - sphereCenter.x = (rect.xMax + rect.xMin) / 2; - sphereCenter.y = (rect.yMax + rect.yMin) / 2; - const sphereRadius = Math.max(rect.xMax - rect.xMin, rect.yMax - rect.yMin); - return { - center: sphereCenter, - radius: sphereRadius - }; + onAfterResize(callback) { + if (callback) { + this._onAfterResizeCallback = callback; + } + return this; } + /* RENDER */ /** - * Check if the Mesh lies inside the {@link camera} view frustum or not using the test defined by {@link frustumCulling}. + * Execute {@link onBeforeRender} callback if needed. Called by the {@link core/scenes/Scene.Scene | Scene} before updating the matrix stack. */ - checkFrustumCulling() { - if (this.matricesNeedUpdate) { - if (this.domFrustum && this.frustumCulling) { - if (this.frustumCulling === "sphere") { - this.domFrustum.setDocumentCoordsFromClipSpaceSphere(this.clipSpaceBoundingSphere); - } else { - this.domFrustum.setDocumentCoordsFromClipSpaceOBB(); - } - this.domFrustum.intersectsContainer(); - } - } + onBeforeRenderScene() { + if (!this.renderer.ready || !this.ready || !this.visible) + return; + this._onBeforeRenderCallback && this._onBeforeRenderCallback(); } /** - * Tell our matrices bindings to update if needed and call {@link MeshBaseClass#onBeforeRenderPass | Mesh base onBeforeRenderPass} super. + * Called before rendering the Mesh + * Set the geometry if needed (create buffers and add attributes to the {@link RenderMaterial}) + * Then executes {@link RenderMaterial#onBeforeRender}: create its bind groups and pipeline if needed and eventually update its bindings */ onBeforeRenderPass() { - if (this.material && this.matricesNeedUpdate) { - this.material.shouldUpdateInputsBindings("matrices"); + if (!this.renderer.ready) + return; + this.setGeometry(); + if (this.visible) { + this._onRenderCallback && this._onRenderCallback(); } - super.onBeforeRenderPass(); + this.material.onBeforeRender(); + this.ready = this.material && this.material.ready && this.geometry && this.geometry.ready; } /** - * Render our Mesh if the {@link RenderMaterial} is ready and if it is not frustum culled. - * @param pass - current render pass + * Render our {@link MeshBase} if the {@link RenderMaterial} is ready + * @param pass - current render pass encoder */ onRenderPass(pass) { if (!this.ready) return; - this._onRenderCallback && this._onRenderCallback(); - if (this.domFrustum && this.domFrustum.isIntersecting || !this.frustumCulling) { - this.material.render(pass); - this.geometry.render(pass); + this.material.render(pass); + this.geometry.render(pass); + } + /** + * Called after having rendered the Mesh + */ + onAfterRenderPass() { + this._onAfterRenderCallback && this._onAfterRenderCallback(); + } + /** + * Render our Mesh + * - Execute {@link onBeforeRenderPass} + * - Stop here if {@link Renderer} is not ready or Mesh is not {@link visible} + * - Execute super render call if it exists + * - {@link onRenderPass | render} our {@link material} and {@link geometry} + * - Execute {@link onAfterRenderPass} + * @param pass - current render pass encoder + */ + render(pass) { + this.onBeforeRenderPass(); + if (!this.renderer.ready || !this.visible) + return; + !this.renderer.production && pass.pushDebugGroup(this.options.label); + this.onRenderPass(pass); + !this.renderer.production && pass.popDebugGroup(); + this.onAfterRenderPass(); + } + /* DESTROY */ + /** + * Remove the Mesh from the {@link core/scenes/Scene.Scene | Scene} and destroy it + */ + remove() { + this.removeFromScene(true); + this.destroy(); + if (!this.renderer.meshes.length) { + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.forceClear(commandEncoder); + }, + { once: true } + ); + } + } + /** + * Destroy the Mesh + */ + destroy() { + if (super.destroy) { + super.destroy(); + } + this.material?.destroy(); + this.geometry.consumers.delete(this.uuid); + if (!this.geometry.consumers.size) { + this.geometry?.destroy(this.renderer); } } - destroy() { - if (this.options.castShadows) { - this.renderer.shadowCastingLights.forEach((light) => { - if (light.shadow.isActive) { - light.shadow.removeMesh(this); - } - }); - } - super.destroy(); + }, _autoRender = new WeakMap(), _a; + } + + class CacheManager { + /** + * CacheManager constructor + */ + constructor() { + this.planeGeometries = []; + } + /** + * Check if a given {@link PlaneGeometry} is already cached based on its {@link PlaneGeometry#definition.id | definition id} + * @param planeGeometry - {@link PlaneGeometry} to check + * @returns - {@link PlaneGeometry} found or null if not found + */ + getPlaneGeometry(planeGeometry) { + return this.planeGeometries.find((element) => element.definition.id === planeGeometry.definition.id); + } + /** + * Check if a given {@link PlaneGeometry} is already cached based on its {@link PlaneGeometry#definition | definition id} + * @param planeGeometryID - {@link PlaneGeometry#definition.id | PlaneGeometry definition id} + * @returns - {@link PlaneGeometry} found or null if not found + */ + getPlaneGeometryByID(planeGeometryID) { + return this.planeGeometries.find((element) => element.definition.id === planeGeometryID); + } + /** + * Add a {@link PlaneGeometry} to our cache {@link planeGeometries} array + * @param planeGeometry + */ + addPlaneGeometry(planeGeometry) { + this.planeGeometries.push(planeGeometry); + } + /** + * Destroy our {@link CacheManager} + */ + destroy() { + this.planeGeometries = []; + } + } + const cacheManager = new CacheManager(); + + class FullscreenPlane extends MeshBaseMixin(class { + }) { + /** + * FullscreenPlane constructor + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link FullscreenPlane} + * @param parameters - {@link MeshBaseRenderParams | parameters} use to create this {@link FullscreenPlane} + */ + constructor(renderer, parameters = {}) { + renderer = isRenderer(renderer, parameters.label ? parameters.label + " FullscreenQuadMesh" : "FullscreenQuadMesh"); + let geometry = cacheManager.getPlaneGeometryByID(2); + if (!geometry) { + geometry = new PlaneGeometry({ widthSegments: 1, heightSegments: 1 }); + cacheManager.addPlaneGeometry(geometry); + } + if (!parameters.shaders || !parameters.shaders.vertex) { + ["uniforms", "storages"].forEach((bindingType) => { + Object.values(parameters[bindingType] ?? {}).forEach( + (binding) => binding.visibility = ["fragment"] + ); + }); } - }; + parameters.depthWriteEnabled = false; + if (!parameters.label) { + parameters.label = "FullscreenQuadMesh"; + } + super(renderer, null, { geometry, ...parameters }); + this.size = { + document: { + width: this.renderer.boundingRect.width, + height: this.renderer.boundingRect.height, + top: this.renderer.boundingRect.top, + left: this.renderer.boundingRect.left + } + }; + this.type = "FullscreenQuadMesh"; + } + /** + * Resize our {@link FullscreenPlane} + * @param boundingRect - the new bounding rectangle + */ + resize(boundingRect = null) { + this.size.document = boundingRect ?? this.renderer.boundingRect; + super.resize(boundingRect); + } + /** + * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link FullscreenPlane} + * It ranges from -1 to 1 on both axis + * @param mouseCoords - pointer {@link Vec2 | vector} coordinates + * @returns - the mapped {@link Vec2 | vector} coordinates in the [-1, 1] range + */ + mouseToPlaneCoords(mouseCoords = new Vec2()) { + return new Vec2( + (mouseCoords.x - this.size.document.left) / this.size.document.width * 2 - 1, + 1 - (mouseCoords.y - this.size.document.top) / this.size.document.height * 2 + ); + } } - class Mesh extends ProjectedMeshBaseMixin(ProjectedObject3D) { + class Mat3 { + // prettier-ignore /** - * Mesh constructor - * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Mesh} - * @param parameters - {@link ProjectedMeshParameters | parameters} use to create this {@link Mesh} + * Mat3 constructor + * @param elements - initial array to use, default to identity matrix */ - constructor(renderer, parameters = {}) { - renderer = isCameraRenderer(renderer, parameters.label ? parameters.label + " Mesh" : "Mesh"); - super(renderer, null, parameters); - this.type = "Mesh"; + constructor(elements = new Float32Array([ + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ])) { + this.type = "Mat3"; + this.elements = elements; + } + /** + * Sets the matrix from 9 numbers + * + * @param n11 - number + * @param n12 - number + * @param n13 - number + * @param n21 - number + * @param n22 - number + * @param n23 - number + * @param n31 - number + * @param n32 - number + * @param n33 - number + * @returns - this {@link Mat3} after being set + */ + set(n11, n12, n13, n21, n22, n23, n31, n32, n33) { + const te = this.elements; + te[0] = n11; + te[1] = n21; + te[2] = n31; + te[3] = n12; + te[4] = n22; + te[5] = n32; + te[6] = n13; + te[7] = n23; + te[8] = n33; + return this; + } + /** + * Sets the {@link Mat3} to an identity matrix + * @returns - this {@link Mat3} after being set + */ + identity() { + this.set(1, 0, 0, 0, 1, 0, 0, 0, 1); + return this; + } + /** + * Sets the {@link Mat3} values from an array + * @param array - array to use + * @returns - this {@link Mat3} after being set + */ + // prettier-ignore + setFromArray(array = new Float32Array([ + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ])) { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i] = array[i]; + } + return this; + } + /** + * Copy another {@link Mat3} + * @param matrix - matrix to copy + * @returns - this {@link Mat3} after being set + */ + copy(matrix = new Mat3()) { + const array = matrix.elements; + this.elements[0] = array[0]; + this.elements[1] = array[1]; + this.elements[2] = array[2]; + this.elements[3] = array[3]; + this.elements[4] = array[4]; + this.elements[5] = array[5]; + this.elements[6] = array[6]; + this.elements[7] = array[7]; + this.elements[8] = array[8]; + return this; + } + /** + * Clone a {@link Mat3} + * @returns - cloned {@link Mat3} + */ + clone() { + return new Mat3().copy(this); + } + /** + * Set a {@link Mat3} from a {@link Mat4}. + * @param matrix - {@link Mat4} to use. + * @returns - this {@link Mat3} after being set. + */ + setFromMat4(matrix = new Mat4()) { + const me = matrix.elements; + this.set(me[0], me[4], me[8], me[1], me[5], me[9], me[2], me[6], me[10]); + return this; + } + /** + * Multiply this {@link Mat3} with another {@link Mat3} + * @param matrix - {@link Mat3} to multiply with + * @returns - this {@link Mat3} after multiplication + */ + multiply(matrix = new Mat3()) { + return this.multiplyMatrices(this, matrix); + } + /** + * Multiply another {@link Mat3} with this {@link Mat3} + * @param matrix - {@link Mat3} to multiply with + * @returns - this {@link Mat3} after multiplication + */ + premultiply(matrix = new Mat3()) { + return this.multiplyMatrices(matrix, this); + } + /** + * Multiply two {@link Mat3} + * @param a - first {@link Mat3} + * @param b - second {@link Mat3} + * @returns - {@link Mat3} resulting from the multiplication + */ + multiplyMatrices(a = new Mat3(), b = new Mat3()) { + const ae = a.elements; + const be = b.elements; + const te = this.elements; + const a11 = ae[0], a12 = ae[3], a13 = ae[6]; + const a21 = ae[1], a22 = ae[4], a23 = ae[7]; + const a31 = ae[2], a32 = ae[5], a33 = ae[8]; + const b11 = be[0], b12 = be[3], b13 = be[6]; + const b21 = be[1], b22 = be[4], b23 = be[7]; + const b31 = be[2], b32 = be[5], b33 = be[8]; + te[0] = a11 * b11 + a12 * b21 + a13 * b31; + te[3] = a11 * b12 + a12 * b22 + a13 * b32; + te[6] = a11 * b13 + a12 * b23 + a13 * b33; + te[1] = a21 * b11 + a22 * b21 + a23 * b31; + te[4] = a21 * b12 + a22 * b22 + a23 * b32; + te[7] = a21 * b13 + a22 * b23 + a23 * b33; + te[2] = a31 * b11 + a32 * b21 + a33 * b31; + te[5] = a31 * b12 + a32 * b22 + a33 * b32; + te[8] = a31 * b13 + a32 * b23 + a33 * b33; + return this; + } + /** + * Invert this {@link Mat3}. + * @returns - this {@link Mat3} after being inverted + */ + invert() { + const te = this.elements, n11 = te[0], n21 = te[1], n31 = te[2], n12 = te[3], n22 = te[4], n32 = te[5], n13 = te[6], n23 = te[7], n33 = te[8], t11 = n33 * n22 - n32 * n23, t12 = n32 * n13 - n33 * n12, t13 = n23 * n12 - n22 * n13, det = n11 * t11 + n21 * t12 + n31 * t13; + if (det === 0) + return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0); + const detInv = 1 / det; + te[0] = t11 * detInv; + te[1] = (n31 * n23 - n33 * n21) * detInv; + te[2] = (n32 * n21 - n31 * n22) * detInv; + te[3] = t12 * detInv; + te[4] = (n33 * n11 - n31 * n13) * detInv; + te[5] = (n31 * n12 - n32 * n11) * detInv; + te[6] = t13 * detInv; + te[7] = (n21 * n13 - n23 * n11) * detInv; + te[8] = (n22 * n11 - n21 * n12) * detInv; + return this; + } + /** + * Transpose this {@link Mat3}. + * @returns - this {@link Mat3} after being transposed + */ + transpose() { + let tmp; + const m = this.elements; + tmp = m[1]; + m[1] = m[3]; + m[3] = tmp; + tmp = m[2]; + m[2] = m[6]; + m[6] = tmp; + tmp = m[5]; + m[5] = m[7]; + m[7] = tmp; + return this; + } + /** + * Compute a normal {@link Mat3} matrix from a {@link Mat4} transformation matrix. + * @param matrix - {@link Mat4} transformation matrix + * @returns - this {@link Mat3} after being inverted and transposed + */ + getNormalMatrix(matrix = new Mat4()) { + return this.setFromMat4(matrix).invert().transpose(); } } - let pipelineId = 0; - class PipelineEntry { + class ProjectedObject3D extends Object3D { /** - * PipelineEntry constructor - * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link PipelineEntry} + * ProjectedObject3D constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link ProjectedObject3D} */ - constructor(parameters) { - this.type = "PipelineEntry"; - let { renderer } = parameters; - const { label, shaders, useAsync } = parameters; - renderer = isRenderer(renderer, label ? label + " " + this.type : this.type); - this.renderer = renderer; - Object.defineProperty(this, "index", { value: pipelineId++ }); - this.layout = null; - this.pipeline = null; - this.status = { - compiling: false, - compiled: false, - error: null - }; - this.options = { - label, - shaders, - useAsync: useAsync !== void 0 ? useAsync : true - }; + constructor(renderer) { + super(); + renderer = isCameraRenderer(renderer, "ProjectedObject3D"); + this.camera = renderer.camera; } /** - * Get whether the {@link pipeline} is ready, i.e. successfully compiled - * @readonly + * Tell our projection matrix stack to update */ - get ready() { - return !this.status.compiling && this.status.compiled && !this.status.error; + applyPosition() { + super.applyPosition(); + this.shouldUpdateProjectionMatrixStack(); } /** - * Get whether the {@link pipeline} is ready to be compiled, i.e. we have not already tried to compile it, and it's not currently compiling neither - * @readonly + * Tell our projection matrix stack to update */ - get canCompile() { - return !this.status.compiling && !this.status.compiled && !this.status.error; + applyRotation() { + super.applyRotation(); + this.shouldUpdateProjectionMatrixStack(); } /** - * Set our {@link PipelineEntry#bindGroups | pipeline entry bind groups} - * @param bindGroups - {@link core/materials/Material.Material#bindGroups | bind groups} to use with this {@link PipelineEntry} + * Tell our projection matrix stack to update */ - setPipelineEntryBindGroups(bindGroups) { - this.bindGroups = bindGroups; + applyScale() { + super.applyScale(); + this.shouldUpdateProjectionMatrixStack(); } - /* SHADERS */ /** - * Create a {@link GPUShaderModule} - * @param parameters - Parameters used - * @param parameters.code - patched WGSL code string - * @param parameters.type - {@link MaterialShadersType | shader type} - * @returns - compiled {@link GPUShaderModule} if successful + * Tell our projection matrix stack to update */ - createShaderModule({ code = "", type = "vertex" }) { - const shaderModule = this.renderer.createShaderModule({ - label: this.options.label + ": " + type + " shader module", - code - }); - if ("getCompilationInfo" in shaderModule && !this.renderer.production) { - shaderModule.getCompilationInfo().then((compilationInfo) => { - for (const message of compilationInfo.messages) { - let formattedMessage = ""; - if (message.lineNum) { - formattedMessage += `Line ${message.lineNum}:${message.linePos} - ${code.substring( - message.offset, - message.offset + message.length - )} -`; - } - formattedMessage += message.message; - switch (message.type) { - case "error": - console.error(`${this.options.label} compilation error: -${formattedMessage}`); - break; - case "warning": - console.warn(`${this.options.label} compilation warning: -${formattedMessage}`); - break; - case "info": - console.log(`${this.options.label} compilation information: -${formattedMessage}`); - break; - } + applyTransformOrigin() { + super.applyTransformOrigin(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Set our transform and projection matrices + */ + setMatrices() { + super.setMatrices(); + this.matrices = { + ...this.matrices, + modelView: { + matrix: new Mat4(), + shouldUpdate: true, + onUpdate: () => { + this.modelViewMatrix.multiplyMatrices(this.viewMatrix, this.worldMatrix); } - }); - } - return shaderModule; + }, + modelViewProjection: { + matrix: new Mat4(), + shouldUpdate: true, + onUpdate: () => { + this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix, this.modelViewMatrix); + } + }, + normal: { + matrix: new Mat3(), + shouldUpdate: true, + onUpdate: () => { + this.normalMatrix.getNormalMatrix(this.worldMatrix); + } + } + }; } - /* SETUP */ /** - * Create the {@link PipelineEntry} shaders + * Get our {@link modelViewMatrix | model view matrix} */ - createShaders() { + get modelViewMatrix() { + return this.matrices.modelView.matrix; } /** - * Create the pipeline entry {@link layout} + * Set our {@link modelViewMatrix | model view matrix} + * @param value - new {@link modelViewMatrix | model view matrix} */ - createPipelineLayout() { - this.layout = this.renderer.createPipelineLayout({ - label: this.options.label + " layout", - bindGroupLayouts: this.bindGroups.map((bindGroup) => bindGroup.bindGroupLayout) - }); + set modelViewMatrix(value) { + this.matrices.modelView.matrix = value; + this.matrices.modelView.shouldUpdate = true; } /** - * Create the {@link PipelineEntry} descriptor + * Get our {@link Camera#viewMatrix | camera view matrix} + * @readonly */ - createPipelineDescriptor() { + get viewMatrix() { + return this.camera.viewMatrix; } /** - * Flush a {@link PipelineEntry}, i.e. reset its {@link bindGroups | bind groups}, {@link layout} and descriptor and recompile the {@link pipeline} - * Used when one of the bind group or rendering property has changed - * @param newBindGroups - new {@link bindGroups | bind groups} in case they have changed + * Get our {@link Camera#projectionMatrix | camera projection matrix} + * @readonly */ - flushPipelineEntry(newBindGroups = []) { - this.status.compiling = false; - this.status.compiled = false; - this.status.error = null; - this.setPipelineEntryBindGroups(newBindGroups); - this.compilePipelineEntry(); + get projectionMatrix() { + return this.camera.projectionMatrix; } /** - * Set up a {@link pipeline} by creating the shaders, the {@link layout} and the descriptor + * Get our {@link modelViewProjectionMatrix | model view projection matrix} */ - compilePipelineEntry() { - this.status.compiling = true; - this.createShaders(); - this.createPipelineLayout(); - this.createPipelineDescriptor(); + get modelViewProjectionMatrix() { + return this.matrices.modelViewProjection.matrix; } - } - - var get_output_position = ( - /* wgsl */ - ` -fn getWorldPosition(position: vec3f) -> vec4f { - return matrices.model * vec4f(position, 1.0); -} - -fn getOutputPosition(position: vec3f) -> vec4f { - return camera.projection * matrices.modelView * vec4f(position, 1.0); -}` - ); - - var get_normals = ( - /* wgsl */ - ` -fn getWorldNormal(normal: vec3f) -> vec3f { - return normalize(matrices.normal * normal); -} - -fn getViewNormal(normal: vec3f) -> vec3f { - return normalize((camera.view * vec4(matrices.normal * normal, 0.0)).xyz); -}` - ); - - var get_uv_cover = ( - /* wgsl */ - ` -fn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f { - return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy; -}` - ); - - var get_vertex_to_uv_coords = ( - /* wgsl */ - ` -fn getVertex2DToUVCoords(vertex: vec2f) -> vec2f { - return vec2( - vertex.x * 0.5 + 0.5, - 0.5 - vertex.y * 0.5 - ); -} - -fn getVertex3DToUVCoords(vertex: vec3f) -> vec2f { - return getVertex2DToUVCoords( vec2(vertex.x, vertex.y) ); -} -` - ); - - const ShaderChunks = { - /** WGSL code chunks added to the vertex shader */ - vertex: { - /** Applies given texture matrix to given uv coordinates */ - get_uv_cover - }, - /** WGSL code chunks added to the fragment shader */ - fragment: { - /** Applies given texture matrix to given uv coordinates */ - get_uv_cover, - /** Convert vertex position to uv coordinates */ - get_vertex_to_uv_coords + /** + * Set our {@link modelViewProjectionMatrix | model view projection matrix} + * @param value - new {@link modelViewProjectionMatrix | model view projection matrix}s + */ + set modelViewProjectionMatrix(value) { + this.matrices.modelViewProjection.matrix = value; + this.matrices.modelViewProjection.shouldUpdate = true; + } + /** + * Get our {@link normalMatrix | normal matrix} + */ + get normalMatrix() { + return this.matrices.normal.matrix; + } + /** + * Set our {@link normalMatrix | normal matrix} + * @param value - new {@link normalMatrix | normal matrix} + */ + set normalMatrix(value) { + this.matrices.normal.matrix = value; + this.matrices.normal.shouldUpdate = true; } - }; - const ProjectedShaderChunks = { - /** WGSL code chunks added to the vertex shader */ - vertex: { - /** Get output vec4f position vector by applying model view projection matrix to vec3f attribute position vector */ - get_output_position, - /** Get vec3f normals in world or view space */ - get_normals - }, - /** WGSL code chunks added to the fragment shader */ - fragment: {} - }; - - class RenderPipelineEntry extends PipelineEntry { /** - * RenderPipelineEntry constructor - * @param parameters - {@link RenderPipelineEntryParams | parameters} used to create this {@link RenderPipelineEntry} + * Set our projection matrices shouldUpdate flags to true (tell them to update) */ - constructor(parameters) { - let { renderer, ...pipelineParams } = parameters; - const { label, attributes, bindGroups, cacheKey, ...renderingOptions } = pipelineParams; - const type = "RenderPipelineEntry"; - isRenderer(renderer, label ? label + " " + type : type); - super(parameters); - this.type = type; - this.shaders = { - vertex: { - head: "", - code: "", - module: null - }, - fragment: { - head: "", - code: "", - module: null - }, - full: { - head: "", - code: "", - module: null - } - }; - this.descriptor = null; - this.options = { - ...this.options, - attributes, - bindGroups, - cacheKey, - ...renderingOptions - }; - this.setPipelineEntryProperties({ attributes, bindGroups }); + shouldUpdateProjectionMatrixStack() { + this.matrices.modelView.shouldUpdate = true; + this.matrices.modelViewProjection.shouldUpdate = true; } /** - * Set {@link RenderPipelineEntry} properties (in this case the {@link bindGroups | bind groups} and {@link attributes}) - * @param parameters - the {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} and {@link core/materials/RenderMaterial.RenderMaterial#attributes | attributes} to use + * When the world matrix update, tell our projection matrix to update as well */ - setPipelineEntryProperties(parameters) { - const { attributes, bindGroups } = parameters; - this.attributes = attributes; - this.setPipelineEntryBindGroups(bindGroups); + shouldUpdateWorldMatrix() { + super.shouldUpdateWorldMatrix(); + this.shouldUpdateProjectionMatrixStack(); + this.matrices.normal.shouldUpdate = true; } - /* SHADERS */ /** - * Patch the shaders by appending all the necessary shader chunks, {@link bindGroups | bind groups}) and {@link attributes} WGSL code fragments to the given {@link types/PipelineEntries.PipelineEntryParams#shaders | parameter shader code} + * Tell all our matrices to update */ - patchShaders() { - this.shaders.vertex.head = ""; - this.shaders.vertex.code = ""; - this.shaders.fragment.head = ""; - this.shaders.fragment.code = ""; - this.shaders.full.head = ""; - this.shaders.full.code = ""; - for (const chunk in ShaderChunks.vertex) { - this.shaders.vertex.head = `${ShaderChunks.vertex[chunk]} -${this.shaders.vertex.head}`; - this.shaders.full.head = `${ShaderChunks.vertex[chunk]} -${this.shaders.full.head}`; + shouldUpdateMatrixStack() { + this.shouldUpdateModelMatrix(); + this.shouldUpdateProjectionMatrixStack(); + } + } + + var default_normal_fsWgsl = ( + /* wgsl */ + ` +struct VSOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normal: vec3f, +}; + +@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { + // normals + return vec4(normalize(fsInput.normal) * 0.5 + 0.5, 1.0); +}` + ); + + const defaultProjectedMeshParams = { + // frustum culling and visibility + frustumCulling: "OBB", + DOMFrustumMargins: { + top: 0, + right: 0, + bottom: 0, + left: 0 + }, + receiveShadows: false, + castShadows: false + }; + function ProjectedMeshBaseMixin(Base) { + return class ProjectedMeshBase extends MeshBaseMixin(Base) { + /** + * ProjectedMeshBase constructor + * + * @typedef MeshBaseArrayParams + * @type {array} + * @property {(CameraRenderer|GPUCurtains)} 0 - our renderer class object + * @property {(string|HTMLElement|null)} 1 - the DOM HTML Element that can be bound to a Mesh + * @property {ProjectedMeshParameters} 2 - Projected Mesh parameters + * + * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters + */ + constructor(...params) { + super( + params[0], + params[1], + { ...defaultProjectedMeshParams, ...params[2], ...{ useProjection: true } } + ); + // callbacks / events + /** function assigned to the {@link onReEnterView} callback */ + this._onReEnterViewCallback = () => { + }; + /** function assigned to the {@link onLeaveView} callback */ + this._onLeaveViewCallback = () => { + }; + let renderer = params[0]; + const parameters = { + ...defaultProjectedMeshParams, + ...params[2], + ...{ useProjection: true } + }; + this.type = "MeshTransformed"; + renderer = isCameraRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); + this.renderer = renderer; + const { frustumCulling, DOMFrustumMargins, receiveShadows, castShadows } = parameters; + this.options = { + ...this.options ?? {}, + // merge possible lower options? + frustumCulling, + DOMFrustumMargins, + receiveShadows, + castShadows + }; + if (this.options.castShadows) { + this.renderer.shadowCastingLights.forEach((light) => { + if (light.shadow.isActive) { + light.shadow.addShadowCastingMesh(this); + } + }); + } + this.setDOMFrustum(); } - if (this.options.shaders.fragment) { - for (const chunk in ShaderChunks.fragment) { - this.shaders.fragment.head = `${ShaderChunks.fragment[chunk]} -${this.shaders.fragment.head}`; - if (this.shaders.full.head.indexOf(ShaderChunks.fragment[chunk]) === -1) { - this.shaders.full.head = `${ShaderChunks.fragment[chunk]} -${this.shaders.full.head}`; - } + /** + * Set or reset this Mesh {@link renderer}. + * @param renderer - New {@link CameraRenderer} or {@link GPUCurtains} instance to use. + */ + setRenderer(renderer) { + super.setRenderer(renderer); + this.camera = this.renderer.camera; + if (this.options.castShadows) { + this.renderer.shadowCastingLights.forEach((light) => { + if (light.shadow.isActive) { + light.shadow.addShadowCastingMesh(this); + } + }); } } - if (this.options.rendering.useProjection) { - for (const chunk in ProjectedShaderChunks.vertex) { - this.shaders.vertex.head = `${ProjectedShaderChunks.vertex[chunk]} -${this.shaders.vertex.head}`; - this.shaders.full.head = `${ProjectedShaderChunks.vertex[chunk]} -${this.shaders.full.head}`; + /** + * Assign or remove a {@link RenderBundle} to this Mesh. + * @param renderBundle - The {@link RenderBundle} to assign or null if we want to remove the current {@link RenderBundle}. + * @param updateScene - Whether to remove and then re-add the Mesh from the {@link core/scenes/Scene.Scene | Scene} or not. + */ + setRenderBundle(renderBundle, updateScene = true) { + if (this.renderBundle && renderBundle && this.renderBundle.uuid === renderBundle.uuid) + return; + const hasRenderBundle = !!this.renderBundle; + const bindGroup = this.material.getBindGroupByBindingName("matrices"); + const matrices = this.material.getBufferBindingByName("matrices"); + if (this.renderBundle && !renderBundle && matrices.parent) { + matrices.parent = null; + matrices.shouldResetBindGroup = true; + bindGroup.createBindingBuffer(matrices); } - if (this.options.shaders.fragment) { - for (const chunk in ProjectedShaderChunks.fragment) { - this.shaders.fragment.head = `${ProjectedShaderChunks.fragment[chunk]} -${this.shaders.fragment.head}`; - if (this.shaders.full.head.indexOf(ProjectedShaderChunks.fragment[chunk]) === -1) { - this.shaders.full.head = `${ProjectedShaderChunks.fragment[chunk]} -${this.shaders.full.head}`; + super.setRenderBundle(renderBundle, updateScene); + if (this.renderBundle && this.renderBundle.binding) { + if (hasRenderBundle) { + bindGroup.destroyBufferBinding(matrices); + } + matrices.options.offset = this.renderBundle.meshes.size - 1; + matrices.parent = this.renderBundle.binding; + matrices.shouldResetBindGroup = true; + } + } + /** + * Reset the {@link BufferBinding | matrices buffer binding} parent and offset and tell its bind group to update. + * @param offset - New offset to use in the parent {@link RenderBundle#binding | RenderBundle binding}. + */ + patchRenderBundleBinding(offset = 0) { + const matrices = this.material.getBufferBindingByName("matrices"); + matrices.options.offset = offset; + matrices.parent = this.renderBundle.binding; + matrices.shouldResetBindGroup = true; + } + /* SHADERS */ + /** + * Set default shaders if one or both of them are missing. + * Can also patch the fragment shader if the mesh should receive shadows. + */ + setShaders() { + const { shaders } = this.options; + if (!shaders) { + this.options.shaders = { + vertex: { + code: default_projected_vsWgsl, + entryPoint: "main" + }, + fragment: { + code: default_normal_fsWgsl, + entryPoint: "main" } + }; + } else { + if (!shaders.vertex || !shaders.vertex.code) { + shaders.vertex = { + code: default_projected_vsWgsl, + entryPoint: "main" + }; + } + if (shaders.fragment === void 0 || shaders.fragment && !shaders.fragment.code) { + shaders.fragment = { + code: default_normal_fsWgsl, + entryPoint: "main" + }; + } + } + if (this.options.receiveShadows) { + const hasActiveShadows = this.renderer.shadowCastingLights.find((light) => light.shadow.isActive); + if (hasActiveShadows && shaders.fragment && typeof shaders.fragment === "object") { + shaders.fragment.code = getPCFDirectionalShadows(this.renderer) + getPCFShadowContribution + getPCFPointShadows(this.renderer) + getPCFPointShadowContribution + shaders.fragment.code; + } + } + return shaders; + } + /* GEOMETRY */ + /** + * Set or update the Projected Mesh {@link Geometry} + * @param geometry - new {@link Geometry} to use + */ + useGeometry(geometry) { + super.useGeometry(geometry); + if (this.domFrustum) { + this.domFrustum.boundingBox = this.geometry.boundingBox; + } + this.shouldUpdateMatrixStack(); + } + /** + * Set the Mesh frustum culling + */ + setDOMFrustum() { + this.domFrustum = new DOMFrustum({ + boundingBox: this.geometry?.boundingBox, + modelViewProjectionMatrix: this.modelViewProjectionMatrix, + containerBoundingRect: this.renderer.boundingRect, + DOMFrustumMargins: this.options.DOMFrustumMargins, + onReEnterView: () => { + this._onReEnterViewCallback && this._onReEnterViewCallback(); + }, + onLeaveView: () => { + this._onLeaveViewCallback && this._onLeaveViewCallback(); } - } - } - const groupsBindings = []; - for (const bindGroup of this.bindGroups) { - let bindIndex = 0; - bindGroup.bindings.forEach((binding, bindingIndex) => { - binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => { - groupsBindings.push({ - groupIndex: bindGroup.index, - visibility: binding.options.visibility, - bindIndex, - wgslStructFragment: binding.wgslStructFragment, - wgslGroupFragment: groupFragment, - newLine: bindingIndex === bindGroup.bindings.length - 1 && groupFragmentIndex === binding.wgslGroupFragment.length - 1 - }); - bindIndex++; - }); }); + this.DOMFrustumMargins = this.domFrustum.DOMFrustumMargins; + this.frustumCulling = this.options.frustumCulling; } - for (const groupBinding of groupsBindings) { - if (groupBinding.visibility.includes("vertex")) { - if (groupBinding.wgslStructFragment && this.shaders.vertex.head.indexOf(groupBinding.wgslStructFragment) === -1) { - this.shaders.vertex.head = ` -${groupBinding.wgslStructFragment} -${this.shaders.vertex.head}`; + /* MATERIAL */ + /** + * Hook used to clean up parameters before sending them to the material. + * @param parameters - parameters to clean before sending them to the {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial} + * @returns - cleaned parameters + */ + cleanupRenderMaterialParameters(parameters) { + delete parameters.frustumCulling; + delete parameters.DOMFrustumMargins; + if (this.options.receiveShadows) { + const depthTextures = []; + let depthSamplers = []; + this.renderer.shadowCastingLights.forEach((light) => { + if (light.shadow.isActive) { + depthTextures.push(light.shadow.depthTexture); + depthSamplers.push(light.shadow.depthComparisonSampler); + } + }); + depthSamplers = depthSamplers.filter( + (sampler, i, array) => array.findIndex((s) => s.uuid === sampler.uuid) === i + ); + if (parameters.textures) { + parameters.textures = [...parameters.textures, ...depthTextures]; + } else { + parameters.textures = depthTextures; } - if (this.shaders.vertex.head.indexOf(groupBinding.wgslGroupFragment) === -1) { - this.shaders.vertex.head = `${this.shaders.vertex.head} -@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; - if (groupBinding.newLine) - this.shaders.vertex.head += ` -`; + if (parameters.samplers) { + parameters.samplers = [...parameters.samplers, ...depthSamplers]; + } else { + parameters.samplers = depthSamplers; } } - if (this.options.shaders.fragment && groupBinding.visibility.includes("fragment")) { - if (groupBinding.wgslStructFragment && this.shaders.fragment.head.indexOf(groupBinding.wgslStructFragment) === -1) { - this.shaders.fragment.head = ` -${groupBinding.wgslStructFragment} -${this.shaders.fragment.head}`; - } - if (this.shaders.fragment.head.indexOf(groupBinding.wgslGroupFragment) === -1) { - this.shaders.fragment.head = `${this.shaders.fragment.head} -@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; - if (groupBinding.newLine) - this.shaders.fragment.head += ` -`; + return super.cleanupRenderMaterialParameters(parameters); + } + /** + * Set a Mesh matrices uniforms inputs then call {@link MeshBaseClass} super method + * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters} + */ + setMaterial(meshParameters) { + const matricesUniforms = { + label: "Matrices", + name: "matrices", + visibility: ["vertex"], + minOffset: this.renderer.device.limits.minUniformBufferOffsetAlignment, + struct: { + model: { + type: "mat4x4f", + value: this.worldMatrix + }, + modelView: { + // model view matrix (world matrix multiplied by camera view matrix) + type: "mat4x4f", + value: this.modelViewMatrix + }, + normal: { + // normal matrix + type: "mat3x3f", + value: this.normalMatrix + } } + }; + if (this.options.renderBundle && this.options.renderBundle.binding) { + matricesUniforms.parent = this.options.renderBundle.binding; + matricesUniforms.offset = this.options.renderBundle.meshes.size; } - if (groupBinding.wgslStructFragment && this.shaders.full.head.indexOf(groupBinding.wgslStructFragment) === -1) { - this.shaders.full.head = ` -${groupBinding.wgslStructFragment} -${this.shaders.full.head}`; + const meshTransformationBinding = new BufferBinding(matricesUniforms); + if (!meshParameters.bindings) + meshParameters.bindings = []; + meshParameters.bindings.unshift(meshTransformationBinding); + super.setMaterial(meshParameters); + } + /** + * Get the visible property value + */ + get visible() { + return this._visible; + } + /** + * Set the visible property value + * @param value - new visibility value + */ + set visible(value) { + this.shouldUpdateMatrixStack(); + this._visible = value; + } + /* SIZE & TRANSFORMS */ + /** + * Resize our {@link ProjectedMeshBaseClass} + * @param boundingRect - the new bounding rectangle + */ + resize(boundingRect) { + if (this.domFrustum) + this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect); + super.resize(boundingRect); + } + /** + * Apply scale and resize textures + */ + applyScale() { + super.applyScale(); + for (const texture of this.domTextures) { + texture.resize(); } - if (this.shaders.full.head.indexOf(groupBinding.wgslGroupFragment) === -1) { - this.shaders.full.head = `${this.shaders.full.head} -@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; - if (groupBinding.newLine) - this.shaders.full.head += ` -`; + } + /** + * Get our {@link DOMFrustum} projected bounding rectangle + * @readonly + */ + get projectedBoundingRect() { + return this.domFrustum?.projectedBoundingRect; + } + /* EVENTS */ + /** + * Assign a callback function to _onReEnterViewCallback + * @param callback - callback to run when {@link ProjectedMeshBaseClass} is reentering the view frustum + * @returns - our Mesh + */ + onReEnterView(callback) { + if (callback) { + this._onReEnterViewCallback = callback; } + return this; } - this.shaders.vertex.head = `${this.attributes.wgslStructFragment} -${this.shaders.vertex.head}`; - this.shaders.full.head = `${this.attributes.wgslStructFragment} -${this.shaders.full.head}`; - this.shaders.vertex.code = this.shaders.vertex.head + this.options.shaders.vertex.code; - if (typeof this.options.shaders.fragment === "object") - this.shaders.fragment.code = this.shaders.fragment.head + this.options.shaders.fragment.code; - if (typeof this.options.shaders.fragment === "object") { - if (this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0) { - this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code; - } else { - this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code + this.options.shaders.fragment.code; + /** + * Assign a callback function to _onLeaveViewCallback + * @param callback - callback to run when {@link ProjectedMeshBaseClass} is leaving the view frustum + * @returns - our Mesh + */ + onLeaveView(callback) { + if (callback) { + this._onLeaveViewCallback = callback; } + return this; } - } - /* SETUP */ - /** - * Get whether the shaders modules have been created - * @readonly - */ - get shadersModulesReady() { - return !(!this.shaders.vertex.module || this.options.shaders.fragment && !this.shaders.fragment.module); - } - /** - * Create the {@link shaders}: patch them and create the {@link GPUShaderModule} - */ - createShaders() { - this.patchShaders(); - const isSameShader = typeof this.options.shaders.fragment === "object" && this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0; - this.shaders.vertex.module = this.createShaderModule({ - code: this.shaders[isSameShader ? "full" : "vertex"].code, - type: "vertex" - }); - if (this.options.shaders.fragment) { - this.shaders.fragment.module = this.createShaderModule({ - code: this.shaders[isSameShader ? "full" : "fragment"].code, - type: "fragment" - }); + /* RENDER */ + /** + * Get the geometry bounding sphere in clip space. + * @readonly + */ + get clipSpaceBoundingSphere() { + const { center, radius, min, max } = this.geometry.boundingBox; + const translation = this.worldMatrix.getTranslation(); + const maxWorldRadius = radius * this.worldMatrix.getMaxScaleOnAxis(); + const cMin = center.clone().add(translation); + cMin.z += min.z; + const cMax = center.clone().add(translation); + cMax.z += max.z; + const sMin = cMin.clone(); + sMin.y += maxWorldRadius; + const sMax = cMax.clone(); + sMax.y += maxWorldRadius; + cMin.applyMat4(this.camera.viewProjectionMatrix); + cMax.applyMat4(this.camera.viewProjectionMatrix); + sMin.applyMat4(this.camera.viewProjectionMatrix); + sMax.applyMat4(this.camera.viewProjectionMatrix); + const rMin = cMin.distance(sMin); + const rMax = cMax.distance(sMax); + const rectMin = { + xMin: cMin.x - rMin, + xMax: cMin.x + rMin, + yMin: cMin.y - rMin, + yMax: cMin.y + rMin + }; + const rectMax = { + xMin: cMax.x - rMax, + xMax: cMax.x + rMax, + yMin: cMax.y - rMax, + yMax: cMax.y + rMax + }; + const rect = { + xMin: Math.min(rectMin.xMin, rectMax.xMin), + yMin: Math.min(rectMin.yMin, rectMax.yMin), + xMax: Math.max(rectMin.xMax, rectMax.xMax), + yMax: Math.max(rectMin.yMax, rectMax.yMax) + }; + const sphereCenter = cMax.add(cMin).multiplyScalar(0.5).clone(); + sphereCenter.x = (rect.xMax + rect.xMin) / 2; + sphereCenter.y = (rect.yMax + rect.yMin) / 2; + const sphereRadius = Math.max(rect.xMax - rect.xMin, rect.yMax - rect.yMin) * 0.5; + return { + center: sphereCenter, + radius: sphereRadius + }; } - } - /** - * Create the render pipeline {@link descriptor} - */ - createPipelineDescriptor() { - if (!this.shadersModulesReady) - return; - let vertexLocationIndex = -1; - if (this.options.rendering.targets.length) { - if (this.options.rendering.transparent) { - this.options.rendering.targets[0].blend = this.options.rendering.targets[0].blend ? this.options.rendering.targets[0].blend : { - color: { - srcFactor: "src-alpha", - dstFactor: "one-minus-src-alpha" - }, - alpha: { - srcFactor: "one", - dstFactor: "one-minus-src-alpha" + /** + * Check if the Mesh lies inside the {@link camera} view frustum or not using the test defined by {@link frustumCulling}. + */ + checkFrustumCulling() { + if (this.matricesNeedUpdate) { + if (this.domFrustum && this.frustumCulling) { + if (this.frustumCulling === "sphere") { + this.domFrustum.setDocumentCoordsFromClipSpaceSphere(this.clipSpaceBoundingSphere); + } else { + this.domFrustum.setDocumentCoordsFromClipSpaceOBB(); } - }; + this.domFrustum.intersectsContainer(); + } } - } else { - this.options.rendering.targets = []; } - this.descriptor = { - label: this.options.label, - layout: this.layout, - vertex: { - module: this.shaders.vertex.module, - entryPoint: this.options.shaders.vertex.entryPoint, - buffers: this.attributes.vertexBuffers.map((vertexBuffer) => { - return { - stepMode: vertexBuffer.stepMode, - arrayStride: vertexBuffer.arrayStride * 4, - // 4 bytes each - attributes: vertexBuffer.attributes.map((attribute) => { - vertexLocationIndex++; - return { - shaderLocation: vertexLocationIndex, - offset: attribute.bufferOffset, - // previous attribute size * 4 - format: attribute.bufferFormat - }; - }) - }; - }) - }, - ...this.options.shaders.fragment && { - fragment: { - module: this.shaders.fragment.module, - entryPoint: this.options.shaders.fragment.entryPoint, - targets: this.options.rendering.targets - } - }, - primitive: { - topology: this.options.rendering.topology, - frontFace: this.options.rendering.verticesOrder, - cullMode: this.options.rendering.cullMode - }, - ...this.options.rendering.depth && { - depthStencil: { - depthWriteEnabled: this.options.rendering.depthWriteEnabled, - depthCompare: this.options.rendering.depthCompare, - format: this.options.rendering.depthFormat - } - }, - ...this.options.rendering.sampleCount > 1 && { - multisample: { - count: this.options.rendering.sampleCount - } + /** + * Tell our matrices bindings to update if needed and call {@link MeshBaseClass#onBeforeRenderPass | Mesh base onBeforeRenderPass} super. + */ + onBeforeRenderPass() { + if (this.material && this.matricesNeedUpdate) { + this.material.shouldUpdateInputsBindings("matrices"); } - }; - } - /** - * Create the render {@link pipeline} - */ - createRenderPipeline() { - if (!this.shadersModulesReady) - return; - try { - this.pipeline = this.renderer.createRenderPipeline(this.descriptor); - } catch (error) { - this.status.error = error; - throwError(error); + super.onBeforeRenderPass(); } - } - /** - * Asynchronously create the render {@link pipeline} - * @async - * @returns - void promise result - */ - async createRenderPipelineAsync() { - if (!this.shadersModulesReady) - return; - try { - this.pipeline = await this.renderer.createRenderPipelineAsync(this.descriptor); - this.status.compiled = true; - this.status.compiling = false; - this.status.error = null; - } catch (error) { - this.status.error = error; - throwError(error); + /** + * Render our Mesh if the {@link RenderMaterial} is ready and if it is not frustum culled. + * @param pass - current render pass + */ + onRenderPass(pass) { + if (!this.ready) + return; + this._onRenderCallback && this._onRenderCallback(); + if (this.domFrustum && this.domFrustum.isIntersecting || !this.frustumCulling) { + this.material.render(pass); + this.geometry.render(pass); + } } - } + destroy() { + if (this.options.castShadows) { + this.renderer.shadowCastingLights.forEach((light) => { + if (light.shadow.isActive) { + light.shadow.removeMesh(this); + } + }); + } + super.destroy(); + } + }; + } + + class Mesh extends ProjectedMeshBaseMixin(ProjectedObject3D) { /** - * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our render {@link pipeline} - * @async + * Mesh constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Mesh} + * @param parameters - {@link ProjectedMeshParameters | parameters} use to create this {@link Mesh} */ - async compilePipelineEntry() { - super.compilePipelineEntry(); - if (this.options.useAsync) { - await this.createRenderPipelineAsync(); - } else { - this.createRenderPipeline(); - this.status.compiled = true; - this.status.compiling = false; - this.status.error = null; - } + constructor(renderer, parameters = {}) { + renderer = isCameraRenderer(renderer, parameters.label ? parameters.label + " Mesh" : "Mesh"); + super(renderer, null, parameters); + this.type = "Mesh"; } } @@ -11060,8 +11600,7 @@ ${this.shaders.full.head}`; * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link ComputePipelineEntry} */ constructor(parameters) { - const { renderer } = parameters; - const { label } = parameters; + const { label, renderer, bindGroups } = parameters; const type = "ComputePipelineEntry"; isRenderer(renderer, label ? label + " " + type : type); super(parameters); @@ -11074,14 +11613,7 @@ ${this.shaders.full.head}`; } }; this.descriptor = null; - } - /** - * Set {@link ComputePipelineEntry} properties (in this case the {@link bindGroups | bind groups}) - * @param parameters - the {@link core/materials/ComputeMaterial.ComputeMaterial#bindGroups | bind groups} to use - */ - setPipelineEntryProperties(parameters) { - const { bindGroups } = parameters; - this.setPipelineEntryBindGroups(bindGroups); + this.setPipelineEntryProperties({ bindGroups }); } /* SHADERS */ /** @@ -11253,15 +11785,41 @@ ${this.shaders.compute.head}`; } } /** - * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineEntryParams | parameters}. - * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * Checks if the provided {@link PipelineEntryParams | PipelineEntry parameters} belongs to an already created {@link ComputePipelineEntry}. * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters} + * @returns - the found {@link ComputePipelineEntry}, or null if not found + */ + isSameComputePipeline(parameters) { + return this.pipelineEntries.filter((pipelineEntry) => pipelineEntry instanceof ComputePipelineEntry).find((pipelineEntry) => { + const { options } = pipelineEntry; + const { shaders, cacheKey } = parameters; + const sameCacheKey = cacheKey === options.cacheKey; + const sameComputeShader = this.compareShaders(shaders.compute, options.shaders.compute); + return sameCacheKey && sameComputeShader; + }); + } + /** + * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineManagerPipelineEntryParams | parameters}. + * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * @param parameters - {@link PipelineManagerPipelineEntryParams | PipelineEntry parameters} * @returns - newly created {@link ComputePipelineEntry} */ createComputePipeline(parameters) { - const pipelineEntry = new ComputePipelineEntry(parameters); - this.pipelineEntries.push(pipelineEntry); - return pipelineEntry; + let cacheKey = ""; + parameters.bindGroups.forEach((bindGroup) => { + bindGroup.bindings.forEach((binding) => { + cacheKey += binding.name + ","; + }); + cacheKey += bindGroup.pipelineCacheKey; + }); + const existingPipelineEntry = this.isSameComputePipeline({ ...parameters, cacheKey }); + if (existingPipelineEntry) { + return existingPipelineEntry; + } else { + const pipelineEntry = new ComputePipelineEntry({ ...parameters, cacheKey }); + this.pipelineEntries.push(pipelineEntry); + return pipelineEntry; + } } /** * Check if the given {@link AllowedPipelineEntries | PipelineEntry} is already set, if not set it @@ -11585,6 +12143,23 @@ ${this.shaders.compute.head}`; const { stack } = renderPassEntry; return mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected; } + /** + * Order a {@link SceneStackedObject} array by using the {@link SceneStackedObject#renderOrder | renderOrder} or {@link SceneStackedObject#index | index} properties. + * @param stack - {@link SceneStackedObject} to sort, filled with {@link RenderedMesh} or {@link RenderBundle}. + */ + orderStack(stack) { + stack.sort((a, b) => { + return a.renderOrder - b.renderOrder || a.index - b.index; + }); + } + /** + * Test whether a {@link SceneStackedObject} is a {@link RenderBundle} or not. + * @param object - Object to test. + * @returns - Whether the {@link object} is a {@link RenderBundle} or not. + */ + isStackObjectRenderBundle(object) { + return object.type === "RenderBundle"; + } /** * Add a Mesh to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. * Meshes are then ordered by their {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#index | indexes (order of creation]}, {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#index | pipeline entry indexes} and then {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#renderOrder | renderOrder} @@ -11592,31 +12167,80 @@ ${this.shaders.compute.head}`; */ addMesh(mesh) { const projectionStack = this.getMeshProjectionStack(mesh); - const similarMeshes = mesh.transparent ? projectionStack.transparent : projectionStack.opaque; - similarMeshes.push(mesh); - similarMeshes.sort((a, b) => { - return a.renderOrder - b.renderOrder || //a.material.pipelineEntry.index - b.material.pipelineEntry.index || - a.index - b.index; - }); - if ("parent" in mesh && !mesh.parent && mesh.material.options.rendering.useProjection) { + const isTransparent = !!mesh.transparent; + const { useProjection } = mesh.material.options.rendering; + if (mesh.renderBundle) { + const { renderBundle } = mesh; + renderBundle.addMesh(mesh, mesh.outputTarget ? mesh.outputTarget.renderPass : this.renderer.renderPass); + if (mesh.renderBundle) { + if (renderBundle.meshes.size === 1) { + if (renderBundle.transparent === null) { + renderBundle.transparent = isTransparent; + } + if (renderBundle.useProjection === null) { + renderBundle.useProjection = useProjection; + } + this.addRenderBundle(renderBundle, projectionStack); + } + } + } + if (!mesh.renderBundle) { + const similarMeshes = isTransparent ? projectionStack.transparent : projectionStack.opaque; + similarMeshes.push(mesh); + this.orderStack(similarMeshes); + } + if ("parent" in mesh && !mesh.parent && useProjection) { mesh.parent = this; } } /** - * Remove a Mesh from our {@link Scene} - * @param mesh - Mesh to remove + * Remove a Mesh from our {@link Scene}. + * @param mesh - Mesh to remove. */ removeMesh(mesh) { const projectionStack = this.getMeshProjectionStack(mesh); - if (mesh.transparent) { - projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid); + const isTransparent = !!mesh.transparent; + if (mesh.renderBundle) { + mesh.renderBundle.removeMesh(mesh, false); } else { - projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid); + if (isTransparent) { + projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid); + } else { + projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid); + } } if ("parent" in mesh && mesh.parent && mesh.parent.object3DIndex === this.object3DIndex) { mesh.parent = null; } } + /** + * Add a {@link RenderBundle} to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. + * @param renderBundle - {@link RenderBundle} to add. + * @param projectionStack - {@link ProjectionStack} onto which to add the {@link RenderBundle}. + */ + addRenderBundle(renderBundle, projectionStack) { + const similarObjects = !!renderBundle.transparent ? projectionStack.transparent : projectionStack.opaque; + similarObjects.push(renderBundle); + this.orderStack(similarObjects); + } + /** + * Remove a {@link RenderBundle} from our {@link Scene}. + * @param renderBundle - {@link RenderBundle} to remove. + */ + removeRenderBundle(renderBundle) { + const renderPassEntry = this.renderPassEntries.renderTarget.find( + (passEntry) => passEntry.renderPass.uuid === renderBundle.options.renderPass?.uuid + ); + const { stack } = renderPassEntry || this.renderPassEntries.screen[0]; + const isProjected = !!renderBundle.useProjection; + const projectionStack = isProjected ? stack.projected : stack.unProjected; + const isTransparent = !!renderBundle.transparent; + if (isTransparent) { + projectionStack.transparent = projectionStack.transparent.filter((bundle) => bundle.uuid !== renderBundle.uuid); + } else { + projectionStack.opaque = projectionStack.opaque.filter((bundle) => bundle.uuid !== renderBundle.uuid); + } + } /** * Add a {@link ShaderPass} to our scene {@link renderPassEntries} screen array. * Before rendering the {@link ShaderPass}, we will copy the correct input texture into its {@link ShaderPass#renderTexture | renderTexture} @@ -11652,9 +12276,10 @@ ${this.shaders.compute.head}`; ); } } : null; + const outputPass = shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass; const shaderPassEntry = { // use output target or postprocessing render pass - renderPass: shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass, + renderPass: outputPass, // render to output target renderTexture or directly to screen renderTexture: shaderPass.outputTarget ? shaderPass.outputTarget.renderTexture : null, onBeforeRenderPass, @@ -11663,6 +12288,24 @@ ${this.shaders.compute.head}`; stack: null // explicitly set to null }; + if (shaderPass.renderBundle) { + const isTransparent = !!shaderPass.transparent; + const { renderBundle } = shaderPass; + if (renderBundle.meshes.size < 1) { + renderBundle.addMesh(shaderPass, outputPass); + renderBundle.size = 1; + } else { + throwWarning( + `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 ShaderPass to a render bundle. This ShaderPass will not be added: ${shaderPass.options.label}` + ); + shaderPass.renderBundle = null; + } + if (shaderPass.renderBundle) { + shaderPass.renderBundle.renderOrder = shaderPass.renderOrder; + renderBundle.transparent = isTransparent; + renderBundle.useProjection = false; + } + } this.renderPassEntries.screen.push(shaderPassEntry); this.renderPassEntries.screen.sort((a, b) => { const isPostProA = a.element && !a.element.outputTarget; @@ -11687,6 +12330,9 @@ ${this.shaders.compute.head}`; * @param shaderPass - {@link ShaderPass} to remove */ removeShaderPass(shaderPass) { + if (shaderPass.renderBundle) { + shaderPass.renderBundle.empty(); + } this.renderPassEntries.screen = this.renderPassEntries.screen.filter( (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid ); @@ -11717,6 +12363,24 @@ ${this.shaders.compute.head}`; stack: null // explicitly set to null }); + if (pingPongPlane.renderBundle) { + const isTransparent = !!pingPongPlane.transparent; + const { renderBundle } = pingPongPlane; + if (renderBundle.meshes.size < 1) { + renderBundle.addMesh(pingPongPlane, pingPongPlane.outputTarget.renderPass); + renderBundle.size = 1; + } else { + throwWarning( + `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 PingPongPlane to a render bundle. This PingPongPlane will not be added: ${pingPongPlane.options.label}` + ); + pingPongPlane.renderBundle = null; + } + if (pingPongPlane.renderBundle) { + pingPongPlane.renderBundle.renderOrder = pingPongPlane.renderOrder; + renderBundle.transparent = isTransparent; + renderBundle.useProjection = false; + } + } this.renderPassEntries.pingPong.sort((a, b) => a.element.renderOrder - b.element.renderOrder); } /** @@ -11724,6 +12388,9 @@ ${this.shaders.compute.head}`; * @param pingPongPlane - {@link PingPongPlane} to remove */ removePingPongPlane(pingPongPlane) { + if (pingPongPlane.renderBundle) { + pingPongPlane.renderBundle.empty(); + } this.renderPassEntries.pingPong = this.renderPassEntries.pingPong.filter( (entry) => entry.element.uuid !== pingPongPlane.uuid ); @@ -11744,14 +12411,27 @@ ${this.shaders.compute.head}`; return this.renderPassEntries.screen.find((entry) => entry.element?.uuid === object.uuid); } else { const entryType = object.outputTarget ? "renderTarget" : "screen"; - return this.renderPassEntries[entryType].find((entry) => { - return [ - ...entry.stack.unProjected.opaque, - ...entry.stack.unProjected.transparent, - ...entry.stack.projected.opaque, - ...entry.stack.projected.transparent - ].some((mesh) => mesh.uuid === object.uuid); - }); + if (object.renderBundle) { + return this.renderPassEntries[entryType].find((entry) => { + return [ + ...entry.stack.unProjected.opaque, + ...entry.stack.unProjected.transparent, + ...entry.stack.projected.opaque, + ...entry.stack.projected.transparent + ].filter((object2) => object2.type === "RenderBundle").some((bundle) => { + return bundle.meshes.get(object.uuid); + }); + }); + } else { + return this.renderPassEntries[entryType].find((entry) => { + return [ + ...entry.stack.unProjected.opaque, + ...entry.stack.unProjected.transparent, + ...entry.stack.projected.opaque, + ...entry.stack.projected.transparent + ].some((mesh) => mesh.uuid === object.uuid); + }); + } } } /** @@ -11763,6 +12443,9 @@ ${this.shaders.compute.head}`; if (meshA.renderOrder !== meshB.renderOrder) { return meshA.renderOrder - meshB.renderOrder; } + if (this.isStackObjectRenderBundle(meshA) || this.isStackObjectRenderBundle(meshB)) { + return meshA.renderOrder - meshB.renderOrder; + } meshA.geometry ? posA.copy(meshA.geometry.boundingBox.center).applyMat4(meshA.worldMatrix) : meshA.worldMatrix.getTranslation(posA); meshB.geometry ? posB.copy(meshB.geometry.boundingBox.center).applyMat4(meshB.worldMatrix) : meshB.worldMatrix.getTranslation(posB); const radiusA = meshA.geometry ? meshA.geometry.boundingBox.radius * meshA.worldMatrix.getMaxScaleOnAxis() : 0; @@ -11786,11 +12469,17 @@ ${this.shaders.compute.head}`; const swapChainTexture = renderPassEntry.renderPass.updateView(renderPassEntry.renderTexture?.texture); renderPassEntry.onBeforeRenderPass && renderPassEntry.onBeforeRenderPass(commandEncoder, swapChainTexture); const pass = commandEncoder.beginRenderPass(renderPassEntry.renderPass.descriptor); - !this.renderer.production && pass.pushDebugGroup( - renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` - ); + if (!this.renderer.production) { + pass.pushDebugGroup( + renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` + ); + } if (renderPassEntry.element) { - renderPassEntry.element.render(pass); + if (renderPassEntry.element.renderBundle) { + renderPassEntry.element.renderBundle.render(pass); + } else { + renderPassEntry.element.render(pass); + } } else if (renderPassEntry.stack) { for (const mesh of renderPassEntry.stack.unProjected.opaque) { mesh.render(pass); @@ -11808,7 +12497,8 @@ ${this.shaders.compute.head}`; } } } - !this.renderer.production && pass.popDebugGroup(); + if (!this.renderer.production) + pass.popDebugGroup(); pass.end(); renderPassEntry.onAfterRenderPass && renderPassEntry.onAfterRenderPass(commandEncoder, swapChainTexture); this.renderer.pipelineManager.resetCurrentPipeline(); @@ -11837,7 +12527,11 @@ ${this.shaders.compute.head}`; render(commandEncoder) { for (const computePass of this.computePassEntries) { const pass = commandEncoder.beginComputePass(); + if (!this.renderer.production) + pass.pushDebugGroup(`${computePass.options.label}: begin compute pass`); computePass.render(pass); + if (!this.renderer.production) + pass.popDebugGroup(); pass.end(); computePass.copyBufferToResult(commandEncoder); this.renderer.pipelineManager.resetCurrentPipeline(); @@ -11857,30 +12551,30 @@ ${this.shaders.compute.head}`; } } - var __accessCheck$5 = (obj, member, msg) => { + var __accessCheck$8 = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$4 = (obj, member, getter) => { - __accessCheck$5(obj, member, "read from private field"); + var __privateGet$6 = (obj, member, getter) => { + __accessCheck$8(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; - var __privateAdd$5 = (obj, member, value) => { + var __privateAdd$8 = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$4 = (obj, member, value, setter) => { - __accessCheck$5(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$6 = (obj, member, value, setter) => { + __accessCheck$8(obj, member, "write to private field"); + member.set(obj, value); return value; }; var __privateWrapper = (obj, member, setter, getter) => ({ set _(value) { - __privateSet$4(obj, member, value, setter); + __privateSet$6(obj, member, value); }, get _() { - return __privateGet$4(obj, member, getter); + return __privateGet$6(obj, member, getter); } }); var _taskCount; @@ -11890,7 +12584,7 @@ ${this.shaders.compute.head}`; */ constructor() { /** Private number to assign a unique id to each {@link TaskQueueItem | task queue item} */ - __privateAdd$5(this, _taskCount, 0); + __privateAdd$8(this, _taskCount, 0); this.queue = []; } /** @@ -11905,7 +12599,7 @@ ${this.shaders.compute.head}`; callback, order, once, - id: __privateGet$4(this, _taskCount) + id: __privateGet$6(this, _taskCount) }; __privateWrapper(this, _taskCount)._++; this.queue.push(task); @@ -11942,12 +12636,11 @@ ${this.shaders.compute.head}`; */ constructor({ deviceManager, - label = "Main renderer", + label, container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass }) { // callbacks / events @@ -11965,27 +12658,36 @@ ${this.shaders.compute.head}`; }; this.type = "GPURenderer"; this.uuid = generateUUID(); - if (!deviceManager) { - throwError(`GPURenderer (${label}): no device manager provided: ${deviceManager}`); + if (!deviceManager || deviceManager.constructor.name !== "GPUDeviceManager") { + throwError( + label ? `${label} (${this.type}): no device manager or wrong device manager provided: ${deviceManager}` : `${this.type}: no device manager or wrong device manager provided: ${deviceManager}` + ); + } + if (!label) { + label = `${this.constructor.name}${deviceManager.renderers.length}`; } this.deviceManager = deviceManager; this.deviceManager.addRenderer(this); this.shouldRender = true; this.shouldRenderScene = true; + const contextOptions = { + ...{ + alphaMode: "premultiplied", + format: this.deviceManager.gpu?.getPreferredCanvasFormat() + }, + ...context + }; renderPass = { ...{ useDepth: true, sampleCount: 4, clearValue: [0, 0, 0, 0] }, ...renderPass }; - preferredFormat = preferredFormat ?? this.deviceManager.gpu?.getPreferredCanvasFormat(); this.options = { deviceManager, label, container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context: contextOptions, renderPass }; this.pixelRatio = pixelRatio ?? window.devicePixelRatio ?? 1; - this.alphaMode = alphaMode; const isOffscreenCanvas = container instanceof OffscreenCanvas; const isContainerCanvas = isOffscreenCanvas || container instanceof HTMLCanvasElement; this.canvas = isContainerCanvas ? container : document.createElement("canvas"); @@ -12185,8 +12887,7 @@ ${this.shaders.compute.head}`; configureContext() { this.context.configure({ device: this.device, - format: this.options.preferredFormat, - alphaMode: this.alphaMode, + ...this.options.context, // needed so we can copy textures for post processing usage usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST //viewFormats: [] @@ -12207,6 +12908,7 @@ ${this.shaders.compute.head}`; * Force all our scene objects to lose context. */ loseContext() { + this.renderBundles.forEach((bundle) => bundle.loseContext()); this.renderedObjects.forEach((sceneObject) => sceneObject.loseContext()); } /** @@ -12289,7 +12991,7 @@ ${this.shaders.compute.head}`; }) { if (!srcBuffer || !srcBuffer.GPUBuffer) { throwWarning( - `${this.type} (${this.options.label}): cannot copy to buffer because the source buffer has not been provided` + `${this.options.label} (${this.type}): cannot copy to buffer because the source buffer has not been provided` ); return null; } @@ -12306,13 +13008,13 @@ ${this.shaders.compute.head}`; } if (srcBuffer.GPUBuffer.mapState !== "unmapped") { throwWarning( - `${this.type} (${this.options.label}): Cannot copy from ${srcBuffer.GPUBuffer} because it is currently mapped` + `${this.options.label} (${this.type}): Cannot copy from ${srcBuffer.GPUBuffer} because it is currently mapped` ); return; } if (dstBuffer.GPUBuffer.mapState !== "unmapped") { throwWarning( - `${this.type} (${this.options.label}): Cannot copy from ${dstBuffer.GPUBuffer} because it is currently mapped` + `${this.options.label} (${this.type}): Cannot copy from ${dstBuffer.GPUBuffer} because it is currently mapped` ); return; } @@ -12532,6 +13234,7 @@ ${this.shaders.compute.head}`; this.renderTargets = []; this.meshes = []; this.textures = []; + this.renderBundles = []; } /** * Get all this {@link GPURenderer} rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) @@ -12712,6 +13415,7 @@ ${this.shaders.compute.head}`; destroy() { this.deviceManager.renderers = this.deviceManager.renderers.filter((renderer) => renderer.uuid !== this.uuid); this.domElement?.destroy(); + this.renderBundles.forEach((bundle) => bundle.destroy()); this.renderPass?.destroy(); this.postProcessingPass?.destroy(); this.renderTargets.forEach((renderTarget) => renderTarget.destroy()); @@ -12721,22 +13425,22 @@ ${this.shaders.compute.head}`; } } - var __accessCheck$4 = (obj, member, msg) => { + var __accessCheck$7 = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$3 = (obj, member, getter) => { - __accessCheck$4(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); + var __privateGet$5 = (obj, member, getter) => { + __accessCheck$7(obj, member, "read from private field"); + return member.get(obj); }; - var __privateAdd$4 = (obj, member, value) => { + var __privateAdd$7 = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$3 = (obj, member, value, setter) => { - __accessCheck$4(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$5 = (obj, member, value, setter) => { + __accessCheck$7(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _shouldUpdateCameraLightsBindGroup; @@ -12751,8 +13455,7 @@ ${this.shaders.compute.head}`; container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass, camera = {}, lights = {} @@ -12763,27 +13466,30 @@ ${this.shaders.compute.head}`; container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context, renderPass }); /** @ignore */ - __privateAdd$4(this, _shouldUpdateCameraLightsBindGroup, void 0); + __privateAdd$7(this, _shouldUpdateCameraLightsBindGroup, void 0); this.type = "GPUCameraRenderer"; camera = { ...{ fov: 50, near: 0.1, far: 1e3 }, ...camera }; - lights = { ...{ maxAmbientLights: 2, maxDirectionalLights: 5, maxPointLights: 5 }, ...lights }; + if (lights !== false) { + lights = { ...{ maxAmbientLights: 2, maxDirectionalLights: 5, maxPointLights: 5 }, ...lights }; + } this.options = { ...this.options, camera, lights }; this.bindings = {}; - __privateSet$3(this, _shouldUpdateCameraLightsBindGroup, true); + __privateSet$5(this, _shouldUpdateCameraLightsBindGroup, true); this.lights = []; this.setCamera(camera); this.setCameraBinding(); - this.setLightsBinding(); - this.setShadowsBinding(); + if (this.options.lights) { + this.setLightsBinding(); + this.setShadowsBinding(); + } this.setCameraLightsBindGroup(); } /** @@ -12898,19 +13604,24 @@ ${this.shaders.compute.head}`; */ addLight(light) { this.lights.push(light); + this.bindings[light.type].inputs.count.value++; + this.bindings[light.type].inputs.count.shouldUpdate = true; } /** - * Remove a {@link Light} from the {@link lights} array and destroy it. + * Remove a {@link Light} from the {@link lights} array. * @param light - {@link Light} to remove. */ removeLight(light) { this.lights = this.lights.filter((l) => l.uuid !== light.uuid); - light.destroy(); + this.bindings[light.type].inputs.count.value--; + this.bindings[light.type].inputs.count.shouldUpdate = true; } /** * Set the lights {@link BufferBinding} based on the {@link lightsBindingParams}. */ setLightsBinding() { + if (!this.options.lights) + return; this.lightsBindingParams = { ambientLights: { max: this.options.lights.maxAmbientLights, @@ -12978,7 +13689,7 @@ ${this.shaders.compute.head}`; }).reduce((acc, binding) => { acc[binding.key] = { type: binding.type, - value: new Float32Array(this.lightsBindingParams[lightsType].max * binding.size) + value: new Float32Array(Math.max(this.lightsBindingParams[lightsType].max, 1) * binding.size) }; return acc; }, {}); @@ -13004,24 +13715,42 @@ ${this.shaders.compute.head}`; onMaxLightOverflow(lightsType) { if (!this.production) { throwWarning( - `${this.type}: You are overflowing the current max lights count of ${this.lightsBindingParams[lightsType].max} for this type of lights: ${lightsType}. This should be avoided by setting a larger ${"max" + lightsType.charAt(0).toUpperCase() + lightsType.slice(1)} when instancing your ${this.type}.` + `${this.options.label} (${this.type}): You are overflowing the current max lights count of '${this.lightsBindingParams[lightsType].max}' for this type of lights: ${lightsType}. This should be avoided by setting a larger ${"max" + lightsType.charAt(0).toUpperCase() + lightsType.slice(1)} when instancing your ${this.type}.` ); } this.lightsBindingParams[lightsType].max++; const oldLightBinding = this.cameraLightsBindGroup.getBindingByName(lightsType); - this.cameraLightsBindGroup.destroyBufferBinding(oldLightBinding); + if (oldLightBinding) { + this.cameraLightsBindGroup.destroyBufferBinding(oldLightBinding); + } this.setLightsTypeBinding(lightsType); const lightBindingIndex = this.cameraLightsBindGroup.bindings.findIndex((binding) => binding.name === lightsType); - this.cameraLightsBindGroup.bindings[lightBindingIndex] = this.bindings[lightsType]; + if (lightBindingIndex !== -1) { + this.cameraLightsBindGroup.bindings[lightBindingIndex] = this.bindings[lightsType]; + } else { + this.bindings[lightsType].shouldResetBindGroup = true; + this.bindings[lightsType].shouldResetBindGroupLayout = true; + this.cameraLightsBindGroup.addBinding(this.bindings[lightsType]); + this.shouldUpdateCameraLightsBindGroup(); + } if (lightsType === "directionalLights" || lightsType === "pointLights") { const shadowsType = lightsType.replace("Lights", "") + "Shadows"; const oldShadowsBinding = this.cameraLightsBindGroup.getBindingByName(shadowsType); - this.cameraLightsBindGroup.destroyBufferBinding(oldShadowsBinding); + if (oldShadowsBinding) { + this.cameraLightsBindGroup.destroyBufferBinding(oldShadowsBinding); + } this.setShadowsTypeBinding(lightsType); const shadowsBindingIndex = this.cameraLightsBindGroup.bindings.findIndex( (binding) => binding.name === shadowsType ); - this.cameraLightsBindGroup.bindings[shadowsBindingIndex] = this.bindings[shadowsType]; + if (shadowsBindingIndex !== -1) { + this.cameraLightsBindGroup.bindings[shadowsBindingIndex] = this.bindings[shadowsType]; + } else { + this.bindings[shadowsType].shouldResetBindGroup = true; + this.bindings[shadowsType].shouldResetBindGroupLayout = true; + this.cameraLightsBindGroup.addBinding(this.bindings[shadowsType]); + this.shouldUpdateCameraLightsBindGroup(); + } } this.cameraLightsBindGroup.resetEntries(); this.cameraLightsBindGroup.createBindGroup(); @@ -13061,35 +13790,26 @@ ${this.shaders.compute.head}`; const shadowsType = type + "Shadows"; const struct = this.shadowsBindingsStruct[type]; const label = type.charAt(0).toUpperCase() + type.slice(1) + " shadows"; - const binding = new BufferBinding({ - label: label + " element", - name: shadowsType + "Elements", - bindingType: "uniform", - visibility: ["vertex", "fragment"], - struct - }); this.bindings[shadowsType] = new BufferBinding({ label, name: shadowsType, bindingType: "storage", visibility: ["vertex", "fragment", "compute"], // TODO needed in compute? - bindings: Array.from(Array(this.lightsBindingParams[lightsType].max).keys()).map((i) => { - return binding.clone({ - ...binding.options, - // clone struct with new arrays - struct: Object.keys(struct).reduce((acc, bindingKey) => { - const binding2 = struct[bindingKey]; - return { - ...acc, - [bindingKey]: { - type: binding2.type, - value: Array.isArray(binding2.value) || ArrayBuffer.isView(binding2.value) ? new binding2.value.constructor(binding2.value.length) : binding2.value - } - }; - }, {}) - }); - }) + childrenBindings: [ + { + binding: new BufferBinding({ + label: label + " element", + name: shadowsType + "Elements", + bindingType: "uniform", + visibility: ["vertex", "fragment"], + struct + }), + count: Math.max(1, this.lightsBindingParams[lightsType].max), + forceArray: true + // needs to be iterable anyway! + } + ] }); } /* CAMERA, LIGHTS & SHADOWS BIND GROUP */ @@ -13098,7 +13818,7 @@ ${this.shaders.compute.head}`; */ setCameraLightsBindGroup() { this.cameraLightsBindGroup = new BindGroup(this, { - label: "Camera and lights uniform bind group", + label: this.options.label + ": Camera and lights uniform bind group", bindings: Object.keys(this.bindings).map((bindingName) => this.bindings[bindingName]).flat() }); this.cameraLightsBindGroup.consumers.add(this.uuid); @@ -13116,7 +13836,7 @@ ${this.shaders.compute.head}`; * Tell our {@link cameraLightsBindGroup | camera, lights and shadows bind group} to update. */ shouldUpdateCameraLightsBindGroup() { - __privateSet$3(this, _shouldUpdateCameraLightsBindGroup, true); + __privateSet$5(this, _shouldUpdateCameraLightsBindGroup, true); } /** * Tell our {@link GPUCameraRenderer#bindings.camera | camera buffer binding} that we should update its bindings and update the bind group. Called each time the camera matrices change. @@ -13127,6 +13847,15 @@ ${this.shaders.compute.head}`; this.bindings.camera?.shouldUpdateBinding("position"); this.shouldUpdateCameraLightsBindGroup(); } + /** + * Update the {@link cameraLightsBindGroup | camera and lights BindGroup}. + */ + updateCameraLightsBindGroup() { + if (this.cameraLightsBindGroup && __privateGet$5(this, _shouldUpdateCameraLightsBindGroup)) { + this.cameraLightsBindGroup.update(); + __privateSet$5(this, _shouldUpdateCameraLightsBindGroup, false); + } + } /** * Get all objects ({@link core/renderers/GPURenderer.RenderedMesh | rendered meshes} or {@link core/computePasses/ComputePass.ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}, including {@link cameraLightsBindGroup | camera and lights bind group}. * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not. @@ -13183,18 +13912,18 @@ ${this.shaders.compute.head}`; if (!this.ready) return; this.setCameraBindGroup(); - if (this.cameraLightsBindGroup && __privateGet$3(this, _shouldUpdateCameraLightsBindGroup)) { - this.cameraLightsBindGroup.update(); - __privateSet$3(this, _shouldUpdateCameraLightsBindGroup, false); - } + this.updateCameraLightsBindGroup(); super.render(commandEncoder); + if (this.cameraLightsBindGroup) { + this.cameraLightsBindGroup.needsPipelineFlush = false; + } } /** * Destroy our {@link GPUCameraRenderer} */ destroy() { this.cameraLightsBindGroup?.destroy(); - this.lights.forEach((light) => this.removeLight(light)); + this.lights.forEach((light) => light.remove()); super.destroy(); } } @@ -13515,6 +14244,404 @@ ${this.shaders.compute.head}`; } } + var __accessCheck$6 = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$4 = (obj, member, getter) => { + __accessCheck$6(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$6 = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$4 = (obj, member, value, setter) => { + __accessCheck$6(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var __privateMethod$4 = (obj, member, method) => { + __accessCheck$6(obj, member, "access private method"); + return method; + }; + var _useProjection, _ready, _setBinding, setBinding_fn, _patchBindingOffset, patchBindingOffset_fn, _onSizeChanged, onSizeChanged_fn, _setDescriptor, setDescriptor_fn, _encodeRenderCommands, encodeRenderCommands_fn, _cleanUp, cleanUp_fn; + let bundleIndex = 0; + class RenderBundle { + /** + * RenderBundle constructor + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link RenderBundle}. + * @param parameters - {@link RenderBundleParams | parameters} use to create this {@link RenderBundle}. + */ + constructor(renderer, { + label = "", + renderPass = null, + renderOrder = 0, + transparent = null, + visible = true, + size = 0, + useBuffer = false + } = {}) { + /** + * Set the {@link binding} and patches its array and buffer size if needed. + * @private + */ + __privateAdd$6(this, _setBinding); + /** + * Path the {@link binding} array and buffer size with the minimum {@link Renderer#device | device} buffer offset alignment. + * @param size - new {@link binding} size to use. + * @private + */ + __privateAdd$6(this, _patchBindingOffset); + /** + * Called each time the {@link RenderBundle} size has actually changed. + * @param newSize - new {@link RenderBundle} size to set. + * @private + */ + __privateAdd$6(this, _onSizeChanged); + /** + * Set the {@link descriptor} based on the {@link RenderBundleOptions#renderPass | render pass}. + * @private + */ + __privateAdd$6(this, _setDescriptor); + /** + * Create the {@link descriptor}, {@link encoder} and {@link bundle} used by this {@link RenderBundle}. + * @private + */ + __privateAdd$6(this, _encodeRenderCommands); + /** + * Destroy the {@link binding} buffer if needed and remove the {@link RenderBundle} from the {@link Renderer}. + * @private + */ + __privateAdd$6(this, _cleanUp); + /** @ignore */ + // whether this render bundle should be added to the 'projected' or 'unProjected' Scene stacks. + __privateAdd$6(this, _useProjection, void 0); + /** @ignore */ + __privateAdd$6(this, _ready, void 0); + this.type = "RenderBundle"; + renderer = isRenderer(renderer, this.type); + this.renderer = renderer; + this.uuid = generateUUID(); + Object.defineProperty(this, "index", { value: bundleIndex++ }); + this.renderOrder = renderOrder; + this.renderer.renderBundles.push(this); + this.transparent = transparent; + this.visible = visible; + this.options = { + label, + renderPass, + useBuffer, + size + }; + this.meshes = /* @__PURE__ */ new Map(); + this.encoder = null; + this.bundle = null; + __privateSet$4(this, _ready, false); + this.binding = null; + if (this.options.useBuffer) { + __privateSet$4(this, _useProjection, true); + if (this.options.size !== 0) { + __privateMethod$4(this, _setBinding, setBinding_fn).call(this); + } else { + this.options.useBuffer = false; + if (!this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): Cannot use a single transformation buffer if the size parameter has not been set upon creation.` + ); + } + } + } + } + /** + * Get whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not (useful to know in which {@link core/scenes/Scene.Scene | Scene} stack it has been added. + * @readonly + * @returns - Whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not. + */ + get useProjection() { + return __privateGet$4(this, _useProjection); + } + /** + * Set whether our {@link RenderBundle} handles {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} or not. + * @param value - New projection value. + */ + set useProjection(value) { + __privateSet$4(this, _useProjection, value); + } + /** + * Set the new {@link RenderBundle} size. Should be used before adding or removing {@link meshes} to the {@link RenderBundle} if the {@link bundle} has already been created (especially if it's using a {@link binding}). + * @param value - New size to set. + */ + set size(value) { + if (value !== this.options.size) { + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not change its size after it has been created.` + ); + } + this.ready = false; + __privateMethod$4(this, _onSizeChanged, onSizeChanged_fn).call(this, value); + this.options.size = value; + } + } + /** + * Get whether our {@link RenderBundle} is ready. + * @readonly + * @returns - Whether our {@link RenderBundle} is ready. + */ + get ready() { + return __privateGet$4(this, _ready); + } + /** + * Set whether our {@link RenderBundle} is ready and encode it if needed. + * @param value - New ready state. + */ + set ready(value) { + if (value && !this.ready) { + this.size = this.meshes.size; + __privateMethod$4(this, _encodeRenderCommands, encodeRenderCommands_fn).call(this); + } else if (!value && this.ready) { + this.bundle = null; + } + __privateSet$4(this, _ready, value); + } + /** + * Called by the {@link core/scenes/Scene.Scene | Scene} to eventually add a {@link RenderedMesh | mesh} to this {@link RenderBundle}. Can set the {@link RenderBundleOptions#renderPass | render pass} if needed. If the {@link RenderBundleOptions#renderPass | render pass} is already set and the {@link mesh} output {@link RenderPass} does not match, it won't be added. + * @param mesh - {@link RenderedMesh | Mesh} to eventually add. + * @param outputPass - The mesh output {@link RenderPass}. + */ + addMesh(mesh, outputPass) { + if (!this.options.renderPass) { + this.options.renderPass = outputPass; + } else if (outputPass.uuid !== this.options.renderPass.uuid) { + throwWarning( + `${this.options.label} (${this.type}): Cannot add Mesh ${mesh.options.label} to this render bundle because the output render passes do not match.` + ); + mesh.renderBundle = null; + return; + } + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not add meshes to it after it has been created (mesh added: ${mesh.options.label}).` + ); + } + this.ready = false; + this.meshes.set(mesh.uuid, mesh); + } + /** + * Remove any {@link RenderedMesh | rendered mesh} from this {@link RenderBundle}. + * @param mesh - {@link RenderedMesh | Mesh} to remove. + */ + removeSceneObject(mesh) { + if (this.ready && !this.renderer.production) { + throwWarning( + `${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not remove meshes from it after it has been created (mesh removed: ${mesh.options.label}).` + ); + } + this.ready = false; + this.meshes.delete(mesh.uuid); + mesh.setRenderBundle(null, false); + } + /** + * Remove a {@link SceneStackedMesh | scene stacked mesh} from this {@link RenderBundle}. + * @param mesh - {@link SceneStackedMesh | Scene stacked mesh} to remove. + * @param keepMesh - Whether to preserve the {@link mesh} in order to render it normally again. Default to `true`. + */ + removeMesh(mesh, keepMesh = true) { + this.removeSceneObject(mesh); + if (keepMesh && mesh.type !== "ShaderPass" && mesh.type !== "PingPongPlane") { + this.renderer.scene.addMesh(mesh); + } + if (this.meshes.size === 0) { + this.renderer.scene.removeRenderBundle(this); + } + } + /** + * Update the {@link binding} buffer if needed. + */ + updateBinding() { + if (this.binding && this.binding.shouldUpdate && this.binding.buffer.GPUBuffer) { + this.renderer.queueWriteBuffer(this.binding.buffer.GPUBuffer, 0, this.binding.arrayBuffer); + this.binding.shouldUpdate = false; + } + } + /** + * Render the {@link RenderBundle}. + * + * If it is ready, execute each {@link RenderedMesh#onBeforeRenderPass | mesh onBeforeRenderPass method}, {@link updateBinding | update the binding} if needed, execute the {@link bundle} and finally execute each {@link RenderedMesh#onAfterRenderPass | mesh onAfterRenderPass method}. + * + * If not, just render its {@link meshes} as usual and check whether they are all ready and if we can therefore encode our {@link RenderBundle}. + * @param pass - {@link GPURenderPassEncoder} to use. + */ + render(pass) { + if (this.ready && this.bundle && this.visible) { + this.meshes.forEach((mesh) => { + mesh.onBeforeRenderPass(); + }); + this.updateBinding(); + this.renderer.pipelineManager.resetCurrentPipeline(); + if (!this.renderer.production) { + pass.pushDebugGroup(`${this.options.label}: execute bundle`); + } + pass.executeBundles([this.bundle]); + if (!this.renderer.production) { + pass.popDebugGroup(); + } + this.renderer.pipelineManager.resetCurrentPipeline(); + this.meshes.forEach((mesh) => { + mesh.onAfterRenderPass(); + }); + } + if (!this.ready) { + let isReady = true; + for (const [_key, mesh] of this.meshes) { + mesh.render(pass); + if (!mesh.ready) { + isReady = false; + } + if ("sourcesReady" in mesh && !mesh.sourcesReady) { + isReady = false; + } + } + this.ready = isReady; + } + } + /** + * Called when the {@link Renderer#device | WebGPU device} has been lost. + * Just set the {@link ready} flag to `false` to eventually invalidate the {@link bundle}. + */ + loseContext() { + this.ready = false; + } + /** + * Empty the {@link RenderBundle}. Can eventually re-add the {@link SceneStackedMesh | scene stacked meshes} to the {@link core/scenes/Scene.Scene | Scene} in order to render them normally again. + * @param keepMeshes - Whether to preserve the {@link meshes} in order to render them normally again. Default to `true`. + */ + empty(keepMeshes = true) { + this.ready = false; + this.meshes.forEach((mesh) => { + this.removeMesh(mesh, keepMeshes); + }); + this.size = 0; + } + /** + * Remove the {@link RenderBundle}, i.e. destroy it while preserving the {@link SceneStackedMesh | scene stacked meshes} by re-adding them to the {@link core/scenes/Scene.Scene | Scene}. + */ + remove() { + this.empty(true); + __privateMethod$4(this, _cleanUp, cleanUp_fn).call(this); + } + /** + * Remove the {@link RenderBundle} from our {@link core/scenes/Scene.Scene | Scene}, {@link RenderedMesh#remove | remove the meshes}, eventually destroy the {@link binding} and remove the {@link RenderBundle} from the {@link Renderer}. + */ + destroy() { + this.ready = false; + this.meshes.forEach((mesh) => { + mesh.remove(); + }); + this.size = 0; + __privateMethod$4(this, _cleanUp, cleanUp_fn).call(this); + } + } + _useProjection = new WeakMap(); + _ready = new WeakMap(); + _setBinding = new WeakSet(); + setBinding_fn = function() { + this.binding = new BufferBinding({ + label: this.options.label + " matrices", + name: "matrices", + struct: { + model: { + type: "array", + value: new Float32Array(16 * this.options.size) + }, + modelView: { + type: "array", + value: new Float32Array(16 * this.options.size) + }, + normal: { + type: "array", + value: new Float32Array(12 * this.options.size) + } + } + }); + __privateMethod$4(this, _patchBindingOffset, patchBindingOffset_fn).call(this, this.options.size); + }; + _patchBindingOffset = new WeakSet(); + patchBindingOffset_fn = function(size) { + const minOffset = this.renderer.device.limits.minUniformBufferOffsetAlignment; + if (this.binding.arrayBufferSize < size * minOffset) { + this.binding.arrayBufferSize = size * minOffset; + this.binding.arrayBuffer = new ArrayBuffer(this.binding.arrayBufferSize); + this.binding.buffer.size = this.binding.arrayBuffer.byteLength; + } + }; + _onSizeChanged = new WeakSet(); + onSizeChanged_fn = function(newSize) { + if (newSize > this.options.size && this.binding) { + __privateMethod$4(this, _patchBindingOffset, patchBindingOffset_fn).call(this, newSize); + if (this.binding.buffer.GPUBuffer) { + this.binding.buffer.GPUBuffer.destroy(); + this.binding.buffer.createBuffer(this.renderer, { + label: this.binding.options.label, + usage: [ + ...["copySrc", "copyDst", this.binding.bindingType], + ...this.binding.options.usage + ] + }); + let offset = 0; + this.meshes.forEach((mesh) => { + mesh.patchRenderBundleBinding(offset); + offset++; + }); + this.binding.shouldUpdate = true; + } + } + }; + _setDescriptor = new WeakSet(); + setDescriptor_fn = function() { + this.descriptor = { + ...this.options.renderPass.options.colorAttachments && { + colorFormats: this.options.renderPass.options.colorAttachments.map( + (colorAttachment) => colorAttachment.targetFormat + ) + }, + ...this.options.renderPass.options.useDepth && { + depthStencilFormat: this.options.renderPass.options.depthFormat + }, + sampleCount: this.options.renderPass.options.sampleCount + }; + }; + _encodeRenderCommands = new WeakSet(); + encodeRenderCommands_fn = function() { + __privateMethod$4(this, _setDescriptor, setDescriptor_fn).call(this); + this.renderer.pipelineManager.resetCurrentPipeline(); + this.encoder = this.renderer.device.createRenderBundleEncoder({ + ...this.descriptor, + label: this.options.label + " (encoder)" + }); + if (!this.renderer.production) { + this.encoder.pushDebugGroup(`${this.options.label}: create encoder`); + } + this.meshes.forEach((mesh) => { + mesh.material.render(this.encoder); + mesh.geometry.render(this.encoder); + }); + if (!this.renderer.production) { + this.encoder.popDebugGroup(); + } + this.bundle = this.encoder.finish({ label: this.options.label + " (bundle)" }); + this.renderer.pipelineManager.resetCurrentPipeline(); + }; + _cleanUp = new WeakSet(); + cleanUp_fn = function() { + if (this.binding) { + this.binding.buffer.destroy(); + } + this.renderer.renderBundles = this.renderer.renderBundles.filter((bundle) => bundle.uuid !== this.uuid); + }; + var default_pass_fsWGSl = ( /* wgsl */ ` @@ -14215,12 +15342,10 @@ fn getIBLIndirect( metallic: f32, diffuseColor: vec3f, f0: vec3f, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ptr_reflectedLight: ptr, // ptr_iblIndirect: ptr ) { @@ -14236,7 +15361,7 @@ fn getIBLIndirect( let brdf: vec3f = textureSample( lutTexture, - lutSampler, + clampSampler, brdfSamplePoint ).rgb; @@ -14249,16 +15374,16 @@ fn getIBLIndirect( let specularLight: vec4f = textureSampleLevel( envSpecularTexture, - envSpecularSampler, - reflection, + clampSampler, + reflection * ibl.envRotation, lod ); // IBL diffuse (irradiance) let diffuseLight: vec4f = textureSample( envDiffuseTexture, - envDiffuseSampler, - normal + clampSampler, + normal * ibl.envRotation ); // product of specularFactor and specularTexture.a @@ -14295,12 +15420,10 @@ fn getIBL( f0: vec3f, metallic: f32, roughness: f32, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ${useOcclusion ? "occlusion: f32," : ""} ) -> vec3f { var directLight: DirectLight; @@ -14335,12 +15458,10 @@ fn getIBL( metallic, diffuseColor, f0, + clampSampler, lutTexture, - lutSampler, envSpecularTexture, - envSpecularSampler, envDiffuseTexture, - envDiffuseSampler, &reflectedLight, // &iblIndirect ); @@ -14368,22 +15489,22 @@ fn getIBL( ` ); - var __accessCheck$3 = (obj, member, msg) => { + var __accessCheck$5 = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet$2 = (obj, member, getter) => { - __accessCheck$3(obj, member, "read from private field"); + var __privateGet$3 = (obj, member, getter) => { + __accessCheck$5(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; - var __privateAdd$3 = (obj, member, value) => { + var __privateAdd$5 = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet$2 = (obj, member, value, setter) => { - __accessCheck$3(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + var __privateSet$3 = (obj, member, value, setter) => { + __accessCheck$5(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _DOMObjectWorldPosition, _DOMObjectWorldScale, _DOMObjectDepthScaleRatio; @@ -14397,11 +15518,11 @@ fn getIBL( constructor(renderer, element, parameters = {}) { super(renderer); /** Private {@link Vec3 | vector} used to keep track of the actual {@link DOMObject3DTransforms#position.world | world position} accounting the {@link DOMObject3DTransforms#position.document | additional document translation} converted into world space */ - __privateAdd$3(this, _DOMObjectWorldPosition, new Vec3()); + __privateAdd$5(this, _DOMObjectWorldPosition, new Vec3()); /** Private {@link Vec3 | vector} used to keep track of the actual {@link DOMObject3D} world scale accounting the {@link DOMObject3D#size.world | DOMObject3D world size} */ - __privateAdd$3(this, _DOMObjectWorldScale, new Vec3(1)); + __privateAdd$5(this, _DOMObjectWorldScale, new Vec3(1)); /** Private number representing the scale ratio of the {@link DOMObject3D} along Z axis to apply. Since it can be difficult to guess the most accurate scale along the Z axis of an object mapped to 2D coordinates, this helps with adjusting the scale along the Z axis. */ - __privateAdd$3(this, _DOMObjectDepthScaleRatio, 1); + __privateAdd$5(this, _DOMObjectDepthScaleRatio, 1); /** Helper {@link Box3 | bounding box} used to map the 3D object onto the 2D DOM element. */ this.boundingBox = new Box3(new Vec3(-1), new Vec3(1)); /** function assigned to the {@link onAfterDOMElementResize} callback */ @@ -14518,7 +15639,7 @@ fn getIBL( * @readonly */ get DOMObjectWorldScale() { - return __privateGet$2(this, _DOMObjectWorldScale).clone(); + return __privateGet$3(this, _DOMObjectWorldScale).clone(); } /** * Get the {@link DOMObject3D} scale in world space (accounting for {@link scale}) @@ -14532,7 +15653,7 @@ fn getIBL( * @readonly */ get worldPosition() { - return __privateGet$2(this, _DOMObjectWorldPosition).clone(); + return __privateGet$3(this, _DOMObjectWorldPosition).clone(); } /** * Get the {@link DOMObject3D} transform origin relative to the {@link DOMObject3D} @@ -14594,7 +15715,7 @@ fn getIBL( if (!this.documentPosition.equals(worldPosition)) { worldPosition = this.documentToWorldSpace(this.documentPosition); } - __privateGet$2(this, _DOMObjectWorldPosition).set( + __privateGet$3(this, _DOMObjectWorldPosition).set( this.position.x + this.size.scaledWorld.position.x + worldPosition.x, this.position.y + this.size.scaledWorld.position.y + worldPosition.y, this.position.z + this.size.scaledWorld.position.z + this.documentPosition.z / this.camera.CSSPerspective @@ -14615,7 +15736,7 @@ fn getIBL( */ updateModelMatrix() { this.modelMatrix.composeFromOrigin( - __privateGet$2(this, _DOMObjectWorldPosition), + __privateGet$3(this, _DOMObjectWorldPosition), this.quaternion, this.scale, this.worldTransformOrigin @@ -14683,10 +15804,10 @@ fn getIBL( * Set the {@link worldScale} accounting for scaled world size and {@link DOMObjectDepthScaleRatio} */ setWorldScale() { - __privateGet$2(this, _DOMObjectWorldScale).set( + __privateGet$3(this, _DOMObjectWorldScale).set( this.size.scaledWorld.size.x, this.size.scaledWorld.size.y, - this.size.scaledWorld.size.z * __privateGet$2(this, _DOMObjectDepthScaleRatio) + this.size.scaledWorld.size.z * __privateGet$3(this, _DOMObjectDepthScaleRatio) ); this.shouldUpdateMatrixStack(); } @@ -14695,7 +15816,7 @@ fn getIBL( * @param value - depth scale ratio value to use */ set DOMObjectDepthScaleRatio(value) { - __privateSet$2(this, _DOMObjectDepthScaleRatio, value); + __privateSet$3(this, _DOMObjectDepthScaleRatio, value); this.setWorldScale(); } /** @@ -14704,10 +15825,10 @@ fn getIBL( setWorldTransformOrigin() { this.transforms.origin.world = new Vec3( (this.transformOrigin.x * 2 - 1) * // between -1 and 1 - __privateGet$2(this, _DOMObjectWorldScale).x, + __privateGet$3(this, _DOMObjectWorldScale).x, -(this.transformOrigin.y * 2 - 1) * // between -1 and 1 - __privateGet$2(this, _DOMObjectWorldScale).y, - this.transformOrigin.z * __privateGet$2(this, _DOMObjectWorldScale).z + __privateGet$3(this, _DOMObjectWorldScale).y, + this.transformOrigin.z * __privateGet$3(this, _DOMObjectWorldScale).z ); this.shouldUpdateMatrixStack(); } @@ -14944,52 +16065,15 @@ fn getIBL( if (!vertexBuffers.length) { geometry = cacheManager.getPlaneGeometryByID(geometryID); } - if (!geometry) { - geometry = new PlaneGeometry({ widthSegments, heightSegments, instancesCount, vertexBuffers }); - cacheManager.addPlaneGeometry(geometry); - } else { - geometry.instancesCount = instancesCount; - } - } - super(renderer, element, { geometry, ...materialParams }); - this.type = "Plane"; - } - /** - * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link Plane} - * It ranges from -1 to 1 on both axis - * @param mouseCoords - pointer {@link Vec2 | vector} coordinates - * @returns - raycasted {@link Vec2 | vector} coordinates relative to the {@link Plane} - */ - mouseToPlaneCoords(mouseCoords = new Vec2()) { - const worldMouse = { - x: 2 * (mouseCoords.x / this.renderer.boundingRect.width) - 1, - y: 2 * (1 - mouseCoords.y / this.renderer.boundingRect.height) - 1 - }; - const rayOrigin = this.camera.position.clone(); - const rayDirection = new Vec3(worldMouse.x, worldMouse.y, -0.5); - rayDirection.unproject(this.camera); - rayDirection.sub(rayOrigin).normalize(); - const planeNormals = new Vec3(0, 0, 1); - planeNormals.applyQuat(this.quaternion).normalize(); - const result = new Vec3(0, 0, 0); - const denominator = planeNormals.dot(rayDirection); - if (Math.abs(denominator) >= 1e-4) { - const inverseViewMatrix = this.worldMatrix.getInverse().premultiply(this.camera.viewMatrix); - const planeOrigin = this.worldTransformOrigin.clone().add(this.worldPosition); - const rotatedOrigin = new Vec3( - this.worldPosition.x - planeOrigin.x, - this.worldPosition.y - planeOrigin.y, - this.worldPosition.z - planeOrigin.z - ); - rotatedOrigin.applyQuat(this.quaternion); - planeOrigin.add(rotatedOrigin); - const distance = planeNormals.dot(planeOrigin.clone().sub(rayOrigin)) / denominator; - result.copy(rayOrigin.add(rayDirection.multiplyScalar(distance))); - result.applyMat4(inverseViewMatrix); - } else { - result.set(Infinity, Infinity, Infinity); + if (!geometry) { + geometry = new PlaneGeometry({ widthSegments, heightSegments, instancesCount, vertexBuffers }); + cacheManager.addPlaneGeometry(geometry); + } else { + geometry.instancesCount = instancesCount; + } } - return new Vec2(result.x, result.y); + super(renderer, element, { geometry, ...materialParams }); + this.type = "Plane"; } } @@ -15004,8 +16088,7 @@ fn getIBL( container, pixelRatio = 1, autoResize = true, - preferredFormat, - alphaMode = "premultiplied", + context = {}, renderPass, camera, lights @@ -15016,8 +16099,7 @@ fn getIBL( container, pixelRatio, autoResize, - preferredFormat, - alphaMode, + context, renderPass, camera, lights @@ -15120,8 +16202,7 @@ fn getIBL( container, label, pixelRatio = window.devicePixelRatio ?? 1, - preferredFormat, - alphaMode = "premultiplied", + context = {}, production = false, adapterOptions = {}, renderPass, @@ -15153,8 +16234,7 @@ fn getIBL( lights, production, adapterOptions, - preferredFormat, - alphaMode, + context, renderPass, autoRender, autoResize, @@ -15170,757 +16250,1790 @@ fn getIBL( } } /** - * Set the {@link container} - * @param container - {@link HTMLElement} or string representing an {@link HTMLElement} selector to use + * Set the {@link container} + * @param container - {@link HTMLElement} or string representing an {@link HTMLElement} selector to use + */ + setContainer(container) { + if (!container) { + const container2 = document.createElement("div"); + container2.setAttribute("id", "curtains-gpu-canvas"); + document.body.appendChild(container2); + this.options.container = container2; + } else { + if (typeof container === "string") { + container = document.querySelector(container); + if (!container) { + const container2 = document.createElement("div"); + container2.setAttribute("id", "curtains-gpu-canvas"); + document.body.appendChild(container2); + this.options.container = container2; + } else { + this.options.container = container; + } + } else if (container instanceof Element) { + this.options.container = container; + } + } + this.container = this.options.container; + this.setMainRenderer(); + } + /** + * Set the default {@link GPUCurtainsRenderer | renderer} + */ + setMainRenderer() { + this.createCurtainsRenderer({ + deviceManager: this.deviceManager, + // TODO ...this.options? + label: this.options.label || "GPUCurtains main GPUCurtainsRenderer", + container: this.options.container, + pixelRatio: this.options.pixelRatio, + autoResize: this.options.autoResize, + context: this.options.context, + renderPass: this.options.renderPass, + camera: this.options.camera, + lights: this.options.lights + }); + } + /** + * Patch the options with default values before creating a {@link Renderer} + * @param parameters - parameters to patch + */ + patchRendererOptions(parameters) { + if (parameters.pixelRatio === void 0) + parameters.pixelRatio = this.options.pixelRatio; + if (parameters.autoResize === void 0) + parameters.autoResize = this.options.autoResize; + return parameters; + } + /** + * Create a new {@link GPURenderer} instance + * @param parameters - {@link GPURendererParams | parameters} to use + */ + createRenderer(parameters) { + parameters = this.patchRendererOptions(parameters); + return new GPURenderer({ ...parameters, deviceManager: this.deviceManager }); + } + /** + * Create a new {@link GPUCameraRenderer} instance + * @param parameters - {@link GPUCameraRendererParams | parameters} to use + */ + createCameraRenderer(parameters) { + parameters = this.patchRendererOptions(parameters); + return new GPUCameraRenderer({ ...parameters, deviceManager: this.deviceManager }); + } + /** + * Create a new {@link GPUCurtainsRenderer} instance + * @param parameters - {@link GPUCameraRendererParams | parameters} to use + */ + createCurtainsRenderer(parameters) { + parameters = this.patchRendererOptions(parameters); + return new GPUCurtainsRenderer({ ...parameters, deviceManager: this.deviceManager }); + } + /** + * Set our {@link GPUDeviceManager} + */ + setDeviceManager() { + this.deviceManager = new GPUDeviceManager({ + label: "GPUCurtains default device", + production: this.options.production, + adapterOptions: this.options.adapterOptions, + onError: () => setTimeout(() => { + this._onErrorCallback && this._onErrorCallback(); + }, 0), + onDeviceLost: (info) => this._onContextLostCallback && this._onContextLostCallback(info) + }); + } + /** + * Get all created {@link Renderer} + * @readonly + */ + get renderers() { + return this.deviceManager.renderers; + } + /** + * Get the first created {@link Renderer} if any + * @readonly + */ + get renderer() { + return this.renderers[0]; + } + /** + * Set the {@link GPUDeviceManager} {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} if possible, then set all created {@link Renderer} contexts. + * @async + * @param parameters - {@link GPUAdapter} and/or {@link GPUDevice} to use if set. + */ + async setDevice({ adapter = null, device = null } = {}) { + await this.deviceManager.init({ adapter, device }); + } + /** + * Restore the {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} + * @async + */ + async restoreContext() { + await this.deviceManager.restoreDevice(); + } + /* RENDERER TRACKED OBJECTS */ + /** + * Get all the created {@link PingPongPlane} + * @readonly + */ + get pingPongPlanes() { + return this.renderers?.map((renderer) => renderer.pingPongPlanes).flat(); + } + /** + * Get all the created {@link ShaderPass} + * @readonly + */ + get shaderPasses() { + return this.renderers?.map((renderer) => renderer.shaderPasses).flat(); + } + /** + * Get all the created {@link SceneStackedMesh | meshes} + * @readonly + */ + get meshes() { + return this.renderers?.map((renderer) => renderer.meshes).flat(); + } + /** + * Get all the created {@link DOMMesh | DOM Meshes} (including {@link Plane | planes}) + * @readonly + */ + get domMeshes() { + return this.renderers?.filter((renderer) => renderer instanceof GPUCurtainsRenderer).map((renderer) => renderer.domMeshes).flat(); + } + /** + * Get all created {@link curtains/objects3D/DOMObject3D.DOMObject3D | DOMObject3D} which position should be updated on scroll. + * @readonly + */ + get domObjects() { + return this.renderers?.filter((renderer) => renderer instanceof GPUCurtainsRenderer).map((renderer) => renderer.domObjects).flat(); + } + /** + * Get all the created {@link Plane | planes} + * @readonly + */ + get planes() { + return this.domMeshes.filter((domMesh) => domMesh instanceof Plane); + } + /** + * Get all the created {@link ComputePass | compute passes} + * @readonly + */ + get computePasses() { + return this.renderers?.map((renderer) => renderer.computePasses).flat(); + } + /** + * Get our {@link GPUCurtainsRenderer#setPerspective | default GPUCurtainsRenderer bounding rectangle} + */ + get boundingRect() { + return this.renderer?.boundingRect; + } + /* SCROLL */ + /** + * Set the {@link scrollManager} + */ + initScroll() { + this.scrollManager = new ScrollManager({ + // init values + scroll: { + x: window.pageXOffset, + y: window.pageYOffset + }, + delta: { + x: 0, + y: 0 + }, + shouldWatch: this.options.watchScroll, + onScroll: (delta) => this.updateScroll(delta) + }); + } + /** + * Update all {@link DOMMesh#updateScrollPosition | DOMMesh scroll positions} + * @param delta - last {@link ScrollManager#delta | scroll delta values} + */ + updateScroll(delta = { x: 0, y: 0 }) { + this.domObjects.forEach((domObject) => { + if (domObject.domElement && domObject.watchScroll) { + domObject.updateScrollPosition(delta); + } + }); + this._onScrollCallback && this._onScrollCallback(); + } + /** + * Update our {@link ScrollManager#scroll | scrollManager scroll values}. Called each time the scroll has changed if {@link GPUCurtains#options.watchScroll | watchScroll option} is set to true. Could be called externally as well. + * @param scroll - new {@link DOMPosition | scroll values} + */ + updateScrollValues(scroll = { x: 0, y: 0 }) { + this.scrollManager.updateScrollValues(scroll); + } + /** + * Get our {@link ScrollManager#delta | scrollManager delta values} + * @readonly + */ + get scrollDelta() { + return this.scrollManager.delta; + } + /** + * Get our {@link ScrollManager#scroll | scrollManager scroll values} + * @readonly + */ + get scrollValues() { + return this.scrollManager.scroll; + } + /* EVENT LISTENERS */ + /** + * Set the resize and scroll event listeners + */ + initEvents() { + resizeManager.useObserver(this.options.autoResize); + this.initScroll(); + } + /* EVENTS */ + /** + * Called at each render frame + * @param callback - callback to run at each render + * @returns - our {@link GPUCurtains} + */ + onRender(callback) { + if (callback) { + this._onRenderCallback = callback; + } + return this; + } + /** + * Called each time the {@link ScrollManager#scroll | scrollManager scroll values} changed + * @param callback - callback to run each time the {@link ScrollManager#scroll | scrollManager scroll values} changed + * @returns - our {@link GPUCurtains} */ - setContainer(container) { - if (!container) { - const container2 = document.createElement("div"); - container2.setAttribute("id", "curtains-gpu-canvas"); - document.body.appendChild(container2); - this.options.container = container2; - } else { - if (typeof container === "string") { - container = document.querySelector(container); - if (!container) { - const container2 = document.createElement("div"); - container2.setAttribute("id", "curtains-gpu-canvas"); - document.body.appendChild(container2); - this.options.container = container2; - } else { - this.options.container = container; - } - } else if (container instanceof Element) { - this.options.container = container; - } + onScroll(callback) { + if (callback) { + this._onScrollCallback = callback; } - this.container = this.options.container; - this.setMainRenderer(); + return this; } /** - * Set the default {@link GPUCurtainsRenderer | renderer} + * Called if there's been an error while trying to create the {@link GPUDeviceManager#device | device} + * @param callback - callback to run if there's been an error while trying to create the {@link GPUDeviceManager#device | device} + * @returns - our {@link GPUCurtains} */ - setMainRenderer() { - this.createCurtainsRenderer({ - deviceManager: this.deviceManager, - // TODO ...this.options? - label: this.options.label, - container: this.options.container, - pixelRatio: this.options.pixelRatio, - autoResize: this.options.autoResize, - preferredFormat: this.options.preferredFormat, - alphaMode: this.options.alphaMode, - renderPass: this.options.renderPass, - camera: this.options.camera, - lights: this.options.lights - }); + onError(callback) { + if (callback) { + this._onErrorCallback = callback; + } + return this; } /** - * Patch the options with default values before creating a {@link Renderer} - * @param parameters - parameters to patch + * Called whenever the {@link GPUDeviceManager#device | device} is lost + * @param callback - callback to run whenever the {@link GPUDeviceManager#device | device} is lost + * @returns - our {@link GPUCurtains} */ - patchRendererOptions(parameters) { - if (parameters.pixelRatio === void 0) - parameters.pixelRatio = this.options.pixelRatio; - if (parameters.autoResize === void 0) - parameters.autoResize = this.options.autoResize; - return parameters; + onContextLost(callback) { + if (callback) { + this._onContextLostCallback = callback; + } + return this; } /** - * Create a new {@link GPURenderer} instance - * @param parameters - {@link GPURendererParams | parameters} to use + * Create a requestAnimationFrame loop and run it */ - createRenderer(parameters) { - parameters = this.patchRendererOptions(parameters); - return new GPURenderer({ ...parameters, deviceManager: this.deviceManager }); + animate() { + this.render(); + this.animationFrameID = window.requestAnimationFrame(this.animate.bind(this)); } /** - * Create a new {@link GPUCameraRenderer} instance - * @param parameters - {@link GPUCameraRendererParams | parameters} to use + * Render our {@link GPUDeviceManager} */ - createCameraRenderer(parameters) { - parameters = this.patchRendererOptions(parameters); - return new GPUCameraRenderer({ ...parameters, deviceManager: this.deviceManager }); + render() { + this._onRenderCallback && this._onRenderCallback(); + this.deviceManager.render(); } /** - * Create a new {@link GPUCurtainsRenderer} instance - * @param parameters - {@link GPUCameraRendererParams | parameters} to use + * Destroy our {@link GPUCurtains} and {@link GPUDeviceManager} */ - createCurtainsRenderer(parameters) { - parameters = this.patchRendererOptions(parameters); - return new GPUCurtainsRenderer({ ...parameters, deviceManager: this.deviceManager }); + destroy() { + if (this.animationFrameID) { + window.cancelAnimationFrame(this.animationFrameID); + } + this.deviceManager.destroy(); + this.scrollManager?.destroy(); + resizeManager.destroy(); } + } + + var __accessCheck$4 = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet$2 = (obj, member, getter) => { + __accessCheck$4(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd$4 = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet$2 = (obj, member, value, setter) => { + __accessCheck$4(obj, member, "write to private field"); + member.set(obj, value); + return value; + }; + var __privateMethod$3 = (obj, member, method) => { + __accessCheck$4(obj, member, "access private method"); + return method; + }; + var _element, _offset, _isOrbiting, _spherical, _rotateStart, _isPaning, _panStart, _panDelta, _setBaseParams, setBaseParams_fn, _addEvents, addEvents_fn, _removeEvents, removeEvents_fn, _onMouseDown, onMouseDown_fn, _onTouchStart, onTouchStart_fn, _onMouseMove, onMouseMove_fn, _onTouchMove, onTouchMove_fn, _onMouseUp, onMouseUp_fn, _onTouchEnd, onTouchEnd_fn, _onMouseWheel, onMouseWheel_fn, _onContextMenu, onContextMenu_fn, _update, update_fn, _rotate, rotate_fn, _pan, pan_fn, _zoom, zoom_fn; + const tempVec2a = new Vec2(); + const tempVec2b = new Vec2(); + const tempVec3 = new Vec3(); + class OrbitControls { /** - * Set our {@link GPUDeviceManager} - */ - setDeviceManager() { - this.deviceManager = new GPUDeviceManager({ - label: "GPUCurtains default device", - production: this.options.production, - adapterOptions: this.options.adapterOptions, - onError: () => setTimeout(() => { - this._onErrorCallback && this._onErrorCallback(); - }, 0), - onDeviceLost: (info) => this._onContextLostCallback && this._onContextLostCallback(info) + * OrbitControls constructor + = * @param parameters - parameters to use. + */ + constructor({ + camera, + element = null, + target = new Vec3(), + // zoom + enableZoom = true, + minZoom = 0, + maxZoom = Infinity, + zoomSpeed = 1, + // rotate + enableRotate = true, + minPolarAngle = 0, + maxPolarAngle = Math.PI, + minAzimuthAngle = -Infinity, + maxAzimuthAngle = Infinity, + rotateSpeed = 1, + // pan + enablePan = true, + panSpeed = 1 + }) { + /** + * Set / reset base params + * @ignore + */ + __privateAdd$4(this, _setBaseParams); + /** + * Add the event listeners. + * @private + */ + __privateAdd$4(this, _addEvents); + /** + * Remove the event listeners. + * @private + */ + __privateAdd$4(this, _removeEvents); + /** + * Callback executed on mouse down event. + * @param e - {@link MouseEvent}. + * @private + */ + __privateAdd$4(this, _onMouseDown); + /** + * Callback executed on touch start event. + * @param e - {@link TouchEvent}. + * @private + */ + __privateAdd$4(this, _onTouchStart); + /** + * Callback executed on mouse move event. + * @param e - {@link MouseEvent}. + */ + __privateAdd$4(this, _onMouseMove); + /** + * Callback executed on touch move event. + * @param e - {@link TouchEvent}. + * @private + */ + __privateAdd$4(this, _onTouchMove); + /** + * Callback executed on mouse up event. + * @param e - {@link MouseEvent}. + * @private + */ + __privateAdd$4(this, _onMouseUp); + /** + * Callback executed on touch end event. + * @param e - {@link MouseEvent}. + * @private + */ + __privateAdd$4(this, _onTouchEnd); + /** + * Callback executed on wheel event. + * @param e - {@link WheelEvent}. + * @private + */ + __privateAdd$4(this, _onMouseWheel); + /** + * Prevent context menu apparition on right click + * @param e - {@link MouseEvent}. + * @private + */ + __privateAdd$4(this, _onContextMenu); + /** + * Update the {@link camera} position based on the {@link target} and internal values. + * @private + */ + __privateAdd$4(this, _update); + /** + * Update the {@link camera} position based on input coordinates so it rotates around the {@link target}. + * @param x - input coordinate along the X axis. + * @param y - input coordinate along the Y axis. + * @private + */ + __privateAdd$4(this, _rotate); + /** + * Pan the {@link camera} position based on input coordinates by updating {@link target}. + * @param x - input coordinate along the X axis. + * @param y - input coordinate along the Y axis. + * @private + */ + __privateAdd$4(this, _pan); + /** + * Move the {@link camera} forward or backward. + * @param value - new value to use for zoom. + * @private + */ + __privateAdd$4(this, _zoom); + /** + * {@link HTMLElement} (or {@link Window} element) to use for event listeners. + * @private + */ + __privateAdd$4(this, _element, null); + /** @ignore */ + __privateAdd$4(this, _offset, new Vec3()); + /** @ignore */ + __privateAdd$4(this, _isOrbiting, false); + /** @ignore */ + __privateAdd$4(this, _spherical, { radius: 1, phi: 0, theta: 0 }); + /** @ignore */ + __privateAdd$4(this, _rotateStart, new Vec2()); + /** @ignore */ + __privateAdd$4(this, _isPaning, false); + /** @ignore */ + __privateAdd$4(this, _panStart, new Vec2()); + /** @ignore */ + __privateAdd$4(this, _panDelta, new Vec3()); + if (!camera) { + throwWarning("OrbitControls: cannot initialize without a camera."); + return; + } + __privateMethod$3(this, _setBaseParams, setBaseParams_fn).call(this, { + target, + enableZoom, + minZoom, + maxZoom, + zoomSpeed, + enableRotate, + minPolarAngle, + maxPolarAngle, + minAzimuthAngle, + maxAzimuthAngle, + rotateSpeed, + enablePan, + panSpeed }); + this.element = element ?? (typeof window !== "undefined" ? window : null); + this.useCamera(camera); } /** - * Get all created {@link Renderer} - * @readonly + * Allow to set or reset this {@link OrbitControls#camera | OrbitControls camera}. + * @param camera - New {@link camera} to use. */ - get renderers() { - return this.deviceManager.renderers; + useCamera(camera) { + this.camera = camera; + this.camera.position.onChange(() => { + this.camera.lookAt(this.target); + }); + __privateGet$2(this, _offset).copy(this.camera.position).sub(this.target); + __privateGet$2(this, _spherical).radius = __privateGet$2(this, _offset).length(); + __privateGet$2(this, _spherical).theta = Math.atan2(__privateGet$2(this, _offset).x, __privateGet$2(this, _offset).z); + __privateGet$2(this, _spherical).phi = Math.acos(Math.min(Math.max(__privateGet$2(this, _offset).y / __privateGet$2(this, _spherical).radius, -1), 1)); + __privateMethod$3(this, _update, update_fn).call(this); } /** - * Get the first created {@link Renderer} if any - * @readonly + * Reset the {@link OrbitControls} values. + * @param parameters - Parameters used to reset the values. Those are the same as {@link OrbitControlsBaseParams} with an additional position parameter to allow to override the {@link OrbitControls} position. */ - get renderer() { - return this.renderers[0]; + reset({ + position, + target, + // zoom + enableZoom = this.enableZoom, + minZoom = this.minZoom, + maxZoom = this.maxZoom, + zoomSpeed = this.zoomSpeed, + // rotate + enableRotate = this.enableRotate, + minPolarAngle = this.minPolarAngle, + maxPolarAngle = this.maxPolarAngle, + minAzimuthAngle = this.minAzimuthAngle, + maxAzimuthAngle = this.maxAzimuthAngle, + rotateSpeed = this.rotateSpeed, + // pan + enablePan = this.enablePan, + panSpeed = this.panSpeed + } = {}) { + __privateMethod$3(this, _setBaseParams, setBaseParams_fn).call(this, { + target, + enableZoom, + minZoom, + maxZoom, + zoomSpeed, + enableRotate, + minPolarAngle, + maxPolarAngle, + minAzimuthAngle, + maxAzimuthAngle, + rotateSpeed, + enablePan, + panSpeed + }); + if (position) { + this.updatePosition(position); + } } /** - * Set the {@link GPUDeviceManager} {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} if possible, then set all created {@link Renderer} contexts. - * @async - * @param parameters - {@link GPUAdapter} and/or {@link GPUDevice} to use if set. + * Allow to override the {@link camera} position. + * @param position - new {@link camera} position to set. */ - async setDevice({ adapter = null, device = null } = {}) { - await this.deviceManager.init({ adapter, device }); + updatePosition(position = new Vec3()) { + position.sub(this.target); + __privateGet$2(this, _spherical).radius = position.length(); + __privateGet$2(this, _spherical).theta = Math.atan2(position.x, position.z); + __privateGet$2(this, _spherical).phi = Math.acos(Math.min(Math.max(position.y / __privateGet$2(this, _spherical).radius, -1), 1)); + __privateMethod$3(this, _update, update_fn).call(this); } /** - * Restore the {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} - * @async + * Set the element to use for event listeners. Can remove previous event listeners first if needed. + * @param value - {@link HTMLElement} (or {@link Window} element) to use. */ - async restoreContext() { - await this.deviceManager.restoreDevice(); + set element(value) { + if (__privateGet$2(this, _element) && (!value || __privateGet$2(this, _element) !== value)) { + __privateMethod$3(this, _removeEvents, removeEvents_fn).call(this); + } + __privateSet$2(this, _element, value); + if (value) { + __privateMethod$3(this, _addEvents, addEvents_fn).call(this); + } } - /* RENDERER TRACKED OBJECTS */ /** - * Get all the created {@link PingPongPlane} - * @readonly + * Get our element to use for event listeners. + * @returns - {@link HTMLElement} (or {@link Window} element) used. */ - get pingPongPlanes() { - return this.renderers?.map((renderer) => renderer.pingPongPlanes).flat(); + get element() { + return __privateGet$2(this, _element); } /** - * Get all the created {@link ShaderPass} - * @readonly + * Destroy the {@link OrbitControls}. */ - get shaderPasses() { - return this.renderers?.map((renderer) => renderer.shaderPasses).flat(); + destroy() { + this.element = null; } - /** - * Get all the created {@link SceneStackedMesh | meshes} - * @readonly - */ - get meshes() { - return this.renderers?.map((renderer) => renderer.meshes).flat(); + } + _element = new WeakMap(); + _offset = new WeakMap(); + _isOrbiting = new WeakMap(); + _spherical = new WeakMap(); + _rotateStart = new WeakMap(); + _isPaning = new WeakMap(); + _panStart = new WeakMap(); + _panDelta = new WeakMap(); + _setBaseParams = new WeakSet(); + setBaseParams_fn = function({ + target, + // zoom + enableZoom = this.enableZoom, + minZoom = this.minZoom, + maxZoom = this.maxZoom, + zoomSpeed = this.zoomSpeed, + // rotate + enableRotate = this.enableRotate, + minPolarAngle = this.minPolarAngle, + maxPolarAngle = this.maxPolarAngle, + minAzimuthAngle = this.minAzimuthAngle, + maxAzimuthAngle = this.maxAzimuthAngle, + rotateSpeed = this.rotateSpeed, + // pan + enablePan = this.enablePan, + panSpeed = this.panSpeed + } = {}) { + if (target) { + this.target = target; } - /** - * Get all the created {@link DOMMesh | DOM Meshes} (including {@link Plane | planes}) - * @readonly - */ - get domMeshes() { - return this.renderers?.filter((renderer) => renderer instanceof GPUCurtainsRenderer).map((renderer) => renderer.domMeshes).flat(); + this.enableZoom = enableZoom; + this.minZoom = minZoom; + this.maxZoom = maxZoom; + this.zoomSpeed = zoomSpeed; + this.enableRotate = enableRotate; + this.minPolarAngle = minPolarAngle; + this.maxPolarAngle = maxPolarAngle; + this.minAzimuthAngle = minAzimuthAngle; + this.maxAzimuthAngle = maxAzimuthAngle; + this.rotateSpeed = rotateSpeed; + this.enablePan = enablePan; + this.panSpeed = panSpeed; + }; + _addEvents = new WeakSet(); + addEvents_fn = function() { + __privateGet$2(this, _element).addEventListener("contextmenu", __privateMethod$3(this, _onContextMenu, onContextMenu_fn).bind(this), false); + __privateGet$2(this, _element).addEventListener("mousedown", __privateMethod$3(this, _onMouseDown, onMouseDown_fn).bind(this), false); + __privateGet$2(this, _element).addEventListener("mousemove", __privateMethod$3(this, _onMouseMove, onMouseMove_fn).bind(this), false); + __privateGet$2(this, _element).addEventListener("mouseup", __privateMethod$3(this, _onMouseUp, onMouseUp_fn).bind(this), false); + __privateGet$2(this, _element).addEventListener("touchstart", __privateMethod$3(this, _onTouchStart, onTouchStart_fn).bind(this), { passive: false }); + __privateGet$2(this, _element).addEventListener("touchmove", __privateMethod$3(this, _onTouchMove, onTouchMove_fn).bind(this), { passive: false }); + __privateGet$2(this, _element).addEventListener("touchend", __privateMethod$3(this, _onTouchEnd, onTouchEnd_fn).bind(this), false); + __privateGet$2(this, _element).addEventListener("wheel", __privateMethod$3(this, _onMouseWheel, onMouseWheel_fn).bind(this), { passive: false }); + }; + _removeEvents = new WeakSet(); + removeEvents_fn = function() { + __privateGet$2(this, _element).removeEventListener("contextmenu", __privateMethod$3(this, _onContextMenu, onContextMenu_fn).bind(this), false); + __privateGet$2(this, _element).removeEventListener("mousedown", __privateMethod$3(this, _onMouseDown, onMouseDown_fn).bind(this), false); + __privateGet$2(this, _element).removeEventListener("mousemove", __privateMethod$3(this, _onMouseMove, onMouseMove_fn).bind(this), false); + __privateGet$2(this, _element).removeEventListener("mouseup", __privateMethod$3(this, _onMouseUp, onMouseUp_fn).bind(this), false); + __privateGet$2(this, _element).removeEventListener("touchstart", __privateMethod$3(this, _onTouchStart, onTouchStart_fn).bind(this), { passive: false }); + __privateGet$2(this, _element).removeEventListener("touchmove", __privateMethod$3(this, _onTouchMove, onTouchMove_fn).bind(this), { passive: false }); + __privateGet$2(this, _element).removeEventListener("touchend", __privateMethod$3(this, _onTouchEnd, onTouchEnd_fn).bind(this), false); + __privateGet$2(this, _element).removeEventListener("wheel", __privateMethod$3(this, _onMouseWheel, onMouseWheel_fn).bind(this), { passive: false }); + }; + _onMouseDown = new WeakSet(); + onMouseDown_fn = function(e) { + if (e.button === 0 && this.enableRotate) { + __privateSet$2(this, _isOrbiting, true); + __privateGet$2(this, _rotateStart).set(e.clientX, e.clientY); + } else if (e.button === 2 && this.enablePan) { + __privateSet$2(this, _isPaning, true); + __privateGet$2(this, _panStart).set(e.clientX, e.clientY); } - /** - * Get all created {@link curtains/objects3D/DOMObject3D.DOMObject3D | DOMObject3D} which position should be updated on scroll. - * @readonly - */ - get domObjects() { - return this.renderers?.filter((renderer) => renderer instanceof GPUCurtainsRenderer).map((renderer) => renderer.domObjects).flat(); + e.stopPropagation(); + e.preventDefault(); + }; + _onTouchStart = new WeakSet(); + onTouchStart_fn = function(e) { + if (e.touches.length === 1 && this.enableRotate) { + __privateSet$2(this, _isOrbiting, true); + __privateGet$2(this, _rotateStart).set(e.touches[0].pageX, e.touches[0].pageY); + } + }; + _onMouseMove = new WeakSet(); + onMouseMove_fn = function(e) { + if (__privateGet$2(this, _isOrbiting) && this.enableRotate) { + __privateMethod$3(this, _rotate, rotate_fn).call(this, e.clientX, e.clientY); + } else if (__privateGet$2(this, _isPaning) && this.enablePan) { + __privateMethod$3(this, _pan, pan_fn).call(this, e.clientX, e.clientY); + } + }; + _onTouchMove = new WeakSet(); + onTouchMove_fn = function(e) { + if (__privateGet$2(this, _isOrbiting) && this.enableRotate) { + __privateMethod$3(this, _rotate, rotate_fn).call(this, e.touches[0].pageX, e.touches[0].pageY); + } + }; + _onMouseUp = new WeakSet(); + onMouseUp_fn = function(e) { + __privateSet$2(this, _isOrbiting, false); + __privateSet$2(this, _isPaning, false); + }; + _onTouchEnd = new WeakSet(); + onTouchEnd_fn = function(e) { + __privateSet$2(this, _isOrbiting, false); + __privateSet$2(this, _isPaning, false); + }; + _onMouseWheel = new WeakSet(); + onMouseWheel_fn = function(e) { + if (this.enableZoom) { + __privateMethod$3(this, _zoom, zoom_fn).call(this, e.deltaY); + e.preventDefault(); + } + }; + _onContextMenu = new WeakSet(); + onContextMenu_fn = function(e) { + e.preventDefault(); + }; + _update = new WeakSet(); + update_fn = function() { + const sinPhiRadius = __privateGet$2(this, _spherical).radius * Math.sin(Math.max(1e-6, __privateGet$2(this, _spherical).phi)); + __privateGet$2(this, _offset).x = sinPhiRadius * Math.sin(__privateGet$2(this, _spherical).theta); + __privateGet$2(this, _offset).y = __privateGet$2(this, _spherical).radius * Math.cos(__privateGet$2(this, _spherical).phi); + __privateGet$2(this, _offset).z = sinPhiRadius * Math.cos(__privateGet$2(this, _spherical).theta); + this.camera.position.copy(this.target).add(__privateGet$2(this, _offset)); + }; + _rotate = new WeakSet(); + rotate_fn = function(x, y) { + tempVec2a.set(x, y); + tempVec2b.copy(tempVec2a).sub(__privateGet$2(this, _rotateStart)).multiplyScalar(this.rotateSpeed); + __privateGet$2(this, _spherical).theta -= 2 * Math.PI * tempVec2b.x / this.camera.size.height; + __privateGet$2(this, _spherical).phi -= 2 * Math.PI * tempVec2b.y / this.camera.size.height; + __privateGet$2(this, _spherical).theta = Math.min(this.maxAzimuthAngle, Math.max(this.minAzimuthAngle, __privateGet$2(this, _spherical).theta)); + __privateGet$2(this, _spherical).phi = Math.min(this.maxPolarAngle, Math.max(this.minPolarAngle, __privateGet$2(this, _spherical).phi)); + __privateGet$2(this, _rotateStart).copy(tempVec2a); + __privateMethod$3(this, _update, update_fn).call(this); + }; + _pan = new WeakSet(); + pan_fn = function(x, y) { + tempVec2a.set(x, y); + tempVec2b.copy(tempVec2a).sub(__privateGet$2(this, _panStart)).multiplyScalar(this.panSpeed); + __privateGet$2(this, _panDelta).set(0); + tempVec3.copy(this.camera.position).sub(this.target); + let targetDistance = tempVec3.length(); + targetDistance *= Math.tan(this.camera.fov / 2 * Math.PI / 180); + tempVec3.set( + this.camera.modelMatrix.elements[0], + this.camera.modelMatrix.elements[1], + this.camera.modelMatrix.elements[2] + ); + tempVec3.multiplyScalar(-(2 * tempVec2b.x * targetDistance) / this.camera.size.height); + __privateGet$2(this, _panDelta).add(tempVec3); + tempVec3.set( + this.camera.modelMatrix.elements[4], + this.camera.modelMatrix.elements[5], + this.camera.modelMatrix.elements[6] + ); + tempVec3.multiplyScalar(2 * tempVec2b.y * targetDistance / this.camera.size.height); + __privateGet$2(this, _panDelta).add(tempVec3); + __privateGet$2(this, _panStart).copy(tempVec2a); + this.target.add(__privateGet$2(this, _panDelta)); + __privateGet$2(this, _offset).copy(this.camera.position).sub(this.target); + __privateGet$2(this, _spherical).radius = __privateGet$2(this, _offset).length(); + __privateMethod$3(this, _update, update_fn).call(this); + }; + _zoom = new WeakSet(); + zoom_fn = function(value) { + __privateGet$2(this, _spherical).radius = Math.min( + this.maxZoom, + Math.max(this.minZoom + 1e-6, __privateGet$2(this, _spherical).radius + value * this.zoomSpeed / 100) + ); + __privateMethod$3(this, _update, update_fn).call(this); + }; + + var __accessCheck$3 = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateAdd$3 = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateMethod$2 = (obj, member, method) => { + __accessCheck$3(obj, member, "access private method"); + return method; + }; + var _decodeRGBE, decodeRGBE_fn, _parseHeader, parseHeader_fn, _parseSize, parseSize_fn, _readLine, readLine_fn, _parseData, parseData_fn, _parseNewRLE, parseNewRLE_fn, _swap, swap_fn, _flipX, flipX_fn, _flipY, flipY_fn; + class HDRLoader { + constructor() { + /** + * @ignore + */ + __privateAdd$3(this, _decodeRGBE); + /** + * @ignore + */ + __privateAdd$3(this, _parseHeader); + /** + * @ignore + */ + __privateAdd$3(this, _parseSize); + /** + * @ignore + */ + __privateAdd$3(this, _readLine); + /** + * @ignore + */ + __privateAdd$3(this, _parseData); + /** + * @ignore + */ + __privateAdd$3(this, _parseNewRLE); + /** + * @ignore + */ + __privateAdd$3(this, _swap); + /** + * @ignore + */ + __privateAdd$3(this, _flipX); + /** + * @ignore + */ + __privateAdd$3(this, _flipY); } /** - * Get all the created {@link Plane | planes} - * @readonly + * Load and decode RGBE-encoded data to a flat list of floating point pixel data (RGBA). + * @param url - The url of the .hdr file to load. + * @returns - The {@link HDRImageData}. */ - get planes() { - return this.domMeshes.filter((domMesh) => domMesh instanceof Plane); + async loadFromUrl(url) { + const buffer = await (await fetch(url)).arrayBuffer(); + return __privateMethod$2(this, _decodeRGBE, decodeRGBE_fn).call(this, new DataView(buffer)); } - /** - * Get all the created {@link ComputePass | compute passes} - * @readonly - */ - get computePasses() { - return this.renderers?.map((renderer) => renderer.computePasses).flat(); + } + _decodeRGBE = new WeakSet(); + decodeRGBE_fn = function(data) { + const stream = { + data, + offset: 0 + }; + const header = __privateMethod$2(this, _parseHeader, parseHeader_fn).call(this, stream); + return { + width: header.width, + height: header.height, + exposure: header.exposure, + gamma: header.gamma, + data: __privateMethod$2(this, _parseData, parseData_fn).call(this, stream, header) + }; + }; + _parseHeader = new WeakSet(); + parseHeader_fn = function(stream) { + let line = __privateMethod$2(this, _readLine, readLine_fn).call(this, stream); + const header = { + colorCorr: [1, 1, 1], + exposure: 1, + gamma: 1, + width: 0, + height: 0, + flipX: false, + flipY: false + }; + if (line !== "#?RADIANCE" && line !== "#?RGBE") + throw new Error("Incorrect file format!"); + while (line !== "") { + line = __privateMethod$2(this, _readLine, readLine_fn).call(this, stream); + const parts2 = line.split("="); + switch (parts2[0]) { + case "GAMMA": + header.gamma = parseFloat(parts2[1]); + break; + case "FORMAT": + if (parts2[1] !== "32-bit_rle_rgbe" && parts2[1] !== "32-bit_rle_xyze") + throw new Error("Incorrect encoding format!"); + break; + case "EXPOSURE": + header.exposure = parseFloat(parts2[1]); + break; + case "COLORCORR": + header.colorCorr = parts2[1].replace(/^\s+|\s+$/g, "").split(" ").map((m) => parseFloat(m)); + break; + } } - /** - * Get our {@link GPUCurtainsRenderer#setPerspective | default GPUCurtainsRenderer bounding rectangle} - */ - get boundingRect() { - return this.renderer?.boundingRect; + line = __privateMethod$2(this, _readLine, readLine_fn).call(this, stream); + const parts = line.split(" "); + __privateMethod$2(this, _parseSize, parseSize_fn).call(this, parts[0], parseInt(parts[1]), header); + __privateMethod$2(this, _parseSize, parseSize_fn).call(this, parts[2], parseInt(parts[3]), header); + return header; + }; + _parseSize = new WeakSet(); + parseSize_fn = function(label, value, header) { + switch (label) { + case "+X": + header.width = value; + break; + case "-X": + header.width = value; + header.flipX = true; + console.warn("Flipping horizontal orientation not currently supported"); + break; + case "-Y": + header.height = value; + header.flipY = true; + break; + case "+Y": + header.height = value; + break; } - /* SCROLL */ - /** - * Set the {@link scrollManager} - */ - initScroll() { - this.scrollManager = new ScrollManager({ - // init values - scroll: { - x: window.pageXOffset, - y: window.pageYOffset - }, - delta: { - x: 0, - y: 0 - }, - shouldWatch: this.options.watchScroll, - onScroll: (delta) => this.updateScroll(delta) - }); + }; + _readLine = new WeakSet(); + readLine_fn = function(stream) { + let ch, str = ""; + while ((ch = stream.data.getUint8(stream.offset++)) !== 10) + str += String.fromCharCode(ch); + return str; + }; + _parseData = new WeakSet(); + parseData_fn = function(stream, header) { + const hash = stream.data.getUint16(stream.offset); + let data; + if (hash === 514) { + data = __privateMethod$2(this, _parseNewRLE, parseNewRLE_fn).call(this, stream, header); + if (header.flipX) + __privateMethod$2(this, _flipX, flipX_fn).call(this, data, header); + if (header.flipY) + __privateMethod$2(this, _flipY, flipY_fn).call(this, data, header); + } else { + throw new Error("Obsolete HDR file version!"); } - /** - * Update all {@link DOMMesh#updateScrollPosition | DOMMesh scroll positions} - * @param delta - last {@link ScrollManager#delta | scroll delta values} - */ - updateScroll(delta = { x: 0, y: 0 }) { - this.domObjects.forEach((domObject) => { - if (domObject.domElement && domObject.watchScroll) { - domObject.updateScrollPosition(delta); + return data; + }; + _parseNewRLE = new WeakSet(); + parseNewRLE_fn = function(stream, header) { + const { width, height, colorCorr } = header; + const tgt = new Float32Array(width * height * 4); + let i = 0; + let { offset, data } = stream; + for (let y = 0; y < height; ++y) { + if (data.getUint16(offset) !== 514) + throw new Error("Incorrect scanline start hash"); + if (data.getUint16(offset + 2) !== width) + throw new Error("Scanline doesn't match picture dimension!"); + offset += 4; + const numComps = width * 4; + const comps = []; + let x = 0; + while (x < numComps) { + let value = data.getUint8(offset++); + if (value > 128) { + const len = value - 128; + value = data.getUint8(offset++); + for (let rle = 0; rle < len; ++rle) { + comps[x++] = value; + } + } else { + for (let n = 0; n < value; ++n) { + comps[x++] = data.getUint8(offset++); + } } - }); - this._onScrollCallback && this._onScrollCallback(); - } - /** - * Update our {@link ScrollManager#scroll | scrollManager scroll values}. Called each time the scroll has changed if {@link GPUCurtains#options.watchScroll | watchScroll option} is set to true. Could be called externally as well. - * @param scroll - new {@link DOMPosition | scroll values} - */ - updateScrollValues(scroll = { x: 0, y: 0 }) { - this.scrollManager.updateScrollValues(scroll); - } - /** - * Get our {@link ScrollManager#delta | scrollManager delta values} - * @readonly - */ - get scrollDelta() { - return this.scrollManager.delta; - } - /** - * Get our {@link ScrollManager#scroll | scrollManager scroll values} - * @readonly - */ - get scrollValues() { - return this.scrollManager.scroll; - } - /* EVENT LISTENERS */ - /** - * Set the resize and scroll event listeners - */ - initEvents() { - resizeManager.useObserver(this.options.autoResize); - this.initScroll(); - } - /* EVENTS */ - /** - * Called at each render frame - * @param callback - callback to run at each render - * @returns - our {@link GPUCurtains} - */ - onRender(callback) { - if (callback) { - this._onRenderCallback = callback; } - return this; - } - /** - * Called each time the {@link ScrollManager#scroll | scrollManager scroll values} changed - * @param callback - callback to run each time the {@link ScrollManager#scroll | scrollManager scroll values} changed - * @returns - our {@link GPUCurtains} - */ - onScroll(callback) { - if (callback) { - this._onScrollCallback = callback; + for (x = 0; x < width; ++x) { + const r = comps[x]; + const g = comps[x + width]; + const b = comps[x + width * 2]; + let e = comps[x + width * 3]; + e = e ? Math.pow(2, e - 136) : 0; + tgt[i++] = r * e * colorCorr[0]; + tgt[i++] = g * e * colorCorr[1]; + tgt[i++] = b * e * colorCorr[2]; + tgt[i++] = e; } - return this; } - /** - * Called if there's been an error while trying to create the {@link GPUDeviceManager#device | device} - * @param callback - callback to run if there's been an error while trying to create the {@link GPUDeviceManager#device | device} - * @returns - our {@link GPUCurtains} - */ - onError(callback) { - if (callback) { - this._onErrorCallback = callback; + return tgt; + }; + _swap = new WeakSet(); + swap_fn = function(data, i1, i2) { + i1 *= 4; + i2 *= 4; + for (let i = 0; i < 4; ++i) { + const tmp = data[i1 + i]; + data[i1 + i] = data[i2 + i]; + data[i2 + i] = tmp; + } + }; + _flipX = new WeakSet(); + flipX_fn = function(data, header) { + const { width, height } = header; + const hw = width >> 1; + for (let y = 0; y < height; ++y) { + const b = y * width; + for (let x = 0; x < hw; ++x) { + const i1 = b + x; + const i2 = b + width - 1 - x; + __privateMethod$2(this, _swap, swap_fn).call(this, data, i1, i2); } - return this; } - /** - * Called whenever the {@link GPUDeviceManager#device | device} is lost - * @param callback - callback to run whenever the {@link GPUDeviceManager#device | device} is lost - * @returns - our {@link GPUCurtains} - */ - onContextLost(callback) { - if (callback) { - this._onContextLostCallback = callback; + }; + _flipY = new WeakSet(); + flipY_fn = function(data, header) { + const { width, height } = header; + const hh = height >> 1; + for (let y = 0; y < hh; ++y) { + const b1 = y * width; + const b2 = (height - 1 - y) * width; + for (let x = 0; x < width; ++x) { + __privateMethod$2(this, _swap, swap_fn).call(this, data, b1 + x, b2 + x); } - return this; } - /** - * Create a requestAnimationFrame loop and run it - */ - animate() { - this.render(); - this.animationFrameID = window.requestAnimationFrame(this.animate.bind(this)); + }; + + var computeBrdfLutWgsl = ( + /* wgsl */ + ` +fn radicalInverse_VdC(inputBits: u32) -> f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// GGX microfacet distribution +struct MicrofacetDistributionSample { + pdf: f32, + cosTheta: f32, + sinTheta: f32, + phi: f32 +}; + +fn D_GGX(NdotH: f32, roughness: f32) -> f32 { + let a: f32 = NdotH * roughness; + let k: f32 = roughness / (1.0 - NdotH * NdotH + a * a); + return k * k * (1.0 / ${Math.PI}); +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.html +// This implementation is based on https://bruop.github.io/ibl/, +// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html +// and https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +fn GGX(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var ggx: MicrofacetDistributionSample; + + // evaluate sampling equations + let alpha: f32 = roughness * roughness; + ggx.cosTheta = clamp(sqrt((1.0 - xi.y) / (1.0 + (alpha * alpha - 1.0) * xi.y)), 0.0, 1.0); + ggx.sinTheta = sqrt(1.0 - ggx.cosTheta * ggx.cosTheta); + ggx.phi = 2.0 * ${Math.PI} * xi.x; + + // evaluate GGX pdf (for half vector) + ggx.pdf = D_GGX(ggx.cosTheta, alpha); + + // Apply the Jacobian to obtain a pdf that is parameterized by l + // see https://bruop.github.io/ibl/ + // Typically you'd have the following: + // float pdf = D_GGX(NoH, roughness) * NoH / (4.0 * VoH); + // but since V = N => VoH == NoH + ggx.pdf /= 4.0; + + return ggx; +} + +fn Lambertian(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var lambertian: MicrofacetDistributionSample; + + // Cosine weighted hemisphere sampling + // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Cosine-WeightedHemisphereSampling + lambertian.cosTheta = sqrt(1.0 - xi.y); + lambertian.sinTheta = sqrt(xi.y); // equivalent to \`sqrt(1.0 - cosTheta*cosTheta)\`; + lambertian.phi = 2.0 * ${Math.PI} * xi.x; + + lambertian.pdf = lambertian.cosTheta / ${Math.PI}; // evaluation for solid angle, therefore drop the sinTheta + + return lambertian; +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); } - /** - * Render our {@link GPUDeviceManager} - */ - render() { - this._onRenderCallback && this._onRenderCallback(); - this.deviceManager.render(); + else { + bitangent = vec3(0.0, 0.0, -1.0); } - /** - * Destroy our {@link GPUCurtains} and {@link GPUDeviceManager} - */ - destroy() { - if (this.animationFrameID) { - window.cancelAnimationFrame(this.animationFrameID); - } - this.deviceManager.destroy(); - this.scrollManager?.destroy(); - resizeManager.destroy(); + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// getImportanceSample returns an importance sample direction with pdf in the .w component +fn getImportanceSample(Xi: vec2, N: vec3f, roughness: f32) -> vec4f { + var importanceSample: MicrofacetDistributionSample; + + importanceSample = GGX(Xi, roughness); + + // transform the hemisphere sample to the normal coordinate frame + // i.e. rotate the hemisphere to the normal direction + let localSpaceDirection: vec3f = normalize(vec3( + importanceSample.sinTheta * cos(importanceSample.phi), + importanceSample.sinTheta * sin(importanceSample.phi), + importanceSample.cosTheta + )); + + let TBN: mat3x3f = generateTBN(N); + let direction: vec3f = TBN * localSpaceDirection; + + return vec4(direction, importanceSample.pdf); +} + +// From the filament docs. Geometric Shadowing function +// https://google.github.io/filament/Filament.html#toc4.4.2 +fn V_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 { + let a2: f32 = pow(roughness, 4.0); + let GGXV: f32 = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2); + let GGXL: f32 = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2); + return 0.5 / (GGXV + GGXL); +} + +@compute @workgroup_size(16, 16, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let texelSize: vec2 = textureDimensions(lutStorageTexture); + + let x: u32 = global_id.x; + let y: u32 = global_id.y; + + // Check bounds + if (x >= texelSize.x || y >= texelSize.y) { + return; + } + + let epsilon: f32 = 1e-6; + + // Compute roughness and N\xB7V from texture coordinates + let NdotV: f32 = max(f32(x) / f32(texelSize.x - 1), epsilon); // Maps x-axis to N\xB7V (0.0 to 1.0) + let roughness: f32 = max(f32(y) / f32(texelSize.y - 1), epsilon); // Maps y-axis to roughness (0.0 to 1.0) + + // Calculate view vector and normal vector + let V: vec3 = vec3(sqrt(1.0 - NdotV * NdotV), 0.0, NdotV); // Normalized view vector + let N: vec3 = vec3(0.0, 0.0, 1.0); // Normal is along z-axis + + // Initialize integration variables + var A: f32 = 0.0; + var B: f32 = 0.0; + var C: f32 = 0.0; + + // Monte Carlo integration to calculate A and B factors + let sampleCount: u32 = params.sampleCount; + for (var i: u32 = 0; i < sampleCount; i++) { + let Xi: vec2 = hammersley2d(i, sampleCount); // Importance sampling (Hammersley sequence) + + //let H: vec3 = importanceSampleGGX(Xi, N, roughness); + let importanceSample: vec4f = getImportanceSample(Xi, N, roughness); + let H: vec3f = importanceSample.xyz; + // let pdf: f32 = importanceSample.w; + + let L: vec3 = normalize(reflect(-V, H)); + + let NdotL: f32 = clamp(L.z, 0.0, 1.0); + let NdotH: f32 = clamp(H.z, 0.0, 1.0); + let VdotH: f32 = clamp(dot(V, H), 0.0, 1.0); + + // Ensure valid light direction + if (NdotL > 0.0) { + // LUT for GGX distribution. + + // Taken from: https://bruop.github.io/ibl + // Shadertoy: https://www.shadertoy.com/view/3lXXDB + // Terms besides V are from the GGX PDF we're dividing by. + let V_pdf: f32 = V_SmithGGXCorrelated(NdotV, NdotL, roughness) * VdotH * NdotL / max(NdotH, epsilon); + let Fc: f32 = pow(1.0 - VdotH, 5.0); + A += (1.0 - Fc) * V_pdf; + B += Fc * V_pdf; + C += 0.0; } } - var __accessCheck$2 = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateGet$1 = (obj, member, getter) => { - __accessCheck$2(obj, member, "read from private field"); - return getter ? getter.call(obj) : member.get(obj); - }; - var __privateAdd$2 = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateSet$1 = (obj, member, value, setter) => { - __accessCheck$2(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); - return value; - }; - var __privateMethod$1 = (obj, member, method) => { - __accessCheck$2(obj, member, "access private method"); - return method; - }; - var _element, _offset, _isOrbiting, _spherical, _rotateStart, _isPaning, _panStart, _panDelta, _setBaseParams, setBaseParams_fn, _addEvents, addEvents_fn, _removeEvents, removeEvents_fn, _onMouseDown, onMouseDown_fn, _onTouchStart, onTouchStart_fn, _onMouseMove, onMouseMove_fn, _onTouchMove, onTouchMove_fn, _onMouseUp, onMouseUp_fn, _onTouchEnd, onTouchEnd_fn, _onMouseWheel, onMouseWheel_fn, _onContextMenu, onContextMenu_fn, _update, update_fn, _rotate, rotate_fn, _pan, pan_fn, _zoom, zoom_fn; - const tempVec2a = new Vec2(); - const tempVec2b = new Vec2(); - const tempVec3 = new Vec3(); - class OrbitControls { - /** - * OrbitControls constructor - = * @param parameters - parameters to use. - */ - constructor({ - camera, - element = null, - target = new Vec3(), - // zoom - enableZoom = true, - minZoom = 0, - maxZoom = Infinity, - zoomSpeed = 1, - // rotate - enableRotate = true, - minPolarAngle = 0, - maxPolarAngle = Math.PI, - minAzimuthAngle = -Infinity, - maxAzimuthAngle = Infinity, - rotateSpeed = 1, - // pan - enablePan = true, - panSpeed = 1 - }) { - /** - * Set / reset base params - * @ignore - */ - __privateAdd$2(this, _setBaseParams); - /** - * Add the event listeners. - * @private - */ - __privateAdd$2(this, _addEvents); - /** - * Remove the event listeners. - * @private - */ - __privateAdd$2(this, _removeEvents); - /** - * Callback executed on mouse down event. - * @param e - {@link MouseEvent}. - * @private - */ - __privateAdd$2(this, _onMouseDown); - /** - * Callback executed on touch start event. - * @param e - {@link TouchEvent}. - * @private - */ - __privateAdd$2(this, _onTouchStart); - /** - * Callback executed on mouse move event. - * @param e - {@link MouseEvent}. - */ - __privateAdd$2(this, _onMouseMove); - /** - * Callback executed on touch move event. - * @param e - {@link TouchEvent}. - * @private - */ - __privateAdd$2(this, _onTouchMove); - /** - * Callback executed on mouse up event. - * @param e - {@link MouseEvent}. - * @private - */ - __privateAdd$2(this, _onMouseUp); - /** - * Callback executed on touch end event. - * @param e - {@link MouseEvent}. - * @private - */ - __privateAdd$2(this, _onTouchEnd); - /** - * Callback executed on wheel event. - * @param e - {@link WheelEvent}. - * @private - */ - __privateAdd$2(this, _onMouseWheel); - /** - * Prevent context menu apparition on right click - * @param e - {@link MouseEvent}. - * @private - */ - __privateAdd$2(this, _onContextMenu); - /** - * Update the {@link camera} position based on the {@link target} and internal values. - * @private - */ - __privateAdd$2(this, _update); - /** - * Update the {@link camera} position based on input coordinates so it rotates around the {@link target}. - * @param x - input coordinate along the X axis. - * @param y - input coordinate along the Y axis. - * @private - */ - __privateAdd$2(this, _rotate); - /** - * Pan the {@link camera} position based on input coordinates by updating {@link target}. - * @param x - input coordinate along the X axis. - * @param y - input coordinate along the Y axis. - * @private - */ - __privateAdd$2(this, _pan); - /** - * Move the {@link camera} forward or backward. - * @param value - new value to use for zoom. - * @private - */ - __privateAdd$2(this, _zoom); + // Average the integration result + // The PDF is simply pdf(v, h) -> NDF * . + // To parametrize the PDF over l, use the Jacobian transform, yielding to: pdf(v, l) -> NDF * / 4 + // Since the BRDF divide through the PDF to be normalized, the 4 can be pulled out of the integral. + A = A * 4.0 / f32(sampleCount); + B = B * 4.0 / f32(sampleCount); + C = C * 4.0 * 2.0 * ${Math.PI} / f32(sampleCount); + + // Store the result in the LUT texture + textureStore(lutStorageTexture, vec2(x, y), vec4(A, B, C, 1.0)); +} +` + ); + + var computeSpecularCubemapFromHdr = ( + /* wgsl */ + ` +// Cube face lookup vectors +// positive and negative Y need to be inverted +const faceVectors = array, 2>, 6>( + array, 2>(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // +X + array, 2>(vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // -X + array, 2>(vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0)), // -Y + array, 2>(vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, -1.0)), // +Y + array, 2>(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0)), // +Z + array, 2>(vec3(0.0, 0.0, -1.0), vec3(0.0, 1.0, 0.0)) // -Z +); + +// Utility to calculate 3D direction for a given cube face pixel +fn texelDirection(faceIndex : u32, u : f32, v : f32) -> vec3 { + let forward = faceVectors[faceIndex][0]; + let up = faceVectors[faceIndex][1]; + let right = normalize(cross(up, forward)); + return normalize(forward + (2.0 * u - 1.0) * right + (2.0 * v - 1.0) * up); +} + +// Map 3D direction to equirectangular coordinates +fn dirToEquirect(dir : vec3) -> vec2 { + let phi = atan2(dir.z, dir.x); + let theta = asin(dir.y); + let u = 0.5 + 0.5 * phi / ${Math.PI}; + let v = 0.5 - theta / ${Math.PI}; + return vec2(u, v); +} + +@compute @workgroup_size(8, 8, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let faceSize = params.faceSize; + let cubeFaceIndex = global_id.z; + let x = global_id.x; + let y = global_id.y; + + if (x >= faceSize || y >= faceSize || cubeFaceIndex >= 6u) { + return; + } + + let u = f32(x) / f32(faceSize); + let v = f32(y) / f32(faceSize); + + // Get the 3D direction for this cube face texel + let dir = texelDirection(cubeFaceIndex, u, v); + + // Map to equirectangular coordinates + let uv = dirToEquirect(dir); + + let hdrWidth = params.imageSize.x; + let hdrHeight = params.imageSize.y; + + let texX = u32(clamp(uv.x * hdrWidth, 0.0, hdrWidth - 1.0)); + let texY = u32(clamp(uv.y * hdrHeight, 0.0, hdrHeight - 1.0)); + + let hdrTexelIndex = texY * u32(hdrWidth) + texX; + + // Sample the equirectangular texture + let sampledColor = params.hdrImageData[hdrTexelIndex]; + + // Correct cube face order in texture store (fix for reversed face indices) + textureStore( + specularStorageCubemap, + vec2(x, y), + cubeFaceIndex, + sampledColor + ); +} +` + ); + + const computeDiffuseFromSpecularCubemap = (specularTexture) => ( + /* wgsl */ + ` +fn radicalInverse_VdC(inputBits: u32) -> f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); + } + else { + bitangent = vec3(0.0, 0.0, -1.0); + } + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// Mipmap Filtered Samples (GPU Gems 3, 20.4) +// https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling +// https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf +fn computeLod(pdf: f32) -> f32 { + // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf + return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); +} + +fn transformDirection(face: u32, uv: vec2f) -> vec3f { + // Transform the direction based on the cubemap face + switch (face) { + case 0u { + // +X + return vec3f( 1.0, uv.y, -uv.x); + } + case 1u { + // -X + return vec3f(-1.0, uv.y, uv.x); + } + case 2u { + // +Y + return vec3f( uv.x, -1.0, uv.y); + } + case 3u { + // -Y + return vec3f( uv.x, 1.0, -uv.y); + } + case 4u { + // +Z + return vec3f( uv.x, uv.y, 1.0); + } + case 5u { + // -Z + return vec3f(-uv.x, uv.y, -1.0); + } + default { + return vec3f(0.0, 0.0, 0.0); + } + } +} + +const PI = ${Math.PI}; + +@compute @workgroup_size(8, 8, 1) fn main( + @builtin(global_invocation_id) GlobalInvocationID: vec3u, +) { + let faceSize: u32 = params.faceSize; + let sampleCount: u32 = params.sampleCount; + + let face: u32 = GlobalInvocationID.z; + let x: u32 = GlobalInvocationID.x; + let y: u32 = GlobalInvocationID.y; + + if (x >= faceSize || y >= faceSize) { + return; + } + + let texelSize: f32 = 1.0 / f32(faceSize); + let halfTexel: f32 = texelSize * 0.5; + + var uv: vec2f = vec2( + (f32(x) + halfTexel) * texelSize, + (f32(y) + halfTexel) * texelSize + ); + + uv = uv * 2.0 - 1.0; + + let normal: vec3 = transformDirection(face, uv); + + var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); + + for (var i: u32 = 0; i < sampleCount; i++) { + // generate a quasi monte carlo point in the unit square [0.1)^2 + let xi: vec2f = hammersley2d(i, sampleCount); + + let cosTheta: f32 = sqrt(1.0 - xi.y); + let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); + let phi: f32 = 2.0 * PI * xi.x; + let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta + + let sampleVec: vec3f = vec3f( + sinTheta * cos(phi), + sinTheta * sin(phi), + cosTheta + ); + + let TBN: mat3x3f = generateTBN(normalize(normal)); + + var direction: vec3f = TBN * sampleVec; + + // invert along Y axis + direction.y *= -1.0; + + let lod: f32 = computeLod(pdf); + + let sampleLevel = min(lod, f32(params.maxMipLevel)); + + // Convert sampleVec to texture coordinates of the specular env map + irradiance += textureSampleLevel( + ${specularTexture.options.name}, + clampSampler, + direction, + sampleLevel + ).rgb; + } + + irradiance /= f32(sampleCount); + + textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); +} +` + ); + + var __accessCheck$2 = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateAdd$2 = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateMethod$1 = (obj, member, method) => { + __accessCheck$2(obj, member, "access private method"); + return method; + }; + var _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn; + class EnvironmentMap { + /** + * {@link EnvironmentMap} constructor. + * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link EnvironmentMap}. + * @param params - {@link EnvironmentMapParams | parameters} use to create this {@link EnvironmentMap}. Defines the various textures options. + */ + constructor(renderer, params = { + lutTextureParams: { + size: 256, + computeSampleCount: 1024, + label: "Environment LUT texture", + name: "lutTexture", + format: "rgba32float" + }, + diffuseTextureParams: { + size: 128, + computeSampleCount: 2048, + label: "Environment diffuse texture", + name: "diffuseTexture", + format: "rgba16float" + }, + specularTextureParams: { + label: "Environment specular texture", + name: "specularTexture", + format: "rgba16float", + generateMips: true + } + }) { /** - * {@link HTMLElement} (or {@link Window} element) to use for event listeners. + * Once the given {@link ComputePass} has written to a temporary storage {@link Texture}, copy it into our permanent {@link Texture}. + * @param commandEncoder - The GPU command encoder to use. + * @param storageTexture - Temporary storage {@link Texture} used in the {@link ComputePass}. + * @param texture - Permanent {@link Texture} (either the {@link lutTexture}, {@link specularTexture} or {@link diffuseTexture}) to copy onto. * @private */ - __privateAdd$2(this, _element, null); - /** @ignore */ - __privateAdd$2(this, _offset, new Vec3()); - /** @ignore */ - __privateAdd$2(this, _isOrbiting, false); - /** @ignore */ - __privateAdd$2(this, _spherical, { radius: 1, phi: 0, theta: 0 }); - /** @ignore */ - __privateAdd$2(this, _rotateStart, new Vec2()); - /** @ignore */ - __privateAdd$2(this, _isPaning, false); - /** @ignore */ - __privateAdd$2(this, _panStart, new Vec2()); - /** @ignore */ - __privateAdd$2(this, _panDelta, new Vec3()); - if (!camera) { - throwWarning("OrbitControls: cannot initialize without a camera."); - return; - } - this.camera = camera; - __privateMethod$1(this, _setBaseParams, setBaseParams_fn).call(this, { - target, - enableZoom, - minZoom, - maxZoom, - zoomSpeed, - enableRotate, - minPolarAngle, - maxPolarAngle, - minAzimuthAngle, - maxAzimuthAngle, - rotateSpeed, - enablePan, - panSpeed - }); - __privateGet$1(this, _offset).copy(this.camera.position).sub(this.target); - __privateGet$1(this, _spherical).radius = __privateGet$1(this, _offset).length(); - __privateGet$1(this, _spherical).theta = Math.atan2(__privateGet$1(this, _offset).x, __privateGet$1(this, _offset).z); - __privateGet$1(this, _spherical).phi = Math.acos(Math.min(Math.max(__privateGet$1(this, _offset).y / __privateGet$1(this, _spherical).radius, -1), 1)); - this.camera.position.onChange(() => { - this.camera.lookAt(this.target); + __privateAdd$2(this, _copyComputeStorageTextureToTexture); + renderer = isRenderer(renderer, "EnvironmentMap"); + this.renderer = renderer; + this.options = params; + this.sampler = new Sampler(this.renderer, { + label: "Clamp sampler", + name: "clampSampler", + magFilter: "linear", + minFilter: "linear", + mipmapFilter: "linear", + addressModeU: "clamp-to-edge", + addressModeV: "clamp-to-edge" }); - this.element = element ?? (typeof window !== "undefined" ? window : null); - __privateMethod$1(this, _update, update_fn).call(this); + this.rotation = new Mat3(new Float32Array([0, 0, 1, 0, 1, 0, -1, 0, 0])); + this.hdrLoader = new HDRLoader(); + this.computeBRDFLUTTexture(); } /** - * Reset the {@link OrbitControls} values. - * @param parameters - Parameters used to reset the values. Those are the same as {@link OrbitControlsBaseParams} with an additional position parameter to allow to override the {@link OrbitControls} position. + * Create the {@link lutTexture | BRDF GGX LUT texture} using the provided {@link LUTTextureParams | LUT texture options} and a {@link ComputePass} that runs once. + * @async */ - reset({ - position, - target, - // zoom - enableZoom = this.enableZoom, - minZoom = this.minZoom, - maxZoom = this.maxZoom, - zoomSpeed = this.zoomSpeed, - // rotate - enableRotate = this.enableRotate, - minPolarAngle = this.minPolarAngle, - maxPolarAngle = this.maxPolarAngle, - minAzimuthAngle = this.minAzimuthAngle, - maxAzimuthAngle = this.maxAzimuthAngle, - rotateSpeed = this.rotateSpeed, - // pan - enablePan = this.enablePan, - panSpeed = this.panSpeed - } = {}) { - __privateMethod$1(this, _setBaseParams, setBaseParams_fn).call(this, { - target, - enableZoom, - minZoom, - maxZoom, - zoomSpeed, - enableRotate, - minPolarAngle, - maxPolarAngle, - minAzimuthAngle, - maxAzimuthAngle, - rotateSpeed, - enablePan, - panSpeed + async computeBRDFLUTTexture() { + const { size, computeSampleCount, ...lutTextureParams } = this.options.lutTextureParams; + this.lutTexture = new Texture(this.renderer, { + ...lutTextureParams, + visibility: ["fragment"], + fixedSize: { + width: size, + height: size + }, + autoDestroy: false }); - if (position) { - this.updatePosition(position); - } - } - /** - * Allow to override the {@link camera} position. - * @param position - new {@link camera} position to set. - */ - updatePosition(position = new Vec3()) { - position.sub(this.target); - __privateGet$1(this, _spherical).radius = position.length(); - __privateGet$1(this, _spherical).theta = Math.atan2(position.x, position.z); - __privateGet$1(this, _spherical).phi = Math.acos(Math.min(Math.max(position.y / __privateGet$1(this, _spherical).radius, -1), 1)); - __privateMethod$1(this, _update, update_fn).call(this); + let lutStorageTexture = new Texture(this.renderer, { + label: "LUT storage texture", + name: "lutStorageTexture", + format: this.lutTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.lutTexture.size.width, + height: this.lutTexture.size.height + } + }); + let computeLUTPass = new ComputePass(this.renderer, { + label: "Compute LUT texture", + autoRender: false, + // we're going to render only on demand + dispatchSize: [Math.ceil(lutStorageTexture.size.width / 16), Math.ceil(lutStorageTexture.size.height / 16), 1], + shaders: { + compute: { + code: computeBrdfLutWgsl + } + }, + uniforms: { + params: { + struct: { + sampleCount: { + type: "u32", + value: computeSampleCount + } + } + } + }, + textures: [lutStorageTexture] + }); + await computeLUTPass.material.compileMaterial(); + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.renderSingleComputePass(commandEncoder, computeLUTPass); + __privateMethod$1(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, lutStorageTexture, this.lutTexture); + }, + { once: true } + ); + this.renderer.onAfterCommandEncoderSubmission.add( + () => { + computeLUTPass.destroy(); + lutStorageTexture.destroy(); + lutStorageTexture = null; + computeLUTPass = null; + }, + { once: true } + ); } /** - * Set the element to use for event listeners. Can remove previous event listeners first if needed. - * @param value - {@link HTMLElement} (or {@link Window} element) to use. + * Create the {@link specularTexture | specular cube map texture} from a loaded {@link HDRImageData} using the provided {@link SpecularTextureParams | specular texture options} and a {@link ComputePass} that runs once. + * @params parsedHdr - parsed {@link HDRImageData} loaded by the {@link hdrLoader}. + * @async */ - set element(value) { - if (__privateGet$1(this, _element) && (!value || __privateGet$1(this, _element) !== value)) { - __privateMethod$1(this, _removeEvents, removeEvents_fn).call(this); - } - __privateSet$1(this, _element, value); - if (value) { - __privateMethod$1(this, _addEvents, addEvents_fn).call(this); + async computeSpecularCubemapFromHDRData(parsedHdr) { + let cubeStorageTexture = new Texture(this.renderer, { + label: "Specular storage cubemap", + name: "specularStorageCubemap", + format: this.specularTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.specularTexture.size.width, + height: this.specularTexture.size.height, + depth: 6 + }, + viewDimension: "2d-array" + }); + let computeCubeMapPass = new ComputePass(this.renderer, { + label: "Compute specular cubemap from equirectangular", + autoRender: false, + // we're going to render only on demand + dispatchSize: [ + Math.ceil(this.specularTexture.size.width / 8), + Math.ceil(this.specularTexture.size.height / 8), + 6 + ], + shaders: { + compute: { + code: computeSpecularCubemapFromHdr + } + }, + storages: { + params: { + struct: { + hdrImageData: { + type: "array", + value: parsedHdr.data + }, + imageSize: { + type: "vec2f", + value: new Vec2(parsedHdr.width, parsedHdr.height) + }, + faceSize: { + type: "u32", + value: this.specularTexture.size.width + } + } + } + }, + textures: [cubeStorageTexture] + }); + await computeCubeMapPass.material.compileMaterial(); + const commandEncoder = this.renderer.device?.createCommandEncoder({ + label: "Render once command encoder" + }); + if (!this.renderer.production) + commandEncoder.pushDebugGroup("Render once command encoder"); + this.renderer.renderSingleComputePass(commandEncoder, computeCubeMapPass); + __privateMethod$1(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, cubeStorageTexture, this.specularTexture); + if (!this.renderer.production) + commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.renderer.device?.queue.submit([commandBuffer]); + if (this.specularTexture.texture.mipLevelCount > 1) { + generateMips(this.renderer.device, this.specularTexture.texture); } + computeCubeMapPass.destroy(); + cubeStorageTexture.destroy(); + cubeStorageTexture = null; + computeCubeMapPass = null; } - /** - * Get our element to use for event listeners. - * @returns - {@link HTMLElement} (or {@link Window} element) used. - */ - get element() { - return __privateGet$1(this, _element); - } - /** - * Destroy the {@link OrbitControls}. - */ - destroy() { - this.element = null; - } - } - _element = new WeakMap(); - _offset = new WeakMap(); - _isOrbiting = new WeakMap(); - _spherical = new WeakMap(); - _rotateStart = new WeakMap(); - _isPaning = new WeakMap(); - _panStart = new WeakMap(); - _panDelta = new WeakMap(); - _setBaseParams = new WeakSet(); - setBaseParams_fn = function({ - target, - // zoom - enableZoom = this.enableZoom, - minZoom = this.minZoom, - maxZoom = this.maxZoom, - zoomSpeed = this.zoomSpeed, - // rotate - enableRotate = this.enableRotate, - minPolarAngle = this.minPolarAngle, - maxPolarAngle = this.maxPolarAngle, - minAzimuthAngle = this.minAzimuthAngle, - maxAzimuthAngle = this.maxAzimuthAngle, - rotateSpeed = this.rotateSpeed, - // pan - enablePan = this.enablePan, - panSpeed = this.panSpeed - } = {}) { - if (target) { - this.target = target; - } - this.enableZoom = enableZoom; - this.minZoom = minZoom; - this.maxZoom = maxZoom; - this.zoomSpeed = zoomSpeed; - this.enableRotate = enableRotate; - this.minPolarAngle = minPolarAngle; - this.maxPolarAngle = maxPolarAngle; - this.minAzimuthAngle = minAzimuthAngle; - this.maxAzimuthAngle = maxAzimuthAngle; - this.rotateSpeed = rotateSpeed; - this.enablePan = enablePan; - this.panSpeed = panSpeed; - }; - _addEvents = new WeakSet(); - addEvents_fn = function() { - __privateGet$1(this, _element).addEventListener("contextmenu", __privateMethod$1(this, _onContextMenu, onContextMenu_fn).bind(this), false); - __privateGet$1(this, _element).addEventListener("mousedown", __privateMethod$1(this, _onMouseDown, onMouseDown_fn).bind(this), false); - __privateGet$1(this, _element).addEventListener("mousemove", __privateMethod$1(this, _onMouseMove, onMouseMove_fn).bind(this), false); - __privateGet$1(this, _element).addEventListener("mouseup", __privateMethod$1(this, _onMouseUp, onMouseUp_fn).bind(this), false); - __privateGet$1(this, _element).addEventListener("touchstart", __privateMethod$1(this, _onTouchStart, onTouchStart_fn).bind(this), { passive: false }); - __privateGet$1(this, _element).addEventListener("touchmove", __privateMethod$1(this, _onTouchMove, onTouchMove_fn).bind(this), { passive: false }); - __privateGet$1(this, _element).addEventListener("touchend", __privateMethod$1(this, _onTouchEnd, onTouchEnd_fn).bind(this), false); - __privateGet$1(this, _element).addEventListener("wheel", __privateMethod$1(this, _onMouseWheel, onMouseWheel_fn).bind(this), { passive: false }); - }; - _removeEvents = new WeakSet(); - removeEvents_fn = function() { - __privateGet$1(this, _element).removeEventListener("contextmenu", __privateMethod$1(this, _onContextMenu, onContextMenu_fn).bind(this), false); - __privateGet$1(this, _element).removeEventListener("mousedown", __privateMethod$1(this, _onMouseDown, onMouseDown_fn).bind(this), false); - __privateGet$1(this, _element).removeEventListener("mousemove", __privateMethod$1(this, _onMouseMove, onMouseMove_fn).bind(this), false); - __privateGet$1(this, _element).removeEventListener("mouseup", __privateMethod$1(this, _onMouseUp, onMouseUp_fn).bind(this), false); - __privateGet$1(this, _element).removeEventListener("touchstart", __privateMethod$1(this, _onTouchStart, onTouchStart_fn).bind(this), { passive: false }); - __privateGet$1(this, _element).removeEventListener("touchmove", __privateMethod$1(this, _onTouchMove, onTouchMove_fn).bind(this), { passive: false }); - __privateGet$1(this, _element).removeEventListener("touchend", __privateMethod$1(this, _onTouchEnd, onTouchEnd_fn).bind(this), false); - __privateGet$1(this, _element).removeEventListener("wheel", __privateMethod$1(this, _onMouseWheel, onMouseWheel_fn).bind(this), { passive: false }); - }; - _onMouseDown = new WeakSet(); - onMouseDown_fn = function(e) { - if (e.button === 0 && this.enableRotate) { - __privateSet$1(this, _isOrbiting, true); - __privateGet$1(this, _rotateStart).set(e.clientX, e.clientY); - } else if (e.button === 2 && this.enablePan) { - __privateSet$1(this, _isPaning, true); - __privateGet$1(this, _panStart).set(e.clientX, e.clientY); - } - e.stopPropagation(); - e.preventDefault(); - }; - _onTouchStart = new WeakSet(); - onTouchStart_fn = function(e) { - if (e.touches.length === 1 && this.enableRotate) { - __privateSet$1(this, _isOrbiting, true); - __privateGet$1(this, _rotateStart).set(e.touches[0].pageX, e.touches[0].pageY); - } - }; - _onMouseMove = new WeakSet(); - onMouseMove_fn = function(e) { - if (__privateGet$1(this, _isOrbiting) && this.enableRotate) { - __privateMethod$1(this, _rotate, rotate_fn).call(this, e.clientX, e.clientY); - } else if (__privateGet$1(this, _isPaning) && this.enablePan) { - __privateMethod$1(this, _pan, pan_fn).call(this, e.clientX, e.clientY); + /** + * Compute the {@link diffuseTexture | diffuse cube map texture} from the {@link specularTexture | specular cube map texture } using the provided {@link DiffuseTextureParams | diffuse texture options} and a {@link ComputePass} that runs once. + */ + async computeDiffuseFromSpecular() { + if (this.specularTexture.options.viewDimension !== "cube") { + throwWarning( + "Could not compute the diffuse texture because the specular texture is not a cube map:" + this.specularTexture.options.viewDimension + ); + return; + } + let diffuseStorageTexture = new Texture(this.renderer, { + label: "Diffuse storage cubemap", + name: "diffuseEnvMap", + format: this.diffuseTexture.options.format, + visibility: ["compute"], + usage: ["copySrc", "storageBinding"], + type: "storage", + fixedSize: { + width: this.diffuseTexture.size.width, + height: this.diffuseTexture.size.height, + depth: 6 + }, + viewDimension: "2d-array" + }); + let computeDiffusePass = new ComputePass(this.renderer, { + label: "Compute diffuse map from specular map", + autoRender: false, + // we're going to render only on demand + dispatchSize: [Math.ceil(this.diffuseTexture.size.width / 8), Math.ceil(this.diffuseTexture.size.height / 8), 6], + shaders: { + compute: { + code: computeDiffuseFromSpecularCubemap(this.specularTexture) + } + }, + uniforms: { + params: { + struct: { + faceSize: { + type: "u32", + value: this.diffuseTexture.size.width + }, + maxMipLevel: { + type: "u32", + value: this.specularTexture.texture.mipLevelCount + }, + sampleCount: { + type: "u32", + value: this.options.diffuseTextureParams.computeSampleCount + } + } + } + }, + samplers: [this.sampler], + textures: [this.specularTexture, diffuseStorageTexture] + }); + await computeDiffusePass.material.compileMaterial(); + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.renderSingleComputePass(commandEncoder, computeDiffusePass); + __privateMethod$1(this, _copyComputeStorageTextureToTexture, copyComputeStorageTextureToTexture_fn).call(this, commandEncoder, diffuseStorageTexture, this.diffuseTexture); + }, + { once: true } + ); + this.renderer.onAfterCommandEncoderSubmission.add( + () => { + computeDiffusePass.destroy(); + diffuseStorageTexture.destroy(); + diffuseStorageTexture = null; + computeDiffusePass = null; + }, + { once: true } + ); } - }; - _onTouchMove = new WeakSet(); - onTouchMove_fn = function(e) { - if (__privateGet$1(this, _isOrbiting) && this.enableRotate) { - __privateMethod$1(this, _rotate, rotate_fn).call(this, e.touches[0].pageX, e.touches[0].pageY); + /** + * Load an HDR environment map and then generates the {@link specularTexture} and {@link diffuseTexture} using two separate {@link ComputePass}. + * @param url - The url of the .hdr file to load. + * @async + */ + async loadAndComputeFromHDR(url) { + const parsedHdr = await this.hdrLoader.loadFromUrl(url); + const { width, height } = parsedHdr ? parsedHdr : { width: 1024, height: 512 }; + const faceSize = Math.max(width / 4, height / 2); + const textureDefaultOptions = { + viewDimension: "cube", + autoDestroy: false + // keep alive when changing glTF + }; + if (!this.specularTexture) { + this.specularTexture = new Texture(this.renderer, { + ...this.options.specularTextureParams, + ...{ + visibility: ["fragment", "compute"], + fixedSize: { + width: faceSize, + height: faceSize + } + }, + ...textureDefaultOptions + }); + } else if (this.specularTexture.size.width !== faceSize || this.specularTexture.size.height !== faceSize) { + this.specularTexture.options.fixedSize.width = faceSize; + this.specularTexture.options.fixedSize.height = faceSize; + this.specularTexture.size.width = faceSize; + this.specularTexture.size.height = faceSize; + this.specularTexture.createTexture(); + } + const { size, computeSampleCount, ...diffuseTextureParams } = this.options.diffuseTextureParams; + const diffuseSize = Math.min(size, faceSize); + if (!this.diffuseTexture) { + this.diffuseTexture = new Texture(this.renderer, { + ...diffuseTextureParams, + ...{ + visibility: ["fragment"], + fixedSize: { + width: diffuseSize, + height: diffuseSize + } + }, + ...textureDefaultOptions + }); + } else if (this.diffuseTexture.size.width !== diffuseSize || this.diffuseTexture.size.height !== diffuseSize) { + this.diffuseTexture.options.fixedSize.width = diffuseSize; + this.diffuseTexture.options.fixedSize.height = diffuseSize; + this.diffuseTexture.size.width = diffuseSize; + this.diffuseTexture.size.height = diffuseSize; + this.diffuseTexture.createTexture(); + } + if (parsedHdr) { + this.computeSpecularCubemapFromHDRData(parsedHdr).then(() => { + this.computeDiffuseFromSpecular(); + }); + } } - }; - _onMouseUp = new WeakSet(); - onMouseUp_fn = function(e) { - __privateSet$1(this, _isOrbiting, false); - __privateSet$1(this, _isPaning, false); - }; - _onTouchEnd = new WeakSet(); - onTouchEnd_fn = function(e) { - __privateSet$1(this, _isOrbiting, false); - __privateSet$1(this, _isPaning, false); - }; - _onMouseWheel = new WeakSet(); - onMouseWheel_fn = function(e) { - if (this.enableZoom) { - __privateMethod$1(this, _zoom, zoom_fn).call(this, e.deltaY); - e.preventDefault(); + /** + * Destroy the {@link EnvironmentMap} and its associated textures. + */ + destroy() { + this.lutTexture?.destroy(); + this.diffuseTexture?.destroy(); + this.specularTexture?.destroy(); } - }; - _onContextMenu = new WeakSet(); - onContextMenu_fn = function(e) { - e.preventDefault(); - }; - _update = new WeakSet(); - update_fn = function() { - const sinPhiRadius = __privateGet$1(this, _spherical).radius * Math.sin(Math.max(1e-6, __privateGet$1(this, _spherical).phi)); - __privateGet$1(this, _offset).x = sinPhiRadius * Math.sin(__privateGet$1(this, _spherical).theta); - __privateGet$1(this, _offset).y = __privateGet$1(this, _spherical).radius * Math.cos(__privateGet$1(this, _spherical).phi); - __privateGet$1(this, _offset).z = sinPhiRadius * Math.cos(__privateGet$1(this, _spherical).theta); - this.camera.position.copy(this.target).add(__privateGet$1(this, _offset)); - }; - _rotate = new WeakSet(); - rotate_fn = function(x, y) { - tempVec2a.set(x, y); - tempVec2b.copy(tempVec2a).sub(__privateGet$1(this, _rotateStart)).multiplyScalar(this.rotateSpeed); - __privateGet$1(this, _spherical).theta -= 2 * Math.PI * tempVec2b.x / this.camera.size.height; - __privateGet$1(this, _spherical).phi -= 2 * Math.PI * tempVec2b.y / this.camera.size.height; - __privateGet$1(this, _spherical).theta = Math.min(this.maxAzimuthAngle, Math.max(this.minAzimuthAngle, __privateGet$1(this, _spherical).theta)); - __privateGet$1(this, _spherical).phi = Math.min(this.maxPolarAngle, Math.max(this.minPolarAngle, __privateGet$1(this, _spherical).phi)); - __privateGet$1(this, _rotateStart).copy(tempVec2a); - __privateMethod$1(this, _update, update_fn).call(this); - }; - _pan = new WeakSet(); - pan_fn = function(x, y) { - tempVec2a.set(x, y); - tempVec2b.copy(tempVec2a).sub(__privateGet$1(this, _panStart)).multiplyScalar(this.panSpeed); - __privateGet$1(this, _panDelta).set(0); - tempVec3.copy(this.camera.position).sub(this.target); - let targetDistance = tempVec3.length(); - targetDistance *= Math.tan(this.camera.fov / 2 * Math.PI / 180); - tempVec3.set( - this.camera.modelMatrix.elements[0], - this.camera.modelMatrix.elements[1], - this.camera.modelMatrix.elements[2] - ); - tempVec3.multiplyScalar(-(2 * tempVec2b.x * targetDistance) / this.camera.size.height); - __privateGet$1(this, _panDelta).add(tempVec3); - tempVec3.set( - this.camera.modelMatrix.elements[4], - this.camera.modelMatrix.elements[5], - this.camera.modelMatrix.elements[6] - ); - tempVec3.multiplyScalar(2 * tempVec2b.y * targetDistance / this.camera.size.height); - __privateGet$1(this, _panDelta).add(tempVec3); - __privateGet$1(this, _panStart).copy(tempVec2a); - this.target.add(__privateGet$1(this, _panDelta)); - __privateGet$1(this, _offset).copy(this.camera.position).sub(this.target); - __privateGet$1(this, _spherical).radius = __privateGet$1(this, _offset).length(); - __privateMethod$1(this, _update, update_fn).call(this); - }; - _zoom = new WeakSet(); - zoom_fn = function(value) { - __privateGet$1(this, _spherical).radius = Math.min( - this.maxZoom, - Math.max(this.minZoom + 1e-6, __privateGet$1(this, _spherical).radius + value * this.zoomSpeed / 100) + } + _copyComputeStorageTextureToTexture = new WeakSet(); + copyComputeStorageTextureToTexture_fn = function(commandEncoder, storageTexture, texture) { + commandEncoder.copyTextureToTexture( + { + texture: storageTexture.texture + }, + { + texture: texture.texture + }, + [texture.texture.width, texture.texture.height, texture.texture.depthOrArrayLayers] ); - __privateMethod$1(this, _update, update_fn).call(this); }; class BoxGeometry extends IndexedGeometry { @@ -16175,7 +18288,7 @@ fn getIBL( if (!member.has(obj)) throw TypeError("Cannot " + msg); }; - var __privateGet = (obj, member, getter) => { + var __privateGet$1 = (obj, member, getter) => { __accessCheck$1(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; @@ -16184,9 +18297,338 @@ fn getIBL( throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; - var __privateSet = (obj, member, value, setter) => { + var __privateSet$1 = (obj, member, value, setter) => { __accessCheck$1(obj, member, "write to private field"); - setter ? setter.call(obj, value) : member.set(obj, value); + member.set(obj, value); + return value; + }; + var __privateMethod = (obj, member, method) => { + __accessCheck$1(obj, member, "access private method"); + return method; + }; + var _localRay, _v0, _v1, _v2, _edge1, _edge2, _uv0, _uv1, _uv2, _n0, _n1, _n2, _intersectMesh, intersectMesh_fn; + class Raycaster { + /** + * Raycaster constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Raycaster} + */ + constructor(renderer) { + /** + * Test whether the {@link ray} is intersecting a given {@link ProjectedMesh | projected mesh} and if so, returns the given {@link Intersection | intersection} information. + * Uses various early exits to optimize the process: + * - if the mesh is frustum culled + * - if the pointer is currently outside the mesh clip space bounding rectangle. + * - based on the face culling. + * @param mesh - {@link ProjectedMesh | Projected mesh} to test against. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + * @private + */ + __privateAdd$1(this, _intersectMesh); + /** @ignore */ + __privateAdd$1(this, _localRay, void 0); + /** @ignore */ + __privateAdd$1(this, _v0, void 0); + /** @ignore */ + __privateAdd$1(this, _v1, void 0); + /** @ignore */ + __privateAdd$1(this, _v2, void 0); + /** @ignore */ + __privateAdd$1(this, _edge1, void 0); + /** @ignore */ + __privateAdd$1(this, _edge2, void 0); + /** @ignore */ + __privateAdd$1(this, _uv0, void 0); + /** @ignore */ + __privateAdd$1(this, _uv1, void 0); + /** @ignore */ + __privateAdd$1(this, _uv2, void 0); + /** @ignore */ + __privateAdd$1(this, _n0, void 0); + /** @ignore */ + __privateAdd$1(this, _n1, void 0); + /** @ignore */ + __privateAdd$1(this, _n2, void 0); + this.type = "Raycaster"; + renderer = isCameraRenderer(renderer, this.type); + this.renderer = renderer; + this.camera = this.renderer.camera; + this.pointer = new Vec2(Infinity); + this.ray = { + origin: new Vec3(), + direction: new Vec3() + }; + __privateSet$1(this, _localRay, { + origin: this.ray.origin.clone(), + direction: this.ray.direction.clone() + }); + __privateSet$1(this, _v0, new Vec3()); + __privateSet$1(this, _v1, new Vec3()); + __privateSet$1(this, _v2, new Vec3()); + __privateSet$1(this, _edge1, new Vec3()); + __privateSet$1(this, _edge2, new Vec3()); + __privateSet$1(this, _uv0, new Vec2()); + __privateSet$1(this, _uv1, new Vec2()); + __privateSet$1(this, _uv2, new Vec2()); + __privateSet$1(this, _n0, new Vec3()); + __privateSet$1(this, _n1, new Vec3()); + __privateSet$1(this, _n2, new Vec3()); + } + /** + * Set the {@link pointer} normalized device coordinates values (in the [-1, 1] range) based on a mouse/pointer/touch event and the {@link CameraRenderer#boundingRect | renderer bounding rectangle}. Useful if the canvas has a fixed position for example, but you might need to directly use {@link setFromNDCCoords} if not. + * @param e - Mouse, pointer or touch event. + */ + setFromMouse(e) { + const { clientX, clientY } = e.targetTouches && e.targetTouches.length ? e.targetTouches[0] : e; + this.setFromNDCCoords( + (clientX - this.renderer.boundingRect.left) / this.renderer.boundingRect.width * 2 - 1, + -((clientY - this.renderer.boundingRect.top) / this.renderer.boundingRect.height) * 2 + 1 + ); + } + /** + * Set the {@link pointer} normalized device coordinates (in the [-1, 1] range). + * @param x - input position along the X axis in the [-1, 1] range where `-1` represents the left edge and `1` the right edge. + * @param y - input position along the Y axis in the [-1, 1] range where `-1` represents the bottom edge and `1` the top edge. + */ + setFromNDCCoords(x = 0, y = 0) { + this.pointer.set(x, y); + this.setRay(); + } + /** + * Sets the {@link ray} origin and direction based on the {@link camera} and the normalized device coordinates of the {@link pointer}. + */ + setRay() { + this.camera.worldMatrix.getTranslation(this.ray.origin); + this.ray.direction.set(this.pointer.x, this.pointer.y, -1).unproject(this.camera).sub(this.ray.origin).normalize(); + } + // INTERSECTIONS + /** + * Ray-Triangle Intersection with Möller–Trumbore Algorithm. + * @param intersectionPoint - {@link Vec3} to store the intersection point if any. + * @returns - Whether an intersection point has been found or not. + */ + rayIntersectsTriangle(intersectionPoint) { + const EPSILON = 1e-6; + const h = new Vec3(); + const q = new Vec3(); + h.crossVectors(__privateGet$1(this, _localRay).direction, __privateGet$1(this, _edge2)); + const a = __privateGet$1(this, _edge1).dot(h); + if (Math.abs(a) < EPSILON) + return false; + const f = 1 / a; + const s = __privateGet$1(this, _localRay).origin.clone().sub(__privateGet$1(this, _v0)); + const u = f * s.dot(h); + if (u < 0 || u > 1) + return false; + q.crossVectors(s, __privateGet$1(this, _edge1)); + const v = f * __privateGet$1(this, _localRay).direction.dot(q); + if (v < 0 || u + v > 1) + return false; + const t = f * __privateGet$1(this, _edge2).dot(q); + if (t > EPSILON) { + intersectionPoint.copy(__privateGet$1(this, _localRay).origin).add(__privateGet$1(this, _localRay).direction.clone().multiplyScalar(t)); + return true; + } + return false; + } + /** + * Find the barycentric contributions of a given intersection point lying inside our current triangle. + * @param intersectionPoint - Given {@link Vec3 | intersection point}. + * @returns - {@link Vec3} barycentric contributions. + */ + getBarycentricCoordinates(intersectionPoint) { + const v0p = intersectionPoint.clone().sub(__privateGet$1(this, _v0)); + const d00 = __privateGet$1(this, _edge1).dot(__privateGet$1(this, _edge1)); + const d01 = __privateGet$1(this, _edge1).dot(__privateGet$1(this, _edge2)); + const d11 = __privateGet$1(this, _edge2).dot(__privateGet$1(this, _edge2)); + const d20 = v0p.dot(__privateGet$1(this, _edge1)); + const d21 = v0p.dot(__privateGet$1(this, _edge2)); + const denom = d00 * d11 - d01 * d01; + const barycentric = new Vec3(0, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); + barycentric.x = 1 - barycentric.y - barycentric.z; + return barycentric; + } + /** + * Get a rough estimation of the current normal of our current triangle, in local space. + * @returns - {@link Vec3} normal. + */ + getTriangleNormal() { + return new Vec3().crossVectors(__privateGet$1(this, _edge1), __privateGet$1(this, _edge2)).normalize(); + } + /** + * Set our input vector with the desired attribute value at the given offset defined by our triangleIndex, offset and whether we're using and indexed geometry or not. + * @param triangleIndex - Index of the triangle for which to look our attribute value. + * @param offset - Index of the point inside our triangle (`0`, `1` or `2`). + * @param indices - Indexed geometry array if defined or `null`. + * @param attribute - {@link VertexBufferAttribute | Vertex buffer attribute} to get the value from. + * @param vector - Input vector to set (can either be a {@link Vec2} or {@link Vec3}). + */ + setAttributeVectorAtIndex(triangleIndex, offset, indices, attribute, vector) { + const index = indices ? indices[triangleIndex * 3 + offset] : triangleIndex * 3 + offset; + vector.x = attribute.array[index * attribute.size]; + vector.y = attribute.array[index * attribute.size + 1]; + if ("z" in vector) { + vector.z = attribute.array[index * attribute.size + 2]; + } + } + /** + * Test whether the {@link ray} is intersecting a given object, if the is object is actually a {@link ProjectedMesh | projected mesh}. + * Then, if the recursive flag is set to `true`, test if the {@link Object3D#children | object's children} are intersecting as well. + * @param object - {@link Object3D | object} to test against. + * @param recursive - Whether we should also test against the {@link Object3D#children | object's children}. Default to `true`. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + */ + intersectObject(object, recursive = true, intersections = []) { + if (!(object instanceof Object3D)) { + if (!this.renderer.production) { + throwWarning(`${this.type}: object to test intersection again is not of type Object3D`); + } + return intersections; + } + const mesh = isProjectedMesh(object); + if (mesh) { + __privateMethod(this, _intersectMesh, intersectMesh_fn).call(this, mesh, intersections); + } + if (recursive) { + object.children.forEach((child) => { + this.intersectObject(child, recursive, intersections); + }); + } + if (intersections.length) { + intersections.sort((a, b) => { + return this.ray.origin.distance(a.point) - this.ray.origin.distance(b.point); + }); + } + return intersections; + } + /** + * Test whether the {@link ray} is intersecting a given array of objects. + * If the recursive flag is set to `true`, test if each {@link Object3D#children | object's children} are intersecting as well. + * @param objects - Array of {@link Object3D | objects} to test against. + * @param recursive - Whether we should also test against each {@link Object3D#children | object's children}. Default to `true`. + * @param intersections - Already existing {@link Intersection | intersections} if any. + * @returns - Updated {@link Intersection | intersections}. + */ + intersectObjects(objects, recursive = true, intersections = []) { + objects.forEach((object) => { + this.intersectObject(object, recursive, intersections); + }); + if (intersections.length) { + intersections.sort((a, b) => { + return this.ray.origin.distance(a.point) - this.ray.origin.distance(b.point); + }); + } + return intersections; + } + } + _localRay = new WeakMap(); + _v0 = new WeakMap(); + _v1 = new WeakMap(); + _v2 = new WeakMap(); + _edge1 = new WeakMap(); + _edge2 = new WeakMap(); + _uv0 = new WeakMap(); + _uv1 = new WeakMap(); + _uv2 = new WeakMap(); + _n0 = new WeakMap(); + _n1 = new WeakMap(); + _n2 = new WeakMap(); + _intersectMesh = new WeakSet(); + intersectMesh_fn = function(mesh, intersections = []) { + if (!mesh.geometry) + return intersections; + const position = mesh.geometry.getAttributeByName("position"); + if (!position) { + if (!this.renderer.production) { + throwWarning(`Raycaster: can't raycast on a mesh that has no position attribute: ${mesh.options.label}`); + } + return intersections; + } + if (!position.array) { + if (!this.renderer.production) { + throwWarning(`Raycaster: can't raycast on a mesh that has no position attribute array: ${mesh.options.label}`); + } + return intersections; + } + if (mesh.frustumCulling && mesh.domFrustum) { + const { clipSpaceBoundingRect } = mesh.domFrustum; + if (!mesh.domFrustum.isIntersecting) { + return intersections; + } else if (this.pointer.x > clipSpaceBoundingRect.left + clipSpaceBoundingRect.width || this.pointer.x < clipSpaceBoundingRect.left || this.pointer.y > clipSpaceBoundingRect.top || this.pointer.y < clipSpaceBoundingRect.top - clipSpaceBoundingRect.height) { + return intersections; + } + } + const inverseModelMatrix = mesh.worldMatrix.getInverse(); + __privateGet$1(this, _localRay).origin.copy(this.ray.origin).applyMat4(inverseModelMatrix); + __privateGet$1(this, _localRay).direction.copy(this.ray.direction).transformDirection(inverseModelMatrix); + const uv = mesh.geometry.getAttributeByName("uv"); + const normal = mesh.geometry.getAttributeByName("normal"); + const indices = mesh.geometry.indexBuffer?.array; + const triangleCount = indices ? indices.length / 3 : position.array.length / 9; + for (let i = 0; i < triangleCount; i++) { + this.setAttributeVectorAtIndex(i, 0, indices, position, __privateGet$1(this, _v0)); + this.setAttributeVectorAtIndex(i, 1, indices, position, __privateGet$1(this, _v1)); + this.setAttributeVectorAtIndex(i, 2, indices, position, __privateGet$1(this, _v2)); + __privateGet$1(this, _edge1).copy(__privateGet$1(this, _v1)).sub(__privateGet$1(this, _v0)); + __privateGet$1(this, _edge2).copy(__privateGet$1(this, _v2)).sub(__privateGet$1(this, _v0)); + if (mesh.material.options.rendering.cullMode !== "none") { + const computedNormal = this.getTriangleNormal(); + const faceDirection = computedNormal.dot(__privateGet$1(this, _localRay).direction); + if (faceDirection > 0 && mesh.material.options.rendering.cullMode === "back") { + continue; + } else if (faceDirection < 0 && mesh.material.options.rendering.cullMode === "front") { + continue; + } + } + const intersectionPoint = new Vec3(); + const isIntersected = this.rayIntersectsTriangle(intersectionPoint); + if (isIntersected) { + const barycentric = this.getBarycentricCoordinates(intersectionPoint); + const point = intersectionPoint.clone().applyMat4(mesh.worldMatrix); + const distance = this.ray.origin.distance(point); + const intersection = { + object: mesh, + distance, + localPoint: intersectionPoint, + point, + triangle: [__privateGet$1(this, _v0).clone(), __privateGet$1(this, _v1).clone(), __privateGet$1(this, _v2).clone()], + triangleIndex: i + }; + if (uv && uv.array && uv.array.length) { + this.setAttributeVectorAtIndex(i, 0, indices, uv, __privateGet$1(this, _uv0)); + this.setAttributeVectorAtIndex(i, 1, indices, uv, __privateGet$1(this, _uv1)); + this.setAttributeVectorAtIndex(i, 2, indices, uv, __privateGet$1(this, _uv2)); + intersection.uv = __privateGet$1(this, _uv0).clone().multiplyScalar(barycentric.x).add(__privateGet$1(this, _uv1).clone().multiplyScalar(barycentric.y)).add(__privateGet$1(this, _uv2).clone().multiplyScalar(barycentric.z)); + } + if (normal && normal.array && normal.array.length) { + this.setAttributeVectorAtIndex(i, 0, indices, normal, __privateGet$1(this, _n0)); + this.setAttributeVectorAtIndex(i, 1, indices, normal, __privateGet$1(this, _n1)); + this.setAttributeVectorAtIndex(i, 2, indices, normal, __privateGet$1(this, _n2)); + intersection.normal = __privateGet$1(this, _n0).clone().multiplyScalar(barycentric.x).add(__privateGet$1(this, _n1).clone().multiplyScalar(barycentric.y)).add(__privateGet$1(this, _n2).clone().multiplyScalar(barycentric.z)); + } + intersections.push(intersection); + } + } + return intersections; + }; + + var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); + }; + var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); + }; + var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + }; + var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + member.set(obj, value); return value; }; var _primitiveInstances; @@ -16201,7 +18643,7 @@ fn getIBL( */ constructor({ renderer, gltf }) { /** The {@link PrimitiveInstances} Map, to group similar {@link Mesh} by instances. */ - __privateAdd$1(this, _primitiveInstances, void 0); + __privateAdd(this, _primitiveInstances, void 0); renderer = isCameraRenderer(renderer, "GLTFScenesManager"); this.renderer = renderer; this.gltf = gltf; @@ -17052,14 +19494,17 @@ fn getIBL( } let { chunks } = shaderParameters || {}; const { iblParameters } = shaderParameters || {}; - const { lutTexture, envDiffuseTexture, envSpecularTexture } = iblParameters || {}; - const useIBLContribution = envDiffuseTexture && envDiffuseTexture.texture && envSpecularTexture && envSpecularTexture.texture && lutTexture && lutTexture.texture; - if (useIBLContribution && shadingModel === "IBL") { + const { environmentMap } = iblParameters || {}; + if (environmentMap && shadingModel === "IBL") { meshDescriptor.parameters.uniforms = { ...meshDescriptor.parameters.uniforms, ...{ ibl: { struct: { + envRotation: { + type: "mat3x3f", + value: environmentMap.rotation + }, diffuseStrength: { type: "f32", value: iblParameters?.diffuseStrength ?? 0.5 @@ -17074,17 +19519,13 @@ fn getIBL( }; meshDescriptor.parameters.textures = [ ...meshDescriptor.parameters.textures, - lutTexture.texture, - envDiffuseTexture.texture, - envSpecularTexture.texture + environmentMap.lutTexture, + environmentMap.diffuseTexture, + environmentMap.specularTexture ]; - lutTexture.samplerName = lutTexture.samplerName || "defaultSampler"; - envDiffuseTexture.samplerName = envDiffuseTexture.samplerName || "defaultSampler"; - envSpecularTexture.samplerName = envSpecularTexture.samplerName || "defaultSampler"; + meshDescriptor.parameters.samplers = [...meshDescriptor.parameters.samplers, environmentMap.sampler]; } else if (shadingModel === "IBL") { - throwWarning( - "IBL shading requested but one of the LUT, environment specular or diffuse texture is missing. Defaulting to PBR shading." - ); + throwWarning("IBL shading requested but the environment map missing. Defaulting to PBR shading."); shadingModel = "PBR"; } const shadingOptions = { @@ -17196,12 +19637,10 @@ fn getIBL( f0, metallic, roughness, - ${lutTexture.texture.options.name}, - ${lutTexture.samplerName}, - ${envSpecularTexture.texture.options.name}, - ${envSpecularTexture.samplerName}, - ${envDiffuseTexture.texture.options.name}, - ${envDiffuseTexture.samplerName}, + ${environmentMap.sampler.name}, + ${environmentMap.lutTexture.options.name}, + ${environmentMap.specularTexture.options.name}, + ${environmentMap.diffuseTexture.options.name}, occlusion ), color.a @@ -17243,256 +19682,21 @@ fn getIBL( // user defined additional color contribution ${chunks.additionalColorContribution} - - ${returnColor} - } - ` - ); - return { - vertex: { - code: vs, - entryPoint: "main" - }, - fragment: { - code: fs, - entryPoint: "main" - } - }; - }; - const computeDiffuseFromSpecular = async (renderer, diffuseTexture, specularTexture) => { - if (specularTexture.options.viewDimension !== "cube") { - throwWarning( - "Could not compute the diffuse texture because the specular texture is not a cube map:" + specularTexture.options.viewDimension - ); - return; - } - const computeDiffuseShader = ` - fn radicalInverse_VdC(inputBits: u32) -> f32 { - var bits: u32 = inputBits; - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 - } - - // hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 - // that can be used for quasi Monte Carlo integration - fn hammersley2d(i: u32, N: u32) -> vec2f { - return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); - } - - // TBN generates a tangent bitangent normal coordinate frame from the normal - // (the normal must be normalized) - fn generateTBN(normal: vec3f) -> mat3x3f { - var bitangent: vec3f = vec3(0.0, 1.0, 0.0); - - let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); - let epsilon: f32 = 0.0000001; - - if (1.0 - abs(NdotUp) <= epsilon) { - // Sampling +Y or -Y, so we need a more robust bitangent. - if (NdotUp > 0.0) { - bitangent = vec3(0.0, 0.0, 1.0); - } - else { - bitangent = vec3(0.0, 0.0, -1.0); - } - } - - let tangent: vec3f = normalize(cross(bitangent, normal)); - bitangent = cross(normal, tangent); - - return mat3x3f(tangent, bitangent, normal); - } - - // Mipmap Filtered Samples (GPU Gems 3, 20.4) - // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - fn computeLod(pdf: f32) -> f32 { - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); - } - - fn transformDirection(face: u32, uv: vec2f) -> vec3f { - // Transform the direction based on the cubemap face - switch (face) { - case 0u { - // +X - return vec3f( 1.0, uv.y, -uv.x); - } - case 1u { - // -X - return vec3f(-1.0, uv.y, uv.x); - } - case 2u { - // +Y - return vec3f( uv.x, -1.0, uv.y); - } - case 3u { - // -Y - return vec3f( uv.x, 1.0, -uv.y); - } - case 4u { - // +Z - return vec3f( uv.x, uv.y, 1.0); - } - case 5u { - // -Z - return vec3f(-uv.x, uv.y, -1.0); - } - default { - return vec3f(0.0, 0.0, 0.0); - } - } - } - - const PI = ${Math.PI}; - - @compute @workgroup_size(8, 8, 1) fn main( - @builtin(global_invocation_id) GlobalInvocationID: vec3u, - ) { - let faceSize: u32 = params.faceSize; - let sampleCount: u32 = params.sampleCount; - - let face: u32 = GlobalInvocationID.z; - let x: u32 = GlobalInvocationID.x; - let y: u32 = GlobalInvocationID.y; - - if (x >= faceSize || y >= faceSize) { - return; - } - - let texelSize: f32 = 1.0 / f32(faceSize); - let halfTexel: f32 = texelSize * 0.5; - - var uv: vec2f = vec2( - (f32(x) + halfTexel) * texelSize, - (f32(y) + halfTexel) * texelSize - ); - - uv = uv * 2.0 - 1.0; - - let normal: vec3 = transformDirection(face, uv); - - var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); - - for (var i: u32 = 0; i < sampleCount; i++) { - // generate a quasi monte carlo point in the unit square [0.1)^2 - let xi: vec2f = hammersley2d(i, sampleCount); - - let cosTheta: f32 = sqrt(1.0 - xi.y); - let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); - let phi: f32 = 2.0 * PI * xi.x; - let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta - - let sampleVec: vec3f = vec3f( - sinTheta * cos(phi), - sinTheta * sin(phi), - cosTheta - ); - - let TBN: mat3x3f = generateTBN(normalize(normal)); - - var direction: vec3f = TBN * sampleVec; - - // invert along Y axis - direction.y *= -1.0; - - let lod: f32 = computeLod(pdf); - - // Convert sampleVec to texture coordinates of the specular env map - irradiance += textureSampleLevel( - envSpecularTexture, - specularSampler, - direction, - min(lod, f32(params.maxMipLevel)) - ).rgb; - } - - irradiance /= f32(sampleCount); - - textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); + + ${returnColor} } - `; - let diffuseStorageTexture = new Texture(renderer, { - label: "Diffuse storage cubemap", - name: "diffuseEnvMap", - format: "rgba32float", - visibility: ["compute"], - usage: ["copySrc", "storageBinding"], - type: "storage", - fixedSize: { - width: specularTexture.size.width, - height: specularTexture.size.height, - depth: 6 - }, - viewDimension: "2d-array" - }); - const sampler = new Sampler(renderer, { - label: "Compute diffuse sampler", - name: "specularSampler", - addressModeU: "clamp-to-edge", - addressModeV: "clamp-to-edge", - minFilter: "linear", - magFilter: "linear" - }); - let computeDiffusePass = new ComputePass(renderer, { - autoRender: false, - // we're going to render only on demand - dispatchSize: [Math.ceil(specularTexture.size.width / 8), Math.ceil(specularTexture.size.height / 8), 6], - shaders: { - compute: { - code: computeDiffuseShader - } - }, - uniforms: { - params: { - struct: { - faceSize: { - type: "u32", - value: specularTexture.size.width - }, - maxMipLevel: { - type: "u32", - value: specularTexture.texture.mipLevelCount - }, - sampleCount: { - type: "u32", - value: 2048 - } - } - } - }, - samplers: [sampler], - textures: [specularTexture, diffuseStorageTexture] - }); - await computeDiffusePass.material.compileMaterial(); - renderer.onBeforeRenderScene.add( - (commandEncoder) => { - renderer.renderSingleComputePass(commandEncoder, computeDiffusePass); - commandEncoder.copyTextureToTexture( - { - texture: diffuseStorageTexture.texture - }, - { - texture: diffuseTexture.texture - }, - [diffuseTexture.texture.width, diffuseTexture.texture.height, diffuseTexture.texture.depthOrArrayLayers] - ); - }, - { once: true } + ` ); - renderer.onAfterCommandEncoderSubmission.add( - () => { - computeDiffusePass.destroy(); - diffuseStorageTexture.destroy(); - diffuseStorageTexture = null; - computeDiffusePass = null; + return { + vertex: { + code: vs, + entryPoint: "main" }, - { once: true } - ); + fragment: { + code: fs, + entryPoint: "main" + } + }; }; const GL = WebGLRenderingContext; @@ -17662,320 +19866,6 @@ fn getIBL( } } - var __accessCheck = (obj, member, msg) => { - if (!member.has(obj)) - throw TypeError("Cannot " + msg); - }; - var __privateAdd = (obj, member, value) => { - if (member.has(obj)) - throw TypeError("Cannot add the same private member more than once"); - member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - }; - var __privateMethod = (obj, member, method) => { - __accessCheck(obj, member, "access private method"); - return method; - }; - var _decodeRGBE, decodeRGBE_fn, _parseHeader, parseHeader_fn, _parseSize, parseSize_fn, _readLine, readLine_fn, _parseData, parseData_fn, _parseNewRLE, parseNewRLE_fn, _swap, swap_fn, _flipX, flipX_fn, _flipY, flipY_fn; - class HDRLoader { - constructor() { - /** - * @ignore - */ - __privateAdd(this, _decodeRGBE); - /** - * @ignore - */ - __privateAdd(this, _parseHeader); - /** - * @ignore - */ - __privateAdd(this, _parseSize); - /** - * @ignore - */ - __privateAdd(this, _readLine); - /** - * @ignore - */ - __privateAdd(this, _parseData); - /** - * @ignore - */ - __privateAdd(this, _parseNewRLE); - /** - * @ignore - */ - __privateAdd(this, _swap); - /** - * @ignore - */ - __privateAdd(this, _flipX); - /** - * @ignore - */ - __privateAdd(this, _flipY); - } - /** - * Load and decode RGBE-encoded data to a flat list of floating point pixel data (RGBA). - * @param url - The url of the .hdr file to load - * @returns - The {@link HDRImageData} - */ - async loadFromUrl(url) { - const buffer = await (await fetch(url)).arrayBuffer(); - return __privateMethod(this, _decodeRGBE, decodeRGBE_fn).call(this, new DataView(buffer)); - } - /** - * Convert an equirectangular {@link HDRImageData} to 6 {@link HDRImageData} cube map faces. Works but can display artifacts at the poles. - * @param parsedHdr - equirectangular {@link HDRImageData} to use. - * @returns - 6 {@link HDRImageData} cube map faces - */ - equirectangularToCubeMap(parsedHdr) { - const faceSize = Math.max(parsedHdr.width / 4, parsedHdr.height / 2); - const faces = { - posX: new Float32Array(faceSize * faceSize * 4), - negX: new Float32Array(faceSize * faceSize * 4), - posY: new Float32Array(faceSize * faceSize * 4), - negY: new Float32Array(faceSize * faceSize * 4), - posZ: new Float32Array(faceSize * faceSize * 4), - negZ: new Float32Array(faceSize * faceSize * 4) - }; - function getPixel(u, v) { - const x = Math.floor(u * parsedHdr.width); - const y = Math.floor(v * parsedHdr.height); - const index = (y * parsedHdr.width + x) * 4; - return [parsedHdr.data[index], parsedHdr.data[index + 1], parsedHdr.data[index + 2], parsedHdr.data[index + 3]]; - } - function setPixel(face, x, y, pixel) { - const index = (y * faceSize + x) * 4; - faces[face][index] = pixel[0]; - faces[face][index + 1] = pixel[1]; - faces[face][index + 2] = pixel[2]; - faces[face][index + 3] = pixel[3]; - } - function mapDirection(face, x, y) { - const a = 2 * (x + 0.5) / faceSize - 1; - const b = 2 * (y + 0.5) / faceSize - 1; - switch (face) { - case "posX": - return [a, -1, -b]; - case "negX": - return [-a, 1, -b]; - case "posY": - return [-b, -a, 1]; - case "negY": - return [b, -a, -1]; - case "posZ": - return [-1, -a, -b]; - case "negZ": - return [1, a, -b]; - } - } - function directionToUV(direction) { - const [x, y, z] = direction; - const r = Math.sqrt(x * x + y * y); - const theta = Math.atan2(y, x); - const phi = Math.atan2(z, r); - const u = (theta + Math.PI) / (2 * Math.PI); - const v = (phi + Math.PI / 2) / Math.PI; - return [u, v]; - } - for (const face in faces) { - for (let y = 0; y < faceSize; y++) { - for (let x = 0; x < faceSize; x++) { - const direction = mapDirection(face, x, y); - const [u, v] = directionToUV(direction); - const pixel = getPixel(u, v); - setPixel(face, x, y, pixel); - } - } - } - const facesData = [faces.posX, faces.negX, faces.posY, faces.negY, faces.posZ, faces.negZ]; - return facesData.map((faceData) => { - return { - data: faceData, - width: faceSize, - height: faceSize, - exposure: parsedHdr.exposure, - gamma: parsedHdr.gamma - }; - }); - } - } - _decodeRGBE = new WeakSet(); - decodeRGBE_fn = function(data) { - const stream = { - data, - offset: 0 - }; - const header = __privateMethod(this, _parseHeader, parseHeader_fn).call(this, stream); - return { - width: header.width, - height: header.height, - exposure: header.exposure, - gamma: header.gamma, - data: __privateMethod(this, _parseData, parseData_fn).call(this, stream, header) - }; - }; - _parseHeader = new WeakSet(); - parseHeader_fn = function(stream) { - let line = __privateMethod(this, _readLine, readLine_fn).call(this, stream); - const header = { - colorCorr: [1, 1, 1], - exposure: 1, - gamma: 1, - width: 0, - height: 0, - flipX: false, - flipY: false - }; - if (line !== "#?RADIANCE" && line !== "#?RGBE") - throw new Error("Incorrect file format!"); - while (line !== "") { - line = __privateMethod(this, _readLine, readLine_fn).call(this, stream); - const parts2 = line.split("="); - switch (parts2[0]) { - case "GAMMA": - header.gamma = parseFloat(parts2[1]); - break; - case "FORMAT": - if (parts2[1] !== "32-bit_rle_rgbe" && parts2[1] !== "32-bit_rle_xyze") - throw new Error("Incorrect encoding format!"); - break; - case "EXPOSURE": - header.exposure = parseFloat(parts2[1]); - break; - case "COLORCORR": - header.colorCorr = parts2[1].replace(/^\s+|\s+$/g, "").split(" ").map((m) => parseFloat(m)); - break; - } - } - line = __privateMethod(this, _readLine, readLine_fn).call(this, stream); - const parts = line.split(" "); - __privateMethod(this, _parseSize, parseSize_fn).call(this, parts[0], parseInt(parts[1]), header); - __privateMethod(this, _parseSize, parseSize_fn).call(this, parts[2], parseInt(parts[3]), header); - return header; - }; - _parseSize = new WeakSet(); - parseSize_fn = function(label, value, header) { - switch (label) { - case "+X": - header.width = value; - break; - case "-X": - header.width = value; - header.flipX = true; - console.warn("Flipping horizontal orientation not currently supported"); - break; - case "-Y": - header.height = value; - header.flipY = true; - break; - case "+Y": - header.height = value; - break; - } - }; - _readLine = new WeakSet(); - readLine_fn = function(stream) { - let ch, str = ""; - while ((ch = stream.data.getUint8(stream.offset++)) !== 10) - str += String.fromCharCode(ch); - return str; - }; - _parseData = new WeakSet(); - parseData_fn = function(stream, header) { - const hash = stream.data.getUint16(stream.offset); - let data; - if (hash === 514) { - data = __privateMethod(this, _parseNewRLE, parseNewRLE_fn).call(this, stream, header); - if (header.flipX) - __privateMethod(this, _flipX, flipX_fn).call(this, data, header); - if (header.flipY) - __privateMethod(this, _flipY, flipY_fn).call(this, data, header); - } else { - throw new Error("Obsolete HDR file version!"); - } - return data; - }; - _parseNewRLE = new WeakSet(); - parseNewRLE_fn = function(stream, header) { - const { width, height, colorCorr } = header; - const tgt = new Float32Array(width * height * 4); - let i = 0; - let { offset, data } = stream; - for (let y = 0; y < height; ++y) { - if (data.getUint16(offset) !== 514) - throw new Error("Incorrect scanline start hash"); - if (data.getUint16(offset + 2) !== width) - throw new Error("Scanline doesn't match picture dimension!"); - offset += 4; - const numComps = width * 4; - const comps = []; - let x = 0; - while (x < numComps) { - let value = data.getUint8(offset++); - if (value > 128) { - const len = value - 128; - value = data.getUint8(offset++); - for (let rle = 0; rle < len; ++rle) { - comps[x++] = value; - } - } else { - for (let n = 0; n < value; ++n) { - comps[x++] = data.getUint8(offset++); - } - } - } - for (x = 0; x < width; ++x) { - const r = comps[x]; - const g = comps[x + width]; - const b = comps[x + width * 2]; - let e = comps[x + width * 3]; - e = e ? Math.pow(2, e - 136) : 0; - tgt[i++] = r * e * colorCorr[0]; - tgt[i++] = g * e * colorCorr[1]; - tgt[i++] = b * e * colorCorr[2]; - tgt[i++] = e; - } - } - return tgt; - }; - _swap = new WeakSet(); - swap_fn = function(data, i1, i2) { - i1 *= 4; - i2 *= 4; - for (let i = 0; i < 4; ++i) { - const tmp = data[i1 + i]; - data[i1 + i] = data[i2 + i]; - data[i2 + i] = tmp; - } - }; - _flipX = new WeakSet(); - flipX_fn = function(data, header) { - const { width, height } = header; - const hw = width >> 1; - for (let y = 0; y < height; ++y) { - const b = y * width; - for (let x = 0; x < hw; ++x) { - const i1 = b + x; - const i2 = b + width - 1 - x; - __privateMethod(this, _swap, swap_fn).call(this, data, i1, i2); - } - } - }; - _flipY = new WeakSet(); - flipY_fn = function(data, header) { - const { width, height } = header; - const hh = height >> 1; - for (let y = 0; y < hh; ++y) { - const b1 = y * width; - const b2 = (height - 1 - y) * width; - for (let x = 0; x < width; ++x) { - __privateMethod(this, _swap, swap_fn).call(this, data, b1 + x, b2 + x); - } - } - }; - const logSceneCommands = (renderer) => { const { scene } = renderer; if (!scene) @@ -18083,6 +19973,7 @@ fn getIBL( exports.DOMObject3D = DOMObject3D; exports.DOMTexture = DOMTexture; exports.DirectionalLight = DirectionalLight; + exports.EnvironmentMap = EnvironmentMap; exports.FullscreenPlane = FullscreenPlane; exports.GLTFLoader = GLTFLoader; exports.GLTFScenesManager = GLTFScenesManager; @@ -18108,6 +19999,8 @@ fn getIBL( exports.PointLight = PointLight; exports.ProjectedObject3D = ProjectedObject3D; exports.Quat = Quat; + exports.Raycaster = Raycaster; + exports.RenderBundle = RenderBundle; exports.RenderMaterial = RenderMaterial; exports.RenderPass = RenderPass; exports.RenderPipelineEntry = RenderPipelineEntry; @@ -18126,7 +20019,6 @@ fn getIBL( exports.applyDirectionalShadows = applyDirectionalShadows; exports.applyPointShadows = applyPointShadows; exports.buildShaders = buildShaders; - exports.computeDiffuseFromSpecular = computeDiffuseFromSpecular; exports.getDefaultPointShadowDepthFs = getDefaultPointShadowDepthFs; exports.getDefaultPointShadowDepthVs = getDefaultPointShadowDepthVs; exports.getDefaultShadowDepthVs = getDefaultShadowDepthVs; diff --git a/dist/gpu-curtains.umd.min.js b/dist/gpu-curtains.umd.min.js index 9939c39c8..af3c742f6 100644 --- a/dist/gpu-curtains.umd.min.js +++ b/dist/gpu-curtains.umd.min.js @@ -1,4 +1,4 @@ -(function(y,Y){typeof exports=="object"&&typeof module<"u"?Y(exports):typeof define=="function"&&define.amd?define(["exports"],Y):(y=typeof globalThis<"u"?globalThis:y||self,Y(y.window=y.window||{}))})(this,function(y){"use strict";const Y=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16).toUpperCase()}),We=n=>n.replace(/(?:^\w|[A-Z]|\b\w)/g,(e,t)=>t===0?e.toLowerCase():e.toUpperCase()).replace(/\s+/g,""),je=n=>{const e=We(n);return e.charAt(0).toUpperCase()+e.slice(1)};let ti=0;const I=n=>{ti>100||(console.warn(ti===100?"GPUCurtains: too many warnings thrown, stop logging.":n),ti++)},te=n=>{throw new Error(n)},ii=(n,e="GPURenderer",t)=>{const i=t?`Unable to create ${t} because the ${e} is not defined: ${n}`:`The ${e} is not defined: ${n}`;te(i)},j=(n,e)=>(n=n&&n.renderer||n,n&&(n.type==="GPURenderer"||n.type==="GPUCameraRenderer"||n.type==="GPUCurtainsRenderer")||ii(n,"GPURenderer",e),n),Le=(n,e)=>(n=n&&n.renderer||n,n&&(n.type==="GPUCameraRenderer"||n.type==="GPUCurtainsRenderer")||ii(n,"GPUCameraRenderer",e),n),si=(n,e)=>(n=n&&n.renderer||n,n&&n.type==="GPUCurtainsRenderer"||ii(n,"GPUCurtainsRenderer",e),n),ri=(()=>{let n,e;const t={};return function(s,r){e||(e=s.createShaderModule({label:"textured quad shaders for mip level generation",code:` +(function(x,H){typeof exports=="object"&&typeof module<"u"?H(exports):typeof define=="function"&&define.amd?define(["exports"],H):(x=typeof globalThis<"u"?globalThis:x||self,H(x.window=x.window||{}))})(this,function(x){"use strict";const H=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16).toUpperCase()}),st=n=>n.replace(/(?:^\w|[A-Z]|\b\w)/g,(e,t)=>t===0?e.toLowerCase():e.toUpperCase()).replace(/\s+/g,""),rt=n=>{const e=st(n);return e.charAt(0).toUpperCase()+e.slice(1)};let Li=0;const L=n=>{Li>100||(console.warn(Li===100?"GPUCurtains: too many warnings thrown, stop logging.":n),Li++)},te=n=>{throw new Error(n)},_i=(n,e="GPURenderer",t)=>{const i=t?`Unable to create ${t} because the ${e} is not defined: ${n}`:`The ${e} is not defined: ${n}`;te(i)},V=(n,e)=>(n=n&&n.renderer||n,n&&(n.type==="GPURenderer"||n.type==="GPUCameraRenderer"||n.type==="GPUCurtainsRenderer")||_i(n,"GPURenderer",e),n),Ae=(n,e)=>(n=n&&n.renderer||n,n&&(n.type==="GPUCameraRenderer"||n.type==="GPUCurtainsRenderer")||_i(n,"GPUCameraRenderer",e),n),Gi=(n,e)=>(n=n&&n.renderer||n,n&&n.type==="GPUCurtainsRenderer"||_i(n,"GPUCurtainsRenderer",e),n),Rn=n=>n.constructor.name==="Mesh"||n.constructor.name==="DOMMesh"||n.constructor.name==="Plane"?n:!1,Et=(()=>{let n,e;const t={};return function(s,r){e||(e=s.createShaderModule({label:"textured quad shaders for mip level generation",code:` struct VSOutput { @builtin(position) position: vec4f, @location(0) texcoord: vec2f, @@ -32,26 +32,80 @@ @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f { return textureSample(ourTexture, ourSampler, fsInput.texcoord); } - `}),n=s.createSampler({minFilter:"linear",magFilter:"linear"})),t[r.format]||(t[r.format]=s.createRenderPipeline({label:"Mip level generator pipeline",layout:"auto",vertex:{module:e},fragment:{module:e,targets:[{format:r.format}]}}));const o=t[r.format],a=s.createCommandEncoder({label:"Mip gen encoder"});let h=r.width,l=r.height,u=0;for(;h>1||l>1;){h=Math.max(1,h/2|0),l=Math.max(1,l/2|0);for(let c=0;cn.reduce((e,t)=>e|Or.get(t),0),Fr={i32:{numElements:1,align:4,size:4,type:"i32",View:Int32Array},u32:{numElements:1,align:4,size:4,type:"u32",View:Uint32Array},f32:{numElements:1,align:4,size:4,type:"f32",View:Float32Array},f16:{numElements:1,align:2,size:2,type:"u16",View:Uint16Array},vec2f:{numElements:2,align:8,size:8,type:"f32",View:Float32Array},vec2i:{numElements:2,align:8,size:8,type:"i32",View:Int32Array},vec2u:{numElements:2,align:8,size:8,type:"u32",View:Uint32Array},vec2h:{numElements:2,align:4,size:4,type:"u16",View:Uint16Array},vec3i:{numElements:3,align:16,size:12,type:"i32",View:Int32Array},vec3u:{numElements:3,align:16,size:12,type:"u32",View:Uint32Array},vec3f:{numElements:3,align:16,size:12,type:"f32",View:Float32Array},vec3h:{numElements:3,align:8,size:6,type:"u16",View:Uint16Array},vec4i:{numElements:4,align:16,size:16,type:"i32",View:Int32Array},vec4u:{numElements:4,align:16,size:16,type:"u32",View:Uint32Array},vec4f:{numElements:4,align:16,size:16,type:"f32",View:Float32Array},vec4h:{numElements:4,align:8,size:8,type:"u16",View:Uint16Array},mat2x2f:{numElements:4,align:8,size:16,type:"f32",View:Float32Array},mat2x2h:{numElements:4,align:4,size:8,type:"u16",View:Uint16Array},mat3x2f:{numElements:6,align:8,size:24,type:"f32",View:Float32Array},mat3x2h:{numElements:6,align:4,size:12,type:"u16",View:Uint16Array},mat4x2f:{numElements:8,align:8,size:32,type:"f32",View:Float32Array},mat4x2h:{numElements:8,align:4,size:16,type:"u16",View:Uint16Array},mat2x3f:{numElements:8,align:16,size:32,pad:[3,1],type:"f32",View:Float32Array},mat2x3h:{numElements:8,align:8,size:16,pad:[3,1],type:"u16",View:Uint16Array},mat3x3f:{numElements:12,align:16,size:48,pad:[3,1],type:"f32",View:Float32Array},mat3x3h:{numElements:12,align:8,size:24,pad:[3,1],type:"u16",View:Uint16Array},mat4x3f:{numElements:16,align:16,size:64,pad:[3,1],type:"f32",View:Float32Array},mat4x3h:{numElements:16,align:8,size:32,pad:[3,1],type:"u16",View:Uint16Array},mat2x4f:{numElements:8,align:16,size:32,type:"f32",View:Float32Array},mat2x4h:{numElements:8,align:8,size:16,type:"u16",View:Uint16Array},mat3x4f:{numElements:12,align:16,size:48,pad:[3,1],type:"f32",View:Float32Array},mat3x4h:{numElements:12,align:8,size:24,pad:[3,1],type:"u16",View:Uint16Array},mat4x4f:{numElements:16,align:16,size:64,type:"f32",View:Float32Array},mat4x4h:{numElements:16,align:8,size:32,type:"u16",View:Uint16Array}},hs=n=>Fr[n],at=n=>(()=>{switch(n.bindingType){case"storage":return`var<${n.bindingType}, ${n.options.access}>`;case"uniform":default:return"var"}})(),Ur=n=>n.bindingType==="externalTexture"?`var ${n.name}: texture_external;`:n.bindingType==="storage"?`var ${n.name}: texture_storage_${n.options.viewDimension.replace("-","_")}<${n.options.format}, ${n.options.access}>;`:n.bindingType==="depth"?`var ${n.name}: texture_depth${n.options.multisampled?"_multisampled":""}_${n.options.viewDimension.replace("-","_")};`:`var ${n.name}: texture${n.options.multisampled?"_multisampled":""}_${n.options.viewDimension.replace("-","_")};`,ls=n=>n.bindingType==="storage"&&n.options.access==="read_write"?"storage":n.bindingType==="storage"?"read-only-storage":"uniform",$r=n=>(()=>{switch(n.bindingType){case"externalTexture":return{externalTexture:{}};case"storage":return{storageTexture:{format:n.options.format,viewDimension:n.options.viewDimension}};case"texture":return{texture:{multisampled:n.options.multisampled,viewDimension:n.options.viewDimension,sampleType:n.options.multisampled?"unfilterable-float":"float"}};case"depth":return{texture:{multisampled:n.options.multisampled,viewDimension:n.options.viewDimension,sampleType:"depth"}};default:return null}})(),kr=n=>(()=>{switch(n.bindingType){case"externalTexture":return`externalTexture,${n.visibility},`;case"storage":return`storageTexture,${n.options.format},${n.options.viewDimension},${n.visibility},`;case"texture":return`texture,${n.options.multisampled},${n.options.viewDimension},${n.options.multisampled?"unfilterable-float":"float"},${n.visibility},`;case"depth":return`depthTexture,${n.options.format},${n.options.viewDimension},${n.visibility},`;default:return`${n.visibility},`}})();class ht{constructor({label:e="Uniform",name:t="uniform",bindingType:i="uniform",visibility:s=["vertex","fragment","compute"]}){this.label=e,this.name=We(t),this.bindingType=i,this.visibility=Dr(s),this.options={label:e,name:t,bindingType:i,visibility:s},this.shouldResetBindGroup=!1,this.shouldResetBindGroupLayout=!1,this.cacheKey=`${i},${this.visibility},`}}class E{constructor(e=0,t=e){this.type="Vec2",this._x=e,this._y=t}get x(){return this._x}set x(e){const t=e!==this._x;this._x=e,t&&this._onChangeCallback&&this._onChangeCallback()}get y(){return this._y}set y(e){const t=e!==this._y;this._y=e,t&&this._onChangeCallback&&this._onChangeCallback()}onChange(e){return e&&(this._onChangeCallback=e),this}set(e=0,t=e){return this.x=e,this.y=t,this}add(e=new E){return this.x+=e.x,this.y+=e.y,this}addScalar(e=0){return this.x+=e,this.y+=e,this}sub(e=new E){return this.x-=e.x,this.y-=e.y,this}subScalar(e=0){return this.x-=e,this.y-=e,this}multiply(e=new E(1)){return this.x*=e.x,this.y*=e.y,this}multiplyScalar(e=1){return this.x*=e,this.y*=e,this}divide(e=new E(1)){return this.x/=e.x,this.y/=e.y,this}divideScalar(e=1){return this.x/=e,this.y/=e,this}copy(e=new E){return this.x=e.x,this.y=e.y,this}clone(){return new E(this.x,this.y)}max(e=new E){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this}min(e=new E){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this}clamp(e=new E,t=new E){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this}equals(e=new E){return this.x===e.x&&this.y===e.y}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.lengthSq())}normalize(){let e=this.x*this.x+this.y*this.y;return e>0&&(e=1/Math.sqrt(e)),this.x*=e,this.y*=e,this}dot(e=new E){return this.x*e.x+this.y*e.y}lerp(e=new E,t=1){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this}}class le{constructor(e=new Float32Array([0,0,0,1]),t="XYZ"){this.type="Quat",this.elements=e,this.axisOrder=t}setFromArray(e=new Float32Array([0,0,0,1])){return this.elements[0]=e[0],this.elements[1]=e[1],this.elements[2]=e[2],this.elements[3]=e[3],this}setAxisOrder(e="XYZ"){switch(e=e.toUpperCase(),e){case"XYZ":case"YXZ":case"ZXY":case"ZYX":case"YZX":case"XZY":this.axisOrder=e;break;default:this.axisOrder="XYZ"}return this}copy(e=new le){return this.elements=e.elements,this.axisOrder=e.axisOrder,this}clone(){return new le().copy(this)}equals(e=new le){return this.elements[0]===e.elements[0]&&this.elements[1]===e.elements[1]&&this.elements[2]===e.elements[2]&&this.elements[3]===e.elements[3]&&this.axisOrder===e.axisOrder}setFromVec3(e){const t=e.x*.5,i=e.y*.5,s=e.z*.5,r=Math.cos(t),o=Math.cos(i),a=Math.cos(s),h=Math.sin(t),l=Math.sin(i),u=Math.sin(s);return this.axisOrder==="XYZ"?(this.elements[0]=h*o*a+r*l*u,this.elements[1]=r*l*a-h*o*u,this.elements[2]=r*o*u+h*l*a,this.elements[3]=r*o*a-h*l*u):this.axisOrder==="YXZ"?(this.elements[0]=h*o*a+r*l*u,this.elements[1]=r*l*a-h*o*u,this.elements[2]=r*o*u-h*l*a,this.elements[3]=r*o*a+h*l*u):this.axisOrder==="ZXY"?(this.elements[0]=h*o*a-r*l*u,this.elements[1]=r*l*a+h*o*u,this.elements[2]=r*o*u+h*l*a,this.elements[3]=r*o*a-h*l*u):this.axisOrder==="ZYX"?(this.elements[0]=h*o*a-r*l*u,this.elements[1]=r*l*a+h*o*u,this.elements[2]=r*o*u-h*l*a,this.elements[3]=r*o*a+h*l*u):this.axisOrder==="YZX"?(this.elements[0]=h*o*a+r*l*u,this.elements[1]=r*l*a+h*o*u,this.elements[2]=r*o*u-h*l*a,this.elements[3]=r*o*a-h*l*u):this.axisOrder==="XZY"&&(this.elements[0]=h*o*a-r*l*u,this.elements[1]=r*l*a-h*o*u,this.elements[2]=r*o*u+h*l*a,this.elements[3]=r*o*a+h*l*u),this}setFromAxisAngle(e,t=0){const i=t/2,s=Math.sin(i);return this.elements[0]=e.x*s,this.elements[1]=e.y*s,this.elements[2]=e.z*s,this.elements[3]=Math.cos(i),this}setFromRotationMatrix(e){const t=e.elements,i=t[0],s=t[4],r=t[8],o=t[1],a=t[5],h=t[9],l=t[2],u=t[6],d=t[10],c=i+a+d;if(c>0){const p=.5/Math.sqrt(c+1);this.elements[3]=.25/p,this.elements[0]=(u-h)*p,this.elements[1]=(r-l)*p,this.elements[2]=(o-s)*p}else if(i>a&&i>d){const p=2*Math.sqrt(1+i-a-d);this.elements[3]=(u-h)/p,this.elements[0]=.25*p,this.elements[1]=(s+o)/p,this.elements[2]=(r+l)/p}else if(a>d){const p=2*Math.sqrt(1+a-i-d);this.elements[3]=(r-l)/p,this.elements[0]=(s+o)/p,this.elements[1]=.25*p,this.elements[2]=(h+u)/p}else{const p=2*Math.sqrt(1+d-i-a);this.elements[3]=(o-s)/p,this.elements[0]=(r+l)/p,this.elements[1]=(h+u)/p,this.elements[2]=.25*p}return this}}class f{constructor(e=0,t=e,i=e){this.type="Vec3",this._x=e,this._y=t,this._z=i}get x(){return this._x}set x(e){const t=e!==this._x;this._x=e,t&&this._onChangeCallback&&this._onChangeCallback()}get y(){return this._y}set y(e){const t=e!==this._y;this._y=e,t&&this._onChangeCallback&&this._onChangeCallback()}get z(){return this._z}set z(e){const t=e!==this._z;this._z=e,t&&this._onChangeCallback&&this._onChangeCallback()}onChange(e){return e&&(this._onChangeCallback=e),this}set(e=0,t=e,i=e){return this.x=e,this.y=t,this.z=i,this}add(e=new f){return this.x+=e.x,this.y+=e.y,this.z+=e.z,this}addScalar(e=0){return this.x+=e,this.y+=e,this.z+=e,this}sub(e=new f){return this.x-=e.x,this.y-=e.y,this.z-=e.z,this}subScalar(e=0){return this.x-=e,this.y-=e,this.z-=e,this}multiply(e=new f(1)){return this.x*=e.x,this.y*=e.y,this.z*=e.z,this}multiplyScalar(e=1){return this.x*=e,this.y*=e,this.z*=e,this}divide(e=new f(1)){return this.x/=e.x,this.y/=e.y,this.z/=e.z,this}divideScalar(e=1){return this.x/=e,this.y/=e,this.z/=e,this}copy(e=new f){return this.x=e.x,this.y=e.y,this.z=e.z,this}clone(){return new f(this.x,this.y,this.z)}max(e=new f){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this.z=Math.max(this.z,e.z),this}min(e=new f){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this.z=Math.min(this.z,e.z),this}clamp(e=new f,t=new f){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this.z=Math.max(e.z,Math.min(t.z,this.z)),this}equals(e=new f){return this.x===e.x&&this.y===e.y&&this.z===e.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.lengthSq())}distance(e=new f){return Math.hypot(e.x-this.x,e.y-this.y,e.z-this.z)}normalize(){let e=this.lengthSq();return e>0&&(e=1/Math.sqrt(e)),this.x*=e,this.y*=e,this.z*=e,this}dot(e=new f){return this.x*e.x+this.y*e.y+this.z*e.z}cross(e=new f){return this.crossVectors(this,e)}crossVectors(e=new f,t=new f){const i=e.x,s=e.y,r=e.z,o=t.x,a=t.y,h=t.z;return this.x=s*h-r*a,this.y=r*o-i*h,this.z=i*a-s*o,this}lerp(e=new f,t=1){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this.z+=(e.z-this.z)*t,this}applyMat4(e){const t=this._x,i=this._y,s=this._z,r=e.elements;let o=r[3]*t+r[7]*i+r[11]*s+r[15];return o=o||1,this.x=(r[0]*t+r[4]*i+r[8]*s+r[12])/o,this.y=(r[1]*t+r[5]*i+r[9]*s+r[13])/o,this.z=(r[2]*t+r[6]*i+r[10]*s+r[14])/o,this}setFromMatrixPosition(e){const t=e.elements;return this.x=t[12],this.y=t[13],this.z=t[14],this}applyQuat(e=new le){const t=this.x,i=this.y,s=this.z,r=e.elements[0],o=e.elements[1],a=e.elements[2],h=e.elements[3],l=h*t+o*s-a*i,u=h*i+a*t-r*s,d=h*s+r*i-o*t,c=-r*t-o*i-a*s;return this.x=l*h+c*-r+u*-a-d*-o,this.y=u*h+c*-o+d*-r-l*-a,this.z=d*h+c*-a+l*-o-u*-r,this}applyAxisAngle(e=new f,t=0,i=new le){return this.applyQuat(i.setFromAxisAngle(e,t))}project(e){return this.applyMat4(e.viewMatrix).applyMat4(e.projectionMatrix),this}unproject(e){return this.applyMat4(e.projectionMatrix.getInverse()).applyMat4(e.modelMatrix),this}}const Ir=4,lt=4,Z=Ir*lt;class ni{constructor({name:e,key:t,type:i="f32"}){this.name=e,this.key=t,this.type=i,this.bufferLayout=hs(this.type.replace("array","").replace("<","").replace(">","")),this.alignment={start:{row:0,byte:0},end:{row:0,byte:0}},this.setValue=null}get rowCount(){return this.alignment.end.row-this.alignment.start.row+1}get byteCount(){return Math.abs(this.endOffset-this.startOffset)+1}get paddedByteCount(){return(this.alignment.end.row+1)*Z}get startOffset(){return this.getByteCountAtPosition(this.alignment.start)}get startOffsetToIndex(){return this.startOffset/lt}get endOffset(){return this.getByteCountAtPosition(this.alignment.end)}get endOffsetToIndex(){return Math.floor(this.endOffset/lt)}getPositionAtOffset(e=0){return{row:Math.floor(e/Z),byte:e%Z}}getByteCountAtPosition(e={row:0,byte:0}){return e.row*Z+e.byte}applyOverflowToPosition(e={row:0,byte:0}){if(e.byte>Z-1){const t=e.byte%Z;e.row+=Math.floor(e.byte/Z),e.byte=t}return e}getByteCountBetweenPositions(e={row:0,byte:0},t={row:0,byte:0}){return Math.abs(this.getByteCountAtPosition(t)-this.getByteCountAtPosition(e))}getElementAlignment(e={row:0,byte:0}){const t={start:e,end:e},{size:i,align:s}=this.bufferLayout;return e.byte%s!==0&&(e.byte+=e.byte%s),(i<=Z&&e.byte+i>Z||i>Z&&(e.byte>Z||e.byte>0))&&(e.row+=1,e.byte=0),t.end={row:e.row+Math.ceil(i/Z)-1,byte:e.byte+(i%Z===0?Z-1:i%Z-1)},t.end=this.applyOverflowToPosition(t.end),t}setAlignmentFromPosition(e={row:0,byte:0}){this.alignment=this.getElementAlignment(e)}setAlignment(e=0){this.setAlignmentFromPosition(this.getPositionAtOffset(e))}setView(e,t){this.view=new this.bufferLayout.View(e,this.startOffset,this.byteCount/this.bufferLayout.View.BYTES_PER_ELEMENT)}setValueFromFloat(e){this.view[0]=e}setValueFromVec2(e){this.view[0]=e.x??e[0]??0,this.view[1]=e.y??e[1]??0}setValueFromVec3(e){this.view[0]=e.x??e[0]??0,this.view[1]=e.y??e[1]??0,this.view[2]=e.z??e[2]??0}setValueFromMat4OrQuat(e){this.view.set(e.elements)}setValueFromMat3(e){this.setValueFromArrayWithPad(e.elements)}setValueFromArray(e){this.view.set(e)}setValueFromArrayWithPad(e){for(let t=0,i=0;t{if(this.type==="f32"||this.type==="u32"||this.type==="i32")return this.setValueFromFloat;if(this.type==="vec2f")return this.setValueFromVec2;if(this.type==="vec3f")return this.setValueFromVec3;if(this.type==="mat3x3f")return t.elements?this.setValueFromMat3:this.setValueFromArrayWithPad;if(t.elements)return this.setValueFromMat4OrQuat;if(ArrayBuffer.isView(t)||Array.isArray(t))return this.bufferLayout.pad?this.setValueFromArrayWithPad:this.setValueFromArray;I(`${this.constructor.name}: value passed to ${this.name} cannot be used: ${t}`)})(e)),this.setValue(e)}extractDataFromBufferResult(e){return e.slice(this.startOffsetToIndex,this.endOffsetToIndex)}}class us extends ni{constructor({name:e,key:t,type:i="f32",arrayLength:s=1}){super({name:e,key:t,type:i}),this.arrayLength=s,this.numElements=Math.ceil(this.arrayLength/this.bufferLayout.numElements)}get arrayStrideToIndex(){return this.arrayStride/lt}setAlignment(e=0){super.setAlignment(e);const t=this.getElementAlignment(this.getPositionAtOffset(this.endOffset+1));this.arrayStride=this.getByteCountBetweenPositions(this.alignment.end,t.end),this.alignment.end=this.getPositionAtOffset(this.endOffset+this.arrayStride*(this.numElements-1))}setValueFromArray(e){let t=0;const i=this.byteCount/this.bufferLayout.View.BYTES_PER_ELEMENT,s=Math.ceil(i/this.numElements);for(let r=0;r{switch(this.bufferLayout.View){case Int32Array:return i.setInt32.bind(i);case Uint16Array:return i.setUint16.bind(i);case Uint32Array:return i.setUint32.bind(i);case Float32Array:default:return i.setFloat32.bind(i)}})(t)}update(e){super.update(e);for(let t=0;t{this.viewSetFunction(s+o*this.bufferLayout.View.BYTES_PER_ELEMENT,r,!0)})}}extractDataFromBufferResult(e){const t=new Float32Array(this.arrayLength);for(let i=0;in.reduce((e,t)=>e|Vr.get(t),0);class _e{constructor({label:e="Buffer",size:t=0,usage:i=["copySrc","copyDst"],mappedAtCreation:s=!1}={}){this.type="Buffer",this.reset(),this.uuid=Y(),this.consumers=new Set,this.options={label:e,size:t,usage:ds(i),mappedAtCreation:s}}reset(){this.GPUBuffer=null}set size(e){this.options.size=e}createBuffer(e,t={}){const{usage:i,...s}=t;this.options={...this.options,...s,...i!==void 0&&{usage:ds(i)}},this.setBuffer(e.createBuffer(this))}setBuffer(e){this.GPUBuffer=e}copy(e,t=!1){t&&this.destroy(),this.options=e.options,this.GPUBuffer=e.GPUBuffer,this.consumers=new Set([...this.consumers,...e.consumers])}async mapBufferAsync(){if(!this.GPUBuffer||this.GPUBuffer.mapState!=="unmapped")return new Float32Array(0);await this.GPUBuffer.mapAsync(GPUMapMode.READ);const e=new Float32Array(this.GPUBuffer.getMappedRange().slice(0));return this.GPUBuffer.unmap(),e}destroy(){this.GPUBuffer?.destroy(),this.reset(),this.consumers.clear()}}class fe extends ht{constructor({label:e="Uniform",name:t="uniform",bindingType:i,visibility:s,useStruct:r=!0,access:o="read",usage:a=[],struct:h={},bindings:l=[]}){i=i??"uniform",super({label:e,name:t,bindingType:i,visibility:s}),this.options={...this.options,useStruct:r,access:o,usage:a,struct:h,bindings:l},this.cacheKey+=`${r},${o},`,this.arrayBufferSize=0,this.shouldUpdate=!1,this.useStruct=r,this.bufferElements=[],this.inputs={},this.buffer=new _e,Object.keys(h).length&&(this.setBindings(h),this.setInputsAlignment()),(Object.keys(h).length||this.options.bindings.length)&&(this.setBufferAttributes(),this.setWGSLFragment())}get resourceLayout(){return{buffer:{type:ls(this)}}}get resourceLayoutCacheKey(){return`buffer,${ls(this)},${this.visibility},`}get resource(){return{buffer:this.buffer.GPUBuffer}}clone(e){const{struct:t,...i}=e,s=new this.constructor(i);return t&&s.setBindings(t),s.options.struct=t,s.arrayBufferSize=this.arrayBufferSize,s.arrayBuffer=new ArrayBuffer(s.arrayBufferSize),s.arrayView=new DataView(s.arrayBuffer,0,s.arrayBuffer.byteLength),s.buffer.size=s.arrayBuffer.byteLength,this.bufferElements.forEach(r=>{const o=new r.constructor({name:r.name,key:r.key,type:r.type,...r.arrayLength&&{arrayLength:r.arrayLength}});o.alignment=JSON.parse(JSON.stringify(r.alignment)),r.arrayStride&&(o.arrayStride=r.arrayStride),o.setView(s.arrayBuffer,s.arrayView),s.bufferElements.push(o)}),this.name===s.name&&this.label===s.label?(s.wgslStructFragment=this.wgslStructFragment,s.wgslGroupFragment=this.wgslGroupFragment):s.setWGSLFragment(),s.shouldUpdate=s.arrayBufferSize>0,s}setBindings(e){for(const t of Object.keys(e)){const i={};for(const s in e[t])s!=="value"&&(i[s]=e[t][s]);if(i.name=t,Object.defineProperty(i,"value",{get(){return i._value},set(s){i._value=s,i.shouldUpdate=!0}}),i.value=e[t].value,i.value instanceof E||i.value instanceof f){const s=i.value._onChangeCallback;i.value._onChangeCallback=()=>{s&&s(),i.shouldUpdate=!0}}this.inputs[t]=i,this.cacheKey+=`${t},${e[t].type},`}}setInputsAlignment(){let e=Object.keys(this.inputs);const t=e.filter(i=>this.inputs[i].type.includes("array"));t.length&&(e.sort((i,s)=>{const r=Math.min(0,this.inputs[i].type.indexOf("array")),o=Math.min(0,this.inputs[s].type.indexOf("array"));return r-o}),t.length>1&&(e=e.filter(i=>!t.includes(i))));for(const i of e){const s=this.inputs[i],r={name:We(s.name??i),key:i,type:s.type},o=s.type.includes("array")&&(Array.isArray(s.value)||ArrayBuffer.isView(s.value));this.bufferElements.push(o?new us({...r,arrayLength:s.value.length}):new ni(r))}if(this.bufferElements.forEach((i,s)=>{const r=s===0?0:this.bufferElements[s-1].endOffset+1;i.setAlignment(r)}),t.length>1)if(t.map(r=>{const o=this.inputs[r],a=hs(o.type.replace("array","").replace("<","").replace(">",""));return Math.ceil(o.value.length/a.numElements)}).every((r,o,a)=>r===a[0])){const r=t.map(h=>{const l=this.inputs[h];return new oi({name:We(l.name??h),key:h,type:l.type,arrayLength:l.value.length})}),o=t.map(h=>{const l=this.inputs[h];return new ni({name:We(l.name??h),key:h,type:l.type.replace("array","").replace("<","").replace(">","")})});o.forEach((h,l)=>{l===0?this.bufferElements.length?h.setAlignmentFromPosition({row:this.bufferElements[this.bufferElements.length-1].alignment.end.row+1,byte:0}):h.setAlignment(0):h.setAlignment(o[l-1].endOffset+1)});const a=o[o.length-1].endOffset+1-o[0].startOffset;r.forEach((h,l)=>{h.setAlignment(o[l].startOffset,Math.ceil(a/Z)*Z)}),this.bufferElements=[...this.bufferElements,...r]}else I(`BufferBinding: "${this.label}" contains multiple array inputs that should use an interleaved array, but their sizes do not match. These inputs cannot be added to the BufferBinding: "${t.join(", ")}"`)}setBufferAttributes(){const e=this.bufferElements.length?this.bufferElements[this.bufferElements.length-1].paddedByteCount:0;this.arrayBufferSize=e,this.options.bindings.forEach(t=>{this.arrayBufferSize+=t.arrayBufferSize}),this.arrayBuffer=new ArrayBuffer(this.arrayBufferSize),this.arrayView=new DataView(this.arrayBuffer,0,e),this.options.bindings.forEach((t,i)=>{let s=e;for(let a=0;a0&&this.options.bindings[i-1].bufferElements.length?this.options.bindings[i-1].bufferElements[this.options.bindings[i-1].bufferElements.length-1].alignment.end.row+1:0;t.bufferElements.forEach(a=>{a.alignment.start.row+=r+o,a.alignment.end.row+=r+o}),t.arrayView=new DataView(this.arrayBuffer,s,t.arrayBuffer.byteLength);for(const a of t.bufferElements)a.setView(this.arrayBuffer,t.arrayView)}),this.buffer.size=this.arrayBuffer.byteLength;for(const t of this.bufferElements)t.setView(this.arrayBuffer,this.arrayView);this.shouldUpdate=this.arrayBufferSize>0}setWGSLFragment(){if(!this.bufferElements.length&&!this.options.bindings.length)return;const e=[];this.options.bindings.forEach(i=>{const s=e.find(r=>r.name===i.name);s?s.count++:e.push({name:i.name,label:i.label,count:1,wgslStructFragment:i.wgslStructFragment})});const t=je(this.label);if(this.useStruct){const i={};i[t]={};const s=this.bufferElements.filter(a=>!(a instanceof oi)),r=this.bufferElements.filter(a=>a instanceof oi);if(r.length){const a=this.bindingType==="uniform"?`, ${r[0].numElements}`:"";if(s.length){i[`${t}Element`]={},r.forEach(u=>{i[`${t}Element`][u.name]=u.type.replace("array","").replace("<","").replace(">","")}),s.forEach(u=>{i[t][u.name]=u.type});const h=this.bufferElements.find(u=>u.name==="elements")?`${this.name}Elements`:"elements";i[t][h]=`array<${t}Element${a}>`;const l=at(this);this.wgslGroupFragment=[`${l} ${this.name}: ${t};`]}else{this.bufferElements.forEach(l=>{i[t][l.name]=l.type.replace("array","").replace("<","").replace(">","")});const h=at(this);this.wgslGroupFragment=[`${h} ${this.name}: array<${t}${a}>;`]}}else{s.forEach(h=>{const l=this.bindingType==="uniform"&&"numElements"in h?`array<${h.type.replace("array","").replace("<","").replace(">","")}, ${h.numElements}>`:h.type;i[t][h.name]=l});const a=at(this);this.wgslGroupFragment=[`${a} ${this.name}: ${t};`]}e.length&&e.forEach(a=>{i[t][a.name]=a.count>1?`array<${je(a.label)}>`:je(a.label)});const o=e.length?e.map(a=>a.wgslStructFragment).join(` + `}),n=s.createSampler({minFilter:"linear",magFilter:"linear"})),t[r.format]||(t[r.format]=s.createRenderPipeline({label:"Mip level generator pipeline",layout:"auto",vertex:{module:e},fragment:{module:e,targets:[{format:r.format}]}}));const o=t[r.format],a=s.createCommandEncoder({label:"Mip gen encoder"});let h=r.width,u=r.height,l=0;for(;h>1||u>1;){h=Math.max(1,h/2|0),u=Math.max(1,u/2|0);for(let p=0;pn.reduce((e,t)=>e|zn.get(t),0),Ln={i32:{numElements:1,align:4,size:4,type:"i32",View:Int32Array},u32:{numElements:1,align:4,size:4,type:"u32",View:Uint32Array},f32:{numElements:1,align:4,size:4,type:"f32",View:Float32Array},f16:{numElements:1,align:2,size:2,type:"u16",View:Uint16Array},vec2f:{numElements:2,align:8,size:8,type:"f32",View:Float32Array},vec2i:{numElements:2,align:8,size:8,type:"i32",View:Int32Array},vec2u:{numElements:2,align:8,size:8,type:"u32",View:Uint32Array},vec2h:{numElements:2,align:4,size:4,type:"u16",View:Uint16Array},vec3i:{numElements:3,align:16,size:12,type:"i32",View:Int32Array},vec3u:{numElements:3,align:16,size:12,type:"u32",View:Uint32Array},vec3f:{numElements:3,align:16,size:12,type:"f32",View:Float32Array},vec3h:{numElements:3,align:8,size:6,type:"u16",View:Uint16Array},vec4i:{numElements:4,align:16,size:16,type:"i32",View:Int32Array},vec4u:{numElements:4,align:16,size:16,type:"u32",View:Uint32Array},vec4f:{numElements:4,align:16,size:16,type:"f32",View:Float32Array},vec4h:{numElements:4,align:8,size:8,type:"u16",View:Uint16Array},mat2x2f:{numElements:4,align:8,size:16,type:"f32",View:Float32Array},mat2x2h:{numElements:4,align:4,size:8,type:"u16",View:Uint16Array},mat3x2f:{numElements:6,align:8,size:24,type:"f32",View:Float32Array},mat3x2h:{numElements:6,align:4,size:12,type:"u16",View:Uint16Array},mat4x2f:{numElements:8,align:8,size:32,type:"f32",View:Float32Array},mat4x2h:{numElements:8,align:4,size:16,type:"u16",View:Uint16Array},mat2x3f:{numElements:8,align:16,size:32,pad:[3,1],type:"f32",View:Float32Array},mat2x3h:{numElements:8,align:8,size:16,pad:[3,1],type:"u16",View:Uint16Array},mat3x3f:{numElements:12,align:16,size:48,pad:[3,1],type:"f32",View:Float32Array},mat3x3h:{numElements:12,align:8,size:24,pad:[3,1],type:"u16",View:Uint16Array},mat4x3f:{numElements:16,align:16,size:64,pad:[3,1],type:"f32",View:Float32Array},mat4x3h:{numElements:16,align:8,size:32,pad:[3,1],type:"u16",View:Uint16Array},mat2x4f:{numElements:8,align:16,size:32,type:"f32",View:Float32Array},mat2x4h:{numElements:8,align:8,size:16,type:"u16",View:Uint16Array},mat3x4f:{numElements:12,align:16,size:48,pad:[3,1],type:"f32",View:Float32Array},mat3x4h:{numElements:12,align:8,size:24,pad:[3,1],type:"u16",View:Uint16Array},mat4x4f:{numElements:16,align:16,size:64,type:"f32",View:Float32Array},mat4x4h:{numElements:16,align:8,size:32,type:"u16",View:Uint16Array}},Hs=n=>Ln[n],Lt=n=>(()=>{switch(n.bindingType){case"storage":return`var<${n.bindingType}, ${n.options.access}>`;case"uniform":default:return"var"}})(),_n=n=>n.bindingType==="externalTexture"?`var ${n.name}: texture_external;`:n.bindingType==="storage"?`var ${n.name}: texture_storage_${n.options.viewDimension.replace("-","_")}<${n.options.format}, ${n.options.access}>;`:n.bindingType==="depth"?`var ${n.name}: texture_depth${n.options.multisampled?"_multisampled":""}_${n.options.viewDimension.replace("-","_")};`:`var ${n.name}: texture${n.options.multisampled?"_multisampled":""}_${n.options.viewDimension.replace("-","_")};`,Xs=n=>n.bindingType==="storage"&&n.options.access==="read_write"?"storage":n.bindingType==="storage"?"read-only-storage":"uniform",Gn=n=>(()=>{switch(n.bindingType){case"externalTexture":return{externalTexture:{}};case"storage":return{storageTexture:{format:n.options.format,viewDimension:n.options.viewDimension}};case"texture":return{texture:{multisampled:n.options.multisampled,viewDimension:n.options.viewDimension,sampleType:n.options.multisampled?"unfilterable-float":"float"}};case"depth":return{texture:{multisampled:n.options.multisampled,viewDimension:n.options.viewDimension,sampleType:"depth"}};default:return null}})(),An=n=>(()=>{switch(n.bindingType){case"externalTexture":return`externalTexture,${n.visibility},`;case"storage":return`storageTexture,${n.options.format},${n.options.viewDimension},${n.visibility},`;case"texture":return`texture,${n.options.multisampled},${n.options.viewDimension},${n.options.multisampled?"unfilterable-float":"float"},${n.visibility},`;case"depth":return`depthTexture,${n.options.format},${n.options.viewDimension},${n.visibility},`;default:return`${n.visibility},`}})();class _t{constructor({label:e="Uniform",name:t="uniform",bindingType:i="uniform",visibility:s=["vertex","fragment","compute"]}){this.label=e,this.name=st(t),this.bindingType=i,this.visibility=En(s),this.options={label:e,name:t,bindingType:i,visibility:s},this.shouldResetBindGroup=!1,this.shouldResetBindGroupLayout=!1,this.cacheKey=`${i},${this.visibility},`}}class _{constructor(e=0,t=e){this.type="Vec2",this._x=e,this._y=t}get x(){return this._x}set x(e){const t=e!==this._x;this._x=e,t&&this._onChangeCallback&&this._onChangeCallback()}get y(){return this._y}set y(e){const t=e!==this._y;this._y=e,t&&this._onChangeCallback&&this._onChangeCallback()}onChange(e){return e&&(this._onChangeCallback=e),this}set(e=0,t=e){return this.x=e,this.y=t,this}add(e=new _){return this.x+=e.x,this.y+=e.y,this}addScalar(e=0){return this.x+=e,this.y+=e,this}sub(e=new _){return this.x-=e.x,this.y-=e.y,this}subScalar(e=0){return this.x-=e,this.y-=e,this}multiply(e=new _(1)){return this.x*=e.x,this.y*=e.y,this}multiplyScalar(e=1){return this.x*=e,this.y*=e,this}divide(e=new _(1)){return this.x/=e.x,this.y/=e.y,this}divideScalar(e=1){return this.x/=e,this.y/=e,this}copy(e=new _){return this.x=e.x,this.y=e.y,this}clone(){return new _(this.x,this.y)}max(e=new _){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this}min(e=new _){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this}clamp(e=new _,t=new _){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this}equals(e=new _){return this.x===e.x&&this.y===e.y}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.lengthSq())}normalize(){let e=this.x*this.x+this.y*this.y;return e>0&&(e=1/Math.sqrt(e)),this.x*=e,this.y*=e,this}dot(e=new _){return this.x*e.x+this.y*e.y}lerp(e=new _,t=1){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this}}class ce{constructor(e=new Float32Array([0,0,0,1]),t="XYZ"){this.type="Quat",this.elements=e,this.axisOrder=t}setFromArray(e=new Float32Array([0,0,0,1])){return this.elements[0]=e[0],this.elements[1]=e[1],this.elements[2]=e[2],this.elements[3]=e[3],this}setAxisOrder(e="XYZ"){switch(e=e.toUpperCase(),e){case"XYZ":case"YXZ":case"ZXY":case"ZYX":case"YZX":case"XZY":this.axisOrder=e;break;default:this.axisOrder="XYZ"}return this}copy(e=new ce){return this.elements=e.elements,this.axisOrder=e.axisOrder,this}clone(){return new ce().copy(this)}equals(e=new ce){return this.elements[0]===e.elements[0]&&this.elements[1]===e.elements[1]&&this.elements[2]===e.elements[2]&&this.elements[3]===e.elements[3]&&this.axisOrder===e.axisOrder}setFromVec3(e){const t=e.x*.5,i=e.y*.5,s=e.z*.5,r=Math.cos(t),o=Math.cos(i),a=Math.cos(s),h=Math.sin(t),u=Math.sin(i),l=Math.sin(s);return this.axisOrder==="XYZ"?(this.elements[0]=h*o*a+r*u*l,this.elements[1]=r*u*a-h*o*l,this.elements[2]=r*o*l+h*u*a,this.elements[3]=r*o*a-h*u*l):this.axisOrder==="YXZ"?(this.elements[0]=h*o*a+r*u*l,this.elements[1]=r*u*a-h*o*l,this.elements[2]=r*o*l-h*u*a,this.elements[3]=r*o*a+h*u*l):this.axisOrder==="ZXY"?(this.elements[0]=h*o*a-r*u*l,this.elements[1]=r*u*a+h*o*l,this.elements[2]=r*o*l+h*u*a,this.elements[3]=r*o*a-h*u*l):this.axisOrder==="ZYX"?(this.elements[0]=h*o*a-r*u*l,this.elements[1]=r*u*a+h*o*l,this.elements[2]=r*o*l-h*u*a,this.elements[3]=r*o*a+h*u*l):this.axisOrder==="YZX"?(this.elements[0]=h*o*a+r*u*l,this.elements[1]=r*u*a+h*o*l,this.elements[2]=r*o*l-h*u*a,this.elements[3]=r*o*a-h*u*l):this.axisOrder==="XZY"&&(this.elements[0]=h*o*a-r*u*l,this.elements[1]=r*u*a-h*o*l,this.elements[2]=r*o*l+h*u*a,this.elements[3]=r*o*a+h*u*l),this}setFromAxisAngle(e,t=0){const i=t/2,s=Math.sin(i);return this.elements[0]=e.x*s,this.elements[1]=e.y*s,this.elements[2]=e.z*s,this.elements[3]=Math.cos(i),this}setFromRotationMatrix(e){const t=e.elements,i=t[0],s=t[4],r=t[8],o=t[1],a=t[5],h=t[9],u=t[2],l=t[6],c=t[10],p=i+a+c;if(p>0){const d=.5/Math.sqrt(p+1);this.elements[3]=.25/d,this.elements[0]=(l-h)*d,this.elements[1]=(r-u)*d,this.elements[2]=(o-s)*d}else if(i>a&&i>c){const d=2*Math.sqrt(1+i-a-c);this.elements[3]=(l-h)/d,this.elements[0]=.25*d,this.elements[1]=(s+o)/d,this.elements[2]=(r+u)/d}else if(a>c){const d=2*Math.sqrt(1+a-i-c);this.elements[3]=(r-u)/d,this.elements[0]=(s+o)/d,this.elements[1]=.25*d,this.elements[2]=(h+l)/d}else{const d=2*Math.sqrt(1+c-i-a);this.elements[3]=(o-s)/d,this.elements[0]=(r+u)/d,this.elements[1]=(h+l)/d,this.elements[2]=.25*d}return this}}class f{constructor(e=0,t=e,i=e){this.type="Vec3",this._x=e,this._y=t,this._z=i}get x(){return this._x}set x(e){const t=e!==this._x;this._x=e,t&&this._onChangeCallback&&this._onChangeCallback()}get y(){return this._y}set y(e){const t=e!==this._y;this._y=e,t&&this._onChangeCallback&&this._onChangeCallback()}get z(){return this._z}set z(e){const t=e!==this._z;this._z=e,t&&this._onChangeCallback&&this._onChangeCallback()}onChange(e){return e&&(this._onChangeCallback=e),this}set(e=0,t=e,i=e){return this.x=e,this.y=t,this.z=i,this}add(e=new f){return this.x+=e.x,this.y+=e.y,this.z+=e.z,this}addScalar(e=0){return this.x+=e,this.y+=e,this.z+=e,this}sub(e=new f){return this.x-=e.x,this.y-=e.y,this.z-=e.z,this}subScalar(e=0){return this.x-=e,this.y-=e,this.z-=e,this}multiply(e=new f(1)){return this.x*=e.x,this.y*=e.y,this.z*=e.z,this}multiplyScalar(e=1){return this.x*=e,this.y*=e,this.z*=e,this}divide(e=new f(1)){return this.x/=e.x,this.y/=e.y,this.z/=e.z,this}divideScalar(e=1){return this.x/=e,this.y/=e,this.z/=e,this}copy(e=new f){return this.x=e.x,this.y=e.y,this.z=e.z,this}clone(){return new f(this.x,this.y,this.z)}max(e=new f){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this.z=Math.max(this.z,e.z),this}min(e=new f){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this.z=Math.min(this.z,e.z),this}clamp(e=new f,t=new f){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this.z=Math.max(e.z,Math.min(t.z,this.z)),this}equals(e=new f){return this.x===e.x&&this.y===e.y&&this.z===e.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.lengthSq())}distance(e=new f){return Math.hypot(e.x-this.x,e.y-this.y,e.z-this.z)}normalize(){let e=this.lengthSq();return e>0&&(e=1/Math.sqrt(e)),this.x*=e,this.y*=e,this.z*=e,this}dot(e=new f){return this.x*e.x+this.y*e.y+this.z*e.z}cross(e=new f){return this.crossVectors(this,e)}crossVectors(e=new f,t=new f){const i=e.x,s=e.y,r=e.z,o=t.x,a=t.y,h=t.z;return this.x=s*h-r*a,this.y=r*o-i*h,this.z=i*a-s*o,this}lerp(e=new f,t=1){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this.z+=(e.z-this.z)*t,this}applyMat4(e){const t=this._x,i=this._y,s=this._z,r=e.elements;let o=r[3]*t+r[7]*i+r[11]*s+r[15];return o=o||1,this.x=(r[0]*t+r[4]*i+r[8]*s+r[12])/o,this.y=(r[1]*t+r[5]*i+r[9]*s+r[13])/o,this.z=(r[2]*t+r[6]*i+r[10]*s+r[14])/o,this}setFromMatrixPosition(e){const t=e.elements;return this.x=t[12],this.y=t[13],this.z=t[14],this}applyQuat(e=new ce){const t=this.x,i=this.y,s=this.z,r=e.elements[0],o=e.elements[1],a=e.elements[2],h=e.elements[3],u=h*t+o*s-a*i,l=h*i+a*t-r*s,c=h*s+r*i-o*t,p=-r*t-o*i-a*s;return this.x=u*h+p*-r+l*-a-c*-o,this.y=l*h+p*-o+c*-r-u*-a,this.z=c*h+p*-a+u*-o-l*-r,this}applyAxisAngle(e=new f,t=0,i=new ce){return this.applyQuat(i.setFromAxisAngle(e,t))}transformDirection(e){const t=this.x,i=this.y,s=this.z,r=e.elements;return this.x=r[0]*t+r[4]*i+r[8]*s,this.y=r[1]*t+r[5]*i+r[9]*s,this.z=r[2]*t+r[6]*i+r[10]*s,this.normalize()}project(e){return this.applyMat4(e.viewMatrix).applyMat4(e.projectionMatrix),this}unproject(e){return this.applyMat4(e.projectionMatrix.getInverse()).applyMat4(e.modelMatrix),this}}const Dn=4,Gt=4,Z=Dn*Gt;class Di{constructor({name:e,key:t,type:i="f32"}){this.name=e,this.key=t,this.type=i,this.bufferLayout=Hs(this.type.replace("array","").replace("<","").replace(">","")),this.alignment={start:{row:0,byte:0},end:{row:0,byte:0}},this.setValue=null}get rowCount(){return this.alignment.end.row-this.alignment.start.row+1}get byteCount(){return Math.abs(this.endOffset-this.startOffset)+1}get paddedByteCount(){return(this.alignment.end.row+1)*Z}get startOffset(){return this.getByteCountAtPosition(this.alignment.start)}get startOffsetToIndex(){return this.startOffset/Gt}get endOffset(){return this.getByteCountAtPosition(this.alignment.end)}get endOffsetToIndex(){return Math.floor(this.endOffset/Gt)}getPositionAtOffset(e=0){return{row:Math.floor(e/Z),byte:e%Z}}getByteCountAtPosition(e={row:0,byte:0}){return e.row*Z+e.byte}applyOverflowToPosition(e={row:0,byte:0}){if(e.byte>Z-1){const t=e.byte%Z;e.row+=Math.floor(e.byte/Z),e.byte=t}return e}getByteCountBetweenPositions(e={row:0,byte:0},t={row:0,byte:0}){return Math.abs(this.getByteCountAtPosition(t)-this.getByteCountAtPosition(e))}getElementAlignment(e={row:0,byte:0}){const t={start:e,end:e},{size:i,align:s}=this.bufferLayout;return e.byte%s!==0&&(e.byte+=e.byte%s),(i<=Z&&e.byte+i>Z||i>Z&&(e.byte>Z||e.byte>0))&&(e.row+=1,e.byte=0),t.end={row:e.row+Math.ceil(i/Z)-1,byte:e.byte+(i%Z===0?Z-1:i%Z-1)},t.end=this.applyOverflowToPosition(t.end),t}setAlignmentFromPosition(e={row:0,byte:0}){this.alignment=this.getElementAlignment(e)}setAlignment(e=0){this.setAlignmentFromPosition(this.getPositionAtOffset(e))}setView(e,t){this.view=new this.bufferLayout.View(e,this.startOffset,this.byteCount/this.bufferLayout.View.BYTES_PER_ELEMENT)}setValueFromFloat(e){this.view[0]=e}setValueFromVec2(e){this.view[0]=e.x??e[0]??0,this.view[1]=e.y??e[1]??0}setValueFromVec3(e){this.view[0]=e.x??e[0]??0,this.view[1]=e.y??e[1]??0,this.view[2]=e.z??e[2]??0}setValueFromMat4OrQuat(e){this.view.set(e.elements)}setValueFromMat3(e){this.setValueFromArrayWithPad(e.elements)}setValueFromArray(e){this.view.set(e)}setValueFromArrayWithPad(e){for(let t=0,i=0;t{if(this.type==="f32"||this.type==="u32"||this.type==="i32")return this.setValueFromFloat;if(this.type==="vec2f")return this.setValueFromVec2;if(this.type==="vec3f")return this.setValueFromVec3;if(this.type==="mat3x3f")return t.elements?this.setValueFromMat3:this.setValueFromArrayWithPad;if(t.elements)return this.setValueFromMat4OrQuat;if(ArrayBuffer.isView(t)||Array.isArray(t))return this.bufferLayout.pad?this.setValueFromArrayWithPad:this.setValueFromArray;L(`${this.constructor.name}: value passed to ${this.name} cannot be used: ${t}`)})(e)),this.setValue(e)}extractDataFromBufferResult(e){return e.slice(this.startOffsetToIndex,this.endOffsetToIndex)}}class Ks extends Di{constructor({name:e,key:t,type:i="f32",arrayLength:s=1}){super({name:e,key:t,type:i}),this.arrayLength=s,this.numElements=Math.ceil(this.arrayLength/this.bufferLayout.numElements)}get arrayStrideToIndex(){return this.arrayStride/Gt}setAlignment(e=0){super.setAlignment(e);const t=this.getElementAlignment(this.getPositionAtOffset(this.endOffset+1));this.arrayStride=this.getByteCountBetweenPositions(this.alignment.end,t.end),this.alignment.end=this.getPositionAtOffset(this.endOffset+this.arrayStride*(this.numElements-1))}setValueFromArray(e){let t=0;const i=this.byteCount/this.bufferLayout.View.BYTES_PER_ELEMENT,s=Math.ceil(i/this.numElements);for(let r=0;r{switch(this.bufferLayout.View){case Int32Array:return i.setInt32.bind(i);case Uint16Array:return i.setUint16.bind(i);case Uint32Array:return i.setUint32.bind(i);case Float32Array:default:return i.setFloat32.bind(i)}})(t)}update(e){super.update(e);for(let t=0;t{this.viewSetFunction(s+o*this.bufferLayout.View.BYTES_PER_ELEMENT,r,!0)})}}extractDataFromBufferResult(e){const t=new Float32Array(this.arrayLength);for(let i=0;in.reduce((e,t)=>e|On.get(t),0);class Ve{constructor({label:e="Buffer",size:t=0,usage:i=["copySrc","copyDst"],mappedAtCreation:s=!1}={}){this.type="Buffer",this.reset(),this.uuid=H(),this.consumers=new Set,this.options={label:e,size:t,usage:Zs(i),mappedAtCreation:s}}reset(){this.GPUBuffer=null}set size(e){this.options.size=e}createBuffer(e,t={}){const{usage:i,...s}=t;this.options={...this.options,...s,...i!==void 0&&{usage:Zs(i)}},this.setBuffer(e.createBuffer(this))}setBuffer(e){this.GPUBuffer=e}copy(e,t=!1){t&&this.destroy(),this.options=e.options,this.GPUBuffer=e.GPUBuffer,this.consumers=new Set([...this.consumers,...e.consumers])}async mapBufferAsync(){if(!this.GPUBuffer||this.GPUBuffer.mapState!=="unmapped")return new Float32Array(0);await this.GPUBuffer.mapAsync(GPUMapMode.READ);const e=new Float32Array(this.GPUBuffer.getMappedRange().slice(0));return this.GPUBuffer.unmap(),e}destroy(){this.GPUBuffer?.destroy(),this.reset(),this.consumers.clear()}}var Js=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Fn=(n,e,t)=>(Js(n,e,"read from private field"),t?t.call(n):e.get(n)),$n=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Un=(n,e,t,i)=>(Js(n,e,"write to private field"),e.set(n,t),t),At;const kn=class Ei extends _t{constructor({label:e="Uniform",name:t="uniform",bindingType:i,visibility:s,useStruct:r=!0,access:o="read",usage:a=[],struct:h={},childrenBindings:u=[],parent:l=null,minOffset:c=256,offset:p=0}){i=i??"uniform",super({label:e,name:t,bindingType:i,visibility:s}),$n(this,At,void 0),this.options={...this.options,useStruct:r,access:o,usage:a,struct:h,childrenBindings:u,parent:l,minOffset:c,offset:p},this.cacheKey+=`${r},${o},`,this.arrayBufferSize=0,this.shouldUpdate=!1,this.useStruct=r,this.bufferElements=[],this.inputs={},this.buffer=new Ve,Object.keys(h).length&&(this.setBindings(h),this.setInputsAlignment()),this.setChildrenBindings(u),(Object.keys(h).length||this.childrenBindings.length)&&(this.setBufferAttributes(),this.setWGSLFragment()),this.parent=l}static cloneStruct(e){return Object.keys(e).reduce((t,i)=>{const s=e[i];let r;return Array.isArray(s.value)||ArrayBuffer.isView(s.value)?r=new s.value.constructor(s.value.length):typeof s.value=="number"?r=0:r=new s.value.constructor,{...t,[i]:{type:s.type,value:r}}},{})}get parent(){return Fn(this,At)}set parent(e){if(e){this.parentView=new DataView(e.arrayBuffer,this.offset,this.getMinOffsetSize(this.arrayBufferSize));const t=s=>[...(o=>o.bufferElements)(s),s.childrenBindings.map(o=>t(o)).flat()].flat(),i=t(this);this.parentViewSetBufferEls=i.map(s=>{switch(s.bufferLayout.View){case Int32Array:return{bufferElement:s,viewSetFunction:this.parentView.setInt32.bind(this.parentView)};case Uint16Array:return{bufferElement:s,viewSetFunction:this.parentView.setUint16.bind(this.parentView)};case Uint32Array:return{bufferElement:s,viewSetFunction:this.parentView.setUint32.bind(this.parentView)};case Float32Array:default:return{bufferElement:s,viewSetFunction:this.parentView.setFloat32.bind(this.parentView)}}}),!this.parent&&this.buffer.GPUBuffer&&this.buffer.destroy()}else this.parentView=null,this.parentViewSetBufferEls=null;Un(this,At,e)}getMinOffsetSize(e){return Math.ceil(e/this.options.minOffset)*this.options.minOffset}get offset(){return this.getMinOffsetSize(this.options.offset*this.getMinOffsetSize(this.arrayBufferSize))}get resourceLayout(){return{buffer:{type:Xs(this)},...this.parent&&{offset:this.offset,size:this.arrayBufferSize}}}get resourceLayoutCacheKey(){return`buffer,${Xs(this)},${this.visibility},`}get resource(){return{buffer:this.parent?this.parent.buffer.GPUBuffer:this.buffer.GPUBuffer,...this.parent&&{offset:this.offset,size:this.arrayBufferSize}}}clone(e={}){let{struct:t,childrenBindings:i,parent:s,...r}=e;const{label:o,name:a,bindingType:h,visibility:u,useStruct:l,access:c,usage:p}=this.options;r={label:o,name:a,bindingType:h,visibility:u,useStruct:l,access:c,usage:p,...r};const d=new this.constructor(r);return t=t||Ei.cloneStruct(this.options.struct),d.options.struct=t,d.setBindings(t),d.arrayBufferSize=this.arrayBufferSize,d.arrayBuffer=new ArrayBuffer(d.arrayBufferSize),d.arrayView=new DataView(d.arrayBuffer,0,d.arrayBuffer.byteLength),d.buffer.size=d.arrayBuffer.byteLength,this.bufferElements.forEach(m=>{const g=new m.constructor({name:m.name,key:m.key,type:m.type,...m.arrayLength&&{arrayLength:m.arrayLength}});g.alignment=JSON.parse(JSON.stringify(m.alignment)),m.arrayStride&&(g.arrayStride=m.arrayStride),g.setView(d.arrayBuffer,d.arrayView),d.bufferElements.push(g)}),this.options.childrenBindings&&(d.options.childrenBindings=this.options.childrenBindings,d.options.childrenBindings.forEach(m=>{const g=m.count?Math.max(1,m.count):1;d.cacheKey+=`child(count:${g}):${m.binding.cacheKey}`}),d.options.childrenBindings.forEach(m=>{d.childrenBindings=[...d.childrenBindings,Array.from(Array(Math.max(1,m.count||1)).keys()).map(g=>m.binding.clone({...m.binding.options,struct:Ei.cloneStruct(m.binding.options.struct)}))].flat()}),d.childrenBindings.forEach((m,g)=>{let y=this.arrayView.byteLength;for(let v=0;v{v.alignment.start.row=this.childrenBindings[g].bufferElements[M].alignment.start.row,v.alignment.end.row=this.childrenBindings[g].bufferElements[M].alignment.end.row}),m.arrayView=new DataView(d.arrayBuffer,y,m.arrayBuffer.byteLength);for(const v of m.bufferElements)v.setView(d.arrayBuffer,m.arrayView)})),this.name===d.name&&this.label===d.label?(d.wgslStructFragment=this.wgslStructFragment,d.wgslGroupFragment=this.wgslGroupFragment):d.setWGSLFragment(),s&&(d.parent=s),d.shouldUpdate=d.arrayBufferSize>0,d}setBindings(e){for(const t of Object.keys(e)){const i={};for(const s in e[t])s!=="value"&&(i[s]=e[t][s]);if(i.name=t,Object.defineProperty(i,"value",{get(){return i._value},set(s){i._value=s,i.shouldUpdate=!0}}),i.value=e[t].value,i.value instanceof _||i.value instanceof f){const s=i.value._onChangeCallback;i.value._onChangeCallback=()=>{s&&s(),i.shouldUpdate=!0}}this.inputs[t]=i,this.cacheKey+=`${t},${e[t].type},`}}setChildrenBindings(e){if(this.childrenBindings=[],e&&e.length){const t=[];e.sort((i,s)=>{const r=i.count?Math.max(i.count):i.forceArray?1:0,o=s.count?Math.max(s.count):s.forceArray?1:0;return r-o}).forEach(i=>{(i.count&&i.count>1||i.forceArray)&&t.push(i.binding)}),t.length>1&&(t.shift(),L(`BufferBinding: "${this.label}" contains multiple children bindings arrays. These children bindings cannot be added to the BufferBinding: "${t.map(i=>i.label).join(", ")}"`),t.forEach(i=>{e=e.filter(s=>s.binding.name!==i.name)})),this.options.childrenBindings=e,e.forEach(i=>{const s=i.count?Math.max(1,i.count):1;this.cacheKey+=`child(count:${s}):${i.binding.cacheKey}`,this.childrenBindings=[...this.childrenBindings,Array.from(Array(s).keys()).map(r=>i.binding.clone({...i.binding.options,struct:Ei.cloneStruct(i.binding.options.struct)}))].flat()})}}setInputsAlignment(){let e=Object.keys(this.inputs);const t=e.filter(i=>this.inputs[i].type.includes("array"));t.length&&(e.sort((i,s)=>{const r=Math.min(0,this.inputs[i].type.indexOf("array")),o=Math.min(0,this.inputs[s].type.indexOf("array"));return r-o}),t.length>1&&(e=e.filter(i=>!t.includes(i))));for(const i of e){const s=this.inputs[i],r={name:st(s.name??i),key:i,type:s.type},o=s.type.includes("array")&&(Array.isArray(s.value)||ArrayBuffer.isView(s.value));this.bufferElements.push(o?new Ks({...r,arrayLength:s.value.length}):new Di(r))}if(this.bufferElements.forEach((i,s)=>{const r=s===0?0:this.bufferElements[s-1].endOffset+1;i.setAlignment(r)}),t.length>1)if(t.map(r=>{const o=this.inputs[r],a=Hs(o.type.replace("array","").replace("<","").replace(">",""));return Math.ceil(o.value.length/a.numElements)}).every((r,o,a)=>r===a[0])){const r=t.map(h=>{const u=this.inputs[h];return new Oi({name:st(u.name??h),key:h,type:u.type,arrayLength:u.value.length})}),o=t.map(h=>{const u=this.inputs[h];return new Di({name:st(u.name??h),key:h,type:u.type.replace("array","").replace("<","").replace(">","")})});o.forEach((h,u)=>{u===0?this.bufferElements.length?h.setAlignmentFromPosition({row:this.bufferElements[this.bufferElements.length-1].alignment.end.row+1,byte:0}):h.setAlignment(0):h.setAlignment(o[u-1].endOffset+1)});const a=o[o.length-1].endOffset+1-o[0].startOffset;r.forEach((h,u)=>{h.setAlignment(o[u].startOffset,Math.ceil(a/Z)*Z)}),this.bufferElements=[...this.bufferElements,...r]}else L(`BufferBinding: "${this.label}" contains multiple array inputs that should use an interleaved array, but their sizes do not match. These inputs cannot be added to the BufferBinding: "${t.join(", ")}"`)}setBufferAttributes(){const e=this.bufferElements.length?this.bufferElements[this.bufferElements.length-1].paddedByteCount:0;this.arrayBufferSize=e,this.childrenBindings.forEach(t=>{this.arrayBufferSize+=t.arrayBufferSize}),this.arrayBuffer=new ArrayBuffer(this.arrayBufferSize),this.arrayView=new DataView(this.arrayBuffer,0,e),this.childrenBindings.forEach((t,i)=>{let s=e;for(let a=0;a0&&this.childrenBindings[i-1].bufferElements.length?this.childrenBindings[i-1].bufferElements[this.childrenBindings[i-1].bufferElements.length-1].alignment.end.row+1:0;t.bufferElements.forEach(a=>{const h=i===0?r+o:o;a.alignment.start.row+=h,a.alignment.end.row+=h}),t.arrayView=new DataView(this.arrayBuffer,s,t.arrayBuffer.byteLength);for(const a of t.bufferElements)a.setView(this.arrayBuffer,t.arrayView)}),this.buffer.size=this.arrayBuffer.byteLength;for(const t of this.bufferElements)t.setView(this.arrayBuffer,this.arrayView);this.shouldUpdate=this.arrayBufferSize>0}setWGSLFragment(){if(!this.bufferElements.length&&!this.childrenBindings.length)return;const e=rt(this.label);if(this.useStruct){const t={};t[e]={};const i=this.bufferElements.filter(o=>!(o instanceof Oi)),s=this.bufferElements.filter(o=>o instanceof Oi);if(s.length){const o=this.bindingType==="uniform"?`, ${s[0].numElements}`:"";if(i.length){t[`${e}Element`]={},s.forEach(u=>{t[`${e}Element`][u.name]=u.type.replace("array","").replace("<","").replace(">","")}),i.forEach(u=>{t[e][u.name]=u.type});const a=this.bufferElements.find(u=>u.name==="elements")?`${this.name}Elements`:"elements";t[e][a]=`array<${e}Element${o}>`;const h=Lt(this);this.wgslGroupFragment=[`${h} ${this.name}: ${e};`]}else{this.bufferElements.forEach(h=>{t[e][h.name]=h.type.replace("array","").replace("<","").replace(">","")});const a=Lt(this);this.wgslGroupFragment=[`${a} ${this.name}: array<${e}${o}>;`]}}else{i.forEach(a=>{const h=this.bindingType==="uniform"&&"numElements"in a?`array<${a.type.replace("array","").replace("<","").replace(">","")}, ${a.numElements}>`:a.type;t[e][a.name]=h});const o=Lt(this);this.wgslGroupFragment=[`${o} ${this.name}: ${e};`]}this.childrenBindings.length&&this.options.childrenBindings.forEach(o=>{t[e][o.binding.name]=o.count&&o.count>1||o.forceArray?`array<${rt(o.binding.label)}>`:rt(o.binding.label)});const r=this.childrenBindings.length?this.options.childrenBindings.map(o=>o.binding.wgslStructFragment).join(` `)+` -`:"";this.wgslStructFragment=o+Object.keys(i).reverse().map(a=>`struct ${a} { - ${Object.keys(i[a]).map(h=>`${h}: ${i[a][h]}`).join(`, +`:"";this.wgslStructFragment=r+Object.keys(t).reverse().map(o=>`struct ${o} { + ${Object.keys(t[o]).map(a=>`${a}: ${t[o][a]}`).join(`, `)} };`).join(` -`)}else this.wgslStructFragment="",this.wgslGroupFragment=this.bufferElements.map(i=>`${at(this)} ${i.name}: ${i.type};`)}shouldUpdateBinding(e=""){this.inputs[e]&&(this.inputs[e].shouldUpdate=!0)}update(){const e=Object.values(this.inputs);for(const t of e){const i=this.bufferElements.find(s=>s.key===t.name);t.shouldUpdate&&i&&(t.onBeforeUpdate&&t.onBeforeUpdate(),i.update(t.value),this.shouldUpdate=!0,t.shouldUpdate=!1)}this.options.bindings.forEach(t=>{t.update(),t.shouldUpdate&&(this.shouldUpdate=!0)})}extractBufferElementDataFromBufferResult({result:e,bufferElementName:t}){const i=this.bufferElements.find(s=>s.name===t);return i?i.extractDataFromBufferResult(e):e}}class ai extends fe{constructor({label:e="Work",name:t="work",bindingType:i,visibility:s,useStruct:r=!0,access:o="read_write",usage:a=[],struct:h={},shouldCopyResult:l=!1}){i="storage",s=["compute"],super({label:e,name:t,bindingType:i,visibility:s,useStruct:r,access:o,usage:a,struct:h}),this.options={...this.options,shouldCopyResult:l},this.shouldCopyResult=l,this.cacheKey+=`${l},`,this.resultBuffer=new _e}}class ut{constructor(e,{label:t="BindGroup",index:i=0,bindings:s=[],uniforms:r,storages:o}={}){this.type="BindGroup",e=j(e,this.type),this.renderer=e,this.options={label:t,index:i,bindings:s,...r&&{uniforms:r},...o&&{storages:o}},this.index=i,this.uuid=Y(),this.bindings=[],s.length&&this.addBindings(s),(this.options.uniforms||this.options.storages)&&this.setInputBindings(),this.layoutCacheKey="",this.pipelineCacheKey="",this.resetEntries(),this.bindGroupLayout=null,this.bindGroup=null,this.needsPipelineFlush=!1,this.consumers=new Set;for(const a of this.bufferBindings)"buffer"in a&&a.buffer.consumers.add(this.uuid),"resultBuffer"in a&&a.resultBuffer.consumers.add(this.uuid);this.renderer.addBindGroup(this)}setIndex(e){this.index=e}addBindings(e=[]){e.forEach(t=>{"buffer"in t&&(this.renderer.deviceManager.bufferBindings.set(t.cacheKey,t),t.buffer.consumers.add(this.uuid))}),this.bindings=[...this.bindings,...e]}addBinding(e){this.bindings.push(e)}destroyBufferBinding(e){"buffer"in e&&(this.renderer.removeBuffer(e.buffer),e.buffer.consumers.delete(this.uuid),e.buffer.consumers.size||e.buffer.destroy()),"resultBuffer"in e&&(this.renderer.removeBuffer(e.resultBuffer),e.resultBuffer.consumers.delete(this.uuid),e.resultBuffer.consumers.size||e.resultBuffer.destroy())}createInputBindings(e="uniform",t={}){let i=[...Object.keys(t).map(s=>{const r=t[s];if(!r.struct)return;const o={label:je(r.label||s),name:s,bindingType:e,visibility:r.access==="read_write"?["compute"]:r.visibility,useStruct:!0,access:r.access??"read",...r.usage&&{usage:r.usage},struct:r.struct,...r.shouldCopyResult!==void 0&&{shouldCopyResult:r.shouldCopyResult}};if(r.useStruct!==!1){let h=`${e},${r.visibility===void 0?"all":r.access==="read_write"?"compute":r.visibility},true,${r.access??"read"},`;Object.keys(r.struct).forEach(u=>{h+=`${u},${r.struct[u].type},`}),r.shouldCopyResult!==void 0&&(h+=`${r.shouldCopyResult},`);const l=this.renderer.deviceManager.bufferBindings.get(h);if(l)return l.clone(o)}const a=o.access==="read_write"?ai:fe;return r.useStruct!==!1?new a(o):Object.keys(r.struct).map(h=>(o.label=je(r.label?r.label+h:s+h),o.name=s+h,o.useStruct=!1,o.struct={[h]:r.struct[h]},new a(o)))})].flat();return i=i.filter(Boolean),i.forEach(s=>{this.renderer.deviceManager.bufferBindings.set(s.cacheKey,s)}),i}setInputBindings(){this.addBindings([...this.createInputBindings("uniform",this.options.uniforms),...this.createInputBindings("storage",this.options.storages)])}get shouldCreateBindGroup(){return!this.bindGroup&&!!this.bindings.length}resetEntries(){this.entries={bindGroupLayout:[],bindGroup:[]}}createBindGroup(){this.fillEntries(),this.setBindGroupLayout(),this.setBindGroup()}resetBindGroup(){this.entries.bindGroup=[],this.pipelineCacheKey="";for(const e of this.bindings)this.addBindGroupEntry(e);this.setBindGroup()}addBindGroupEntry(e){this.entries.bindGroup.push({binding:this.entries.bindGroup.length,resource:e.resource}),this.pipelineCacheKey+=e.cacheKey}resetBindGroupLayout(){this.entries.bindGroupLayout=[],this.layoutCacheKey="";for(const e of this.bindings)this.addBindGroupLayoutEntry(e);this.setBindGroupLayout()}addBindGroupLayoutEntry(e){this.entries.bindGroupLayout.push({binding:this.entries.bindGroupLayout.length,...e.resourceLayout,visibility:e.visibility}),this.layoutCacheKey+=e.resourceLayoutCacheKey}loseContext(){this.resetEntries();for(const e of this.bufferBindings)e.buffer.reset(),"resultBuffer"in e&&e.resultBuffer.reset();this.bindGroup=null,this.bindGroupLayout=null,this.needsPipelineFlush=!0}restoreContext(){this.shouldCreateBindGroup&&this.createBindGroup();for(const e of this.bufferBindings)e.shouldUpdate=!0}get bufferBindings(){return this.bindings.filter(e=>e instanceof fe||e instanceof ai)}createBindingBuffer(e){e.buffer.createBuffer(this.renderer,{label:this.options.label+": "+e.bindingType+" buffer from: "+e.label,usage:["copySrc","copyDst",e.bindingType,...e.options.usage]}),"resultBuffer"in e&&e.resultBuffer.createBuffer(this.renderer,{label:this.options.label+": Result buffer from: "+e.label,size:e.arrayBuffer.byteLength,usage:["copyDst","mapRead"]})}fillEntries(){for(const e of this.bindings)e.visibility||(e.visibility=GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT|GPUShaderStage.COMPUTE),"buffer"in e&&(e.buffer.GPUBuffer||this.createBindingBuffer(e)),this.addBindGroupLayoutEntry(e),this.addBindGroupEntry(e)}getBindingByName(e=""){return this.bindings.find(t=>t.name===e)}setBindGroupLayout(){const e=this.renderer.deviceManager.bindGroupLayouts.get(this.layoutCacheKey);e?this.bindGroupLayout=e:(this.bindGroupLayout=this.renderer.createBindGroupLayout({label:this.options.label+" layout",entries:this.entries.bindGroupLayout}),this.renderer.deviceManager.bindGroupLayouts.set(this.layoutCacheKey,this.bindGroupLayout))}setBindGroup(){this.bindGroup=this.renderer.createBindGroup({label:this.options.label,layout:this.bindGroupLayout,entries:this.entries.bindGroup})}updateBufferBindings(){this.bindings.forEach((e,t)=>{"buffer"in e&&(e.update(),e.shouldUpdate&&e.buffer.GPUBuffer&&(!e.useStruct&&e.bufferElements.length>1?this.renderer.queueWriteBuffer(e.buffer.GPUBuffer,0,e.bufferElements[t].view):this.renderer.queueWriteBuffer(e.buffer.GPUBuffer,0,e.arrayBuffer),e.shouldUpdate=!1))})}update(){this.updateBufferBindings();const e=this.bindings.some(i=>i.shouldResetBindGroup),t=this.bindings.some(i=>i.shouldResetBindGroupLayout);(e||t)&&this.renderer.onAfterCommandEncoderSubmission.add(()=>{for(const i of this.bindings)i.shouldResetBindGroup=!1,i.shouldResetBindGroupLayout=!1},{once:!0}),t&&(this.resetBindGroupLayout(),this.needsPipelineFlush=!0),e&&this.resetBindGroup()}clone({bindings:e=[],keepLayout:t=!1}={}){const i={...this.options};i.label+=" (copy)";const s=new this.constructor(this.renderer,{label:i.label});s.setIndex(this.index),s.options=i;const r=e.length?e:this.bindings;for(const o of r)s.addBinding(o),"buffer"in o&&(o.buffer.GPUBuffer||this.createBindingBuffer(o),o.buffer.consumers.add(s.uuid),"resultBuffer"in o&&o.resultBuffer.consumers.add(s.uuid)),t||s.addBindGroupLayoutEntry(o),s.addBindGroupEntry(o);return t&&(s.entries.bindGroupLayout=[...this.entries.bindGroupLayout],s.layoutCacheKey=this.layoutCacheKey),s.setBindGroupLayout(),s.setBindGroup(),s}destroy(){this.renderer.removeBindGroup(this);for(const e of this.bufferBindings)this.destroyBufferBinding(e);this.bindings=[],this.bindGroupLayout=null,this.bindGroup=null,this.resetEntries()}}class hi extends ht{constructor({label:e="Texture",name:t="texture",bindingType:i,visibility:s,texture:r,format:o="rgba8unorm",access:a="write",viewDimension:h="2d",multisampled:l=!1}){i=i??"texture",i==="storage"&&(s=["compute"]),super({label:e,name:t,bindingType:i,visibility:s}),this.options={...this.options,texture:r,format:o,access:a,viewDimension:h,multisampled:l},this.cacheKey+=`${o},${a},${h},${l},`,this.resource=r,this.setWGSLFragment()}get resourceLayout(){return $r(this)}get resourceLayoutCacheKey(){return kr(this)}get resource(){return this.texture instanceof GPUTexture?this.texture.createView({label:this.options.label+" view",dimension:this.options.viewDimension}):this.texture instanceof GPUExternalTexture?this.texture:null}set resource(e){(e||this.texture)&&(this.shouldResetBindGroup=!0),this.texture=e}setBindingType(e){e!==this.bindingType&&(e&&(this.shouldResetBindGroupLayout=!0),this.bindingType=e,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`,this.setWGSLFragment())}setFormat(e){const t=e!==this.options.format;this.options.format=e,t&&this.bindingType==="storage"&&(this.setWGSLFragment(),this.shouldResetBindGroupLayout=!0,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`)}setMultisampled(e){const t=e!==this.options.multisampled;this.options.multisampled=e,t&&this.bindingType!=="storage"&&(this.setWGSLFragment(),this.shouldResetBindGroupLayout=!0,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`)}setWGSLFragment(){this.wgslGroupFragment=[`${Ur(this)}`]}}const J=new f,ue=new f,$=new f;class A{constructor(e=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])){this.type="Mat4",this.elements=e}set(e,t,i,s,r,o,a,h,l,u,d,c,p,g,m,x){const v=this.elements;return v[0]=e,v[1]=t,v[2]=i,v[3]=s,v[4]=r,v[5]=o,v[6]=a,v[7]=h,v[8]=l,v[9]=u,v[10]=d,v[11]=c,v[12]=p,v[13]=g,v[14]=m,v[15]=x,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}setFromArray(e=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])){for(let t=0;tt.object3DIndex!==this.object3DIndex)),e&&this.shouldUpdateWorldMatrix(),this._parent=e,this._parent?.children.push(this))}setTransforms(){this.transforms={origin:{model:new f},quaternion:new le,rotation:new f,position:{world:new f},scale:new f(1)},this.rotation.onChange(()=>this.applyRotation()),this.position.onChange(()=>this.applyPosition()),this.scale.onChange(()=>this.applyScale()),this.transformOrigin.onChange(()=>this.applyTransformOrigin())}get rotation(){return this.transforms.rotation}set rotation(e){this.transforms.rotation=e,this.applyRotation()}get quaternion(){return this.transforms.quaternion}set quaternion(e){this.transforms.quaternion=e}get position(){return this.transforms.position.world}set position(e){this.transforms.position.world=e}get scale(){return this.transforms.scale}set scale(e){this.transforms.scale=e,this.applyScale()}get transformOrigin(){return this.transforms.origin.model}set transformOrigin(e){this.transforms.origin.model=e}applyRotation(){this.quaternion.setFromVec3(this.rotation),this.shouldUpdateModelMatrix()}applyPosition(){this.shouldUpdateModelMatrix()}applyScale(){this.shouldUpdateModelMatrix()}applyTransformOrigin(){this.shouldUpdateModelMatrix()}setMatrices(){this.matrices={model:{matrix:new A,shouldUpdate:!0,onUpdate:()=>this.updateModelMatrix()},world:{matrix:new A,shouldUpdate:!0,onUpdate:()=>this.updateWorldMatrix()}}}get modelMatrix(){return this.matrices.model.matrix}set modelMatrix(e){this.matrices.model.matrix=e,this.shouldUpdateModelMatrix()}shouldUpdateModelMatrix(){this.matrices.model.shouldUpdate=!0,this.shouldUpdateWorldMatrix()}get worldMatrix(){return this.matrices.world.matrix}set worldMatrix(e){this.matrices.world.matrix=e,this.shouldUpdateWorldMatrix()}shouldUpdateWorldMatrix(){this.matrices.world.shouldUpdate=!0}lookAt(e=new f,t=this.position,i=new f(0,1,0)){const s=Wr.lookAt(e,t,i);this.quaternion.setFromRotationMatrix(s),this.shouldUpdateModelMatrix()}updateModelMatrix(){this.modelMatrix=this.modelMatrix.composeFromOrigin(this.position,this.quaternion,this.scale,this.transformOrigin),this.shouldUpdateWorldMatrix()}updateWorldMatrix(){this.parent?this.worldMatrix.multiplyMatrices(this.parent.worldMatrix,this.modelMatrix):this.worldMatrix.copy(this.modelMatrix);for(let e=0,t=this.children.length;ee.shouldUpdate)}updateMatrixStack(){if(this.shouldUpdateMatrices(),this.matricesNeedUpdate)for(const e in this.matrices)this.matrices[e].shouldUpdate&&(this.matrices[e].onUpdate(),this.matrices[e].shouldUpdate=!1);for(let e=0,t=this.children.length;en.reduce((e,t)=>e|jr.get(t),0),Yr=(n=[],e)=>n.length?qr(n):e!=="storage"?GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_SRC|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT:GPUTextureUsage.STORAGE_BINDING|GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST,cs=(...n)=>{const e=Math.max(...n);return 1+Math.log2(e)|0};var Xr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},ie=(n,e,t)=>(Xr(n,e,"read from private field"),t?t.call(n):e.get(n)),dt=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Be,Pe,ct,pt;const ps={name:"texture",generateMips:!1,flipY:!1,format:"rgba8unorm",premultipliedAlpha:!1,placeholderColor:[0,0,0,255],useExternalTextures:!0,fromTexture:null,viewDimension:"2d",visibility:["fragment"],cache:!0};class Se extends me{constructor(e,t=ps){super(),dt(this,Be,new f(1)),dt(this,Pe,new f(1)),dt(this,ct,new f(1)),dt(this,pt,new A),this._onSourceLoadedCallback=()=>{},this._onSourceUploadedCallback=()=>{},this.type="Texture",e=j(e,t.label?t.label+" "+this.type:this.type),this.renderer=e,this.uuid=Y();const i={...ps,source:t.fromTexture?t.fromTexture.options.source:null,sourceType:t.fromTexture?t.fromTexture.options.sourceType:null};this.options={...i,...t},this.options.label=this.options.label??this.options.name,this.texture=null,this.externalTexture=null,this.source=null,this.size={width:1,height:1,depth:1},this.textureMatrix=new fe({label:this.options.label+": model matrix",name:this.options.name+"Matrix",useStruct:!1,struct:{[this.options.name+"Matrix"]:{type:"mat4x4f",value:this.modelMatrix}}}),this.renderer.deviceManager.bufferBindings.set(this.textureMatrix.cacheKey,this.textureMatrix),this.setBindings(),this._parentMesh=null,this.sourceLoaded=!1,this.sourceUploaded=!1,this.shouldUpdate=!1,this.renderer.addDOMTexture(this),this.createTexture()}setBindings(){this.bindings=[new hi({label:this.options.label+": texture",name:this.options.name,bindingType:this.options.sourceType==="externalVideo"?"externalTexture":"texture",visibility:this.options.visibility,texture:this.options.sourceType==="externalVideo"?this.externalTexture:this.texture,viewDimension:this.options.viewDimension}),this.textureMatrix]}get textureBinding(){return this.bindings[0]}get parentMesh(){return this._parentMesh}set parentMesh(e){this._parentMesh=e,this.resize()}get sourceLoaded(){return this._sourceLoaded}set sourceLoaded(e){e&&!this.sourceLoaded&&this._onSourceLoadedCallback&&this._onSourceLoadedCallback(),this._sourceLoaded=e}get sourceUploaded(){return this._sourceUploaded}set sourceUploaded(e){e&&!this.sourceUploaded&&this._onSourceUploadedCallback&&this._onSourceUploadedCallback(),this._sourceUploaded=e}setTransforms(){super.setTransforms(),this.transforms.quaternion.setAxisOrder("ZXY"),this.transforms.origin.model.set(.5,.5,0)}updateModelMatrix(){if(!this.parentMesh)return;const e=this.parentMesh.scale?this.parentMesh.scale:new f(1,1,1),t=this.parentMesh.boundingRect?this.parentMesh.boundingRect.width*e.x:this.size.width,i=this.parentMesh.boundingRect?this.parentMesh.boundingRect.height*e.y:this.size.height,s=t/i,r=this.size.width/this.size.height;t>i?(ie(this,Be).set(s,1,1),ie(this,Pe).set(1/r,1,1)):(ie(this,Be).set(1,1/s,1),ie(this,Pe).set(1,r,1));const o=s>r!=t>i?1:t>i?ie(this,Be).x*ie(this,Pe).x:ie(this,Pe).y*ie(this,Be).y;ie(this,ct).set(1/(o*this.scale.x),1/(o*this.scale.y),1),ie(this,pt).rotateFromQuaternion(this.quaternion),this.modelMatrix.identity().premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1)).premultiplyScale(ie(this,ct)).premultiplyScale(ie(this,Be)).premultiply(ie(this,pt)).premultiplyScale(ie(this,Pe)).premultiplyTranslate(this.transformOrigin).translate(this.position)}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.textureMatrix.shouldUpdateBinding(this.options.name+"Matrix")}resize(){this.source&&this.source instanceof HTMLCanvasElement&&(this.source.width!==this.size.width||this.source.height!==this.size.height)&&(this.setSourceSize(),this.createTexture()),this.shouldUpdateModelMatrix()}uploadTexture(){this.renderer.uploadTexture(this),this.shouldUpdate=!1}uploadVideoTexture(){this.externalTexture=this.renderer.importExternalTexture(this.source),this.textureBinding.resource=this.externalTexture,this.textureBinding.setBindingType("externalTexture"),this.shouldUpdate=!1,this.sourceUploaded=!0}copy(e){if(this.options.sourceType==="externalVideo"&&e.options.sourceType!=="externalVideo"){I(`${this.options.label}: cannot copy a GPUTexture to a GPUExternalTexture`);return}else if(this.options.sourceType!=="externalVideo"&&e.options.sourceType==="externalVideo"){I(`${this.options.label}: cannot copy a GPUExternalTexture to a GPUTexture`);return}this.options.fromTexture=e,this.options.sourceType=e.options.sourceType,this.options.generateMips=e.options.generateMips,this.options.flipY=e.options.flipY,this.options.format=e.options.format,this.options.premultipliedAlpha=e.options.premultipliedAlpha,this.options.placeholderColor=e.options.placeholderColor,this.options.useExternalTextures=e.options.useExternalTextures,this.sourceLoaded=e.sourceLoaded,this.sourceUploaded=e.sourceUploaded,e.texture&&(e.sourceLoaded&&(this.size=e.size,this.source=e.source,this.resize()),e.sourceUploaded?(this.texture=e.texture,this.textureBinding.resource=this.texture):this.createTexture())}createTexture(){const e={label:this.options.label,format:this.options.format,size:[this.size.width,this.size.height,this.size.depth],dimensions:this.options.viewDimension==="1d"?"1d":this.options.viewDimension==="3d"?"3d":"2d",usage:this.source?GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST};this.options.sourceType!=="externalVideo"&&(e.mipLevelCount=this.options.generateMips?cs(this.size.width,this.size.height):1,this.texture?.destroy(),this.texture=this.renderer.createTexture(e),this.textureBinding.resource=this.texture),this.shouldUpdate=!0}setSourceSize(){this.size={width:this.source.naturalWidth||this.source.width||this.source.videoWidth,height:this.source.naturalHeight||this.source.height||this.source.videoHeight,depth:1}}async loadImageBitmap(e){const i=await(await fetch(e)).blob();return await createImageBitmap(i,{colorSpaceConversion:"none"})}async loadImage(e){const t=typeof e=="string"?e:e.getAttribute("src");this.options.source=t,this.options.sourceType="image";const i=this.renderer.domTextures.find(s=>s.options.source===t);if(i&&i.texture&&i.sourceUploaded){this.copy(i);return}this.sourceLoaded=!1,this.sourceUploaded=!1,this.source=await this.loadImageBitmap(this.options.source),this.setSourceSize(),this.resize(),this.sourceLoaded=!0,this.createTexture()}onVideoFrameCallback(){this.videoFrameCallbackId&&(this.shouldUpdate=!0,this.source.requestVideoFrameCallback(this.onVideoFrameCallback.bind(this)))}onVideoLoaded(e){this.sourceLoaded||(this.source=e,this.setSourceSize(),this.resize(),this.options.useExternalTextures?(this.options.sourceType="externalVideo",this.texture?.destroy()):(this.options.sourceType="video",this.createTexture()),"requestVideoFrameCallback"in HTMLVideoElement.prototype&&(this.videoFrameCallbackId=this.source.requestVideoFrameCallback(this.onVideoFrameCallback.bind(this))),this.sourceLoaded=!0)}get isVideoSource(){return this.source&&(this.options.sourceType==="video"||this.options.sourceType==="externalVideo")}loadVideo(e){let t;typeof e=="string"?(t=document.createElement("video"),t.src=e):t=e,t.preload="auto",t.muted=!0,t.loop=!0,t.crossOrigin="anonymous",t.setAttribute("playsinline",""),this.options.source=t.src,this.sourceLoaded=!1,this.sourceUploaded=!1,t.readyState>=t.HAVE_ENOUGH_DATA?this.onVideoLoaded(t):t.addEventListener("canplaythrough",this.onVideoLoaded.bind(this,t),{once:!0}),isNaN(t.duration)&&t.load()}loadCanvas(e){this.options.source=e,this.options.sourceType="canvas",this.sourceLoaded=!1,this.sourceUploaded=!1,this.source=e,this.setSourceSize(),this.resize(),this.sourceLoaded=!0,this.createTexture()}onSourceLoaded(e){return e&&(this._onSourceLoadedCallback=e),this}onSourceUploaded(e){return e&&(this._onSourceUploadedCallback=e),this}render(){this.updateMatrixStack(),this.textureMatrix.update(),this.options.sourceType==="externalVideo"&&(this.shouldUpdate=!0),this.isVideoSource&&!this.videoFrameCallbackId&&this.source.readyState>=this.source.HAVE_CURRENT_DATA&&!this.source.paused&&(this.shouldUpdate=!0),this.shouldUpdate&&this.options.sourceType&&this.options.sourceType!=="externalVideo"&&this.uploadTexture()}destroy(){this.videoFrameCallbackId&&this.source.cancelVideoFrameCallback(this.videoFrameCallbackId),this.isVideoSource&&this.source.removeEventListener("canplaythrough",this.onVideoLoaded.bind(this,this.source),{once:!0}),this.renderer.removeDOMTexture(this),this.texture?.destroy(),this.texture=null}}Be=new WeakMap,Pe=new WeakMap,ct=new WeakMap,pt=new WeakMap;class li extends ut{constructor(e,{label:t,index:i=0,bindings:s=[],uniforms:r,storages:o,textures:a=[],samplers:h=[]}={}){const l="TextureBindGroup";if(e=j(e,l),super(e,{label:t,index:i,bindings:s,uniforms:r,storages:o}),this.options={...this.options,textures:[],samplers:[]},a.length)for(const u of a)this.addTexture(u);if(h.length)for(const u of h)this.addSampler(u);this.type=l}addTexture(e){this.textures.push(e),this.addBindings([...e.bindings])}get textures(){return this.options.textures}addSampler(e){this.samplers.push(e),this.addBindings([e.binding])}get samplers(){return this.options.samplers}get shouldCreateBindGroup(){return!this.bindGroup&&!!this.bindings.length&&!this.textures.find(e=>!(e.texture||e.externalTexture))&&!this.samplers.find(e=>!e.sampler)}updateTextures(){for(const e of this.textures)e instanceof Se&&(e.options.fromTexture&&e.options.fromTexture.sourceUploaded&&!e.sourceUploaded&&e.copy(e.options.fromTexture),e.shouldUpdate&&e.options.sourceType&&e.options.sourceType==="externalVideo"&&e.uploadVideoTexture())}update(){this.updateTextures(),super.update()}destroy(){super.destroy(),this.options.textures=[],this.options.samplers=[]}}class fs extends ht{constructor({label:e="Sampler",name:t="sampler",bindingType:i,visibility:s,sampler:r,type:o="filtering"}){i=i??"sampler",super({label:e,name:t,bindingType:i,visibility:s}),this.cacheKey+=`${o},`,this.options={...this.options,sampler:r,type:o},this.resource=r,this.setWGSLFragment()}get resourceLayout(){return{sampler:{type:this.options.type}}}get resourceLayoutCacheKey(){return`sampler,${this.options.type},${this.visibility},`}get resource(){return this.sampler}set resource(e){e&&this.sampler&&(this.shouldResetBindGroup=!0),this.sampler=e}setWGSLFragment(){this.wgslGroupFragment=[`var ${this.name}: ${this.options.type==="comparison"?`${this.bindingType}_comparison`:this.bindingType};`]}}var ms=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},ft=(n,e,t)=>(ms(n,e,"read from private field"),t?t.call(n):e.get(n)),mt=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},gt=(n,e,t,i)=>(ms(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),xt,yt,vt,wt;class gs extends me{constructor({fov:e=50,near:t=.1,far:i=150,width:s=1,height:r=1,pixelRatio:o=1,onMatricesChanged:a=()=>{}}={}){super(),mt(this,xt,void 0),mt(this,yt,void 0),mt(this,vt,void 0),mt(this,wt,void 0),this.uuid=Y(),this.position.set(0,0,10),this.up=new f(0,1,0),this.onMatricesChanged=a,this.size={width:1,height:1},this.setPerspective({fov:e,near:t,far:i,width:s,height:r,pixelRatio:o})}setMatrices(){super.setMatrices(),this.matrices={...this.matrices,view:{matrix:new A,shouldUpdate:!0,onUpdate:()=>{this.viewMatrix.copy(this.worldMatrix).invert()}},projection:{matrix:new A,shouldUpdate:!0,onUpdate:()=>this.updateProjectionMatrix()},viewProjection:{matrix:new A,shouldUpdate:!0,onUpdate:()=>this.viewProjectionMatrix.multiplyMatrices(this.projectionMatrix,this.viewMatrix)}}}get viewMatrix(){return this.matrices.view.matrix}set viewMatrix(e){this.matrices.view.matrix=e,this.shouldUpdateViewMatrices()}get projectionMatrix(){return this.matrices.projection.matrix}set projectionMatrix(e){this.matrices.projection.matrix=e,this.shouldUpdateProjectionMatrices()}get viewProjectionMatrix(){return this.matrices.viewProjection.matrix}shouldUpdateViewMatrices(){this.matrices.view.shouldUpdate=!0,this.matrices.viewProjection.shouldUpdate=!0}shouldUpdateProjectionMatrices(){this.matrices.projection.shouldUpdate=!0,this.matrices.viewProjection.shouldUpdate=!0}updateModelMatrix(){super.updateModelMatrix(),this.setVisibleSize(),this.shouldUpdateViewMatrices()}updateWorldMatrix(){super.updateWorldMatrix(),this.shouldUpdateViewMatrices()}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.onMatricesChanged()}get fov(){return ft(this,xt)}set fov(e){e=Math.max(1,Math.min(e??this.fov,179)),e!==this.fov&&(gt(this,xt,e),this.shouldUpdateProjectionMatrices()),this.setVisibleSize(),this.setCSSPerspective()}get near(){return ft(this,yt)}set near(e){e=Math.max(e??this.near,.01),e!==this.near&&(gt(this,yt,e),this.shouldUpdateProjectionMatrices())}get far(){return ft(this,vt)}set far(e){e=Math.max(e??this.far,this.near+1),e!==this.far&&(gt(this,vt,e),this.shouldUpdateProjectionMatrices())}get pixelRatio(){return ft(this,wt)}set pixelRatio(e){gt(this,wt,e??this.pixelRatio),this.setCSSPerspective()}setSize({width:e,height:t}){(e!==this.size.width||t!==this.size.height)&&this.shouldUpdateProjectionMatrices(),this.size.width=e,this.size.height=t,this.setVisibleSize(),this.setCSSPerspective()}setPerspective({fov:e=this.fov,near:t=this.near,far:i=this.far,width:s=this.size.width,height:r=this.size.height,pixelRatio:o=this.pixelRatio}={}){this.setSize({width:s,height:r}),this.pixelRatio=o,this.fov=e,this.near=t,this.far=i}setCSSPerspective(){this.CSSPerspective=Math.pow(Math.pow(this.size.width/(2*this.pixelRatio),2)+Math.pow(this.size.height/(2*this.pixelRatio),2),.5)/Math.tan(this.fov*.5*Math.PI/180)}getVisibleSizeAtDepth(e=0){const t=this.position.z;e{if(!e.has(n))throw TypeError("Cannot "+t)},Hr=(n,e,t)=>(xs(n,e,"read from private field"),t?t.call(n):e.get(n)),Zr=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Kr=(n,e,t,i)=>(xs(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),bt;const ys={label:"Texture",name:"renderTexture",type:"texture",access:"write",fromTexture:null,viewDimension:"2d",sampleCount:1,qualityRatio:1,generateMips:!1,flipY:!1,premultipliedAlpha:!1,autoDestroy:!0};class se{constructor(e,t=ys){Zr(this,bt,!0),e=j(e,t.label?t.label+" Texture":"Texture"),this.type="Texture",this.renderer=e,this.uuid=Y(),this.options={...ys,...t},this.options.format==="rgba32float"&&!this.renderer.deviceManager.adapter.features.has("float32-filterable")&&(this.options.format="rgba16float"),t.fromTexture&&(this.options.format=t.fromTexture.texture.format,this.options.sampleCount=t.fromTexture.texture.sampleCount,this.options.viewDimension=t.fromTexture.options.viewDimension),this.options.format||(this.options.format=this.renderer.options.preferredFormat),this.size=this.options.fixedSize?{width:this.options.fixedSize.width*this.options.qualityRatio,height:this.options.fixedSize.height*this.options.qualityRatio,depth:this.options.fixedSize.depth??this.options.viewDimension.indexOf("cube")!==-1?6:1}:{width:Math.floor(this.renderer.canvas.width*this.options.qualityRatio),height:Math.floor(this.renderer.canvas.height*this.options.qualityRatio),depth:this.options.viewDimension.indexOf("cube")!==-1?6:1},this.options.fixedSize&&Kr(this,bt,!1),this.setBindings(),this.renderer.addTexture(this),this.createTexture()}copy(e){this.options.fromTexture=e,this.createTexture()}copyGPUTexture(e){this.size={width:e.width,height:e.height,depth:e.depthOrArrayLayers},this.options.format=e.format,this.options.sampleCount=e.sampleCount,this.texture=e,this.textureBinding.setFormat(this.options.format),this.textureBinding.setMultisampled(this.options.sampleCount>1),this.textureBinding.resource=this.texture}createTexture(){if(!(!this.size.width||!this.size.height)){if(this.options.fromTexture){this.copyGPUTexture(this.options.fromTexture.texture);return}this.texture?.destroy(),this.texture=this.renderer.createTexture({label:this.options.label,format:this.options.format,size:[this.size.width,this.size.height,this.size.depth??1],dimensions:this.options.viewDimension,sampleCount:this.options.sampleCount,mipLevelCount:this.options.generateMips?cs(this.size.width,this.size.height,this.size.depth??1):1,usage:Yr(this.options.usage,this.options.type)}),this.textureBinding.resource=this.texture}}uploadSource({source:e,width:t=this.size.width,height:i=this.size.height,depth:s=this.size.depth,origin:r=[0,0,0],colorSpace:o="srgb"}){this.renderer.device.queue.copyExternalImageToTexture({source:e,flipY:this.options.flipY},{texture:this.texture,premultipliedAlpha:this.options.premultipliedAlpha,origin:r,colorSpace:o},[t,i,s]),this.texture.mipLevelCount>1&&ri(this.renderer.device,this.texture)}uploadData({width:e=this.size.width,height:t=this.size.height,depth:i=this.size.depth,origin:s=[0,0,0],data:r=new Float32Array(e*t*4)}){this.renderer.device.queue.writeTexture({texture:this.texture,origin:s},r,{bytesPerRow:e*r.BYTES_PER_ELEMENT*4,rowsPerImage:t},[e,t,i]),this.texture.mipLevelCount>1&&ri(this.renderer.device,this.texture)}setBindings(){this.bindings=[new hi({label:this.options.label+": "+this.options.name+" texture",name:this.options.name,bindingType:this.options.type,visibility:this.options.visibility,texture:this.texture,format:this.options.format,viewDimension:this.options.viewDimension,multisampled:this.options.sampleCount>1})]}get textureBinding(){return this.bindings[0]}resize(e=null){Hr(this,bt)&&(e||(e={width:Math.floor(this.renderer.canvas.width*this.options.qualityRatio),height:Math.floor(this.renderer.canvas.height*this.options.qualityRatio),depth:1}),!(e.width===this.size.width&&e.height===this.size.height&&e.depth===this.size.depth)&&(this.size=e,this.createTexture()))}destroy(){this.renderer.removeTexture(this),this.options.fromTexture||this.texture?.destroy(),this.texture=null}}bt=new WeakMap;class ui{constructor(e,t){this.type="Material",e=j(e,this.type),this.renderer=e,this.uuid=Y();const{shaders:i,label:s,useAsyncPipeline:r,uniforms:o,storages:a,bindings:h,bindGroups:l,samplers:u,textures:d,domTextures:c}=t;this.options={shaders:i,label:s,useAsyncPipeline:r===void 0?!0:r,...o!==void 0&&{uniforms:o},...a!==void 0&&{storages:a},...h!==void 0&&{bindings:h},...l!==void 0&&{bindGroups:l},...u!==void 0&&{samplers:u},...d!==void 0&&{textures:d},...c!==void 0&&{domTextures:c}},this.bindGroups=[],this.texturesBindGroups=[],this.clonedBindGroups=[],this.setBindGroups(),this.setTextures(),this.setSamplers()}compileMaterial(){const e=this.texturesBindGroup.bindings.length?1:0;this.bindGroups.length>=this.inputsBindGroups.length+e||this.createBindGroups()}get ready(){return!!(this.renderer.ready&&this.pipelineEntry&&this.pipelineEntry.pipeline&&this.pipelineEntry.ready)}loseContext(){for(const e of this.domTextures)e.texture=null,e.sourceUploaded=!1;for(const e of this.textures)e.texture=null;[...this.bindGroups,...this.clonedBindGroups,...this.inputsBindGroups].forEach(e=>e.loseContext()),this.pipelineEntry.pipeline=null}restoreContext(){for(const e of this.samplers)e.createSampler(),e.binding.resource=e.sampler;for(const e of this.domTextures)e.createTexture(),e.resize();for(const e of this.textures)e.resize(e.size);[...this.bindGroups,...this.clonedBindGroups,...this.inputsBindGroups].forEach(e=>{e.restoreContext()})}getShaderCode(e="full"){return this.pipelineEntry?(e=(()=>{switch(e){case"vertex":case"fragment":case"compute":case"full":return e;default:return"full"}})(),this.pipelineEntry.shaders[e].code):""}getAddedShaderCode(e="vertex"){return this.pipelineEntry?(e=(()=>{switch(e){case"vertex":case"fragment":case"compute":return e;default:return"vertex"}})(),this.pipelineEntry.shaders[e].head):""}setBindGroups(){if(this.uniforms={},this.storages={},this.inputsBindGroups=[],this.inputsBindings=new Map,this.options.uniforms||this.options.storages||this.options.bindings){const e=new ut(this.renderer,{label:this.options.label+": Bindings bind group",uniforms:this.options.uniforms,storages:this.options.storages,bindings:this.options.bindings});this.processBindGroupBindings(e),this.inputsBindGroups.push(e),e.consumers.add(this.uuid)}this.options.bindGroups?.forEach(e=>{this.processBindGroupBindings(e),this.inputsBindGroups.push(e),e.consumers.add(this.uuid)})}get texturesBindGroup(){return this.texturesBindGroups[0]}processBindGroupBindings(e){for(const t of e.bindings)t.bindingType==="uniform"&&(this.uniforms={...this.uniforms,[t.name]:t.inputs}),t.bindingType==="storage"&&(this.storages={...this.storages,[t.name]:t.inputs}),this.inputsBindings.set(t.name,t)}createBindGroups(){this.texturesBindGroup.shouldCreateBindGroup&&(this.texturesBindGroup.setIndex(this.bindGroups.length),this.texturesBindGroup.createBindGroup(),this.bindGroups.push(this.texturesBindGroup));for(const e of this.inputsBindGroups)e.shouldCreateBindGroup&&(e.setIndex(this.bindGroups.length),e.createBindGroup(),this.bindGroups.push(e));this.options.bindGroups?.forEach(e=>{if(!e.shouldCreateBindGroup&&!this.bindGroups.find(t=>t.uuid===e.uuid)&&(e.setIndex(this.bindGroups.length),this.bindGroups.push(e)),e instanceof li&&!this.texturesBindGroups.find(t=>t.uuid===e.uuid)){this.texturesBindGroups.push(e);for(const t of e.textures)t instanceof Se&&!this.domTextures.find(i=>i.uuid===t.uuid)?this.domTextures.push(t):t instanceof se&&!this.textures.find(i=>i.uuid===t.uuid)&&this.textures.push(t)}})}cloneBindGroup({bindGroup:e,bindings:t=[],keepLayout:i=!0}){if(!e)return null;const s=e.clone({bindings:t,keepLayout:i});return this.clonedBindGroups.push(s),s}getBindGroupByBindingName(e=""){return(this.ready?this.bindGroups:this.inputsBindGroups).find(t=>t.bindings.find(i=>i.name===e))}destroyBindGroup(e){e.consumers.delete(this.uuid),e.consumers.size||e.destroy()}destroyBindGroups(){this.bindGroups.forEach(e=>this.destroyBindGroup(e)),this.clonedBindGroups.forEach(e=>this.destroyBindGroup(e)),this.texturesBindGroups.forEach(e=>this.destroyBindGroup(e)),this.texturesBindGroups=[],this.inputsBindGroups=[],this.bindGroups=[],this.clonedBindGroups=[]}updateBindGroups(){for(const e of this.bindGroups)this.updateBindGroup(e)}updateBindGroup(e){e.update(),e.needsPipelineFlush&&this.pipelineEntry.ready&&(this.pipelineEntry.flushPipelineEntry(this.bindGroups),e.needsPipelineFlush=!1)}getBindingByName(e=""){return this.inputsBindings.get(e)}getBufferBindingByName(e=""){const t=this.getBindingByName(e);return t&&"buffer"in t?t:void 0}shouldUpdateInputsBindings(e,t){if(!e)return;const i=this.getBindingByName(e);i&&(t?i.shouldUpdateBinding(t):Object.keys(i.inputs).forEach(s=>i.shouldUpdateBinding(s)))}setTextures(){this.domTextures=[],this.textures=[],this.texturesBindGroups.push(new li(this.renderer,{label:this.options.label+": Textures bind group"})),this.texturesBindGroup.consumers.add(this.uuid),this.options.domTextures?.forEach(e=>{this.addTexture(e)}),this.options.textures?.forEach(e=>{this.addTexture(e)})}addTexture(e){e instanceof Se?this.domTextures.push(e):e instanceof se&&this.textures.push(e),(this.options.shaders.vertex&&this.options.shaders.vertex.code.indexOf(e.options.name)!==-1||this.options.shaders.fragment&&this.options.shaders.fragment.code.indexOf(e.options.name)!==-1||this.options.shaders.compute&&this.options.shaders.compute.code.indexOf(e.options.name)!==-1)&&this.texturesBindGroup.addTexture(e)}destroyTexture(e){if(e.options.cache||!e.options.autoDestroy)return;const t=this.renderer.getObjectsByTexture(e);(!t||!t.some(s=>s.material.uuid!==this.uuid))&&e.destroy()}destroyTextures(){this.domTextures?.forEach(e=>this.destroyTexture(e)),this.textures?.forEach(e=>this.destroyTexture(e)),this.domTextures=[],this.textures=[]}setSamplers(){if(this.samplers=[],this.options.samplers?.forEach(t=>{this.addSampler(t)}),!this.samplers.find(t=>t.name==="defaultSampler")){const t=new Ae(this.renderer,{label:"Default sampler",name:"defaultSampler"});this.addSampler(t)}}addSampler(e){this.samplers.push(e),(this.options.shaders.vertex&&this.options.shaders.vertex.code.indexOf(e.name)!==-1||this.options.shaders.fragment&&this.options.shaders.fragment.code.indexOf(e.name)!==-1||this.options.shaders.compute&&this.options.shaders.compute.code.indexOf(e.name)!==-1)&&this.texturesBindGroup.addSampler(e)}async getBufferResult(e){return await e.mapBufferAsync()}async getBufferBindingResultByBindingName(e=""){const t=this.getBufferBindingByName(e);if(t&&"buffer"in t){const i=this.renderer.copyBufferToBuffer({srcBuffer:t.buffer});return await this.getBufferResult(i)}else return new Float32Array(0)}async getBufferElementResultByNames({bindingName:e,bufferElementName:t}){const i=await this.getBufferBindingResultByBindingName(e);if(!t||i.length)return i;{const s=this.getBufferBindingByName(e);return s?s.extractBufferElementDataFromBufferResult({result:i,bufferElementName:t}):i}}onBeforeRender(){this.compileMaterial();for(const e of this.domTextures)e.render();this.updateBindGroups()}setPipeline(e){this.renderer.pipelineManager.setCurrentPipeline(e,this.pipelineEntry)}setActiveBindGroups(e){this.renderer.pipelineManager.setActiveBindGroups(e,this.bindGroups)}render(e){this.ready&&(this.setPipeline(e),this.setActiveBindGroups(e))}destroy(){this.destroyBindGroups(),this.destroyTextures()}}class vs extends ui{constructor(e,t){const i="ComputeMaterial";e=j(e,i),super(e,t),this.type=i,this.renderer=e;let{shaders:s,dispatchSize:r}=t;(!s||!s.compute)&&(s={compute:{code:"",entryPoint:"main"}}),s.compute.code||(s.compute.code="@compute @workgroup_size(1) fn main(){}"),s.compute.entryPoint||(s.compute.entryPoint="main"),this.options={...this.options,shaders:s,...t.dispatchSize!==void 0&&{dispatchSize:t.dispatchSize}},r||(r=1),Array.isArray(r)?(r[0]=Math.ceil(r[0]??1),r[1]=Math.ceil(r[1]??1),r[2]=Math.ceil(r[2]??1)):isNaN(r)||(r=[Math.ceil(r),1,1]),this.dispatchSize=r,this.pipelineEntry=this.renderer.pipelineManager.createComputePipeline({renderer:this.renderer,label:this.options.label+" compute pipeline",shaders:this.options.shaders,useAsync:this.options.useAsyncPipeline})}setPipelineEntryProperties(){this.pipelineEntry.setPipelineEntryProperties({bindGroups:this.bindGroups})}async compilePipelineEntry(){await this.pipelineEntry.compilePipelineEntry()}async compileMaterial(){this.ready||(super.compileMaterial(),this.pipelineEntry&&this.pipelineEntry.canCompile&&(this.setPipelineEntryProperties(),await this.compilePipelineEntry()))}getShaderCode(e="compute"){return super.getShaderCode(e)}getAddedShaderCode(e="compute"){return super.getAddedShaderCode(e)}useCustomRender(e){e&&(this._useCustomRenderCallback=e)}render(e){if(this.ready)if(this.setPipeline(e),this._useCustomRenderCallback!==void 0)this._useCustomRenderCallback(e);else{for(const t of this.bindGroups)e.setBindGroup(t.index,t.bindGroup);e.dispatchWorkgroups(this.dispatchSize[0],this.dispatchSize[1],this.dispatchSize[2])}}copyBufferToResult(e){for(const t of this.bindGroups)t.bufferBindings.forEach(i=>{i.shouldCopyResult&&this.renderer.copyBufferToBuffer({srcBuffer:i.buffer,dstBuffer:i.resultBuffer,commandEncoder:e})})}async getComputeResult({bindingName:e="",bufferElementName:t=""}){const i=this.getBufferBindingByName(e);if(i&&"resultBuffer"in i){const s=await this.getBufferResult(i.resultBuffer);return t&&s.length?i.extractBufferElementDataFromBufferResult({result:s,bufferElementName:t}):s}else return new Float32Array(0)}}var ws=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},bs=(n,e,t)=>(ws(n,e,"read from private field"),t?t.call(n):e.get(n)),Qr=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Jr=(n,e,t,i)=>(ws(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),qe;let en=0;class Ms{constructor(e,t={}){Qr(this,qe,!0),this._onReadyCallback=()=>{},this._onBeforeRenderCallback=()=>{},this._onRenderCallback=()=>{},this._onAfterRenderCallback=()=>{},this._onAfterResizeCallback=()=>{};const i="ComputePass";e=j(e,t.label?`${t.label} ${i}`:i),t.label=t.label??"ComputePass "+e.computePasses?.length,this.renderer=e,this.type=i,this.uuid=Y(),Object.defineProperty(this,"index",{value:en++});const{label:s,shaders:r,renderOrder:o,uniforms:a,storages:h,bindGroups:l,samplers:u,domTextures:d,textures:c,autoRender:p,useAsyncPipeline:g,texturesOptions:m,dispatchSize:x}=t;this.options={label:s,shaders:r,...p!==void 0&&{autoRender:p},...o!==void 0&&{renderOrder:o},...x!==void 0&&{dispatchSize:x},useAsyncPipeline:g===void 0?!0:g,texturesOptions:m},this.renderOrder=o??0,p!==void 0&&Jr(this,qe,p),this.userData={},this.ready=!1,this.setMaterial({label:this.options.label,shaders:this.options.shaders,uniforms:a,storages:h,bindGroups:l,samplers:u,textures:c,domTextures:d,useAsyncPipeline:g,dispatchSize:x}),this.addToScene()}get ready(){return this._ready}set ready(e){e&&this._onReadyCallback&&this._onReadyCallback(),this._ready=e}addToScene(){this.renderer.computePasses.push(this),bs(this,qe)&&this.renderer.scene.addComputePass(this)}removeFromScene(){bs(this,qe)&&this.renderer.scene.removeComputePass(this),this.renderer.computePasses=this.renderer.computePasses.filter(e=>e.uuid!==this.uuid)}setMaterial(e){this.useMaterial(new vs(this.renderer,e))}useMaterial(e){this.material=e}loseContext(){this.material.loseContext()}restoreContext(){this.material.restoreContext()}get domTextures(){return this.material?.domTextures||[]}get textures(){return this.material?.textures||[]}createDOMTexture(e){e.name||(e.name="texture"+(this.textures.length+this.domTextures.length)),e.label||(e.label=this.options.label+" "+e.name);const t=new Se(this.renderer,{...e,...this.options.texturesOptions});return this.addTexture(t),t}createTexture(e){e.name||(e.name="texture"+(this.textures.length+this.domTextures.length));const t=new se(this.renderer,e);return this.addTexture(t),t}addTexture(e){this.material.addTexture(e)}get uniforms(){return this.material?.uniforms}get storages(){return this.material?.storages}resize(){this._onAfterResizeCallback&&this._onAfterResizeCallback()}onReady(e){return e&&(this._onReadyCallback=e),this}onBeforeRender(e){return e&&(this._onBeforeRenderCallback=e),this}onRender(e){return e&&(this._onRenderCallback=e),this}onAfterRender(e){return e&&(this._onAfterRenderCallback=e),this}useCustomRender(e){return this.material.useCustomRender(e),this}onAfterResize(e){return e&&(this._onAfterResizeCallback=e),this}onBeforeRenderPass(){this.renderer.ready&&(this.material&&this.material.ready&&!this.ready&&(this.ready=!0),this._onBeforeRenderCallback&&this._onBeforeRenderCallback(),this.material.onBeforeRender())}onRenderPass(e){this.material.ready&&(this._onRenderCallback&&this._onRenderCallback(),this.material.render(e))}onAfterRenderPass(){this._onAfterRenderCallback&&this._onAfterRenderCallback()}render(e){this.onBeforeRenderPass(),this.renderer.ready&&(!this.renderer.production&&e.pushDebugGroup(this.options.label),this.onRenderPass(e),!this.renderer.production&&e.popDebugGroup(),this.onAfterRenderPass())}copyBufferToResult(e){this.material?.copyBufferToResult(e)}async getComputeResult({bindingName:e,bufferElementName:t}){return await this.material?.getComputeResult({bindingName:e,bufferElementName:t})}remove(){this.removeFromScene(),this.destroy()}destroy(){this.material?.destroy()}}qe=new WeakMap;const ae=[new f,new f,new f,new f,new f,new f,new f,new f];class de{constructor(e=new f(1/0),t=new f(-1/0)){this.min=e,this.max=t}set(e=new f(1/0),t=new f(-1/0)){return this.min.copy(e),this.max.copy(t),this}isEmpty(){return this.max.x{},onLeaveView:o=()=>{}}){this.boundingBox=e,this.clipSpaceOBB=new de,this.modelViewProjectionMatrix=t,this.containerBoundingRect=i,this.DOMFrustumMargins={...Cs,...s},this.projectedBoundingRect={top:0,right:0,bottom:0,left:0,width:0,height:0,x:0,y:0},this.onReEnterView=r,this.onLeaveView=o,this.isIntersecting=!1}setContainerBoundingRect(e){this.containerBoundingRect=e}get DOMFrustumBoundingRect(){return{top:this.projectedBoundingRect.top-this.DOMFrustumMargins.top,right:this.projectedBoundingRect.right+this.DOMFrustumMargins.right,bottom:this.projectedBoundingRect.bottom+this.DOMFrustumMargins.bottom,left:this.projectedBoundingRect.left-this.DOMFrustumMargins.left}}computeClipSpaceOBB(){this.clipSpaceOBB.set(),this.boundingBox.applyMat4(this.modelViewProjectionMatrix,this.clipSpaceOBB)}setDocumentCoordsFromClipSpaceOBB(){this.computeClipSpaceOBB();const e=(this.clipSpaceOBB.min.x+1)*.5,t=(this.clipSpaceOBB.max.x+1)*.5,i=1-(this.clipSpaceOBB.min.y+1)*.5,s=1-(this.clipSpaceOBB.max.y+1)*.5,{width:r,height:o,top:a,left:h}=this.containerBoundingRect;this.projectedBoundingRect={left:e*r+h,x:e*r+h,top:s*o+a,y:s*o+a,right:t*r+h,bottom:i*o+a,width:t*r+h-(e*r+h),height:i*o+a-(s*o+a)}}setDocumentCoordsFromClipSpaceSphere(e={center:new f,radius:0}){const t=(e.center.x+1)*.5,i=1-(e.center.y+1)*.5,{width:s,height:r,top:o,left:a}=this.containerBoundingRect;this.projectedBoundingRect.width=e.radius*r*.5,this.projectedBoundingRect.height=e.radius*r*.5,this.projectedBoundingRect.left=t*s+a-this.projectedBoundingRect.width*.5,this.projectedBoundingRect.x=t*s+a-this.projectedBoundingRect.width*.5,this.projectedBoundingRect.top=i*r+o-this.projectedBoundingRect.height*.5,this.projectedBoundingRect.y=i*r+o-this.projectedBoundingRect.height*.5,this.projectedBoundingRect.right=this.projectedBoundingRect.left+this.projectedBoundingRect.width,this.projectedBoundingRect.bottom=this.projectedBoundingRect.top+this.projectedBoundingRect.height}intersectsContainer(){Math.round(this.DOMFrustumBoundingRect.right)<=this.containerBoundingRect.left||Math.round(this.DOMFrustumBoundingRect.left)>=this.containerBoundingRect.left+this.containerBoundingRect.width||Math.round(this.DOMFrustumBoundingRect.bottom)<=this.containerBoundingRect.top||Math.round(this.DOMFrustumBoundingRect.top)>=this.containerBoundingRect.top+this.containerBoundingRect.height?(this.isIntersecting&&this.onLeaveView(),this.isIntersecting=!1):(this.isIntersecting||this.onReEnterView(),this.isIntersecting=!0)}}class di{constructor({verticesOrder:e="ccw",topology:t="triangle-list",instancesCount:i=1,vertexBuffers:s=[],mapBuffersAtCreation:r=!0}={}){this.verticesCount=0,this.verticesOrder=e,this.topology=t,this.instancesCount=i,this.ready=!1,this.boundingBox=new de,this.type="Geometry",this.uuid=Y(),this.vertexBuffers=[],this.consumers=new Set,this.options={verticesOrder:e,topology:t,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:r};const o=s.find(a=>a.name==="attributes");!s.length||!o?this.addVertexBuffer({name:"attributes"}):o&&s.sort((a,h)=>{const l=a.name!=="attributes"?1/0:-1,u=h.name!=="attributes"?1/0:-1;return l-u});for(const a of s)this.addVertexBuffer({stepMode:a.stepMode??"vertex",name:a.name,attributes:a.attributes,...a.array&&{array:a.array},...a.buffer&&{buffer:a.buffer},...a.bufferOffset&&{bufferOffset:a.bufferOffset},...a.bufferSize&&{bufferSize:a.bufferSize}});o&&this.setWGSLFragment()}loseContext(){this.ready=!1;for(const e of this.vertexBuffers)e.buffer.destroy()}restoreContext(e){if(!this.ready){for(const t of this.vertexBuffers)!t.buffer.GPUBuffer&&t.buffer.consumers.size===0&&(t.buffer.createBuffer(e),this.uploadBuffer(e,t)),t.buffer.consumers.add(this.uuid);this.ready=!0}}addVertexBuffer({stepMode:e="vertex",name:t,attributes:i=[],buffer:s=null,array:r=null,bufferOffset:o=0,bufferSize:a=null}={}){s=s||new _e;const h={name:t??"attributes"+this.vertexBuffers.length,stepMode:e,arrayStride:0,bufferLength:0,attributes:[],buffer:s,array:r,bufferOffset:o,bufferSize:a};return i?.forEach(l=>{this.setAttribute({vertexBuffer:h,...l})}),this.vertexBuffers.push(h),h}getVertexBufferByName(e=""){return this.vertexBuffers.find(t=>t.name===e)}setAttribute({vertexBuffer:e=this.vertexBuffers[0],name:t,type:i="vec3f",bufferFormat:s="float32x3",size:r=3,array:o=new Float32Array(this.verticesCount*r),verticesStride:a=1}){const h=e.attributes,l=h.length;t||(t="geometryAttribute"+l),t==="position"&&(i!=="vec3f"||s!=="float32x3"||r!==3)&&(I(`Geometry 'position' attribute must have this exact properties set: +`)}else this.wgslStructFragment="",this.wgslGroupFragment=this.bufferElements.map(t=>`${Lt(this)} ${t.name}: ${t.type};`)}shouldUpdateBinding(e=""){this.inputs[e]&&(this.inputs[e].shouldUpdate=!0)}update(){const e=Object.values(this.inputs);for(const t of e){const i=this.bufferElements.find(s=>s.key===t.name);t.shouldUpdate&&i&&(t.onBeforeUpdate&&t.onBeforeUpdate(),i.update(t.value),this.shouldUpdate=!0,t.shouldUpdate=!1)}if(this.childrenBindings.forEach(t=>{t.update(),t.shouldUpdate&&(this.shouldUpdate=!0),t.shouldUpdate=!1}),this.shouldUpdate&&this.parent&&this.parentViewSetBufferEls){let t=0;this.parentViewSetBufferEls.forEach((i,s)=>{const{bufferElement:r,viewSetFunction:o}=i;r.view.forEach(a=>{o(t*r.view.BYTES_PER_ELEMENT,a,!0),t++})}),this.parent.shouldUpdate=!0,this.shouldUpdate=!1}}extractBufferElementDataFromBufferResult({result:e,bufferElementName:t}){const i=this.bufferElements.find(s=>s.name===t);return i?i.extractDataFromBufferResult(e):e}};At=new WeakMap;let pe=kn;class Fi extends pe{constructor({label:e="Work",name:t="work",bindingType:i,visibility:s,useStruct:r=!0,access:o="read_write",usage:a=[],struct:h={},childrenBindings:u=[],parent:l=null,minOffset:c=256,offset:p=0,shouldCopyResult:d=!1}){i="storage",s=["compute"],super({label:e,name:t,bindingType:i,visibility:s,useStruct:r,access:o,usage:a,struct:h,childrenBindings:u,parent:l,minOffset:c,offset:p}),this.options={...this.options,shouldCopyResult:d},this.shouldCopyResult=d,this.cacheKey+=`${d},`,this.resultBuffer=new Ve}}class Dt{constructor(e,{label:t="BindGroup",index:i=0,bindings:s=[],uniforms:r,storages:o}={}){this.type="BindGroup",e=V(e,this.type),this.renderer=e,this.options={label:t,index:i,bindings:s,...r&&{uniforms:r},...o&&{storages:o}},this.index=i,this.uuid=H(),this.bindings=[],s.length&&this.addBindings(s),(this.options.uniforms||this.options.storages)&&this.setInputBindings(),this.layoutCacheKey="",this.pipelineCacheKey="",this.resetEntries(),this.bindGroupLayout=null,this.bindGroup=null,this.needsPipelineFlush=!1,this.consumers=new Set;for(const a of this.bufferBindings)"buffer"in a&&(a.parent?a.parent.buffer.consumers.add(this.uuid):a.buffer.consumers.add(this.uuid)),"resultBuffer"in a&&a.resultBuffer.consumers.add(this.uuid);this.renderer.addBindGroup(this)}setIndex(e){this.index=e}addBindings(e=[]){e.forEach(t=>{"buffer"in t&&(t.parent?(this.renderer.deviceManager.bufferBindings.set(t.parent.cacheKey,t.parent),t.parent.buffer.consumers.add(this.uuid)):(this.renderer.deviceManager.bufferBindings.set(t.cacheKey,t),t.buffer.consumers.add(this.uuid)))}),this.bindings=[...this.bindings,...e]}addBinding(e){this.bindings.push(e)}destroyBufferBinding(e){"buffer"in e&&(this.renderer.removeBuffer(e.buffer),e.buffer.consumers.delete(this.uuid),e.buffer.consumers.size||e.buffer.destroy(),e.parent&&(e.parent.buffer.consumers.delete(this.uuid),e.parent.buffer.consumers.size||(this.renderer.removeBuffer(e.parent.buffer),e.parent.buffer.destroy()))),"resultBuffer"in e&&(this.renderer.removeBuffer(e.resultBuffer),e.resultBuffer.consumers.delete(this.uuid),e.resultBuffer.consumers.size||e.resultBuffer.destroy())}createInputBindings(e="uniform",t={}){let i=[...Object.keys(t).map(s=>{const r=t[s];if(!r.struct)return;const o={label:rt(r.label||s),name:s,bindingType:e,visibility:r.access==="read_write"?["compute"]:r.visibility,useStruct:!0,access:r.access??"read",...r.usage&&{usage:r.usage},struct:r.struct,...r.shouldCopyResult!==void 0&&{shouldCopyResult:r.shouldCopyResult}};if(r.useStruct!==!1){let h=`${e},${r.visibility===void 0?"all":r.access==="read_write"?"compute":r.visibility},true,${r.access??"read"},`;Object.keys(r.struct).forEach(l=>{h+=`${l},${r.struct[l].type},`}),r.shouldCopyResult!==void 0&&(h+=`${r.shouldCopyResult},`);const u=this.renderer.deviceManager.bufferBindings.get(h);if(u)return u.clone(o)}const a=o.access==="read_write"?Fi:pe;return r.useStruct!==!1?new a(o):Object.keys(r.struct).map(h=>(o.label=rt(r.label?r.label+h:s+h),o.name=s+h,o.useStruct=!1,o.struct={[h]:r.struct[h]},new a(o)))})].flat();return i=i.filter(Boolean),i.forEach(s=>{this.renderer.deviceManager.bufferBindings.set(s.cacheKey,s)}),i}setInputBindings(){this.addBindings([...this.createInputBindings("uniform",this.options.uniforms),...this.createInputBindings("storage",this.options.storages)])}get shouldCreateBindGroup(){return!this.bindGroup&&!!this.bindings.length}resetEntries(){this.entries={bindGroupLayout:[],bindGroup:[]}}createBindGroup(){this.fillEntries(),this.setBindGroupLayout(),this.setBindGroup()}resetBindGroup(){this.entries.bindGroup=[],this.pipelineCacheKey="";for(const e of this.bindings)this.addBindGroupEntry(e);this.setBindGroup()}addBindGroupEntry(e){this.entries.bindGroup.push({binding:this.entries.bindGroup.length,resource:e.resource}),this.pipelineCacheKey+=e.cacheKey}resetBindGroupLayout(){this.entries.bindGroupLayout=[],this.layoutCacheKey="";for(const e of this.bindings)this.addBindGroupLayoutEntry(e);this.setBindGroupLayout()}addBindGroupLayoutEntry(e){this.entries.bindGroupLayout.push({binding:this.entries.bindGroupLayout.length,...e.resourceLayout,visibility:e.visibility}),this.layoutCacheKey+=e.resourceLayoutCacheKey}loseContext(){this.resetEntries();for(const e of this.bufferBindings)e.buffer.reset(),e.parent&&e.parent.buffer.reset(),"resultBuffer"in e&&e.resultBuffer.reset();this.bindGroup=null,this.bindGroupLayout=null,this.needsPipelineFlush=!0}restoreContext(){this.shouldCreateBindGroup&&this.createBindGroup();for(const e of this.bufferBindings)e.shouldUpdate=!0}get bufferBindings(){return this.bindings.filter(e=>e instanceof pe||e instanceof Fi)}createBindingBuffer(e,t=null){e.buffer.createBuffer(this.renderer,{label:t||this.options.label+": "+e.bindingType+" buffer from: "+e.label,usage:["copySrc","copyDst",e.bindingType,...e.options.usage]}),"resultBuffer"in e&&e.resultBuffer.createBuffer(this.renderer,{label:this.options.label+": Result buffer from: "+e.label,size:e.arrayBuffer.byteLength,usage:["copyDst","mapRead"]})}fillEntries(){for(const e of this.bindings)e.visibility||(e.visibility=GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT|GPUShaderStage.COMPUTE),"buffer"in e&&(e.parent&&!e.parent.buffer.GPUBuffer?this.createBindingBuffer(e.parent,e.parent.options.label):!e.buffer.GPUBuffer&&!e.parent&&this.createBindingBuffer(e)),this.addBindGroupLayoutEntry(e),this.addBindGroupEntry(e)}getBindingByName(e=""){return this.bindings.find(t=>t.name===e)}setBindGroupLayout(){const e=this.renderer.deviceManager.bindGroupLayouts.get(this.layoutCacheKey);e?this.bindGroupLayout=e:(this.bindGroupLayout=this.renderer.createBindGroupLayout({label:this.options.label+" layout",entries:this.entries.bindGroupLayout}),this.renderer.deviceManager.bindGroupLayouts.set(this.layoutCacheKey,this.bindGroupLayout))}setBindGroup(){this.bindGroup=this.renderer.createBindGroup({label:this.options.label,layout:this.bindGroupLayout,entries:this.entries.bindGroup})}updateBufferBindings(){this.bindings.forEach((e,t)=>{"buffer"in e&&(e.update(),e.shouldUpdate&&e.buffer.GPUBuffer&&(!e.useStruct&&e.bufferElements.length>1?this.renderer.queueWriteBuffer(e.buffer.GPUBuffer,0,e.bufferElements[t].view):this.renderer.queueWriteBuffer(e.buffer.GPUBuffer,0,e.arrayBuffer),e.shouldUpdate=!1))})}update(){this.updateBufferBindings();const e=this.bindings.some(i=>i.shouldResetBindGroup),t=this.bindings.some(i=>i.shouldResetBindGroupLayout);(e||t)&&this.renderer.onAfterCommandEncoderSubmission.add(()=>{for(const i of this.bindings)i.shouldResetBindGroup=!1,i.shouldResetBindGroupLayout=!1},{once:!0}),t&&(this.resetBindGroupLayout(),this.needsPipelineFlush=!0),e&&this.resetBindGroup()}clone({bindings:e=[],keepLayout:t=!1}={}){const i={...this.options};i.label+=" (copy)";const s=new this.constructor(this.renderer,{label:i.label});s.setIndex(this.index),s.options=i;const r=e.length?e:this.bindings;for(const o of r)s.addBinding(o),"buffer"in o&&(o.parent&&!o.parent.buffer.GPUBuffer?(this.createBindingBuffer(o.parent,o.parent.options.label),o.parent.buffer.consumers.add(s.uuid)):!o.buffer.GPUBuffer&&!o.parent&&this.createBindingBuffer(o),"resultBuffer"in o&&o.resultBuffer.consumers.add(s.uuid)),t||s.addBindGroupLayoutEntry(o),s.addBindGroupEntry(o);return t&&(s.entries.bindGroupLayout=[...this.entries.bindGroupLayout],s.layoutCacheKey=this.layoutCacheKey),s.setBindGroupLayout(),s.setBindGroup(),s}destroy(){this.renderer.removeBindGroup(this);for(const e of this.bufferBindings)this.destroyBufferBinding(e);this.bindings=[],this.bindGroupLayout=null,this.bindGroup=null,this.resetEntries()}}class $i extends _t{constructor({label:e="Texture",name:t="texture",bindingType:i,visibility:s,texture:r,format:o="rgba8unorm",access:a="write",viewDimension:h="2d",multisampled:u=!1}){i=i??"texture",i==="storage"&&(s=["compute"]),super({label:e,name:t,bindingType:i,visibility:s}),this.options={...this.options,texture:r,format:o,access:a,viewDimension:h,multisampled:u},this.cacheKey+=`${o},${a},${h},${u},`,this.resource=r,this.setWGSLFragment()}get resourceLayout(){return Gn(this)}get resourceLayoutCacheKey(){return An(this)}get resource(){return this.texture instanceof GPUTexture?this.texture.createView({label:this.options.label+" view",dimension:this.options.viewDimension}):this.texture instanceof GPUExternalTexture?this.texture:null}set resource(e){(e||this.texture)&&(this.shouldResetBindGroup=!0),this.texture=e}setBindingType(e){e!==this.bindingType&&(e&&(this.shouldResetBindGroupLayout=!0),this.bindingType=e,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`,this.setWGSLFragment())}setFormat(e){const t=e!==this.options.format;this.options.format=e,t&&this.bindingType==="storage"&&(this.setWGSLFragment(),this.shouldResetBindGroupLayout=!0,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`)}setMultisampled(e){const t=e!==this.options.multisampled;this.options.multisampled=e,t&&this.bindingType!=="storage"&&(this.setWGSLFragment(),this.shouldResetBindGroupLayout=!0,this.cacheKey=`${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`)}setWGSLFragment(){this.wgslGroupFragment=[`${_n(this)}`]}}const Q=new f,fe=new f,N=new f;class D{constructor(e=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])){this.type="Mat4",this.elements=e}set(e,t,i,s,r,o,a,h,u,l,c,p,d,m,g,y){const v=this.elements;return v[0]=e,v[1]=t,v[2]=i,v[3]=s,v[4]=r,v[5]=o,v[6]=a,v[7]=h,v[8]=u,v[9]=l,v[10]=c,v[11]=p,v[12]=d,v[13]=m,v[14]=g,v[15]=y,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}setFromArray(e=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])){for(let t=0;tt.object3DIndex!==this.object3DIndex)),e&&this.shouldUpdateWorldMatrix(),this._parent=e,this._parent?.children.push(this))}setTransforms(){this.transforms={origin:{model:new f},quaternion:new ce,rotation:new f,position:{world:new f},scale:new f(1)},this.rotation.onChange(()=>this.applyRotation()),this.position.onChange(()=>this.applyPosition()),this.scale.onChange(()=>this.applyScale()),this.transformOrigin.onChange(()=>this.applyTransformOrigin())}get rotation(){return this.transforms.rotation}set rotation(e){this.transforms.rotation=e,this.applyRotation()}get quaternion(){return this.transforms.quaternion}set quaternion(e){this.transforms.quaternion=e}get position(){return this.transforms.position.world}set position(e){this.transforms.position.world=e}get scale(){return this.transforms.scale}set scale(e){this.transforms.scale=e,this.applyScale()}get transformOrigin(){return this.transforms.origin.model}set transformOrigin(e){this.transforms.origin.model=e}applyRotation(){this.quaternion.setFromVec3(this.rotation),this.shouldUpdateModelMatrix()}applyPosition(){this.shouldUpdateModelMatrix()}applyScale(){this.shouldUpdateModelMatrix()}applyTransformOrigin(){this.shouldUpdateModelMatrix()}setMatrices(){this.matrices={model:{matrix:new D,shouldUpdate:!0,onUpdate:()=>this.updateModelMatrix()},world:{matrix:new D,shouldUpdate:!0,onUpdate:()=>this.updateWorldMatrix()}}}get modelMatrix(){return this.matrices.model.matrix}set modelMatrix(e){this.matrices.model.matrix=e,this.shouldUpdateModelMatrix()}shouldUpdateModelMatrix(){this.matrices.model.shouldUpdate=!0,this.shouldUpdateWorldMatrix()}get worldMatrix(){return this.matrices.world.matrix}set worldMatrix(e){this.matrices.world.matrix=e,this.shouldUpdateWorldMatrix()}shouldUpdateWorldMatrix(){this.matrices.world.shouldUpdate=!0}lookAt(e=new f,t=this.position,i=new f(0,1,0)){const s=Vn.lookAt(e,t,i);this.quaternion.setFromRotationMatrix(s),this.shouldUpdateModelMatrix()}updateModelMatrix(){this.modelMatrix=this.modelMatrix.composeFromOrigin(this.position,this.quaternion,this.scale,this.transformOrigin),this.shouldUpdateWorldMatrix()}updateWorldMatrix(){this.parent?this.worldMatrix.multiplyMatrices(this.parent.worldMatrix,this.modelMatrix):this.worldMatrix.copy(this.modelMatrix);for(let e=0,t=this.children.length;ee.shouldUpdate)}updateMatrixStack(){if(this.shouldUpdateMatrices(),this.matricesNeedUpdate)for(const e in this.matrices)this.matrices[e].shouldUpdate&&(this.matrices[e].onUpdate(),this.matrices[e].shouldUpdate=!1);for(let e=0,t=this.children.length;en.reduce((e,t)=>e|Nn.get(t),0),jn=(n=[],e)=>n.length?Wn(n):e!=="storage"?GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_SRC|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT:GPUTextureUsage.STORAGE_BINDING|GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST,Qs=(...n)=>{const e=Math.max(...n);return 1+Math.log2(e)|0};var qn=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},se=(n,e,t)=>(qn(n,e,"read from private field"),t?t.call(n):e.get(n)),Ot=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},De,Oe,Ft,$t;const er={name:"texture",generateMips:!1,flipY:!1,format:"rgba8unorm",premultipliedAlpha:!1,placeholderColor:[0,0,0,255],useExternalTextures:!0,fromTexture:null,viewDimension:"2d",visibility:["fragment"],cache:!0};class Fe extends ve{constructor(e,t=er){super(),Ot(this,De,new f(1)),Ot(this,Oe,new f(1)),Ot(this,Ft,new f(1)),Ot(this,$t,new D),this._onSourceLoadedCallback=()=>{},this._onSourceUploadedCallback=()=>{},this.type="Texture",e=V(e,t.label?t.label+" "+this.type:this.type),this.renderer=e,this.uuid=H();const i={...er,source:t.fromTexture?t.fromTexture.options.source:null,sourceType:t.fromTexture?t.fromTexture.options.sourceType:null};this.options={...i,...t},this.options.label=this.options.label??this.options.name,this.texture=null,this.externalTexture=null,this.source=null,this.size={width:1,height:1,depth:1},this.textureMatrix=new pe({label:this.options.label+": model matrix",name:this.options.name+"Matrix",useStruct:!1,struct:{[this.options.name+"Matrix"]:{type:"mat4x4f",value:this.modelMatrix}}}),this.renderer.deviceManager.bufferBindings.set(this.textureMatrix.cacheKey,this.textureMatrix),this.setBindings(),this._parentMesh=null,this.sourceLoaded=!1,this.sourceUploaded=!1,this.shouldUpdate=!1,this.renderer.addDOMTexture(this),this.createTexture()}setBindings(){this.bindings=[new $i({label:this.options.label+": texture",name:this.options.name,bindingType:this.options.sourceType==="externalVideo"?"externalTexture":"texture",visibility:this.options.visibility,texture:this.options.sourceType==="externalVideo"?this.externalTexture:this.texture,viewDimension:this.options.viewDimension}),this.textureMatrix]}get textureBinding(){return this.bindings[0]}get parentMesh(){return this._parentMesh}set parentMesh(e){this._parentMesh=e,this.resize()}get sourceLoaded(){return this._sourceLoaded}set sourceLoaded(e){e&&!this.sourceLoaded&&this._onSourceLoadedCallback&&this._onSourceLoadedCallback(),this._sourceLoaded=e}get sourceUploaded(){return this._sourceUploaded}set sourceUploaded(e){e&&!this.sourceUploaded&&this._onSourceUploadedCallback&&this._onSourceUploadedCallback(),this._sourceUploaded=e}setTransforms(){super.setTransforms(),this.transforms.quaternion.setAxisOrder("ZXY"),this.transforms.origin.model.set(.5,.5,0)}updateModelMatrix(){if(!this.parentMesh)return;const e=this.parentMesh.scale?this.parentMesh.scale:new f(1,1,1),t=this.parentMesh.boundingRect?this.parentMesh.boundingRect.width*e.x:this.size.width,i=this.parentMesh.boundingRect?this.parentMesh.boundingRect.height*e.y:this.size.height,s=t/i,r=this.size.width/this.size.height;t>i?(se(this,De).set(s,1,1),se(this,Oe).set(1/r,1,1)):(se(this,De).set(1,1/s,1),se(this,Oe).set(1,r,1));const o=s>r!=t>i?1:t>i?se(this,De).x*se(this,Oe).x:se(this,Oe).y*se(this,De).y;se(this,Ft).set(1/(o*this.scale.x),1/(o*this.scale.y),1),se(this,$t).rotateFromQuaternion(this.quaternion),this.modelMatrix.identity().premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1)).premultiplyScale(se(this,Ft)).premultiplyScale(se(this,De)).premultiply(se(this,$t)).premultiplyScale(se(this,Oe)).premultiplyTranslate(this.transformOrigin).translate(this.position)}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.textureMatrix.shouldUpdateBinding(this.options.name+"Matrix")}resize(){this.source&&this.source instanceof HTMLCanvasElement&&(this.source.width!==this.size.width||this.source.height!==this.size.height)&&(this.setSourceSize(),this.createTexture()),this.shouldUpdateModelMatrix()}uploadTexture(){this.renderer.uploadTexture(this),this.shouldUpdate=!1}uploadVideoTexture(){this.externalTexture=this.renderer.importExternalTexture(this.source),this.textureBinding.resource=this.externalTexture,this.textureBinding.setBindingType("externalTexture"),this.shouldUpdate=!1,this.sourceUploaded=!0}copy(e){if(this.options.sourceType==="externalVideo"&&e.options.sourceType!=="externalVideo"){L(`${this.options.label}: cannot copy a GPUTexture to a GPUExternalTexture`);return}else if(this.options.sourceType!=="externalVideo"&&e.options.sourceType==="externalVideo"){L(`${this.options.label}: cannot copy a GPUExternalTexture to a GPUTexture`);return}this.options.fromTexture=e,this.options.sourceType=e.options.sourceType,this.options.generateMips=e.options.generateMips,this.options.flipY=e.options.flipY,this.options.format=e.options.format,this.options.premultipliedAlpha=e.options.premultipliedAlpha,this.options.placeholderColor=e.options.placeholderColor,this.options.useExternalTextures=e.options.useExternalTextures,this.sourceLoaded=e.sourceLoaded,this.sourceUploaded=e.sourceUploaded,e.texture&&(e.sourceLoaded&&(this.size=e.size,this.source=e.source,this.resize()),e.sourceUploaded?(this.texture=e.texture,this.textureBinding.resource=this.texture):this.createTexture())}createTexture(){const e={label:this.options.label,format:this.options.format,size:[this.size.width,this.size.height,this.size.depth],dimensions:this.options.viewDimension==="1d"?"1d":this.options.viewDimension==="3d"?"3d":"2d",usage:this.source?GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST};this.options.sourceType!=="externalVideo"&&(e.mipLevelCount=this.options.generateMips?Qs(this.size.width,this.size.height):1,this.texture?.destroy(),this.texture=this.renderer.createTexture(e),this.textureBinding.resource=this.texture),this.shouldUpdate=!0}setSourceSize(){this.size={width:this.source.naturalWidth||this.source.width||this.source.videoWidth,height:this.source.naturalHeight||this.source.height||this.source.videoHeight,depth:1}}async loadImageBitmap(e){const i=await(await fetch(e)).blob();return await createImageBitmap(i,{colorSpaceConversion:"none"})}async loadImage(e){const t=typeof e=="string"?e:e.getAttribute("src");this.options.source=t,this.options.sourceType="image";const i=this.renderer.domTextures.find(s=>s.options.source===t);if(i&&i.texture&&i.sourceUploaded){this.copy(i);return}this.sourceLoaded=!1,this.sourceUploaded=!1,this.source=await this.loadImageBitmap(this.options.source),this.setSourceSize(),this.resize(),this.sourceLoaded=!0,this.createTexture()}onVideoFrameCallback(){this.videoFrameCallbackId&&(this.shouldUpdate=!0,this.source.requestVideoFrameCallback(this.onVideoFrameCallback.bind(this)))}onVideoLoaded(e){this.sourceLoaded||(this.source=e,this.setSourceSize(),this.resize(),this.options.useExternalTextures?(this.options.sourceType="externalVideo",this.texture?.destroy()):(this.options.sourceType="video",this.createTexture()),"requestVideoFrameCallback"in HTMLVideoElement.prototype&&(this.videoFrameCallbackId=this.source.requestVideoFrameCallback(this.onVideoFrameCallback.bind(this))),this.sourceLoaded=!0)}get isVideoSource(){return this.source&&(this.options.sourceType==="video"||this.options.sourceType==="externalVideo")}loadVideo(e){let t;typeof e=="string"?(t=document.createElement("video"),t.src=e):t=e,t.preload="auto",t.muted=!0,t.loop=!0,t.crossOrigin="anonymous",t.setAttribute("playsinline",""),this.options.source=t.src,this.sourceLoaded=!1,this.sourceUploaded=!1,t.readyState>=t.HAVE_ENOUGH_DATA?this.onVideoLoaded(t):t.addEventListener("canplaythrough",this.onVideoLoaded.bind(this,t),{once:!0}),isNaN(t.duration)&&t.load()}loadCanvas(e){this.options.source=e,this.options.sourceType="canvas",this.sourceLoaded=!1,this.sourceUploaded=!1,this.source=e,this.setSourceSize(),this.resize(),this.sourceLoaded=!0,this.createTexture()}onSourceLoaded(e){return e&&(this._onSourceLoadedCallback=e),this}onSourceUploaded(e){return e&&(this._onSourceUploadedCallback=e),this}render(){this.updateMatrixStack(),this.textureMatrix.update(),this.options.sourceType==="externalVideo"&&(this.shouldUpdate=!0),this.isVideoSource&&!this.videoFrameCallbackId&&this.source.readyState>=this.source.HAVE_CURRENT_DATA&&!this.source.paused&&(this.shouldUpdate=!0),this.shouldUpdate&&this.options.sourceType&&this.options.sourceType!=="externalVideo"&&this.uploadTexture()}destroy(){this.videoFrameCallbackId&&this.source.cancelVideoFrameCallback(this.videoFrameCallbackId),this.isVideoSource&&this.source.removeEventListener("canplaythrough",this.onVideoLoaded.bind(this,this.source),{once:!0}),this.renderer.removeDOMTexture(this),this.texture?.destroy(),this.texture=null}}De=new WeakMap,Oe=new WeakMap,Ft=new WeakMap,$t=new WeakMap;class Ui extends Dt{constructor(e,{label:t,index:i=0,bindings:s=[],uniforms:r,storages:o,textures:a=[],samplers:h=[]}={}){const u="TextureBindGroup";if(e=V(e,u),super(e,{label:t,index:i,bindings:s,uniforms:r,storages:o}),this.options={...this.options,textures:[],samplers:[]},a.length)for(const l of a)this.addTexture(l);if(h.length)for(const l of h)this.addSampler(l);this.type=u}addTexture(e){this.textures.push(e),this.addBindings([...e.bindings])}get textures(){return this.options.textures}addSampler(e){this.samplers.push(e),this.addBindings([e.binding])}get samplers(){return this.options.samplers}get shouldCreateBindGroup(){return!this.bindGroup&&!!this.bindings.length&&!this.textures.find(e=>!(e.texture||e.externalTexture))&&!this.samplers.find(e=>!e.sampler)}updateTextures(){for(const e of this.textures)e instanceof Fe&&(e.options.fromTexture&&e.options.fromTexture.sourceUploaded&&!e.sourceUploaded&&e.copy(e.options.fromTexture),e.shouldUpdate&&e.options.sourceType&&e.options.sourceType==="externalVideo"&&e.uploadVideoTexture())}update(){this.updateTextures(),super.update()}destroy(){super.destroy(),this.options.textures=[],this.options.samplers=[]}}class tr extends _t{constructor({label:e="Sampler",name:t="sampler",bindingType:i,visibility:s,sampler:r,type:o="filtering"}){i=i??"sampler",super({label:e,name:t,bindingType:i,visibility:s}),this.cacheKey+=`${o},`,this.options={...this.options,sampler:r,type:o},this.resource=r,this.setWGSLFragment()}get resourceLayout(){return{sampler:{type:this.options.type}}}get resourceLayoutCacheKey(){return`sampler,${this.options.type},${this.visibility},`}get resource(){return this.sampler}set resource(e){e&&this.sampler&&(this.shouldResetBindGroup=!0),this.sampler=e}setWGSLFragment(){this.wgslGroupFragment=[`var ${this.name}: ${this.options.type==="comparison"?`${this.bindingType}_comparison`:this.bindingType};`]}}var ir=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Ut=(n,e,t)=>(ir(n,e,"read from private field"),t?t.call(n):e.get(n)),kt=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},It=(n,e,t,i)=>(ir(n,e,"write to private field"),e.set(n,t),t),Vt,Nt,Wt,jt;class sr extends ve{constructor({fov:e=50,near:t=.1,far:i=150,width:s=1,height:r=1,pixelRatio:o=1,onMatricesChanged:a=()=>{}}={}){super(),kt(this,Vt,void 0),kt(this,Nt,void 0),kt(this,Wt,void 0),kt(this,jt,void 0),this.uuid=H(),this.position.set(0,0,10),this.up=new f(0,1,0),this.onMatricesChanged=a,this.size={width:1,height:1},this.setPerspective({fov:e,near:t,far:i,width:s,height:r,pixelRatio:o})}setMatrices(){super.setMatrices(),this.matrices={...this.matrices,view:{matrix:new D,shouldUpdate:!0,onUpdate:()=>{this.viewMatrix.copy(this.worldMatrix).invert()}},projection:{matrix:new D,shouldUpdate:!0,onUpdate:()=>this.updateProjectionMatrix()},viewProjection:{matrix:new D,shouldUpdate:!0,onUpdate:()=>this.viewProjectionMatrix.multiplyMatrices(this.projectionMatrix,this.viewMatrix)}}}get viewMatrix(){return this.matrices.view.matrix}set viewMatrix(e){this.matrices.view.matrix=e,this.shouldUpdateViewMatrices()}get projectionMatrix(){return this.matrices.projection.matrix}set projectionMatrix(e){this.matrices.projection.matrix=e,this.shouldUpdateProjectionMatrices()}get viewProjectionMatrix(){return this.matrices.viewProjection.matrix}shouldUpdateViewMatrices(){this.matrices.view.shouldUpdate=!0,this.matrices.viewProjection.shouldUpdate=!0}shouldUpdateProjectionMatrices(){this.matrices.projection.shouldUpdate=!0,this.matrices.viewProjection.shouldUpdate=!0}updateModelMatrix(){super.updateModelMatrix(),this.setVisibleSize(),this.shouldUpdateViewMatrices()}updateWorldMatrix(){super.updateWorldMatrix(),this.shouldUpdateViewMatrices()}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.onMatricesChanged()}get fov(){return Ut(this,Vt)}set fov(e){e=Math.max(1,Math.min(e??this.fov,179)),e!==this.fov&&(It(this,Vt,e),this.shouldUpdateProjectionMatrices()),this.setVisibleSize(),this.setCSSPerspective()}get near(){return Ut(this,Nt)}set near(e){e=Math.max(e??this.near,.01),e!==this.near&&(It(this,Nt,e),this.shouldUpdateProjectionMatrices())}get far(){return Ut(this,Wt)}set far(e){e=Math.max(e??this.far,this.near+1),e!==this.far&&(It(this,Wt,e),this.shouldUpdateProjectionMatrices())}get pixelRatio(){return Ut(this,jt)}set pixelRatio(e){It(this,jt,e??this.pixelRatio),this.setCSSPerspective()}setSize({width:e,height:t}){(e!==this.size.width||t!==this.size.height)&&this.shouldUpdateProjectionMatrices(),this.size.width=e,this.size.height=t,this.setVisibleSize(),this.setCSSPerspective()}setPerspective({fov:e=this.fov,near:t=this.near,far:i=this.far,width:s=this.size.width,height:r=this.size.height,pixelRatio:o=this.pixelRatio}={}){this.setSize({width:s,height:r}),this.pixelRatio=o,this.fov=e,this.near=t,this.far=i}setCSSPerspective(){this.CSSPerspective=Math.pow(Math.pow(this.size.width/(2*this.pixelRatio),2)+Math.pow(this.size.height/(2*this.pixelRatio),2),.5)/Math.tan(this.fov*.5*Math.PI/180)}getVisibleSizeAtDepth(e=0){const t=this.position.z;e{if(!e.has(n))throw TypeError("Cannot "+t)},Yn=(n,e,t)=>(rr(n,e,"read from private field"),e.get(n)),Hn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Xn=(n,e,t,i)=>(rr(n,e,"write to private field"),e.set(n,t),t),qt;const nr={label:"Texture",name:"renderTexture",type:"texture",access:"write",fromTexture:null,viewDimension:"2d",sampleCount:1,qualityRatio:1,generateMips:!1,flipY:!1,premultipliedAlpha:!1,autoDestroy:!0};class K{constructor(e,t=nr){Hn(this,qt,!0),e=V(e,t.label?t.label+" Texture":"Texture"),this.type="Texture",this.renderer=e,this.uuid=H(),this.options={...nr,...t},this.options.format==="rgba32float"&&!this.renderer.deviceManager.adapter.features.has("float32-filterable")&&(this.options.format="rgba16float"),t.fromTexture&&(this.options.format=t.fromTexture.texture.format,this.options.sampleCount=t.fromTexture.texture.sampleCount,this.options.viewDimension=t.fromTexture.options.viewDimension),this.options.format||(this.options.format=this.renderer.options.context.format),this.size=this.options.fixedSize?{width:this.options.fixedSize.width*this.options.qualityRatio,height:this.options.fixedSize.height*this.options.qualityRatio,depth:this.options.fixedSize.depth??this.options.viewDimension.indexOf("cube")!==-1?6:1}:{width:Math.floor(this.renderer.canvas.width*this.options.qualityRatio),height:Math.floor(this.renderer.canvas.height*this.options.qualityRatio),depth:this.options.viewDimension.indexOf("cube")!==-1?6:1},this.options.fixedSize&&Xn(this,qt,!1),this.setBindings(),this.renderer.addTexture(this),this.createTexture()}copy(e){this.options.fromTexture=e,this.createTexture()}copyGPUTexture(e){this.size={width:e.width,height:e.height,depth:e.depthOrArrayLayers},this.options.format=e.format,this.options.sampleCount=e.sampleCount,this.texture=e,this.textureBinding.setFormat(this.options.format),this.textureBinding.setMultisampled(this.options.sampleCount>1),this.textureBinding.resource=this.texture}createTexture(){if(!(!this.size.width||!this.size.height)){if(this.options.fromTexture){this.copyGPUTexture(this.options.fromTexture.texture);return}this.texture?.destroy(),this.texture=this.renderer.createTexture({label:this.options.label,format:this.options.format,size:[this.size.width,this.size.height,this.size.depth??1],dimensions:this.options.viewDimension,sampleCount:this.options.sampleCount,mipLevelCount:this.options.generateMips?Qs(this.size.width,this.size.height,this.size.depth??1):1,usage:jn(this.options.usage,this.options.type)}),this.textureBinding.resource=this.texture}}uploadSource({source:e,width:t=this.size.width,height:i=this.size.height,depth:s=this.size.depth,origin:r=[0,0,0],colorSpace:o="srgb"}){this.renderer.device.queue.copyExternalImageToTexture({source:e,flipY:this.options.flipY},{texture:this.texture,premultipliedAlpha:this.options.premultipliedAlpha,origin:r,colorSpace:o},[t,i,s]),this.texture.mipLevelCount>1&&Et(this.renderer.device,this.texture)}uploadData({width:e=this.size.width,height:t=this.size.height,depth:i=this.size.depth,origin:s=[0,0,0],data:r=new Float32Array(e*t*4)}){this.renderer.device.queue.writeTexture({texture:this.texture,origin:s},r,{bytesPerRow:e*r.BYTES_PER_ELEMENT*4,rowsPerImage:t},[e,t,i]),this.texture.mipLevelCount>1&&Et(this.renderer.device,this.texture)}setBindings(){this.bindings=[new $i({label:this.options.label+": "+this.options.name+" texture",name:this.options.name,bindingType:this.options.type,visibility:this.options.visibility,texture:this.texture,format:this.options.format,viewDimension:this.options.viewDimension,multisampled:this.options.sampleCount>1})]}get textureBinding(){return this.bindings[0]}resize(e=null){Yn(this,qt)&&(e||(e={width:Math.floor(this.renderer.canvas.width*this.options.qualityRatio),height:Math.floor(this.renderer.canvas.height*this.options.qualityRatio),depth:1}),!(e.width===this.size.width&&e.height===this.size.height&&e.depth===this.size.depth)&&(this.size=e,this.createTexture()))}destroy(){this.renderer.removeTexture(this),this.options.fromTexture||this.texture?.destroy(),this.texture=null}}qt=new WeakMap;class ki{constructor(e,t){this.type="Material",e=V(e,this.type),this.renderer=e,this.uuid=H();const{shaders:i,label:s,useAsyncPipeline:r,uniforms:o,storages:a,bindings:h,bindGroups:u,samplers:l,textures:c,domTextures:p}=t;this.options={shaders:i,label:s,useAsyncPipeline:r===void 0?!0:r,...o!==void 0&&{uniforms:o},...a!==void 0&&{storages:a},...h!==void 0&&{bindings:h},...u!==void 0&&{bindGroups:u},...l!==void 0&&{samplers:l},...c!==void 0&&{textures:c},...p!==void 0&&{domTextures:p}},this.bindGroups=[],this.texturesBindGroups=[],this.clonedBindGroups=[],this.setBindGroups(),this.setTextures(),this.setSamplers()}setRenderer(e){e=V(e,this.type),this.renderer=e}compileMaterial(){const e=this.texturesBindGroup.bindings.length?1:0;this.bindGroups.length>=this.inputsBindGroups.length+e||this.createBindGroups()}get ready(){return!!(this.renderer.ready&&this.pipelineEntry&&this.pipelineEntry.pipeline&&this.pipelineEntry.ready)}loseContext(){for(const e of this.domTextures)e.texture=null,e.sourceUploaded=!1;for(const e of this.textures)e.texture=null;[...this.bindGroups,...this.clonedBindGroups,...this.inputsBindGroups].forEach(e=>e.loseContext()),this.pipelineEntry.pipeline=null}restoreContext(){for(const e of this.samplers)e.createSampler(),e.binding.resource=e.sampler;for(const e of this.domTextures)e.createTexture(),e.resize();for(const e of this.textures)e.resize(e.size);[...this.bindGroups,...this.clonedBindGroups,...this.inputsBindGroups].forEach(e=>{e.restoreContext()})}getShaderCode(e="full"){return this.pipelineEntry?(e=(()=>{switch(e){case"vertex":case"fragment":case"compute":case"full":return e;default:return"full"}})(),this.pipelineEntry.shaders[e].code):""}getAddedShaderCode(e="vertex"){return this.pipelineEntry?(e=(()=>{switch(e){case"vertex":case"fragment":case"compute":return e;default:return"vertex"}})(),this.pipelineEntry.shaders[e].head):""}setBindGroups(){if(this.uniforms={},this.storages={},this.inputsBindGroups=[],this.inputsBindings=new Map,this.options.uniforms||this.options.storages||this.options.bindings){const e=new Dt(this.renderer,{label:this.options.label+": Bindings bind group",uniforms:this.options.uniforms,storages:this.options.storages,bindings:this.options.bindings});this.processBindGroupBindings(e),this.inputsBindGroups.push(e),e.consumers.add(this.uuid)}this.options.bindGroups?.forEach(e=>{this.processBindGroupBindings(e),this.inputsBindGroups.push(e),e.consumers.add(this.uuid)})}get texturesBindGroup(){return this.texturesBindGroups[0]}processBindGroupBindings(e){for(const t of e.bindings)t.bindingType==="uniform"&&(this.uniforms={...this.uniforms,[t.name]:t.inputs}),t.bindingType==="storage"&&(this.storages={...this.storages,[t.name]:t.inputs}),this.inputsBindings.set(t.name,t)}createBindGroups(){this.texturesBindGroup.shouldCreateBindGroup&&(this.texturesBindGroup.setIndex(this.bindGroups.length),this.texturesBindGroup.createBindGroup(),this.bindGroups.push(this.texturesBindGroup));for(const e of this.inputsBindGroups)e.shouldCreateBindGroup&&(e.setIndex(this.bindGroups.length),e.createBindGroup(),this.bindGroups.push(e));this.options.bindGroups?.forEach(e=>{if(!e.shouldCreateBindGroup&&!this.bindGroups.find(t=>t.uuid===e.uuid)&&(e.setIndex(this.bindGroups.length),this.bindGroups.push(e)),e instanceof Ui&&!this.texturesBindGroups.find(t=>t.uuid===e.uuid)){this.texturesBindGroups.push(e);for(const t of e.textures)t instanceof Fe&&!this.domTextures.find(i=>i.uuid===t.uuid)?this.domTextures.push(t):t instanceof K&&!this.textures.find(i=>i.uuid===t.uuid)&&this.textures.push(t)}})}cloneBindGroup({bindGroup:e,bindings:t=[],keepLayout:i=!0}){if(!e)return null;const s=e.clone({bindings:t,keepLayout:i});return this.clonedBindGroups.push(s),s}getBindGroupByBindingName(e=""){return(this.ready?this.bindGroups:this.inputsBindGroups).find(t=>t.bindings.find(i=>i.name===e))}destroyBindGroup(e){e.consumers.delete(this.uuid),e.consumers.size||e.destroy()}destroyBindGroups(){this.bindGroups.forEach(e=>this.destroyBindGroup(e)),this.clonedBindGroups.forEach(e=>this.destroyBindGroup(e)),this.texturesBindGroups.forEach(e=>this.destroyBindGroup(e)),this.texturesBindGroups=[],this.inputsBindGroups=[],this.bindGroups=[],this.clonedBindGroups=[]}updateBindGroups(){for(const e of this.bindGroups)this.updateBindGroup(e)}updateBindGroup(e){e.update(),e.needsPipelineFlush&&this.pipelineEntry.ready&&(this.pipelineEntry.flushPipelineEntry(this.bindGroups),e.needsPipelineFlush=!1)}getBindingByName(e=""){return this.inputsBindings.get(e)}getBufferBindingByName(e=""){const t=this.getBindingByName(e);return t&&"buffer"in t?t:void 0}shouldUpdateInputsBindings(e,t){if(!e)return;const i=this.getBindingByName(e);i&&(t?i.shouldUpdateBinding(t):Object.keys(i.inputs).forEach(s=>i.shouldUpdateBinding(s)))}setTextures(){this.domTextures=[],this.textures=[],this.texturesBindGroups.push(new Ui(this.renderer,{label:this.options.label+": Textures bind group"})),this.texturesBindGroup.consumers.add(this.uuid),this.options.domTextures?.forEach(e=>{this.addTexture(e)}),this.options.textures?.forEach(e=>{this.addTexture(e)})}addTexture(e){e instanceof Fe?this.domTextures.push(e):e instanceof K&&this.textures.push(e),(this.options.shaders.vertex&&this.options.shaders.vertex.code.indexOf(e.options.name)!==-1||this.options.shaders.fragment&&this.options.shaders.fragment.code.indexOf(e.options.name)!==-1||this.options.shaders.compute&&this.options.shaders.compute.code.indexOf(e.options.name)!==-1)&&this.texturesBindGroup.addTexture(e)}destroyTexture(e){if(e.options.cache||!e.options.autoDestroy)return;const t=this.renderer.getObjectsByTexture(e);(!t||!t.some(s=>s.material.uuid!==this.uuid))&&e.destroy()}destroyTextures(){this.domTextures?.forEach(e=>this.destroyTexture(e)),this.textures?.forEach(e=>this.destroyTexture(e)),this.domTextures=[],this.textures=[]}setSamplers(){if(this.samplers=[],this.options.samplers?.forEach(t=>{this.addSampler(t)}),!this.samplers.find(t=>t.name==="defaultSampler")){const t=new Ne(this.renderer,{label:"Default sampler",name:"defaultSampler"});this.addSampler(t)}}addSampler(e){this.samplers.push(e),(this.options.shaders.vertex&&this.options.shaders.vertex.code.indexOf(e.name)!==-1||this.options.shaders.fragment&&this.options.shaders.fragment.code.indexOf(e.name)!==-1||this.options.shaders.compute&&this.options.shaders.compute.code.indexOf(e.name)!==-1)&&this.texturesBindGroup.addSampler(e)}async getBufferResult(e){return await e.mapBufferAsync()}async getBufferBindingResultByBindingName(e=""){const t=this.getBufferBindingByName(e);if(t&&"buffer"in t){const i=this.renderer.copyBufferToBuffer({srcBuffer:t.buffer});return await this.getBufferResult(i)}else return new Float32Array(0)}async getBufferElementResultByNames({bindingName:e,bufferElementName:t}){const i=await this.getBufferBindingResultByBindingName(e);if(!t||i.length)return i;{const s=this.getBufferBindingByName(e);return s?s.extractBufferElementDataFromBufferResult({result:i,bufferElementName:t}):i}}onBeforeRender(){this.compileMaterial();for(const e of this.domTextures)e.render();this.updateBindGroups()}setPipeline(e){this.renderer.pipelineManager.setCurrentPipeline(e,this.pipelineEntry)}setActiveBindGroups(e){this.renderer.pipelineManager.setActiveBindGroups(e,this.bindGroups)}render(e){this.ready&&(this.setPipeline(e),this.setActiveBindGroups(e))}destroy(){this.destroyBindGroups(),this.destroyTextures()}}class or extends ki{constructor(e,t){const i="ComputeMaterial";e=V(e,i),super(e,t),this.type=i,this.renderer=e;let{shaders:s,dispatchSize:r}=t;(!s||!s.compute)&&(s={compute:{code:"",entryPoint:"main"}}),s.compute.code||(s.compute.code="@compute @workgroup_size(1) fn main(){}"),s.compute.entryPoint||(s.compute.entryPoint="main"),this.options={...this.options,shaders:s,...t.dispatchSize!==void 0&&{dispatchSize:t.dispatchSize}},r||(r=1),Array.isArray(r)?(r[0]=Math.ceil(r[0]??1),r[1]=Math.ceil(r[1]??1),r[2]=Math.ceil(r[2]??1)):isNaN(r)||(r=[Math.ceil(r),1,1]),this.dispatchSize=r}setPipelineEntry(){this.pipelineEntry=this.renderer.pipelineManager.createComputePipeline({renderer:this.renderer,label:this.options.label+" compute pipeline",shaders:this.options.shaders,useAsync:this.options.useAsyncPipeline,bindGroups:this.bindGroups})}async compilePipelineEntry(){await this.pipelineEntry.compilePipelineEntry()}async compileMaterial(){this.ready||(super.compileMaterial(),this.pipelineEntry||this.setPipelineEntry(),this.pipelineEntry&&this.pipelineEntry.canCompile&&await this.compilePipelineEntry())}getShaderCode(e="compute"){return super.getShaderCode(e)}getAddedShaderCode(e="compute"){return super.getAddedShaderCode(e)}useCustomRender(e){e&&(this._useCustomRenderCallback=e)}render(e){if(this.ready)if(this.setPipeline(e),this._useCustomRenderCallback!==void 0)this._useCustomRenderCallback(e);else{for(const t of this.bindGroups)e.setBindGroup(t.index,t.bindGroup);e.dispatchWorkgroups(this.dispatchSize[0],this.dispatchSize[1],this.dispatchSize[2])}}copyBufferToResult(e){for(const t of this.bindGroups)t.bufferBindings.forEach(i=>{i.shouldCopyResult&&this.renderer.copyBufferToBuffer({srcBuffer:i.buffer,dstBuffer:i.resultBuffer,commandEncoder:e})})}async getComputeResult({bindingName:e="",bufferElementName:t=""}){const i=this.getBufferBindingByName(e);if(i&&"resultBuffer"in i){const s=await this.getBufferResult(i.resultBuffer);return t&&s.length?i.extractBufferElementDataFromBufferResult({result:s,bufferElementName:t}):s}else return new Float32Array(0)}}var ar=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},hr=(n,e,t)=>(ar(n,e,"read from private field"),e.get(n)),Kn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Zn=(n,e,t,i)=>(ar(n,e,"write to private field"),e.set(n,t),t),ot;let Jn=0;class Yt{constructor(e,t={}){Kn(this,ot,!0),this._onReadyCallback=()=>{},this._onBeforeRenderCallback=()=>{},this._onRenderCallback=()=>{},this._onAfterRenderCallback=()=>{},this._onAfterResizeCallback=()=>{};const i="ComputePass";e=V(e,t.label?`${t.label} ${i}`:i),t.label=t.label??"ComputePass "+e.computePasses?.length,this.renderer=e,this.type=i,this.uuid=H(),Object.defineProperty(this,"index",{value:Jn++});const{label:s,shaders:r,renderOrder:o,uniforms:a,storages:h,bindGroups:u,samplers:l,domTextures:c,textures:p,autoRender:d,useAsyncPipeline:m,texturesOptions:g,dispatchSize:y}=t;this.options={label:s,shaders:r,...d!==void 0&&{autoRender:d},...o!==void 0&&{renderOrder:o},...y!==void 0&&{dispatchSize:y},useAsyncPipeline:m===void 0?!0:m,texturesOptions:g},this.renderOrder=o??0,d!==void 0&&Zn(this,ot,d),this.userData={},this.ready=!1,this.setMaterial({label:this.options.label,shaders:this.options.shaders,uniforms:a,storages:h,bindGroups:u,samplers:l,textures:p,domTextures:c,useAsyncPipeline:m,dispatchSize:y}),this.addToScene(!0)}get ready(){return this._ready}set ready(e){e&&this._onReadyCallback&&this._onReadyCallback(),this._ready=e}addToScene(e=!1){e&&this.renderer.computePasses.push(this),hr(this,ot)&&this.renderer.scene.addComputePass(this)}removeFromScene(e=!1){hr(this,ot)&&this.renderer.scene.removeComputePass(this),e&&(this.renderer.computePasses=this.renderer.computePasses.filter(t=>t.uuid!==this.uuid))}setRenderer(e){if(e=e&&e.renderer||e,!e||!(e.type==="GPURenderer"||e.type==="GPUCameraRenderer"||e.type==="GPUCurtainsRenderer")){L(`${this.options.label}: Cannot set ${e} as a renderer because it is not of a valid Renderer type.`);return}this.material?.setRenderer(e),this.removeFromScene(!0),this.renderer=e,this.addToScene(!0)}setMaterial(e){this.useMaterial(new or(this.renderer,e))}useMaterial(e){this.material=e}loseContext(){this.material.loseContext()}restoreContext(){this.material.restoreContext()}get domTextures(){return this.material?.domTextures||[]}get textures(){return this.material?.textures||[]}createDOMTexture(e){e.name||(e.name="texture"+(this.textures.length+this.domTextures.length)),e.label||(e.label=this.options.label+" "+e.name);const t=new Fe(this.renderer,{...e,...this.options.texturesOptions});return this.addTexture(t),t}createTexture(e){e.name||(e.name="texture"+(this.textures.length+this.domTextures.length));const t=new K(this.renderer,e);return this.addTexture(t),t}addTexture(e){this.material.addTexture(e)}get uniforms(){return this.material?.uniforms}get storages(){return this.material?.storages}resize(){this._onAfterResizeCallback&&this._onAfterResizeCallback()}onReady(e){return e&&(this._onReadyCallback=e),this}onBeforeRender(e){return e&&(this._onBeforeRenderCallback=e),this}onRender(e){return e&&(this._onRenderCallback=e),this}onAfterRender(e){return e&&(this._onAfterRenderCallback=e),this}useCustomRender(e){return this.material.useCustomRender(e),this}onAfterResize(e){return e&&(this._onAfterResizeCallback=e),this}onBeforeRenderPass(){this.renderer.ready&&(this.material&&this.material.ready&&!this.ready&&(this.ready=!0),this._onBeforeRenderCallback&&this._onBeforeRenderCallback(),this.material.onBeforeRender())}onRenderPass(e){this.material.ready&&(this._onRenderCallback&&this._onRenderCallback(),this.material.render(e))}onAfterRenderPass(){this._onAfterRenderCallback&&this._onAfterRenderCallback()}render(e){this.onBeforeRenderPass(),this.renderer.ready&&(!this.renderer.production&&e.pushDebugGroup(this.options.label),this.onRenderPass(e),!this.renderer.production&&e.popDebugGroup(),this.onAfterRenderPass())}copyBufferToResult(e){this.material?.copyBufferToResult(e)}async getComputeResult({bindingName:e,bufferElementName:t}){return await this.material?.getComputeResult({bindingName:e,bufferElementName:t})}remove(){this.removeFromScene(!0),this.destroy()}destroy(){this.material?.destroy()}}ot=new WeakMap;const le=[new f,new f,new f,new f,new f,new f,new f,new f];class be{constructor(e=new f(1/0),t=new f(-1/0)){this.min=e,this.max=t}set(e=new f(1/0),t=new f(-1/0)){return this.min.copy(e),this.max.copy(t),this}isEmpty(){return this.max.x{},onLeaveView:o=()=>{}}){this.boundingBox=e,this.clipSpaceOBB=new be,this.modelViewProjectionMatrix=t,this.containerBoundingRect=i,this.DOMFrustumMargins={...ur,...s},this.clipSpaceBoundingRect={top:0,left:0,width:0,height:0},this.projectedBoundingRect={top:0,right:0,bottom:0,left:0,width:0,height:0,x:0,y:0},this.onReEnterView=r,this.onLeaveView=o,this.isIntersecting=!1}setContainerBoundingRect(e){this.containerBoundingRect=e}get DOMFrustumBoundingRect(){return{top:this.projectedBoundingRect.top-this.DOMFrustumMargins.top,right:this.projectedBoundingRect.right+this.DOMFrustumMargins.right,bottom:this.projectedBoundingRect.bottom+this.DOMFrustumMargins.bottom,left:this.projectedBoundingRect.left-this.DOMFrustumMargins.left}}computeClipSpaceOBB(){this.clipSpaceOBB.set(),this.boundingBox.applyMat4(this.modelViewProjectionMatrix,this.clipSpaceOBB)}setDocumentCoordsFromClipSpaceOBB(){this.computeClipSpaceOBB(),this.clipSpaceBoundingRect={top:this.clipSpaceOBB.max.y,left:this.clipSpaceOBB.min.x,width:this.clipSpaceOBB.max.x-this.clipSpaceOBB.min.x,height:this.clipSpaceOBB.max.y-this.clipSpaceOBB.min.y};const e=(this.clipSpaceOBB.min.x+1)*.5,t=(this.clipSpaceOBB.max.x+1)*.5,i=1-(this.clipSpaceOBB.min.y+1)*.5,s=1-(this.clipSpaceOBB.max.y+1)*.5,{width:r,height:o,top:a,left:h}=this.containerBoundingRect;this.projectedBoundingRect={left:e*r+h,x:e*r+h,top:s*o+a,y:s*o+a,right:t*r+h,bottom:i*o+a,width:t*r+h-(e*r+h),height:i*o+a-(s*o+a)}}setDocumentCoordsFromClipSpaceSphere(e={center:new f,radius:0}){this.clipSpaceBoundingRect={top:e.center.y+e.radius,left:e.center.x-e.radius,width:e.radius*2,height:e.radius*2};const t=(e.center.x+1)*.5,i=1-(e.center.y+1)*.5,{width:s,height:r,top:o,left:a}=this.containerBoundingRect;this.projectedBoundingRect.width=e.radius*r,this.projectedBoundingRect.height=e.radius*r,this.projectedBoundingRect.left=t*s+a-this.projectedBoundingRect.width*.5,this.projectedBoundingRect.x=this.projectedBoundingRect.left,this.projectedBoundingRect.top=i*r+o-this.projectedBoundingRect.height*.5,this.projectedBoundingRect.y=this.projectedBoundingRect.top,this.projectedBoundingRect.right=this.projectedBoundingRect.left+this.projectedBoundingRect.width,this.projectedBoundingRect.bottom=this.projectedBoundingRect.top+this.projectedBoundingRect.height}intersectsContainer(){Math.round(this.DOMFrustumBoundingRect.right)<=this.containerBoundingRect.left||Math.round(this.DOMFrustumBoundingRect.left)>=this.containerBoundingRect.left+this.containerBoundingRect.width||Math.round(this.DOMFrustumBoundingRect.bottom)<=this.containerBoundingRect.top||Math.round(this.DOMFrustumBoundingRect.top)>=this.containerBoundingRect.top+this.containerBoundingRect.height?(this.isIntersecting&&this.onLeaveView(),this.isIntersecting=!1):(this.isIntersecting||this.onReEnterView(),this.isIntersecting=!0)}}class Ii{constructor({verticesOrder:e="ccw",topology:t="triangle-list",instancesCount:i=1,vertexBuffers:s=[],mapBuffersAtCreation:r=!0}={}){this.verticesCount=0,this.verticesOrder=e,this.topology=t,this.instancesCount=i,this.ready=!1,this.boundingBox=new be,this.type="Geometry",this.uuid=H(),this.vertexBuffers=[],this.consumers=new Set,this.options={verticesOrder:e,topology:t,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:r};const o=s.find(a=>a.name==="attributes");!s.length||!o?this.addVertexBuffer({name:"attributes"}):o&&s.sort((a,h)=>{const u=a.name!=="attributes"?1/0:-1,l=h.name!=="attributes"?1/0:-1;return u-l});for(const a of s)this.addVertexBuffer({stepMode:a.stepMode??"vertex",name:a.name,attributes:a.attributes,...a.array&&{array:a.array},...a.buffer&&{buffer:a.buffer},...a.bufferOffset&&{bufferOffset:a.bufferOffset},...a.bufferSize&&{bufferSize:a.bufferSize}});o&&this.setWGSLFragment()}loseContext(){this.ready=!1;for(const e of this.vertexBuffers)e.buffer.destroy()}restoreContext(e){if(!this.ready){for(const t of this.vertexBuffers)!t.buffer.GPUBuffer&&t.buffer.consumers.size===0&&(t.buffer.createBuffer(e),this.uploadBuffer(e,t)),t.buffer.consumers.add(this.uuid);this.ready=!0}}addVertexBuffer({stepMode:e="vertex",name:t,attributes:i=[],buffer:s=null,array:r=null,bufferOffset:o=0,bufferSize:a=null}={}){s=s||new Ve;const h={name:t??"attributes"+this.vertexBuffers.length,stepMode:e,arrayStride:0,bufferLength:0,attributes:[],buffer:s,array:r,bufferOffset:o,bufferSize:a};return i?.forEach(u=>{this.setAttribute({vertexBuffer:h,...u})}),this.vertexBuffers.push(h),h}getVertexBufferByName(e=""){return this.vertexBuffers.find(t=>t.name===e)}setAttribute({vertexBuffer:e=this.vertexBuffers[0],name:t,type:i="vec3f",bufferFormat:s="float32x3",size:r=3,array:o=new Float32Array(this.verticesCount*r),verticesStride:a=1}){const h=e.attributes,u=h.length;t||(t="geometryAttribute"+u),t==="position"&&(i!=="vec3f"||s!=="float32x3"||r!==3)&&(L(`Geometry 'position' attribute must have this exact properties set: type: 'vec3f', bufferFormat: 'float32x3', - size: 3`),i="vec3f",s="float32x3",r=3);let u=o.length;const d=u/r;t==="position"&&(this.verticesCount=d),e.stepMode==="vertex"&&this.verticesCount&&this.verticesCount!==d*a?te(`Geometry vertex attribute error. Attribute array of size ${r} must be of length: ${this.verticesCount*r}, current given: ${o.length}. (${this.verticesCount} vertices).`):e.stepMode==="instance"&&d!==this.instancesCount&&(e.buffer?u=this.instancesCount*r:te(`Geometry instance attribute error. Attribute array of size ${r} must be of length: ${this.instancesCount*r}, current given: ${o.length}. (${this.instancesCount} instances).`));const c={name:t,type:i,bufferFormat:s,size:r,bufferLength:u,offset:l?h.reduce((p,g)=>p+g.bufferLength,0):0,bufferOffset:l?h[l-1].bufferOffset+h[l-1].size*4:0,array:o,verticesStride:a};e.bufferLength+=c.bufferLength*a,e.arrayStride+=c.size,e.attributes.push(c)}get shouldCompute(){return this.vertexBuffers.length&&!this.vertexBuffers[0].array}getAttributeByName(e){let t;return this.vertexBuffers.forEach(i=>{t=i.attributes.find(s=>s.name===e)}),t}computeGeometry(){this.ready||(this.vertexBuffers.forEach((e,t)=>{if(t===0){const r=e.attributes.find(o=>o.name==="position");r||te("Geometry must have a 'position' attribute"),(r.type!=="vec3f"||r.bufferFormat!=="float32x3"||r.size!==3)&&(I(`Geometry 'position' attribute must have this exact properties set: + size: 3`),i="vec3f",s="float32x3",r=3);let l=o.length;const c=l/r;t==="position"&&(this.verticesCount=c),e.stepMode==="vertex"&&this.verticesCount&&this.verticesCount!==c*a?te(`Geometry vertex attribute error. Attribute array of size ${r} must be of length: ${this.verticesCount*r}, current given: ${o.length}. (${this.verticesCount} vertices).`):e.stepMode==="instance"&&c!==this.instancesCount&&(e.buffer?l=this.instancesCount*r:te(`Geometry instance attribute error. Attribute array of size ${r} must be of length: ${this.instancesCount*r}, current given: ${o.length}. (${this.instancesCount} instances).`));const p={name:t,type:i,bufferFormat:s,size:r,bufferLength:l,offset:u?h.reduce((d,m)=>d+m.bufferLength,0):0,bufferOffset:u?h[u-1].bufferOffset+h[u-1].size*4:0,array:o,verticesStride:a};e.bufferLength+=p.bufferLength*a,e.arrayStride+=p.size,e.attributes.push(p)}get shouldCompute(){return this.vertexBuffers.length&&!this.vertexBuffers[0].array}getAttributeByName(e){let t;for(const i of this.vertexBuffers)if(t=i.attributes.find(s=>s.name===e),t)break;return t}computeGeometry(){this.ready||(this.vertexBuffers.forEach((e,t)=>{if(t===0){const r=e.attributes.find(o=>o.name==="position");r||te("Geometry must have a 'position' attribute"),(r.type!=="vec3f"||r.bufferFormat!=="float32x3"||r.size!==3)&&(L(`Geometry 'position' attribute must have this exact properties set: type: 'vec3f', bufferFormat: 'float32x3', - size: 3`),r.type="vec3f",r.bufferFormat="float32x3",r.size=3)}e.array=new Float32Array(e.bufferLength);let i=0,s=0;for(let r=0;rc&&(this.boundingBox.min.x=c),this.boundingBox.max.xc&&(this.boundingBox.min.y=c),this.boundingBox.max.yc&&(this.boundingBox.min.z=c),this.boundingBox.max.zp&&(this.boundingBox.min.x=p),this.boundingBox.max.xp&&(this.boundingBox.min.y=p),this.boundingBox.max.yp&&(this.boundingBox.min.z=p),this.boundingBox.max.zt.attributes.map(i=>(e++,` @location(${e}) ${i.name}: ${i.type}`))).join(",")} -};`,this.layoutCacheKey=this.vertexBuffers.map(t=>t.name+","+t.attributes.map(i=>`${i.name},${i.size}`)).join(",")+","}createBuffers({renderer:e,label:t=this.type}){if(!this.ready){for(const i of this.vertexBuffers)i.bufferSize||(i.bufferSize=i.array.length*i.array.constructor.BYTES_PER_ELEMENT),!i.buffer.GPUBuffer&&!i.buffer.consumers.size&&(i.buffer.createBuffer(e,{label:t+": "+i.name+" buffer",size:i.bufferSize,usage:this.options.mapBuffersAtCreation?["vertex"]:["copyDst","vertex"],mappedAtCreation:this.options.mapBuffersAtCreation}),this.uploadBuffer(e,i)),i.buffer.consumers.add(this.uuid);this.ready=!0}}uploadBuffer(e,t){this.options.mapBuffersAtCreation?(new t.array.constructor(t.buffer.GPUBuffer.getMappedRange()).set(t.array),t.buffer.GPUBuffer.unmap()):e.queueWriteBuffer(t.buffer.GPUBuffer,0,t.array)}setGeometryBuffers(e){this.vertexBuffers.forEach((t,i)=>{e.setVertexBuffer(i,t.buffer.GPUBuffer,t.bufferOffset,t.bufferSize)})}drawGeometry(e){e.draw(this.verticesCount,this.instancesCount)}render(e){this.ready&&(this.setGeometryBuffers(e),this.drawGeometry(e))}destroy(e=null){this.ready=!1;for(const t of this.vertexBuffers)t.buffer.consumers.delete(this.uuid),t.buffer.consumers.size||t.buffer.destroy(),t.array=null,e&&e.removeBuffer(t.buffer)}}class Ye extends di{constructor({verticesOrder:e="ccw",topology:t="triangle-list",instancesCount:i=1,vertexBuffers:s=[],mapBuffersAtCreation:r=!0}={}){super({verticesOrder:e,topology:t,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:r}),this.type="IndexedGeometry"}loseContext(){super.loseContext(),this.indexBuffer&&this.indexBuffer.buffer.destroy()}restoreContext(e){this.ready||(this.indexBuffer.buffer.GPUBuffer||(this.indexBuffer.buffer.createBuffer(e),this.uploadBuffer(e,this.indexBuffer),this.indexBuffer.buffer.consumers.add(this.uuid)),super.restoreContext(e))}get useUint16IndexArray(){return this.verticesCount<256*256}setIndexBuffer({bufferFormat:e="uint32",array:t=new Uint32Array(0),buffer:i=new _e,bufferOffset:s=0,bufferSize:r=null}){this.indexBuffer={array:t,bufferFormat:e,bufferLength:t.length,buffer:i,bufferOffset:s,bufferSize:r!==null?r:t.length*t.constructor.BYTES_PER_ELEMENT}}createBuffers({renderer:e,label:t=this.type}){this.indexBuffer.buffer.GPUBuffer||(this.indexBuffer.buffer.createBuffer(e,{label:t+": index buffer",size:this.indexBuffer.array.byteLength,usage:this.options.mapBuffersAtCreation?["index"]:["copyDst","index"],mappedAtCreation:this.options.mapBuffersAtCreation}),this.uploadBuffer(e,this.indexBuffer)),this.indexBuffer.buffer.consumers.add(this.uuid),super.createBuffers({renderer:e,label:t})}setGeometryBuffers(e){super.setGeometryBuffers(e),e.setIndexBuffer(this.indexBuffer.buffer.GPUBuffer,this.indexBuffer.bufferFormat,this.indexBuffer.bufferOffset,this.indexBuffer.bufferSize)}drawGeometry(e){e.drawIndexed(this.indexBuffer.bufferLength,this.instancesCount)}destroy(e=null){super.destroy(e),this.indexBuffer&&(this.indexBuffer.buffer.consumers.delete(this.uuid),this.indexBuffer.buffer.destroy(),e&&e.removeBuffer(this.indexBuffer.buffer))}}class ci extends Ye{constructor({widthSegments:e=1,heightSegments:t=1,instancesCount:i=1,vertexBuffers:s=[],topology:r}={}){super({verticesOrder:"ccw",topology:r,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:!0}),this.type="PlaneGeometry",e=Math.floor(e),t=Math.floor(t),this.definition={id:e*t+e,width:e,height:t,count:e*t};const o=(this.definition.width+1)*(this.definition.height+1),a=this.getIndexedVerticesAndUVs(o);for(const h of Object.values(a))this.setAttribute(h);this.setIndexArray()}setIndexArray(){const e=this.useUint16IndexArray?new Uint16Array(this.definition.count*6):new Uint32Array(this.definition.count*6);let t=0;for(let i=0;i{if(!e.has(n))throw TypeError("Cannot "+t)},Mt=(n,e,t)=>(Ps(n,e,"read from private field"),t?t.call(n):e.get(n)),Ss=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Ts=(n,e,t,i)=>(Ps(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),Ct,Ge;class pi extends me{constructor(e,{color:t=new f(1),intensity:i=1,index:s=0,type:r="lights"}={}){super(),Ss(this,Ct,void 0),Ss(this,Ge,void 0),this.type=r,Object.defineProperty(this,"index",{value:s}),e=Le(e,this.constructor.name),this.renderer=e,this.setRendererBinding(),this.uuid=Y(),this.options={color:t,intensity:i},this.color=t,Ts(this,Ge,this.color.clone()),this.color.onChange(()=>this.onPropertyChanged("color",Mt(this,Ge).copy(this.color).multiplyScalar(this.intensity))),this.intensity=i,this.renderer.addLight(this)}setRendererBinding(){this.renderer.bindings[this.type]&&(this.rendererBinding=this.renderer.bindings[this.type])}reset(){this.setRendererBinding(),this.onPropertyChanged("color",Mt(this,Ge).copy(this.color).multiplyScalar(this.intensity))}get intensity(){return Mt(this,Ct)}set intensity(e){Ts(this,Ct,e),this.onPropertyChanged("color",Mt(this,Ge).copy(this.color).multiplyScalar(this.intensity))}onPropertyChanged(e,t){this.rendererBinding&&this.rendererBinding.inputs[e]&&(t instanceof f?(this.rendererBinding.inputs[e].value[this.index*3]=t.x,this.rendererBinding.inputs[e].value[this.index*3+1]=t.y,this.rendererBinding.inputs[e].value[this.index*3+2]=t.z):this.rendererBinding.inputs[e].value[this.index]=t,this.rendererBinding.inputs[e].shouldUpdate=!0,this.renderer.shouldUpdateCameraLightsBindGroup())}onMaxLightOverflow(e){this.rendererBinding&&(this.renderer.onMaxLightOverflow(e),this.rendererBinding=this.renderer.bindings[e])}remove(){this.renderer.removeLight(this)}destroy(){this.parent=null}}Ct=new WeakMap,Ge=new WeakMap;class tn extends pi{constructor(e,{color:t=new f(1),intensity:i=.1}={}){const s="ambientLights",r=e.lights.filter(o=>o.type===s).length;super(e,{color:t,intensity:i,index:r,type:s}),this.index+1>this.renderer.lightsBindingParams[this.type].max&&this.onMaxLightOverflow(this.type),this.rendererBinding.inputs.count.value=this.index+1,this.rendererBinding.inputs.count.shouldUpdate=!0}applyRotation(){}applyPosition(){}applyScale(){}applyTransformOrigin(){}}class Bt{constructor(e,{label:t="Render Pass",sampleCount:i=4,qualityRatio:s=1,useColorAttachments:r=!0,renderToSwapChain:o=!0,colorAttachments:a=[],useDepth:h=!0,depthTexture:l=null,depthLoadOp:u="clear",depthStoreOp:d="store",depthClearValue:c=1,depthFormat:p="depth24plus"}={}){if(e=j(e,"RenderPass"),this.type="RenderPass",this.uuid=Y(),this.renderer=e,r){const g={loadOp:"clear",storeOp:"store",clearValue:[0,0,0,0],targetFormat:this.renderer.options.preferredFormat};a.length?a=a.map(m=>({...g,...m})):a=[g]}this.options={label:t,sampleCount:i,qualityRatio:s,useColorAttachments:r,renderToSwapChain:o,colorAttachments:a,useDepth:h,...l!==void 0&&{depthTexture:l},depthLoadOp:u,depthStoreOp:d,depthClearValue:c,depthFormat:p},this.options.useDepth&&this.createDepthTexture(),this.viewTextures=[],this.resolveTargets=[],this.options.useColorAttachments&&(!this.options.renderToSwapChain||this.options.sampleCount>1)&&(this.createViewTextures(),this.createResolveTargets()),this.setRenderPassDescriptor()}createDepthTexture(){this.options.depthTexture?(this.depthTexture=this.options.depthTexture,this.options.depthFormat=this.options.depthTexture.options.format):this.depthTexture=new se(this.renderer,{label:this.options.label+" depth texture",name:"depthTexture",format:this.options.depthFormat,sampleCount:this.options.sampleCount,qualityRatio:this.options.qualityRatio,type:"depth",usage:["renderAttachment","textureBinding"]})}createViewTextures(){this.options.colorAttachments.forEach((e,t)=>{this.viewTextures.push(new se(this.renderer,{label:`${this.options.label} colorAttachment[${t}] view texture`,name:`colorAttachment${t}ViewTexture`,format:e.targetFormat,sampleCount:this.options.sampleCount,qualityRatio:this.options.qualityRatio,type:"texture",usage:["copySrc","copyDst","renderAttachment","textureBinding"]}))})}createResolveTargets(){this.options.sampleCount>1&&this.options.colorAttachments.forEach((e,t)=>{this.resolveTargets.push(this.options.renderToSwapChain&&t===0?null:new se(this.renderer,{label:`${this.options.label} resolve target[${t}] texture`,name:`resolveTarget${t}Texture`,format:e.targetFormat,sampleCount:1,qualityRatio:this.options.qualityRatio,type:"texture"}))})}get outputTextures(){return this.options.sampleCount>1?this.resolveTargets:this.viewTextures}setRenderPassDescriptor(e=null){this.descriptor={label:this.options.label+" descriptor",colorAttachments:this.options.colorAttachments.map((t,i)=>({view:this.viewTextures[i]?.texture.createView({label:this.viewTextures[i]?.texture.label+" view"}),...this.resolveTargets.length&&{resolveTarget:this.resolveTargets[i]?.texture.createView({label:this.resolveTargets[i]?.texture.label+" view"})},clearValue:t.clearValue,loadOp:t.loadOp,storeOp:t.storeOp})),...this.options.useDepth&&{depthStencilAttachment:{view:e||this.depthTexture.texture.createView({label:this.depthTexture.texture.label+" view"}),depthClearValue:this.options.depthClearValue,depthLoadOp:this.options.depthLoadOp,depthStoreOp:this.options.depthStoreOp}}}}resize(){this.options.useDepth&&(this.descriptor.depthStencilAttachment.view=this.depthTexture.texture.createView({label:this.depthTexture.options.label+" view"})),this.viewTextures.forEach((e,t)=>{this.descriptor.colorAttachments[t].view=e.texture.createView({label:e.options.label+" view"})}),this.resolveTargets.forEach((e,t)=>{e&&(this.descriptor.colorAttachments[t].resolveTarget=e.texture.createView({label:e.options.label+" view"}))})}setLoadOp(e="clear",t=0){this.options.useColorAttachments&&(this.options.colorAttachments[t]&&(this.options.colorAttachments[t].loadOp=e),this.descriptor&&this.descriptor.colorAttachments&&this.descriptor.colorAttachments[t]&&(this.descriptor.colorAttachments[t].loadOp=e))}setDepthLoadOp(e="clear"){this.options.depthLoadOp=e,this.options.useDepth&&this.descriptor.depthStencilAttachment&&(this.descriptor.depthStencilAttachment.depthLoadOp=e)}setClearValue(e=[0,0,0,0],t=0){if(this.options.useColorAttachments){if(this.renderer.alphaMode==="premultiplied"){const i=e[3];e[0]=Math.min(e[0],i),e[1]=Math.min(e[1],i),e[2]=Math.min(e[2],i)}this.options.colorAttachments[t]&&(this.options.colorAttachments[t].clearValue=e),this.descriptor&&this.descriptor.colorAttachments&&this.descriptor.colorAttachments[t]&&(this.descriptor.colorAttachments[t].clearValue=e)}}updateView(e=null){return!this.options.colorAttachments.length||!this.options.renderToSwapChain||(e||(e=this.renderer.context.getCurrentTexture(),e.label=`${this.renderer.type} context current texture`),this.options.sampleCount>1?(this.descriptor.colorAttachments[0].view=this.viewTextures[0].texture.createView({label:this.viewTextures[0].options.label+" view"}),this.descriptor.colorAttachments[0].resolveTarget=e.createView({label:e.label+" resolve target view"})):this.descriptor.colorAttachments[0].view=e.createView({label:e.label+" view"})),e}destroy(){this.viewTextures.forEach(e=>e.destroy()),this.resolveTargets.forEach(e=>e?.destroy()),!this.options.depthTexture&&this.depthTexture&&this.depthTexture.destroy()}}var Rs=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},zs=(n,e,t)=>(Rs(n,e,"read from private field"),t?t.call(n):e.get(n)),sn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},rn=(n,e,t,i)=>(Rs(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),Xe;class fi{constructor(e,t={}){sn(this,Xe,!0),e=j(e,"RenderTarget"),this.type="RenderTarget",this.renderer=e,this.uuid=Y();const{label:i,colorAttachments:s,depthTexture:r,autoRender:o,...a}=t,h=r||(this.renderer.renderPass.options.sampleCount===(t.sampleCount??4)?this.renderer.renderPass.depthTexture:null);this.options={label:i,...a,...h&&{depthTexture:h},...s&&{colorAttachments:s},autoRender:o===void 0?!0:o},o!==void 0&&rn(this,Xe,o),this.renderPass=new Bt(this.renderer,{label:this.options.label?`${this.options.label} Render Pass`:"Render Target Render Pass",...s&&{colorAttachments:s},depthTexture:this.options.depthTexture,...a}),a.useColorAttachments!==!1&&(this.renderTexture=new se(this.renderer,{label:this.options.label?`${this.options.label} Render Texture`:"Render Target render texture",name:"renderTexture",format:s&&s.length&&s[0].targetFormat?s[0].targetFormat:this.renderer.options.preferredFormat,...this.options.qualityRatio!==void 0&&{qualityRatio:this.options.qualityRatio},usage:["copySrc","renderAttachment","textureBinding"]})),this.addToScene()}get outputTextures(){return this.renderPass.outputTextures.length?this.renderPass.outputTextures.map((e,t)=>t===0&&this.renderPass.options.renderToSwapChain?this.renderTexture:e):this.renderTexture?[this.renderTexture]:[]}addToScene(){this.renderer.renderTargets.push(this),zs(this,Xe)&&this.renderer.scene.addRenderTarget(this)}removeFromScene(){zs(this,Xe)&&this.renderer.scene.removeRenderTarget(this),this.renderer.renderTargets=this.renderer.renderTargets.filter(e=>e.uuid!==this.uuid)}resize(){this.options.depthTexture&&(this.renderPass.options.depthTexture.texture=this.options.depthTexture.texture),this.renderPass?.resize()}remove(){this.destroy()}destroy(){this.renderer.meshes.forEach(e=>{e.outputTarget&&e.outputTarget.uuid===this.uuid&&e.setOutputTarget(null)}),this.renderer.shaderPasses.forEach(e=>{e.outputTarget&&e.outputTarget.uuid===this.uuid&&(e.outputTarget=null,e.setOutputTarget(null))}),this.removeFromScene(),this.renderPass?.destroy(),this.renderTexture?.destroy()}}Xe=new WeakMap;const Es=(n={},e={})=>Object.keys(n).filter(t=>Array.isArray(n[t])?JSON.stringify(n[t])!==JSON.stringify(e[t]):n[t]!==e[t]);var mi=` +};`,this.layoutCacheKey=this.vertexBuffers.map(t=>t.name+","+t.attributes.map(i=>`${i.name},${i.size}`)).join(",")+","}createBuffers({renderer:e,label:t=this.type}){if(!this.ready){for(const i of this.vertexBuffers)i.bufferSize||(i.bufferSize=i.array.length*i.array.constructor.BYTES_PER_ELEMENT),!i.buffer.GPUBuffer&&!i.buffer.consumers.size&&(i.buffer.createBuffer(e,{label:t+": "+i.name+" buffer",size:i.bufferSize,usage:this.options.mapBuffersAtCreation?["vertex"]:["copyDst","vertex"],mappedAtCreation:this.options.mapBuffersAtCreation}),this.uploadBuffer(e,i)),i.buffer.consumers.add(this.uuid);this.ready=!0}}uploadBuffer(e,t){this.options.mapBuffersAtCreation?(new t.array.constructor(t.buffer.GPUBuffer.getMappedRange()).set(t.array),t.buffer.GPUBuffer.unmap()):e.queueWriteBuffer(t.buffer.GPUBuffer,0,t.array)}setGeometryBuffers(e){this.vertexBuffers.forEach((t,i)=>{e.setVertexBuffer(i,t.buffer.GPUBuffer,t.bufferOffset,t.bufferSize)})}drawGeometry(e){e.draw(this.verticesCount,this.instancesCount)}render(e){this.ready&&(this.setGeometryBuffers(e),this.drawGeometry(e))}destroy(e=null){this.ready=!1;for(const t of this.vertexBuffers)t.buffer.consumers.delete(this.uuid),t.buffer.consumers.size||t.buffer.destroy(),t.array=null,e&&e.removeBuffer(t.buffer)}}class at extends Ii{constructor({verticesOrder:e="ccw",topology:t="triangle-list",instancesCount:i=1,vertexBuffers:s=[],mapBuffersAtCreation:r=!0}={}){super({verticesOrder:e,topology:t,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:r}),this.type="IndexedGeometry"}loseContext(){super.loseContext(),this.indexBuffer&&this.indexBuffer.buffer.destroy()}restoreContext(e){this.ready||(this.indexBuffer.buffer.GPUBuffer||(this.indexBuffer.buffer.createBuffer(e),this.uploadBuffer(e,this.indexBuffer),this.indexBuffer.buffer.consumers.add(this.uuid)),super.restoreContext(e))}get useUint16IndexArray(){return this.verticesCount<256*256}setIndexBuffer({bufferFormat:e="uint32",array:t=new Uint32Array(0),buffer:i=new Ve,bufferOffset:s=0,bufferSize:r=null}){this.indexBuffer={array:t,bufferFormat:e,bufferLength:t.length,buffer:i,bufferOffset:s,bufferSize:r!==null?r:t.length*t.constructor.BYTES_PER_ELEMENT}}createBuffers({renderer:e,label:t=this.type}){this.indexBuffer.buffer.GPUBuffer||(this.indexBuffer.buffer.createBuffer(e,{label:t+": index buffer",size:this.indexBuffer.array.byteLength,usage:this.options.mapBuffersAtCreation?["index"]:["copyDst","index"],mappedAtCreation:this.options.mapBuffersAtCreation}),this.uploadBuffer(e,this.indexBuffer)),this.indexBuffer.buffer.consumers.add(this.uuid),super.createBuffers({renderer:e,label:t})}setGeometryBuffers(e){super.setGeometryBuffers(e),e.setIndexBuffer(this.indexBuffer.buffer.GPUBuffer,this.indexBuffer.bufferFormat,this.indexBuffer.bufferOffset,this.indexBuffer.bufferSize)}drawGeometry(e){e.drawIndexed(this.indexBuffer.bufferLength,this.instancesCount)}destroy(e=null){super.destroy(e),this.indexBuffer&&(this.indexBuffer.buffer.consumers.delete(this.uuid),this.indexBuffer.buffer.destroy(),e&&e.removeBuffer(this.indexBuffer.buffer))}}class Vi extends at{constructor({widthSegments:e=1,heightSegments:t=1,instancesCount:i=1,vertexBuffers:s=[],topology:r}={}){super({verticesOrder:"ccw",topology:r,instancesCount:i,vertexBuffers:s,mapBuffersAtCreation:!0}),this.type="PlaneGeometry",e=Math.floor(e),t=Math.floor(t),this.definition={id:e*t+e,width:e,height:t,count:e*t};const o=(this.definition.width+1)*(this.definition.height+1),a=this.getIndexedVerticesAndUVs(o);for(const h of Object.values(a))this.setAttribute(h);this.setIndexArray()}setIndexArray(){const e=this.useUint16IndexArray?new Uint16Array(this.definition.count*6):new Uint32Array(this.definition.count*6);let t=0;for(let i=0;i{if(!e.has(n))throw TypeError("Cannot "+t)},Ht=(n,e,t)=>(dr(n,e,"read from private field"),t?t.call(n):e.get(n)),cr=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},pr=(n,e,t,i)=>(dr(n,e,"write to private field"),e.set(n,t),t),Xt,We;class Ni extends ve{constructor(e,{color:t=new f(1),intensity:i=1,type:s="lights"}={}){super(),cr(this,Xt,void 0),cr(this,We,void 0),this.type=s,this.setRenderer(e),this.uuid=H(),this.options={color:t,intensity:i},this.color=t,pr(this,We,this.color.clone()),this.color.onChange(()=>this.onPropertyChanged("color",Ht(this,We).copy(this.color).multiplyScalar(this.intensity))),this.intensity=i}setRenderer(e){const t=!!this.renderer;this.renderer&&this.renderer.removeLight(this),e=Ae(e,this.constructor.name),this.renderer=e,this.index=this.renderer.lights.filter(i=>i.type===this.type).length,this.index+1>this.renderer.lightsBindingParams[this.type].max&&this.onMaxLightOverflow(this.type),this.renderer.addLight(this),this.setRendererBinding(),t&&this.reset()}setRendererBinding(){this.renderer.bindings[this.type]&&(this.rendererBinding=this.renderer.bindings[this.type])}reset(){this.setRendererBinding(),this.onPropertyChanged("color",Ht(this,We).copy(this.color).multiplyScalar(this.intensity))}get intensity(){return Ht(this,Xt)}set intensity(e){pr(this,Xt,e),this.onPropertyChanged("color",Ht(this,We).copy(this.color).multiplyScalar(this.intensity))}onPropertyChanged(e,t){this.rendererBinding&&this.rendererBinding.inputs[e]&&(t instanceof f?(this.rendererBinding.inputs[e].value[this.index*3]=t.x,this.rendererBinding.inputs[e].value[this.index*3+1]=t.y,this.rendererBinding.inputs[e].value[this.index*3+2]=t.z):this.rendererBinding.inputs[e].value[this.index]=t,this.rendererBinding.inputs[e].shouldUpdate=!0,this.renderer.shouldUpdateCameraLightsBindGroup())}onMaxLightOverflow(e){this.renderer.onMaxLightOverflow(e),this.rendererBinding&&(this.rendererBinding=this.renderer.bindings[e])}remove(){this.renderer.removeLight(this),this.destroy()}destroy(){super.destroy()}}Xt=new WeakMap,We=new WeakMap;class Qn extends Ni{constructor(e,{color:t=new f(1),intensity:i=.1}={}){super(e,{color:t,intensity:i,type:"ambientLights"})}applyRotation(){}applyPosition(){}applyScale(){}applyTransformOrigin(){}}class Kt{constructor(e,{label:t="Render Pass",sampleCount:i=4,qualityRatio:s=1,useColorAttachments:r=!0,renderToSwapChain:o=!0,colorAttachments:a=[],useDepth:h=!0,depthTexture:u=null,depthLoadOp:l="clear",depthStoreOp:c="store",depthClearValue:p=1,depthFormat:d="depth24plus"}={}){if(e=V(e,"RenderPass"),this.type="RenderPass",this.uuid=H(),this.renderer=e,r){const m={loadOp:"clear",storeOp:"store",clearValue:[0,0,0,0],targetFormat:this.renderer.options.context.format};a.length?a=a.map(g=>({...m,...g})):a=[m]}this.options={label:t,sampleCount:i,qualityRatio:s,useColorAttachments:r,renderToSwapChain:o,colorAttachments:a,useDepth:h,...u!==void 0&&{depthTexture:u},depthLoadOp:l,depthStoreOp:c,depthClearValue:p,depthFormat:d},this.options.useDepth&&this.createDepthTexture(),this.viewTextures=[],this.resolveTargets=[],this.options.useColorAttachments&&(!this.options.renderToSwapChain||this.options.sampleCount>1)&&(this.createViewTextures(),this.createResolveTargets()),this.setRenderPassDescriptor()}createDepthTexture(){this.options.depthTexture?(this.depthTexture=this.options.depthTexture,this.options.depthFormat=this.options.depthTexture.options.format):this.depthTexture=new K(this.renderer,{label:this.options.label+" depth texture",name:"depthTexture",format:this.options.depthFormat,sampleCount:this.options.sampleCount,qualityRatio:this.options.qualityRatio,type:"depth",usage:["renderAttachment","textureBinding"]})}createViewTextures(){this.options.colorAttachments.forEach((e,t)=>{this.viewTextures.push(new K(this.renderer,{label:`${this.options.label} colorAttachment[${t}] view texture`,name:`colorAttachment${t}ViewTexture`,format:e.targetFormat,sampleCount:this.options.sampleCount,qualityRatio:this.options.qualityRatio,type:"texture",usage:["copySrc","copyDst","renderAttachment","textureBinding"]}))})}createResolveTargets(){this.options.sampleCount>1&&this.options.colorAttachments.forEach((e,t)=>{this.resolveTargets.push(this.options.renderToSwapChain&&t===0?null:new K(this.renderer,{label:`${this.options.label} resolve target[${t}] texture`,name:`resolveTarget${t}Texture`,format:e.targetFormat,sampleCount:1,qualityRatio:this.options.qualityRatio,type:"texture"}))})}get outputTextures(){return this.options.sampleCount>1?this.resolveTargets:this.viewTextures}setRenderPassDescriptor(e=null){this.descriptor={label:this.options.label+" descriptor",colorAttachments:this.options.colorAttachments.map((t,i)=>({view:this.viewTextures[i]?.texture.createView({label:this.viewTextures[i]?.texture.label+" view"}),...this.resolveTargets.length&&{resolveTarget:this.resolveTargets[i]?.texture.createView({label:this.resolveTargets[i]?.texture.label+" view"})},clearValue:t.clearValue,loadOp:t.loadOp,storeOp:t.storeOp})),...this.options.useDepth&&{depthStencilAttachment:{view:e||this.depthTexture.texture.createView({label:this.depthTexture.texture.label+" view"}),depthClearValue:this.options.depthClearValue,depthLoadOp:this.options.depthLoadOp,depthStoreOp:this.options.depthStoreOp}}}}resize(){this.options.useDepth&&(this.descriptor.depthStencilAttachment.view=this.depthTexture.texture.createView({label:this.depthTexture.options.label+" view"})),this.viewTextures.forEach((e,t)=>{this.descriptor.colorAttachments[t].view=e.texture.createView({label:e.options.label+" view"})}),this.resolveTargets.forEach((e,t)=>{e&&(this.descriptor.colorAttachments[t].resolveTarget=e.texture.createView({label:e.options.label+" view"}))})}setLoadOp(e="clear",t=0){this.options.useColorAttachments&&(this.options.colorAttachments[t]&&(this.options.colorAttachments[t].loadOp=e),this.descriptor&&this.descriptor.colorAttachments&&this.descriptor.colorAttachments[t]&&(this.descriptor.colorAttachments[t].loadOp=e))}setDepthLoadOp(e="clear"){this.options.depthLoadOp=e,this.options.useDepth&&this.descriptor.depthStencilAttachment&&(this.descriptor.depthStencilAttachment.depthLoadOp=e)}setClearValue(e=[0,0,0,0],t=0){if(this.options.useColorAttachments){if(this.renderer.options.context.alphaMode==="premultiplied"){const i=e[3];e[0]=Math.min(e[0],i),e[1]=Math.min(e[1],i),e[2]=Math.min(e[2],i)}this.options.colorAttachments[t]&&(this.options.colorAttachments[t].clearValue=e),this.descriptor&&this.descriptor.colorAttachments&&this.descriptor.colorAttachments[t]&&(this.descriptor.colorAttachments[t].clearValue=e)}}updateView(e=null){return!this.options.colorAttachments.length||!this.options.renderToSwapChain||(e||(e=this.renderer.context.getCurrentTexture(),e.label=`${this.renderer.type} context current texture`),this.options.sampleCount>1?(this.descriptor.colorAttachments[0].view=this.viewTextures[0].texture.createView({label:this.viewTextures[0].options.label+" view"}),this.descriptor.colorAttachments[0].resolveTarget=e.createView({label:e.label+" resolve target view"})):this.descriptor.colorAttachments[0].view=e.createView({label:e.label+" view"})),e}destroy(){this.viewTextures.forEach(e=>e.destroy()),this.resolveTargets.forEach(e=>e?.destroy()),!this.options.depthTexture&&this.depthTexture&&this.depthTexture.destroy()}}var fr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},mr=(n,e,t)=>(fr(n,e,"read from private field"),e.get(n)),eo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},to=(n,e,t,i)=>(fr(n,e,"write to private field"),e.set(n,t),t),ht;class Wi{constructor(e,t={}){eo(this,ht,!0),e=V(e,"RenderTarget"),this.type="RenderTarget",this.renderer=e,this.uuid=H();const{label:i,colorAttachments:s,depthTexture:r,autoRender:o,...a}=t,h=r||(this.renderer.renderPass.options.sampleCount===(t.sampleCount??4)?this.renderer.renderPass.depthTexture:null);this.options={label:i,...a,...h&&{depthTexture:h},...s&&{colorAttachments:s},autoRender:o===void 0?!0:o},o!==void 0&&to(this,ht,o),this.renderPass=new Kt(this.renderer,{label:this.options.label?`${this.options.label} Render Pass`:"Render Target Render Pass",...s&&{colorAttachments:s},depthTexture:this.options.depthTexture,...a}),a.useColorAttachments!==!1&&(this.renderTexture=new K(this.renderer,{label:this.options.label?`${this.options.label} Render Texture`:"Render Target render texture",name:"renderTexture",format:s&&s.length&&s[0].targetFormat?s[0].targetFormat:this.renderer.options.context.format,...this.options.qualityRatio!==void 0&&{qualityRatio:this.options.qualityRatio},usage:["copySrc","renderAttachment","textureBinding"]})),this.addToScene()}get outputTextures(){return this.renderPass.outputTextures.length?this.renderPass.outputTextures.map((e,t)=>t===0&&this.renderPass.options.renderToSwapChain?this.renderTexture:e):this.renderTexture?[this.renderTexture]:[]}addToScene(){this.renderer.renderTargets.push(this),mr(this,ht)&&this.renderer.scene.addRenderTarget(this)}removeFromScene(){mr(this,ht)&&this.renderer.scene.removeRenderTarget(this),this.renderer.renderTargets=this.renderer.renderTargets.filter(e=>e.uuid!==this.uuid)}resize(){this.options.depthTexture&&(this.renderPass.options.depthTexture.texture=this.options.depthTexture.texture),this.renderPass?.resize()}remove(){this.destroy()}destroy(){this.renderer.meshes.forEach(e=>{e.outputTarget&&e.outputTarget.uuid===this.uuid&&e.setOutputTarget(null)}),this.renderer.shaderPasses.forEach(e=>{e.outputTarget&&e.outputTarget.uuid===this.uuid&&(e.outputTarget=null,e.setOutputTarget(null))}),this.removeFromScene(),this.renderPass?.destroy(),this.renderTexture?.destroy()}}ht=new WeakMap;let io=0;class ji{constructor(e){this.type="PipelineEntry";let{renderer:t}=e;const{label:i,shaders:s,useAsync:r,bindGroups:o,cacheKey:a}=e;t=V(t,i?i+" "+this.type:this.type),this.renderer=t,Object.defineProperty(this,"index",{value:io++}),this.layout=null,this.pipeline=null,this.status={compiling:!1,compiled:!1,error:null},this.options={label:i,shaders:s,useAsync:r!==void 0?r:!0,bindGroups:o,cacheKey:a}}get ready(){return!this.status.compiling&&this.status.compiled&&!this.status.error}get canCompile(){return!this.status.compiling&&!this.status.compiled&&!this.status.error}setPipelineEntryProperties(e){const{bindGroups:t}=e;this.setPipelineEntryBindGroups(t)}setPipelineEntryBindGroups(e){this.bindGroups=e}createShaderModule({code:e="",type:t="vertex"}){const i=this.renderer.createShaderModule({label:this.options.label+": "+t+" shader module",code:e});return"getCompilationInfo"in i&&!this.renderer.production&&i.getCompilationInfo().then(s=>{for(const r of s.messages){let o="";switch(r.lineNum&&(o+=`Line ${r.lineNum}:${r.linePos} - ${e.substring(r.offset,r.offset+r.length)} +`),o+=r.message,r.type){case"error":console.error(`${this.options.label} compilation error: +${o}`);break;case"warning":console.warn(`${this.options.label} compilation warning: +${o}`);break;case"info":console.log(`${this.options.label} compilation information: +${o}`);break}}}),i}createShaders(){}createPipelineLayout(){this.layout=this.renderer.createPipelineLayout({label:this.options.label+" layout",bindGroupLayouts:this.bindGroups.map(e=>e.bindGroupLayout)})}createPipelineDescriptor(){}flushPipelineEntry(e=[]){this.status.compiling=!1,this.status.compiled=!1,this.status.error=null,this.setPipelineEntryBindGroups(e),this.compilePipelineEntry()}compilePipelineEntry(){this.status.compiling=!0,this.createShaders(),this.createPipelineLayout(),this.createPipelineDescriptor()}}var so=` +fn getWorldPosition(position: vec3f) -> vec4f { + return matrices.model * vec4f(position, 1.0); +} + +fn getOutputPosition(position: vec3f) -> vec4f { + return camera.projection * matrices.modelView * vec4f(position, 1.0); +}`,ro=` +fn getWorldNormal(normal: vec3f) -> vec3f { + return normalize(matrices.normal * normal); +} + +fn getViewNormal(normal: vec3f) -> vec3f { + return normalize((camera.view * vec4(matrices.normal * normal, 0.0)).xyz); +}`,gr=` +fn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f { + return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy; +}`,no=` +fn getVertex2DToUVCoords(vertex: vec2f) -> vec2f { + return vec2( + vertex.x * 0.5 + 0.5, + 0.5 - vertex.y * 0.5 + ); +} + +fn getVertex3DToUVCoords(vertex: vec3f) -> vec2f { + return getVertex2DToUVCoords( vec2(vertex.x, vertex.y) ); +} +`;const $e={vertex:{get_uv_cover:gr},fragment:{get_uv_cover:gr,get_vertex_to_uv_coords:no}},Ue={vertex:{get_output_position:so,get_normals:ro},fragment:{}};class je extends ji{constructor(e){let{renderer:t,...i}=e;const{label:s,attributes:r,bindGroups:o,cacheKey:a,...h}=i,u="RenderPipelineEntry";V(t,s?s+" "+u:u),super(e),this.type=u,this.shaders={vertex:{head:"",code:"",module:null},fragment:{head:"",code:"",module:null},full:{head:"",code:"",module:null}},this.descriptor=null,this.options={...this.options,attributes:r,...h},this.setPipelineEntryProperties({attributes:r,bindGroups:o})}setPipelineEntryProperties(e){const{attributes:t,bindGroups:i}=e;this.attributes=t,this.setPipelineEntryBindGroups(i)}patchShaders(){this.shaders.vertex.head="",this.shaders.vertex.code="",this.shaders.fragment.head="",this.shaders.fragment.code="",this.shaders.full.head="",this.shaders.full.code="";for(const t in $e.vertex)this.shaders.vertex.head=`${$e.vertex[t]} +${this.shaders.vertex.head}`,this.shaders.full.head=`${$e.vertex[t]} +${this.shaders.full.head}`;if(this.options.shaders.fragment)for(const t in $e.fragment)this.shaders.fragment.head=`${$e.fragment[t]} +${this.shaders.fragment.head}`,this.shaders.full.head.indexOf($e.fragment[t])===-1&&(this.shaders.full.head=`${$e.fragment[t]} +${this.shaders.full.head}`);if(this.options.rendering.useProjection){for(const t in Ue.vertex)this.shaders.vertex.head=`${Ue.vertex[t]} +${this.shaders.vertex.head}`,this.shaders.full.head=`${Ue.vertex[t]} +${this.shaders.full.head}`;if(this.options.shaders.fragment)for(const t in Ue.fragment)this.shaders.fragment.head=`${Ue.fragment[t]} +${this.shaders.fragment.head}`,this.shaders.full.head.indexOf(Ue.fragment[t])===-1&&(this.shaders.full.head=`${Ue.fragment[t]} +${this.shaders.full.head}`)}const e=[];for(const t of this.bindGroups){let i=0;t.bindings.forEach((s,r)=>{s.wgslGroupFragment.forEach((o,a)=>{e.push({groupIndex:t.index,visibility:s.options.visibility,bindIndex:i,wgslStructFragment:s.wgslStructFragment,wgslGroupFragment:o,newLine:r===t.bindings.length-1&&a===s.wgslGroupFragment.length-1}),i++})})}for(const t of e)t.visibility.includes("vertex")&&(t.wgslStructFragment&&this.shaders.vertex.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.vertex.head=` +${t.wgslStructFragment} +${this.shaders.vertex.head}`),this.shaders.vertex.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.vertex.head=`${this.shaders.vertex.head} +@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.vertex.head+=` +`))),this.options.shaders.fragment&&t.visibility.includes("fragment")&&(t.wgslStructFragment&&this.shaders.fragment.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.fragment.head=` +${t.wgslStructFragment} +${this.shaders.fragment.head}`),this.shaders.fragment.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.fragment.head=`${this.shaders.fragment.head} +@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.fragment.head+=` +`))),t.wgslStructFragment&&this.shaders.full.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.full.head=` +${t.wgslStructFragment} +${this.shaders.full.head}`),this.shaders.full.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.full.head=`${this.shaders.full.head} +@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.full.head+=` +`));this.shaders.vertex.head=`${this.attributes.wgslStructFragment} +${this.shaders.vertex.head}`,this.shaders.full.head=`${this.attributes.wgslStructFragment} +${this.shaders.full.head}`,this.shaders.vertex.code=this.shaders.vertex.head+this.options.shaders.vertex.code,typeof this.options.shaders.fragment=="object"&&(this.shaders.fragment.code=this.shaders.fragment.head+this.options.shaders.fragment.code),typeof this.options.shaders.fragment=="object"&&(this.options.shaders.vertex.entryPoint!==this.options.shaders.fragment.entryPoint&&this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code)===0?this.shaders.full.code=this.shaders.full.head+this.options.shaders.vertex.code:this.shaders.full.code=this.shaders.full.head+this.options.shaders.vertex.code+this.options.shaders.fragment.code)}get shadersModulesReady(){return!(!this.shaders.vertex.module||this.options.shaders.fragment&&!this.shaders.fragment.module)}createShaders(){this.patchShaders();const e=typeof this.options.shaders.fragment=="object"&&this.options.shaders.vertex.entryPoint!==this.options.shaders.fragment.entryPoint&&this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code)===0;this.shaders.vertex.module=this.createShaderModule({code:this.shaders[e?"full":"vertex"].code,type:"vertex"}),this.options.shaders.fragment&&(this.shaders.fragment.module=this.createShaderModule({code:this.shaders[e?"full":"fragment"].code,type:"fragment"}))}static getDefaultTransparentBlending(){return{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}}}createPipelineDescriptor(){if(!this.shadersModulesReady)return;let e=-1;this.options.rendering.targets.length?this.options.rendering.transparent&&(this.options.rendering.targets[0].blend=this.options.rendering.targets[0].blend?this.options.rendering.targets[0].blend:je.getDefaultTransparentBlending()):this.options.rendering.targets=[],this.descriptor={label:this.options.label,layout:this.layout,vertex:{module:this.shaders.vertex.module,entryPoint:this.options.shaders.vertex.entryPoint,buffers:this.attributes.vertexBuffers.map(t=>({stepMode:t.stepMode,arrayStride:t.arrayStride*4,attributes:t.attributes.map(i=>(e++,{shaderLocation:e,offset:i.bufferOffset,format:i.bufferFormat}))}))},...this.options.shaders.fragment&&{fragment:{module:this.shaders.fragment.module,entryPoint:this.options.shaders.fragment.entryPoint,targets:this.options.rendering.targets}},primitive:{topology:this.options.rendering.topology,frontFace:this.options.rendering.verticesOrder,cullMode:this.options.rendering.cullMode},...this.options.rendering.depth&&{depthStencil:{depthWriteEnabled:this.options.rendering.depthWriteEnabled,depthCompare:this.options.rendering.depthCompare,format:this.options.rendering.depthFormat}},...this.options.rendering.sampleCount>1&&{multisample:{count:this.options.rendering.sampleCount}}}}createRenderPipeline(){if(this.shadersModulesReady)try{this.pipeline=this.renderer.createRenderPipeline(this.descriptor)}catch(e){this.status.error=e,te(e)}}async createRenderPipelineAsync(){if(this.shadersModulesReady)try{this.pipeline=await this.renderer.createRenderPipelineAsync(this.descriptor),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null}catch(e){this.status.error=e,te(e)}}async compilePipelineEntry(){super.compilePipelineEntry(),this.options.useAsync?await this.createRenderPipelineAsync():(this.createRenderPipeline(),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null)}}const yr=(n={},e={})=>Object.keys(n).filter(t=>Array.isArray(n[t])?JSON.stringify(n[t])!==JSON.stringify(e[t]):n[t]!==e[t]);var qi=` struct VSOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f, @@ -72,7 +126,7 @@ struct VSOutput { vsOutput.viewDirection = camera.position - vsOutput.worldPosition; return vsOutput; -}`,gi=` +}`,Yi=` struct VSOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f, @@ -87,28 +141,28 @@ struct VSOutput { vsOutput.uv = attributes.uv; return vsOutput; -}`,xi=` +}`,Hi=` @fragment fn main() -> @location(0) vec4f { return vec4(0.0, 0.0, 0.0, 1.0); -}`;class yi extends ui{constructor(e,t){const i="RenderMaterial";e=j(e,i),t.shaders||(t.shaders={}),t.shaders?.vertex||(t.shaders.vertex={code:t.useProjection?mi:gi,entryPoint:"main"}),t.shaders.vertex.entryPoint||(t.shaders.vertex.entryPoint="main"),t.shaders.fragment===void 0&&(t.shaders.fragment={entryPoint:"main",code:xi}),super(e,t),this.type=i,this.renderer=e;const{shaders:s}=t,{useProjection:r,transparent:o,depth:a,depthWriteEnabled:h,depthCompare:l,depthFormat:u,cullMode:d,sampleCount:c,verticesOrder:p,topology:g}=t;let{targets:m}=t;m===void 0&&(m=[{format:this.renderer.options.preferredFormat}]),m&&m.length&&!m[0].format&&(m[0].format=this.renderer.options.preferredFormat),this.options={...this.options,shaders:s,rendering:{useProjection:r,transparent:o,depth:a,depthWriteEnabled:h,depthCompare:l,depthFormat:u,cullMode:d,sampleCount:c,targets:m,verticesOrder:p,topology:g}},this.attributes=null,this.pipelineEntry=null}setPipelineEntry(){this.pipelineEntry=this.renderer.pipelineManager.createRenderPipeline({renderer:this.renderer,label:this.options.label+" render pipeline",shaders:this.options.shaders,useAsync:this.options.useAsyncPipeline,rendering:this.options.rendering,attributes:this.attributes,bindGroups:this.bindGroups})}async compilePipelineEntry(){await this.pipelineEntry.compilePipelineEntry()}async compileMaterial(){this.ready||(super.compileMaterial(),this.attributes&&!this.pipelineEntry&&this.setPipelineEntry(),this.pipelineEntry&&this.pipelineEntry.canCompile&&await this.compilePipelineEntry())}setRenderingOptions(e={}){const t=Es(e,this.options.rendering),i={...this.options.rendering};if(this.options.rendering={...this.options.rendering,...e},this.pipelineEntry)if(this.pipelineEntry.ready&&t.length){if(!this.renderer.production){const s=t.map(o=>({[o]:Array.isArray(i[o])?i[o].map(a=>a):i[o]})),r=t.map(o=>({[o]:Array.isArray(e[o])?e[o].map(a=>a):e[o]}));I(`${this.options.label}: the change of rendering options is causing this RenderMaterial pipeline to be recompiled. This should be avoided. +}`;class Xi extends ki{constructor(e,t){const i="RenderMaterial";e=V(e,i),t.shaders||(t.shaders={}),t.shaders?.vertex||(t.shaders.vertex={code:t.useProjection?qi:Yi,entryPoint:"main"}),t.shaders.vertex.entryPoint||(t.shaders.vertex.entryPoint="main"),t.shaders.fragment===void 0&&(t.shaders.fragment={entryPoint:"main",code:Hi}),super(e,t),this.type=i,this.renderer=e;const{shaders:s}=t,{useProjection:r,transparent:o,depth:a,depthWriteEnabled:h,depthCompare:u,depthFormat:l,cullMode:c,sampleCount:p,verticesOrder:d,topology:m}=t;let{targets:g}=t;g===void 0&&(g=[{format:this.renderer.options.context.format}]),g&&g.length&&!g[0].format&&(g[0].format=this.renderer.options.context.format),this.options={...this.options,shaders:s,rendering:{useProjection:r,transparent:o,depth:a,depthWriteEnabled:h,depthCompare:u,depthFormat:l,cullMode:c,sampleCount:p,targets:g,verticesOrder:d,topology:m}},this.attributes=null,this.pipelineEntry=null}setRenderer(e){this.useCameraBindGroup&&this.renderer&&this.renderer.cameraLightsBindGroup.consumers.delete(this.uuid),super.setRenderer(e),this.useCameraBindGroup&&(this.bindGroups[0]=this.renderer.cameraLightsBindGroup,this.renderer.cameraLightsBindGroup.consumers.add(this.uuid))}setPipelineEntry(){this.pipelineEntry=this.renderer.pipelineManager.createRenderPipeline({renderer:this.renderer,label:this.options.label+" render pipeline",shaders:this.options.shaders,useAsync:this.options.useAsyncPipeline,rendering:this.options.rendering,attributes:this.attributes,bindGroups:this.bindGroups})}async compilePipelineEntry(){await this.pipelineEntry.compilePipelineEntry()}async compileMaterial(){this.ready||(super.compileMaterial(),this.attributes&&!this.pipelineEntry&&this.setPipelineEntry(),this.pipelineEntry&&this.pipelineEntry.canCompile&&await this.compilePipelineEntry())}setRenderingOptions(e={}){e.transparent&&e.targets.length&&!e.targets[0].blend&&(e.targets[0].blend=je.getDefaultTransparentBlending());const t=yr(e,this.options.rendering),i={...this.options.rendering};if(this.options.rendering={...this.options.rendering,...e},this.pipelineEntry)if(this.pipelineEntry.ready&&t.length){if(!this.renderer.production){const s=t.map(o=>({[o]:Array.isArray(i[o])?i[o].map(a=>a):i[o]})),r=t.map(o=>({[o]:Array.isArray(e[o])?e[o].map(a=>a):e[o]}));L(`${this.options.label}: the change of rendering options is causing this RenderMaterial pipeline to be recompiled. This should be avoided. Old rendering options: ${JSON.stringify(s.reduce((o,a)=>({...o,...a}),{}),null,4)} -------- -New rendering options: ${JSON.stringify(r.reduce((o,a)=>({...o,...a}),{}),null,4)}`)}this.setPipelineEntry()}else this.pipelineEntry.options.rendering={...this.pipelineEntry.options.rendering,...this.options.rendering}}setAttributesFromGeometry(e){this.attributes={wgslStructFragment:e.wgslStructFragment,vertexBuffers:e.vertexBuffers,layoutCacheKey:e.layoutCacheKey}}get useCameraBindGroup(){return"cameraLightsBindGroup"in this.renderer&&this.options.rendering.useProjection}createBindGroups(){this.useCameraBindGroup&&(this.bindGroups.push(this.renderer.cameraLightsBindGroup),this.renderer.cameraLightsBindGroup.consumers.add(this.uuid)),super.createBindGroups()}updateBindGroups(){const e=this.useCameraBindGroup?1:0;for(let t=e;tn?` +New rendering options: ${JSON.stringify(r.reduce((o,a)=>({...o,...a}),{}),null,4)}`)}this.setPipelineEntry()}else this.pipelineEntry.options.rendering={...this.pipelineEntry.options.rendering,...this.options.rendering}}setAttributesFromGeometry(e){this.attributes={wgslStructFragment:e.wgslStructFragment,vertexBuffers:e.vertexBuffers,layoutCacheKey:e.layoutCacheKey}}get useCameraBindGroup(){return"cameraLightsBindGroup"in this.renderer&&this.options.rendering.useProjection}createBindGroups(){this.useCameraBindGroup&&(this.bindGroups.push(this.renderer.cameraLightsBindGroup),this.renderer.cameraLightsBindGroup.consumers.add(this.uuid)),super.createBindGroups()}updateBindGroups(){const e=this.useCameraBindGroup?1:0;this.useCameraBindGroup&&this.bindGroups[0].needsPipelineFlush&&this.pipelineEntry.ready&&this.pipelineEntry.flushPipelineEntry(this.bindGroups);for(let t=e;tn?` var worldPosition: vec4f = instances[attributes.instanceIndex].modelMatrix * vec4f(attributes.position, 1.0); let normal = (instances[attributes.instanceIndex].normalMatrix * vec4(attributes.normal, 0.0)).xyz; `:` var worldPosition: vec4f = matrices.model * vec4(attributes.position, 1.0); let normal = getWorldNormal(attributes.normal); - `,_s=(n=0,e=!1)=>` + `,vr=(n=0,e=!1)=>` @vertex fn main( attributes: Attributes, ) -> @builtin(position) vec4f { let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[${n}]; - ${Ls(e)} + ${xr(e)} let lightDirection: vec3f = normalize(worldPosition.xyz - directionalLights.elements[${n}].direction); let NdotL: f32 = dot(normalize(normal), lightDirection); @@ -118,7 +172,7 @@ New rendering options: ${JSON.stringify(r.reduce((o,a)=>({...o,...a}),{}),null,4 worldPosition = vec4(worldPosition.xyz - normal * normalBias, worldPosition.w); return directionalShadow.projectionMatrix * directionalShadow.viewMatrix * worldPosition; -}`,As=` +}`,br=` fn getPCFShadowContribution(index: i32, worldPosition: vec3f, depthTexture: texture_depth_2d) -> f32 { let directionalShadow: DirectionalShadowsElement = directionalShadows.directionalShadowsElements[index]; @@ -132,55 +186,70 @@ fn getPCFShadowContribution(index: i32, worldPosition: vec3f, depthTexture: text shadowCoords.z ); - // Percentage-closer filtering. Sample texels in the region - // to smooth the result. var visibility = 0.0; - let size: vec2f = vec2f(textureDimensions(depthTexture).xy); + let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; + let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; - let texelSize: vec2f = 1.0 / size; + if(frustumTest) { + // Percentage-closer filtering. Sample texels in the region + // to smooth the result. + let size: vec2f = vec2f(textureDimensions(depthTexture).xy); - let sampleCount: i32 = directionalShadow.pcfSamples; - let maxSamples: f32 = f32(sampleCount) - 1.0; + let texelSize: vec2f = 1.0 / size; + + let sampleCount: i32 = directionalShadow.pcfSamples; + let maxSamples: f32 = f32(sampleCount) - 1.0; - for (var x = 0; x < sampleCount; x++) { - for (var y = 0; y < sampleCount; y++) { - let offset = texelSize * vec2( - f32(x) - maxSamples * 0.5, - f32(y) - maxSamples * 0.5 - ); - - visibility += textureSampleCompare( - depthTexture, - depthComparisonSampler, - shadowCoords.xy + offset, - shadowCoords.z - directionalShadow.bias - ); + for (var x = 0; x < sampleCount; x++) { + for (var y = 0; y < sampleCount; y++) { + let offset = texelSize * vec2( + f32(x) - maxSamples * 0.5, + f32(y) - maxSamples * 0.5 + ); + + visibility += textureSampleCompareLevel( + depthTexture, + depthComparisonSampler, + shadowCoords.xy + offset, + shadowCoords.z - directionalShadow.bias + ); + } } + visibility /= f32(sampleCount * sampleCount); + + visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); + } + else { + visibility = 1.0; } - visibility /= f32(sampleCount * sampleCount); - - visibility = clamp(visibility, 1.0 - clamp(directionalShadow.intensity, 0.0, 1.0), 1.0); - - let inFrustum: bool = shadowCoords.x >= 0.0 && shadowCoords.x <= 1.0 && shadowCoords.y >= 0.0 && shadowCoords.y <= 1.0; - let frustumTest: bool = inFrustum && shadowCoords.z <= 1.0; - return select(1.0, visibility, frustumTest); + return visibility; } -`,Gs=n=>{const e=n.shadowCastingLights.filter(t=>t.type==="directionalLights");return` -fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { - var directionalShadowContribution: array; +`,wr=n=>{const e=n.shadowCastingLights.filter(i=>i.type==="directionalLights"),t=Math.max(n.lightsBindingParams.directionalLights.max,1);return` +fn getPCFDirectionalShadows(worldPosition: vec3f) -> array { + var directionalShadowContribution: array; var lightDirection: vec3f; - ${e.map((t,i)=>`lightDirection = worldPosition - directionalLights.elements[${i}].direction; + ${e.map((i,s)=>`lightDirection = worldPosition - directionalLights.elements[${s}].direction; - ${t.shadow.isActive?`directionalShadowContribution[${i}] = select( 1.0, getPCFShadowContribution(${i}, worldPosition, shadowDepthTexture${i}), directionalShadows.directionalShadowsElements[${i}].isActive > 0);`:""}`).join(` + ${i.shadow.isActive?` + if(directionalShadows.directionalShadowsElements[${s}].isActive > 0) { + directionalShadowContribution[${s}] = getPCFShadowContribution( + ${s}, + worldPosition, + shadowDepthTexture${s} + ); + } else { + directionalShadowContribution[${s}] = 1.0; + } + `:`directionalShadowContribution[${s}] = 1.0;`}`).join(` `)} return directionalShadowContribution; } -`},Os=(n=0,e=!1)=>` +`},Br=(n=0,e=!1)=>` struct PointShadowVSOutput { @builtin(position) position: vec4f, @location(0) worldPosition: vec3f, @@ -191,7 +260,7 @@ struct PointShadowVSOutput { ) -> PointShadowVSOutput { var pointShadowVSOutput: PointShadowVSOutput; - ${Ls(e)} + ${xr(e)} let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[${n}]; @@ -208,7 +277,7 @@ struct PointShadowVSOutput { pointShadowVSOutput.worldPosition = worldPosition.xyz; return pointShadowVSOutput; -}`,Ds=(n=0)=>` +}`,Mr=(n=0)=>` struct PointShadowVSOutput { @builtin(position) position: vec4f, @location(0) worldPosition: vec3f, @@ -225,7 +294,7 @@ struct PointShadowVSOutput { // write this as modified depth return clamp(lightDistance, 0.0, 1.0); -}`,Fs=` +}`,Cr=` fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTexture: texture_depth_cube) -> f32 { let pointShadow: PointShadowsElement = pointShadows.pointShadowsElements[index]; @@ -252,7 +321,7 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex f32(z) - maxSamples * 0.5 ); - closestDepth = textureSampleCompare( + closestDepth = textureSampleCompareLevel( depthCubeTexture, depthComparisonSampler, shadowPosition.xyz + offset, @@ -260,9 +329,8 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex ); closestDepth *= cameraRange; - if(currentDepth <= closestDepth) { - visibility += 1.0; - } + + visibility += select(0.0, 1.0, currentDepth <= closestDepth); } } } @@ -272,29 +340,42 @@ fn getPCFPointShadowContribution(index: i32, shadowPosition: vec4f, depthCubeTex visibility = clamp(visibility, 1.0 - clamp(pointShadow.intensity, 0.0, 1.0), 1.0); return visibility; -}`,Us=n=>{const e=n.shadowCastingLights.filter(t=>t.type==="pointLights");return` -fn getPCFPointShadows(worldPosition: vec3f) -> array { - var pointShadowContribution: array; +}`,Sr=n=>{const e=n.shadowCastingLights.filter(i=>i.type==="pointLights"),t=Math.max(n.lightsBindingParams.pointLights.max,1);return` +fn getPCFPointShadows(worldPosition: vec3f) -> array { + var pointShadowContribution: array; var lightDirection: vec3f; + var lightDistance: f32; + var lightColor: vec3f; - ${e.map((t,i)=>`lightDirection = pointLights.elements[${i}].position - worldPosition; + ${e.map((i,s)=>`lightDirection = pointLights.elements[${s}].position - worldPosition; - ${t.shadow.isActive?`pointShadowContribution[${i}] = select( 1.0, getPCFPointShadowContribution(${i}, vec4(lightDirection, length(lightDirection)), pointShadowCubeDepthTexture${i}), pointShadows.pointShadowsElements[${i}].isActive > 0);`:""}`).join(` + lightDistance = length(lightDirection); + lightColor = pointLights.elements[${s}].color * rangeAttenuation(pointLights.elements[${s}].range, lightDistance); + + ${i.shadow.isActive?` + if(pointShadows.pointShadowsElements[${s}].isActive > 0 && length(lightColor) > 0.0001) { + pointShadowContribution[${s}] = getPCFPointShadowContribution( + ${s}, + vec4(lightDirection, length(lightDirection)), + pointShadowCubeDepthTexture${s} + ); + } else { + pointShadowContribution[${s}] = 1.0; + } + `:`pointShadowContribution[${s}] = 1.0;`}`).join(` `)} return pointShadowContribution; } -`},He=` +`},ut=` let pointShadows = getPCFPointShadows(worldPosition); let directionalShadows = getPCFDirectionalShadows(worldPosition); -`,Ze=` +`,lt=` directLight.color *= directionalShadows[i]; -`,Ke=` - if(directLight.visible) { - directLight.color *= pointShadows[i]; - } -`;var vi=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},N=(n,e,t)=>(vi(n,e,"read from private field"),t?t.call(n):e.get(n)),ce=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},re=(n,e,t,i)=>(vi(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),$s=(n,e,t)=>(vi(n,e,"access private method"),t),Pt,St,Tt,Rt,zt,Qe,Oe,he,we,Et,wi;const ks={isActive:{type:"i32",value:0},pcfSamples:{type:"i32",value:0},bias:{type:"f32",value:0},normalBias:{type:"f32",value:0},intensity:{type:"f32",value:0}};class Is{constructor(e,{light:t,intensity:i=1,bias:s=0,normalBias:r=0,pcfSamples:o=1,depthTextureSize:a=new E(512),depthTextureFormat:h="depth24plus",autoRender:l=!0}={}){ce(this,Et),ce(this,Pt,void 0),ce(this,St,void 0),ce(this,Tt,void 0),ce(this,Rt,void 0),ce(this,zt,void 0),ce(this,Qe,void 0),ce(this,Oe,void 0),ce(this,he,void 0),ce(this,we,void 0),e=Le(e,this.constructor.name),this.renderer=e,this.rendererBinding=null,this.light=t,this.index=this.light.index,this.options={light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h},this.sampleCount=1,this.meshes=new Map,re(this,Oe,new Map),re(this,he,new Map),re(this,we,null),$s(this,Et,wi).call(this,{intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:l}),this.isActive=!1}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a}={}){$s(this,Et,wi).call(this,{intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a}),this.isActive=!0}setRendererBinding(){}reset(){this.isActive&&(this.onPropertyChanged("isActive",1),this.onPropertyChanged("intensity",this.intensity),this.onPropertyChanged("bias",this.bias),this.onPropertyChanged("normalBias",this.normalBias),this.onPropertyChanged("pcfSamples",this.pcfSamples))}get isActive(){return N(this,zt)}set isActive(e){!e&&this.isActive?this.destroy():e&&!this.isActive&&this.init(),re(this,zt,e)}get intensity(){return N(this,Pt)}set intensity(e){re(this,Pt,e),this.onPropertyChanged("intensity",this.intensity)}get bias(){return N(this,St)}set bias(e){re(this,St,e),this.onPropertyChanged("bias",this.bias)}get normalBias(){return N(this,Tt)}set normalBias(e){re(this,Tt,e),this.onPropertyChanged("normalBias",this.normalBias)}get pcfSamples(){return N(this,Rt)}set pcfSamples(e){re(this,Rt,Math.max(1,Math.ceil(e))),this.onPropertyChanged("pcfSamples",this.pcfSamples)}init(){if(!this.depthComparisonSampler){const e=this.renderer.samplers.find(t=>t.name==="depthComparisonSampler");this.depthComparisonSampler=e||new Ae(this.renderer,{label:"Depth comparison sampler",name:"depthComparisonSampler",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge",compare:"less",minFilter:"linear",magFilter:"linear",type:"comparison"})}this.setDepthTexture(),this.depthPassTarget||this.createDepthPassTarget(),N(this,we)===null&&N(this,Qe)&&(this.setDepthPass(),this.onPropertyChanged("isActive",1))}onDepthTextureSizeChanged(){this.setDepthTexture()}setDepthTexture(){this.depthTexture&&(this.depthTexture.size.width!==this.depthTextureSize.x||this.depthTexture.size.height!==this.depthTextureSize.y)?(this.depthTexture.options.fixedSize.width=this.depthTextureSize.x,this.depthTexture.options.fixedSize.height=this.depthTextureSize.y,this.depthTexture.size.width=this.depthTextureSize.x,this.depthTexture.size.height=this.depthTextureSize.y,this.depthTexture.createTexture(),this.depthPassTarget&&this.depthPassTarget.resize()):this.depthTexture||this.createDepthTexture()}createDepthTexture(){this.depthTexture=new se(this.renderer,{label:"Shadow depth texture "+this.index,name:"shadowDepthTexture"+this.index,type:"depth",format:this.depthTextureFormat,sampleCount:this.sampleCount,fixedSize:{width:this.depthTextureSize.x,height:this.depthTextureSize.y},autoDestroy:!1})}createDepthPassTarget(){this.depthPassTarget=new fi(this.renderer,{label:"Depth pass render target for "+this.constructor.name+" "+this.index,useColorAttachments:!1,depthTexture:this.depthTexture,sampleCount:this.sampleCount})}onPropertyChanged(e,t){if(this.rendererBinding){if(t instanceof A){for(let i=0;i{this.meshes.size&&(this.useDepthMaterials(),this.renderDepthPass(t),this.useOriginalMaterials(),this.renderer.pipelineManager.resetCurrentPipeline())},{once:e,order:this.index})}async renderOnce(){N(this,Qe)||(this.onPropertyChanged("isActive",1),this.useDepthMaterials(),this.meshes.forEach(e=>{e.setGeometry()}),await Promise.all([...N(this,he).values()].map(async e=>{await e.compileMaterial()})),this.render(!0))}renderDepthPass(e){this.renderer.pipelineManager.resetCurrentPipeline();const t=e.beginRenderPass(this.depthPassTarget.renderPass.descriptor);this.meshes.forEach(i=>{i.render(t)}),t.end()}getDefaultShadowDepthVs(e=!1){return{code:_s(this.index,e)}}getDefaultShadowDepthFs(){return!1}patchShadowCastingMeshParams(e,t={}){t={...e.material.options.rendering,...t},t.targets=[],t.sampleCount=this.sampleCount,t.depthFormat=this.depthTextureFormat,t.bindings?t.bindings=[e.material.getBufferBindingByName("matrices"),...t.bindings]:t.bindings=[e.material.getBufferBindingByName("matrices")];const i=e.material.inputsBindings.get("instances")&&e.geometry.instancesCount>1;return t.shaders||(t.shaders={vertex:this.getDefaultShadowDepthVs(i),fragment:this.getDefaultShadowDepthFs()}),t}addShadowCastingMesh(e,t={}){e.options.castShadows=!0,N(this,Oe).set(e.uuid,e.material),t=this.patchShadowCastingMeshParams(e,t),N(this,he).get(e.uuid)&&(N(this,he).get(e.uuid).destroy(),N(this,he).delete(e.uuid)),N(this,he).set(e.uuid,new yi(this.renderer,{label:e.options.label+" depth render material",...t})),this.meshes.set(e.uuid,e)}useDepthMaterials(){this.meshes.forEach(e=>{e.useMaterial(N(this,he).get(e.uuid))})}useOriginalMaterials(){this.meshes.forEach(e=>{e.useMaterial(N(this,Oe).get(e.uuid))})}removeMesh(e){const t=N(this,he).get(e.uuid);t&&(t.destroy(),N(this,he).delete(e.uuid)),this.meshes.delete(e.uuid)}destroy(){this.onPropertyChanged("isActive",0),N(this,we)!==null&&(this.removeDepthPass(N(this,we)),re(this,we,null)),this.meshes.forEach(e=>this.removeMesh(e)),re(this,Oe,new Map),re(this,he,new Map),this.meshes=new Map,this.depthPassTarget?.destroy(),this.depthTexture?.destroy()}}Pt=new WeakMap,St=new WeakMap,Tt=new WeakMap,Rt=new WeakMap,zt=new WeakMap,Qe=new WeakMap,Oe=new WeakMap,he=new WeakMap,we=new WeakMap,Et=new WeakSet,wi=function({intensity:n=1,bias:e=0,normalBias:t=0,pcfSamples:i=1,depthTextureSize:s=new E(512),depthTextureFormat:r="depth24plus",autoRender:o=!0}={}){this.intensity=n,this.bias=e,this.normalBias=t,this.pcfSamples=i,this.depthTextureSize=s,this.depthTextureSize.onChange(()=>this.onDepthTextureSizeChanged()),this.depthTextureFormat=r,re(this,Qe,o)};const nn={...ks,viewMatrix:{type:"mat4x4f",value:new Float32Array(16)},projectionMatrix:{type:"mat4x4f",value:new Float32Array(16)}};class on extends Is{constructor(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:l,camera:u={left:-10,right:10,bottom:-10,top:10,near:.1,far:50}}={}){super(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:l}),this.options={...this.options,camera:u},this.setRendererBinding(),this.camera={projectionMatrix:new A,viewMatrix:new A,up:new f(0,1,0),_left:u.left,_right:u.right,_bottom:u.bottom,_top:u.top,_near:u.near,_far:u.far};const d=this;["left","right","bottom","top","near","far"].forEach(p=>{Object.defineProperty(d.camera,p,{get(){return d.camera["_"+p]},set(g){d.camera["_"+p]=g,d.updateProjectionMatrix()}})})}setRendererBinding(){this.rendererBinding=this.renderer.bindings.directionalShadows}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a,camera:h}={}){h&&(this.camera.left=h.left??-10,this.camera.right=h.right??10,this.camera.bottom=h.bottom??-10,this.camera.top=h.right??10,this.camera.near=h.near??.1,this.camera.far=h.far??50),super.cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a})}init(){super.init(),this.updateProjectionMatrix()}reset(){this.setRendererBinding(),super.reset(),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix),this.onPropertyChanged("viewMatrix",this.camera.viewMatrix)}updateProjectionMatrix(){this.camera.projectionMatrix.identity().makeOrthographic({left:this.camera.left,right:this.camera.right,bottom:this.camera.bottom,top:this.camera.top,near:this.camera.near,far:this.camera.far}),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix)}updateViewMatrix(e=new f,t=new f){e.x===0&&e.z===0?this.camera.up.set(0,0,1):this.camera.up.set(0,1,0),this.camera.viewMatrix.makeView(e,t,this.camera.up),this.onPropertyChanged("viewMatrix",this.camera.viewMatrix)}}var Vs=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Lt=(n,e,t)=>(Vs(n,e,"read from private field"),t?t.call(n):e.get(n)),Ns=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Ws=(n,e,t,i)=>(Vs(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),Je,et;class an extends pi{constructor(e,{color:t=new f(1),intensity:i=1,position:s=new f(1),target:r=new f,shadow:o=null}={}){const a="directionalLights",h=e.lights.filter(l=>l.type===a).length;super(e,{color:t,intensity:i,index:h,type:a}),Ns(this,Je,void 0),Ns(this,et,void 0),this.options={...this.options,position:s,target:r,shadow:o},Ws(this,et,new f),Ws(this,Je,new f),this.target=r,this.target.onChange(()=>this.setDirection()),this.position.copy(s),this.parent=this.renderer.scene,this.index+1>this.renderer.lightsBindingParams[this.type].max&&this.onMaxLightOverflow(this.type),this.rendererBinding.inputs.count.value=this.index+1,this.rendererBinding.inputs.count.shouldUpdate=!0,this.shadow=new on(this.renderer,{autoRender:!1,light:this}),o&&this.shadow.cast(o)}reset(){super.reset(),this.setDirection(),this.shadow?.reset()}setDirection(){Lt(this,et).copy(this.target).sub(this.worldMatrix.getTranslation(Lt(this,Je))),this.onPropertyChanged("direction",Lt(this,et)),this.shadow?.updateViewMatrix(Lt(this,Je),this.target)}applyScale(){}applyTransformOrigin(){}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.setDirection()}onMaxLightOverflow(e){super.onMaxLightOverflow(e),this.shadow?.setRendererBinding()}destroy(){super.destroy(),this.shadow.destroy()}}Je=new WeakMap,et=new WeakMap;var js=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},qs=(n,e,t)=>(js(n,e,"read from private field"),t?t.call(n):e.get(n)),hn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},ln=(n,e,t,i)=>(js(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),tt;const un={face:{type:"i32",value:0},...ks,cameraNear:{type:"f32",value:0},cameraFar:{type:"f32",value:0},projectionMatrix:{type:"mat4x4f",value:new Float32Array(16)},viewMatrices:{type:"array",value:new Float32Array(16*6)}};class dn extends Is{constructor(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:l,camera:u={near:.1,far:150}}={}){super(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:l}),hn(this,tt,void 0),this.options={...this.options,camera:u},this.setRendererBinding(),this.cubeDirections=[new f(-1,0,0),new f(1,0,0),new f(0,-1,0),new f(0,1,0),new f(0,0,-1),new f(0,0,1)],ln(this,tt,new f),this.cubeUps=[new f(0,-1,0),new f(0,-1,0),new f(0,0,1),new f(0,0,-1),new f(0,-1,0),new f(0,-1,0)],u.far<=0&&(u.far=150),this.camera={projectionMatrix:new A,viewMatrices:[],_near:u.near,_far:u.far};for(let p=0;p<6;p++)this.camera.viewMatrices.push(new A);const d=this;["near","far"].forEach(p=>{Object.defineProperty(d.camera,p,{get(){return d.camera["_"+p]},set(g){d.camera["_"+p]=g,d.updateProjectionMatrix()}})})}setRendererBinding(){this.rendererBinding=this.renderer.bindings.pointShadows}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a,camera:h}={}){h&&(this.camera.near=h.near??.1,this.camera.far=h.far!==void 0?h.far:this.light.range>0?this.light.range:150),super.cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a})}init(){super.init(),this.updateProjectionMatrix()}reset(){this.setRendererBinding(),super.reset(),this.onPropertyChanged("cameraNear",this.camera.near),this.onPropertyChanged("cameraFar",this.camera.far),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix),this.updateViewMatrices()}updateProjectionMatrix(){this.camera.projectionMatrix.identity().makePerspective({near:this.camera.near,far:this.camera.far,fov:90,aspect:1}),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix),this.onPropertyChanged("cameraNear",this.camera.near),this.onPropertyChanged("cameraFar",this.camera.far)}updateViewMatrices(e=new f){for(let t=0;t<6;t++){qs(this,tt).copy(this.cubeDirections[t]).add(e),this.camera.viewMatrices[t].makeView(e,qs(this,tt),this.cubeUps[t]);for(let i=0;i<16;i++)this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.value[t*16+i]=this.camera.viewMatrices[t].elements[i]}this.rendererBinding.options.bindings[this.index].inputs.viewMatrices.shouldUpdate=!0}setDepthTexture(){if(this.depthTexture&&(this.depthTexture.size.width!==this.depthTextureSize.x||this.depthTexture.size.height!==this.depthTextureSize.y)){const e=Math.max(this.depthTextureSize.x,this.depthTextureSize.y);this.depthTexture.options.fixedSize.width=e,this.depthTexture.options.fixedSize.height=e,this.depthTexture.size.width=e,this.depthTexture.size.height=e,this.depthTexture.createTexture(),this.depthPassTarget&&this.depthPassTarget.resize()}else this.depthTexture||this.createDepthTexture()}createDepthTexture(){const e=Math.max(this.depthTextureSize.x,this.depthTextureSize.y);this.depthTexture=new se(this.renderer,{label:"Point shadow cube depth texture "+this.index,name:"pointShadowCubeDepthTexture"+this.index,type:"depth",format:this.depthTextureFormat,viewDimension:"cube",sampleCount:this.sampleCount,fixedSize:{width:e,height:e},autoDestroy:!1})}removeDepthPass(e){this.renderer.onBeforeCommandEncoderCreation.remove(e)}render(e=!1){return this.renderer.onBeforeCommandEncoderCreation.add(()=>{if(this.meshes.size){this.useDepthMaterials();for(let t=0;t<6;t++){const i=this.renderer.device.createCommandEncoder();this.depthPassTarget.renderPass.setRenderPassDescriptor(this.depthTexture.texture.createView({label:this.depthTexture.texture.label+" cube face view "+t,dimension:"2d",arrayLayerCount:1,baseArrayLayer:t})),this.rendererBinding.options.bindings[this.index].inputs.face.value=t,this.renderer.cameraLightsBindGroup.update(),this.renderDepthPass(i);const s=i.finish();this.renderer.device.queue.submit([s])}this.useOriginalMaterials(),this.renderer.pipelineManager.resetCurrentPipeline()}},{once:e,order:this.index})}getDefaultShadowDepthVs(e=!1){return{code:Os(this.index,e)}}getDefaultShadowDepthFs(){return{code:Ds(this.index)}}}tt=new WeakMap;var Ys=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},bi=(n,e,t)=>(Ys(n,e,"read from private field"),t?t.call(n):e.get(n)),Xs=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Hs=(n,e,t,i)=>(Ys(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),_t,it;class cn extends pi{constructor(e,{color:t=new f(1),intensity:i=1,position:s=new f,range:r=0,shadow:o=null}={}){const a="pointLights",h=e.lights.filter(l=>l.type===a).length;super(e,{color:t,intensity:i,index:h,type:a}),Xs(this,_t,void 0),Xs(this,it,void 0),this.options={...this.options,position:s,range:r,shadow:o},Hs(this,it,new f),this.position.copy(s),this.range=r,this.parent=this.renderer.scene,this.index+1>this.renderer.lightsBindingParams[this.type].max&&this.onMaxLightOverflow(this.type),this.rendererBinding.inputs.count.value=this.index+1,this.rendererBinding.inputs.count.shouldUpdate=!0,this.shadow=new dn(this.renderer,{autoRender:!1,light:this}),o&&this.shadow.cast(o)}reset(){super.reset(),this.onPropertyChanged("range",this.range),this.setPosition(),this.shadow?.reset()}get range(){return bi(this,_t)}set range(e){Hs(this,_t,e),this.onPropertyChanged("range",this.range)}setPosition(){this.onPropertyChanged("position",this.worldMatrix.getTranslation(bi(this,it))),this.shadow?.updateViewMatrices(bi(this,it))}applyScale(){}applyTransformOrigin(){}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.setPosition()}onMaxLightOverflow(e){super.onMaxLightOverflow(e),this.shadow?.setRendererBinding()}destroy(){super.destroy(),this.shadow.destroy()}}_t=new WeakMap,it=new WeakMap;var Zs=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Mi=(n,e,t)=>(Zs(n,e,"read from private field"),t?t.call(n):e.get(n)),pn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},fn=(n,e,t,i)=>(Zs(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t);let mn=0;const Ks={autoRender:!0,useProjection:!1,useAsyncPipeline:!0,cullMode:"back",depth:!0,depthWriteEnabled:!0,depthCompare:"less",depthFormat:"depth24plus",transparent:!1,visible:!0,renderOrder:0,texturesOptions:{}};function Qs(n){var e,t;return t=class extends n{constructor(...i){super(i[0],i[1],{...Ks,...i[2]}),pn(this,e,!0),this._onReadyCallback=()=>{},this._onBeforeRenderCallback=()=>{},this._onRenderCallback=()=>{},this._onAfterRenderCallback=()=>{},this._onAfterResizeCallback=()=>{};let s=i[0];const r={...Ks,...i[2]};this.type="MeshBase",this.uuid=Y(),Object.defineProperty(this,"index",{value:mn++}),s=j(s,r.label?r.label+" "+this.type:this.type),this.renderer=s;const{label:o,shaders:a,geometry:h,visible:l,renderOrder:u,outputTarget:d,texturesOptions:c,autoRender:p,...g}=r;this.outputTarget=d??null,g.sampleCount=g.sampleCount?g.sampleCount:this.outputTarget?this.outputTarget.renderPass.options.sampleCount:this.renderer&&this.renderer.renderPass?this.renderer.renderPass.options.sampleCount:1,this.options={...this.options??{},label:o??"Mesh "+this.renderer.meshes.length,...a!==void 0?{shaders:a}:{},...d!==void 0&&{outputTarget:d},texturesOptions:c,...p!==void 0&&{autoRender:p},...g},p!==void 0&&fn(this,e,p),this.visible=l,this.renderOrder=u,this.ready=!1,this.userData={},h&&this.useGeometry(h),this.setMaterial({...this.cleanupRenderMaterialParameters({...this.options}),...h&&{verticesOrder:h.verticesOrder,topology:h.topology}}),this.addToScene(!0)}get autoRender(){return Mi(this,e)}get ready(){return this._ready}set ready(i){i&&!this._ready&&this._onReadyCallback&&this._onReadyCallback(),this._ready=i}addToScene(i=!1){i&&this.renderer.meshes.push(this),this.setRenderingOptionsForRenderPass(this.outputTarget?this.outputTarget.renderPass:this.renderer.renderPass),Mi(this,e)&&this.renderer.scene.addMesh(this)}removeFromScene(i=!1){Mi(this,e)&&this.renderer.scene.removeMesh(this),i&&(this.renderer.meshes=this.renderer.meshes.filter(s=>s.uuid!==this.uuid))}setRenderer(i){if(i=i&&i.renderer||i,!i||!(i.type==="GPURenderer"||i.type==="GPUCameraRenderer"||i.type==="GPUCurtainsRenderer")){I(`${this.options.label}: Cannot set ${i} as a renderer because it is not of a valid Renderer type.`);return}const s=this.renderer;this.removeFromScene(!0),this.renderer=i,this.addToScene(!0),s.meshes.length||s.onBeforeRenderScene.add(r=>{s.forceClear(r)},{once:!0})}setOutputTarget(i){if(i&&i.type!=="RenderTarget"){I(`${this.options.label??this.type}: outputTarget is not a RenderTarget: ${i}`);return}this.removeFromScene(),this.outputTarget=i,this.addToScene()}loseContext(){this.ready=!1,this.geometry.loseContext(),this.material.loseContext()}restoreContext(){this.geometry.restoreContext(this.renderer),this.material.restoreContext()}setShaders(){const{shaders:i}=this.options;i?((!i.vertex||!i.vertex.code)&&(i.vertex={code:gi,entryPoint:"main"}),(i.fragment===void 0||i.fragment&&!i.fragment.code)&&(i.fragment={code:xi,entryPoint:"main"})):this.options.shaders={vertex:{code:gi,entryPoint:"main"},fragment:{code:xi,entryPoint:"main"}}}useGeometry(i){if(this.geometry&&(i.shouldCompute&&i.computeGeometry(),this.geometry.layoutCacheKey!==i.layoutCacheKey&&(I(`${this.options.label} (${this.type}): the current and new geometries do not have the same vertexBuffers layout, causing a probable pipeline recompilation. This should be avoided. +`,dt=` + directLight.color *= pointShadows[i]; +`;var Ki=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},j=(n,e,t)=>(Ki(n,e,"read from private field"),t?t.call(n):e.get(n)),we=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},re=(n,e,t,i)=>(Ki(n,e,"write to private field"),e.set(n,t),t),Tr=(n,e,t)=>(Ki(n,e,"access private method"),t),Zt,Jt,Qt,ei,ti,ct,qe,ne,Pe,ii,Zi;const Pr={isActive:{type:"i32",value:0},pcfSamples:{type:"i32",value:0},bias:{type:"f32",value:0},normalBias:{type:"f32",value:0},intensity:{type:"f32",value:0}};class Rr{constructor(e,{light:t,intensity:i=1,bias:s=0,normalBias:r=0,pcfSamples:o=1,depthTextureSize:a=new _(512),depthTextureFormat:h="depth24plus",autoRender:u=!0}={}){we(this,ii),we(this,Zt,void 0),we(this,Jt,void 0),we(this,Qt,void 0),we(this,ei,void 0),we(this,ti,void 0),we(this,ct,void 0),we(this,qe,void 0),we(this,ne,void 0),we(this,Pe,void 0),this.setRenderer(e),this.light=t,this.index=this.light.index,this.options={light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h},this.sampleCount=1,this.meshes=new Map,re(this,qe,new Map),re(this,ne,new Map),re(this,Pe,null),Tr(this,ii,Zi).call(this,{intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:u}),this.isActive=!1}setRenderer(e){e=Ae(e,this.constructor.name),this.renderer=e,this.setRendererBinding(),j(this,ne)?.forEach(t=>{t.setRenderer(this.renderer)})}setRendererBinding(){this.rendererBinding=null}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a}={}){Tr(this,ii,Zi).call(this,{intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a}),this.isActive=!0}reset(){this.onPropertyChanged("isActive",this.isActive?1:0),this.onPropertyChanged("intensity",this.intensity),this.onPropertyChanged("bias",this.bias),this.onPropertyChanged("normalBias",this.normalBias),this.onPropertyChanged("pcfSamples",this.pcfSamples)}get isActive(){return j(this,ti)}set isActive(e){!e&&this.isActive?this.destroy():e&&!this.isActive&&this.init(),re(this,ti,e)}get intensity(){return j(this,Zt)}set intensity(e){re(this,Zt,e),this.onPropertyChanged("intensity",this.intensity)}get bias(){return j(this,Jt)}set bias(e){re(this,Jt,e),this.onPropertyChanged("bias",this.bias)}get normalBias(){return j(this,Qt)}set normalBias(e){re(this,Qt,e),this.onPropertyChanged("normalBias",this.normalBias)}get pcfSamples(){return j(this,ei)}set pcfSamples(e){re(this,ei,Math.max(1,Math.ceil(e))),this.onPropertyChanged("pcfSamples",this.pcfSamples)}init(){if(!this.depthComparisonSampler){const e=this.renderer.samplers.find(t=>t.name==="depthComparisonSampler");this.depthComparisonSampler=e||new Ne(this.renderer,{label:"Depth comparison sampler",name:"depthComparisonSampler",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge",compare:"less",minFilter:"linear",magFilter:"linear",type:"comparison"})}this.setDepthTexture(),this.depthPassTarget||this.createDepthPassTarget(),j(this,Pe)===null&&j(this,ct)&&(this.setDepthPass(),this.onPropertyChanged("isActive",1))}onDepthTextureSizeChanged(){this.setDepthTexture()}setDepthTexture(){this.depthTexture&&(this.depthTexture.size.width!==this.depthTextureSize.x||this.depthTexture.size.height!==this.depthTextureSize.y)?(this.depthTexture.options.fixedSize.width=this.depthTextureSize.x,this.depthTexture.options.fixedSize.height=this.depthTextureSize.y,this.depthTexture.size.width=this.depthTextureSize.x,this.depthTexture.size.height=this.depthTextureSize.y,this.depthTexture.createTexture(),this.depthPassTarget&&this.depthPassTarget.resize()):this.depthTexture||this.createDepthTexture()}createDepthTexture(){this.depthTexture=new K(this.renderer,{label:`${this.constructor.name} (index: ${this.light.index}) depth texture`,name:"shadowDepthTexture"+this.index,type:"depth",format:this.depthTextureFormat,sampleCount:this.sampleCount,fixedSize:{width:this.depthTextureSize.x,height:this.depthTextureSize.y},autoDestroy:!1})}createDepthPassTarget(){this.depthPassTarget=new Wi(this.renderer,{label:"Depth pass render target for "+this.constructor.name+" "+this.index,useColorAttachments:!1,depthTexture:this.depthTexture,sampleCount:this.sampleCount})}onPropertyChanged(e,t){if(this.rendererBinding){if(t instanceof D){for(let i=0;i{this.meshes.size&&(this.useDepthMaterials(),this.renderDepthPass(t),this.useOriginalMaterials(),this.renderer.pipelineManager.resetCurrentPipeline())},{once:e,order:this.index})}async renderOnce(){j(this,ct)||(this.onPropertyChanged("isActive",1),this.useDepthMaterials(),this.meshes.forEach(e=>{e.setGeometry()}),await Promise.all([...j(this,ne).values()].map(async e=>{await e.compileMaterial()})),this.render(!0))}renderDepthPass(e){const t=new Map;this.meshes.forEach(s=>{s.options.renderBundle&&t.set(s.options.renderBundle.uuid,s.options.renderBundle)}),t.forEach(s=>{s.updateBinding()}),t.clear(),this.renderer.pipelineManager.resetCurrentPipeline();const i=e.beginRenderPass(this.depthPassTarget.renderPass.descriptor);this.renderer.production||i.pushDebugGroup(`${this.constructor.name} (index: ${this.index}): depth pass`),this.meshes.forEach(s=>{s.render(i)}),this.renderer.production||i.popDebugGroup(),i.end()}getDefaultShadowDepthVs(e=!1){return{code:vr(this.index,e)}}getDefaultShadowDepthFs(){return!1}patchShadowCastingMeshParams(e,t={}){t={...e.material.options.rendering,...t},t.targets=[],t.sampleCount=this.sampleCount,t.depthFormat=this.depthTextureFormat,t.bindings?t.bindings=[e.material.getBufferBindingByName("matrices"),...t.bindings]:t.bindings=[e.material.getBufferBindingByName("matrices")];const i=e.material.inputsBindings.get("instances")&&e.geometry.instancesCount>1;return t.shaders||(t.shaders={vertex:this.getDefaultShadowDepthVs(i),fragment:this.getDefaultShadowDepthFs()}),t}addShadowCastingMesh(e,t={}){this.meshes.get(e.uuid)||(e.options.castShadows=!0,j(this,qe).set(e.uuid,e.material),t=this.patchShadowCastingMeshParams(e,t),j(this,ne).get(e.uuid)&&(j(this,ne).get(e.uuid).destroy(),j(this,ne).delete(e.uuid)),j(this,ne).set(e.uuid,new Xi(this.renderer,{label:`${this.constructor.name} (index: ${this.index}) ${e.options.label} depth render material`,...t})),this.meshes.set(e.uuid,e))}useDepthMaterials(){this.meshes.forEach(e=>{e.useMaterial(j(this,ne).get(e.uuid))})}useOriginalMaterials(){this.meshes.forEach(e=>{e.useMaterial(j(this,qe).get(e.uuid))})}removeMesh(e){const t=j(this,ne).get(e.uuid);t&&(t.destroy(),j(this,ne).delete(e.uuid)),this.meshes.delete(e.uuid)}destroy(){this.onPropertyChanged("isActive",0),j(this,Pe)!==null&&(this.removeDepthPass(j(this,Pe)),re(this,Pe,null)),this.meshes.forEach(e=>this.removeMesh(e)),re(this,qe,new Map),re(this,ne,new Map),this.meshes=new Map,this.depthPassTarget?.destroy(),this.depthTexture?.destroy()}}Zt=new WeakMap,Jt=new WeakMap,Qt=new WeakMap,ei=new WeakMap,ti=new WeakMap,ct=new WeakMap,qe=new WeakMap,ne=new WeakMap,Pe=new WeakMap,ii=new WeakSet,Zi=function({intensity:n=1,bias:e=0,normalBias:t=0,pcfSamples:i=1,depthTextureSize:s=new _(512),depthTextureFormat:r="depth24plus",autoRender:o=!0}={}){this.intensity=n,this.bias=e,this.normalBias=t,this.pcfSamples=i,this.depthTextureSize=s,this.depthTextureSize.onChange(()=>this.onDepthTextureSizeChanged()),this.depthTextureFormat=r,re(this,ct,o)};const oo={...Pr,viewMatrix:{type:"mat4x4f",value:new Float32Array(16)},projectionMatrix:{type:"mat4x4f",value:new Float32Array(16)}};class ao extends Rr{constructor(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:u,camera:l={left:-10,right:10,bottom:-10,top:10,near:.1,far:50}}={}){super(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:u}),this.options={...this.options,camera:l},this.camera={projectionMatrix:new D,viewMatrix:new D,up:new f(0,1,0),_left:l.left,_right:l.right,_bottom:l.bottom,_top:l.top,_near:l.near,_far:l.far};const c=this;["left","right","bottom","top","near","far"].forEach(d=>{Object.defineProperty(c.camera,d,{get(){return c.camera["_"+d]},set(m){c.camera["_"+d]=m,c.updateProjectionMatrix()}})})}setRendererBinding(){this.rendererBinding=this.renderer.bindings.directionalShadows}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a,camera:h}={}){h&&(this.camera.left=h.left??-10,this.camera.right=h.right??10,this.camera.bottom=h.bottom??-10,this.camera.top=h.right??10,this.camera.near=h.near??.1,this.camera.far=h.far??50),super.cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a})}init(){super.init(),this.updateProjectionMatrix()}reset(){this.setRendererBinding(),super.reset(),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix),this.onPropertyChanged("viewMatrix",this.camera.viewMatrix)}updateProjectionMatrix(){this.camera.projectionMatrix.identity().makeOrthographic({left:this.camera.left,right:this.camera.right,bottom:this.camera.bottom,top:this.camera.top,near:this.camera.near,far:this.camera.far}),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix)}updateViewMatrix(e=new f,t=new f){e.x===0&&e.z===0?this.camera.up.set(0,0,1):this.camera.up.set(0,1,0),this.camera.viewMatrix.makeView(e,t,this.camera.up),this.onPropertyChanged("viewMatrix",this.camera.viewMatrix)}}var zr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},si=(n,e,t)=>(zr(n,e,"read from private field"),t?t.call(n):e.get(n)),Er=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Lr=(n,e,t,i)=>(zr(n,e,"write to private field"),e.set(n,t),t),pt,ft;class ho extends Ni{constructor(e,{color:t=new f(1),intensity:i=1,position:s=new f(1),target:r=new f,shadow:o=null}={}){super(e,{color:t,intensity:i,type:"directionalLights"}),Er(this,pt,void 0),Er(this,ft,void 0),this.options={...this.options,position:s,target:r,shadow:o},Lr(this,ft,new f),Lr(this,pt,new f),this.target=r,this.target.onChange(()=>this.setDirection()),this.position.copy(s),this.parent=this.renderer.scene,this.shadow=new ao(this.renderer,{autoRender:!1,light:this}),o&&this.shadow.cast(o)}setRenderer(e){this.shadow?.setRenderer(e),super.setRenderer(e)}reset(){super.reset(),this.setDirection(),this.shadow?.reset()}setDirection(){si(this,ft).copy(this.target).sub(this.worldMatrix.getTranslation(si(this,pt))),this.onPropertyChanged("direction",si(this,ft)),this.shadow?.updateViewMatrix(si(this,pt),this.target)}applyScale(){}applyTransformOrigin(){}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.setDirection()}onMaxLightOverflow(e){super.onMaxLightOverflow(e),this.shadow?.setRendererBinding()}destroy(){super.destroy(),this.shadow.destroy()}}pt=new WeakMap,ft=new WeakMap;var _r=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Gr=(n,e,t)=>(_r(n,e,"read from private field"),t?t.call(n):e.get(n)),uo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},lo=(n,e,t,i)=>(_r(n,e,"write to private field"),e.set(n,t),t),mt;const co={face:{type:"i32",value:0},...Pr,cameraNear:{type:"f32",value:0},cameraFar:{type:"f32",value:0},projectionMatrix:{type:"mat4x4f",value:new Float32Array(16)},viewMatrices:{type:"array",value:new Float32Array(16*6)}};class po extends Rr{constructor(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:u,camera:l={near:.1,far:150}}={}){super(e,{light:t,intensity:i,bias:s,normalBias:r,pcfSamples:o,depthTextureSize:a,depthTextureFormat:h,autoRender:u}),uo(this,mt,void 0),this.options={...this.options,camera:l},this.cubeDirections=[new f(-1,0,0),new f(1,0,0),new f(0,-1,0),new f(0,1,0),new f(0,0,-1),new f(0,0,1)],lo(this,mt,new f),this.cubeUps=[new f(0,-1,0),new f(0,-1,0),new f(0,0,1),new f(0,0,-1),new f(0,-1,0),new f(0,-1,0)],l.far<=0&&(l.far=150),this.camera={projectionMatrix:new D,viewMatrices:[],_near:l.near,_far:l.far};for(let d=0;d<6;d++)this.camera.viewMatrices.push(new D);const c=this;["near","far"].forEach(d=>{Object.defineProperty(c.camera,d,{get(){return c.camera["_"+d]},set(m){c.camera["_"+d]=m,c.updateProjectionMatrix()}})})}setRendererBinding(){this.rendererBinding=this.renderer.bindings.pointShadows}cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a,camera:h}={}){h&&(this.camera.near=h.near??.1,this.camera.far=h.far!==void 0?h.far:this.light.range>0?this.light.range:150),super.cast({intensity:e,bias:t,normalBias:i,pcfSamples:s,depthTextureSize:r,depthTextureFormat:o,autoRender:a})}init(){super.init(),this.updateProjectionMatrix()}reset(){this.setRendererBinding(),super.reset(),this.updateProjectionMatrix()}updateProjectionMatrix(){this.camera.projectionMatrix.identity().makePerspective({near:this.camera.near,far:this.camera.far,fov:90,aspect:1}),this.onPropertyChanged("projectionMatrix",this.camera.projectionMatrix),this.onPropertyChanged("cameraNear",this.camera.near),this.onPropertyChanged("cameraFar",this.camera.far)}updateViewMatrices(e=new f){for(let t=0;t<6;t++){Gr(this,mt).copy(this.cubeDirections[t]).add(e),this.camera.viewMatrices[t].makeView(e,Gr(this,mt),this.cubeUps[t]);for(let i=0;i<16;i++)this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.value[t*16+i]=this.camera.viewMatrices[t].elements[i]}this.rendererBinding.childrenBindings[this.index].inputs.viewMatrices.shouldUpdate=!0}setDepthTexture(){if(this.depthTexture&&(this.depthTexture.size.width!==this.depthTextureSize.x||this.depthTexture.size.height!==this.depthTextureSize.y)){const e=Math.max(this.depthTextureSize.x,this.depthTextureSize.y);this.depthTexture.options.fixedSize.width=e,this.depthTexture.options.fixedSize.height=e,this.depthTexture.size.width=e,this.depthTexture.size.height=e,this.depthTexture.createTexture(),this.depthPassTarget&&this.depthPassTarget.resize()}else this.depthTexture||this.createDepthTexture()}createDepthTexture(){const e=Math.max(this.depthTextureSize.x,this.depthTextureSize.y);this.depthTexture=new K(this.renderer,{label:`${this.constructor.name} (index: ${this.index}) depth texture`,name:"pointShadowCubeDepthTexture"+this.index,type:"depth",format:this.depthTextureFormat,viewDimension:"cube",sampleCount:this.sampleCount,fixedSize:{width:e,height:e},autoDestroy:!1})}removeDepthPass(e){this.renderer.onBeforeCommandEncoderCreation.remove(e)}render(e=!1){return this.renderer.onBeforeCommandEncoderCreation.add(()=>{if(this.meshes.size){this.renderer.setCameraBindGroup(),this.useDepthMaterials();for(let t=0;t<6;t++){const i=this.renderer.device.createCommandEncoder();this.renderer.production||i.pushDebugGroup(`${this.constructor.name} (index: ${this.index}): depth pass command encoder for face ${t}`),this.depthPassTarget.renderPass.setRenderPassDescriptor(this.depthTexture.texture.createView({label:this.depthTexture.texture.label+" cube face view "+t,dimension:"2d",arrayLayerCount:1,baseArrayLayer:t})),this.rendererBinding.childrenBindings[this.index].inputs.face.value=t,this.renderer.shouldUpdateCameraLightsBindGroup(),this.renderer.updateCameraLightsBindGroup(),this.renderDepthPass(i),this.renderer.production||i.popDebugGroup();const s=i.finish();this.renderer.device.queue.submit([s])}this.useOriginalMaterials(),this.renderer.pipelineManager.resetCurrentPipeline()}},{once:e,order:this.index})}getDefaultShadowDepthVs(e=!1){return{code:Br(this.index,e)}}getDefaultShadowDepthFs(){return{code:Mr(this.index)}}}mt=new WeakMap;var Ar=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Ji=(n,e,t)=>(Ar(n,e,"read from private field"),t?t.call(n):e.get(n)),Dr=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Or=(n,e,t,i)=>(Ar(n,e,"write to private field"),e.set(n,t),t),ri,gt;class fo extends Ni{constructor(e,{color:t=new f(1),intensity:i=1,position:s=new f,range:r=0,shadow:o=null}={}){super(e,{color:t,intensity:i,type:"pointLights"}),Dr(this,ri,void 0),Dr(this,gt,void 0),this.options={...this.options,position:s,range:r,shadow:o},Or(this,gt,new f),this.position.copy(s),this.range=r,this.parent=this.renderer.scene,this.shadow=new po(this.renderer,{autoRender:!1,light:this}),o&&this.shadow.cast(o)}setRenderer(e){this.shadow&&this.shadow.setRenderer(e),super.setRenderer(e)}reset(){super.reset(),this.onPropertyChanged("range",this.range),this.setPosition(),this.shadow?.reset()}get range(){return Ji(this,ri)}set range(e){Or(this,ri,e),this.onPropertyChanged("range",this.range)}setPosition(){this.onPropertyChanged("position",this.worldMatrix.getTranslation(Ji(this,gt))),this.shadow?.updateViewMatrices(Ji(this,gt))}applyScale(){}applyTransformOrigin(){}updateMatrixStack(){super.updateMatrixStack(),this.matricesNeedUpdate&&this.setPosition()}onMaxLightOverflow(e){super.onMaxLightOverflow(e),this.shadow?.setRendererBinding()}destroy(){super.destroy(),this.shadow.destroy()}}ri=new WeakMap,gt=new WeakMap;var Fr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Qi=(n,e,t)=>(Fr(n,e,"read from private field"),t?t.call(n):e.get(n)),mo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},go=(n,e,t,i)=>(Fr(n,e,"write to private field"),e.set(n,t),t);let yo=0;const $r={autoRender:!0,useProjection:!1,useAsyncPipeline:!0,cullMode:"back",depth:!0,depthWriteEnabled:!0,depthCompare:"less",depthFormat:"depth24plus",transparent:!1,visible:!0,renderOrder:0,texturesOptions:{},renderBundle:null};function Ur(n){var e,t;return t=class extends n{constructor(...i){super(i[0],i[1],{...$r,...i[2]}),mo(this,e,!0),this._onReadyCallback=()=>{},this._onBeforeRenderCallback=()=>{},this._onRenderCallback=()=>{},this._onAfterRenderCallback=()=>{},this._onAfterResizeCallback=()=>{};let s=i[0];const r={...$r,...i[2]};this.type="MeshBase",this.uuid=H(),Object.defineProperty(this,"index",{value:yo++}),s=V(s,r.label?r.label+" "+this.type:this.type),this.renderer=s;const{label:o,shaders:a,geometry:h,visible:u,renderOrder:l,outputTarget:c,renderBundle:p,texturesOptions:d,autoRender:m,...g}=r;this.outputTarget=c??null,this.renderBundle=p??null,g.sampleCount=g.sampleCount?g.sampleCount:this.outputTarget?this.outputTarget.renderPass.options.sampleCount:this.renderer&&this.renderer.renderPass?this.renderer.renderPass.options.sampleCount:1,this.options={...this.options??{},label:o??"Mesh "+this.renderer.meshes.length,...a!==void 0?{shaders:a}:{},...c!==void 0&&{outputTarget:c},...p!==void 0&&{renderBundle:p},texturesOptions:d,...m!==void 0&&{autoRender:m},...g},m!==void 0&&go(this,e,m),this.visible=u,this.renderOrder=l,this.ready=!1,this.userData={},h&&this.useGeometry(h),this.setMaterial({...this.cleanupRenderMaterialParameters({...this.options}),...h&&{verticesOrder:h.verticesOrder,topology:h.topology}}),this.addToScene(!0)}get autoRender(){return Qi(this,e)}get ready(){return this._ready}set ready(i){i&&!this._ready&&this._onReadyCallback&&this._onReadyCallback(),this._ready=i}addToScene(i=!1){i&&this.renderer.meshes.push(this),this.setRenderingOptionsForRenderPass(this.outputTarget?this.outputTarget.renderPass:this.renderer.renderPass),Qi(this,e)&&this.renderer.scene.addMesh(this)}removeFromScene(i=!1){Qi(this,e)&&this.renderer.scene.removeMesh(this),i&&(this.renderer.meshes=this.renderer.meshes.filter(s=>s.uuid!==this.uuid))}setRenderer(i){if(i=i&&i.renderer||i,!i||!(i.type==="GPURenderer"||i.type==="GPUCameraRenderer"||i.type==="GPUCurtainsRenderer")){L(`${this.options.label}: Cannot set ${i} as a renderer because it is not of a valid Renderer type.`);return}this.material?.setRenderer(i);const s=this.renderer;this.removeFromScene(!0),this.renderer=i,this.addToScene(!0),s.meshes.length||s.onBeforeRenderScene.add(r=>{s.forceClear(r)},{once:!0})}setOutputTarget(i){if(i&&i.type!=="RenderTarget"){L(`${this.options.label??this.type}: outputTarget is not a RenderTarget: ${i.type}`);return}this.removeFromScene(),this.outputTarget=i,this.addToScene()}setRenderBundle(i,s=!0){s?(this.removeFromScene(),this.renderBundle=i,this.addToScene()):this.renderBundle=i}loseContext(){this.ready=!1,this.geometry.loseContext(),this.material.loseContext()}restoreContext(){this.geometry.restoreContext(this.renderer),this.material.restoreContext()}setShaders(){const{shaders:i}=this.options;i?((!i.vertex||!i.vertex.code)&&(i.vertex={code:Yi,entryPoint:"main"}),(i.fragment===void 0||i.fragment&&!i.fragment.code)&&(i.fragment={code:Hi,entryPoint:"main"})):this.options.shaders={vertex:{code:Yi,entryPoint:"main"},fragment:{code:Hi,entryPoint:"main"}}}useGeometry(i){if(this.geometry&&(i.shouldCompute&&i.computeGeometry(),this.geometry.layoutCacheKey!==i.layoutCacheKey&&(L(`${this.options.label} (${this.type}): the current and new geometries do not have the same vertexBuffers layout, causing a probable pipeline recompilation. This should be avoided. Current geometry layout: @@ -304,7 +385,7 @@ ${this.geometry.wgslStructFragment} New geometry layout: -${i.wgslStructFragment}`),this.material.setAttributesFromGeometry(i),this.material.setPipelineEntry()),this.geometry.consumers.delete(this.uuid)),this.geometry=i,this.geometry.consumers.add(this.uuid),this.computeGeometry(),this.material){const s={...this.material.options.rendering,verticesOrder:i.verticesOrder,topology:i.topology};this.material.setRenderingOptions(s)}}computeGeometry(){this.geometry.shouldCompute&&this.geometry.computeGeometry()}setGeometry(){this.geometry&&(this.geometry.ready||this.geometry.createBuffers({renderer:this.renderer,label:this.options.label+" geometry"}),this.setMaterialGeometryAttributes())}setRenderingOptionsForRenderPass(i){const s={transparent:this.transparent,sampleCount:i.options.sampleCount,...i.options.colorAttachments.length&&{targets:i.options.colorAttachments.map((r,o)=>({format:r.targetFormat,...this.options.targets?.length&&this.options.targets[o]&&this.options.targets[o].blend&&{blend:this.options.targets[o].blend}}))},depth:i.options.useDepth,...i.options.useDepth&&{depthFormat:i.options.depthFormat}};this.material?.setRenderingOptions(s)}cleanupRenderMaterialParameters(i){return delete i.texturesOptions,delete i.outputTarget,delete i.autoRender,i}useMaterial(i){this.material=i,this.transparent=this.material.options.rendering.transparent,this.material.options.domTextures?.filter(s=>s instanceof Se).forEach(s=>this.onDOMTextureAdded(s))}setMaterial(i){this.setShaders(),i.shaders=this.options.shaders,i.label=i.label+" material",this.useMaterial(new yi(this.renderer,i))}setMaterialGeometryAttributes(){this.material&&!this.material.attributes&&this.material.setAttributesFromGeometry(this.geometry)}get transparent(){return this._transparent}set transparent(i){const s=this.transparent!==void 0&&i!==this.transparent;s&&this.removeFromScene(),this._transparent=i,s&&this.addToScene()}get visible(){return this._visible}set visible(i){this._visible=i}get domTextures(){return this.material?.domTextures||[]}get textures(){return this.material?.textures||[]}createDOMTexture(i){i.name||(i.name="texture"+(this.textures.length+this.domTextures.length)),i.label||(i.label=this.options.label+" "+i.name);const s=new Se(this.renderer,{...i,...this.options.texturesOptions});return this.addDOMTexture(s),s}addDOMTexture(i){this.material.addTexture(i),this.onDOMTextureAdded(i)}onDOMTextureAdded(i){i.parentMesh=this}createTexture(i){i.name||(i.name="texture"+(this.textures.length+this.domTextures.length));const s=new se(this.renderer,i);return this.addTexture(s),s}addTexture(i){this.material.addTexture(i)}get uniforms(){return this.material?.uniforms}get storages(){return this.material?.storages}resize(i){super.resize&&super.resize(i),this.textures?.forEach(s=>{s.options.fromTexture&&s.copy(s.options.fromTexture)}),this.domTextures?.forEach(s=>{s.resize()}),this._onAfterResizeCallback&&this._onAfterResizeCallback()}onReady(i){return i&&(this._onReadyCallback=i),this}onBeforeRender(i){return i&&(this._onBeforeRenderCallback=i),this}onRender(i){return i&&(this._onRenderCallback=i),this}onAfterRender(i){return i&&(this._onAfterRenderCallback=i),this}onAfterResize(i){return i&&(this._onAfterResizeCallback=i),this}onBeforeRenderScene(){!this.renderer.ready||!this.ready||!this.visible||this._onBeforeRenderCallback&&this._onBeforeRenderCallback()}onBeforeRenderPass(){this.renderer.ready&&(this.ready=this.material&&this.material.ready&&this.geometry&&this.geometry.ready,this.setGeometry(),this.material.onBeforeRender())}onRenderPass(i){this.ready&&(this._onRenderCallback&&this._onRenderCallback(),this.material.render(i),this.geometry.render(i))}onAfterRenderPass(){this._onAfterRenderCallback&&this._onAfterRenderCallback()}render(i){this.onBeforeRenderPass(),!(!this.renderer.ready||!this.visible)&&(super.render&&super.render(),!this.renderer.production&&i.pushDebugGroup(this.options.label),this.onRenderPass(i),!this.renderer.production&&i.popDebugGroup(),this.onAfterRenderPass())}remove(){this.removeFromScene(!0),this.destroy(),this.renderer.meshes.length||this.renderer.onBeforeRenderScene.add(i=>{this.renderer.forceClear(i)},{once:!0})}destroy(){super.destroy&&super.destroy(),this.material?.destroy(),this.geometry.consumers.delete(this.uuid),this.geometry.consumers.size||this.geometry?.destroy(this.renderer)}},e=new WeakMap,t}class gn{constructor(){this.planeGeometries=[]}getPlaneGeometry(e){return this.planeGeometries.find(t=>t.definition.id===e.definition.id)}getPlaneGeometryByID(e){return this.planeGeometries.find(t=>t.definition.id===e)}addPlaneGeometry(e){this.planeGeometries.push(e)}destroy(){this.planeGeometries=[]}}const At=new gn;class Ci extends Qs(class{}){constructor(e,t={}){e=j(e,t.label?t.label+" FullscreenQuadMesh":"FullscreenQuadMesh");let i=At.getPlaneGeometryByID(2);i||(i=new ci({widthSegments:1,heightSegments:1}),At.addPlaneGeometry(i)),(!t.shaders||!t.shaders.vertex)&&["uniforms","storages"].forEach(s=>{Object.values(t[s]??{}).forEach(r=>r.visibility=["fragment"])}),t.depthWriteEnabled=!1,t.label||(t.label="FullscreenQuadMesh"),super(e,null,{geometry:i,...t}),this.size={document:{width:this.renderer.boundingRect.width,height:this.renderer.boundingRect.height,top:this.renderer.boundingRect.top,left:this.renderer.boundingRect.left}},this.type="FullscreenQuadMesh"}resize(e=null){this.size.document=e??this.renderer.boundingRect,super.resize(e)}mouseToPlaneCoords(e=new E){return new E((e.x-this.size.document.left)/this.size.document.width*2-1,1-(e.y-this.size.document.top)/this.size.document.height*2)}}class ge{constructor(e=new Float32Array([1,0,0,0,1,0,0,0,1])){this.type="Mat3",this.elements=e}set(e,t,i,s,r,o,a,h,l){const u=this.elements;return u[0]=e,u[1]=s,u[2]=a,u[3]=t,u[4]=r,u[5]=h,u[6]=i,u[7]=o,u[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}setFromArray(e=new Float32Array([1,0,0,0,1,0,0,0,1])){for(let t=0;t{this.modelViewMatrix.multiplyMatrices(this.viewMatrix,this.worldMatrix)}},modelViewProjection:{matrix:new A,shouldUpdate:!0,onUpdate:()=>{this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix,this.modelViewMatrix)}},normal:{matrix:new ge,shouldUpdate:!0,onUpdate:()=>{this.normalMatrix.getNormalMatrix(this.worldMatrix)}}}}get modelViewMatrix(){return this.matrices.modelView.matrix}set modelViewMatrix(e){this.matrices.modelView.matrix=e,this.matrices.modelView.shouldUpdate=!0}get viewMatrix(){return this.camera.viewMatrix}get projectionMatrix(){return this.camera.projectionMatrix}get modelViewProjectionMatrix(){return this.matrices.modelViewProjection.matrix}set modelViewProjectionMatrix(e){this.matrices.modelViewProjection.matrix=e,this.matrices.modelViewProjection.shouldUpdate=!0}get normalMatrix(){return this.matrices.normal.matrix}set normalMatrix(e){this.matrices.normal.matrix=e,this.matrices.normal.shouldUpdate=!0}shouldUpdateProjectionMatrixStack(){this.matrices.modelView.shouldUpdate=!0,this.matrices.modelViewProjection.shouldUpdate=!0}shouldUpdateWorldMatrix(){super.shouldUpdateWorldMatrix(),this.shouldUpdateProjectionMatrixStack(),this.matrices.normal.shouldUpdate=!0}shouldUpdateMatrixStack(){this.shouldUpdateModelMatrix(),this.shouldUpdateProjectionMatrixStack()}}var Js=` +${i.wgslStructFragment}`),this.material.setAttributesFromGeometry(i),this.material.setPipelineEntry()),this.geometry.consumers.delete(this.uuid)),this.geometry=i,this.geometry.consumers.add(this.uuid),this.computeGeometry(),this.material){const s={...this.material.options.rendering,verticesOrder:i.verticesOrder,topology:i.topology};this.material.setRenderingOptions(s)}}computeGeometry(){this.geometry.shouldCompute&&this.geometry.computeGeometry()}setGeometry(){this.geometry&&(this.geometry.ready||this.geometry.createBuffers({renderer:this.renderer,label:this.options.label+" geometry"}),this.setMaterialGeometryAttributes())}setRenderingOptionsForRenderPass(i){const s={transparent:this.transparent,sampleCount:i.options.sampleCount,...i.options.colorAttachments.length&&{targets:i.options.colorAttachments.map((r,o)=>({format:r.targetFormat,...this.options.targets?.length&&this.options.targets[o]&&this.options.targets[o].blend&&{blend:this.options.targets[o].blend}}))},depth:i.options.useDepth,...i.options.useDepth&&{depthFormat:i.options.depthFormat}};this.material?.setRenderingOptions(s)}cleanupRenderMaterialParameters(i){return delete i.texturesOptions,delete i.outputTarget,delete i.autoRender,i}useMaterial(i){this.material=i,this.transparent=this.material.options.rendering.transparent,this.material.options.domTextures?.filter(s=>s instanceof Fe).forEach(s=>this.onDOMTextureAdded(s))}setMaterial(i){this.setShaders(),i.shaders=this.options.shaders,i.label=i.label+" material",this.useMaterial(new Xi(this.renderer,i))}setMaterialGeometryAttributes(){this.material&&!this.material.attributes&&this.material.setAttributesFromGeometry(this.geometry)}get transparent(){return this._transparent}set transparent(i){const s=this.transparent!==void 0&&i!==this.transparent;s&&this.removeFromScene(),this._transparent=i,s&&this.addToScene()}get visible(){return this._visible}set visible(i){this._visible=i}get domTextures(){return this.material?.domTextures||[]}get textures(){return this.material?.textures||[]}createDOMTexture(i){i.name||(i.name="texture"+(this.textures.length+this.domTextures.length)),i.label||(i.label=this.options.label+" "+i.name);const s={...i,...this.options.texturesOptions};this.renderBundle&&(s.useExternalTextures=!1);const r=new Fe(this.renderer,s);return this.addDOMTexture(r),r}addDOMTexture(i){this.renderBundle&&(this.renderBundle.ready=!1),this.material.addTexture(i),this.onDOMTextureAdded(i)}onDOMTextureAdded(i){i.parentMesh=this}createTexture(i){i.name||(i.name="texture"+(this.textures.length+this.domTextures.length));const s=new K(this.renderer,i);return this.addTexture(s),s}addTexture(i){this.renderBundle&&(this.renderBundle.ready=!1),this.material.addTexture(i)}get uniforms(){return this.material?.uniforms}get storages(){return this.material?.storages}resize(i){super.resize&&super.resize(i),this.textures?.forEach(s=>{s.options.fromTexture&&s.copy(s.options.fromTexture)}),this.domTextures?.forEach(s=>{s.resize()}),this._onAfterResizeCallback&&this._onAfterResizeCallback()}onReady(i){return i&&(this._onReadyCallback=i),this}onBeforeRender(i){return i&&(this._onBeforeRenderCallback=i),this}onRender(i){return i&&(this._onRenderCallback=i),this}onAfterRender(i){return i&&(this._onAfterRenderCallback=i),this}onAfterResize(i){return i&&(this._onAfterResizeCallback=i),this}onBeforeRenderScene(){!this.renderer.ready||!this.ready||!this.visible||this._onBeforeRenderCallback&&this._onBeforeRenderCallback()}onBeforeRenderPass(){this.renderer.ready&&(this.setGeometry(),this.visible&&this._onRenderCallback&&this._onRenderCallback(),this.material.onBeforeRender(),this.ready=this.material&&this.material.ready&&this.geometry&&this.geometry.ready)}onRenderPass(i){this.ready&&(this.material.render(i),this.geometry.render(i))}onAfterRenderPass(){this._onAfterRenderCallback&&this._onAfterRenderCallback()}render(i){this.onBeforeRenderPass(),!(!this.renderer.ready||!this.visible)&&(!this.renderer.production&&i.pushDebugGroup(this.options.label),this.onRenderPass(i),!this.renderer.production&&i.popDebugGroup(),this.onAfterRenderPass())}remove(){this.removeFromScene(!0),this.destroy(),this.renderer.meshes.length||this.renderer.onBeforeRenderScene.add(i=>{this.renderer.forceClear(i)},{once:!0})}destroy(){super.destroy&&super.destroy(),this.material?.destroy(),this.geometry.consumers.delete(this.uuid),this.geometry.consumers.size||this.geometry?.destroy(this.renderer)}},e=new WeakMap,t}class xo{constructor(){this.planeGeometries=[]}getPlaneGeometry(e){return this.planeGeometries.find(t=>t.definition.id===e.definition.id)}getPlaneGeometryByID(e){return this.planeGeometries.find(t=>t.definition.id===e)}addPlaneGeometry(e){this.planeGeometries.push(e)}destroy(){this.planeGeometries=[]}}const ni=new xo;class es extends Ur(class{}){constructor(e,t={}){e=V(e,t.label?t.label+" FullscreenQuadMesh":"FullscreenQuadMesh");let i=ni.getPlaneGeometryByID(2);i||(i=new Vi({widthSegments:1,heightSegments:1}),ni.addPlaneGeometry(i)),(!t.shaders||!t.shaders.vertex)&&["uniforms","storages"].forEach(s=>{Object.values(t[s]??{}).forEach(r=>r.visibility=["fragment"])}),t.depthWriteEnabled=!1,t.label||(t.label="FullscreenQuadMesh"),super(e,null,{geometry:i,...t}),this.size={document:{width:this.renderer.boundingRect.width,height:this.renderer.boundingRect.height,top:this.renderer.boundingRect.top,left:this.renderer.boundingRect.left}},this.type="FullscreenQuadMesh"}resize(e=null){this.size.document=e??this.renderer.boundingRect,super.resize(e)}mouseToPlaneCoords(e=new _){return new _((e.x-this.size.document.left)/this.size.document.width*2-1,1-(e.y-this.size.document.top)/this.size.document.height*2)}}class Be{constructor(e=new Float32Array([1,0,0,0,1,0,0,0,1])){this.type="Mat3",this.elements=e}set(e,t,i,s,r,o,a,h,u){const l=this.elements;return l[0]=e,l[1]=s,l[2]=a,l[3]=t,l[4]=r,l[5]=h,l[6]=i,l[7]=o,l[8]=u,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}setFromArray(e=new Float32Array([1,0,0,0,1,0,0,0,1])){for(let t=0;t{this.modelViewMatrix.multiplyMatrices(this.viewMatrix,this.worldMatrix)}},modelViewProjection:{matrix:new D,shouldUpdate:!0,onUpdate:()=>{this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix,this.modelViewMatrix)}},normal:{matrix:new Be,shouldUpdate:!0,onUpdate:()=>{this.normalMatrix.getNormalMatrix(this.worldMatrix)}}}}get modelViewMatrix(){return this.matrices.modelView.matrix}set modelViewMatrix(e){this.matrices.modelView.matrix=e,this.matrices.modelView.shouldUpdate=!0}get viewMatrix(){return this.camera.viewMatrix}get projectionMatrix(){return this.camera.projectionMatrix}get modelViewProjectionMatrix(){return this.matrices.modelViewProjection.matrix}set modelViewProjectionMatrix(e){this.matrices.modelViewProjection.matrix=e,this.matrices.modelViewProjection.shouldUpdate=!0}get normalMatrix(){return this.matrices.normal.matrix}set normalMatrix(e){this.matrices.normal.matrix=e,this.matrices.normal.shouldUpdate=!0}shouldUpdateProjectionMatrixStack(){this.matrices.modelView.shouldUpdate=!0,this.matrices.modelViewProjection.shouldUpdate=!0}shouldUpdateWorldMatrix(){super.shouldUpdateWorldMatrix(),this.shouldUpdateProjectionMatrixStack(),this.matrices.normal.shouldUpdate=!0}shouldUpdateMatrixStack(){this.shouldUpdateModelMatrix(),this.shouldUpdateProjectionMatrixStack()}}var kr=` struct VSOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f, @@ -314,65 +395,11 @@ struct VSOutput { @fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { // normals return vec4(normalize(fsInput.normal) * 0.5 + 0.5, 1.0); -}`;const er={frustumCulling:"OBB",DOMFrustumMargins:{top:0,right:0,bottom:0,left:0},receiveShadows:!1,castShadows:!1};function tr(n){return class extends Qs(n){constructor(...t){super(t[0],t[1],{...er,...t[2],useProjection:!0}),this._onReEnterViewCallback=()=>{},this._onLeaveViewCallback=()=>{};let i=t[0];const s={...er,...t[2],useProjection:!0};this.type="MeshTransformed",i=Le(i,s.label?s.label+" "+this.type:this.type),this.renderer=i;const{frustumCulling:r,DOMFrustumMargins:o,receiveShadows:a,castShadows:h}=s;this.options={...this.options??{},frustumCulling:r,DOMFrustumMargins:o,receiveShadows:a,castShadows:h},this.options.castShadows&&this.renderer.shadowCastingLights.forEach(l=>{l.shadow.isActive&&l.shadow.addShadowCastingMesh(this)}),this.setDOMFrustum()}setShaders(){const{shaders:t}=this.options;t?((!t.vertex||!t.vertex.code)&&(t.vertex={code:mi,entryPoint:"main"}),(t.fragment===void 0||t.fragment&&!t.fragment.code)&&(t.fragment={code:Js,entryPoint:"main"})):this.options.shaders={vertex:{code:mi,entryPoint:"main"},fragment:{code:Js,entryPoint:"main"}}}useGeometry(t){super.useGeometry(t),this.domFrustum&&(this.domFrustum.boundingBox=this.geometry.boundingBox),this.shouldUpdateMatrixStack()}setDOMFrustum(){this.domFrustum=new Bs({boundingBox:this.geometry?.boundingBox,modelViewProjectionMatrix:this.modelViewProjectionMatrix,containerBoundingRect:this.renderer.boundingRect,DOMFrustumMargins:this.options.DOMFrustumMargins,onReEnterView:()=>{this._onReEnterViewCallback&&this._onReEnterViewCallback()},onLeaveView:()=>{this._onLeaveViewCallback&&this._onLeaveViewCallback()}}),this.DOMFrustumMargins=this.domFrustum.DOMFrustumMargins,this.frustumCulling=this.options.frustumCulling}cleanupRenderMaterialParameters(t){if(delete t.frustumCulling,delete t.DOMFrustumMargins,this.options.receiveShadows){const i=[];let s=[];this.renderer.shadowCastingLights.forEach(o=>{o.shadow.isActive&&(i.push(o.shadow.depthTexture),s.push(o.shadow.depthComparisonSampler))}),this.renderer.shadowCastingLights.find(o=>o.shadow.isActive)&&t.shaders.fragment&&typeof t.shaders.fragment=="object"&&(t.shaders.fragment.code=Gs(this.renderer)+As+Us(this.renderer)+Fs+t.shaders.fragment.code),s=s.filter((o,a,h)=>h.findIndex(l=>l.uuid===o.uuid)===a),t.textures?t.textures=[...t.textures,...i]:t.textures=i,t.samplers?t.samplers=[...t.samplers,...s]:t.samplers=s}return super.cleanupRenderMaterialParameters(t)}setMaterial(t){const i={label:"Matrices",visibility:["vertex"],struct:{model:{type:"mat4x4f",value:this.worldMatrix},modelView:{type:"mat4x4f",value:this.modelViewMatrix},normal:{type:"mat3x3f",value:this.normalMatrix}}};t.uniforms||(t.uniforms={}),t.uniforms={matrices:i,...t.uniforms},super.setMaterial(t)}get visible(){return this._visible}set visible(t){this.shouldUpdateMatrixStack(),this._visible=t}resize(t){this.domFrustum&&this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect),super.resize(t)}applyScale(){super.applyScale();for(const t of this.domTextures)t.resize()}get projectedBoundingRect(){return this.domFrustum?.projectedBoundingRect}onReEnterView(t){return t&&(this._onReEnterViewCallback=t),this}onLeaveView(t){return t&&(this._onLeaveViewCallback=t),this}get clipSpaceBoundingSphere(){const{center:t,radius:i,min:s,max:r}=this.geometry.boundingBox,o=this.worldMatrix.getTranslation(),a=i*this.worldMatrix.getMaxScaleOnAxis(),h=t.clone().add(o);h.z+=s.z;const l=t.clone().add(o);l.z+=r.z;const u=h.clone();u.y+=a;const d=l.clone();d.y+=a,h.applyMat4(this.camera.viewProjectionMatrix),l.applyMat4(this.camera.viewProjectionMatrix),u.applyMat4(this.camera.viewProjectionMatrix),d.applyMat4(this.camera.viewProjectionMatrix);const c=h.distance(u),p=l.distance(d),g={xMin:h.x-c,xMax:h.x+c,yMin:h.y-c,yMax:h.y+c},m={xMin:l.x-p,xMax:l.x+p,yMin:l.y-p,yMax:l.y+p},x={xMin:Math.min(g.xMin,m.xMin),yMin:Math.min(g.yMin,m.yMin),xMax:Math.max(g.xMax,m.xMax),yMax:Math.max(g.yMax,m.yMax)},v=l.add(h).multiplyScalar(.5).clone();v.x=(x.xMax+x.xMin)/2,v.y=(x.yMax+x.yMin)/2;const C=Math.max(x.xMax-x.xMin,x.yMax-x.yMin);return{center:v,radius:C}}checkFrustumCulling(){this.matricesNeedUpdate&&this.domFrustum&&this.frustumCulling&&(this.frustumCulling==="sphere"?this.domFrustum.setDocumentCoordsFromClipSpaceSphere(this.clipSpaceBoundingSphere):this.domFrustum.setDocumentCoordsFromClipSpaceOBB(),this.domFrustum.intersectsContainer())}onBeforeRenderPass(){this.material&&this.matricesNeedUpdate&&this.material.shouldUpdateInputsBindings("matrices"),super.onBeforeRenderPass()}onRenderPass(t){this.ready&&(this._onRenderCallback&&this._onRenderCallback(),(this.domFrustum&&this.domFrustum.isIntersecting||!this.frustumCulling)&&(this.material.render(t),this.geometry.render(t)))}destroy(){this.options.castShadows&&this.renderer.shadowCastingLights.forEach(t=>{t.shadow.isActive&&t.shadow.removeMesh(this)}),super.destroy()}}}class ir extends tr(Bi){constructor(e,t={}){e=Le(e,t.label?t.label+" Mesh":"Mesh"),super(e,null,t),this.type="Mesh"}}let xn=0;class Pi{constructor(e){this.type="PipelineEntry";let{renderer:t}=e;const{label:i,shaders:s,useAsync:r}=e;t=j(t,i?i+" "+this.type:this.type),this.renderer=t,Object.defineProperty(this,"index",{value:xn++}),this.layout=null,this.pipeline=null,this.status={compiling:!1,compiled:!1,error:null},this.options={label:i,shaders:s,useAsync:r!==void 0?r:!0}}get ready(){return!this.status.compiling&&this.status.compiled&&!this.status.error}get canCompile(){return!this.status.compiling&&!this.status.compiled&&!this.status.error}setPipelineEntryBindGroups(e){this.bindGroups=e}createShaderModule({code:e="",type:t="vertex"}){const i=this.renderer.createShaderModule({label:this.options.label+": "+t+" shader module",code:e});return"getCompilationInfo"in i&&!this.renderer.production&&i.getCompilationInfo().then(s=>{for(const r of s.messages){let o="";switch(r.lineNum&&(o+=`Line ${r.lineNum}:${r.linePos} - ${e.substring(r.offset,r.offset+r.length)} -`),o+=r.message,r.type){case"error":console.error(`${this.options.label} compilation error: -${o}`);break;case"warning":console.warn(`${this.options.label} compilation warning: -${o}`);break;case"info":console.log(`${this.options.label} compilation information: -${o}`);break}}}),i}createShaders(){}createPipelineLayout(){this.layout=this.renderer.createPipelineLayout({label:this.options.label+" layout",bindGroupLayouts:this.bindGroups.map(e=>e.bindGroupLayout)})}createPipelineDescriptor(){}flushPipelineEntry(e=[]){this.status.compiling=!1,this.status.compiled=!1,this.status.error=null,this.setPipelineEntryBindGroups(e),this.compilePipelineEntry()}compilePipelineEntry(){this.status.compiling=!0,this.createShaders(),this.createPipelineLayout(),this.createPipelineDescriptor()}}var yn=` -fn getWorldPosition(position: vec3f) -> vec4f { - return matrices.model * vec4f(position, 1.0); -} - -fn getOutputPosition(position: vec3f) -> vec4f { - return camera.projection * matrices.modelView * vec4f(position, 1.0); -}`,vn=` -fn getWorldNormal(normal: vec3f) -> vec3f { - return normalize(matrices.normal * normal); -} - -fn getViewNormal(normal: vec3f) -> vec3f { - return normalize((camera.view * vec4(matrices.normal * normal, 0.0)).xyz); -}`,sr=` -fn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f { - return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy; -}`,wn=` -fn getVertex2DToUVCoords(vertex: vec2f) -> vec2f { - return vec2( - vertex.x * 0.5 + 0.5, - 0.5 - vertex.y * 0.5 - ); -} - -fn getVertex3DToUVCoords(vertex: vec3f) -> vec2f { - return getVertex2DToUVCoords( vec2(vertex.x, vertex.y) ); -} -`;const Te={vertex:{get_uv_cover:sr},fragment:{get_uv_cover:sr,get_vertex_to_uv_coords:wn}},Re={vertex:{get_output_position:yn,get_normals:vn},fragment:{}};class Si extends Pi{constructor(e){let{renderer:t,...i}=e;const{label:s,attributes:r,bindGroups:o,cacheKey:a,...h}=i,l="RenderPipelineEntry";j(t,s?s+" "+l:l),super(e),this.type=l,this.shaders={vertex:{head:"",code:"",module:null},fragment:{head:"",code:"",module:null},full:{head:"",code:"",module:null}},this.descriptor=null,this.options={...this.options,attributes:r,bindGroups:o,cacheKey:a,...h},this.setPipelineEntryProperties({attributes:r,bindGroups:o})}setPipelineEntryProperties(e){const{attributes:t,bindGroups:i}=e;this.attributes=t,this.setPipelineEntryBindGroups(i)}patchShaders(){this.shaders.vertex.head="",this.shaders.vertex.code="",this.shaders.fragment.head="",this.shaders.fragment.code="",this.shaders.full.head="",this.shaders.full.code="";for(const t in Te.vertex)this.shaders.vertex.head=`${Te.vertex[t]} -${this.shaders.vertex.head}`,this.shaders.full.head=`${Te.vertex[t]} -${this.shaders.full.head}`;if(this.options.shaders.fragment)for(const t in Te.fragment)this.shaders.fragment.head=`${Te.fragment[t]} -${this.shaders.fragment.head}`,this.shaders.full.head.indexOf(Te.fragment[t])===-1&&(this.shaders.full.head=`${Te.fragment[t]} -${this.shaders.full.head}`);if(this.options.rendering.useProjection){for(const t in Re.vertex)this.shaders.vertex.head=`${Re.vertex[t]} -${this.shaders.vertex.head}`,this.shaders.full.head=`${Re.vertex[t]} -${this.shaders.full.head}`;if(this.options.shaders.fragment)for(const t in Re.fragment)this.shaders.fragment.head=`${Re.fragment[t]} -${this.shaders.fragment.head}`,this.shaders.full.head.indexOf(Re.fragment[t])===-1&&(this.shaders.full.head=`${Re.fragment[t]} -${this.shaders.full.head}`)}const e=[];for(const t of this.bindGroups){let i=0;t.bindings.forEach((s,r)=>{s.wgslGroupFragment.forEach((o,a)=>{e.push({groupIndex:t.index,visibility:s.options.visibility,bindIndex:i,wgslStructFragment:s.wgslStructFragment,wgslGroupFragment:o,newLine:r===t.bindings.length-1&&a===s.wgslGroupFragment.length-1}),i++})})}for(const t of e)t.visibility.includes("vertex")&&(t.wgslStructFragment&&this.shaders.vertex.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.vertex.head=` -${t.wgslStructFragment} -${this.shaders.vertex.head}`),this.shaders.vertex.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.vertex.head=`${this.shaders.vertex.head} -@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.vertex.head+=` -`))),this.options.shaders.fragment&&t.visibility.includes("fragment")&&(t.wgslStructFragment&&this.shaders.fragment.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.fragment.head=` -${t.wgslStructFragment} -${this.shaders.fragment.head}`),this.shaders.fragment.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.fragment.head=`${this.shaders.fragment.head} -@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.fragment.head+=` -`))),t.wgslStructFragment&&this.shaders.full.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.full.head=` -${t.wgslStructFragment} -${this.shaders.full.head}`),this.shaders.full.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.full.head=`${this.shaders.full.head} -@group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`,t.newLine&&(this.shaders.full.head+=` -`));this.shaders.vertex.head=`${this.attributes.wgslStructFragment} -${this.shaders.vertex.head}`,this.shaders.full.head=`${this.attributes.wgslStructFragment} -${this.shaders.full.head}`,this.shaders.vertex.code=this.shaders.vertex.head+this.options.shaders.vertex.code,typeof this.options.shaders.fragment=="object"&&(this.shaders.fragment.code=this.shaders.fragment.head+this.options.shaders.fragment.code),typeof this.options.shaders.fragment=="object"&&(this.options.shaders.vertex.entryPoint!==this.options.shaders.fragment.entryPoint&&this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code)===0?this.shaders.full.code=this.shaders.full.head+this.options.shaders.vertex.code:this.shaders.full.code=this.shaders.full.head+this.options.shaders.vertex.code+this.options.shaders.fragment.code)}get shadersModulesReady(){return!(!this.shaders.vertex.module||this.options.shaders.fragment&&!this.shaders.fragment.module)}createShaders(){this.patchShaders();const e=typeof this.options.shaders.fragment=="object"&&this.options.shaders.vertex.entryPoint!==this.options.shaders.fragment.entryPoint&&this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code)===0;this.shaders.vertex.module=this.createShaderModule({code:this.shaders[e?"full":"vertex"].code,type:"vertex"}),this.options.shaders.fragment&&(this.shaders.fragment.module=this.createShaderModule({code:this.shaders[e?"full":"fragment"].code,type:"fragment"}))}createPipelineDescriptor(){if(!this.shadersModulesReady)return;let e=-1;this.options.rendering.targets.length?this.options.rendering.transparent&&(this.options.rendering.targets[0].blend=this.options.rendering.targets[0].blend?this.options.rendering.targets[0].blend:{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}}):this.options.rendering.targets=[],this.descriptor={label:this.options.label,layout:this.layout,vertex:{module:this.shaders.vertex.module,entryPoint:this.options.shaders.vertex.entryPoint,buffers:this.attributes.vertexBuffers.map(t=>({stepMode:t.stepMode,arrayStride:t.arrayStride*4,attributes:t.attributes.map(i=>(e++,{shaderLocation:e,offset:i.bufferOffset,format:i.bufferFormat}))}))},...this.options.shaders.fragment&&{fragment:{module:this.shaders.fragment.module,entryPoint:this.options.shaders.fragment.entryPoint,targets:this.options.rendering.targets}},primitive:{topology:this.options.rendering.topology,frontFace:this.options.rendering.verticesOrder,cullMode:this.options.rendering.cullMode},...this.options.rendering.depth&&{depthStencil:{depthWriteEnabled:this.options.rendering.depthWriteEnabled,depthCompare:this.options.rendering.depthCompare,format:this.options.rendering.depthFormat}},...this.options.rendering.sampleCount>1&&{multisample:{count:this.options.rendering.sampleCount}}}}createRenderPipeline(){if(this.shadersModulesReady)try{this.pipeline=this.renderer.createRenderPipeline(this.descriptor)}catch(e){this.status.error=e,te(e)}}async createRenderPipelineAsync(){if(this.shadersModulesReady)try{this.pipeline=await this.renderer.createRenderPipelineAsync(this.descriptor),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null}catch(e){this.status.error=e,te(e)}}async compilePipelineEntry(){super.compilePipelineEntry(),this.options.useAsync?await this.createRenderPipelineAsync():(this.createRenderPipeline(),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null)}}class rr extends Pi{constructor(e){const{renderer:t}=e,{label:i}=e,s="ComputePipelineEntry";j(t,i?i+" "+s:s),super(e),this.type=s,this.shaders={compute:{head:"",code:"",module:null}},this.descriptor=null}setPipelineEntryProperties(e){const{bindGroups:t}=e;this.setPipelineEntryBindGroups(t)}patchShaders(){this.shaders.compute.head="",this.shaders.compute.code="";const e=[];for(const t of this.bindGroups){let i=0;t.bindings.forEach((s,r)=>{s.wgslGroupFragment.forEach((o,a)=>{e.push({groupIndex:t.index,bindIndex:i,wgslStructFragment:s.wgslStructFragment,wgslGroupFragment:o,newLine:r===t.bindings.length-1&&a===s.wgslGroupFragment.length-1}),i++})})}for(const t of e)t.wgslStructFragment&&this.shaders.compute.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.compute.head=` +}`;const Ir={frustumCulling:"OBB",DOMFrustumMargins:{top:0,right:0,bottom:0,left:0},receiveShadows:!1,castShadows:!1};function Vr(n){return class extends Ur(n){constructor(...t){super(t[0],t[1],{...Ir,...t[2],useProjection:!0}),this._onReEnterViewCallback=()=>{},this._onLeaveViewCallback=()=>{};let i=t[0];const s={...Ir,...t[2],useProjection:!0};this.type="MeshTransformed",i=Ae(i,s.label?s.label+" "+this.type:this.type),this.renderer=i;const{frustumCulling:r,DOMFrustumMargins:o,receiveShadows:a,castShadows:h}=s;this.options={...this.options??{},frustumCulling:r,DOMFrustumMargins:o,receiveShadows:a,castShadows:h},this.options.castShadows&&this.renderer.shadowCastingLights.forEach(u=>{u.shadow.isActive&&u.shadow.addShadowCastingMesh(this)}),this.setDOMFrustum()}setRenderer(t){super.setRenderer(t),this.camera=this.renderer.camera,this.options.castShadows&&this.renderer.shadowCastingLights.forEach(i=>{i.shadow.isActive&&i.shadow.addShadowCastingMesh(this)})}setRenderBundle(t,i=!0){if(this.renderBundle&&t&&this.renderBundle.uuid===t.uuid)return;const s=!!this.renderBundle,r=this.material.getBindGroupByBindingName("matrices"),o=this.material.getBufferBindingByName("matrices");this.renderBundle&&!t&&o.parent&&(o.parent=null,o.shouldResetBindGroup=!0,r.createBindingBuffer(o)),super.setRenderBundle(t,i),this.renderBundle&&this.renderBundle.binding&&(s&&r.destroyBufferBinding(o),o.options.offset=this.renderBundle.meshes.size-1,o.parent=this.renderBundle.binding,o.shouldResetBindGroup=!0)}patchRenderBundleBinding(t=0){const i=this.material.getBufferBindingByName("matrices");i.options.offset=t,i.parent=this.renderBundle.binding,i.shouldResetBindGroup=!0}setShaders(){const{shaders:t}=this.options;return t?((!t.vertex||!t.vertex.code)&&(t.vertex={code:qi,entryPoint:"main"}),(t.fragment===void 0||t.fragment&&!t.fragment.code)&&(t.fragment={code:kr,entryPoint:"main"})):this.options.shaders={vertex:{code:qi,entryPoint:"main"},fragment:{code:kr,entryPoint:"main"}},this.options.receiveShadows&&this.renderer.shadowCastingLights.find(s=>s.shadow.isActive)&&t.fragment&&typeof t.fragment=="object"&&(t.fragment.code=wr(this.renderer)+br+Sr(this.renderer)+Cr+t.fragment.code),t}useGeometry(t){super.useGeometry(t),this.domFrustum&&(this.domFrustum.boundingBox=this.geometry.boundingBox),this.shouldUpdateMatrixStack()}setDOMFrustum(){this.domFrustum=new lr({boundingBox:this.geometry?.boundingBox,modelViewProjectionMatrix:this.modelViewProjectionMatrix,containerBoundingRect:this.renderer.boundingRect,DOMFrustumMargins:this.options.DOMFrustumMargins,onReEnterView:()=>{this._onReEnterViewCallback&&this._onReEnterViewCallback()},onLeaveView:()=>{this._onLeaveViewCallback&&this._onLeaveViewCallback()}}),this.DOMFrustumMargins=this.domFrustum.DOMFrustumMargins,this.frustumCulling=this.options.frustumCulling}cleanupRenderMaterialParameters(t){if(delete t.frustumCulling,delete t.DOMFrustumMargins,this.options.receiveShadows){const i=[];let s=[];this.renderer.shadowCastingLights.forEach(r=>{r.shadow.isActive&&(i.push(r.shadow.depthTexture),s.push(r.shadow.depthComparisonSampler))}),s=s.filter((r,o,a)=>a.findIndex(h=>h.uuid===r.uuid)===o),t.textures?t.textures=[...t.textures,...i]:t.textures=i,t.samplers?t.samplers=[...t.samplers,...s]:t.samplers=s}return super.cleanupRenderMaterialParameters(t)}setMaterial(t){const i={label:"Matrices",name:"matrices",visibility:["vertex"],minOffset:this.renderer.device.limits.minUniformBufferOffsetAlignment,struct:{model:{type:"mat4x4f",value:this.worldMatrix},modelView:{type:"mat4x4f",value:this.modelViewMatrix},normal:{type:"mat3x3f",value:this.normalMatrix}}};this.options.renderBundle&&this.options.renderBundle.binding&&(i.parent=this.options.renderBundle.binding,i.offset=this.options.renderBundle.meshes.size);const s=new pe(i);t.bindings||(t.bindings=[]),t.bindings.unshift(s),super.setMaterial(t)}get visible(){return this._visible}set visible(t){this.shouldUpdateMatrixStack(),this._visible=t}resize(t){this.domFrustum&&this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect),super.resize(t)}applyScale(){super.applyScale();for(const t of this.domTextures)t.resize()}get projectedBoundingRect(){return this.domFrustum?.projectedBoundingRect}onReEnterView(t){return t&&(this._onReEnterViewCallback=t),this}onLeaveView(t){return t&&(this._onLeaveViewCallback=t),this}get clipSpaceBoundingSphere(){const{center:t,radius:i,min:s,max:r}=this.geometry.boundingBox,o=this.worldMatrix.getTranslation(),a=i*this.worldMatrix.getMaxScaleOnAxis(),h=t.clone().add(o);h.z+=s.z;const u=t.clone().add(o);u.z+=r.z;const l=h.clone();l.y+=a;const c=u.clone();c.y+=a,h.applyMat4(this.camera.viewProjectionMatrix),u.applyMat4(this.camera.viewProjectionMatrix),l.applyMat4(this.camera.viewProjectionMatrix),c.applyMat4(this.camera.viewProjectionMatrix);const p=h.distance(l),d=u.distance(c),m={xMin:h.x-p,xMax:h.x+p,yMin:h.y-p,yMax:h.y+p},g={xMin:u.x-d,xMax:u.x+d,yMin:u.y-d,yMax:u.y+d},y={xMin:Math.min(m.xMin,g.xMin),yMin:Math.min(m.yMin,g.yMin),xMax:Math.max(m.xMax,g.xMax),yMax:Math.max(m.yMax,g.yMax)},v=u.add(h).multiplyScalar(.5).clone();v.x=(y.xMax+y.xMin)/2,v.y=(y.yMax+y.yMin)/2;const M=Math.max(y.xMax-y.xMin,y.yMax-y.yMin)*.5;return{center:v,radius:M}}checkFrustumCulling(){this.matricesNeedUpdate&&this.domFrustum&&this.frustumCulling&&(this.frustumCulling==="sphere"?this.domFrustum.setDocumentCoordsFromClipSpaceSphere(this.clipSpaceBoundingSphere):this.domFrustum.setDocumentCoordsFromClipSpaceOBB(),this.domFrustum.intersectsContainer())}onBeforeRenderPass(){this.material&&this.matricesNeedUpdate&&this.material.shouldUpdateInputsBindings("matrices"),super.onBeforeRenderPass()}onRenderPass(t){this.ready&&(this._onRenderCallback&&this._onRenderCallback(),(this.domFrustum&&this.domFrustum.isIntersecting||!this.frustumCulling)&&(this.material.render(t),this.geometry.render(t)))}destroy(){this.options.castShadows&&this.renderer.shadowCastingLights.forEach(t=>{t.shadow.isActive&&t.shadow.removeMesh(this)}),super.destroy()}}}class Nr extends Vr(ts){constructor(e,t={}){e=Ae(e,t.label?t.label+" Mesh":"Mesh"),super(e,null,t),this.type="Mesh"}}class is extends ji{constructor(e){const{label:t,renderer:i,bindGroups:s}=e,r="ComputePipelineEntry";V(i,t?t+" "+r:r),super(e),this.type=r,this.shaders={compute:{head:"",code:"",module:null}},this.descriptor=null,this.setPipelineEntryProperties({bindGroups:s})}patchShaders(){this.shaders.compute.head="",this.shaders.compute.code="";const e=[];for(const t of this.bindGroups){let i=0;t.bindings.forEach((s,r)=>{s.wgslGroupFragment.forEach((o,a)=>{e.push({groupIndex:t.index,bindIndex:i,wgslStructFragment:s.wgslStructFragment,wgslGroupFragment:o,newLine:r===t.bindings.length-1&&a===s.wgslGroupFragment.length-1}),i++})})}for(const t of e)t.wgslStructFragment&&this.shaders.compute.head.indexOf(t.wgslStructFragment)===-1&&(this.shaders.compute.head=` ${t.wgslStructFragment} ${this.shaders.compute.head}`),this.shaders.compute.head.indexOf(t.wgslGroupFragment)===-1&&(this.shaders.compute.head=`${this.shaders.compute.head} @group(${t.groupIndex}) @binding(${t.bindIndex}) ${t.wgslGroupFragment}`),t.newLine&&(this.shaders.compute.head+=` -`);this.shaders.compute.code=this.shaders.compute.head+this.options.shaders.compute.code}createShaders(){this.patchShaders(),this.shaders.compute.module=this.createShaderModule({code:this.shaders.compute.code,type:"compute"})}createPipelineDescriptor(){this.shaders.compute.module&&(this.descriptor={label:this.options.label,layout:this.layout,compute:{module:this.shaders.compute.module,entryPoint:this.options.shaders.compute.entryPoint}})}createComputePipeline(){if(this.shaders.compute.module)try{this.pipeline=this.renderer.createComputePipeline(this.descriptor)}catch(e){this.status.error=e,te(e)}}async createComputePipelineAsync(){if(this.shaders.compute.module)try{this.pipeline=await this.renderer.createComputePipelineAsync(this.descriptor),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null}catch(e){this.status.error=e,te(e)}}async compilePipelineEntry(){super.compilePipelineEntry(),this.options.useAsync?await this.createComputePipelineAsync():(this.createComputePipeline(),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null)}}class nr{constructor(){this.type="PipelineManager",this.currentPipelineIndex=null,this.pipelineEntries=[],this.activeBindGroups=[]}compareShaders(e,t){return e.code===t.code&&e.entryPoint===t.entryPoint}isSameRenderPipeline(e){return this.pipelineEntries.filter(t=>t instanceof Si).find(t=>{const{options:i}=t,{shaders:s,rendering:r,cacheKey:o}=e,a=o===i.cacheKey,h=this.compareShaders(s.vertex,i.shaders.vertex),l=!s.fragment&&!i.shaders.fragment||this.compareShaders(s.fragment,i.shaders.fragment),u=Es(r,i.rendering);return a&&!u.length&&h&&l})}createRenderPipeline(e){const{attributes:t,bindGroups:i}=e;let s=t.layoutCacheKey;i.forEach(o=>{o.bindings.forEach(a=>{s+=a.name+","}),s+=o.pipelineCacheKey});const r=this.isSameRenderPipeline({...e,cacheKey:s});if(r)return r;{const o=new Si({...e,cacheKey:s});return this.pipelineEntries.push(o),o}}createComputePipeline(e){const t=new rr(e);return this.pipelineEntries.push(t),t}setCurrentPipeline(e,t){t.index!==this.currentPipelineIndex&&(e.setPipeline(t.pipeline),this.currentPipelineIndex=t.index)}setActiveBindGroups(e,t){t.forEach((i,s)=>{(!this.activeBindGroups[s]||this.activeBindGroups[s].uuid!==i.uuid||this.activeBindGroups[s].index!==i.index)&&(this.activeBindGroups[s]=i,e.setBindGroup(i.index,i.bindGroup))})}resetCurrentPipeline(){this.currentPipelineIndex=null,this.activeBindGroups=[]}}class bn{constructor(){this.shouldWatch=!0,this.entries=[],typeof window=="object"&&"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(e=>{e.map(i=>this.entries.filter(s=>s.element.isSameNode(i.target))).flat().sort((i,s)=>s.priority-i.priority)?.forEach(i=>{i&&i.callback&&i.callback()})}))}useObserver(e=!0){this.shouldWatch=e}observe({element:e,priority:t,callback:i}){if(!e||!this.shouldWatch)return;this.resizeObserver?.observe(e);const s={element:e,priority:t,callback:i};this.entries.push(s)}unobserve(e){this.resizeObserver?.unobserve(e),this.entries=this.entries.filter(t=>!t.element.isSameNode(e))}destroy(){this.resizeObserver?.disconnect()}}const Ti=new bn;class Ri{constructor({element:e=document.body,priority:t=1,onSizeChanged:i=(r=null)=>{},onPositionChanged:s=(r=null)=>{}}={}){if(typeof e=="string"){if(this.element=document.querySelector(e),!this.element){const r=typeof e=="string"?`'${e}' selector`:`${e} HTMLElement`;te(`DOMElement: corresponding ${r} not found.`)}}else this.element=e;this.priority=t,this.isResizing=!1,this.onSizeChanged=i,this.onPositionChanged=s,this.resizeManager=Ti,this.resizeManager.observe({element:this.element,priority:this.priority,callback:()=>{this.setSize()}}),this.setSize()}compareBoundingRect(e,t){return!["x","y","left","top","right","bottom","width","height"].some(i=>e[i]!==t[i])}get boundingRect(){return this._boundingRect}set boundingRect(e){const t=!!this.boundingRect&&this.compareBoundingRect(e,this.boundingRect);this._boundingRect={top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.width,height:e.height,x:e.x,y:e.y},t||this.onSizeChanged(this.boundingRect)}updateScrollPosition(e={x:0,y:0}){this.isResizing||(this._boundingRect.top+=e.y,this._boundingRect.left+=e.x,(e.x||e.y)&&this.onPositionChanged(this.boundingRect))}setSize(e=null){!this.element||this.isResizing||(this.isResizing=!0,this.boundingRect=e??this.element.getBoundingClientRect(),setTimeout(()=>{this.isResizing=!1},10))}destroy(){this.resizeManager.unobserve(this.element)}}const Mn=new f,Cn=new f,zi=new f,Ei=new f;class or extends me{constructor({renderer:e}){super(),e=j(e,"Scene"),this.renderer=e,this.computePassEntries=[],this.renderPassEntries={pingPong:[],renderTarget:[],screen:[]}}setMainRenderPassEntry(){this.renderPassEntries.screen.push({renderPass:this.renderer.renderPass,renderTexture:null,onBeforeRenderPass:null,onAfterRenderPass:null,element:null,stack:{unProjected:{opaque:[],transparent:[]},projected:{opaque:[],transparent:[]}}})}getRenderPassEntryLength(e){return e?e.element?e.element.visible?1:0:e.stack.unProjected.opaque.length+e.stack.unProjected.transparent.length+e.stack.projected.opaque.length+e.stack.projected.transparent.length:0}addComputePass(e){this.computePassEntries.push(e),this.computePassEntries.sort((t,i)=>t.renderOrder!==i.renderOrder?t.renderOrder-i.renderOrder:t.index-i.index)}removeComputePass(e){this.computePassEntries=this.computePassEntries.filter(t=>t.uuid!==e.uuid)}addRenderTarget(e){this.renderPassEntries.renderTarget.find(t=>t.renderPass.uuid===e.renderPass.uuid)||this.renderPassEntries.renderTarget.push({renderPass:e.renderPass,renderTexture:e.renderTexture,onBeforeRenderPass:null,onAfterRenderPass:null,element:null,stack:{unProjected:{opaque:[],transparent:[]},projected:{opaque:[],transparent:[]}}})}removeRenderTarget(e){this.renderPassEntries.renderTarget=this.renderPassEntries.renderTarget.filter(t=>t.renderPass.uuid!==e.renderPass.uuid)}getMeshProjectionStack(e){const t=e.outputTarget?this.renderPassEntries.renderTarget.find(s=>s.renderPass.uuid===e.outputTarget.renderPass.uuid):this.renderPassEntries.screen[0],{stack:i}=t;return e.material.options.rendering.useProjection?i.projected:i.unProjected}addMesh(e){const t=this.getMeshProjectionStack(e),i=e.transparent?t.transparent:t.opaque;i.push(e),i.sort((s,r)=>s.renderOrder-r.renderOrder||s.index-r.index),"parent"in e&&!e.parent&&e.material.options.rendering.useProjection&&(e.parent=this)}removeMesh(e){const t=this.getMeshProjectionStack(e);e.transparent?t.transparent=t.transparent.filter(i=>i.uuid!==e.uuid):t.opaque=t.opaque.filter(i=>i.uuid!==e.uuid),"parent"in e&&e.parent&&e.parent.object3DIndex===this.object3DIndex&&(e.parent=null)}addShaderPass(e){const t=e.inputTarget||e.outputTarget?null:(r,o)=>{e.renderTexture&&o&&r.copyTextureToTexture({texture:o},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height]),this.renderer.postProcessingPass.setLoadOp("clear")},i=!e.outputTarget&&e.options.copyOutputToRenderTexture?(r,o)=>{e.renderTexture&&o&&r.copyTextureToTexture({texture:o},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height])}:null,s={renderPass:e.outputTarget?e.outputTarget.renderPass:this.renderer.postProcessingPass,renderTexture:e.outputTarget?e.outputTarget.renderTexture:null,onBeforeRenderPass:t,onAfterRenderPass:i,element:e,stack:null};this.renderPassEntries.screen.push(s),this.renderPassEntries.screen.sort((r,o)=>{const a=r.element&&!r.element.outputTarget,h=r.element?r.element.renderOrder:0,l=r.element?r.element.index:0,u=o.element&&!o.element.outputTarget,d=o.element?o.element.renderOrder:0,c=o.element?o.element.index:0;return a&&!u?1:!a&&u?-1:h!==d?h-d:l-c})}removeShaderPass(e){this.renderPassEntries.screen=this.renderPassEntries.screen.filter(t=>!t.element||t.element.uuid!==e.uuid)}addPingPongPlane(e){this.renderPassEntries.pingPong.push({renderPass:e.outputTarget.renderPass,renderTexture:e.outputTarget.renderTexture,onBeforeRenderPass:null,onAfterRenderPass:(t,i)=>{t.copyTextureToTexture({texture:i},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height])},element:e,stack:null}),this.renderPassEntries.pingPong.sort((t,i)=>t.element.renderOrder-i.element.renderOrder)}removePingPongPlane(e){this.renderPassEntries.pingPong=this.renderPassEntries.pingPong.filter(t=>t.element.uuid!==e.uuid)}getObjectRenderPassEntry(e){if(e.type==="RenderTarget")return this.renderPassEntries.renderTarget.find(t=>t.renderPass.uuid===e.renderPass.uuid);if(e.type==="PingPongPlane")return this.renderPassEntries.pingPong.find(t=>t.element.uuid===e.uuid);if(e.type==="ShaderPass")return this.renderPassEntries.screen.find(t=>t.element?.uuid===e.uuid);{const t=e.outputTarget?"renderTarget":"screen";return this.renderPassEntries[t].find(i=>[...i.stack.unProjected.opaque,...i.stack.unProjected.transparent,...i.stack.projected.opaque,...i.stack.projected.transparent].some(s=>s.uuid===e.uuid))}}sortTransparentMeshes(e){e.sort((t,i)=>{if(t.renderOrder!==i.renderOrder)return t.renderOrder-i.renderOrder;t.geometry?zi.copy(t.geometry.boundingBox.center).applyMat4(t.worldMatrix):t.worldMatrix.getTranslation(zi),i.geometry?Ei.copy(i.geometry.boundingBox.center).applyMat4(i.worldMatrix):i.worldMatrix.getTranslation(Ei);const s=t.geometry?t.geometry.boundingBox.radius*t.worldMatrix.getMaxScaleOnAxis():0,r=i.geometry?i.geometry.boundingBox.radius*i.worldMatrix.getMaxScaleOnAxis():0;return i.camera.worldMatrix.getTranslation(Cn).distance(Ei)-r-(t.camera.worldMatrix.getTranslation(Mn).distance(zi)-s)})}renderSinglePassEntry(e,t){const i=t.renderPass.updateView(t.renderTexture?.texture);t.onBeforeRenderPass&&t.onBeforeRenderPass(e,i);const s=e.beginRenderPass(t.renderPass.descriptor);if(!this.renderer.production&&s.pushDebugGroup(t.element?`${t.element.options.label} render pass using ${t.renderPass.options.label} descriptor`:`Render stack pass using ${t.renderPass.options.label}${t.renderTexture?" onto "+t.renderTexture.options.label:""}`),t.element)t.element.render(s);else if(t.stack){for(const r of t.stack.unProjected.opaque)r.render(s);for(const r of t.stack.unProjected.transparent)r.render(s);if(t.stack.projected.opaque.length||t.stack.projected.transparent.length){for(const r of t.stack.projected.opaque)r.render(s);this.sortTransparentMeshes(t.stack.projected.transparent);for(const r of t.stack.projected.transparent)r.render(s)}}!this.renderer.production&&s.popDebugGroup(),s.end(),t.onAfterRenderPass&&t.onAfterRenderPass(e,i),this.renderer.pipelineManager.resetCurrentPipeline()}onBeforeRender(){for(let e=0,t=this.renderer.meshes.length;e{this.getRenderPassEntryLength(s)&&(s.renderPass.setLoadOp(t==="screen"&&i!==0?"load":"clear"),i++,this.renderSinglePassEntry(e,s))})}}}var ar=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},hr=(n,e,t)=>(ar(n,e,"read from private field"),t?t.call(n):e.get(n)),Bn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Pn=(n,e,t,i)=>(ar(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),Sn=(n,e,t,i)=>({set _(s){Pn(n,e,s,t)},get _(){return hr(n,e,i)}}),Gt;class Ot{constructor(){Bn(this,Gt,0),this.queue=[]}add(e=s=>{},{order:t=this.queue.length,once:i=!1}={}){const s={callback:e,order:t,once:i,id:hr(this,Gt)};return Sn(this,Gt)._++,this.queue.push(s),this.queue.sort((r,o)=>r.order-o.order),s.id}remove(e=0){this.queue=this.queue.filter(t=>t.id!==e)}execute(e){this.queue.forEach(t=>{t.callback(e),t.once&&this.remove(t.id)})}}Gt=new WeakMap;class Li{constructor({deviceManager:e,label:t="Main renderer",container:i,pixelRatio:s=1,autoResize:r=!0,preferredFormat:o,alphaMode:a="premultiplied",renderPass:h}){this._onBeforeRenderCallback=p=>{},this._onAfterRenderCallback=p=>{},this._onResizeCallback=()=>{},this._onAfterResizeCallback=()=>{},this.type="GPURenderer",this.uuid=Y(),e||te(`GPURenderer (${t}): no device manager provided: ${e}`),this.deviceManager=e,this.deviceManager.addRenderer(this),this.shouldRender=!0,this.shouldRenderScene=!0,h={useDepth:!0,sampleCount:4,clearValue:[0,0,0,0],...h},o=o??this.deviceManager.gpu?.getPreferredCanvasFormat(),this.options={deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,preferredFormat:o,alphaMode:a,renderPass:h},this.pixelRatio=s??window.devicePixelRatio??1,this.alphaMode=a;const l=i instanceof OffscreenCanvas,u=l||i instanceof HTMLCanvasElement;this.canvas=u?i:document.createElement("canvas");const{width:d,height:c}=this.canvas;this.rectBBox={width:d,height:c,top:0,left:0},this.setScene(),this.setTasksQueues(),this.setRendererObjects(),l||(this.domElement=new Ri({element:i,priority:5,onSizeChanged:()=>{this.options.autoResize&&this.resize()}}),this.resize(),u||this.domElement.element.appendChild(this.canvas)),this.deviceManager.device&&this.setContext()}setSize(e=null){e={width:Math.max(1,this.boundingRect.width),height:Math.max(1,this.boundingRect.height),top:this.boundingRect.top,left:this.boundingRect.left,...e},this.rectBBox=e;const t={width:this.rectBBox.width,height:this.rectBBox.height};t.width*=this.pixelRatio,t.height*=this.pixelRatio,this.clampToMaxDimension(t),this.canvas.width=Math.floor(t.width),this.canvas.height=Math.floor(t.height),this.canvas.style&&(this.canvas.style.width=this.rectBBox.width+"px",this.canvas.style.height=this.rectBBox.height+"px")}setPixelRatio(e=1){this.pixelRatio=e,this.resize(this.rectBBox)}resize(e=null){this.setSize(e),this._onResizeCallback&&this._onResizeCallback(),this.resizeObjects(),this._onAfterResizeCallback&&this._onAfterResizeCallback()}resizeObjects(){this.textures.forEach(e=>{e.resize()}),this.renderPass?.resize(),this.postProcessingPass?.resize(),this.renderTargets.forEach(e=>e.resize()),this.computePasses.forEach(e=>e.resize()),this.pingPongPlanes.forEach(e=>e.resize(this.boundingRect)),this.shaderPasses.forEach(e=>e.resize(this.boundingRect)),this.resizeMeshes()}resizeMeshes(){this.meshes.forEach(e=>{e.resize(this.boundingRect)})}get boundingRect(){if(this.domElement&&this.domElement.boundingRect)return this.domElement.boundingRect;if(this.domElement){const e=this.domElement.element?.getBoundingClientRect();return{top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.width,height:e.height,x:e.x,y:e.y}}else return{top:this.rectBBox.top,right:this.rectBBox.left+this.rectBBox.width,bottom:this.rectBBox.top+this.rectBBox.height,left:this.rectBBox.left,width:this.rectBBox.width,height:this.rectBBox.height,x:this.rectBBox.left,y:this.rectBBox.top}}clampToMaxDimension(e){this.device&&(e.width=Math.min(this.device.limits.maxTextureDimension2D,e.width),e.height=Math.min(this.device.limits.maxTextureDimension2D,e.height))}get device(){return this.deviceManager.device}get ready(){return this.deviceManager.ready&&!!this.context&&!!this.canvas.width&&!!this.canvas.height}get production(){return this.deviceManager.production}get samplers(){return this.deviceManager.samplers}get buffers(){return this.deviceManager.buffers}get pipelineManager(){return this.deviceManager.pipelineManager}get deviceRenderedObjects(){return this.deviceManager.deviceRenderedObjects}configureContext(){this.context.configure({device:this.device,format:this.options.preferredFormat,alphaMode:this.alphaMode,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC|GPUTextureUsage.COPY_DST})}setContext(){this.context=this.canvas.getContext("webgpu"),this.device&&(this.configureContext(),this.setMainRenderPasses())}loseContext(){this.renderedObjects.forEach(e=>e.loseContext())}restoreContext(){this.configureContext(),this.textures.forEach(e=>{e.createTexture()}),this.renderPass?.resize(),this.postProcessingPass?.resize(),this.renderTargets.forEach(e=>e.resize()),this.renderedObjects.forEach(e=>e.restoreContext())}setMainRenderPasses(){this.renderPass=new Bt(this,{label:this.options.label+" render pass",...this.options.renderPass}),this.scene.setMainRenderPassEntry(),this.postProcessingPass=new Bt(this,{label:this.options.label+" post processing render pass",useDepth:!1,sampleCount:1})}setScene(){this.scene=new or({renderer:this})}createBuffer(e){const t=this.deviceManager.device?.createBuffer(e.options);return this.deviceManager.addBuffer(e),t}removeBuffer(e){this.deviceManager.removeBuffer(e)}queueWriteBuffer(e,t,i){this.deviceManager.device?.queue.writeBuffer(e,t,i)}copyBufferToBuffer({srcBuffer:e,dstBuffer:t,commandEncoder:i}){if(!e||!e.GPUBuffer)return I(`${this.type} (${this.options.label}): cannot copy to buffer because the source buffer has not been provided`),null;if(t||(t=new _e),t.GPUBuffer||t.createBuffer(this,{label:`GPURenderer (${this.options.label}): destination copy buffer from: ${e.options.label}`,size:e.GPUBuffer.size,usage:["copyDst","mapRead"]}),e.GPUBuffer.mapState!=="unmapped"){I(`${this.type} (${this.options.label}): Cannot copy from ${e.GPUBuffer} because it is currently mapped`);return}if(t.GPUBuffer.mapState!=="unmapped"){I(`${this.type} (${this.options.label}): Cannot copy from ${t.GPUBuffer} because it is currently mapped`);return}const s=!!i;if(s||(i=this.deviceManager.device?.createCommandEncoder({label:`${this.type} (${this.options.label}): Copy buffer command encoder`}),!this.production&&i.pushDebugGroup(`${this.type} (${this.options.label}): Copy buffer command encoder`)),i.copyBufferToBuffer(e.GPUBuffer,0,t.GPUBuffer,0,t.GPUBuffer.size),!s){!this.production&&i.popDebugGroup();const r=i.finish();this.deviceManager.device?.queue.submit([r])}return t}get bindGroups(){return this.deviceManager.bindGroups}addBindGroup(e){this.deviceManager.addBindGroup(e)}removeBindGroup(e){this.deviceManager.removeBindGroup(e)}createBindGroupLayout(e){return this.deviceManager.device?.createBindGroupLayout(e)}createBindGroup(e){return this.deviceManager.device?.createBindGroup(e)}createShaderModule(e){return this.device?.createShaderModule(e)}createPipelineLayout(e){return this.device?.createPipelineLayout(e)}createRenderPipeline(e){return this.device?.createRenderPipeline(e)}async createRenderPipelineAsync(e){return await this.device?.createRenderPipelineAsync(e)}createComputePipeline(e){return this.device?.createComputePipeline(e)}async createComputePipelineAsync(e){return await this.device?.createComputePipelineAsync(e)}get domTextures(){return this.deviceManager.domTextures}addDOMTexture(e){this.deviceManager.addDOMTexture(e)}removeDOMTexture(e){this.deviceManager.removeDOMTexture(e)}addTexture(e){this.textures.push(e)}removeTexture(e){this.textures=this.textures.filter(t=>t.uuid!==e.uuid)}createTexture(e){return this.deviceManager.device?.createTexture(e)}uploadTexture(e){this.deviceManager.uploadTexture(e)}importExternalTexture(e){return this.deviceManager.device?.importExternalTexture({source:e})}createSampler(e){const t=this.samplers.find(i=>JSON.stringify(i.options)===JSON.stringify(e.options)&&i.sampler);if(t)return t.sampler;{const{type:i,...s}=e.options,r=this.deviceManager.device?.createSampler({label:e.label,...s});return this.deviceManager.addSampler(e),r}}removeSampler(e){this.deviceManager.removeSampler(e)}setTasksQueues(){this.onBeforeCommandEncoderCreation=new Ot,this.onBeforeRenderScene=new Ot,this.onAfterRenderScene=new Ot,this.onAfterCommandEncoderSubmission=new Ot}setRendererObjects(){this.computePasses=[],this.pingPongPlanes=[],this.shaderPasses=[],this.renderTargets=[],this.meshes=[],this.textures=[]}get renderedObjects(){return[...this.computePasses,...this.meshes,...this.shaderPasses,...this.pingPongPlanes]}getObjectsByBindGroup(e){return this.deviceRenderedObjects.filter(t=>[...t.material.bindGroups,...t.material.inputsBindGroups,...t.material.clonedBindGroups].some(i=>i.uuid===e.uuid))}getObjectsByTexture(e){return this.deviceRenderedObjects.filter(t=>[...t.material.domTextures,...t.material.textures].some(i=>i.uuid===e.uuid))}onBeforeRender(e){return e&&(this._onBeforeRenderCallback=e),this}onAfterRender(e){return e&&(this._onAfterRenderCallback=e),this}onResize(e){return e&&(this._onResizeCallback=e),this}onAfterResize(e){return e&&(this._onAfterResizeCallback=e),this}renderSingleComputePass(e,t){const i=e.beginComputePass();t.render(i),i.end(),t.copyBufferToResult(e)}renderSingleMesh(e,t){const i=e.beginRenderPass(this.renderPass.descriptor);t.render(i),i.end()}renderOnce(e){const t=this.device?.createCommandEncoder({label:"Render once command encoder"});!this.production&&t.pushDebugGroup("Render once command encoder"),this.pipelineManager.resetCurrentPipeline(),e.forEach(s=>{s.type==="ComputePass"?this.renderSingleComputePass(t,s):this.renderSingleMesh(t,s)}),!this.production&&t.popDebugGroup();const i=t.finish();this.device?.queue.submit([i]),this.pipelineManager.resetCurrentPipeline()}forceClear(e){const t=!!e;if(t||(e=this.device?.createCommandEncoder({label:`${this.type} (${this.options.label}): Force clear command encoder`}),!this.production&&e.pushDebugGroup(`${this.type} (${this.options.label}): Force clear command encoder`)),this.renderPass.updateView(),e.beginRenderPass(this.renderPass.descriptor).end(),!t){!this.production&&e.popDebugGroup();const s=e.finish();this.device?.queue.submit([s])}}onBeforeCommandEncoder(){this.ready&&(this.shouldRenderScene&&this.scene?.onBeforeRender(),this.onBeforeCommandEncoderCreation.execute())}onAfterCommandEncoder(){this.ready&&this.onAfterCommandEncoderSubmission.execute()}render(e){!this.ready||!this.shouldRender||(this._onBeforeRenderCallback&&this._onBeforeRenderCallback(e),this.onBeforeRenderScene.execute(e),this.shouldRenderScene&&this.scene?.render(e),this._onAfterRenderCallback&&this._onAfterRenderCallback(e),this.onAfterRenderScene.execute(e))}destroy(){this.deviceManager.renderers=this.deviceManager.renderers.filter(e=>e.uuid!==this.uuid),this.domElement?.destroy(),this.renderPass?.destroy(),this.postProcessingPass?.destroy(),this.renderTargets.forEach(e=>e.destroy()),this.renderedObjects.forEach(e=>e.remove()),this.textures.forEach(e=>e.destroy()),this.context?.unconfigure()}}var lr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Tn=(n,e,t)=>(lr(n,e,"read from private field"),t?t.call(n):e.get(n)),Rn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},_i=(n,e,t,i)=>(lr(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),De;class Ai extends Li{constructor({deviceManager:e,label:t,container:i,pixelRatio:s=1,autoResize:r=!0,preferredFormat:o,alphaMode:a="premultiplied",renderPass:h,camera:l={},lights:u={}}){super({deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,preferredFormat:o,alphaMode:a,renderPass:h}),Rn(this,De,void 0),this.type="GPUCameraRenderer",l={fov:50,near:.1,far:1e3,...l},u={maxAmbientLights:2,maxDirectionalLights:5,maxPointLights:5,...u},this.options={...this.options,camera:l,lights:u},this.bindings={},_i(this,De,!0),this.lights=[],this.setCamera(l),this.setCameraBinding(),this.setLightsBinding(),this.setShadowsBinding(),this.setCameraLightsBindGroup()}loseContext(){super.loseContext(),this.cameraLightsBindGroup.loseContext()}restoreContext(){super.restoreContext(),this.cameraLightsBindGroup?.restoreContext(),this.updateCameraBindings()}setCamera(e){const{width:t,height:i}=this.rectBBox;this.useCamera(new gs({fov:e.fov,near:e.near,far:e.far,width:t,height:i,pixelRatio:this.pixelRatio,onMatricesChanged:()=>{this.onCameraMatricesChanged()}}))}useCamera(e){if(!(this.camera&&e&&this.camera.uuid===e.uuid)&&(this.camera&&(this.camera.parent=null,this.camera.onMatricesChanged=()=>{}),this.camera=e,this.camera.parent=this.scene,this.bindings.camera)){this.camera.onMatricesChanged=()=>this.onCameraMatricesChanged(),this.bindings.camera.inputs.view.value=this.camera.viewMatrix,this.bindings.camera.inputs.projection.value=this.camera.projectionMatrix;for(const t of this.meshes)"modelViewMatrix"in t&&(t.camera=this.camera)}}onCameraMatricesChanged(){this.updateCameraBindings();for(const e of this.meshes)"modelViewMatrix"in e&&e.shouldUpdateProjectionMatrixStack()}setCameraBinding(){this.bindings.camera=new fe({label:"Camera",name:"camera",visibility:["vertex"],struct:{view:{type:"mat4x4f",value:this.camera.viewMatrix},projection:{type:"mat4x4f",value:this.camera.projectionMatrix},position:{type:"vec3f",value:this.camera.position.clone().setFromMatrixPosition(this.camera.worldMatrix),onBeforeUpdate:()=>{this.bindings.camera.inputs.position.value.copy(this.camera.position).setFromMatrixPosition(this.camera.worldMatrix)}}}})}addLight(e){this.lights.push(e)}removeLight(e){this.lights=this.lights.filter(t=>t.uuid!==e.uuid),e.destroy()}setLightsBinding(){this.lightsBindingParams={ambientLights:{max:this.options.lights.maxAmbientLights,label:"Ambient lights",params:{color:{type:"array",size:3}}},directionalLights:{max:this.options.lights.maxDirectionalLights,label:"Directional lights",params:{color:{type:"array",size:3},direction:{type:"array",size:3}}},pointLights:{max:this.options.lights.maxPointLights,label:"Point lights",params:{color:{type:"array",size:3},position:{type:"array",size:3},range:{type:"array",size:1}}}},Object.keys({ambientLights:null,directionalLights:null,pointLights:null}).forEach(t=>{this.setLightsTypeBinding(t)})}setLightsTypeBinding(e){const t=Object.keys(this.lightsBindingParams[e].params).map(i=>({key:i,type:this.lightsBindingParams[e].params[i].type,size:this.lightsBindingParams[e].params[i].size})).reduce((i,s)=>(i[s.key]={type:s.type,value:new Float32Array(this.lightsBindingParams[e].max*s.size)},i),{});this.bindings[e]=new fe({label:this.lightsBindingParams[e].label,name:e,bindingType:"storage",visibility:["vertex","fragment","compute"],struct:{count:{type:"i32",value:0},...t}})}onMaxLightOverflow(e){this.production||I(`${this.type}: You are overflowing the current max lights count of ${this.lightsBindingParams[e].max} for this type of lights: ${e}. This should be avoided by setting a larger ${"max"+e.charAt(0).toUpperCase()+e.slice(1)} when instancing your ${this.type}.`),this.lightsBindingParams[e].max++;const t=this.cameraLightsBindGroup.getBindingByName(e);this.cameraLightsBindGroup.destroyBufferBinding(t),this.setLightsTypeBinding(e);const i=this.cameraLightsBindGroup.bindings.findIndex(s=>s.name===e);if(this.cameraLightsBindGroup.bindings[i]=this.bindings[e],e==="directionalLights"||e==="pointLights"){const s=e.replace("Lights","")+"Shadows",r=this.cameraLightsBindGroup.getBindingByName(s);this.cameraLightsBindGroup.destroyBufferBinding(r),this.setShadowsTypeBinding(e);const o=this.cameraLightsBindGroup.bindings.findIndex(a=>a.name===s);this.cameraLightsBindGroup.bindings[o]=this.bindings[s]}this.cameraLightsBindGroup.resetEntries(),this.cameraLightsBindGroup.createBindGroup(),this.lights.forEach(s=>{s.type===e&&s.reset()})}get shadowCastingLights(){return this.lights.filter(e=>e.type==="directionalLights"||e.type==="pointLights")}setShadowsBinding(){this.shadowsBindingsStruct={directional:nn,point:un},this.setShadowsTypeBinding("directionalLights"),this.setShadowsTypeBinding("pointLights")}setShadowsTypeBinding(e){const t=e.replace("Lights",""),i=t+"Shadows",s=this.shadowsBindingsStruct[t],r=t.charAt(0).toUpperCase()+t.slice(1)+" shadows",o=new fe({label:r+" element",name:i+"Elements",bindingType:"uniform",visibility:["vertex","fragment"],struct:s});this.bindings[i]=new fe({label:r,name:i,bindingType:"storage",visibility:["vertex","fragment","compute"],bindings:Array.from(Array(this.lightsBindingParams[e].max).keys()).map(a=>o.clone({...o.options,struct:Object.keys(s).reduce((h,l)=>{const u=s[l];return{...h,[l]:{type:u.type,value:Array.isArray(u.value)||ArrayBuffer.isView(u.value)?new u.value.constructor(u.value.length):u.value}}},{})}))})}setCameraLightsBindGroup(){this.cameraLightsBindGroup=new ut(this,{label:"Camera and lights uniform bind group",bindings:Object.keys(this.bindings).map(e=>this.bindings[e]).flat()}),this.cameraLightsBindGroup.consumers.add(this.uuid)}setCameraBindGroup(){this.cameraLightsBindGroup&&this.cameraLightsBindGroup.shouldCreateBindGroup&&(this.cameraLightsBindGroup.setIndex(0),this.cameraLightsBindGroup.createBindGroup())}shouldUpdateCameraLightsBindGroup(){_i(this,De,!0)}updateCameraBindings(){this.bindings.camera?.shouldUpdateBinding("view"),this.bindings.camera?.shouldUpdateBinding("projection"),this.bindings.camera?.shouldUpdateBinding("position"),this.shouldUpdateCameraLightsBindGroup()}getObjectsByBindGroup(e){return this.deviceRenderedObjects.filter(t=>[...t.material.bindGroups,...t.material.inputsBindGroups,...t.material.clonedBindGroups,this.cameraLightsBindGroup].some(i=>i.uuid===e.uuid))}setPerspective({fov:e,near:t,far:i}={}){this.camera?.setPerspective({fov:e,near:t,far:i,width:this.rectBBox.width,height:this.rectBBox.height,pixelRatio:this.pixelRatio})}setCameraPosition(e=new f(0,0,1)){this.camera.position.copy(e)}resize(e=null){this.setSize(e),this.setPerspective(),this._onResizeCallback&&this._onResizeCallback(),this.resizeObjects(),this._onAfterResizeCallback&&this._onAfterResizeCallback()}render(e){this.ready&&(this.setCameraBindGroup(),this.cameraLightsBindGroup&&Tn(this,De)&&(this.cameraLightsBindGroup.update(),_i(this,De,!1)),super.render(e))}destroy(){this.cameraLightsBindGroup?.destroy(),this.lights.forEach(e=>this.removeLight(e)),super.destroy()}}De=new WeakMap;class ur{constructor({label:e,production:t=!1,adapterOptions:i={},onError:s=()=>{},onDeviceLost:r=o=>{}}={}){this.index=0,this.label=e??"GPUDeviceManager instance",this.production=t,this.ready=!1,this.adapterOptions=i,this.onError=s,this.onDeviceLost=r,this.gpu=navigator.gpu,this.setPipelineManager(),this.setDeviceObjects()}async setAdapterAndDevice({adapter:e=null,device:t=null}={}){await this.setAdapter(e),await this.setDevice(t)}async init({adapter:e=null,device:t=null}={}){if(await this.setAdapterAndDevice({adapter:e,device:t}),this.device)for(const i of this.renderers)i.context||i.setContext()}async setAdapter(e=null){if(this.gpu||(this.onError(),te("GPUDeviceManager: WebGPU is not supported on your browser/OS. No 'gpu' object in 'navigator'.")),e)this.adapter=e;else try{this.adapter=await this.gpu?.requestAdapter(this.adapterOptions),this.adapter||(this.onError(),te("GPUDeviceManager: WebGPU is not supported on your browser/OS. 'requestAdapter' failed."))}catch(t){this.onError(),te("GPUDeviceManager: "+t.message)}}async setDevice(e=null){if(e)this.device=e,this.ready=!0,this.index++;else try{const t=[];this.adapter.features.has("float32-filterable")&&t.push("float32-filterable"),this.device=await this.adapter?.requestDevice({label:this.label+" "+this.index,requiredFeatures:t}),this.device&&(this.ready=!0,this.index++)}catch(t){this.onError(),te(`${this.label}: WebGPU is not supported on your browser/OS. 'requestDevice' failed: ${t}`)}this.device?.lost.then(t=>{I(`${this.label}: WebGPU device was lost: ${t.message}`),this.loseDevice(),t.reason!=="destroyed"&&this.onDeviceLost(t)})}setPipelineManager(){this.pipelineManager=new nr}loseDevice(){this.ready=!1,this.pipelineManager.resetCurrentPipeline(),this.samplers.forEach(e=>e.sampler=null),this.renderers.forEach(e=>e.loseContext()),this.bindGroupLayouts.clear(),this.buffers.clear()}async restoreDevice({adapter:e=null,device:t=null}={}){await this.setAdapterAndDevice({adapter:e,device:t}),this.device&&(this.samplers.forEach(i=>{const{type:s,...r}=i.options;i.sampler=this.device.createSampler({label:i.label,...r})}),this.renderers.forEach(i=>i.restoreContext()))}setDeviceObjects(){this.renderers=[],this.bindGroups=new Map,this.buffers=new Map,this.bindGroupLayouts=new Map,this.bufferBindings=new Map,this.samplers=[],this.domTextures=[],this.texturesQueue=[]}addRenderer(e){this.renderers.push(e)}removeRenderer(e){this.renderers=this.renderers.filter(t=>t.uuid!==e.uuid)}get deviceRenderedObjects(){return this.renderers.map(e=>e.renderedObjects).flat()}addBindGroup(e){this.bindGroups.set(e.uuid,e)}removeBindGroup(e){this.bindGroups.delete(e.uuid)}addBuffer(e){this.buffers.set(e.uuid,e)}removeBuffer(e){this.buffers.delete(e?.uuid)}addSampler(e){this.samplers.push(e)}removeSampler(e){this.samplers=this.samplers.filter(t=>t.uuid!==e.uuid)}addDOMTexture(e){this.domTextures.push(e)}uploadTexture(e){if(e.source)try{this.device?.queue.copyExternalImageToTexture({source:e.source,flipY:e.options.flipY},{texture:e.texture,premultipliedAlpha:e.options.premultipliedAlpha},{width:e.size.width,height:e.size.height}),e.texture.mipLevelCount>1&&ri(this.device,e.texture),this.texturesQueue.push(e)}catch({message:t}){te(`GPUDeviceManager: could not upload texture: ${e.options.name} because: ${t}`)}else this.device?.queue.writeTexture({texture:e.texture},new Uint8Array(e.options.placeholderColor),{bytesPerRow:e.size.width*4},{width:e.size.width,height:e.size.height})}removeDOMTexture(e){this.domTextures=this.domTextures.filter(t=>t.uuid!==e.uuid)}render(){if(!this.ready)return;for(const i of this.renderers)i.shouldRender&&i.onBeforeCommandEncoder();const e=this.device?.createCommandEncoder({label:this.label+" command encoder"});!this.production&&e.pushDebugGroup(this.label+" command encoder: main render loop"),this.renderers.forEach(i=>i.render(e)),!this.production&&e.popDebugGroup();const t=e.finish();this.device?.queue.submit([t]),this.domTextures.filter(i=>!i.parentMesh&&i.sourceLoaded&&!i.sourceUploaded).forEach(i=>this.uploadTexture(i));for(const i of this.texturesQueue)i.sourceUploaded=!0;this.texturesQueue=[];for(const i of this.renderers)i.shouldRender&&i.onAfterCommandEncoder()}destroy(){this.device?.destroy(),this.device=null,this.renderers.forEach(e=>e.destroy()),this.bindGroups.forEach(e=>e.destroy()),this.buffers.forEach(e=>e?.destroy()),this.domTextures.forEach(e=>e.destroy()),this.setDeviceObjects()}}var zn=` +`);this.shaders.compute.code=this.shaders.compute.head+this.options.shaders.compute.code}createShaders(){this.patchShaders(),this.shaders.compute.module=this.createShaderModule({code:this.shaders.compute.code,type:"compute"})}createPipelineDescriptor(){this.shaders.compute.module&&(this.descriptor={label:this.options.label,layout:this.layout,compute:{module:this.shaders.compute.module,entryPoint:this.options.shaders.compute.entryPoint}})}createComputePipeline(){if(this.shaders.compute.module)try{this.pipeline=this.renderer.createComputePipeline(this.descriptor)}catch(e){this.status.error=e,te(e)}}async createComputePipelineAsync(){if(this.shaders.compute.module)try{this.pipeline=await this.renderer.createComputePipelineAsync(this.descriptor),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null}catch(e){this.status.error=e,te(e)}}async compilePipelineEntry(){super.compilePipelineEntry(),this.options.useAsync?await this.createComputePipelineAsync():(this.createComputePipeline(),this.status.compiled=!0,this.status.compiling=!1,this.status.error=null)}}class Wr{constructor(){this.type="PipelineManager",this.currentPipelineIndex=null,this.pipelineEntries=[],this.activeBindGroups=[]}compareShaders(e,t){return e.code===t.code&&e.entryPoint===t.entryPoint}isSameRenderPipeline(e){return this.pipelineEntries.filter(t=>t instanceof je).find(t=>{const{options:i}=t,{shaders:s,rendering:r,cacheKey:o}=e,a=o===i.cacheKey,h=this.compareShaders(s.vertex,i.shaders.vertex),u=!s.fragment&&!i.shaders.fragment||this.compareShaders(s.fragment,i.shaders.fragment),l=yr(r,i.rendering);return a&&!l.length&&h&&u})}createRenderPipeline(e){const{attributes:t,bindGroups:i}=e;let s=t.layoutCacheKey;i.forEach(o=>{o.bindings.forEach(a=>{s+=a.name+","}),s+=o.pipelineCacheKey});const r=this.isSameRenderPipeline({...e,cacheKey:s});if(r)return r;{const o=new je({...e,cacheKey:s});return this.pipelineEntries.push(o),o}}isSameComputePipeline(e){return this.pipelineEntries.filter(t=>t instanceof is).find(t=>{const{options:i}=t,{shaders:s,cacheKey:r}=e,o=r===i.cacheKey,a=this.compareShaders(s.compute,i.shaders.compute);return o&&a})}createComputePipeline(e){let t="";e.bindGroups.forEach(s=>{s.bindings.forEach(r=>{t+=r.name+","}),t+=s.pipelineCacheKey});const i=this.isSameComputePipeline({...e,cacheKey:t});if(i)return i;{const s=new is({...e,cacheKey:t});return this.pipelineEntries.push(s),s}}setCurrentPipeline(e,t){t.index!==this.currentPipelineIndex&&(e.setPipeline(t.pipeline),this.currentPipelineIndex=t.index)}setActiveBindGroups(e,t){t.forEach((i,s)=>{(!this.activeBindGroups[s]||this.activeBindGroups[s].uuid!==i.uuid||this.activeBindGroups[s].index!==i.index)&&(this.activeBindGroups[s]=i,e.setBindGroup(i.index,i.bindGroup))})}resetCurrentPipeline(){this.currentPipelineIndex=null,this.activeBindGroups=[]}}class vo{constructor(){this.shouldWatch=!0,this.entries=[],typeof window=="object"&&"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(e=>{e.map(i=>this.entries.filter(s=>s.element.isSameNode(i.target))).flat().sort((i,s)=>s.priority-i.priority)?.forEach(i=>{i&&i.callback&&i.callback()})}))}useObserver(e=!0){this.shouldWatch=e}observe({element:e,priority:t,callback:i}){if(!e||!this.shouldWatch)return;this.resizeObserver?.observe(e);const s={element:e,priority:t,callback:i};this.entries.push(s)}unobserve(e){this.resizeObserver?.unobserve(e),this.entries=this.entries.filter(t=>!t.element.isSameNode(e))}destroy(){this.resizeObserver?.disconnect()}}const ss=new vo;class rs{constructor({element:e=document.body,priority:t=1,onSizeChanged:i=(r=null)=>{},onPositionChanged:s=(r=null)=>{}}={}){if(typeof e=="string"){if(this.element=document.querySelector(e),!this.element){const r=typeof e=="string"?`'${e}' selector`:`${e} HTMLElement`;te(`DOMElement: corresponding ${r} not found.`)}}else this.element=e;this.priority=t,this.isResizing=!1,this.onSizeChanged=i,this.onPositionChanged=s,this.resizeManager=ss,this.resizeManager.observe({element:this.element,priority:this.priority,callback:()=>{this.setSize()}}),this.setSize()}compareBoundingRect(e,t){return!["x","y","left","top","right","bottom","width","height"].some(i=>e[i]!==t[i])}get boundingRect(){return this._boundingRect}set boundingRect(e){const t=!!this.boundingRect&&this.compareBoundingRect(e,this.boundingRect);this._boundingRect={top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.width,height:e.height,x:e.x,y:e.y},t||this.onSizeChanged(this.boundingRect)}updateScrollPosition(e={x:0,y:0}){this.isResizing||(this._boundingRect.top+=e.y,this._boundingRect.left+=e.x,(e.x||e.y)&&this.onPositionChanged(this.boundingRect))}setSize(e=null){!this.element||this.isResizing||(this.isResizing=!0,this.boundingRect=e??this.element.getBoundingClientRect(),setTimeout(()=>{this.isResizing=!1},10))}destroy(){this.resizeManager.unobserve(this.element)}}const bo=new f,wo=new f,ns=new f,os=new f;class jr extends ve{constructor({renderer:e}){super(),e=V(e,"Scene"),this.renderer=e,this.computePassEntries=[],this.renderPassEntries={pingPong:[],renderTarget:[],screen:[]}}setMainRenderPassEntry(){this.renderPassEntries.screen.push({renderPass:this.renderer.renderPass,renderTexture:null,onBeforeRenderPass:null,onAfterRenderPass:null,element:null,stack:{unProjected:{opaque:[],transparent:[]},projected:{opaque:[],transparent:[]}}})}getRenderPassEntryLength(e){return e?e.element?e.element.visible?1:0:e.stack.unProjected.opaque.length+e.stack.unProjected.transparent.length+e.stack.projected.opaque.length+e.stack.projected.transparent.length:0}addComputePass(e){this.computePassEntries.push(e),this.computePassEntries.sort((t,i)=>t.renderOrder!==i.renderOrder?t.renderOrder-i.renderOrder:t.index-i.index)}removeComputePass(e){this.computePassEntries=this.computePassEntries.filter(t=>t.uuid!==e.uuid)}addRenderTarget(e){this.renderPassEntries.renderTarget.find(t=>t.renderPass.uuid===e.renderPass.uuid)||this.renderPassEntries.renderTarget.push({renderPass:e.renderPass,renderTexture:e.renderTexture,onBeforeRenderPass:null,onAfterRenderPass:null,element:null,stack:{unProjected:{opaque:[],transparent:[]},projected:{opaque:[],transparent:[]}}})}removeRenderTarget(e){this.renderPassEntries.renderTarget=this.renderPassEntries.renderTarget.filter(t=>t.renderPass.uuid!==e.renderPass.uuid)}getMeshProjectionStack(e){const t=e.outputTarget?this.renderPassEntries.renderTarget.find(s=>s.renderPass.uuid===e.outputTarget.renderPass.uuid):this.renderPassEntries.screen[0],{stack:i}=t;return e.material.options.rendering.useProjection?i.projected:i.unProjected}orderStack(e){e.sort((t,i)=>t.renderOrder-i.renderOrder||t.index-i.index)}isStackObjectRenderBundle(e){return e.type==="RenderBundle"}addMesh(e){const t=this.getMeshProjectionStack(e),i=!!e.transparent,{useProjection:s}=e.material.options.rendering;if(e.renderBundle){const{renderBundle:r}=e;r.addMesh(e,e.outputTarget?e.outputTarget.renderPass:this.renderer.renderPass),e.renderBundle&&r.meshes.size===1&&(r.transparent===null&&(r.transparent=i),r.useProjection===null&&(r.useProjection=s),this.addRenderBundle(r,t))}if(!e.renderBundle){const r=i?t.transparent:t.opaque;r.push(e),this.orderStack(r)}"parent"in e&&!e.parent&&s&&(e.parent=this)}removeMesh(e){const t=this.getMeshProjectionStack(e),i=!!e.transparent;e.renderBundle?e.renderBundle.removeMesh(e,!1):i?t.transparent=t.transparent.filter(s=>s.uuid!==e.uuid):t.opaque=t.opaque.filter(s=>s.uuid!==e.uuid),"parent"in e&&e.parent&&e.parent.object3DIndex===this.object3DIndex&&(e.parent=null)}addRenderBundle(e,t){const i=e.transparent?t.transparent:t.opaque;i.push(e),this.orderStack(i)}removeRenderBundle(e){const t=this.renderPassEntries.renderTarget.find(a=>a.renderPass.uuid===e.options.renderPass?.uuid),{stack:i}=t||this.renderPassEntries.screen[0],r=!!e.useProjection?i.projected:i.unProjected;!!e.transparent?r.transparent=r.transparent.filter(a=>a.uuid!==e.uuid):r.opaque=r.opaque.filter(a=>a.uuid!==e.uuid)}addShaderPass(e){const t=e.inputTarget||e.outputTarget?null:(o,a)=>{e.renderTexture&&a&&o.copyTextureToTexture({texture:a},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height]),this.renderer.postProcessingPass.setLoadOp("clear")},i=!e.outputTarget&&e.options.copyOutputToRenderTexture?(o,a)=>{e.renderTexture&&a&&o.copyTextureToTexture({texture:a},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height])}:null,s=e.outputTarget?e.outputTarget.renderPass:this.renderer.postProcessingPass,r={renderPass:s,renderTexture:e.outputTarget?e.outputTarget.renderTexture:null,onBeforeRenderPass:t,onAfterRenderPass:i,element:e,stack:null};if(e.renderBundle){const o=!!e.transparent,{renderBundle:a}=e;a.meshes.size<1?(a.addMesh(e,s),a.size=1):(L(`${a.options.label} (${a.type}): Cannot add more than 1 ShaderPass to a render bundle. This ShaderPass will not be added: ${e.options.label}`),e.renderBundle=null),e.renderBundle&&(e.renderBundle.renderOrder=e.renderOrder,a.transparent=o,a.useProjection=!1)}this.renderPassEntries.screen.push(r),this.renderPassEntries.screen.sort((o,a)=>{const h=o.element&&!o.element.outputTarget,u=o.element?o.element.renderOrder:0,l=o.element?o.element.index:0,c=a.element&&!a.element.outputTarget,p=a.element?a.element.renderOrder:0,d=a.element?a.element.index:0;return h&&!c?1:!h&&c?-1:u!==p?u-p:l-d})}removeShaderPass(e){e.renderBundle&&e.renderBundle.empty(),this.renderPassEntries.screen=this.renderPassEntries.screen.filter(t=>!t.element||t.element.uuid!==e.uuid)}addPingPongPlane(e){if(this.renderPassEntries.pingPong.push({renderPass:e.outputTarget.renderPass,renderTexture:e.outputTarget.renderTexture,onBeforeRenderPass:null,onAfterRenderPass:(t,i)=>{t.copyTextureToTexture({texture:i},{texture:e.renderTexture.texture},[e.renderTexture.size.width,e.renderTexture.size.height])},element:e,stack:null}),e.renderBundle){const t=!!e.transparent,{renderBundle:i}=e;i.meshes.size<1?(i.addMesh(e,e.outputTarget.renderPass),i.size=1):(L(`${i.options.label} (${i.type}): Cannot add more than 1 PingPongPlane to a render bundle. This PingPongPlane will not be added: ${e.options.label}`),e.renderBundle=null),e.renderBundle&&(e.renderBundle.renderOrder=e.renderOrder,i.transparent=t,i.useProjection=!1)}this.renderPassEntries.pingPong.sort((t,i)=>t.element.renderOrder-i.element.renderOrder)}removePingPongPlane(e){e.renderBundle&&e.renderBundle.empty(),this.renderPassEntries.pingPong=this.renderPassEntries.pingPong.filter(t=>t.element.uuid!==e.uuid)}getObjectRenderPassEntry(e){if(e.type==="RenderTarget")return this.renderPassEntries.renderTarget.find(t=>t.renderPass.uuid===e.renderPass.uuid);if(e.type==="PingPongPlane")return this.renderPassEntries.pingPong.find(t=>t.element.uuid===e.uuid);if(e.type==="ShaderPass")return this.renderPassEntries.screen.find(t=>t.element?.uuid===e.uuid);{const t=e.outputTarget?"renderTarget":"screen";return e.renderBundle?this.renderPassEntries[t].find(i=>[...i.stack.unProjected.opaque,...i.stack.unProjected.transparent,...i.stack.projected.opaque,...i.stack.projected.transparent].filter(s=>s.type==="RenderBundle").some(s=>s.meshes.get(e.uuid))):this.renderPassEntries[t].find(i=>[...i.stack.unProjected.opaque,...i.stack.unProjected.transparent,...i.stack.projected.opaque,...i.stack.projected.transparent].some(s=>s.uuid===e.uuid))}}sortTransparentMeshes(e){e.sort((t,i)=>{if(t.renderOrder!==i.renderOrder||this.isStackObjectRenderBundle(t)||this.isStackObjectRenderBundle(i))return t.renderOrder-i.renderOrder;t.geometry?ns.copy(t.geometry.boundingBox.center).applyMat4(t.worldMatrix):t.worldMatrix.getTranslation(ns),i.geometry?os.copy(i.geometry.boundingBox.center).applyMat4(i.worldMatrix):i.worldMatrix.getTranslation(os);const s=t.geometry?t.geometry.boundingBox.radius*t.worldMatrix.getMaxScaleOnAxis():0,r=i.geometry?i.geometry.boundingBox.radius*i.worldMatrix.getMaxScaleOnAxis():0;return i.camera.worldMatrix.getTranslation(wo).distance(os)-r-(t.camera.worldMatrix.getTranslation(bo).distance(ns)-s)})}renderSinglePassEntry(e,t){const i=t.renderPass.updateView(t.renderTexture?.texture);t.onBeforeRenderPass&&t.onBeforeRenderPass(e,i);const s=e.beginRenderPass(t.renderPass.descriptor);if(this.renderer.production||s.pushDebugGroup(t.element?`${t.element.options.label} render pass using ${t.renderPass.options.label} descriptor`:`Render stack pass using ${t.renderPass.options.label}${t.renderTexture?" onto "+t.renderTexture.options.label:""}`),t.element)t.element.renderBundle?t.element.renderBundle.render(s):t.element.render(s);else if(t.stack){for(const r of t.stack.unProjected.opaque)r.render(s);for(const r of t.stack.unProjected.transparent)r.render(s);if(t.stack.projected.opaque.length||t.stack.projected.transparent.length){for(const r of t.stack.projected.opaque)r.render(s);this.sortTransparentMeshes(t.stack.projected.transparent);for(const r of t.stack.projected.transparent)r.render(s)}}this.renderer.production||s.popDebugGroup(),s.end(),t.onAfterRenderPass&&t.onAfterRenderPass(e,i),this.renderer.pipelineManager.resetCurrentPipeline()}onBeforeRender(){for(let e=0,t=this.renderer.meshes.length;e{this.getRenderPassEntryLength(s)&&(s.renderPass.setLoadOp(t==="screen"&&i!==0?"load":"clear"),i++,this.renderSinglePassEntry(e,s))})}}}var qr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Yr=(n,e,t)=>(qr(n,e,"read from private field"),t?t.call(n):e.get(n)),Bo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Mo=(n,e,t,i)=>(qr(n,e,"write to private field"),e.set(n,t),t),Co=(n,e,t,i)=>({set _(s){Mo(n,e,s)},get _(){return Yr(n,e,i)}}),oi;class ai{constructor(){Bo(this,oi,0),this.queue=[]}add(e=s=>{},{order:t=this.queue.length,once:i=!1}={}){const s={callback:e,order:t,once:i,id:Yr(this,oi)};return Co(this,oi)._++,this.queue.push(s),this.queue.sort((r,o)=>r.order-o.order),s.id}remove(e=0){this.queue=this.queue.filter(t=>t.id!==e)}execute(e){this.queue.forEach(t=>{t.callback(e),t.once&&this.remove(t.id)})}}oi=new WeakMap;class as{constructor({deviceManager:e,label:t,container:i,pixelRatio:s=1,autoResize:r=!0,context:o={},renderPass:a}){this._onBeforeRenderCallback=d=>{},this._onAfterRenderCallback=d=>{},this._onResizeCallback=()=>{},this._onAfterResizeCallback=()=>{},this.type="GPURenderer",this.uuid=H(),(!e||e.constructor.name!=="GPUDeviceManager")&&te(t?`${t} (${this.type}): no device manager or wrong device manager provided: ${e}`:`${this.type}: no device manager or wrong device manager provided: ${e}`),t||(t=`${this.constructor.name}${e.renderers.length}`),this.deviceManager=e,this.deviceManager.addRenderer(this),this.shouldRender=!0,this.shouldRenderScene=!0;const h={alphaMode:"premultiplied",format:this.deviceManager.gpu?.getPreferredCanvasFormat(),...o};a={useDepth:!0,sampleCount:4,clearValue:[0,0,0,0],...a},this.options={deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,context:h,renderPass:a},this.pixelRatio=s??window.devicePixelRatio??1;const u=i instanceof OffscreenCanvas,l=u||i instanceof HTMLCanvasElement;this.canvas=l?i:document.createElement("canvas");const{width:c,height:p}=this.canvas;this.rectBBox={width:c,height:p,top:0,left:0},this.setScene(),this.setTasksQueues(),this.setRendererObjects(),u||(this.domElement=new rs({element:i,priority:5,onSizeChanged:()=>{this.options.autoResize&&this.resize()}}),this.resize(),l||this.domElement.element.appendChild(this.canvas)),this.deviceManager.device&&this.setContext()}setSize(e=null){e={width:Math.max(1,this.boundingRect.width),height:Math.max(1,this.boundingRect.height),top:this.boundingRect.top,left:this.boundingRect.left,...e},this.rectBBox=e;const t={width:this.rectBBox.width,height:this.rectBBox.height};t.width*=this.pixelRatio,t.height*=this.pixelRatio,this.clampToMaxDimension(t),this.canvas.width=Math.floor(t.width),this.canvas.height=Math.floor(t.height),this.canvas.style&&(this.canvas.style.width=this.rectBBox.width+"px",this.canvas.style.height=this.rectBBox.height+"px")}setPixelRatio(e=1){this.pixelRatio=e,this.resize(this.rectBBox)}resize(e=null){this.setSize(e),this._onResizeCallback&&this._onResizeCallback(),this.resizeObjects(),this._onAfterResizeCallback&&this._onAfterResizeCallback()}resizeObjects(){this.textures.forEach(e=>{e.resize()}),this.renderPass?.resize(),this.postProcessingPass?.resize(),this.renderTargets.forEach(e=>e.resize()),this.computePasses.forEach(e=>e.resize()),this.pingPongPlanes.forEach(e=>e.resize(this.boundingRect)),this.shaderPasses.forEach(e=>e.resize(this.boundingRect)),this.resizeMeshes()}resizeMeshes(){this.meshes.forEach(e=>{e.resize(this.boundingRect)})}get boundingRect(){if(this.domElement&&this.domElement.boundingRect)return this.domElement.boundingRect;if(this.domElement){const e=this.domElement.element?.getBoundingClientRect();return{top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.width,height:e.height,x:e.x,y:e.y}}else return{top:this.rectBBox.top,right:this.rectBBox.left+this.rectBBox.width,bottom:this.rectBBox.top+this.rectBBox.height,left:this.rectBBox.left,width:this.rectBBox.width,height:this.rectBBox.height,x:this.rectBBox.left,y:this.rectBBox.top}}clampToMaxDimension(e){this.device&&(e.width=Math.min(this.device.limits.maxTextureDimension2D,e.width),e.height=Math.min(this.device.limits.maxTextureDimension2D,e.height))}get device(){return this.deviceManager.device}get ready(){return this.deviceManager.ready&&!!this.context&&!!this.canvas.width&&!!this.canvas.height}get production(){return this.deviceManager.production}get samplers(){return this.deviceManager.samplers}get buffers(){return this.deviceManager.buffers}get pipelineManager(){return this.deviceManager.pipelineManager}get deviceRenderedObjects(){return this.deviceManager.deviceRenderedObjects}configureContext(){this.context.configure({device:this.device,...this.options.context,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC|GPUTextureUsage.COPY_DST})}setContext(){this.context=this.canvas.getContext("webgpu"),this.device&&(this.configureContext(),this.setMainRenderPasses())}loseContext(){this.renderBundles.forEach(e=>e.loseContext()),this.renderedObjects.forEach(e=>e.loseContext())}restoreContext(){this.configureContext(),this.textures.forEach(e=>{e.createTexture()}),this.renderPass?.resize(),this.postProcessingPass?.resize(),this.renderTargets.forEach(e=>e.resize()),this.renderedObjects.forEach(e=>e.restoreContext())}setMainRenderPasses(){this.renderPass=new Kt(this,{label:this.options.label+" render pass",...this.options.renderPass}),this.scene.setMainRenderPassEntry(),this.postProcessingPass=new Kt(this,{label:this.options.label+" post processing render pass",useDepth:!1,sampleCount:1})}setScene(){this.scene=new jr({renderer:this})}createBuffer(e){const t=this.deviceManager.device?.createBuffer(e.options);return this.deviceManager.addBuffer(e),t}removeBuffer(e){this.deviceManager.removeBuffer(e)}queueWriteBuffer(e,t,i){this.deviceManager.device?.queue.writeBuffer(e,t,i)}copyBufferToBuffer({srcBuffer:e,dstBuffer:t,commandEncoder:i}){if(!e||!e.GPUBuffer)return L(`${this.options.label} (${this.type}): cannot copy to buffer because the source buffer has not been provided`),null;if(t||(t=new Ve),t.GPUBuffer||t.createBuffer(this,{label:`GPURenderer (${this.options.label}): destination copy buffer from: ${e.options.label}`,size:e.GPUBuffer.size,usage:["copyDst","mapRead"]}),e.GPUBuffer.mapState!=="unmapped"){L(`${this.options.label} (${this.type}): Cannot copy from ${e.GPUBuffer} because it is currently mapped`);return}if(t.GPUBuffer.mapState!=="unmapped"){L(`${this.options.label} (${this.type}): Cannot copy from ${t.GPUBuffer} because it is currently mapped`);return}const s=!!i;if(s||(i=this.deviceManager.device?.createCommandEncoder({label:`${this.type} (${this.options.label}): Copy buffer command encoder`}),!this.production&&i.pushDebugGroup(`${this.type} (${this.options.label}): Copy buffer command encoder`)),i.copyBufferToBuffer(e.GPUBuffer,0,t.GPUBuffer,0,t.GPUBuffer.size),!s){!this.production&&i.popDebugGroup();const r=i.finish();this.deviceManager.device?.queue.submit([r])}return t}get bindGroups(){return this.deviceManager.bindGroups}addBindGroup(e){this.deviceManager.addBindGroup(e)}removeBindGroup(e){this.deviceManager.removeBindGroup(e)}createBindGroupLayout(e){return this.deviceManager.device?.createBindGroupLayout(e)}createBindGroup(e){return this.deviceManager.device?.createBindGroup(e)}createShaderModule(e){return this.device?.createShaderModule(e)}createPipelineLayout(e){return this.device?.createPipelineLayout(e)}createRenderPipeline(e){return this.device?.createRenderPipeline(e)}async createRenderPipelineAsync(e){return await this.device?.createRenderPipelineAsync(e)}createComputePipeline(e){return this.device?.createComputePipeline(e)}async createComputePipelineAsync(e){return await this.device?.createComputePipelineAsync(e)}get domTextures(){return this.deviceManager.domTextures}addDOMTexture(e){this.deviceManager.addDOMTexture(e)}removeDOMTexture(e){this.deviceManager.removeDOMTexture(e)}addTexture(e){this.textures.push(e)}removeTexture(e){this.textures=this.textures.filter(t=>t.uuid!==e.uuid)}createTexture(e){return this.deviceManager.device?.createTexture(e)}uploadTexture(e){this.deviceManager.uploadTexture(e)}importExternalTexture(e){return this.deviceManager.device?.importExternalTexture({source:e})}createSampler(e){const t=this.samplers.find(i=>JSON.stringify(i.options)===JSON.stringify(e.options)&&i.sampler);if(t)return t.sampler;{const{type:i,...s}=e.options,r=this.deviceManager.device?.createSampler({label:e.label,...s});return this.deviceManager.addSampler(e),r}}removeSampler(e){this.deviceManager.removeSampler(e)}setTasksQueues(){this.onBeforeCommandEncoderCreation=new ai,this.onBeforeRenderScene=new ai,this.onAfterRenderScene=new ai,this.onAfterCommandEncoderSubmission=new ai}setRendererObjects(){this.computePasses=[],this.pingPongPlanes=[],this.shaderPasses=[],this.renderTargets=[],this.meshes=[],this.textures=[],this.renderBundles=[]}get renderedObjects(){return[...this.computePasses,...this.meshes,...this.shaderPasses,...this.pingPongPlanes]}getObjectsByBindGroup(e){return this.deviceRenderedObjects.filter(t=>[...t.material.bindGroups,...t.material.inputsBindGroups,...t.material.clonedBindGroups].some(i=>i.uuid===e.uuid))}getObjectsByTexture(e){return this.deviceRenderedObjects.filter(t=>[...t.material.domTextures,...t.material.textures].some(i=>i.uuid===e.uuid))}onBeforeRender(e){return e&&(this._onBeforeRenderCallback=e),this}onAfterRender(e){return e&&(this._onAfterRenderCallback=e),this}onResize(e){return e&&(this._onResizeCallback=e),this}onAfterResize(e){return e&&(this._onAfterResizeCallback=e),this}renderSingleComputePass(e,t){const i=e.beginComputePass();t.render(i),i.end(),t.copyBufferToResult(e)}renderSingleMesh(e,t){const i=e.beginRenderPass(this.renderPass.descriptor);t.render(i),i.end()}renderOnce(e){const t=this.device?.createCommandEncoder({label:"Render once command encoder"});!this.production&&t.pushDebugGroup("Render once command encoder"),this.pipelineManager.resetCurrentPipeline(),e.forEach(s=>{s.type==="ComputePass"?this.renderSingleComputePass(t,s):this.renderSingleMesh(t,s)}),!this.production&&t.popDebugGroup();const i=t.finish();this.device?.queue.submit([i]),this.pipelineManager.resetCurrentPipeline()}forceClear(e){const t=!!e;if(t||(e=this.device?.createCommandEncoder({label:`${this.type} (${this.options.label}): Force clear command encoder`}),!this.production&&e.pushDebugGroup(`${this.type} (${this.options.label}): Force clear command encoder`)),this.renderPass.updateView(),e.beginRenderPass(this.renderPass.descriptor).end(),!t){!this.production&&e.popDebugGroup();const s=e.finish();this.device?.queue.submit([s])}}onBeforeCommandEncoder(){this.ready&&(this.shouldRenderScene&&this.scene?.onBeforeRender(),this.onBeforeCommandEncoderCreation.execute())}onAfterCommandEncoder(){this.ready&&this.onAfterCommandEncoderSubmission.execute()}render(e){!this.ready||!this.shouldRender||(this._onBeforeRenderCallback&&this._onBeforeRenderCallback(e),this.onBeforeRenderScene.execute(e),this.shouldRenderScene&&this.scene?.render(e),this._onAfterRenderCallback&&this._onAfterRenderCallback(e),this.onAfterRenderScene.execute(e))}destroy(){this.deviceManager.renderers=this.deviceManager.renderers.filter(e=>e.uuid!==this.uuid),this.domElement?.destroy(),this.renderBundles.forEach(e=>e.destroy()),this.renderPass?.destroy(),this.postProcessingPass?.destroy(),this.renderTargets.forEach(e=>e.destroy()),this.renderedObjects.forEach(e=>e.remove()),this.textures.forEach(e=>e.destroy()),this.context?.unconfigure()}}var Hr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},So=(n,e,t)=>(Hr(n,e,"read from private field"),e.get(n)),To=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},hs=(n,e,t,i)=>(Hr(n,e,"write to private field"),e.set(n,t),t),Ye;class us extends as{constructor({deviceManager:e,label:t,container:i,pixelRatio:s=1,autoResize:r=!0,context:o={},renderPass:a,camera:h={},lights:u={}}){super({deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,context:o,renderPass:a}),To(this,Ye,void 0),this.type="GPUCameraRenderer",h={fov:50,near:.1,far:1e3,...h},u!==!1&&(u={maxAmbientLights:2,maxDirectionalLights:5,maxPointLights:5,...u}),this.options={...this.options,camera:h,lights:u},this.bindings={},hs(this,Ye,!0),this.lights=[],this.setCamera(h),this.setCameraBinding(),this.options.lights&&(this.setLightsBinding(),this.setShadowsBinding()),this.setCameraLightsBindGroup()}loseContext(){super.loseContext(),this.cameraLightsBindGroup.loseContext()}restoreContext(){super.restoreContext(),this.cameraLightsBindGroup?.restoreContext(),this.updateCameraBindings()}setCamera(e){const{width:t,height:i}=this.rectBBox;this.useCamera(new sr({fov:e.fov,near:e.near,far:e.far,width:t,height:i,pixelRatio:this.pixelRatio,onMatricesChanged:()=>{this.onCameraMatricesChanged()}}))}useCamera(e){if(!(this.camera&&e&&this.camera.uuid===e.uuid)&&(this.camera&&(this.camera.parent=null,this.camera.onMatricesChanged=()=>{}),this.camera=e,this.camera.parent=this.scene,this.bindings.camera)){this.camera.onMatricesChanged=()=>this.onCameraMatricesChanged(),this.bindings.camera.inputs.view.value=this.camera.viewMatrix,this.bindings.camera.inputs.projection.value=this.camera.projectionMatrix;for(const t of this.meshes)"modelViewMatrix"in t&&(t.camera=this.camera)}}onCameraMatricesChanged(){this.updateCameraBindings();for(const e of this.meshes)"modelViewMatrix"in e&&e.shouldUpdateProjectionMatrixStack()}setCameraBinding(){this.bindings.camera=new pe({label:"Camera",name:"camera",visibility:["vertex"],struct:{view:{type:"mat4x4f",value:this.camera.viewMatrix},projection:{type:"mat4x4f",value:this.camera.projectionMatrix},position:{type:"vec3f",value:this.camera.position.clone().setFromMatrixPosition(this.camera.worldMatrix),onBeforeUpdate:()=>{this.bindings.camera.inputs.position.value.copy(this.camera.position).setFromMatrixPosition(this.camera.worldMatrix)}}}})}addLight(e){this.lights.push(e),this.bindings[e.type].inputs.count.value++,this.bindings[e.type].inputs.count.shouldUpdate=!0}removeLight(e){this.lights=this.lights.filter(t=>t.uuid!==e.uuid),this.bindings[e.type].inputs.count.value--,this.bindings[e.type].inputs.count.shouldUpdate=!0}setLightsBinding(){if(!this.options.lights)return;this.lightsBindingParams={ambientLights:{max:this.options.lights.maxAmbientLights,label:"Ambient lights",params:{color:{type:"array",size:3}}},directionalLights:{max:this.options.lights.maxDirectionalLights,label:"Directional lights",params:{color:{type:"array",size:3},direction:{type:"array",size:3}}},pointLights:{max:this.options.lights.maxPointLights,label:"Point lights",params:{color:{type:"array",size:3},position:{type:"array",size:3},range:{type:"array",size:1}}}},Object.keys({ambientLights:null,directionalLights:null,pointLights:null}).forEach(t=>{this.setLightsTypeBinding(t)})}setLightsTypeBinding(e){const t=Object.keys(this.lightsBindingParams[e].params).map(i=>({key:i,type:this.lightsBindingParams[e].params[i].type,size:this.lightsBindingParams[e].params[i].size})).reduce((i,s)=>(i[s.key]={type:s.type,value:new Float32Array(Math.max(this.lightsBindingParams[e].max,1)*s.size)},i),{});this.bindings[e]=new pe({label:this.lightsBindingParams[e].label,name:e,bindingType:"storage",visibility:["vertex","fragment","compute"],struct:{count:{type:"i32",value:0},...t}})}onMaxLightOverflow(e){this.production||L(`${this.options.label} (${this.type}): You are overflowing the current max lights count of '${this.lightsBindingParams[e].max}' for this type of lights: ${e}. This should be avoided by setting a larger ${"max"+e.charAt(0).toUpperCase()+e.slice(1)} when instancing your ${this.type}.`),this.lightsBindingParams[e].max++;const t=this.cameraLightsBindGroup.getBindingByName(e);t&&this.cameraLightsBindGroup.destroyBufferBinding(t),this.setLightsTypeBinding(e);const i=this.cameraLightsBindGroup.bindings.findIndex(s=>s.name===e);if(i!==-1?this.cameraLightsBindGroup.bindings[i]=this.bindings[e]:(this.bindings[e].shouldResetBindGroup=!0,this.bindings[e].shouldResetBindGroupLayout=!0,this.cameraLightsBindGroup.addBinding(this.bindings[e]),this.shouldUpdateCameraLightsBindGroup()),e==="directionalLights"||e==="pointLights"){const s=e.replace("Lights","")+"Shadows",r=this.cameraLightsBindGroup.getBindingByName(s);r&&this.cameraLightsBindGroup.destroyBufferBinding(r),this.setShadowsTypeBinding(e);const o=this.cameraLightsBindGroup.bindings.findIndex(a=>a.name===s);o!==-1?this.cameraLightsBindGroup.bindings[o]=this.bindings[s]:(this.bindings[s].shouldResetBindGroup=!0,this.bindings[s].shouldResetBindGroupLayout=!0,this.cameraLightsBindGroup.addBinding(this.bindings[s]),this.shouldUpdateCameraLightsBindGroup())}this.cameraLightsBindGroup.resetEntries(),this.cameraLightsBindGroup.createBindGroup(),this.lights.forEach(s=>{s.type===e&&s.reset()})}get shadowCastingLights(){return this.lights.filter(e=>e.type==="directionalLights"||e.type==="pointLights")}setShadowsBinding(){this.shadowsBindingsStruct={directional:oo,point:co},this.setShadowsTypeBinding("directionalLights"),this.setShadowsTypeBinding("pointLights")}setShadowsTypeBinding(e){const t=e.replace("Lights",""),i=t+"Shadows",s=this.shadowsBindingsStruct[t],r=t.charAt(0).toUpperCase()+t.slice(1)+" shadows";this.bindings[i]=new pe({label:r,name:i,bindingType:"storage",visibility:["vertex","fragment","compute"],childrenBindings:[{binding:new pe({label:r+" element",name:i+"Elements",bindingType:"uniform",visibility:["vertex","fragment"],struct:s}),count:Math.max(1,this.lightsBindingParams[e].max),forceArray:!0}]})}setCameraLightsBindGroup(){this.cameraLightsBindGroup=new Dt(this,{label:this.options.label+": Camera and lights uniform bind group",bindings:Object.keys(this.bindings).map(e=>this.bindings[e]).flat()}),this.cameraLightsBindGroup.consumers.add(this.uuid)}setCameraBindGroup(){this.cameraLightsBindGroup&&this.cameraLightsBindGroup.shouldCreateBindGroup&&(this.cameraLightsBindGroup.setIndex(0),this.cameraLightsBindGroup.createBindGroup())}shouldUpdateCameraLightsBindGroup(){hs(this,Ye,!0)}updateCameraBindings(){this.bindings.camera?.shouldUpdateBinding("view"),this.bindings.camera?.shouldUpdateBinding("projection"),this.bindings.camera?.shouldUpdateBinding("position"),this.shouldUpdateCameraLightsBindGroup()}updateCameraLightsBindGroup(){this.cameraLightsBindGroup&&So(this,Ye)&&(this.cameraLightsBindGroup.update(),hs(this,Ye,!1))}getObjectsByBindGroup(e){return this.deviceRenderedObjects.filter(t=>[...t.material.bindGroups,...t.material.inputsBindGroups,...t.material.clonedBindGroups,this.cameraLightsBindGroup].some(i=>i.uuid===e.uuid))}setPerspective({fov:e,near:t,far:i}={}){this.camera?.setPerspective({fov:e,near:t,far:i,width:this.rectBBox.width,height:this.rectBBox.height,pixelRatio:this.pixelRatio})}setCameraPosition(e=new f(0,0,1)){this.camera.position.copy(e)}resize(e=null){this.setSize(e),this.setPerspective(),this._onResizeCallback&&this._onResizeCallback(),this.resizeObjects(),this._onAfterResizeCallback&&this._onAfterResizeCallback()}render(e){this.ready&&(this.setCameraBindGroup(),this.updateCameraLightsBindGroup(),super.render(e),this.cameraLightsBindGroup&&(this.cameraLightsBindGroup.needsPipelineFlush=!1))}destroy(){this.cameraLightsBindGroup?.destroy(),this.lights.forEach(e=>e.remove()),super.destroy()}}Ye=new WeakMap;class Xr{constructor({label:e,production:t=!1,adapterOptions:i={},onError:s=()=>{},onDeviceLost:r=o=>{}}={}){this.index=0,this.label=e??"GPUDeviceManager instance",this.production=t,this.ready=!1,this.adapterOptions=i,this.onError=s,this.onDeviceLost=r,this.gpu=navigator.gpu,this.setPipelineManager(),this.setDeviceObjects()}async setAdapterAndDevice({adapter:e=null,device:t=null}={}){await this.setAdapter(e),await this.setDevice(t)}async init({adapter:e=null,device:t=null}={}){if(await this.setAdapterAndDevice({adapter:e,device:t}),this.device)for(const i of this.renderers)i.context||i.setContext()}async setAdapter(e=null){if(this.gpu||(this.onError(),te("GPUDeviceManager: WebGPU is not supported on your browser/OS. No 'gpu' object in 'navigator'.")),e)this.adapter=e;else try{this.adapter=await this.gpu?.requestAdapter(this.adapterOptions),this.adapter||(this.onError(),te("GPUDeviceManager: WebGPU is not supported on your browser/OS. 'requestAdapter' failed."))}catch(t){this.onError(),te("GPUDeviceManager: "+t.message)}}async setDevice(e=null){if(e)this.device=e,this.ready=!0,this.index++;else try{const t=[];this.adapter.features.has("float32-filterable")&&t.push("float32-filterable"),this.device=await this.adapter?.requestDevice({label:this.label+" "+this.index,requiredFeatures:t}),this.device&&(this.ready=!0,this.index++)}catch(t){this.onError(),te(`${this.label}: WebGPU is not supported on your browser/OS. 'requestDevice' failed: ${t}`)}this.device?.lost.then(t=>{L(`${this.label}: WebGPU device was lost: ${t.message}`),this.loseDevice(),t.reason!=="destroyed"&&this.onDeviceLost(t)})}setPipelineManager(){this.pipelineManager=new Wr}loseDevice(){this.ready=!1,this.pipelineManager.resetCurrentPipeline(),this.samplers.forEach(e=>e.sampler=null),this.renderers.forEach(e=>e.loseContext()),this.bindGroupLayouts.clear(),this.buffers.clear()}async restoreDevice({adapter:e=null,device:t=null}={}){await this.setAdapterAndDevice({adapter:e,device:t}),this.device&&(this.samplers.forEach(i=>{const{type:s,...r}=i.options;i.sampler=this.device.createSampler({label:i.label,...r})}),this.renderers.forEach(i=>i.restoreContext()))}setDeviceObjects(){this.renderers=[],this.bindGroups=new Map,this.buffers=new Map,this.bindGroupLayouts=new Map,this.bufferBindings=new Map,this.samplers=[],this.domTextures=[],this.texturesQueue=[]}addRenderer(e){this.renderers.push(e)}removeRenderer(e){this.renderers=this.renderers.filter(t=>t.uuid!==e.uuid)}get deviceRenderedObjects(){return this.renderers.map(e=>e.renderedObjects).flat()}addBindGroup(e){this.bindGroups.set(e.uuid,e)}removeBindGroup(e){this.bindGroups.delete(e.uuid)}addBuffer(e){this.buffers.set(e.uuid,e)}removeBuffer(e){this.buffers.delete(e?.uuid)}addSampler(e){this.samplers.push(e)}removeSampler(e){this.samplers=this.samplers.filter(t=>t.uuid!==e.uuid)}addDOMTexture(e){this.domTextures.push(e)}uploadTexture(e){if(e.source)try{this.device?.queue.copyExternalImageToTexture({source:e.source,flipY:e.options.flipY},{texture:e.texture,premultipliedAlpha:e.options.premultipliedAlpha},{width:e.size.width,height:e.size.height}),e.texture.mipLevelCount>1&&Et(this.device,e.texture),this.texturesQueue.push(e)}catch({message:t}){te(`GPUDeviceManager: could not upload texture: ${e.options.name} because: ${t}`)}else this.device?.queue.writeTexture({texture:e.texture},new Uint8Array(e.options.placeholderColor),{bytesPerRow:e.size.width*4},{width:e.size.width,height:e.size.height})}removeDOMTexture(e){this.domTextures=this.domTextures.filter(t=>t.uuid!==e.uuid)}render(){if(!this.ready)return;for(const i of this.renderers)i.shouldRender&&i.onBeforeCommandEncoder();const e=this.device?.createCommandEncoder({label:this.label+" command encoder"});!this.production&&e.pushDebugGroup(this.label+" command encoder: main render loop"),this.renderers.forEach(i=>i.render(e)),!this.production&&e.popDebugGroup();const t=e.finish();this.device?.queue.submit([t]),this.domTextures.filter(i=>!i.parentMesh&&i.sourceLoaded&&!i.sourceUploaded).forEach(i=>this.uploadTexture(i));for(const i of this.texturesQueue)i.sourceUploaded=!0;this.texturesQueue=[];for(const i of this.renderers)i.shouldRender&&i.onAfterCommandEncoder()}destroy(){this.device?.destroy(),this.device=null,this.renderers.forEach(e=>e.destroy()),this.bindGroups.forEach(e=>e.destroy()),this.buffers.forEach(e=>e?.destroy()),this.domTextures.forEach(e=>e.destroy()),this.setDeviceObjects()}}var ls=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Kr=(n,e,t)=>(ls(n,e,"read from private field"),t?t.call(n):e.get(n)),Re=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},hi=(n,e,t,i)=>(ls(n,e,"write to private field"),e.set(n,t),t),ze=(n,e,t)=>(ls(n,e,"access private method"),t),yt,xt,ds,Zr,ui,cs,ps,Jr,fs,Qr,ms,en,li,gs;let Po=0;class Ro{constructor(e,{label:t="",renderPass:i=null,renderOrder:s=0,transparent:r=null,visible:o=!0,size:a=0,useBuffer:h=!1}={}){Re(this,ds),Re(this,ui),Re(this,ps),Re(this,fs),Re(this,ms),Re(this,li),Re(this,yt,void 0),Re(this,xt,void 0),this.type="RenderBundle",e=V(e,this.type),this.renderer=e,this.uuid=H(),Object.defineProperty(this,"index",{value:Po++}),this.renderOrder=s,this.renderer.renderBundles.push(this),this.transparent=r,this.visible=o,this.options={label:t,renderPass:i,useBuffer:h,size:a},this.meshes=new Map,this.encoder=null,this.bundle=null,hi(this,xt,!1),this.binding=null,this.options.useBuffer&&(hi(this,yt,!0),this.options.size!==0?ze(this,ds,Zr).call(this):(this.options.useBuffer=!1,this.renderer.production||L(`${this.options.label} (${this.type}): Cannot use a single transformation buffer if the size parameter has not been set upon creation.`)))}get useProjection(){return Kr(this,yt)}set useProjection(e){hi(this,yt,e)}set size(e){e!==this.options.size&&(this.ready&&!this.renderer.production&&L(`${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not change its size after it has been created.`),this.ready=!1,ze(this,ps,Jr).call(this,e),this.options.size=e)}get ready(){return Kr(this,xt)}set ready(e){e&&!this.ready?(this.size=this.meshes.size,ze(this,ms,en).call(this)):!e&&this.ready&&(this.bundle=null),hi(this,xt,e)}addMesh(e,t){if(!this.options.renderPass)this.options.renderPass=t;else if(t.uuid!==this.options.renderPass.uuid){L(`${this.options.label} (${this.type}): Cannot add Mesh ${e.options.label} to this render bundle because the output render passes do not match.`),e.renderBundle=null;return}this.ready&&!this.renderer.production&&L(`${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not add meshes to it after it has been created (mesh added: ${e.options.label}).`),this.ready=!1,this.meshes.set(e.uuid,e)}removeSceneObject(e){this.ready&&!this.renderer.production&&L(`${this.options.label} (${this.type}): The content of a render bundle is meant to be static. You should not remove meshes from it after it has been created (mesh removed: ${e.options.label}).`),this.ready=!1,this.meshes.delete(e.uuid),e.setRenderBundle(null,!1)}removeMesh(e,t=!0){this.removeSceneObject(e),t&&e.type!=="ShaderPass"&&e.type!=="PingPongPlane"&&this.renderer.scene.addMesh(e),this.meshes.size===0&&this.renderer.scene.removeRenderBundle(this)}updateBinding(){this.binding&&this.binding.shouldUpdate&&this.binding.buffer.GPUBuffer&&(this.renderer.queueWriteBuffer(this.binding.buffer.GPUBuffer,0,this.binding.arrayBuffer),this.binding.shouldUpdate=!1)}render(e){if(this.ready&&this.bundle&&this.visible&&(this.meshes.forEach(t=>{t.onBeforeRenderPass()}),this.updateBinding(),this.renderer.pipelineManager.resetCurrentPipeline(),this.renderer.production||e.pushDebugGroup(`${this.options.label}: execute bundle`),e.executeBundles([this.bundle]),this.renderer.production||e.popDebugGroup(),this.renderer.pipelineManager.resetCurrentPipeline(),this.meshes.forEach(t=>{t.onAfterRenderPass()})),!this.ready){let t=!0;for(const[i,s]of this.meshes)s.render(e),s.ready||(t=!1),"sourcesReady"in s&&!s.sourcesReady&&(t=!1);this.ready=t}}loseContext(){this.ready=!1}empty(e=!0){this.ready=!1,this.meshes.forEach(t=>{this.removeMesh(t,e)}),this.size=0}remove(){this.empty(!0),ze(this,li,gs).call(this)}destroy(){this.ready=!1,this.meshes.forEach(e=>{e.remove()}),this.size=0,ze(this,li,gs).call(this)}}yt=new WeakMap,xt=new WeakMap,ds=new WeakSet,Zr=function(){this.binding=new pe({label:this.options.label+" matrices",name:"matrices",struct:{model:{type:"array",value:new Float32Array(16*this.options.size)},modelView:{type:"array",value:new Float32Array(16*this.options.size)},normal:{type:"array",value:new Float32Array(12*this.options.size)}}}),ze(this,ui,cs).call(this,this.options.size)},ui=new WeakSet,cs=function(n){const e=this.renderer.device.limits.minUniformBufferOffsetAlignment;this.binding.arrayBufferSizethis.options.size&&this.binding&&(ze(this,ui,cs).call(this,n),this.binding.buffer.GPUBuffer)){this.binding.buffer.GPUBuffer.destroy(),this.binding.buffer.createBuffer(this.renderer,{label:this.binding.options.label,usage:["copySrc","copyDst",this.binding.bindingType,...this.binding.options.usage]});let e=0;this.meshes.forEach(t=>{t.patchRenderBundleBinding(e),e++}),this.binding.shouldUpdate=!0}},fs=new WeakSet,Qr=function(){this.descriptor={...this.options.renderPass.options.colorAttachments&&{colorFormats:this.options.renderPass.options.colorAttachments.map(n=>n.targetFormat)},...this.options.renderPass.options.useDepth&&{depthStencilFormat:this.options.renderPass.options.depthFormat},sampleCount:this.options.renderPass.options.sampleCount}},ms=new WeakSet,en=function(){ze(this,fs,Qr).call(this),this.renderer.pipelineManager.resetCurrentPipeline(),this.encoder=this.renderer.device.createRenderBundleEncoder({...this.descriptor,label:this.options.label+" (encoder)"}),this.renderer.production||this.encoder.pushDebugGroup(`${this.options.label}: create encoder`),this.meshes.forEach(n=>{n.material.render(this.encoder),n.geometry.render(this.encoder)}),this.renderer.production||this.encoder.popDebugGroup(),this.bundle=this.encoder.finish({label:this.options.label+" (bundle)"}),this.renderer.pipelineManager.resetCurrentPipeline()},li=new WeakSet,gs=function(){this.binding&&this.binding.buffer.destroy(),this.renderer.renderBundles=this.renderer.renderBundles.filter(n=>n.uuid!==this.uuid)};var zo=` struct VSOutput { @builtin(position) position: vec4f, @location(0) uv: vec2f, @@ -380,7 +407,7 @@ struct VSOutput { @fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { return textureSample(renderTexture, defaultSampler, fsInput.uv); -}`;class En extends Ci{constructor(e,t={}){e=j(e,t.label?t.label+" ShaderPass":"ShaderPass"),t.depth=!1;const i={color:{srcFactor:"one",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}};t.targets?t.targets&&t.targets.length&&!t.targets[0].blend&&(t.targets[0].blend=i):t.targets=[{blend:i}],t.label=t.label??"ShaderPass "+e.shaderPasses?.length,t.sampleCount=t.sampleCount?t.sampleCount:e&&e.postProcessingPass?e&&e.postProcessingPass.options.sampleCount:1,t.shaders||(t.shaders={}),t.shaders.fragment||(t.shaders.fragment={code:zn,entryPoint:"main"}),t.depth=!1,super(e,t),t.inputTarget&&this.setInputTarget(t.inputTarget),this.outputTarget&&this.setRenderingOptionsForRenderPass(this.outputTarget.renderPass),this.type="ShaderPass",this.createTexture({label:t.label?`${t.label} render texture`:"Shader pass render texture",name:"renderTexture",fromTexture:this.inputTarget?this.inputTarget.renderTexture:null,usage:["copySrc","copyDst","textureBinding"],...this.outputTarget&&this.outputTarget.options.qualityRatio&&{qualityRatio:this.outputTarget.options.qualityRatio}})}cleanupRenderMaterialParameters(e){return delete e.copyOutputToRenderTexture,delete e.inputTarget,super.cleanupRenderMaterialParameters(e),e}get renderTexture(){return this.textures.find(e=>e.options.name==="renderTexture")}setInputTarget(e){if(e&&e.type!=="RenderTarget"){I(`${this.options.label??this.type}: inputTarget is not a RenderTarget: ${e}`);return}this.removeFromScene(),this.inputTarget=e,this.addToScene(),this.renderTexture&&(e?this.renderTexture.copy(this.inputTarget.renderTexture):(this.renderTexture.options.fromTexture=null,this.renderTexture.createTexture()))}addToScene(e=!1){e&&this.renderer.shaderPasses.push(this),this.setRenderingOptionsForRenderPass(this.outputTarget?this.outputTarget.renderPass:this.renderer.postProcessingPass),this.autoRender&&this.renderer.scene.addShaderPass(this)}removeFromScene(e=!1){this.outputTarget&&this.outputTarget.destroy(),this.autoRender&&this.renderer.scene.removeShaderPass(this),e&&(this.renderer.shaderPasses=this.renderer.shaderPasses.filter(t=>t.uuid!==this.uuid))}}var Ln=` +}`;class Eo extends es{constructor(e,t={}){e=V(e,t.label?t.label+" ShaderPass":"ShaderPass"),t.depth=!1;const i={color:{srcFactor:"one",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}};t.targets?t.targets&&t.targets.length&&!t.targets[0].blend&&(t.targets[0].blend=i):t.targets=[{blend:i}],t.label=t.label??"ShaderPass "+e.shaderPasses?.length,t.sampleCount=t.sampleCount?t.sampleCount:e&&e.postProcessingPass?e&&e.postProcessingPass.options.sampleCount:1,t.shaders||(t.shaders={}),t.shaders.fragment||(t.shaders.fragment={code:zo,entryPoint:"main"}),t.depth=!1,super(e,t),t.inputTarget&&this.setInputTarget(t.inputTarget),this.outputTarget&&this.setRenderingOptionsForRenderPass(this.outputTarget.renderPass),this.type="ShaderPass",this.createTexture({label:t.label?`${t.label} render texture`:"Shader pass render texture",name:"renderTexture",fromTexture:this.inputTarget?this.inputTarget.renderTexture:null,usage:["copySrc","copyDst","textureBinding"],...this.outputTarget&&this.outputTarget.options.qualityRatio&&{qualityRatio:this.outputTarget.options.qualityRatio}})}cleanupRenderMaterialParameters(e){return delete e.copyOutputToRenderTexture,delete e.inputTarget,super.cleanupRenderMaterialParameters(e),e}get renderTexture(){return this.textures.find(e=>e.options.name==="renderTexture")}setInputTarget(e){if(e&&e.type!=="RenderTarget"){L(`${this.options.label??this.type}: inputTarget is not a RenderTarget: ${e}`);return}this.removeFromScene(),this.inputTarget=e,this.addToScene(),this.renderTexture&&(e?this.renderTexture.copy(this.inputTarget.renderTexture):(this.renderTexture.options.fromTexture=null,this.renderTexture.createTexture()))}addToScene(e=!1){e&&this.renderer.shaderPasses.push(this),this.setRenderingOptionsForRenderPass(this.outputTarget?this.outputTarget.renderPass:this.renderer.postProcessingPass),this.autoRender&&this.renderer.scene.addShaderPass(this)}removeFromScene(e=!1){this.outputTarget&&this.outputTarget.destroy(),this.autoRender&&this.renderer.scene.removeShaderPass(this),e&&(this.renderer.shaderPasses=this.renderer.shaderPasses.filter(t=>t.uuid!==this.uuid))}}var Lo=` fn lessThan3(a: vec3f, b: vec3f) -> vec3f { return vec3f(vec3(a.x < b.x, a.y < b.y, a.z < b.z)); } @@ -396,8 +423,8 @@ fn pow3( x: f32 ) -> f32 { fn pow4( x: f32 ) -> f32 { return pow2(x) * pow2(x); } -`,_n=` -${Ln} +`,_o=` +${Lo} struct ReflectedLight { directDiffuse: vec3f, @@ -448,7 +475,7 @@ fn getPointLightInfo(pointLight: PointLightsElement, worldPosition: vec3f, ptr_l (*ptr_light).color *= rangeAttenuation(pointLight.range, lightDistance); (*ptr_light).visible = length((*ptr_light).color) > 0.0001; } -`;const st=` +`;const vt=` fn linearToOutput3(value: vec3f) -> vec3f { return vec3( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThan3( value.rgb, vec3( 0.0031308 ) ) ) ) ); } @@ -489,7 +516,7 @@ fn toneMapKhronosPbrNeutral( color: vec3f ) -> vec3f { let g: f32 = 1. - 1. / (desaturation * (peak - newPeak) + 1.); return mix(toneMapColor, newPeak * vec3(1, 1, 1), g); } -`;var An=` +`;var Go=` fn getIndirectDiffuse(irradiance: vec3f, diffuseColor: vec3f, ptr_reflectedLight: ptr) { (*ptr_reflectedLight).indirectDiffuse += irradiance * BRDF_Lambert( diffuseColor ); } @@ -504,15 +531,15 @@ fn RE_IndirectDiffuse(irradiance: vec3f, diffuseColor: vec3f, ptr_reflectedLight getIndirectDiffuse(totalAmbientIrradiance, diffuseColor, ptr_reflectedLight); } -`,Gn=` +`,Ao=` const PI = ${Math.PI}; const RECIPROCAL_PI = ${1/Math.PI}; const RECIPROCAL_PI2 = ${.5/Math.PI}; -const EPSILON = 1e-6;`;const Dt=` -${Gn} -${_n} -${An} -`,dr=` +const EPSILON = 1e-6;`;const di=` +${Ao} +${_o} +${Go} +`,tn=` fn getLambertDirect( normal: vec3f, diffuseColor: vec3f, @@ -525,10 +552,10 @@ fn getLambertDirect( let irradiance: vec3f = NdotL * directLight.color; (*ptr_reflectedLight).directDiffuse += irradiance * BRDF_Lambert( diffuseColor ); } -`,cr=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` -${n?Dt:""} -${dr} -${t?st:""} +`,sn=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` +${n?di:""} +${tn} +${t?vt:""} fn getLambert( normal: vec3f, @@ -539,19 +566,19 @@ fn getLambert( var directLight: DirectLight; var reflectedLight: ReflectedLight; - ${e?He:""} + ${e?ut:""} // point lights for(var i = 0; i < pointLights.count; i++) { getPointLightInfo(pointLights.elements[i], worldPosition, &directLight); - ${e?Ke:""} + ${e?dt:""} getLambertDirect(normal, diffuseColor, directLight, &reflectedLight); } // directional lights for(var i = 0; i < directionalLights.count; i++) { getDirectionalLightInfo(directionalLights.elements[i], worldPosition, &directLight); - ${e?Ze:""} + ${e?lt:""} getLambertDirect(normal, diffuseColor, directLight, &reflectedLight); } @@ -570,7 +597,7 @@ fn getLambert( return outgoingLight; } -`,pr=` +`,rn=` fn D_BlinnPhong( shininess: f32, NdotH: f32 ) -> f32 { return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( NdotH, shininess ); } @@ -618,10 +645,10 @@ fn getPhongDirect( (*ptr_reflectedLight).directDiffuse += irradiance * BRDF_Lambert( diffuseColor ); (*ptr_reflectedLight).directSpecular += irradiance * BRDF_BlinnPhong( normal, viewDirection, specularColor, shininess, directLight ) * specularStrength; } -`,fr=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` -${n?Dt:""} -${pr} -${t?st:""} +`,nn=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` +${n?di:""} +${rn} +${t?vt:""} fn getPhong( normal: vec3f, @@ -636,19 +663,19 @@ fn getPhong( var directLight: DirectLight; var reflectedLight: ReflectedLight; - ${e?He:""} + ${e?ut:""} // point lights for(var i = 0; i < pointLights.count; i++) { getPointLightInfo(pointLights.elements[i], worldPosition, &directLight); - ${e?Ke:""} + ${e?dt:""} getPhongDirect(normal, diffuseColor, viewDirection, specularColor, specularStrength, shininess, directLight, &reflectedLight); } // directional lights for(var i = 0; i < directionalLights.count; i++) { getDirectionalLightInfo(directionalLights.elements[i], worldPosition, &directLight); - ${e?Ze:""} + ${e?lt:""} getPhongDirect(normal, diffuseColor, viewDirection, specularColor, specularStrength, shininess, directLight, &reflectedLight); } @@ -667,7 +694,7 @@ fn getPhong( return outgoingLight; } -`;var On=` +`;var Do=` fn DFGApprox( normal: vec3f, viewDirection: vec3f, @@ -743,10 +770,10 @@ fn RE_IndirectSpecular( (*ptr_reflectedLight).indirectDiffuse += diffuse * cosineWeightedIrradiance; } -`;const Gi=` -${Dt} -${On} -`,Oi=` +`;const ys=` +${di} +${Do} +`,xs=` fn DistributionGGX(NdotH: f32, roughness: f32) -> f32 { let a: f32 = pow2( roughness ); let a2: f32 = pow2( a ); @@ -807,10 +834,10 @@ fn getPBRDirect( (*ptr_reflectedLight).directDiffuse += irradiance * BRDF_Lambert( diffuseColor ); (*ptr_reflectedLight).directSpecular += ggx * irradiance; } -`,mr=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` -${n?Gi:""} -${Oi} -${t?st:""} +`,on=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` +${n?ys:""} +${xs} +${t?vt:""} fn getPBR( normal: vec3f, @@ -825,19 +852,19 @@ fn getPBR( var directLight: DirectLight; var reflectedLight: ReflectedLight; - ${e?He:""} + ${e?ut:""} // point lights for(var i = 0; i < pointLights.count; i++) { getPointLightInfo(pointLights.elements[i], worldPosition, &directLight); - ${e?Ke:""} + ${e?dt:""} getPBRDirect(normal, diffuseColor, viewDirection, f0, metallic, roughness, directLight, &reflectedLight); } // directional lights for(var i = 0; i < directionalLights.count; i++) { getDirectionalLightInfo(directionalLights.elements[i], worldPosition, &directLight); - ${e?Ze:""} + ${e?lt:""} getPBRDirect(normal, diffuseColor, viewDirection, f0, metallic, roughness, directLight, &reflectedLight); } @@ -860,7 +887,7 @@ fn getPBR( return outgoingLight; } -`,gr=` +`,an=` // struct IBLIndirect { // diffuse: vec3f, // specular: vec3f @@ -873,12 +900,10 @@ fn getIBLIndirect( metallic: f32, diffuseColor: vec3f, f0: vec3f, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ptr_reflectedLight: ptr, // ptr_iblIndirect: ptr ) { @@ -894,7 +919,7 @@ fn getIBLIndirect( let brdf: vec3f = textureSample( lutTexture, - lutSampler, + clampSampler, brdfSamplePoint ).rgb; @@ -907,16 +932,16 @@ fn getIBLIndirect( let specularLight: vec4f = textureSampleLevel( envSpecularTexture, - envSpecularSampler, - reflection, + clampSampler, + reflection * ibl.envRotation, lod ); // IBL diffuse (irradiance) let diffuseLight: vec4f = textureSample( envDiffuseTexture, - envDiffuseSampler, - normal + clampSampler, + normal * ibl.envRotation ); // product of specularFactor and specularTexture.a @@ -935,11 +960,11 @@ fn getIBLIndirect( // (*ptr_iblIndirect).diffuse = PI * diffuseLight.rgb * ibl.diffuseStrength; // (*ptr_iblIndirect).specular = specularLight.rgb * ibl.specularStrength; } -`,xr=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` -${n?Gi:""} -${Oi} -${gr} -${t?st:""} +`,hn=({addUtils:n=!0,receiveShadows:e=!1,toneMapping:t="linear",useOcclusion:i=!1}={})=>` +${n?ys:""} +${xs} +${an} +${t?vt:""} fn getIBL( normal: vec3f, @@ -949,30 +974,28 @@ fn getIBL( f0: vec3f, metallic: f32, roughness: f32, + clampSampler: sampler, lutTexture: texture_2d, - lutSampler: sampler, envSpecularTexture: texture_cube, - envSpecularSampler: sampler, envDiffuseTexture: texture_cube, - envDiffuseSampler: sampler, ${i?"occlusion: f32,":""} ) -> vec3f { var directLight: DirectLight; var reflectedLight: ReflectedLight; - ${e?He:""} + ${e?ut:""} // point lights for(var i = 0; i < pointLights.count; i++) { getPointLightInfo(pointLights.elements[i], worldPosition, &directLight); - ${e?Ke:""} + ${e?dt:""} getPBRDirect(normal, diffuseColor, viewDirection, f0, metallic, roughness, directLight, &reflectedLight); } // directional lights for(var i = 0; i < directionalLights.count; i++) { getDirectionalLightInfo(directionalLights.elements[i], worldPosition, &directLight); - ${e?Ze:""} + ${e?lt:""} getPBRDirect(normal, diffuseColor, viewDirection, f0, metallic, roughness, directLight, &reflectedLight); } @@ -989,12 +1012,10 @@ fn getIBL( metallic, diffuseColor, f0, + clampSampler, lutTexture, - lutSampler, envSpecularTexture, - envSpecularSampler, envDiffuseTexture, - envDiffuseSampler, &reflectedLight, // &iblIndirect ); @@ -1019,92 +1040,504 @@ fn getIBL( return outgoingLight; } -`;var yr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},xe=(n,e,t)=>(yr(n,e,"read from private field"),t?t.call(n):e.get(n)),Di=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Dn=(n,e,t,i)=>(yr(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),rt,ze,Ft;class vr extends Bi{constructor(e,t,i={}){super(e),Di(this,rt,new f),Di(this,ze,new f(1)),Di(this,Ft,1),this.boundingBox=new de(new f(-1),new f(1)),this._onAfterDOMElementResizeCallback=()=>{},e=si(e,"DOM3DObject"),this.renderer=e,this.size={shouldUpdate:!0,normalizedWorld:{size:new E(1),position:new E},cameraWorld:{size:new E(1)},scaledWorld:{size:new f(1),position:new f}},this.watchScroll=i.watchScroll,this.camera=this.renderer.camera,this.boundingBox.min.onChange(()=>this.shouldUpdateComputedSizes()),this.boundingBox.max.onChange(()=>this.shouldUpdateComputedSizes()),this.setDOMElement(t),this.renderer.domObjects.push(this)}setDOMElement(e){this.domElement=new Ri({element:e,onSizeChanged:t=>this.resize(t),onPositionChanged:()=>this.onPositionChanged()}),this.updateSizeAndPosition()}onPositionChanged(){this.watchScroll&&this.shouldUpdateComputedSizes()}resetDOMElement(e){this.domElement&&this.domElement.destroy(),this.setDOMElement(e)}resize(e=null){!e&&(!this.domElement||this.domElement?.isResizing)||(this.updateSizeAndPosition(),this._onAfterDOMElementResizeCallback&&this._onAfterDOMElementResizeCallback())}get boundingRect(){return this.domElement?.boundingRect??{width:1,height:1,top:0,right:0,bottom:0,left:0,x:0,y:0}}setTransforms(){super.setTransforms(),this.transforms.origin.model.set(.5,.5,0),this.transforms.origin.world=new f,this.transforms.position.document=new f,this.documentPosition.onChange(()=>this.applyPosition()),this.transformOrigin.onChange(()=>this.setWorldTransformOrigin())}get documentPosition(){return this.transforms.position.document}set documentPosition(e){this.transforms.position.document=e,this.applyPosition()}get DOMObjectWorldScale(){return xe(this,ze).clone()}get worldScale(){return this.DOMObjectWorldScale.multiply(this.scale)}get worldPosition(){return xe(this,rt).clone()}get transformOrigin(){return this.transforms.origin.model}set transformOrigin(e){this.transforms.origin.model=e,this.setWorldTransformOrigin()}get worldTransformOrigin(){return this.transforms.origin.world}set worldTransformOrigin(e){this.transforms.origin.world=e}shouldUpdateMatrices(){super.shouldUpdateMatrices(),(this.matricesNeedUpdate||this.size.shouldUpdate)&&(this.updateSizeAndPosition(),this.matricesNeedUpdate=!0),this.size.shouldUpdate=!1}shouldUpdateComputedSizes(){this.size.shouldUpdate=!0}updateSizeAndPosition(){this.setWorldSizes(),this.applyDocumentPosition(),this.shouldUpdateModelMatrix()}applyDocumentPosition(){let e=new f(0,0,0);this.documentPosition.equals(e)||(e=this.documentToWorldSpace(this.documentPosition)),xe(this,rt).set(this.position.x+this.size.scaledWorld.position.x+e.x,this.position.y+this.size.scaledWorld.position.y+e.y,this.position.z+this.size.scaledWorld.position.z+this.documentPosition.z/this.camera.CSSPerspective)}applyTransformOrigin(){this.size&&(this.setWorldTransformOrigin(),super.applyTransformOrigin())}updateModelMatrix(){this.modelMatrix.composeFromOrigin(xe(this,rt),this.quaternion,this.scale,this.worldTransformOrigin),this.modelMatrix.scale(this.DOMObjectWorldScale),this.shouldUpdateWorldMatrix()}documentToWorldSpace(e=new f){return new f(e.x*this.renderer.pixelRatio/this.renderer.boundingRect.width*this.camera.visibleSize.width,-(e.y*this.renderer.pixelRatio/this.renderer.boundingRect.height)*this.camera.visibleSize.height,e.z)}computeWorldSizes(){const e=this.renderer.boundingRect,t={x:this.boundingRect.width/2+this.boundingRect.left,y:this.boundingRect.height/2+this.boundingRect.top},i={x:e.width/2+e.left,y:e.height/2+e.top},{size:s,center:r}=this.boundingBox;s.x!==0&&s.y!==0&&s.z!==0&&r.divide(s),this.size.normalizedWorld.size.set(this.boundingRect.width/e.width,this.boundingRect.height/e.height),this.size.normalizedWorld.position.set((t.x-i.x)/e.width,(i.y-t.y)/e.height),this.size.cameraWorld.size.set(this.size.normalizedWorld.size.x*this.camera.visibleSize.width,this.size.normalizedWorld.size.y*this.camera.visibleSize.height),this.size.scaledWorld.size.set(this.size.cameraWorld.size.x/s.x,this.size.cameraWorld.size.y/s.y,1),this.size.scaledWorld.size.z=this.size.scaledWorld.size.y*(s.x/s.y/(this.boundingRect.width/this.boundingRect.height)),this.size.scaledWorld.position.set(this.size.normalizedWorld.position.x*this.camera.visibleSize.width,this.size.normalizedWorld.position.y*this.camera.visibleSize.height,0)}setWorldSizes(){this.computeWorldSizes(),this.setWorldScale(),this.setWorldTransformOrigin()}setWorldScale(){xe(this,ze).set(this.size.scaledWorld.size.x,this.size.scaledWorld.size.y,this.size.scaledWorld.size.z*xe(this,Ft)),this.shouldUpdateMatrixStack()}set DOMObjectDepthScaleRatio(e){Dn(this,Ft,e),this.setWorldScale()}setWorldTransformOrigin(){this.transforms.origin.world=new f((this.transformOrigin.x*2-1)*xe(this,ze).x,-(this.transformOrigin.y*2-1)*xe(this,ze).y,this.transformOrigin.z*xe(this,ze).z),this.shouldUpdateMatrixStack()}updateScrollPosition(e={x:0,y:0}){(e.x||e.y)&&this.domElement.updateScrollPosition(e)}onAfterDOMElementResize(e){return e&&(this._onAfterDOMElementResizeCallback=e),this}destroy(){super.destroy(),this.domElement?.destroy()}}rt=new WeakMap,ze=new WeakMap,Ft=new WeakMap;const wr={autoloadSources:!0,watchScroll:!0};class br extends tr(vr){constructor(e,t,i){super(e,t,{...wr,...i}),this._onLoadingCallback=r=>{},i={...wr,...i},si(e,i.label?i.label+" DOMMesh":"DOMMesh"),this.type="DOMMesh";const{autoloadSources:s}=i;this.autoloadSources=s,this.sourcesReady=!1,this.setInitSources()}get ready(){return this._ready}set ready(e){e&&!this._ready&&this.sourcesReady&&this._onReadyCallback&&this._onReadyCallback(),this._ready=e}get sourcesReady(){return this._sourcesReady}set sourcesReady(e){e&&!this._sourcesReady&&this.ready&&this._onReadyCallback&&this._onReadyCallback(),this._sourcesReady=e}addToScene(e=!1){super.addToScene(e),e&&this.renderer.domMeshes.push(this)}removeFromScene(e=!1){super.removeFromScene(e),e&&(this.renderer.domMeshes=this.renderer.domMeshes.filter(t=>t.uuid!==this.uuid))}setInitSources(){let e=0,t=0;if(this.autoloadSources){const i=this.domElement.element.querySelectorAll("img"),s=this.domElement.element.querySelectorAll("video"),r=this.domElement.element.querySelectorAll("canvas");e=i.length+s.length+r.length;const o=a=>{t++,this._onLoadingCallback&&this._onLoadingCallback(a),t===e&&(this.sourcesReady=!0)};e||(this.sourcesReady=!0),i.length&&i.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadImage(a.src)}),s.length&&s.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadVideo(a)}),r.length&&r.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadCanvas(a)})}else this.sourcesReady=!0}resetDOMElement(e){e?super.resetDOMElement(e):!e&&!this.renderer.production&&I(`${this.options.label}: You are trying to reset a ${this.type} with a HTML element that does not exist. The old HTML element will be kept instead.`)}get pixelRatioBoundingRect(){const e=window.devicePixelRatio??1,t=this.renderer.pixelRatio/e;return Object.keys(this.domElement.boundingRect).reduce((i,s)=>({...i,[s]:this.domElement.boundingRect[s]*t}),{x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0})}computeGeometry(){super.computeGeometry(),this.boundingBox.copy(this.geometry.boundingBox)}onLoading(e){return e&&(this._onLoadingCallback=e),this}}const Fn={label:"Plane",instancesCount:1,vertexBuffers:[]};class Mr extends br{constructor(e,t,i={}){e=si(e,i.label?i.label+" Plane":"Plane");const s={...Fn,...i};let{geometry:r,widthSegments:o,heightSegments:a,...h}=s;const{instancesCount:l,vertexBuffers:u,...d}=h;if(!r||r.type!=="PlaneGeometry"){o=o??1,a=a??1;const c=o*a+o;u.length||(r=At.getPlaneGeometryByID(c)),r?r.instancesCount=l:(r=new ci({widthSegments:o,heightSegments:a,instancesCount:l,vertexBuffers:u}),At.addPlaneGeometry(r))}super(e,t,{geometry:r,...d}),this.type="Plane"}mouseToPlaneCoords(e=new E){const t={x:2*(e.x/this.renderer.boundingRect.width)-1,y:2*(1-e.y/this.renderer.boundingRect.height)-1},i=this.camera.position.clone(),s=new f(t.x,t.y,-.5);s.unproject(this.camera),s.sub(i).normalize();const r=new f(0,0,1);r.applyQuat(this.quaternion).normalize();const o=new f(0,0,0),a=r.dot(s);if(Math.abs(a)>=1e-4){const h=this.worldMatrix.getInverse().premultiply(this.camera.viewMatrix),l=this.worldTransformOrigin.clone().add(this.worldPosition),u=new f(this.worldPosition.x-l.x,this.worldPosition.y-l.y,this.worldPosition.z-l.z);u.applyQuat(this.quaternion),l.add(u);const d=r.dot(l.clone().sub(i))/a;o.copy(i.add(s.multiplyScalar(d))),o.applyMat4(h)}else o.set(1/0,1/0,1/0);return new E(o.x,o.y)}}class Ut extends Ai{constructor({deviceManager:e,label:t,container:i,pixelRatio:s=1,autoResize:r=!0,preferredFormat:o,alphaMode:a="premultiplied",renderPass:h,camera:l,lights:u}){super({deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,preferredFormat:o,alphaMode:a,renderPass:h,camera:l,lights:u}),this.type="GPUCurtainsRenderer"}setRendererObjects(){super.setRendererObjects(),this.domMeshes=[],this.domObjects=[]}onCameraMatricesChanged(){super.onCameraMatricesChanged(),this.domObjects.forEach(e=>{e.updateSizeAndPosition()})}resizeMeshes(){this.meshes.forEach(e=>{"domElement"in e||e.resize(this.boundingRect)}),this.domObjects.forEach(e=>{e.domElement.isResizing||e.domElement.setSize()})}}class Un{constructor({scroll:e={x:0,y:0},delta:t={x:0,y:0},shouldWatch:i=!0,onScroll:s=(r={x:0,y:0})=>{}}={}){this.scroll=e,this.delta=t,this.shouldWatch=i,this.onScroll=s,this.shouldWatch&&window.addEventListener("scroll",this.setScroll.bind(this),{passive:!0})}setScroll(){this.updateScrollValues({x:window.pageXOffset,y:window.pageYOffset})}updateScrollValues({x:e,y:t}){const i=this.scroll;this.scroll={x:e,y:t},this.delta={x:i.x-this.scroll.x,y:i.y-this.scroll.y},this.onScroll&&this.onScroll(this.delta)}destroy(){this.shouldWatch&&window.removeEventListener("scroll",this.setScroll.bind(this),{passive:!0})}}class $n{constructor({container:e,label:t,pixelRatio:i=window.devicePixelRatio??1,preferredFormat:s,alphaMode:r="premultiplied",production:o=!1,adapterOptions:a={},renderPass:h,camera:l,lights:u,autoRender:d=!0,autoResize:c=!0,watchScroll:p=!0}={}){this._onRenderCallback=()=>{},this._onScrollCallback=()=>{},this._onErrorCallback=()=>{},this._onContextLostCallback=()=>{},this.type="CurtainsGPU",this.options={container:e,label:t,pixelRatio:i,camera:l,lights:u,production:o,adapterOptions:a,preferredFormat:s,alphaMode:r,renderPass:h,autoRender:d,autoResize:c,watchScroll:p},this.setDeviceManager(),e&&this.setContainer(e),this.initEvents(),this.options.autoRender&&this.animate()}setContainer(e){if(e)if(typeof e=="string")if(e=document.querySelector(e),e)this.options.container=e;else{const t=document.createElement("div");t.setAttribute("id","curtains-gpu-canvas"),document.body.appendChild(t),this.options.container=t}else e instanceof Element&&(this.options.container=e);else{const t=document.createElement("div");t.setAttribute("id","curtains-gpu-canvas"),document.body.appendChild(t),this.options.container=t}this.container=this.options.container,this.setMainRenderer()}setMainRenderer(){this.createCurtainsRenderer({deviceManager:this.deviceManager,label:this.options.label,container:this.options.container,pixelRatio:this.options.pixelRatio,autoResize:this.options.autoResize,preferredFormat:this.options.preferredFormat,alphaMode:this.options.alphaMode,renderPass:this.options.renderPass,camera:this.options.camera,lights:this.options.lights})}patchRendererOptions(e){return e.pixelRatio===void 0&&(e.pixelRatio=this.options.pixelRatio),e.autoResize===void 0&&(e.autoResize=this.options.autoResize),e}createRenderer(e){return e=this.patchRendererOptions(e),new Li({...e,deviceManager:this.deviceManager})}createCameraRenderer(e){return e=this.patchRendererOptions(e),new Ai({...e,deviceManager:this.deviceManager})}createCurtainsRenderer(e){return e=this.patchRendererOptions(e),new Ut({...e,deviceManager:this.deviceManager})}setDeviceManager(){this.deviceManager=new ur({label:"GPUCurtains default device",production:this.options.production,adapterOptions:this.options.adapterOptions,onError:()=>setTimeout(()=>{this._onErrorCallback&&this._onErrorCallback()},0),onDeviceLost:e=>this._onContextLostCallback&&this._onContextLostCallback(e)})}get renderers(){return this.deviceManager.renderers}get renderer(){return this.renderers[0]}async setDevice({adapter:e=null,device:t=null}={}){await this.deviceManager.init({adapter:e,device:t})}async restoreContext(){await this.deviceManager.restoreDevice()}get pingPongPlanes(){return this.renderers?.map(e=>e.pingPongPlanes).flat()}get shaderPasses(){return this.renderers?.map(e=>e.shaderPasses).flat()}get meshes(){return this.renderers?.map(e=>e.meshes).flat()}get domMeshes(){return this.renderers?.filter(e=>e instanceof Ut).map(e=>e.domMeshes).flat()}get domObjects(){return this.renderers?.filter(e=>e instanceof Ut).map(e=>e.domObjects).flat()}get planes(){return this.domMeshes.filter(e=>e instanceof Mr)}get computePasses(){return this.renderers?.map(e=>e.computePasses).flat()}get boundingRect(){return this.renderer?.boundingRect}initScroll(){this.scrollManager=new Un({scroll:{x:window.pageXOffset,y:window.pageYOffset},delta:{x:0,y:0},shouldWatch:this.options.watchScroll,onScroll:e=>this.updateScroll(e)})}updateScroll(e={x:0,y:0}){this.domObjects.forEach(t=>{t.domElement&&t.watchScroll&&t.updateScrollPosition(e)}),this._onScrollCallback&&this._onScrollCallback()}updateScrollValues(e={x:0,y:0}){this.scrollManager.updateScrollValues(e)}get scrollDelta(){return this.scrollManager.delta}get scrollValues(){return this.scrollManager.scroll}initEvents(){Ti.useObserver(this.options.autoResize),this.initScroll()}onRender(e){return e&&(this._onRenderCallback=e),this}onScroll(e){return e&&(this._onScrollCallback=e),this}onError(e){return e&&(this._onErrorCallback=e),this}onContextLost(e){return e&&(this._onContextLostCallback=e),this}animate(){this.render(),this.animationFrameID=window.requestAnimationFrame(this.animate.bind(this))}render(){this._onRenderCallback&&this._onRenderCallback(),this.deviceManager.render()}destroy(){this.animationFrameID&&window.cancelAnimationFrame(this.animationFrameID),this.deviceManager.destroy(),this.scrollManager?.destroy(),Ti.destroy()}}var Fi=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},M=(n,e,t)=>(Fi(n,e,"read from private field"),t?t.call(n):e.get(n)),U=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},be=(n,e,t,i)=>(Fi(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),L=(n,e,t)=>(Fi(n,e,"access private method"),t),k,ne,Me,F,Fe,Ue,nt,$e,$t,Ui,$i,Cr,ki,Br,kt,Ii,It,Vi,Vt,Ni,Nt,Wi,Wt,ji,jt,qi,qt,Yi,Yt,Xi,Ee,ke,Xt,Hi,Zi,Pr,Ki,Sr;const Ie=new E,Ve=new E,Ce=new f;class kn{constructor({camera:e,element:t=null,target:i=new f,enableZoom:s=!0,minZoom:r=0,maxZoom:o=1/0,zoomSpeed:a=1,enableRotate:h=!0,minPolarAngle:l=0,maxPolarAngle:u=Math.PI,minAzimuthAngle:d=-1/0,maxAzimuthAngle:c=1/0,rotateSpeed:p=1,enablePan:g=!0,panSpeed:m=1}){if(U(this,$t),U(this,$i),U(this,ki),U(this,kt),U(this,It),U(this,Vt),U(this,Nt),U(this,Wt),U(this,jt),U(this,qt),U(this,Yt),U(this,Ee),U(this,Xt),U(this,Zi),U(this,Ki),U(this,k,null),U(this,ne,new f),U(this,Me,!1),U(this,F,{radius:1,phi:0,theta:0}),U(this,Fe,new E),U(this,Ue,!1),U(this,nt,new E),U(this,$e,new f),!e){I("OrbitControls: cannot initialize without a camera.");return}this.camera=e,L(this,$t,Ui).call(this,{target:i,enableZoom:s,minZoom:r,maxZoom:o,zoomSpeed:a,enableRotate:h,minPolarAngle:l,maxPolarAngle:u,minAzimuthAngle:d,maxAzimuthAngle:c,rotateSpeed:p,enablePan:g,panSpeed:m}),M(this,ne).copy(this.camera.position).sub(this.target),M(this,F).radius=M(this,ne).length(),M(this,F).theta=Math.atan2(M(this,ne).x,M(this,ne).z),M(this,F).phi=Math.acos(Math.min(Math.max(M(this,ne).y/M(this,F).radius,-1),1)),this.camera.position.onChange(()=>{this.camera.lookAt(this.target)}),this.element=t??(typeof window<"u"?window:null),L(this,Ee,ke).call(this)}reset({position:e,target:t,enableZoom:i=this.enableZoom,minZoom:s=this.minZoom,maxZoom:r=this.maxZoom,zoomSpeed:o=this.zoomSpeed,enableRotate:a=this.enableRotate,minPolarAngle:h=this.minPolarAngle,maxPolarAngle:l=this.maxPolarAngle,minAzimuthAngle:u=this.minAzimuthAngle,maxAzimuthAngle:d=this.maxAzimuthAngle,rotateSpeed:c=this.rotateSpeed,enablePan:p=this.enablePan,panSpeed:g=this.panSpeed}={}){L(this,$t,Ui).call(this,{target:t,enableZoom:i,minZoom:s,maxZoom:r,zoomSpeed:o,enableRotate:a,minPolarAngle:h,maxPolarAngle:l,minAzimuthAngle:u,maxAzimuthAngle:d,rotateSpeed:c,enablePan:p,panSpeed:g}),e&&this.updatePosition(e)}updatePosition(e=new f){e.sub(this.target),M(this,F).radius=e.length(),M(this,F).theta=Math.atan2(e.x,e.z),M(this,F).phi=Math.acos(Math.min(Math.max(e.y/M(this,F).radius,-1),1)),L(this,Ee,ke).call(this)}set element(e){M(this,k)&&(!e||M(this,k)!==e)&&L(this,ki,Br).call(this),be(this,k,e),e&&L(this,$i,Cr).call(this)}get element(){return M(this,k)}destroy(){this.element=null}}k=new WeakMap,ne=new WeakMap,Me=new WeakMap,F=new WeakMap,Fe=new WeakMap,Ue=new WeakMap,nt=new WeakMap,$e=new WeakMap,$t=new WeakSet,Ui=function({target:n,enableZoom:e=this.enableZoom,minZoom:t=this.minZoom,maxZoom:i=this.maxZoom,zoomSpeed:s=this.zoomSpeed,enableRotate:r=this.enableRotate,minPolarAngle:o=this.minPolarAngle,maxPolarAngle:a=this.maxPolarAngle,minAzimuthAngle:h=this.minAzimuthAngle,maxAzimuthAngle:l=this.maxAzimuthAngle,rotateSpeed:u=this.rotateSpeed,enablePan:d=this.enablePan,panSpeed:c=this.panSpeed}={}){n&&(this.target=n),this.enableZoom=e,this.minZoom=t,this.maxZoom=i,this.zoomSpeed=s,this.enableRotate=r,this.minPolarAngle=o,this.maxPolarAngle=a,this.minAzimuthAngle=h,this.maxAzimuthAngle=l,this.rotateSpeed=u,this.enablePan=d,this.panSpeed=c},$i=new WeakSet,Cr=function(){M(this,k).addEventListener("contextmenu",L(this,Yt,Xi).bind(this),!1),M(this,k).addEventListener("mousedown",L(this,kt,Ii).bind(this),!1),M(this,k).addEventListener("mousemove",L(this,Vt,Ni).bind(this),!1),M(this,k).addEventListener("mouseup",L(this,Wt,ji).bind(this),!1),M(this,k).addEventListener("touchstart",L(this,It,Vi).bind(this),{passive:!1}),M(this,k).addEventListener("touchmove",L(this,Nt,Wi).bind(this),{passive:!1}),M(this,k).addEventListener("touchend",L(this,jt,qi).bind(this),!1),M(this,k).addEventListener("wheel",L(this,qt,Yi).bind(this),{passive:!1})},ki=new WeakSet,Br=function(){M(this,k).removeEventListener("contextmenu",L(this,Yt,Xi).bind(this),!1),M(this,k).removeEventListener("mousedown",L(this,kt,Ii).bind(this),!1),M(this,k).removeEventListener("mousemove",L(this,Vt,Ni).bind(this),!1),M(this,k).removeEventListener("mouseup",L(this,Wt,ji).bind(this),!1),M(this,k).removeEventListener("touchstart",L(this,It,Vi).bind(this),{passive:!1}),M(this,k).removeEventListener("touchmove",L(this,Nt,Wi).bind(this),{passive:!1}),M(this,k).removeEventListener("touchend",L(this,jt,qi).bind(this),!1),M(this,k).removeEventListener("wheel",L(this,qt,Yi).bind(this),{passive:!1})},kt=new WeakSet,Ii=function(n){n.button===0&&this.enableRotate?(be(this,Me,!0),M(this,Fe).set(n.clientX,n.clientY)):n.button===2&&this.enablePan&&(be(this,Ue,!0),M(this,nt).set(n.clientX,n.clientY)),n.stopPropagation(),n.preventDefault()},It=new WeakSet,Vi=function(n){n.touches.length===1&&this.enableRotate&&(be(this,Me,!0),M(this,Fe).set(n.touches[0].pageX,n.touches[0].pageY))},Vt=new WeakSet,Ni=function(n){M(this,Me)&&this.enableRotate?L(this,Xt,Hi).call(this,n.clientX,n.clientY):M(this,Ue)&&this.enablePan&&L(this,Zi,Pr).call(this,n.clientX,n.clientY)},Nt=new WeakSet,Wi=function(n){M(this,Me)&&this.enableRotate&&L(this,Xt,Hi).call(this,n.touches[0].pageX,n.touches[0].pageY)},Wt=new WeakSet,ji=function(n){be(this,Me,!1),be(this,Ue,!1)},jt=new WeakSet,qi=function(n){be(this,Me,!1),be(this,Ue,!1)},qt=new WeakSet,Yi=function(n){this.enableZoom&&(L(this,Ki,Sr).call(this,n.deltaY),n.preventDefault())},Yt=new WeakSet,Xi=function(n){n.preventDefault()},Ee=new WeakSet,ke=function(){const n=M(this,F).radius*Math.sin(Math.max(1e-6,M(this,F).phi));M(this,ne).x=n*Math.sin(M(this,F).theta),M(this,ne).y=M(this,F).radius*Math.cos(M(this,F).phi),M(this,ne).z=n*Math.cos(M(this,F).theta),this.camera.position.copy(this.target).add(M(this,ne))},Xt=new WeakSet,Hi=function(n,e){Ie.set(n,e),Ve.copy(Ie).sub(M(this,Fe)).multiplyScalar(this.rotateSpeed),M(this,F).theta-=2*Math.PI*Ve.x/this.camera.size.height,M(this,F).phi-=2*Math.PI*Ve.y/this.camera.size.height,M(this,F).theta=Math.min(this.maxAzimuthAngle,Math.max(this.minAzimuthAngle,M(this,F).theta)),M(this,F).phi=Math.min(this.maxPolarAngle,Math.max(this.minPolarAngle,M(this,F).phi)),M(this,Fe).copy(Ie),L(this,Ee,ke).call(this)},Zi=new WeakSet,Pr=function(n,e){Ie.set(n,e),Ve.copy(Ie).sub(M(this,nt)).multiplyScalar(this.panSpeed),M(this,$e).set(0),Ce.copy(this.camera.position).sub(this.target);let t=Ce.length();t*=Math.tan(this.camera.fov/2*Math.PI/180),Ce.set(this.camera.modelMatrix.elements[0],this.camera.modelMatrix.elements[1],this.camera.modelMatrix.elements[2]),Ce.multiplyScalar(-(2*Ve.x*t)/this.camera.size.height),M(this,$e).add(Ce),Ce.set(this.camera.modelMatrix.elements[4],this.camera.modelMatrix.elements[5],this.camera.modelMatrix.elements[6]),Ce.multiplyScalar(2*Ve.y*t/this.camera.size.height),M(this,$e).add(Ce),M(this,nt).copy(Ie),this.target.add(M(this,$e)),M(this,ne).copy(this.camera.position).sub(this.target),M(this,F).radius=M(this,ne).length(),L(this,Ee,ke).call(this)},Ki=new WeakSet,Sr=function(n){M(this,F).radius=Math.min(this.maxZoom,Math.max(this.minZoom+1e-6,M(this,F).radius+n*this.zoomSpeed/100)),L(this,Ee,ke).call(this)};class In extends Ye{constructor({instancesCount:e=1,vertexBuffers:t=[],topology:i,mapBuffersAtCreation:s=!0,widthSegments:r=1,heightSegments:o=1,depthSegments:a=1}={}){super({verticesOrder:"ccw",topology:i,instancesCount:e,vertexBuffers:t,mapBuffersAtCreation:s}),this.type="BoxGeometry",r=Math.floor(r),o=Math.floor(o),a=Math.floor(a);const h=[],l=[],u=[],d=[];let c=0;const p=(g,m,x,v,C,S,b,P,B,w)=>{const T=S/B,z=b/w,R=S/2,D=b/2,G=P/2,K=B+1,Q=w+1;let X=0;const _=new f;for(let W=0;W0?1:-1,u.push(_.x,_.y,_.z),l.push(H/B),l.push(W/w),X+=1}}for(let W=0;W0)&&v.push(w,T,R),(P!==o-1||c({targetFormat:s.format}));t.outputTarget=new fi(e,{label:t.label?t.label+" render target":"Ping Pong render target",useDepth:!1,...i&&{colorAttachments:i}}),t.transparent=!1,t.depth=!1,t.label=t.label??"PingPongPlane "+e.pingPongPlanes?.length,super(e,t),this.type="PingPongPlane",this.createTexture({label:t.label?`${t.label} render texture`:"PingPongPlane render texture",name:"renderTexture",...t.targets&&t.targets.length&&{format:t.targets[0].format},usage:["copyDst","textureBinding"]})}get renderTexture(){return this.textures.find(e=>e.options.name==="renderTexture")}addToScene(e=!1){e&&this.renderer.pingPongPlanes.push(this),this.autoRender&&this.renderer.scene.addPingPongPlane(this)}removeFromScene(e=!1){this.outputTarget&&this.outputTarget.destroy(),this.autoRender&&this.renderer.scene.removePingPongPlane(this),e&&(this.renderer.pingPongPlanes=this.renderer.pingPongPlanes.filter(t=>t.uuid!==this.uuid))}}var Tr=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Qi=(n,e,t)=>(Tr(n,e,"read from private field"),t?t.call(n):e.get(n)),Wn=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},jn=(n,e,t,i)=>(Tr(n,e,"write to private field"),i?i.call(n,t):e.set(n,t),t),Ne;const q=WebGLRenderingContext,Ht=new A,qn=class ve{constructor({renderer:e,gltf:t}){Wn(this,Ne,void 0),e=Le(e,"GLTFScenesManager"),this.renderer=e,this.gltf=t,jn(this,Ne,new Map);const i=s=>[s.node,...s.children?.map(r=>[...i(r)]).flat()].flat();this.scenesManager={node:new me,boundingBox:new de,samplers:[],materialsTextures:[],scenes:[],meshes:[],meshesDescriptors:[],getScenesNodes:()=>this.scenesManager.scenes.map(s=>i(s)).flat()},this.createSamplers(),this.createMaterialTextures(),this.createScenes()}static getVertexAttributeParamsFromType(e){switch(e){case"VEC2":return{type:"vec2f",bufferFormat:"float32x2",size:2};case"VEC3":return{type:"vec3f",bufferFormat:"float32x3",size:3};case"VEC4":return{type:"vec4f",bufferFormat:"float32x4",size:4};case"SCALAR":default:return{type:"f32",bufferFormat:"float32",size:1}}}static getTypedArrayConstructorFromComponentType(e){switch(e){case q.BYTE:return Int8Array;case q.UNSIGNED_BYTE:return Uint8Array;case q.SHORT:return Int16Array;case q.UNSIGNED_SHORT:return Uint16Array;case q.UNSIGNED_INT:return Uint32Array;case q.FLOAT:default:return Float32Array}}static gpuPrimitiveTopologyForMode(e){switch(e){case q.TRIANGLES:return"triangle-list";case q.TRIANGLE_STRIP:return"triangle-strip";case q.LINES:return"line-list";case q.LINE_STRIP:return"line-strip";case q.POINTS:return"point-list"}}static gpuAddressModeForWrap(e){switch(e){case q.CLAMP_TO_EDGE:return"clamp-to-edge";case q.MIRRORED_REPEAT:return"mirror-repeat";default:return"repeat"}}createSamplers(){if(this.gltf.samplers)for(const[e,t]of Object.entries(this.gltf.samplers)){const i={label:"glTF sampler "+e,name:"gltfSampler"+e,addressModeU:ve.gpuAddressModeForWrap(t.wrapS),addressModeV:ve.gpuAddressModeForWrap(t.wrapT)};switch((!t.magFilter||t.magFilter===q.LINEAR)&&(i.magFilter="linear"),t.minFilter){case q.NEAREST:break;case q.LINEAR:case q.LINEAR_MIPMAP_NEAREST:i.minFilter="linear";break;case q.NEAREST_MIPMAP_LINEAR:i.mipmapFilter="linear";break;case q.LINEAR_MIPMAP_LINEAR:default:i.minFilter="linear",i.mipmapFilter="linear";break}this.scenesManager.samplers.push(new Ae(this.renderer,i))}else this.scenesManager.samplers.push(new Ae(this.renderer,{label:"Default sampler",name:"defaultSampler",magFilter:"linear",minFilter:"linear",mipmapFilter:"linear"}))}createTexture(e,t,i){const s=(()=>{switch(i){case"baseColorTexture":case"emissiveTexture":return"bgra8unorm-srgb";case"occlusionTexture":return"r8unorm";default:return"bgra8unorm"}})(),r=new se(this.renderer,{label:e.name?e.name+": "+i:i,name:i,format:s,visibility:["fragment"],generateMips:!0,fixedSize:{width:t.width,height:t.height}});return r.uploadSource({source:t}),r}createMaterialTextures(){if(this.scenesManager.materialsTextures=[],this.gltf.materials)for(const[e,t]of Object.entries(this.gltf.materials)){const i={material:e,texturesDescriptors:[]},s=r=>r.texCoord&&r.texCoord!==0?"uv"+r.texCoord:"uv";if(this.scenesManager.materialsTextures[e]=i,t.pbrMetallicRoughness){if(t.pbrMetallicRoughness.baseColorTexture&&t.pbrMetallicRoughness.baseColorTexture.index!==void 0){const r=t.pbrMetallicRoughness.baseColorTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"baseColorTexture"),h=this.gltf.textures.find(l=>l.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.pbrMetallicRoughness.baseColorTexture)})}if(t.pbrMetallicRoughness.metallicRoughnessTexture&&t.pbrMetallicRoughness.metallicRoughnessTexture.index!==void 0){const r=t.pbrMetallicRoughness.metallicRoughnessTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"metallicRoughnessTexture"),h=this.gltf.textures.find(l=>l.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.pbrMetallicRoughness.metallicRoughnessTexture)})}}if(t.normalTexture&&t.normalTexture.index!==void 0){const r=t.normalTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"normalTexture"),h=this.gltf.textures.find(l=>l.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.normalTexture)})}if(t.occlusionTexture&&t.occlusionTexture.index!==void 0){const r=t.occlusionTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"occlusionTexture"),h=this.gltf.textures.find(l=>l.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.occlusionTexture)})}if(t.emissiveTexture&&t.emissiveTexture.index!==void 0){const r=t.emissiveTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"emissiveTexture"),h=this.gltf.textures.find(l=>l.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.emissiveTexture)})}}}createNode(e,t){if(t.camera!==void 0)return;const i={name:t.name,node:new me,children:[]};e.children.push(i),i.node.parent=e.node,t.matrix?(i.node.modelMatrix.setFromArray(new Float32Array(t.matrix)),i.node.matrices.model.shouldUpdate=!1):(t.translation&&i.node.position.set(t.translation[0],t.translation[1],t.translation[2]),t.scale&&i.node.scale.set(t.scale[0],t.scale[1],t.scale[2]),t.rotation&&i.node.quaternion.setFromArray(new Float32Array(t.rotation)));const s=this.gltf.meshes[t.mesh];t.children&&t.children.forEach(r=>{const o=this.gltf.nodes[r];this.createNode(i,o)}),s&&s.primitives.forEach((r,o)=>{const a={parent:i.node,attributes:[],textures:[],parameters:{label:s.name?s.name+" "+o:"glTF mesh "+o},nodes:[]};let h=Qi(this,Ne).get(r);h||(h={instances:[],nodes:[],meshDescriptor:a},Qi(this,Ne).set(r,h)),h.instances.push(t),h.nodes.push(i.node)})}createScenes(){this.scenesManager.node.parent=this.renderer.scene,this.gltf.scenes.forEach(e=>{const t={name:e.name,children:[],node:new me};t.node.parent=this.scenesManager.node,this.scenesManager.scenes.push(t),e.nodes.forEach(i=>{const s=this.gltf.nodes[i];this.createNode(t,s)})}),this.scenesManager.node.updateMatrixStack();for(const[e,t]of Qi(this,Ne)){const{instances:i,nodes:s,meshDescriptor:r}=t,o=i.length;r.nodes=s,this.scenesManager.meshesDescriptors.push(r);const a=new de,h=[];let l=null,u=null,d=0;for(const[C,S]of Object.entries(e.attributes)){const b=this.gltf.accessors[S],P=ve.getTypedArrayConstructorFromComponentType(b.componentType),B=this.gltf.bufferViews[b.bufferView],w=C==="TEXCOORD_0"?"uv":C.replace("_","").replace("TEXCOORD","uv").toLowerCase(),T=B.byteStride||0,z=b.byteOffset||0;T&&z&&z0){const C=Object.values(e.attributes).map(S=>this.gltf.accessors[S].bufferView);if(C.every(S=>S===C[0]))l=new Float32Array(this.gltf.arrayBuffers[u.buffer],u.byteOffset,Math.ceil(u.byteLength/4)*4/Float32Array.BYTES_PER_ELEMENT);else{let S=0;const b={},P=Object.values(e.attributes).reduce((B,w)=>{const T=this.gltf.accessors[w],z=ve.getVertexAttributeParamsFromType(T.type).size;return b[T.bufferView]||(b[T.bufferView]=0),b[T.bufferView]=Math.max(b[T.bufferView],T.byteOffset+z*Float32Array.BYTES_PER_ELEMENT),S+=z*Float32Array.BYTES_PER_ELEMENT,B+T.count*z},0);l=new Float32Array(Math.ceil(P/4)*4),Object.values(e.attributes).forEach(B=>{const w=this.gltf.accessors[B],T=this.gltf.bufferViews[w.bufferView],z=ve.getVertexAttributeParamsFromType(w.type).size;for(let R=0;R{let P=C.findIndex(w=>w===S.name);P=P===-1?1/0:P;let B=C.findIndex(w=>w===b.name);return B=B===-1?1/0:B,P-B})}const c={instancesCount:o,topology:ve.gpuPrimitiveTopologyForMode(e.mode),vertexBuffers:[{name:"attributes",stepMode:"vertex",attributes:h,...l&&{array:l}}]},p="indices"in e,g=p?Ye:di;if(r.parameters.geometry=new g(c),r.parameters.geometry.boundingBox=a,p){const C=this.gltf.accessors[e.indices],S=this.gltf.bufferViews[C.bufferView],b=ve.getTypedArrayConstructorFromComponentType(C.componentType),P=C.byteOffset+S.byteOffset,B=this.gltf.arrayBuffers[S.buffer],w=Math.min((B.byteLength-P)/b.BYTES_PER_ELEMENT,Math.ceil(C.count/4)*4),T=b.name==="Uint8Array"?Uint16Array.from(new b(B,P,w)):new b(B,P,w);r.parameters.geometry.setIndexBuffer({bufferFormat:b.name==="Uint32Array"?"uint32":"uint16",array:T})}const m=this.scenesManager.materialsTextures[e.material];r.parameters.samplers=[],r.parameters.textures=[],m?.texturesDescriptors.forEach(C=>{r.textures.push({texture:C.texture.options.name,sampler:C.sampler.name,texCoordAttributeName:C.texCoordAttributeName}),r.parameters.samplers.find(b=>b.uuid===C.sampler.uuid)||r.parameters.samplers.push(C.sampler),r.parameters.textures.push(C.texture)});const x=this.gltf.materials&&this.gltf.materials[e.material]||{};r.parameters.cullMode=x.doubleSided?"none":"back",(x.alphaMode==="BLEND"||x.extensions&&x.extensions.KHR_materials_transmission)&&(r.parameters.transparent=!0,r.parameters.targets=[{blend:{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one"}}}]);const v={baseColorFactor:{type:"vec4f",value:x.pbrMetallicRoughness?.baseColorFactor||[1,1,1,1]},alphaCutoff:{type:"f32",value:x.alphaCutoff!==void 0?x.alphaCutoff:x.alphaMode==="MASK"?.5:0},metallicFactor:{type:"f32",value:x.pbrMetallicRoughness?.metallicFactor===void 0?1:x.pbrMetallicRoughness.metallicFactor},roughnessFactor:{type:"f32",value:x.pbrMetallicRoughness?.roughnessFactor===void 0?1:x.pbrMetallicRoughness.roughnessFactor},normalMapScale:{type:"f32",value:x.normalTexture?.scale===void 0?1:x.normalTexture.scale},occlusionStrength:{type:"f32",value:x.occlusionTexture?.strength===void 0?1:x.occlusionTexture.strength},emissiveFactor:{type:"vec3f",value:x.emissiveFactor!==void 0?x.emissiveFactor:[1,1,1]}};if(Object.keys(v).length&&(r.parameters.uniforms={material:{visibility:["vertex","fragment"],struct:v}}),o>1){const C=new Float32Array(o*16),S=new Float32Array(o*16);for(let b=0;b",value:C},normalMatrix:{type:"array",value:S}}}}}for(let C=0;C{}){return this.scenesManager.node.updateMatrixStack(),this.scenesManager.meshesDescriptors.map(t=>{if(t.parameters.geometry){e(t);const i=t.parameters.geometry.instancesCount>1&&t.parameters.castShadows;i&&(t.parameters.castShadows=!1);const s=new ir(this.renderer,{...t.parameters});if(t.nodes.length>1){const r=s.updateWorldMatrix.bind(s);s.updateWorldMatrix=()=>{r(),t.nodes.forEach((o,a)=>{s.storages.instances.modelMatrix.value.set(o.worldMatrix.elements,a*16),Ht.copy(o.worldMatrix).invert().transpose(),s.storages.instances.normalMatrix.value.set(Ht.elements,a*16)}),s.storages.instances.modelMatrix.shouldUpdate=!0,s.storages.instances.normalMatrix.shouldUpdate=!0}}if(i){const r=s.material.inputsBindings.get("instances");this.renderer.shadowCastingLights.forEach(o=>{o.shadow.isActive&&o.shadow.addShadowCastingMesh(s,{bindings:[r]})})}return s.parent=t.parent,this.scenesManager.meshes.push(s),s}})}destroy(){this.scenesManager.meshes.forEach(t=>t.remove()),this.scenesManager.meshes=[],this.scenesManager.getScenesNodes().forEach(t=>{t.destroy()}),this.scenesManager.node.destroy()}};Ne=new WeakMap;let Yn=qn;const Xn=(n,e={})=>{const t=n.textures.find(O=>O.texture==="baseColorTexture"),i=n.textures.find(O=>O.texture==="normalTexture"),s=n.textures.find(O=>O.texture==="emissiveTexture"),r=n.textures.find(O=>O.texture==="occlusionTexture"),o=n.textures.find(O=>O.texture==="metallicRoughnessTexture"),a=n.attributes.filter(O=>O.name!=="position"),h=a.map((O,oo)=>`@location(${oo}) ${O.name}: ${O.type},`).join(` - `);let l=` +`;var un=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Me=(n,e,t)=>(un(n,e,"read from private field"),t?t.call(n):e.get(n)),vs=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Oo=(n,e,t,i)=>(un(n,e,"write to private field"),e.set(n,t),t),bt,ke,ci;class ln extends ts{constructor(e,t,i={}){super(e),vs(this,bt,new f),vs(this,ke,new f(1)),vs(this,ci,1),this.boundingBox=new be(new f(-1),new f(1)),this._onAfterDOMElementResizeCallback=()=>{},e=Gi(e,"DOM3DObject"),this.renderer=e,this.size={shouldUpdate:!0,normalizedWorld:{size:new _(1),position:new _},cameraWorld:{size:new _(1)},scaledWorld:{size:new f(1),position:new f}},this.watchScroll=i.watchScroll,this.camera=this.renderer.camera,this.boundingBox.min.onChange(()=>this.shouldUpdateComputedSizes()),this.boundingBox.max.onChange(()=>this.shouldUpdateComputedSizes()),this.setDOMElement(t),this.renderer.domObjects.push(this)}setDOMElement(e){this.domElement=new rs({element:e,onSizeChanged:t=>this.resize(t),onPositionChanged:()=>this.onPositionChanged()}),this.updateSizeAndPosition()}onPositionChanged(){this.watchScroll&&this.shouldUpdateComputedSizes()}resetDOMElement(e){this.domElement&&this.domElement.destroy(),this.setDOMElement(e)}resize(e=null){!e&&(!this.domElement||this.domElement?.isResizing)||(this.updateSizeAndPosition(),this._onAfterDOMElementResizeCallback&&this._onAfterDOMElementResizeCallback())}get boundingRect(){return this.domElement?.boundingRect??{width:1,height:1,top:0,right:0,bottom:0,left:0,x:0,y:0}}setTransforms(){super.setTransforms(),this.transforms.origin.model.set(.5,.5,0),this.transforms.origin.world=new f,this.transforms.position.document=new f,this.documentPosition.onChange(()=>this.applyPosition()),this.transformOrigin.onChange(()=>this.setWorldTransformOrigin())}get documentPosition(){return this.transforms.position.document}set documentPosition(e){this.transforms.position.document=e,this.applyPosition()}get DOMObjectWorldScale(){return Me(this,ke).clone()}get worldScale(){return this.DOMObjectWorldScale.multiply(this.scale)}get worldPosition(){return Me(this,bt).clone()}get transformOrigin(){return this.transforms.origin.model}set transformOrigin(e){this.transforms.origin.model=e,this.setWorldTransformOrigin()}get worldTransformOrigin(){return this.transforms.origin.world}set worldTransformOrigin(e){this.transforms.origin.world=e}shouldUpdateMatrices(){super.shouldUpdateMatrices(),(this.matricesNeedUpdate||this.size.shouldUpdate)&&(this.updateSizeAndPosition(),this.matricesNeedUpdate=!0),this.size.shouldUpdate=!1}shouldUpdateComputedSizes(){this.size.shouldUpdate=!0}updateSizeAndPosition(){this.setWorldSizes(),this.applyDocumentPosition(),this.shouldUpdateModelMatrix()}applyDocumentPosition(){let e=new f(0,0,0);this.documentPosition.equals(e)||(e=this.documentToWorldSpace(this.documentPosition)),Me(this,bt).set(this.position.x+this.size.scaledWorld.position.x+e.x,this.position.y+this.size.scaledWorld.position.y+e.y,this.position.z+this.size.scaledWorld.position.z+this.documentPosition.z/this.camera.CSSPerspective)}applyTransformOrigin(){this.size&&(this.setWorldTransformOrigin(),super.applyTransformOrigin())}updateModelMatrix(){this.modelMatrix.composeFromOrigin(Me(this,bt),this.quaternion,this.scale,this.worldTransformOrigin),this.modelMatrix.scale(this.DOMObjectWorldScale),this.shouldUpdateWorldMatrix()}documentToWorldSpace(e=new f){return new f(e.x*this.renderer.pixelRatio/this.renderer.boundingRect.width*this.camera.visibleSize.width,-(e.y*this.renderer.pixelRatio/this.renderer.boundingRect.height)*this.camera.visibleSize.height,e.z)}computeWorldSizes(){const e=this.renderer.boundingRect,t={x:this.boundingRect.width/2+this.boundingRect.left,y:this.boundingRect.height/2+this.boundingRect.top},i={x:e.width/2+e.left,y:e.height/2+e.top},{size:s,center:r}=this.boundingBox;s.x!==0&&s.y!==0&&s.z!==0&&r.divide(s),this.size.normalizedWorld.size.set(this.boundingRect.width/e.width,this.boundingRect.height/e.height),this.size.normalizedWorld.position.set((t.x-i.x)/e.width,(i.y-t.y)/e.height),this.size.cameraWorld.size.set(this.size.normalizedWorld.size.x*this.camera.visibleSize.width,this.size.normalizedWorld.size.y*this.camera.visibleSize.height),this.size.scaledWorld.size.set(this.size.cameraWorld.size.x/s.x,this.size.cameraWorld.size.y/s.y,1),this.size.scaledWorld.size.z=this.size.scaledWorld.size.y*(s.x/s.y/(this.boundingRect.width/this.boundingRect.height)),this.size.scaledWorld.position.set(this.size.normalizedWorld.position.x*this.camera.visibleSize.width,this.size.normalizedWorld.position.y*this.camera.visibleSize.height,0)}setWorldSizes(){this.computeWorldSizes(),this.setWorldScale(),this.setWorldTransformOrigin()}setWorldScale(){Me(this,ke).set(this.size.scaledWorld.size.x,this.size.scaledWorld.size.y,this.size.scaledWorld.size.z*Me(this,ci)),this.shouldUpdateMatrixStack()}set DOMObjectDepthScaleRatio(e){Oo(this,ci,e),this.setWorldScale()}setWorldTransformOrigin(){this.transforms.origin.world=new f((this.transformOrigin.x*2-1)*Me(this,ke).x,-(this.transformOrigin.y*2-1)*Me(this,ke).y,this.transformOrigin.z*Me(this,ke).z),this.shouldUpdateMatrixStack()}updateScrollPosition(e={x:0,y:0}){(e.x||e.y)&&this.domElement.updateScrollPosition(e)}onAfterDOMElementResize(e){return e&&(this._onAfterDOMElementResizeCallback=e),this}destroy(){super.destroy(),this.domElement?.destroy()}}bt=new WeakMap,ke=new WeakMap,ci=new WeakMap;const dn={autoloadSources:!0,watchScroll:!0};class cn extends Vr(ln){constructor(e,t,i){super(e,t,{...dn,...i}),this._onLoadingCallback=r=>{},i={...dn,...i},Gi(e,i.label?i.label+" DOMMesh":"DOMMesh"),this.type="DOMMesh";const{autoloadSources:s}=i;this.autoloadSources=s,this.sourcesReady=!1,this.setInitSources()}get ready(){return this._ready}set ready(e){e&&!this._ready&&this.sourcesReady&&this._onReadyCallback&&this._onReadyCallback(),this._ready=e}get sourcesReady(){return this._sourcesReady}set sourcesReady(e){e&&!this._sourcesReady&&this.ready&&this._onReadyCallback&&this._onReadyCallback(),this._sourcesReady=e}addToScene(e=!1){super.addToScene(e),e&&this.renderer.domMeshes.push(this)}removeFromScene(e=!1){super.removeFromScene(e),e&&(this.renderer.domMeshes=this.renderer.domMeshes.filter(t=>t.uuid!==this.uuid))}setInitSources(){let e=0,t=0;if(this.autoloadSources){const i=this.domElement.element.querySelectorAll("img"),s=this.domElement.element.querySelectorAll("video"),r=this.domElement.element.querySelectorAll("canvas");e=i.length+s.length+r.length;const o=a=>{t++,this._onLoadingCallback&&this._onLoadingCallback(a),t===e&&(this.sourcesReady=!0)};e||(this.sourcesReady=!0),i.length&&i.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadImage(a.src)}),s.length&&s.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadVideo(a)}),r.length&&r.forEach(a=>{const h=this.createDOMTexture({name:a.getAttribute("data-texture-name")??"texture"+this.domTextures.length});h.onSourceUploaded(()=>o(h)).loadCanvas(a)})}else this.sourcesReady=!0}resetDOMElement(e){e?super.resetDOMElement(e):!e&&!this.renderer.production&&L(`${this.options.label}: You are trying to reset a ${this.type} with a HTML element that does not exist. The old HTML element will be kept instead.`)}get pixelRatioBoundingRect(){const e=window.devicePixelRatio??1,t=this.renderer.pixelRatio/e;return Object.keys(this.domElement.boundingRect).reduce((i,s)=>({...i,[s]:this.domElement.boundingRect[s]*t}),{x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0})}computeGeometry(){super.computeGeometry(),this.boundingBox.copy(this.geometry.boundingBox)}onLoading(e){return e&&(this._onLoadingCallback=e),this}}const Fo={label:"Plane",instancesCount:1,vertexBuffers:[]};class pn extends cn{constructor(e,t,i={}){e=Gi(e,i.label?i.label+" Plane":"Plane");const s={...Fo,...i};let{geometry:r,widthSegments:o,heightSegments:a,...h}=s;const{instancesCount:u,vertexBuffers:l,...c}=h;if(!r||r.type!=="PlaneGeometry"){o=o??1,a=a??1;const p=o*a+o;l.length||(r=ni.getPlaneGeometryByID(p)),r?r.instancesCount=u:(r=new Vi({widthSegments:o,heightSegments:a,instancesCount:u,vertexBuffers:l}),ni.addPlaneGeometry(r))}super(e,t,{geometry:r,...c}),this.type="Plane"}}class pi extends us{constructor({deviceManager:e,label:t,container:i,pixelRatio:s=1,autoResize:r=!0,context:o={},renderPass:a,camera:h,lights:u}){super({deviceManager:e,label:t,container:i,pixelRatio:s,autoResize:r,context:o,renderPass:a,camera:h,lights:u}),this.type="GPUCurtainsRenderer"}setRendererObjects(){super.setRendererObjects(),this.domMeshes=[],this.domObjects=[]}onCameraMatricesChanged(){super.onCameraMatricesChanged(),this.domObjects.forEach(e=>{e.updateSizeAndPosition()})}resizeMeshes(){this.meshes.forEach(e=>{"domElement"in e||e.resize(this.boundingRect)}),this.domObjects.forEach(e=>{e.domElement.isResizing||e.domElement.setSize()})}}class $o{constructor({scroll:e={x:0,y:0},delta:t={x:0,y:0},shouldWatch:i=!0,onScroll:s=(r={x:0,y:0})=>{}}={}){this.scroll=e,this.delta=t,this.shouldWatch=i,this.onScroll=s,this.shouldWatch&&window.addEventListener("scroll",this.setScroll.bind(this),{passive:!0})}setScroll(){this.updateScrollValues({x:window.pageXOffset,y:window.pageYOffset})}updateScrollValues({x:e,y:t}){const i=this.scroll;this.scroll={x:e,y:t},this.delta={x:i.x-this.scroll.x,y:i.y-this.scroll.y},this.onScroll&&this.onScroll(this.delta)}destroy(){this.shouldWatch&&window.removeEventListener("scroll",this.setScroll.bind(this),{passive:!0})}}class Uo{constructor({container:e,label:t,pixelRatio:i=window.devicePixelRatio??1,context:s={},production:r=!1,adapterOptions:o={},renderPass:a,camera:h,lights:u,autoRender:l=!0,autoResize:c=!0,watchScroll:p=!0}={}){this._onRenderCallback=()=>{},this._onScrollCallback=()=>{},this._onErrorCallback=()=>{},this._onContextLostCallback=()=>{},this.type="CurtainsGPU",this.options={container:e,label:t,pixelRatio:i,camera:h,lights:u,production:r,adapterOptions:o,context:s,renderPass:a,autoRender:l,autoResize:c,watchScroll:p},this.setDeviceManager(),e&&this.setContainer(e),this.initEvents(),this.options.autoRender&&this.animate()}setContainer(e){if(e)if(typeof e=="string")if(e=document.querySelector(e),e)this.options.container=e;else{const t=document.createElement("div");t.setAttribute("id","curtains-gpu-canvas"),document.body.appendChild(t),this.options.container=t}else e instanceof Element&&(this.options.container=e);else{const t=document.createElement("div");t.setAttribute("id","curtains-gpu-canvas"),document.body.appendChild(t),this.options.container=t}this.container=this.options.container,this.setMainRenderer()}setMainRenderer(){this.createCurtainsRenderer({deviceManager:this.deviceManager,label:this.options.label||"GPUCurtains main GPUCurtainsRenderer",container:this.options.container,pixelRatio:this.options.pixelRatio,autoResize:this.options.autoResize,context:this.options.context,renderPass:this.options.renderPass,camera:this.options.camera,lights:this.options.lights})}patchRendererOptions(e){return e.pixelRatio===void 0&&(e.pixelRatio=this.options.pixelRatio),e.autoResize===void 0&&(e.autoResize=this.options.autoResize),e}createRenderer(e){return e=this.patchRendererOptions(e),new as({...e,deviceManager:this.deviceManager})}createCameraRenderer(e){return e=this.patchRendererOptions(e),new us({...e,deviceManager:this.deviceManager})}createCurtainsRenderer(e){return e=this.patchRendererOptions(e),new pi({...e,deviceManager:this.deviceManager})}setDeviceManager(){this.deviceManager=new Xr({label:"GPUCurtains default device",production:this.options.production,adapterOptions:this.options.adapterOptions,onError:()=>setTimeout(()=>{this._onErrorCallback&&this._onErrorCallback()},0),onDeviceLost:e=>this._onContextLostCallback&&this._onContextLostCallback(e)})}get renderers(){return this.deviceManager.renderers}get renderer(){return this.renderers[0]}async setDevice({adapter:e=null,device:t=null}={}){await this.deviceManager.init({adapter:e,device:t})}async restoreContext(){await this.deviceManager.restoreDevice()}get pingPongPlanes(){return this.renderers?.map(e=>e.pingPongPlanes).flat()}get shaderPasses(){return this.renderers?.map(e=>e.shaderPasses).flat()}get meshes(){return this.renderers?.map(e=>e.meshes).flat()}get domMeshes(){return this.renderers?.filter(e=>e instanceof pi).map(e=>e.domMeshes).flat()}get domObjects(){return this.renderers?.filter(e=>e instanceof pi).map(e=>e.domObjects).flat()}get planes(){return this.domMeshes.filter(e=>e instanceof pn)}get computePasses(){return this.renderers?.map(e=>e.computePasses).flat()}get boundingRect(){return this.renderer?.boundingRect}initScroll(){this.scrollManager=new $o({scroll:{x:window.pageXOffset,y:window.pageYOffset},delta:{x:0,y:0},shouldWatch:this.options.watchScroll,onScroll:e=>this.updateScroll(e)})}updateScroll(e={x:0,y:0}){this.domObjects.forEach(t=>{t.domElement&&t.watchScroll&&t.updateScrollPosition(e)}),this._onScrollCallback&&this._onScrollCallback()}updateScrollValues(e={x:0,y:0}){this.scrollManager.updateScrollValues(e)}get scrollDelta(){return this.scrollManager.delta}get scrollValues(){return this.scrollManager.scroll}initEvents(){ss.useObserver(this.options.autoResize),this.initScroll()}onRender(e){return e&&(this._onRenderCallback=e),this}onScroll(e){return e&&(this._onScrollCallback=e),this}onError(e){return e&&(this._onErrorCallback=e),this}onContextLost(e){return e&&(this._onContextLostCallback=e),this}animate(){this.render(),this.animationFrameID=window.requestAnimationFrame(this.animate.bind(this))}render(){this._onRenderCallback&&this._onRenderCallback(),this.deviceManager.render()}destroy(){this.animationFrameID&&window.cancelAnimationFrame(this.animationFrameID),this.deviceManager.destroy(),this.scrollManager?.destroy(),ss.destroy()}}var bs=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},B=(n,e,t)=>(bs(n,e,"read from private field"),t?t.call(n):e.get(n)),k=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Ee=(n,e,t,i)=>(bs(n,e,"write to private field"),e.set(n,t),t),A=(n,e,t)=>(bs(n,e,"access private method"),t),W,oe,Le,$,He,Xe,wt,Ke,fi,ws,Bs,fn,Ms,mn,mi,Cs,gi,Ss,yi,Ts,xi,Ps,vi,Rs,bi,zs,wi,Es,Bi,Ls,Ie,Ze,Mi,_s,Gs,gn,As,yn;const Je=new _,Qe=new _,_e=new f;class ko{constructor({camera:e,element:t=null,target:i=new f,enableZoom:s=!0,minZoom:r=0,maxZoom:o=1/0,zoomSpeed:a=1,enableRotate:h=!0,minPolarAngle:u=0,maxPolarAngle:l=Math.PI,minAzimuthAngle:c=-1/0,maxAzimuthAngle:p=1/0,rotateSpeed:d=1,enablePan:m=!0,panSpeed:g=1}){if(k(this,fi),k(this,Bs),k(this,Ms),k(this,mi),k(this,gi),k(this,yi),k(this,xi),k(this,vi),k(this,bi),k(this,wi),k(this,Bi),k(this,Ie),k(this,Mi),k(this,Gs),k(this,As),k(this,W,null),k(this,oe,new f),k(this,Le,!1),k(this,$,{radius:1,phi:0,theta:0}),k(this,He,new _),k(this,Xe,!1),k(this,wt,new _),k(this,Ke,new f),!e){L("OrbitControls: cannot initialize without a camera.");return}A(this,fi,ws).call(this,{target:i,enableZoom:s,minZoom:r,maxZoom:o,zoomSpeed:a,enableRotate:h,minPolarAngle:u,maxPolarAngle:l,minAzimuthAngle:c,maxAzimuthAngle:p,rotateSpeed:d,enablePan:m,panSpeed:g}),this.element=t??(typeof window<"u"?window:null),this.useCamera(e)}useCamera(e){this.camera=e,this.camera.position.onChange(()=>{this.camera.lookAt(this.target)}),B(this,oe).copy(this.camera.position).sub(this.target),B(this,$).radius=B(this,oe).length(),B(this,$).theta=Math.atan2(B(this,oe).x,B(this,oe).z),B(this,$).phi=Math.acos(Math.min(Math.max(B(this,oe).y/B(this,$).radius,-1),1)),A(this,Ie,Ze).call(this)}reset({position:e,target:t,enableZoom:i=this.enableZoom,minZoom:s=this.minZoom,maxZoom:r=this.maxZoom,zoomSpeed:o=this.zoomSpeed,enableRotate:a=this.enableRotate,minPolarAngle:h=this.minPolarAngle,maxPolarAngle:u=this.maxPolarAngle,minAzimuthAngle:l=this.minAzimuthAngle,maxAzimuthAngle:c=this.maxAzimuthAngle,rotateSpeed:p=this.rotateSpeed,enablePan:d=this.enablePan,panSpeed:m=this.panSpeed}={}){A(this,fi,ws).call(this,{target:t,enableZoom:i,minZoom:s,maxZoom:r,zoomSpeed:o,enableRotate:a,minPolarAngle:h,maxPolarAngle:u,minAzimuthAngle:l,maxAzimuthAngle:c,rotateSpeed:p,enablePan:d,panSpeed:m}),e&&this.updatePosition(e)}updatePosition(e=new f){e.sub(this.target),B(this,$).radius=e.length(),B(this,$).theta=Math.atan2(e.x,e.z),B(this,$).phi=Math.acos(Math.min(Math.max(e.y/B(this,$).radius,-1),1)),A(this,Ie,Ze).call(this)}set element(e){B(this,W)&&(!e||B(this,W)!==e)&&A(this,Ms,mn).call(this),Ee(this,W,e),e&&A(this,Bs,fn).call(this)}get element(){return B(this,W)}destroy(){this.element=null}}W=new WeakMap,oe=new WeakMap,Le=new WeakMap,$=new WeakMap,He=new WeakMap,Xe=new WeakMap,wt=new WeakMap,Ke=new WeakMap,fi=new WeakSet,ws=function({target:n,enableZoom:e=this.enableZoom,minZoom:t=this.minZoom,maxZoom:i=this.maxZoom,zoomSpeed:s=this.zoomSpeed,enableRotate:r=this.enableRotate,minPolarAngle:o=this.minPolarAngle,maxPolarAngle:a=this.maxPolarAngle,minAzimuthAngle:h=this.minAzimuthAngle,maxAzimuthAngle:u=this.maxAzimuthAngle,rotateSpeed:l=this.rotateSpeed,enablePan:c=this.enablePan,panSpeed:p=this.panSpeed}={}){n&&(this.target=n),this.enableZoom=e,this.minZoom=t,this.maxZoom=i,this.zoomSpeed=s,this.enableRotate=r,this.minPolarAngle=o,this.maxPolarAngle=a,this.minAzimuthAngle=h,this.maxAzimuthAngle=u,this.rotateSpeed=l,this.enablePan=c,this.panSpeed=p},Bs=new WeakSet,fn=function(){B(this,W).addEventListener("contextmenu",A(this,Bi,Ls).bind(this),!1),B(this,W).addEventListener("mousedown",A(this,mi,Cs).bind(this),!1),B(this,W).addEventListener("mousemove",A(this,yi,Ts).bind(this),!1),B(this,W).addEventListener("mouseup",A(this,vi,Rs).bind(this),!1),B(this,W).addEventListener("touchstart",A(this,gi,Ss).bind(this),{passive:!1}),B(this,W).addEventListener("touchmove",A(this,xi,Ps).bind(this),{passive:!1}),B(this,W).addEventListener("touchend",A(this,bi,zs).bind(this),!1),B(this,W).addEventListener("wheel",A(this,wi,Es).bind(this),{passive:!1})},Ms=new WeakSet,mn=function(){B(this,W).removeEventListener("contextmenu",A(this,Bi,Ls).bind(this),!1),B(this,W).removeEventListener("mousedown",A(this,mi,Cs).bind(this),!1),B(this,W).removeEventListener("mousemove",A(this,yi,Ts).bind(this),!1),B(this,W).removeEventListener("mouseup",A(this,vi,Rs).bind(this),!1),B(this,W).removeEventListener("touchstart",A(this,gi,Ss).bind(this),{passive:!1}),B(this,W).removeEventListener("touchmove",A(this,xi,Ps).bind(this),{passive:!1}),B(this,W).removeEventListener("touchend",A(this,bi,zs).bind(this),!1),B(this,W).removeEventListener("wheel",A(this,wi,Es).bind(this),{passive:!1})},mi=new WeakSet,Cs=function(n){n.button===0&&this.enableRotate?(Ee(this,Le,!0),B(this,He).set(n.clientX,n.clientY)):n.button===2&&this.enablePan&&(Ee(this,Xe,!0),B(this,wt).set(n.clientX,n.clientY)),n.stopPropagation(),n.preventDefault()},gi=new WeakSet,Ss=function(n){n.touches.length===1&&this.enableRotate&&(Ee(this,Le,!0),B(this,He).set(n.touches[0].pageX,n.touches[0].pageY))},yi=new WeakSet,Ts=function(n){B(this,Le)&&this.enableRotate?A(this,Mi,_s).call(this,n.clientX,n.clientY):B(this,Xe)&&this.enablePan&&A(this,Gs,gn).call(this,n.clientX,n.clientY)},xi=new WeakSet,Ps=function(n){B(this,Le)&&this.enableRotate&&A(this,Mi,_s).call(this,n.touches[0].pageX,n.touches[0].pageY)},vi=new WeakSet,Rs=function(n){Ee(this,Le,!1),Ee(this,Xe,!1)},bi=new WeakSet,zs=function(n){Ee(this,Le,!1),Ee(this,Xe,!1)},wi=new WeakSet,Es=function(n){this.enableZoom&&(A(this,As,yn).call(this,n.deltaY),n.preventDefault())},Bi=new WeakSet,Ls=function(n){n.preventDefault()},Ie=new WeakSet,Ze=function(){const n=B(this,$).radius*Math.sin(Math.max(1e-6,B(this,$).phi));B(this,oe).x=n*Math.sin(B(this,$).theta),B(this,oe).y=B(this,$).radius*Math.cos(B(this,$).phi),B(this,oe).z=n*Math.cos(B(this,$).theta),this.camera.position.copy(this.target).add(B(this,oe))},Mi=new WeakSet,_s=function(n,e){Je.set(n,e),Qe.copy(Je).sub(B(this,He)).multiplyScalar(this.rotateSpeed),B(this,$).theta-=2*Math.PI*Qe.x/this.camera.size.height,B(this,$).phi-=2*Math.PI*Qe.y/this.camera.size.height,B(this,$).theta=Math.min(this.maxAzimuthAngle,Math.max(this.minAzimuthAngle,B(this,$).theta)),B(this,$).phi=Math.min(this.maxPolarAngle,Math.max(this.minPolarAngle,B(this,$).phi)),B(this,He).copy(Je),A(this,Ie,Ze).call(this)},Gs=new WeakSet,gn=function(n,e){Je.set(n,e),Qe.copy(Je).sub(B(this,wt)).multiplyScalar(this.panSpeed),B(this,Ke).set(0),_e.copy(this.camera.position).sub(this.target);let t=_e.length();t*=Math.tan(this.camera.fov/2*Math.PI/180),_e.set(this.camera.modelMatrix.elements[0],this.camera.modelMatrix.elements[1],this.camera.modelMatrix.elements[2]),_e.multiplyScalar(-(2*Qe.x*t)/this.camera.size.height),B(this,Ke).add(_e),_e.set(this.camera.modelMatrix.elements[4],this.camera.modelMatrix.elements[5],this.camera.modelMatrix.elements[6]),_e.multiplyScalar(2*Qe.y*t/this.camera.size.height),B(this,Ke).add(_e),B(this,wt).copy(Je),this.target.add(B(this,Ke)),B(this,oe).copy(this.camera.position).sub(this.target),B(this,$).radius=B(this,oe).length(),A(this,Ie,Ze).call(this)},As=new WeakSet,yn=function(n){B(this,$).radius=Math.min(this.maxZoom,Math.max(this.minZoom+1e-6,B(this,$).radius+n*this.zoomSpeed/100)),A(this,Ie,Ze).call(this)};var Io=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},Ce=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},ae=(n,e,t)=>(Io(n,e,"access private method"),t),Ds,xn,Os,vn,Ci,Fs,Bt,Si,$s,bn,Us,wn,Ti,ks,Is,Bn,Vs,Mn;class Cn{constructor(){Ce(this,Ds),Ce(this,Os),Ce(this,Ci),Ce(this,Bt),Ce(this,$s),Ce(this,Us),Ce(this,Ti),Ce(this,Is),Ce(this,Vs)}async loadFromUrl(e){const t=await(await fetch(e)).arrayBuffer();return ae(this,Ds,xn).call(this,new DataView(t))}}Ds=new WeakSet,xn=function(n){const e={data:n,offset:0},t=ae(this,Os,vn).call(this,e);return{width:t.width,height:t.height,exposure:t.exposure,gamma:t.gamma,data:ae(this,$s,bn).call(this,e,t)}},Os=new WeakSet,vn=function(n){let e=ae(this,Bt,Si).call(this,n);const t={colorCorr:[1,1,1],exposure:1,gamma:1,width:0,height:0,flipX:!1,flipY:!1};if(e!=="#?RADIANCE"&&e!=="#?RGBE")throw new Error("Incorrect file format!");for(;e!=="";){e=ae(this,Bt,Si).call(this,n);const s=e.split("=");switch(s[0]){case"GAMMA":t.gamma=parseFloat(s[1]);break;case"FORMAT":if(s[1]!=="32-bit_rle_rgbe"&&s[1]!=="32-bit_rle_xyze")throw new Error("Incorrect encoding format!");break;case"EXPOSURE":t.exposure=parseFloat(s[1]);break;case"COLORCORR":t.colorCorr=s[1].replace(/^\s+|\s+$/g,"").split(" ").map(r=>parseFloat(r));break}}e=ae(this,Bt,Si).call(this,n);const i=e.split(" ");return ae(this,Ci,Fs).call(this,i[0],parseInt(i[1]),t),ae(this,Ci,Fs).call(this,i[2],parseInt(i[3]),t),t},Ci=new WeakSet,Fs=function(n,e,t){switch(n){case"+X":t.width=e;break;case"-X":t.width=e,t.flipX=!0,console.warn("Flipping horizontal orientation not currently supported");break;case"-Y":t.height=e,t.flipY=!0;break;case"+Y":t.height=e;break}},Bt=new WeakSet,Si=function(n){let e,t="";for(;(e=n.data.getUint8(n.offset++))!==10;)t+=String.fromCharCode(e);return t},$s=new WeakSet,bn=function(n,e){const t=n.data.getUint16(n.offset);let i;if(t===514)i=ae(this,Us,wn).call(this,n,e),e.flipX&&ae(this,Is,Bn).call(this,i,e),e.flipY&&ae(this,Vs,Mn).call(this,i,e);else throw new Error("Obsolete HDR file version!");return i},Us=new WeakSet,wn=function(n,e){const{width:t,height:i,colorCorr:s}=e,r=new Float32Array(t*i*4);let o=0,{offset:a,data:h}=n;for(let u=0;u128){const m=d-128;d=h.getUint8(a++);for(let g=0;g>1;for(let r=0;r>1;for(let r=0;r f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// GGX microfacet distribution +struct MicrofacetDistributionSample { + pdf: f32, + cosTheta: f32, + sinTheta: f32, + phi: f32 +}; + +fn D_GGX(NdotH: f32, roughness: f32) -> f32 { + let a: f32 = NdotH * roughness; + let k: f32 = roughness / (1.0 - NdotH * NdotH + a * a); + return k * k * (1.0 / ${Math.PI}); +} + +// https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.html +// This implementation is based on https://bruop.github.io/ibl/, +// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html +// and https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +fn GGX(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var ggx: MicrofacetDistributionSample; + + // evaluate sampling equations + let alpha: f32 = roughness * roughness; + ggx.cosTheta = clamp(sqrt((1.0 - xi.y) / (1.0 + (alpha * alpha - 1.0) * xi.y)), 0.0, 1.0); + ggx.sinTheta = sqrt(1.0 - ggx.cosTheta * ggx.cosTheta); + ggx.phi = 2.0 * ${Math.PI} * xi.x; + + // evaluate GGX pdf (for half vector) + ggx.pdf = D_GGX(ggx.cosTheta, alpha); + + // Apply the Jacobian to obtain a pdf that is parameterized by l + // see https://bruop.github.io/ibl/ + // Typically you'd have the following: + // float pdf = D_GGX(NoH, roughness) * NoH / (4.0 * VoH); + // but since V = N => VoH == NoH + ggx.pdf /= 4.0; + + return ggx; +} + +fn Lambertian(xi: vec2f, roughness: f32) -> MicrofacetDistributionSample { + var lambertian: MicrofacetDistributionSample; + + // Cosine weighted hemisphere sampling + // http://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations.html#Cosine-WeightedHemisphereSampling + lambertian.cosTheta = sqrt(1.0 - xi.y); + lambertian.sinTheta = sqrt(xi.y); // equivalent to \`sqrt(1.0 - cosTheta*cosTheta)\`; + lambertian.phi = 2.0 * ${Math.PI} * xi.x; + + lambertian.pdf = lambertian.cosTheta / ${Math.PI}; // evaluation for solid angle, therefore drop the sinTheta + + return lambertian; +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); + } + else { + bitangent = vec3(0.0, 0.0, -1.0); + } + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// getImportanceSample returns an importance sample direction with pdf in the .w component +fn getImportanceSample(Xi: vec2, N: vec3f, roughness: f32) -> vec4f { + var importanceSample: MicrofacetDistributionSample; + + importanceSample = GGX(Xi, roughness); + + // transform the hemisphere sample to the normal coordinate frame + // i.e. rotate the hemisphere to the normal direction + let localSpaceDirection: vec3f = normalize(vec3( + importanceSample.sinTheta * cos(importanceSample.phi), + importanceSample.sinTheta * sin(importanceSample.phi), + importanceSample.cosTheta + )); + + let TBN: mat3x3f = generateTBN(N); + let direction: vec3f = TBN * localSpaceDirection; + + return vec4(direction, importanceSample.pdf); +} + +// From the filament docs. Geometric Shadowing function +// https://google.github.io/filament/Filament.html#toc4.4.2 +fn V_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 { + let a2: f32 = pow(roughness, 4.0); + let GGXV: f32 = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2); + let GGXL: f32 = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2); + return 0.5 / (GGXV + GGXL); +} + +@compute @workgroup_size(16, 16, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let texelSize: vec2 = textureDimensions(lutStorageTexture); + + let x: u32 = global_id.x; + let y: u32 = global_id.y; + + // Check bounds + if (x >= texelSize.x || y >= texelSize.y) { + return; + } + + let epsilon: f32 = 1e-6; + + // Compute roughness and N\xB7V from texture coordinates + let NdotV: f32 = max(f32(x) / f32(texelSize.x - 1), epsilon); // Maps x-axis to N\xB7V (0.0 to 1.0) + let roughness: f32 = max(f32(y) / f32(texelSize.y - 1), epsilon); // Maps y-axis to roughness (0.0 to 1.0) + + // Calculate view vector and normal vector + let V: vec3 = vec3(sqrt(1.0 - NdotV * NdotV), 0.0, NdotV); // Normalized view vector + let N: vec3 = vec3(0.0, 0.0, 1.0); // Normal is along z-axis + + // Initialize integration variables + var A: f32 = 0.0; + var B: f32 = 0.0; + var C: f32 = 0.0; + + // Monte Carlo integration to calculate A and B factors + let sampleCount: u32 = params.sampleCount; + for (var i: u32 = 0; i < sampleCount; i++) { + let Xi: vec2 = hammersley2d(i, sampleCount); // Importance sampling (Hammersley sequence) + + //let H: vec3 = importanceSampleGGX(Xi, N, roughness); + let importanceSample: vec4f = getImportanceSample(Xi, N, roughness); + let H: vec3f = importanceSample.xyz; + // let pdf: f32 = importanceSample.w; + + let L: vec3 = normalize(reflect(-V, H)); + + let NdotL: f32 = clamp(L.z, 0.0, 1.0); + let NdotH: f32 = clamp(H.z, 0.0, 1.0); + let VdotH: f32 = clamp(dot(V, H), 0.0, 1.0); + + // Ensure valid light direction + if (NdotL > 0.0) { + // LUT for GGX distribution. + + // Taken from: https://bruop.github.io/ibl + // Shadertoy: https://www.shadertoy.com/view/3lXXDB + // Terms besides V are from the GGX PDF we're dividing by. + let V_pdf: f32 = V_SmithGGXCorrelated(NdotV, NdotL, roughness) * VdotH * NdotL / max(NdotH, epsilon); + let Fc: f32 = pow(1.0 - VdotH, 5.0); + A += (1.0 - Fc) * V_pdf; + B += Fc * V_pdf; + C += 0.0; + } + } + + // Average the integration result + // The PDF is simply pdf(v, h) -> NDF * . + // To parametrize the PDF over l, use the Jacobian transform, yielding to: pdf(v, l) -> NDF * / 4 + // Since the BRDF divide through the PDF to be normalized, the 4 can be pulled out of the integral. + A = A * 4.0 / f32(sampleCount); + B = B * 4.0 / f32(sampleCount); + C = C * 4.0 * 2.0 * ${Math.PI} / f32(sampleCount); + + // Store the result in the LUT texture + textureStore(lutStorageTexture, vec2(x, y), vec4(A, B, C, 1.0)); +} +`,No=` +// Cube face lookup vectors +// positive and negative Y need to be inverted +const faceVectors = array, 2>, 6>( + array, 2>(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // +X + array, 2>(vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)), // -X + array, 2>(vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0)), // -Y + array, 2>(vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, -1.0)), // +Y + array, 2>(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0)), // +Z + array, 2>(vec3(0.0, 0.0, -1.0), vec3(0.0, 1.0, 0.0)) // -Z +); + +// Utility to calculate 3D direction for a given cube face pixel +fn texelDirection(faceIndex : u32, u : f32, v : f32) -> vec3 { + let forward = faceVectors[faceIndex][0]; + let up = faceVectors[faceIndex][1]; + let right = normalize(cross(up, forward)); + return normalize(forward + (2.0 * u - 1.0) * right + (2.0 * v - 1.0) * up); +} + +// Map 3D direction to equirectangular coordinates +fn dirToEquirect(dir : vec3) -> vec2 { + let phi = atan2(dir.z, dir.x); + let theta = asin(dir.y); + let u = 0.5 + 0.5 * phi / ${Math.PI}; + let v = 0.5 - theta / ${Math.PI}; + return vec2(u, v); +} + +@compute @workgroup_size(8, 8, 1) +fn main(@builtin(global_invocation_id) global_id : vec3) { + let faceSize = params.faceSize; + let cubeFaceIndex = global_id.z; + let x = global_id.x; + let y = global_id.y; + + if (x >= faceSize || y >= faceSize || cubeFaceIndex >= 6u) { + return; + } + + let u = f32(x) / f32(faceSize); + let v = f32(y) / f32(faceSize); + + // Get the 3D direction for this cube face texel + let dir = texelDirection(cubeFaceIndex, u, v); + + // Map to equirectangular coordinates + let uv = dirToEquirect(dir); + + let hdrWidth = params.imageSize.x; + let hdrHeight = params.imageSize.y; + + let texX = u32(clamp(uv.x * hdrWidth, 0.0, hdrWidth - 1.0)); + let texY = u32(clamp(uv.y * hdrHeight, 0.0, hdrHeight - 1.0)); + + let hdrTexelIndex = texY * u32(hdrWidth) + texX; + + // Sample the equirectangular texture + let sampledColor = params.hdrImageData[hdrTexelIndex]; + + // Correct cube face order in texture store (fix for reversed face indices) + textureStore( + specularStorageCubemap, + vec2(x, y), + cubeFaceIndex, + sampledColor + ); +} +`;const Wo=n=>` +fn radicalInverse_VdC(inputBits: u32) -> f32 { + var bits: u32 = inputBits; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +// hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 +// that can be used for quasi Monte Carlo integration +fn hammersley2d(i: u32, N: u32) -> vec2f { + return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); +} + +// TBN generates a tangent bitangent normal coordinate frame from the normal +// (the normal must be normalized) +fn generateTBN(normal: vec3f) -> mat3x3f { + var bitangent: vec3f = vec3(0.0, 1.0, 0.0); + + let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); + let epsilon: f32 = 0.0000001; + + if (1.0 - abs(NdotUp) <= epsilon) { + // Sampling +Y or -Y, so we need a more robust bitangent. + if (NdotUp > 0.0) { + bitangent = vec3(0.0, 0.0, 1.0); + } + else { + bitangent = vec3(0.0, 0.0, -1.0); + } + } + + let tangent: vec3f = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); + + return mat3x3f(tangent, bitangent, normal); +} + +// Mipmap Filtered Samples (GPU Gems 3, 20.4) +// https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling +// https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf +fn computeLod(pdf: f32) -> f32 { + // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf + return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); +} + +fn transformDirection(face: u32, uv: vec2f) -> vec3f { + // Transform the direction based on the cubemap face + switch (face) { + case 0u { + // +X + return vec3f( 1.0, uv.y, -uv.x); + } + case 1u { + // -X + return vec3f(-1.0, uv.y, uv.x); + } + case 2u { + // +Y + return vec3f( uv.x, -1.0, uv.y); + } + case 3u { + // -Y + return vec3f( uv.x, 1.0, -uv.y); + } + case 4u { + // +Z + return vec3f( uv.x, uv.y, 1.0); + } + case 5u { + // -Z + return vec3f(-uv.x, uv.y, -1.0); + } + default { + return vec3f(0.0, 0.0, 0.0); + } + } +} + +const PI = ${Math.PI}; + +@compute @workgroup_size(8, 8, 1) fn main( + @builtin(global_invocation_id) GlobalInvocationID: vec3u, +) { + let faceSize: u32 = params.faceSize; + let sampleCount: u32 = params.sampleCount; + + let face: u32 = GlobalInvocationID.z; + let x: u32 = GlobalInvocationID.x; + let y: u32 = GlobalInvocationID.y; + + if (x >= faceSize || y >= faceSize) { + return; + } + + let texelSize: f32 = 1.0 / f32(faceSize); + let halfTexel: f32 = texelSize * 0.5; + + var uv: vec2f = vec2( + (f32(x) + halfTexel) * texelSize, + (f32(y) + halfTexel) * texelSize + ); + + uv = uv * 2.0 - 1.0; + + let normal: vec3 = transformDirection(face, uv); + + var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); + + for (var i: u32 = 0; i < sampleCount; i++) { + // generate a quasi monte carlo point in the unit square [0.1)^2 + let xi: vec2f = hammersley2d(i, sampleCount); + + let cosTheta: f32 = sqrt(1.0 - xi.y); + let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); + let phi: f32 = 2.0 * PI * xi.x; + let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta + + let sampleVec: vec3f = vec3f( + sinTheta * cos(phi), + sinTheta * sin(phi), + cosTheta + ); + + let TBN: mat3x3f = generateTBN(normalize(normal)); + + var direction: vec3f = TBN * sampleVec; + + // invert along Y axis + direction.y *= -1.0; + + let lod: f32 = computeLod(pdf); + + let sampleLevel = min(lod, f32(params.maxMipLevel)); + + // Convert sampleVec to texture coordinates of the specular env map + irradiance += textureSampleLevel( + ${n.options.name}, + clampSampler, + direction, + sampleLevel + ).rgb; + } + + irradiance /= f32(sampleCount); + + textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); +} +`;var jo=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},qo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},Ns=(n,e,t)=>(jo(n,e,"access private method"),t),Mt,Pi;class Yo{constructor(e,t={lutTextureParams:{size:256,computeSampleCount:1024,label:"Environment LUT texture",name:"lutTexture",format:"rgba32float"},diffuseTextureParams:{size:128,computeSampleCount:2048,label:"Environment diffuse texture",name:"diffuseTexture",format:"rgba16float"},specularTextureParams:{label:"Environment specular texture",name:"specularTexture",format:"rgba16float",generateMips:!0}}){qo(this,Mt),e=V(e,"EnvironmentMap"),this.renderer=e,this.options=t,this.sampler=new Ne(this.renderer,{label:"Clamp sampler",name:"clampSampler",magFilter:"linear",minFilter:"linear",mipmapFilter:"linear",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge"}),this.rotation=new Be(new Float32Array([0,0,1,0,1,0,-1,0,0])),this.hdrLoader=new Cn,this.computeBRDFLUTTexture()}async computeBRDFLUTTexture(){const{size:e,computeSampleCount:t,...i}=this.options.lutTextureParams;this.lutTexture=new K(this.renderer,{...i,visibility:["fragment"],fixedSize:{width:e,height:e},autoDestroy:!1});let s=new K(this.renderer,{label:"LUT storage texture",name:"lutStorageTexture",format:this.lutTexture.options.format,visibility:["compute"],usage:["copySrc","storageBinding"],type:"storage",fixedSize:{width:this.lutTexture.size.width,height:this.lutTexture.size.height}}),r=new Yt(this.renderer,{label:"Compute LUT texture",autoRender:!1,dispatchSize:[Math.ceil(s.size.width/16),Math.ceil(s.size.height/16),1],shaders:{compute:{code:Vo}},uniforms:{params:{struct:{sampleCount:{type:"u32",value:t}}}},textures:[s]});await r.material.compileMaterial(),this.renderer.onBeforeRenderScene.add(o=>{this.renderer.renderSingleComputePass(o,r),Ns(this,Mt,Pi).call(this,o,s,this.lutTexture)},{once:!0}),this.renderer.onAfterCommandEncoderSubmission.add(()=>{r.destroy(),s.destroy(),s=null,r=null},{once:!0})}async computeSpecularCubemapFromHDRData(e){let t=new K(this.renderer,{label:"Specular storage cubemap",name:"specularStorageCubemap",format:this.specularTexture.options.format,visibility:["compute"],usage:["copySrc","storageBinding"],type:"storage",fixedSize:{width:this.specularTexture.size.width,height:this.specularTexture.size.height,depth:6},viewDimension:"2d-array"}),i=new Yt(this.renderer,{label:"Compute specular cubemap from equirectangular",autoRender:!1,dispatchSize:[Math.ceil(this.specularTexture.size.width/8),Math.ceil(this.specularTexture.size.height/8),6],shaders:{compute:{code:No}},storages:{params:{struct:{hdrImageData:{type:"array",value:e.data},imageSize:{type:"vec2f",value:new _(e.width,e.height)},faceSize:{type:"u32",value:this.specularTexture.size.width}}}},textures:[t]});await i.material.compileMaterial();const s=this.renderer.device?.createCommandEncoder({label:"Render once command encoder"});this.renderer.production||s.pushDebugGroup("Render once command encoder"),this.renderer.renderSingleComputePass(s,i),Ns(this,Mt,Pi).call(this,s,t,this.specularTexture),this.renderer.production||s.popDebugGroup();const r=s.finish();this.renderer.device?.queue.submit([r]),this.specularTexture.texture.mipLevelCount>1&&Et(this.renderer.device,this.specularTexture.texture),i.destroy(),t.destroy(),t=null,i=null}async computeDiffuseFromSpecular(){if(this.specularTexture.options.viewDimension!=="cube"){L("Could not compute the diffuse texture because the specular texture is not a cube map:"+this.specularTexture.options.viewDimension);return}let e=new K(this.renderer,{label:"Diffuse storage cubemap",name:"diffuseEnvMap",format:this.diffuseTexture.options.format,visibility:["compute"],usage:["copySrc","storageBinding"],type:"storage",fixedSize:{width:this.diffuseTexture.size.width,height:this.diffuseTexture.size.height,depth:6},viewDimension:"2d-array"}),t=new Yt(this.renderer,{label:"Compute diffuse map from specular map",autoRender:!1,dispatchSize:[Math.ceil(this.diffuseTexture.size.width/8),Math.ceil(this.diffuseTexture.size.height/8),6],shaders:{compute:{code:Wo(this.specularTexture)}},uniforms:{params:{struct:{faceSize:{type:"u32",value:this.diffuseTexture.size.width},maxMipLevel:{type:"u32",value:this.specularTexture.texture.mipLevelCount},sampleCount:{type:"u32",value:this.options.diffuseTextureParams.computeSampleCount}}}},samplers:[this.sampler],textures:[this.specularTexture,e]});await t.material.compileMaterial(),this.renderer.onBeforeRenderScene.add(i=>{this.renderer.renderSingleComputePass(i,t),Ns(this,Mt,Pi).call(this,i,e,this.diffuseTexture)},{once:!0}),this.renderer.onAfterCommandEncoderSubmission.add(()=>{t.destroy(),e.destroy(),e=null,t=null},{once:!0})}async loadAndComputeFromHDR(e){const t=await this.hdrLoader.loadFromUrl(e),{width:i,height:s}=t||{width:1024,height:512},r=Math.max(i/4,s/2),o={viewDimension:"cube",autoDestroy:!1};this.specularTexture?(this.specularTexture.size.width!==r||this.specularTexture.size.height!==r)&&(this.specularTexture.options.fixedSize.width=r,this.specularTexture.options.fixedSize.height=r,this.specularTexture.size.width=r,this.specularTexture.size.height=r,this.specularTexture.createTexture()):this.specularTexture=new K(this.renderer,{...this.options.specularTextureParams,visibility:["fragment","compute"],fixedSize:{width:r,height:r},...o});const{size:a,computeSampleCount:h,...u}=this.options.diffuseTextureParams,l=Math.min(a,r);this.diffuseTexture?(this.diffuseTexture.size.width!==l||this.diffuseTexture.size.height!==l)&&(this.diffuseTexture.options.fixedSize.width=l,this.diffuseTexture.options.fixedSize.height=l,this.diffuseTexture.size.width=l,this.diffuseTexture.size.height=l,this.diffuseTexture.createTexture()):this.diffuseTexture=new K(this.renderer,{...u,visibility:["fragment"],fixedSize:{width:l,height:l},...o}),t&&this.computeSpecularCubemapFromHDRData(t).then(()=>{this.computeDiffuseFromSpecular()})}destroy(){this.lutTexture?.destroy(),this.diffuseTexture?.destroy(),this.specularTexture?.destroy()}}Mt=new WeakSet,Pi=function(n,e,t){n.copyTextureToTexture({texture:e.texture},{texture:t.texture},[t.texture.width,t.texture.height,t.texture.depthOrArrayLayers])};class Ho extends at{constructor({instancesCount:e=1,vertexBuffers:t=[],topology:i,mapBuffersAtCreation:s=!0,widthSegments:r=1,heightSegments:o=1,depthSegments:a=1}={}){super({verticesOrder:"ccw",topology:i,instancesCount:e,vertexBuffers:t,mapBuffersAtCreation:s}),this.type="BoxGeometry",r=Math.floor(r),o=Math.floor(o),a=Math.floor(a);const h=[],u=[],l=[],c=[];let p=0;const d=(m,g,y,v,M,P,w,S,C,b)=>{const R=P/C,E=w/b,z=P/2,F=w/2,O=S/2,J=C+1,q=b+1;let ie=0;const U=new f;for(let I=0;I0?1:-1,l.push(U.x,U.y,U.z),u.push(ee/C),u.push(I/b),ie+=1}}for(let I=0;I0)&&v.push(b,R,z),(S!==o-1||p({targetFormat:s.format}));t.outputTarget=new Wi(e,{label:t.label?t.label+" render target":"Ping Pong render target",useDepth:!1,...i&&{colorAttachments:i}}),t.transparent=!1,t.depth=!1,t.label=t.label??"PingPongPlane "+e.pingPongPlanes?.length,super(e,t),this.type="PingPongPlane",this.createTexture({label:t.label?`${t.label} render texture`:"PingPongPlane render texture",name:"renderTexture",...t.targets&&t.targets.length&&{format:t.targets[0].format},usage:["copyDst","textureBinding"]})}get renderTexture(){return this.textures.find(e=>e.options.name==="renderTexture")}addToScene(e=!1){e&&this.renderer.pingPongPlanes.push(this),this.autoRender&&this.renderer.scene.addPingPongPlane(this)}removeFromScene(e=!1){this.outputTarget&&this.outputTarget.destroy(),this.autoRender&&this.renderer.scene.removePingPongPlane(this),e&&(this.renderer.pingPongPlanes=this.renderer.pingPongPlanes.filter(t=>t.uuid!==this.uuid))}}var Ws=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},T=(n,e,t)=>(Ws(n,e,"read from private field"),t?t.call(n):e.get(n)),he=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},de=(n,e,t,i)=>(Ws(n,e,"write to private field"),e.set(n,t),t),Zo=(n,e,t)=>(Ws(n,e,"access private method"),t),me,Se,et,tt,ge,ye,Ct,St,Tt,Pt,Rt,zt,js,Sn;class Jo{constructor(e){he(this,js),he(this,me,void 0),he(this,Se,void 0),he(this,et,void 0),he(this,tt,void 0),he(this,ge,void 0),he(this,ye,void 0),he(this,Ct,void 0),he(this,St,void 0),he(this,Tt,void 0),he(this,Pt,void 0),he(this,Rt,void 0),he(this,zt,void 0),this.type="Raycaster",e=Ae(e,this.type),this.renderer=e,this.camera=this.renderer.camera,this.pointer=new _(1/0),this.ray={origin:new f,direction:new f},de(this,me,{origin:this.ray.origin.clone(),direction:this.ray.direction.clone()}),de(this,Se,new f),de(this,et,new f),de(this,tt,new f),de(this,ge,new f),de(this,ye,new f),de(this,Ct,new _),de(this,St,new _),de(this,Tt,new _),de(this,Pt,new f),de(this,Rt,new f),de(this,zt,new f)}setFromMouse(e){const{clientX:t,clientY:i}=e.targetTouches&&e.targetTouches.length?e.targetTouches[0]:e;this.setFromNDCCoords((t-this.renderer.boundingRect.left)/this.renderer.boundingRect.width*2-1,-((i-this.renderer.boundingRect.top)/this.renderer.boundingRect.height)*2+1)}setFromNDCCoords(e=0,t=0){this.pointer.set(e,t),this.setRay()}setRay(){this.camera.worldMatrix.getTranslation(this.ray.origin),this.ray.direction.set(this.pointer.x,this.pointer.y,-1).unproject(this.camera).sub(this.ray.origin).normalize()}rayIntersectsTriangle(e){const i=new f,s=new f;i.crossVectors(T(this,me).direction,T(this,ye));const r=T(this,ge).dot(i);if(Math.abs(r)<1e-6)return!1;const o=1/r,a=T(this,me).origin.clone().sub(T(this,Se)),h=o*a.dot(i);if(h<0||h>1)return!1;s.crossVectors(a,T(this,ge));const u=o*T(this,me).direction.dot(s);if(u<0||h+u>1)return!1;const l=o*T(this,ye).dot(s);return l>1e-6?(e.copy(T(this,me).origin).add(T(this,me).direction.clone().multiplyScalar(l)),!0):!1}getBarycentricCoordinates(e){const t=e.clone().sub(T(this,Se)),i=T(this,ge).dot(T(this,ge)),s=T(this,ge).dot(T(this,ye)),r=T(this,ye).dot(T(this,ye)),o=t.dot(T(this,ge)),a=t.dot(T(this,ye)),h=i*r-s*s,u=new f(0,(r*o-s*a)/h,(i*a-s*o)/h);return u.x=1-u.y-u.z,u}getTriangleNormal(){return new f().crossVectors(T(this,ge),T(this,ye)).normalize()}setAttributeVectorAtIndex(e,t,i,s,r){const o=i?i[e*3+t]:e*3+t;r.x=s.array[o*s.size],r.y=s.array[o*s.size+1],"z"in r&&(r.z=s.array[o*s.size+2])}intersectObject(e,t=!0,i=[]){if(!(e instanceof ve))return this.renderer.production||L(`${this.type}: object to test intersection again is not of type Object3D`),i;const s=Rn(e);return s&&Zo(this,js,Sn).call(this,s,i),t&&e.children.forEach(r=>{this.intersectObject(r,t,i)}),i.length&&i.sort((r,o)=>this.ray.origin.distance(r.point)-this.ray.origin.distance(o.point)),i}intersectObjects(e,t=!0,i=[]){return e.forEach(s=>{this.intersectObject(s,t,i)}),i.length&&i.sort((s,r)=>this.ray.origin.distance(s.point)-this.ray.origin.distance(r.point)),i}}me=new WeakMap,Se=new WeakMap,et=new WeakMap,tt=new WeakMap,ge=new WeakMap,ye=new WeakMap,Ct=new WeakMap,St=new WeakMap,Tt=new WeakMap,Pt=new WeakMap,Rt=new WeakMap,zt=new WeakMap,js=new WeakSet,Sn=function(n,e=[]){if(!n.geometry)return e;const t=n.geometry.getAttributeByName("position");if(!t)return this.renderer.production||L(`Raycaster: can't raycast on a mesh that has no position attribute: ${n.options.label}`),e;if(!t.array)return this.renderer.production||L(`Raycaster: can't raycast on a mesh that has no position attribute array: ${n.options.label}`),e;if(n.frustumCulling&&n.domFrustum){const{clipSpaceBoundingRect:h}=n.domFrustum;if(n.domFrustum.isIntersecting){if(this.pointer.x>h.left+h.width||this.pointer.xh.top||this.pointer.y0&&n.material.options.rendering.cullMode==="back")continue;if(p<0&&n.material.options.rendering.cullMode==="front")continue}const u=new f;if(this.rayIntersectsTriangle(u)){const c=this.getBarycentricCoordinates(u),p=u.clone().applyMat4(n.worldMatrix),d=this.ray.origin.distance(p),m={object:n,distance:d,localPoint:u,point:p,triangle:[T(this,Se).clone(),T(this,et).clone(),T(this,tt).clone()],triangleIndex:h};s&&s.array&&s.array.length&&(this.setAttributeVectorAtIndex(h,0,o,s,T(this,Ct)),this.setAttributeVectorAtIndex(h,1,o,s,T(this,St)),this.setAttributeVectorAtIndex(h,2,o,s,T(this,Tt)),m.uv=T(this,Ct).clone().multiplyScalar(c.x).add(T(this,St).clone().multiplyScalar(c.y)).add(T(this,Tt).clone().multiplyScalar(c.z))),r&&r.array&&r.array.length&&(this.setAttributeVectorAtIndex(h,0,o,r,T(this,Pt)),this.setAttributeVectorAtIndex(h,1,o,r,T(this,Rt)),this.setAttributeVectorAtIndex(h,2,o,r,T(this,zt)),m.normal=T(this,Pt).clone().multiplyScalar(c.x).add(T(this,Rt).clone().multiplyScalar(c.y)).add(T(this,zt).clone().multiplyScalar(c.z))),e.push(m)}}return e};var Tn=(n,e,t)=>{if(!e.has(n))throw TypeError("Cannot "+t)},qs=(n,e,t)=>(Tn(n,e,"read from private field"),t?t.call(n):e.get(n)),Qo=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},ea=(n,e,t,i)=>(Tn(n,e,"write to private field"),e.set(n,t),t),it;const X=WebGLRenderingContext,Ri=new D,ta=class Te{constructor({renderer:e,gltf:t}){Qo(this,it,void 0),e=Ae(e,"GLTFScenesManager"),this.renderer=e,this.gltf=t,ea(this,it,new Map);const i=s=>[s.node,...s.children?.map(r=>[...i(r)]).flat()].flat();this.scenesManager={node:new ve,boundingBox:new be,samplers:[],materialsTextures:[],scenes:[],meshes:[],meshesDescriptors:[],getScenesNodes:()=>this.scenesManager.scenes.map(s=>i(s)).flat()},this.createSamplers(),this.createMaterialTextures(),this.createScenes()}static getVertexAttributeParamsFromType(e){switch(e){case"VEC2":return{type:"vec2f",bufferFormat:"float32x2",size:2};case"VEC3":return{type:"vec3f",bufferFormat:"float32x3",size:3};case"VEC4":return{type:"vec4f",bufferFormat:"float32x4",size:4};case"SCALAR":default:return{type:"f32",bufferFormat:"float32",size:1}}}static getTypedArrayConstructorFromComponentType(e){switch(e){case X.BYTE:return Int8Array;case X.UNSIGNED_BYTE:return Uint8Array;case X.SHORT:return Int16Array;case X.UNSIGNED_SHORT:return Uint16Array;case X.UNSIGNED_INT:return Uint32Array;case X.FLOAT:default:return Float32Array}}static gpuPrimitiveTopologyForMode(e){switch(e){case X.TRIANGLES:return"triangle-list";case X.TRIANGLE_STRIP:return"triangle-strip";case X.LINES:return"line-list";case X.LINE_STRIP:return"line-strip";case X.POINTS:return"point-list"}}static gpuAddressModeForWrap(e){switch(e){case X.CLAMP_TO_EDGE:return"clamp-to-edge";case X.MIRRORED_REPEAT:return"mirror-repeat";default:return"repeat"}}createSamplers(){if(this.gltf.samplers)for(const[e,t]of Object.entries(this.gltf.samplers)){const i={label:"glTF sampler "+e,name:"gltfSampler"+e,addressModeU:Te.gpuAddressModeForWrap(t.wrapS),addressModeV:Te.gpuAddressModeForWrap(t.wrapT)};switch((!t.magFilter||t.magFilter===X.LINEAR)&&(i.magFilter="linear"),t.minFilter){case X.NEAREST:break;case X.LINEAR:case X.LINEAR_MIPMAP_NEAREST:i.minFilter="linear";break;case X.NEAREST_MIPMAP_LINEAR:i.mipmapFilter="linear";break;case X.LINEAR_MIPMAP_LINEAR:default:i.minFilter="linear",i.mipmapFilter="linear";break}this.scenesManager.samplers.push(new Ne(this.renderer,i))}else this.scenesManager.samplers.push(new Ne(this.renderer,{label:"Default sampler",name:"defaultSampler",magFilter:"linear",minFilter:"linear",mipmapFilter:"linear"}))}createTexture(e,t,i){const s=(()=>{switch(i){case"baseColorTexture":case"emissiveTexture":return"bgra8unorm-srgb";case"occlusionTexture":return"r8unorm";default:return"bgra8unorm"}})(),r=new K(this.renderer,{label:e.name?e.name+": "+i:i,name:i,format:s,visibility:["fragment"],generateMips:!0,fixedSize:{width:t.width,height:t.height}});return r.uploadSource({source:t}),r}createMaterialTextures(){if(this.scenesManager.materialsTextures=[],this.gltf.materials)for(const[e,t]of Object.entries(this.gltf.materials)){const i={material:e,texturesDescriptors:[]},s=r=>r.texCoord&&r.texCoord!==0?"uv"+r.texCoord:"uv";if(this.scenesManager.materialsTextures[e]=i,t.pbrMetallicRoughness){if(t.pbrMetallicRoughness.baseColorTexture&&t.pbrMetallicRoughness.baseColorTexture.index!==void 0){const r=t.pbrMetallicRoughness.baseColorTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"baseColorTexture"),h=this.gltf.textures.find(u=>u.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.pbrMetallicRoughness.baseColorTexture)})}if(t.pbrMetallicRoughness.metallicRoughnessTexture&&t.pbrMetallicRoughness.metallicRoughnessTexture.index!==void 0){const r=t.pbrMetallicRoughness.metallicRoughnessTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"metallicRoughnessTexture"),h=this.gltf.textures.find(u=>u.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.pbrMetallicRoughness.metallicRoughnessTexture)})}}if(t.normalTexture&&t.normalTexture.index!==void 0){const r=t.normalTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"normalTexture"),h=this.gltf.textures.find(u=>u.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.normalTexture)})}if(t.occlusionTexture&&t.occlusionTexture.index!==void 0){const r=t.occlusionTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"occlusionTexture"),h=this.gltf.textures.find(u=>u.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.occlusionTexture)})}if(t.emissiveTexture&&t.emissiveTexture.index!==void 0){const r=t.emissiveTexture.index,o=this.gltf.imagesBitmaps[this.gltf.textures[r].source],a=this.createTexture(t,o,"emissiveTexture"),h=this.gltf.textures.find(u=>u.source===r)?.sampler;i.texturesDescriptors.push({texture:a,sampler:this.scenesManager.samplers[h??0],texCoordAttributeName:s(t.emissiveTexture)})}}}createNode(e,t){if(t.camera!==void 0)return;const i={name:t.name,node:new ve,children:[]};e.children.push(i),i.node.parent=e.node,t.matrix?(i.node.modelMatrix.setFromArray(new Float32Array(t.matrix)),i.node.matrices.model.shouldUpdate=!1):(t.translation&&i.node.position.set(t.translation[0],t.translation[1],t.translation[2]),t.scale&&i.node.scale.set(t.scale[0],t.scale[1],t.scale[2]),t.rotation&&i.node.quaternion.setFromArray(new Float32Array(t.rotation)));const s=this.gltf.meshes[t.mesh];t.children&&t.children.forEach(r=>{const o=this.gltf.nodes[r];this.createNode(i,o)}),s&&s.primitives.forEach((r,o)=>{const a={parent:i.node,attributes:[],textures:[],parameters:{label:s.name?s.name+" "+o:"glTF mesh "+o},nodes:[]};let h=qs(this,it).get(r);h||(h={instances:[],nodes:[],meshDescriptor:a},qs(this,it).set(r,h)),h.instances.push(t),h.nodes.push(i.node)})}createScenes(){this.scenesManager.node.parent=this.renderer.scene,this.gltf.scenes.forEach(e=>{const t={name:e.name,children:[],node:new ve};t.node.parent=this.scenesManager.node,this.scenesManager.scenes.push(t),e.nodes.forEach(i=>{const s=this.gltf.nodes[i];this.createNode(t,s)})}),this.scenesManager.node.updateMatrixStack();for(const[e,t]of qs(this,it)){const{instances:i,nodes:s,meshDescriptor:r}=t,o=i.length;r.nodes=s,this.scenesManager.meshesDescriptors.push(r);const a=new be,h=[];let u=null,l=null,c=0;for(const[M,P]of Object.entries(e.attributes)){const w=this.gltf.accessors[P],S=Te.getTypedArrayConstructorFromComponentType(w.componentType),C=this.gltf.bufferViews[w.bufferView],b=M==="TEXCOORD_0"?"uv":M.replace("_","").replace("TEXCOORD","uv").toLowerCase(),R=C.byteStride||0,E=w.byteOffset||0;R&&E&&E0){const M=Object.values(e.attributes).map(P=>this.gltf.accessors[P].bufferView);if(M.every(P=>P===M[0]))u=new Float32Array(this.gltf.arrayBuffers[l.buffer],l.byteOffset,Math.ceil(l.byteLength/4)*4/Float32Array.BYTES_PER_ELEMENT);else{let P=0;const w={},S=Object.values(e.attributes).reduce((C,b)=>{const R=this.gltf.accessors[b],E=Te.getVertexAttributeParamsFromType(R.type).size;return w[R.bufferView]||(w[R.bufferView]=0),w[R.bufferView]=Math.max(w[R.bufferView],R.byteOffset+E*Float32Array.BYTES_PER_ELEMENT),P+=E*Float32Array.BYTES_PER_ELEMENT,C+R.count*E},0);u=new Float32Array(Math.ceil(S/4)*4),Object.values(e.attributes).forEach(C=>{const b=this.gltf.accessors[C],R=this.gltf.bufferViews[b.bufferView],E=Te.getVertexAttributeParamsFromType(b.type).size;for(let z=0;z{let S=M.findIndex(b=>b===P.name);S=S===-1?1/0:S;let C=M.findIndex(b=>b===w.name);return C=C===-1?1/0:C,S-C})}const p={instancesCount:o,topology:Te.gpuPrimitiveTopologyForMode(e.mode),vertexBuffers:[{name:"attributes",stepMode:"vertex",attributes:h,...u&&{array:u}}]},d="indices"in e,m=d?at:Ii;if(r.parameters.geometry=new m(p),r.parameters.geometry.boundingBox=a,d){const M=this.gltf.accessors[e.indices],P=this.gltf.bufferViews[M.bufferView],w=Te.getTypedArrayConstructorFromComponentType(M.componentType),S=M.byteOffset+P.byteOffset,C=this.gltf.arrayBuffers[P.buffer],b=Math.min((C.byteLength-S)/w.BYTES_PER_ELEMENT,Math.ceil(M.count/4)*4),R=w.name==="Uint8Array"?Uint16Array.from(new w(C,S,b)):new w(C,S,b);r.parameters.geometry.setIndexBuffer({bufferFormat:w.name==="Uint32Array"?"uint32":"uint16",array:R})}const g=this.scenesManager.materialsTextures[e.material];r.parameters.samplers=[],r.parameters.textures=[],g?.texturesDescriptors.forEach(M=>{r.textures.push({texture:M.texture.options.name,sampler:M.sampler.name,texCoordAttributeName:M.texCoordAttributeName}),r.parameters.samplers.find(w=>w.uuid===M.sampler.uuid)||r.parameters.samplers.push(M.sampler),r.parameters.textures.push(M.texture)});const y=this.gltf.materials&&this.gltf.materials[e.material]||{};r.parameters.cullMode=y.doubleSided?"none":"back",(y.alphaMode==="BLEND"||y.extensions&&y.extensions.KHR_materials_transmission)&&(r.parameters.transparent=!0,r.parameters.targets=[{blend:{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one"}}}]);const v={baseColorFactor:{type:"vec4f",value:y.pbrMetallicRoughness?.baseColorFactor||[1,1,1,1]},alphaCutoff:{type:"f32",value:y.alphaCutoff!==void 0?y.alphaCutoff:y.alphaMode==="MASK"?.5:0},metallicFactor:{type:"f32",value:y.pbrMetallicRoughness?.metallicFactor===void 0?1:y.pbrMetallicRoughness.metallicFactor},roughnessFactor:{type:"f32",value:y.pbrMetallicRoughness?.roughnessFactor===void 0?1:y.pbrMetallicRoughness.roughnessFactor},normalMapScale:{type:"f32",value:y.normalTexture?.scale===void 0?1:y.normalTexture.scale},occlusionStrength:{type:"f32",value:y.occlusionTexture?.strength===void 0?1:y.occlusionTexture.strength},emissiveFactor:{type:"vec3f",value:y.emissiveFactor!==void 0?y.emissiveFactor:[1,1,1]}};if(Object.keys(v).length&&(r.parameters.uniforms={material:{visibility:["vertex","fragment"],struct:v}}),o>1){const M=new Float32Array(o*16),P=new Float32Array(o*16);for(let w=0;w",value:M},normalMatrix:{type:"array",value:P}}}}}for(let M=0;M{}){return this.scenesManager.node.updateMatrixStack(),this.scenesManager.meshesDescriptors.map(t=>{if(t.parameters.geometry){e(t);const i=t.parameters.geometry.instancesCount>1&&t.parameters.castShadows;i&&(t.parameters.castShadows=!1);const s=new Nr(this.renderer,{...t.parameters});if(t.nodes.length>1){const r=s.updateWorldMatrix.bind(s);s.updateWorldMatrix=()=>{r(),t.nodes.forEach((o,a)=>{s.storages.instances.modelMatrix.value.set(o.worldMatrix.elements,a*16),Ri.copy(o.worldMatrix).invert().transpose(),s.storages.instances.normalMatrix.value.set(Ri.elements,a*16)}),s.storages.instances.modelMatrix.shouldUpdate=!0,s.storages.instances.normalMatrix.shouldUpdate=!0}}if(i){const r=s.material.inputsBindings.get("instances");this.renderer.shadowCastingLights.forEach(o=>{o.shadow.isActive&&o.shadow.addShadowCastingMesh(s,{bindings:[r]})})}return s.parent=t.parent,this.scenesManager.meshes.push(s),s}})}destroy(){this.scenesManager.meshes.forEach(t=>t.remove()),this.scenesManager.meshes=[],this.scenesManager.getScenesNodes().forEach(t=>{t.destroy()}),this.scenesManager.node.destroy()}};it=new WeakMap;let ia=ta;const sa=(n,e={})=>{const t=n.textures.find(G=>G.texture==="baseColorTexture"),i=n.textures.find(G=>G.texture==="normalTexture"),s=n.textures.find(G=>G.texture==="emissiveTexture"),r=n.textures.find(G=>G.texture==="occlusionTexture"),o=n.textures.find(G=>G.texture==="metallicRoughnessTexture"),a=n.attributes.filter(G=>G.name!=="position"),h=a.map((G,da)=>`@location(${da}) ${G.name}: ${G.type},`).join(` + `);let u=` let worldPos = matrices.model * vec4(attributes.position, 1.0); vsOutput.position = camera.projection * camera.view * worldPos; vsOutput.worldPosition = worldPos.xyz / worldPos.w; vsOutput.viewDirection = camera.position - vsOutput.worldPosition.xyz; - `,u=a.find(O=>O.name==="normal")?"vsOutput.normal = getWorldNormal(attributes.normal);":"";n.parameters.storages&&n.parameters.storages.instances&&(l=` + `,l=a.find(G=>G.name==="normal")?"vsOutput.normal = getWorldNormal(attributes.normal);":"";n.parameters.storages&&n.parameters.storages.instances&&(u=` let worldPos: vec4f = instances[attributes.instanceIndex].modelMatrix * vec4f(attributes.position, 1.0); vsOutput.position = camera.projection * camera.view * worldPos; vsOutput.worldPosition = worldPos.xyz; vsOutput.viewDirection = camera.position - vsOutput.worldPosition; - `,u="vsOutput.normal = normalize((instances[attributes.instanceIndex].normalMatrix * vec4(attributes.normal, 0.0)).xyz);");const d=a.filter(O=>O.name!=="normal").map(O=>`vsOutput.${O.name} = attributes.${O.name};`).join(` - `);let c=` + `,l="vsOutput.normal = normalize((instances[attributes.instanceIndex].normalMatrix * vec4(attributes.normal, 0.0)).xyz);");const c=a.filter(G=>G.name!=="normal").map(G=>`vsOutput.${G.name} = attributes.${G.name};`).join(` + `);let p=` @builtin(position) position: vec4f, @location(${a.length}) viewDirection: vec3f, @location(${a.length+1}) worldPosition: vec3f, ${h} - `,p="";const g=a.find(O=>O.name==="tangent"),m=!!(i&&g);m&&(c+=` + `,d="";const m=a.find(G=>G.name==="tangent"),g=!!(i&&m);g&&(p+=` @location(${a.length+2}) bitangent: vec3f, - `,p=` + `,d=` vsOutput.tangent = normalize(matrices.model * attributes.tangent); vsOutput.bitangent = cross(vsOutput.normal, vsOutput.tangent.xyz) * attributes.tangent.w; - `);const x=` + `);const y=` struct VSOutput { - ${c} + ${p} };`,v=` struct VSOutput { @builtin(front_facing) frontFacing: bool, - ${c} - };`,C=` - ${x} + ${p} + };`,M=` + ${y} @vertex fn main( attributes: Attributes, ) -> VSOutput { var vsOutput: VSOutput; - ${l} ${u} - ${d} + ${l} + ${c} - ${p} + ${d} return vsOutput; } - `,S="var color: vec4f = vec4();",b=` + `,P="var color: vec4f = vec4();",w=` return color; - `,P=n.attributes.find(O=>O.name==="color0");let B=P?P.type==="vec3f"?"var baseColor: vec4f = vec4(fsInput.color0, 1.0) * material.baseColorFactor;":"var baseColor: vec4f = fsInput.color0 * material.baseColorFactor;":"var baseColor: vec4f = material.baseColorFactor;";t&&(B=` + `,S=n.attributes.find(G=>G.name==="color0");let C=S?S.type==="vec3f"?"var baseColor: vec4f = vec4(fsInput.color0, 1.0) * material.baseColorFactor;":"var baseColor: vec4f = fsInput.color0 * material.baseColorFactor;":"var baseColor: vec4f = material.baseColorFactor;";t&&(C=` var baseColor: vec4f = textureSample(baseColorTexture, ${t.sampler}, fsInput.${t.texCoordAttributeName}) * material.baseColorFactor; if (baseColor.a < material.alphaCutoff) { discard; } - `),B+=` + `),C+=` color = baseColor; - `;let w=n.attributes.find(O=>O.name==="normal")?` + `;let b=n.attributes.find(G=>G.name==="normal")?` let faceDirection = select(-1.0, 1.0, fsInput.frontFacing); let geometryNormal: vec3f = normalize(faceDirection * fsInput.normal); - `:"let geometryNormal: vec3f = normalize(vec3(0.0, 0.0, 1.0));";m?w+=` + `:"let geometryNormal: vec3f = normalize(vec3(0.0, 0.0, 1.0));";g?b+=` let tbn = mat3x3(normalize(fsInput.tangent.xyz), normalize(fsInput.bitangent), geometryNormal); let normalMap = textureSample(normalTexture, ${i.sampler}, fsInput.${i.texCoordAttributeName}).rgb; let normal = normalize(tbn * (2.0 * normalMap - vec3(material.normalMapScale, material.normalMapScale, 1.0))); - `:w+=` + `:b+=` let normal = geometryNormal; - `;let T=` + `;let R=` var metallic = material.metallicFactor; var roughness = material.roughnessFactor; - `;o&&(T+=` + `;o&&(R+=` let metallicRoughness = textureSample(metallicRoughnessTexture, ${o.sampler}, fsInput.${o.texCoordAttributeName}); metallic = clamp(metallic * metallicRoughness.b, 0.0, 1.0); roughness = clamp(roughness * metallicRoughness.g, 0.0, 1.0); - `);const z=` + `);const E=` let f0: vec3f = mix(vec3(0.04), color.rgb, vec3(metallic)); - `;let R=` + `;let z=` var emissive: vec3f = vec3(0.0); var occlusion: f32 = 1.0; - `;s&&(R+=` + `;s&&(z+=` emissive = textureSample(emissiveTexture, ${s.sampler}, fsInput.${s.texCoordAttributeName}).rgb; emissive *= material.emissiveFactor; - `,r&&(R+=` + `,r&&(z+=` occlusion = textureSample(occlusionTexture, ${r.sampler}, fsInput.${r.texCoordAttributeName}).r; - `)),R+=` + `)),z+=` occlusion = 1.0 + material.occlusionStrength * (occlusion - 1.0); - `;let{shadingModel:D}=e;D||(D="PBR");let{chunks:G}=e||{};const{iblParameters:K}=e||{},{lutTexture:Q,envDiffuseTexture:X,envSpecularTexture:_}=K||{};X&&X.texture&&_&&_.texture&&Q&&Q.texture&&D==="IBL"?(n.parameters.uniforms={...n.parameters.uniforms,ibl:{struct:{diffuseStrength:{type:"f32",value:K?.diffuseStrength??.5},specularStrength:{type:"f32",value:K?.specularStrength??.5}}}},n.parameters.textures=[...n.parameters.textures,Q.texture,X.texture,_.texture],Q.samplerName=Q.samplerName||"defaultSampler",X.samplerName=X.samplerName||"defaultSampler",_.samplerName=_.samplerName||"defaultSampler"):D==="IBL"&&(I("IBL shading requested but one of the LUT, environment specular or diffuse texture is missing. Defaulting to PBR shading."),D="PBR");const V={toneMapping:"khronos",receiveShadows:!!n.parameters.receiveShadows,useOcclusion:!0},H=(()=>{switch(D){case"Lambert":default:return cr(V);case"Phong":return fr(V);case"PBR":return mr(V);case"IBL":return xr(V)}})(),ee="",pe="";G?(G.additionalFragmentHead?G.additionalFragmentHead=H+G.additionalFragmentHead:G.additionalFragmentHead=H,G.preliminaryColorContribution?G.preliminaryColorContribution=ee+G.preliminaryColorContribution:G.preliminaryColorContribution=ee,G.additionalColorContribution?G.additionalColorContribution=pe+G.additionalColorContribution:G.additionalColorContribution=pe):G={additionalFragmentHead:H,preliminaryColorContribution:ee,additionalColorContribution:pe};const ei=(()=>{switch(D){case"Lambert":default:return` + `;let{shadingModel:F}=e;F||(F="PBR");let{chunks:O}=e||{};const{iblParameters:J}=e||{},{environmentMap:q}=J||{};q&&F==="IBL"?(n.parameters.uniforms={...n.parameters.uniforms,ibl:{struct:{envRotation:{type:"mat3x3f",value:q.rotation},diffuseStrength:{type:"f32",value:J?.diffuseStrength??.5},specularStrength:{type:"f32",value:J?.specularStrength??.5}}}},n.parameters.textures=[...n.parameters.textures,q.lutTexture,q.diffuseTexture,q.specularTexture],n.parameters.samplers=[...n.parameters.samplers,q.sampler]):F==="IBL"&&(L("IBL shading requested but the environment map missing. Defaulting to PBR shading."),F="PBR");const ie={toneMapping:"khronos",receiveShadows:!!n.parameters.receiveShadows,useOcclusion:!0},U=(()=>{switch(F){case"Lambert":default:return sn(ie);case"Phong":return nn(ie);case"PBR":return on(ie);case"IBL":return hn(ie)}})(),I="",Y="";O?(O.additionalFragmentHead?O.additionalFragmentHead=U+O.additionalFragmentHead:O.additionalFragmentHead=U,O.preliminaryColorContribution?O.preliminaryColorContribution=I+O.preliminaryColorContribution:O.preliminaryColorContribution=I,O.additionalColorContribution?O.additionalColorContribution=Y+O.additionalColorContribution:O.additionalColorContribution=Y):O={additionalFragmentHead:U,preliminaryColorContribution:I,additionalColorContribution:Y};const ee=(()=>{switch(F){case"Lambert":default:return` color = vec4( getLambert( normal, @@ -1149,194 +1582,43 @@ fn getIBL( f0, metallic, roughness, - ${Q.texture.options.name}, - ${Q.samplerName}, - ${_.texture.options.name}, - ${_.samplerName}, - ${X.texture.options.name}, - ${X.samplerName}, + ${q.sampler.name}, + ${q.lutTexture.options.name}, + ${q.specularTexture.options.name}, + ${q.diffuseTexture.options.name}, occlusion ), color.a - );`}})(),no=` - ${G.additionalFragmentHead} + );`}})(),Ge=` + ${O.additionalFragmentHead} ${v} @fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { - ${S} - ${B} + ${P} + ${C} let worldPosition: vec3f = fsInput.worldPosition; let viewDirection: vec3f = fsInput.viewDirection; - ${w} - ${T} + ${b} + ${R} // user defined preliminary color contribution - ${G.preliminaryColorContribution} + ${O.preliminaryColorContribution} + ${E} ${z} - ${R} - ${ei} + ${ee} color = vec4(color.rgb + emissive, color.a); // user defined additional color contribution - ${G.additionalColorContribution} - - ${b} - } - `;return{vertex:{code:C,entryPoint:"main"},fragment:{code:no,entryPoint:"main"}}},Hn=async(n,e,t)=>{if(t.options.viewDimension!=="cube"){I("Could not compute the diffuse texture because the specular texture is not a cube map:"+t.options.viewDimension);return}const i=` - fn radicalInverse_VdC(inputBits: u32) -> f32 { - var bits: u32 = inputBits; - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return f32(bits) * 2.3283064365386963e-10; // / 0x100000000 - } - - // hammersley2d describes a sequence of points in the 2d unit square [0,1)^2 - // that can be used for quasi Monte Carlo integration - fn hammersley2d(i: u32, N: u32) -> vec2f { - return vec2(f32(i) / f32(N), radicalInverse_VdC(i)); - } - - // TBN generates a tangent bitangent normal coordinate frame from the normal - // (the normal must be normalized) - fn generateTBN(normal: vec3f) -> mat3x3f { - var bitangent: vec3f = vec3(0.0, 1.0, 0.0); - - let NdotUp: f32 = dot(normal, vec3(0.0, 1.0, 0.0)); - let epsilon: f32 = 0.0000001; - - if (1.0 - abs(NdotUp) <= epsilon) { - // Sampling +Y or -Y, so we need a more robust bitangent. - if (NdotUp > 0.0) { - bitangent = vec3(0.0, 0.0, 1.0); - } - else { - bitangent = vec3(0.0, 0.0, -1.0); - } - } - - let tangent: vec3f = normalize(cross(bitangent, normal)); - bitangent = cross(normal, tangent); - - return mat3x3f(tangent, bitangent, normal); - } - - // Mipmap Filtered Samples (GPU Gems 3, 20.4) - // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - fn computeLod(pdf: f32) -> f32 { - // https://cgg.mff.cuni.cz/~jaroslav/papers/2007-sketch-fis/Final_sap_0073.pdf - return 0.5 * log2( 6.0 * f32(params.faceSize) * f32(params.faceSize) / (f32(params.sampleCount) * pdf)); - } - - fn transformDirection(face: u32, uv: vec2f) -> vec3f { - // Transform the direction based on the cubemap face - switch (face) { - case 0u { - // +X - return vec3f( 1.0, uv.y, -uv.x); - } - case 1u { - // -X - return vec3f(-1.0, uv.y, uv.x); - } - case 2u { - // +Y - return vec3f( uv.x, -1.0, uv.y); - } - case 3u { - // -Y - return vec3f( uv.x, 1.0, -uv.y); - } - case 4u { - // +Z - return vec3f( uv.x, uv.y, 1.0); - } - case 5u { - // -Z - return vec3f(-uv.x, uv.y, -1.0); - } - default { - return vec3f(0.0, 0.0, 0.0); - } - } - } - - const PI = ${Math.PI}; - - @compute @workgroup_size(8, 8, 1) fn main( - @builtin(global_invocation_id) GlobalInvocationID: vec3u, - ) { - let faceSize: u32 = params.faceSize; - let sampleCount: u32 = params.sampleCount; - - let face: u32 = GlobalInvocationID.z; - let x: u32 = GlobalInvocationID.x; - let y: u32 = GlobalInvocationID.y; - - if (x >= faceSize || y >= faceSize) { - return; - } - - let texelSize: f32 = 1.0 / f32(faceSize); - let halfTexel: f32 = texelSize * 0.5; + ${O.additionalColorContribution} - var uv: vec2f = vec2( - (f32(x) + halfTexel) * texelSize, - (f32(y) + halfTexel) * texelSize - ); - - uv = uv * 2.0 - 1.0; - - let normal: vec3 = transformDirection(face, uv); - - var irradiance: vec3f = vec3f(0.0, 0.0, 0.0); - - for (var i: u32 = 0; i < sampleCount; i++) { - // generate a quasi monte carlo point in the unit square [0.1)^2 - let xi: vec2f = hammersley2d(i, sampleCount); - - let cosTheta: f32 = sqrt(1.0 - xi.y); - let sinTheta: f32 = sqrt(1.0 - cosTheta * cosTheta); - let phi: f32 = 2.0 * PI * xi.x; - let pdf: f32 = cosTheta / PI; // evaluation for solid angle, therefore drop the sinTheta - - let sampleVec: vec3f = vec3f( - sinTheta * cos(phi), - sinTheta * sin(phi), - cosTheta - ); - - let TBN: mat3x3f = generateTBN(normalize(normal)); - - var direction: vec3f = TBN * sampleVec; - - // invert along Y axis - direction.y *= -1.0; - - let lod: f32 = computeLod(pdf); - - // Convert sampleVec to texture coordinates of the specular env map - irradiance += textureSampleLevel( - envSpecularTexture, - specularSampler, - direction, - min(lod, f32(params.maxMipLevel)) - ).rgb; - } - - irradiance /= f32(sampleCount); - - textureStore(diffuseEnvMap, vec2(x, y), face, vec4f(irradiance, 1.0)); + ${w} } - `;let s=new se(n,{label:"Diffuse storage cubemap",name:"diffuseEnvMap",format:"rgba32float",visibility:["compute"],usage:["copySrc","storageBinding"],type:"storage",fixedSize:{width:t.size.width,height:t.size.height,depth:6},viewDimension:"2d-array"});const r=new Ae(n,{label:"Compute diffuse sampler",name:"specularSampler",addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge",minFilter:"linear",magFilter:"linear"});let o=new Ms(n,{autoRender:!1,dispatchSize:[Math.ceil(t.size.width/8),Math.ceil(t.size.height/8),6],shaders:{compute:{code:i}},uniforms:{params:{struct:{faceSize:{type:"u32",value:t.size.width},maxMipLevel:{type:"u32",value:t.texture.mipLevelCount},sampleCount:{type:"u32",value:2048}}}},samplers:[r],textures:[t,s]});await o.material.compileMaterial(),n.onBeforeRenderScene.add(a=>{n.renderSingleComputePass(a,o),a.copyTextureToTexture({texture:s.texture},{texture:e.texture},[e.texture.width,e.texture.height,e.texture.depthOrArrayLayers])},{once:!0}),n.onAfterCommandEncoderSubmission.add(()=>{o.destroy(),s.destroy(),s=null,o=null},{once:!0})},Rr=WebGLRenderingContext,Zn=1179937895,Ji={JSON:1313821514,BIN:5130562},Kn=[0,0,0],Qn=[0,0,0,1],Jn=[1,1,1],eo=typeof window<"u"&&new RegExp(`^${window.location.protocol}`,"i")||RegExp("^(http|https):","i"),to=/^data:/;class Zt{constructor(){this.gltf=null}static resolveUri(e,t){return e.match(eo)||e.match(to)?e:t+e}async loadFromUrl(e){const t=e.lastIndexOf("/"),i=t!==0?e.substring(0,t+1):"",s=await fetch(e);if(e.endsWith(".gltf"))return this.loadFromJson(await s.json(),i);if(e.endsWith(".glb"))return this.loadFromBinary(await s.arrayBuffer(),i);throw new Error("Unrecognized file extension")}async loadFromJsonBase(e,t,i=null){if(!t)throw new Error("baseUrl must be specified.");if(!e.asset)throw new Error("Missing asset description.");if(e.asset.minVersion!=="2.0"&&e.asset.version!=="2.0")throw new Error("Incompatible asset version.");for(const o of e.accessors)o.byteOffset=o.byteOffset??0,o.normalized=o.normalized??!1;for(const o of e.bufferViews)o.byteOffset=o.byteOffset??0;for(const o of e.nodes)o.matrix||(o.rotation=o.rotation??Qn,o.scale=o.scale??Jn,o.translation=o.translation??Kn);if(e.samplers)for(const o of e.samplers)o.wrapS=o.wrapS??Rr.REPEAT,o.wrapT=o.wrapT??Rr.REPEAT;const s=[];if(i)s.push(Promise.resolve(i));else for(const o in e.buffers){const a=e.buffers[o],h=Zt.resolveUri(a.uri,t);s[o]=fetch(h).then(l=>l.arrayBuffer())}const r=[];for(let o=0;ocreateImageBitmap(await h.blob()));else{const h=e.bufferViews[a.bufferView];r[o]=s[h.buffer].then(l=>{const u=new Blob([new Uint8Array(l,h.byteOffset,h.byteLength)],{type:a.mimeType});return createImageBitmap(u)})}}return{...e,arrayBuffers:await Promise.all(s),imagesBitmaps:await Promise.all(r)}}async loadFromBinary(e,t){const i=new DataView(e,0,12),s=i.getUint32(0,!0),r=i.getUint32(4,!0),o=i.getUint32(8,!0);if(s!==Zn)throw new Error("Invalid magic string in binary header.");if(r!==2)throw new Error("Incompatible version in binary header.");const a={};let h=12;for(;h{if(!e.has(n))throw TypeError("Cannot "+t)},ye=(n,e,t)=>{if(e.has(n))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(n):e.set(n,t)},oe=(n,e,t)=>(io(n,e,"access private method"),t),es,zr,ts,Er,Kt,is,ot,Qt,ss,Lr,rs,_r,Jt,ns,os,Ar,as,Gr;class so{constructor(){ye(this,es),ye(this,ts),ye(this,Kt),ye(this,ot),ye(this,ss),ye(this,rs),ye(this,Jt),ye(this,os),ye(this,as)}async loadFromUrl(e){const t=await(await fetch(e)).arrayBuffer();return oe(this,es,zr).call(this,new DataView(t))}equirectangularToCubeMap(e){const t=Math.max(e.width/4,e.height/2),i={posX:new Float32Array(t*t*4),negX:new Float32Array(t*t*4),posY:new Float32Array(t*t*4),negY:new Float32Array(t*t*4),posZ:new Float32Array(t*t*4),negZ:new Float32Array(t*t*4)};function s(l,u){const d=Math.floor(l*e.width),p=(Math.floor(u*e.height)*e.width+d)*4;return[e.data[p],e.data[p+1],e.data[p+2],e.data[p+3]]}function r(l,u,d,c){const p=(d*t+u)*4;i[l][p]=c[0],i[l][p+1]=c[1],i[l][p+2]=c[2],i[l][p+3]=c[3]}function o(l,u,d){const c=2*(u+.5)/t-1,p=2*(d+.5)/t-1;switch(l){case"posX":return[c,-1,-p];case"negX":return[-c,1,-p];case"posY":return[-p,-c,1];case"negY":return[p,-c,-1];case"posZ":return[-1,-c,-p];case"negZ":return[1,c,-p]}}function a(l){const[u,d,c]=l,p=Math.sqrt(u*u+d*d),g=Math.atan2(d,u),m=Math.atan2(c,p),x=(g+Math.PI)/(2*Math.PI),v=(m+Math.PI/2)/Math.PI;return[x,v]}for(const l in i)for(let u=0;u({data:l,width:t,height:t,exposure:e.exposure,gamma:e.gamma}))}}es=new WeakSet,zr=function(n){const e={data:n,offset:0},t=oe(this,ts,Er).call(this,e);return{width:t.width,height:t.height,exposure:t.exposure,gamma:t.gamma,data:oe(this,ss,Lr).call(this,e,t)}},ts=new WeakSet,Er=function(n){let e=oe(this,ot,Qt).call(this,n);const t={colorCorr:[1,1,1],exposure:1,gamma:1,width:0,height:0,flipX:!1,flipY:!1};if(e!=="#?RADIANCE"&&e!=="#?RGBE")throw new Error("Incorrect file format!");for(;e!=="";){e=oe(this,ot,Qt).call(this,n);const s=e.split("=");switch(s[0]){case"GAMMA":t.gamma=parseFloat(s[1]);break;case"FORMAT":if(s[1]!=="32-bit_rle_rgbe"&&s[1]!=="32-bit_rle_xyze")throw new Error("Incorrect encoding format!");break;case"EXPOSURE":t.exposure=parseFloat(s[1]);break;case"COLORCORR":t.colorCorr=s[1].replace(/^\s+|\s+$/g,"").split(" ").map(r=>parseFloat(r));break}}e=oe(this,ot,Qt).call(this,n);const i=e.split(" ");return oe(this,Kt,is).call(this,i[0],parseInt(i[1]),t),oe(this,Kt,is).call(this,i[2],parseInt(i[3]),t),t},Kt=new WeakSet,is=function(n,e,t){switch(n){case"+X":t.width=e;break;case"-X":t.width=e,t.flipX=!0,console.warn("Flipping horizontal orientation not currently supported");break;case"-Y":t.height=e,t.flipY=!0;break;case"+Y":t.height=e;break}},ot=new WeakSet,Qt=function(n){let e,t="";for(;(e=n.data.getUint8(n.offset++))!==10;)t+=String.fromCharCode(e);return t},ss=new WeakSet,Lr=function(n,e){const t=n.data.getUint16(n.offset);let i;if(t===514)i=oe(this,rs,_r).call(this,n,e),e.flipX&&oe(this,os,Ar).call(this,i,e),e.flipY&&oe(this,as,Gr).call(this,i,e);else throw new Error("Obsolete HDR file version!");return i},rs=new WeakSet,_r=function(n,e){const{width:t,height:i,colorCorr:s}=e,r=new Float32Array(t*i*4);let o=0,{offset:a,data:h}=n;for(let l=0;l128){const g=p-128;p=h.getUint8(a++);for(let m=0;m>1;for(let r=0;r>1;for(let r=0;r{const{scene:e}=n;if(!e)return;const t=[];e.computePassEntries.forEach(i=>{t.push({command:"Render ComputePass",content:i.options.label}),i.material.bindGroups.forEach(s=>{s.bufferBindings.forEach(r=>{r.shouldCopyResult&&t.push({command:"Copy buffer to buffer",source:`${r.name} buffer`,destination:`${r.name} result buffer`})})})});for(const i in e.renderPassEntries){let s=0;e.renderPassEntries[i].forEach(r=>{if(!e.getRenderPassEntryLength(r))return;const o=r.renderPass.options.useColorAttachments?r.renderPass.options.colorAttachments.length===0&&r.renderPass.options.useDepth?`${r.renderTexture.options.label} depth pass`:r.renderPass.options.colorAttachments.length>1?`${r.renderTexture.options.label} multiple targets`:r.renderTexture?`${r.renderTexture.options.label}`:"Context current texture":void 0;let a=r.renderPass.options.label;const h={loadOp:r.renderPass.options.useColorAttachments?i==="screen"&&s>0?"load":r.renderPass.options.loadOp:void 0,depthLoadOp:void 0,sampleCount:r.renderPass.options.sampleCount,...r.renderPass.options.qualityRatio!==1&&{qualityRatio:r.renderPass.options.qualityRatio}};if(r.renderPass.options.useDepth&&(h.depthLoadOp=r.renderPass.options.depthLoadOp),s++,r.element)r.element.type==="ShaderPass"&&!(r.element.inputTarget||r.element.outputTarget)&&(t.push({command:"Copy texture to texture",source:o,destination:`${r.element.options.label} renderTexture`}),h.loadOp="clear"),a+=" "+JSON.stringify(h),t.push({command:`Render ${r.element.type}`,source:r.element.options.label,destination:o,descriptor:a}),r.element.type==="ShaderPass"&&!r.element.outputTarget&&r.element.options.copyOutputToRenderTexture?t.push({command:"Copy texture to texture",source:o,destination:`${r.element.options.label} renderTexture`}):r.element.type==="PingPongPlane"&&t.push({command:"Copy texture to texture",source:o,destination:`${r.element.renderTexture.options.label}`});else if(r.stack){a+=" "+JSON.stringify(h);for(const l in r.stack)for(const u in r.stack[l])r.stack[l][u].length&&t.push({command:`Render stack (${l} ${u} objects)`,source:r.stack[l][u],destination:o,descriptor:a})}})}console.table(t)};y.AmbientLight=tn,y.BindGroup=ut,y.Binding=ht,y.Box3=de,y.BoxGeometry=In,y.Buffer=_e,y.BufferBinding=fe,y.Camera=gs,y.ComputeMaterial=vs,y.ComputePass=Ms,y.ComputePipelineEntry=rr,y.DOMElement=Ri,y.DOMFrustum=Bs,y.DOMMesh=br,y.DOMObject3D=vr,y.DOMTexture=Se,y.DirectionalLight=an,y.FullscreenPlane=Ci,y.GLTFLoader=Zt,y.GLTFScenesManager=Yn,y.GPUCameraRenderer=Ai,y.GPUCurtains=$n,y.GPUCurtainsRenderer=Ut,y.GPUDeviceManager=ur,y.GPURenderer=Li,y.Geometry=di,y.HDRLoader=so,y.IndexedGeometry=Ye,y.Mat3=ge,y.Mat4=A,y.Material=ui,y.Mesh=ir,y.Object3D=me,y.OrbitControls=kn,y.PingPongPlane=Nn,y.PipelineEntry=Pi,y.PipelineManager=nr,y.Plane=Mr,y.PlaneGeometry=ci,y.PointLight=cn,y.ProjectedObject3D=Bi,y.Quat=le,y.RenderMaterial=yi,y.RenderPass=Bt,y.RenderPipelineEntry=Si,y.RenderTarget=fi,y.Sampler=Ae,y.SamplerBinding=fs,y.Scene=or,y.ShaderPass=En,y.SphereGeometry=Vn,y.Texture=se,y.TextureBindGroup=li,y.TextureBinding=hi,y.Vec2=E,y.Vec3=f,y.WritableBufferBinding=ai,y.applyDirectionalShadows=Ze,y.applyPointShadows=Ke,y.buildShaders=Xn,y.computeDiffuseFromSpecular=Hn,y.getDefaultPointShadowDepthFs=Ds,y.getDefaultPointShadowDepthVs=Os,y.getDefaultShadowDepthVs=_s,y.getIBL=xr,y.getIBLIndirect=gr,y.getLambert=cr,y.getLambertDirect=dr,y.getPBR=mr,y.getPBRDirect=Oi,y.getPCFDirectionalShadows=Gs,y.getPCFPointShadowContribution=Fs,y.getPCFPointShadows=Us,y.getPCFShadowContribution=As,y.getPCFShadows=He,y.getPhong=fr,y.getPhongDirect=pr,y.lambertUtils=Dt,y.logSceneCommands=ro,y.pbrUtils=Gi,y.toneMappingUtils=st}); + `;return{vertex:{code:M,entryPoint:"main"},fragment:{code:Ge,entryPoint:"main"}}},Pn=WebGLRenderingContext,ra=1179937895,Ys={JSON:1313821514,BIN:5130562},na=[0,0,0],oa=[0,0,0,1],aa=[1,1,1],ha=typeof window<"u"&&new RegExp(`^${window.location.protocol}`,"i")||RegExp("^(http|https):","i"),ua=/^data:/;class zi{constructor(){this.gltf=null}static resolveUri(e,t){return e.match(ha)||e.match(ua)?e:t+e}async loadFromUrl(e){const t=e.lastIndexOf("/"),i=t!==0?e.substring(0,t+1):"",s=await fetch(e);if(e.endsWith(".gltf"))return this.loadFromJson(await s.json(),i);if(e.endsWith(".glb"))return this.loadFromBinary(await s.arrayBuffer(),i);throw new Error("Unrecognized file extension")}async loadFromJsonBase(e,t,i=null){if(!t)throw new Error("baseUrl must be specified.");if(!e.asset)throw new Error("Missing asset description.");if(e.asset.minVersion!=="2.0"&&e.asset.version!=="2.0")throw new Error("Incompatible asset version.");for(const o of e.accessors)o.byteOffset=o.byteOffset??0,o.normalized=o.normalized??!1;for(const o of e.bufferViews)o.byteOffset=o.byteOffset??0;for(const o of e.nodes)o.matrix||(o.rotation=o.rotation??oa,o.scale=o.scale??aa,o.translation=o.translation??na);if(e.samplers)for(const o of e.samplers)o.wrapS=o.wrapS??Pn.REPEAT,o.wrapT=o.wrapT??Pn.REPEAT;const s=[];if(i)s.push(Promise.resolve(i));else for(const o in e.buffers){const a=e.buffers[o],h=zi.resolveUri(a.uri,t);s[o]=fetch(h).then(u=>u.arrayBuffer())}const r=[];for(let o=0;ocreateImageBitmap(await h.blob()));else{const h=e.bufferViews[a.bufferView];r[o]=s[h.buffer].then(u=>{const l=new Blob([new Uint8Array(u,h.byteOffset,h.byteLength)],{type:a.mimeType});return createImageBitmap(l)})}}return{...e,arrayBuffers:await Promise.all(s),imagesBitmaps:await Promise.all(r)}}async loadFromBinary(e,t){const i=new DataView(e,0,12),s=i.getUint32(0,!0),r=i.getUint32(4,!0),o=i.getUint32(8,!0);if(s!==ra)throw new Error("Invalid magic string in binary header.");if(r!==2)throw new Error("Incompatible version in binary header.");const a={};let h=12;for(;h{const{scene:e}=n;if(!e)return;const t=[];e.computePassEntries.forEach(i=>{t.push({command:"Render ComputePass",content:i.options.label}),i.material.bindGroups.forEach(s=>{s.bufferBindings.forEach(r=>{r.shouldCopyResult&&t.push({command:"Copy buffer to buffer",source:`${r.name} buffer`,destination:`${r.name} result buffer`})})})});for(const i in e.renderPassEntries){let s=0;e.renderPassEntries[i].forEach(r=>{if(!e.getRenderPassEntryLength(r))return;const o=r.renderPass.options.useColorAttachments?r.renderPass.options.colorAttachments.length===0&&r.renderPass.options.useDepth?`${r.renderTexture.options.label} depth pass`:r.renderPass.options.colorAttachments.length>1?`${r.renderTexture.options.label} multiple targets`:r.renderTexture?`${r.renderTexture.options.label}`:"Context current texture":void 0;let a=r.renderPass.options.label;const h={loadOp:r.renderPass.options.useColorAttachments?i==="screen"&&s>0?"load":r.renderPass.options.loadOp:void 0,depthLoadOp:void 0,sampleCount:r.renderPass.options.sampleCount,...r.renderPass.options.qualityRatio!==1&&{qualityRatio:r.renderPass.options.qualityRatio}};if(r.renderPass.options.useDepth&&(h.depthLoadOp=r.renderPass.options.depthLoadOp),s++,r.element)r.element.type==="ShaderPass"&&!(r.element.inputTarget||r.element.outputTarget)&&(t.push({command:"Copy texture to texture",source:o,destination:`${r.element.options.label} renderTexture`}),h.loadOp="clear"),a+=" "+JSON.stringify(h),t.push({command:`Render ${r.element.type}`,source:r.element.options.label,destination:o,descriptor:a}),r.element.type==="ShaderPass"&&!r.element.outputTarget&&r.element.options.copyOutputToRenderTexture?t.push({command:"Copy texture to texture",source:o,destination:`${r.element.options.label} renderTexture`}):r.element.type==="PingPongPlane"&&t.push({command:"Copy texture to texture",source:o,destination:`${r.element.renderTexture.options.label}`});else if(r.stack){a+=" "+JSON.stringify(h);for(const u in r.stack)for(const l in r.stack[u])r.stack[u][l].length&&t.push({command:`Render stack (${u} ${l} objects)`,source:r.stack[u][l],destination:o,descriptor:a})}})}console.table(t)};x.AmbientLight=Qn,x.BindGroup=Dt,x.Binding=_t,x.Box3=be,x.BoxGeometry=Ho,x.Buffer=Ve,x.BufferBinding=pe,x.Camera=sr,x.ComputeMaterial=or,x.ComputePass=Yt,x.ComputePipelineEntry=is,x.DOMElement=rs,x.DOMFrustum=lr,x.DOMMesh=cn,x.DOMObject3D=ln,x.DOMTexture=Fe,x.DirectionalLight=ho,x.EnvironmentMap=Yo,x.FullscreenPlane=es,x.GLTFLoader=zi,x.GLTFScenesManager=ia,x.GPUCameraRenderer=us,x.GPUCurtains=Uo,x.GPUCurtainsRenderer=pi,x.GPUDeviceManager=Xr,x.GPURenderer=as,x.Geometry=Ii,x.HDRLoader=Cn,x.IndexedGeometry=at,x.Mat3=Be,x.Mat4=D,x.Material=ki,x.Mesh=Nr,x.Object3D=ve,x.OrbitControls=ko,x.PingPongPlane=Ko,x.PipelineEntry=ji,x.PipelineManager=Wr,x.Plane=pn,x.PlaneGeometry=Vi,x.PointLight=fo,x.ProjectedObject3D=ts,x.Quat=ce,x.Raycaster=Jo,x.RenderBundle=Ro,x.RenderMaterial=Xi,x.RenderPass=Kt,x.RenderPipelineEntry=je,x.RenderTarget=Wi,x.Sampler=Ne,x.SamplerBinding=tr,x.Scene=jr,x.ShaderPass=Eo,x.SphereGeometry=Xo,x.Texture=K,x.TextureBindGroup=Ui,x.TextureBinding=$i,x.Vec2=_,x.Vec3=f,x.WritableBufferBinding=Fi,x.applyDirectionalShadows=lt,x.applyPointShadows=dt,x.buildShaders=sa,x.getDefaultPointShadowDepthFs=Mr,x.getDefaultPointShadowDepthVs=Br,x.getDefaultShadowDepthVs=vr,x.getIBL=hn,x.getIBLIndirect=an,x.getLambert=sn,x.getLambertDirect=tn,x.getPBR=on,x.getPBRDirect=xs,x.getPCFDirectionalShadows=wr,x.getPCFPointShadowContribution=Cr,x.getPCFPointShadows=Sr,x.getPCFShadowContribution=br,x.getPCFShadows=ut,x.getPhong=nn,x.getPhongDirect=rn,x.lambertUtils=di,x.logSceneCommands=la,x.pbrUtils=ys,x.toneMappingUtils=vt}); //# sourceMappingURL=gpu-curtains.umd.min.js.map diff --git a/dist/gpu-curtains.umd.min.js.map b/dist/gpu-curtains.umd.min.js.map index ee8c22274..a2ab6362b 100644 --- a/dist/gpu-curtains.umd.min.js.map +++ b/dist/gpu-curtains.umd.min.js.map @@ -1 +1 @@ -{"version":3,"file":"gpu-curtains.umd.min.js","sources":["../src/utils/utils.ts","../src/core/renderers/utils.ts","../src/core/bindings/utils.ts","../src/core/bindings/Binding.ts","../src/math/Vec2.ts","../src/math/Quat.ts","../src/math/Vec3.ts","../src/core/bindings/bufferElements/BufferElement.ts","../src/core/bindings/bufferElements/BufferArrayElement.ts","../src/core/bindings/bufferElements/BufferInterleavedArrayElement.ts","../src/core/buffers/utils.ts","../src/core/buffers/Buffer.ts","../src/core/bindings/BufferBinding.ts","../src/core/bindings/WritableBufferBinding.ts","../src/core/bindGroups/BindGroup.ts","../src/core/bindings/TextureBinding.ts","../src/math/Mat4.ts","../src/core/objects3D/Object3D.ts","../src/core/textures/utils.ts","../src/core/textures/DOMTexture.ts","../src/core/bindGroups/TextureBindGroup.ts","../src/core/bindings/SamplerBinding.ts","../src/core/camera/Camera.ts","../src/core/samplers/Sampler.ts","../src/core/textures/Texture.ts","../src/core/materials/Material.ts","../src/core/materials/ComputeMaterial.ts","../src/core/computePasses/ComputePass.ts","../src/math/Box3.ts","../src/core/DOM/DOMFrustum.ts","../src/core/geometries/Geometry.ts","../src/core/geometries/IndexedGeometry.ts","../src/core/geometries/PlaneGeometry.ts","../src/core/lights/Light.ts","../src/core/lights/AmbientLight.ts","../src/core/renderPasses/RenderPass.ts","../src/core/renderPasses/RenderTarget.ts","../src/core/materials/utils.ts","../src/core/shaders/chunks/default/default_projected_vs.wgsl.js","../src/core/shaders/chunks/default/default_vs.wgsl.js","../src/core/shaders/chunks/default/default_fs.wgsl.js","../src/core/materials/RenderMaterial.ts","../src/core/shaders/chunks/shading/shadows.ts","../src/core/shadows/Shadow.ts","../src/core/shadows/DirectionalShadow.ts","../src/core/lights/DirectionalLight.ts","../src/core/shadows/PointShadow.ts","../src/core/lights/PointLight.ts","../src/core/meshes/mixins/MeshBaseMixin.ts","../src/utils/CacheManager.ts","../src/core/meshes/FullscreenPlane.ts","../src/math/Mat3.ts","../src/core/objects3D/ProjectedObject3D.ts","../src/core/shaders/chunks/default/default_normal_fs.wgsl.js","../src/core/meshes/mixins/ProjectedMeshBaseMixin.ts","../src/core/meshes/Mesh.ts","../src/core/pipelines/PipelineEntry.ts","../src/core/shaders/chunks/helpers/get_output_position.wgsl.js","../src/core/shaders/chunks/helpers/get_normals.wgsl.js","../src/core/shaders/chunks/helpers/get_uv_cover.wgsl.js","../src/core/shaders/chunks/helpers/get_vertex_to_uv_coords.wgsl.js","../src/core/shaders/ShaderChunks.ts","../src/core/pipelines/RenderPipelineEntry.ts","../src/core/pipelines/ComputePipelineEntry.ts","../src/core/pipelines/PipelineManager.ts","../src/utils/ResizeManager.ts","../src/core/DOM/DOMElement.ts","../src/core/scenes/Scene.ts","../src/utils/TasksQueueManager.ts","../src/core/renderers/GPURenderer.ts","../src/core/renderers/GPUCameraRenderer.ts","../src/core/renderers/GPUDeviceManager.ts","../src/core/shaders/chunks/default/default_pass_fs.wgsl.js","../src/core/renderPasses/ShaderPass.ts","../src/core/shaders/chunks/helpers/common.wgsl.js","../src/core/shaders/chunks/helpers/lights/light_utils.wgsl.js","../src/core/shaders/chunks/shading/tone-mapping-utils.ts","../src/core/shaders/chunks/helpers/lights/RE_indirect_diffuse.wgsl.js","../src/core/shaders/chunks/helpers/constants.wgsl.js","../src/core/shaders/chunks/shading/lambert-shading.ts","../src/core/shaders/chunks/shading/phong-shading.ts","../src/core/shaders/chunks/helpers/lights/RE_indirect_specular.wgsl.js","../src/core/shaders/chunks/shading/pbr-shading.ts","../src/core/shaders/chunks/shading/ibl-shading.ts","../src/curtains/objects3D/DOMObject3D.ts","../src/curtains/meshes/DOMMesh.ts","../src/curtains/meshes/Plane.ts","../src/curtains/renderers/GPUCurtainsRenderer.ts","../src/utils/ScrollManager.ts","../src/curtains/GPUCurtains.ts","../src/extras/controls/OrbitControls.ts","../src/extras/geometries/BoxGeometry.ts","../src/extras/geometries/SphereGeometry.ts","../src/extras/meshes/PingPongPlane.ts","../src/extras/gltf/GLTFScenesManager.ts","../src/extras/gltf/utils.ts","../src/extras/loaders/GLTFLoader.ts","../src/extras/loaders/HDRLoader.ts","../src/utils/debug.ts"],"sourcesContent":["/**\r\n * Generate a unique universal id\r\n * @returns - unique universal id generated\r\n */\r\nexport const generateUUID = (): string => {\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0,\r\n v = c === 'x' ? r : (r & 0x3) | 0x8\r\n return v.toString(16).toUpperCase()\r\n })\r\n}\r\n\r\n/**\r\n * Turns a string into a camel case string\r\n * @param string - string to transform\r\n * @returns - camel case string created\r\n */\r\nexport const toCamelCase = (string: string): string => {\r\n return string\r\n .replace(/(?:^\\w|[A-Z]|\\b\\w)/g, (ltr, idx) => (idx === 0 ? ltr.toLowerCase() : ltr.toUpperCase()))\r\n .replace(/\\s+/g, '')\r\n}\r\n\r\n/**\r\n * Turns a string into a kebab case string\r\n * @param string - string to transform\r\n * @returns - kebab case string created\r\n */\r\nexport const toKebabCase = (string: string): string => {\r\n const camelCase = toCamelCase(string)\r\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1)\r\n}\r\n\r\nlet warningThrown = 0\r\n\r\n/**\r\n * Throw a console warning with the passed arguments\r\n * @param warning - warning to be thrown\r\n */\r\nexport const throwWarning = (warning: string) => {\r\n if (warningThrown > 100) {\r\n return\r\n } else if (warningThrown === 100) {\r\n console.warn('GPUCurtains: too many warnings thrown, stop logging.')\r\n } else {\r\n console.warn(warning)\r\n }\r\n\r\n warningThrown++\r\n}\r\n\r\n/**\r\n * Throw a javascript error with the passed arguments\r\n * @param error - error to be thrown\r\n */\r\nexport const throwError = (error: string) => {\r\n throw new Error(error)\r\n}\r\n","import { throwError } from '../../utils/utils'\r\nimport { GPURenderer } from './GPURenderer'\r\nimport { GPUCameraRenderer } from './GPUCameraRenderer'\r\nimport { GPUCurtainsRenderer } from '../../curtains/renderers/GPUCurtainsRenderer'\r\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\r\n\r\n/**\r\n * A Renderer could be either a {@link GPURenderer}, a {@link GPUCameraRenderer} or a {@link GPUCurtainsRenderer}\r\n * @type {Renderer}\r\n */\r\nexport type Renderer = GPUCurtainsRenderer | GPUCameraRenderer | GPURenderer\r\n/**\r\n * A CameraRenderer could be either a {@link GPUCameraRenderer} or a {@link GPUCurtainsRenderer}\r\n * @type {CameraRenderer}\r\n */\r\nexport type CameraRenderer = GPUCurtainsRenderer | GPUCameraRenderer\r\n\r\n/**\r\n * Format a renderer error based on given renderer, renderer type and object type\r\n * @param renderer - renderer that failed the test\r\n * @param rendererType - expected renderer type\r\n * @param type - object type\r\n */\r\nconst formatRendererError = (renderer: Renderer, rendererType = 'GPURenderer', type: string | null): void => {\r\n const error = type\r\n ? `Unable to create ${type} because the ${rendererType} is not defined: ${renderer}`\r\n : `The ${rendererType} is not defined: ${renderer}`\r\n throwError(error)\r\n}\r\n\r\n/**\r\n * Check if the given renderer is a {@link Renderer}\r\n * @param renderer - renderer to test\r\n * @param type - object type used to format the error if needed\r\n * @returns - the {@link Renderer} if correctly set\r\n */\r\nexport const isRenderer = (renderer: GPUCurtains | Renderer | undefined, type: string | null): Renderer => {\r\n renderer = ((renderer && (renderer as GPUCurtains).renderer) || renderer) as Renderer\r\n\r\n const isRenderer =\r\n renderer &&\r\n (renderer.type === 'GPURenderer' ||\r\n renderer.type === 'GPUCameraRenderer' ||\r\n renderer.type === 'GPUCurtainsRenderer')\r\n\r\n if (!isRenderer) {\r\n formatRendererError(renderer, 'GPURenderer', type)\r\n }\r\n\r\n return renderer\r\n}\r\n\r\n/**\r\n * Check if the given renderer is a {@link CameraRenderer}\r\n * @param renderer - renderer to test\r\n * @param type - object type used to format the error if needed\r\n * @returns - the {@link CameraRenderer} if correctly set\r\n */\r\nexport const isCameraRenderer = (\r\n renderer: GPUCurtains | CameraRenderer | undefined,\r\n type: string | null\r\n): CameraRenderer => {\r\n renderer = ((renderer && (renderer as GPUCurtains).renderer) || renderer) as CameraRenderer\r\n\r\n const isCameraRenderer =\r\n renderer && (renderer.type === 'GPUCameraRenderer' || renderer.type === 'GPUCurtainsRenderer')\r\n\r\n if (!isCameraRenderer) {\r\n formatRendererError(renderer, 'GPUCameraRenderer', type)\r\n }\r\n\r\n return renderer\r\n}\r\n\r\n/**\r\n * Check if the given renderer is a {@link GPUCurtainsRenderer}\r\n * @param renderer - renderer to test\r\n * @param type - object type used to format the error if needed\r\n * @returns - the {@link GPUCurtainsRenderer} if correctly set\r\n */\r\nexport const isCurtainsRenderer = (\r\n renderer: GPUCurtains | GPUCurtainsRenderer | undefined,\r\n type: string | null\r\n): GPUCurtainsRenderer => {\r\n renderer = ((renderer && (renderer as GPUCurtains).renderer) || renderer) as GPUCurtainsRenderer\r\n\r\n const isCurtainsRenderer = renderer && renderer.type === 'GPUCurtainsRenderer'\r\n\r\n if (!isCurtainsRenderer) {\r\n formatRendererError(renderer, 'GPUCurtainsRenderer', type)\r\n }\r\n\r\n return renderer\r\n}\r\n\r\n/**\r\n * Helper to generate mips on the GPU\r\n * Taken from https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html\r\n */\r\nexport const generateMips = (() => {\r\n let sampler\r\n let module\r\n const pipelineByFormat = {}\r\n\r\n return function generateMips(device: GPUDevice, texture: GPUTexture) {\r\n if (!module) {\r\n module = device.createShaderModule({\r\n label: 'textured quad shaders for mip level generation',\r\n code: `\r\n struct VSOutput {\r\n @builtin(position) position: vec4f,\r\n @location(0) texcoord: vec2f,\r\n };\r\n\r\n @vertex fn vs(\r\n @builtin(vertex_index) vertexIndex : u32\r\n ) -> VSOutput {\r\n let pos = array(\r\n\r\n vec2f( 0.0, 0.0), // center\r\n vec2f( 1.0, 0.0), // right, center\r\n vec2f( 0.0, 1.0), // center, top\r\n\r\n // 2st triangle\r\n vec2f( 0.0, 1.0), // center, top\r\n vec2f( 1.0, 0.0), // right, center\r\n vec2f( 1.0, 1.0), // right, top\r\n );\r\n\r\n var vsOutput: VSOutput;\r\n let xy = pos[vertexIndex];\r\n vsOutput.position = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);\r\n vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y);\r\n return vsOutput;\r\n }\r\n\r\n @group(0) @binding(0) var ourSampler: sampler;\r\n @group(0) @binding(1) var ourTexture: texture_2d;\r\n\r\n @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {\r\n return textureSample(ourTexture, ourSampler, fsInput.texcoord);\r\n }\r\n `,\r\n })\r\n\r\n sampler = device.createSampler({\r\n minFilter: 'linear',\r\n magFilter: 'linear',\r\n })\r\n }\r\n\r\n if (!pipelineByFormat[texture.format]) {\r\n pipelineByFormat[texture.format] = device.createRenderPipeline({\r\n label: 'Mip level generator pipeline',\r\n layout: 'auto',\r\n vertex: {\r\n module,\r\n },\r\n fragment: {\r\n module,\r\n targets: [{ format: texture.format }],\r\n },\r\n })\r\n }\r\n const pipeline = pipelineByFormat[texture.format]\r\n\r\n const encoder = device.createCommandEncoder({\r\n label: 'Mip gen encoder',\r\n })\r\n\r\n let width = texture.width\r\n let height = texture.height\r\n let baseMipLevel = 0\r\n while (width > 1 || height > 1) {\r\n width = Math.max(1, (width / 2) | 0)\r\n height = Math.max(1, (height / 2) | 0)\r\n\r\n for (let layer = 0; layer < texture.depthOrArrayLayers; ++layer) {\r\n const bindGroup = device.createBindGroup({\r\n layout: pipeline.getBindGroupLayout(0),\r\n entries: [\r\n { binding: 0, resource: sampler },\r\n {\r\n binding: 1,\r\n resource: texture.createView({\r\n dimension: '2d',\r\n baseMipLevel,\r\n mipLevelCount: 1,\r\n baseArrayLayer: layer,\r\n arrayLayerCount: 1,\r\n }),\r\n },\r\n ],\r\n })\r\n\r\n const renderPassDescriptor = {\r\n label: 'Mip generation render pass',\r\n colorAttachments: [\r\n {\r\n view: texture.createView({\r\n dimension: '2d',\r\n baseMipLevel: baseMipLevel + 1,\r\n mipLevelCount: 1,\r\n baseArrayLayer: layer,\r\n arrayLayerCount: 1,\r\n }),\r\n loadOp: 'clear',\r\n storeOp: 'store',\r\n },\r\n ],\r\n }\r\n\r\n const pass = encoder.beginRenderPass(renderPassDescriptor as GPURenderPassDescriptor)\r\n pass.setPipeline(pipeline)\r\n pass.setBindGroup(0, bindGroup)\r\n pass.draw(6) // call our vertex shader 6 times\r\n pass.end()\r\n }\r\n ++baseMipLevel\r\n }\r\n\r\n const commandBuffer = encoder.finish()\r\n device.queue.submit([commandBuffer])\r\n }\r\n})()\r\n","import { BufferBinding } from './BufferBinding'\r\nimport { TextureBinding } from './TextureBinding'\r\nimport { MaterialShadersType } from '../../types/Materials'\r\n\r\n/**\r\n * Map {@link MaterialShadersType | shaders types names} with actual {@link GPUShaderStageFlags | shaders visibility bitwise flags}.\r\n */\r\nconst bindingVisibilities: Map = new Map([\r\n ['vertex', GPUShaderStage.VERTEX],\r\n ['fragment', GPUShaderStage.FRAGMENT],\r\n ['compute', GPUShaderStage.COMPUTE],\r\n])\r\n\r\n/**\r\n * Get the corresponding {@link GPUShaderStageFlags | shaders visibility bitwise flags} based on an array of {@link MaterialShadersType | shaders types names}.\r\n * @param visibilities - array of {@link MaterialShadersType | shaders types names}.\r\n * @returns - corresponding {@link GPUShaderStageFlags | shaders visibility bitwise flags}.\r\n */\r\nexport const getBindingVisibility = (visibilities: MaterialShadersType[] = []): GPUShaderStageFlags => {\r\n return visibilities.reduce((acc, v) => {\r\n return acc | bindingVisibilities.get(v)\r\n }, 0)\r\n}\r\n\r\n/** Defines a typed array */\r\nexport type TypedArray =\r\n | Int8Array\r\n | Uint8Array\r\n | Uint8ClampedArray\r\n | Int16Array\r\n | Uint16Array\r\n | Int32Array\r\n | Uint32Array\r\n | Float32Array\r\n | Float64Array\r\n\r\n/** Defines a typed array constructor */\r\nexport type TypedArrayConstructor =\r\n | Int8ArrayConstructor\r\n | Uint8ArrayConstructor\r\n | Int16ArrayConstructor\r\n | Uint16ArrayConstructor\r\n | Int32ArrayConstructor\r\n | Uint32ArrayConstructor\r\n | Float32ArrayConstructor\r\n | Float64ArrayConstructor\r\n\r\n/** Defines the possible WGSL variable types */\r\nexport type WGSLVariableType = string // TODO 'mat4x4f', 'mat3x3f', 'vec3f', 'vec2f', 'f32' etc\r\n\r\n/**\r\n * Defines a {@link BufferLayout} object used to pad our {@link GPUBuffer} arrays\r\n */\r\nexport type BufferLayout = {\r\n /** Number of elements hold by this variable type */\r\n numElements: number\r\n /** Required alignment by this variable type */\r\n align: number\r\n /** Size in bytes of this variable type */\r\n size: number\r\n /** Variable type */\r\n type: WGSLVariableType\r\n /** Typed array constructor required by this variable type */\r\n View: TypedArrayConstructor\r\n /** Pad values required by this variable type */\r\n pad?: number[]\r\n}\r\n\r\n/** Object containing all buffer layouts */\r\nconst bufferLayouts: Record = {\r\n i32: { numElements: 1, align: 4, size: 4, type: 'i32', View: Int32Array },\r\n u32: { numElements: 1, align: 4, size: 4, type: 'u32', View: Uint32Array },\r\n f32: { numElements: 1, align: 4, size: 4, type: 'f32', View: Float32Array },\r\n f16: { numElements: 1, align: 2, size: 2, type: 'u16', View: Uint16Array },\r\n\r\n vec2f: { numElements: 2, align: 8, size: 8, type: 'f32', View: Float32Array },\r\n vec2i: { numElements: 2, align: 8, size: 8, type: 'i32', View: Int32Array },\r\n vec2u: { numElements: 2, align: 8, size: 8, type: 'u32', View: Uint32Array },\r\n vec2h: { numElements: 2, align: 4, size: 4, type: 'u16', View: Uint16Array },\r\n vec3i: { numElements: 3, align: 16, size: 12, type: 'i32', View: Int32Array },\r\n vec3u: { numElements: 3, align: 16, size: 12, type: 'u32', View: Uint32Array },\r\n vec3f: { numElements: 3, align: 16, size: 12, type: 'f32', View: Float32Array },\r\n vec3h: { numElements: 3, align: 8, size: 6, type: 'u16', View: Uint16Array },\r\n vec4i: { numElements: 4, align: 16, size: 16, type: 'i32', View: Int32Array },\r\n vec4u: { numElements: 4, align: 16, size: 16, type: 'u32', View: Uint32Array },\r\n vec4f: { numElements: 4, align: 16, size: 16, type: 'f32', View: Float32Array },\r\n vec4h: { numElements: 4, align: 8, size: 8, type: 'u16', View: Uint16Array },\r\n\r\n // AlignOf(vecR)\tSizeOf(array)\r\n mat2x2f: { numElements: 4, align: 8, size: 16, type: 'f32', View: Float32Array },\r\n mat2x2h: { numElements: 4, align: 4, size: 8, type: 'u16', View: Uint16Array },\r\n mat3x2f: { numElements: 6, align: 8, size: 24, type: 'f32', View: Float32Array },\r\n mat3x2h: { numElements: 6, align: 4, size: 12, type: 'u16', View: Uint16Array },\r\n mat4x2f: { numElements: 8, align: 8, size: 32, type: 'f32', View: Float32Array },\r\n mat4x2h: { numElements: 8, align: 4, size: 16, type: 'u16', View: Uint16Array },\r\n mat2x3f: { numElements: 8, align: 16, size: 32, pad: [3, 1], type: 'f32', View: Float32Array },\r\n mat2x3h: { numElements: 8, align: 8, size: 16, pad: [3, 1], type: 'u16', View: Uint16Array },\r\n mat3x3f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: 'f32', View: Float32Array },\r\n mat3x3h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: 'u16', View: Uint16Array },\r\n mat4x3f: { numElements: 16, align: 16, size: 64, pad: [3, 1], type: 'f32', View: Float32Array },\r\n mat4x3h: { numElements: 16, align: 8, size: 32, pad: [3, 1], type: 'u16', View: Uint16Array },\r\n mat2x4f: { numElements: 8, align: 16, size: 32, type: 'f32', View: Float32Array },\r\n mat2x4h: { numElements: 8, align: 8, size: 16, type: 'u16', View: Uint16Array },\r\n mat3x4f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: 'f32', View: Float32Array },\r\n mat3x4h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: 'u16', View: Uint16Array },\r\n mat4x4f: { numElements: 16, align: 16, size: 64, type: 'f32', View: Float32Array },\r\n mat4x4h: { numElements: 16, align: 8, size: 32, type: 'u16', View: Uint16Array },\r\n}\r\n\r\n// from https://github.com/greggman/webgpu-utils/blob/main/src/buffer-views.ts\r\n/**\r\n * Get the correct {@link BufferLayout | buffer layout} for given {@link WGSLVariableType | variable type}\r\n * @param bufferType - [{@link WGSLVariableType | variable type} to use\r\n * @returns - the ={@link BufferLayout | buffer layout}\r\n */\r\nexport const getBufferLayout = (bufferType: WGSLVariableType): BufferLayout => {\r\n return bufferLayouts[bufferType]\r\n}\r\n\r\n/**\r\n * Get the correct WGSL variable declaration code fragment based on the given {@link BufferBinding}\r\n * @param binding - {@link BufferBinding} to use\r\n * @returns - WGSL variable declaration code fragment\r\n */\r\nexport const getBindingWGSLVarType = (binding: BufferBinding): string => {\r\n return (() => {\r\n switch (binding.bindingType) {\r\n case 'storage':\r\n return `var<${binding.bindingType}, ${binding.options.access}>`\r\n case 'uniform':\r\n default:\r\n return 'var'\r\n }\r\n })()\r\n}\r\n\r\n/**\r\n * Get the correct WGSL variable declaration code fragment based on the given {@link TextureBinding}\r\n * @param binding - {@link TextureBinding} to use\r\n * @returns - WGSL variable declaration code fragment\r\n */\r\nexport const getTextureBindingWGSLVarType = (binding: TextureBinding): string => {\r\n if (binding.bindingType === 'externalTexture') {\r\n return `var ${binding.name}: texture_external;`\r\n }\r\n\r\n return binding.bindingType === 'storage'\r\n ? `var ${binding.name}: texture_storage_${binding.options.viewDimension.replace('-', '_')}<${\r\n binding.options.format\r\n }, ${binding.options.access}>;`\r\n : binding.bindingType === 'depth'\r\n ? `var ${binding.name}: texture_depth${\r\n binding.options.multisampled ? '_multisampled' : ''\r\n }_${binding.options.viewDimension.replace('-', '_')};`\r\n : `var ${binding.name}: texture${\r\n binding.options.multisampled ? '_multisampled' : ''\r\n }_${binding.options.viewDimension.replace('-', '_')};`\r\n}\r\n\r\n/**\r\n * Get the correct {@link GPUBindGroupLayout | bind group layout} resource type based on the given {@link core/bindings/Binding.BindingType | binding type}\r\n * @param binding - {@link BufferBinding | buffer binding} to use\r\n * @returns - {@link GPUBindGroupLayout | bind group layout} resource type\r\n */\r\nexport const getBindGroupLayoutBindingType = (binding: BufferBinding): GPUBufferBindingType => {\r\n if (binding.bindingType === 'storage' && binding.options.access === 'read_write') {\r\n return 'storage'\r\n } else if (binding.bindingType === 'storage') {\r\n return 'read-only-storage'\r\n } else {\r\n return 'uniform'\r\n }\r\n}\r\n\r\n/**\r\n * Get the correct {@link GPUBindGroupLayout} resource type based on the given {@link core/bindings/Binding.BindingType | texture binding type}\r\n * @param binding - {@link TextureBinding | texture binding} to use\r\n * @returns - {@link GPUBindGroupLayout} resource type\r\n */\r\nexport const getBindGroupLayoutTextureBindingType = (\r\n binding: TextureBinding\r\n): GPUTextureBindingLayout | GPUExternalTextureBindingLayout | GPUStorageTextureBindingLayout | null => {\r\n return (() => {\r\n switch (binding.bindingType) {\r\n case 'externalTexture':\r\n return { externalTexture: {} }\r\n case 'storage':\r\n return {\r\n storageTexture: {\r\n format: binding.options.format,\r\n viewDimension: binding.options.viewDimension,\r\n } as GPUStorageTextureBindingLayout,\r\n }\r\n case 'texture':\r\n return {\r\n texture: {\r\n multisampled: binding.options.multisampled,\r\n viewDimension: binding.options.viewDimension,\r\n sampleType: binding.options.multisampled ? 'unfilterable-float' : 'float',\r\n } as GPUTextureBindingLayout,\r\n }\r\n case 'depth':\r\n return {\r\n texture: {\r\n multisampled: binding.options.multisampled,\r\n viewDimension: binding.options.viewDimension,\r\n sampleType: 'depth',\r\n } as GPUTextureBindingLayout,\r\n }\r\n default:\r\n return null\r\n }\r\n })()\r\n}\r\n\r\n/**\r\n * Get the correct {@link TextureBinding | texture binding} cache key.\r\n * @param binding - {@link TextureBinding | texture binding} to use\r\n * @returns - binding cache key\r\n */\r\nexport const getBindGroupLayoutTextureBindingCacheKey = (binding: TextureBinding): string => {\r\n return (() => {\r\n switch (binding.bindingType) {\r\n case 'externalTexture':\r\n return `externalTexture,${binding.visibility},`\r\n case 'storage':\r\n return `storageTexture,${binding.options.format},${binding.options.viewDimension},${binding.visibility},`\r\n case 'texture':\r\n return `texture,${binding.options.multisampled},${binding.options.viewDimension},${\r\n binding.options.multisampled ? 'unfilterable-float' : 'float'\r\n },${binding.visibility},`\r\n case 'depth':\r\n return `depthTexture,${binding.options.format},${binding.options.viewDimension},${binding.visibility},`\r\n default:\r\n return `${binding.visibility},`\r\n }\r\n })()\r\n}\r\n","import { toCamelCase } from '../../utils/utils'\r\nimport { MaterialShadersType } from '../../types/Materials'\r\nimport { TextureBinding } from './TextureBinding'\r\nimport { SamplerBinding } from './SamplerBinding'\r\nimport { getBindingVisibility } from './utils'\r\n\r\n/** Defines all kind of buffer binding types */\r\nexport type BufferBindingType = 'uniform' | 'storage'\r\n/** Defines all kind of texture binding types */\r\nexport type TextureBindingType = 'texture' | 'storage' | 'depth'\r\n/** Defines all kind of DOM texture binding types */\r\nexport type DOMTextureBindingType = 'externalTexture' | TextureBindingType\r\n/** Defines all kind of sampler binding types */\r\nexport type SamplerBindingType = 'sampler'\r\n/** Defines all kind of binding types */\r\nexport type BindingType = BufferBindingType | DOMTextureBindingType | SamplerBindingType\r\n\r\n// see https://www.w3.org/TR/WGSL/#memory-access-mode\r\n/** Defines buffer binding memory access types (read only or read/write) */\r\nexport type BufferBindingMemoryAccessType = 'read' | 'read_write'\r\n/** Defines texture binding memory access types (read only, write only or read/write) */\r\nexport type BindingMemoryAccessType = BufferBindingMemoryAccessType | 'write'\r\n\r\n/**\r\n * Defines all kind of {@link Binding} that are related to textures or samplers\r\n */\r\nexport type TextureSamplerBindings = TextureBinding | SamplerBinding\r\n\r\n/**\r\n * An object defining all possible {@link Binding} class instancing parameters\r\n */\r\nexport interface BindingParams {\r\n /** {@link Binding} label */\r\n label?: string\r\n /** {@link Binding} name/key */\r\n name?: string\r\n /** {@link BindingType | binding type} to use with this {@link Binding} */\r\n bindingType?: BindingType\r\n /** {@link Binding} variables shaders visibility as an array of {@link MaterialShadersType | shaders types names} */\r\n visibility?: MaterialShadersType[]\r\n}\r\n\r\n/**\r\n * Used as a shell to build actual bindings upon, like {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}, {@link core/bindings/WritableBufferBinding.WritableBufferBinding | WritableBufferBinding}, {@link TextureBinding} and {@link SamplerBinding}.\r\n *\r\n * Ultimately the goal of a {@link Binding} element is to provide correct resources for {@link GPUBindGroupLayoutEntry} and {@link GPUBindGroupEntry}\r\n *\r\n * ## WGSL\r\n *\r\n * Each {@link Binding} creates its own WGSL code snippet variable declaration, using structured types or not.\r\n */\r\nexport class Binding {\r\n /** The label of the {@link Binding} */\r\n label: string\r\n /** The name/key of the {@link Binding} */\r\n name: string\r\n /** The binding type of the {@link Binding} */\r\n bindingType: BindingType\r\n /** The visibility of the {@link Binding} in the shaders */\r\n visibility: GPUShaderStageFlags\r\n /** Options used to create this {@link Binding} */\r\n options: BindingParams\r\n\r\n /** Flag indicating whether we should recreate the parentMesh {@link core/bindGroups/BindGroup.BindGroup#bindGroup | bind group}, usually when a resource has changed */\r\n shouldResetBindGroup: boolean\r\n /** Flag indicating whether we should recreate the parentMesh {@link core/bindGroups/BindGroup.BindGroup#bindGroupLayout | GPU bind group layout}, usually when a resource layout has changed */\r\n shouldResetBindGroupLayout: boolean\r\n\r\n /** A cache key allowing to get / set bindings from the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#bufferBindings | device manager map cache}. Used for {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} only at the moment. */\r\n cacheKey: string\r\n\r\n /**\r\n * Binding constructor\r\n * @param parameters - {@link BindingParams | parameters} used to create our {@link Binding}\r\n */\r\n constructor({\r\n label = 'Uniform',\r\n name = 'uniform',\r\n bindingType = 'uniform',\r\n visibility = ['vertex', 'fragment', 'compute'],\r\n }: BindingParams) {\r\n this.label = label\r\n this.name = toCamelCase(name)\r\n this.bindingType = bindingType\r\n\r\n this.visibility = getBindingVisibility(visibility)\r\n\r\n this.options = {\r\n label,\r\n name,\r\n bindingType,\r\n visibility,\r\n }\r\n\r\n this.shouldResetBindGroup = false\r\n this.shouldResetBindGroupLayout = false\r\n\r\n this.cacheKey = `${bindingType},${this.visibility},`\r\n }\r\n}\r\n","/**\r\n * Really basic 2D vector class used for vector calculations\r\n * @see https://github.com/mrdoob/three.js/blob/dev/src/math/Vector2.js\r\n * @see http://glmatrix.net/docs/vec2.js.html\r\n */\r\nexport class Vec2 {\r\n /** The type of the {@link Vec2} */\r\n type: string\r\n /** X component of our {@link Vec2} */\r\n private _x: number\r\n /** Y component of our {@link Vec2} */\r\n private _y: number\r\n\r\n /** function assigned to the {@link onChange} callback */\r\n _onChangeCallback?(): void\r\n\r\n /**\r\n * Vec2 constructor\r\n * @param x - X component of our {@link Vec2}\r\n * @param y - Y component of our {@link Vec2}\r\n */\r\n constructor(x = 0, y = x) {\r\n this.type = 'Vec2'\r\n\r\n this._x = x\r\n this._y = y\r\n }\r\n\r\n /**\r\n * Get the X component of the {@link Vec2}\r\n */\r\n get x(): number {\r\n return this._x\r\n }\r\n\r\n /**\r\n * Set the X component of the {@link Vec2}\r\n * Can trigger {@link onChange} callback\r\n * @param value - X component to set\r\n */\r\n set x(value: number) {\r\n const changed = value !== this._x\r\n this._x = value\r\n changed && this._onChangeCallback && this._onChangeCallback()\r\n }\r\n\r\n /**\r\n * Get the Y component of the {@link Vec2}\r\n */\r\n get y(): number {\r\n return this._y\r\n }\r\n\r\n /**\r\n * Set the Y component of the {@link Vec2}\r\n * Can trigger {@link onChange} callback\r\n * @param value - Y component to set\r\n */\r\n set y(value: number) {\r\n const changed = value !== this._y\r\n this._y = value\r\n changed && this._onChangeCallback && this._onChangeCallback()\r\n }\r\n\r\n /**\r\n * Called when at least one component of the {@link Vec2} has changed\r\n * @param callback - callback to run when at least one component of the {@link Vec2} has changed\r\n * @returns - our {@link Vec2}\r\n */\r\n onChange(callback: () => void): Vec2 {\r\n if (callback) {\r\n this._onChangeCallback = callback\r\n }\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set the {@link Vec2} from values\r\n * @param x - new X component to set\r\n * @param y - new Y component to set\r\n * @returns - this {@link Vec2} after being set\r\n */\r\n set(x = 0, y = x): Vec2 {\r\n this.x = x\r\n this.y = y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Add a {@link Vec2} to this {@link Vec2}\r\n * @param vector - {@link Vec2} to add\r\n * @returns - this {@link Vec2} after addition\r\n */\r\n add(vector: Vec2 = new Vec2()): Vec2 {\r\n this.x += vector.x\r\n this.y += vector.y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Add a scalar to all the components of this {@link Vec2}\r\n * @param value - number to add\r\n * @returns - this {@link Vec2} after addition\r\n */\r\n addScalar(value = 0): Vec2 {\r\n this.x += value\r\n this.y += value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Subtract a {@link Vec2} from this {@link Vec2}\r\n * @param vector - {@link Vec2} to subtract\r\n * @returns - this {@link Vec2} after subtraction\r\n */\r\n sub(vector: Vec2 = new Vec2()): Vec2 {\r\n this.x -= vector.x\r\n this.y -= vector.y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Subtract a scalar to all the components of this {@link Vec2}\r\n * @param value - number to subtract\r\n * @returns - this {@link Vec2} after subtraction\r\n */\r\n subScalar(value = 0): Vec2 {\r\n this.x -= value\r\n this.y -= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Multiply a {@link Vec2} with this {@link Vec2}\r\n * @param vector - {@link Vec2} to multiply with\r\n * @returns - this {@link Vec2} after multiplication\r\n */\r\n multiply(vector: Vec2 = new Vec2(1)): Vec2 {\r\n this.x *= vector.x\r\n this.y *= vector.y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Multiply all components of this {@link Vec2} with a scalar\r\n * @param value - number to multiply with\r\n * @returns - this {@link Vec2} after multiplication\r\n */\r\n multiplyScalar(value = 1): Vec2 {\r\n this.x *= value\r\n this.y *= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Divide a {@link Vec2} with this {@link Vec2}\r\n * @param vector - {@link Vec2} to divide with\r\n * @returns - this {@link Vec2} after division\r\n */\r\n divide(vector: Vec2 = new Vec2(1)): Vec2 {\r\n this.x /= vector.x\r\n this.y /= vector.y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Divide all components of this {@link Vec2} with a scalar\r\n * @param value - number to divide with\r\n * @returns - this {@link Vec2} after division\r\n */\r\n divideScalar(value = 1): Vec2 {\r\n this.x /= value\r\n this.y /= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Copy a {@link Vec2} into this {@link Vec2}\r\n * @param vector - {@link Vec2} to copy\r\n * @returns - this {@link Vec2} after copy\r\n */\r\n copy(vector: Vec2 = new Vec2()): Vec2 {\r\n this.x = vector.x\r\n this.y = vector.y\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clone this {@link Vec2}\r\n * @returns - cloned {@link Vec2}\r\n */\r\n clone(): Vec2 {\r\n return new Vec2(this.x, this.y)\r\n }\r\n\r\n /**\r\n * Apply max values to this {@link Vec2} components\r\n * @param vector - {@link Vec2} representing max values\r\n * @returns - {@link Vec2} with max values applied\r\n */\r\n max(vector: Vec2 = new Vec2()): Vec2 {\r\n this.x = Math.max(this.x, vector.x)\r\n this.y = Math.max(this.y, vector.y)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Apply min values to this {@link Vec2} components\r\n * @param vector - {@link Vec2} representing min values\r\n * @returns - {@link Vec2} with min values applied\r\n */\r\n min(vector: Vec2 = new Vec2()): Vec2 {\r\n this.x = Math.min(this.x, vector.x)\r\n this.y = Math.min(this.y, vector.y)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clamp this {@link Vec2} components by min and max {@link Vec2} vectors\r\n * @param min - minimum {@link Vec2} components to compare with\r\n * @param max - maximum {@link Vec2} components to compare with\r\n * @returns - clamped {@link Vec2}\r\n */\r\n clamp(min: Vec2 = new Vec2(), max: Vec2 = new Vec2()): Vec2 {\r\n this.x = Math.max(min.x, Math.min(max.x, this.x))\r\n this.y = Math.max(min.y, Math.min(max.y, this.y))\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Check if 2 {@link Vec2} are equal\r\n * @param vector - {@link Vec2} to compare\r\n * @returns - whether the {@link Vec2} are equals or not\r\n */\r\n equals(vector: Vec2 = new Vec2()): boolean {\r\n return this.x === vector.x && this.y === vector.y\r\n }\r\n\r\n /**\r\n * Get the square length of this {@link Vec2}\r\n * @returns - square length of this {@link Vec2}\r\n */\r\n lengthSq(): number {\r\n return this.x * this.x + this.y * this.y\r\n }\r\n\r\n /**\r\n * Get the length of this {@link Vec2}\r\n * @returns - length of this {@link Vec2}\r\n */\r\n length(): number {\r\n return Math.sqrt(this.lengthSq())\r\n }\r\n\r\n /**\r\n * Normalize this {@link Vec2}\r\n * @returns - normalized {@link Vec2}\r\n */\r\n normalize(): Vec2 {\r\n // normalize\r\n let len = this.x * this.x + this.y * this.y\r\n if (len > 0) {\r\n len = 1 / Math.sqrt(len)\r\n }\r\n this.x *= len\r\n this.y *= len\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Calculate the dot product of 2 {@link Vec2}\r\n * @param vector - {@link Vec2} to use for dot product\r\n * @returns - dot product of the 2 {@link Vec2}\r\n */\r\n dot(vector: Vec2 = new Vec2()): number {\r\n return this.x * vector.x + this.y * vector.y\r\n }\r\n\r\n /**\r\n * Calculate the linear interpolation of this {@link Vec2} by given {@link Vec2} and alpha, where alpha is the percent distance along the line\r\n * @param vector - {@link Vec2} to interpolate towards\r\n * @param [alpha=1] - interpolation factor in the [0, 1] interval\r\n * @returns - this {@link Vec2} after linear interpolation\r\n */\r\n lerp(vector: Vec2 = new Vec2(), alpha = 1): Vec2 {\r\n this.x += (vector.x - this.x) * alpha\r\n this.y += (vector.y - this.y) * alpha\r\n\r\n return this\r\n }\r\n}\r\n","import { Vec3 } from './Vec3'\r\nimport { Mat4 } from './Mat4'\r\n\r\n/** Defines all possible rotations axis orders */\r\nexport type AxisOrder = 'XYZ' | 'XZY' | 'YXZ' | 'YZX' | 'ZXY' | 'ZYX'\r\n\r\n/**\r\n * Really basic quaternion class used for 3D rotation calculations\r\n * @see https://github.com/mrdoosb/three.js/blob/dev/src/math/Quaternion.js\r\n */\r\nexport class Quat {\r\n /** The type of the {@link Quat} */\r\n type: string\r\n /** Our quaternion array */\r\n elements: Float32Array\r\n /** Rotation axis order */\r\n axisOrder: AxisOrder\r\n\r\n /**\r\n * Quat constructor\r\n * @param [elements] - initial array to use\r\n * @param [axisOrder='XYZ'] - axis order to use\r\n */\r\n constructor(elements: Float32Array = new Float32Array([0, 0, 0, 1]), axisOrder: AxisOrder = 'XYZ') {\r\n this.type = 'Quat'\r\n this.elements = elements\r\n // rotation axis order\r\n this.axisOrder = axisOrder\r\n }\r\n\r\n /**\r\n * Sets the {@link Quat} values from an array\r\n * @param array - an array of at least 4 elements\r\n * @returns - this {@link Quat} after being set\r\n */\r\n setFromArray(array: Float32Array | number[] = new Float32Array([0, 0, 0, 1])): Quat {\r\n this.elements[0] = array[0]\r\n this.elements[1] = array[1]\r\n this.elements[2] = array[2]\r\n this.elements[3] = array[3]\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Sets the {@link Quat} axis order\r\n * @param axisOrder - axis order to use\r\n * @returns - this {@link Quat} after axis order has been set\r\n */\r\n setAxisOrder(axisOrder: AxisOrder | string = 'XYZ'): Quat {\r\n // force uppercase for strict equality tests\r\n axisOrder = axisOrder.toUpperCase()\r\n\r\n switch (axisOrder) {\r\n case 'XYZ':\r\n case 'YXZ':\r\n case 'ZXY':\r\n case 'ZYX':\r\n case 'YZX':\r\n case 'XZY':\r\n this.axisOrder = axisOrder\r\n break\r\n default:\r\n // apply a default axis order\r\n this.axisOrder = 'XYZ'\r\n }\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Copy a {@link Quat} into this {@link Quat}\r\n * @param quaternion - {@link Quat} to copy\r\n * @returns - this {@link Quat} after copy\r\n */\r\n copy(quaternion: Quat = new Quat()): Quat {\r\n this.elements = quaternion.elements\r\n this.axisOrder = quaternion.axisOrder\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clone a {@link Quat}\r\n * @returns - cloned {@link Quat}\r\n */\r\n clone(): Quat {\r\n return new Quat().copy(this)\r\n }\r\n\r\n /**\r\n * Check if 2 {@link Quat} are equal\r\n * @param quaternion - {@link Quat} to check against\r\n * @returns - whether the {@link Quat} are equal or not\r\n */\r\n equals(quaternion: Quat = new Quat()): boolean {\r\n return (\r\n this.elements[0] === quaternion.elements[0] &&\r\n this.elements[1] === quaternion.elements[1] &&\r\n this.elements[2] === quaternion.elements[2] &&\r\n this.elements[3] === quaternion.elements[3] &&\r\n this.axisOrder === quaternion.axisOrder\r\n )\r\n }\r\n\r\n /**\r\n * Sets a rotation {@link Quat} using Euler angles {@link Vec3 | vector} and its axis order\r\n * @param vector - rotation {@link Vec3 | vector} to set our {@link Quat} from\r\n * @returns - {@link Quat} after having applied the rotation\r\n */\r\n setFromVec3(vector: Vec3): Quat {\r\n const ax = vector.x * 0.5\r\n const ay = vector.y * 0.5\r\n const az = vector.z * 0.5\r\n\r\n const cosx = Math.cos(ax)\r\n const cosy = Math.cos(ay)\r\n const cosz = Math.cos(az)\r\n const sinx = Math.sin(ax)\r\n const siny = Math.sin(ay)\r\n const sinz = Math.sin(az)\r\n\r\n // XYZ order\r\n if (this.axisOrder === 'XYZ') {\r\n this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz\r\n } else if (this.axisOrder === 'YXZ') {\r\n this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz\r\n } else if (this.axisOrder === 'ZXY') {\r\n this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz\r\n } else if (this.axisOrder === 'ZYX') {\r\n this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz\r\n } else if (this.axisOrder === 'YZX') {\r\n this.elements[0] = sinx * cosy * cosz + cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz + sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz - sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz - sinx * siny * sinz\r\n } else if (this.axisOrder === 'XZY') {\r\n this.elements[0] = sinx * cosy * cosz - cosx * siny * sinz\r\n this.elements[1] = cosx * siny * cosz - sinx * cosy * sinz\r\n this.elements[2] = cosx * cosy * sinz + sinx * siny * cosz\r\n this.elements[3] = cosx * cosy * cosz + sinx * siny * sinz\r\n }\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set a {@link Quat} from a rotation axis {@link Vec3 | vector} and an angle\r\n * @param axis - normalized {@link Vec3 | vector} around which to rotate\r\n * @param angle - angle (in radians) to rotate\r\n * @returns - {@link Quat} after having applied the rotation\r\n */\r\n setFromAxisAngle(axis: Vec3, angle = 0): Quat {\r\n // https://github.com/mrdoob/three.js/blob/dev/src/math/Quaternion.js#L275\r\n // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm\r\n\r\n // assumes axis is normalized\r\n\r\n const halfAngle = angle / 2,\r\n s = Math.sin(halfAngle)\r\n\r\n this.elements[0] = axis.x * s\r\n this.elements[1] = axis.y * s\r\n this.elements[2] = axis.z * s\r\n this.elements[3] = Math.cos(halfAngle)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set a {@link Quat} from a rotation {@link Mat4 | matrix}\r\n * @param matrix - rotation {@link Mat4 | matrix} to use\r\n * @returns - {@link Quat} after having applied the rotation\r\n */\r\n setFromRotationMatrix(matrix: Mat4): Quat {\r\n // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm\r\n // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\r\n const te = matrix.elements,\r\n m11 = te[0],\r\n m12 = te[4],\r\n m13 = te[8],\r\n m21 = te[1],\r\n m22 = te[5],\r\n m23 = te[9],\r\n m31 = te[2],\r\n m32 = te[6],\r\n m33 = te[10],\r\n trace = m11 + m22 + m33\r\n\r\n if (trace > 0) {\r\n const s = 0.5 / Math.sqrt(trace + 1.0)\r\n\r\n this.elements[3] = 0.25 / s\r\n this.elements[0] = (m32 - m23) * s\r\n this.elements[1] = (m13 - m31) * s\r\n this.elements[2] = (m21 - m12) * s\r\n } else if (m11 > m22 && m11 > m33) {\r\n const s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33)\r\n\r\n this.elements[3] = (m32 - m23) / s\r\n this.elements[0] = 0.25 * s\r\n this.elements[1] = (m12 + m21) / s\r\n this.elements[2] = (m13 + m31) / s\r\n } else if (m22 > m33) {\r\n const s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33)\r\n\r\n this.elements[3] = (m13 - m31) / s\r\n this.elements[0] = (m12 + m21) / s\r\n this.elements[1] = 0.25 * s\r\n this.elements[2] = (m23 + m32) / s\r\n } else {\r\n const s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22)\r\n\r\n this.elements[3] = (m21 - m12) / s\r\n this.elements[0] = (m13 + m31) / s\r\n this.elements[1] = (m23 + m32) / s\r\n this.elements[2] = 0.25 * s\r\n }\r\n\r\n return this\r\n }\r\n}\r\n","import { Mat4 } from './Mat4'\r\nimport { Quat } from './Quat'\r\nimport { Camera } from '../core/camera/Camera'\r\n\r\n/**\r\n * Really basic 3D vector class used for vector calculations\r\n * @see https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js\r\n * @see http://glmatrix.net/docs/vec3.js.html\r\n */\r\nexport class Vec3 {\r\n /** The type of the {@link Vec3} */\r\n type: string\r\n /** X component of our {@link Vec3} */\r\n private _x: number\r\n /** Y component of our {@link Vec3} */\r\n private _y: number\r\n /** Z component of our {@link Vec3} */\r\n private _z: number\r\n\r\n /** function assigned to the {@link onChange} callback */\r\n _onChangeCallback?(): void\r\n\r\n /**\r\n * Vec3 constructor\r\n * @param x - X component of our {@link Vec3}\r\n * @param y - Y component of our {@link Vec3}\r\n * @param z - Z component of our {@link Vec3}\r\n */\r\n constructor(x = 0, y = x, z = x) {\r\n this.type = 'Vec3'\r\n\r\n this._x = x\r\n this._y = y\r\n this._z = z\r\n }\r\n\r\n /**\r\n * Get the X component of the {@link Vec3}\r\n */\r\n get x(): number {\r\n return this._x\r\n }\r\n\r\n /**\r\n * Set the X component of the {@link Vec3}\r\n * Can trigger {@link onChange} callback\r\n * @param value - X component to set\r\n */\r\n set x(value: number) {\r\n const changed = value !== this._x\r\n this._x = value\r\n changed && this._onChangeCallback && this._onChangeCallback()\r\n }\r\n\r\n /**\r\n * Get the Y component of the {@link Vec3}\r\n */\r\n get y(): number {\r\n return this._y\r\n }\r\n\r\n /**\r\n * Set the Y component of the {@link Vec3}\r\n * Can trigger {@link onChange} callback\r\n * @param value - Y component to set\r\n */\r\n set y(value: number) {\r\n const changed = value !== this._y\r\n this._y = value\r\n changed && this._onChangeCallback && this._onChangeCallback()\r\n }\r\n\r\n /**\r\n * Get the Z component of the {@link Vec3}\r\n */\r\n get z(): number {\r\n return this._z\r\n }\r\n\r\n /**\r\n * Set the Z component of the {@link Vec3}\r\n * Can trigger {@link onChange} callback\r\n * @param value - Z component to set\r\n */\r\n set z(value: number) {\r\n const changed = value !== this._z\r\n this._z = value\r\n changed && this._onChangeCallback && this._onChangeCallback()\r\n }\r\n\r\n /**\r\n * Called when at least one component of the {@link Vec3} has changed\r\n * @param callback - callback to run when at least one component of the {@link Vec3} has changed\r\n * @returns - our {@link Vec3}\r\n */\r\n onChange(callback: () => void): Vec3 {\r\n if (callback) {\r\n this._onChangeCallback = callback\r\n }\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set the {@link Vec3} from values\r\n * @param x - new X component to set\r\n * @param y - new Y component to set\r\n * @param z - new Z component to set\r\n * @returns - this {@link Vec3} after being set\r\n */\r\n set(x = 0, y = x, z = x): Vec3 {\r\n this.x = x\r\n this.y = y\r\n this.z = z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Add a {@link Vec3} to this {@link Vec3}\r\n * @param vector - {@link Vec3} to add\r\n * @returns - this {@link Vec3} after addition\r\n */\r\n add(vector: Vec3 = new Vec3()): Vec3 {\r\n this.x += vector.x\r\n this.y += vector.y\r\n this.z += vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Add a scalar to all the components of this {@link Vec3}\r\n * @param value - number to add\r\n * @returns - this {@link Vec3} after addition\r\n */\r\n addScalar(value = 0): Vec3 {\r\n this.x += value\r\n this.y += value\r\n this.z += value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Subtract a {@link Vec3} from this {@link Vec3}\r\n * @param vector - {@link Vec3} to subtract\r\n * @returns - this {@link Vec3} after subtraction\r\n */\r\n sub(vector: Vec3 = new Vec3()): Vec3 {\r\n this.x -= vector.x\r\n this.y -= vector.y\r\n this.z -= vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Subtract a scalar to all the components of this {@link Vec3}\r\n * @param value - number to subtract\r\n * @returns - this {@link Vec3} after subtraction\r\n */\r\n subScalar(value = 0): Vec3 {\r\n this.x -= value\r\n this.y -= value\r\n this.z -= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Multiply a {@link Vec3} with this {@link Vec3}\r\n * @param vector - {@link Vec3} to multiply with\r\n * @returns - this {@link Vec3} after multiplication\r\n */\r\n multiply(vector: Vec3 = new Vec3(1)): Vec3 {\r\n this.x *= vector.x\r\n this.y *= vector.y\r\n this.z *= vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Multiply all components of this {@link Vec3} with a scalar\r\n * @param value - number to multiply with\r\n * @returns - this {@link Vec3} after multiplication\r\n */\r\n multiplyScalar(value = 1): Vec3 {\r\n this.x *= value\r\n this.y *= value\r\n this.z *= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Divide a {@link Vec3} with this {@link Vec3}\r\n * @param vector - {@link Vec3} to divide with\r\n * @returns - this {@link Vec3} after division\r\n */\r\n divide(vector: Vec3 = new Vec3(1)): Vec3 {\r\n this.x /= vector.x\r\n this.y /= vector.y\r\n this.z /= vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Divide all components of this {@link Vec3} with a scalar\r\n * @param value - number to divide with\r\n * @returns - this {@link Vec3} after division\r\n */\r\n divideScalar(value = 1): Vec3 {\r\n this.x /= value\r\n this.y /= value\r\n this.z /= value\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Copy a {@link Vec3} into this {@link Vec3}\r\n * @param vector - {@link Vec3} to copy\r\n * @returns - this {@link Vec3} after copy\r\n */\r\n copy(vector: Vec3 = new Vec3()): Vec3 {\r\n this.x = vector.x\r\n this.y = vector.y\r\n this.z = vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clone this {@link Vec3}\r\n * @returns - cloned {@link Vec3}\r\n */\r\n clone(): Vec3 {\r\n return new Vec3(this.x, this.y, this.z)\r\n }\r\n\r\n /**\r\n * Apply max values to this {@link Vec3} components\r\n * @param vector - {@link Vec3} representing max values\r\n * @returns - {@link Vec3} with max values applied\r\n */\r\n max(vector: Vec3 = new Vec3()): Vec3 {\r\n this.x = Math.max(this.x, vector.x)\r\n this.y = Math.max(this.y, vector.y)\r\n this.z = Math.max(this.z, vector.z)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Apply min values to this {@link Vec3} components\r\n * @param vector - {@link Vec3} representing min values\r\n * @returns - {@link Vec3} with min values applied\r\n */\r\n min(vector: Vec3 = new Vec3()): Vec3 {\r\n this.x = Math.min(this.x, vector.x)\r\n this.y = Math.min(this.y, vector.y)\r\n this.z = Math.min(this.z, vector.z)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clamp this {@link Vec3} components by min and max {@link Vec3} vectors\r\n * @param min - minimum {@link Vec3} components to compare with\r\n * @param max - maximum {@link Vec3} components to compare with\r\n * @returns - clamped {@link Vec3}\r\n */\r\n clamp(min: Vec3 = new Vec3(), max: Vec3 = new Vec3()): Vec3 {\r\n this.x = Math.max(min.x, Math.min(max.x, this.x))\r\n this.y = Math.max(min.y, Math.min(max.y, this.y))\r\n this.z = Math.max(min.z, Math.min(max.z, this.z))\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Check if 2 {@link Vec3} are equal\r\n * @param vector - {@link Vec3} to compare\r\n * @returns - whether the {@link Vec3} are equals or not\r\n */\r\n equals(vector: Vec3 = new Vec3()): boolean {\r\n return this.x === vector.x && this.y === vector.y && this.z === vector.z\r\n }\r\n\r\n /**\r\n * Get the square length of this {@link Vec3}\r\n * @returns - square length of this {@link Vec3}\r\n */\r\n lengthSq(): number {\r\n return this.x * this.x + this.y * this.y + this.z * this.z\r\n }\r\n\r\n /**\r\n * Get the length of this {@link Vec3}\r\n * @returns - length of this {@link Vec3}\r\n */\r\n length(): number {\r\n return Math.sqrt(this.lengthSq())\r\n }\r\n\r\n /**\r\n * Get the euclidian distance between this {@link Vec3} and another {@link Vec3}\r\n * @param vector - {@link Vec3} to use for distance calculation\r\n * @returns - euclidian distance\r\n */\r\n distance(vector: Vec3 = new Vec3()): number {\r\n return Math.hypot(vector.x - this.x, vector.y - this.y, vector.z - this.z)\r\n }\r\n\r\n /**\r\n * Normalize this {@link Vec3}\r\n * @returns - normalized {@link Vec3}\r\n */\r\n normalize(): Vec3 {\r\n // normalize\r\n let len = this.lengthSq()\r\n if (len > 0) {\r\n len = 1 / Math.sqrt(len)\r\n }\r\n this.x *= len\r\n this.y *= len\r\n this.z *= len\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Calculate the dot product of 2 {@link Vec3}\r\n * @param vector - {@link Vec3} to use for dot product\r\n * @returns - dot product of the 2 {@link Vec3}\r\n */\r\n dot(vector: Vec3 = new Vec3()): number {\r\n return this.x * vector.x + this.y * vector.y + this.z * vector.z\r\n }\r\n\r\n /**\r\n * Get the cross product of this {@link Vec3} with another {@link Vec3}\r\n * @param vector - {@link Vec3} to use for cross product\r\n * @returns - this {@link Vec3} after cross product\r\n */\r\n cross(vector: Vec3 = new Vec3()): Vec3 {\r\n return this.crossVectors(this, vector)\r\n }\r\n\r\n /**\r\n * Set this {@link Vec3} as the result of the cross product of two {@link Vec3}\r\n * @param a - first {@link Vec3} to use for cross product\r\n * @param b - second {@link Vec3} to use for cross product\r\n * @returns - this {@link Vec3} after cross product\r\n */\r\n crossVectors(a: Vec3 = new Vec3(), b: Vec3 = new Vec3()): Vec3 {\r\n const ax = a.x,\r\n ay = a.y,\r\n az = a.z\r\n const bx = b.x,\r\n by = b.y,\r\n bz = b.z\r\n\r\n this.x = ay * bz - az * by\r\n this.y = az * bx - ax * bz\r\n this.z = ax * by - ay * bx\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Calculate the linear interpolation of this {@link Vec3} by given {@link Vec3} and alpha, where alpha is the percent distance along the line\r\n * @param vector - {@link Vec3} to interpolate towards\r\n * @param alpha - interpolation factor in the [0, 1] interval\r\n * @returns - this {@link Vec3} after linear interpolation\r\n */\r\n lerp(vector: Vec3 = new Vec3(), alpha = 1): Vec3 {\r\n this.x += (vector.x - this.x) * alpha\r\n this.y += (vector.y - this.y) * alpha\r\n this.z += (vector.z - this.z) * alpha\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Apply a {@link Mat4 | matrix} to a {@link Vec3}\r\n * Useful to convert a position {@link Vec3} from plane local world to webgl space using projection view matrix for example\r\n * Source code from: http://glmatrix.net/docs/vec3.js.html\r\n * @param matrix - {@link Mat4 | matrix} to use\r\n * @returns - this {@link Vec3} after {@link Mat4 | matrix} application\r\n */\r\n applyMat4(matrix: Mat4): Vec3 {\r\n const x = this._x,\r\n y = this._y,\r\n z = this._z\r\n const mArray = matrix.elements\r\n\r\n let w = mArray[3] * x + mArray[7] * y + mArray[11] * z + mArray[15]\r\n w = w || 1\r\n\r\n this.x = (mArray[0] * x + mArray[4] * y + mArray[8] * z + mArray[12]) / w\r\n this.y = (mArray[1] * x + mArray[5] * y + mArray[9] * z + mArray[13]) / w\r\n this.z = (mArray[2] * x + mArray[6] * y + mArray[10] * z + mArray[14]) / w\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set this {@link Vec3} to the translation component of a {@link Mat4 | matrix}.\r\n * @param matrix - {@link Mat4 | matrix} to use\r\n * @returns - this {@link Vec3} after {@link Mat4 | matrix} application.\r\n */\r\n setFromMatrixPosition(matrix: Mat4) {\r\n const e = matrix.elements\r\n\r\n this.x = e[12]\r\n this.y = e[13]\r\n this.z = e[14]\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Apply a {@link Quat | quaternion} (rotation in 3D space) to this {@link Vec3}\r\n * @param quaternion - {@link Quat | quaternion} to use\r\n * @returns - this {@link Vec3} with the transformation applied\r\n */\r\n applyQuat(quaternion: Quat = new Quat()): Vec3 {\r\n const x = this.x,\r\n y = this.y,\r\n z = this.z\r\n const qx = quaternion.elements[0],\r\n qy = quaternion.elements[1],\r\n qz = quaternion.elements[2],\r\n qw = quaternion.elements[3]\r\n\r\n // calculate quat * vector\r\n\r\n const ix = qw * x + qy * z - qz * y\r\n const iy = qw * y + qz * x - qx * z\r\n const iz = qw * z + qx * y - qy * x\r\n const iw = -qx * x - qy * y - qz * z\r\n\r\n // calculate result * inverse quat\r\n\r\n this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy\r\n this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz\r\n this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Rotate a {@link Vec3} around and axis by a given angle\r\n * @param axis - normalized {@link Vec3} around which to rotate\r\n * @param angle - angle (in radians) to rotate\r\n * @param quaternion - optional {@link Quat | quaternion} to use for rotation computations\r\n * @returns - this {@link Vec3} with the rotation applied\r\n */\r\n applyAxisAngle(axis = new Vec3(), angle = 0, quaternion = new Quat()) {\r\n // https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js#L212\r\n return this.applyQuat(quaternion.setFromAxisAngle(axis, angle))\r\n }\r\n\r\n /**\r\n * Project a 3D coordinate {@link Vec3} to a 2D coordinate {@link Vec3}\r\n * @param camera - {@link Camera} to use for projection\r\n * @returns - projected {@link Vec3}\r\n */\r\n project(camera: Camera): Vec3 {\r\n this.applyMat4(camera.viewMatrix).applyMat4(camera.projectionMatrix)\r\n return this\r\n }\r\n\r\n /**\r\n * Unproject a 2D coordinate {@link Vec3} to 3D coordinate {@link Vec3}\r\n * @param camera - {@link Camera} to use for projection\r\n * @returns - unprojected {@link Vec3}\r\n */\r\n unproject(camera: Camera): Vec3 {\r\n this.applyMat4(camera.projectionMatrix.getInverse()).applyMat4(camera.modelMatrix)\r\n return this\r\n }\r\n}\r\n","import { BufferLayout, getBufferLayout, TypedArray, WGSLVariableType } from '../utils'\r\nimport { Vec2 } from '../../../math/Vec2'\r\nimport { Vec3 } from '../../../math/Vec3'\r\nimport { Quat } from '../../../math/Quat'\r\nimport { Mat4 } from '../../../math/Mat4'\r\nimport { throwWarning } from '../../../utils/utils'\r\nimport { Mat3 } from '../../../math/Mat3'\r\nimport { InputValue } from '../../../types/BindGroups'\r\n\r\n/** Number of slots per row */\r\nexport const slotsPerRow = 4\r\n/** Number of bytes per slot */\r\nexport const bytesPerSlot = 4\r\n/** Number of bytes per row */\r\nexport const bytesPerRow = slotsPerRow * bytesPerSlot\r\n\r\n/**\r\n * Defines a position in our array buffer with a row index and a byte index\r\n */\r\nexport interface BufferElementAlignmentPosition {\r\n /** row index of that position */\r\n row: number\r\n /** byte index of that position */\r\n byte: number\r\n}\r\n\r\n/**\r\n * Defines our {@link BufferElement} alignment:\r\n * Keep track of an entry start and end row and bytes indexes (16 bytes per row)\r\n */\r\nexport interface BufferElementAlignment {\r\n /** The row and byte indexes at which this {@link BufferElement} starts */\r\n start: BufferElementAlignmentPosition\r\n /** The row and byte indexes at which this {@link BufferElement} ends */\r\n end: BufferElementAlignmentPosition\r\n}\r\n\r\n/**\r\n * Parameters used to create a {@link BufferElement}\r\n */\r\nexport interface BufferElementParams {\r\n /** The name of the {@link BufferElement} */\r\n name: string\r\n /** The key of the {@link BufferElement} */\r\n key: string\r\n /** The WGSL variable type of the {@link BufferElement} */\r\n type: WGSLVariableType\r\n}\r\n\r\n/**\r\n * Used to handle each {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} view and data layout alignment.\r\n * Compute the exact alignment offsets needed to fill an {@link ArrayBuffer} that will be sent to a {@link GPUBuffer}, based on an input type and value.\r\n * Also update the view array at the correct offset.\r\n *\r\n * So all our struct need to be packed into our arrayBuffer using a precise layout.\r\n * They will be stored in rows, each row made of 4 slots and each slots made of 4 bytes. Depending on the binding element type, its row and slot may vary and we may have to insert empty padded values.\r\n * All in all it looks like that:
\r\n *
\r\n *          slot 0    slot 1    slot 2    slot 3\r\n * row 0 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 1 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 2 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * 
\r\n * see https://webgpufundamentals.org/webgpu/lessons/resources/wgsl-offset-computer.html\r\n */\r\nexport class BufferElement {\r\n /** The name of the {@link BufferElement} */\r\n name: string\r\n /** The WGSL variable type of the {@link BufferElement} */\r\n type: WGSLVariableType\r\n /** The key of the {@link BufferElement} */\r\n key: string\r\n\r\n /** {@link BufferLayout} used to fill the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} at the right offsets */\r\n bufferLayout: BufferLayout\r\n\r\n /**\r\n * Object defining exactly at which place a binding should be inserted into the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n alignment: BufferElementAlignment\r\n\r\n /** Array containing the {@link BufferElement} values */\r\n view?: TypedArray\r\n\r\n /** Function assigned to set the {@link view} values */\r\n setValue: (value: InputValue) => void | null\r\n\r\n /**\r\n * BufferElement constructor\r\n * @param parameters - {@link BufferElementParams | parameters} used to create our {@link BufferElement}\r\n */\r\n constructor({ name, key, type = 'f32' }: BufferElementParams) {\r\n this.name = name\r\n this.key = key\r\n this.type = type\r\n\r\n this.bufferLayout = getBufferLayout(this.type.replace('array', '').replace('<', '').replace('>', ''))\r\n\r\n // set init alignment\r\n this.alignment = {\r\n start: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n end: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n }\r\n\r\n this.setValue = null\r\n }\r\n\r\n /**\r\n * Get the total number of rows used by this {@link BufferElement}\r\n * @readonly\r\n */\r\n get rowCount(): number {\r\n return this.alignment.end.row - this.alignment.start.row + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement} based on {@link BufferElementAlignment | alignment} start and end offsets\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return Math.abs(this.endOffset - this.startOffset) + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement}, including final padding\r\n * @readonly\r\n */\r\n get paddedByteCount(): number {\r\n return (this.alignment.end.row + 1) * bytesPerRow\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.start)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffsetToIndex(): number {\r\n return this.startOffset / bytesPerSlot\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.end)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffsetToIndex(): number {\r\n return Math.floor(this.endOffset / bytesPerSlot)\r\n }\r\n\r\n /**\r\n * Get the position at given offset (i.e. byte index)\r\n * @param offset - byte index to use\r\n */\r\n getPositionAtOffset(offset = 0): BufferElementAlignmentPosition {\r\n return {\r\n row: Math.floor(offset / bytesPerRow),\r\n byte: offset % bytesPerRow,\r\n }\r\n }\r\n\r\n /**\r\n * Get the number of bytes at a given {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} from which to count\r\n * @returns - byte count at the given {@link BufferElementAlignmentPosition | position}\r\n */\r\n getByteCountAtPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }): number {\r\n return position.row * bytesPerRow + position.byte\r\n }\r\n\r\n /**\r\n * Check that a {@link BufferElementAlignmentPosition#byte | byte position} does not overflow its max value (16)\r\n * @param position - {@link BufferElementAlignmentPosition | position}\r\n * @returns - updated {@link BufferElementAlignmentPosition | position}\r\n */\r\n applyOverflowToPosition(\r\n position: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignmentPosition {\r\n if (position.byte > bytesPerRow - 1) {\r\n const overflow = position.byte % bytesPerRow\r\n position.row += Math.floor(position.byte / bytesPerRow)\r\n position.byte = overflow\r\n }\r\n\r\n return position\r\n }\r\n\r\n /**\r\n * Get the number of bytes between two {@link BufferElementAlignmentPosition | positions}\r\n * @param p1 - first {@link BufferElementAlignmentPosition | position}\r\n * @param p2 - second {@link BufferElementAlignmentPosition | position}\r\n * @returns - number of bytes\r\n */\r\n getByteCountBetweenPositions(\r\n p1: BufferElementAlignmentPosition = { row: 0, byte: 0 },\r\n p2: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): number {\r\n return Math.abs(this.getByteCountAtPosition(p2) - this.getByteCountAtPosition(p1))\r\n }\r\n\r\n /**\r\n * Compute the right alignment (i.e. start and end rows and bytes) given the size and align properties and the next available {@link BufferElementAlignmentPosition | position}\r\n * @param nextPositionAvailable - next {@link BufferElementAlignmentPosition | position} at which we should insert this element\r\n * @returns - computed {@link BufferElementAlignment | alignment}\r\n */\r\n getElementAlignment(\r\n nextPositionAvailable: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignment {\r\n const alignment = {\r\n start: nextPositionAvailable,\r\n end: nextPositionAvailable,\r\n }\r\n\r\n const { size, align } = this.bufferLayout\r\n\r\n // check the alignment, i.e. even if there's enough space for our binding\r\n // we might have to pad the slot because some types need a specific alignment\r\n if (nextPositionAvailable.byte % align !== 0) {\r\n nextPositionAvailable.byte += nextPositionAvailable.byte % align\r\n }\r\n\r\n if (size <= bytesPerRow && nextPositionAvailable.byte + size > bytesPerRow) {\r\n // in the case of a binding that could fit on one row\r\n // but we don't have space on the current row for this binding element\r\n // go to next row\r\n nextPositionAvailable.row += 1\r\n nextPositionAvailable.byte = 0\r\n } else if (size > bytesPerRow && (nextPositionAvailable.byte > bytesPerRow || nextPositionAvailable.byte > 0)) {\r\n // there's also the case where the binding size is too big\r\n // and we have already padded it above\r\n // or we've just started a new row\r\n // but the binding size is too big to fit in one row\r\n // just go to next row as well\r\n nextPositionAvailable.row += 1\r\n nextPositionAvailable.byte = 0\r\n }\r\n\r\n alignment.end = {\r\n row: nextPositionAvailable.row + Math.ceil(size / bytesPerRow) - 1,\r\n byte: nextPositionAvailable.byte + (size % bytesPerRow === 0 ? bytesPerRow - 1 : (size % bytesPerRow) - 1), // end of row ? then it ends on slot (bytesPerRow - 1)\r\n }\r\n\r\n // now final check, if end slot has overflown\r\n alignment.end = this.applyOverflowToPosition(alignment.end)\r\n\r\n return alignment\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from a {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} at which to start inserting the values in the {@link !core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignmentFromPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }) {\r\n this.alignment = this.getElementAlignment(position)\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from an offset (byte count)\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignment(startOffset = 0) {\r\n this.setAlignmentFromPosition(this.getPositionAtOffset(startOffset))\r\n }\r\n\r\n /**\r\n * Set the {@link view}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n this.view = new this.bufferLayout.View(\r\n arrayBuffer,\r\n this.startOffset,\r\n this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\r\n )\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from a float or an int\r\n * @param value - float or int to use\r\n */\r\n setValueFromFloat(value: number) {\r\n this.view[0] = value as number\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from a {@link Vec2} or an array\r\n * @param value - {@link Vec2} or array to use\r\n */\r\n setValueFromVec2(value: Vec2 | number[]) {\r\n this.view[0] = (value as Vec2).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec2).y ?? value[1] ?? 0\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from a {@link Vec3} or an array\r\n * @param value - {@link Vec3} or array to use\r\n */\r\n setValueFromVec3(value: Vec3 | number[]) {\r\n this.view[0] = (value as Vec3).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec3).y ?? value[1] ?? 0\r\n this.view[2] = (value as Vec3).z ?? value[2] ?? 0\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from a {@link Mat4} or {@link Quat}\r\n * @param value - {@link Mat4} or {@link Quat} to use\r\n */\r\n setValueFromMat4OrQuat(value: Mat4 | Quat) {\r\n this.view.set(value.elements)\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from a {@link Mat3}\r\n * @param value - {@link Mat3} to use\r\n */\r\n setValueFromMat3(value: Mat3) {\r\n // mat3x3f are padded!\r\n this.setValueFromArrayWithPad(value.elements)\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from an array\r\n * @param value - array to use\r\n */\r\n setValueFromArray(value: number[] | TypedArray) {\r\n this.view.set(value as number[] | TypedArray)\r\n }\r\n\r\n /**\r\n * Set the {@link view} value from an array with pad applied\r\n * @param value - array to use\r\n */\r\n setValueFromArrayWithPad(value: number[] | TypedArray) {\r\n for (\r\n let i = 0, offset = 0;\r\n i < this.view.length;\r\n i += this.bufferLayout.pad[0] + this.bufferLayout.pad[1], offset++\r\n ) {\r\n for (let j = 0; j < this.bufferLayout.pad[0]; j++) {\r\n this.view[i + j] = value[i + j - offset]\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value\r\n * @param value - new value to use\r\n */\r\n update(value: InputValue) {\r\n if (!this.setValue) {\r\n this.setValue = ((value) => {\r\n if (this.type === 'f32' || this.type === 'u32' || this.type === 'i32') {\r\n return this.setValueFromFloat\r\n } else if (this.type === 'vec2f') {\r\n return this.setValueFromVec2\r\n } else if (this.type === 'vec3f') {\r\n return this.setValueFromVec3\r\n } else if (this.type === 'mat3x3f') {\r\n return (value as Mat3).elements ? this.setValueFromMat3 : this.setValueFromArrayWithPad\r\n } else if ((value as Quat | Mat4).elements) {\r\n return this.setValueFromMat4OrQuat\r\n } else if (ArrayBuffer.isView(value) || Array.isArray(value)) {\r\n if (!this.bufferLayout.pad) {\r\n return this.setValueFromArray\r\n } else {\r\n return this.setValueFromArrayWithPad\r\n }\r\n } else {\r\n throwWarning(`${this.constructor.name}: value passed to ${this.name} cannot be used: ${value}`)\r\n }\r\n })(value)\r\n }\r\n\r\n this.setValue(value)\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n * @returns - extracted data from the {@link Float32Array}\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n return result.slice(this.startOffsetToIndex, this.endOffsetToIndex)\r\n }\r\n}\r\n","import { BufferElement, BufferElementParams, bytesPerSlot } from './BufferElement'\r\nimport { TypedArray } from '../utils'\r\n\r\n/**\r\n * Parameters used to create a {@link BufferArrayElement}\r\n */\r\nexport interface BufferArrayElementParams extends BufferElementParams {\r\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\r\n arrayLength: number\r\n}\r\n\r\n/**\r\n * Used to handle specific array {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} types\r\n */\r\nexport class BufferArrayElement extends BufferElement {\r\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\r\n arrayLength: number\r\n /** Total number of elements (i.e. {@link arrayLength} divided by {@link core/bindings/utils.BufferLayout | buffer layout} number of elements */\r\n numElements: number\r\n /** Number of bytes in the {@link ArrayBuffer} between two elements {@link startOffset} */\r\n arrayStride: number\r\n\r\n /**\r\n * BufferArrayElement constructor\r\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferArrayElement}\r\n */\r\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\r\n super({ name, key, type })\r\n\r\n this.arrayLength = arrayLength\r\n this.numElements = Math.ceil(this.arrayLength / this.bufferLayout.numElements)\r\n }\r\n\r\n /**\r\n * Get the array stride between two elements of the array, in indices\r\n * @readonly\r\n */\r\n get arrayStrideToIndex(): number {\r\n return this.arrayStride / bytesPerSlot\r\n }\r\n\r\n /**\r\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\r\n * To compute how arrays are packed, we get the second item alignment as well and use it to calculate the arrayStride between two array elements. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array buffer}\r\n */\r\n setAlignment(startOffset = 0) {\r\n super.setAlignment(startOffset)\r\n\r\n // repeat for a second element to know how things are laid out\r\n const nextAlignment = this.getElementAlignment(this.getPositionAtOffset(this.endOffset + 1))\r\n this.arrayStride = this.getByteCountBetweenPositions(this.alignment.end, nextAlignment.end)\r\n\r\n this.alignment.end = this.getPositionAtOffset(this.endOffset + this.arrayStride * (this.numElements - 1))\r\n }\r\n\r\n /**\r\n * Set the strided {@link view} value from an array\r\n * @param value - array to use\r\n */\r\n setValueFromArray(value: number[] | TypedArray) {\r\n let valueIndex = 0\r\n\r\n const viewLength = this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\r\n // arrayStride is our view length divided by the number of elements in our array\r\n const stride = Math.ceil(viewLength / this.numElements)\r\n\r\n for (let i = 0; i < this.numElements; i++) {\r\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\r\n this.view[j + i * stride] = value[valueIndex]\r\n\r\n valueIndex++\r\n }\r\n }\r\n }\r\n}\r\n","import { BufferArrayElement, BufferArrayElementParams } from './BufferArrayElement'\r\nimport { InputValue } from '../../../types/BindGroups'\r\n\r\n/**\r\n * Used to compute alignment when dealing with arrays of Struct\r\n */\r\nexport class BufferInterleavedArrayElement extends BufferArrayElement {\r\n /** Corresponding {@link DataView} set function based on {@link view} type */\r\n viewSetFunction: DataView['setInt32'] | DataView['setUint16'] | DataView['setUint32'] | DataView['setFloat32']\r\n\r\n /**\r\n * BufferInterleavedArrayElement constructor\r\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferInterleavedArrayElement}\r\n */\r\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\r\n super({ name, key, type, arrayLength })\r\n\r\n this.arrayStride = 1\r\n\r\n this.arrayLength = arrayLength\r\n this.numElements = Math.ceil(this.arrayLength / this.bufferLayout.numElements)\r\n }\r\n\r\n /**\r\n * Get the total number of slots used by this {@link BufferInterleavedArrayElement} based on buffer layout size and total number of elements\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return this.bufferLayout.size * this.numElements\r\n }\r\n\r\n /**\r\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\r\n * To compute how arrays are packed, we need to compute the arrayStride between two elements beforehand and pass it here. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param stride - Stride in the {@link ArrayBuffer} between two elements of the array\r\n */\r\n setAlignment(startOffset = 0, stride = 0) {\r\n this.alignment = this.getElementAlignment(this.getPositionAtOffset(startOffset))\r\n\r\n this.arrayStride = stride\r\n\r\n this.alignment.end = this.getPositionAtOffset(this.endOffset + stride * (this.numElements - 1))\r\n }\r\n\r\n /**\r\n * Set the {@link view} and {@link viewSetFunction}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n // our view will be a simple typed array, not linked to the array buffer\r\n this.view = new this.bufferLayout.View(this.bufferLayout.numElements * this.numElements)\r\n\r\n // but our viewSetFunction is linked to the array view\r\n this.viewSetFunction = ((arrayView) => {\r\n switch (this.bufferLayout.View) {\r\n case Int32Array:\r\n return arrayView.setInt32.bind(arrayView) as DataView['setInt32']\r\n case Uint16Array:\r\n return arrayView.setUint16.bind(arrayView) as DataView['setUint16']\r\n case Uint32Array:\r\n return arrayView.setUint32.bind(arrayView) as DataView['setUint32']\r\n case Float32Array:\r\n default:\r\n return arrayView.setFloat32.bind(arrayView) as DataView['setFloat32']\r\n }\r\n })(arrayView)\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value, and then update the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} using sub arrays\r\n * @param value - new value to use\r\n */\r\n update(value: InputValue) {\r\n super.update(value)\r\n\r\n // now use our viewSetFunction to fill the array view with interleaved alignment\r\n for (let i = 0; i < this.numElements; i++) {\r\n const subarray = this.view.subarray(\r\n i * this.bufferLayout.numElements,\r\n i * this.bufferLayout.numElements + this.bufferLayout.numElements\r\n )\r\n\r\n const startByteOffset = this.startOffset + i * this.arrayStride\r\n\r\n // view set function need to be called for each subarray entry, so loop over subarray entries\r\n subarray.forEach((value, index) => {\r\n this.viewSetFunction(startByteOffset + index * this.bufferLayout.View.BYTES_PER_ELEMENT, value, true)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferInterleavedArrayElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n const interleavedResult = new Float32Array(this.arrayLength)\r\n for (let i = 0; i < this.numElements; i++) {\r\n const resultOffset = this.startOffsetToIndex + i * this.arrayStrideToIndex\r\n\r\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\r\n interleavedResult[i * this.bufferLayout.numElements + j] = result[resultOffset + j]\r\n }\r\n }\r\n return interleavedResult\r\n }\r\n}\r\n","// buffers bitwise flags\r\nimport { BufferBindingType } from '../bindings/Binding'\r\n\r\n/** Defines all kinds of allowed buffer usages as camel case strings. */\r\nexport type BufferUsageKeys =\r\n | 'copySrc'\r\n | 'copyDst'\r\n | 'index'\r\n | 'indirect'\r\n | 'mapRead'\r\n | 'mapWrite'\r\n | 'queryResolve'\r\n | 'vertex'\r\n | BufferBindingType\r\n\r\n/**\r\n * Map {@link BufferUsageKeys | buffer usage names} with actual {@link GPUBufferUsageFlags | buffer usage bitwise flags}.\r\n */\r\nconst bufferUsages: Map = new Map([\r\n ['copySrc', GPUBufferUsage.COPY_SRC],\r\n ['copyDst', GPUBufferUsage.COPY_DST],\r\n ['index', GPUBufferUsage.INDEX],\r\n ['indirect', GPUBufferUsage.INDIRECT],\r\n ['mapRead', GPUBufferUsage.MAP_READ],\r\n ['mapWrite', GPUBufferUsage.MAP_WRITE],\r\n ['queryResolve', GPUBufferUsage.QUERY_RESOLVE],\r\n ['storage', GPUBufferUsage.STORAGE],\r\n ['uniform', GPUBufferUsage.UNIFORM],\r\n ['vertex', GPUBufferUsage.VERTEX],\r\n])\r\n\r\n/**\r\n * Get the corresponding {@link GPUBufferUsageFlags | buffer usage bitwise flags} based on an array of {@link BufferUsageKeys | buffer usage names}.\r\n * @param usages - array of {@link BufferUsageKeys | buffer usage names}.\r\n * @returns - corresponding {@link GPUBufferUsageFlags | buffer usage bitwise flags}.\r\n */\r\nexport const getBufferUsages = (usages: BufferUsageKeys[] = []): GPUBufferUsageFlags => {\r\n return usages.reduce((acc, v) => {\r\n return acc | bufferUsages.get(v)\r\n }, 0)\r\n}\r\n","import { generateUUID } from '../../utils/utils'\r\nimport { BufferUsageKeys, getBufferUsages } from './utils'\r\n\r\n/**\r\n * Parameters used to create a {@link Buffer}.\r\n */\r\nexport interface BufferParams extends Partial> {\r\n /** Allowed usages for the {@link Buffer#GPUBuffer | GPU buffer} as an array of {@link BufferUsageKeys | buffer usages names} */\r\n usage?: BufferUsageKeys[]\r\n}\r\n\r\n/**\r\n * Used as a wrapper around {@link GPUBuffer}.\r\n *\r\n * Useful to keep tracks of all the {@link GPUBuffer} created thanks to the {@link uuid} property.\r\n */\r\nexport class Buffer {\r\n /** The type of the {@link Buffer} */\r\n type: string\r\n /** The universal unique id of the {@link Buffer} */\r\n uuid: string\r\n /** Options used to create this {@link Buffer}, also used as {@link GPUBufferDescriptor} */\r\n options: GPUBufferDescriptor\r\n\r\n /** The actual {@link GPUBuffer} after having been created. */\r\n GPUBuffer: null | GPUBuffer\r\n\r\n /** A Set to store this {@link Buffer} consumers (usually {@link core/geometries/Geometry.Geometry#uuid | Geometry uuid} or {@link core/bindGroups/BindGroup.BindGroup#uuid | BindGroup uuid}) */\r\n consumers: Set\r\n\r\n /**\r\n * Buffer constructor\r\n * @param parameters - {@link GPUBufferDescriptor | parameters} used to create our Buffer\r\n */\r\n constructor(\r\n {\r\n label = 'Buffer',\r\n size = 0,\r\n usage = ['copySrc', 'copyDst'],\r\n mappedAtCreation = false,\r\n }: BufferParams = {} as BufferParams\r\n ) {\r\n this.type = 'Buffer'\r\n\r\n this.reset()\r\n\r\n this.uuid = generateUUID()\r\n\r\n this.consumers = new Set()\r\n\r\n this.options = {\r\n label,\r\n size,\r\n usage: getBufferUsages(usage),\r\n mappedAtCreation,\r\n }\r\n }\r\n\r\n /** Reset the {@link GPUBuffer} value to `null`. */\r\n reset() {\r\n this.GPUBuffer = null\r\n }\r\n\r\n /** Allow to dynamically set the size of the {@link GPUBuffer}. */\r\n set size(value: number) {\r\n this.options.size = value\r\n }\r\n\r\n /**\r\n * Create a {@link GPUBuffer} based on the descriptor stored in the {@link options | Buffer options}.\r\n * @param renderer - {@link core/renderers/GPURenderer.GPURenderer | renderer} used to create the {@link GPUBuffer}.\r\n * @param options - optional way to update the {@link options} previously set before creating the {@link GPUBuffer}.\r\n */\r\n createBuffer(renderer, options: BufferParams = {}) {\r\n const { usage, ...staticOptions } = options\r\n\r\n this.options = {\r\n ...this.options,\r\n ...staticOptions,\r\n ...(usage !== undefined && { usage: getBufferUsages(usage) }),\r\n }\r\n\r\n this.setBuffer(renderer.createBuffer(this))\r\n }\r\n\r\n /**\r\n * Set the {@link GPUBuffer}. This allows to use a {@link Buffer} with a {@link GPUBuffer} created separately.\r\n * @param GPUBuffer - GPU buffer to use.\r\n */\r\n setBuffer(GPUBuffer: GPUBuffer) {\r\n this.GPUBuffer = GPUBuffer\r\n }\r\n\r\n /**\r\n * Copy an {@link Buffer#GPUBuffer | Buffer GPUBuffer} and its {@link options} into this {@link Buffer}.\r\n * @param buffer - {@link Buffer} to use for the copy.\r\n * @param destroyPreviousBuffer - whether to destroy the previous {@link Buffer} before the copy.\r\n */\r\n copy(buffer: Buffer, destroyPreviousBuffer: boolean = false) {\r\n if (destroyPreviousBuffer) {\r\n this.destroy()\r\n }\r\n\r\n this.options = buffer.options\r\n this.GPUBuffer = buffer.GPUBuffer\r\n this.consumers = new Set([...this.consumers, ...buffer.consumers])\r\n }\r\n\r\n /**\r\n * Map the {@link GPUBuffer} and put a copy of the data into a {@link Float32Array}.\r\n * @async\r\n * @returns - {@link Float32Array} holding the {@link GPUBuffer} data.\r\n */\r\n async mapBufferAsync() {\r\n if (!this.GPUBuffer || this.GPUBuffer.mapState !== 'unmapped') return new Float32Array(0)\r\n\r\n await this.GPUBuffer.mapAsync(GPUMapMode.READ)\r\n const result = new Float32Array(this.GPUBuffer.getMappedRange().slice(0))\r\n this.GPUBuffer.unmap()\r\n\r\n return result\r\n }\r\n\r\n /**\r\n * Destroy the {@link GPUBuffer} and {@link reset} its value.\r\n */\r\n destroy() {\r\n this.GPUBuffer?.destroy()\r\n this.reset()\r\n this.consumers.clear()\r\n }\r\n}\r\n","import { Binding, BindingParams, BufferBindingMemoryAccessType, BufferBindingType } from './Binding'\r\nimport { getBindGroupLayoutBindingType, getBindingWGSLVarType, getBufferLayout, TypedArray } from './utils'\r\nimport { throwWarning, toCamelCase, toKebabCase } from '../../utils/utils'\r\nimport { Vec2 } from '../../math/Vec2'\r\nimport { Vec3 } from '../../math/Vec3'\r\nimport { Input, InputBase, InputValue } from '../../types/BindGroups'\r\nimport { BufferElement, bytesPerRow } from './bufferElements/BufferElement'\r\nimport { BufferArrayElement } from './bufferElements/BufferArrayElement'\r\nimport { BufferInterleavedArrayElement } from './bufferElements/BufferInterleavedArrayElement'\r\nimport { Buffer, BufferParams } from '../buffers/Buffer'\r\n\r\n/**\r\n * Defines a {@link BufferBinding} input object that can set a value and run a callback function when this happens\r\n */\r\nexport interface BufferBindingInput extends InputBase {\r\n /** Original {@link InputValue | input value} */\r\n _value: InputValue\r\n\r\n /** Get the {@link InputValue | input value} */\r\n get value(): InputValue\r\n\r\n /** Set the {@link InputValue | input value} */\r\n set value(value: InputValue)\r\n\r\n /** Whether the {@link InputValue | input value} has changed and we should update the {@link BufferBinding#arrayBuffer | buffer binding array} */\r\n shouldUpdate: boolean\r\n\r\n /** {@link BufferBindingInput} name */\r\n name: string\r\n}\r\n\r\n/**\r\n * Base parameters used to create a {@link BufferBinding}\r\n */\r\nexport interface BufferBindingBaseParams {\r\n /** Whether this {@link BufferBinding} should use structured WGSL variables */\r\n useStruct?: boolean\r\n /** {@link BufferBinding} memory access types (read only or read/write) */\r\n access?: BufferBindingMemoryAccessType\r\n /** Object containing one or multiple {@link Input | inputs} describing the structure of the {@link BufferBinding} */\r\n struct?: Record\r\n /** Allowed usages for the {@link BufferBinding#buffer} as an array of {@link core/buffers/utils.BufferUsageKeys | buffer usages names} */\r\n usage?: BufferParams['usage']\r\n}\r\n\r\n/**\r\n * Parameters used to create a {@link BufferBinding}\r\n */\r\nexport interface BufferBindingParams extends BindingParams, BufferBindingBaseParams {\r\n /** The binding type of the {@link BufferBinding} */\r\n bindingType?: BufferBindingType\r\n\r\n /** Optional array of already created {@link BufferBinding} to add to this {@link BufferBinding}. */\r\n bindings?: BufferBinding[]\r\n}\r\n\r\n/** All allowed {@link BufferElement | buffer elements} */\r\nexport type AllowedBufferElement = BufferElement | BufferArrayElement | BufferInterleavedArrayElement\r\n\r\n/**\r\n * Used to format {@link BufferBindingParams#struct | uniforms or storages struct inputs} and create a single typed array that will hold all those inputs values. The array needs to be correctly padded depending on every value type, so it can be safely used as a GPUBuffer input.
\r\n * It will also create WGSL Structs and variables according to the BufferBindings inputs parameters.
\r\n * The WGSL structs and variables declaration may vary based on the input types, especially if there's one or more arrays involved (i.e. `array`, `array` etc.).\r\n *\r\n * @example\r\n * ```javascript\r\n * // create a GPU buffer binding\r\n * const bufferBinding = new BufferBinding({\r\n * name: 'params', // name of the WGSL object\r\n * bindingType: 'uniform', // should be 'storage' for large arrays\r\n * struct: {\r\n * opacity: {\r\n * type: 'f32',\r\n * value: 1,\r\n * },\r\n * mousePosition: {\r\n * type: 'vec2f',\r\n * value: new Vec2(),\r\n * },\r\n * },\r\n * })\r\n * ```\r\n */\r\nexport class BufferBinding extends Binding {\r\n /** The binding type of the {@link BufferBinding} */\r\n bindingType: BufferBindingType\r\n /** Flag to indicate whether this {@link BufferBinding} {@link bufferElements | buffer elements} should be packed in a single structured object or if each one of them should be a separate binding. */\r\n useStruct: boolean\r\n /** All the {@link BufferBinding} data inputs */\r\n inputs: Record\r\n\r\n /** Flag to indicate whether one of the {@link inputs} value has changed and we need to update the GPUBuffer linked to the {@link arrayBuffer} array */\r\n shouldUpdate: boolean\r\n\r\n /** An array describing how each corresponding {@link inputs} should be inserted into our {@link arrayView} array */\r\n bufferElements: AllowedBufferElement[]\r\n\r\n /** Total size of our {@link arrayBuffer} array in bytes */\r\n arrayBufferSize: number\r\n /** Array buffer that will be sent to the {@link GPUBuffer} */\r\n arrayBuffer: ArrayBuffer\r\n /** Data view of our {@link arrayBuffer | array buffer} */\r\n arrayView: DataView\r\n\r\n /** The {@link Buffer} holding the {@link GPUBuffer} */\r\n buffer: Buffer\r\n\r\n /** A string to append to our shaders code describing the WGSL structure representing this {@link BufferBinding} */\r\n wgslStructFragment: string\r\n /** An array of strings to append to our shaders code declaring all the WGSL variables representing this {@link BufferBinding} */\r\n wgslGroupFragment: string[]\r\n /** Options used to create this {@link BufferBinding} */\r\n options: BufferBindingParams\r\n\r\n /**\r\n * BufferBinding constructor\r\n * @param parameters - {@link BufferBindingParams | parameters} used to create our BufferBindings\r\n */\r\n constructor({\r\n label = 'Uniform',\r\n name = 'uniform',\r\n bindingType,\r\n visibility,\r\n useStruct = true,\r\n access = 'read',\r\n usage = [],\r\n struct = {},\r\n bindings = [],\r\n }: BufferBindingParams) {\r\n bindingType = bindingType ?? 'uniform'\r\n\r\n super({ label, name, bindingType, visibility })\r\n\r\n this.options = {\r\n ...this.options,\r\n useStruct,\r\n access,\r\n usage,\r\n struct,\r\n bindings,\r\n }\r\n\r\n this.cacheKey += `${useStruct},${access},`\r\n\r\n this.arrayBufferSize = 0\r\n\r\n this.shouldUpdate = false\r\n this.useStruct = useStruct\r\n\r\n this.bufferElements = []\r\n this.inputs = {}\r\n this.buffer = new Buffer()\r\n\r\n if (Object.keys(struct).length) {\r\n this.setBindings(struct)\r\n this.setInputsAlignment()\r\n }\r\n\r\n if (Object.keys(struct).length || this.options.bindings.length) {\r\n this.setBufferAttributes()\r\n this.setWGSLFragment()\r\n }\r\n }\r\n\r\n /**\r\n * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource}\r\n * @readonly\r\n */\r\n get resourceLayout(): {\r\n /** {@link GPUBindGroupLayout | bind group layout} resource */\r\n buffer: GPUBufferBindingLayout\r\n } {\r\n return {\r\n buffer: {\r\n type: getBindGroupLayoutBindingType(this),\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get the resource cache key\r\n * @readonly\r\n */\r\n get resourceLayoutCacheKey(): string {\r\n return `buffer,${getBindGroupLayoutBindingType(this)},${this.visibility},`\r\n }\r\n\r\n /**\r\n * Get {@link GPUBindGroupEntry#resource | bind group resource}\r\n * @readonly\r\n */\r\n get resource(): {\r\n /** {@link GPUBindGroup | bind group} resource */\r\n buffer: GPUBuffer | null\r\n } {\r\n return { buffer: this.buffer.GPUBuffer }\r\n }\r\n\r\n /**\r\n * Clone this {@link BufferBinding} into a new one. Allows to skip buffer layout alignment computations.\r\n * @param params - params to use for cloning\r\n */\r\n clone(params: BufferBindingParams) {\r\n const { struct, ...defaultParams } = params\r\n\r\n const bufferBindingCopy = new (this.constructor as typeof BufferBinding)(defaultParams)\r\n struct && bufferBindingCopy.setBindings(struct)\r\n bufferBindingCopy.options.struct = struct\r\n\r\n bufferBindingCopy.arrayBufferSize = this.arrayBufferSize\r\n\r\n bufferBindingCopy.arrayBuffer = new ArrayBuffer(bufferBindingCopy.arrayBufferSize)\r\n bufferBindingCopy.arrayView = new DataView(\r\n bufferBindingCopy.arrayBuffer,\r\n 0,\r\n bufferBindingCopy.arrayBuffer.byteLength\r\n )\r\n\r\n bufferBindingCopy.buffer.size = bufferBindingCopy.arrayBuffer.byteLength\r\n\r\n this.bufferElements.forEach((bufferElement: BufferArrayElement) => {\r\n const newBufferElement = new (bufferElement.constructor as typeof BufferArrayElement)({\r\n name: bufferElement.name,\r\n key: bufferElement.key,\r\n type: bufferElement.type,\r\n ...(bufferElement.arrayLength && {\r\n arrayLength: bufferElement.arrayLength,\r\n }),\r\n })\r\n\r\n newBufferElement.alignment = JSON.parse(JSON.stringify(bufferElement.alignment))\r\n if (bufferElement.arrayStride) {\r\n newBufferElement.arrayStride = bufferElement.arrayStride\r\n }\r\n\r\n newBufferElement.setView(bufferBindingCopy.arrayBuffer, bufferBindingCopy.arrayView)\r\n bufferBindingCopy.bufferElements.push(newBufferElement)\r\n })\r\n\r\n // TODO bindings\r\n\r\n if (this.name === bufferBindingCopy.name && this.label === bufferBindingCopy.label) {\r\n bufferBindingCopy.wgslStructFragment = this.wgslStructFragment\r\n bufferBindingCopy.wgslGroupFragment = this.wgslGroupFragment\r\n } else {\r\n bufferBindingCopy.setWGSLFragment()\r\n }\r\n\r\n bufferBindingCopy.shouldUpdate = bufferBindingCopy.arrayBufferSize > 0\r\n\r\n return bufferBindingCopy\r\n }\r\n\r\n /**\r\n * Format bindings struct and set our {@link inputs}\r\n * @param bindings - bindings inputs\r\n */\r\n setBindings(bindings: Record) {\r\n for (const bindingKey of Object.keys(bindings)) {\r\n const binding = {} as BufferBindingInput\r\n\r\n for (const key in bindings[bindingKey]) {\r\n if (key !== 'value') {\r\n binding[key] = bindings[bindingKey][key]\r\n }\r\n }\r\n\r\n // binding name is the key\r\n binding.name = bindingKey\r\n\r\n // define a \"value\" getter/setter so we can now when to update the buffer binding\r\n Object.defineProperty(binding, 'value', {\r\n get() {\r\n return binding._value\r\n },\r\n set(v) {\r\n binding._value = v\r\n binding.shouldUpdate = true\r\n },\r\n })\r\n\r\n binding.value = bindings[bindingKey].value\r\n\r\n if (binding.value instanceof Vec2 || binding.value instanceof Vec3) {\r\n // add binding update to _onChangeCallback\r\n const _onChangeCallback = binding.value._onChangeCallback\r\n\r\n binding.value._onChangeCallback = () => {\r\n if (_onChangeCallback) {\r\n _onChangeCallback()\r\n }\r\n\r\n binding.shouldUpdate = true\r\n }\r\n }\r\n\r\n this.inputs[bindingKey] = binding\r\n\r\n this.cacheKey += `${bindingKey},${bindings[bindingKey].type},`\r\n }\r\n }\r\n\r\n /**\r\n * Set the buffer alignments from {@link inputs}.\r\n */\r\n setInputsAlignment() {\r\n // early on, check if there's at least one array binding\r\n // If there's one and only one, put it at the end of the binding elements array, treat it as a single entry of the type, but loop on it by array.length / size to fill the alignment\r\n // If there's more than one, create buffer interleaved elements.\r\n\r\n // if length === 0, OK\r\n // if length === 1, put it at the end of our struct\r\n // if length > 1, create a buffer interleaved elements\r\n let orderedBindings = Object.keys(this.inputs)\r\n\r\n const arrayBindings = orderedBindings.filter((bindingKey) => {\r\n return this.inputs[bindingKey].type.includes('array')\r\n })\r\n\r\n // put the array struct at the end\r\n if (arrayBindings.length) {\r\n orderedBindings.sort((bindingKeyA, bindingKeyB) => {\r\n // 0 if it's an array, -1 else\r\n const isBindingAArray = Math.min(0, this.inputs[bindingKeyA].type.indexOf('array'))\r\n const isBindingBArray = Math.min(0, this.inputs[bindingKeyB].type.indexOf('array'))\r\n\r\n return isBindingAArray - isBindingBArray\r\n })\r\n\r\n if (arrayBindings.length > 1) {\r\n // remove interleaved arrays from the ordered struct key array\r\n orderedBindings = orderedBindings.filter((bindingKey) => !arrayBindings.includes(bindingKey))\r\n }\r\n }\r\n\r\n // handle buffer (non interleaved) elements\r\n for (const bindingKey of orderedBindings) {\r\n const binding = this.inputs[bindingKey]\r\n\r\n const bufferElementOptions = {\r\n name: toCamelCase(binding.name ?? bindingKey),\r\n key: bindingKey,\r\n type: binding.type,\r\n }\r\n\r\n const isArray =\r\n binding.type.includes('array') && (Array.isArray(binding.value) || ArrayBuffer.isView(binding.value))\r\n\r\n this.bufferElements.push(\r\n isArray\r\n ? new BufferArrayElement({\r\n ...bufferElementOptions,\r\n arrayLength: (binding.value as number[]).length,\r\n })\r\n : new BufferElement(bufferElementOptions)\r\n )\r\n }\r\n\r\n // set their alignments\r\n this.bufferElements.forEach((bufferElement, index) => {\r\n const startOffset = index === 0 ? 0 : this.bufferElements[index - 1].endOffset + 1\r\n\r\n bufferElement.setAlignment(startOffset)\r\n })\r\n\r\n // now create our interleaved buffer elements\r\n if (arrayBindings.length > 1) {\r\n // first get the sizes of the arrays\r\n const arraySizes = arrayBindings.map((bindingKey) => {\r\n const binding = this.inputs[bindingKey]\r\n const bufferLayout = getBufferLayout(binding.type.replace('array', '').replace('<', '').replace('>', ''))\r\n\r\n return Math.ceil((binding.value as number[] | TypedArray).length / bufferLayout.numElements)\r\n })\r\n\r\n // are they all of the same size?\r\n const equalSize = arraySizes.every((size, i, array) => size === array[0])\r\n\r\n if (equalSize) {\r\n // this will hold our interleaved buffer elements\r\n const interleavedBufferElements = arrayBindings.map((bindingKey) => {\r\n const binding = this.inputs[bindingKey]\r\n return new BufferInterleavedArrayElement({\r\n name: toCamelCase(binding.name ?? bindingKey),\r\n key: bindingKey,\r\n type: binding.type,\r\n arrayLength: (binding.value as number[]).length,\r\n })\r\n })\r\n\r\n // now create temp buffer elements that we'll use to fill the interleaved buffer elements alignments\r\n const tempBufferElements = arrayBindings.map((bindingKey) => {\r\n const binding = this.inputs[bindingKey]\r\n return new BufferElement({\r\n name: toCamelCase(binding.name ?? bindingKey),\r\n key: bindingKey,\r\n type: binding.type.replace('array', '').replace('<', '').replace('>', ''),\r\n })\r\n })\r\n\r\n // set temp buffer alignments as if it was regular buffer elements\r\n tempBufferElements.forEach((bufferElement, index) => {\r\n if (index === 0) {\r\n if (this.bufferElements.length) {\r\n // if we already have buffer elements\r\n // get last one end row, and start at the next row\r\n bufferElement.setAlignmentFromPosition({\r\n row: this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1,\r\n byte: 0,\r\n })\r\n } else {\r\n bufferElement.setAlignment(0)\r\n }\r\n } else {\r\n bufferElement.setAlignment(tempBufferElements[index - 1].endOffset + 1)\r\n }\r\n })\r\n\r\n // now use last temp buffer end offset as our interleaved arrayStride\r\n const totalStride =\r\n tempBufferElements[tempBufferElements.length - 1].endOffset + 1 - tempBufferElements[0].startOffset\r\n\r\n // finally, set interleaved buffer elements alignment\r\n interleavedBufferElements.forEach((bufferElement, index) => {\r\n bufferElement.setAlignment(\r\n tempBufferElements[index].startOffset,\r\n Math.ceil(totalStride / bytesPerRow) * bytesPerRow\r\n )\r\n })\r\n\r\n // add to our buffer elements array\r\n this.bufferElements = [...this.bufferElements, ...interleavedBufferElements]\r\n } else {\r\n throwWarning(\r\n `BufferBinding: \"${\r\n this.label\r\n }\" contains multiple array inputs that should use an interleaved array, but their sizes do not match. These inputs cannot be added to the BufferBinding: \"${arrayBindings.join(\r\n ', '\r\n )}\"`\r\n )\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Set our buffer attributes:\r\n * Takes all the {@link inputs} and adds them to the {@link bufferElements} array with the correct start and end offsets (padded), then fill our {@link arrayBuffer} typed array accordingly.\r\n */\r\n setBufferAttributes() {\r\n const bufferElementsArrayBufferSize = this.bufferElements.length\r\n ? this.bufferElements[this.bufferElements.length - 1].paddedByteCount\r\n : 0\r\n\r\n this.arrayBufferSize = bufferElementsArrayBufferSize\r\n\r\n this.options.bindings.forEach((binding) => {\r\n this.arrayBufferSize += binding.arrayBufferSize\r\n })\r\n\r\n this.arrayBuffer = new ArrayBuffer(this.arrayBufferSize)\r\n this.arrayView = new DataView(this.arrayBuffer, 0, bufferElementsArrayBufferSize)\r\n\r\n this.options.bindings.forEach((binding, index) => {\r\n let offset = bufferElementsArrayBufferSize\r\n\r\n for (let i = 0; i < index; i++) {\r\n offset += this.options.bindings[i].arrayBuffer.byteLength\r\n }\r\n\r\n const bufferElLastRow = this.bufferElements.length\r\n ? this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1\r\n : 0\r\n\r\n const bindingLastRow =\r\n index > 0\r\n ? this.options.bindings[index - 1].bufferElements.length\r\n ? this.options.bindings[index - 1].bufferElements[\r\n this.options.bindings[index - 1].bufferElements.length - 1\r\n ].alignment.end.row + 1\r\n : 0\r\n : 0\r\n\r\n binding.bufferElements.forEach((bufferElement) => {\r\n bufferElement.alignment.start.row += bufferElLastRow + bindingLastRow\r\n bufferElement.alignment.end.row += bufferElLastRow + bindingLastRow\r\n })\r\n\r\n binding.arrayView = new DataView(this.arrayBuffer, offset, binding.arrayBuffer.byteLength)\r\n\r\n for (const bufferElement of binding.bufferElements) {\r\n bufferElement.setView(this.arrayBuffer, binding.arrayView)\r\n }\r\n })\r\n\r\n this.buffer.size = this.arrayBuffer.byteLength\r\n\r\n for (const bufferElement of this.bufferElements) {\r\n bufferElement.setView(this.arrayBuffer, this.arrayView)\r\n }\r\n\r\n this.shouldUpdate = this.arrayBufferSize > 0\r\n }\r\n\r\n /**\r\n * Set the WGSL code snippet to append to the shaders code. It consists of variable (and Struct structures if needed) declarations.\r\n */\r\n setWGSLFragment() {\r\n if (!this.bufferElements.length && !this.options.bindings.length) return\r\n\r\n const uniqueBindings = []\r\n this.options.bindings.forEach((binding) => {\r\n const bindingExists = uniqueBindings.find((b) => b.name === binding.name)\r\n if (!bindingExists) {\r\n uniqueBindings.push({\r\n name: binding.name,\r\n label: binding.label,\r\n count: 1,\r\n wgslStructFragment: binding.wgslStructFragment,\r\n })\r\n } else {\r\n bindingExists.count++\r\n }\r\n })\r\n\r\n const kebabCaseLabel = toKebabCase(this.label)\r\n\r\n if (this.useStruct) {\r\n const structs = {}\r\n\r\n structs[kebabCaseLabel] = {}\r\n\r\n const bufferElements = this.bufferElements.filter(\r\n (bufferElement) => !(bufferElement instanceof BufferInterleavedArrayElement)\r\n )\r\n const interleavedBufferElements = this.bufferElements.filter(\r\n (bufferElement) => bufferElement instanceof BufferInterleavedArrayElement\r\n ) as BufferInterleavedArrayElement[]\r\n\r\n if (interleavedBufferElements.length) {\r\n const arrayLength = this.bindingType === 'uniform' ? `, ${interleavedBufferElements[0].numElements}` : ''\r\n\r\n if (bufferElements.length) {\r\n structs[`${kebabCaseLabel}Element`] = {}\r\n\r\n interleavedBufferElements.forEach((binding) => {\r\n structs[`${kebabCaseLabel}Element`][binding.name] = binding.type\r\n .replace('array', '')\r\n .replace('<', '')\r\n .replace('>', '')\r\n })\r\n\r\n bufferElements.forEach((binding) => {\r\n structs[kebabCaseLabel][binding.name] = binding.type\r\n })\r\n\r\n const interleavedBufferName = this.bufferElements.find((bufferElement) => bufferElement.name === 'elements')\r\n ? `${this.name}Elements`\r\n : 'elements'\r\n\r\n structs[kebabCaseLabel][interleavedBufferName] = `array<${kebabCaseLabel}Element${arrayLength}>`\r\n\r\n const varType = getBindingWGSLVarType(this)\r\n this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]\r\n } else {\r\n this.bufferElements.forEach((binding) => {\r\n structs[kebabCaseLabel][binding.name] = binding.type.replace('array', '').replace('<', '').replace('>', '')\r\n })\r\n\r\n const varType = getBindingWGSLVarType(this)\r\n this.wgslGroupFragment = [`${varType} ${this.name}: array<${kebabCaseLabel}${arrayLength}>;`]\r\n }\r\n } else {\r\n bufferElements.forEach((binding) => {\r\n const bindingType =\r\n this.bindingType === 'uniform' && 'numElements' in binding\r\n ? `array<${binding.type.replace('array', '').replace('<', '').replace('>', '')}, ${binding.numElements}>`\r\n : binding.type\r\n\r\n structs[kebabCaseLabel][binding.name] = bindingType\r\n })\r\n\r\n const varType = getBindingWGSLVarType(this)\r\n this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]\r\n }\r\n\r\n if (uniqueBindings.length) {\r\n uniqueBindings.forEach((binding) => {\r\n structs[kebabCaseLabel][binding.name] =\r\n binding.count > 1 ? `array<${toKebabCase(binding.label)}>` : toKebabCase(binding.label)\r\n })\r\n }\r\n\r\n const additionalBindings = uniqueBindings.length\r\n ? uniqueBindings.map((binding) => binding.wgslStructFragment).join('\\n\\n') + '\\n\\n'\r\n : ''\r\n\r\n this.wgslStructFragment =\r\n additionalBindings +\r\n Object.keys(structs)\r\n .reverse()\r\n .map((struct) => {\r\n return `struct ${struct} {\\n\\t${Object.keys(structs[struct])\r\n .map((binding) => `${binding}: ${structs[struct][binding]}`)\r\n .join(',\\n\\t')}\\n};`\r\n })\r\n .join('\\n\\n')\r\n } else {\r\n this.wgslStructFragment = ''\r\n this.wgslGroupFragment = this.bufferElements.map((binding) => {\r\n const varType = getBindingWGSLVarType(this)\r\n return `${varType} ${binding.name}: ${binding.type};`\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Set a {@link BufferBinding#shouldUpdate | binding shouldUpdate} flag to `true` to update our {@link arrayBuffer} array during next render.\r\n * @param bindingName - the binding name/key to update\r\n */\r\n shouldUpdateBinding(bindingName = '') {\r\n if (this.inputs[bindingName]) {\r\n this.inputs[bindingName].shouldUpdate = true\r\n }\r\n }\r\n\r\n /**\r\n * Executed at the beginning of a Material render call.\r\n * If any of the {@link inputs} has changed, run its onBeforeUpdate callback then updates our {@link arrayBuffer} array.\r\n * Also sets the {@link shouldUpdate} property to true so the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} knows it will need to update the {@link GPUBuffer}.\r\n */\r\n update() {\r\n const inputs = Object.values(this.inputs)\r\n for (const binding of inputs) {\r\n const bufferElement = this.bufferElements.find((bufferEl) => bufferEl.key === binding.name)\r\n\r\n if (binding.shouldUpdate && bufferElement) {\r\n binding.onBeforeUpdate && binding.onBeforeUpdate()\r\n // we're going to directly update the arrayBuffer from the buffer element update method\r\n bufferElement.update(binding.value)\r\n\r\n this.shouldUpdate = true\r\n binding.shouldUpdate = false\r\n }\r\n }\r\n\r\n this.options.bindings.forEach((binding) => {\r\n binding.update()\r\n if (binding.shouldUpdate) {\r\n this.shouldUpdate = true\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to a specific {@link BufferElement} from a {@link Float32Array} holding the {@link BufferBinding#buffer | GPU buffer} data of this {@link BufferBinding}\r\n * @param parameters - parameters used to extract the data\r\n * @param parameters.result - {@link Float32Array} holding {@link GPUBuffer} data\r\n * @param parameters.bufferElementName - name of the {@link BufferElement} to use to extract the data\r\n * @returns - extracted data from the {@link Float32Array}\r\n */\r\n extractBufferElementDataFromBufferResult({\r\n result,\r\n bufferElementName,\r\n }: {\r\n result: Float32Array\r\n bufferElementName: BufferElement['name']\r\n }): Float32Array {\r\n const bufferElement = this.bufferElements.find((bufferElement) => bufferElement.name === bufferElementName)\r\n if (bufferElement) {\r\n return bufferElement.extractDataFromBufferResult(result)\r\n } else {\r\n return result\r\n }\r\n }\r\n}\r\n","import { BufferBinding, BufferBindingParams } from './BufferBinding'\r\nimport { Buffer } from '../buffers/Buffer'\r\n\r\n/**\r\n * Parameters used to create a {@link WritableBufferBinding}\r\n */\r\nexport interface WritableBufferBindingParams extends BufferBindingParams {\r\n /** Whether whe should automatically copy the {@link WritableBufferBinding#buffer | GPU buffer} content into our {@link WritableBufferBinding#resultBuffer | result GPU buffer} */\r\n shouldCopyResult?: boolean\r\n}\r\n\r\n/**\r\n * Used to create a {@link BufferBinding} that can hold read/write storage bindings along with a {@link WritableBufferBinding#resultBuffer | result GPU buffer} that can be used to get data back from the GPU.\r\n *\r\n * Note that it is automatically created by the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} when a {@link types/BindGroups.BindGroupInputs#storages | storages input} has its {@link BufferBindingParams#access | access} property set to `\"read_write\"`.\r\n */\r\nexport class WritableBufferBinding extends BufferBinding {\r\n /** Flag indicating whether whe should automatically copy the {@link buffer | GPU buffer} content into our {@link resultBuffer | result GPU buffer} */\r\n shouldCopyResult: boolean\r\n /** The result GPUBuffer */\r\n resultBuffer: Buffer\r\n /** Options used to create this {@link WritableBufferBinding} */\r\n options: WritableBufferBindingParams\r\n\r\n /**\r\n * WritableBufferBinding constructor\r\n * @param parameters - {@link WritableBufferBindingParams | parameters} used to create our {@link WritableBufferBinding}\r\n */\r\n constructor({\r\n label = 'Work',\r\n name = 'work',\r\n bindingType,\r\n visibility,\r\n useStruct = true,\r\n access = 'read_write',\r\n usage = [],\r\n struct = {},\r\n shouldCopyResult = false,\r\n }: WritableBufferBindingParams) {\r\n bindingType = 'storage'\r\n visibility = ['compute']\r\n\r\n super({ label, name, bindingType, visibility, useStruct, access, usage, struct })\r\n\r\n this.options = {\r\n ...this.options,\r\n shouldCopyResult,\r\n }\r\n\r\n this.shouldCopyResult = shouldCopyResult\r\n this.cacheKey += `${shouldCopyResult},`\r\n\r\n // can be used as our buffer copy destination\r\n this.resultBuffer = new Buffer()\r\n }\r\n}\r\n","import { isRenderer, Renderer } from '../renderers/utils'\r\nimport { generateUUID, toKebabCase } from '../../utils/utils'\r\nimport { WritableBufferBinding, WritableBufferBindingParams } from '../bindings/WritableBufferBinding'\r\nimport { BufferBinding } from '../bindings/BufferBinding'\r\nimport {\r\n AllowedBindGroups,\r\n BindGroupBindingElement,\r\n BindGroupBufferBindingElement,\r\n BindGroupEntries,\r\n BindGroupParams,\r\n ReadOnlyInputBindings,\r\n} from '../../types/BindGroups'\r\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\r\nimport { TextureBindGroupParams } from './TextureBindGroup'\r\nimport { BufferBindingType } from '../bindings/Binding'\r\nimport { BufferUsageKeys } from '../buffers/utils'\r\n\r\n/**\r\n * Used to handle all inputs data sent to the GPU.
\r\n * In WebGPU, data (buffers, textures or samplers, called bindings) are organised by bind groups, containing those bindings.\r\n *\r\n * ## Bindings\r\n *\r\n * A {@link BindGroup} is responsible for creating each {@link BufferBinding} {@link GPUBuffer} and then the {@link GPUBindGroup} and {@link GPUBindGroupLayout} that are used to create {@link GPUComputePipeline} or {@link GPURenderPipeline}.
\r\n * Those are generally automatically created by the {@link core/materials/Material.Material | Material} using this {@link BindGroup}. If you need to manually create them, you will have to call its {@link BindGroup#createBindGroup | `createBindGroup()` method}\r\n *\r\n * ### Samplers and textures\r\n *\r\n * A {@link BindGroup} is best suited to handle {@link GPUBuffer} only bindings. If you need to handle {@link GPUSampler}, a {@link GPUTexture} or a {@link GPUExternalTexture}, you should use a {@link core/bindGroups/TextureBindGroup.TextureBindGroup | TextureBindGroup} instead.\r\n *\r\n * ### Updating a GPUBindGroup or GPUBindGroupLayout\r\n *\r\n * Each time one of the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroup#resource | binding resource} changes, its {@link BindGroup#bindGroup | bindGroup} will be recreated (usually, when a {@link GPUTexture} is uploaded).
\r\n * Each time one of the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroupLayout#resource_layout_objects | binding resource layout} changes, its {@link BindGroup#bindGroupLayout | bindGroupLayout} and {@link BindGroup#bindGroup | bindGroup} will be recreated, and the {@link GPUComputePipeline} or {@link GPURenderPipeline} will be recreated as well.\r\n *\r\n * @example\r\n * ```javascript\r\n * // set our main GPUCurtains instance\r\n * const gpuCurtains = new GPUCurtains({\r\n * container: '#canvas' // selector of our WebGPU canvas container\r\n * })\r\n *\r\n * // set the GPU device\r\n * // note this is asynchronous\r\n * await gpuCurtains.setDevice()\r\n *\r\n * const bindGroup = new BindGroup(gpuCurtains, {\r\n * label: 'My bind group',\r\n * uniforms: {\r\n * params: {\r\n * visibility: ['fragment'],\r\n * struct: {\r\n * opacity: {\r\n * value: 'f32',\r\n * value: 1,\r\n * },\r\n * mousePosition: {\r\n * value: 'vec2f',\r\n * value: new Vec2(),\r\n * },\r\n * },\r\n * },\r\n * },\r\n * })\r\n *\r\n * // create the GPU buffer, bindGroupLayout and bindGroup\r\n * bindGroup.createBindGroup()\r\n * ```\r\n */\r\nexport class BindGroup {\r\n /** The type of the {@link BindGroup} */\r\n type: string\r\n /** The universal unique id of the {@link BindGroup} */\r\n uuid: string\r\n /** The {@link Renderer} used */\r\n renderer: Renderer\r\n /** Options used to create this {@link BindGroup} */\r\n options: TextureBindGroupParams\r\n /** Index of this {@link BindGroup}, used to link struct in the shaders */\r\n index: number\r\n\r\n /** List of {@link BindGroupBindingElement | bindings} (buffers, texture, etc.) handled by this {@link BindGroup} */\r\n bindings: BindGroupBindingElement[]\r\n\r\n /** Our {@link BindGroup} {@link BindGroupEntries | entries} objects */\r\n entries: BindGroupEntries\r\n\r\n /** Our {@link BindGroup}{@link GPUBindGroupLayout} */\r\n bindGroupLayout: null | GPUBindGroupLayout\r\n /** Our {@link BindGroup} {@link GPUBindGroup} */\r\n bindGroup: null | GPUBindGroup\r\n\r\n /** A cache key allowing to get / set {@link GPUBindGroupLayout} from the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#bindGroupLayouts | device manager map cache}. */\r\n layoutCacheKey: string\r\n /** A cache key allowing the {@link core/pipelines/PipelineManager.PipelineManager | PipelineManager} to compare {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry | RenderPipelineEntry} bind groups content. */\r\n pipelineCacheKey: string\r\n\r\n /** Flag indicating whether we need to flush and recreate the pipeline using this {@link BindGroup} s*/\r\n needsPipelineFlush: boolean\r\n\r\n /** A Set to store this {@link BindGroup} consumers ({@link core/materials/Material.Material#uuid | Material uuid}) */\r\n consumers: Set\r\n\r\n /**\r\n * BindGroup constructor\r\n * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object\r\n * @param parameters - {@link BindGroupParams | parameters} used to create our {@link BindGroup}\r\n */\r\n constructor(\r\n renderer: Renderer | GPUCurtains,\r\n { label = 'BindGroup', index = 0, bindings = [], uniforms, storages }: BindGroupParams = {}\r\n ) {\r\n this.type = 'BindGroup'\r\n\r\n renderer = isRenderer(renderer, this.type)\r\n\r\n this.renderer = renderer\r\n this.options = {\r\n label,\r\n index,\r\n bindings,\r\n ...(uniforms && { uniforms }),\r\n ...(storages && { storages }),\r\n }\r\n\r\n this.index = index\r\n this.uuid = generateUUID()\r\n\r\n this.bindings = []\r\n bindings.length && this.addBindings(bindings)\r\n if (this.options.uniforms || this.options.storages) this.setInputBindings()\r\n\r\n this.layoutCacheKey = ''\r\n this.pipelineCacheKey = ''\r\n this.resetEntries()\r\n\r\n this.bindGroupLayout = null\r\n this.bindGroup = null\r\n\r\n // if we ever update our bind group layout\r\n // we will have to recreate the whole pipeline again\r\n this.needsPipelineFlush = false\r\n\r\n this.consumers = new Set()\r\n\r\n // add the bind group to the buffers consumers\r\n for (const binding of this.bufferBindings) {\r\n if ('buffer' in binding) {\r\n binding.buffer.consumers.add(this.uuid)\r\n }\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer.consumers.add(this.uuid)\r\n }\r\n }\r\n\r\n this.renderer.addBindGroup(this)\r\n }\r\n\r\n /**\r\n * Sets our {@link BindGroup#index | bind group index}\r\n * @param index - {@link BindGroup#index | bind group index} to set\r\n */\r\n setIndex(index: number) {\r\n this.index = index\r\n }\r\n\r\n /**\r\n * Adds an array of already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array\r\n * @param bindings - {@link bindings} to add\r\n */\r\n addBindings(bindings: BindGroupBindingElement[] = []) {\r\n bindings.forEach((binding) => {\r\n if ('buffer' in binding) {\r\n this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding)\r\n binding.buffer.consumers.add(this.uuid)\r\n }\r\n })\r\n\r\n this.bindings = [...this.bindings, ...bindings]\r\n }\r\n\r\n /**\r\n * Adds an already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array\r\n * @param binding - binding to add\r\n */\r\n addBinding(binding: BindGroupBindingElement) {\r\n this.bindings.push(binding)\r\n }\r\n\r\n /**\r\n * Destroy a {@link BufferBinding} buffers.\r\n * @param binding - {@link BufferBinding} from which to destroy the buffers.\r\n */\r\n destroyBufferBinding(binding: BindGroupBufferBindingElement) {\r\n if ('buffer' in binding) {\r\n this.renderer.removeBuffer(binding.buffer)\r\n\r\n binding.buffer.consumers.delete(this.uuid)\r\n if (!binding.buffer.consumers.size) {\r\n binding.buffer.destroy()\r\n }\r\n }\r\n\r\n if ('resultBuffer' in binding) {\r\n this.renderer.removeBuffer(binding.resultBuffer)\r\n\r\n binding.resultBuffer.consumers.delete(this.uuid)\r\n if (!binding.resultBuffer.consumers.size) {\r\n binding.resultBuffer.destroy()\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Creates Bindings based on a list of inputs\r\n * @param bindingType - {@link core/bindings/Binding.Binding#bindingType | binding type}\r\n * @param inputs - {@link ReadOnlyInputBindings | inputs (uniform or storage)} that will be used to create the binding\r\n * @returns - a {@link bindings} array\r\n */\r\n createInputBindings(\r\n bindingType: BufferBindingType = 'uniform',\r\n inputs: ReadOnlyInputBindings = {}\r\n ): BindGroupBindingElement[] {\r\n let bindings = [\r\n ...Object.keys(inputs).map((inputKey) => {\r\n const binding = inputs[inputKey] as WritableBufferBindingParams\r\n\r\n // bail if no struct\r\n if (!binding.struct) return\r\n\r\n const bindingParams: WritableBufferBindingParams = {\r\n label: toKebabCase(binding.label || inputKey),\r\n name: inputKey,\r\n bindingType,\r\n visibility: binding.access === 'read_write' ? ['compute'] : binding.visibility,\r\n useStruct: true, // by default\r\n access: binding.access ?? 'read', // read by default\r\n ...(binding.usage && { usage: binding.usage }),\r\n struct: binding.struct,\r\n ...(binding.shouldCopyResult !== undefined && { shouldCopyResult: binding.shouldCopyResult }),\r\n }\r\n\r\n if (binding.useStruct !== false) {\r\n let key = `${bindingType},${\r\n binding.visibility === undefined ? 'all' : binding.access === 'read_write' ? 'compute' : binding.visibility\r\n },true,${binding.access ?? 'read'},`\r\n\r\n Object.keys(binding.struct).forEach((bindingKey) => {\r\n key += `${bindingKey},${binding.struct[bindingKey].type},`\r\n })\r\n\r\n if (binding.shouldCopyResult !== undefined) {\r\n key += `${binding.shouldCopyResult},`\r\n }\r\n\r\n const cachedBinding = this.renderer.deviceManager.bufferBindings.get(key)\r\n\r\n if (cachedBinding) {\r\n return cachedBinding.clone(bindingParams)\r\n }\r\n }\r\n\r\n const BufferBindingConstructor = bindingParams.access === 'read_write' ? WritableBufferBinding : BufferBinding\r\n\r\n return binding.useStruct !== false\r\n ? new BufferBindingConstructor(bindingParams)\r\n : Object.keys(binding.struct).map((bindingKey) => {\r\n bindingParams.label = toKebabCase(binding.label ? binding.label + bindingKey : inputKey + bindingKey)\r\n bindingParams.name = inputKey + bindingKey\r\n bindingParams.useStruct = false\r\n bindingParams.struct = { [bindingKey]: binding.struct[bindingKey] }\r\n\r\n return new BufferBindingConstructor(bindingParams)\r\n })\r\n }),\r\n ].flat()\r\n\r\n // filter to leave only valid bindings\r\n bindings = bindings.filter(Boolean)\r\n\r\n bindings.forEach((binding) => {\r\n this.renderer.deviceManager.bufferBindings.set(binding.cacheKey, binding)\r\n })\r\n\r\n return bindings\r\n }\r\n\r\n /**\r\n * Create and adds {@link bindings} based on inputs provided upon creation\r\n */\r\n setInputBindings() {\r\n this.addBindings([\r\n ...this.createInputBindings('uniform', this.options.uniforms),\r\n ...this.createInputBindings('storage', this.options.storages),\r\n ])\r\n }\r\n\r\n /**\r\n * Get whether the GPU bind group is ready to be created\r\n * It can be created if it has {@link bindings} and has not been created yet\r\n * @readonly\r\n */\r\n get shouldCreateBindGroup(): boolean {\r\n return !this.bindGroup && !!this.bindings.length\r\n }\r\n\r\n /**\r\n * Reset our {@link BindGroup} {@link entries}\r\n */\r\n resetEntries() {\r\n this.entries = {\r\n bindGroupLayout: [],\r\n bindGroup: [],\r\n }\r\n }\r\n\r\n /**\r\n * Create the GPU buffers, {@link bindings}, {@link entries}, {@link bindGroupLayout} and {@link bindGroup}\r\n */\r\n createBindGroup() {\r\n this.fillEntries()\r\n this.setBindGroupLayout()\r\n this.setBindGroup()\r\n }\r\n\r\n /**\r\n * Reset the {@link BindGroup#entries.bindGroup | bindGroup entries}, recreates them and then recreate the {@link BindGroup#bindGroup | GPU bind group}\r\n */\r\n resetBindGroup() {\r\n this.entries.bindGroup = []\r\n this.pipelineCacheKey = ''\r\n\r\n for (const binding of this.bindings) {\r\n this.addBindGroupEntry(binding)\r\n }\r\n\r\n this.setBindGroup()\r\n }\r\n\r\n /**\r\n * Add a {@link BindGroup#entries.bindGroup | bindGroup entry}\r\n * @param binding - {@link BindGroupBindingElement | binding} to add\r\n */\r\n addBindGroupEntry(binding: BindGroupBindingElement) {\r\n this.entries.bindGroup.push({\r\n binding: this.entries.bindGroup.length,\r\n resource: binding.resource,\r\n })\r\n\r\n this.pipelineCacheKey += binding.cacheKey\r\n }\r\n\r\n /**\r\n * Reset the {@link BindGroup#entries.bindGroupLayout | bindGroupLayout entries}, recreates them and then recreate the {@link BindGroup#bindGroupLayout | GPU bind group layout}\r\n */\r\n resetBindGroupLayout() {\r\n this.entries.bindGroupLayout = []\r\n this.layoutCacheKey = ''\r\n\r\n for (const binding of this.bindings) {\r\n this.addBindGroupLayoutEntry(binding)\r\n }\r\n\r\n this.setBindGroupLayout()\r\n }\r\n\r\n /**\r\n * Add a {@link BindGroup#entries.bindGroupLayout | bindGroupLayout entry}\r\n * @param binding - {@link BindGroupBindingElement | binding} to add\r\n */\r\n addBindGroupLayoutEntry(binding: BindGroupBindingElement) {\r\n this.entries.bindGroupLayout.push({\r\n binding: this.entries.bindGroupLayout.length,\r\n ...binding.resourceLayout,\r\n visibility: binding.visibility,\r\n })\r\n\r\n this.layoutCacheKey += binding.resourceLayoutCacheKey\r\n }\r\n\r\n /**\r\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration\r\n */\r\n loseContext() {\r\n this.resetEntries()\r\n\r\n for (const binding of this.bufferBindings) {\r\n binding.buffer.reset()\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer.reset()\r\n }\r\n }\r\n\r\n this.bindGroup = null\r\n this.bindGroupLayout = null\r\n this.needsPipelineFlush = true\r\n }\r\n\r\n /**\r\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored to update our bindings.\r\n */\r\n restoreContext() {\r\n if (this.shouldCreateBindGroup) {\r\n this.createBindGroup()\r\n }\r\n\r\n // finally re-write all our buffers\r\n for (const bufferBinding of this.bufferBindings) {\r\n bufferBinding.shouldUpdate = true\r\n }\r\n }\r\n\r\n /**\r\n * Get all {@link BindGroup#bindings | bind group bindings} that handle a {@link GPUBuffer}\r\n */\r\n get bufferBindings(): BindGroupBufferBindingElement[] {\r\n return this.bindings.filter(\r\n (binding) => binding instanceof BufferBinding || binding instanceof WritableBufferBinding\r\n ) as BindGroupBufferBindingElement[]\r\n }\r\n\r\n /**\r\n * Creates binding GPUBuffer with correct params\r\n * @param binding - the binding element\r\n */\r\n createBindingBuffer(binding: BindGroupBufferBindingElement) {\r\n // [Kangz](https://github.com/Kangz) said:\r\n // \"In general though COPY_SRC/DST is free (at least in Dawn / Chrome because we add it all the time for our own purpose).\"\r\n binding.buffer.createBuffer(this.renderer, {\r\n label: this.options.label + ': ' + binding.bindingType + ' buffer from: ' + binding.label,\r\n usage: [...(['copySrc', 'copyDst', binding.bindingType] as BufferUsageKeys[]), ...binding.options.usage],\r\n })\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer.createBuffer(this.renderer, {\r\n label: this.options.label + ': Result buffer from: ' + binding.label,\r\n size: binding.arrayBuffer.byteLength,\r\n usage: ['copyDst', 'mapRead'],\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Fill in our entries bindGroupLayout and bindGroup arrays with the correct binding resources.\r\n * For buffer struct, create a GPUBuffer first if needed\r\n */\r\n fillEntries() {\r\n for (const binding of this.bindings) {\r\n // if no visibility specified, just set it to the maximum default capabilities\r\n if (!binding.visibility) {\r\n binding.visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE\r\n }\r\n\r\n // if it's a buffer binding, create the GPUBuffer\r\n if ('buffer' in binding) {\r\n if (!binding.buffer.GPUBuffer) {\r\n this.createBindingBuffer(binding)\r\n }\r\n }\r\n\r\n // now that everything is ready, fill our entries\r\n this.addBindGroupLayoutEntry(binding)\r\n this.addBindGroupEntry(binding)\r\n }\r\n }\r\n\r\n /**\r\n * Get a bind group binding by name/key\r\n * @param bindingName - the binding name or key\r\n * @returns - the found binding, or null if not found\r\n */\r\n getBindingByName(bindingName = ''): BindGroupBindingElement | null {\r\n return this.bindings.find((binding) => binding.name === bindingName)\r\n }\r\n\r\n /**\r\n * Create a GPUBindGroupLayout and set our {@link bindGroupLayout}\r\n */\r\n setBindGroupLayout() {\r\n const bindGroupLayout = this.renderer.deviceManager.bindGroupLayouts.get(this.layoutCacheKey)\r\n\r\n if (bindGroupLayout) {\r\n this.bindGroupLayout = bindGroupLayout\r\n } else {\r\n this.bindGroupLayout = this.renderer.createBindGroupLayout({\r\n label: this.options.label + ' layout',\r\n entries: this.entries.bindGroupLayout,\r\n })\r\n\r\n this.renderer.deviceManager.bindGroupLayouts.set(this.layoutCacheKey, this.bindGroupLayout)\r\n }\r\n }\r\n\r\n /**\r\n * Create a GPUBindGroup and set our {@link bindGroup}\r\n */\r\n setBindGroup() {\r\n this.bindGroup = this.renderer.createBindGroup({\r\n label: this.options.label,\r\n layout: this.bindGroupLayout,\r\n entries: this.entries.bindGroup,\r\n })\r\n }\r\n\r\n /**\r\n * Check whether we should update (write) our {@link GPUBuffer} or not.\r\n */\r\n updateBufferBindings() {\r\n this.bindings.forEach((binding, index) => {\r\n if ('buffer' in binding) {\r\n // update binding elements\r\n binding.update()\r\n\r\n // now write to the GPUBuffer if needed\r\n if (binding.shouldUpdate && binding.buffer.GPUBuffer) {\r\n // bufferOffset is always equals to 0 in our case\r\n if (!binding.useStruct && binding.bufferElements.length > 1) {\r\n // we're in a non struct buffer binding with multiple entries\r\n // that should not happen but that way we're covered\r\n this.renderer.queueWriteBuffer(binding.buffer.GPUBuffer, 0, binding.bufferElements[index].view)\r\n } else {\r\n this.renderer.queueWriteBuffer(binding.buffer.GPUBuffer, 0, binding.arrayBuffer)\r\n }\r\n\r\n // reset update flag\r\n binding.shouldUpdate = false\r\n }\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Update the {@link BindGroup}, which means update its {@link BindGroup#bufferBindings | buffer bindings} and {@link BindGroup#resetBindGroup | reset it} if needed.\r\n * Called at each render from the parentMesh {@link core/materials/Material.Material | material}\r\n */\r\n update() {\r\n this.updateBufferBindings()\r\n\r\n const needBindGroupReset = this.bindings.some((binding) => binding.shouldResetBindGroup)\r\n const needBindGroupLayoutReset = this.bindings.some((binding) => binding.shouldResetBindGroupLayout)\r\n\r\n // since other bind groups might be using that binding\r\n // wait for the end of the render loop to reset the bindings flags\r\n if (needBindGroupReset || needBindGroupLayoutReset) {\r\n this.renderer.onAfterCommandEncoderSubmission.add(\r\n () => {\r\n for (const binding of this.bindings) {\r\n binding.shouldResetBindGroup = false\r\n binding.shouldResetBindGroupLayout = false\r\n }\r\n },\r\n { once: true }\r\n )\r\n }\r\n\r\n if (needBindGroupLayoutReset) {\r\n this.resetBindGroupLayout()\r\n // bind group layout has changed, we have to recreate the pipeline\r\n this.needsPipelineFlush = true\r\n }\r\n\r\n if (needBindGroupReset) {\r\n this.resetBindGroup()\r\n }\r\n }\r\n\r\n /**\r\n * Clones a {@link BindGroup} from a list of {@link bindings}\r\n * Useful to create a new bind group with already created buffers, but swapped\r\n * @param parameters - parameters to use for cloning\r\n * @param parameters.bindings - our input {@link bindings}\r\n * @param [parameters.keepLayout=false] - whether we should keep original {@link bindGroupLayout} or not\r\n * @returns - the cloned {@link BindGroup}\r\n */\r\n clone({\r\n bindings = [],\r\n keepLayout = false,\r\n }: {\r\n bindings?: BindGroupBindingElement[]\r\n keepLayout?: boolean\r\n } = {}): AllowedBindGroups {\r\n const params = { ...this.options }\r\n params.label += ' (copy)'\r\n\r\n const bindGroupCopy = new (this.constructor as typeof BindGroup)(this.renderer, {\r\n label: params.label,\r\n })\r\n\r\n bindGroupCopy.setIndex(this.index)\r\n bindGroupCopy.options = params\r\n\r\n const bindingsRef = bindings.length ? bindings : this.bindings\r\n\r\n for (const binding of bindingsRef) {\r\n bindGroupCopy.addBinding(binding)\r\n\r\n // if it's a buffer binding without a GPUBuffer, create it now\r\n if ('buffer' in binding) {\r\n if (!binding.buffer.GPUBuffer) {\r\n this.createBindingBuffer(binding)\r\n }\r\n\r\n binding.buffer.consumers.add(bindGroupCopy.uuid)\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer.consumers.add(bindGroupCopy.uuid)\r\n }\r\n }\r\n\r\n // if we should create a new bind group layout, fill it\r\n if (!keepLayout) {\r\n bindGroupCopy.addBindGroupLayoutEntry(binding)\r\n }\r\n\r\n bindGroupCopy.addBindGroupEntry(binding)\r\n }\r\n\r\n // if we should copy the given bind group layout\r\n if (keepLayout) {\r\n bindGroupCopy.entries.bindGroupLayout = [...this.entries.bindGroupLayout]\r\n bindGroupCopy.layoutCacheKey = this.layoutCacheKey\r\n }\r\n\r\n bindGroupCopy.setBindGroupLayout()\r\n bindGroupCopy.setBindGroup()\r\n\r\n return bindGroupCopy\r\n }\r\n\r\n /**\r\n * Destroy our {@link BindGroup}\r\n * Most important is to destroy the GPUBuffers to free the memory\r\n */\r\n destroy() {\r\n this.renderer.removeBindGroup(this)\r\n\r\n for (const binding of this.bufferBindings) {\r\n this.destroyBufferBinding(binding)\r\n }\r\n\r\n this.bindings = []\r\n this.bindGroupLayout = null\r\n this.bindGroup = null\r\n this.resetEntries()\r\n }\r\n}\r\n","import { Binding, BindingMemoryAccessType, BindingParams, DOMTextureBindingType } from './Binding'\r\nimport {\r\n getBindGroupLayoutTextureBindingCacheKey,\r\n getBindGroupLayoutTextureBindingType,\r\n getTextureBindingWGSLVarType,\r\n} from './utils'\r\n\r\n/** Defines a {@link TextureBinding} {@link TextureBinding#resource | resource} */\r\nexport type TextureBindingResource = GPUTexture | GPUExternalTexture | null\r\n\r\n/**\r\n * An object defining all possible {@link TextureBinding} class instancing parameters\r\n */\r\nexport interface TextureBindingParams extends BindingParams {\r\n /** The binding type of the {@link TextureBinding} */\r\n bindingType?: DOMTextureBindingType\r\n /** {@link TextureBinding} {@link TextureBinding#resource | resource} */\r\n texture: TextureBindingResource\r\n /** The {@link GPUTexture | texture} format to use */\r\n format?: GPUTextureFormat\r\n /** The storage {@link GPUTexture | texture} binding memory access types (read only, write only or read/write) */\r\n access?: BindingMemoryAccessType\r\n /** The {@link GPUTexture | texture} view dimension to use */\r\n viewDimension?: GPUTextureViewDimension\r\n /** Whethe the {@link GPUTexture | texture} is a multisampled texture. Mainly used internally by depth textures if needed. */\r\n multisampled?: boolean\r\n}\r\n\r\n/**\r\n * Used to handle {@link GPUTexture} and {@link GPUExternalTexture} bindings.\r\n *\r\n * Provide both {@link TextureBinding#resourceLayout | resourceLayout} and {@link TextureBinding#resource | resource} to the {@link GPUBindGroupLayout} and {@link GPUBindGroup}.
\r\n * Also create the appropriate WGSL code snippet to add to the shaders.\r\n */\r\nexport class TextureBinding extends Binding {\r\n /** The binding type of the {@link TextureBinding} */\r\n bindingType: DOMTextureBindingType\r\n /** Our {@link TextureBinding} resource, i.e. a {@link GPUTexture} or {@link GPUExternalTexture} */\r\n texture: TextureBindingResource\r\n /** An array of strings to append to our shaders code declaring all the WGSL variables representing this {@link TextureBinding} */\r\n wgslGroupFragment: string[]\r\n /** Options used to create this {@link TextureBinding} */\r\n options: TextureBindingParams\r\n\r\n /**\r\n * TextureBinding constructor\r\n * @param parameters - {@link TextureBindingParams | parameters} used to create our {@link TextureBinding}\r\n */\r\n constructor({\r\n label = 'Texture',\r\n name = 'texture',\r\n bindingType,\r\n visibility,\r\n texture,\r\n format = 'rgba8unorm',\r\n access = 'write',\r\n viewDimension = '2d',\r\n multisampled = false,\r\n }: TextureBindingParams) {\r\n bindingType = bindingType ?? 'texture'\r\n\r\n if (bindingType === 'storage') {\r\n visibility = ['compute']\r\n }\r\n\r\n super({ label, name, bindingType, visibility })\r\n\r\n this.options = {\r\n ...this.options,\r\n texture,\r\n format,\r\n access,\r\n viewDimension,\r\n multisampled,\r\n }\r\n\r\n this.cacheKey += `${format},${access},${viewDimension},${multisampled},`\r\n\r\n this.resource = texture // should be a texture or an external texture\r\n\r\n this.setWGSLFragment()\r\n }\r\n\r\n /**\r\n * Get bind group layout entry resource, either for {@link GPUBindGroupLayoutEntry#texture | texture} or {@link GPUBindGroupLayoutEntry#externalTexture | external texture}\r\n * @readonly\r\n */\r\n get resourceLayout():\r\n | GPUTextureBindingLayout\r\n | GPUExternalTextureBindingLayout\r\n | GPUStorageTextureBindingLayout\r\n | null {\r\n return getBindGroupLayoutTextureBindingType(this)\r\n }\r\n\r\n /**\r\n * Get the resource cache key\r\n * @readonly\r\n */\r\n get resourceLayoutCacheKey(): string {\r\n return getBindGroupLayoutTextureBindingCacheKey(this)\r\n }\r\n\r\n /**\r\n * Get the {@link GPUBindGroupEntry#resource | bind group resource}\r\n */\r\n get resource(): GPUExternalTexture | GPUTextureView | null {\r\n return this.texture instanceof GPUTexture\r\n ? this.texture.createView({ label: this.options.label + ' view', dimension: this.options.viewDimension })\r\n : this.texture instanceof GPUExternalTexture\r\n ? this.texture\r\n : null\r\n }\r\n\r\n /**\r\n * Set the {@link GPUBindGroupEntry#resource | bind group resource}\r\n * @param value - new bind group resource\r\n */\r\n set resource(value: TextureBindingResource) {\r\n // resource changed, update bind group!\r\n if (value || this.texture) this.shouldResetBindGroup = true\r\n this.texture = value\r\n }\r\n\r\n /**\r\n * Set or update our {@link Binding#bindingType | bindingType} and our WGSL code snippet\r\n * @param bindingType - the new {@link Binding#bindingType | binding type}\r\n */\r\n setBindingType(bindingType: DOMTextureBindingType) {\r\n if (bindingType !== this.bindingType) {\r\n // binding type has changed!\r\n if (bindingType) this.shouldResetBindGroupLayout = true\r\n\r\n this.bindingType = bindingType\r\n this.cacheKey = `${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`\r\n this.setWGSLFragment()\r\n }\r\n }\r\n\r\n /**\r\n * Set or update our texture {@link TextureBindingParams#format | format}. Note that if the texture is a `storage` {@link bindingType} and the `format` value is different from the previous one, the associated {@link core/bindGroups/BindGroup.BindGroup#bindGroupLayout | GPU bind group layout} will be recreated.\r\n * @param format - new texture {@link TextureBindingParams#format | format} value to use\r\n */\r\n setFormat(format: GPUTextureFormat) {\r\n const isNewFormat = format !== this.options.format\r\n this.options.format = format\r\n\r\n if (isNewFormat && this.bindingType === 'storage') {\r\n this.setWGSLFragment()\r\n this.shouldResetBindGroupLayout = true\r\n this.cacheKey = `${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`\r\n }\r\n }\r\n\r\n /**\r\n * Set or update our texture {@link TextureBindingParams#multisampled | multisampled}. Note that if the texture is not a `storage` {@link bindingType} and the `multisampled` value is different from the previous one, the associated {@link core/bindGroups/BindGroup.BindGroup#bindGroupLayout | GPU bind group layout} will be recreated.\r\n * @param multisampled - new texture {@link TextureBindingParams#multisampled | multisampled} value to use\r\n */\r\n setMultisampled(multisampled: boolean) {\r\n const isNewMultisampled = multisampled !== this.options.multisampled\r\n this.options.multisampled = multisampled\r\n\r\n if (isNewMultisampled && this.bindingType !== 'storage') {\r\n this.setWGSLFragment()\r\n this.shouldResetBindGroupLayout = true\r\n this.cacheKey = `${this.bindingType},${this.visibility},${this.options.format},${this.options.access},${this.options.viewDimension},${this.options.multisampled},`\r\n }\r\n }\r\n\r\n /**\r\n * Set the correct WGSL code snippet.\r\n */\r\n setWGSLFragment() {\r\n this.wgslGroupFragment = [`${getTextureBindingWGSLVarType(this)}`]\r\n }\r\n}\r\n","import { Vec3 } from './Vec3'\r\nimport { Quat } from './Quat'\r\n\r\nconst xAxis = new Vec3()\r\nconst yAxis = new Vec3()\r\nconst zAxis = new Vec3()\r\n\r\n/** Defines the base parameters to create an orthographic projection {@link Mat4} */\r\nexport interface OrthographicProjectionParams {\r\n /** Left side of the projection near clipping plane viewport. Default to `-5`. */\r\n left?: number\r\n /** Right side of the projection near clipping plane viewport. Default to `5`. */\r\n right?: number\r\n /** Bottom side of the projection near clipping plane viewport. Default to `-5`. */\r\n bottom?: number\r\n /** Top side of the projection near clipping plane viewport. Default to `5`. */\r\n top?: number\r\n /** Projection near plane. Default to `0.1`. */\r\n near?: number\r\n /** Projection far plane. Default to `50`. */\r\n far?: number\r\n}\r\n\r\n/** Defines the base parameters to create a perspective projection {@link Mat4} */\r\nexport interface PerspectiveProjectionParams {\r\n /** Perspective field of view (in radians). Default to `90`. */\r\n fov?: number\r\n /** Perspective aspect ratio (width / height). Default to `1`. */\r\n aspect?: number\r\n /** Projection near plane. Default to `0.1`. */\r\n near?: number\r\n /** Projection far plane. Default to `150`. */\r\n far?: number\r\n}\r\n\r\n/**\r\n * Basic 4x4 matrix class used for matrix calculations.\r\n *\r\n * Note that like three.js, the constructor and {@link set} method take arguments in row-major order, while internally they are stored in the {@link elements} array in column-major order.\r\n *\r\n * @see https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js\r\n * @see http://glmatrix.net/docs/mat4.js.html\r\n */\r\nexport class Mat4 {\r\n /** The type of the {@link Mat4} */\r\n type: string\r\n /** Our matrix array */\r\n elements: Float32Array\r\n\r\n // prettier-ignore\r\n /**\r\n * Mat4 constructor\r\n * @param elements - initial array to use, default to identity matrix\r\n */\r\n constructor(elements: Float32Array = new Float32Array([\r\n 1, 0, 0, 0,\r\n 0, 1, 0, 0,\r\n 0, 0, 1, 0,\r\n 0, 0, 0, 1\r\n ])) {\r\n this.type = 'Mat4'\r\n this.elements = elements\r\n }\r\n\r\n /***\r\n * Sets the matrix from 16 numbers\r\n *\r\n * @param n11 - number\r\n * @param n12 - number\r\n * @param n13 - number\r\n * @param n14 - number\r\n * @param n21 - number\r\n * @param n22 - number\r\n * @param n23 - number\r\n * @param n24 - number\r\n * @param n31 - number\r\n * @param n32 - number\r\n * @param n33 - number\r\n * @param n34 - number\r\n * @param n41 - number\r\n * @param n42 - number\r\n * @param n43 - number\r\n * @param n44 - number\r\n *\r\n * @returns - this {@link Mat4} after being set\r\n */\r\n set(\r\n n11: number,\r\n n12: number,\r\n n13: number,\r\n n14: number,\r\n n21: number,\r\n n22: number,\r\n n23: number,\r\n n24: number,\r\n n31: number,\r\n n32: number,\r\n n33: number,\r\n n34: number,\r\n n41: number,\r\n n42: number,\r\n n43: number,\r\n n44: number\r\n ): Mat4 {\r\n const te = this.elements\r\n\r\n te[0] = n11\r\n te[1] = n12\r\n te[2] = n13\r\n te[3] = n14\r\n te[4] = n21\r\n te[5] = n22\r\n te[6] = n23\r\n te[7] = n24\r\n te[8] = n31\r\n te[9] = n32\r\n te[10] = n33\r\n te[11] = n34\r\n te[12] = n41\r\n te[13] = n42\r\n te[14] = n43\r\n te[15] = n44\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Sets the {@link Mat4} to an identity matrix\r\n * @returns - this {@link Mat4} after being set\r\n */\r\n identity(): Mat4 {\r\n // prettier-ignore\r\n this.set(\r\n 1, 0, 0, 0,\r\n 0, 1, 0, 0,\r\n 0, 0, 1, 0,\r\n 0, 0, 0, 1\r\n )\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Sets the {@link Mat4} values from an array\r\n * @param array - array to use\r\n * @returns - this {@link Mat4} after being set\r\n */\r\n // prettier-ignore\r\n setFromArray(array: Float32Array | number[] = new Float32Array([\r\n 1, 0, 0, 0,\r\n 0, 1, 0, 0,\r\n 0, 0, 1, 0,\r\n 0, 0, 0, 1\r\n ])): Mat4 {\r\n for (let i = 0; i < this.elements.length; i++) {\r\n this.elements[i] = array[i]\r\n }\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Copy another {@link Mat4}\r\n * @param matrix - matrix to copy\r\n * @returns - this {@link Mat4} after being set\r\n */\r\n copy(matrix: Mat4 = new Mat4()): Mat4 {\r\n const array = matrix.elements\r\n this.elements[0] = array[0]\r\n this.elements[1] = array[1]\r\n this.elements[2] = array[2]\r\n this.elements[3] = array[3]\r\n this.elements[4] = array[4]\r\n this.elements[5] = array[5]\r\n this.elements[6] = array[6]\r\n this.elements[7] = array[7]\r\n this.elements[8] = array[8]\r\n this.elements[9] = array[9]\r\n this.elements[10] = array[10]\r\n this.elements[11] = array[11]\r\n this.elements[12] = array[12]\r\n this.elements[13] = array[13]\r\n this.elements[14] = array[14]\r\n this.elements[15] = array[15]\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clone a {@link Mat4}\r\n * @returns - cloned {@link Mat4}\r\n */\r\n clone(): Mat4 {\r\n return new Mat4().copy(this)\r\n }\r\n\r\n /**\r\n * Multiply this {@link Mat4} with another {@link Mat4}\r\n * @param matrix - {@link Mat4} to multiply with\r\n * @returns - this {@link Mat4} after multiplication\r\n */\r\n multiply(matrix: Mat4 = new Mat4()): Mat4 {\r\n return this.multiplyMatrices(this, matrix)\r\n }\r\n\r\n /**\r\n * Multiply another {@link Mat4} with this {@link Mat4}\r\n * @param matrix - {@link Mat4} to multiply with\r\n * @returns - this {@link Mat4} after multiplication\r\n */\r\n premultiply(matrix: Mat4 = new Mat4()): Mat4 {\r\n return this.multiplyMatrices(matrix, this)\r\n }\r\n\r\n /**\r\n * Multiply two {@link Mat4}\r\n * @param a - first {@link Mat4}\r\n * @param b - second {@link Mat4}\r\n * @returns - {@link Mat4} resulting from the multiplication\r\n */\r\n multiplyMatrices(a: Mat4 = new Mat4(), b: Mat4 = new Mat4()): Mat4 {\r\n const ae = a.elements\r\n const be = b.elements\r\n const te = this.elements\r\n\r\n const a11 = ae[0],\r\n a12 = ae[4],\r\n a13 = ae[8],\r\n a14 = ae[12]\r\n const a21 = ae[1],\r\n a22 = ae[5],\r\n a23 = ae[9],\r\n a24 = ae[13]\r\n const a31 = ae[2],\r\n a32 = ae[6],\r\n a33 = ae[10],\r\n a34 = ae[14]\r\n const a41 = ae[3],\r\n a42 = ae[7],\r\n a43 = ae[11],\r\n a44 = ae[15]\r\n\r\n const b11 = be[0],\r\n b12 = be[4],\r\n b13 = be[8],\r\n b14 = be[12]\r\n const b21 = be[1],\r\n b22 = be[5],\r\n b23 = be[9],\r\n b24 = be[13]\r\n const b31 = be[2],\r\n b32 = be[6],\r\n b33 = be[10],\r\n b34 = be[14]\r\n const b41 = be[3],\r\n b42 = be[7],\r\n b43 = be[11],\r\n b44 = be[15]\r\n\r\n te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41\r\n te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42\r\n te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43\r\n te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44\r\n\r\n te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41\r\n te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42\r\n te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43\r\n te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44\r\n\r\n te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41\r\n te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42\r\n te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43\r\n te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44\r\n\r\n te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41\r\n te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42\r\n te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43\r\n te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * {@link premultiply} this {@link Mat4} by a translate matrix (i.e. translateMatrix = new Mat4().translate(vector))\r\n * @param vector - translation {@link Vec3 | vector} to use\r\n * @returns - this {@link Mat4} after the premultiply translate operation\r\n */\r\n premultiplyTranslate(vector: Vec3 = new Vec3()): Mat4 {\r\n // premultiply by a translateMatrix, ie translateMatrix = new Mat4().translate(vector)\r\n // where translateMatrix[0] = 1, translateMatrix[5] = 1, scaleMatrix[10] = 1, translateMatrix[15] = 1 from identity\r\n // and translateMatrix[12] = vector.x, translateMatrix[13] = vector.y, translateMatrix[14] = vector.z from translation\r\n // equivalent (but faster) to this.multiply(translateMatrix, this)\r\n\r\n // from identity matrix\r\n const a11 = 1\r\n const a22 = 1\r\n const a33 = 1\r\n const a44 = 1\r\n\r\n // from translation\r\n const a14 = vector.x\r\n const a24 = vector.y\r\n const a34 = vector.z\r\n\r\n const be = this.elements\r\n const te = this.elements\r\n\r\n const b11 = be[0],\r\n b12 = be[4],\r\n b13 = be[8],\r\n b14 = be[12]\r\n const b21 = be[1],\r\n b22 = be[5],\r\n b23 = be[9],\r\n b24 = be[13]\r\n const b31 = be[2],\r\n b32 = be[6],\r\n b33 = be[10],\r\n b34 = be[14]\r\n const b41 = be[3],\r\n b42 = be[7],\r\n b43 = be[11],\r\n b44 = be[15]\r\n\r\n te[0] = a11 * b11 + a14 * b41\r\n te[4] = a11 * b12 + a14 * b42\r\n te[8] = a11 * b13 + a14 * b43\r\n te[12] = a11 * b14 + a14 * b44\r\n\r\n te[1] = a22 * b21 + a24 * b41\r\n te[5] = a22 * b22 + a24 * b42\r\n te[9] = a22 * b23 + a24 * b43\r\n te[13] = a22 * b24 + a24 * b44\r\n\r\n te[2] = a33 * b31 + a34 * b41\r\n te[6] = a33 * b32 + a34 * b42\r\n te[10] = a33 * b33 + a34 * b43\r\n te[14] = a33 * b34 + a34 * b44\r\n\r\n te[3] = a44 * b41\r\n te[7] = a44 * b42\r\n te[11] = a44 * b43\r\n te[15] = a44 * b44\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * {@link premultiply} this {@link Mat4} by a scale matrix (i.e. translateMatrix = new Mat4().scale(vector))\r\n * @param vector - scale {@link Vec3 | vector} to use\r\n * @returns - this {@link Mat4} after the premultiply scale operation\r\n */\r\n premultiplyScale(vector: Vec3 = new Vec3()): Mat4 {\r\n // premultiply by a scaleMatrix, ie scaleMatrix = new Mat4().scale(vector)\r\n // where scaleMatrix[0] = vector.x, scaleMatrix[5] = vector.y, scaleMatrix[10] = vector.z, scaleMatrix[15] = 1\r\n // equivalent (but faster) to this.multiply(scaleMatrix, this)\r\n\r\n const be = this.elements\r\n const te = this.elements\r\n\r\n const a11 = vector.x\r\n const a22 = vector.y\r\n const a33 = vector.z\r\n const a44 = 1\r\n\r\n const b11 = be[0],\r\n b12 = be[4],\r\n b13 = be[8],\r\n b14 = be[12]\r\n const b21 = be[1],\r\n b22 = be[5],\r\n b23 = be[9],\r\n b24 = be[13]\r\n const b31 = be[2],\r\n b32 = be[6],\r\n b33 = be[10],\r\n b34 = be[14]\r\n const b41 = be[3],\r\n b42 = be[7],\r\n b43 = be[11],\r\n b44 = be[15]\r\n\r\n te[0] = a11 * b11\r\n te[4] = a11 * b12\r\n te[8] = a11 * b13\r\n te[12] = a11 * b14\r\n\r\n te[1] = a22 * b21\r\n te[5] = a22 * b22\r\n te[9] = a22 * b23\r\n te[13] = a22 * b24\r\n\r\n te[2] = a33 * b31\r\n te[6] = a33 * b32\r\n te[10] = a33 * b33\r\n te[14] = a33 * b34\r\n\r\n te[3] = a44 * b41\r\n te[7] = a44 * b42\r\n te[11] = a44 * b43\r\n te[15] = a44 * b44\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Get the {@link Mat4} inverse\r\n * @returns - the inverted {@link Mat4}\r\n */\r\n invert() {\r\n // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm\r\n const te = this.elements,\r\n n11 = te[0],\r\n n21 = te[1],\r\n n31 = te[2],\r\n n41 = te[3],\r\n n12 = te[4],\r\n n22 = te[5],\r\n n32 = te[6],\r\n n42 = te[7],\r\n n13 = te[8],\r\n n23 = te[9],\r\n n33 = te[10],\r\n n43 = te[11],\r\n n14 = te[12],\r\n n24 = te[13],\r\n n34 = te[14],\r\n n44 = te[15],\r\n t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,\r\n t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,\r\n t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,\r\n t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34\r\n\r\n const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14\r\n\r\n if (det === 0) return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)\r\n\r\n const detInv = 1 / det\r\n\r\n te[0] = t11 * detInv\r\n te[1] =\r\n (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) *\r\n detInv\r\n te[2] =\r\n (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) *\r\n detInv\r\n te[3] =\r\n (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) *\r\n detInv\r\n\r\n te[4] = t12 * detInv\r\n te[5] =\r\n (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) *\r\n detInv\r\n te[6] =\r\n (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) *\r\n detInv\r\n te[7] =\r\n (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) *\r\n detInv\r\n\r\n te[8] = t13 * detInv\r\n te[9] =\r\n (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) *\r\n detInv\r\n te[10] =\r\n (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) *\r\n detInv\r\n te[11] =\r\n (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) *\r\n detInv\r\n\r\n te[12] = t14 * detInv\r\n te[13] =\r\n (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) *\r\n detInv\r\n te[14] =\r\n (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) *\r\n detInv\r\n te[15] =\r\n (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) *\r\n detInv\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Clone and invert the {@link Mat4}\r\n * @returns - inverted cloned {@link Mat4}\r\n */\r\n getInverse(): Mat4 {\r\n return this.clone().invert()\r\n }\r\n\r\n /**\r\n * Transpose this {@link Mat4}\r\n * @returns - the transposed {@link Mat4}\r\n */\r\n transpose(): Mat4 {\r\n let t\r\n const te = this.elements\r\n\r\n t = te[1]\r\n te[1] = te[4]\r\n te[4] = t\r\n\r\n t = te[2]\r\n te[2] = te[8]\r\n te[8] = t\r\n\r\n t = te[3]\r\n te[3] = te[12]\r\n te[12] = t\r\n\r\n t = te[6]\r\n te[6] = te[9]\r\n te[9] = t\r\n\r\n t = te[7]\r\n te[7] = te[13]\r\n te[13] = t\r\n\r\n t = te[11]\r\n te[11] = te[14]\r\n te[14] = t\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Translate a {@link Mat4}\r\n * @param vector - translation {@link Vec3 | vector} to use\r\n * @returns - translated {@link Mat4}\r\n */\r\n translate(vector: Vec3 = new Vec3()): Mat4 {\r\n const a = this.elements\r\n\r\n a[12] = a[0] * vector.x + a[4] * vector.y + a[8] * vector.z + a[12]\r\n a[13] = a[1] * vector.x + a[5] * vector.y + a[9] * vector.z + a[13]\r\n a[14] = a[2] * vector.x + a[6] * vector.y + a[10] * vector.z + a[14]\r\n a[15] = a[3] * vector.x + a[7] * vector.y + a[11] * vector.z + a[15]\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Get the translation {@link Vec3} component of a {@link Mat4}\r\n * @param position - {@link Vec3} to set\r\n * @returns - translation {@link Vec3} component of this {@link Mat4}\r\n */\r\n getTranslation(position = new Vec3()): Vec3 {\r\n return position.set(this.elements[12], this.elements[13], this.elements[14])\r\n }\r\n\r\n /**\r\n * Scale a {@link Mat4}\r\n * @param vector - scale {@link Vec3 | vector} to use\r\n * @returns - scaled {@link Mat4}\r\n */\r\n scale(vector: Vec3 = new Vec3()): Mat4 {\r\n const a = this.elements\r\n\r\n a[0] *= vector.x\r\n a[1] *= vector.x\r\n a[2] *= vector.x\r\n a[3] *= vector.x\r\n a[4] *= vector.y\r\n a[5] *= vector.y\r\n a[6] *= vector.y\r\n a[7] *= vector.y\r\n a[8] *= vector.z\r\n a[9] *= vector.z\r\n a[10] *= vector.z\r\n a[11] *= vector.z\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Rotate a {@link Mat4} from a {@link Quat | quaternion}\r\n * @param quaternion - {@link Quat | quaternion} to use\r\n * @returns - rotated {@link Mat4}\r\n */\r\n rotateFromQuaternion(quaternion: Quat = new Quat()): Mat4 {\r\n const te = this.elements\r\n\r\n const x = quaternion.elements[0],\r\n y = quaternion.elements[1],\r\n z = quaternion.elements[2],\r\n w = quaternion.elements[3]\r\n\r\n const x2 = x + x,\r\n y2 = y + y,\r\n z2 = z + z\r\n const xx = x * x2,\r\n xy = x * y2,\r\n xz = x * z2\r\n const yy = y * y2,\r\n yz = y * z2,\r\n zz = z * z2\r\n const wx = w * x2,\r\n wy = w * y2,\r\n wz = w * z2\r\n\r\n te[0] = 1 - (yy + zz)\r\n te[4] = xy - wz\r\n te[8] = xz + wy\r\n\r\n te[1] = xy + wz\r\n te[5] = 1 - (xx + zz)\r\n te[9] = yz - wx\r\n\r\n te[2] = xz - wy\r\n te[6] = yz + wx\r\n te[10] = 1 - (xx + yy)\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Get the maximum scale of the {@link Mat4} on all axes\r\n * @returns - maximum scale of the {@link Mat4}\r\n */\r\n getMaxScaleOnAxis(): number {\r\n const te = this.elements\r\n\r\n const scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2]\r\n const scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6]\r\n const scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10]\r\n\r\n return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq))\r\n }\r\n\r\n /**\r\n * Creates a {@link Mat4} from a {@link Quat | quaternion} rotation, {@link Vec3 | vector} translation and {@link Vec3 | vector} scale\r\n * Equivalent for applying translation, rotation and scale matrices but much faster\r\n * Source code from: http://glmatrix.net/docs/mat4.js.html\r\n *\r\n * @param translation - translation {@link Vec3 | vector} to use\r\n * @param quaternion - {@link Quat | quaternion} to use\r\n * @param scale - translation {@link Vec3 | vector} to use\r\n * @returns - transformed {@link Mat4}\r\n */\r\n compose(translation: Vec3 = new Vec3(), quaternion: Quat = new Quat(), scale: Vec3 = new Vec3(1)): Mat4 {\r\n const matrix = this.elements\r\n\r\n // Quaternion math\r\n const x = quaternion.elements[0],\r\n y = quaternion.elements[1],\r\n z = quaternion.elements[2],\r\n w = quaternion.elements[3]\r\n\r\n const x2 = x + x\r\n const y2 = y + y\r\n const z2 = z + z\r\n const xx = x * x2\r\n const xy = x * y2\r\n const xz = x * z2\r\n const yy = y * y2\r\n const yz = y * z2\r\n const zz = z * z2\r\n const wx = w * x2\r\n const wy = w * y2\r\n const wz = w * z2\r\n const sx = scale.x\r\n const sy = scale.y\r\n const sz = scale.z\r\n\r\n matrix[0] = (1 - (yy + zz)) * sx\r\n matrix[1] = (xy + wz) * sx\r\n matrix[2] = (xz - wy) * sx\r\n matrix[3] = 0\r\n matrix[4] = (xy - wz) * sy\r\n matrix[5] = (1 - (xx + zz)) * sy\r\n matrix[6] = (yz + wx) * sy\r\n matrix[7] = 0\r\n matrix[8] = (xz + wy) * sz\r\n matrix[9] = (yz - wx) * sz\r\n matrix[10] = (1 - (xx + yy)) * sz\r\n matrix[11] = 0\r\n matrix[12] = translation.x\r\n matrix[13] = translation.y\r\n matrix[14] = translation.z\r\n matrix[15] = 1\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Creates a {@link Mat4} from a {@link Quat | quaternion} rotation, {@link Vec3 | vector} translation and {@link Vec3 | vector} scale, rotating and scaling around the given {@link Vec3 | origin vector}\r\n * Equivalent for applying translation, rotation and scale matrices but much faster\r\n * Source code from: http://glmatrix.net/docs/mat4.js.html\r\n *\r\n * @param translation - translation {@link Vec3 | vector} to use\r\n * @param quaternion - {@link Quat | quaternion} to use\r\n * @param scale - translation {@link Vec3 | vector} to use\r\n * @param origin - origin {@link Vec3 | vector} around which to scale and rotate\r\n * @returns - transformed {@link Mat4}\r\n */\r\n composeFromOrigin(\r\n translation: Vec3 = new Vec3(),\r\n quaternion: Quat = new Quat(),\r\n scale: Vec3 = new Vec3(1),\r\n origin: Vec3 = new Vec3()\r\n ): Mat4 {\r\n const matrix = this.elements\r\n\r\n // Quaternion math\r\n const x = quaternion.elements[0],\r\n y = quaternion.elements[1],\r\n z = quaternion.elements[2],\r\n w = quaternion.elements[3]\r\n\r\n const x2 = x + x\r\n const y2 = y + y\r\n const z2 = z + z\r\n\r\n const xx = x * x2\r\n const xy = x * y2\r\n const xz = x * z2\r\n const yy = y * y2\r\n const yz = y * z2\r\n const zz = z * z2\r\n\r\n const wx = w * x2\r\n const wy = w * y2\r\n const wz = w * z2\r\n\r\n const sx = scale.x\r\n const sy = scale.y\r\n const sz = scale.z\r\n\r\n const ox = origin.x\r\n const oy = origin.y\r\n const oz = origin.z\r\n\r\n const out0 = (1 - (yy + zz)) * sx\r\n const out1 = (xy + wz) * sx\r\n const out2 = (xz - wy) * sx\r\n const out4 = (xy - wz) * sy\r\n const out5 = (1 - (xx + zz)) * sy\r\n const out6 = (yz + wx) * sy\r\n const out8 = (xz + wy) * sz\r\n const out9 = (yz - wx) * sz\r\n const out10 = (1 - (xx + yy)) * sz\r\n\r\n matrix[0] = out0\r\n matrix[1] = out1\r\n matrix[2] = out2\r\n matrix[3] = 0\r\n matrix[4] = out4\r\n matrix[5] = out5\r\n matrix[6] = out6\r\n matrix[7] = 0\r\n matrix[8] = out8\r\n matrix[9] = out9\r\n matrix[10] = out10\r\n matrix[11] = 0\r\n matrix[12] = translation.x + ox - (out0 * ox + out4 * oy + out8 * oz)\r\n matrix[13] = translation.y + oy - (out1 * ox + out5 * oy + out9 * oz)\r\n matrix[14] = translation.z + oz - (out2 * ox + out6 * oy + out10 * oz)\r\n matrix[15] = 1\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Set this {@link Mat4} as a rotation matrix based on an eye, target and up {@link Vec3 | vectors}\r\n * @param eye - {@link Vec3 | position vector} of the object that should be rotated\r\n * @param target - {@link Vec3 | target vector} to look at\r\n * @param up - up {@link Vec3 | vector}\r\n * @returns - rotated {@link Mat4}\r\n */\r\n lookAt(eye: Vec3 = new Vec3(), target: Vec3 = new Vec3(), up: Vec3 = new Vec3(0, 1, 0)): Mat4 {\r\n const te = this.elements\r\n\r\n zAxis.copy(eye).sub(target)\r\n\r\n if (zAxis.lengthSq() === 0) {\r\n // eye and target are in the same position\r\n zAxis.z = 1\r\n }\r\n\r\n zAxis.normalize()\r\n xAxis.crossVectors(up, zAxis)\r\n\r\n if (xAxis.lengthSq() === 0) {\r\n // up and z are parallel\r\n if (Math.abs(up.z) === 1) {\r\n zAxis.x += 0.0001\r\n } else {\r\n zAxis.z += 0.0001\r\n }\r\n\r\n zAxis.normalize()\r\n xAxis.crossVectors(up, zAxis)\r\n }\r\n\r\n xAxis.normalize()\r\n yAxis.crossVectors(zAxis, xAxis)\r\n\r\n te[0] = xAxis.x\r\n te[1] = xAxis.y\r\n te[2] = xAxis.z\r\n te[3] = 0\r\n te[4] = yAxis.x\r\n te[5] = yAxis.y\r\n te[6] = yAxis.z\r\n te[7] = 0\r\n te[8] = zAxis.x\r\n te[9] = zAxis.y\r\n te[10] = zAxis.z\r\n te[11] = 0\r\n te[12] = eye.x\r\n te[13] = eye.y\r\n te[14] = eye.z\r\n te[15] = 1\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Compute a view {@link Mat4} matrix.\r\n *\r\n * This is a view matrix which transforms all other objects\r\n * to be in the space of the view defined by the parameters.\r\n *\r\n * Equivalent to `matrix.lookAt(eye, target, up).invert()` but faster.\r\n *\r\n * @param eye - the position of the object.\r\n * @param target - the position meant to be aimed at.\r\n * @param up - a vector pointing up.\r\n * @returns - the view {@link Mat4} matrix.\r\n */\r\n makeView(eye: Vec3 = new Vec3(), target: Vec3 = new Vec3(), up: Vec3 = new Vec3(0, 1, 0)): Mat4 {\r\n const te = this.elements\r\n\r\n zAxis.copy(eye).sub(target).normalize()\r\n xAxis.crossVectors(up, zAxis).normalize()\r\n yAxis.crossVectors(zAxis, xAxis).normalize()\r\n\r\n te[0] = xAxis.x\r\n te[1] = yAxis.x\r\n te[2] = zAxis.x\r\n te[3] = 0\r\n te[4] = xAxis.y\r\n te[5] = yAxis.y\r\n te[6] = zAxis.y\r\n te[7] = 0\r\n te[8] = xAxis.z\r\n te[9] = yAxis.z\r\n te[10] = zAxis.z\r\n te[11] = 0\r\n\r\n te[12] = -(xAxis.x * eye.x + xAxis.y * eye.y + xAxis.z * eye.z)\r\n te[13] = -(yAxis.x * eye.x + yAxis.y * eye.y + yAxis.z * eye.z)\r\n te[14] = -(zAxis.x * eye.x + zAxis.y * eye.y + zAxis.z * eye.z)\r\n te[15] = 1\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Create an orthographic {@link Mat4} matrix based on the parameters. Transforms from\r\n * * the given the left, right, bottom, and top dimensions to -1 +1 in x, and y\r\n * * and 0 to +1 in z.\r\n *\r\n * @param parameters - {@link OrthographicProjectionParams | parameters} used to create the camera orthographic matrix.\r\n * @returns - the camera orthographic {@link Mat4} matrix.\r\n */\r\n makeOrthographic({\r\n left = -5,\r\n right = 5,\r\n bottom = -5,\r\n top = 5,\r\n near = 0.1,\r\n far = 50,\r\n }: OrthographicProjectionParams): Mat4 {\r\n const te = this.elements\r\n\r\n te[0] = 2 / (right - left)\r\n te[1] = 0\r\n te[2] = 0\r\n te[3] = 0\r\n\r\n te[4] = 0\r\n te[5] = 2 / (top - bottom)\r\n te[6] = 0\r\n te[7] = 0\r\n\r\n te[8] = 0\r\n te[9] = 0\r\n te[10] = 1 / (near - far)\r\n te[11] = 0\r\n\r\n te[12] = (right + left) / (left - right)\r\n te[13] = (top + bottom) / (bottom - top)\r\n te[14] = near / (near - far)\r\n te[15] = 1\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Create a perspective {@link Mat4} matrix based on the parameters.\r\n *\r\n * Note, The matrix generated sends the viewing frustum to the unit box.\r\n * We assume a unit box extending from -1 to 1 in the x and y dimensions and\r\n * from -1 to 1 in the z dimension, as three.js and more generally WebGL handles it.\r\n *\r\n * @param parameters - {@link PerspectiveProjectionParams | parameters} used to create the camera perspective matrix.\r\n * @returns - the camera perspective {@link Mat4} matrix.\r\n */\r\n makePerspective({ fov = 90, aspect = 1, near = 0.1, far = 150 }: PerspectiveProjectionParams): Mat4 {\r\n const top = near * Math.tan((Math.PI / 180) * 0.5 * fov)\r\n const height = 2 * top\r\n const width = aspect * height\r\n const left = -0.5 * width\r\n\r\n const right = left + width\r\n const bottom = top - height\r\n\r\n const x = (2 * near) / (right - left)\r\n const y = (2 * near) / (top - bottom)\r\n\r\n const a = (right + left) / (right - left)\r\n const b = (top + bottom) / (top - bottom)\r\n\r\n // this should handle depth from 0 to 1\r\n // and correct near / far clipping planes\r\n // see https://github.com/mrdoob/three.js/blob/master/src/math/Matrix4.js#L777\r\n const c = -far / (far - near)\r\n const d = (-far * near) / (far - near)\r\n\r\n // prettier-ignore\r\n this.set(\r\n x, 0, 0, 0,\r\n 0, y, 0, 0,\r\n a, b, c, -1,\r\n 0, 0, d, 0\r\n )\r\n\r\n return this\r\n }\r\n}\r\n","import { Vec3 } from '../../math/Vec3'\r\nimport { Quat } from '../../math/Quat'\r\nimport { Mat4 } from '../../math/Mat4'\r\n\r\nlet objectIndex = 0\r\nconst tempMatrix = new Mat4()\r\n\r\n/** Defines all kind of possible {@link Object3D} matrix types */\r\nexport type Object3DMatricesType = 'model' | 'world'\r\n\r\n/**\r\n * Defines an {@link Object3D} matrix object\r\n */\r\nexport interface Object3DTransformMatrix {\r\n /** The {@link Mat4 | matrix} used */\r\n matrix: Mat4\r\n /** Whether we should update the {@link Mat4 | matrix} */\r\n shouldUpdate: boolean\r\n /** Function to update our {@link Mat4 | matrix} */\r\n onUpdate: () => void\r\n}\r\n\r\n/** Defines all possible {@link Object3DTransformMatrix | matrix object} used by our {@link Object3D} */\r\nexport type Object3DMatrices = Record\r\n\r\n/**\r\n * Defines all necessary {@link Vec3 | vectors}/{@link Quat | quaternions} to compute a 3D {@link Mat4 | model matrix}\r\n */\r\nexport interface Object3DTransforms {\r\n /** Transformation origin object */\r\n origin: {\r\n /** Transformation origin {@link Vec3 | vector} relative to the {@link Object3D} */\r\n model: Vec3\r\n }\r\n /** Model {@link Quat | quaternion} defining its rotation in 3D space */\r\n quaternion: Quat\r\n /** Model rotation {@link Vec3 | vector} used to compute its {@link Quat | quaternion} */\r\n rotation: Vec3\r\n /** Position object */\r\n position: {\r\n /** Position {@link Vec3 | vector} relative to the 3D world */\r\n world: Vec3\r\n }\r\n /** Model 3D scale {@link Vec3 | vector} */\r\n scale: Vec3\r\n}\r\n\r\n/**\r\n * Used to create an object with transformation properties such as position, scale, rotation and transform origin {@link Vec3 | vectors} and a {@link Quat | quaternion} in order to compute the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}.\r\n *\r\n * If an {@link Object3D} does not have any {@link Object3D#parent | parent}, then its {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix} are the same.\r\n *\r\n * The transformations {@link Vec3 | vectors} are reactive to changes, which mean that updating one of their components will automatically update the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}.\r\n */\r\nexport class Object3D {\r\n /** {@link Object3DTransforms | Transformation object} of the {@link Object3D} */\r\n transforms: Object3DTransforms\r\n /** {@link Object3DMatrices | Matrices object} of the {@link Object3D} */\r\n matrices: Object3DMatrices\r\n\r\n /** Parent {@link Object3D} in the scene graph, used to compute the {@link worldMatrix | world matrix} */\r\n private _parent: null | Object3D\r\n /** Children {@link Object3D} in the scene graph, used to compute their own {@link worldMatrix | world matrix} */\r\n children: Object3D[]\r\n\r\n /** Index (order of creation) of this {@link Object3D}. Used in the {@link parent} / {@link children} relation. */\r\n object3DIndex: number\r\n\r\n /** Whether at least one of this {@link Object3D} matrix needs an update. */\r\n matricesNeedUpdate: boolean\r\n\r\n /**\r\n * Object3D constructor\r\n */\r\n constructor() {\r\n this._parent = null\r\n this.children = []\r\n\r\n this.matricesNeedUpdate = false\r\n\r\n Object.defineProperty(this as Object3D, 'object3DIndex', { value: objectIndex++ })\r\n\r\n this.setMatrices()\r\n this.setTransforms()\r\n }\r\n\r\n /* PARENT */\r\n\r\n /**\r\n * Get the parent of this {@link Object3D} if any\r\n */\r\n get parent(): Object3D | null {\r\n return this._parent\r\n }\r\n\r\n /**\r\n * Set the parent of this {@link Object3D}\r\n * @param value - new parent to set, could be an {@link Object3D} or null\r\n */\r\n set parent(value: Object3D | null) {\r\n if (this._parent && value && this._parent.object3DIndex === value.object3DIndex) {\r\n return\r\n }\r\n\r\n if (this._parent) {\r\n // if we already have a parent, remove it first\r\n this._parent.children = this._parent.children.filter((child) => child.object3DIndex !== this.object3DIndex)\r\n }\r\n\r\n if (value) {\r\n this.shouldUpdateWorldMatrix()\r\n }\r\n\r\n this._parent = value\r\n this._parent?.children.push(this)\r\n }\r\n\r\n /* TRANSFORMS */\r\n\r\n /**\r\n * Set our transforms properties and {@link Vec3#onChange | vectors onChange} callbacks\r\n */\r\n setTransforms() {\r\n this.transforms = {\r\n origin: {\r\n model: new Vec3(),\r\n },\r\n quaternion: new Quat(),\r\n rotation: new Vec3(),\r\n position: {\r\n world: new Vec3(),\r\n },\r\n scale: new Vec3(1),\r\n }\r\n\r\n this.rotation.onChange(() => this.applyRotation())\r\n this.position.onChange(() => this.applyPosition())\r\n this.scale.onChange(() => this.applyScale())\r\n this.transformOrigin.onChange(() => this.applyTransformOrigin())\r\n }\r\n\r\n /**\r\n * Get our rotation {@link Vec3 | vector}\r\n */\r\n get rotation(): Vec3 {\r\n return this.transforms.rotation\r\n }\r\n\r\n /**\r\n * Set our rotation {@link Vec3 | vector}\r\n * @param value - new rotation {@link Vec3 | vector}\r\n */\r\n set rotation(value: Vec3) {\r\n this.transforms.rotation = value\r\n this.applyRotation()\r\n }\r\n\r\n /**\r\n * Get our {@link Quat | quaternion}\r\n */\r\n get quaternion(): Quat {\r\n return this.transforms.quaternion\r\n }\r\n\r\n /**\r\n * Set our {@link Quat | quaternion}\r\n * @param value - new {@link Quat | quaternion}\r\n */\r\n set quaternion(value: Quat) {\r\n this.transforms.quaternion = value\r\n }\r\n\r\n /**\r\n * Get our position {@link Vec3 | vector}\r\n */\r\n get position(): Vec3 {\r\n return this.transforms.position.world\r\n }\r\n\r\n /**\r\n * Set our position {@link Vec3 | vector}\r\n * @param value - new position {@link Vec3 | vector}\r\n */\r\n set position(value: Vec3) {\r\n this.transforms.position.world = value\r\n }\r\n\r\n /**\r\n * Get our scale {@link Vec3 | vector}\r\n */\r\n get scale(): Vec3 {\r\n return this.transforms.scale\r\n }\r\n\r\n /**\r\n * Set our scale {@link Vec3 | vector}\r\n * @param value - new scale {@link Vec3 | vector}\r\n */\r\n set scale(value: Vec3) {\r\n // force scale to 1 on Z axis\r\n this.transforms.scale = value\r\n this.applyScale()\r\n }\r\n\r\n /**\r\n * Get our transform origin {@link Vec3 | vector}\r\n */\r\n get transformOrigin(): Vec3 {\r\n return this.transforms.origin.model\r\n }\r\n\r\n /**\r\n * Set our transform origin {@link Vec3 | vector}\r\n * @param value - new transform origin {@link Vec3 | vector}\r\n */\r\n set transformOrigin(value: Vec3) {\r\n this.transforms.origin.model = value\r\n }\r\n\r\n /**\r\n * Apply our rotation and tell our {@link modelMatrix | model matrix} to update\r\n */\r\n applyRotation() {\r\n this.quaternion.setFromVec3(this.rotation)\r\n\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Tell our {@link modelMatrix | model matrix} to update\r\n */\r\n applyPosition() {\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Tell our {@link modelMatrix | model matrix} to update\r\n */\r\n applyScale() {\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Tell our {@link modelMatrix | model matrix} to update\r\n */\r\n applyTransformOrigin() {\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /* MATRICES */\r\n\r\n /**\r\n * Set our {@link modelMatrix | model matrix} and {@link worldMatrix | world matrix}\r\n */\r\n setMatrices() {\r\n this.matrices = {\r\n model: {\r\n matrix: new Mat4(),\r\n shouldUpdate: true,\r\n onUpdate: () => this.updateModelMatrix(),\r\n },\r\n world: {\r\n matrix: new Mat4(),\r\n shouldUpdate: true,\r\n onUpdate: () => this.updateWorldMatrix(),\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get our {@link Mat4 | model matrix}\r\n */\r\n get modelMatrix(): Mat4 {\r\n return this.matrices.model.matrix\r\n }\r\n\r\n /**\r\n * Set our {@link Mat4 | model matrix}\r\n * @param value - new {@link Mat4 | model matrix}\r\n */\r\n set modelMatrix(value: Mat4) {\r\n this.matrices.model.matrix = value\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Set our {@link modelMatrix | model matrix} shouldUpdate flag to true (tell it to update)\r\n */\r\n shouldUpdateModelMatrix() {\r\n this.matrices.model.shouldUpdate = true\r\n this.shouldUpdateWorldMatrix()\r\n }\r\n\r\n /**\r\n * Get our {@link Mat4 | world matrix}\r\n */\r\n get worldMatrix(): Mat4 {\r\n return this.matrices.world.matrix\r\n }\r\n\r\n /**\r\n * Set our {@link Mat4 | world matrix}\r\n * @param value - new {@link Mat4 | world matrix}\r\n */\r\n set worldMatrix(value: Mat4) {\r\n this.matrices.world.matrix = value\r\n this.shouldUpdateWorldMatrix()\r\n }\r\n\r\n /**\r\n * Set our {@link worldMatrix | world matrix} shouldUpdate flag to true (tell it to update)\r\n */\r\n shouldUpdateWorldMatrix() {\r\n this.matrices.world.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Rotate this {@link Object3D} so it looks at the {@link Vec3 | target}\r\n * @param target - {@link Vec3 | target} to look at\r\n * @param position - {@link Vec3 | postion} from which to look at\r\n */\r\n lookAt(target: Vec3 = new Vec3(), position = this.position, up = new Vec3(0, 1, 0)) {\r\n const rotationMatrix = tempMatrix.lookAt(target, position, up)\r\n this.quaternion.setFromRotationMatrix(rotationMatrix)\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Update our {@link modelMatrix | model matrix}\r\n */\r\n updateModelMatrix() {\r\n // compose our model transformation matrix from custom origin\r\n this.modelMatrix = this.modelMatrix.composeFromOrigin(\r\n this.position,\r\n this.quaternion,\r\n this.scale,\r\n this.transformOrigin\r\n )\r\n\r\n // tell our world matrix to update\r\n this.shouldUpdateWorldMatrix()\r\n }\r\n\r\n /**\r\n * Update our {@link worldMatrix | model matrix}\r\n */\r\n updateWorldMatrix() {\r\n if (!this.parent) {\r\n this.worldMatrix.copy(this.modelMatrix)\r\n } else {\r\n this.worldMatrix.multiplyMatrices(this.parent.worldMatrix, this.modelMatrix)\r\n }\r\n\r\n // update the children world matrix as well\r\n for (let i = 0, l = this.children.length; i < l; i++) {\r\n this.children[i].shouldUpdateWorldMatrix()\r\n }\r\n }\r\n\r\n /**\r\n * Check whether at least one of the matrix should be updated\r\n */\r\n shouldUpdateMatrices() {\r\n this.matricesNeedUpdate = !!Object.values(this.matrices).find((matrix) => matrix.shouldUpdate)\r\n }\r\n\r\n /**\r\n * Check at each render whether we should update our matrices, and update them if needed\r\n */\r\n updateMatrixStack() {\r\n this.shouldUpdateMatrices()\r\n\r\n if (this.matricesNeedUpdate) {\r\n for (const matrixName in this.matrices) {\r\n if (this.matrices[matrixName].shouldUpdate) {\r\n this.matrices[matrixName].onUpdate()\r\n this.matrices[matrixName].shouldUpdate = false\r\n }\r\n }\r\n }\r\n\r\n for (let i = 0, l = this.children.length; i < l; i++) {\r\n this.children[i].updateMatrixStack()\r\n }\r\n }\r\n\r\n /**\r\n * Destroy this {@link Object3D}. Removes its parent and set its children free.\r\n */\r\n destroy() {\r\n for (let i = 0, l = this.children.length; i < l; i++) {\r\n if (this.children[i]) this.children[i].parent = null\r\n }\r\n\r\n this.parent = null\r\n }\r\n}\r\n","// texture bitwise flags\r\nimport { TextureBindingType } from '../bindings/Binding'\r\n\r\n/** Defines all kinds of allowed texture usages as camel case strings. */\r\nexport type TextureUsageKeys = 'copySrc' | 'copyDst' | 'renderAttachment' | 'storageBinding' | 'textureBinding'\r\n\r\n/**\r\n * Map {@link TextureUsageKeys | texture usage names} with actual {@link GPUTextureUsageFlags | texture usage bitwise flags}.\r\n */\r\nconst textureUsages: Map = new Map([\r\n ['copySrc', GPUTextureUsage.COPY_SRC],\r\n ['copyDst', GPUTextureUsage.COPY_DST],\r\n ['renderAttachment', GPUTextureUsage.RENDER_ATTACHMENT],\r\n ['storageBinding', GPUTextureUsage.STORAGE_BINDING],\r\n ['textureBinding', GPUTextureUsage.TEXTURE_BINDING],\r\n])\r\n\r\n/**\r\n * Get the corresponding {@link GPUTextureUsageFlags | texture usage bitwise flags} based on an array of {@link TextureUsageKeys | texture usage names}.\r\n * @param usages - array of {@link TextureUsageKeys | texture usage names}.\r\n * @returns - corresponding {@link GPUTextureUsageFlags | texture usage bitwise flags}.\r\n */\r\nexport const getTextureUsages = (usages: TextureUsageKeys[] = []): GPUTextureUsageFlags => {\r\n return usages.reduce((acc, v) => {\r\n return acc | textureUsages.get(v)\r\n }, 0)\r\n}\r\n\r\n/**\r\n * Get the corresponding {@link GPUTextureUsageFlags | texture usage bitwise flags} based on an array of {@link TextureUsageKeys | texture usage names} if specified. If not, will try to fall back to a usage based on the {@link TextureBindingType | texture type}.\r\n * @param usages - array of {@link TextureUsageKeys | texture usage names}.\r\n * @param textureType - the {@link TextureBindingType | texture type}.\r\n * @returns - corresponding {@link GPUTextureUsageFlags | texture usage bitwise flags}.\r\n */\r\nexport const getDefaultTextureUsage = (usages: TextureUsageKeys[] = [], textureType: TextureBindingType) => {\r\n if (usages.length) {\r\n return getTextureUsages(usages)\r\n }\r\n\r\n return textureType !== 'storage'\r\n ? GPUTextureUsage.TEXTURE_BINDING |\r\n GPUTextureUsage.COPY_SRC |\r\n GPUTextureUsage.COPY_DST |\r\n GPUTextureUsage.RENDER_ATTACHMENT\r\n : GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST\r\n}\r\n\r\n/**\r\n * Get the number of mip levels create based on {@link types/Textures.TextureSize | size}\r\n * @param sizes - Array containing our texture width, height and depth\r\n * @returns - number of mip levels\r\n */\r\nexport const getNumMipLevels = (...sizes: number[]): number => {\r\n const maxSize = Math.max(...sizes)\r\n return (1 + Math.log2(maxSize)) | 0\r\n}\r\n","import { Vec3 } from '../../math/Vec3'\r\nimport { isRenderer, Renderer } from '../renderers/utils'\r\nimport { TextureBinding } from '../bindings/TextureBinding'\r\nimport { BufferBinding } from '../bindings/BufferBinding'\r\nimport { Object3D } from '../objects3D/Object3D'\r\nimport { Mat4 } from '../../math/Mat4'\r\nimport { generateUUID, throwWarning } from '../../utils/utils'\r\nimport { BindGroupBindingElement } from '../../types/BindGroups'\r\nimport { DOMTextureOptions, DOMTextureParams, DOMTextureParent, TextureSize, TextureSource } from '../../types/Textures'\r\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\r\nimport { DOMProjectedMesh } from '../renderers/GPURenderer'\r\nimport { getNumMipLevels } from './utils'\r\n\r\n/** @const - default {@link DOMTexture} parameters */\r\nconst defaultDOMTextureParams: DOMTextureParams = {\r\n name: 'texture',\r\n generateMips: false,\r\n flipY: false,\r\n format: 'rgba8unorm',\r\n premultipliedAlpha: false,\r\n placeholderColor: [0, 0, 0, 255], // default to black\r\n useExternalTextures: true,\r\n fromTexture: null,\r\n viewDimension: '2d',\r\n visibility: ['fragment'],\r\n cache: true,\r\n}\r\n\r\n/**\r\n * Used to create {@link GPUTexture} or {@link GPUExternalTexture}, specially made to handle different kinds of DOM elements {@link TextureSource | sources}, like {@link HTMLImageElement}, {@link HTMLVideoElement} or {@link HTMLCanvasElement}.\r\n *\r\n * Handles the various sources loading and uploading, GPU textures creation,{@link BufferBinding | texture model matrix binding} and {@link TextureBinding | GPU texture binding}.\r\n *\r\n * @example\r\n * ```javascript\r\n * // set our main GPUCurtains instance\r\n * const gpuCurtains = new GPUCurtains({\r\n * container: '#canvas' // selector of our WebGPU canvas container\r\n * })\r\n *\r\n * // set the GPU device\r\n * // note this is asynchronous\r\n * await gpuCurtains.setDevice()\r\n *\r\n * // create a DOM texture\r\n * const imageTexture = new DOMTexture(gpuCurtains, {\r\n * label: 'My image texture',\r\n * name: 'imageTexture',\r\n * })\r\n *\r\n * // load an image\r\n * await imageTexture.loadImage(document.querySelector('img'))\r\n * ```\r\n */\r\nexport class DOMTexture extends Object3D {\r\n /** The type of the {@link DOMTexture} */\r\n type: string\r\n /** The universal unique id of this {@link DOMTexture} */\r\n readonly uuid: string\r\n /** {@link Renderer} used by this {@link DOMTexture} */\r\n renderer: Renderer\r\n\r\n /** The {@link GPUTexture} used if any */\r\n texture: null | GPUTexture\r\n /** The {@link GPUExternalTexture} used if any */\r\n externalTexture: null | GPUExternalTexture\r\n\r\n /** The {@link DOMTexture} {@link TextureSource | source} to use */\r\n source: TextureSource\r\n /** The {@link GPUTexture}, matching the {@link TextureSource | source} {@link core/DOM/DOMElement.RectSize | size} (with 1 for depth) */\r\n size: TextureSize\r\n\r\n /** Options used to create this {@link DOMTexture} */\r\n options: DOMTextureOptions\r\n\r\n /** A {@link BufferBinding | buffer binding} that will hold the texture model matrix */\r\n textureMatrix: BufferBinding\r\n /** The bindings used by this {@link DOMTexture}, i.e. its {@link textureMatrix} and its {@link TextureBinding | GPU texture binding} */\r\n bindings: BindGroupBindingElement[]\r\n\r\n /** {@link DOMTexture} parentMesh if any */\r\n private _parentMesh: DOMTextureParent\r\n\r\n /** Whether the source has been loaded */\r\n private _sourceLoaded: boolean\r\n /** Whether the source has been uploaded to the GPU, handled by the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#texturesQueue | GPUDeviceManager texturesQueue array} */\r\n private _sourceUploaded: boolean\r\n /** Whether the texture should be uploaded to the GPU */\r\n shouldUpdate: boolean\r\n\r\n /** {@link HTMLVideoElement.requestVideoFrameCallback | requestVideoFrameCallback} returned id if used */\r\n videoFrameCallbackId: null | number\r\n\r\n /** Private {@link Vec3 | vector} used for {@link#modelMatrix} calculations, based on {@link parentMesh} {@link core/DOM/DOMElement.RectSize | size} */\r\n #parentRatio: Vec3 = new Vec3(1)\r\n /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on {@link size | source size} */\r\n #sourceRatio: Vec3 = new Vec3(1)\r\n /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on #parentRatio and #sourceRatio */\r\n #coverScale: Vec3 = new Vec3(1)\r\n /** Private rotation {@link Mat4 | matrix} based on texture {@link quaternion} */\r\n #rotationMatrix: Mat4 = new Mat4()\r\n\r\n // callbacks / events\r\n /** function assigned to the {@link onSourceLoaded} callback */\r\n _onSourceLoadedCallback = () => {\r\n /* allow empty callback */\r\n }\r\n /** function assigned to the {@link onSourceUploaded} callback */\r\n _onSourceUploadedCallback = () => {\r\n /* allow empty callback */\r\n }\r\n\r\n /**\r\n * DOMTexture constructor\r\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link DOMTexture}\r\n * @param parameters - {@link DOMTextureParams | parameters} used to create this {@link DOMTexture}\r\n */\r\n constructor(renderer: Renderer | GPUCurtains, parameters = defaultDOMTextureParams) {\r\n super()\r\n\r\n this.type = 'Texture'\r\n\r\n renderer = isRenderer(renderer, parameters.label ? parameters.label + ' ' + this.type : this.type)\r\n\r\n this.renderer = renderer\r\n\r\n this.uuid = generateUUID()\r\n\r\n const defaultOptions = {\r\n ...defaultDOMTextureParams,\r\n source: parameters.fromTexture ? parameters.fromTexture.options.source : null,\r\n sourceType: parameters.fromTexture ? parameters.fromTexture.options.sourceType : null,\r\n }\r\n\r\n this.options = { ...defaultOptions, ...parameters }\r\n // force merge of texture object\r\n //this.options.texture = { ...defaultOptions.texture, ...parameters.texture }\r\n\r\n this.options.label = this.options.label ?? this.options.name\r\n\r\n this.texture = null\r\n this.externalTexture = null\r\n this.source = null\r\n\r\n // sizes\r\n this.size = {\r\n width: 1,\r\n height: 1,\r\n depth: 1,\r\n }\r\n\r\n // we will always declare a texture matrix\r\n this.textureMatrix = new BufferBinding({\r\n label: this.options.label + ': model matrix',\r\n name: this.options.name + 'Matrix',\r\n useStruct: false,\r\n struct: {\r\n [this.options.name + 'Matrix']: {\r\n type: 'mat4x4f',\r\n value: this.modelMatrix,\r\n },\r\n },\r\n })\r\n\r\n this.renderer.deviceManager.bufferBindings.set(this.textureMatrix.cacheKey, this.textureMatrix)\r\n\r\n this.setBindings()\r\n\r\n this._parentMesh = null\r\n\r\n this.sourceLoaded = false\r\n this.sourceUploaded = false\r\n this.shouldUpdate = false\r\n\r\n this.renderer.addDOMTexture(this)\r\n this.createTexture()\r\n }\r\n\r\n /**\r\n * Set our {@link bindings}\r\n */\r\n setBindings() {\r\n this.bindings = [\r\n new TextureBinding({\r\n label: this.options.label + ': texture',\r\n name: this.options.name,\r\n bindingType: this.options.sourceType === 'externalVideo' ? 'externalTexture' : 'texture',\r\n visibility: this.options.visibility,\r\n texture: this.options.sourceType === 'externalVideo' ? this.externalTexture : this.texture,\r\n viewDimension: this.options.viewDimension,\r\n }),\r\n this.textureMatrix,\r\n ]\r\n }\r\n\r\n /**\r\n * Get our {@link TextureBinding | GPU texture binding}\r\n * @readonly\r\n */\r\n get textureBinding(): TextureBinding {\r\n return this.bindings[0] as TextureBinding\r\n }\r\n\r\n /**\r\n * Get our texture {@link parentMesh}\r\n */\r\n get parentMesh(): DOMTextureParent {\r\n return this._parentMesh\r\n }\r\n\r\n /**\r\n * Set our texture {@link parentMesh}\r\n * @param value - texture {@link parentMesh} to set (i.e. any kind of {@link core/renderers/GPURenderer.RenderedMesh | Mesh}\r\n */\r\n set parentMesh(value: DOMTextureParent) {\r\n this._parentMesh = value\r\n this.resize()\r\n }\r\n\r\n /**\r\n * Get whether our {@link source} has been loaded\r\n */\r\n get sourceLoaded(): boolean {\r\n return this._sourceLoaded\r\n }\r\n\r\n /**\r\n * Set whether our {@link source} has been loaded\r\n * @param value - boolean flag indicating if the {@link source} has been loaded\r\n */\r\n set sourceLoaded(value: boolean) {\r\n if (value && !this.sourceLoaded) {\r\n this._onSourceLoadedCallback && this._onSourceLoadedCallback()\r\n }\r\n this._sourceLoaded = value\r\n }\r\n\r\n /**\r\n * Get whether our {@link source} has been uploaded\r\n */\r\n get sourceUploaded(): boolean {\r\n return this._sourceUploaded\r\n }\r\n\r\n /**\r\n * Set whether our {@link source} has been uploaded\r\n * @param value - boolean flag indicating if the {@link source} has been uploaded\r\n */\r\n set sourceUploaded(value: boolean) {\r\n if (value && !this.sourceUploaded) {\r\n this._onSourceUploadedCallback && this._onSourceUploadedCallback()\r\n }\r\n this._sourceUploaded = value\r\n }\r\n\r\n /**\r\n * Set our texture {@link transforms} object\r\n */\r\n setTransforms() {\r\n super.setTransforms()\r\n\r\n this.transforms.quaternion.setAxisOrder('ZXY')\r\n\r\n // reset our model transform origin to reflect CSS transform origins\r\n this.transforms.origin.model.set(0.5, 0.5, 0)\r\n }\r\n\r\n /* TEXTURE MATRIX */\r\n\r\n /**\r\n * Update the {@link modelMatrix}\r\n */\r\n updateModelMatrix() {\r\n if (!this.parentMesh) return\r\n\r\n const parentScale = (this.parentMesh as DOMProjectedMesh).scale\r\n ? (this.parentMesh as DOMProjectedMesh).scale\r\n : new Vec3(1, 1, 1)\r\n\r\n const parentWidth = (this.parentMesh as DOMProjectedMesh).boundingRect\r\n ? (this.parentMesh as DOMProjectedMesh).boundingRect.width * parentScale.x\r\n : this.size.width\r\n const parentHeight = (this.parentMesh as DOMProjectedMesh).boundingRect\r\n ? (this.parentMesh as DOMProjectedMesh).boundingRect.height * parentScale.y\r\n : this.size.height\r\n\r\n const parentRatio = parentWidth / parentHeight\r\n const sourceRatio = this.size.width / this.size.height\r\n\r\n // handle the texture rotation\r\n // huge props to [@grgrdvrt](https://github.com/grgrdvrt) for this solution!\r\n if (parentWidth > parentHeight) {\r\n this.#parentRatio.set(parentRatio, 1, 1)\r\n this.#sourceRatio.set(1 / sourceRatio, 1, 1)\r\n } else {\r\n this.#parentRatio.set(1, 1 / parentRatio, 1)\r\n this.#sourceRatio.set(1, sourceRatio, 1)\r\n }\r\n\r\n // cover ratio is a bit tricky!\r\n const coverRatio =\r\n parentRatio > sourceRatio !== parentWidth > parentHeight\r\n ? 1\r\n : parentWidth > parentHeight\r\n ? this.#parentRatio.x * this.#sourceRatio.x\r\n : this.#sourceRatio.y * this.#parentRatio.y\r\n\r\n this.#coverScale.set(1 / (coverRatio * this.scale.x), 1 / (coverRatio * this.scale.y), 1)\r\n\r\n this.#rotationMatrix.rotateFromQuaternion(this.quaternion)\r\n\r\n // here we could create a matrix for each translations / scales and do:\r\n // this.modelMatrix\r\n // .identity()\r\n // .premultiply(negativeOriginMatrix)\r\n // .premultiply(coverScaleMatrix)\r\n // .premultiply(parentRatioMatrix)\r\n // .premultiply(rotationMatrix)\r\n // .premultiply(textureRatioMatrix)\r\n // .premultiply(originMatrix)\r\n // .translate(this.position)\r\n\r\n // but this is faster!\r\n this.modelMatrix\r\n .identity()\r\n .premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1))\r\n .premultiplyScale(this.#coverScale)\r\n .premultiplyScale(this.#parentRatio)\r\n .premultiply(this.#rotationMatrix)\r\n .premultiplyScale(this.#sourceRatio)\r\n .premultiplyTranslate(this.transformOrigin)\r\n .translate(this.position)\r\n }\r\n\r\n /**\r\n * If our {@link modelMatrix} has been updated, tell the {@link textureMatrix | texture matrix binding} to update as well\r\n */\r\n updateMatrixStack() {\r\n super.updateMatrixStack()\r\n\r\n if (this.matricesNeedUpdate) {\r\n this.textureMatrix.shouldUpdateBinding(this.options.name + 'Matrix')\r\n }\r\n }\r\n\r\n /**\r\n * Resize our {@link DOMTexture}\r\n */\r\n resize() {\r\n // this should only happen with canvas textures\r\n if (\r\n this.source &&\r\n this.source instanceof HTMLCanvasElement &&\r\n (this.source.width !== this.size.width || this.source.height !== this.size.height)\r\n ) {\r\n // since the source size has changed, we have to recreate a new texture\r\n this.setSourceSize()\r\n this.createTexture()\r\n }\r\n\r\n // tell our model matrix to update\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Tell the {@link Renderer} to upload or texture\r\n */\r\n uploadTexture() {\r\n this.renderer.uploadTexture(this)\r\n this.shouldUpdate = false\r\n }\r\n\r\n /**\r\n * Import a {@link GPUExternalTexture} from the {@link Renderer}, update the {@link textureBinding} and its {@link core/bindGroups/TextureBindGroup.TextureBindGroup | bind group}\r\n */\r\n uploadVideoTexture() {\r\n this.externalTexture = this.renderer.importExternalTexture(this.source as HTMLVideoElement)\r\n this.textureBinding.resource = this.externalTexture\r\n this.textureBinding.setBindingType('externalTexture')\r\n this.shouldUpdate = false\r\n this.sourceUploaded = true\r\n }\r\n\r\n /**\r\n * Copy a {@link DOMTexture}\r\n * @param texture - {@link DOMTexture} to copy\r\n */\r\n copy(texture: DOMTexture) {\r\n if (this.options.sourceType === 'externalVideo' && texture.options.sourceType !== 'externalVideo') {\r\n throwWarning(`${this.options.label}: cannot copy a GPUTexture to a GPUExternalTexture`)\r\n return\r\n } else if (this.options.sourceType !== 'externalVideo' && texture.options.sourceType === 'externalVideo') {\r\n throwWarning(`${this.options.label}: cannot copy a GPUExternalTexture to a GPUTexture`)\r\n return\r\n }\r\n\r\n this.options.fromTexture = texture\r\n\r\n // now copy all desired texture options except source\r\n // const { source, ...optionsToCopy } = texture.options\r\n // this.options = { ...this.options, ...optionsToCopy }\r\n\r\n this.options.sourceType = texture.options.sourceType\r\n\r\n // TODO better way to do that?\r\n this.options.generateMips = texture.options.generateMips\r\n this.options.flipY = texture.options.flipY\r\n this.options.format = texture.options.format\r\n this.options.premultipliedAlpha = texture.options.premultipliedAlpha\r\n this.options.placeholderColor = texture.options.placeholderColor\r\n this.options.useExternalTextures = texture.options.useExternalTextures\r\n\r\n this.sourceLoaded = texture.sourceLoaded\r\n this.sourceUploaded = texture.sourceUploaded\r\n\r\n // TODO external texture?\r\n if (texture.texture) {\r\n if (texture.sourceLoaded) {\r\n this.size = texture.size\r\n this.source = texture.source\r\n\r\n this.resize()\r\n }\r\n\r\n if (texture.sourceUploaded) {\r\n // texture to copy is ready, update our texture and binding\r\n this.texture = texture.texture\r\n this.textureBinding.resource = this.texture\r\n } else {\r\n this.createTexture()\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Set the {@link texture | GPU texture}\r\n */\r\n createTexture() {\r\n const options = {\r\n label: this.options.label,\r\n format: this.options.format,\r\n size: [this.size.width, this.size.height, this.size.depth], // [1, 1] if no source\r\n dimensions: this.options.viewDimension === '1d' ? '1d' : this.options.viewDimension === '3d' ? '3d' : '2d',\r\n //sampleCount: this.source ? this.renderer.sampleCount : 1,\r\n usage: !!this.source\r\n ? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT\r\n : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\r\n } as GPUTextureDescriptor\r\n\r\n if (this.options.sourceType !== 'externalVideo') {\r\n options.mipLevelCount = this.options.generateMips ? getNumMipLevels(this.size.width, this.size.height) : 1\r\n\r\n this.texture?.destroy()\r\n\r\n this.texture = this.renderer.createTexture(options)\r\n\r\n // update texture binding\r\n this.textureBinding.resource = this.texture\r\n }\r\n\r\n this.shouldUpdate = true\r\n }\r\n\r\n /* SOURCES */\r\n\r\n /**\r\n * Set the {@link size} based on the {@link source}\r\n */\r\n setSourceSize() {\r\n this.size = {\r\n width:\r\n (this.source as HTMLImageElement).naturalWidth ||\r\n (this.source as HTMLCanvasElement).width ||\r\n (this.source as HTMLVideoElement).videoWidth,\r\n height:\r\n (this.source as HTMLImageElement).naturalHeight ||\r\n (this.source as HTMLCanvasElement).height ||\r\n (this.source as HTMLVideoElement).videoHeight,\r\n depth: 1,\r\n }\r\n }\r\n\r\n /**\r\n * Load an {@link HTMLImageElement} from a URL and create an {@link ImageBitmap} to use as a {@link source}\r\n * @async\r\n * @param url - URL of the image to load\r\n * @returns - the newly created {@link ImageBitmap}\r\n */\r\n async loadImageBitmap(url: string): Promise {\r\n const res = await fetch(url)\r\n const blob = await res.blob()\r\n return await createImageBitmap(blob, { colorSpaceConversion: 'none' })\r\n }\r\n\r\n /**\r\n * Load and create an {@link ImageBitmap} from a URL or {@link HTMLImageElement}, use it as a {@link source} and create the {@link GPUTexture}\r\n * @async\r\n * @param source - the image URL or {@link HTMLImageElement} to load\r\n * @returns - the newly created {@link ImageBitmap}\r\n */\r\n async loadImage(source: string | HTMLImageElement): Promise {\r\n const url = typeof source === 'string' ? source : source.getAttribute('src')\r\n\r\n this.options.source = url\r\n this.options.sourceType = 'image'\r\n\r\n const cachedTexture = this.renderer.domTextures.find((t) => t.options.source === url)\r\n if (cachedTexture && cachedTexture.texture && cachedTexture.sourceUploaded) {\r\n this.copy(cachedTexture)\r\n return\r\n }\r\n\r\n this.sourceLoaded = false\r\n this.sourceUploaded = false\r\n\r\n this.source = await this.loadImageBitmap(this.options.source)\r\n\r\n this.setSourceSize()\r\n this.resize()\r\n\r\n this.sourceLoaded = true\r\n this.createTexture()\r\n }\r\n\r\n // weirldy enough, we don't have to do anything in that callback\r\n // because the