From 03d8f2392d73d82713a8042e05344eaa927754d7 Mon Sep 17 00:00:00 2001 From: Juan Hoyos Date: Sat, 30 Dec 2023 12:50:41 -0500 Subject: [PATCH] fix: screen culler buggy when multiple models loaded --- resources/openbim-components.js | 33323 ++++++++++++----------- src/core/LocalCacher/index.ts | 8 +- src/core/ScreenCuller/index.ts | 14 + src/fragments/FragmentCacher/index.ts | 7 +- src/fragments/FragmentManager/index.ts | 7 +- 5 files changed, 16696 insertions(+), 16663 deletions(-) diff --git a/resources/openbim-components.js b/resources/openbim-components.js index bfe6c67f1..2fedc512b 100644 --- a/resources/openbim-components.js +++ b/resources/openbim-components.js @@ -1,5 +1,5 @@ import * as THREE$1 from 'https://unpkg.com/three@0.152.2/build/three.module.js'; -import { Vector3 as Vector3$1, Matrix4, Object3D, Vector2 as Vector2$1, BufferAttribute as BufferAttribute$1, Plane, Line3, Triangle, Sphere, BackSide, DoubleSide, Box3, FrontSide, Mesh, Ray, Raycaster, Quaternion as Quaternion$1, Euler, MeshBasicMaterial, LineBasicMaterial, CylinderGeometry, BoxGeometry, BufferGeometry, Float32BufferAttribute, OctahedronGeometry, Line as Line$2, SphereGeometry, TorusGeometry, PlaneGeometry, EventDispatcher as EventDispatcher$1, MOUSE, TOUCH, Spherical, OrthographicCamera, ShaderMaterial, UniformsUtils, WebGLRenderTarget, Clock, Color, REVISION, LinearFilter, NearestFilter, HalfFloatType, RGBAFormat, DepthTexture, UnsignedInt248Type, UnsignedIntType, DepthStencilFormat, DepthFormat, DataTexture, NoColorSpace, RepeatWrapping, WebGLMultipleRenderTargets, RedFormat, FloatType, PropertyBinding, InterpolateLinear, Source, MathUtils, InterpolateDiscrete, Scene, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, ClampToEdgeWrapping, MirroredRepeatWrapping, SRGBColorSpace, InstancedMesh, UniformsLib, ShaderLib, InstancedBufferGeometry, InstancedInterleavedBuffer, InterleavedBufferAttribute, WireframeGeometry, Vector4 } from 'https://unpkg.com/three@0.152.2/build/three.module.js'; +import { Vector3 as Vector3$1, Matrix4, Object3D, Vector2 as Vector2$1, BufferAttribute as BufferAttribute$1, Plane, Line3, Triangle, Sphere, BackSide, DoubleSide, Box3, FrontSide, Mesh, Ray, Raycaster, Quaternion as Quaternion$1, Euler, MeshBasicMaterial, LineBasicMaterial, CylinderGeometry, BoxGeometry, BufferGeometry, Float32BufferAttribute, OctahedronGeometry, Line as Line$2, SphereGeometry, TorusGeometry, PlaneGeometry, Color, PropertyBinding, InterpolateLinear, Source, NoColorSpace, MathUtils, RGBAFormat, InterpolateDiscrete, Scene, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping, SRGBColorSpace, InstancedMesh, EventDispatcher as EventDispatcher$1, MOUSE, TOUCH, Spherical, OrthographicCamera, ShaderMaterial, UniformsUtils, WebGLRenderTarget, Clock, REVISION, HalfFloatType, DepthTexture, UnsignedInt248Type, UnsignedIntType, DepthStencilFormat, DepthFormat, DataTexture, WebGLMultipleRenderTargets, RedFormat, FloatType, UniformsLib, ShaderLib, InstancedBufferGeometry, InstancedInterleavedBuffer, InterleavedBufferAttribute, WireframeGeometry, Vector4 } from 'https://unpkg.com/three@0.152.2/build/three.module.js'; /** * Components are the building blocks of this library. Everything is a @@ -13779,19212 +13779,19221 @@ class SimpleClipper extends Component { SimpleClipper.uuid = "66290bc5-18c4-4cd1-9379-2e17a0617611"; ToolComponent.libraryUUIDs.add(SimpleClipper.uuid); -// -// Thanks to the advice here https://github.com/zalo/TetSim/commit/9696c2e1cd6354fb9bd40dbd299c58f4de0341dd -// -function clientWaitAsync(gl, sync, flags, intervalMilliseconds) { - return new Promise((resolve, reject) => { - function test() { - const res = gl.clientWaitSync(sync, flags, 0); - if (res === gl.WAIT_FAILED) { - reject(); - return; - } - if (res === gl.TIMEOUT_EXPIRED) { - setTimeout(test, intervalMilliseconds); - return; - } - resolve(); - } - test(); - }); -} -async function getBufferSubDataAsync(gl, target, buffer, srcByteOffset, dstBuffer, dstOffset, length) { - const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); - gl.flush(); - await clientWaitAsync(gl, sync, 0, 10); - gl.deleteSync(sync); - gl.bindBuffer(target, buffer); - gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length); - gl.bindBuffer(target, null); -} -async function readPixelsAsync(gl, x, y, w, h, format, type, dest) { - const buf = gl.createBuffer(); - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); - gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ); - gl.readPixels(x, y, w, h, format, type, 0); - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); - await getBufferSubDataAsync(gl, gl.PIXEL_PACK_BUFFER, buf, 0, dest); - gl.deleteBuffer(buf); - return dest; +/** + * @param {Array} geometries + * @param {Boolean} useGroups + * @return {BufferGeometry} + */ +function mergeGeometries( geometries, useGroups = false ) { + + const isIndexed = geometries[ 0 ].index !== null; + + const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) ); + const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) ); + + const attributes = {}; + const morphAttributes = {}; + + const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative; + + const mergedGeometry = new BufferGeometry(); + + let offset = 0; + + for ( let i = 0; i < geometries.length; ++ i ) { + + const geometry = geometries[ i ]; + let attributesCount = 0; + + // ensure that all geometries are indexed, or none + + if ( isIndexed !== ( geometry.index !== null ) ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' ); + return null; + + } + + // gather attributes, exit early if they're different + + for ( const name in geometry.attributes ) { + + if ( ! attributesUsed.has( name ) ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' ); + return null; + + } + + if ( attributes[ name ] === undefined ) attributes[ name ] = []; + + attributes[ name ].push( geometry.attributes[ name ] ); + + attributesCount ++; + + } + + // ensure geometries have the same number of attributes + + if ( attributesCount !== attributesUsed.size ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' ); + return null; + + } + + // gather morph attributes, exit early if they're different + + if ( morphTargetsRelative !== geometry.morphTargetsRelative ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' ); + return null; + + } + + for ( const name in geometry.morphAttributes ) { + + if ( ! morphAttributesUsed.has( name ) ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' ); + return null; + + } + + if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = []; + + morphAttributes[ name ].push( geometry.morphAttributes[ name ] ); + + } + + if ( useGroups ) { + + let count; + + if ( isIndexed ) { + + count = geometry.index.count; + + } else if ( geometry.attributes.position !== undefined ) { + + count = geometry.attributes.position.count; + + } else { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' ); + return null; + + } + + mergedGeometry.addGroup( offset, count, i ); + + offset += count; + + } + + } + + // merge indices + + if ( isIndexed ) { + + let indexOffset = 0; + const mergedIndex = []; + + for ( let i = 0; i < geometries.length; ++ i ) { + + const index = geometries[ i ].index; + + for ( let j = 0; j < index.count; ++ j ) { + + mergedIndex.push( index.getX( j ) + indexOffset ); + + } + + indexOffset += geometries[ i ].attributes.position.count; + + } + + mergedGeometry.setIndex( mergedIndex ); + + } + + // merge attributes + + for ( const name in attributes ) { + + const mergedAttribute = mergeAttributes( attributes[ name ] ); + + if ( ! mergedAttribute ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' ); + return null; + + } + + mergedGeometry.setAttribute( name, mergedAttribute ); + + } + + // merge morph attributes + + for ( const name in morphAttributes ) { + + const numMorphTargets = morphAttributes[ name ][ 0 ].length; + + if ( numMorphTargets === 0 ) break; + + mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}; + mergedGeometry.morphAttributes[ name ] = []; + + for ( let i = 0; i < numMorphTargets; ++ i ) { + + const morphAttributesToMerge = []; + + for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) { + + morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] ); + + } + + const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge ); + + if ( ! mergedMorphAttribute ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' ); + return null; + + } + + mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute ); + + } + + } + + return mergedGeometry; + } -// TODO: Work at the instance level instead of the mesh level? /** - * A tool to handle big scenes efficiently by automatically hiding the objects - * that are not visible to the camera. + * @param {Array} attributes + * @return {BufferAttribute} */ -class ScreenCuller extends Component { - constructor(components, updateInterval = 1000, rtWidth = 512, rtHeight = 512, autoUpdate = true) { - super(components); - this.updateInterval = updateInterval; - this.rtWidth = rtWidth; - this.rtHeight = rtHeight; - this.autoUpdate = autoUpdate; - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - /** Fires after hiding the objects that were not visible to the camera. */ - this.onViewUpdated = new Event(); - /** {@link Component.enabled} */ - this.enabled = true; - /** - * Needs to check whether there are objects that need to be hidden or shown. - * You can bind this to the camera movement, to a certain interval, etc. - */ - this.needsUpdate = false; - /** - * Render the internal scene used to determine the object visibility. Used - * for debugging purposes. - */ - this.renderDebugFrame = false; - this._meshColorMap = new Map(); - this._visibleMeshes = []; - this._colorMeshes = new Map(); - this._meshes = new Map(); - this._currentVisibleMeshes = new Set(); - this._recentlyHiddenMeshes = new Set(); - this._transparentMat = new THREE$1.MeshBasicMaterial({ - transparent: true, - opacity: 0, - }); - this._colors = { r: 0, g: 0, b: 0, i: 0 }; - // Alternative scene and meshes to make the visibility check - this._scene = new THREE$1.Scene(); - /** - * The function that the culler uses to reprocess the scene. Generally it's - * better to call needsUpdate, but you can also call this to force it. - * @param force if true, it will refresh the scene even if needsUpdate is - * not true. - */ - this.updateVisibility = async (force) => { - if (!this.enabled) - return; - if (!this.needsUpdate && !force) - return; - const camera = this.components.camera.get(); - camera.updateMatrix(); - this.renderer.setSize(this.rtWidth, this.rtHeight); - this.renderer.setRenderTarget(this.renderTarget); - this.renderer.render(this._scene, camera); - const context = this.renderer.getContext(); - await readPixelsAsync(context, 0, 0, this.rtWidth, this.rtHeight, context.RGBA, context.UNSIGNED_BYTE, this._buffer); - this.renderer.setRenderTarget(null); - if (this.renderDebugFrame) { - this.renderer.render(this._scene, camera); - } - this.worker.postMessage({ - buffer: this._buffer, - }); - this.needsUpdate = false; - }; - this.handleWorkerMessage = async (event) => { - const colors = event.data.colors; - this._recentlyHiddenMeshes = new Set(this._currentVisibleMeshes); - this._currentVisibleMeshes.clear(); - this._visibleMeshes = []; - // Make found meshes visible - for (const code of colors.values()) { - const mesh = this._meshColorMap.get(code); - if (mesh) { - this._visibleMeshes.push(mesh); - mesh.visible = true; - this._currentVisibleMeshes.add(mesh.uuid); - this._recentlyHiddenMeshes.delete(mesh.uuid); - } - } - // Hide meshes that were visible before but not anymore - for (const uuid of this._recentlyHiddenMeshes) { - const mesh = this._meshes.get(uuid); - if (mesh === undefined) - continue; - mesh.visible = false; - } - await this.onViewUpdated.trigger(); - }; - components.tools.add(ScreenCuller.uuid, this); - this.renderer = new THREE$1.WebGLRenderer(); - const planes = this.components.renderer.clippingPlanes; - this.renderer.clippingPlanes = planes; - this.renderTarget = new THREE$1.WebGLRenderTarget(rtWidth, rtHeight); - this.bufferSize = rtWidth * rtHeight * 4; - this._buffer = new Uint8Array(this.bufferSize); - this.materialCache = new Map(); - const code = ` - addEventListener("message", (event) => { - const { buffer } = event.data; - const colors = new Set(); - for (let i = 0; i < buffer.length; i += 4) { - const r = buffer[i]; - const g = buffer[i + 1]; - const b = buffer[i + 2]; - const code = "" + r + "-" + g + "-" + b; - colors.add(code); +function mergeAttributes( attributes ) { + + let TypedArray; + let itemSize; + let normalized; + let arrayLength = 0; + + for ( let i = 0; i < attributes.length; ++ i ) { + + const attribute = attributes[ i ]; + + if ( attribute.isInterleavedBufferAttribute ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. InterleavedBufferAttributes are not supported.' ); + return null; + + } + + if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; + if ( TypedArray !== attribute.array.constructor ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' ); + return null; + + } + + if ( itemSize === undefined ) itemSize = attribute.itemSize; + if ( itemSize !== attribute.itemSize ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' ); + return null; + + } + + if ( normalized === undefined ) normalized = attribute.normalized; + if ( normalized !== attribute.normalized ) { + + console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' ); + return null; + + } + + arrayLength += attribute.array.length; + + } + + const array = new TypedArray( arrayLength ); + let offset = 0; + + for ( let i = 0; i < attributes.length; ++ i ) { + + array.set( attributes[ i ].array, offset ); + + offset += attributes[ i ].array.length; + + } + + return new BufferAttribute$1( array, itemSize, normalized ); + +} + +class GeometryUtils { + static merge(geometriesByMaterial, splitByBlocks = false) { + const geometriesByMat = []; + const sizes = []; + for (const geometries of geometriesByMaterial) { + const merged = this.mergeGeomsOfSameMaterial(geometries, splitByBlocks); + geometriesByMat.push(merged); + sizes.push(merged.index.count); } - postMessage({ colors }); - }); - `; - const blob = new Blob([code], { type: "application/javascript" }); - this.worker = new Worker(URL.createObjectURL(blob)); - this.worker.addEventListener("message", this.handleWorkerMessage); - if (autoUpdate) - window.setInterval(this.updateVisibility, updateInterval); + const geometry = mergeGeometries(geometriesByMat); + this.setupMaterialGroups(sizes, geometry); + this.cleanUp(geometriesByMat); + return geometry; } - /** - * {@link Component.get}. - * @returns the map of internal meshes used to determine visibility. - */ - get() { - return this._colorMeshes; + // When Three.js exports to glTF, it generates one separate mesh per material. All meshes + // share the same BufferAttributes and have different indices + static async mergeGltfMeshes(meshes) { + const geometry = new BufferGeometry(); + const attributes = meshes[0].geometry.attributes; + this.getMeshesAttributes(geometry, attributes); + this.getMeshesIndices(geometry, meshes); + return geometry; } - /** {@link Disposable.dispose} */ - async dispose() { - this.enabled = false; - this._currentVisibleMeshes.clear(); - this._recentlyHiddenMeshes.clear(); - this._scene.children.length = 0; - this.onViewUpdated.reset(); - this.worker.terminate(); - this.renderer.dispose(); - this.renderTarget.dispose(); - this._buffer = null; - this._transparentMat.dispose(); - this._meshColorMap.clear(); - this._visibleMeshes = []; - for (const id in this.materialCache) { - const material = this.materialCache.get(id); - if (material) { - material.dispose(); - } + static getMeshesAttributes(geometry, attributes) { + // Three.js GLTFExporter exports custom BufferAttributes as underscore lowercase + // eslint-disable-next-line no-underscore-dangle + geometry.setAttribute("blockID", attributes._blockid); + geometry.setAttribute("position", attributes.position); + geometry.setAttribute("normal", attributes.normal); + geometry.groups = []; + } + static getMeshesIndices(geometry, meshes) { + const counter = { index: 0, material: 0 }; + const indices = []; + for (const mesh of meshes) { + const index = mesh.geometry.index; + this.getIndicesOfMesh(index, indices); + this.getMeshGroup(geometry, counter, index); + this.cleanUpMesh(mesh); } - const disposer = this.components.tools.get(Disposer); - for (const id in this._colorMeshes) { - const mesh = this._colorMeshes.get(id); - if (mesh) { - disposer.destroy(mesh); - } + geometry.setIndex(indices); + } + static getMeshGroup(geometry, counter, index) { + geometry.groups.push({ + start: counter.index, + count: index.count, + materialIndex: counter.material++, + }); + counter.index += index.count; + } + static cleanUpMesh(mesh) { + mesh.geometry.setIndex([]); + mesh.geometry.attributes = {}; + mesh.geometry.dispose(); + } + static getIndicesOfMesh(index, indices) { + for (const number of index.array) { + indices.push(number); } - this._colorMeshes.clear(); - this._meshes.clear(); - await this.onDisposed.trigger(ScreenCuller.uuid); - this.onDisposed.reset(); } - /** - * Adds a new mesh to be processed and managed by the culler. - * @mesh the mesh or instanced mesh to add. - */ - add(mesh) { - if (!this.enabled) - return; - const isInstanced = mesh instanceof THREE$1.InstancedMesh; - const { geometry, material } = mesh; - const { r, g, b, code } = this.getNextColor(); - const colorMaterial = this.getMaterial(r, g, b); - let newMaterial; - if (Array.isArray(material)) { - let transparentOnly = true; - const matArray = []; - for (const mat of material) { - if (this.isTransparent(mat)) { - matArray.push(this._transparentMat); - } - else { - transparentOnly = false; - matArray.push(colorMaterial); - } - } - // If we find that all the materials are transparent then we must remove this from analysis - if (transparentOnly) { - colorMaterial.dispose(); - return; - } - newMaterial = matArray; - } - else if (this.isTransparent(material)) { - // This material is transparent, so we must remove it from analysis - colorMaterial.dispose(); - return; - } - else { - newMaterial = colorMaterial; - } - this._meshColorMap.set(code, mesh); - const count = isInstanced ? mesh.count : 1; - const colorMesh = new THREE$1.InstancedMesh(geometry, newMaterial, count); - if (isInstanced) { - colorMesh.instanceMatrix = mesh.instanceMatrix; - } - else { - colorMesh.setMatrixAt(0, new THREE$1.Matrix4()); - } - mesh.visible = false; - colorMesh.applyMatrix4(mesh.matrix); - colorMesh.updateMatrix(); - this._scene.add(colorMesh); - this._colorMeshes.set(mesh.uuid, colorMesh); - this._meshes.set(mesh.uuid, mesh); + static cleanUp(geometries) { + geometries.forEach((geometry) => geometry.dispose()); + geometries.length = 0; } - getMaterial(r, g, b) { - const colorEnabled = THREE$1.ColorManagement.enabled; - THREE$1.ColorManagement.enabled = false; - const code = `rgb(${r}, ${g}, ${b})`; - const color = new THREE$1.Color(code); - let material = this.materialCache.get(code); - const clippingPlanes = this.components.renderer.clippingPlanes; - if (!material) { - material = new THREE$1.MeshBasicMaterial({ - color, - clippingPlanes, - side: THREE$1.DoubleSide, - }); - this.materialCache.set(code, material); + static setupMaterialGroups(sizes, geometry) { + let vertexCounter = 0; + let counter = 0; + for (const size of sizes) { + const group = { + start: vertexCounter, + count: size, + materialIndex: counter++, + }; + geometry.groups.push(group); + vertexCounter += size; } - THREE$1.ColorManagement.enabled = colorEnabled; - return material; } - isTransparent(material) { - return material.transparent && material.opacity < 1; - } - getNextColor() { - if (this._colors.i === 0) { - this._colors.b++; - if (this._colors.b === 256) { - this._colors.b = 0; - this._colors.i = 1; - } + static mergeGeomsOfSameMaterial(geometries, splitByBlocks) { + this.checkAllGeometriesAreIndexed(geometries); + if (splitByBlocks) { + this.splitByBlocks(geometries); } - if (this._colors.i === 1) { - this._colors.g++; - this._colors.i = 0; - if (this._colors.g === 256) { - this._colors.g = 0; - this._colors.i = 2; - } + const merged = mergeGeometries(geometries); + this.cleanUp(geometries); + return merged; + } + static splitByBlocks(geometries) { + let i = 0; + for (const geometry of geometries) { + const size = geometry.attributes.position.count; + // TODO: Substitute blockID attribute by block id map + const array = new Uint16Array(size).fill(i++); + geometry.setAttribute("blockID", new BufferAttribute$1(array, 1)); } - if (this._colors.i === 2) { - this._colors.r++; - this._colors.i = 1; - if (this._colors.r === 256) { - this._colors.r = 0; - this._colors.i = 0; + } + static checkAllGeometriesAreIndexed(geometries) { + for (const geometry of geometries) { + if (!geometry.index) { + throw new Error("All geometries must be indexed!"); } } - return { - r: this._colors.r, - g: this._colors.g, - b: this._colors.b, - code: `${this._colors.r}-${this._colors.g}-${this._colors.b}`, - }; } } -ScreenCuller.uuid = "69f2a50d-c266-44fc-b1bd-fa4d34be89e6"; -ToolComponent.libraryUUIDs.add(ScreenCuller.uuid); -/* - * Dexie.js - a minimalistic wrapper for IndexedDB - * =============================================== - * - * By David Fahlander, david.fahlander@gmail.com - * - * Version 3.2.4, Tue May 30 2023 - * - * https://dexie.org +/** + * The KHR_mesh_quantization extension allows these extra attribute component types * - * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ + * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes */ - -const _global = typeof globalThis !== 'undefined' ? globalThis : - typeof self !== 'undefined' ? self : - typeof window !== 'undefined' ? window : - global; +const KHR_mesh_quantization_ExtraAttrTypes = { + POSITION: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'unsigned byte normalized', + 'short', + 'short normalized', + 'unsigned short', + 'unsigned short normalized', + ], + NORMAL: [ + 'byte normalized', + 'short normalized', + ], + TANGENT: [ + 'byte normalized', + 'short normalized', + ], + TEXCOORD: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'short', + 'short normalized', + 'unsigned short', + ], +}; + + +class GLTFExporter { + + constructor() { + + this.pluginCallbacks = []; + + this.register( function ( writer ) { + + return new GLTFLightExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsUnlitExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsTransmissionExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsVolumeExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsIorExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsSpecularExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsClearcoatExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsIridescenceExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsSheenExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsEmissiveStrengthExtension( writer ); + + } ); + + } + + register( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + + this.pluginCallbacks.push( callback ); + + } + + return this; + + } + + unregister( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + + } + + return this; + + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Function} onError Callback on errors + * @param {Object} options options + */ + parse( input, onDone, onError, options ) { + + const writer = new GLTFWriter(); + const plugins = []; + + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + + plugins.push( this.pluginCallbacks[ i ]( writer ) ); + + } + + writer.setPlugins( plugins ); + writer.write( input, onDone, options ).catch( onError ); + + } + + parseAsync( input, options ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.parse( input, resolve, reject, options ); + + } ); + + } -const keys = Object.keys; -const isArray = Array.isArray; -if (typeof Promise !== 'undefined' && !_global.Promise) { - _global.Promise = Promise; } -function extend(obj, extension) { - if (typeof extension !== 'object') - return obj; - keys(extension).forEach(function (key) { - obj[key] = extension[key]; - }); - return obj; + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + BYTE: 0x1400, + UNSIGNED_BYTE: 0x1401, + SHORT: 0x1402, + UNSIGNED_SHORT: 0x1403, + INT: 0x1404, + UNSIGNED_INT: 0x1405, + FLOAT: 0x1406, + + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 +}; + +const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; + +const THREE_TO_WEBGL = {}; + +THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; +THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; +THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; +THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; +THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; +THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + +THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; +THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; +THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + +const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' +}; + +const DEFAULT_SPECULAR_COLOR = new Color(); + +// GLB constants +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + +const GLB_HEADER_BYTES = 12; +const GLB_HEADER_MAGIC = 0x46546C67; +const GLB_VERSION = 2; + +const GLB_CHUNK_PREFIX_BYTES = 8; +const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; +const GLB_CHUNK_TYPE_BIN = 0x004E4942; + +//------------------------------------------------------------------------------ +// Utility functions +//------------------------------------------------------------------------------ + +/** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ +function equalArray( array1, array2 ) { + + return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + + return element === array2[ index ]; + + } ); + } -const getProto = Object.getPrototypeOf; -const _hasOwn = {}.hasOwnProperty; -function hasOwn(obj, prop) { - return _hasOwn.call(obj, prop); + +/** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ +function stringToArrayBuffer( text ) { + + return new TextEncoder().encode( text ).buffer; + } -function props(proto, extension) { - if (typeof extension === 'function') - extension = extension(getProto(proto)); - (typeof Reflect === "undefined" ? keys : Reflect.ownKeys)(extension).forEach(key => { - setProp(proto, key, extension[key]); - }); + +/** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ +function isIdentityMatrix( matrix ) { + + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); + } -const defineProperty = Object.defineProperty; -function setProp(obj, prop, functionOrGetSet, options) { - defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, "get") && typeof functionOrGetSet.get === 'function' ? - { get: functionOrGetSet.get, set: functionOrGetSet.set, configurable: true } : - { value: functionOrGetSet, configurable: true, writable: true }, options)); + +/** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ +function getMinMax( attribute, start, count ) { + + const output = { + + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + + }; + + for ( let i = start; i < start + count; i ++ ) { + + for ( let a = 0; a < attribute.itemSize; a ++ ) { + + let value; + + if ( attribute.itemSize > 4 ) { + + // no support for interleaved data for itemSize > 4 + + value = attribute.array[ i * attribute.itemSize + a ]; + + } else { + + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); + + if ( attribute.normalized === true ) { + + value = MathUtils.normalize( value, attribute.array ); + + } + + } + + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); + + } + + } + + return output; + } -function derive(Child) { - return { - from: function (Parent) { - Child.prototype = Object.create(Parent.prototype); - setProp(Child.prototype, "constructor", Child); - return { - extend: props.bind(null, Child.prototype) - }; - } - }; + +/** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ +function getPaddedBufferSize( bufferSize ) { + + return Math.ceil( bufferSize / 4 ) * 4; + } -const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -function getPropertyDescriptor(obj, prop) { - const pd = getOwnPropertyDescriptor(obj, prop); - let proto; - return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); -} -const _slice = [].slice; -function slice(args, start, end) { - return _slice.call(args, start, end); -} -function override(origFunc, overridedFactory) { - return overridedFactory(origFunc); -} -function assert(b) { - if (!b) - throw new Error("Assertion Failed"); -} -function asap$1(fn) { - if (_global.setImmediate) - setImmediate(fn); - else - setTimeout(fn, 0); -} -function arrayToObject(array, extractor) { - return array.reduce((result, item, i) => { - var nameAndValue = extractor(item, i); - if (nameAndValue) - result[nameAndValue[0]] = nameAndValue[1]; - return result; - }, {}); -} -function tryCatch(fn, onerror, args) { - try { - fn.apply(null, args); - } - catch (ex) { - onerror && onerror(ex); - } -} -function getByKeyPath(obj, keyPath) { - if (hasOwn(obj, keyPath)) - return obj[keyPath]; - if (!keyPath) - return obj; - if (typeof keyPath !== 'string') { - var rv = []; - for (var i = 0, l = keyPath.length; i < l; ++i) { - var val = getByKeyPath(obj, keyPath[i]); - rv.push(val); - } - return rv; - } - var period = keyPath.indexOf('.'); - if (period !== -1) { - var innerObj = obj[keyPath.substr(0, period)]; - return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); - } - return undefined; -} -function setByKeyPath(obj, keyPath, value) { - if (!obj || keyPath === undefined) - return; - if ('isFrozen' in Object && Object.isFrozen(obj)) - return; - if (typeof keyPath !== 'string' && 'length' in keyPath) { - assert(typeof value !== 'string' && 'length' in value); - for (var i = 0, l = keyPath.length; i < l; ++i) { - setByKeyPath(obj, keyPath[i], value[i]); - } - } - else { - var period = keyPath.indexOf('.'); - if (period !== -1) { - var currentKeyPath = keyPath.substr(0, period); - var remainingKeyPath = keyPath.substr(period + 1); - if (remainingKeyPath === "") - if (value === undefined) { - if (isArray(obj) && !isNaN(parseInt(currentKeyPath))) - obj.splice(currentKeyPath, 1); - else - delete obj[currentKeyPath]; - } - else - obj[currentKeyPath] = value; - else { - var innerObj = obj[currentKeyPath]; - if (!innerObj || !hasOwn(obj, currentKeyPath)) - innerObj = (obj[currentKeyPath] = {}); - setByKeyPath(innerObj, remainingKeyPath, value); - } - } - else { - if (value === undefined) { - if (isArray(obj) && !isNaN(parseInt(keyPath))) - obj.splice(keyPath, 1); - else - delete obj[keyPath]; - } - else - obj[keyPath] = value; - } - } -} -function delByKeyPath(obj, keyPath) { - if (typeof keyPath === 'string') - setByKeyPath(obj, keyPath, undefined); - else if ('length' in keyPath) - [].map.call(keyPath, function (kp) { - setByKeyPath(obj, kp, undefined); - }); -} -function shallowClone(obj) { - var rv = {}; - for (var m in obj) { - if (hasOwn(obj, m)) - rv[m] = obj[m]; - } - return rv; -} -const concat = [].concat; -function flatten(a) { - return concat.apply([], a); -} -const intrinsicTypeNames = "Boolean,String,Date,RegExp,Blob,File,FileList,FileSystemFileHandle,ArrayBuffer,DataView,Uint8ClampedArray,ImageBitmap,ImageData,Map,Set,CryptoKey" - .split(',').concat(flatten([8, 16, 32, 64].map(num => ["Int", "Uint", "Float"].map(t => t + num + "Array")))).filter(t => _global[t]); -const intrinsicTypes = intrinsicTypeNames.map(t => _global[t]); -arrayToObject(intrinsicTypeNames, x => [x, true]); -let circularRefs = null; -function deepClone(any) { - circularRefs = typeof WeakMap !== 'undefined' && new WeakMap(); - const rv = innerDeepClone(any); - circularRefs = null; - return rv; -} -function innerDeepClone(any) { - if (!any || typeof any !== 'object') - return any; - let rv = circularRefs && circularRefs.get(any); - if (rv) - return rv; - if (isArray(any)) { - rv = []; - circularRefs && circularRefs.set(any, rv); - for (var i = 0, l = any.length; i < l; ++i) { - rv.push(innerDeepClone(any[i])); - } - } - else if (intrinsicTypes.indexOf(any.constructor) >= 0) { - rv = any; - } - else { - const proto = getProto(any); - rv = proto === Object.prototype ? {} : Object.create(proto); - circularRefs && circularRefs.set(any, rv); - for (var prop in any) { - if (hasOwn(any, prop)) { - rv[prop] = innerDeepClone(any[prop]); - } - } - } - return rv; -} -const { toString } = {}; -function toStringTag(o) { - return toString.call(o).slice(8, -1); -} -const iteratorSymbol = typeof Symbol !== 'undefined' ? - Symbol.iterator : - '@@iterator'; -const getIteratorOf = typeof iteratorSymbol === "symbol" ? function (x) { - var i; - return x != null && (i = x[iteratorSymbol]) && i.apply(x); -} : function () { return null; }; -const NO_CHAR_ARRAY = {}; -function getArrayOf(arrayLike) { - var i, a, x, it; - if (arguments.length === 1) { - if (isArray(arrayLike)) - return arrayLike.slice(); - if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') - return [arrayLike]; - if ((it = getIteratorOf(arrayLike))) { - a = []; - while ((x = it.next()), !x.done) - a.push(x.value); - return a; - } - if (arrayLike == null) - return [arrayLike]; - i = arrayLike.length; - if (typeof i === 'number') { - a = new Array(i); - while (i--) - a[i] = arrayLike[i]; - return a; - } - return [arrayLike]; - } - i = arguments.length; - a = new Array(i); - while (i--) - a[i] = arguments[i]; - return a; -} -const isAsyncFunction = typeof Symbol !== 'undefined' - ? (fn) => fn[Symbol.toStringTag] === 'AsyncFunction' - : () => false; -var debug = typeof location !== 'undefined' && - /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); -function setDebug(value, filter) { - debug = value; - libraryFilter = filter; -} -var libraryFilter = () => true; -const NEEDS_THROW_FOR_STACK = !new Error("").stack; -function getErrorWithStack() { - if (NEEDS_THROW_FOR_STACK) - try { - getErrorWithStack.arguments; - throw new Error(); - } - catch (e) { - return e; - } - return new Error(); -} -function prettyStack(exception, numIgnoredFrames) { - var stack = exception.stack; - if (!stack) - return ""; - numIgnoredFrames = (numIgnoredFrames || 0); - if (stack.indexOf(exception.name) === 0) - numIgnoredFrames += (exception.name + exception.message).split('\n').length; - return stack.split('\n') - .slice(numIgnoredFrames) - .filter(libraryFilter) - .map(frame => "\n" + frame) - .join(''); -} +/** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ +function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { -var dexieErrorNames = [ - 'Modify', - 'Bulk', - 'OpenFailed', - 'VersionChange', - 'Schema', - 'Upgrade', - 'InvalidTable', - 'MissingAPI', - 'NoSuchDatabase', - 'InvalidArgument', - 'SubTransaction', - 'Unsupported', - 'Internal', - 'DatabaseClosed', - 'PrematureCommit', - 'ForeignAwait' -]; -var idbDomErrorNames = [ - 'Unknown', - 'Constraint', - 'Data', - 'TransactionInactive', - 'ReadOnly', - 'Version', - 'NotFound', - 'InvalidState', - 'InvalidAccess', - 'Abort', - 'Timeout', - 'QuotaExceeded', - 'Syntax', - 'DataClone' -]; -var errorList = dexieErrorNames.concat(idbDomErrorNames); -var defaultTexts = { - VersionChanged: "Database version changed by other database connection", - DatabaseClosed: "Database has been closed", - Abort: "Transaction aborted", - TransactionInactive: "Transaction has already completed or failed", - MissingAPI: "IndexedDB API missing. Please visit https://tinyurl.com/y2uuvskb" -}; -function DexieError(name, msg) { - this._e = getErrorWithStack(); - this.name = name; - this.message = msg; -} -derive(DexieError).from(Error).extend({ - stack: { - get: function () { - return this._stack || - (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); - } - }, - toString: function () { return this.name + ": " + this.message; } -}); -function getMultiErrorMessage(msg, failures) { - return msg + ". Errors: " + Object.keys(failures) - .map(key => failures[key].toString()) - .filter((v, i, s) => s.indexOf(v) === i) - .join('\n'); -} -function ModifyError(msg, failures, successCount, failedKeys) { - this._e = getErrorWithStack(); - this.failures = failures; - this.failedKeys = failedKeys; - this.successCount = successCount; - this.message = getMultiErrorMessage(msg, failures); -} -derive(ModifyError).from(DexieError); -function BulkError(msg, failures) { - this._e = getErrorWithStack(); - this.name = "BulkError"; - this.failures = Object.keys(failures).map(pos => failures[pos]); - this.failuresByPos = failures; - this.message = getMultiErrorMessage(msg, failures); -} -derive(BulkError).from(DexieError); -var errnames = errorList.reduce((obj, name) => (obj[name] = name + "Error", obj), {}); -const BaseException = DexieError; -var exceptions = errorList.reduce((obj, name) => { - var fullName = name + "Error"; - function DexieError(msgOrInner, inner) { - this._e = getErrorWithStack(); - this.name = fullName; - if (!msgOrInner) { - this.message = defaultTexts[name] || fullName; - this.inner = null; - } - else if (typeof msgOrInner === 'string') { - this.message = `${msgOrInner}${!inner ? '' : '\n ' + inner}`; - this.inner = inner || null; - } - else if (typeof msgOrInner === 'object') { - this.message = `${msgOrInner.name} ${msgOrInner.message}`; - this.inner = msgOrInner; - } - } - derive(DexieError).from(BaseException); - obj[name] = DexieError; - return obj; -}, {}); -exceptions.Syntax = SyntaxError; -exceptions.Type = TypeError; -exceptions.Range = RangeError; -var exceptionMap = idbDomErrorNames.reduce((obj, name) => { - obj[name + "Error"] = exceptions[name]; - return obj; -}, {}); -function mapError(domError, message) { - if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name]) - return domError; - var rv = new exceptionMap[domError.name](message || domError.message, domError); - if ("stack" in domError) { - setProp(rv, "stack", { get: function () { - return this.inner.stack; - } }); - } - return rv; -} -var fullNameExceptions = errorList.reduce((obj, name) => { - if (["Syntax", "Type", "Range"].indexOf(name) === -1) - obj[name + "Error"] = exceptions[name]; - return obj; -}, {}); -fullNameExceptions.ModifyError = ModifyError; -fullNameExceptions.DexieError = DexieError; -fullNameExceptions.BulkError = BulkError; + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + + if ( paddedLength !== arrayBuffer.byteLength ) { + + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); + + if ( paddingByte !== 0 ) { + + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + + array[ i ] = paddingByte; + + } + + } + + return array.buffer; + + } + + return arrayBuffer; -function nop() { } -function mirror(val) { return val; } -function pureFunctionChain(f1, f2) { - if (f1 == null || f1 === mirror) - return f2; - return function (val) { - return f2(f1(val)); - }; -} -function callBoth(on1, on2) { - return function () { - on1.apply(this, arguments); - on2.apply(this, arguments); - }; -} -function hookCreatingChain(f1, f2) { - if (f1 === nop) - return f2; - return function () { - var res = f1.apply(this, arguments); - if (res !== undefined) - arguments[0] = res; - var onsuccess = this.onsuccess, - onerror = this.onerror; - this.onsuccess = null; - this.onerror = null; - var res2 = f2.apply(this, arguments); - if (onsuccess) - this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; - if (onerror) - this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; - return res2 !== undefined ? res2 : res; - }; -} -function hookDeletingChain(f1, f2) { - if (f1 === nop) - return f2; - return function () { - f1.apply(this, arguments); - var onsuccess = this.onsuccess, - onerror = this.onerror; - this.onsuccess = this.onerror = null; - f2.apply(this, arguments); - if (onsuccess) - this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; - if (onerror) - this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; - }; -} -function hookUpdatingChain(f1, f2) { - if (f1 === nop) - return f2; - return function (modifications) { - var res = f1.apply(this, arguments); - extend(modifications, res); - var onsuccess = this.onsuccess, - onerror = this.onerror; - this.onsuccess = null; - this.onerror = null; - var res2 = f2.apply(this, arguments); - if (onsuccess) - this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; - if (onerror) - this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; - return res === undefined ? - (res2 === undefined ? undefined : res2) : - (extend(res, res2)); - }; -} -function reverseStoppableEventChain(f1, f2) { - if (f1 === nop) - return f2; - return function () { - if (f2.apply(this, arguments) === false) - return false; - return f1.apply(this, arguments); - }; } -function promisableChain(f1, f2) { - if (f1 === nop) - return f2; - return function () { - var res = f1.apply(this, arguments); - if (res && typeof res.then === 'function') { - var thiz = this, i = arguments.length, args = new Array(i); - while (i--) - args[i] = arguments[i]; - return res.then(function () { - return f2.apply(thiz, args); - }); - } - return f2.apply(this, arguments); - }; + +function getCanvas() { + + if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { + + return new OffscreenCanvas( 1, 1 ); + + } + + return document.createElement( 'canvas' ); + } -var INTERNAL = {}; -const LONG_STACKS_CLIP_LIMIT = 100, -MAX_LONG_STACKS = 20, ZONE_ECHO_LIMIT = 100, [resolvedNativePromise, nativePromiseProto, resolvedGlobalPromise] = typeof Promise === 'undefined' ? - [] : - (() => { - let globalP = Promise.resolve(); - if (typeof crypto === 'undefined' || !crypto.subtle) - return [globalP, getProto(globalP), globalP]; - const nativeP = crypto.subtle.digest("SHA-512", new Uint8Array([0])); - return [ - nativeP, - getProto(nativeP), - globalP - ]; - })(), nativePromiseThen = nativePromiseProto && nativePromiseProto.then; -const NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; -const patchGlobalPromise = !!resolvedGlobalPromise; -var stack_being_generated = false; -var schedulePhysicalTick = resolvedGlobalPromise ? - () => { resolvedGlobalPromise.then(physicalTick); } - : - _global.setImmediate ? - setImmediate.bind(null, physicalTick) : - _global.MutationObserver ? - () => { - var hiddenDiv = document.createElement("div"); - (new MutationObserver(() => { - physicalTick(); - hiddenDiv = null; - })).observe(hiddenDiv, { attributes: true }); - hiddenDiv.setAttribute('i', '1'); - } : - () => { setTimeout(physicalTick, 0); }; -var asap = function (callback, args) { - microtickQueue.push([callback, args]); - if (needsNewPhysicalTick) { - schedulePhysicalTick(); - needsNewPhysicalTick = false; - } -}; -var isOutsideMicroTick = true, -needsNewPhysicalTick = true, -unhandledErrors = [], -rejectingErrors = [], -currentFulfiller = null, rejectionMapper = mirror; -var globalPSD = { - id: 'global', - global: true, - ref: 0, - unhandleds: [], - onunhandled: globalError, - pgp: false, - env: {}, - finalize: function () { - this.unhandleds.forEach(uh => { - try { - globalError(uh[0], uh[1]); - } - catch (e) { } - }); - } -}; -var PSD = globalPSD; -var microtickQueue = []; -var numScheduledCalls = 0; -var tickFinalizers = []; -function DexiePromise(fn) { - if (typeof this !== 'object') - throw new TypeError('Promises must be constructed via new'); - this._listeners = []; - this.onuncatched = nop; - this._lib = false; - var psd = (this._PSD = PSD); - if (debug) { - this._stackHolder = getErrorWithStack(); - this._prev = null; - this._numPrev = 0; - } - if (typeof fn !== 'function') { - if (fn !== INTERNAL) - throw new TypeError('Not a function'); - this._state = arguments[1]; - this._value = arguments[2]; - if (this._state === false) - handleRejection(this, this._value); - return; - } - this._state = null; - this._value = null; - ++psd.ref; - executePromiseTask(this, fn); -} -const thenProp = { - get: function () { - var psd = PSD, microTaskId = totalEchoes; - function then(onFulfilled, onRejected) { - var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); - const cleanup = possibleAwait && !decrementExpectedAwaits(); - var rv = new DexiePromise((resolve, reject) => { - propagateToListener(this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait, cleanup), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait, cleanup), resolve, reject, psd)); - }); - debug && linkToPreviousPromise(rv, this); - return rv; - } - then.prototype = INTERNAL; - return then; - }, - set: function (value) { - setProp(this, 'then', value && value.prototype === INTERNAL ? - thenProp : - { - get: function () { - return value; - }, - set: thenProp.set - }); - } -}; -props(DexiePromise.prototype, { - then: thenProp, - _then: function (onFulfilled, onRejected) { - propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); - }, - catch: function (onRejected) { - if (arguments.length === 1) - return this.then(null, onRejected); - var type = arguments[0], handler = arguments[1]; - return typeof type === 'function' ? this.then(null, err => - err instanceof type ? handler(err) : PromiseReject(err)) - : this.then(null, err => - err && err.name === type ? handler(err) : PromiseReject(err)); - }, - finally: function (onFinally) { - return this.then(value => { - onFinally(); - return value; - }, err => { - onFinally(); - return PromiseReject(err); - }); - }, - stack: { - get: function () { - if (this._stack) - return this._stack; - try { - stack_being_generated = true; - var stacks = getStack(this, [], MAX_LONG_STACKS); - var stack = stacks.join("\nFrom previous: "); - if (this._state !== null) - this._stack = stack; - return stack; - } - finally { - stack_being_generated = false; - } - } - }, - timeout: function (ms, msg) { - return ms < Infinity ? - new DexiePromise((resolve, reject) => { - var handle = setTimeout(() => reject(new exceptions.Timeout(msg)), ms); - this.then(resolve, reject).finally(clearTimeout.bind(null, handle)); - }) : this; - } -}); -if (typeof Symbol !== 'undefined' && Symbol.toStringTag) - setProp(DexiePromise.prototype, Symbol.toStringTag, 'Dexie.Promise'); -globalPSD.env = snapShot(); -function Listener(onFulfilled, onRejected, resolve, reject, zone) { - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; - this.onRejected = typeof onRejected === 'function' ? onRejected : null; - this.resolve = resolve; - this.reject = reject; - this.psd = zone; -} -props(DexiePromise, { - all: function () { - var values = getArrayOf.apply(null, arguments) - .map(onPossibleParallellAsync); - return new DexiePromise(function (resolve, reject) { - if (values.length === 0) - resolve([]); - var remaining = values.length; - values.forEach((a, i) => DexiePromise.resolve(a).then(x => { - values[i] = x; - if (!--remaining) - resolve(values); - }, reject)); - }); - }, - resolve: value => { - if (value instanceof DexiePromise) - return value; - if (value && typeof value.then === 'function') - return new DexiePromise((resolve, reject) => { - value.then(resolve, reject); - }); - var rv = new DexiePromise(INTERNAL, true, value); - linkToPreviousPromise(rv, currentFulfiller); - return rv; - }, - reject: PromiseReject, - race: function () { - var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); - return new DexiePromise((resolve, reject) => { - values.map(value => DexiePromise.resolve(value).then(resolve, reject)); - }); - }, - PSD: { - get: () => PSD, - set: value => PSD = value - }, - totalEchoes: { get: () => totalEchoes }, - newPSD: newScope, - usePSD: usePSD, - scheduler: { - get: () => asap, - set: value => { asap = value; } - }, - rejectionMapper: { - get: () => rejectionMapper, - set: value => { rejectionMapper = value; } - }, - follow: (fn, zoneProps) => { - return new DexiePromise((resolve, reject) => { - return newScope((resolve, reject) => { - var psd = PSD; - psd.unhandleds = []; - psd.onunhandled = reject; - psd.finalize = callBoth(function () { - run_at_end_of_this_or_next_physical_tick(() => { - this.unhandleds.length === 0 ? resolve() : reject(this.unhandleds[0]); - }); - }, psd.finalize); - fn(); - }, zoneProps, resolve, reject); - }); - } -}); -if (NativePromise) { - if (NativePromise.allSettled) - setProp(DexiePromise, "allSettled", function () { - const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); - return new DexiePromise(resolve => { - if (possiblePromises.length === 0) - resolve([]); - let remaining = possiblePromises.length; - const results = new Array(remaining); - possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(value => results[i] = { status: "fulfilled", value }, reason => results[i] = { status: "rejected", reason }) - .then(() => --remaining || resolve(results))); - }); - }); - if (NativePromise.any && typeof AggregateError !== 'undefined') - setProp(DexiePromise, "any", function () { - const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); - return new DexiePromise((resolve, reject) => { - if (possiblePromises.length === 0) - reject(new AggregateError([])); - let remaining = possiblePromises.length; - const failures = new Array(remaining); - possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(value => resolve(value), failure => { - failures[i] = failure; - if (!--remaining) - reject(new AggregateError(failures)); - })); - }); - }); -} -function executePromiseTask(promise, fn) { - try { - fn(value => { - if (promise._state !== null) - return; - if (value === promise) - throw new TypeError('A promise cannot be resolved with itself.'); - var shouldExecuteTick = promise._lib && beginMicroTickScope(); - if (value && typeof value.then === 'function') { - executePromiseTask(promise, (resolve, reject) => { - value instanceof DexiePromise ? - value._then(resolve, reject) : - value.then(resolve, reject); - }); - } - else { - promise._state = true; - promise._value = value; - propagateAllListeners(promise); - } - if (shouldExecuteTick) - endMicroTickScope(); - }, handleRejection.bind(null, promise)); - } - catch (ex) { - handleRejection(promise, ex); - } -} -function handleRejection(promise, reason) { - rejectingErrors.push(reason); - if (promise._state !== null) - return; - var shouldExecuteTick = promise._lib && beginMicroTickScope(); - reason = rejectionMapper(reason); - promise._state = false; - promise._value = reason; - debug && reason !== null && typeof reason === 'object' && !reason._promise && tryCatch(() => { - var origProp = getPropertyDescriptor(reason, "stack"); - reason._promise = promise; - setProp(reason, "stack", { - get: () => stack_being_generated ? - origProp && (origProp.get ? - origProp.get.apply(reason) : - origProp.value) : - promise.stack - }); - }); - addPossiblyUnhandledError(promise); - propagateAllListeners(promise); - if (shouldExecuteTick) - endMicroTickScope(); -} -function propagateAllListeners(promise) { - var listeners = promise._listeners; - promise._listeners = []; - for (var i = 0, len = listeners.length; i < len; ++i) { - propagateToListener(promise, listeners[i]); - } - var psd = promise._PSD; - --psd.ref || psd.finalize(); - if (numScheduledCalls === 0) { - ++numScheduledCalls; - asap(() => { - if (--numScheduledCalls === 0) - finalizePhysicalTick(); - }, []); - } -} -function propagateToListener(promise, listener) { - if (promise._state === null) { - promise._listeners.push(listener); - return; - } - var cb = promise._state ? listener.onFulfilled : listener.onRejected; - if (cb === null) { - return (promise._state ? listener.resolve : listener.reject)(promise._value); - } - ++listener.psd.ref; - ++numScheduledCalls; - asap(callListener, [cb, promise, listener]); -} -function callListener(cb, promise, listener) { - try { - currentFulfiller = promise; - var ret, value = promise._value; - if (promise._state) { - ret = cb(value); - } - else { - if (rejectingErrors.length) - rejectingErrors = []; - ret = cb(value); - if (rejectingErrors.indexOf(value) === -1) - markErrorAsHandled(promise); - } - listener.resolve(ret); - } - catch (e) { - listener.reject(e); - } - finally { - currentFulfiller = null; - if (--numScheduledCalls === 0) - finalizePhysicalTick(); - --listener.psd.ref || listener.psd.finalize(); - } -} -function getStack(promise, stacks, limit) { - if (stacks.length === limit) - return stacks; - var stack = ""; - if (promise._state === false) { - var failure = promise._value, errorName, message; - if (failure != null) { - errorName = failure.name || "Error"; - message = failure.message || failure; - stack = prettyStack(failure, 0); - } - else { - errorName = failure; - message = ""; - } - stacks.push(errorName + (message ? ": " + message : "") + stack); - } - if (debug) { - stack = prettyStack(promise._stackHolder, 2); - if (stack && stacks.indexOf(stack) === -1) - stacks.push(stack); - if (promise._prev) - getStack(promise._prev, stacks, limit); - } - return stacks; -} -function linkToPreviousPromise(promise, prev) { - var numPrev = prev ? prev._numPrev + 1 : 0; - if (numPrev < LONG_STACKS_CLIP_LIMIT) { - promise._prev = prev; - promise._numPrev = numPrev; - } -} -function physicalTick() { - beginMicroTickScope() && endMicroTickScope(); -} -function beginMicroTickScope() { - var wasRootExec = isOutsideMicroTick; - isOutsideMicroTick = false; - needsNewPhysicalTick = false; - return wasRootExec; -} -function endMicroTickScope() { - var callbacks, i, l; - do { - while (microtickQueue.length > 0) { - callbacks = microtickQueue; - microtickQueue = []; - l = callbacks.length; - for (i = 0; i < l; ++i) { - var item = callbacks[i]; - item[0].apply(null, item[1]); - } - } - } while (microtickQueue.length > 0); - isOutsideMicroTick = true; - needsNewPhysicalTick = true; -} -function finalizePhysicalTick() { - var unhandledErrs = unhandledErrors; - unhandledErrors = []; - unhandledErrs.forEach(p => { - p._PSD.onunhandled.call(null, p._value, p); - }); - var finalizers = tickFinalizers.slice(0); - var i = finalizers.length; - while (i) - finalizers[--i](); -} -function run_at_end_of_this_or_next_physical_tick(fn) { - function finalizer() { - fn(); - tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1); - } - tickFinalizers.push(finalizer); - ++numScheduledCalls; - asap(() => { - if (--numScheduledCalls === 0) - finalizePhysicalTick(); - }, []); -} -function addPossiblyUnhandledError(promise) { - if (!unhandledErrors.some(p => p._value === promise._value)) - unhandledErrors.push(promise); -} -function markErrorAsHandled(promise) { - var i = unhandledErrors.length; - while (i) - if (unhandledErrors[--i]._value === promise._value) { - unhandledErrors.splice(i, 1); - return; - } -} -function PromiseReject(reason) { - return new DexiePromise(INTERNAL, false, reason); -} -function wrap(fn, errorCatcher) { - var psd = PSD; - return function () { - var wasRootExec = beginMicroTickScope(), outerScope = PSD; - try { - switchToZone(psd, true); - return fn.apply(this, arguments); - } - catch (e) { - errorCatcher && errorCatcher(e); - } - finally { - switchToZone(outerScope, false); - if (wasRootExec) - endMicroTickScope(); - } - }; -} -const task = { awaits: 0, echoes: 0, id: 0 }; -var taskCounter = 0; -var zoneStack = []; -var zoneEchoes = 0; -var totalEchoes = 0; -var zone_id_counter = 0; -function newScope(fn, props, a1, a2) { - var parent = PSD, psd = Object.create(parent); - psd.parent = parent; - psd.ref = 0; - psd.global = false; - psd.id = ++zone_id_counter; - var globalEnv = globalPSD.env; - psd.env = patchGlobalPromise ? { - Promise: DexiePromise, - PromiseProp: { value: DexiePromise, configurable: true, writable: true }, - all: DexiePromise.all, - race: DexiePromise.race, - allSettled: DexiePromise.allSettled, - any: DexiePromise.any, - resolve: DexiePromise.resolve, - reject: DexiePromise.reject, - nthen: getPatchedPromiseThen(globalEnv.nthen, psd), - gthen: getPatchedPromiseThen(globalEnv.gthen, psd) - } : {}; - if (props) - extend(psd, props); - ++parent.ref; - psd.finalize = function () { - --this.parent.ref || this.parent.finalize(); - }; - var rv = usePSD(psd, fn, a1, a2); - if (psd.ref === 0) - psd.finalize(); - return rv; -} -function incrementExpectedAwaits() { - if (!task.id) - task.id = ++taskCounter; - ++task.awaits; - task.echoes += ZONE_ECHO_LIMIT; - return task.id; -} -function decrementExpectedAwaits() { - if (!task.awaits) - return false; - if (--task.awaits === 0) - task.id = 0; - task.echoes = task.awaits * ZONE_ECHO_LIMIT; - return true; -} -if (('' + nativePromiseThen).indexOf('[native code]') === -1) { - incrementExpectedAwaits = decrementExpectedAwaits = nop; -} -function onPossibleParallellAsync(possiblePromise) { - if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { - incrementExpectedAwaits(); - return possiblePromise.then(x => { - decrementExpectedAwaits(); - return x; - }, e => { - decrementExpectedAwaits(); - return rejection(e); - }); - } - return possiblePromise; -} -function zoneEnterEcho(targetZone) { - ++totalEchoes; - if (!task.echoes || --task.echoes === 0) { - task.echoes = task.id = 0; - } - zoneStack.push(PSD); - switchToZone(targetZone, true); -} -function zoneLeaveEcho() { - var zone = zoneStack[zoneStack.length - 1]; - zoneStack.pop(); - switchToZone(zone, false); -} -function switchToZone(targetZone, bEnteringZone) { - var currentZone = PSD; - if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (!--zoneEchoes || targetZone !== PSD)) { - enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); - } - if (targetZone === PSD) - return; - PSD = targetZone; - if (currentZone === globalPSD) - globalPSD.env = snapShot(); - if (patchGlobalPromise) { - var GlobalPromise = globalPSD.env.Promise; - var targetEnv = targetZone.env; - nativePromiseProto.then = targetEnv.nthen; - GlobalPromise.prototype.then = targetEnv.gthen; - if (currentZone.global || targetZone.global) { - Object.defineProperty(_global, 'Promise', targetEnv.PromiseProp); - GlobalPromise.all = targetEnv.all; - GlobalPromise.race = targetEnv.race; - GlobalPromise.resolve = targetEnv.resolve; - GlobalPromise.reject = targetEnv.reject; - if (targetEnv.allSettled) - GlobalPromise.allSettled = targetEnv.allSettled; - if (targetEnv.any) - GlobalPromise.any = targetEnv.any; - } - } -} -function snapShot() { - var GlobalPromise = _global.Promise; - return patchGlobalPromise ? { - Promise: GlobalPromise, - PromiseProp: Object.getOwnPropertyDescriptor(_global, "Promise"), - all: GlobalPromise.all, - race: GlobalPromise.race, - allSettled: GlobalPromise.allSettled, - any: GlobalPromise.any, - resolve: GlobalPromise.resolve, - reject: GlobalPromise.reject, - nthen: nativePromiseProto.then, - gthen: GlobalPromise.prototype.then - } : {}; -} -function usePSD(psd, fn, a1, a2, a3) { - var outerScope = PSD; - try { - switchToZone(psd, true); - return fn(a1, a2, a3); - } - finally { - switchToZone(outerScope, false); - } -} -function enqueueNativeMicroTask(job) { - nativePromiseThen.call(resolvedNativePromise, job); -} -function nativeAwaitCompatibleWrap(fn, zone, possibleAwait, cleanup) { - return typeof fn !== 'function' ? fn : function () { - var outerZone = PSD; - if (possibleAwait) - incrementExpectedAwaits(); - switchToZone(zone, true); - try { - return fn.apply(this, arguments); - } - finally { - switchToZone(outerZone, false); - if (cleanup) - enqueueNativeMicroTask(decrementExpectedAwaits); - } - }; -} -function getPatchedPromiseThen(origThen, zone) { - return function (onResolved, onRejected) { - return origThen.call(this, nativeAwaitCompatibleWrap(onResolved, zone), nativeAwaitCompatibleWrap(onRejected, zone)); - }; -} -const UNHANDLEDREJECTION = "unhandledrejection"; -function globalError(err, promise) { - var rv; - try { - rv = promise.onuncatched(err); - } - catch (e) { } - if (rv !== false) - try { - var event, eventData = { promise: promise, reason: err }; - if (_global.document && document.createEvent) { - event = document.createEvent('Event'); - event.initEvent(UNHANDLEDREJECTION, true, true); - extend(event, eventData); - } - else if (_global.CustomEvent) { - event = new CustomEvent(UNHANDLEDREJECTION, { detail: eventData }); - extend(event, eventData); - } - if (event && _global.dispatchEvent) { - dispatchEvent(event); - if (!_global.PromiseRejectionEvent && _global.onunhandledrejection) - try { - _global.onunhandledrejection(event); - } - catch (_) { } - } - if (debug && event && !event.defaultPrevented) { - console.warn(`Unhandled rejection: ${err.stack || err}`); - } - } - catch (e) { } -} -var rejection = DexiePromise.reject; - -function tempTransaction(db, mode, storeNames, fn) { - if (!db.idbdb || (!db._state.openComplete && (!PSD.letThrough && !db._vip))) { - if (db._state.openComplete) { - return rejection(new exceptions.DatabaseClosed(db._state.dbOpenError)); - } - if (!db._state.isBeingOpened) { - if (!db._options.autoOpen) - return rejection(new exceptions.DatabaseClosed()); - db.open().catch(nop); - } - return db._state.dbReadyPromise.then(() => tempTransaction(db, mode, storeNames, fn)); - } - else { - var trans = db._createTransaction(mode, storeNames, db._dbSchema); - try { - trans.create(); - db._state.PR1398_maxLoop = 3; - } - catch (ex) { - if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { - console.warn('Dexie: Need to reopen db'); - db._close(); - return db.open().then(() => tempTransaction(db, mode, storeNames, fn)); - } - return rejection(ex); - } - return trans._promise(mode, (resolve, reject) => { - return newScope(() => { - PSD.trans = trans; - return fn(resolve, reject, trans); - }); - }).then(result => { - return trans._completion.then(() => result); - }); - } -} - -const DEXIE_VERSION = '3.2.4'; -const maxString = String.fromCharCode(65535); -const minKey = -Infinity; -const INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array."; -const STRING_EXPECTED = "String expected."; -const connections = []; -const isIEOrEdge = typeof navigator !== 'undefined' && /(MSIE|Trident|Edge)/.test(navigator.userAgent); -const hasIEDeleteObjectStoreBug = isIEOrEdge; -const hangsOnDeleteLargeKeyRange = isIEOrEdge; -const dexieStackFrameFilter = frame => !/(dexie\.js|dexie\.min\.js)/.test(frame); -const DBNAMES_DB = '__dbnames'; -const READONLY = 'readonly'; -const READWRITE = 'readwrite'; - -function combine(filter1, filter2) { - return filter1 ? - filter2 ? - function () { return filter1.apply(this, arguments) && filter2.apply(this, arguments); } : - filter1 : - filter2; -} - -const AnyRange = { - type: 3 , - lower: -Infinity, - lowerOpen: false, - upper: [[]], - upperOpen: false -}; - -function workaroundForUndefinedPrimKey(keyPath) { - return typeof keyPath === "string" && !/\./.test(keyPath) - ? (obj) => { - if (obj[keyPath] === undefined && (keyPath in obj)) { - obj = deepClone(obj); - delete obj[keyPath]; - } - return obj; - } - : (obj) => obj; -} - -let Table$3 = class Table { - _trans(mode, fn, writeLocked) { - const trans = this._tx || PSD.trans; - const tableName = this.name; - function checkTableInTransaction(resolve, reject, trans) { - if (!trans.schema[tableName]) - throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); - return fn(trans.idbtrans, trans); - } - const wasRootExec = beginMicroTickScope(); - try { - return trans && trans.db === this.db ? - trans === PSD.trans ? - trans._promise(mode, checkTableInTransaction, writeLocked) : - newScope(() => trans._promise(mode, checkTableInTransaction, writeLocked), { trans: trans, transless: PSD.transless || PSD }) : - tempTransaction(this.db, mode, [this.name], checkTableInTransaction); - } - finally { - if (wasRootExec) - endMicroTickScope(); - } - } - get(keyOrCrit, cb) { - if (keyOrCrit && keyOrCrit.constructor === Object) - return this.where(keyOrCrit).first(cb); - return this._trans('readonly', (trans) => { - return this.core.get({ trans, key: keyOrCrit }) - .then(res => this.hook.reading.fire(res)); - }).then(cb); - } - where(indexOrCrit) { - if (typeof indexOrCrit === 'string') - return new this.db.WhereClause(this, indexOrCrit); - if (isArray(indexOrCrit)) - return new this.db.WhereClause(this, `[${indexOrCrit.join('+')}]`); - const keyPaths = keys(indexOrCrit); - if (keyPaths.length === 1) - return this - .where(keyPaths[0]) - .equals(indexOrCrit[keyPaths[0]]); - const compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(ix => ix.compound && - keyPaths.every(keyPath => ix.keyPath.indexOf(keyPath) >= 0) && - ix.keyPath.every(keyPath => keyPaths.indexOf(keyPath) >= 0))[0]; - if (compoundIndex && this.db._maxKey !== maxString) - return this - .where(compoundIndex.name) - .equals(compoundIndex.keyPath.map(kp => indexOrCrit[kp])); - if (!compoundIndex && debug) - console.warn(`The query ${JSON.stringify(indexOrCrit)} on ${this.name} would benefit of a ` + - `compound index [${keyPaths.join('+')}]`); - const { idxByName } = this.schema; - const idb = this.db._deps.indexedDB; - function equals(a, b) { - try { - return idb.cmp(a, b) === 0; - } - catch (e) { - return false; - } - } - const [idx, filterFunction] = keyPaths.reduce(([prevIndex, prevFilterFn], keyPath) => { - const index = idxByName[keyPath]; - const value = indexOrCrit[keyPath]; - return [ - prevIndex || index, - prevIndex || !index ? - combine(prevFilterFn, index && index.multi ? - x => { - const prop = getByKeyPath(x, keyPath); - return isArray(prop) && prop.some(item => equals(value, item)); - } : x => equals(value, getByKeyPath(x, keyPath))) - : prevFilterFn - ]; - }, [null, null]); - return idx ? - this.where(idx.name).equals(indexOrCrit[idx.keyPath]) - .filter(filterFunction) : - compoundIndex ? - this.filter(filterFunction) : - this.where(keyPaths).equals(''); - } - filter(filterFunction) { - return this.toCollection().and(filterFunction); - } - count(thenShortcut) { - return this.toCollection().count(thenShortcut); - } - offset(offset) { - return this.toCollection().offset(offset); - } - limit(numRows) { - return this.toCollection().limit(numRows); - } - each(callback) { - return this.toCollection().each(callback); - } - toArray(thenShortcut) { - return this.toCollection().toArray(thenShortcut); - } - toCollection() { - return new this.db.Collection(new this.db.WhereClause(this)); - } - orderBy(index) { - return new this.db.Collection(new this.db.WhereClause(this, isArray(index) ? - `[${index.join('+')}]` : - index)); - } - reverse() { - return this.toCollection().reverse(); - } - mapToClass(constructor) { - this.schema.mappedClass = constructor; - const readHook = obj => { - if (!obj) - return obj; - const res = Object.create(constructor.prototype); - for (var m in obj) - if (hasOwn(obj, m)) - try { - res[m] = obj[m]; - } - catch (_) { } - return res; - }; - if (this.schema.readHook) { - this.hook.reading.unsubscribe(this.schema.readHook); - } - this.schema.readHook = readHook; - this.hook("reading", readHook); - return constructor; - } - defineClass() { - function Class(content) { - extend(this, content); - } - return this.mapToClass(Class); - } - add(obj, key) { - const { auto, keyPath } = this.schema.primKey; - let objToAdd = obj; - if (keyPath && auto) { - objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); - } - return this._trans('readwrite', trans => { - return this.core.mutate({ trans, type: 'add', keys: key != null ? [key] : null, values: [objToAdd] }); - }).then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult) - .then(lastResult => { - if (keyPath) { - try { - setByKeyPath(obj, keyPath, lastResult); - } - catch (_) { } - } - return lastResult; - }); - } - update(keyOrObject, modifications) { - if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) { - const key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); - if (key === undefined) - return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); - try { - if (typeof modifications !== "function") { - keys(modifications).forEach(keyPath => { - setByKeyPath(keyOrObject, keyPath, modifications[keyPath]); - }); - } - else { - modifications(keyOrObject, { value: keyOrObject, primKey: key }); - } - } - catch (_a) { - } - return this.where(":id").equals(key).modify(modifications); - } - else { - return this.where(":id").equals(keyOrObject).modify(modifications); - } - } - put(obj, key) { - const { auto, keyPath } = this.schema.primKey; - let objToAdd = obj; - if (keyPath && auto) { - objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); - } - return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'put', values: [objToAdd], keys: key != null ? [key] : null })) - .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult) - .then(lastResult => { - if (keyPath) { - try { - setByKeyPath(obj, keyPath, lastResult); - } - catch (_) { } - } - return lastResult; - }); - } - delete(key) { - return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'delete', keys: [key] })) - .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined); - } - clear() { - return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'deleteRange', range: AnyRange })) - .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined); - } - bulkGet(keys) { - return this._trans('readonly', trans => { - return this.core.getMany({ - keys, - trans - }).then(result => result.map(res => this.hook.reading.fire(res))); - }); - } - bulkAdd(objects, keysOrOptions, options) { - const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; - options = options || (keys ? undefined : keysOrOptions); - const wantResults = options ? options.allKeys : undefined; - return this._trans('readwrite', trans => { - const { auto, keyPath } = this.schema.primKey; - if (keyPath && keys) - throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); - if (keys && keys.length !== objects.length) - throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); - const numObjects = objects.length; - let objectsToAdd = keyPath && auto ? - objects.map(workaroundForUndefinedPrimKey(keyPath)) : - objects; - return this.core.mutate({ trans, type: 'add', keys: keys, values: objectsToAdd, wantResults }) - .then(({ numFailures, results, lastResult, failures }) => { - const result = wantResults ? results : lastResult; - if (numFailures === 0) - return result; - throw new BulkError(`${this.name}.bulkAdd(): ${numFailures} of ${numObjects} operations failed`, failures); - }); - }); - } - bulkPut(objects, keysOrOptions, options) { - const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; - options = options || (keys ? undefined : keysOrOptions); - const wantResults = options ? options.allKeys : undefined; - return this._trans('readwrite', trans => { - const { auto, keyPath } = this.schema.primKey; - if (keyPath && keys) - throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); - if (keys && keys.length !== objects.length) - throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); - const numObjects = objects.length; - let objectsToPut = keyPath && auto ? - objects.map(workaroundForUndefinedPrimKey(keyPath)) : - objects; - return this.core.mutate({ trans, type: 'put', keys: keys, values: objectsToPut, wantResults }) - .then(({ numFailures, results, lastResult, failures }) => { - const result = wantResults ? results : lastResult; - if (numFailures === 0) - return result; - throw new BulkError(`${this.name}.bulkPut(): ${numFailures} of ${numObjects} operations failed`, failures); - }); - }); - } - bulkDelete(keys) { - const numKeys = keys.length; - return this._trans('readwrite', trans => { - return this.core.mutate({ trans, type: 'delete', keys: keys }); - }).then(({ numFailures, lastResult, failures }) => { - if (numFailures === 0) - return lastResult; - throw new BulkError(`${this.name}.bulkDelete(): ${numFailures} of ${numKeys} operations failed`, failures); - }); - } -}; - -function Events(ctx) { - var evs = {}; - var rv = function (eventName, subscriber) { - if (subscriber) { - var i = arguments.length, args = new Array(i - 1); - while (--i) - args[i - 1] = arguments[i]; - evs[eventName].subscribe.apply(null, args); - return ctx; - } - else if (typeof (eventName) === 'string') { - return evs[eventName]; - } - }; - rv.addEventType = add; - for (var i = 1, l = arguments.length; i < l; ++i) { - add(arguments[i]); - } - return rv; - function add(eventName, chainFunction, defaultFunction) { - if (typeof eventName === 'object') - return addConfiguredEvents(eventName); - if (!chainFunction) - chainFunction = reverseStoppableEventChain; - if (!defaultFunction) - defaultFunction = nop; - var context = { - subscribers: [], - fire: defaultFunction, - subscribe: function (cb) { - if (context.subscribers.indexOf(cb) === -1) { - context.subscribers.push(cb); - context.fire = chainFunction(context.fire, cb); - } - }, - unsubscribe: function (cb) { - context.subscribers = context.subscribers.filter(function (fn) { return fn !== cb; }); - context.fire = context.subscribers.reduce(chainFunction, defaultFunction); - } - }; - evs[eventName] = rv[eventName] = context; - return context; - } - function addConfiguredEvents(cfg) { - keys(cfg).forEach(function (eventName) { - var args = cfg[eventName]; - if (isArray(args)) { - add(eventName, cfg[eventName][0], cfg[eventName][1]); - } - else if (args === 'asap') { - var context = add(eventName, mirror, function fire() { - var i = arguments.length, args = new Array(i); - while (i--) - args[i] = arguments[i]; - context.subscribers.forEach(function (fn) { - asap$1(function fireEvent() { - fn.apply(null, args); - }); - }); - }); - } - else - throw new exceptions.InvalidArgument("Invalid event config"); - }); - } -} - -function makeClassConstructor(prototype, constructor) { - derive(constructor).from({ prototype }); - return constructor; -} - -function createTableConstructor(db) { - return makeClassConstructor(Table$3.prototype, function Table(name, tableSchema, trans) { - this.db = db; - this._tx = trans; - this.name = name; - this.schema = tableSchema; - this.hook = db._allTables[name] ? db._allTables[name].hook : Events(null, { - "creating": [hookCreatingChain, nop], - "reading": [pureFunctionChain, mirror], - "updating": [hookUpdatingChain, nop], - "deleting": [hookDeletingChain, nop] - }); - }); -} - -function isPlainKeyRange(ctx, ignoreLimitFilter) { - return !(ctx.filter || ctx.algorithm || ctx.or) && - (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); -} -function addFilter(ctx, fn) { - ctx.filter = combine(ctx.filter, fn); -} -function addReplayFilter(ctx, factory, isLimitFilter) { - var curr = ctx.replayFilter; - ctx.replayFilter = curr ? () => combine(curr(), factory()) : factory; - ctx.justLimit = isLimitFilter && !curr; -} -function addMatchFilter(ctx, fn) { - ctx.isMatch = combine(ctx.isMatch, fn); -} -function getIndexOrStore(ctx, coreSchema) { - if (ctx.isPrimKey) - return coreSchema.primaryKey; - const index = coreSchema.getIndexByKeyPath(ctx.index); - if (!index) - throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + coreSchema.name + " is not indexed"); - return index; -} -function openCursor(ctx, coreTable, trans) { - const index = getIndexOrStore(ctx, coreTable.schema); - return coreTable.openCursor({ - trans, - values: !ctx.keysOnly, - reverse: ctx.dir === 'prev', - unique: !!ctx.unique, - query: { - index, - range: ctx.range - } - }); -} -function iter(ctx, fn, coreTrans, coreTable) { - const filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; - if (!ctx.or) { - return iterate(openCursor(ctx, coreTable, coreTrans), combine(ctx.algorithm, filter), fn, !ctx.keysOnly && ctx.valueMapper); - } - else { - const set = {}; - const union = (item, cursor, advance) => { - if (!filter || filter(cursor, advance, result => cursor.stop(result), err => cursor.fail(err))) { - var primaryKey = cursor.primaryKey; - var key = '' + primaryKey; - if (key === '[object ArrayBuffer]') - key = '' + new Uint8Array(primaryKey); - if (!hasOwn(set, key)) { - set[key] = true; - fn(item, cursor, advance); - } - } - }; - return Promise.all([ - ctx.or._iterate(union, coreTrans), - iterate(openCursor(ctx, coreTable, coreTrans), ctx.algorithm, union, !ctx.keysOnly && ctx.valueMapper) - ]); - } -} -function iterate(cursorPromise, filter, fn, valueMapper) { - var mappedFn = valueMapper ? (x, c, a) => fn(valueMapper(x), c, a) : fn; - var wrappedFn = wrap(mappedFn); - return cursorPromise.then(cursor => { - if (cursor) { - return cursor.start(() => { - var c = () => cursor.continue(); - if (!filter || filter(cursor, advancer => c = advancer, val => { cursor.stop(val); c = nop; }, e => { cursor.fail(e); c = nop; })) - wrappedFn(cursor.value, cursor, advancer => c = advancer); - c(); - }); - } - }); -} - -function cmp(a, b) { - try { - const ta = type(a); - const tb = type(b); - if (ta !== tb) { - if (ta === 'Array') - return 1; - if (tb === 'Array') - return -1; - if (ta === 'binary') - return 1; - if (tb === 'binary') - return -1; - if (ta === 'string') - return 1; - if (tb === 'string') - return -1; - if (ta === 'Date') - return 1; - if (tb !== 'Date') - return NaN; - return -1; - } - switch (ta) { - case 'number': - case 'Date': - case 'string': - return a > b ? 1 : a < b ? -1 : 0; - case 'binary': { - return compareUint8Arrays(getUint8Array(a), getUint8Array(b)); - } - case 'Array': - return compareArrays(a, b); - } - } - catch (_a) { } - return NaN; -} -function compareArrays(a, b) { - const al = a.length; - const bl = b.length; - const l = al < bl ? al : bl; - for (let i = 0; i < l; ++i) { - const res = cmp(a[i], b[i]); - if (res !== 0) - return res; - } - return al === bl ? 0 : al < bl ? -1 : 1; -} -function compareUint8Arrays(a, b) { - const al = a.length; - const bl = b.length; - const l = al < bl ? al : bl; - for (let i = 0; i < l; ++i) { - if (a[i] !== b[i]) - return a[i] < b[i] ? -1 : 1; - } - return al === bl ? 0 : al < bl ? -1 : 1; -} -function type(x) { - const t = typeof x; - if (t !== 'object') - return t; - if (ArrayBuffer.isView(x)) - return 'binary'; - const tsTag = toStringTag(x); - return tsTag === 'ArrayBuffer' ? 'binary' : tsTag; -} -function getUint8Array(a) { - if (a instanceof Uint8Array) - return a; - if (ArrayBuffer.isView(a)) - return new Uint8Array(a.buffer, a.byteOffset, a.byteLength); - return new Uint8Array(a); -} - -class Collection { - _read(fn, cb) { - var ctx = this._ctx; - return ctx.error ? - ctx.table._trans(null, rejection.bind(null, ctx.error)) : - ctx.table._trans('readonly', fn).then(cb); - } - _write(fn) { - var ctx = this._ctx; - return ctx.error ? - ctx.table._trans(null, rejection.bind(null, ctx.error)) : - ctx.table._trans('readwrite', fn, "locked"); - } - _addAlgorithm(fn) { - var ctx = this._ctx; - ctx.algorithm = combine(ctx.algorithm, fn); - } - _iterate(fn, coreTrans) { - return iter(this._ctx, fn, coreTrans, this._ctx.table.core); - } - clone(props) { - var rv = Object.create(this.constructor.prototype), ctx = Object.create(this._ctx); - if (props) - extend(ctx, props); - rv._ctx = ctx; - return rv; - } - raw() { - this._ctx.valueMapper = null; - return this; - } - each(fn) { - var ctx = this._ctx; - return this._read(trans => iter(ctx, fn, trans, ctx.table.core)); - } - count(cb) { - return this._read(trans => { - const ctx = this._ctx; - const coreTable = ctx.table.core; - if (isPlainKeyRange(ctx, true)) { - return coreTable.count({ - trans, - query: { - index: getIndexOrStore(ctx, coreTable.schema), - range: ctx.range - } - }).then(count => Math.min(count, ctx.limit)); - } - else { - var count = 0; - return iter(ctx, () => { ++count; return false; }, trans, coreTable) - .then(() => count); - } - }).then(cb); - } - sortBy(keyPath, cb) { - const parts = keyPath.split('.').reverse(), lastPart = parts[0], lastIndex = parts.length - 1; - function getval(obj, i) { - if (i) - return getval(obj[parts[i]], i - 1); - return obj[lastPart]; - } - var order = this._ctx.dir === "next" ? 1 : -1; - function sorter(a, b) { - var aVal = getval(a, lastIndex), bVal = getval(b, lastIndex); - return aVal < bVal ? -order : aVal > bVal ? order : 0; - } - return this.toArray(function (a) { - return a.sort(sorter); - }).then(cb); - } - toArray(cb) { - return this._read(trans => { - var ctx = this._ctx; - if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { - const { valueMapper } = ctx; - const index = getIndexOrStore(ctx, ctx.table.core.schema); - return ctx.table.core.query({ - trans, - limit: ctx.limit, - values: true, - query: { - index, - range: ctx.range - } - }).then(({ result }) => valueMapper ? result.map(valueMapper) : result); - } - else { - const a = []; - return iter(ctx, item => a.push(item), trans, ctx.table.core).then(() => a); - } - }, cb); - } - offset(offset) { - var ctx = this._ctx; - if (offset <= 0) - return this; - ctx.offset += offset; - if (isPlainKeyRange(ctx)) { - addReplayFilter(ctx, () => { - var offsetLeft = offset; - return (cursor, advance) => { - if (offsetLeft === 0) - return true; - if (offsetLeft === 1) { - --offsetLeft; - return false; - } - advance(() => { - cursor.advance(offsetLeft); - offsetLeft = 0; - }); - return false; - }; - }); - } - else { - addReplayFilter(ctx, () => { - var offsetLeft = offset; - return () => (--offsetLeft < 0); - }); - } - return this; - } - limit(numRows) { - this._ctx.limit = Math.min(this._ctx.limit, numRows); - addReplayFilter(this._ctx, () => { - var rowsLeft = numRows; - return function (cursor, advance, resolve) { - if (--rowsLeft <= 0) - advance(resolve); - return rowsLeft >= 0; - }; - }, true); - return this; - } - until(filterFunction, bIncludeStopEntry) { - addFilter(this._ctx, function (cursor, advance, resolve) { - if (filterFunction(cursor.value)) { - advance(resolve); - return bIncludeStopEntry; - } - else { - return true; - } - }); - return this; - } - first(cb) { - return this.limit(1).toArray(function (a) { return a[0]; }).then(cb); - } - last(cb) { - return this.reverse().first(cb); - } - filter(filterFunction) { - addFilter(this._ctx, function (cursor) { - return filterFunction(cursor.value); - }); - addMatchFilter(this._ctx, filterFunction); - return this; - } - and(filter) { - return this.filter(filter); - } - or(indexName) { - return new this.db.WhereClause(this._ctx.table, indexName, this); - } - reverse() { - this._ctx.dir = (this._ctx.dir === "prev" ? "next" : "prev"); - if (this._ondirectionchange) - this._ondirectionchange(this._ctx.dir); - return this; - } - desc() { - return this.reverse(); - } - eachKey(cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - return this.each(function (val, cursor) { cb(cursor.key, cursor); }); - } - eachUniqueKey(cb) { - this._ctx.unique = "unique"; - return this.eachKey(cb); - } - eachPrimaryKey(cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - return this.each(function (val, cursor) { cb(cursor.primaryKey, cursor); }); - } - keys(cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - var a = []; - return this.each(function (item, cursor) { - a.push(cursor.key); - }).then(function () { - return a; - }).then(cb); - } - primaryKeys(cb) { - var ctx = this._ctx; - if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { - return this._read(trans => { - var index = getIndexOrStore(ctx, ctx.table.core.schema); - return ctx.table.core.query({ - trans, - values: false, - limit: ctx.limit, - query: { - index, - range: ctx.range - } - }); - }).then(({ result }) => result).then(cb); - } - ctx.keysOnly = !ctx.isMatch; - var a = []; - return this.each(function (item, cursor) { - a.push(cursor.primaryKey); - }).then(function () { - return a; - }).then(cb); - } - uniqueKeys(cb) { - this._ctx.unique = "unique"; - return this.keys(cb); - } - firstKey(cb) { - return this.limit(1).keys(function (a) { return a[0]; }).then(cb); - } - lastKey(cb) { - return this.reverse().firstKey(cb); - } - distinct() { - var ctx = this._ctx, idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; - if (!idx || !idx.multi) - return this; - var set = {}; - addFilter(this._ctx, function (cursor) { - var strKey = cursor.primaryKey.toString(); - var found = hasOwn(set, strKey); - set[strKey] = true; - return !found; - }); - return this; - } - modify(changes) { - var ctx = this._ctx; - return this._write(trans => { - var modifyer; - if (typeof changes === 'function') { - modifyer = changes; - } - else { - var keyPaths = keys(changes); - var numKeys = keyPaths.length; - modifyer = function (item) { - var anythingModified = false; - for (var i = 0; i < numKeys; ++i) { - var keyPath = keyPaths[i], val = changes[keyPath]; - if (getByKeyPath(item, keyPath) !== val) { - setByKeyPath(item, keyPath, val); - anythingModified = true; - } - } - return anythingModified; - }; - } - const coreTable = ctx.table.core; - const { outbound, extractKey } = coreTable.schema.primaryKey; - const limit = this.db._options.modifyChunkSize || 200; - const totalFailures = []; - let successCount = 0; - const failedKeys = []; - const applyMutateResult = (expectedCount, res) => { - const { failures, numFailures } = res; - successCount += expectedCount - numFailures; - for (let pos of keys(failures)) { - totalFailures.push(failures[pos]); - } - }; - return this.clone().primaryKeys().then(keys => { - const nextChunk = (offset) => { - const count = Math.min(limit, keys.length - offset); - return coreTable.getMany({ - trans, - keys: keys.slice(offset, offset + count), - cache: "immutable" - }).then(values => { - const addValues = []; - const putValues = []; - const putKeys = outbound ? [] : null; - const deleteKeys = []; - for (let i = 0; i < count; ++i) { - const origValue = values[i]; - const ctx = { - value: deepClone(origValue), - primKey: keys[offset + i] - }; - if (modifyer.call(ctx, ctx.value, ctx) !== false) { - if (ctx.value == null) { - deleteKeys.push(keys[offset + i]); - } - else if (!outbound && cmp(extractKey(origValue), extractKey(ctx.value)) !== 0) { - deleteKeys.push(keys[offset + i]); - addValues.push(ctx.value); - } - else { - putValues.push(ctx.value); - if (outbound) - putKeys.push(keys[offset + i]); - } - } - } - const criteria = isPlainKeyRange(ctx) && - ctx.limit === Infinity && - (typeof changes !== 'function' || changes === deleteCallback) && { - index: ctx.index, - range: ctx.range - }; - return Promise.resolve(addValues.length > 0 && - coreTable.mutate({ trans, type: 'add', values: addValues }) - .then(res => { - for (let pos in res.failures) { - deleteKeys.splice(parseInt(pos), 1); - } - applyMutateResult(addValues.length, res); - })).then(() => (putValues.length > 0 || (criteria && typeof changes === 'object')) && - coreTable.mutate({ - trans, - type: 'put', - keys: putKeys, - values: putValues, - criteria, - changeSpec: typeof changes !== 'function' - && changes - }).then(res => applyMutateResult(putValues.length, res))).then(() => (deleteKeys.length > 0 || (criteria && changes === deleteCallback)) && - coreTable.mutate({ - trans, - type: 'delete', - keys: deleteKeys, - criteria - }).then(res => applyMutateResult(deleteKeys.length, res))).then(() => { - return keys.length > offset + count && nextChunk(offset + limit); - }); - }); - }; - return nextChunk(0).then(() => { - if (totalFailures.length > 0) - throw new ModifyError("Error modifying one or more objects", totalFailures, successCount, failedKeys); - return keys.length; - }); - }); - }); - } - delete() { - var ctx = this._ctx, range = ctx.range; - if (isPlainKeyRange(ctx) && - ((ctx.isPrimKey && !hangsOnDeleteLargeKeyRange) || range.type === 3 )) - { - return this._write(trans => { - const { primaryKey } = ctx.table.core.schema; - const coreRange = range; - return ctx.table.core.count({ trans, query: { index: primaryKey, range: coreRange } }).then(count => { - return ctx.table.core.mutate({ trans, type: 'deleteRange', range: coreRange }) - .then(({ failures, lastResult, results, numFailures }) => { - if (numFailures) - throw new ModifyError("Could not delete some values", Object.keys(failures).map(pos => failures[pos]), count - numFailures); - return count - numFailures; - }); - }); - }); - } - return this.modify(deleteCallback); - } -} -const deleteCallback = (value, ctx) => ctx.value = null; - -function createCollectionConstructor(db) { - return makeClassConstructor(Collection.prototype, function Collection(whereClause, keyRangeGenerator) { - this.db = db; - let keyRange = AnyRange, error = null; - if (keyRangeGenerator) - try { - keyRange = keyRangeGenerator(); - } - catch (ex) { - error = ex; - } - const whereCtx = whereClause._ctx; - const table = whereCtx.table; - const readingHook = table.hook.reading.fire; - this._ctx = { - table: table, - index: whereCtx.index, - isPrimKey: (!whereCtx.index || (table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name)), - range: keyRange, - keysOnly: false, - dir: "next", - unique: "", - algorithm: null, - filter: null, - replayFilter: null, - justLimit: true, - isMatch: null, - offset: 0, - limit: Infinity, - error: error, - or: whereCtx.or, - valueMapper: readingHook !== mirror ? readingHook : null - }; - }); -} - -function simpleCompare(a, b) { - return a < b ? -1 : a === b ? 0 : 1; -} -function simpleCompareReverse(a, b) { - return a > b ? -1 : a === b ? 0 : 1; -} - -function fail(collectionOrWhereClause, err, T) { - var collection = collectionOrWhereClause instanceof WhereClause ? - new collectionOrWhereClause.Collection(collectionOrWhereClause) : - collectionOrWhereClause; - collection._ctx.error = T ? new T(err) : new TypeError(err); - return collection; -} -function emptyCollection(whereClause) { - return new whereClause.Collection(whereClause, () => rangeEqual("")).limit(0); -} -function upperFactory(dir) { - return dir === "next" ? - (s) => s.toUpperCase() : - (s) => s.toLowerCase(); -} -function lowerFactory(dir) { - return dir === "next" ? - (s) => s.toLowerCase() : - (s) => s.toUpperCase(); -} -function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { - var length = Math.min(key.length, lowerNeedle.length); - var llp = -1; - for (var i = 0; i < length; ++i) { - var lwrKeyChar = lowerKey[i]; - if (lwrKeyChar !== lowerNeedle[i]) { - if (cmp(key[i], upperNeedle[i]) < 0) - return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); - if (cmp(key[i], lowerNeedle[i]) < 0) - return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); - if (llp >= 0) - return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); - return null; - } - if (cmp(key[i], lwrKeyChar) < 0) - llp = i; - } - if (length < lowerNeedle.length && dir === "next") - return key + upperNeedle.substr(key.length); - if (length < key.length && dir === "prev") - return key.substr(0, upperNeedle.length); - return (llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1)); -} -function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { - var upper, lower, compare, upperNeedles, lowerNeedles, direction, nextKeySuffix, needlesLen = needles.length; - if (!needles.every(s => typeof s === 'string')) { - return fail(whereClause, STRING_EXPECTED); - } - function initDirection(dir) { - upper = upperFactory(dir); - lower = lowerFactory(dir); - compare = (dir === "next" ? simpleCompare : simpleCompareReverse); - var needleBounds = needles.map(function (needle) { - return { lower: lower(needle), upper: upper(needle) }; - }).sort(function (a, b) { - return compare(a.lower, b.lower); - }); - upperNeedles = needleBounds.map(function (nb) { return nb.upper; }); - lowerNeedles = needleBounds.map(function (nb) { return nb.lower; }); - direction = dir; - nextKeySuffix = (dir === "next" ? "" : suffix); - } - initDirection("next"); - var c = new whereClause.Collection(whereClause, () => createRange(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix)); - c._ondirectionchange = function (direction) { - initDirection(direction); - }; - var firstPossibleNeedle = 0; - c._addAlgorithm(function (cursor, advance, resolve) { - var key = cursor.key; - if (typeof key !== 'string') - return false; - var lowerKey = lower(key); - if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { - return true; - } - else { - var lowestPossibleCasing = null; - for (var i = firstPossibleNeedle; i < needlesLen; ++i) { - var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); - if (casing === null && lowestPossibleCasing === null) - firstPossibleNeedle = i + 1; - else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { - lowestPossibleCasing = casing; - } - } - if (lowestPossibleCasing !== null) { - advance(function () { cursor.continue(lowestPossibleCasing + nextKeySuffix); }); - } - else { - advance(resolve); - } - return false; - } - }); - return c; -} -function createRange(lower, upper, lowerOpen, upperOpen) { - return { - type: 2 , - lower, - upper, - lowerOpen, - upperOpen - }; -} -function rangeEqual(value) { - return { - type: 1 , - lower: value, - upper: value - }; -} - -class WhereClause { - get Collection() { - return this._ctx.table.db.Collection; - } - between(lower, upper, includeLower, includeUpper) { - includeLower = includeLower !== false; - includeUpper = includeUpper === true; - try { - if ((this._cmp(lower, upper) > 0) || - (this._cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper))) - return emptyCollection(this); - return new this.Collection(this, () => createRange(lower, upper, !includeLower, !includeUpper)); - } - catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); - } - } - equals(value) { - if (value == null) - return fail(this, INVALID_KEY_ARGUMENT); - return new this.Collection(this, () => rangeEqual(value)); - } - above(value) { - if (value == null) - return fail(this, INVALID_KEY_ARGUMENT); - return new this.Collection(this, () => createRange(value, undefined, true)); - } - aboveOrEqual(value) { - if (value == null) - return fail(this, INVALID_KEY_ARGUMENT); - return new this.Collection(this, () => createRange(value, undefined, false)); - } - below(value) { - if (value == null) - return fail(this, INVALID_KEY_ARGUMENT); - return new this.Collection(this, () => createRange(undefined, value, false, true)); - } - belowOrEqual(value) { - if (value == null) - return fail(this, INVALID_KEY_ARGUMENT); - return new this.Collection(this, () => createRange(undefined, value)); - } - startsWith(str) { - if (typeof str !== 'string') - return fail(this, STRING_EXPECTED); - return this.between(str, str + maxString, true, true); - } - startsWithIgnoreCase(str) { - if (str === "") - return this.startsWith(str); - return addIgnoreCaseAlgorithm(this, (x, a) => x.indexOf(a[0]) === 0, [str], maxString); - } - equalsIgnoreCase(str) { - return addIgnoreCaseAlgorithm(this, (x, a) => x === a[0], [str], ""); - } - anyOfIgnoreCase() { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return emptyCollection(this); - return addIgnoreCaseAlgorithm(this, (x, a) => a.indexOf(x) !== -1, set, ""); - } - startsWithAnyOfIgnoreCase() { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return emptyCollection(this); - return addIgnoreCaseAlgorithm(this, (x, a) => a.some(n => x.indexOf(n) === 0), set, maxString); - } - anyOf() { - const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - let compare = this._cmp; - try { - set.sort(compare); - } - catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); - } - if (set.length === 0) - return emptyCollection(this); - const c = new this.Collection(this, () => createRange(set[0], set[set.length - 1])); - c._ondirectionchange = direction => { - compare = (direction === "next" ? - this._ascending : - this._descending); - set.sort(compare); - }; - let i = 0; - c._addAlgorithm((cursor, advance, resolve) => { - const key = cursor.key; - while (compare(key, set[i]) > 0) { - ++i; - if (i === set.length) { - advance(resolve); - return false; - } - } - if (compare(key, set[i]) === 0) { - return true; - } - else { - advance(() => { cursor.continue(set[i]); }); - return false; - } - }); - return c; - } - notEqual(value) { - return this.inAnyRange([[minKey, value], [value, this.db._maxKey]], { includeLowers: false, includeUppers: false }); - } - noneOf() { - const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return new this.Collection(this); - try { - set.sort(this._ascending); - } - catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); - } - const ranges = set.reduce((res, val) => res ? - res.concat([[res[res.length - 1][1], val]]) : - [[minKey, val]], null); - ranges.push([set[set.length - 1], this.db._maxKey]); - return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false }); - } - inAnyRange(ranges, options) { - const cmp = this._cmp, ascending = this._ascending, descending = this._descending, min = this._min, max = this._max; - if (ranges.length === 0) - return emptyCollection(this); - if (!ranges.every(range => range[0] !== undefined && - range[1] !== undefined && - ascending(range[0], range[1]) <= 0)) { - return fail(this, "First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower", exceptions.InvalidArgument); - } - const includeLowers = !options || options.includeLowers !== false; - const includeUppers = options && options.includeUppers === true; - function addRange(ranges, newRange) { - let i = 0, l = ranges.length; - for (; i < l; ++i) { - const range = ranges[i]; - if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { - range[0] = min(range[0], newRange[0]); - range[1] = max(range[1], newRange[1]); - break; - } - } - if (i === l) - ranges.push(newRange); - return ranges; - } - let sortDirection = ascending; - function rangeSorter(a, b) { return sortDirection(a[0], b[0]); } - let set; - try { - set = ranges.reduce(addRange, []); - set.sort(rangeSorter); - } - catch (ex) { - return fail(this, INVALID_KEY_ARGUMENT); - } - let rangePos = 0; - const keyIsBeyondCurrentEntry = includeUppers ? - key => ascending(key, set[rangePos][1]) > 0 : - key => ascending(key, set[rangePos][1]) >= 0; - const keyIsBeforeCurrentEntry = includeLowers ? - key => descending(key, set[rangePos][0]) > 0 : - key => descending(key, set[rangePos][0]) >= 0; - function keyWithinCurrentRange(key) { - return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); - } - let checkKey = keyIsBeyondCurrentEntry; - const c = new this.Collection(this, () => createRange(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers)); - c._ondirectionchange = direction => { - if (direction === "next") { - checkKey = keyIsBeyondCurrentEntry; - sortDirection = ascending; - } - else { - checkKey = keyIsBeforeCurrentEntry; - sortDirection = descending; - } - set.sort(rangeSorter); - }; - c._addAlgorithm((cursor, advance, resolve) => { - var key = cursor.key; - while (checkKey(key)) { - ++rangePos; - if (rangePos === set.length) { - advance(resolve); - return false; - } - } - if (keyWithinCurrentRange(key)) { - return true; - } - else if (this._cmp(key, set[rangePos][1]) === 0 || this._cmp(key, set[rangePos][0]) === 0) { - return false; - } - else { - advance(() => { - if (sortDirection === ascending) - cursor.continue(set[rangePos][0]); - else - cursor.continue(set[rangePos][1]); - }); - return false; - } - }); - return c; - } - startsWithAnyOf() { - const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (!set.every(s => typeof s === 'string')) { - return fail(this, "startsWithAnyOf() only works with strings"); - } - if (set.length === 0) - return emptyCollection(this); - return this.inAnyRange(set.map((str) => [str, str + maxString])); - } -} - -function createWhereClauseConstructor(db) { - return makeClassConstructor(WhereClause.prototype, function WhereClause(table, index, orCollection) { - this.db = db; - this._ctx = { - table: table, - index: index === ":id" ? null : index, - or: orCollection - }; - const indexedDB = db._deps.indexedDB; - if (!indexedDB) - throw new exceptions.MissingAPI(); - this._cmp = this._ascending = indexedDB.cmp.bind(indexedDB); - this._descending = (a, b) => indexedDB.cmp(b, a); - this._max = (a, b) => indexedDB.cmp(a, b) > 0 ? a : b; - this._min = (a, b) => indexedDB.cmp(a, b) < 0 ? a : b; - this._IDBKeyRange = db._deps.IDBKeyRange; - }); -} - -function eventRejectHandler(reject) { - return wrap(function (event) { - preventDefault(event); - reject(event.target.error); - return false; - }); -} -function preventDefault(event) { - if (event.stopPropagation) - event.stopPropagation(); - if (event.preventDefault) - event.preventDefault(); -} - -const DEXIE_STORAGE_MUTATED_EVENT_NAME = 'storagemutated'; -const STORAGE_MUTATED_DOM_EVENT_NAME = 'x-storagemutated-1'; -const globalEvents = Events(null, DEXIE_STORAGE_MUTATED_EVENT_NAME); - -class Transaction { - _lock() { - assert(!PSD.global); - ++this._reculock; - if (this._reculock === 1 && !PSD.global) - PSD.lockOwnerFor = this; - return this; - } - _unlock() { - assert(!PSD.global); - if (--this._reculock === 0) { - if (!PSD.global) - PSD.lockOwnerFor = null; - while (this._blockedFuncs.length > 0 && !this._locked()) { - var fnAndPSD = this._blockedFuncs.shift(); - try { - usePSD(fnAndPSD[1], fnAndPSD[0]); - } - catch (e) { } - } - } - return this; - } - _locked() { - return this._reculock && PSD.lockOwnerFor !== this; - } - create(idbtrans) { - if (!this.mode) - return this; - const idbdb = this.db.idbdb; - const dbOpenError = this.db._state.dbOpenError; - assert(!this.idbtrans); - if (!idbtrans && !idbdb) { - switch (dbOpenError && dbOpenError.name) { - case "DatabaseClosedError": - throw new exceptions.DatabaseClosed(dbOpenError); - case "MissingAPIError": - throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); - default: - throw new exceptions.OpenFailed(dbOpenError); - } - } - if (!this.active) - throw new exceptions.TransactionInactive(); - assert(this._completion._state === null); - idbtrans = this.idbtrans = idbtrans || - (this.db.core - ? this.db.core.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability }) - : idbdb.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability })); - idbtrans.onerror = wrap(ev => { - preventDefault(ev); - this._reject(idbtrans.error); - }); - idbtrans.onabort = wrap(ev => { - preventDefault(ev); - this.active && this._reject(new exceptions.Abort(idbtrans.error)); - this.active = false; - this.on("abort").fire(ev); - }); - idbtrans.oncomplete = wrap(() => { - this.active = false; - this._resolve(); - if ('mutatedParts' in idbtrans) { - globalEvents.storagemutated.fire(idbtrans["mutatedParts"]); - } - }); - return this; - } - _promise(mode, fn, bWriteLock) { - if (mode === 'readwrite' && this.mode !== 'readwrite') - return rejection(new exceptions.ReadOnly("Transaction is readonly")); - if (!this.active) - return rejection(new exceptions.TransactionInactive()); - if (this._locked()) { - return new DexiePromise((resolve, reject) => { - this._blockedFuncs.push([() => { - this._promise(mode, fn, bWriteLock).then(resolve, reject); - }, PSD]); - }); - } - else if (bWriteLock) { - return newScope(() => { - var p = new DexiePromise((resolve, reject) => { - this._lock(); - const rv = fn(resolve, reject, this); - if (rv && rv.then) - rv.then(resolve, reject); - }); - p.finally(() => this._unlock()); - p._lib = true; - return p; - }); - } - else { - var p = new DexiePromise((resolve, reject) => { - var rv = fn(resolve, reject, this); - if (rv && rv.then) - rv.then(resolve, reject); - }); - p._lib = true; - return p; - } - } - _root() { - return this.parent ? this.parent._root() : this; - } - waitFor(promiseLike) { - var root = this._root(); - const promise = DexiePromise.resolve(promiseLike); - if (root._waitingFor) { - root._waitingFor = root._waitingFor.then(() => promise); - } - else { - root._waitingFor = promise; - root._waitingQueue = []; - var store = root.idbtrans.objectStore(root.storeNames[0]); - (function spin() { - ++root._spinCount; - while (root._waitingQueue.length) - (root._waitingQueue.shift())(); - if (root._waitingFor) - store.get(-Infinity).onsuccess = spin; - }()); - } - var currentWaitPromise = root._waitingFor; - return new DexiePromise((resolve, reject) => { - promise.then(res => root._waitingQueue.push(wrap(resolve.bind(null, res))), err => root._waitingQueue.push(wrap(reject.bind(null, err)))).finally(() => { - if (root._waitingFor === currentWaitPromise) { - root._waitingFor = null; - } - }); - }); - } - abort() { - if (this.active) { - this.active = false; - if (this.idbtrans) - this.idbtrans.abort(); - this._reject(new exceptions.Abort()); - } - } - table(tableName) { - const memoizedTables = (this._memoizedTables || (this._memoizedTables = {})); - if (hasOwn(memoizedTables, tableName)) - return memoizedTables[tableName]; - const tableSchema = this.schema[tableName]; - if (!tableSchema) { - throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); - } - const transactionBoundTable = new this.db.Table(tableName, tableSchema, this); - transactionBoundTable.core = this.db.core.table(tableName); - memoizedTables[tableName] = transactionBoundTable; - return transactionBoundTable; - } -} - -function createTransactionConstructor(db) { - return makeClassConstructor(Transaction.prototype, function Transaction(mode, storeNames, dbschema, chromeTransactionDurability, parent) { - this.db = db; - this.mode = mode; - this.storeNames = storeNames; - this.schema = dbschema; - this.chromeTransactionDurability = chromeTransactionDurability; - this.idbtrans = null; - this.on = Events(this, "complete", "error", "abort"); - this.parent = parent || null; - this.active = true; - this._reculock = 0; - this._blockedFuncs = []; - this._resolve = null; - this._reject = null; - this._waitingFor = null; - this._waitingQueue = null; - this._spinCount = 0; - this._completion = new DexiePromise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - }); - this._completion.then(() => { - this.active = false; - this.on.complete.fire(); - }, e => { - var wasActive = this.active; - this.active = false; - this.on.error.fire(e); - this.parent ? - this.parent._reject(e) : - wasActive && this.idbtrans && this.idbtrans.abort(); - return rejection(e); - }); - }); -} - -function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey) { - return { - name, - keyPath, - unique, - multi, - auto, - compound, - src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + nameFromKeyPath(keyPath) - }; -} -function nameFromKeyPath(keyPath) { - return typeof keyPath === 'string' ? - keyPath : - keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : ""; -} - -function createTableSchema(name, primKey, indexes) { - return { - name, - primKey, - indexes, - mappedClass: null, - idxByName: arrayToObject(indexes, index => [index.name, index]) - }; -} - -function safariMultiStoreFix(storeNames) { - return storeNames.length === 1 ? storeNames[0] : storeNames; -} -let getMaxKey = (IdbKeyRange) => { - try { - IdbKeyRange.only([[]]); - getMaxKey = () => [[]]; - return [[]]; - } - catch (e) { - getMaxKey = () => maxString; - return maxString; - } -}; - -function getKeyExtractor(keyPath) { - if (keyPath == null) { - return () => undefined; - } - else if (typeof keyPath === 'string') { - return getSinglePathKeyExtractor(keyPath); - } - else { - return obj => getByKeyPath(obj, keyPath); - } -} -function getSinglePathKeyExtractor(keyPath) { - const split = keyPath.split('.'); - if (split.length === 1) { - return obj => obj[keyPath]; - } - else { - return obj => getByKeyPath(obj, keyPath); - } -} - -function arrayify(arrayLike) { - return [].slice.call(arrayLike); -} -let _id_counter = 0; -function getKeyPathAlias(keyPath) { - return keyPath == null ? - ":id" : - typeof keyPath === 'string' ? - keyPath : - `[${keyPath.join('+')}]`; -} -function createDBCore(db, IdbKeyRange, tmpTrans) { - function extractSchema(db, trans) { - const tables = arrayify(db.objectStoreNames); - return { - schema: { - name: db.name, - tables: tables.map(table => trans.objectStore(table)).map(store => { - const { keyPath, autoIncrement } = store; - const compound = isArray(keyPath); - const outbound = keyPath == null; - const indexByKeyPath = {}; - const result = { - name: store.name, - primaryKey: { - name: null, - isPrimaryKey: true, - outbound, - compound, - keyPath, - autoIncrement, - unique: true, - extractKey: getKeyExtractor(keyPath) - }, - indexes: arrayify(store.indexNames).map(indexName => store.index(indexName)) - .map(index => { - const { name, unique, multiEntry, keyPath } = index; - const compound = isArray(keyPath); - const result = { - name, - compound, - keyPath, - unique, - multiEntry, - extractKey: getKeyExtractor(keyPath) - }; - indexByKeyPath[getKeyPathAlias(keyPath)] = result; - return result; - }), - getIndexByKeyPath: (keyPath) => indexByKeyPath[getKeyPathAlias(keyPath)] - }; - indexByKeyPath[":id"] = result.primaryKey; - if (keyPath != null) { - indexByKeyPath[getKeyPathAlias(keyPath)] = result.primaryKey; - } - return result; - }) - }, - hasGetAll: tables.length > 0 && ('getAll' in trans.objectStore(tables[0])) && - !(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && - !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && - [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) - }; - } - function makeIDBKeyRange(range) { - if (range.type === 3 ) - return null; - if (range.type === 4 ) - throw new Error("Cannot convert never type to IDBKeyRange"); - const { lower, upper, lowerOpen, upperOpen } = range; - const idbRange = lower === undefined ? - upper === undefined ? - null : - IdbKeyRange.upperBound(upper, !!upperOpen) : - upper === undefined ? - IdbKeyRange.lowerBound(lower, !!lowerOpen) : - IdbKeyRange.bound(lower, upper, !!lowerOpen, !!upperOpen); - return idbRange; - } - function createDbCoreTable(tableSchema) { - const tableName = tableSchema.name; - function mutate({ trans, type, keys, values, range }) { - return new Promise((resolve, reject) => { - resolve = wrap(resolve); - const store = trans.objectStore(tableName); - const outbound = store.keyPath == null; - const isAddOrPut = type === "put" || type === "add"; - if (!isAddOrPut && type !== 'delete' && type !== 'deleteRange') - throw new Error("Invalid operation type: " + type); - const { length } = keys || values || { length: 1 }; - if (keys && values && keys.length !== values.length) { - throw new Error("Given keys array must have same length as given values array."); - } - if (length === 0) - return resolve({ numFailures: 0, failures: {}, results: [], lastResult: undefined }); - let req; - const reqs = []; - const failures = []; - let numFailures = 0; - const errorHandler = event => { - ++numFailures; - preventDefault(event); - }; - if (type === 'deleteRange') { - if (range.type === 4 ) - return resolve({ numFailures, failures, results: [], lastResult: undefined }); - if (range.type === 3 ) - reqs.push(req = store.clear()); - else - reqs.push(req = store.delete(makeIDBKeyRange(range))); - } - else { - const [args1, args2] = isAddOrPut ? - outbound ? - [values, keys] : - [values, null] : - [keys, null]; - if (isAddOrPut) { - for (let i = 0; i < length; ++i) { - reqs.push(req = (args2 && args2[i] !== undefined ? - store[type](args1[i], args2[i]) : - store[type](args1[i]))); - req.onerror = errorHandler; - } - } - else { - for (let i = 0; i < length; ++i) { - reqs.push(req = store[type](args1[i])); - req.onerror = errorHandler; - } - } - } - const done = event => { - const lastResult = event.target.result; - reqs.forEach((req, i) => req.error != null && (failures[i] = req.error)); - resolve({ - numFailures, - failures, - results: type === "delete" ? keys : reqs.map(req => req.result), - lastResult - }); - }; - req.onerror = event => { - errorHandler(event); - done(event); - }; - req.onsuccess = done; - }); - } - function openCursor({ trans, values, query, reverse, unique }) { - return new Promise((resolve, reject) => { - resolve = wrap(resolve); - const { index, range } = query; - const store = trans.objectStore(tableName); - const source = index.isPrimaryKey ? - store : - store.index(index.name); - const direction = reverse ? - unique ? - "prevunique" : - "prev" : - unique ? - "nextunique" : - "next"; - const req = values || !('openKeyCursor' in source) ? - source.openCursor(makeIDBKeyRange(range), direction) : - source.openKeyCursor(makeIDBKeyRange(range), direction); - req.onerror = eventRejectHandler(reject); - req.onsuccess = wrap(ev => { - const cursor = req.result; - if (!cursor) { - resolve(null); - return; - } - cursor.___id = ++_id_counter; - cursor.done = false; - const _cursorContinue = cursor.continue.bind(cursor); - let _cursorContinuePrimaryKey = cursor.continuePrimaryKey; - if (_cursorContinuePrimaryKey) - _cursorContinuePrimaryKey = _cursorContinuePrimaryKey.bind(cursor); - const _cursorAdvance = cursor.advance.bind(cursor); - const doThrowCursorIsNotStarted = () => { throw new Error("Cursor not started"); }; - const doThrowCursorIsStopped = () => { throw new Error("Cursor not stopped"); }; - cursor.trans = trans; - cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsNotStarted; - cursor.fail = wrap(reject); - cursor.next = function () { - let gotOne = 1; - return this.start(() => gotOne-- ? this.continue() : this.stop()).then(() => this); - }; - cursor.start = (callback) => { - const iterationPromise = new Promise((resolveIteration, rejectIteration) => { - resolveIteration = wrap(resolveIteration); - req.onerror = eventRejectHandler(rejectIteration); - cursor.fail = rejectIteration; - cursor.stop = value => { - cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsStopped; - resolveIteration(value); - }; - }); - const guardedCallback = () => { - if (req.result) { - try { - callback(); - } - catch (err) { - cursor.fail(err); - } - } - else { - cursor.done = true; - cursor.start = () => { throw new Error("Cursor behind last entry"); }; - cursor.stop(); - } - }; - req.onsuccess = wrap(ev => { - req.onsuccess = guardedCallback; - guardedCallback(); - }); - cursor.continue = _cursorContinue; - cursor.continuePrimaryKey = _cursorContinuePrimaryKey; - cursor.advance = _cursorAdvance; - guardedCallback(); - return iterationPromise; - }; - resolve(cursor); - }, reject); - }); - } - function query(hasGetAll) { - return (request) => { - return new Promise((resolve, reject) => { - resolve = wrap(resolve); - const { trans, values, limit, query } = request; - const nonInfinitLimit = limit === Infinity ? undefined : limit; - const { index, range } = query; - const store = trans.objectStore(tableName); - const source = index.isPrimaryKey ? store : store.index(index.name); - const idbKeyRange = makeIDBKeyRange(range); - if (limit === 0) - return resolve({ result: [] }); - if (hasGetAll) { - const req = values ? - source.getAll(idbKeyRange, nonInfinitLimit) : - source.getAllKeys(idbKeyRange, nonInfinitLimit); - req.onsuccess = event => resolve({ result: event.target.result }); - req.onerror = eventRejectHandler(reject); - } - else { - let count = 0; - const req = values || !('openKeyCursor' in source) ? - source.openCursor(idbKeyRange) : - source.openKeyCursor(idbKeyRange); - const result = []; - req.onsuccess = event => { - const cursor = req.result; - if (!cursor) - return resolve({ result }); - result.push(values ? cursor.value : cursor.primaryKey); - if (++count === limit) - return resolve({ result }); - cursor.continue(); - }; - req.onerror = eventRejectHandler(reject); - } - }); - }; - } - return { - name: tableName, - schema: tableSchema, - mutate, - getMany({ trans, keys }) { - return new Promise((resolve, reject) => { - resolve = wrap(resolve); - const store = trans.objectStore(tableName); - const length = keys.length; - const result = new Array(length); - let keyCount = 0; - let callbackCount = 0; - let req; - const successHandler = event => { - const req = event.target; - if ((result[req._pos] = req.result) != null) - ; - if (++callbackCount === keyCount) - resolve(result); - }; - const errorHandler = eventRejectHandler(reject); - for (let i = 0; i < length; ++i) { - const key = keys[i]; - if (key != null) { - req = store.get(keys[i]); - req._pos = i; - req.onsuccess = successHandler; - req.onerror = errorHandler; - ++keyCount; - } - } - if (keyCount === 0) - resolve(result); - }); - }, - get({ trans, key }) { - return new Promise((resolve, reject) => { - resolve = wrap(resolve); - const store = trans.objectStore(tableName); - const req = store.get(key); - req.onsuccess = event => resolve(event.target.result); - req.onerror = eventRejectHandler(reject); - }); - }, - query: query(hasGetAll), - openCursor, - count({ query, trans }) { - const { index, range } = query; - return new Promise((resolve, reject) => { - const store = trans.objectStore(tableName); - const source = index.isPrimaryKey ? store : store.index(index.name); - const idbKeyRange = makeIDBKeyRange(range); - const req = idbKeyRange ? source.count(idbKeyRange) : source.count(); - req.onsuccess = wrap(ev => resolve(ev.target.result)); - req.onerror = eventRejectHandler(reject); - }); - } - }; - } - const { schema, hasGetAll } = extractSchema(db, tmpTrans); - const tables = schema.tables.map(tableSchema => createDbCoreTable(tableSchema)); - const tableMap = {}; - tables.forEach(table => tableMap[table.name] = table); - return { - stack: "dbcore", - transaction: db.transaction.bind(db), - table(name) { - const result = tableMap[name]; - if (!result) - throw new Error(`Table '${name}' not found`); - return tableMap[name]; - }, - MIN_KEY: -Infinity, - MAX_KEY: getMaxKey(IdbKeyRange), - schema - }; -} - -function createMiddlewareStack(stackImpl, middlewares) { - return middlewares.reduce((down, { create }) => ({ ...down, ...create(down) }), stackImpl); -} -function createMiddlewareStacks(middlewares, idbdb, { IDBKeyRange, indexedDB }, tmpTrans) { - const dbcore = createMiddlewareStack(createDBCore(idbdb, IDBKeyRange, tmpTrans), middlewares.dbcore); - return { - dbcore - }; -} -function generateMiddlewareStacks({ _novip: db }, tmpTrans) { - const idbdb = tmpTrans.db; - const stacks = createMiddlewareStacks(db._middlewares, idbdb, db._deps, tmpTrans); - db.core = stacks.dbcore; - db.tables.forEach(table => { - const tableName = table.name; - if (db.core.schema.tables.some(tbl => tbl.name === tableName)) { - table.core = db.core.table(tableName); - if (db[tableName] instanceof db.Table) { - db[tableName].core = table.core; - } - } - }); -} - -function setApiOnPlace({ _novip: db }, objs, tableNames, dbschema) { - tableNames.forEach(tableName => { - const schema = dbschema[tableName]; - objs.forEach(obj => { - const propDesc = getPropertyDescriptor(obj, tableName); - if (!propDesc || ("value" in propDesc && propDesc.value === undefined)) { - if (obj === db.Transaction.prototype || obj instanceof db.Transaction) { - setProp(obj, tableName, { - get() { return this.table(tableName); }, - set(value) { - defineProperty(this, tableName, { value, writable: true, configurable: true, enumerable: true }); - } - }); - } - else { - obj[tableName] = new db.Table(tableName, schema); - } - } - }); - }); -} -function removeTablesApi({ _novip: db }, objs) { - objs.forEach(obj => { - for (let key in obj) { - if (obj[key] instanceof db.Table) - delete obj[key]; - } - }); -} -function lowerVersionFirst(a, b) { - return a._cfg.version - b._cfg.version; -} -function runUpgraders(db, oldVersion, idbUpgradeTrans, reject) { - const globalSchema = db._dbSchema; - const trans = db._createTransaction('readwrite', db._storeNames, globalSchema); - trans.create(idbUpgradeTrans); - trans._completion.catch(reject); - const rejectTransaction = trans._reject.bind(trans); - const transless = PSD.transless || PSD; - newScope(() => { - PSD.trans = trans; - PSD.transless = transless; - if (oldVersion === 0) { - keys(globalSchema).forEach(tableName => { - createTable(idbUpgradeTrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); - }); - generateMiddlewareStacks(db, idbUpgradeTrans); - DexiePromise.follow(() => db.on.populate.fire(trans)).catch(rejectTransaction); - } - else - updateTablesAndIndexes(db, oldVersion, trans, idbUpgradeTrans).catch(rejectTransaction); - }); -} -function updateTablesAndIndexes({ _novip: db }, oldVersion, trans, idbUpgradeTrans) { - const queue = []; - const versions = db._versions; - let globalSchema = db._dbSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans); - let anyContentUpgraderHasRun = false; - const versToRun = versions.filter(v => v._cfg.version >= oldVersion); - versToRun.forEach(version => { - queue.push(() => { - const oldSchema = globalSchema; - const newSchema = version._cfg.dbschema; - adjustToExistingIndexNames(db, oldSchema, idbUpgradeTrans); - adjustToExistingIndexNames(db, newSchema, idbUpgradeTrans); - globalSchema = db._dbSchema = newSchema; - const diff = getSchemaDiff(oldSchema, newSchema); - diff.add.forEach(tuple => { - createTable(idbUpgradeTrans, tuple[0], tuple[1].primKey, tuple[1].indexes); - }); - diff.change.forEach(change => { - if (change.recreate) { - throw new exceptions.Upgrade("Not yet support for changing primary key"); - } - else { - const store = idbUpgradeTrans.objectStore(change.name); - change.add.forEach(idx => addIndex(store, idx)); - change.change.forEach(idx => { - store.deleteIndex(idx.name); - addIndex(store, idx); - }); - change.del.forEach(idxName => store.deleteIndex(idxName)); - } - }); - const contentUpgrade = version._cfg.contentUpgrade; - if (contentUpgrade && version._cfg.version > oldVersion) { - generateMiddlewareStacks(db, idbUpgradeTrans); - trans._memoizedTables = {}; - anyContentUpgraderHasRun = true; - let upgradeSchema = shallowClone(newSchema); - diff.del.forEach(table => { - upgradeSchema[table] = oldSchema[table]; - }); - removeTablesApi(db, [db.Transaction.prototype]); - setApiOnPlace(db, [db.Transaction.prototype], keys(upgradeSchema), upgradeSchema); - trans.schema = upgradeSchema; - const contentUpgradeIsAsync = isAsyncFunction(contentUpgrade); - if (contentUpgradeIsAsync) { - incrementExpectedAwaits(); - } - let returnValue; - const promiseFollowed = DexiePromise.follow(() => { - returnValue = contentUpgrade(trans); - if (returnValue) { - if (contentUpgradeIsAsync) { - var decrementor = decrementExpectedAwaits.bind(null, null); - returnValue.then(decrementor, decrementor); - } - } - }); - return (returnValue && typeof returnValue.then === 'function' ? - DexiePromise.resolve(returnValue) : promiseFollowed.then(() => returnValue)); - } - }); - queue.push(idbtrans => { - if (!anyContentUpgraderHasRun || !hasIEDeleteObjectStoreBug) { - const newSchema = version._cfg.dbschema; - deleteRemovedTables(newSchema, idbtrans); - } - removeTablesApi(db, [db.Transaction.prototype]); - setApiOnPlace(db, [db.Transaction.prototype], db._storeNames, db._dbSchema); - trans.schema = db._dbSchema; - }); - }); - function runQueue() { - return queue.length ? DexiePromise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : - DexiePromise.resolve(); - } - return runQueue().then(() => { - createMissingTables(globalSchema, idbUpgradeTrans); - }); -} -function getSchemaDiff(oldSchema, newSchema) { - const diff = { - del: [], - add: [], - change: [] - }; - let table; - for (table in oldSchema) { - if (!newSchema[table]) - diff.del.push(table); - } - for (table in newSchema) { - const oldDef = oldSchema[table], newDef = newSchema[table]; - if (!oldDef) { - diff.add.push([table, newDef]); - } - else { - const change = { - name: table, - def: newDef, - recreate: false, - del: [], - add: [], - change: [] - }; - if (( - '' + (oldDef.primKey.keyPath || '')) !== ('' + (newDef.primKey.keyPath || '')) || - (oldDef.primKey.auto !== newDef.primKey.auto && !isIEOrEdge)) - { - change.recreate = true; - diff.change.push(change); - } - else { - const oldIndexes = oldDef.idxByName; - const newIndexes = newDef.idxByName; - let idxName; - for (idxName in oldIndexes) { - if (!newIndexes[idxName]) - change.del.push(idxName); - } - for (idxName in newIndexes) { - const oldIdx = oldIndexes[idxName], newIdx = newIndexes[idxName]; - if (!oldIdx) - change.add.push(newIdx); - else if (oldIdx.src !== newIdx.src) - change.change.push(newIdx); - } - if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { - diff.change.push(change); - } - } - } - } - return diff; -} -function createTable(idbtrans, tableName, primKey, indexes) { - const store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? - { keyPath: primKey.keyPath, autoIncrement: primKey.auto } : - { autoIncrement: primKey.auto }); - indexes.forEach(idx => addIndex(store, idx)); - return store; -} -function createMissingTables(newSchema, idbtrans) { - keys(newSchema).forEach(tableName => { - if (!idbtrans.db.objectStoreNames.contains(tableName)) { - createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); - } - }); -} -function deleteRemovedTables(newSchema, idbtrans) { - [].slice.call(idbtrans.db.objectStoreNames).forEach(storeName => newSchema[storeName] == null && idbtrans.db.deleteObjectStore(storeName)); -} -function addIndex(store, idx) { - store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi }); -} -function buildGlobalSchema(db, idbdb, tmpTrans) { - const globalSchema = {}; - const dbStoreNames = slice(idbdb.objectStoreNames, 0); - dbStoreNames.forEach(storeName => { - const store = tmpTrans.objectStore(storeName); - let keyPath = store.keyPath; - const primKey = createIndexSpec(nameFromKeyPath(keyPath), keyPath || "", false, false, !!store.autoIncrement, keyPath && typeof keyPath !== "string", true); - const indexes = []; - for (let j = 0; j < store.indexNames.length; ++j) { - const idbindex = store.index(store.indexNames[j]); - keyPath = idbindex.keyPath; - var index = createIndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== "string", false); - indexes.push(index); - } - globalSchema[storeName] = createTableSchema(storeName, primKey, indexes); - }); - return globalSchema; -} -function readGlobalSchema({ _novip: db }, idbdb, tmpTrans) { - db.verno = idbdb.version / 10; - const globalSchema = db._dbSchema = buildGlobalSchema(db, idbdb, tmpTrans); - db._storeNames = slice(idbdb.objectStoreNames, 0); - setApiOnPlace(db, [db._allTables], keys(globalSchema), globalSchema); -} -function verifyInstalledSchema(db, tmpTrans) { - const installedSchema = buildGlobalSchema(db, db.idbdb, tmpTrans); - const diff = getSchemaDiff(installedSchema, db._dbSchema); - return !(diff.add.length || diff.change.some(ch => ch.add.length || ch.change.length)); -} -function adjustToExistingIndexNames({ _novip: db }, schema, idbtrans) { - const storeNames = idbtrans.db.objectStoreNames; - for (let i = 0; i < storeNames.length; ++i) { - const storeName = storeNames[i]; - const store = idbtrans.objectStore(storeName); - db._hasGetAll = 'getAll' in store; - for (let j = 0; j < store.indexNames.length; ++j) { - const indexName = store.indexNames[j]; - const keyPath = store.index(indexName).keyPath; - const dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; - if (schema[storeName]) { - const indexSpec = schema[storeName].idxByName[dexieName]; - if (indexSpec) { - indexSpec.name = indexName; - delete schema[storeName].idxByName[dexieName]; - schema[storeName].idxByName[indexName] = indexSpec; - } - } - } - } - if (typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && - !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && - _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope && - [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) { - db._hasGetAll = false; - } -} -function parseIndexSyntax(primKeyAndIndexes) { - return primKeyAndIndexes.split(',').map((index, indexNum) => { - index = index.trim(); - const name = index.replace(/([&*]|\+\+)/g, ""); - const keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; - return createIndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), indexNum === 0); - }); -} - -class Version { - _parseStoresSpec(stores, outSchema) { - keys(stores).forEach(tableName => { - if (stores[tableName] !== null) { - var indexes = parseIndexSyntax(stores[tableName]); - var primKey = indexes.shift(); - if (primKey.multi) - throw new exceptions.Schema("Primary key cannot be multi-valued"); - indexes.forEach(idx => { - if (idx.auto) - throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)"); - if (!idx.keyPath) - throw new exceptions.Schema("Index must have a name and cannot be an empty string"); - }); - outSchema[tableName] = createTableSchema(tableName, primKey, indexes); - } - }); - } - stores(stores) { - const db = this.db; - this._cfg.storesSource = this._cfg.storesSource ? - extend(this._cfg.storesSource, stores) : - stores; - const versions = db._versions; - const storesSpec = {}; - let dbschema = {}; - versions.forEach(version => { - extend(storesSpec, version._cfg.storesSource); - dbschema = (version._cfg.dbschema = {}); - version._parseStoresSpec(storesSpec, dbschema); - }); - db._dbSchema = dbschema; - removeTablesApi(db, [db._allTables, db, db.Transaction.prototype]); - setApiOnPlace(db, [db._allTables, db, db.Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); - db._storeNames = keys(dbschema); - return this; - } - upgrade(upgradeFunction) { - this._cfg.contentUpgrade = promisableChain(this._cfg.contentUpgrade || nop, upgradeFunction); - return this; - } -} - -function createVersionConstructor(db) { - return makeClassConstructor(Version.prototype, function Version(versionNumber) { - this.db = db; - this._cfg = { - version: versionNumber, - storesSource: null, - dbschema: {}, - tables: {}, - contentUpgrade: null - }; - }); -} - -function getDbNamesTable(indexedDB, IDBKeyRange) { - let dbNamesDB = indexedDB["_dbNamesDB"]; - if (!dbNamesDB) { - dbNamesDB = indexedDB["_dbNamesDB"] = new Dexie$1(DBNAMES_DB, { - addons: [], - indexedDB, - IDBKeyRange, - }); - dbNamesDB.version(1).stores({ dbnames: "name" }); - } - return dbNamesDB.table("dbnames"); -} -function hasDatabasesNative(indexedDB) { - return indexedDB && typeof indexedDB.databases === "function"; -} -function getDatabaseNames({ indexedDB, IDBKeyRange, }) { - return hasDatabasesNative(indexedDB) - ? Promise.resolve(indexedDB.databases()).then((infos) => infos - .map((info) => info.name) - .filter((name) => name !== DBNAMES_DB)) - : getDbNamesTable(indexedDB, IDBKeyRange).toCollection().primaryKeys(); -} -function _onDatabaseCreated({ indexedDB, IDBKeyRange }, name) { - !hasDatabasesNative(indexedDB) && - name !== DBNAMES_DB && - getDbNamesTable(indexedDB, IDBKeyRange).put({ name }).catch(nop); -} -function _onDatabaseDeleted({ indexedDB, IDBKeyRange }, name) { - !hasDatabasesNative(indexedDB) && - name !== DBNAMES_DB && - getDbNamesTable(indexedDB, IDBKeyRange).delete(name).catch(nop); -} - -function vip(fn) { - return newScope(function () { - PSD.letThrough = true; - return fn(); - }); -} - -function idbReady() { - var isSafari = !navigator.userAgentData && - /Safari\//.test(navigator.userAgent) && - !/Chrom(e|ium)\//.test(navigator.userAgent); - if (!isSafari || !indexedDB.databases) - return Promise.resolve(); - var intervalId; - return new Promise(function (resolve) { - var tryIdb = function () { return indexedDB.databases().finally(resolve); }; - intervalId = setInterval(tryIdb, 100); - tryIdb(); - }).finally(function () { return clearInterval(intervalId); }); -} - -function dexieOpen(db) { - const state = db._state; - const { indexedDB } = db._deps; - if (state.isBeingOpened || db.idbdb) - return state.dbReadyPromise.then(() => state.dbOpenError ? - rejection(state.dbOpenError) : - db); - debug && (state.openCanceller._stackHolder = getErrorWithStack()); - state.isBeingOpened = true; - state.dbOpenError = null; - state.openComplete = false; - const openCanceller = state.openCanceller; - function throwIfCancelled() { - if (state.openCanceller !== openCanceller) - throw new exceptions.DatabaseClosed('db.open() was cancelled'); - } - let resolveDbReady = state.dbReadyResolve, - upgradeTransaction = null, wasCreated = false; - return DexiePromise.race([openCanceller, (typeof navigator === 'undefined' ? DexiePromise.resolve() : idbReady()).then(() => new DexiePromise((resolve, reject) => { - throwIfCancelled(); - if (!indexedDB) - throw new exceptions.MissingAPI(); - const dbName = db.name; - const req = state.autoSchema ? - indexedDB.open(dbName) : - indexedDB.open(dbName, Math.round(db.verno * 10)); - if (!req) - throw new exceptions.MissingAPI(); - req.onerror = eventRejectHandler(reject); - req.onblocked = wrap(db._fireOnBlocked); - req.onupgradeneeded = wrap(e => { - upgradeTransaction = req.transaction; - if (state.autoSchema && !db._options.allowEmptyDB) { - req.onerror = preventDefault; - upgradeTransaction.abort(); - req.result.close(); - const delreq = indexedDB.deleteDatabase(dbName); - delreq.onsuccess = delreq.onerror = wrap(() => { - reject(new exceptions.NoSuchDatabase(`Database ${dbName} doesnt exist`)); - }); - } - else { - upgradeTransaction.onerror = eventRejectHandler(reject); - var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; - wasCreated = oldVer < 1; - db._novip.idbdb = req.result; - runUpgraders(db, oldVer / 10, upgradeTransaction, reject); - } - }, reject); - req.onsuccess = wrap(() => { - upgradeTransaction = null; - const idbdb = db._novip.idbdb = req.result; - const objectStoreNames = slice(idbdb.objectStoreNames); - if (objectStoreNames.length > 0) - try { - const tmpTrans = idbdb.transaction(safariMultiStoreFix(objectStoreNames), 'readonly'); - if (state.autoSchema) - readGlobalSchema(db, idbdb, tmpTrans); - else { - adjustToExistingIndexNames(db, db._dbSchema, tmpTrans); - if (!verifyInstalledSchema(db, tmpTrans)) { - console.warn(`Dexie SchemaDiff: Schema was extended without increasing the number passed to db.version(). Some queries may fail.`); - } - } - generateMiddlewareStacks(db, tmpTrans); - } - catch (e) { - } - connections.push(db); - idbdb.onversionchange = wrap(ev => { - state.vcFired = true; - db.on("versionchange").fire(ev); - }); - idbdb.onclose = wrap(ev => { - db.on("close").fire(ev); - }); - if (wasCreated) - _onDatabaseCreated(db._deps, dbName); - resolve(); - }, reject); - }))]).then(() => { - throwIfCancelled(); - state.onReadyBeingFired = []; - return DexiePromise.resolve(vip(() => db.on.ready.fire(db.vip))).then(function fireRemainders() { - if (state.onReadyBeingFired.length > 0) { - let remainders = state.onReadyBeingFired.reduce(promisableChain, nop); - state.onReadyBeingFired = []; - return DexiePromise.resolve(vip(() => remainders(db.vip))).then(fireRemainders); - } - }); - }).finally(() => { - state.onReadyBeingFired = null; - state.isBeingOpened = false; - }).then(() => { - return db; - }).catch(err => { - state.dbOpenError = err; - try { - upgradeTransaction && upgradeTransaction.abort(); - } - catch (_a) { } - if (openCanceller === state.openCanceller) { - db._close(); - } - return rejection(err); - }).finally(() => { - state.openComplete = true; - resolveDbReady(); - }); -} - -function awaitIterator(iterator) { - var callNext = result => iterator.next(result), doThrow = error => iterator.throw(error), onSuccess = step(callNext), onError = step(doThrow); - function step(getNext) { - return (val) => { - var next = getNext(val), value = next.value; - return next.done ? value : - (!value || typeof value.then !== 'function' ? - isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : - value.then(onSuccess, onError)); - }; - } - return step(callNext)(); -} - -function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { - var i = arguments.length; - if (i < 2) - throw new exceptions.InvalidArgument("Too few arguments"); - var args = new Array(i - 1); - while (--i) - args[i - 1] = arguments[i]; - scopeFunc = args.pop(); - var tables = flatten(args); - return [mode, tables, scopeFunc]; -} -function enterTransactionScope(db, mode, storeNames, parentTransaction, scopeFunc) { - return DexiePromise.resolve().then(() => { - const transless = PSD.transless || PSD; - const trans = db._createTransaction(mode, storeNames, db._dbSchema, parentTransaction); - const zoneProps = { - trans: trans, - transless: transless - }; - if (parentTransaction) { - trans.idbtrans = parentTransaction.idbtrans; - } - else { - try { - trans.create(); - db._state.PR1398_maxLoop = 3; - } - catch (ex) { - if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { - console.warn('Dexie: Need to reopen db'); - db._close(); - return db.open().then(() => enterTransactionScope(db, mode, storeNames, null, scopeFunc)); - } - return rejection(ex); - } - } - const scopeFuncIsAsync = isAsyncFunction(scopeFunc); - if (scopeFuncIsAsync) { - incrementExpectedAwaits(); - } - let returnValue; - const promiseFollowed = DexiePromise.follow(() => { - returnValue = scopeFunc.call(trans, trans); - if (returnValue) { - if (scopeFuncIsAsync) { - var decrementor = decrementExpectedAwaits.bind(null, null); - returnValue.then(decrementor, decrementor); - } - else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { - returnValue = awaitIterator(returnValue); - } - } - }, zoneProps); - return (returnValue && typeof returnValue.then === 'function' ? - DexiePromise.resolve(returnValue).then(x => trans.active ? - x - : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn"))) - : promiseFollowed.then(() => returnValue)).then(x => { - if (parentTransaction) - trans._resolve(); - return trans._completion.then(() => x); - }).catch(e => { - trans._reject(e); - return rejection(e); - }); - }); -} - -function pad(a, value, count) { - const result = isArray(a) ? a.slice() : [a]; - for (let i = 0; i < count; ++i) - result.push(value); - return result; -} -function createVirtualIndexMiddleware(down) { - return { - ...down, - table(tableName) { - const table = down.table(tableName); - const { schema } = table; - const indexLookup = {}; - const allVirtualIndexes = []; - function addVirtualIndexes(keyPath, keyTail, lowLevelIndex) { - const keyPathAlias = getKeyPathAlias(keyPath); - const indexList = (indexLookup[keyPathAlias] = indexLookup[keyPathAlias] || []); - const keyLength = keyPath == null ? 0 : typeof keyPath === 'string' ? 1 : keyPath.length; - const isVirtual = keyTail > 0; - const virtualIndex = { - ...lowLevelIndex, - isVirtual, - keyTail, - keyLength, - extractKey: getKeyExtractor(keyPath), - unique: !isVirtual && lowLevelIndex.unique - }; - indexList.push(virtualIndex); - if (!virtualIndex.isPrimaryKey) { - allVirtualIndexes.push(virtualIndex); - } - if (keyLength > 1) { - const virtualKeyPath = keyLength === 2 ? - keyPath[0] : - keyPath.slice(0, keyLength - 1); - addVirtualIndexes(virtualKeyPath, keyTail + 1, lowLevelIndex); - } - indexList.sort((a, b) => a.keyTail - b.keyTail); - return virtualIndex; - } - const primaryKey = addVirtualIndexes(schema.primaryKey.keyPath, 0, schema.primaryKey); - indexLookup[":id"] = [primaryKey]; - for (const index of schema.indexes) { - addVirtualIndexes(index.keyPath, 0, index); - } - function findBestIndex(keyPath) { - const result = indexLookup[getKeyPathAlias(keyPath)]; - return result && result[0]; - } - function translateRange(range, keyTail) { - return { - type: range.type === 1 ? - 2 : - range.type, - lower: pad(range.lower, range.lowerOpen ? down.MAX_KEY : down.MIN_KEY, keyTail), - lowerOpen: true, - upper: pad(range.upper, range.upperOpen ? down.MIN_KEY : down.MAX_KEY, keyTail), - upperOpen: true - }; - } - function translateRequest(req) { - const index = req.query.index; - return index.isVirtual ? { - ...req, - query: { - index, - range: translateRange(req.query.range, index.keyTail) - } - } : req; - } - const result = { - ...table, - schema: { - ...schema, - primaryKey, - indexes: allVirtualIndexes, - getIndexByKeyPath: findBestIndex - }, - count(req) { - return table.count(translateRequest(req)); - }, - query(req) { - return table.query(translateRequest(req)); - }, - openCursor(req) { - const { keyTail, isVirtual, keyLength } = req.query.index; - if (!isVirtual) - return table.openCursor(req); - function createVirtualCursor(cursor) { - function _continue(key) { - key != null ? - cursor.continue(pad(key, req.reverse ? down.MAX_KEY : down.MIN_KEY, keyTail)) : - req.unique ? - cursor.continue(cursor.key.slice(0, keyLength) - .concat(req.reverse - ? down.MIN_KEY - : down.MAX_KEY, keyTail)) : - cursor.continue(); - } - const virtualCursor = Object.create(cursor, { - continue: { value: _continue }, - continuePrimaryKey: { - value(key, primaryKey) { - cursor.continuePrimaryKey(pad(key, down.MAX_KEY, keyTail), primaryKey); - } - }, - primaryKey: { - get() { - return cursor.primaryKey; - } - }, - key: { - get() { - const key = cursor.key; - return keyLength === 1 ? - key[0] : - key.slice(0, keyLength); - } - }, - value: { - get() { - return cursor.value; - } - } - }); - return virtualCursor; - } - return table.openCursor(translateRequest(req)) - .then(cursor => cursor && createVirtualCursor(cursor)); - } - }; - return result; - } - }; -} -const virtualIndexMiddleware = { - stack: "dbcore", - name: "VirtualIndexMiddleware", - level: 1, - create: createVirtualIndexMiddleware -}; - -function getObjectDiff(a, b, rv, prfx) { - rv = rv || {}; - prfx = prfx || ''; - keys(a).forEach((prop) => { - if (!hasOwn(b, prop)) { - rv[prfx + prop] = undefined; - } - else { - var ap = a[prop], bp = b[prop]; - if (typeof ap === 'object' && typeof bp === 'object' && ap && bp) { - const apTypeName = toStringTag(ap); - const bpTypeName = toStringTag(bp); - if (apTypeName !== bpTypeName) { - rv[prfx + prop] = b[prop]; - } - else if (apTypeName === 'Object') { - getObjectDiff(ap, bp, rv, prfx + prop + '.'); - } - else if (ap !== bp) { - rv[prfx + prop] = b[prop]; - } - } - else if (ap !== bp) - rv[prfx + prop] = b[prop]; - } - }); - keys(b).forEach((prop) => { - if (!hasOwn(a, prop)) { - rv[prfx + prop] = b[prop]; - } - }); - return rv; -} - -function getEffectiveKeys(primaryKey, req) { - if (req.type === 'delete') - return req.keys; - return req.keys || req.values.map(primaryKey.extractKey); -} - -const hooksMiddleware = { - stack: "dbcore", - name: "HooksMiddleware", - level: 2, - create: (downCore) => ({ - ...downCore, - table(tableName) { - const downTable = downCore.table(tableName); - const { primaryKey } = downTable.schema; - const tableMiddleware = { - ...downTable, - mutate(req) { - const dxTrans = PSD.trans; - const { deleting, creating, updating } = dxTrans.table(tableName).hook; - switch (req.type) { - case 'add': - if (creating.fire === nop) - break; - return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); - case 'put': - if (creating.fire === nop && updating.fire === nop) - break; - return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); - case 'delete': - if (deleting.fire === nop) - break; - return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); - case 'deleteRange': - if (deleting.fire === nop) - break; - return dxTrans._promise('readwrite', () => deleteRange(req), true); - } - return downTable.mutate(req); - function addPutOrDelete(req) { - const dxTrans = PSD.trans; - const keys = req.keys || getEffectiveKeys(primaryKey, req); - if (!keys) - throw new Error("Keys missing"); - req = req.type === 'add' || req.type === 'put' ? - { ...req, keys } : - { ...req }; - if (req.type !== 'delete') - req.values = [...req.values]; - if (req.keys) - req.keys = [...req.keys]; - return getExistingValues(downTable, req, keys).then(existingValues => { - const contexts = keys.map((key, i) => { - const existingValue = existingValues[i]; - const ctx = { onerror: null, onsuccess: null }; - if (req.type === 'delete') { - deleting.fire.call(ctx, key, existingValue, dxTrans); - } - else if (req.type === 'add' || existingValue === undefined) { - const generatedPrimaryKey = creating.fire.call(ctx, key, req.values[i], dxTrans); - if (key == null && generatedPrimaryKey != null) { - key = generatedPrimaryKey; - req.keys[i] = key; - if (!primaryKey.outbound) { - setByKeyPath(req.values[i], primaryKey.keyPath, key); - } - } - } - else { - const objectDiff = getObjectDiff(existingValue, req.values[i]); - const additionalChanges = updating.fire.call(ctx, objectDiff, key, existingValue, dxTrans); - if (additionalChanges) { - const requestedValue = req.values[i]; - Object.keys(additionalChanges).forEach(keyPath => { - if (hasOwn(requestedValue, keyPath)) { - requestedValue[keyPath] = additionalChanges[keyPath]; - } - else { - setByKeyPath(requestedValue, keyPath, additionalChanges[keyPath]); - } - }); - } - } - return ctx; - }); - return downTable.mutate(req).then(({ failures, results, numFailures, lastResult }) => { - for (let i = 0; i < keys.length; ++i) { - const primKey = results ? results[i] : keys[i]; - const ctx = contexts[i]; - if (primKey == null) { - ctx.onerror && ctx.onerror(failures[i]); - } - else { - ctx.onsuccess && ctx.onsuccess(req.type === 'put' && existingValues[i] ? - req.values[i] : - primKey - ); - } - } - return { failures, results, numFailures, lastResult }; - }).catch(error => { - contexts.forEach(ctx => ctx.onerror && ctx.onerror(error)); - return Promise.reject(error); - }); - }); - } - function deleteRange(req) { - return deleteNextChunk(req.trans, req.range, 10000); - } - function deleteNextChunk(trans, range, limit) { - return downTable.query({ trans, values: false, query: { index: primaryKey, range }, limit }) - .then(({ result }) => { - return addPutOrDelete({ type: 'delete', keys: result, trans }).then(res => { - if (res.numFailures > 0) - return Promise.reject(res.failures[0]); - if (result.length < limit) { - return { failures: [], numFailures: 0, lastResult: undefined }; - } - else { - return deleteNextChunk(trans, { ...range, lower: result[result.length - 1], lowerOpen: true }, limit); - } - }); - }); - } - } - }; - return tableMiddleware; - }, - }) -}; -function getExistingValues(table, req, effectiveKeys) { - return req.type === "add" - ? Promise.resolve([]) - : table.getMany({ trans: req.trans, keys: effectiveKeys, cache: "immutable" }); -} - -function getFromTransactionCache(keys, cache, clone) { - try { - if (!cache) - return null; - if (cache.keys.length < keys.length) - return null; - const result = []; - for (let i = 0, j = 0; i < cache.keys.length && j < keys.length; ++i) { - if (cmp(cache.keys[i], keys[j]) !== 0) - continue; - result.push(clone ? deepClone(cache.values[i]) : cache.values[i]); - ++j; - } - return result.length === keys.length ? result : null; - } - catch (_a) { - return null; - } -} -const cacheExistingValuesMiddleware = { - stack: "dbcore", - level: -1, - create: (core) => { - return { - table: (tableName) => { - const table = core.table(tableName); - return { - ...table, - getMany: (req) => { - if (!req.cache) { - return table.getMany(req); - } - const cachedResult = getFromTransactionCache(req.keys, req.trans["_cache"], req.cache === "clone"); - if (cachedResult) { - return DexiePromise.resolve(cachedResult); - } - return table.getMany(req).then((res) => { - req.trans["_cache"] = { - keys: req.keys, - values: req.cache === "clone" ? deepClone(res) : res, - }; - return res; - }); - }, - mutate: (req) => { - if (req.type !== "add") - req.trans["_cache"] = null; - return table.mutate(req); - }, - }; - }, - }; - }, -}; - -function isEmptyRange(node) { - return !("from" in node); -} -const RangeSet = function (fromOrTree, to) { - if (this) { - extend(this, arguments.length ? { d: 1, from: fromOrTree, to: arguments.length > 1 ? to : fromOrTree } : { d: 0 }); - } - else { - const rv = new RangeSet(); - if (fromOrTree && ("d" in fromOrTree)) { - extend(rv, fromOrTree); - } - return rv; - } -}; -props(RangeSet.prototype, { - add(rangeSet) { - mergeRanges(this, rangeSet); - return this; - }, - addKey(key) { - addRange(this, key, key); - return this; - }, - addKeys(keys) { - keys.forEach(key => addRange(this, key, key)); - return this; - }, - [iteratorSymbol]() { - return getRangeSetIterator(this); - } -}); -function addRange(target, from, to) { - const diff = cmp(from, to); - if (isNaN(diff)) - return; - if (diff > 0) - throw RangeError(); - if (isEmptyRange(target)) - return extend(target, { from, to, d: 1 }); - const left = target.l; - const right = target.r; - if (cmp(to, target.from) < 0) { - left - ? addRange(left, from, to) - : (target.l = { from, to, d: 1, l: null, r: null }); - return rebalance(target); - } - if (cmp(from, target.to) > 0) { - right - ? addRange(right, from, to) - : (target.r = { from, to, d: 1, l: null, r: null }); - return rebalance(target); - } - if (cmp(from, target.from) < 0) { - target.from = from; - target.l = null; - target.d = right ? right.d + 1 : 1; - } - if (cmp(to, target.to) > 0) { - target.to = to; - target.r = null; - target.d = target.l ? target.l.d + 1 : 1; - } - const rightWasCutOff = !target.r; - if (left && !target.l) { - mergeRanges(target, left); - } - if (right && rightWasCutOff) { - mergeRanges(target, right); - } -} -function mergeRanges(target, newSet) { - function _addRangeSet(target, { from, to, l, r }) { - addRange(target, from, to); - if (l) - _addRangeSet(target, l); - if (r) - _addRangeSet(target, r); - } - if (!isEmptyRange(newSet)) - _addRangeSet(target, newSet); -} -function rangesOverlap(rangeSet1, rangeSet2) { - const i1 = getRangeSetIterator(rangeSet2); - let nextResult1 = i1.next(); - if (nextResult1.done) - return false; - let a = nextResult1.value; - const i2 = getRangeSetIterator(rangeSet1); - let nextResult2 = i2.next(a.from); - let b = nextResult2.value; - while (!nextResult1.done && !nextResult2.done) { - if (cmp(b.from, a.to) <= 0 && cmp(b.to, a.from) >= 0) - return true; - cmp(a.from, b.from) < 0 - ? (a = (nextResult1 = i1.next(b.from)).value) - : (b = (nextResult2 = i2.next(a.from)).value); - } - return false; -} -function getRangeSetIterator(node) { - let state = isEmptyRange(node) ? null : { s: 0, n: node }; - return { - next(key) { - const keyProvided = arguments.length > 0; - while (state) { - switch (state.s) { - case 0: - state.s = 1; - if (keyProvided) { - while (state.n.l && cmp(key, state.n.from) < 0) - state = { up: state, n: state.n.l, s: 1 }; - } - else { - while (state.n.l) - state = { up: state, n: state.n.l, s: 1 }; - } - case 1: - state.s = 2; - if (!keyProvided || cmp(key, state.n.to) <= 0) - return { value: state.n, done: false }; - case 2: - if (state.n.r) { - state.s = 3; - state = { up: state, n: state.n.r, s: 0 }; - continue; - } - case 3: - state = state.up; - } - } - return { done: true }; - }, - }; -} -function rebalance(target) { - var _a, _b; - const diff = (((_a = target.r) === null || _a === void 0 ? void 0 : _a.d) || 0) - (((_b = target.l) === null || _b === void 0 ? void 0 : _b.d) || 0); - const r = diff > 1 ? "r" : diff < -1 ? "l" : ""; - if (r) { - const l = r === "r" ? "l" : "r"; - const rootClone = { ...target }; - const oldRootRight = target[r]; - target.from = oldRootRight.from; - target.to = oldRootRight.to; - target[r] = oldRootRight[r]; - rootClone[r] = oldRootRight[l]; - target[l] = rootClone; - rootClone.d = computeDepth(rootClone); - } - target.d = computeDepth(target); -} -function computeDepth({ r, l }) { - return (r ? (l ? Math.max(r.d, l.d) : r.d) : l ? l.d : 0) + 1; -} - -const observabilityMiddleware = { - stack: "dbcore", - level: 0, - create: (core) => { - const dbName = core.schema.name; - const FULL_RANGE = new RangeSet(core.MIN_KEY, core.MAX_KEY); - return { - ...core, - table: (tableName) => { - const table = core.table(tableName); - const { schema } = table; - const { primaryKey } = schema; - const { extractKey, outbound } = primaryKey; - const tableClone = { - ...table, - mutate: (req) => { - const trans = req.trans; - const mutatedParts = trans.mutatedParts || (trans.mutatedParts = {}); - const getRangeSet = (indexName) => { - const part = `idb://${dbName}/${tableName}/${indexName}`; - return (mutatedParts[part] || - (mutatedParts[part] = new RangeSet())); - }; - const pkRangeSet = getRangeSet(""); - const delsRangeSet = getRangeSet(":dels"); - const { type } = req; - let [keys, newObjs] = req.type === "deleteRange" - ? [req.range] - : req.type === "delete" - ? [req.keys] - : req.values.length < 50 - ? [[], req.values] - : []; - const oldCache = req.trans["_cache"]; - return table.mutate(req).then((res) => { - if (isArray(keys)) { - if (type !== "delete") - keys = res.results; - pkRangeSet.addKeys(keys); - const oldObjs = getFromTransactionCache(keys, oldCache); - if (!oldObjs && type !== "add") { - delsRangeSet.addKeys(keys); - } - if (oldObjs || newObjs) { - trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs); - } - } - else if (keys) { - const range = { from: keys.lower, to: keys.upper }; - delsRangeSet.add(range); - pkRangeSet.add(range); - } - else { - pkRangeSet.add(FULL_RANGE); - delsRangeSet.add(FULL_RANGE); - schema.indexes.forEach(idx => getRangeSet(idx.name).add(FULL_RANGE)); - } - return res; - }); - }, - }; - const getRange = ({ query: { index, range }, }) => { - var _a, _b; - return [ - index, - new RangeSet((_a = range.lower) !== null && _a !== void 0 ? _a : core.MIN_KEY, (_b = range.upper) !== null && _b !== void 0 ? _b : core.MAX_KEY), - ]; - }; - const readSubscribers = { - get: (req) => [primaryKey, new RangeSet(req.key)], - getMany: (req) => [primaryKey, new RangeSet().addKeys(req.keys)], - count: getRange, - query: getRange, - openCursor: getRange, - }; - keys(readSubscribers).forEach(method => { - tableClone[method] = function (req) { - const { subscr } = PSD; - if (subscr) { - const getRangeSet = (indexName) => { - const part = `idb://${dbName}/${tableName}/${indexName}`; - return (subscr[part] || - (subscr[part] = new RangeSet())); - }; - const pkRangeSet = getRangeSet(""); - const delsRangeSet = getRangeSet(":dels"); - const [queriedIndex, queriedRanges] = readSubscribers[method](req); - getRangeSet(queriedIndex.name || "").add(queriedRanges); - if (!queriedIndex.isPrimaryKey) { - if (method === "count") { - delsRangeSet.add(FULL_RANGE); - } - else { - const keysPromise = method === "query" && - outbound && - req.values && - table.query({ - ...req, - values: false, - }); - return table[method].apply(this, arguments).then((res) => { - if (method === "query") { - if (outbound && req.values) { - return keysPromise.then(({ result: resultingKeys }) => { - pkRangeSet.addKeys(resultingKeys); - return res; - }); - } - const pKeys = req.values - ? res.result.map(extractKey) - : res.result; - if (req.values) { - pkRangeSet.addKeys(pKeys); - } - else { - delsRangeSet.addKeys(pKeys); - } - } - else if (method === "openCursor") { - const cursor = res; - const wantValues = req.values; - return (cursor && - Object.create(cursor, { - key: { - get() { - delsRangeSet.addKey(cursor.primaryKey); - return cursor.key; - }, - }, - primaryKey: { - get() { - const pkey = cursor.primaryKey; - delsRangeSet.addKey(pkey); - return pkey; - }, - }, - value: { - get() { - wantValues && pkRangeSet.addKey(cursor.primaryKey); - return cursor.value; - }, - }, - })); - } - return res; - }); - } - } - } - return table[method].apply(this, arguments); - }; - }); - return tableClone; - }, - }; - }, -}; -function trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs) { - function addAffectedIndex(ix) { - const rangeSet = getRangeSet(ix.name || ""); - function extractKey(obj) { - return obj != null ? ix.extractKey(obj) : null; - } - const addKeyOrKeys = (key) => ix.multiEntry && isArray(key) - ? key.forEach(key => rangeSet.addKey(key)) - : rangeSet.addKey(key); - (oldObjs || newObjs).forEach((_, i) => { - const oldKey = oldObjs && extractKey(oldObjs[i]); - const newKey = newObjs && extractKey(newObjs[i]); - if (cmp(oldKey, newKey) !== 0) { - if (oldKey != null) - addKeyOrKeys(oldKey); - if (newKey != null) - addKeyOrKeys(newKey); - } - }); - } - schema.indexes.forEach(addAffectedIndex); -} - -class Dexie$1 { - constructor(name, options) { - this._middlewares = {}; - this.verno = 0; - const deps = Dexie$1.dependencies; - this._options = options = { - addons: Dexie$1.addons, - autoOpen: true, - indexedDB: deps.indexedDB, - IDBKeyRange: deps.IDBKeyRange, - ...options - }; - this._deps = { - indexedDB: options.indexedDB, - IDBKeyRange: options.IDBKeyRange - }; - const { addons, } = options; - this._dbSchema = {}; - this._versions = []; - this._storeNames = []; - this._allTables = {}; - this.idbdb = null; - this._novip = this; - const state = { - dbOpenError: null, - isBeingOpened: false, - onReadyBeingFired: null, - openComplete: false, - dbReadyResolve: nop, - dbReadyPromise: null, - cancelOpen: nop, - openCanceller: null, - autoSchema: true, - PR1398_maxLoop: 3 - }; - state.dbReadyPromise = new DexiePromise(resolve => { - state.dbReadyResolve = resolve; - }); - state.openCanceller = new DexiePromise((_, reject) => { - state.cancelOpen = reject; - }); - this._state = state; - this.name = name; - this.on = Events(this, "populate", "blocked", "versionchange", "close", { ready: [promisableChain, nop] }); - this.on.ready.subscribe = override(this.on.ready.subscribe, subscribe => { - return (subscriber, bSticky) => { - Dexie$1.vip(() => { - const state = this._state; - if (state.openComplete) { - if (!state.dbOpenError) - DexiePromise.resolve().then(subscriber); - if (bSticky) - subscribe(subscriber); - } - else if (state.onReadyBeingFired) { - state.onReadyBeingFired.push(subscriber); - if (bSticky) - subscribe(subscriber); - } - else { - subscribe(subscriber); - const db = this; - if (!bSticky) - subscribe(function unsubscribe() { - db.on.ready.unsubscribe(subscriber); - db.on.ready.unsubscribe(unsubscribe); - }); - } - }); - }; - }); - this.Collection = createCollectionConstructor(this); - this.Table = createTableConstructor(this); - this.Transaction = createTransactionConstructor(this); - this.Version = createVersionConstructor(this); - this.WhereClause = createWhereClauseConstructor(this); - this.on("versionchange", ev => { - if (ev.newVersion > 0) - console.warn(`Another connection wants to upgrade database '${this.name}'. Closing db now to resume the upgrade.`); - else - console.warn(`Another connection wants to delete database '${this.name}'. Closing db now to resume the delete request.`); - this.close(); - }); - this.on("blocked", ev => { - if (!ev.newVersion || ev.newVersion < ev.oldVersion) - console.warn(`Dexie.delete('${this.name}') was blocked`); - else - console.warn(`Upgrade '${this.name}' blocked by other connection holding version ${ev.oldVersion / 10}`); - }); - this._maxKey = getMaxKey(options.IDBKeyRange); - this._createTransaction = (mode, storeNames, dbschema, parentTransaction) => new this.Transaction(mode, storeNames, dbschema, this._options.chromeTransactionDurability, parentTransaction); - this._fireOnBlocked = ev => { - this.on("blocked").fire(ev); - connections - .filter(c => c.name === this.name && c !== this && !c._state.vcFired) - .map(c => c.on("versionchange").fire(ev)); - }; - this.use(virtualIndexMiddleware); - this.use(hooksMiddleware); - this.use(observabilityMiddleware); - this.use(cacheExistingValuesMiddleware); - this.vip = Object.create(this, { _vip: { value: true } }); - addons.forEach(addon => addon(this)); - } - version(versionNumber) { - if (isNaN(versionNumber) || versionNumber < 0.1) - throw new exceptions.Type(`Given version is not a positive number`); - versionNumber = Math.round(versionNumber * 10) / 10; - if (this.idbdb || this._state.isBeingOpened) - throw new exceptions.Schema("Cannot add version when database is open"); - this.verno = Math.max(this.verno, versionNumber); - const versions = this._versions; - var versionInstance = versions.filter(v => v._cfg.version === versionNumber)[0]; - if (versionInstance) - return versionInstance; - versionInstance = new this.Version(versionNumber); - versions.push(versionInstance); - versions.sort(lowerVersionFirst); - versionInstance.stores({}); - this._state.autoSchema = false; - return versionInstance; - } - _whenReady(fn) { - return (this.idbdb && (this._state.openComplete || PSD.letThrough || this._vip)) ? fn() : new DexiePromise((resolve, reject) => { - if (this._state.openComplete) { - return reject(new exceptions.DatabaseClosed(this._state.dbOpenError)); - } - if (!this._state.isBeingOpened) { - if (!this._options.autoOpen) { - reject(new exceptions.DatabaseClosed()); - return; - } - this.open().catch(nop); - } - this._state.dbReadyPromise.then(resolve, reject); - }).then(fn); - } - use({ stack, create, level, name }) { - if (name) - this.unuse({ stack, name }); - const middlewares = this._middlewares[stack] || (this._middlewares[stack] = []); - middlewares.push({ stack, create, level: level == null ? 10 : level, name }); - middlewares.sort((a, b) => a.level - b.level); - return this; - } - unuse({ stack, name, create }) { - if (stack && this._middlewares[stack]) { - this._middlewares[stack] = this._middlewares[stack].filter(mw => create ? mw.create !== create : - name ? mw.name !== name : - false); - } - return this; - } - open() { - return dexieOpen(this); - } - _close() { - const state = this._state; - const idx = connections.indexOf(this); - if (idx >= 0) - connections.splice(idx, 1); - if (this.idbdb) { - try { - this.idbdb.close(); - } - catch (e) { } - this._novip.idbdb = null; - } - state.dbReadyPromise = new DexiePromise(resolve => { - state.dbReadyResolve = resolve; - }); - state.openCanceller = new DexiePromise((_, reject) => { - state.cancelOpen = reject; - }); - } - close() { - this._close(); - const state = this._state; - this._options.autoOpen = false; - state.dbOpenError = new exceptions.DatabaseClosed(); - if (state.isBeingOpened) - state.cancelOpen(state.dbOpenError); - } - delete() { - const hasArguments = arguments.length > 0; - const state = this._state; - return new DexiePromise((resolve, reject) => { - const doDelete = () => { - this.close(); - var req = this._deps.indexedDB.deleteDatabase(this.name); - req.onsuccess = wrap(() => { - _onDatabaseDeleted(this._deps, this.name); - resolve(); - }); - req.onerror = eventRejectHandler(reject); - req.onblocked = this._fireOnBlocked; - }; - if (hasArguments) - throw new exceptions.InvalidArgument("Arguments not allowed in db.delete()"); - if (state.isBeingOpened) { - state.dbReadyPromise.then(doDelete); - } - else { - doDelete(); - } - }); - } - backendDB() { - return this.idbdb; - } - isOpen() { - return this.idbdb !== null; - } - hasBeenClosed() { - const dbOpenError = this._state.dbOpenError; - return dbOpenError && (dbOpenError.name === 'DatabaseClosed'); - } - hasFailed() { - return this._state.dbOpenError !== null; - } - dynamicallyOpened() { - return this._state.autoSchema; - } - get tables() { - return keys(this._allTables).map(name => this._allTables[name]); - } - transaction() { - const args = extractTransactionArgs.apply(this, arguments); - return this._transaction.apply(this, args); - } - _transaction(mode, tables, scopeFunc) { - let parentTransaction = PSD.trans; - if (!parentTransaction || parentTransaction.db !== this || mode.indexOf('!') !== -1) - parentTransaction = null; - const onlyIfCompatible = mode.indexOf('?') !== -1; - mode = mode.replace('!', '').replace('?', ''); - let idbMode, storeNames; - try { - storeNames = tables.map(table => { - var storeName = table instanceof this.Table ? table.name : table; - if (typeof storeName !== 'string') - throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); - return storeName; - }); - if (mode == "r" || mode === READONLY) - idbMode = READONLY; - else if (mode == "rw" || mode == READWRITE) - idbMode = READWRITE; - else - throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); - if (parentTransaction) { - if (parentTransaction.mode === READONLY && idbMode === READWRITE) { - if (onlyIfCompatible) { - parentTransaction = null; - } - else - throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); - } - if (parentTransaction) { - storeNames.forEach(storeName => { - if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { - if (onlyIfCompatible) { - parentTransaction = null; - } - else - throw new exceptions.SubTransaction("Table " + storeName + - " not included in parent transaction."); - } - }); - } - if (onlyIfCompatible && parentTransaction && !parentTransaction.active) { - parentTransaction = null; - } - } - } - catch (e) { - return parentTransaction ? - parentTransaction._promise(null, (_, reject) => { reject(e); }) : - rejection(e); - } - const enterTransaction = enterTransactionScope.bind(null, this, idbMode, storeNames, parentTransaction, scopeFunc); - return (parentTransaction ? - parentTransaction._promise(idbMode, enterTransaction, "lock") : - PSD.trans ? - usePSD(PSD.transless, () => this._whenReady(enterTransaction)) : - this._whenReady(enterTransaction)); - } - table(tableName) { - if (!hasOwn(this._allTables, tableName)) { - throw new exceptions.InvalidTable(`Table ${tableName} does not exist`); - } - return this._allTables[tableName]; - } -} - -const symbolObservable = typeof Symbol !== "undefined" && "observable" in Symbol - ? Symbol.observable - : "@@observable"; -class Observable { - constructor(subscribe) { - this._subscribe = subscribe; - } - subscribe(x, error, complete) { - return this._subscribe(!x || typeof x === "function" ? { next: x, error, complete } : x); - } - [symbolObservable]() { - return this; - } -} - -function extendObservabilitySet(target, newSet) { - keys(newSet).forEach(part => { - const rangeSet = target[part] || (target[part] = new RangeSet()); - mergeRanges(rangeSet, newSet[part]); - }); - return target; -} - -function liveQuery(querier) { - let hasValue = false; - let currentValue = undefined; - const observable = new Observable((observer) => { - const scopeFuncIsAsync = isAsyncFunction(querier); - function execute(subscr) { - if (scopeFuncIsAsync) { - incrementExpectedAwaits(); - } - const exec = () => newScope(querier, { subscr, trans: null }); - const rv = PSD.trans - ? - usePSD(PSD.transless, exec) - : exec(); - if (scopeFuncIsAsync) { - rv.then(decrementExpectedAwaits, decrementExpectedAwaits); - } - return rv; - } - let closed = false; - let accumMuts = {}; - let currentObs = {}; - const subscription = { - get closed() { - return closed; - }, - unsubscribe: () => { - closed = true; - globalEvents.storagemutated.unsubscribe(mutationListener); - }, - }; - observer.start && observer.start(subscription); - let querying = false, startedListening = false; - function shouldNotify() { - return keys(currentObs).some((key) => accumMuts[key] && rangesOverlap(accumMuts[key], currentObs[key])); - } - const mutationListener = (parts) => { - extendObservabilitySet(accumMuts, parts); - if (shouldNotify()) { - doQuery(); - } - }; - const doQuery = () => { - if (querying || closed) - return; - accumMuts = {}; - const subscr = {}; - const ret = execute(subscr); - if (!startedListening) { - globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener); - startedListening = true; - } - querying = true; - Promise.resolve(ret).then((result) => { - hasValue = true; - currentValue = result; - querying = false; - if (closed) - return; - if (shouldNotify()) { - doQuery(); - } - else { - accumMuts = {}; - currentObs = subscr; - observer.next && observer.next(result); - } - }, (err) => { - querying = false; - hasValue = false; - observer.error && observer.error(err); - subscription.unsubscribe(); - }); - }; - doQuery(); - return subscription; - }); - observable.hasValue = () => hasValue; - observable.getValue = () => currentValue; - return observable; -} - -let domDeps; -try { - domDeps = { - indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, - IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange - }; -} -catch (e) { - domDeps = { indexedDB: null, IDBKeyRange: null }; -} - -const Dexie = Dexie$1; -props(Dexie, { - ...fullNameExceptions, - delete(databaseName) { - const db = new Dexie(databaseName, { addons: [] }); - return db.delete(); - }, - exists(name) { - return new Dexie(name, { addons: [] }).open().then(db => { - db.close(); - return true; - }).catch('NoSuchDatabaseError', () => false); - }, - getDatabaseNames(cb) { - try { - return getDatabaseNames(Dexie.dependencies).then(cb); - } - catch (_a) { - return rejection(new exceptions.MissingAPI()); - } - }, - defineClass() { - function Class(content) { - extend(this, content); - } - return Class; - }, - ignoreTransaction(scopeFunc) { - return PSD.trans ? - usePSD(PSD.transless, scopeFunc) : - scopeFunc(); - }, - vip, - async: function (generatorFn) { - return function () { - try { - var rv = awaitIterator(generatorFn.apply(this, arguments)); - if (!rv || typeof rv.then !== 'function') - return DexiePromise.resolve(rv); - return rv; - } - catch (e) { - return rejection(e); - } - }; - }, - spawn: function (generatorFn, args, thiz) { - try { - var rv = awaitIterator(generatorFn.apply(thiz, args || [])); - if (!rv || typeof rv.then !== 'function') - return DexiePromise.resolve(rv); - return rv; - } - catch (e) { - return rejection(e); - } - }, - currentTransaction: { - get: () => PSD.trans || null - }, - waitFor: function (promiseOrFunction, optionalTimeout) { - const promise = DexiePromise.resolve(typeof promiseOrFunction === 'function' ? - Dexie.ignoreTransaction(promiseOrFunction) : - promiseOrFunction) - .timeout(optionalTimeout || 60000); - return PSD.trans ? - PSD.trans.waitFor(promise) : - promise; - }, - Promise: DexiePromise, - debug: { - get: () => debug, - set: value => { - setDebug(value, value === 'dexie' ? () => true : dexieStackFrameFilter); - } - }, - derive: derive, - extend: extend, - props: props, - override: override, - Events: Events, - on: globalEvents, - liveQuery, - extendObservabilitySet, - getByKeyPath: getByKeyPath, - setByKeyPath: setByKeyPath, - delByKeyPath: delByKeyPath, - shallowClone: shallowClone, - deepClone: deepClone, - getObjectDiff: getObjectDiff, - cmp, - asap: asap$1, - minKey: minKey, - addons: [], - connections: connections, - errnames: errnames, - dependencies: domDeps, - semVer: DEXIE_VERSION, - version: DEXIE_VERSION.split('.') - .map(n => parseInt(n)) - .reduce((p, c, i) => p + (c / Math.pow(10, i * 2))), -}); -Dexie.maxKey = getMaxKey(Dexie.dependencies.IDBKeyRange); - -if (typeof dispatchEvent !== 'undefined' && typeof addEventListener !== 'undefined') { - globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, updatedParts => { - if (!propagatingLocally) { - let event; - if (isIEOrEdge) { - event = document.createEvent('CustomEvent'); - event.initCustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, true, true, updatedParts); - } - else { - event = new CustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, { - detail: updatedParts - }); - } - propagatingLocally = true; - dispatchEvent(event); - propagatingLocally = false; - } - }); - addEventListener(STORAGE_MUTATED_DOM_EVENT_NAME, ({ detail }) => { - if (!propagatingLocally) { - propagateLocally(detail); - } - }); -} -function propagateLocally(updateParts) { - let wasMe = propagatingLocally; - try { - propagatingLocally = true; - globalEvents.storagemutated.fire(updateParts); - } - finally { - propagatingLocally = wasMe; - } -} -let propagatingLocally = false; - -if (typeof BroadcastChannel !== 'undefined') { - const bc = new BroadcastChannel(STORAGE_MUTATED_DOM_EVENT_NAME); - if (typeof bc.unref === 'function') { - bc.unref(); - } - globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, (changedParts) => { - if (!propagatingLocally) { - bc.postMessage(changedParts); - } - }); - bc.onmessage = (ev) => { - if (ev.data) - propagateLocally(ev.data); - }; -} -else if (typeof self !== 'undefined' && typeof navigator !== 'undefined') { - globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, (changedParts) => { - try { - if (!propagatingLocally) { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(STORAGE_MUTATED_DOM_EVENT_NAME, JSON.stringify({ - trig: Math.random(), - changedParts, - })); - } - if (typeof self['clients'] === 'object') { - [...self['clients'].matchAll({ includeUncontrolled: true })].forEach((client) => client.postMessage({ - type: STORAGE_MUTATED_DOM_EVENT_NAME, - changedParts, - })); - } - } - } - catch (_a) { } - }); - if (typeof addEventListener !== 'undefined') { - addEventListener('storage', (ev) => { - if (ev.key === STORAGE_MUTATED_DOM_EVENT_NAME) { - const data = JSON.parse(ev.newValue); - if (data) - propagateLocally(data.changedParts); - } - }); - } - const swContainer = self.document && navigator.serviceWorker; - if (swContainer) { - swContainer.addEventListener('message', propagateMessageLocally); - } -} -function propagateMessageLocally({ data }) { - if (data && data.type === STORAGE_MUTATED_DOM_EVENT_NAME) { - propagateLocally(data.changedParts); - } -} - -DexiePromise.rejectionMapper = mapError; -setDebug(debug, dexieStackFrameFilter); - -class ModelDatabase extends Dexie$1 { - constructor() { - super("ModelDatabase"); - this.version(2).stores({ - models: "id, file", - }); - } -} - -// TODO: Implement UI elements (this is probably just for 3d scans) -/** - * A tool to cache files using the browser's IndexedDB API. This might - * save loading time and infrastructure costs for files that need to be - * fetched from the cloud. - */ -class LocalCacher extends Component { - /** The IDs of all the stored files. */ - get ids() { - const serialized = localStorage.getItem(this._storedModels) || "[]"; - return JSON.parse(serialized); - } - constructor(components) { - super(components); - /** Fires when a file has been loaded from cache. */ - this.onFileLoaded = new Event(); - /** Fires when a file has been saved into cache. */ - this.onItemSaved = new Event(); - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - /** {@link Component.enabled} */ - this.enabled = true; - /** {@link UI.uiElement} */ - this.uiElement = new UIElement(); - this.cards = []; - this._storedModels = "open-bim-components-stored-files"; - components.tools.add(LocalCacher.uuid, this); - this._db = new ModelDatabase(); - if (components.uiEnabled) { - this.setUI(components); - } - } - /** - * {@link Component.get}. - * @param id the ID of the file to fetch. - */ - async get(id) { - if (this.exists(id)) { - await this._db.open(); - const result = await this.getModelFromLocalCache(id); - this._db.close(); - return result; - } - return null; - } - /** - * Saves the file with the given ID. - * @param id the ID to assign to the file. - * @param url the URL where the file is located. - */ - async save(id, url) { - this.addStoredID(id); - const rawData = await fetch(url); - const file = await rawData.blob(); - await this._db.open(); - await this._db.models.add({ - id, - file, - }); - this._db.close(); - } - /** - * Checks if there's a file stored with the given ID. - * @param id to check. - */ - exists(id) { - const stored = localStorage.getItem(id); - return stored !== null; - } - /** - * Deletes the files stored in the given ids. - * @param ids the identifiers of the files to delete. - */ - async delete(ids) { - await this._db.open(); - for (const id of ids) { - if (this.exists(id)) { - this.removeStoredID(id); - await this._db.models.where("id").equals(id).delete(); - } - } - this._db.close(); - } - /** Deletes all the stored files. */ - async deleteAll() { - await this._db.open(); - this.clearStoredIDs(); - await this._db.delete(); - this._db = new ModelDatabase(); - this._db.close(); - } - /** {@link Disposable.dispose} */ - async dispose() { - this.onFileLoaded.reset(); - this.onItemSaved.reset(); - for (const card of this.cards) { - await card.dispose(); - } - this.cards = []; - await this.uiElement.dispose(); - this._db = null; - await this.onDisposed.trigger(LocalCacher.uuid); - this.onDisposed.reset(); - } - setUI(components) { - const main = new Button(components); - main.materialIcon = "storage"; - main.tooltip = "Local cacher"; - const saveButton = new Button(components); - saveButton.label = "Save"; - saveButton.materialIcon = "save"; - const loadButton = new Button(components); - loadButton.label = "Download"; - loadButton.materialIcon = "download"; - main.addChild(saveButton, loadButton); - const floatingMenu = new FloatingWindow(components, "file-list-menu"); - this.uiElement.set({ main, loadButton, saveButton, floatingMenu }); - floatingMenu.title = "Saved Files"; - floatingMenu.visible = false; - const savedFilesMenuHTML = floatingMenu.get(); - savedFilesMenuHTML.style.left = "70px"; - savedFilesMenuHTML.style.top = "100px"; - savedFilesMenuHTML.style.width = "340px"; - savedFilesMenuHTML.style.height = "400px"; - const renderer = this.components.renderer.get(); - const viewerContainer = renderer.domElement.parentElement; - viewerContainer.appendChild(floatingMenu.get()); - } - async getModelFromLocalCache(id) { - const found = await this._db.models.where("id").equals(id).toArray(); - return found[0].file; - } - clearStoredIDs() { - const ids = this.ids; - for (const id of ids) { - this.removeStoredID(id); - } - } - removeStoredID(id) { - localStorage.removeItem(id); - const allIDs = this.ids; - const ids = allIDs.filter((savedId) => savedId !== id); - this.setStoredIDs(ids); - } - addStoredID(id) { - const time = performance.now().toString(); - localStorage.setItem(id, time); - const ids = this.ids; - ids.push(id); - this.setStoredIDs(ids); - } - setStoredIDs(ids) { - localStorage.setItem(this._storedModels, JSON.stringify(ids)); - } -} -LocalCacher.uuid = "22ae591a-3a67-4988-86c6-68d7b83febf2"; -ToolComponent.libraryUUIDs.add(LocalCacher.uuid); - -const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; - -// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 -function generateUUID() { - - const d0 = Math.random() * 0xffffffff | 0; - const d1 = Math.random() * 0xffffffff | 0; - const d2 = Math.random() * 0xffffffff | 0; - const d3 = Math.random() * 0xffffffff | 0; - const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + - _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + - _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + - _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; - - // .toLowerCase() here flattens concatenated strings to save heap memory space. - return uuid.toLowerCase(); - -} - -function clamp( value, min, max ) { - - return Math.max( min, Math.min( max, value ) ); - -} - -function denormalize( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint16Array: - - return value / 65535.0; - - case Uint8Array: - - return value / 255.0; - - case Int16Array: - - return Math.max( value / 32767.0, - 1.0 ); - - case Int8Array: - - return Math.max( value / 127.0, - 1.0 ); - - default: - - throw new Error( 'Invalid component type.' ); - - } - -} - -function normalize( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint16Array: - - return Math.round( value * 65535.0 ); - - case Uint8Array: - - return Math.round( value * 255.0 ); - - case Int16Array: - - return Math.round( value * 32767.0 ); - - case Int8Array: - - return Math.round( value * 127.0 ); - - default: - - throw new Error( 'Invalid component type.' ); - - } - -} - -class SimpleSVGViewport extends Component { - get enabled() { - return this._enabled; - } - set enabled(value) { - this._enabled = value; - this.resize(); - this._undoList = []; - this.uiElement.get("toolbar").visible = value; - if (value) { - this._viewport.classList.remove("pointer-events-none"); - } - else { - this.clear(); - this.uiElement.get("settingsWindow").visible = false; - this._viewport.classList.add("pointer-events-none"); - } - } - set config(value) { - this._config = { ...this._config, ...value }; - } - get config() { - return this._config; - } - constructor(components, config) { - super(components); - this.uiElement = new UIElement(); - this.id = generateUUID().toLowerCase(); - this._enabled = false; - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - this._viewport = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - this._size = new Vector2$1(); - this._undoList = []; - this.onResize = () => { - this.resize(); - }; - const defaultConfig = { - fillColor: "transparent", - strokeColor: "#ff0000", - strokeWidth: 4, - }; - this.config = { ...defaultConfig, ...(config !== null && config !== void 0 ? config : {}) }; - this._viewport.classList.add("absolute", "top-0", "right-0"); - // this._viewport.setAttribute("preserveAspectRatio", "xMidYMid") - this._viewport.setAttribute("width", "100%"); - this._viewport.setAttribute("height", "100%"); - // const renderer = this._components.renderer; - // const rendererSize = renderer.getSize(); - // const width = rendererSize.x - // const height = rendererSize.y - // this._viewport.setAttribute("viewBox", `0 0 ${width} ${height}`); - this.setUI(); - this.enabled = false; - this.components.ui.viewerContainer.append(this._viewport); - this.setupEvents(true); - } - async dispose() { - this._undoList = []; - this.uiElement.dispose(); - await this.onDisposed.trigger(); - this.onDisposed.reset(); - } - get() { - return this._viewport; - } - clear() { - const viewport = this.get(); - this._undoList = []; - while (viewport.firstChild) { - viewport.removeChild(viewport.firstChild); - } - } - getDrawing() { - return this.get().childNodes; - } - // setDrawing() { - // if (!this.enabled) { } - // } - /** {@link Resizeable.resize}. */ - resize() { - const renderer = this.components.renderer; - const rendererSize = renderer.getSize(); - const width = this.enabled ? rendererSize.x : 0; - const height = this.enabled ? rendererSize.y : 0; - this._size.set(width, height); - // this._viewport.setAttribute("viewBox", `0 0 ${this._size.x} ${this._size.y}`); - } - /** {@link Resizeable.getSize}. */ - getSize() { - return this._size; - } - setupEvents(active) { - if (active) { - window.addEventListener("resize", this.onResize); - } - else { - window.removeEventListener("resize", this.onResize); - } - } - setUI() { - var _a, _b; - const undoDrawingBtn = new Button(this.components, { - materialIconName: "undo", - }); - undoDrawingBtn.onClick.add(() => { - if (this._viewport.lastChild) { - this._undoList.push(this._viewport.lastChild); - this._viewport.lastChild.remove(); - } - }); - const redoDrawingBtn = new Button(this.components, { - materialIconName: "redo", - }); - redoDrawingBtn.onClick.add(() => { - const childNode = this._undoList[this._undoList.length - 1]; - if (childNode) { - this._undoList.pop(); - this._viewport.append(childNode); - } - }); - const clearDrawingBtn = new Button(this.components, { - materialIconName: "delete", - }); - clearDrawingBtn.onClick.add(() => this.clear()); - // #region Settings window - const settingsWindow = new FloatingWindow(this.components, this.id); - settingsWindow.title = "Drawing Settings"; - settingsWindow.visible = false; - const viewerContainer = this.components.renderer.get().domElement - .parentElement; - viewerContainer.append(settingsWindow.get()); - const strokeWidth = new RangeInput(this.components); - strokeWidth.label = "Stroke Width"; - strokeWidth.min = 2; - strokeWidth.max = 6; - strokeWidth.value = 4; - // strokeWidth.id = this.id; - strokeWidth.onChange.add((value) => { - // @ts-ignore - this.config = { strokeWidth: value }; - }); - const strokeColorInput = new ColorInput(this.components); - strokeColorInput.label = "Stroke Color"; - strokeColorInput.value = (_a = this.config.strokeColor) !== null && _a !== void 0 ? _a : "#BCF124"; - // strokeColorInput.name = "stroke-color"; - // strokeColorInput.id = this.id; - strokeColorInput.onChange.add((value) => { - this.config = { strokeColor: value }; - }); - const fillColorInput = new ColorInput(this.components); - strokeColorInput.label = "Fill Color"; - strokeColorInput.value = (_b = this.config.fillColor) !== null && _b !== void 0 ? _b : "#BCF124"; - // strokeColorInput.name = "fill-color"; - // strokeColorInput.id = this.id; - fillColorInput.onChange.add((value) => { - this.config = { fillColor: value }; - }); - settingsWindow.addChild(strokeColorInput, fillColorInput, strokeWidth); - const settingsBtn = new Button(this.components, { - materialIconName: "settings", - }); - settingsBtn.onClick.add(() => { - settingsWindow.visible = !settingsWindow.visible; - settingsBtn.active = settingsWindow.visible; - }); - settingsWindow.onHidden.add(() => (settingsBtn.active = false)); - const toolbar = new Toolbar(this.components, { position: "right" }); - toolbar.addChild(settingsBtn, undoDrawingBtn, redoDrawingBtn, clearDrawingBtn); - this.uiElement.set({ toolbar, settingsWindow }); - } -} - -// TODO: Clean up and document -// TODO: Disable / enable instance color for instance meshes -/** - * A tool to easily handle the materials of massive amounts of - * objects and scene background easily. - */ -class MaterialManager extends Component { - constructor(components) { - super(components); - /** {@link Component.enabled} */ - this.enabled = true; - this._originalBackground = null; - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - this._originals = {}; - this._list = {}; - this.components.tools.add(MaterialManager.uuid, this); - } - /** - * {@link Component.get}. - * @return list of created materials. - */ - get() { - return Object.keys(this._list); - } - /** - * Turns the specified material styles on or off. - * - * @param active whether to turn it on or off. - * @param ids the ids of the style to turn on or off. - */ - set(active, ids = Object.keys(this._list)) { - for (const id of ids) { - const { material, meshes } = this._list[id]; - for (const mesh of meshes) { - if (active) { - if (!this._originals[mesh.uuid]) { - this._originals[mesh.uuid] = { material: mesh.material }; - } - if (mesh instanceof THREE$1.InstancedMesh && mesh.instanceColor) { - this._originals[mesh.uuid].instances = mesh.instanceColor; - mesh.instanceColor = null; - } - mesh.material = material; - } - else { - if (!this._originals[mesh.uuid]) - continue; - mesh.material = this._originals[mesh.uuid].material; - const instances = this._originals[mesh.uuid].instances; - if (mesh instanceof THREE$1.InstancedMesh && instances) { - mesh.instanceColor = instances; - } - } - } - } - } - /** {@link Disposable.dispose} */ - async dispose() { - for (const id in this._list) { - const { material } = this._list[id]; - material.dispose(); - } - this._list = {}; - this._originals = {}; - await this.onDisposed.trigger(MaterialManager.uuid); - this.onDisposed.reset(); - } - /** - * Sets the color of the background of the scene. - * - * @param color: the color to apply. - */ - setBackgroundColor(color) { - const scene = this.components.scene.get(); - if (!this._originalBackground) { - this._originalBackground = scene.background; - } - if (this._originalBackground) { - scene.background = color; - } - } - /** - * Resets the scene background to the color that was being used - * before applying the material manager. - */ - resetBackgroundColor() { - const scene = this.components.scene.get(); - if (this._originalBackground) { - scene.background = this._originalBackground; - } - } - /** - * Creates a new material style. - * @param id the identifier of the style to create. - * @param material the material of the style. - */ - addMaterial(id, material) { - if (this._list[id]) { - throw new Error("This ID already exists!"); - } - this._list[id] = { material, meshes: new Set() }; - } - /** - * Assign meshes to a certain style. - * @param id the identifier of the style. - * @param meshes the meshes to assign to the style. - */ - addMeshes(id, meshes) { - if (!this._list[id]) { - throw new Error("This ID doesn't exists!"); - } - for (const mesh of meshes) { - this._list[id].meshes.add(mesh); - } - } -} -MaterialManager.uuid = "24989d27-fa2f-4797-8b08-35918f74e502"; -ToolComponent.libraryUUIDs.add(MaterialManager.uuid); - -// OrbitControls performs orbiting, dollying (zooming), and panning. -// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). -// -// Orbit - left mouse / touch: one-finger move -// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish -// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move - -const _changeEvent = { type: 'change' }; -const _startEvent = { type: 'start' }; -const _endEvent = { type: 'end' }; - -class OrbitControls extends EventDispatcher$1 { - - constructor( object, domElement ) { - - super(); - - this.object = object; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll - - // Set to false to disable this control - this.enabled = true; - - // "target" sets the location of focus, where the object orbits around - this.target = new Vector3$1(); - - // How far you can dolly in and out ( PerspectiveCamera only ) - this.minDistance = 0; - this.maxDistance = Infinity; - - // How far you can zoom in and out ( OrthographicCamera only ) - this.minZoom = 0; - this.maxZoom = Infinity; - - // How far you can orbit vertically, upper and lower limits. - // Range is 0 to Math.PI radians. - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - - // How far you can orbit horizontally, upper and lower limits. - // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians - - // Set to true to enable damping (inertia) - // If damping is enabled, you must call controls.update() in your animation loop - this.enableDamping = false; - this.dampingFactor = 0.05; - - // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. - // Set to false to disable zooming - this.enableZoom = true; - this.zoomSpeed = 1.0; - - // Set to false to disable rotating - this.enableRotate = true; - this.rotateSpeed = 1.0; - - // Set to false to disable panning - this.enablePan = true; - this.panSpeed = 1.0; - this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up - this.keyPanSpeed = 7.0; // pixels moved per arrow key push - - // Set to true to automatically rotate around the target - // If auto-rotate is enabled, you must call controls.update() in your animation loop - this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 - - // The four arrow keys - this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - - // Mouse buttons - this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - - // Touch fingers - this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - - // for reset - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.zoom0 = this.object.zoom; - - // the target DOM element for key events - this._domElementKeyEvents = null; - - // - // public methods - // - - this.getPolarAngle = function () { - - return spherical.phi; - - }; - - this.getAzimuthalAngle = function () { - - return spherical.theta; - - }; - - this.getDistance = function () { - - return this.object.position.distanceTo( this.target ); - - }; - - this.listenToKeyEvents = function ( domElement ) { - - domElement.addEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = domElement; - - }; - - this.stopListenToKeyEvents = function () { - - this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = null; - - }; - - this.saveState = function () { - - scope.target0.copy( scope.target ); - scope.position0.copy( scope.object.position ); - scope.zoom0 = scope.object.zoom; - - }; - - this.reset = function () { - - scope.target.copy( scope.target0 ); - scope.object.position.copy( scope.position0 ); - scope.object.zoom = scope.zoom0; - - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( _changeEvent ); - - scope.update(); - - state = STATE.NONE; - - }; - - // this method is exposed, but perhaps it would be better if we can make it private... - this.update = function () { - - const offset = new Vector3$1(); - - // so camera.up is the orbit axis - const quat = new Quaternion$1().setFromUnitVectors( object.up, new Vector3$1( 0, 1, 0 ) ); - const quatInverse = quat.clone().invert(); - - const lastPosition = new Vector3$1(); - const lastQuaternion = new Quaternion$1(); - - const twoPI = 2 * Math.PI; - - return function update() { - - const position = scope.object.position; - - offset.copy( position ).sub( scope.target ); - - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); - - // angle from z-axis around y-axis - spherical.setFromVector3( offset ); - - if ( scope.autoRotate && state === STATE.NONE ) { - - rotateLeft( getAutoRotationAngle() ); - - } - - if ( scope.enableDamping ) { - - spherical.theta += sphericalDelta.theta * scope.dampingFactor; - spherical.phi += sphericalDelta.phi * scope.dampingFactor; - - } else { - - spherical.theta += sphericalDelta.theta; - spherical.phi += sphericalDelta.phi; - - } - - // restrict theta to be between desired limits - - let min = scope.minAzimuthAngle; - let max = scope.maxAzimuthAngle; - - if ( isFinite( min ) && isFinite( max ) ) { - - if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; - - if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; - - if ( min <= max ) { - - spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); - - } else { - - spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, spherical.theta ) : - Math.min( max, spherical.theta ); - - } - - } - - // restrict phi to be between desired limits - spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); - - spherical.makeSafe(); - - - spherical.radius *= scale; - - // restrict radius to be between desired limits - spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); - - // move target to panned location - - if ( scope.enableDamping === true ) { - - scope.target.addScaledVector( panOffset, scope.dampingFactor ); - - } else { - - scope.target.add( panOffset ); - - } - - offset.setFromSpherical( spherical ); - - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); - - position.copy( scope.target ).add( offset ); - - scope.object.lookAt( scope.target ); - - if ( scope.enableDamping === true ) { - - sphericalDelta.theta *= ( 1 - scope.dampingFactor ); - sphericalDelta.phi *= ( 1 - scope.dampingFactor ); - - panOffset.multiplyScalar( 1 - scope.dampingFactor ); - - } else { - - sphericalDelta.set( 0, 0, 0 ); - - panOffset.set( 0, 0, 0 ); - - } - - scale = 1; - - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - - if ( zoomChanged || - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { - - scope.dispatchEvent( _changeEvent ); - - lastPosition.copy( scope.object.position ); - lastQuaternion.copy( scope.object.quaternion ); - zoomChanged = false; - - return true; - - } - - return false; - - }; - - }(); - - this.dispose = function () { - - scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); - - scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); - scope.domElement.removeEventListener( 'wheel', onMouseWheel ); - - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); - - - if ( scope._domElementKeyEvents !== null ) { - - scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - scope._domElementKeyEvents = null; - - } - - //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? - - }; - - // - // internals - // - - const scope = this; - - const STATE = { - NONE: - 1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 - }; - - let state = STATE.NONE; - - const EPS = 0.000001; - - // current position in spherical coordinates - const spherical = new Spherical(); - const sphericalDelta = new Spherical(); - - let scale = 1; - const panOffset = new Vector3$1(); - let zoomChanged = false; - - const rotateStart = new Vector2$1(); - const rotateEnd = new Vector2$1(); - const rotateDelta = new Vector2$1(); - - const panStart = new Vector2$1(); - const panEnd = new Vector2$1(); - const panDelta = new Vector2$1(); - - const dollyStart = new Vector2$1(); - const dollyEnd = new Vector2$1(); - const dollyDelta = new Vector2$1(); - - const pointers = []; - const pointerPositions = {}; - - function getAutoRotationAngle() { - - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; - - } - - function getZoomScale() { - - return Math.pow( 0.95, scope.zoomSpeed ); - - } - - function rotateLeft( angle ) { - - sphericalDelta.theta -= angle; - - } - - function rotateUp( angle ) { - - sphericalDelta.phi -= angle; - - } - - const panLeft = function () { - - const v = new Vector3$1(); - - return function panLeft( distance, objectMatrix ) { - - v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - v.multiplyScalar( - distance ); - - panOffset.add( v ); - - }; - - }(); - - const panUp = function () { - - const v = new Vector3$1(); - - return function panUp( distance, objectMatrix ) { - - if ( scope.screenSpacePanning === true ) { - - v.setFromMatrixColumn( objectMatrix, 1 ); - - } else { - - v.setFromMatrixColumn( objectMatrix, 0 ); - v.crossVectors( scope.object.up, v ); - - } - - v.multiplyScalar( distance ); - - panOffset.add( v ); - - }; - - }(); - - // deltaX and deltaY are in pixels; right and down are positive - const pan = function () { - - const offset = new Vector3$1(); - - return function pan( deltaX, deltaY ) { - - const element = scope.domElement; - - if ( scope.object.isPerspectiveCamera ) { - - // perspective - const position = scope.object.position; - offset.copy( position ).sub( scope.target ); - let targetDistance = offset.length(); - - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); - - // we use only clientHeight here so aspect ratio does not distort speed - panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); - panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); - - } else if ( scope.object.isOrthographicCamera ) { - - // orthographic - panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); - panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); - - } else { - - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - scope.enablePan = false; - - } - - }; - - }(); - - function dollyOut( dollyScale ) { - - if ( scope.object.isPerspectiveCamera ) { - - scale /= dollyScale; - - } else if ( scope.object.isOrthographicCamera ) { - - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); - scope.object.updateProjectionMatrix(); - zoomChanged = true; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; - - } - - } - - function dollyIn( dollyScale ) { - - if ( scope.object.isPerspectiveCamera ) { - - scale *= dollyScale; - - } else if ( scope.object.isOrthographicCamera ) { - - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); - scope.object.updateProjectionMatrix(); - zoomChanged = true; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; - - } - - } - - // - // event callbacks - update the object state - // - - function handleMouseDownRotate( event ) { - - rotateStart.set( event.clientX, event.clientY ); - - } - - function handleMouseDownDolly( event ) { - - dollyStart.set( event.clientX, event.clientY ); - - } - - function handleMouseDownPan( event ) { - - panStart.set( event.clientX, event.clientY ); - - } - - function handleMouseMoveRotate( event ) { - - rotateEnd.set( event.clientX, event.clientY ); - - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); - - const element = scope.domElement; - - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height - - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); - - rotateStart.copy( rotateEnd ); - - scope.update(); - - } - - function handleMouseMoveDolly( event ) { - - dollyEnd.set( event.clientX, event.clientY ); - - dollyDelta.subVectors( dollyEnd, dollyStart ); - - if ( dollyDelta.y > 0 ) { - - dollyOut( getZoomScale() ); - - } else if ( dollyDelta.y < 0 ) { - - dollyIn( getZoomScale() ); - - } - - dollyStart.copy( dollyEnd ); - - scope.update(); - - } - - function handleMouseMovePan( event ) { - - panEnd.set( event.clientX, event.clientY ); - - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); - - pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - scope.update(); - - } - - function handleMouseWheel( event ) { - - if ( event.deltaY < 0 ) { - - dollyIn( getZoomScale() ); - - } else if ( event.deltaY > 0 ) { - - dollyOut( getZoomScale() ); - - } - - scope.update(); - - } - - function handleKeyDown( event ) { - - let needsUpdate = false; - - switch ( event.code ) { - - case scope.keys.UP: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - - } else { - - pan( 0, scope.keyPanSpeed ); - - } - - needsUpdate = true; - break; - - case scope.keys.BOTTOM: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - - } else { - - pan( 0, - scope.keyPanSpeed ); - - } - - needsUpdate = true; - break; - - case scope.keys.LEFT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - - } else { - - pan( scope.keyPanSpeed, 0 ); - - } - - needsUpdate = true; - break; - - case scope.keys.RIGHT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - - } else { - - pan( - scope.keyPanSpeed, 0 ); - - } - - needsUpdate = true; - break; - - } - - if ( needsUpdate ) { - - // prevent the browser from scrolling on cursor keys - event.preventDefault(); - - scope.update(); - - } - - - } - - function handleTouchStartRotate() { - - if ( pointers.length === 1 ) { - - rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); - - } else { - - const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); - const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); - - rotateStart.set( x, y ); - - } - - } - - function handleTouchStartPan() { - - if ( pointers.length === 1 ) { - - panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); - - } else { - - const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); - const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); - - panStart.set( x, y ); - - } - - } - - function handleTouchStartDolly() { - - const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; - const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - dollyStart.set( 0, distance ); - - } - - function handleTouchStartDollyPan() { - - if ( scope.enableZoom ) handleTouchStartDolly(); - - if ( scope.enablePan ) handleTouchStartPan(); - - } - - function handleTouchStartDollyRotate() { - - if ( scope.enableZoom ) handleTouchStartDolly(); - - if ( scope.enableRotate ) handleTouchStartRotate(); - - } - - function handleTouchMoveRotate( event ) { - - if ( pointers.length == 1 ) { - - rotateEnd.set( event.pageX, event.pageY ); - - } else { - - const position = getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - rotateEnd.set( x, y ); - - } - - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); - - const element = scope.domElement; - - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height - - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); - - rotateStart.copy( rotateEnd ); - - } - - function handleTouchMovePan( event ) { - - if ( pointers.length === 1 ) { - - panEnd.set( event.pageX, event.pageY ); - - } else { - - const position = getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - panEnd.set( x, y ); - - } - - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); - - pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - } - - function handleTouchMoveDolly( event ) { - - const position = getSecondPointerPosition( event ); - - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - dollyEnd.set( 0, distance ); - - dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); - - dollyOut( dollyDelta.y ); - - dollyStart.copy( dollyEnd ); - - } - - function handleTouchMoveDollyPan( event ) { - - if ( scope.enableZoom ) handleTouchMoveDolly( event ); - - if ( scope.enablePan ) handleTouchMovePan( event ); - - } - - function handleTouchMoveDollyRotate( event ) { - - if ( scope.enableZoom ) handleTouchMoveDolly( event ); - - if ( scope.enableRotate ) handleTouchMoveRotate( event ); - - } - - // - // event handlers - FSM: listen for events and reset state - // - - function onPointerDown( event ) { - - if ( scope.enabled === false ) return; - - if ( pointers.length === 0 ) { - - scope.domElement.setPointerCapture( event.pointerId ); - - scope.domElement.addEventListener( 'pointermove', onPointerMove ); - scope.domElement.addEventListener( 'pointerup', onPointerUp ); - - } - - // - - addPointer( event ); - - if ( event.pointerType === 'touch' ) { - - onTouchStart( event ); - - } else { - - onMouseDown( event ); - - } - - } - - function onPointerMove( event ) { - - if ( scope.enabled === false ) return; - - if ( event.pointerType === 'touch' ) { - - onTouchMove( event ); - - } else { - - onMouseMove( event ); - - } - - } - - function onPointerUp( event ) { - - removePointer( event ); - - if ( pointers.length === 0 ) { - - scope.domElement.releasePointerCapture( event.pointerId ); - - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); - - } - - scope.dispatchEvent( _endEvent ); - - state = STATE.NONE; - - } - - function onMouseDown( event ) { - - let mouseAction; - - switch ( event.button ) { - - case 0: - - mouseAction = scope.mouseButtons.LEFT; - break; - - case 1: - - mouseAction = scope.mouseButtons.MIDDLE; - break; - - case 2: - - mouseAction = scope.mouseButtons.RIGHT; - break; - - default: - - mouseAction = - 1; - - } - - switch ( mouseAction ) { - - case MOUSE.DOLLY: - - if ( scope.enableZoom === false ) return; - - handleMouseDownDolly( event ); - - state = STATE.DOLLY; - - break; - - case MOUSE.ROTATE: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( scope.enablePan === false ) return; - - handleMouseDownPan( event ); - - state = STATE.PAN; - - } else { - - if ( scope.enableRotate === false ) return; - - handleMouseDownRotate( event ); - - state = STATE.ROTATE; - - } - - break; - - case MOUSE.PAN: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( scope.enableRotate === false ) return; - - handleMouseDownRotate( event ); - - state = STATE.ROTATE; - - } else { - - if ( scope.enablePan === false ) return; - - handleMouseDownPan( event ); - - state = STATE.PAN; - - } - - break; - - default: - - state = STATE.NONE; - - } - - if ( state !== STATE.NONE ) { - - scope.dispatchEvent( _startEvent ); - - } - - } - - function onMouseMove( event ) { - - switch ( state ) { - - case STATE.ROTATE: - - if ( scope.enableRotate === false ) return; - - handleMouseMoveRotate( event ); - - break; - - case STATE.DOLLY: - - if ( scope.enableZoom === false ) return; - - handleMouseMoveDolly( event ); - - break; - - case STATE.PAN: - - if ( scope.enablePan === false ) return; - - handleMouseMovePan( event ); - - break; - - } - - } - - function onMouseWheel( event ) { - - if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; - - event.preventDefault(); - - scope.dispatchEvent( _startEvent ); - - handleMouseWheel( event ); - - scope.dispatchEvent( _endEvent ); - - } - - function onKeyDown( event ) { - - if ( scope.enabled === false || scope.enablePan === false ) return; - - handleKeyDown( event ); - - } - - function onTouchStart( event ) { - - trackPointer( event ); - - switch ( pointers.length ) { - - case 1: - - switch ( scope.touches.ONE ) { - - case TOUCH.ROTATE: - - if ( scope.enableRotate === false ) return; - - handleTouchStartRotate(); - - state = STATE.TOUCH_ROTATE; - - break; - - case TOUCH.PAN: - - if ( scope.enablePan === false ) return; - - handleTouchStartPan(); - - state = STATE.TOUCH_PAN; - - break; - - default: - - state = STATE.NONE; - - } - - break; - - case 2: - - switch ( scope.touches.TWO ) { - - case TOUCH.DOLLY_PAN: - - if ( scope.enableZoom === false && scope.enablePan === false ) return; - - handleTouchStartDollyPan(); - - state = STATE.TOUCH_DOLLY_PAN; - - break; - - case TOUCH.DOLLY_ROTATE: - - if ( scope.enableZoom === false && scope.enableRotate === false ) return; - - handleTouchStartDollyRotate(); - - state = STATE.TOUCH_DOLLY_ROTATE; - - break; - - default: - - state = STATE.NONE; - - } - - break; - - default: - - state = STATE.NONE; - - } - - if ( state !== STATE.NONE ) { - - scope.dispatchEvent( _startEvent ); - - } - - } - - function onTouchMove( event ) { - - trackPointer( event ); - - switch ( state ) { - - case STATE.TOUCH_ROTATE: - - if ( scope.enableRotate === false ) return; - - handleTouchMoveRotate( event ); - - scope.update(); - - break; - - case STATE.TOUCH_PAN: - - if ( scope.enablePan === false ) return; - - handleTouchMovePan( event ); - - scope.update(); - - break; - - case STATE.TOUCH_DOLLY_PAN: - - if ( scope.enableZoom === false && scope.enablePan === false ) return; - - handleTouchMoveDollyPan( event ); - - scope.update(); - - break; - - case STATE.TOUCH_DOLLY_ROTATE: - - if ( scope.enableZoom === false && scope.enableRotate === false ) return; - - handleTouchMoveDollyRotate( event ); - - scope.update(); - - break; - - default: - - state = STATE.NONE; - - } - - } - - function onContextMenu( event ) { - - if ( scope.enabled === false ) return; - - event.preventDefault(); - - } - - function addPointer( event ) { - - pointers.push( event ); - - } - - function removePointer( event ) { - - delete pointerPositions[ event.pointerId ]; - - for ( let i = 0; i < pointers.length; i ++ ) { - - if ( pointers[ i ].pointerId == event.pointerId ) { - - pointers.splice( i, 1 ); - return; - - } - - } - - } - - function trackPointer( event ) { - - let position = pointerPositions[ event.pointerId ]; - - if ( position === undefined ) { - - position = new Vector2$1(); - pointerPositions[ event.pointerId ] = position; - - } - - position.set( event.pageX, event.pageY ); - - } - - function getSecondPointerPosition( event ) { - - const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; - - return pointerPositions[ pointer.pointerId ]; - - } - - // - - scope.domElement.addEventListener( 'contextmenu', onContextMenu ); - - scope.domElement.addEventListener( 'pointerdown', onPointerDown ); - scope.domElement.addEventListener( 'pointercancel', onPointerUp ); - scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - - // force an update at start - - this.update(); - - } - -} - -/** - * Full-screen textured quad shader - */ - -const CopyShader = { - - uniforms: { - - 'tDiffuse': { value: null }, - 'opacity': { value: 1.0 } - - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - uniform float opacity; - - uniform sampler2D tDiffuse; - - varying vec2 vUv; - - void main() { - - gl_FragColor = texture2D( tDiffuse, vUv ); - gl_FragColor.a *= opacity; - - - }` - -}; - -class Pass { - - constructor() { - - this.isPass = true; - - // if set to true, the pass is processed by the composer - this.enabled = true; - - // if set to true, the pass indicates to swap read and write buffer after rendering - this.needsSwap = true; - - // if set to true, the pass clears its buffer before rendering - this.clear = false; - - // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. - this.renderToScreen = false; - - } - - setSize( /* width, height */ ) {} - - render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - - console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); - - } - - dispose() {} - -} - -// Helper for passes that need to fill the viewport with a single quad. - -const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - -// https://github.com/mrdoob/three.js/pull/21358 - -const _geometry = new BufferGeometry(); -_geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); -_geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); - -class FullScreenQuad { - - constructor( material ) { - - this._mesh = new Mesh( _geometry, material ); - - } - - dispose() { - - this._mesh.geometry.dispose(); - - } - - render( renderer ) { - - renderer.render( this._mesh, _camera ); - - } - - get material() { - - return this._mesh.material; - - } - - set material( value ) { - - this._mesh.material = value; - - } - -} - -class ShaderPass extends Pass { - - constructor( shader, textureID ) { - - super(); - - this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; - - if ( shader instanceof ShaderMaterial ) { - - this.uniforms = shader.uniforms; - - this.material = shader; - - } else if ( shader ) { - - this.uniforms = UniformsUtils.clone( shader.uniforms ); - - this.material = new ShaderMaterial( { - - defines: Object.assign( {}, shader.defines ), - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - - } ); - - } - - this.fsQuad = new FullScreenQuad( this.material ); - - } - - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - if ( this.uniforms[ this.textureID ] ) { - - this.uniforms[ this.textureID ].value = readBuffer.texture; - - } - - this.fsQuad.material = this.material; - - if ( this.renderToScreen ) { - - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); - - } else { - - renderer.setRenderTarget( writeBuffer ); - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.fsQuad.render( renderer ); - - } - - } - - dispose() { - - this.material.dispose(); - - this.fsQuad.dispose(); - - } - -} - -class MaskPass extends Pass { - - constructor( scene, camera ) { - - super(); - - this.scene = scene; - this.camera = camera; - - this.clear = true; - this.needsSwap = false; - - this.inverse = false; - - } - - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - const context = renderer.getContext(); - const state = renderer.state; - - // don't update color or depth - - state.buffers.color.setMask( false ); - state.buffers.depth.setMask( false ); - - // lock buffers - - state.buffers.color.setLocked( true ); - state.buffers.depth.setLocked( true ); - - // set up stencil - - let writeValue, clearValue; - - if ( this.inverse ) { - - writeValue = 0; - clearValue = 1; - - } else { - - writeValue = 1; - clearValue = 0; - - } - - state.buffers.stencil.setTest( true ); - state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE ); - state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff ); - state.buffers.stencil.setClear( clearValue ); - state.buffers.stencil.setLocked( true ); - - // draw into the stencil buffer - - renderer.setRenderTarget( readBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); - - renderer.setRenderTarget( writeBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); - - // unlock color and depth buffer for subsequent rendering - - state.buffers.color.setLocked( false ); - state.buffers.depth.setLocked( false ); - - // only render where stencil is set to 1 - - state.buffers.stencil.setLocked( false ); - state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 - state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP ); - state.buffers.stencil.setLocked( true ); - - } - -} - -class ClearMaskPass extends Pass { - - constructor() { - - super(); - - this.needsSwap = false; - - } - - render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - - renderer.state.buffers.stencil.setLocked( false ); - renderer.state.buffers.stencil.setTest( false ); - - } - -} - -class EffectComposer { - - constructor( renderer, renderTarget ) { - - this.renderer = renderer; - - this._pixelRatio = renderer.getPixelRatio(); - - if ( renderTarget === undefined ) { - - const size = renderer.getSize( new Vector2$1() ); - this._width = size.width; - this._height = size.height; - - renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio ); - renderTarget.texture.name = 'EffectComposer.rt1'; - - } else { - - this._width = renderTarget.width; - this._height = renderTarget.height; - - } - - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); - this.renderTarget2.texture.name = 'EffectComposer.rt2'; - - this.writeBuffer = this.renderTarget1; - this.readBuffer = this.renderTarget2; - - this.renderToScreen = true; - - this.passes = []; - - this.copyPass = new ShaderPass( CopyShader ); - - this.clock = new Clock(); - - } - - swapBuffers() { - - const tmp = this.readBuffer; - this.readBuffer = this.writeBuffer; - this.writeBuffer = tmp; - - } - - addPass( pass ) { - - this.passes.push( pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - insertPass( pass, index ) { - - this.passes.splice( index, 0, pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - removePass( pass ) { - - const index = this.passes.indexOf( pass ); - - if ( index !== - 1 ) { - - this.passes.splice( index, 1 ); - - } - - } - - isLastEnabledPass( passIndex ) { - - for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { - - if ( this.passes[ i ].enabled ) { - - return false; - - } - - } - - return true; - - } - - render( deltaTime ) { - - // deltaTime value is in seconds - - if ( deltaTime === undefined ) { - - deltaTime = this.clock.getDelta(); - - } - - const currentRenderTarget = this.renderer.getRenderTarget(); - - let maskActive = false; - - for ( let i = 0, il = this.passes.length; i < il; i ++ ) { - - const pass = this.passes[ i ]; - - if ( pass.enabled === false ) continue; - - pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); - pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); - - if ( pass.needsSwap ) { - - if ( maskActive ) { - - const context = this.renderer.getContext(); - const stencil = this.renderer.state.buffers.stencil; - - //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); - stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); - - this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); - - //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); - stencil.setFunc( context.EQUAL, 1, 0xffffffff ); - - } - - this.swapBuffers(); - - } - - if ( MaskPass !== undefined ) { - - if ( pass instanceof MaskPass ) { - - maskActive = true; - - } else if ( pass instanceof ClearMaskPass ) { - - maskActive = false; - - } - - } - - } - - this.renderer.setRenderTarget( currentRenderTarget ); - - } - - reset( renderTarget ) { - - if ( renderTarget === undefined ) { - - const size = this.renderer.getSize( new Vector2$1() ); - this._pixelRatio = this.renderer.getPixelRatio(); - this._width = size.width; - this._height = size.height; - - renderTarget = this.renderTarget1.clone(); - renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); - - this.writeBuffer = this.renderTarget1; - this.readBuffer = this.renderTarget2; - - } - - setSize( width, height ) { - - this._width = width; - this._height = height; - - const effectiveWidth = this._width * this._pixelRatio; - const effectiveHeight = this._height * this._pixelRatio; - - this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); - this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); - - for ( let i = 0; i < this.passes.length; i ++ ) { - - this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); - - } - - } - - setPixelRatio( pixelRatio ) { - - this._pixelRatio = pixelRatio; - - this.setSize( this._width, this._height ); - - } - - dispose() { - - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); - - this.copyPass.dispose(); - - } - -} - -class RenderPass extends Pass { - - constructor( scene, camera, overrideMaterial, clearColor, clearAlpha ) { - - super(); - - this.scene = scene; - this.camera = camera; - - this.overrideMaterial = overrideMaterial; - - this.clearColor = clearColor; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; - - this.clear = true; - this.clearDepth = false; - this.needsSwap = false; - this._oldClearColor = new Color(); - - } - - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; - - let oldClearAlpha, oldOverrideMaterial; - - if ( this.overrideMaterial !== undefined ) { - - oldOverrideMaterial = this.scene.overrideMaterial; - - this.scene.overrideMaterial = this.overrideMaterial; - - } - - if ( this.clearColor ) { - - renderer.getClearColor( this._oldClearColor ); - oldClearAlpha = renderer.getClearAlpha(); - - renderer.setClearColor( this.clearColor, this.clearAlpha ); - - } - - if ( this.clearDepth ) { - - renderer.clearDepth(); - - } - - renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); - - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - renderer.render( this.scene, this.camera ); - - if ( this.clearColor ) { - - renderer.setClearColor( this._oldClearColor, oldClearAlpha ); - - } - - if ( this.overrideMaterial !== undefined ) { - - this.scene.overrideMaterial = oldOverrideMaterial; - - } - - renderer.autoClear = oldAutoClear; - - } - -} - -/** - * postprocessing v6.33.3 build Mon Oct 30 2023 - * https://github.com/pmndrs/postprocessing - * Copyright 2015-2023 Raoul van Rüschen - * @license Zlib - */ - - -// src/utils/BackCompat.js -Number(REVISION.replace(/\D+/g, "")); - -const $e4ca8dcb0218f846$var$_geometry = new BufferGeometry(); -$e4ca8dcb0218f846$var$_geometry.setAttribute("position", new BufferAttribute$1(new Float32Array([ - -1, - -1, - 3, - -1, - -1, - 3 -]), 2)); -$e4ca8dcb0218f846$var$_geometry.setAttribute("uv", new BufferAttribute$1(new Float32Array([ - 0, - 0, - 2, - 0, - 0, - 2 -]), 2)); -// Recent three.js versions break setDrawRange or itemSize <3 position -$e4ca8dcb0218f846$var$_geometry.boundingSphere = new Sphere(); -$e4ca8dcb0218f846$var$_geometry.computeBoundingSphere = function() {}; -const $e4ca8dcb0218f846$var$_camera = new OrthographicCamera(); -class $e4ca8dcb0218f846$export$dcd670d73db751f5 { - constructor(material){ - this._mesh = new Mesh($e4ca8dcb0218f846$var$_geometry, material); - this._mesh.frustumCulled = false; - } - render(renderer) { - renderer.render(this._mesh, $e4ca8dcb0218f846$var$_camera); - } - get material() { - return this._mesh.material; - } - set material(value) { - this._mesh.material = value; - } - dispose() { - this._mesh.material.dispose(); - this._mesh.geometry.dispose(); - } -} - - - -const $1ed45968c1160c3c$export$c9b263b9a17dffd7 = { - uniforms: { - "sceneDiffuse": { - value: null - }, - "sceneDepth": { - value: null - }, - "sceneNormal": { - value: null - }, - "projMat": { - value: new Matrix4() - }, - "viewMat": { - value: new Matrix4() - }, - "projViewMat": { - value: new Matrix4() - }, - "projectionMatrixInv": { - value: new Matrix4() - }, - "viewMatrixInv": { - value: new Matrix4() - }, - "cameraPos": { - value: new Vector3$1() - }, - "resolution": { - value: new Vector2$1() - }, - "time": { - value: 0.0 - }, - "samples": { - value: [] - }, - "bluenoise": { - value: null - }, - "distanceFalloff": { - value: 1.0 - }, - "radius": { - value: 5.0 - }, - "near": { - value: 0.1 - }, - "far": { - value: 1000.0 - }, - "logDepth": { - value: false - }, - "ortho": { - value: false - }, - "screenSpaceRadius": { - value: false - } - }, - depthWrite: false, - depthTest: false, - vertexShader: /* glsl */ ` -varying vec2 vUv; -void main() { - vUv = uv; - gl_Position = vec4(position, 1); -}`, - fragmentShader: /* glsl */ ` - #define SAMPLES 16 - #define FSAMPLES 16.0 -uniform sampler2D sceneDiffuse; -uniform highp sampler2D sceneNormal; -uniform highp sampler2D sceneDepth; -uniform mat4 projectionMatrixInv; -uniform mat4 viewMatrixInv; -uniform mat4 projMat; -uniform mat4 viewMat; -uniform mat4 projViewMat; -uniform vec3 cameraPos; -uniform vec2 resolution; -uniform float time; -uniform vec3[SAMPLES] samples; -uniform float radius; -uniform float distanceFalloff; -uniform float near; -uniform float far; -uniform bool logDepth; -uniform bool ortho; -uniform bool screenSpaceRadius; -uniform sampler2D bluenoise; - varying vec2 vUv; - highp float linearize_depth(highp float d, highp float zNear,highp float zFar) - { - return (zFar * zNear) / (zFar - d * (zFar - zNear)); - } - highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { - return nearZ + (farZ - nearZ) * d; - } - highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { - float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - return ortho ? linearize_depth_ortho( - linDepth, - nearZ, - farZ - ) :linearize_depth(linDepth, nearZ, farZ); - } - - vec3 getWorldPosLog(vec3 posS) { - vec2 uv = posS.xy; - float z = posS.z; - float nearZ =near; - float farZ = far; - float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; - vec4 wpos = projectionMatrixInv * clipVec; - return wpos.xyz / wpos.w; - } - vec3 getWorldPos(float depth, vec2 coord) { - #ifdef LOGDEPTH - return getWorldPosLog(vec3(coord, depth)); - #endif - float z = depth * 2.0 - 1.0; - vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); - vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; - // Perspective division - vec4 worldSpacePosition = viewSpacePosition; - worldSpacePosition.xyz /= worldSpacePosition.w; - return worldSpacePosition.xyz; - } - - vec3 computeNormal(vec3 worldPos, vec2 vUv) { - ivec2 p = ivec2(vUv * resolution); - float c0 = texelFetch(sceneDepth, p, 0).x; - float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; - float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; - float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; - float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; - float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; - float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; - float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; - float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; - - float dl = abs((2.0 * l1 - l2) - c0); - float dr = abs((2.0 * r1 - r2) - c0); - float db = abs((2.0 * b1 - b2) - c0); - float dt = abs((2.0 * t1 - t2) - c0); - - vec3 ce = getWorldPos(c0, vUv).xyz; - - vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz - : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; - vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz - : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; - - return normalize(cross(dpdx, dpdy)); -} - -mat3 makeRotationZ(float theta) { - float c = cos(theta); - float s = sin(theta); - return mat3(c, - s, 0, - s, c, 0, - 0, 0, 1); - } - -void main() { - vec4 diffuse = texture2D(sceneDiffuse, vUv); - float depth = texture2D(sceneDepth, vUv).x; - if (depth == 1.0) { - gl_FragColor = vec4(vec3(1.0), 1.0); - return; - } - vec3 worldPos = getWorldPos(depth, vUv); - #ifdef HALFRES - vec3 normal = texture2D(sceneNormal, vUv).rgb; - #else - vec3 normal = computeNormal(worldPos, vUv); - #endif - vec4 noise = texture2D(bluenoise, gl_FragCoord.xy / 128.0); - vec3 helperVec = vec3(0.0, 1.0, 0.0); - if (dot(helperVec, normal) > 0.99) { - helperVec = vec3(1.0, 0.0, 0.0); - } - vec3 tangent = normalize(cross(helperVec, normal)); - vec3 bitangent = cross(normal, tangent); - mat3 tbn = mat3(tangent, bitangent, normal) * makeRotationZ(noise.r * 2.0 * 3.1415962) ; - - float occluded = 0.0; - float totalWeight = 0.0; - float radiusToUse = screenSpaceRadius ? distance( - worldPos, - getWorldPos(depth, vUv + - vec2(radius, 0.0) / resolution) - ) : radius; - float distanceFalloffToUse =screenSpaceRadius ? - radiusToUse * distanceFalloff - : radiusToUse * distanceFalloff * 0.2; - float bias = (min( - 0.1, - distanceFalloffToUse * 0.1 - ) / near) * fwidth(distance(worldPos, cameraPos)) / radiusToUse; - float phi = 1.61803398875; - float offsetMove = 0.0; - float offsetMoveInv = 1.0 / FSAMPLES; - for(float i = 0.0; i < FSAMPLES; i++) { - vec3 sampleDirection = tbn * samples[int(i)]; - - float moveAmt = fract(noise.g + offsetMove); - offsetMove += offsetMoveInv; - - vec3 samplePos = worldPos + radiusToUse * moveAmt * sampleDirection; - vec4 offset = projMat * vec4(samplePos, 1.0); - offset.xyz /= offset.w; - offset.xyz = offset.xyz * 0.5 + 0.5; - - vec2 diff = gl_FragCoord.xy - floor(offset.xy * resolution); - // From Rabbid76's hbao - vec2 clipRangeCheck = step(vec2(0.0),offset.xy) * step(offset.xy, vec2(1.0)); - float sampleDepth = textureLod(sceneDepth, offset.xy, 0.0).x; - - #ifdef LOGDEPTH - - float distSample = linearize_depth_log(sampleDepth, near, far); - - #else - - float distSample = ortho ? linearize_depth_ortho(sampleDepth, near, far) : linearize_depth(sampleDepth, near, far); - - #endif - - float distWorld = ortho ? linearize_depth_ortho(offset.z, near, far) : linearize_depth(offset.z, near, far); - - float rangeCheck = smoothstep(0.0, 1.0, distanceFalloffToUse / (abs(distSample - distWorld))); - - float sampleValid = (clipRangeCheck.x * clipRangeCheck.y); - occluded += rangeCheck * float(sampleDepth != depth) * float(distSample + bias < distWorld) * step( - 1.0, - dot(diff, diff) - ) * sampleValid; - - totalWeight += sampleValid; - } - float occ = clamp(1.0 - occluded / (totalWeight == 0.0 ? 1.0 : totalWeight), 0.0, 1.0); - gl_FragColor = vec4(0.5 + 0.5 * normal, occ); -}` -}; - - - -const $12b21d24d1192a04$export$a815acccbd2c9a49 = { - uniforms: { - "sceneDiffuse": { - value: null - }, - "sceneDepth": { - value: null - }, - "tDiffuse": { - value: null - }, - "transparencyDWFalse": { - value: null - }, - "transparencyDWTrue": { - value: null - }, - "transparencyDWTrueDepth": { - value: null - }, - "transparencyAware": { - value: false - }, - "projMat": { - value: new Matrix4() - }, - "viewMat": { - value: new Matrix4() - }, - "projectionMatrixInv": { - value: new Matrix4() - }, - "viewMatrixInv": { - value: new Matrix4() - }, - "cameraPos": { - value: new Vector3$1() - }, - "resolution": { - value: new Vector2$1() - }, - "color": { - value: new Vector3$1(0, 0, 0) - }, - "blueNoise": { - value: null - }, - "downsampledDepth": { - value: null - }, - "time": { - value: 0.0 - }, - "intensity": { - value: 10.0 - }, - "renderMode": { - value: 0.0 - }, - "gammaCorrection": { - value: false - }, - "logDepth": { - value: false - }, - "ortho": { - value: false - }, - "near": { - value: 0.1 - }, - "far": { - value: 1000.0 - }, - "screenSpaceRadius": { - value: false - }, - "radius": { - value: 0.0 - }, - "distanceFalloff": { - value: 1.0 - }, - "fog": { - value: false - }, - "fogExp": { - value: false - }, - "fogDensity": { - value: 0.0 - }, - "fogNear": { - value: Infinity - }, - "fogFar": { - value: Infinity - }, - "colorMultiply": { - value: true - } - }, - depthWrite: false, - depthTest: false, - vertexShader: /* glsl */ ` - varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = vec4(position, 1); - }`, - fragmentShader: /* glsl */ ` - uniform sampler2D sceneDiffuse; - uniform highp sampler2D sceneDepth; - uniform highp sampler2D downsampledDepth; - uniform highp sampler2D transparencyDWFalse; - uniform highp sampler2D transparencyDWTrue; - uniform highp sampler2D transparencyDWTrueDepth; - uniform sampler2D tDiffuse; - uniform sampler2D blueNoise; - uniform vec2 resolution; - uniform vec3 color; - uniform mat4 projectionMatrixInv; - uniform mat4 viewMatrixInv; - uniform float intensity; - uniform float renderMode; - uniform float near; - uniform float far; - uniform bool gammaCorrection; - uniform bool logDepth; - uniform bool ortho; - uniform bool screenSpaceRadius; - uniform bool fog; - uniform bool fogExp; - uniform bool colorMultiply; - uniform bool transparencyAware; - uniform float fogDensity; - uniform float fogNear; - uniform float fogFar; - uniform float radius; - uniform float distanceFalloff; - uniform vec3 cameraPos; - varying vec2 vUv; - highp float linearize_depth(highp float d, highp float zNear,highp float zFar) - { - return (zFar * zNear) / (zFar - d * (zFar - zNear)); - } - highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { - return nearZ + (farZ - nearZ) * d; - } - highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { - float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - return ortho ? linearize_depth_ortho( - linDepth, - nearZ, - farZ - ) :linearize_depth(linDepth, nearZ, farZ); - } - vec3 getWorldPosLog(vec3 posS) { - vec2 uv = posS.xy; - float z = posS.z; - float nearZ =near; - float farZ = far; - float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; - vec4 wpos = projectionMatrixInv * clipVec; - return wpos.xyz / wpos.w; - } - vec3 getWorldPos(float depth, vec2 coord) { - // if (logDepth) { - #ifdef LOGDEPTH - return getWorldPosLog(vec3(coord, depth)); - #endif - // } - float z = depth * 2.0 - 1.0; - vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); - vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; - // Perspective division - vec4 worldSpacePosition = viewSpacePosition; - worldSpacePosition.xyz /= worldSpacePosition.w; - return worldSpacePosition.xyz; - } - - vec3 computeNormal(vec3 worldPos, vec2 vUv) { - ivec2 p = ivec2(vUv * resolution); - float c0 = texelFetch(sceneDepth, p, 0).x; - float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; - float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; - float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; - float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; - float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; - float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; - float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; - float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; - - float dl = abs((2.0 * l1 - l2) - c0); - float dr = abs((2.0 * r1 - r2) - c0); - float db = abs((2.0 * b1 - b2) - c0); - float dt = abs((2.0 * t1 - t2) - c0); - - vec3 ce = getWorldPos(c0, vUv).xyz; - - vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz - : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; - vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz - : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; - - return normalize(cross(dpdx, dpdy)); - } - - #include - #include - void main() { - //vec4 texel = texture2D(tDiffuse, vUv);//vec3(0.0); - vec4 sceneTexel = texture2D(sceneDiffuse, vUv); - float depth = texture2D( - sceneDepth, - vUv - ).x; - #ifdef HALFRES - vec4 texel; - if (depth == 1.0) { - texel = vec4(0.0, 0.0, 0.0, 1.0); - } else { - vec3 worldPos = getWorldPos(depth, vUv); - vec3 normal = computeNormal(getWorldPos(depth, vUv), vUv); - // vec4 texel = texture2D(tDiffuse, vUv); - // Find closest depth; - float totalWeight = 0.0; - float radiusToUse = screenSpaceRadius ? distance( - worldPos, - getWorldPos(depth, vUv + - vec2(radius, 0.0) / resolution) - ) : radius; - float distanceFalloffToUse =screenSpaceRadius ? - radiusToUse * distanceFalloff - : distanceFalloff; - for(float x = -1.0; x <= 1.0; x++) { - for(float y = -1.0; y <= 1.0; y++) { - vec2 offset = vec2(x, y); - ivec2 p = ivec2( - (vUv * resolution * 0.5) + offset - ); - vec2 pUv = vec2(p) / (resolution * 0.5); - float sampleDepth = texelFetch(downsampledDepth,p, 0).x; - vec4 sampleInfo = texelFetch(tDiffuse, p, 0); - vec3 normalSample = sampleInfo.xyz * 2.0 - 1.0; - vec3 worldPosSample = getWorldPos(sampleDepth, pUv); - float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal)); - float rangeCheck = exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0); - float weight = rangeCheck; - totalWeight += weight; - texel += sampleInfo * weight; - } - } - if (totalWeight == 0.0) { - texel = texture2D(tDiffuse, vUv); - } else { - texel /= totalWeight; - } - } - #else - vec4 texel = texture2D(tDiffuse, vUv); - #endif - - #ifdef LOGDEPTH - texel.a = clamp(texel.a, 0.0, 1.0); - if (texel.a == 0.0) { - texel.a = 1.0; - } - #endif - - float finalAo = pow(texel.a, intensity); - float fogFactor; - float fogDepth = distance( - cameraPos, - getWorldPos(depth, vUv) - ); - if (fog) { - if (fogExp) { - fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth ); - } else { - fogFactor = smoothstep( fogNear, fogFar, fogDepth ); - } - } - if (transparencyAware) { - float transparencyDWOff = texture2D(transparencyDWFalse, vUv).a; - float transparencyDWOn = texture2D(transparencyDWTrue, vUv).a; - float adjustmentFactorOff = transparencyDWOff; - float adjustmentFactorOn = (1.0 - transparencyDWOn) * ( - texture2D(transparencyDWTrueDepth, vUv).r == texture2D(sceneDepth, vUv).r ? 1.0 : 0.0 - ); - float adjustmentFactor = max(adjustmentFactorOff, adjustmentFactorOn); - finalAo = mix(finalAo, 1.0, adjustmentFactor); - } - finalAo = mix(finalAo, 1.0, fogFactor); - vec3 aoApplied = color * mix(vec3(1.0), sceneTexel.rgb, float(colorMultiply)); - if (renderMode == 0.0) { - gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a); - } else if (renderMode == 1.0) { - gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a); - } else if (renderMode == 2.0) { - gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); - } else if (renderMode == 3.0) { - if (vUv.x < 0.5) { - gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); - } else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) { - gl_FragColor = vec4(1.0); - } else { - gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a); - } - } else if (renderMode == 4.0) { - if (vUv.x < 0.5) { - gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); - } else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) { - gl_FragColor = vec4(1.0); - } else { - gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a); - } - } - #include - if (gammaCorrection) { - gl_FragColor = LinearTosRGB(gl_FragColor); - } - } - ` -}; - - - -const $e52378cd0f5a973d$export$57856b59f317262e = { - uniforms: { - "sceneDiffuse": { - value: null - }, - "sceneDepth": { - value: null - }, - "tDiffuse": { - value: null - }, - "projMat": { - value: new Matrix4() - }, - "viewMat": { - value: new Matrix4() - }, - "projectionMatrixInv": { - value: new Matrix4() - }, - "viewMatrixInv": { - value: new Matrix4() - }, - "cameraPos": { - value: new Vector3$1() - }, - "resolution": { - value: new Vector2$1() - }, - "time": { - value: 0.0 - }, - "r": { - value: 5.0 - }, - "blueNoise": { - value: null - }, - "radius": { - value: 12.0 - }, - "worldRadius": { - value: 5.0 - }, - "index": { - value: 0.0 - }, - "poissonDisk": { - value: [] - }, - "distanceFalloff": { - value: 1.0 - }, - "near": { - value: 0.1 - }, - "far": { - value: 1000.0 - }, - "logDepth": { - value: false - }, - "screenSpaceRadius": { - value: false - } - }, - depthWrite: false, - depthTest: false, - vertexShader: /* glsl */ ` - varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = vec4(position, 1.0); - }`, - fragmentShader: /* glsl */ ` - uniform sampler2D sceneDiffuse; - uniform highp sampler2D sceneDepth; - uniform sampler2D tDiffuse; - uniform sampler2D blueNoise; - uniform mat4 projectionMatrixInv; - uniform mat4 viewMatrixInv; - uniform vec2 resolution; - uniform float r; - uniform float radius; - uniform float worldRadius; - uniform float index; - uniform float near; - uniform float far; - uniform float distanceFalloff; - uniform bool logDepth; - uniform bool screenSpaceRadius; - varying vec2 vUv; - - highp float linearize_depth(highp float d, highp float zNear,highp float zFar) - { - highp float z_n = 2.0 * d - 1.0; - return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); - } - highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { - float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - return linearize_depth(linDepth, nearZ, farZ); - } - highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { - return nearZ + (farZ - nearZ) * d; - } - vec3 getWorldPosLog(vec3 posS) { - vec2 uv = posS.xy; - float z = posS.z; - float nearZ =near; - float farZ = far; - float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; - vec4 wpos = projectionMatrixInv * clipVec; - return wpos.xyz / wpos.w; - } - vec3 getWorldPos(float depth, vec2 coord) { - #ifdef LOGDEPTH - return getWorldPosLog(vec3(coord, depth)); - #endif - - float z = depth * 2.0 - 1.0; - vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); - vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; - // Perspective division - vec4 worldSpacePosition = viewSpacePosition; - worldSpacePosition.xyz /= worldSpacePosition.w; - return worldSpacePosition.xyz; - } - #include - #define NUM_SAMPLES 16 - uniform vec2 poissonDisk[NUM_SAMPLES]; - void main() { - const float pi = 3.14159; - vec2 texelSize = vec2(1.0 / resolution.x, 1.0 / resolution.y); - vec2 uv = vUv; - vec4 data = texture2D(tDiffuse, vUv); - float occlusion = data.a; - float baseOcc = data.a; - vec3 normal = data.rgb * 2.0 - 1.0; - float count = 1.0; - float d = texture2D(sceneDepth, vUv).x; - if (d == 1.0) { - gl_FragColor = data; - return; - } - vec3 worldPos = getWorldPos(d, vUv); - float size = radius; - float angle; - if (index == 0.0) { - angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).x * PI2; - } else if (index == 1.0) { - angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).y * PI2; - } else if (index == 2.0) { - angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).z * PI2; - } else { - angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).w * PI2; - } - - mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); - float radiusToUse = screenSpaceRadius ? distance( - worldPos, - getWorldPos(d, vUv + - vec2(worldRadius, 0.0) / resolution) - ) : worldRadius; - float distanceFalloffToUse =screenSpaceRadius ? - radiusToUse * distanceFalloff - : radiusToUse * distanceFalloff * 0.2; - - - for(int i = 0; i < NUM_SAMPLES; i++) { - vec2 offset = (rotationMatrix * poissonDisk[i]) * texelSize * size; - vec4 dataSample = texture2D(tDiffuse, uv + offset); - float occSample = dataSample.a; - vec3 normalSample = dataSample.rgb * 2.0 - 1.0; - float dSample = texture2D(sceneDepth, uv + offset).x; - vec3 worldPosSample = getWorldPos(dSample, uv + offset); - float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal)); - float rangeCheck = dSample == 1.0 ? 0.0 :exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0) * (1.0 - abs(occSample - baseOcc)); - occlusion += occSample * rangeCheck; - count += rangeCheck; - } - if (count > 0.0) { - occlusion /= count; - } - #ifdef LOGDEPTH - occlusion = clamp(occlusion, 0.0, 1.0); - if (occlusion == 0.0) { - occlusion = 1.0; - } - #endif - gl_FragColor = vec4(0.5 + 0.5 * normal, occlusion); - } - ` -}; - - - -const $26aca173e0984d99$export$1efdf491687cd442 = { - uniforms: { - "sceneDepth": { - value: null - }, - "resolution": { - value: new Vector2$1() - }, - "near": { - value: 0.1 - }, - "far": { - value: 1000.0 - }, - "viewMatrixInv": { - value: new Matrix4() - }, - "projectionMatrixInv": { - value: new Matrix4() - }, - "logDepth": { - value: false - } - }, - depthWrite: false, - depthTest: false, - vertexShader: /* glsl */ ` - varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = vec4(position, 1); - }`, - fragmentShader: /* glsl */ ` - uniform highp sampler2D sceneDepth; - uniform vec2 resolution; - uniform float near; - uniform float far; - uniform bool logDepth; - uniform mat4 viewMatrixInv; - uniform mat4 projectionMatrixInv; - varying vec2 vUv; - layout(location = 1) out vec4 gNormal; - vec3 getWorldPosLog(vec3 posS) { - vec2 uv = posS.xy; - float z = posS.z; - float nearZ =near; - float farZ = far; - float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; - float a = farZ / (farZ - nearZ); - float b = farZ * nearZ / (nearZ - farZ); - float linDepth = a + b / depth; - vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; - vec4 wpos = projectionMatrixInv * clipVec; - return wpos.xyz / wpos.w; - } - vec3 getWorldPos(float depth, vec2 coord) { - if (logDepth) { - return getWorldPosLog(vec3(coord, depth)); - } - float z = depth * 2.0 - 1.0; - vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); - vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; - // Perspective division - vec4 worldSpacePosition = viewSpacePosition; - worldSpacePosition.xyz /= worldSpacePosition.w; - return worldSpacePosition.xyz; - } - - vec3 computeNormal(vec3 worldPos, vec2 vUv) { - ivec2 p = ivec2(vUv * resolution); - float c0 = texelFetch(sceneDepth, p, 0).x; - float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; - float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; - float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; - float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; - float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; - float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; - float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; - float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; - - float dl = abs((2.0 * l1 - l2) - c0); - float dr = abs((2.0 * r1 - r2) - c0); - float db = abs((2.0 * b1 - b2) - c0); - float dt = abs((2.0 * t1 - t2) - c0); - - vec3 ce = getWorldPos(c0, vUv).xyz; - - vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz - : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; - vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz - : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; - - return normalize(cross(dpdx, dpdy)); - } - void main() { - vec2 uv = vUv - vec2(0.5) / resolution; - vec2 pixelSize = vec2(1.0) / resolution; - vec2[] uvSamples = vec2[4]( - uv, - uv + vec2(pixelSize.x, 0.0), - uv + vec2(0.0, pixelSize.y), - uv + pixelSize - ); - float depth00 = texture2D(sceneDepth, uvSamples[0]).r; - float depth10 = texture2D(sceneDepth, uvSamples[1]).r; - float depth01 = texture2D(sceneDepth, uvSamples[2]).r; - float depth11 = texture2D(sceneDepth, uvSamples[3]).r; - float minDepth = min(min(depth00, depth10), min(depth01, depth11)); - float maxDepth = max(max(depth00, depth10), max(depth01, depth11)); - float targetDepth = minDepth; - // Checkerboard pattern to avoid artifacts - if (mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) > 0.5) { - targetDepth = maxDepth; - } - int chosenIndex = 0; - float[] samples = float[4](depth00, depth10, depth01, depth11); - for(int i = 0; i < 4; ++i) { - if (samples[i] == targetDepth) { - chosenIndex = i; - break; - } - } - gl_FragColor = vec4(samples[chosenIndex], 0.0, 0.0, 1.0); - gNormal = vec4(computeNormal( - getWorldPos(samples[chosenIndex], uvSamples[chosenIndex]), uvSamples[chosenIndex] - ), 0.0); - }` -}; - - - - - - - - - -var $06269ad78f3c5fdf$export$2e2bcd8739ae039 = `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`; - - -Uint8Array.from(atob(($06269ad78f3c5fdf$export$2e2bcd8739ae039)), (c)=>c.charCodeAt(0)); - - - -const $05f6997e4b65da14$var$bluenoiseBits = Uint8Array.from(atob(($06269ad78f3c5fdf$export$2e2bcd8739ae039)), (c)=>c.charCodeAt(0)); -/** - * - * @param {*} timerQuery - * @param {THREE.WebGLRenderer} gl - * @param {N8AOPass} pass - */ function $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, pass) { - const available = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT_AVAILABLE); - if (available) { - const elapsedTimeInNs = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT); - const elapsedTimeInMs = elapsedTimeInNs / 1000000; - pass.lastTime = elapsedTimeInMs; - } else // If the result is not available yet, check again after a delay - setTimeout(()=>{ - $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, pass); - }, 1); -} -class $05f6997e4b65da14$export$2d57db20b5eb5e0a extends (Pass) { - /** - * - * @param {THREE.Scene} scene - * @param {THREE.Camera} camera - * @param {number} width - * @param {number} height - * - * @property {THREE.Scene} scene - * @property {THREE.Camera} camera - * @property {number} width - * @property {number} height - */ constructor(scene, camera, width = 512, height = 512){ - super(); - this.width = width; - this.height = height; - this.clear = true; - this.camera = camera; - this.scene = scene; - /** - * @type {Proxy & { - * aoSamples: number, - * aoRadius: number, - * denoiseSamples: number, - * denoiseRadius: number, - * distanceFalloff: number, - * intensity: number, - * denoiseIterations: number, - * renderMode: 0 | 1 | 2 | 3 | 4, - * color: THREE.Color, - * gammaCorrection: boolean, - * logarithmicDepthBuffer: boolean - * screenSpaceRadius: boolean, - * halfRes: boolean, - * depthAwareUpsampling: boolean, - * autoRenderBeauty: boolean - * colorMultiply: boolean - * } - */ this.configuration = new Proxy({ - aoSamples: 16, - aoRadius: 5.0, - denoiseSamples: 8, - denoiseRadius: 12, - distanceFalloff: 1.0, - intensity: 5, - denoiseIterations: 2.0, - renderMode: 0, - color: new Color(0, 0, 0), - gammaCorrection: true, - logarithmicDepthBuffer: false, - screenSpaceRadius: false, - halfRes: false, - depthAwareUpsampling: true, - autoRenderBeauty: true, - colorMultiply: true, - transparencyAware: false, - stencil: false - }, { - set: (target, propName, value)=>{ - const oldProp = target[propName]; - target[propName] = value; - if (propName === "aoSamples" && oldProp !== value) this.configureAOPass(this.configuration.logarithmicDepthBuffer); - if (propName === "denoiseSamples" && oldProp !== value) this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); - if (propName === "halfRes" && oldProp !== value) { - this.configureAOPass(this.configuration.logarithmicDepthBuffer); - this.configureHalfResTargets(); - this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); - this.setSize(this.width, this.height); - } - if (propName === "depthAwareUpsampling" && oldProp !== value) this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); - if (propName === "transparencyAware" && oldProp !== value) { - this.autoDetectTransparency = false; - this.configureTransparencyTarget(); - } - if (propName === "stencil" && oldProp !== value) { - /* this.beautyRenderTarget.stencilBuffer = value; - this.beautyRenderTarget.depthTexture.format = value ? THREE.DepthStencilFormat : THREE.DepthFormat; - this.beautyRenderTarget.depthTexture.type = value ? THREE.UnsignedInt248Type : THREE.UnsignedIntType; - this.beautyRenderTarget.depthTexture.needsUpdate = true; - this.beautyRenderTarget.needsUpdate = true;*/ this.beautyRenderTarget.dispose(); - this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: NearestFilter, - type: HalfFloatType, - format: RGBAFormat, - stencilBuffer: value - }); - this.beautyRenderTarget.depthTexture = new DepthTexture(this.width, this.height, value ? UnsignedInt248Type : UnsignedIntType); - this.beautyRenderTarget.depthTexture.format = value ? DepthStencilFormat : DepthFormat; - } - return true; - } - }); - /** @type {THREE.Vector3[]} */ this.samples = []; - /** @type {THREE.Vector2[]} */ this.samplesDenoise = []; - this.autoDetectTransparency = true; - this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: NearestFilter, - type: HalfFloatType, - format: RGBAFormat, - stencilBuffer: false - }); - this.beautyRenderTarget.depthTexture = new DepthTexture(this.width, this.height, UnsignedIntType); - this.beautyRenderTarget.depthTexture.format = DepthFormat; - this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); - this.configureSampleDependentPasses(); - this.configureHalfResTargets(); - this.detectTransparency(); - this.configureTransparencyTarget(); - this.writeTargetInternal = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - depthBuffer: false - }); - this.readTargetInternal = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - depthBuffer: false - }); - /** @type {THREE.DataTexture} */ this.bluenoise = new DataTexture($05f6997e4b65da14$var$bluenoiseBits, 128, 128); - this.bluenoise.colorSpace = NoColorSpace; - this.bluenoise.wrapS = RepeatWrapping; - this.bluenoise.wrapT = RepeatWrapping; - this.bluenoise.minFilter = NearestFilter; - this.bluenoise.magFilter = NearestFilter; - this.bluenoise.needsUpdate = true; - this.lastTime = 0; - this._r = new Vector2$1(); - this._c = new Color(); - } - configureHalfResTargets() { - if (this.configuration.halfRes) { - this.depthDownsampleTarget = /*new THREE.WebGLRenderTarget(this.width / 2, this.height / 2, { - minFilter: THREE.NearestFilter, - magFilter: THREE.NearestFilter, - depthBuffer: false, - format: THREE.RedFormat, - type: THREE.FloatType - });*/ new WebGLMultipleRenderTargets(this.width / 2, this.height / 2, 2); - this.depthDownsampleTarget.texture[0].format = RedFormat; - this.depthDownsampleTarget.texture[0].type = FloatType; - this.depthDownsampleTarget.texture[0].minFilter = NearestFilter; - this.depthDownsampleTarget.texture[0].magFilter = NearestFilter; - this.depthDownsampleTarget.texture[0].depthBuffer = false; - this.depthDownsampleTarget.texture[1].format = RGBAFormat; - this.depthDownsampleTarget.texture[1].type = HalfFloatType; - this.depthDownsampleTarget.texture[1].minFilter = NearestFilter; - this.depthDownsampleTarget.texture[1].magFilter = NearestFilter; - this.depthDownsampleTarget.texture[1].depthBuffer = false; - this.depthDownsampleQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(($26aca173e0984d99$export$1efdf491687cd442))); - } else { - if (this.depthDownsampleTarget) { - this.depthDownsampleTarget.dispose(); - this.depthDownsampleTarget = null; - } - if (this.depthDownsampleQuad) { - this.depthDownsampleQuad.dispose(); - this.depthDownsampleQuad = null; - } - } - } - detectTransparency() { - if (this.autoDetectTransparency) { - let isTransparency = false; - this.scene.traverse((obj)=>{ - if (obj.material && obj.material.transparent) isTransparency = true; - }); - this.configuration.transparencyAware = isTransparency; - } - } - configureTransparencyTarget() { - if (this.configuration.transparencyAware) { - this.transparencyRenderTargetDWFalse = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: NearestFilter, - type: HalfFloatType, - format: RGBAFormat - }); - this.transparencyRenderTargetDWTrue = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: NearestFilter, - type: HalfFloatType, - format: RGBAFormat - }); - this.transparencyRenderTargetDWTrue.depthTexture = new DepthTexture(this.width, this.height, UnsignedIntType); - this.depthCopyPass = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial({ - uniforms: { - depthTexture: { - value: this.beautyRenderTarget.depthTexture - } - }, - vertexShader: /* glsl */ ` - varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = vec4(position, 1); - }`, - fragmentShader: /* glsl */ ` - uniform sampler2D depthTexture; - varying vec2 vUv; - void main() { - gl_FragDepth = texture2D(depthTexture, vUv).r + 0.00001; - gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); - } - ` - })); - } else { - if (this.transparencyRenderTargetDWFalse) { - this.transparencyRenderTargetDWFalse.dispose(); - this.transparencyRenderTargetDWFalse = null; - } - if (this.transparencyRenderTargetDWTrue) { - this.transparencyRenderTargetDWTrue.dispose(); - this.transparencyRenderTargetDWTrue = null; - } - if (this.depthCopyPass) { - this.depthCopyPass.dispose(); - this.depthCopyPass = null; - } - } - } - renderTransparency(renderer) { - const oldBackground = this.scene.background; - const oldClearColor = renderer.getClearColor(new Color()); - const oldClearAlpha = renderer.getClearAlpha(); - const oldVisibility = new Map(); - const oldAutoClearDepth = renderer.autoClearDepth; - this.scene.traverse((obj)=>{ - oldVisibility.set(obj, obj.visible); - }); - // Override the state - this.scene.background = null; - renderer.autoClearDepth = false; - renderer.setClearColor(new Color(0, 0, 0), 0); - this.depthCopyPass.material.uniforms.depthTexture.value = this.beautyRenderTarget.depthTexture; - // Render out transparent objects WITHOUT depth write - renderer.setRenderTarget(this.transparencyRenderTargetDWFalse); - this.scene.traverse((obj)=>{ - if (obj.material) obj.visible = oldVisibility.get(obj) && obj.material.transparent && !obj.material.depthWrite && !obj.userData.treatAsOpaque; - }); - renderer.clear(true, true, true); - this.depthCopyPass.render(renderer); - renderer.render(this.scene, this.camera); - // Render out transparent objects WITH depth write - renderer.setRenderTarget(this.transparencyRenderTargetDWTrue); - this.scene.traverse((obj)=>{ - if (obj.material) obj.visible = oldVisibility.get(obj) && obj.material.transparent && obj.material.depthWrite && !obj.userData.treatAsOpaque; - }); - renderer.clear(true, true, true); - this.depthCopyPass.render(renderer); - renderer.render(this.scene, this.camera); - // Restore - this.scene.traverse((obj)=>{ - obj.visible = oldVisibility.get(obj); - }); - renderer.setClearColor(oldClearColor, oldClearAlpha); - this.scene.background = oldBackground; - renderer.autoClearDepth = oldAutoClearDepth; - } - configureSampleDependentPasses() { - this.configureAOPass(this.configuration.logarithmicDepthBuffer); - this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); - } - configureAOPass(logarithmicDepthBuffer = false) { - this.samples = this.generateHemisphereSamples(this.configuration.aoSamples); - const e = { - ...($1ed45968c1160c3c$export$c9b263b9a17dffd7) - }; - e.fragmentShader = e.fragmentShader.replace("16", this.configuration.aoSamples).replace("16.0", this.configuration.aoSamples + ".0"); - if (logarithmicDepthBuffer) e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader; - if (this.configuration.halfRes) e.fragmentShader = "#define HALFRES\n" + e.fragmentShader; - if (this.effectShaderQuad) { - this.effectShaderQuad.material.dispose(); - this.effectShaderQuad.material = new ShaderMaterial(e); - } else this.effectShaderQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(e)); - } - configureDenoisePass(logarithmicDepthBuffer = false) { - this.samplesDenoise = this.generateDenoiseSamples(this.configuration.denoiseSamples, 11); - const p = { - ...($e52378cd0f5a973d$export$57856b59f317262e) - }; - p.fragmentShader = p.fragmentShader.replace("16", this.configuration.denoiseSamples); - if (logarithmicDepthBuffer) p.fragmentShader = "#define LOGDEPTH\n" + p.fragmentShader; - if (this.poissonBlurQuad) { - this.poissonBlurQuad.material.dispose(); - this.poissonBlurQuad.material = new ShaderMaterial(p); - } else this.poissonBlurQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(p)); - } - configureEffectCompositer(logarithmicDepthBuffer = false) { - const e = { - ...($12b21d24d1192a04$export$a815acccbd2c9a49) - }; - if (logarithmicDepthBuffer) e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader; - if (this.configuration.halfRes && this.configuration.depthAwareUpsampling) e.fragmentShader = "#define HALFRES\n" + e.fragmentShader; - if (this.effectCompositerQuad) { - this.effectCompositerQuad.material.dispose(); - this.effectCompositerQuad.material = new ShaderMaterial(e); - } else this.effectCompositerQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(e)); - } - /** - * - * @param {Number} n - * @returns {THREE.Vector3[]} - */ generateHemisphereSamples(n) { - const points = []; - for(let k = 0; k < n; k++){ - const theta = 2.399963 * k; - let r = Math.sqrt(k + 0.5) / Math.sqrt(n); - const x = r * Math.cos(theta); - const y = r * Math.sin(theta); - // Project to hemisphere - const z = Math.sqrt(1 - (x * x + y * y)); - points.push(new Vector3$1(x, y, z)); - } - return points; - } - /** - * - * @param {number} numSamples - * @param {number} numRings - * @returns {THREE.Vector2[]} - */ generateDenoiseSamples(numSamples, numRings) { - const angleStep = 2 * Math.PI * numRings / numSamples; - const invNumSamples = 1.0 / numSamples; - const radiusStep = invNumSamples; - const samples = []; - let radius = invNumSamples; - let angle = 0; - for(let i = 0; i < numSamples; i++){ - samples.push(new Vector2$1(Math.cos(angle), Math.sin(angle)).multiplyScalar(Math.pow(radius, 0.75))); - radius += radiusStep; - angle += angleStep; - } - return samples; - } - setSize(width, height) { - this.width = width; - this.height = height; - const c = this.configuration.halfRes ? 0.5 : 1; - this.beautyRenderTarget.setSize(width, height); - this.writeTargetInternal.setSize(width * c, height * c); - this.readTargetInternal.setSize(width * c, height * c); - if (this.configuration.halfRes) this.depthDownsampleTarget.setSize(width * c, height * c); - if (this.configuration.transparencyAware) { - this.transparencyRenderTargetDWFalse.setSize(width, height); - this.transparencyRenderTargetDWTrue.setSize(width, height); - } - } - render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) { - if (renderer.capabilities.logarithmicDepthBuffer !== this.configuration.logarithmicDepthBuffer) { - this.configuration.logarithmicDepthBuffer = renderer.capabilities.logarithmicDepthBuffer; - this.configureAOPass(this.configuration.logarithmicDepthBuffer); - this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); - this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); - } - this.detectTransparency(); - let gl; - let ext; - let timerQuery; - if (this.debugMode) { - gl = renderer.getContext(); - ext = gl.getExtension("EXT_disjoint_timer_query_webgl2"); - if (ext === null) { - console.error("EXT_disjoint_timer_query_webgl2 not available, disabling debug mode."); - this.debugMode = false; - } - } - if (this.configuration.autoRenderBeauty) { - renderer.setRenderTarget(this.beautyRenderTarget); - renderer.render(this.scene, this.camera); - if (this.configuration.transparencyAware) this.renderTransparency(renderer); - } - if (this.debugMode) { - timerQuery = gl.createQuery(); - gl.beginQuery(ext.TIME_ELAPSED_EXT, timerQuery); - } - const xrEnabled = renderer.xr.enabled; - renderer.xr.enabled = false; - this.camera.updateMatrixWorld(); - this._r.set(this.width, this.height); - let trueRadius = this.configuration.aoRadius; - if (this.configuration.halfRes && this.configuration.screenSpaceRadius) trueRadius *= 0.5; - if (this.configuration.halfRes) { - renderer.setRenderTarget(this.depthDownsampleTarget); - this.depthDownsampleQuad.material.uniforms.sceneDepth.value = this.beautyRenderTarget.depthTexture; - this.depthDownsampleQuad.material.uniforms.resolution.value = this._r; - this.depthDownsampleQuad.material.uniforms["near"].value = this.camera.near; - this.depthDownsampleQuad.material.uniforms["far"].value = this.camera.far; - this.depthDownsampleQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; - this.depthDownsampleQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; - this.depthDownsampleQuad.material.uniforms["logDepth"].value = this.configuration.logarithmicDepthBuffer; - this.depthDownsampleQuad.render(renderer); - } - this.effectShaderQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture; - this.effectShaderQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; - this.effectShaderQuad.material.uniforms["sceneNormal"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[1] : null; - this.effectShaderQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix; - this.effectShaderQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse; - this.effectShaderQuad.material.uniforms["projViewMat"].value = this.camera.projectionMatrix.clone().multiply(this.camera.matrixWorldInverse.clone()); - this.effectShaderQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; - this.effectShaderQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; - this.effectShaderQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); - this.effectShaderQuad.material.uniforms["resolution"].value = this.configuration.halfRes ? this._r.clone().multiplyScalar(0.5).floor() : this._r; - this.effectShaderQuad.material.uniforms["time"].value = performance.now() / 1000; - this.effectShaderQuad.material.uniforms["samples"].value = this.samples; - this.effectShaderQuad.material.uniforms["bluenoise"].value = this.bluenoise; - this.effectShaderQuad.material.uniforms["radius"].value = trueRadius; - this.effectShaderQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; - this.effectShaderQuad.material.uniforms["near"].value = this.camera.near; - this.effectShaderQuad.material.uniforms["far"].value = this.camera.far; - this.effectShaderQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; - this.effectShaderQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera; - this.effectShaderQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; - // Start the AO - renderer.setRenderTarget(this.writeTargetInternal); - this.effectShaderQuad.render(renderer); - // End the AO - // Start the blur - for(let i = 0; i < this.configuration.denoiseIterations; i++){ - [this.writeTargetInternal, this.readTargetInternal] = [ - this.readTargetInternal, - this.writeTargetInternal - ]; - this.poissonBlurQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.texture; - this.poissonBlurQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; - this.poissonBlurQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix; - this.poissonBlurQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse; - this.poissonBlurQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; - this.poissonBlurQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; - this.poissonBlurQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); - this.poissonBlurQuad.material.uniforms["resolution"].value = this.configuration.halfRes ? this._r.clone().multiplyScalar(0.5).floor() : this._r; - this.poissonBlurQuad.material.uniforms["time"].value = performance.now() / 1000; - this.poissonBlurQuad.material.uniforms["blueNoise"].value = this.bluenoise; - this.poissonBlurQuad.material.uniforms["radius"].value = this.configuration.denoiseRadius * (this.configuration.halfRes ? 0.5 : 1); - this.poissonBlurQuad.material.uniforms["worldRadius"].value = trueRadius; - this.poissonBlurQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; - this.poissonBlurQuad.material.uniforms["index"].value = i; - this.poissonBlurQuad.material.uniforms["poissonDisk"].value = this.samplesDenoise; - this.poissonBlurQuad.material.uniforms["near"].value = this.camera.near; - this.poissonBlurQuad.material.uniforms["far"].value = this.camera.far; - this.poissonBlurQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; - this.poissonBlurQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; - renderer.setRenderTarget(this.writeTargetInternal); - this.poissonBlurQuad.render(renderer); - } - // Now, we have the blurred AO in writeTargetInternal - // End the blur - // Start the composition - if (this.configuration.transparencyAware) { - this.effectCompositerQuad.material.uniforms["transparencyDWFalse"].value = this.transparencyRenderTargetDWFalse.texture; - this.effectCompositerQuad.material.uniforms["transparencyDWTrue"].value = this.transparencyRenderTargetDWTrue.texture; - this.effectCompositerQuad.material.uniforms["transparencyDWTrueDepth"].value = this.transparencyRenderTargetDWTrue.depthTexture; - this.effectCompositerQuad.material.uniforms["transparencyAware"].value = true; - } - this.effectCompositerQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture; - this.effectCompositerQuad.material.uniforms["sceneDepth"].value = this.beautyRenderTarget.depthTexture; - this.effectCompositerQuad.material.uniforms["near"].value = this.camera.near; - this.effectCompositerQuad.material.uniforms["far"].value = this.camera.far; - this.effectCompositerQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; - this.effectCompositerQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; - this.effectCompositerQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; - this.effectCompositerQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera; - this.effectCompositerQuad.material.uniforms["downsampledDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; - this.effectCompositerQuad.material.uniforms["resolution"].value = this._r; - this.effectCompositerQuad.material.uniforms["blueNoise"].value = this.bluenoise; - this.effectCompositerQuad.material.uniforms["intensity"].value = this.configuration.intensity; - this.effectCompositerQuad.material.uniforms["renderMode"].value = this.configuration.renderMode; - this.effectCompositerQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; - this.effectCompositerQuad.material.uniforms["radius"].value = trueRadius; - this.effectCompositerQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; - this.effectCompositerQuad.material.uniforms["gammaCorrection"].value = this.configuration.gammaCorrection; - this.effectCompositerQuad.material.uniforms["tDiffuse"].value = this.writeTargetInternal.texture; - this.effectCompositerQuad.material.uniforms["color"].value = this._c.copy(this.configuration.color).convertSRGBToLinear(); - this.effectCompositerQuad.material.uniforms["colorMultiply"].value = this.configuration.colorMultiply; - this.effectCompositerQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); - this.effectCompositerQuad.material.uniforms["fog"].value = !!this.scene.fog; - if (this.scene.fog) { - if (this.scene.fog.isFog) { - this.effectCompositerQuad.material.uniforms["fogExp"].value = false; - this.effectCompositerQuad.material.uniforms["fogNear"].value = this.scene.fog.near; - this.effectCompositerQuad.material.uniforms["fogFar"].value = this.scene.fog.far; - } else if (this.scene.fog.isFogExp2) { - this.effectCompositerQuad.material.uniforms["fogExp"].value = true; - this.effectCompositerQuad.material.uniforms["fogDensity"].value = this.scene.fog.density; - } else console.error(`Unsupported fog type ${this.scene.fog.constructor.name} in SSAOPass.`); - } - renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer); - this.effectCompositerQuad.render(renderer); - if (this.debugMode) { - gl.endQuery(ext.TIME_ELAPSED_EXT); - $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, this); - } - renderer.xr.enabled = xrEnabled; - } - /** - * Enables the debug mode of the AO, meaning the lastTime value will be updated. - */ enableDebugMode() { - this.debugMode = true; - } - /** - * Disables the debug mode of the AO, meaning the lastTime value will not be updated. - */ disableDebugMode() { - this.debugMode = false; - } - /** - * Sets the display mode of the AO - * @param {"Combined" | "AO" | "No AO" | "Split" | "Split AO"} mode - The display mode. - */ setDisplayMode(mode) { - this.configuration.renderMode = [ - "Combined", - "AO", - "No AO", - "Split", - "Split AO" - ].indexOf(mode); - } - /** - * - * @param {"Performance" | "Low" | "Medium" | "High" | "Ultra"} mode - */ setQualityMode(mode) { - if (mode === "Performance") { - this.configuration.aoSamples = 8; - this.configuration.denoiseSamples = 4; - this.configuration.denoiseRadius = 12; - } else if (mode === "Low") { - this.configuration.aoSamples = 16; - this.configuration.denoiseSamples = 4; - this.configuration.denoiseRadius = 12; - } else if (mode === "Medium") { - this.configuration.aoSamples = 16; - this.configuration.denoiseSamples = 8; - this.configuration.denoiseRadius = 12; - } else if (mode === "High") { - this.configuration.aoSamples = 64; - this.configuration.denoiseSamples = 8; - this.configuration.denoiseRadius = 6; - } else if (mode === "Ultra") { - this.configuration.aoSamples = 64; - this.configuration.denoiseSamples = 16; - this.configuration.denoiseRadius = 6; - } - } +function getToBlobPromise( canvas, mimeType ) { + + if ( canvas.toBlob !== undefined ) { + + return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); + + } + + let quality; + + // Blink's implementation of convertToBlob seems to default to a quality level of 100% + // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. + if ( mimeType === 'image/jpeg' ) { + + quality = 0.92; + + } else if ( mimeType === 'image/webp' ) { + + quality = 0.8; + + } + + return canvas.convertToBlob( { + + type: mimeType, + quality: quality + + } ); + } -/** - * Gamma Correction Shader - * http://en.wikipedia.org/wiki/gamma_correction - */ +/** + * Writer + */ +class GLTFWriter { + + constructor() { + + this.plugins = []; + + this.options = {}; + this.pending = []; + this.buffers = []; + + this.byteOffset = 0; + this.buffers = []; + this.nodeMap = new Map(); + this.skins = []; + + this.extensionsUsed = {}; + this.extensionsRequired = {}; + + this.uids = new Map(); + this.uid = 0; + + this.json = { + asset: { + version: '2.0', + generator: 'THREE.GLTFExporter' + } + }; + + this.cache = { + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map() + }; + + } + + setPlugins( plugins ) { + + this.plugins = plugins; + + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + async write( input, onDone, options = {} ) { + + this.options = Object.assign( { + // default options + binary: false, + trs: false, + onlyVisible: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); + + if ( this.options.animations.length > 0 ) { + + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; + + } + + this.processInput( input ); + + await Promise.all( this.pending ); + + const writer = this; + const buffers = writer.buffers; + const json = writer.json; + options = writer.options; + + const extensionsUsed = writer.extensionsUsed; + const extensionsRequired = writer.extensionsRequired; + + // Merge buffers. + const blob = new Blob( buffers, { type: 'application/octet-stream' } ); + + // Declare extensions. + const extensionsUsedList = Object.keys( extensionsUsed ); + const extensionsRequiredList = Object.keys( extensionsRequired ); + + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; + if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; + + // Update bytelength of the single buffer. + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + + if ( options.binary === true ) { + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { + + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + + // GLB header. + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); + + const glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); + + const glbReader = new FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { + + onDone( glbReader.result ); + + }; + + }; + + } else { + + if ( json.buffers && json.buffers.length > 0 ) { + + const reader = new FileReader(); + reader.readAsDataURL( blob ); + reader.onloadend = function () { + + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); + + }; + + } else { + + onDone( json ); + + } + + } + + + } + + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData( object, objectDef ) { + + if ( Object.keys( object.userData ).length === 0 ) return; + + const options = this.options; + const extensionsUsed = this.extensionsUsed; + + try { + + const json = JSON.parse( JSON.stringify( object.userData ) ); + + if ( options.includeCustomExtensions && json.gltfExtensions ) { + + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + + for ( const extensionName in json.gltfExtensions ) { + + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; + + } + + delete json.gltfExtensions; + + } + + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + + } catch ( error ) { + + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + + } + + } + + /** + * Returns ids for buffer attributes. + * @param {Object} object + * @return {Integer} + */ + getUID( attribute, isRelativeCopy = false ) { -const GammaCorrectionShader = { + if ( this.uids.has( attribute ) === false ) { - uniforms: { + const uids = new Map(); - 'tDiffuse': { value: null } + uids.set( true, this.uid ++ ); + uids.set( false, this.uid ++ ); - }, + this.uids.set( attribute, uids ); + + } + + const uids = this.uids.get( attribute ); + + return uids.get( isRelativeCopy ); + + } + + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute( normal ) { + + const cache = this.cache; + + if ( cache.attributesNormalized.has( normal ) ) return false; + + const v = new Vector3$1(); + + for ( let i = 0, il = normal.count; i < il; i ++ ) { + + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + + } + + return true; + + } + + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute( normal ) { + + const cache = this.cache; + + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + + const attribute = normal.clone(); + const v = new Vector3$1(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + v.fromBufferAttribute( attribute, i ); + + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); + + } else { + + v.normalize(); + + } + + attribute.setXYZ( i, v.x, v.y, v.z ); + + } + + cache.attributesNormalized.set( normal, attribute ); + + return attribute; + + } + + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform( mapDef, texture ) { + + let didTransform = false; + const transformDef = {}; + + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + + transformDef.offset = texture.offset.toArray(); + didTransform = true; + + } + + if ( texture.rotation !== 0 ) { + + transformDef.rotation = texture.rotation; + didTransform = true; + + } + + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + + transformDef.scale = texture.repeat.toArray(); + didTransform = true; + + } + + if ( didTransform ) { + + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; + + } + + } + + buildMetalRoughTexture( metalnessMap, roughnessMap ) { + + if ( metalnessMap === roughnessMap ) return metalnessMap; + + function getEncodingConversion( map ) { + + if ( map.colorSpace === SRGBColorSpace ) { + + return function SRGBToLinear( c ) { + + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + + }; + + } + + return function LinearToLinear( c ) { + + return c; + + }; + + } + + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + + const metalness = metalnessMap ? metalnessMap.image : null; + const roughness = roughnessMap ? roughnessMap.image : null; + + const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); + const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); + + const canvas = getCanvas(); + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext( '2d' ); + context.fillStyle = '#00ffff'; + context.fillRect( 0, 0, width, height ); + + const composite = context.getImageData( 0, 0, width, height ); + + if ( metalness ) { + + context.drawImage( metalness, 0, 0, width, height ); + + const convert = getEncodingConversion( metalnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 2; i < data.length; i += 4 ) { + + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + + } + + } + + if ( roughness ) { + + context.drawImage( roughness, 0, 0, width, height ); + + const convert = getEncodingConversion( roughnessMap ); + const data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 1; i < data.length; i += 4 ) { + + composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + + } + + } + + context.putImageData( composite, 0, 0 ); + + // + + const reference = metalnessMap || roughnessMap; + + const texture = reference.clone(); + + texture.source = new Source( canvas ); + texture.colorSpace = NoColorSpace; + texture.channel = ( metalnessMap || roughnessMap ).channel; + + if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { + + console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); + + } + + return texture; + + } + + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer( buffer ) { + + const json = this.json; + const buffers = this.buffers; + + if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + + // All buffers are merged before export. + buffers.push( buffer ); + + return 0; + + } + + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView( attribute, componentType, start, count, target ) { + + const json = this.json; + + if ( ! json.bufferViews ) json.bufferViews = []; + + // Create a new dataview and dump the attribute's array into it + + let componentSize; + + switch ( componentType ) { + + case WEBGL_CONSTANTS.BYTE: + case WEBGL_CONSTANTS.UNSIGNED_BYTE: + + componentSize = 1; + + break; + + case WEBGL_CONSTANTS.SHORT: + case WEBGL_CONSTANTS.UNSIGNED_SHORT: + + componentSize = 2; + + break; + + default: + + componentSize = 4; + + } + + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; + + for ( let i = start; i < start + count; i ++ ) { + + for ( let a = 0; a < attribute.itemSize; a ++ ) { + + let value; + + if ( attribute.itemSize > 4 ) { + + // no support for interleaved data for itemSize > 4 + + value = attribute.array[ i * attribute.itemSize + a ]; + + } else { + + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); + + if ( attribute.normalized === true ) { + + value = MathUtils.normalize( value, attribute.array ); + + } + + } + + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + + dataView.setFloat32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.INT ) { + + dataView.setInt32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + + dataView.setUint32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { + + dataView.setInt16( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + + dataView.setUint16( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { + + dataView.setInt8( offset, value ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + + dataView.setUint8( offset, value ); + + } + + offset += componentSize; + + } + + } + + const bufferViewDef = { + + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength + + }; + + if ( target !== undefined ) bufferViewDef.target = target; + + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; + + } + + this.byteOffset += byteLength; + + json.bufferViews.push( bufferViewDef ); + + // @TODO Merge bufferViews where possible. + const output = { + + id: json.bufferViews.length - 1, + byteLength: 0 + + }; + + return output; + + } + + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage( blob ) { + + const writer = this; + const json = writer.json; + + if ( ! json.bufferViews ) json.bufferViews = []; + + return new Promise( function ( resolve ) { + + const reader = new FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { + + const buffer = getPaddedArrayBuffer( reader.result ); + + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength + }; + + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + + }; + + } ); + + } + + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor( attribute, geometry, start, count ) { + + const json = this.json; + + const types = { + + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 9: 'MAT3', + 16: 'MAT4' + + }; + + let componentType; + + // Detect the component type of the attribute array + if ( attribute.array.constructor === Float32Array ) { + + componentType = WEBGL_CONSTANTS.FLOAT; + + } else if ( attribute.array.constructor === Int32Array ) { + + componentType = WEBGL_CONSTANTS.INT; + + } else if ( attribute.array.constructor === Uint32Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + + } else if ( attribute.array.constructor === Int16Array ) { + + componentType = WEBGL_CONSTANTS.SHORT; + + } else if ( attribute.array.constructor === Uint16Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + + } else if ( attribute.array.constructor === Int8Array ) { + + componentType = WEBGL_CONSTANTS.BYTE; + + } else if ( attribute.array.constructor === Uint8Array ) { + + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + + } else { + + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + + } + + if ( start === undefined ) start = 0; + if ( count === undefined ) count = attribute.count; + + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) return null; + + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; + + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if ( geometry !== undefined ) { + + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + + } + + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + + const accessorDef = { + + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] - vertexShader: /* glsl */` + }; - varying vec2 vUv; + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; - void main() { + return json.accessors.push( accessorDef ) - 1; - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + } - }`, + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage( image, format, flipY, mimeType = 'image/png' ) { - fragmentShader: /* glsl */` + if ( image !== null ) { - uniform sampler2D tDiffuse; + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; - varying vec2 vUv; + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - void main() { + const cachedImages = cache.images.get( image ); - vec4 tex = texture2D( tDiffuse, vUv ); + const key = mimeType + ':flipY/' + flipY.toString(); - gl_FragColor = LinearTosRGB( tex ); + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - }` + if ( ! json.images ) json.images = []; -}; + const imageDef = { mimeType: mimeType }; -/** - * Object to control the {@link CameraProjection} of the {@link OrthoPerspectiveCamera}. - */ -class ProjectionManager { - get projection() { - return this._currentProjection; - } - constructor(components, camera) { - this.components = components; - this._previousDistance = -1; - this.matchOrthoDistanceEnabled = false; - this._camera = camera; - const perspective = "Perspective"; - this._currentCamera = camera.get(perspective); - this._currentProjection = perspective; - } - /** - * Sets the {@link CameraProjection} of the {@link OrthoPerspectiveCamera}. - * - * @param projection - the new projection to set. If it is the current projection, - * it will have no effect. - */ - async setProjection(projection) { - if (this.projection === projection) - return; - if (projection === "Orthographic") { - this.setOrthoCamera(); - } - else { - await this.setPerspectiveCamera(); - } - await this.updateActiveCamera(); - } - setOrthoCamera() { - // Matching orthographic camera to perspective camera - // Resource: https://stackoverflow.com/questions/48758959/what-is-required-to-convert-threejs-perspective-camera-to-orthographic - if (this._camera.currentMode.id === "FirstPerson") { - return; - } - this._previousDistance = this._camera.controls.distance; - this._camera.controls.distance = 200; - const { width, height } = this.getDims(); - this.setupOrthoCamera(height, width); - this._currentCamera = this._camera.get("Orthographic"); - this._currentProjection = "Orthographic"; - } - // This small delay is needed to hide weirdness during the transition - async updateActiveCamera() { - await new Promise((resolve) => { - setTimeout(() => { - this._camera.activeCamera = this._currentCamera; - resolve(); - }, 50); - }); - } - getDims() { - const lineOfSight = new THREE$1.Vector3(); - this._camera.get("Perspective").getWorldDirection(lineOfSight); - const target = new THREE$1.Vector3(); - this._camera.controls.getTarget(target); - const distance = target - .clone() - .sub(this._camera.get("Perspective").position); - const depth = distance.dot(lineOfSight); - const dims = this.components.renderer.getSize(); - const aspect = dims.x / dims.y; - const camera = this._camera.get("Perspective"); - const height = depth * 2 * Math.atan((camera.fov * (Math.PI / 180)) / 2); - const width = height * aspect; - return { width, height }; - } - setupOrthoCamera(height, width) { - this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.ZOOM; - this._camera.controls.mouseButtons.middle = CameraControls.ACTION.ZOOM; - const pCamera = this._camera.get("Perspective"); - const oCamera = this._camera.get("Orthographic"); - oCamera.zoom = 1; - oCamera.left = width / -2; - oCamera.right = width / 2; - oCamera.top = height / 2; - oCamera.bottom = height / -2; - oCamera.updateProjectionMatrix(); - oCamera.position.copy(pCamera.position); - oCamera.quaternion.copy(pCamera.quaternion); - this._camera.controls.camera = oCamera; - } - getDistance() { - // this handles ortho zoom to perpective distance - const pCamera = this._camera.get("Perspective"); - const oCamera = this._camera.get("Orthographic"); - // this is the reverse of - // const height = depth * 2 * Math.atan((pCamera.fov * (Math.PI / 180)) / 2); - // accounting for zoom - const depth = (oCamera.top - oCamera.bottom) / - oCamera.zoom / - (2 * Math.atan((pCamera.fov * (Math.PI / 180)) / 2)); - return depth; - } - async setPerspectiveCamera() { - this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; - this._camera.controls.mouseButtons.middle = CameraControls.ACTION.DOLLY; - const pCamera = this._camera.get("Perspective"); - const oCamera = this._camera.get("Orthographic"); - pCamera.position.copy(oCamera.position); - pCamera.quaternion.copy(oCamera.quaternion); - this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; - if (this.matchOrthoDistanceEnabled) { - this._camera.controls.distance = this.getDistance(); - } - else { - this._camera.controls.distance = this._previousDistance; - } - await this._camera.controls.zoomTo(1); - pCamera.updateProjectionMatrix(); - this._camera.controls.camera = pCamera; - this._currentCamera = pCamera; - this._currentProjection = "Perspective"; - } -} + const canvas = getCanvas(); -/** - * A {@link NavigationMode} that allows 3D navigation and panning - * like in many 3D and CAD softwares. - */ -class OrbitMode { - constructor(camera) { - this.camera = camera; - /** {@link NavigationMode.enabled} */ - this.enabled = true; - /** {@link NavigationMode.id} */ - this.id = "Orbit"; - /** {@link NavigationMode.projectionChanged} */ - this.projectionChanged = new Event(); - this.activateOrbitControls(); - } - /** {@link NavigationMode.toggle} */ - toggle(active) { - this.enabled = active; - if (active) { - this.activateOrbitControls(); - } - } - activateOrbitControls() { - const controls = this.camera.controls; - controls.minDistance = 1; - controls.maxDistance = 300; - const position = new THREE$1.Vector3(); - controls.getPosition(position); - const distance = position.length(); - controls.distance = distance; - controls.truckSpeed = 2; - const { rotation } = this.camera.get(); - const direction = new THREE$1.Vector3(0, 0, -1).applyEuler(rotation); - const target = position.addScaledVector(direction, distance); - controls.moveTo(target.x, target.y, target.z); - } -} + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); -/** - * A {@link NavigationMode} that allows first person navigation, - * simulating FPS video games. - */ -class FirstPersonMode { - constructor(camera) { - this.camera = camera; - /** {@link NavigationMode.enabled} */ - this.enabled = false; - /** {@link NavigationMode.id} */ - this.id = "FirstPerson"; - /** {@link NavigationMode.projectionChanged} */ - this.projectionChanged = new Event(); - } - /** {@link NavigationMode.toggle} */ - toggle(active) { - this.enabled = active; - if (active) { - const projection = this.camera.getProjection(); - if (projection !== "Perspective") { - this.camera.setNavigationMode("Orbit"); - return; - } - this.setupFirstPersonCamera(); - } - } - setupFirstPersonCamera() { - const controls = this.camera.controls; - const newTargetPosition = new THREE$1.Vector3(); - controls.distance--; - controls.getPosition(newTargetPosition); - controls.minDistance = 1; - controls.maxDistance = 1; - controls.distance = 1; - controls.moveTo(newTargetPosition.x, newTargetPosition.y, newTargetPosition.z); - controls.truckSpeed = 50; - controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; - controls.touches.two = CameraControls.ACTION.TOUCH_ZOOM_TRUCK; - } -} + const ctx = canvas.getContext( '2d' ); -/** - * A {@link NavigationMode} that allows to navigate floorplans in 2D, - * like many BIM tools. - */ -class PlanMode { - constructor(camera) { - this.camera = camera; - /** {@link NavigationMode.enabled} */ - this.enabled = false; - /** {@link NavigationMode.id} */ - this.id = "Plan"; - /** {@link NavigationMode.projectionChanged} */ - this.projectionChanged = new Event(); - this.mouseInitialized = false; - this.defaultAzimuthSpeed = camera.controls.azimuthRotateSpeed; - this.defaultPolarSpeed = camera.controls.polarRotateSpeed; - } - /** {@link NavigationMode.toggle} */ - toggle(active) { - this.enabled = active; - const controls = this.camera.controls; - controls.azimuthRotateSpeed = active ? 0 : this.defaultAzimuthSpeed; - controls.polarRotateSpeed = active ? 0 : this.defaultPolarSpeed; - if (!this.mouseInitialized) { - this.mouseAction1 = controls.touches.one; - this.mouseAction2 = controls.touches.two; - this.mouseInitialized = true; - } - if (active) { - controls.mouseButtons.left = CameraControls.ACTION.TRUCK; - controls.touches.one = CameraControls.ACTION.TOUCH_TRUCK; - controls.touches.two = CameraControls.ACTION.TOUCH_ZOOM; - } - else { - controls.mouseButtons.left = CameraControls.ACTION.ROTATE; - controls.touches.one = this.mouseAction1; - controls.touches.two = this.mouseAction2; - } - } -} + if ( flipY === true ) { -/** - * A flexible camera that uses - * [yomotsu's cameracontrols](https://github.com/yomotsu/camera-controls) to - * easily control the camera in 2D and 3D. It supports multiple navigation - * modes, such as 2D floor plan navigation, first person and 3D orbit. - */ -class OrthoPerspectiveCamera extends SimpleCamera { - constructor(components) { - super(components); - /** - * Event that fires when the {@link CameraProjection} changes. - */ - this.projectionChanged = new Event(); - this._userInputButtons = {}; - this._frustumSize = 50; - this._navigationModes = new Map(); - this.uiElement = new UIElement(); - this._orthoCamera = this.newOrthoCamera(); - this._navigationModes.set("Orbit", new OrbitMode(this)); - this._navigationModes.set("FirstPerson", new FirstPersonMode(this)); - this._navigationModes.set("Plan", new PlanMode(this)); - this.currentMode = this._navigationModes.get("Orbit"); - this.currentMode.toggle(true, { preventTargetAdjustment: true }); - this.toggleEvents(true); - this._projectionManager = new ProjectionManager(components, this); - components.onInitialized.add(() => { - if (components.uiEnabled) - this.setUI(); - }); - this.onAspectUpdated.add(() => this.setOrthoCameraAspect()); - } - setUI() { - const mainButton = new Button(this.components); - mainButton.materialIcon = "video_camera_back"; - mainButton.tooltip = "Camera"; - const projection = new Button(this.components, { - materialIconName: "camera", - name: "Projection", - }); - const perspective = new Button(this.components, { name: "Perspective" }); - perspective.active = true; - perspective.onClick.add(() => this.setProjection("Perspective")); - const orthographic = new Button(this.components, { name: "Orthographic" }); - orthographic.onClick.add(() => this.setProjection("Orthographic")); - projection.addChild(perspective, orthographic); - const navigation = new Button(this.components, { - materialIconName: "open_with", - name: "Navigation", - }); - const orbit = new Button(this.components, { name: "Orbit Around" }); - orbit.onClick.add(() => this.setNavigationMode("Orbit")); - const plan = new Button(this.components, { name: "Plan View" }); - plan.onClick.add(() => this.setNavigationMode("Plan")); - const firstPerson = new Button(this.components, { name: "First person" }); - firstPerson.onClick.add(() => this.setNavigationMode("FirstPerson")); - navigation.addChild(orbit, plan, firstPerson); - mainButton.addChild(navigation, projection); - this.projectionChanged.add((camera) => { - if (camera instanceof THREE$1.PerspectiveCamera) { - perspective.active = true; - orthographic.active = false; - } - else { - perspective.active = false; - orthographic.active = true; - } - }); - this.uiElement.set({ main: mainButton }); - } - /** {@link Disposable.dispose} */ - async dispose() { - await super.dispose(); - this.toggleEvents(false); - this._orthoCamera.removeFromParent(); - } - /** - * Similar to {@link Component.get}, but with an optional argument - * to specify which camera to get. - * - * @param projection - The camera corresponding to the - * {@link CameraProjection} specified. If no projection is specified, - * the active camera will be returned. - */ - get(projection) { - if (!projection) { - return this.activeCamera; - } - return projection === "Orthographic" - ? this._orthoCamera - : this._perspectiveCamera; - } - /** Returns the current {@link CameraProjection}. */ - getProjection() { - return this._projectionManager.projection; - } - /** Match Ortho zoom with Perspective distance when changing projection mode */ - set matchOrthoDistanceEnabled(value) { - this._projectionManager.matchOrthoDistanceEnabled = value; - } - /** - * Changes the current {@link CameraProjection} from Ortographic to Perspective - * and Viceversa. - */ - async toggleProjection() { - const projection = this.getProjection(); - const newProjection = projection === "Perspective" ? "Orthographic" : "Perspective"; - await this.setProjection(newProjection); - } - /** - * Sets the current {@link CameraProjection}. This triggers the event - * {@link projectionChanged}. - * - * @param projection - The new {@link CameraProjection} to set. - */ - async setProjection(projection) { - await this._projectionManager.setProjection(projection); - await this.projectionChanged.trigger(this.activeCamera); - } - /** - * Allows or prevents all user input. - * - * @param active - whether to enable or disable user inputs. - */ - toggleUserInput(active) { - if (active) { - this.enableUserInput(); - } - else { - this.disableUserInput(); - } - } - /** - * Sets a new {@link NavigationMode} and disables the previous one. - * - * @param mode - The {@link NavigationMode} to set. - */ - setNavigationMode(mode) { - if (this.currentMode.id === mode) - return; - this.currentMode.toggle(false); - if (!this._navigationModes.has(mode)) { - throw new Error("The specified mode does not exist!"); - } - this.currentMode = this._navigationModes.get(mode); - this.currentMode.toggle(true); - } - /** - * Make the camera view fit all the specified meshes. - * - * @param meshes the meshes to fit. If it is not defined, it will - * evaluate {@link Components.meshes}. - * @param offset the distance to the fit object - */ - async fit(meshes = this.components.meshes, offset = 1.5) { - if (!this.enabled) - return; - const maxNum = Number.MAX_VALUE; - const minNum = Number.MIN_VALUE; - const min = new THREE$1.Vector3(maxNum, maxNum, maxNum); - const max = new THREE$1.Vector3(minNum, minNum, minNum); - for (const mesh of meshes) { - const box = new THREE$1.Box3().setFromObject(mesh); - if (box.min.x < min.x) - min.x = box.min.x; - if (box.min.y < min.y) - min.y = box.min.y; - if (box.min.z < min.z) - min.z = box.min.z; - if (box.max.x > max.x) - max.x = box.max.x; - if (box.max.y > max.y) - max.y = box.max.y; - if (box.max.z > max.z) - max.z = box.max.z; - } - const box = new THREE$1.Box3(min, max); - const sceneSize = new THREE$1.Vector3(); - box.getSize(sceneSize); - const sceneCenter = new THREE$1.Vector3(); - box.getCenter(sceneCenter); - const radius = Math.max(sceneSize.x, sceneSize.y, sceneSize.z) * offset; - const sphere = new THREE$1.Sphere(sceneCenter, radius); - await this.controls.fitToSphere(sphere, true); - } - disableUserInput() { - this._userInputButtons.left = this.controls.mouseButtons.left; - this._userInputButtons.right = this.controls.mouseButtons.right; - this._userInputButtons.middle = this.controls.mouseButtons.middle; - this._userInputButtons.wheel = this.controls.mouseButtons.wheel; - this.controls.mouseButtons.left = 0; - this.controls.mouseButtons.right = 0; - this.controls.mouseButtons.middle = 0; - this.controls.mouseButtons.wheel = 0; - } - enableUserInput() { - if (Object.keys(this._userInputButtons).length === 0) - return; - this.controls.mouseButtons.left = this._userInputButtons.left; - this.controls.mouseButtons.right = this._userInputButtons.right; - this.controls.mouseButtons.middle = this._userInputButtons.middle; - this.controls.mouseButtons.wheel = this._userInputButtons.wheel; - } - newOrthoCamera() { - const dims = this.components.renderer.getSize(); - const aspect = dims.x / dims.y; - return new THREE$1.OrthographicCamera((this._frustumSize * aspect) / -2, (this._frustumSize * aspect) / 2, this._frustumSize / 2, this._frustumSize / -2, 0.1, 1000); - } - setOrthoCameraAspect() { - const size = this.components.renderer.getSize(); - const aspect = size.x / size.y; - this._orthoCamera.left = (-this._frustumSize * aspect) / 2; - this._orthoCamera.right = (this._frustumSize * aspect) / 2; - this._orthoCamera.top = this._frustumSize / 2; - this._orthoCamera.bottom = -this._frustumSize / 2; - this._orthoCamera.updateProjectionMatrix(); - } - toggleEvents(active) { - const modes = Object.values(this._navigationModes); - for (const mode of modes) { - if (active) { - mode.projectionChanged.on(this.projectionChanged.trigger); - } - else { - mode.projectionChanged.reset(); - } - } - } -} + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); -// Gets the plane information (ax + by + cz = d) of each face, where: -// - (a, b, c) is the normal vector of the plane -// - d is the signed distance to the origin -function getPlaneDistanceMaterial() { - return new THREE$1.ShaderMaterial({ - side: 2, - clipping: true, - uniforms: {}, - vertexShader: ` - varying vec4 vColor; - - #include - - void main() { - #include - - vec4 absPosition = vec4(position, 1.0); - vec3 trueNormal = normal; - - #ifdef USE_INSTANCING - absPosition = instanceMatrix * absPosition; - trueNormal = (instanceMatrix * vec4(normal, 0.)).xyz; - #endif - - absPosition = modelMatrix * absPosition; - trueNormal = (normalize(modelMatrix * vec4(trueNormal, 0.))).xyz; - - vec3 planePosition = absPosition.xyz / 40.; - float d = abs(dot(trueNormal, planePosition)); - vColor = vec4(abs(trueNormal), d); - gl_Position = projectionMatrix * viewMatrix * absPosition; - - #include - #include - } - `, - fragmentShader: ` - varying vec4 vColor; - - #include - - void main() { - #include - gl_FragColor = vColor; - } - `, - }); -} + } -// Gets the plane information (ax + by + cz = d) of each face, where: -// - (a, b, c) is the normal vector of the plane -// - d is the signed distance to the origin -function getProjectedNormalMaterial() { - return new THREE$1.ShaderMaterial({ - side: 2, - clipping: true, - uniforms: {}, - vertexShader: ` - varying vec3 vCameraPosition; - varying vec3 vPosition; - varying vec3 vNormal; - - #include - - void main() { - #include - - vec4 absPosition = vec4(position, 1.0); - vNormal = normal; - - #ifdef USE_INSTANCING - absPosition = instanceMatrix * absPosition; - vNormal = (instanceMatrix * vec4(normal, 0.)).xyz; - #endif - - absPosition = modelMatrix * absPosition; - vNormal = (normalize(modelMatrix * vec4(vNormal, 0.))).xyz; - - gl_Position = projectionMatrix * viewMatrix * absPosition; - - vCameraPosition = cameraPosition; - vPosition = absPosition.xyz; - - #include - #include - } - `, - fragmentShader: ` - varying vec3 vCameraPosition; - varying vec3 vPosition; - varying vec3 vNormal; - - #include - - void main() { - #include - vec3 cameraPixelVec = normalize(vCameraPosition - vPosition); - float difference = abs(dot(vNormal, cameraPixelVec)); - - // This achieves a double gloss effect: when the surface is perpendicular and when it's parallel - difference = abs((difference * 2.) - 1.); - - gl_FragColor = vec4(difference, difference, difference, 1.); - } - `, - }); -} + if ( image.data !== undefined ) { // THREE.DataTexture -// Follows the structure of -// https://github.com/mrdoob/three.js/blob/master/examples/jsm/postprocessing/OutlinePass.js -class CustomEffectsPass extends Pass { - get lineColor() { - return this._lineColor; - } - set lineColor(lineColor) { - this._lineColor = lineColor; - const material = this.fsQuad.material; - material.uniforms.lineColor.value.set(lineColor); - } - get tolerance() { - return this._tolerance; - } - set tolerance(value) { - this._tolerance = value; - const material = this.fsQuad.material; - material.uniforms.tolerance.value = value; - } - get opacity() { - return this._opacity; - } - set opacity(value) { - this._opacity = value; - const material = this.fsQuad.material; - material.uniforms.opacity.value = value; - } - get glossEnabled() { - return this._glossEnabled; - } - set glossEnabled(active) { - if (active === this._glossEnabled) - return; - this._glossEnabled = active; - const material = this.fsQuad.material; - material.uniforms.glossEnabled.value = active ? 1 : 0; - } - get glossExponent() { - return this._glossExponent; - } - set glossExponent(value) { - this._glossExponent = value; - const material = this.fsQuad.material; - material.uniforms.glossExponent.value = value; - } - get minGloss() { - return this._minGloss; - } - set minGloss(value) { - this._minGloss = value; - const material = this.fsQuad.material; - material.uniforms.minGloss.value = value; - } - get maxGloss() { - new THREE$1.MeshBasicMaterial().color.convertLinearToSRGB(); - return this._maxGloss; - } - set maxGloss(value) { - this._maxGloss = value; - const material = this.fsQuad.material; - material.uniforms.maxGloss.value = value; - } - get outlineEnabled() { - return this._outlineEnabled; - } - set outlineEnabled(active) { - if (active === this._outlineEnabled) - return; - this._outlineEnabled = active; - const material = this.fsQuad.material; - material.uniforms.outlineEnabled.value = active ? 1 : 0; - } - constructor(resolution, components, scene, camera) { - super(); - this.excludedMeshes = []; - this.outlinedMeshes = {}; - this._outlineScene = new THREE$1.Scene(); - this._outlineEnabled = false; - this._lineColor = 0x999999; - this._opacity = 0.4; - this._tolerance = 3; - this._glossEnabled = true; - this._glossExponent = 1.9; - this._minGloss = -0.1; - this._maxGloss = 0.1; - this._outlinesNeedsUpdate = false; - this.components = components; - this.renderScene = scene; - this.renderCamera = camera; - this.resolution = new THREE$1.Vector2(resolution.x, resolution.y); - this.fsQuad = new FullScreenQuad(); - this.fsQuad.material = this.createOutlinePostProcessMaterial(); - this.planeBuffer = this.newRenderTarget(); - this.glossBuffer = this.newRenderTarget(); - this.outlineBuffer = this.newRenderTarget(); - const normalMaterial = getPlaneDistanceMaterial(); - normalMaterial.clippingPlanes = components.renderer.clippingPlanes; - this.normalOverrideMaterial = normalMaterial; - const glossMaterial = getProjectedNormalMaterial(); - glossMaterial.clippingPlanes = components.renderer.clippingPlanes; - this.glossOverrideMaterial = glossMaterial; - } - async dispose() { - this.planeBuffer.dispose(); - this.glossBuffer.dispose(); - this.outlineBuffer.dispose(); - this.normalOverrideMaterial.dispose(); - this.glossOverrideMaterial.dispose(); - this.fsQuad.dispose(); - this.excludedMeshes = []; - this._outlineScene.children = []; - const disposer = await this.components.tools.get(Disposer); - for (const name in this.outlinedMeshes) { - const style = this.outlinedMeshes[name]; - for (const mesh of style.meshes) { - disposer.destroy(mesh, true, true); - } - style.material.dispose(); - } - } - setSize(width, height) { - this.planeBuffer.setSize(width, height); - this.glossBuffer.setSize(width, height); - this.outlineBuffer.setSize(width, height); - this.resolution.set(width, height); - const material = this.fsQuad.material; - material.uniforms.screenSize.value.set(this.resolution.x, this.resolution.y, 1 / this.resolution.x, 1 / this.resolution.y); - } - render(renderer, writeBuffer, readBuffer) { - // Turn off writing to the depth buffer - // because we need to read from it in the subsequent passes. - const depthBufferValue = writeBuffer.depthBuffer; - writeBuffer.depthBuffer = false; - // 1. Re-render the scene to capture all normals in a texture. - const previousOverrideMaterial = this.renderScene.overrideMaterial; - const previousBackground = this.renderScene.background; - this.renderScene.background = null; - for (const mesh of this.excludedMeshes) { - mesh.visible = false; - } - // Render normal pass - renderer.setRenderTarget(this.planeBuffer); - this.renderScene.overrideMaterial = this.normalOverrideMaterial; - renderer.render(this.renderScene, this.renderCamera); - // Render gloss pass - if (this._glossEnabled) { - renderer.setRenderTarget(this.glossBuffer); - this.renderScene.overrideMaterial = this.glossOverrideMaterial; - renderer.render(this.renderScene, this.renderCamera); - } - this.renderScene.overrideMaterial = previousOverrideMaterial; - // Render outline pass - if (this._outlineEnabled) { - let outlinedMeshesFound = false; - for (const name in this.outlinedMeshes) { - const style = this.outlinedMeshes[name]; - for (const mesh of style.meshes) { - outlinedMeshesFound = true; - mesh.userData.materialPreOutline = mesh.material; - mesh.material = style.material; - mesh.userData.groupsPreOutline = mesh.geometry.groups; - mesh.geometry.groups = []; - if (mesh instanceof THREE$1.InstancedMesh) { - mesh.userData.colorPreOutline = mesh.instanceColor; - mesh.instanceColor = null; - } - mesh.userData.parentPreOutline = mesh.parent; - this._outlineScene.add(mesh); - } - } - // This way, when there are no outlines meshes, it clears the outlines buffer only once - // and then skips this render - if (outlinedMeshesFound || this._outlinesNeedsUpdate) { - renderer.setRenderTarget(this.outlineBuffer); - renderer.render(this._outlineScene, this.renderCamera); - this._outlinesNeedsUpdate = outlinedMeshesFound; - } - for (const name in this.outlinedMeshes) { - const style = this.outlinedMeshes[name]; - for (const mesh of style.meshes) { - mesh.material = mesh.userData.materialPreOutline; - mesh.geometry.groups = mesh.userData.groupsPreOutline; - if (mesh instanceof THREE$1.InstancedMesh) { - mesh.instanceColor = mesh.userData.colorPreOutline; - } - if (mesh.userData.parentPreOutline) { - mesh.userData.parentPreOutline.add(mesh); - } - mesh.userData.materialPreOutline = undefined; - mesh.userData.groupsPreOutline = undefined; - mesh.userData.colorPreOutline = undefined; - mesh.userData.parentPreOutline = undefined; - } - } - } - for (const mesh of this.excludedMeshes) { - mesh.visible = true; - } - this.renderScene.background = previousBackground; - const material = this.fsQuad.material; - material.uniforms.planeBuffer.value = this.planeBuffer.texture; - material.uniforms.glossBuffer.value = this.glossBuffer.texture; - material.uniforms.outlineBuffer.value = this.outlineBuffer.texture; - material.uniforms.sceneColorBuffer.value = readBuffer.texture; - if (this.renderToScreen) { - // If this is the last effect, then renderToScreen is true. - // So we should render to the screen by setting target null - // Otherwise, just render into the writeBuffer that the next effect will use as its read buffer. - renderer.setRenderTarget(null); - this.fsQuad.render(renderer); - } - else { - renderer.setRenderTarget(writeBuffer); - this.fsQuad.render(renderer); - } - // Reset the depthBuffer value so we continue writing to it in the next render. - writeBuffer.depthBuffer = depthBufferValue; - } - get vertexShader() { - return ` - varying vec2 vUv; - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } - `; - } - get fragmentShader() { - return ` - uniform sampler2D sceneColorBuffer; - uniform sampler2D planeBuffer; - uniform sampler2D glossBuffer; - uniform sampler2D outlineBuffer; - uniform vec4 screenSize; - uniform vec3 lineColor; - - uniform float outlineEnabled; - - uniform int width; - uniform float opacity; - uniform float tolerance; - uniform float glossExponent; - uniform float minGloss; - uniform float maxGloss; - uniform float glossEnabled; + if ( format !== RGBAFormat ) { + + console.error( 'GLTFExporter: Only RGBAFormat is supported.' ); + + } + + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + + } + + const data = new Uint8ClampedArray( image.height * image.width * 4 ); + + for ( let i = 0; i < data.length; i += 4 ) { + + data[ i + 0 ] = image.data[ i + 0 ]; + data[ i + 1 ] = image.data[ i + 1 ]; + data[ i + 2 ] = image.data[ i + 2 ]; + data[ i + 3 ] = image.data[ i + 3 ]; + + } + + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + + } else { + + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + + } + + if ( options.binary === true ) { + + pending.push( + + getToBlobPromise( canvas, mimeType ) + .then( blob => writer.processBufferViewImage( blob ) ) + .then( bufferViewIndex => { + + imageDef.bufferView = bufferViewIndex; + + } ) + + ); + + } else { + + if ( canvas.toDataURL !== undefined ) { + + imageDef.uri = canvas.toDataURL( mimeType ); + + } else { + + pending.push( + + getToBlobPromise( canvas, mimeType ) + .then( blob => new FileReader().readAsDataURL( blob ) ) + .then( dataURL => { + + imageDef.uri = dataURL; + + } ) + + ); + + } + + } + + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; + + } else { + + throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); + + } + + } + + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler( map ) { + + const json = this.json; + + if ( ! json.samplers ) json.samplers = []; + + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; + + return json.samplers.push( samplerDef ) - 1; + + } + + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture( map ) { + + const cache = this.cache; + const json = this.json; + + if ( cache.textures.has( map ) ) return cache.textures.get( map ); + + if ( ! json.textures ) json.textures = []; + + let mimeType = map.userData.mimeType; + + if ( mimeType === 'image/webp' ) mimeType = 'image/png'; + + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY, mimeType ) + }; + + if ( map.name ) textureDef.name = map.name; + + this._invokeAll( function ( ext ) { + + ext.writeTexture && ext.writeTexture( map, textureDef ); + + } ); + + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; + + } + + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial( material ) { + + const cache = this.cache; + const json = this.json; + + if ( cache.materials.has( material ) ) return cache.materials.get( material ); + + if ( material.isShaderMaterial ) { + + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; + + } - varying vec2 vUv; + if ( ! json.materials ) json.materials = []; - vec4 getValue(sampler2D buffer, int x, int y) { - return texture2D(buffer, vUv + screenSize.zw * vec2(x, y)); - } + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} }; - float normalDiff(vec3 normal1, vec3 normal2) { - return ((dot(normal1, normal2) - 1.) * -1.) / 2.; - } + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - // Returns 0 if it's background, 1 if it's not - float getIsBackground(vec3 normal) { - float background = 1.0; - background *= step(normal.x, 0.); - background *= step(normal.y, 0.); - background *= step(normal.z, 0.); - background = (background - 1.) * -1.; - return background; - } + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - void main() { - - vec4 sceneColor = getValue(sceneColorBuffer, 0, 0); - vec3 normSceneColor = normalize(sceneColor.rgb); - - vec4 plane = getValue(planeBuffer, 0, 0); - vec3 normal = plane.xyz; - float distance = plane.w; - - vec3 normalTop = getValue(planeBuffer, 0, width).rgb; - vec3 normalBottom = getValue(planeBuffer, 0, -width).rgb; - vec3 normalRight = getValue(planeBuffer, width, 0).rgb; - vec3 normalLeft = getValue(planeBuffer, -width, 0).rgb; - vec3 normalTopRight = getValue(planeBuffer, width, width).rgb; - vec3 normalTopLeft = getValue(planeBuffer, -width, width).rgb; - vec3 normalBottomRight = getValue(planeBuffer, width, -width).rgb; - vec3 normalBottomLeft = getValue(planeBuffer, -width, -width).rgb; - - float distanceTop = getValue(planeBuffer, 0, width).a; - float distanceBottom = getValue(planeBuffer, 0, -width).a; - float distanceRight = getValue(planeBuffer, width, 0).a; - float distanceLeft = getValue(planeBuffer, -width, 0).a; - float distanceTopRight = getValue(planeBuffer, width, width).a; - float distanceTopLeft = getValue(planeBuffer, -width, width).a; - float distanceBottomRight = getValue(planeBuffer, width, -width).a; - float distanceBottomLeft = getValue(planeBuffer, -width, -width).a; - - vec3 sceneColorTop = normalize(getValue(sceneColorBuffer, 1, 0).rgb); - vec3 sceneColorBottom = normalize(getValue(sceneColorBuffer, -1, 0).rgb); - vec3 sceneColorLeft = normalize(getValue(sceneColorBuffer, 0, -1).rgb); - vec3 sceneColorRight = normalize(getValue(sceneColorBuffer, 0, 1).rgb); - vec3 sceneColorTopRight = normalize(getValue(sceneColorBuffer, 1, 1).rgb); - vec3 sceneColorBottomRight = normalize(getValue(sceneColorBuffer, -1, 1).rgb); - vec3 sceneColorTopLeft = normalize(getValue(sceneColorBuffer, 1, 1).rgb); - vec3 sceneColorBottomLeft = normalize(getValue(sceneColorBuffer, -1, 1).rgb); + } - // Checks if the planes of this texel and the neighbour texels are different + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat( [ material.opacity ] ); - float planeDiff = 0.0; + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - planeDiff += step(0.001, normalDiff(normal, normalTop)); - planeDiff += step(0.001, normalDiff(normal, normalBottom)); - planeDiff += step(0.001, normalDiff(normal, normalLeft)); - planeDiff += step(0.001, normalDiff(normal, normalRight)); - planeDiff += step(0.001, normalDiff(normal, normalTopRight)); - planeDiff += step(0.001, normalDiff(normal, normalTopLeft)); - planeDiff += step(0.001, normalDiff(normal, normalBottomRight)); - planeDiff += step(0.001, normalDiff(normal, normalBottomLeft)); - - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTop)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottom)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorLeft)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorRight)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTopRight)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTopLeft)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottomRight)); - planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottomLeft)); + materialDef.pbrMetallicRoughness.baseColorFactor = color; - planeDiff += step(0.001, abs(distance - distanceTop)); - planeDiff += step(0.001, abs(distance - distanceBottom)); - planeDiff += step(0.001, abs(distance - distanceLeft)); - planeDiff += step(0.001, abs(distance - distanceRight)); - planeDiff += step(0.001, abs(distance - distanceTopRight)); - planeDiff += step(0.001, abs(distance - distanceTopLeft)); - planeDiff += step(0.001, abs(distance - distanceBottomRight)); - planeDiff += step(0.001, abs(distance - distanceBottomLeft)); + } - // Add extra background outline + if ( material.isMeshStandardMaterial ) { - int width2 = width + 1; - vec3 normalTop2 = getValue(planeBuffer, 0, width2).rgb; - vec3 normalBottom2 = getValue(planeBuffer, 0, -width2).rgb; - vec3 normalRight2 = getValue(planeBuffer, width2, 0).rgb; - vec3 normalLeft2 = getValue(planeBuffer, -width2, 0).rgb; - vec3 normalTopRight2 = getValue(planeBuffer, width2, width2).rgb; - vec3 normalTopLeft2 = getValue(planeBuffer, -width2, width2).rgb; - vec3 normalBottomRight2 = getValue(planeBuffer, width2, -width2).rgb; - vec3 normalBottomLeft2 = getValue(planeBuffer, -width2, -width2).rgb; + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - planeDiff += -(getIsBackground(normalTop2) - 1.); - planeDiff += -(getIsBackground(normalBottom2) - 1.); - planeDiff += -(getIsBackground(normalRight2) - 1.); - planeDiff += -(getIsBackground(normalLeft2) - 1.); - planeDiff += -(getIsBackground(normalTopRight2) - 1.); - planeDiff += -(getIsBackground(normalBottomRight2) - 1.); - planeDiff += -(getIsBackground(normalBottomRight2) - 1.); - planeDiff += -(getIsBackground(normalBottomLeft2) - 1.); + } else { - // Tolerance sets the minimum amount of differences to consider - // this texel an edge + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; - float line = step(tolerance, planeDiff); + } - // Exclude background and apply opacity + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { - float background = getIsBackground(normal); - line *= background; - line *= opacity; - - // Add gloss - - vec3 gloss = getValue(glossBuffer, 0, 0).xyz; - float diffGloss = abs(maxGloss - minGloss); - vec3 glossExpVector = vec3(glossExponent,glossExponent,glossExponent); - gloss = min(pow(gloss, glossExpVector), vec3(1.,1.,1.)); - gloss *= diffGloss; - gloss += minGloss; - vec4 glossedColor = sceneColor + vec4(gloss, 1.) * glossEnabled; - - vec4 corrected = mix(sceneColor, glossedColor, background); - - // Draw lines - - corrected = mix(corrected, vec4(lineColor, 1.), line); - - // Add outline - - vec4 outlinePreview =getValue(outlineBuffer, 0, 0); - float outlineColorCorrection = 1. / max(0.2, outlinePreview.a); - vec3 outlineColor = outlinePreview.rgb * outlineColorCorrection; - - // thickness between 10 and 2, opacity between 1 and 0.2 - int outlineThickness = int(outlinePreview.a * 10.); - - float outlineDiff = 0.; - - outlineDiff += step(0.1, getValue(outlineBuffer, 0, 0).a); - outlineDiff += step(0.1, getValue(outlineBuffer, 1, 0).a); - outlineDiff += step(0.1, getValue(outlineBuffer, -1, 0).a); - outlineDiff += step(0.1, getValue(outlineBuffer, 0, -1).a); - outlineDiff += step(0.1, getValue(outlineBuffer, 0, 1).a); - outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, 0).a); - outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, 0).a); - outlineDiff += step(0.1, getValue(outlineBuffer, 0, -outlineThickness).a); - outlineDiff += step(0.1, getValue(outlineBuffer, 0, outlineThickness).a); - outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, outlineThickness).a); - outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, outlineThickness).a); - outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, -outlineThickness).a); - outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, -outlineThickness).a); - - float outLine = step(4., outlineDiff) * step(outlineDiff, 12.) * outlineEnabled; - corrected = mix(corrected, vec4(outlineColor, 1.), outLine); - - gl_FragColor = corrected; - } - `; - } - createOutlinePostProcessMaterial() { - return new THREE$1.ShaderMaterial({ - uniforms: { - opacity: { value: this._opacity }, - debugVisualize: { value: 0 }, - sceneColorBuffer: { value: null }, - tolerance: { value: this._tolerance }, - planeBuffer: { value: null }, - glossBuffer: { value: null }, - outlineBuffer: { value: null }, - glossEnabled: { value: 1 }, - minGloss: { value: this._minGloss }, - maxGloss: { value: this._maxGloss }, - outlineEnabled: { value: 0 }, - glossExponent: { value: this._glossExponent }, - width: { value: 1 }, - lineColor: { value: new THREE$1.Color(this._lineColor) }, - screenSize: { - value: new THREE$1.Vector4(this.resolution.x, this.resolution.y, 1 / this.resolution.x, 1 / this.resolution.y), - }, - }, - vertexShader: this.vertexShader, - fragmentShader: this.fragmentShader, - }); - } - newRenderTarget() { - const planeBuffer = new THREE$1.WebGLRenderTarget(this.resolution.x, this.resolution.y); - planeBuffer.texture.colorSpace = "srgb-linear"; - planeBuffer.texture.format = THREE$1.RGBAFormat; - planeBuffer.texture.type = THREE$1.HalfFloatType; - planeBuffer.texture.minFilter = THREE$1.NearestFilter; - planeBuffer.texture.magFilter = THREE$1.NearestFilter; - planeBuffer.texture.generateMipmaps = false; - planeBuffer.stencilBuffer = false; - return planeBuffer; - } -} + const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); -// source: https://discourse.threejs.org/t/how-to-render-full-outlines-as-a-post-process-tutorial/22674 -class Postproduction { - get basePass() { - if (!this._basePass) { - throw new Error("Custom effects not initialized!"); - } - return this._basePass; - } - get gammaPass() { - if (!this._gammaPass) { - throw new Error("Custom effects not initialized!"); - } - return this._gammaPass; - } - get customEffects() { - if (!this._customEffects) { - throw new Error("Custom effects not initialized!"); - } - return this._customEffects; - } - get n8ao() { - if (!this._n8ao) { - throw new Error("Custom effects not initialized!"); - } - return this._n8ao; - } - get enabled() { - return this._enabled; - } - set enabled(active) { - if (!this._initialized) { - this.initialize(); - } - this._enabled = active; - } - get settings() { - return { ...this._settings }; - } - constructor(components, renderer) { - this.components = components; - this.renderer = renderer; - this.excludedItems = new Set(); - this.overrideClippingPlanes = false; - this._enabled = false; - this._initialized = false; - this._settings = { - gamma: true, - custom: true, - ao: false, - }; - this._renderTarget = new THREE$1.WebGLRenderTarget(window.innerWidth, window.innerHeight); - this._renderTarget.texture.colorSpace = "srgb-linear"; - this.composer = new EffectComposer(this.renderer, this._renderTarget); - this.composer.setSize(window.innerWidth, window.innerHeight); - } - async dispose() { - var _a, _b, _c, _d; - this._renderTarget.dispose(); - (_a = this._depthTexture) === null || _a === void 0 ? void 0 : _a.dispose(); - await ((_b = this._customEffects) === null || _b === void 0 ? void 0 : _b.dispose()); - (_c = this._gammaPass) === null || _c === void 0 ? void 0 : _c.dispose(); - (_d = this._n8ao) === null || _d === void 0 ? void 0 : _d.dispose(); - this.excludedItems.clear(); - } - setPasses(settings) { - // This check can prevent some bugs - let settingsChanged = false; - for (const name in settings) { - const key = name; - if (this.settings[key] !== settings[key]) { - settingsChanged = true; - break; - } - } - if (!settingsChanged) { - return; - } - for (const name in settings) { - const key = name; - if (this._settings[key] !== undefined) { - this._settings[key] = settings[key]; - } - } - this.updatePasses(); - } - setSize(width, height) { - if (this._initialized) { - this.composer.setSize(width, height); - this.basePass.setSize(width, height); - this.n8ao.setSize(width, height); - this.customEffects.setSize(width, height); - this.gammaPass.setSize(width, height); - } - } - update() { - if (!this._enabled) - return; - this.composer.render(); - } - updateCamera() { - const camera = this.components.camera.get(); - if (this._n8ao) { - this._n8ao.camera = camera; - } - if (this._customEffects) { - this._customEffects.renderCamera = camera; - } - if (this._basePass) { - this._basePass.camera = camera; - } - } - initialize() { - const scene = this.overrideScene || this.components.scene.get(); - const camera = this.overrideCamera || this.components.camera.get(); - if (!scene || !camera) - return; - if (this.components.camera instanceof OrthoPerspectiveCamera) { - this.components.camera.projectionChanged.add(() => { - this.updateCamera(); - }); - } - const renderer = this.components.renderer; - if (!this.overrideClippingPlanes) { - this.renderer.clippingPlanes = renderer.clippingPlanes; - } - this.renderer.outputColorSpace = "srgb"; - this.renderer.toneMapping = THREE$1.NoToneMapping; - this.newBasePass(scene, camera); - this.newSaoPass(scene, camera); - this.newGammaPass(); - this.newCustomPass(scene, camera); - this._initialized = true; - this.updatePasses(); - } - updateProjection(camera) { - this.composer.passes.forEach((pass) => { - // @ts-ignore - pass.camera = camera; - }); - this.update(); - } - updatePasses() { - for (const pass of this.composer.passes) { - this.composer.removePass(pass); - } - if (this._basePass) { - this.composer.addPass(this.basePass); - } - if (this._settings.gamma) { - this.composer.addPass(this.gammaPass); - } - if (this._settings.ao) { - this.composer.addPass(this.n8ao); - } - if (this._settings.custom) { - this.composer.addPass(this.customEffects); - } - } - newCustomPass(scene, camera) { - this._customEffects = new CustomEffectsPass(new THREE$1.Vector2(window.innerWidth, window.innerHeight), this.components, scene, camera); - } - newGammaPass() { - this._gammaPass = new ShaderPass(GammaCorrectionShader); - } - newSaoPass(scene, camera) { - const { width, height } = this.components.renderer.getSize(); - this._n8ao = new $05f6997e4b65da14$export$2d57db20b5eb5e0a(scene, camera, width, height); - // this.composer.addPass(this.n8ao); - const { configuration } = this._n8ao; - configuration.aoSamples = 16; - configuration.denoiseSamples = 1; - configuration.denoiseRadius = 13; - configuration.aoRadius = 1; - configuration.distanceFalloff = 4; - configuration.aoRadius = 1; - configuration.intensity = 4; - configuration.halfRes = true; - configuration.color = new THREE$1.Color().setHex(0xcccccc, "srgb-linear"); - } - newBasePass(scene, camera) { - this._basePass = new RenderPass(scene, camera); - } -} + const metalRoughMapDef = { + index: this.processTexture( metalRoughTexture ), + channel: metalRoughTexture.channel + }; + this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + + } + + // pbrMetallicRoughness.baseColorTexture + if ( material.map ) { + + const baseColorMapDef = { + index: this.processTexture( material.map ), + texCoord: material.map.channel + }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + + } + + if ( material.emissive ) { + + const emissive = material.emissive; + const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); + + if ( maxEmissiveComponent > 0 ) { + + materialDef.emissiveFactor = material.emissive.toArray(); + + } + + // emissiveTexture + if ( material.emissiveMap ) { + + const emissiveMapDef = { + index: this.processTexture( material.emissiveMap ), + texCoord: material.emissiveMap.channel + }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; + + } + + } + + // normalTexture + if ( material.normalMap ) { + + const normalMapDef = { + index: this.processTexture( material.normalMap ), + texCoord: material.normalMap.channel + }; + + if ( material.normalScale && material.normalScale.x !== 1 ) { + + // glTF normal scale is univariate. Ignore `y`, which may be flipped. + // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + normalMapDef.scale = material.normalScale.x; + + } + + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; + + } + + // occlusionTexture + if ( material.aoMap ) { + + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: material.aoMap.channel + }; + + if ( material.aoMapIntensity !== 1.0 ) { + + occlusionMapDef.strength = material.aoMapIntensity; + + } + + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; + + } + + // alphaMode + if ( material.transparent ) { + + materialDef.alphaMode = 'BLEND'; + + } else { + + if ( material.alphaTest > 0.0 ) { + + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; + + } + + } + + // doubleSided + if ( material.side === DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; + + this.serializeUserData( material, materialDef ); + + this._invokeAll( function ( ext ) { + + ext.writeMaterial && ext.writeMaterial( material, materialDef ); + + } ); + + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; + + } -/** - * Renderer that uses efficient postproduction effects (e.g. Ambient Occlusion). - */ -class PostproductionRenderer extends SimpleRenderer { - constructor(components, container, parameters) { - super(components, container, parameters); - this.postproduction = new Postproduction(components, this._renderer); - this.setPostproductionSize(); - this.onResize.add((size) => this.resizePostproduction(size)); - } - /** {@link Updateable.update} */ - async update() { - if (!this.enabled) - return; - await this.onBeforeUpdate.trigger(); - const scene = this.overrideScene || this.components.scene.get(); - const camera = this.overrideCamera || this.components.camera.get(); - if (!scene || !camera) - return; - if (this.postproduction.enabled) { - this.postproduction.composer.render(); - } - else { - this._renderer.render(scene, camera); - } - this._renderer2D.render(scene, camera); - await this.onAfterUpdate.trigger(); - } - /** {@link Disposable.dispose}. */ - async dispose() { - await super.dispose(); - await this.postproduction.dispose(); - } - resizePostproduction(size) { - if (this.postproduction) { - this.setPostproductionSize(size); - } - } - setPostproductionSize(size) { - if (!this.container) - return; - const width = size ? size.x : this.container.clientWidth; - const height = size ? size.y : this.container.clientHeight; - this.postproduction.setSize(width, height); - } -} + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh( mesh ) { -/** - * An infinite lightweight 2D grid that can be used for any - * kind of 2d viewports. - */ -class Infinite2dGrid { - constructor(camera, container) { - this.numbers = new THREE$1.Group(); - this.maxRegenerateRetrys = 4; - this.gridsFactor = 5; - this.scaleX = 1; - this.scaleY = 1; - this._group = new THREE$1.Group(); - this._frustum = new THREE$1.Frustum(); - this._frustumMat = new THREE$1.Matrix4(); - this._regenerateDelay = 200; - this._regenerateCounter = 0; - this._camera = camera; - this._container = container; - const main = this.newGrid(0x222222, -1); - const secondary = this.newGrid(0x111111, -2); - this.grids = { main, secondary }; - this._group.add(secondary, main, this.numbers); - } - get() { - return this._group; - } - dispose() { - const { main, secondary } = this.grids; - main.removeFromParent(); - secondary.removeFromParent(); - main.geometry.dispose(); - const mMat = main.material; - mMat.dispose(); - secondary.geometry.dispose(); - const sMat = secondary.material; - sMat.dispose(); - } - regenerate() { - const isReady = this.isGridReady(); - if (!isReady) { - this._regenerateCounter++; - if (this._regenerateCounter > this.maxRegenerateRetrys) { - throw new Error("Grid could not be regenerated"); - } - setTimeout(() => this.regenerate, this._regenerateDelay); - return; - } - this._regenerateCounter = 0; - const matrix = this._frustumMat.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse); - this._frustum.setFromProjectionMatrix(matrix); - // Step 1: find out the distance of the visible area of the 2D scene - // and the translation pixel / 3d unit - const { planes } = this._frustum; - const right = planes[0].constant * -planes[0].normal.x; - const left = planes[1].constant * -planes[1].normal.x; - const bottom = planes[2].constant * -planes[2].normal.y; - const top = planes[3].constant * -planes[3].normal.y; - const horizontalDistance = Math.abs(right - left); - const verticalDistance = Math.abs(top - bottom); - const { clientWidth, clientHeight } = this._container; - const maxPixelDist = Math.max(clientWidth, clientHeight); - const maxUnit3dDist = Math.max(horizontalDistance, verticalDistance); - const unit3dPixelRel = maxUnit3dDist / maxPixelDist; - // Step 2: find out its order of magnitude - const magnitudeX = Math.ceil(Math.log10(horizontalDistance / this.scaleX)); - const magnitudeY = Math.ceil(Math.log10(verticalDistance / this.scaleY)); - // Step 3: represent main grid - const sDistanceHor = 10 ** (magnitudeX - 2) * this.scaleX; - const sDistanceVert = 10 ** (magnitudeY - 2) * this.scaleY; - const mDistanceHor = sDistanceHor * this.gridsFactor; - const mDistanceVert = sDistanceVert * this.gridsFactor; - const mainGridCountVert = Math.ceil(verticalDistance / mDistanceVert); - const mainGridCountHor = Math.ceil(horizontalDistance / mDistanceHor); - const secondaryGridCountVert = Math.ceil(verticalDistance / sDistanceVert); - const secondaryGridCountHor = Math.ceil(horizontalDistance / sDistanceHor); - // Step 4: find out position of first lines - const sTrueLeft = sDistanceHor * Math.ceil(left / sDistanceHor); - const sTrueBottom = sDistanceVert * Math.ceil(bottom / sDistanceVert); - const mTrueLeft = mDistanceHor * Math.ceil(left / mDistanceHor); - const mTrueBottom = mDistanceVert * Math.ceil(bottom / mDistanceVert); - // Step 5: draw lines and texts - const numbers = [...this.numbers.children]; - for (const number of numbers) { - number.removeFromParent(); - } - this.numbers.children = []; - const mPoints = []; - for (let i = 0; i < mainGridCountHor; i++) { - const offset = mTrueLeft + i * mDistanceHor; - mPoints.push(offset, top, 0, offset, bottom, 0); - const sign = this.newNumber(offset / this.scaleX); - const textOffsetPixels = 12; - const textOffset = textOffsetPixels * unit3dPixelRel; - sign.position.set(offset, bottom + textOffset, 0); - } - for (let i = 0; i < mainGridCountVert; i++) { - const offset = mTrueBottom + i * mDistanceVert; - mPoints.push(left, offset, 0, right, offset, 0); - const sign = this.newNumber(offset / this.scaleY); - let textOffsetPixels = 12; - if (sign.element.textContent) { - textOffsetPixels += 4 * sign.element.textContent.length; - } - const textOffset = textOffsetPixels * unit3dPixelRel; - sign.position.set(left + textOffset, offset, 0); - } - const sPoints = []; - for (let i = 0; i < secondaryGridCountHor; i++) { - const offset = sTrueLeft + i * sDistanceHor; - sPoints.push(offset, top, 0, offset, bottom, 0); - } - for (let i = 0; i < secondaryGridCountVert; i++) { - const offset = sTrueBottom + i * sDistanceVert; - sPoints.push(left, offset, 0, right, offset, 0); - } - const mIndices = []; - const sIndices = []; - this.fillIndices(mPoints, mIndices); - this.fillIndices(sPoints, sIndices); - const mBuffer = new THREE$1.BufferAttribute(new Float32Array(mPoints), 3); - const sBuffer = new THREE$1.BufferAttribute(new Float32Array(sPoints), 3); - const { main, secondary } = this.grids; - main.geometry.setAttribute("position", mBuffer); - main.geometry.setIndex(mIndices); - secondary.geometry.setAttribute("position", sBuffer); - secondary.geometry.setIndex(sIndices); - } - fillIndices(points, indices) { - for (let i = 0; i < points.length / 2 - 1; i += 2) { - indices.push(i, i + 1); - } - } - newNumber(offset) { - const text = document.createElement("div"); - text.textContent = `${offset}`; - if (text.textContent.length > 6) { - text.textContent = text.textContent.slice(0, 6); - } - text.style.height = "24px"; - text.style.fontSize = "12px"; - const sign = new CSS2DObject(text); - this.numbers.add(sign); - return sign; - } - newGrid(color, renderOrder) { - const geometry = new THREE$1.BufferGeometry(); - const material = new THREE$1.LineBasicMaterial({ color }); - const grid = new THREE$1.LineSegments(geometry, material); - grid.frustumCulled = false; - grid.renderOrder = renderOrder; - return grid; - } - isGridReady() { - const nums = this._camera.projectionMatrix.elements; - for (let i = 0; i < nums.length; i++) { - const num = nums[i]; - if (Number.isNaN(num)) { - return false; - } - } - return true; - } -} + const cache = this.cache; + const json = this.json; -// TODO: Make a scene manager as a Tool (so that it as an UUID) -/** - * A simple floating 2D scene that you can use to easily draw 2D graphics - * with all the power of Three.js. - */ -class Simple2DScene extends Component { - get scaleX() { - return this._scaleX; - } - set scaleX(value) { - this._scaleX = value; - this._root.scale.x = value; - this.grid.scaleX = value; - this.grid.regenerate(); - } - get scaleY() { - return this._scaleY; - } - set scaleY(value) { - this._scaleY = value; - this._root.scale.y = value; - this.grid.scaleY = value; - this.grid.regenerate(); - } - constructor(components, postproduction = false) { - super(components); - /** {@link Updateable.onAfterUpdate} */ - this.onAfterUpdate = new Event(); - /** {@link Updateable.onBeforeUpdate} */ - this.onBeforeUpdate = new Event(); - /** {@link Resizeable.onResize} */ - this.onResize = new Event(); - /** {@link Component.enabled} */ - this.enabled = true; - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - /** {@link UI.uiElement} */ - this.uiElement = new UIElement(); - this._scaleX = 1; - this._scaleY = 1; - this._root = new THREE$1.Group(); - this._size = new THREE$1.Vector2(); - this._frustumSize = 50; - /** {@link Resizeable.resize} */ - this.resize = () => { - const { height, width } = this._size; - const aspect = width / height; - this.camera.left = (-this._frustumSize * aspect) / 2; - this.camera.right = (this._frustumSize * aspect) / 2; - this.camera.top = this._frustumSize / 2; - this.camera.bottom = -this._frustumSize / 2; - this.camera.updateProjectionMatrix(); - this.camera.updateProjectionMatrix(); - this.renderer.resize(this._size); - }; - if (!components.uiEnabled) { - throw new Error("The Simple2DScene component needs to use UI elements (TODO: Decouple from them)."); - } - const container = new SimpleUIComponent(components); - container.domElement.classList.add("relative"); - this.uiElement.set({ container }); - this.scene = new THREE$1.Scene(); - this._size.set(window.innerWidth, window.innerHeight); - const { width, height } = this._size; - // Creates the camera (point of view of the user) - this.camera = new THREE$1.OrthographicCamera(75, width / height); - this.scene.add(this.camera); - this.camera.position.z = 10; - const domContainer = container.domElement; - this.scene.add(this._root); - this.grid = new Infinite2dGrid(this.camera, domContainer); - const gridObject = this.grid.get(); - this.scene.add(gridObject); - if (postproduction) { - this.renderer = new PostproductionRenderer(this.components, domContainer); - } - else { - this.renderer = new SimpleRenderer(this.components, domContainer); - } - const renderer = this.renderer.get(); - renderer.localClippingEnabled = false; - this.renderer.setupEvents(false); - this.renderer.overrideScene = this.scene; - this.renderer.overrideCamera = this.camera; - this.controls = new OrbitControls(this.camera, renderer.domElement); - this.controls.target.set(0, 0, 0); - this.controls.enableRotate = false; - this.controls.enableZoom = true; - this.controls.addEventListener("change", () => this.grid.regenerate()); - } - /** - * {@link Component.get} - * @returns the 2D scene. - */ - get() { - return this._root; - } - /** {@link Disposable.dispose} */ - async dispose() { - const disposer = this.components.tools.get(Disposer); - for (const child of this.scene.children) { - const item = child; - if (item instanceof THREE$1.Object3D) { - disposer.destroy(item); - } - } - await this.renderer.dispose(); - await this.uiElement.dispose(); - await this.onDisposed.trigger(Simple2DScene.uuid); - this.onDisposed.reset(); - } - /** {@link Updateable.update} */ - async update() { - await this.onBeforeUpdate.trigger(); - this.controls.update(); - await this.renderer.update(); - await this.onAfterUpdate.trigger(); - } - /** {@link Resizeable.getSize} */ - getSize() { - return new THREE$1.Vector2(this._size.width, this._size.height); - } - setSize(height, width) { - this._size.width = width; - this._size.height = height; - this.resize(); - } -} -Simple2DScene.uuid = "b48b7194-0f9a-43a4-a718-270b1522595f"; + const meshCacheKeyParts = [ mesh.geometry.uuid ]; -/** - * @param {Array} geometries - * @param {Boolean} useGroups - * @return {BufferGeometry} - */ -function mergeGeometries( geometries, useGroups = false ) { + if ( Array.isArray( mesh.material ) ) { + + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { + + meshCacheKeyParts.push( mesh.material[ i ].uuid ); + + } + + } else { + + meshCacheKeyParts.push( mesh.material.uuid ); + + } + + const meshCacheKey = meshCacheKeyParts.join( ':' ); + + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + + const geometry = mesh.geometry; + + let mode; + + // Use the correct mode + if ( mesh.isLineSegments ) { + + mode = WEBGL_CONSTANTS.LINES; + + } else if ( mesh.isLineLoop ) { + + mode = WEBGL_CONSTANTS.LINE_LOOP; + + } else if ( mesh.isLine ) { + + mode = WEBGL_CONSTANTS.LINE_STRIP; + + } else if ( mesh.isPoints ) { + + mode = WEBGL_CONSTANTS.POINTS; + + } else { + + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + + } + + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + uv1: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; + + const originalNormal = geometry.getAttribute( 'normal' ); + + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + + } + + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null; + + for ( let attributeName in geometry.attributes ) { + + // Ignore morph target attributes, which are exported later. + if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; + + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = + /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + + if ( cache.attributes.has( this.getUID( attribute ) ) ) { + + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; + + } + + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + const array = attribute.array; + + if ( attributeName === 'JOINTS_0' && + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { + + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute$1( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + + } + + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + + if ( accessor !== null ) { + + if ( ! attributeName.startsWith( '_' ) ) { + + this.detectMeshQuantization( attributeName, attribute ); + + } + + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); + + } + + } + + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) return null; + + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + + const weights = []; + const targetNames = []; + const reverseDictionary = {}; + + if ( mesh.morphTargetDictionary !== undefined ) { + + for ( const key in mesh.morphTargetDictionary ) { + + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + + } + + } + + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { + + const target = {}; + let warned = false; + + for ( const attributeName in geometry.morphAttributes ) { + + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. + + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - const isIndexed = geometries[ 0 ].index !== null; + if ( ! warned ) { - const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) ); - const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) ); + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; - const attributes = {}; - const morphAttributes = {}; + } - const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative; + continue; - const mergedGeometry = new BufferGeometry(); + } - let offset = 0; + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); - for ( let i = 0; i < geometries.length; ++ i ) { + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - const geometry = geometries[ i ]; - let attributesCount = 0; + const baseAttribute = geometry.attributes[ attributeName ]; - // ensure that all geometries are indexed, or none + if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - if ( isIndexed !== ( geometry.index !== null ) ) { + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); + continue; - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' ); - return null; + } - } + // Clones attribute not to override + const relativeAttribute = attribute.clone(); - // gather attributes, exit early if they're different + if ( ! geometry.morphTargetsRelative ) { - for ( const name in geometry.attributes ) { + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - if ( ! attributesUsed.has( name ) ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' ); - return null; + if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); + if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); + if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); + if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); - } + } - if ( attributes[ name ] === undefined ) attributes[ name ] = []; + } - attributes[ name ].push( geometry.attributes[ name ] ); + } - attributesCount ++; + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - } + } - // ensure geometries have the same number of attributes + targets.push( target ); - if ( attributesCount !== attributesUsed.size ) { + weights.push( mesh.morphTargetInfluences[ i ] ); - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' ); - return null; + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - } + } - // gather morph attributes, exit early if they're different + meshDef.weights = weights; - if ( morphTargetsRelative !== geometry.morphTargetsRelative ) { + if ( targetNames.length > 0 ) { - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' ); - return null; + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; + + } } - for ( const name in geometry.morphAttributes ) { + const isMultiMaterial = Array.isArray( mesh.material ); - if ( ! morphAttributesUsed.has( name ) ) { + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' ); - return null; + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - } + for ( let i = 0, il = groups.length; i < il; i ++ ) { - if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = []; + const primitive = { + mode: mode, + attributes: attributes, + }; - morphAttributes[ name ].push( geometry.morphAttributes[ name ] ); + this.serializeUserData( geometry, primitive ); - } + if ( targets.length > 0 ) primitive.targets = targets; - if ( useGroups ) { + if ( geometry.index !== null ) { - let count; + let cacheKey = this.getUID( geometry.index ); - if ( isIndexed ) { + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - count = geometry.index.count; + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - } else if ( geometry.attributes.position !== undefined ) { + } - count = geometry.attributes.position.count; + if ( cache.attributes.has( cacheKey ) ) { - } else { + primitive.indices = cache.attributes.get( cacheKey ); - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' ); - return null; + } else { + + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); + + } + + if ( primitive.indices === null ) delete primitive.indices; } - mergedGeometry.addGroup( offset, count, i ); + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); - offset += count; + if ( material !== null ) primitive.material = material; + + primitives.push( primitive ); } + meshDef.primitives = primitives; + + if ( ! json.meshes ) json.meshes = []; + + this._invokeAll( function ( ext ) { + + ext.writeMesh && ext.writeMesh( mesh, meshDef ); + + } ); + + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; + } - // merge indices + /** + * If a vertex attribute with a + * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) + * is used, it is checked whether it is a valid data type according to the + * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) + * extension. + * In this case the extension is automatically added to the list of used extensions. + * + * @param {string} attributeName + * @param {THREE.BufferAttribute} attribute + */ + detectMeshQuantization( attributeName, attribute ) { - if ( isIndexed ) { + if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; - let indexOffset = 0; - const mergedIndex = []; + let attrType = undefined; - for ( let i = 0; i < geometries.length; ++ i ) { + switch ( attribute.array.constructor ) { - const index = geometries[ i ].index; + case Int8Array: - for ( let j = 0; j < index.count; ++ j ) { + attrType = 'byte'; - mergedIndex.push( index.getX( j ) + indexOffset ); + break; - } + case Uint8Array: - indexOffset += geometries[ i ].attributes.position.count; + attrType = 'unsigned byte'; - } + break; - mergedGeometry.setIndex( mergedIndex ); + case Int16Array: - } + attrType = 'short'; - // merge attributes + break; - for ( const name in attributes ) { + case Uint16Array: - const mergedAttribute = mergeAttributes( attributes[ name ] ); + attrType = 'unsigned short'; - if ( ! mergedAttribute ) { + break; - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' ); - return null; + default: + + return; } - mergedGeometry.setAttribute( name, mergedAttribute ); + if ( attribute.normalized ) attrType += ' normalized'; - } + const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; - // merge morph attributes + if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { - for ( const name in morphAttributes ) { + this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; + this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; - const numMorphTargets = morphAttributes[ name ][ 0 ].length; + } - if ( numMorphTargets === 0 ) break; + } - mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}; - mergedGeometry.morphAttributes[ name ] = []; + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera( camera ) { - for ( let i = 0; i < numMorphTargets; ++ i ) { + const json = this.json; - const morphAttributesToMerge = []; + if ( ! json.cameras ) json.cameras = []; - for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) { + const isOrtho = camera.isOrthographicCamera; - morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] ); + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' + }; - } + if ( isOrtho ) { - const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge ); + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - if ( ! mergedMorphAttribute ) { + } else { - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' ); - return null; + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - } + } - mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute ); + // Question: Is saving "type" as name intentional? + if ( camera.name !== '' ) cameraDef.name = camera.type; - } + return json.cameras.push( cameraDef ) - 1; } - return mergedGeometry; + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation( clip, root ) { -} + const json = this.json; + const nodeMap = this.nodeMap; -/** - * @param {Array} attributes - * @return {BufferAttribute} - */ -function mergeAttributes( attributes ) { + if ( ! json.animations ) json.animations = []; - let TypedArray; - let itemSize; - let normalized; - let arrayLength = 0; + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - for ( let i = 0; i < attributes.length; ++ i ) { + const tracks = clip.tracks; + const channels = []; + const samplers = []; - const attribute = attributes[ i ]; + for ( let i = 0; i < tracks.length; ++ i ) { - if ( attribute.isInterleavedBufferAttribute ) { + const track = tracks[ i ]; + const trackBinding = PropertyBinding.parseTrackName( track.name ); + let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. InterleavedBufferAttributes are not supported.' ); - return null; + if ( trackBinding.objectName === 'bones' ) { - } + if ( trackNode.isSkinnedMesh === true ) { - if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; - if ( TypedArray !== attribute.array.constructor ) { + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' ); - return null; + } else { - } + trackNode = undefined; - if ( itemSize === undefined ) itemSize = attribute.itemSize; - if ( itemSize !== attribute.itemSize ) { + } - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' ); - return null; + } - } + if ( ! trackNode || ! trackProperty ) { - if ( normalized === undefined ) normalized = attribute.normalized; - if ( normalized !== attribute.normalized ) { + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' ); - return null; + } - } + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; - arrayLength += attribute.array.length; + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - } + outputItemSize /= trackNode.morphTargetInfluences.length; - const array = new TypedArray( arrayLength ); - let offset = 0; + } - for ( let i = 0; i < attributes.length; ++ i ) { + let interpolation; - array.set( attributes[ i ].array, offset ); + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - offset += attributes[ i ].array.length; + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - } + interpolation = 'CUBICSPLINE'; - return new BufferAttribute$1( array, itemSize, normalized ); + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3; -} + } else if ( track.getInterpolation() === InterpolateDiscrete ) { -class GeometryUtils { - static merge(geometriesByMaterial, splitByBlocks = false) { - const geometriesByMat = []; - const sizes = []; - for (const geometries of geometriesByMaterial) { - const merged = this.mergeGeomsOfSameMaterial(geometries, splitByBlocks); - geometriesByMat.push(merged); - sizes.push(merged.index.count); - } - const geometry = mergeGeometries(geometriesByMat); - this.setupMaterialGroups(sizes, geometry); - this.cleanUp(geometriesByMat); - return geometry; - } - // When Three.js exports to glTF, it generates one separate mesh per material. All meshes - // share the same BufferAttributes and have different indices - static async mergeGltfMeshes(meshes) { - const geometry = new BufferGeometry(); - const attributes = meshes[0].geometry.attributes; - this.getMeshesAttributes(geometry, attributes); - this.getMeshesIndices(geometry, meshes); - return geometry; - } - static getMeshesAttributes(geometry, attributes) { - // Three.js GLTFExporter exports custom BufferAttributes as underscore lowercase - // eslint-disable-next-line no-underscore-dangle - geometry.setAttribute("blockID", attributes._blockid); - geometry.setAttribute("position", attributes.position); - geometry.setAttribute("normal", attributes.normal); - geometry.groups = []; - } - static getMeshesIndices(geometry, meshes) { - const counter = { index: 0, material: 0 }; - const indices = []; - for (const mesh of meshes) { - const index = mesh.geometry.index; - this.getIndicesOfMesh(index, indices); - this.getMeshGroup(geometry, counter, index); - this.cleanUpMesh(mesh); - } - geometry.setIndex(indices); - } - static getMeshGroup(geometry, counter, index) { - geometry.groups.push({ - start: counter.index, - count: index.count, - materialIndex: counter.material++, - }); - counter.index += index.count; - } - static cleanUpMesh(mesh) { - mesh.geometry.setIndex([]); - mesh.geometry.attributes = {}; - mesh.geometry.dispose(); - } - static getIndicesOfMesh(index, indices) { - for (const number of index.array) { - indices.push(number); - } - } - static cleanUp(geometries) { - geometries.forEach((geometry) => geometry.dispose()); - geometries.length = 0; - } - static setupMaterialGroups(sizes, geometry) { - let vertexCounter = 0; - let counter = 0; - for (const size of sizes) { - const group = { - start: vertexCounter, - count: size, - materialIndex: counter++, - }; - geometry.groups.push(group); - vertexCounter += size; - } - } - static mergeGeomsOfSameMaterial(geometries, splitByBlocks) { - this.checkAllGeometriesAreIndexed(geometries); - if (splitByBlocks) { - this.splitByBlocks(geometries); - } - const merged = mergeGeometries(geometries); - this.cleanUp(geometries); - return merged; - } - static splitByBlocks(geometries) { - let i = 0; - for (const geometry of geometries) { - const size = geometry.attributes.position.count; - // TODO: Substitute blockID attribute by block id map - const array = new Uint16Array(size).fill(i++); - geometry.setAttribute("blockID", new BufferAttribute$1(array, 1)); - } - } - static checkAllGeometriesAreIndexed(geometries) { - for (const geometry of geometries) { - if (!geometry.index) { - throw new Error("All geometries must be indexed!"); - } - } - } -} + interpolation = 'STEP'; -/** - * The KHR_mesh_quantization extension allows these extra attribute component types - * - * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes - */ -const KHR_mesh_quantization_ExtraAttrTypes = { - POSITION: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'unsigned byte normalized', - 'short', - 'short normalized', - 'unsigned short', - 'unsigned short normalized', - ], - NORMAL: [ - 'byte normalized', - 'short normalized', - ], - TANGENT: [ - 'byte normalized', - 'short normalized', - ], - TEXCOORD: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'short', - 'short normalized', - 'unsigned short', - ], -}; + } else { + + interpolation = 'LINEAR'; + + } + + samplers.push( { + input: this.processAccessor( new BufferAttribute$1( track.times, inputItemSize ) ), + output: this.processAccessor( new BufferAttribute$1( track.values, outputItemSize ) ), + interpolation: interpolation + } ); + + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } + } ); + + } + + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); + + return json.animations.length - 1; + + } + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ + processSkin( object ) { -class GLTFExporter { + const json = this.json; + const nodeMap = this.nodeMap; - constructor() { + const node = json.nodes[ nodeMap.get( object ) ]; - this.pluginCallbacks = []; + const skeleton = object.skeleton; - this.register( function ( writer ) { + if ( skeleton === undefined ) return null; - return new GLTFLightExtension( writer ); + const rootJoint = object.skeleton.bones[ 0 ]; - } ); + if ( rootJoint === undefined ) return null; - this.register( function ( writer ) { + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new Matrix4(); - return new GLTFMaterialsUnlitExtension( writer ); + for ( let i = 0; i < skeleton.bones.length; ++ i ) { - } ); + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - this.register( function ( writer ) { + } - return new GLTFMaterialsTransmissionExtension( writer ); + if ( json.skins === undefined ) json.skins = []; + json.skins.push( { + inverseBindMatrices: this.processAccessor( new BufferAttribute$1( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) } ); - this.register( function ( writer ) { + const skinIndex = node.skin = json.skins.length - 1; - return new GLTFMaterialsVolumeExtension( writer ); + return skinIndex; - } ); + } - this.register( function ( writer ) { + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode( object ) { - return new GLTFMaterialsIorExtension( writer ); + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; - } ); + if ( ! json.nodes ) json.nodes = []; - this.register( function ( writer ) { + const nodeDef = {}; - return new GLTFMaterialsSpecularExtension( writer ); + if ( options.trs ) { - } ); + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); - this.register( function ( writer ) { + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - return new GLTFMaterialsClearcoatExtension( writer ); + nodeDef.rotation = rotation; - } ); + } - this.register( function ( writer ) { + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - return new GLTFMaterialsIridescenceExtension( writer ); + nodeDef.translation = position; - } ); + } - this.register( function ( writer ) { + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - return new GLTFMaterialsSheenExtension( writer ); + nodeDef.scale = scale; - } ); + } - this.register( function ( writer ) { + } else { - return new GLTFMaterialsEmissiveStrengthExtension( writer ); + if ( object.matrixAutoUpdate ) { - } ); + object.updateMatrix(); - } + } - register( callback ) { + if ( isIdentityMatrix( object.matrix ) === false ) { - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + nodeDef.matrix = object.matrix.elements; - this.pluginCallbacks.push( callback ); + } } - return this; + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) nodeDef.name = String( object.name ); - } + this.serializeUserData( object, nodeDef ); - unregister( callback ) { + if ( object.isMesh || object.isLine || object.isPoints ) { - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + const meshIndex = this.processMesh( object ); - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + + } else if ( object.isCamera ) { + + nodeDef.camera = this.processCamera( object ); } - return this; + if ( object.isSkinnedMesh ) this.skins.push( object ); - } + if ( object.children.length > 0 ) { - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Function} onError Callback on errors - * @param {Object} options options - */ - parse( input, onDone, onError, options ) { + const children = []; - const writer = new GLTFWriter(); - const plugins = []; + for ( let i = 0, l = object.children.length; i < l; i ++ ) { - for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + const child = object.children[ i ]; - plugins.push( this.pluginCallbacks[ i ]( writer ) ); + if ( child.visible || options.onlyVisible === false ) { - } + const nodeIndex = this.processNode( child ); - writer.setPlugins( plugins ); - writer.write( input, onDone, options ).catch( onError ); + if ( nodeIndex !== null ) children.push( nodeIndex ); - } + } - parseAsync( input, options ) { + } - const scope = this; + if ( children.length > 0 ) nodeDef.children = children; - return new Promise( function ( resolve, reject ) { + } - scope.parse( input, resolve, reject, options ); + this._invokeAll( function ( ext ) { + + ext.writeNode && ext.writeNode( object, nodeDef ); } ); + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; + } -} + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene( scene ) { -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ + const json = this.json; + const options = this.options; -const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, + if ( ! json.scenes ) { - BYTE: 0x1400, - UNSIGNED_BYTE: 0x1401, - SHORT: 0x1402, - UNSIGNED_SHORT: 0x1403, - INT: 0x1404, - UNSIGNED_INT: 0x1405, - FLOAT: 0x1406, + json.scenes = []; + json.scene = 0; - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, + } - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, + const sceneDef = {}; - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 -}; + if ( scene.name !== '' ) sceneDef.name = scene.name; -const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; + json.scenes.push( sceneDef ); -const THREE_TO_WEBGL = {}; + const nodes = []; -THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; -THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; -THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; -THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; -THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; -THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { -THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; -THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; -THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + const child = scene.children[ i ]; -const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' -}; + if ( child.visible || options.onlyVisible === false ) { -const DEFAULT_SPECULAR_COLOR = new Color(); + const nodeIndex = this.processNode( child ); -// GLB constants -// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + if ( nodeIndex !== null ) nodes.push( nodeIndex ); -const GLB_HEADER_BYTES = 12; -const GLB_HEADER_MAGIC = 0x46546C67; -const GLB_VERSION = 2; + } -const GLB_CHUNK_PREFIX_BYTES = 8; -const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; -const GLB_CHUNK_TYPE_BIN = 0x004E4942; + } -//------------------------------------------------------------------------------ -// Utility functions -//------------------------------------------------------------------------------ + if ( nodes.length > 0 ) sceneDef.nodes = nodes; -/** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal - */ -function equalArray( array1, array2 ) { + this.serializeUserData( scene, sceneDef ); - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + } - return element === array2[ index ]; + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects( objects ) { - } ); + const scene = new Scene(); + scene.name = 'AuxScene'; -} + for ( let i = 0; i < objects.length; i ++ ) { -/** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} - */ -function stringToArrayBuffer( text ) { + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); - return new TextEncoder().encode( text ).buffer; + } -} + this.processScene( scene ); -/** - * Is identity matrix - * - * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix - */ -function isIdentityMatrix( matrix ) { + } - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); + /** + * @param {THREE.Object3D|Array} input + */ + processInput( input ) { -} + const options = this.options; -/** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ -function getMinMax( attribute, start, count ) { + input = input instanceof Array ? input : [ input ]; - const output = { + this._invokeAll( function ( ext ) { - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + ext.beforeParse && ext.beforeParse( input ); - }; + } ); - for ( let i = start; i < start + count; i ++ ) { + const objectsWithoutScene = []; - for ( let a = 0; a < attribute.itemSize; a ++ ) { + for ( let i = 0; i < input.length; i ++ ) { - let value; + if ( input[ i ] instanceof Scene ) { - if ( attribute.itemSize > 4 ) { + this.processScene( input[ i ] ); - // no support for interleaved data for itemSize > 4 + } else { - value = attribute.array[ i * attribute.itemSize + a ]; + objectsWithoutScene.push( input[ i ] ); - } else { + } - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + } - if ( attribute.normalized === true ) { + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); - value = MathUtils.normalize( value, attribute.array ); + for ( let i = 0; i < this.skins.length; ++ i ) { - } + this.processSkin( this.skins[ i ] ); - } + } - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); + for ( let i = 0; i < options.animations.length; ++ i ) { + + this.processAnimation( options.animations[ i ], input[ 0 ] ); } + this._invokeAll( function ( ext ) { + + ext.afterParse && ext.afterParse( input ); + + } ); + } - return output; + _invokeAll( func ) { -} + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { -/** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ -function getPaddedBufferSize( bufferSize ) { + func( this.plugins[ i ] ); - return Math.ceil( bufferSize / 4 ) * 4; + } + + } } /** - * Returns a buffer aligned to 4-byte boundary. + * Punctual Lights Extension * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ -function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { +class GLTFLightExtension { - const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + constructor( writer ) { - if ( paddedLength !== arrayBuffer.byteLength ) { + this.writer = writer; + this.name = 'KHR_lights_punctual'; - const array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + } - if ( paddingByte !== 0 ) { + writeNode( light, nodeDef ) { - for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + if ( ! light.isLight ) return; - array[ i ] = paddingByte; + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - } + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; } - return array.buffer; + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; - } + const lightDef = {}; - return arrayBuffer; + if ( light.name ) lightDef.name = light.name; -} + lightDef.color = light.color.toArray(); -function getCanvas() { + lightDef.intensity = light.intensity; - if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { + if ( light.isDirectionalLight ) { - return new OffscreenCanvas( 1, 1 ); + lightDef.type = 'directional'; - } + } else if ( light.isPointLight ) { - return document.createElement( 'canvas' ); + lightDef.type = 'point'; -} + if ( light.distance > 0 ) lightDef.range = light.distance; -function getToBlobPromise( canvas, mimeType ) { + } else if ( light.isSpotLight ) { - if ( canvas.toBlob !== undefined ) { + lightDef.type = 'spot'; - return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); + if ( light.distance > 0 ) lightDef.range = light.distance; - } + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.outerConeAngle = light.angle; - let quality; + } - // Blink's implementation of convertToBlob seems to default to a quality level of 100% - // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. - if ( mimeType === 'image/jpeg' ) { + if ( light.decay !== undefined && light.decay !== 2 ) { - quality = 0.92; + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + + 'and expects light.decay=2.' ); - } else if ( mimeType === 'image/webp' ) { + } - quality = 0.8; + if ( light.target + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { - } + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + + 'make light.target a child of the light with position 0,0,-1.' ); - return canvas.convertToBlob( { + } - type: mimeType, - quality: quality + if ( ! extensionsUsed[ this.name ] ) { - } ); + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { lights: [] }; + extensionsUsed[ this.name ] = true; + + } + + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); + + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + + } } /** - * Writer + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ -class GLTFWriter { +class GLTFMaterialsUnlitExtension { - constructor() { + constructor( writer ) { - this.plugins = []; + this.writer = writer; + this.name = 'KHR_materials_unlit'; - this.options = {}; - this.pending = []; - this.buffers = []; + } - this.byteOffset = 0; - this.buffers = []; - this.nodeMap = new Map(); - this.skins = []; + writeMaterial( material, materialDef ) { - this.extensionsUsed = {}; - this.extensionsRequired = {}; + if ( ! material.isMeshBasicMaterial ) return; - this.uids = new Map(); - this.uid = 0; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter' - } - }; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map() - }; + extensionsUsed[ this.name ] = true; + + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; } - setPlugins( plugins ) { +} - this.plugins = plugins; +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { - } + constructor( writer ) { - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - async write( input, onDone, options = {} ) { + this.writer = writer; + this.name = 'KHR_materials_clearcoat'; - this.options = Object.assign( { - // default options - binary: false, - trs: false, - onlyVisible: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false - }, options ); + } - if ( this.options.animations.length > 0 ) { + writeMaterial( material, materialDef ) { - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; + if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; - } + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - this.processInput( input ); + const extensionDef = {}; - await Promise.all( this.pending ); + extensionDef.clearcoatFactor = material.clearcoat; - const writer = this; - const buffers = writer.buffers; - const json = writer.json; - options = writer.options; + if ( material.clearcoatMap ) { - const extensionsUsed = writer.extensionsUsed; - const extensionsRequired = writer.extensionsRequired; + const clearcoatMapDef = { + index: writer.processTexture( material.clearcoatMap ), + texCoord: material.clearcoatMap.channel + }; + writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); + extensionDef.clearcoatTexture = clearcoatMapDef; - // Merge buffers. - const blob = new Blob( buffers, { type: 'application/octet-stream' } ); + } - // Declare extensions. - const extensionsUsedList = Object.keys( extensionsUsed ); - const extensionsRequiredList = Object.keys( extensionsRequired ); + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; + if ( material.clearcoatRoughnessMap ) { - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + const clearcoatRoughnessMapDef = { + index: writer.processTexture( material.clearcoatRoughnessMap ), + texCoord: material.clearcoatRoughnessMap.channel + }; + writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; - if ( options.binary === true ) { + } - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + if ( material.clearcoatNormalMap ) { - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + const clearcoatNormalMapDef = { + index: writer.processTexture( material.clearcoatNormalMap ), + texCoord: material.clearcoatNormalMap.channel + }; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; - // Binary chunk. - const binaryChunk = getPaddedArrayBuffer( reader.result ); - const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + } - // JSON chunk. - const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - // GLB header. - const header = new ArrayBuffer( GLB_HEADER_BYTES ); - const headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - const totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); + extensionsUsed[ this.name ] = true; - const glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); - const glbReader = new FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { + } - onDone( glbReader.result ); +} - }; +/** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ +class GLTFMaterialsIridescenceExtension { - }; + constructor( writer ) { - } else { + this.writer = writer; + this.name = 'KHR_materials_iridescence'; - if ( json.buffers && json.buffers.length > 0 ) { + } - const reader = new FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { + writeMaterial( material, materialDef ) { - const base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); + if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; - }; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - } else { + const extensionDef = {}; - onDone( json ); + extensionDef.iridescenceFactor = material.iridescence; - } + if ( material.iridescenceMap ) { + + const iridescenceMapDef = { + index: writer.processTexture( material.iridescenceMap ), + texCoord: material.iridescenceMap.channel + }; + writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); + extensionDef.iridescenceTexture = iridescenceMapDef; } + extensionDef.iridescenceIor = material.iridescenceIOR; + extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; + extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; - } + if ( material.iridescenceThicknessMap ) { - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - serializeUserData( object, objectDef ) { + const iridescenceThicknessMapDef = { + index: writer.processTexture( material.iridescenceThicknessMap ), + texCoord: material.iridescenceThicknessMap.channel + }; + writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); + extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; - if ( Object.keys( object.userData ).length === 0 ) return; + } - const options = this.options; - const extensionsUsed = this.extensionsUsed; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - try { + extensionsUsed[ this.name ] = true; - const json = JSON.parse( JSON.stringify( object.userData ) ); + } - if ( options.includeCustomExtensions && json.gltfExtensions ) { +} - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + */ +class GLTFMaterialsTransmissionExtension { - for ( const extensionName in json.gltfExtensions ) { + constructor( writer ) { - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; + this.writer = writer; + this.name = 'KHR_materials_transmission'; - } + } - delete json.gltfExtensions; + writeMaterial( material, materialDef ) { - } + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - } catch ( error ) { + const extensionDef = {}; - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + extensionDef.transmissionFactor = material.transmission; - } + if ( material.transmissionMap ) { - } + const transmissionMapDef = { + index: writer.processTexture( material.transmissionMap ), + texCoord: material.transmissionMap.channel + }; + writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); + extensionDef.transmissionTexture = transmissionMapDef; - /** - * Returns ids for buffer attributes. - * @param {Object} object - * @return {Integer} - */ - getUID( attribute, isRelativeCopy = false ) { + } - if ( this.uids.has( attribute ) === false ) { + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - const uids = new Map(); + extensionsUsed[ this.name ] = true; - uids.set( true, this.uid ++ ); - uids.set( false, this.uid ++ ); + } - this.uids.set( attribute, uids ); +} - } +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { - const uids = this.uids.get( attribute ); + constructor( writer ) { - return uids.get( isRelativeCopy ); + this.writer = writer; + this.name = 'KHR_materials_volume'; } - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - isNormalizedNormalAttribute( normal ) { + writeMaterial( material, materialDef ) { - const cache = this.cache; + if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - if ( cache.attributesNormalized.has( normal ) ) return false; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const v = new Vector3$1(); + const extensionDef = {}; - for ( let i = 0, il = normal.count; i < il; i ++ ) { + extensionDef.thicknessFactor = material.thickness; - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + if ( material.thicknessMap ) { + + const thicknessMapDef = { + index: writer.processTexture( material.thicknessMap ), + texCoord: material.thicknessMap.channel + }; + writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); + extensionDef.thicknessTexture = thicknessMapDef; } - return true; + extensionDef.attenuationDistance = material.attenuationDistance; + extensionDef.attenuationColor = material.attenuationColor.toArray(); + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; } - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute( normal ) { +} - const cache = this.cache; +/** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ +class GLTFMaterialsIorExtension { - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + constructor( writer ) { - const attribute = normal.clone(); - const v = new Vector3$1(); + this.writer = writer; + this.name = 'KHR_materials_ior'; - for ( let i = 0, il = attribute.count; i < il; i ++ ) { + } - v.fromBufferAttribute( attribute, i ); + writeMaterial( material, materialDef ) { - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - } else { + const extensionDef = {}; - v.normalize(); + extensionDef.ior = material.ior; - } + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - attribute.setXYZ( i, v.x, v.y, v.z ); + extensionsUsed[ this.name ] = true; - } + } - cache.attributesNormalized.set( normal, attribute ); +} - return attribute; +/** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ +class GLTFMaterialsSpecularExtension { - } + constructor( writer ) { - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform( mapDef, texture ) { + this.writer = writer; + this.name = 'KHR_materials_specular'; - let didTransform = false; - const transformDef = {}; + } - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + writeMaterial( material, materialDef ) { - transformDef.offset = texture.offset.toArray(); - didTransform = true; + if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && + material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && + ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; - } + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - if ( texture.rotation !== 0 ) { + const extensionDef = {}; - transformDef.rotation = texture.rotation; - didTransform = true; + if ( material.specularIntensityMap ) { + + const specularIntensityMapDef = { + index: writer.processTexture( material.specularIntensityMap ), + texCoord: material.specularIntensityMap.channel + }; + writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); + extensionDef.specularTexture = specularIntensityMapDef; } - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + if ( material.specularColorMap ) { - transformDef.scale = texture.repeat.toArray(); - didTransform = true; + const specularColorMapDef = { + index: writer.processTexture( material.specularColorMap ), + texCoord: material.specularColorMap.channel + }; + writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); + extensionDef.specularColorTexture = specularColorMapDef; } - if ( didTransform ) { + extensionDef.specularFactor = material.specularIntensity; + extensionDef.specularColorFactor = material.specularColor.toArray(); - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; } - buildMetalRoughTexture( metalnessMap, roughnessMap ) { +} - if ( metalnessMap === roughnessMap ) return metalnessMap; +/** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ +class GLTFMaterialsSheenExtension { - function getEncodingConversion( map ) { + constructor( writer ) { - if ( map.colorSpace === SRGBColorSpace ) { + this.writer = writer; + this.name = 'KHR_materials_sheen'; - return function SRGBToLinear( c ) { + } - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + writeMaterial( material, materialDef ) { - }; + if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; - } + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - return function LinearToLinear( c ) { + const extensionDef = {}; - return c; + if ( material.sheenRoughnessMap ) { + const sheenRoughnessMapDef = { + index: writer.processTexture( material.sheenRoughnessMap ), + texCoord: material.sheenRoughnessMap.channel }; + writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); + extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; } - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + if ( material.sheenColorMap ) { - const metalness = metalnessMap ? metalnessMap.image : null; - const roughness = roughnessMap ? roughnessMap.image : null; + const sheenColorMapDef = { + index: writer.processTexture( material.sheenColorMap ), + texCoord: material.sheenColorMap.channel + }; + writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); + extensionDef.sheenColorTexture = sheenColorMapDef; - const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); - const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); + } - const canvas = getCanvas(); - canvas.width = width; - canvas.height = height; + extensionDef.sheenRoughnessFactor = material.sheenRoughness; + extensionDef.sheenColorFactor = material.sheenColor.toArray(); - const context = canvas.getContext( '2d' ); - context.fillStyle = '#00ffff'; - context.fillRect( 0, 0, width, height ); + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - const composite = context.getImageData( 0, 0, width, height ); + extensionsUsed[ this.name ] = true; - if ( metalness ) { + } - context.drawImage( metalness, 0, 0, width, height ); +} - const convert = getEncodingConversion( metalnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; +/** + * Materials Emissive Strength Extension + * + * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + */ +class GLTFMaterialsEmissiveStrengthExtension { - for ( let i = 2; i < data.length; i += 4 ) { + constructor( writer ) { - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + this.writer = writer; + this.name = 'KHR_materials_emissive_strength'; - } + } - } + writeMaterial( material, materialDef ) { - if ( roughness ) { + if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; - context.drawImage( roughness, 0, 0, width, height ); + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - const convert = getEncodingConversion( roughnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; + const extensionDef = {}; - for ( let i = 1; i < data.length; i += 4 ) { + extensionDef.emissiveStrength = material.emissiveIntensity; - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; - } + extensionsUsed[ this.name ] = true; - } + } - context.putImageData( composite, 0, 0 ); +} - // +/** + * Static utility functions + */ +GLTFExporter.Utils = { - const reference = metalnessMap || roughnessMap; + insertKeyframe: function ( track, time ) { - const texture = reference.clone(); + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); - texture.source = new Source( canvas ); - texture.colorSpace = NoColorSpace; - texture.channel = ( metalnessMap || roughnessMap ).channel; + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { + let index; - console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); + if ( track.times.length === 0 ) { - } + times[ 0 ] = time; - return texture; + for ( let i = 0; i < valueSize; i ++ ) { - } + values[ i ] = 0; - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - processBuffer( buffer ) { + } + + index = 0; - const json = this.json; - const buffers = this.buffers; + } else if ( time < track.times[ 0 ] ) { - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - // All buffers are merged before export. - buffers.push( buffer ); + times[ 0 ] = time; + times.set( track.times, 1 ); - return 0; + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - } + index = 0; - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - processBufferView( attribute, componentType, start, count, target ) { + } else if ( time > track.times[ track.times.length - 1 ] ) { - const json = this.json; + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - if ( ! json.bufferViews ) json.bufferViews = []; + return track.times.length - 1; - // Create a new dataview and dump the attribute's array into it + } - let componentSize; + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - switch ( componentType ) { + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - case WEBGL_CONSTANTS.BYTE: - case WEBGL_CONSTANTS.UNSIGNED_BYTE: + index = times.length - 1; - componentSize = 1; + } else { - break; + for ( let i = 0; i < track.times.length; i ++ ) { - case WEBGL_CONSTANTS.SHORT: - case WEBGL_CONSTANTS.UNSIGNED_SHORT: + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - componentSize = 2; + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - break; + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - default: + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - componentSize = 4; + index = i + 1; + + break; + + } + + } } - const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - const dataView = new DataView( new ArrayBuffer( byteLength ) ); - let offset = 0; + track.times = times; + track.values = values; - for ( let i = start; i < start + count; i ++ ) { + return index; - for ( let a = 0; a < attribute.itemSize; a ++ ) { + }, - let value; + mergeMorphTargetTracks: function ( clip, root ) { - if ( attribute.itemSize > 4 ) { + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - // no support for interleaved data for itemSize > 4 + for ( let i = 0; i < sourceTracks.length; ++ i ) { - value = attribute.array[ i * attribute.itemSize + a ]; + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - } else { + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - if ( attribute.normalized === true ) { + } - value = MathUtils.normalize( value, attribute.array ); + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - } + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - } + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + } - dataView.setFloat32( offset, value, true ); + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - } else if ( componentType === WEBGL_CONSTANTS.INT ) { + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - dataView.setInt32( offset, value, true ); + } - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - dataView.setUint32( offset, value, true ); + if ( targetIndex === undefined ) { - } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - dataView.setInt16( offset, value, true ); + } - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + let mergedTrack; - dataView.setUint16( offset, value, true ); + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { + mergedTrack = sourceTrack.clone(); - dataView.setInt8( offset, value ); + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - dataView.setUint8( offset, value ); + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; } - offset += componentSize; - - } + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; - } + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - const bufferViewDef = { + continue; - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength + } - }; + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - if ( target !== undefined ) bufferViewDef.target = target; + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - } + } - this.byteOffset += byteLength; + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - json.bufferViews.push( bufferViewDef ); + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - // @TODO Merge bufferViews where possible. - const output = { + } - id: json.bufferViews.length - 1, - byteLength: 0 + } - }; + clip.tracks = tracks; - return output; + return clip; } - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - processBufferViewImage( blob ) { - - const writer = this; - const json = writer.json; +}; - if ( ! json.bufferViews ) json.bufferViews = []; +const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; - return new Promise( function ( resolve ) { +// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 +function generateUUID() { - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + const d0 = Math.random() * 0xffffffff | 0; + const d1 = Math.random() * 0xffffffff | 0; + const d2 = Math.random() * 0xffffffff | 0; + const d3 = Math.random() * 0xffffffff | 0; + const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + + _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + + _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + + _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; - const buffer = getPaddedArrayBuffer( reader.result ); + // .toLowerCase() here flattens concatenated strings to save heap memory space. + return uuid.toLowerCase(); - const bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; +} - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); +function clamp( value, min, max ) { - }; + return Math.max( min, Math.min( max, value ) ); - } ); +} - } +function denormalize( value, array ) { - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - processAccessor( attribute, geometry, start, count ) { + switch ( array.constructor ) { - const json = this.json; + case Float32Array: - const types = { + return value; - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 9: 'MAT3', - 16: 'MAT4' + case Uint16Array: - }; + return value / 65535.0; - let componentType; + case Uint8Array: - // Detect the component type of the attribute array - if ( attribute.array.constructor === Float32Array ) { + return value / 255.0; - componentType = WEBGL_CONSTANTS.FLOAT; + case Int16Array: - } else if ( attribute.array.constructor === Int32Array ) { + return Math.max( value / 32767.0, - 1.0 ); - componentType = WEBGL_CONSTANTS.INT; + case Int8Array: - } else if ( attribute.array.constructor === Uint32Array ) { + return Math.max( value / 127.0, - 1.0 ); - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + default: - } else if ( attribute.array.constructor === Int16Array ) { + throw new Error( 'Invalid component type.' ); - componentType = WEBGL_CONSTANTS.SHORT; + } - } else if ( attribute.array.constructor === Uint16Array ) { +} - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; +function normalize( value, array ) { - } else if ( attribute.array.constructor === Int8Array ) { + switch ( array.constructor ) { - componentType = WEBGL_CONSTANTS.BYTE; + case Float32Array: - } else if ( attribute.array.constructor === Uint8Array ) { + return value; - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + case Uint16Array: - } else { + return Math.round( value * 65535.0 ); - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + case Uint8Array: - } + return Math.round( value * 255.0 ); - if ( start === undefined ) start = 0; - if ( count === undefined ) count = attribute.count; + case Int16Array: - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; + return Math.round( value * 32767.0 ); - const minMax = getMinMax( attribute, start, count ); - let bufferViewTarget; + case Int8Array: - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { + return Math.round( value * 127.0 ); - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + default: - } + throw new Error( 'Invalid component type.' ); - const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + } - const accessorDef = { +} - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] +class Quaternion { - }; + constructor( x = 0, y = 0, z = 0, w = 1 ) { - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; + this.isQuaternion = true; - return json.accessors.push( accessorDef ) - 1; + this._x = x; + this._y = y; + this._z = z; + this._w = w; } - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @param {String} mimeType export format - * @return {Integer} Index of the processed texture in the "images" array - */ - processImage( image, format, flipY, mimeType = 'image/png' ) { + static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - if ( image !== null ) { + // fuzz-free, array-based Quaternion SLERP operation - const writer = this; - const cache = writer.cache; - const json = writer.json; - const options = writer.options; - const pending = writer.pending; + let x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ]; - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + const x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; - const cachedImages = cache.images.get( image ); + if ( t === 0 ) { - const key = mimeType + ':flipY/' + flipY.toString(); + dst[ dstOffset + 0 ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + return; - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + } - if ( ! json.images ) json.images = []; + if ( t === 1 ) { - const imageDef = { mimeType: mimeType }; + dst[ dstOffset + 0 ] = x1; + dst[ dstOffset + 1 ] = y1; + dst[ dstOffset + 2 ] = z1; + dst[ dstOffset + 3 ] = w1; + return; - const canvas = getCanvas(); + } - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - const ctx = canvas.getContext( '2d' ); + let s = 1 - t; + const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + dir = ( cos >= 0 ? 1 : - 1 ), + sqrSin = 1 - cos * cos; - if ( flipY === true ) { + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + const sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); + + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; } - if ( image.data !== undefined ) { // THREE.DataTexture + const tDir = t * dir; - if ( format !== RGBAFormat ) { + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; - console.error( 'GLTFExporter: Only RGBAFormat is supported.' ); + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { - } + const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + } - } + } - const data = new Uint8ClampedArray( image.height * image.width * 4 ); + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; - for ( let i = 0; i < data.length; i += 4 ) { + } - data[ i + 0 ] = image.data[ i + 0 ]; - data[ i + 1 ] = image.data[ i + 1 ]; - data[ i + 2 ] = image.data[ i + 2 ]; - data[ i + 3 ] = image.data[ i + 3 ]; + static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - } + const x0 = src0[ srcOffset0 ]; + const y0 = src0[ srcOffset0 + 1 ]; + const z0 = src0[ srcOffset0 + 2 ]; + const w0 = src0[ srcOffset0 + 3 ]; - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + const x1 = src1[ srcOffset1 ]; + const y1 = src1[ srcOffset1 + 1 ]; + const z1 = src1[ srcOffset1 + 2 ]; + const w1 = src1[ srcOffset1 + 3 ]; - } else { + dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; + dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; + dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; + dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + return dst; - } + } - if ( options.binary === true ) { + get x() { - pending.push( + return this._x; - getToBlobPromise( canvas, mimeType ) - .then( blob => writer.processBufferViewImage( blob ) ) - .then( bufferViewIndex => { + } - imageDef.bufferView = bufferViewIndex; + set x( value ) { - } ) + this._x = value; + this._onChangeCallback(); - ); + } - } else { + get y() { - if ( canvas.toDataURL !== undefined ) { + return this._y; - imageDef.uri = canvas.toDataURL( mimeType ); + } - } else { + set y( value ) { - pending.push( + this._y = value; + this._onChangeCallback(); - getToBlobPromise( canvas, mimeType ) - .then( blob => new FileReader().readAsDataURL( blob ) ) - .then( dataURL => { + } - imageDef.uri = dataURL; + get z() { - } ) + return this._z; - ); + } - } + set z( value ) { - } + this._z = value; + this._onChangeCallback(); - const index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; + } - } else { + get w() { - throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); + return this._w; - } + } + + set w( value ) { + + this._w = value; + this._onChangeCallback(); } - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - processSampler( map ) { + set( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; - const json = this.json; + this._onChangeCallback(); - if ( ! json.samplers ) json.samplers = []; + return this; - const samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; + } - return json.samplers.push( samplerDef ) - 1; + clone() { + + return new this.constructor( this._x, this._y, this._z, this._w ); } - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - processTexture( map ) { + copy( quaternion ) { - const cache = this.cache; - const json = this.json; + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; - if ( cache.textures.has( map ) ) return cache.textures.get( map ); + this._onChangeCallback(); - if ( ! json.textures ) json.textures = []; + return this; - let mimeType = map.userData.mimeType; + } - if ( mimeType === 'image/webp' ) mimeType = 'image/png'; + setFromEuler( euler, update ) { - const textureDef = { - sampler: this.processSampler( map ), - source: this.processImage( map.image, map.format, map.flipY, mimeType ) - }; + const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - if ( map.name ) textureDef.name = map.name; + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m - this._invokeAll( function ( ext ) { + const cos = Math.cos; + const sin = Math.sin; - ext.writeTexture && ext.writeTexture( map, textureDef ); + const c1 = cos( x / 2 ); + const c2 = cos( y / 2 ); + const c3 = cos( z / 2 ); - } ); + const s1 = sin( x / 2 ); + const s2 = sin( y / 2 ); + const s3 = sin( z / 2 ); - const index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; + switch ( order ) { - } + case 'XYZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - processMaterial( material ) { + case 'YXZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - const cache = this.cache; - const json = this.json; + case 'ZXY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - if ( cache.materials.has( material ) ) return cache.materials.get( material ); + case 'ZYX': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - if ( material.isShaderMaterial ) { + case 'YZX': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; + case 'XZY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; + + default: + console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); } - if ( ! json.materials ) json.materials = []; + if ( update !== false ) this._onChangeCallback(); - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef = { pbrMetallicRoughness: {} }; + return this; - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + } - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + setFromAxisAngle( axis, angle ) { - } + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat( [ material.opacity ] ); + // assumes axis is normalized - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + const halfAngle = angle / 2, s = Math.sin( halfAngle ); - materialDef.pbrMetallicRoughness.baseColorFactor = color; + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); - } + this._onChangeCallback(); - if ( material.isMeshStandardMaterial ) { + return this; - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + } - } else { + setFromRotationMatrix( m ) { - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - } + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { + const te = m.elements, - const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - const metalRoughMapDef = { - index: this.processTexture( metalRoughTexture ), - channel: metalRoughTexture.channel - }; - this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + trace = m11 + m22 + m33; - } + if ( trace > 0 ) { - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { + const s = 0.5 / Math.sqrt( trace + 1.0 ); - const baseColorMapDef = { - index: this.processTexture( material.map ), - texCoord: material.map.channel - }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; - } + } else if ( m11 > m22 && m11 > m33 ) { - if ( material.emissive ) { + const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - const emissive = material.emissive; - const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; - if ( maxEmissiveComponent > 0 ) { + } else if ( m22 > m33 ) { - materialDef.emissiveFactor = material.emissive.toArray(); + const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - } + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; - // emissiveTexture - if ( material.emissiveMap ) { + } else { - const emissiveMapDef = { - index: this.processTexture( material.emissiveMap ), - texCoord: material.emissiveMap.channel - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; + const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - } + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; } - // normalTexture - if ( material.normalMap ) { + this._onChangeCallback(); - const normalMapDef = { - index: this.processTexture( material.normalMap ), - texCoord: material.normalMap.channel - }; + return this; - if ( material.normalScale && material.normalScale.x !== 1 ) { + } - // glTF normal scale is univariate. Ignore `y`, which may be flipped. - // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - normalMapDef.scale = material.normalScale.x; + setFromUnitVectors( vFrom, vTo ) { - } + // assumes direction vectors vFrom and vTo are normalized - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; + let r = vFrom.dot( vTo ) + 1; - } + if ( r < Number.EPSILON ) { - // occlusionTexture - if ( material.aoMap ) { + // vFrom and vTo point in opposite directions - const occlusionMapDef = { - index: this.processTexture( material.aoMap ), - texCoord: material.aoMap.channel - }; + r = 0; - if ( material.aoMapIntensity !== 1.0 ) { + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - occlusionMapDef.strength = material.aoMapIntensity; + this._x = - vFrom.y; + this._y = vFrom.x; + this._z = 0; + this._w = r; + + } else { + + this._x = 0; + this._y = - vFrom.z; + this._z = vFrom.y; + this._w = r; } - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; + } else { + + // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 + + this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; + this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; + this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; + this._w = r; } - // alphaMode - if ( material.transparent ) { + return this.normalize(); - materialDef.alphaMode = 'BLEND'; + } - } else { + angleTo( q ) { - if ( material.alphaTest > 0.0 ) { + return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; + } - } + rotateTowards( q, step ) { - } + const angle = this.angleTo( q ); - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; + if ( angle === 0 ) return this; - this.serializeUserData( material, materialDef ); + const t = Math.min( 1, step / angle ); - this._invokeAll( function ( ext ) { + this.slerp( q, t ); - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + return this; - } ); + } - const index = json.materials.push( materialDef ) - 1; - cache.materials.set( material, index ); - return index; + identity() { + + return this.set( 0, 0, 0, 1 ); } - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - processMesh( mesh ) { + invert() { - const cache = this.cache; - const json = this.json; + // quaternion is assumed to have unit length - const meshCacheKeyParts = [ mesh.geometry.uuid ]; + return this.conjugate(); - if ( Array.isArray( mesh.material ) ) { + } - for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { + conjugate() { - meshCacheKeyParts.push( mesh.material[ i ].uuid ); + this._x *= - 1; + this._y *= - 1; + this._z *= - 1; - } + this._onChangeCallback(); - } else { + return this; - meshCacheKeyParts.push( mesh.material.uuid ); + } - } + dot( v ) { - const meshCacheKey = meshCacheKeyParts.join( ':' ); + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + } - const geometry = mesh.geometry; + lengthSq() { - let mode; + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - // Use the correct mode - if ( mesh.isLineSegments ) { + } - mode = WEBGL_CONSTANTS.LINES; + length() { - } else if ( mesh.isLineLoop ) { + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - mode = WEBGL_CONSTANTS.LINE_LOOP; + } - } else if ( mesh.isLine ) { + normalize() { - mode = WEBGL_CONSTANTS.LINE_STRIP; + let l = this.length(); - } else if ( mesh.isPoints ) { + if ( l === 0 ) { - mode = WEBGL_CONSTANTS.POINTS; + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; } else { - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + l = 1 / l; - } + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; - const meshDef = {}; - const attributes = {}; - const primitives = []; - const targets = []; + } - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv1: 'TEXCOORD_1', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; + this._onChangeCallback(); - const originalNormal = geometry.getAttribute( 'normal' ); + return this; - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + } - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + multiply( q ) { - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + return this.multiplyQuaternions( this, q ); - } + } - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null; + premultiply( q ) { - for ( let attributeName in geometry.attributes ) { + return this.multiplyQuaternions( q, this ); - // Ignore morph target attributes, which are exported later. - if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; + } - const attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + multiplyQuaternions( a, b ) { - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + this._onChangeCallback(); - } + return this; - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null; - const array = attribute.array; + } - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { + slerp( qb, t ) { - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new BufferAttribute$1( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); - } + const x = this._x, y = this._y, z = this._z, w = this._w; - const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - if ( accessor !== null ) { + let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - if ( ! attributeName.startsWith( '_' ) ) { + if ( cosHalfTheta < 0 ) { - this.detectMeshQuantization( attributeName, attribute ); + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; - } + cosHalfTheta = - cosHalfTheta; - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); + } else { - } + this.copy( qb ); } - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + if ( cosHalfTheta >= 1.0 ) { - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; + this._w = w; + this._x = x; + this._y = y; + this._z = z; - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + return this; - const weights = []; - const targetNames = []; - const reverseDictionary = {}; + } - if ( mesh.morphTargetDictionary !== undefined ) { + const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - for ( const key in mesh.morphTargetDictionary ) { + if ( sqrSinHalfTheta <= Number.EPSILON ) { - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + const s = 1 - t; + this._w = s * w + t * this._w; + this._x = s * x + t * this._x; + this._y = s * y + t * this._y; + this._z = s * z + t * this._z; - } + this.normalize(); + this._onChangeCallback(); - } + return this; - for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { + } - const target = {}; - let warned = false; + const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); + const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - for ( const attributeName in geometry.morphAttributes ) { + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. + this._onChangeCallback(); - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + return this; - if ( ! warned ) { + } - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + slerpQuaternions( qa, qb, t ) { - } + return this.copy( qa ).slerp( qb, t ); - continue; + } - } + random() { - const attribute = geometry.morphAttributes[ attributeName ][ i ]; - const gltfAttributeName = attributeName.toUpperCase(); + // Derived from http://planning.cs.uiuc.edu/node198.html + // Note, this source uses w, x, y, z ordering, + // so we swap the order below. + + const u1 = Math.random(); + const sqrt1u1 = Math.sqrt( 1 - u1 ); + const sqrtu1 = Math.sqrt( u1 ); - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + const u2 = 2 * Math.PI * Math.random(); - const baseAttribute = geometry.attributes[ attributeName ]; + const u3 = 2 * Math.PI * Math.random(); - if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { + return this.set( + sqrt1u1 * Math.cos( u2 ), + sqrtu1 * Math.sin( u3 ), + sqrtu1 * Math.cos( u3 ), + sqrt1u1 * Math.sin( u2 ), + ); - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); - continue; + } - } + equals( quaternion ) { - // Clones attribute not to override - const relativeAttribute = attribute.clone(); + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - if ( ! geometry.morphTargetsRelative ) { + } - for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { + fromArray( array, offset = 0 ) { - for ( let a = 0; a < attribute.itemSize; a ++ ) { + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; - if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); - if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); - if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); - if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); + this._onChangeCallback(); - } + return this; - } + } - } + toArray( array = [], offset = 0 ) { - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; - } + return array; - targets.push( target ); + } - weights.push( mesh.morphTargetInfluences[ i ] ); + fromBufferAttribute( attribute, index ) { - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + this._x = attribute.getX( index ); + this._y = attribute.getY( index ); + this._z = attribute.getZ( index ); + this._w = attribute.getW( index ); - } + return this; - meshDef.weights = weights; + } - if ( targetNames.length > 0 ) { + toJSON() { - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; + return this.toArray(); - } + } - } + _onChange( callback ) { - const isMultiMaterial = Array.isArray( mesh.material ); + this._onChangeCallback = callback; - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + return this; - const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + } - for ( let i = 0, il = groups.length; i < il; i ++ ) { + _onChangeCallback() {} - const primitive = { - mode: mode, - attributes: attributes, - }; + *[ Symbol.iterator ]() { - this.serializeUserData( geometry, primitive ); + yield this._x; + yield this._y; + yield this._z; + yield this._w; - if ( targets.length > 0 ) primitive.targets = targets; + } - if ( geometry.index !== null ) { +} - let cacheKey = this.getUID( geometry.index ); +class Vector3 { - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + constructor( x = 0, y = 0, z = 0 ) { - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + Vector3.prototype.isVector3 = true; - } + this.x = x; + this.y = y; + this.z = z; - if ( cache.attributes.has( cacheKey ) ) { + } - primitive.indices = cache.attributes.get( cacheKey ); + set( x, y, z ) { - } else { + if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); + this.x = x; + this.y = y; + this.z = z; - } + return this; - if ( primitive.indices === null ) delete primitive.indices; + } - } + setScalar( scalar ) { - const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + this.x = scalar; + this.y = scalar; + this.z = scalar; - if ( material !== null ) primitive.material = material; + return this; - primitives.push( primitive ); + } - } + setX( x ) { - meshDef.primitives = primitives; + this.x = x; - if ( ! json.meshes ) json.meshes = []; + return this; - this._invokeAll( function ( ext ) { + } - ext.writeMesh && ext.writeMesh( mesh, meshDef ); + setY( y ) { - } ); + this.y = y; - const index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; + return this; } - /** - * If a vertex attribute with a - * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) - * is used, it is checked whether it is a valid data type according to the - * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) - * extension. - * In this case the extension is automatically added to the list of used extensions. - * - * @param {string} attributeName - * @param {THREE.BufferAttribute} attribute - */ - detectMeshQuantization( attributeName, attribute ) { + setZ( z ) { - if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; + this.z = z; - let attrType = undefined; + return this; - switch ( attribute.array.constructor ) { + } - case Int8Array: + setComponent( index, value ) { - attrType = 'byte'; + switch ( index ) { - break; + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); - case Uint8Array: + } - attrType = 'unsigned byte'; + return this; - break; + } - case Int16Array: + getComponent( index ) { - attrType = 'short'; + switch ( index ) { - break; + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); - case Uint16Array: + } - attrType = 'unsigned short'; + } - break; + clone() { - default: + return new this.constructor( this.x, this.y, this.z ); - return; + } - } + copy( v ) { - if ( attribute.normalized ) attrType += ' normalized'; + this.x = v.x; + this.y = v.y; + this.z = v.z; - const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; + return this; - if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { + } - this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; + add( v ) { - } + this.x += v.x; + this.y += v.y; + this.z += v.z; - } + return this; - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - processCamera( camera ) { + } - const json = this.json; + addScalar( s ) { - if ( ! json.cameras ) json.cameras = []; + this.x += s; + this.y += s; + this.z += s; - const isOrtho = camera.isOrthographicCamera; + return this; - const cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; + } - if ( isOrtho ) { + addVectors( a, b ) { - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; - } else { + return this; - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + } - } + addScaledVector( v, s ) { - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; - return json.cameras.push( cameraDef ) - 1; + return this; } - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - processAnimation( clip, root ) { + sub( v ) { - const json = this.json; - const nodeMap = this.nodeMap; + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; - if ( ! json.animations ) json.animations = []; + return this; - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + } - const tracks = clip.tracks; - const channels = []; - const samplers = []; + subScalar( s ) { - for ( let i = 0; i < tracks.length; ++ i ) { + this.x -= s; + this.y -= s; + this.z -= s; - const track = tracks[ i ]; - const trackBinding = PropertyBinding.parseTrackName( track.name ); - let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + return this; - if ( trackBinding.objectName === 'bones' ) { + } - if ( trackNode.isSkinnedMesh === true ) { + subVectors( a, b ) { - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; - } else { + return this; - trackNode = undefined; + } - } + multiply( v ) { - } + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; - if ( ! trackNode || ! trackProperty ) { + return this; - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + } - } + multiplyScalar( scalar ) { - const inputItemSize = 1; - let outputItemSize = track.values.length / track.times.length; + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + return this; - outputItemSize /= trackNode.morphTargetInfluences.length; + } - } + multiplyVectors( a, b ) { - let interpolation; + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + return this; - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + } - interpolation = 'CUBICSPLINE'; + applyEuler( euler ) { - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; + return this.applyQuaternion( _quaternion.setFromEuler( euler ) ); - } else if ( track.getInterpolation() === InterpolateDiscrete ) { + } - interpolation = 'STEP'; + applyAxisAngle( axis, angle ) { - } else { + return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) ); - interpolation = 'LINEAR'; + } - } + applyMatrix3( m ) { - samplers.push( { - input: this.processAccessor( new BufferAttribute$1( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute$1( track.values, outputItemSize ) ), - interpolation: interpolation - } ); + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - } + return this; - json.animations.push( { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels - } ); + } - return json.animations.length - 1; + applyNormalMatrix( m ) { + + return this.applyMatrix3( m ).normalize(); } - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ - processSkin( object ) { + applyMatrix4( m ) { - const json = this.json; - const nodeMap = this.nodeMap; + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - const node = json.nodes[ nodeMap.get( object ) ]; + const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - const skeleton = object.skeleton; + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - if ( skeleton === undefined ) return null; + return this; - const rootJoint = object.skeleton.bones[ 0 ]; + } - if ( rootJoint === undefined ) return null; + applyQuaternion( q ) { - const joints = []; - const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - const temporaryBoneInverse = new Matrix4(); + const x = this.x, y = this.y, z = this.z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - for ( let i = 0; i < skeleton.bones.length; ++ i ) { + // calculate quat * vector - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + const ix = qw * x + qy * z - qz * y; + const iy = qw * y + qz * x - qx * z; + const iz = qw * z + qx * y - qy * x; + const iw = - qx * x - qy * y - qz * z; - } + // calculate result * inverse quat - if ( json.skins === undefined ) json.skins = []; + this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; + this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; + this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute$1( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); + return this; - const skinIndex = node.skin = json.skins.length - 1; + } - return skinIndex; + project( camera ) { + + return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); } - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - processNode( object ) { + unproject( camera ) { - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; + return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - if ( ! json.nodes ) json.nodes = []; + } - const nodeDef = {}; + transformDirection( m ) { - if ( options.trs ) { + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction - const rotation = object.quaternion.toArray(); - const position = object.position.toArray(); - const scale = object.scale.toArray(); + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + 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; - nodeDef.rotation = rotation; + return this.normalize(); - } + } - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + divide( v ) { - nodeDef.translation = position; + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; - } + return this; - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + } - nodeDef.scale = scale; + divideScalar( scalar ) { - } + return this.multiplyScalar( 1 / scalar ); - } else { + } - if ( object.matrixAutoUpdate ) { + min( v ) { - object.updateMatrix(); + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); - } + return this; - if ( isIdentityMatrix( object.matrix ) === false ) { + } - nodeDef.matrix = object.matrix.elements; + max( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); - } + return this; - } + } - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); + clamp( min, max ) { - this.serializeUserData( object, nodeDef ); + // assumes min < max, componentwise - if ( object.isMesh || object.isLine || object.isPoints ) { + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - const meshIndex = this.processMesh( object ); + return this; - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + } - } else if ( object.isCamera ) { + clampScalar( minVal, maxVal ) { - nodeDef.camera = this.processCamera( object ); + this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); + this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - } + return this; - if ( object.isSkinnedMesh ) this.skins.push( object ); + } - if ( object.children.length > 0 ) { + clampLength( min, max ) { - const children = []; + const length = this.length(); - for ( let i = 0, l = object.children.length; i < l; i ++ ) { + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - const child = object.children[ i ]; + } - if ( child.visible || options.onlyVisible === false ) { + floor() { - const nodeIndex = this.processNode( child ); + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); - if ( nodeIndex !== null ) children.push( nodeIndex ); + return this; - } + } - } + ceil() { - if ( children.length > 0 ) nodeDef.children = children; + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); - } + return this; - this._invokeAll( function ( ext ) { + } - ext.writeNode && ext.writeNode( object, nodeDef ); + round() { - } ); + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - return nodeIndex; + return this; } - /** - * Process Scene - * @param {Scene} node Scene to process - */ - processScene( scene ) { - - const json = this.json; - const options = this.options; + roundToZero() { - if ( ! json.scenes ) { + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - json.scenes = []; - json.scene = 0; + return this; - } + } - const sceneDef = {}; + negate() { - if ( scene.name !== '' ) sceneDef.name = scene.name; + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; - json.scenes.push( sceneDef ); + return this; - const nodes = []; + } - for ( let i = 0, l = scene.children.length; i < l; i ++ ) { + dot( v ) { - const child = scene.children[ i ]; + return this.x * v.x + this.y * v.y + this.z * v.z; - if ( child.visible || options.onlyVisible === false ) { + } - const nodeIndex = this.processNode( child ); + // TODO lengthSquared? - if ( nodeIndex !== null ) nodes.push( nodeIndex ); + lengthSq() { - } + return this.x * this.x + this.y * this.y + this.z * this.z; - } + } - if ( nodes.length > 0 ) sceneDef.nodes = nodes; + length() { - this.serializeUserData( scene, sceneDef ); + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); } - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - processObjects( objects ) { - - const scene = new Scene(); - scene.name = 'AuxScene'; + manhattanLength() { - for ( let i = 0; i < objects.length; i ++ ) { + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); + } - } + normalize() { - this.processScene( scene ); + return this.divideScalar( this.length() || 1 ); } - /** - * @param {THREE.Object3D|Array} input - */ - processInput( input ) { + setLength( length ) { - const options = this.options; + return this.normalize().multiplyScalar( length ); - input = input instanceof Array ? input : [ input ]; + } - this._invokeAll( function ( ext ) { + lerp( v, alpha ) { - ext.beforeParse && ext.beforeParse( input ); + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; - } ); + return this; - const objectsWithoutScene = []; + } - for ( let i = 0; i < input.length; i ++ ) { + lerpVectors( v1, v2, alpha ) { - if ( input[ i ] instanceof Scene ) { + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.processScene( input[ i ] ); + return this; - } else { + } - objectsWithoutScene.push( input[ i ] ); + cross( v ) { - } + return this.crossVectors( this, v ); - } + } - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + crossVectors( a, b ) { - for ( let i = 0; i < this.skins.length; ++ i ) { + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; - this.processSkin( this.skins[ i ] ); + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; - } + return this; - for ( let i = 0; i < options.animations.length; ++ i ) { + } - this.processAnimation( options.animations[ i ], input[ 0 ] ); + projectOnVector( v ) { - } + const denominator = v.lengthSq(); - this._invokeAll( function ( ext ) { + if ( denominator === 0 ) return this.set( 0, 0, 0 ); - ext.afterParse && ext.afterParse( input ); + const scalar = v.dot( this ) / denominator; - } ); + return this.copy( v ).multiplyScalar( scalar ); } - _invokeAll( func ) { - - for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { + projectOnPlane( planeNormal ) { - func( this.plugins[ i ] ); + _vector$2.copy( this ).projectOnVector( planeNormal ); - } + return this.sub( _vector$2 ); } -} - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ -class GLTFLightExtension { + reflect( normal ) { - constructor( writer ) { + // reflect incident vector off plane orthogonal to normal + // normal is assumed to have unit length - this.writer = writer; - this.name = 'KHR_lights_punctual'; + return this.sub( _vector$2.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); } - writeNode( light, nodeDef ) { + angleTo( v ) { - if ( ! light.isLight ) return; + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + if ( denominator === 0 ) return Math.PI / 2; - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; + const theta = this.dot( v ) / denominator; - } + // clamp, to handle numerical problems - const writer = this.writer; - const json = writer.json; - const extensionsUsed = writer.extensionsUsed; + return Math.acos( clamp( theta, - 1, 1 ) ); - const lightDef = {}; + } - if ( light.name ) lightDef.name = light.name; + distanceTo( v ) { - lightDef.color = light.color.toArray(); + return Math.sqrt( this.distanceToSquared( v ) ); - lightDef.intensity = light.intensity; + } - if ( light.isDirectionalLight ) { + distanceToSquared( v ) { - lightDef.type = 'directional'; + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - } else if ( light.isPointLight ) { + return dx * dx + dy * dy + dz * dz; - lightDef.type = 'point'; + } - if ( light.distance > 0 ) lightDef.range = light.distance; + manhattanDistanceTo( v ) { - } else if ( light.isSpotLight ) { + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - lightDef.type = 'spot'; + } - if ( light.distance > 0 ) lightDef.range = light.distance; + setFromSpherical( s ) { - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; - lightDef.spot.outerConeAngle = light.angle; + return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); - } + } - if ( light.decay !== undefined && light.decay !== 2 ) { + setFromSphericalCoords( radius, phi, theta ) { - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); + const sinPhiRadius = Math.sin( phi ) * radius; - } + this.x = sinPhiRadius * Math.sin( theta ); + this.y = Math.cos( phi ) * radius; + this.z = sinPhiRadius * Math.cos( theta ); - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { + return this; - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); + } - } + setFromCylindrical( c ) { - if ( ! extensionsUsed[ this.name ] ) { + return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; + } - } + setFromCylindricalCoords( radius, theta, y ) { - const lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); + this.x = radius * Math.sin( theta ); + this.y = y; + this.z = radius * Math.cos( theta ); - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + return this; } -} + setFromMatrixPosition( m ) { -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ -class GLTFMaterialsUnlitExtension { + const e = m.elements; - constructor( writer ) { + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; - this.writer = writer; - this.name = 'KHR_materials_unlit'; + return this; } - writeMaterial( material, materialDef ) { + setFromMatrixScale( m ) { - if ( ! material.isMeshBasicMaterial ) return; + const sx = this.setFromMatrixColumn( m, 0 ).length(); + const sy = this.setFromMatrixColumn( m, 1 ).length(); + const sz = this.setFromMatrixColumn( m, 2 ).length(); - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + this.x = sx; + this.y = sy; + this.z = sz; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; + return this; - extensionsUsed[ this.name ] = true; + } - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + setFromMatrixColumn( m, index ) { + + return this.fromArray( m.elements, index * 4 ); } -} + setFromMatrix3Column( m, index ) { -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - */ -class GLTFMaterialsClearcoatExtension { + return this.fromArray( m.elements, index * 3 ); - constructor( writer ) { + } - this.writer = writer; - this.name = 'KHR_materials_clearcoat'; + setFromEuler( e ) { + + this.x = e._x; + this.y = e._y; + this.z = e._z; + + return this; } - writeMaterial( material, materialDef ) { + setFromColor( c ) { - if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; + this.x = c.r; + this.y = c.g; + this.z = c.b; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - extensionDef.clearcoatFactor = material.clearcoat; + equals( v ) { - if ( material.clearcoatMap ) { + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - const clearcoatMapDef = { - index: writer.processTexture( material.clearcoatMap ), - texCoord: material.clearcoatMap.channel - }; - writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); - extensionDef.clearcoatTexture = clearcoatMapDef; + } - } + fromArray( array, offset = 0 ) { - extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; - if ( material.clearcoatRoughnessMap ) { + return this; - const clearcoatRoughnessMapDef = { - index: writer.processTexture( material.clearcoatRoughnessMap ), - texCoord: material.clearcoatRoughnessMap.channel - }; - writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); - extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; + } - } + toArray( array = [], offset = 0 ) { - if ( material.clearcoatNormalMap ) { + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; - const clearcoatNormalMapDef = { - index: writer.processTexture( material.clearcoatNormalMap ), - texCoord: material.clearcoatNormalMap.channel - }; - writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); - extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; + return array; - } + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + fromBufferAttribute( attribute, index ) { - extensionsUsed[ this.name ] = true; + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + return this; } -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - */ -class GLTFMaterialsIridescenceExtension { + random() { - constructor( writer ) { + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); - this.writer = writer; - this.name = 'KHR_materials_iridescence'; + return this; } - writeMaterial( material, materialDef ) { + randomDirection() { - if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; + // Derived from https://mathworld.wolfram.com/SpherePointPicking.html - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + const u = ( Math.random() - 0.5 ) * 2; + const t = Math.random() * Math.PI * 2; + const f = Math.sqrt( 1 - u ** 2 ); - const extensionDef = {}; + this.x = f * Math.cos( t ); + this.y = f * Math.sin( t ); + this.z = u; - extensionDef.iridescenceFactor = material.iridescence; + return this; - if ( material.iridescenceMap ) { + } - const iridescenceMapDef = { - index: writer.processTexture( material.iridescenceMap ), - texCoord: material.iridescenceMap.channel - }; - writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); - extensionDef.iridescenceTexture = iridescenceMapDef; + *[ Symbol.iterator ]() { - } + yield this.x; + yield this.y; + yield this.z; - extensionDef.iridescenceIor = material.iridescenceIOR; - extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; - extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; + } - if ( material.iridescenceThicknessMap ) { +} - const iridescenceThicknessMapDef = { - index: writer.processTexture( material.iridescenceThicknessMap ), - texCoord: material.iridescenceThicknessMap.channel - }; - writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); - extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; +const _vector$2 = /*@__PURE__*/ new Vector3(); +const _quaternion = /*@__PURE__*/ new Quaternion(); - } +class Vector2 { - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + constructor( x = 0, y = 0 ) { - extensionsUsed[ this.name ] = true; + Vector2.prototype.isVector2 = true; + + this.x = x; + this.y = y; } -} + get width() { -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - */ -class GLTFMaterialsTransmissionExtension { + return this.x; - constructor( writer ) { + } - this.writer = writer; - this.name = 'KHR_materials_transmission'; + set width( value ) { - } + this.x = value; - writeMaterial( material, materialDef ) { + } - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + get height() { - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this.y; - const extensionDef = {}; + } - extensionDef.transmissionFactor = material.transmission; + set height( value ) { - if ( material.transmissionMap ) { + this.y = value; - const transmissionMapDef = { - index: writer.processTexture( material.transmissionMap ), - texCoord: material.transmissionMap.channel - }; - writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); - extensionDef.transmissionTexture = transmissionMapDef; + } - } + set( x, y ) { - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + this.x = x; + this.y = y; - extensionsUsed[ this.name ] = true; + return this; } -} + setScalar( scalar ) { -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ -class GLTFMaterialsVolumeExtension { + this.x = scalar; + this.y = scalar; - constructor( writer ) { + return this; - this.writer = writer; - this.name = 'KHR_materials_volume'; + } + + setX( x ) { + + this.x = x; + + return this; } - writeMaterial( material, materialDef ) { + setY( y ) { - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; + this.y = y; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - extensionDef.thicknessFactor = material.thickness; + setComponent( index, value ) { - if ( material.thicknessMap ) { + switch ( index ) { - const thicknessMapDef = { - index: writer.processTexture( material.thicknessMap ), - texCoord: material.thicknessMap.channel - }; - writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); - extensionDef.thicknessTexture = thicknessMapDef; + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( 'index is out of range: ' + index ); } - extensionDef.attenuationDistance = material.attenuationDistance; - extensionDef.attenuationColor = material.attenuationColor.toArray(); + return this; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + getComponent( index ) { - } + switch ( index ) { -} + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - */ -class GLTFMaterialsIorExtension { + } - constructor( writer ) { + } - this.writer = writer; - this.name = 'KHR_materials_ior'; + clone() { + + return new this.constructor( this.x, this.y ); } - writeMaterial( material, materialDef ) { + copy( v ) { - if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; + this.x = v.x; + this.y = v.y; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - extensionDef.ior = material.ior; + add( v ) { - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + this.x += v.x; + this.y += v.y; - extensionsUsed[ this.name ] = true; + return this; } -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - */ -class GLTFMaterialsSpecularExtension { + addScalar( s ) { - constructor( writer ) { + this.x += s; + this.y += s; - this.writer = writer; - this.name = 'KHR_materials_specular'; + return this; } - writeMaterial( material, materialDef ) { + addVectors( a, b ) { - if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && - material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; + this.x = a.x + b.x; + this.y = a.y + b.y; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - if ( material.specularIntensityMap ) { + addScaledVector( v, s ) { - const specularIntensityMapDef = { - index: writer.processTexture( material.specularIntensityMap ), - texCoord: material.specularIntensityMap.channel - }; - writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); - extensionDef.specularTexture = specularIntensityMapDef; + this.x += v.x * s; + this.y += v.y * s; - } + return this; - if ( material.specularColorMap ) { + } - const specularColorMapDef = { - index: writer.processTexture( material.specularColorMap ), - texCoord: material.specularColorMap.channel - }; - writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); - extensionDef.specularColorTexture = specularColorMapDef; + sub( v ) { - } + this.x -= v.x; + this.y -= v.y; - extensionDef.specularFactor = material.specularIntensity; - extensionDef.specularColorFactor = material.specularColor.toArray(); + return this; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + subScalar( s ) { - } + this.x -= s; + this.y -= s; -} + return this; -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - */ -class GLTFMaterialsSheenExtension { + } - constructor( writer ) { + subVectors( a, b ) { - this.writer = writer; - this.name = 'KHR_materials_sheen'; + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; } - writeMaterial( material, materialDef ) { + multiply( v ) { - if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; + this.x *= v.x; + this.y *= v.y; - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - if ( material.sheenRoughnessMap ) { + multiplyScalar( scalar ) { - const sheenRoughnessMapDef = { - index: writer.processTexture( material.sheenRoughnessMap ), - texCoord: material.sheenRoughnessMap.channel - }; - writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); - extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; + this.x *= scalar; + this.y *= scalar; - } + return this; - if ( material.sheenColorMap ) { + } - const sheenColorMapDef = { - index: writer.processTexture( material.sheenColorMap ), - texCoord: material.sheenColorMap.channel - }; - writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); - extensionDef.sheenColorTexture = sheenColorMapDef; + divide( v ) { - } + this.x /= v.x; + this.y /= v.y; - extensionDef.sheenRoughnessFactor = material.sheenRoughness; - extensionDef.sheenColorFactor = material.sheenColor.toArray(); + return this; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + } - extensionsUsed[ this.name ] = true; + divideScalar( scalar ) { + + return this.multiplyScalar( 1 / scalar ); } -} + applyMatrix3( m ) { -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - */ -class GLTFMaterialsEmissiveStrengthExtension { + const x = this.x, y = this.y; + const e = m.elements; - constructor( writer ) { + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; - this.writer = writer; - this.name = 'KHR_materials_emissive_strength'; + return this; } - writeMaterial( material, materialDef ) { + min( v ) { - if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; + return this; - const extensionDef = {}; + } - extensionDef.emissiveStrength = material.emissiveIntensity; + max( v ) { - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); - extensionsUsed[ this.name ] = true; + return this; } -} + clamp( min, max ) { -/** - * Static utility functions - */ -GLTFExporter.Utils = { + // assumes min < max, componentwise - insertKeyframe: function ( track, time ) { + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); + return this; - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + } - let index; + clampScalar( minVal, maxVal ) { - if ( track.times.length === 0 ) { + this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); + this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - times[ 0 ] = time; + return this; - for ( let i = 0; i < valueSize; i ++ ) { + } - values[ i ] = 0; + clampLength( min, max ) { - } + const length = this.length(); - index = 0; + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - } else if ( time < track.times[ 0 ] ) { + } - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + floor() { - times[ 0 ] = time; - times.set( track.times, 1 ); + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + return this; - index = 0; + } - } else if ( time > track.times[ track.times.length - 1 ] ) { + ceil() { - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); - return track.times.length - 1; + return this; - } + } - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); + round() { - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); - index = times.length - 1; + return this; - } else { + } - for ( let i = 0; i < track.times.length; i ++ ) { + roundToZero() { - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + return this; - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + } - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + negate() { - index = i + 1; + this.x = - this.x; + this.y = - this.y; - break; + return this; - } + } - } + dot( v ) { - } + return this.x * v.x + this.y * v.y; - track.times = times; - track.values = values; + } - return index; + cross( v ) { - }, + return this.x * v.y - this.y * v.x; - mergeMorphTargetTracks: function ( clip, root ) { + } - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; + lengthSq() { - for ( let i = 0; i < sourceTracks.length; ++ i ) { + return this.x * this.x + this.y * this.y; - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + } - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + length() { - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + return Math.sqrt( this.x * this.x + this.y * this.y ); - } + } - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + manhattanLength() { - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + return Math.abs( this.x ) + Math.abs( this.y ); - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + } - } + normalize() { - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + return this.divideScalar( this.length() || 1 ); - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + } - } + angle() { - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + // computes the angle in radians with respect to the positive x-axis - if ( targetIndex === undefined ) { + const angle = Math.atan2( - this.y, - this.x ) + Math.PI; - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + return angle; - } + } - let mergedTrack; + angleTo( v ) { - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - mergedTrack = sourceTrack.clone(); + if ( denominator === 0 ) return Math.PI / 2; - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + const theta = this.dot( v ) / denominator; - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + // clamp, to handle numerical problems - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + return Math.acos( clamp( theta, - 1, 1 ) ); - } + } - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; + distanceTo( v ) { - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + return Math.sqrt( this.distanceToSquared( v ) ); - continue; + } - } + distanceToSquared( v ) { - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + const dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + } - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + manhattanDistanceTo( v ) { - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - } + } - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { + setLength( length ) { - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + return this.normalize().multiplyScalar( length ); - } + } - } + lerp( v, alpha ) { - clip.tracks = tracks; + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; - return clip; + return this; } -}; + lerpVectors( v1, v2, alpha ) { -class Quaternion { + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; - constructor( x = 0, y = 0, z = 0, w = 1 ) { + return this; - this.isQuaternion = true; + } - this._x = x; - this._y = y; - this._z = z; - this._w = w; + equals( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); } - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { + fromArray( array, offset = 0 ) { - // fuzz-free, array-based Quaternion SLERP operation + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; + return this; - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; + } - if ( t === 0 ) { + toArray( array = [], offset = 0 ) { - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; - } + return array; - if ( t === 1 ) { + } - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; + fromBufferAttribute( attribute, index ) { - } + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + return this; - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; + } - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { + rotateAround( center, angle ) { - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); + const c = Math.cos( angle ), s = Math.sin( angle ); - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; + const x = this.x - center.x; + const y = this.y - center.y; - } + this.x = x * c - y * s + center.x; + this.y = x * s + y * c + center.y; - const tDir = t * dir; + return this; - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; + } - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { + random() { - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + this.x = Math.random(); + this.y = Math.random(); - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; + return this; - } + } - } + *[ Symbol.iterator ]() { - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; + yield this.x; + yield this.y; } - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { +} - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; +const StaticDrawUsage = 35044; - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; +const _vector$1 = /*@__PURE__*/ new Vector3(); +const _vector2 = /*@__PURE__*/ new Vector2(); - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; +class BufferAttribute { - return dst; + constructor( array, itemSize, normalized = false ) { - } + if ( Array.isArray( array ) ) { - get x() { + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - return this._x; + } - } + this.isBufferAttribute = true; - set x( value ) { + this.name = ''; - this._x = value; - this._onChangeCallback(); + this.array = array; + this.itemSize = itemSize; + this.count = array !== undefined ? array.length / itemSize : 0; + this.normalized = normalized; + + this.usage = StaticDrawUsage; + this.updateRange = { offset: 0, count: - 1 }; + + this.version = 0; } - get y() { + onUploadCallback() {} - return this._y; + set needsUpdate( value ) { + + if ( value === true ) this.version ++; } - set y( value ) { + setUsage( value ) { - this._y = value; - this._onChangeCallback(); + this.usage = value; + + return this; } - get z() { + copy( source ) { - return this._z; + this.name = source.name; + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; + this.count = source.count; + this.normalized = source.normalized; + + this.usage = source.usage; + + return this; } - set z( value ) { + copyAt( index1, attribute, index2 ) { - this._z = value; - this._onChangeCallback(); + index1 *= this.itemSize; + index2 *= attribute.itemSize; - } + for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - get w() { + this.array[ index1 + i ] = attribute.array[ index2 + i ]; - return this._w; + } + + return this; } - set w( value ) { + copyArray( array ) { - this._w = value; - this._onChangeCallback(); + this.array.set( array ); + + return this; } - set( x, y, z, w ) { + applyMatrix3( m ) { - this._x = x; - this._y = y; - this._z = z; - this._w = w; + if ( this.itemSize === 2 ) { - this._onChangeCallback(); + for ( let i = 0, l = this.count; i < l; i ++ ) { - return this; + _vector2.fromBufferAttribute( this, i ); + _vector2.applyMatrix3( m ); - } + this.setXY( i, _vector2.x, _vector2.y ); - clone() { + } - return new this.constructor( this._x, this._y, this._z, this._w ); + } else if ( this.itemSize === 3 ) { - } + for ( let i = 0, l = this.count; i < l; i ++ ) { - copy( quaternion ) { + _vector$1.fromBufferAttribute( this, i ); + _vector$1.applyMatrix3( m ); - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; + this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); - this._onChangeCallback(); + } + + } return this; } - setFromEuler( euler, update ) { - - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; + applyMatrix4( m ) { - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m + for ( let i = 0, l = this.count; i < l; i ++ ) { - const cos = Math.cos; - const sin = Math.sin; + _vector$1.fromBufferAttribute( this, i ); - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); + _vector$1.applyMatrix4( m ); - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); + this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); - switch ( order ) { + } - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + return this; - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + } - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + applyNormalMatrix( m ) { - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + for ( let i = 0, l = this.count; i < l; i ++ ) { - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + _vector$1.fromBufferAttribute( this, i ); - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + _vector$1.applyNormalMatrix( m ); - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); + this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); } - if ( update !== false ) this._onChangeCallback(); - return this; } - setFromAxisAngle( axis, angle ) { + transformDirection( m ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + for ( let i = 0, l = this.count; i < l; i ++ ) { - // assumes axis is normalized + _vector$1.fromBufferAttribute( this, i ); - const halfAngle = angle / 2, s = Math.sin( halfAngle ); + _vector$1.transformDirection( m ); - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); + this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); - this._onChangeCallback(); + } return this; } - setFromRotationMatrix( m ) { + set( value, offset = 0 ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + // Matching BufferAttribute constructor, do not normalize the array. + this.array.set( value, offset ); - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + return this; - const te = m.elements, + } - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + getX( index ) { - trace = m11 + m22 + m33; + let x = this.array[ index * this.itemSize ]; - if ( trace > 0 ) { + if ( this.normalized ) x = denormalize( x, this.array ); - const s = 0.5 / Math.sqrt( trace + 1.0 ); + return x; - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; + } - } else if ( m11 > m22 && m11 > m33 ) { + setX( index, x ) { - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + if ( this.normalized ) x = normalize( x, this.array ); - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; + this.array[ index * this.itemSize ] = x; - } else if ( m22 > m33 ) { + return this; - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + } - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; + getY( index ) { - } else { + let y = this.array[ index * this.itemSize + 1 ]; - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + if ( this.normalized ) y = denormalize( y, this.array ); - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; + return y; - } + } - this._onChangeCallback(); + setY( index, y ) { + + if ( this.normalized ) y = normalize( y, this.array ); + + this.array[ index * this.itemSize + 1 ] = y; return this; } - setFromUnitVectors( vFrom, vTo ) { + getZ( index ) { - // assumes direction vectors vFrom and vTo are normalized + let z = this.array[ index * this.itemSize + 2 ]; - let r = vFrom.dot( vTo ) + 1; + if ( this.normalized ) z = denormalize( z, this.array ); - if ( r < Number.EPSILON ) { + return z; - // vFrom and vTo point in opposite directions + } - r = 0; + setZ( index, z ) { - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + if ( this.normalized ) z = normalize( z, this.array ); - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; + this.array[ index * this.itemSize + 2 ] = z; - } else { + return this; - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; + } - } + getW( index ) { - } else { + let w = this.array[ index * this.itemSize + 3 ]; - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 + if ( this.normalized ) w = denormalize( w, this.array ); - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; + return w; - } + } - return this.normalize(); + setW( index, w ) { - } + if ( this.normalized ) w = normalize( w, this.array ); - angleTo( q ) { + this.array[ index * this.itemSize + 3 ] = w; - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); + return this; } - rotateTowards( q, step ) { + setXY( index, x, y ) { - const angle = this.angleTo( q ); + index *= this.itemSize; - if ( angle === 0 ) return this; + if ( this.normalized ) { - const t = Math.min( 1, step / angle ); + x = normalize( x, this.array ); + y = normalize( y, this.array ); - this.slerp( q, t ); + } + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; return this; } - identity() { + setXYZ( index, x, y, z ) { - return this.set( 0, 0, 0, 1 ); + index *= this.itemSize; - } + if ( this.normalized ) { - invert() { + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); - // quaternion is assumed to have unit length + } - return this.conjugate(); + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + + return this; } - conjugate() { + setXYZW( index, x, y, z, w ) { - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; + index *= this.itemSize; - this._onChangeCallback(); + if ( this.normalized ) { - return this; + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); - } + } - dot( v ) { + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + return this; } - lengthSq() { + onUpload( callback ) { - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + this.onUploadCallback = callback; + + return this; } - length() { + clone() { - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + return new this.constructor( this.array, this.itemSize ).copy( this ); } - normalize() { + toJSON() { - let l = this.length(); + const data = { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: Array.from( this.array ), + normalized: this.normalized + }; - if ( l === 0 ) { + if ( this.name !== '' ) data.name = this.name; + if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; + if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange; - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; + return data; - } else { + } - l = 1 / l; + copyColorsArray() { // @deprecated, r144 - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; + console.error( 'THREE.BufferAttribute: copyColorsArray() was removed in r144.' ); - } + } - this._onChangeCallback(); + copyVector2sArray() { // @deprecated, r144 - return this; + console.error( 'THREE.BufferAttribute: copyVector2sArray() was removed in r144.' ); } - multiply( q ) { + copyVector3sArray() { // @deprecated, r144 - return this.multiplyQuaternions( this, q ); + console.error( 'THREE.BufferAttribute: copyVector3sArray() was removed in r144.' ); } - premultiply( q ) { + copyVector4sArray() { // @deprecated, r144 - return this.multiplyQuaternions( q, this ); + console.error( 'THREE.BufferAttribute: copyVector4sArray() was removed in r144.' ); } - multiplyQuaternions( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm +} - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; +class FragmentMesh extends InstancedMesh { + constructor(geometry, material, count, fragment) { + super(geometry, material, count); + this.elementCount = 0; + this.exportOptions = { + trs: false, + onlyVisible: false, + truncateDrawRange: true, + binary: true, + maxTextureSize: 0, + }; + this.exporter = new GLTFExporter(); + this.material = FragmentMesh.newMaterialArray(material); + this.geometry = this.newFragmentGeometry(geometry); + this.fragment = fragment; + } + exportData() { + const position = this.geometry.attributes.position.array; + const normal = this.geometry.attributes.normal.array; + const blockID = Array.from(this.geometry.attributes.blockID.array); + const index = Array.from(this.geometry.index.array); + const groups = []; + for (const group of this.geometry.groups) { + const index = group.materialIndex || 0; + const { start, count } = group; + groups.push(start, count, index); + } + const materials = []; + if (Array.isArray(this.material)) { + for (const material of this.material) { + const opacity = material.opacity; + const transparent = material.transparent ? 1 : 0; + const color = new Color(material.color).toArray(); + materials.push(opacity, transparent, ...color); + } + } + const matrices = Array.from(this.instanceMatrix.array); + let colors; + if (this.instanceColor !== null) { + colors = Array.from(this.instanceColor.array); + } + else { + colors = []; + } + return { + position, + normal, + index, + blockID, + groups, + materials, + matrices, + colors, + }; + } + export() { + const mesh = this; + return new Promise((resolve) => { + this.exporter.parse(mesh, (geometry) => resolve(geometry), (error) => console.log(error), this.exportOptions); + }); + } + newFragmentGeometry(geometry) { + if (!geometry.index) { + throw new Error("The geometry must be indexed!"); + } + if (!geometry.attributes.blockID) { + const vertexSize = geometry.attributes.position.count; + const array = new Uint16Array(vertexSize); + array.fill(this.elementCount++); + geometry.attributes.blockID = new BufferAttribute(array, 1); + } + const size = geometry.index.count; + FragmentMesh.initializeGroups(geometry, size); + return geometry; + } + static initializeGroups(geometry, size) { + if (!geometry.groups.length) { + geometry.groups.push({ + start: 0, + count: size, + materialIndex: 0, + }); + } + } + static newMaterialArray(material) { + if (!Array.isArray(material)) + material = [material]; + return material; + } +} - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; +/** + * Contains the logic to get, create and delete geometric subsets of an IFC model. For example, + * this can extract all the items in a specific IfcBuildingStorey and create a new Mesh. + */ +class Blocks { + get count() { + return this.ids.size; + } + constructor(fragment) { + this.fragment = fragment; + this._visibilityInitialized = false; + this._originalIndex = new Map(); + this._idIndexIndexMap = {}; + const rawIds = fragment.mesh.geometry.attributes.blockID.array; + this.ids = new Set(rawIds); + this.visibleIds = new Set(this.ids); + } + setVisibility(visible, itemIDs = new Set(this.fragment.items), isolate = false) { + const geometry = this.fragment.mesh.geometry; + const index = geometry.index; + if (!this._visibilityInitialized) { + this.initializeVisibility(index, geometry); + } + if (isolate) { + index.array.fill(0); + } + for (const id of itemIDs) { + const indices = this._idIndexIndexMap[id]; + if (!indices) + continue; + for (const i of indices) { + const originalIndex = this._originalIndex.get(i); + if (originalIndex === undefined) + continue; + const blockID = geometry.attributes.blockID.getX(originalIndex); + const itemID = this.fragment.items[blockID]; + if (itemIDs.has(itemID)) { + if (visible) { + this.visibleIds.add(blockID); + } + else { + this.visibleIds.delete(blockID); + } + const newIndex = visible ? originalIndex : 0; + index.setX(i, newIndex); + } + } + } + index.needsUpdate = true; + } + initializeVisibility(index, geometry) { + for (let i = 0; i < index.count; i++) { + const foundIndex = index.getX(i); + this._originalIndex.set(i, foundIndex); + const blockID = geometry.attributes.blockID.getX(foundIndex); + const itemID = this.fragment.getItemID(0, blockID); + if (!this._idIndexIndexMap[itemID]) { + this._idIndexIndexMap[itemID] = []; + } + this._idIndexIndexMap[itemID].push(i); + } + this._visibilityInitialized = true; + } + // Use this only for destroying the current Fragment instance + dispose() { + this._idIndexIndexMap = {}; + this.ids.clear(); + this.visibleIds.clear(); + this._originalIndex.clear(); + this.ids = null; + this.visibleIds = null; + this._originalIndex = null; + } +} - this._onChangeCallback(); +// Source: https://github.com/gkjohnson/three-mesh-bvh +class BVH { + static apply(geometry) { + if (!BVH.initialized) { + BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; + BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; + Mesh.prototype.raycast = acceleratedRaycast; + BVH.initialized = true; + } + if (!geometry.boundsTree) { + geometry.computeBoundsTree(); + } + } + static dispose(geometry) { + geometry.disposeBoundsTree(); + } +} +BVH.initialized = false; - return this; +/* + * Fragments can contain one or multiple Instances of one or multiple Blocks + * Each Instance is identified by an instanceID (property of THREE.InstancedMesh) + * Each Block identified by a blockID (custom bufferAttribute per vertex) + * Both instanceId and blockId are unsigned integers starting at 0 and going up sequentially + * A specific Block of a specific Instance is an Item, identified by an itemID + * + * For example: + * Imagine a fragment mesh with 8 instances and 2 elements (16 items, identified from A to P) + * It will have instanceIds from 0 to 8, and blockIds from 0 to 2 + * If we raycast it, we will get an instanceId and the index of the found triangle + * We can use the index to get the blockId for that triangle + * Combining instanceId and blockId using the elementMap will give us the itemId + * The items will look like this: + * + * [ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P ] + * + * Where the criteria to sort the items is the following (Y-axis is instance, X-axis is block): + * + * A C E G I K M O + * B D F H J L N P + * */ +let Fragment$1 = class Fragment { + get ids() { + const ids = new Set(); + for (const id of this.items) { + ids.add(id); + } + for (const id in this.hiddenInstances) { + ids.add(id); + } + return ids; + } + constructor(geometry, material, count) { + this.fragments = {}; + this.items = []; + this.hiddenInstances = {}; + // When multiple instances represent the same object + // this allows to create a composite ID for each instance + // E.g. all the steps in a stair are a single thing + // so if the ID of the stair is asdf, then each step could be + // asdf.1, asdf.2, asdf.3, etc + // the value is the number of instances + this.composites = {}; + this.mesh = new FragmentMesh(geometry, material, count, this); + this.id = this.mesh.uuid; + this.capacity = count; + this.blocks = new Blocks(this); + BVH.apply(geometry); + } + dispose(disposeResources = true) { + this.items = null; + this.group = undefined; + if (this.mesh) { + if (disposeResources) { + this.mesh.material.forEach((mat) => mat.dispose()); + this.mesh.material = []; + BVH.dispose(this.mesh.geometry); + this.mesh.geometry.dispose(); + this.mesh.geometry = null; + } + this.mesh.removeFromParent(); + this.mesh.dispose(); + this.mesh.fragment = null; + this.mesh = null; + } + this.disposeNestedFragments(); + } + getItemID(instanceID, blockID) { + const index = this.getItemIndex(instanceID, blockID); + return this.items[index]; + } + getInstanceAndBlockID(itemID) { + const index = this.items.indexOf(itemID); + const instanceID = this.getInstanceIDFromIndex(index); + const blockID = index % this.blocks.count; + return { instanceID, blockID }; + } + getVertexBlockID(geometry, index) { + const blocks = geometry.attributes.blockID; + return blocks.array[index]; + } + getItemData(itemID) { + const index = this.items.indexOf(itemID); + const instanceID = Math.ceil(index / this.blocks.count); + const blockID = index % this.blocks.count; + return { instanceID, blockID }; + } + getInstance(instanceID, matrix) { + return this.mesh.getMatrixAt(instanceID, matrix); + } + setInstance(instanceID, items) { + this.checkIfInstanceExist(instanceID); + this.mesh.setMatrixAt(instanceID, items.transform); + this.mesh.instanceMatrix.needsUpdate = true; + if (items.color && this.mesh.instanceColor) { + this.mesh.setColorAt(instanceID, items.color); + this.mesh.instanceColor.needsUpdate = true; + } + if (items.ids) { + this.saveItemsInMap(items.ids, instanceID); + } + } + addInstances(items) { + this.resizeCapacityIfNeeded(items.length); + const start = this.mesh.count; + this.mesh.count += items.length; + for (let i = 0; i < items.length; i++) { + this.setInstance(start + i, items[i]); + } + } + removeInstances(itemsIDs) { + if (this.mesh.count <= 1) { + this.clear(); + return; + } + this.deleteAndRearrangeInstances(itemsIDs); + this.mesh.count -= itemsIDs.length; + this.mesh.instanceMatrix.needsUpdate = true; + } + clear() { + this.mesh.clear(); + this.mesh.count = 0; + this.items = []; + } + addFragment(id, material = this.mesh.material) { + const newGeometry = this.initializeGeometry(); + if (material === this.mesh.material) { + this.copyGroups(newGeometry); + } + const newFragment = new Fragment(newGeometry, material, this.capacity); + newFragment.mesh.applyMatrix4(this.mesh.matrix); + newFragment.mesh.updateMatrix(); + this.fragments[id] = newFragment; + return this.fragments[id]; + } + removeFragment(id) { + const fragment = this.fragments[id]; + if (fragment) { + fragment.dispose(false); + delete this.fragments[id]; + } + } + resetVisibility() { + if (this.blocks.count > 1) { + this.blocks.setVisibility(true); + } + else { + const hiddenInstances = Object.keys(this.hiddenInstances); + this.makeInstancesVisible(hiddenInstances); + this.hiddenInstances = {}; + } + } + setVisibility(visible, itemIDs = this.ids) { + if (this.blocks.count > 1) { + this.blocks.setVisibility(visible, itemIDs); + } + else { + this.toggleInstanceVisibility(visible, itemIDs); + } + } + resize(size) { + var _a; + const newMesh = this.createFragmentMeshWithNewSize(size); + this.capacity = size; + const oldMesh = this.mesh; + (_a = oldMesh.parent) === null || _a === void 0 ? void 0 : _a.add(newMesh); + oldMesh.removeFromParent(); + this.mesh = newMesh; + oldMesh.dispose(); + } + exportData() { + const geometry = this.mesh.exportData(); + const ids = this.items.join("|"); + const id = this.id; + return { ...geometry, ids, id }; + } + copyGroups(newGeometry) { + newGeometry.groups = []; + for (const group of this.mesh.geometry.groups) { + newGeometry.groups.push({ ...group }); + } + } + initializeGeometry() { + const newGeometry = new THREE$1.BufferGeometry(); + newGeometry.setAttribute("position", this.mesh.geometry.attributes.position); + newGeometry.setAttribute("normal", this.mesh.geometry.attributes.normal); + newGeometry.setAttribute("blockID", this.mesh.geometry.attributes.blockID); + newGeometry.setIndex(Array.from(this.mesh.geometry.index.array)); + return newGeometry; + } + saveItemsInMap(ids, instanceId) { + this.checkBlockNumberValid(ids); + let counter = 0; + for (const id of ids) { + const index = this.getItemIndex(instanceId, counter); + this.items[index] = id; + counter++; + } + } + resizeCapacityIfNeeded(newSize) { + const necessaryCapacity = newSize + this.mesh.count; + if (necessaryCapacity > this.capacity) { + this.resize(necessaryCapacity); + } + } + createFragmentMeshWithNewSize(capacity) { + const newMesh = new FragmentMesh(this.mesh.geometry, this.mesh.material, capacity, this); + newMesh.count = this.mesh.count; + return newMesh; + } + disposeNestedFragments() { + const fragments = Object.values(this.fragments); + for (let i = 0; i < fragments.length; i++) { + fragments[i].dispose(); + } + this.fragments = {}; + } + checkBlockNumberValid(ids) { + if (ids.length > this.blocks.count) { + throw new Error(`You passed more items (${ids.length}) than blocks in this instance (${this.blocks.count})`); + } + } + checkIfInstanceExist(index) { + if (index > this.mesh.count) { + throw new Error(`The given index (${index}) exceeds the instances in this fragment (${this.mesh.count})`); + } + } + // Assigns the index of the removed instance to the last instance + // F.e. let there be 6 instances: (A) (B) (C) (D) (E) (F) + // If instance (C) is removed: -> (A) (B) (F) (D) (E) + deleteAndRearrangeInstances(ids) { + const deletedItems = []; + for (const id of ids) { + const deleted = this.deleteAndRearrange(id); + if (deleted) { + deletedItems.push(deleted); + } + } + for (const id of ids) { + delete this.hiddenInstances[id]; + } + return deletedItems; + } + deleteAndRearrange(id) { + const index = this.items.indexOf(id); + if (index === -1) + return null; + this.mesh.count--; + const isLastElement = index === this.mesh.count; + const instanceId = this.getInstanceIDFromIndex(index); + const tempMatrix = new THREE$1.Matrix4(); + const tempColor = new THREE$1.Color(); + const transform = new THREE$1.Matrix4(); + this.mesh.getMatrixAt(instanceId, transform); + const result = { ids: [id], transform }; + if (this.mesh.instanceColor) { + const color = new THREE$1.Color(); + this.mesh.getColorAt(instanceId, color); + result.color = color; + } + if (isLastElement) { + this.items.pop(); + return result; + } + const lastElement = this.mesh.count; + this.items[index] = this.items[lastElement]; + this.items.pop(); + this.mesh.getMatrixAt(lastElement, tempMatrix); + this.mesh.setMatrixAt(instanceId, tempMatrix); + this.mesh.instanceMatrix.needsUpdate = true; + if (this.mesh.instanceColor) { + this.mesh.getColorAt(lastElement, tempColor); + this.mesh.setColorAt(instanceId, tempColor); + this.mesh.instanceColor.needsUpdate = true; + } + return result; + } + getItemIndex(instanceId, blockId) { + return instanceId * this.blocks.count + blockId; + } + getInstanceIDFromIndex(itemIndex) { + return Math.trunc(itemIndex / this.blocks.count); + } + toggleInstanceVisibility(visible, itemIDs) { + if (visible) { + this.makeInstancesVisible(itemIDs); + } + else { + this.makeInstancesInvisible(itemIDs); + } + } + makeInstancesInvisible(itemIDs) { + itemIDs = this.filterHiddenItems(itemIDs, false); + const deletedItems = this.deleteAndRearrangeInstances(itemIDs); + for (const item of deletedItems) { + if (item.ids) { + this.hiddenInstances[item.ids[0]] = item; + } + } + } + makeInstancesVisible(itemIDs) { + const items = []; + itemIDs = this.filterHiddenItems(itemIDs, true); + for (const id of itemIDs) { + const found = this.hiddenInstances[id]; + if (found !== undefined) { + items.push(found); + delete this.hiddenInstances[id]; + } + } + this.addInstances(items); + } + filterHiddenItems(itemIDs, hidden) { + const hiddenItems = Object.keys(this.hiddenInstances); + const result = []; + for (const id of itemIDs) { + const isHidden = hidden && hiddenItems.includes(id); + const isNotHidden = !hidden && !hiddenItems.includes(id); + if (isHidden || isNotHidden) { + result.push(id); + } + } + return result; + } +}; - } +const SIZEOF_SHORT = 2; +const SIZEOF_INT = 4; +const FILE_IDENTIFIER_LENGTH = 4; +const SIZE_PREFIX_LENGTH = 4; - slerp( qb, t ) { +const int32 = new Int32Array(2); +const float32 = new Float32Array(int32.buffer); +const float64 = new Float64Array(int32.buffer); +const isLittleEndian = new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1; - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); +var Encoding; +(function (Encoding) { + Encoding[Encoding["UTF8_BYTES"] = 1] = "UTF8_BYTES"; + Encoding[Encoding["UTF16_STRING"] = 2] = "UTF16_STRING"; +})(Encoding || (Encoding = {})); - const x = this._x, y = this._y, z = this._z, w = this._w; +class ByteBuffer { + /** + * Create a new ByteBuffer with a given array of bytes (`Uint8Array`) + */ + constructor(bytes_) { + this.bytes_ = bytes_; + this.position_ = 0; + this.text_decoder_ = new TextDecoder(); + } + /** + * Create and allocate a new ByteBuffer with a given size. + */ + static allocate(byte_size) { + return new ByteBuffer(new Uint8Array(byte_size)); + } + clear() { + this.position_ = 0; + } + /** + * Get the underlying `Uint8Array`. + */ + bytes() { + return this.bytes_; + } + /** + * Get the buffer's position. + */ + position() { + return this.position_; + } + /** + * Set the buffer's position. + */ + setPosition(position) { + this.position_ = position; + } + /** + * Get the buffer's capacity. + */ + capacity() { + return this.bytes_.length; + } + readInt8(offset) { + return this.readUint8(offset) << 24 >> 24; + } + readUint8(offset) { + return this.bytes_[offset]; + } + readInt16(offset) { + return this.readUint16(offset) << 16 >> 16; + } + readUint16(offset) { + return this.bytes_[offset] | this.bytes_[offset + 1] << 8; + } + readInt32(offset) { + return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24; + } + readUint32(offset) { + return this.readInt32(offset) >>> 0; + } + readInt64(offset) { + return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); + } + readUint64(offset) { + return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); + } + readFloat32(offset) { + int32[0] = this.readInt32(offset); + return float32[0]; + } + readFloat64(offset) { + int32[isLittleEndian ? 0 : 1] = this.readInt32(offset); + int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4); + return float64[0]; + } + writeInt8(offset, value) { + this.bytes_[offset] = value; + } + writeUint8(offset, value) { + this.bytes_[offset] = value; + } + writeInt16(offset, value) { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + } + writeUint16(offset, value) { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + } + writeInt32(offset, value) { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + this.bytes_[offset + 2] = value >> 16; + this.bytes_[offset + 3] = value >> 24; + } + writeUint32(offset, value) { + this.bytes_[offset] = value; + this.bytes_[offset + 1] = value >> 8; + this.bytes_[offset + 2] = value >> 16; + this.bytes_[offset + 3] = value >> 24; + } + writeInt64(offset, value) { + this.writeInt32(offset, Number(BigInt.asIntN(32, value))); + this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32)))); + } + writeUint64(offset, value) { + this.writeUint32(offset, Number(BigInt.asUintN(32, value))); + this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32)))); + } + writeFloat32(offset, value) { + float32[0] = value; + this.writeInt32(offset, int32[0]); + } + writeFloat64(offset, value) { + float64[0] = value; + this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]); + this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]); + } + /** + * Return the file identifier. Behavior is undefined for FlatBuffers whose + * schema does not include a file_identifier (likely points at padding or the + * start of a the root vtable). + */ + getBufferIdentifier() { + if (this.bytes_.length < this.position_ + SIZEOF_INT + + FILE_IDENTIFIER_LENGTH) { + throw new Error('FlatBuffers: ByteBuffer is too short to contain an identifier.'); + } + let result = ""; + for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { + result += String.fromCharCode(this.readInt8(this.position_ + SIZEOF_INT + i)); + } + return result; + } + /** + * Look up a field in the vtable, return an offset into the object, or 0 if the + * field is not present. + */ + __offset(bb_pos, vtable_offset) { + const vtable = bb_pos - this.readInt32(bb_pos); + return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0; + } + /** + * Initialize any Table-derived type to point to the union at the given offset. + */ + __union(t, offset) { + t.bb_pos = offset + this.readInt32(offset); + t.bb = this; + return t; + } + /** + * Create a JavaScript string from UTF-8 data stored inside the FlatBuffer. + * This allocates a new string and converts to wide chars upon each access. + * + * To avoid the conversion to string, pass Encoding.UTF8_BYTES as the + * "optionalEncoding" argument. This is useful for avoiding conversion when + * the data will just be packaged back up in another FlatBuffer later on. + * + * @param offset + * @param opt_encoding Defaults to UTF16_STRING + */ + __string(offset, opt_encoding) { + offset += this.readInt32(offset); + const length = this.readInt32(offset); + offset += SIZEOF_INT; + const utf8bytes = this.bytes_.subarray(offset, offset + length); + if (opt_encoding === Encoding.UTF8_BYTES) + return utf8bytes; + else + return this.text_decoder_.decode(utf8bytes); + } + /** + * Handle unions that can contain string as its member, if a Table-derived type then initialize it, + * if a string then return a new one + * + * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this + * makes the behaviour of __union_with_string different compared to __union + */ + __union_with_string(o, offset) { + if (typeof o === 'string') { + return this.__string(offset); + } + return this.__union(o, offset); + } + /** + * Retrieve the relative offset stored at "offset" + */ + __indirect(offset) { + return offset + this.readInt32(offset); + } + /** + * Get the start of data of a vector whose offset is stored at "offset" in this object. + */ + __vector(offset) { + return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length + } + /** + * Get the length of a vector whose offset is stored at "offset" in this object. + */ + __vector_len(offset) { + return this.readInt32(offset + this.readInt32(offset)); + } + __has_identifier(ident) { + if (ident.length != FILE_IDENTIFIER_LENGTH) { + throw new Error('FlatBuffers: file identifier must be length ' + + FILE_IDENTIFIER_LENGTH); + } + for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { + if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) { + return false; + } + } + return true; + } + /** + * A helper function for generating list for obj api + */ + createScalarList(listAccessor, listLength) { + const ret = []; + for (let i = 0; i < listLength; ++i) { + const val = listAccessor(i); + if (val !== null) { + ret.push(val); + } + } + return ret; + } + /** + * A helper function for generating list for obj api + * @param listAccessor function that accepts an index and return data at that index + * @param listLength listLength + * @param res result list + */ + createObjList(listAccessor, listLength) { + const ret = []; + for (let i = 0; i < listLength; ++i) { + const val = listAccessor(i); + if (val !== null) { + ret.push(val.unpack()); + } + } + return ret; + } +} - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ +class Builder { + /** + * Create a FlatBufferBuilder. + */ + constructor(opt_initial_size) { + /** Minimum alignment encountered so far. */ + this.minalign = 1; + /** The vtable for the current table. */ + this.vtable = null; + /** The amount of fields we're actually using. */ + this.vtable_in_use = 0; + /** Whether we are currently serializing a table. */ + this.isNested = false; + /** Starting offset of the current struct/table. */ + this.object_start = 0; + /** List of offsets of all vtables. */ + this.vtables = []; + /** For the current vector being built. */ + this.vector_num_elems = 0; + /** False omits default values from the serialized data */ + this.force_defaults = false; + this.string_maps = null; + this.text_encoder = new TextEncoder(); + let initial_size; + if (!opt_initial_size) { + initial_size = 1024; + } + else { + initial_size = opt_initial_size; + } + /** + * @type {ByteBuffer} + * @private + */ + this.bb = ByteBuffer.allocate(initial_size); + this.space = initial_size; + } + clear() { + this.bb.clear(); + this.space = this.bb.capacity(); + this.minalign = 1; + this.vtable = null; + this.vtable_in_use = 0; + this.isNested = false; + this.object_start = 0; + this.vtables = []; + this.vector_num_elems = 0; + this.force_defaults = false; + this.string_maps = null; + } + /** + * In order to save space, fields that are set to their default value + * don't get serialized into the buffer. Forcing defaults provides a + * way to manually disable this optimization. + * + * @param forceDefaults true always serializes default values + */ + forceDefaults(forceDefaults) { + this.force_defaults = forceDefaults; + } + /** + * Get the ByteBuffer representing the FlatBuffer. Only call this after you've + * called finish(). The actual data starts at the ByteBuffer's current position, + * not necessarily at 0. + */ + dataBuffer() { + return this.bb; + } + /** + * Get the bytes representing the FlatBuffer. Only call this after you've + * called finish(). + */ + asUint8Array() { + return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset()); + } + /** + * Prepare to write an element of `size` after `additional_bytes` have been + * written, e.g. if you write a string, you need to align such the int length + * field is aligned to 4 bytes, and the string data follows it directly. If all + * you need to do is alignment, `additional_bytes` will be 0. + * + * @param size This is the of the new element to write + * @param additional_bytes The padding size + */ + prep(size, additional_bytes) { + // Track the biggest thing we've ever aligned to. + if (size > this.minalign) { + this.minalign = size; + } + // Find the amount of alignment needed such that `size` is properly + // aligned after `additional_bytes` + const align_size = ((~(this.bb.capacity() - this.space + additional_bytes)) + 1) & (size - 1); + // Reallocate the buffer if needed. + while (this.space < align_size + size + additional_bytes) { + const old_buf_size = this.bb.capacity(); + this.bb = Builder.growByteBuffer(this.bb); + this.space += this.bb.capacity() - old_buf_size; + } + this.pad(align_size); + } + pad(byte_size) { + for (let i = 0; i < byte_size; i++) { + this.bb.writeInt8(--this.space, 0); + } + } + writeInt8(value) { + this.bb.writeInt8(this.space -= 1, value); + } + writeInt16(value) { + this.bb.writeInt16(this.space -= 2, value); + } + writeInt32(value) { + this.bb.writeInt32(this.space -= 4, value); + } + writeInt64(value) { + this.bb.writeInt64(this.space -= 8, value); + } + writeFloat32(value) { + this.bb.writeFloat32(this.space -= 4, value); + } + writeFloat64(value) { + this.bb.writeFloat64(this.space -= 8, value); + } + /** + * Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int8` to add the buffer. + */ + addInt8(value) { + this.prep(1, 0); + this.writeInt8(value); + } + /** + * Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int16` to add the buffer. + */ + addInt16(value) { + this.prep(2, 0); + this.writeInt16(value); + } + /** + * Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int32` to add the buffer. + */ + addInt32(value) { + this.prep(4, 0); + this.writeInt32(value); + } + /** + * Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `int64` to add the buffer. + */ + addInt64(value) { + this.prep(8, 0); + this.writeInt64(value); + } + /** + * Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `float32` to add the buffer. + */ + addFloat32(value) { + this.prep(4, 0); + this.writeFloat32(value); + } + /** + * Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary). + * @param value The `float64` to add the buffer. + */ + addFloat64(value) { + this.prep(8, 0); + this.writeFloat64(value); + } + addFieldInt8(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt8(value); + this.slot(voffset); + } + } + addFieldInt16(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt16(value); + this.slot(voffset); + } + } + addFieldInt32(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addInt32(value); + this.slot(voffset); + } + } + addFieldInt64(voffset, value, defaultValue) { + if (this.force_defaults || value !== defaultValue) { + this.addInt64(value); + this.slot(voffset); + } + } + addFieldFloat32(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addFloat32(value); + this.slot(voffset); + } + } + addFieldFloat64(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addFloat64(value); + this.slot(voffset); + } + } + addFieldOffset(voffset, value, defaultValue) { + if (this.force_defaults || value != defaultValue) { + this.addOffset(value); + this.slot(voffset); + } + } + /** + * Structs are stored inline, so nothing additional is being added. `d` is always 0. + */ + addFieldStruct(voffset, value, defaultValue) { + if (value != defaultValue) { + this.nested(value); + this.slot(voffset); + } + } + /** + * Structures are always stored inline, they need to be created right + * where they're used. You'll get this assertion failure if you + * created it elsewhere. + */ + nested(obj) { + if (obj != this.offset()) { + throw new TypeError('FlatBuffers: struct must be serialized inline.'); + } + } + /** + * Should not be creating any other object, string or vector + * while an object is being constructed + */ + notNested() { + if (this.isNested) { + throw new TypeError('FlatBuffers: object serialization must not be nested.'); + } + } + /** + * Set the current vtable at `voffset` to the current location in the buffer. + */ + slot(voffset) { + if (this.vtable !== null) + this.vtable[voffset] = this.offset(); + } + /** + * @returns Offset relative to the end of the buffer. + */ + offset() { + return this.bb.capacity() - this.space; + } + /** + * Doubles the size of the backing ByteBuffer and copies the old data towards + * the end of the new buffer (since we build the buffer backwards). + * + * @param bb The current buffer with the existing data + * @returns A new byte buffer with the old data copied + * to it. The data is located at the end of the buffer. + * + * uint8Array.set() formally takes {Array|ArrayBufferView}, so to pass + * it a uint8Array we need to suppress the type check: + * @suppress {checkTypes} + */ + static growByteBuffer(bb) { + const old_buf_size = bb.capacity(); + // Ensure we don't grow beyond what fits in an int. + if (old_buf_size & 0xC0000000) { + throw new Error('FlatBuffers: cannot grow buffer beyond 2 gigabytes.'); + } + const new_buf_size = old_buf_size << 1; + const nbb = ByteBuffer.allocate(new_buf_size); + nbb.setPosition(new_buf_size - old_buf_size); + nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size); + return nbb; + } + /** + * Adds on offset, relative to where it will be written. + * + * @param offset The offset to add. + */ + addOffset(offset) { + this.prep(SIZEOF_INT, 0); // Ensure alignment is already done. + this.writeInt32(this.offset() - offset + SIZEOF_INT); + } + /** + * Start encoding a new object in the buffer. Users will not usually need to + * call this directly. The FlatBuffers compiler will generate helper methods + * that call this method internally. + */ + startObject(numfields) { + this.notNested(); + if (this.vtable == null) { + this.vtable = []; + } + this.vtable_in_use = numfields; + for (let i = 0; i < numfields; i++) { + this.vtable[i] = 0; // This will push additional elements as needed + } + this.isNested = true; + this.object_start = this.offset(); + } + /** + * Finish off writing the object that is under construction. + * + * @returns The offset to the object inside `dataBuffer` + */ + endObject() { + if (this.vtable == null || !this.isNested) { + throw new Error('FlatBuffers: endObject called without startObject'); + } + this.addInt32(0); + const vtableloc = this.offset(); + // Trim trailing zeroes. + let i = this.vtable_in_use - 1; + // eslint-disable-next-line no-empty + for (; i >= 0 && this.vtable[i] == 0; i--) { } + const trimmed_size = i + 1; + // Write out the current vtable. + for (; i >= 0; i--) { + // Offset relative to the start of the table. + this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0); + } + const standard_fields = 2; // The fields below: + this.addInt16(vtableloc - this.object_start); + const len = (trimmed_size + standard_fields) * SIZEOF_SHORT; + this.addInt16(len); + // Search for an existing vtable that matches the current one. + let existing_vtable = 0; + const vt1 = this.space; + outer_loop: for (i = 0; i < this.vtables.length; i++) { + const vt2 = this.bb.capacity() - this.vtables[i]; + if (len == this.bb.readInt16(vt2)) { + for (let j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) { + if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) { + continue outer_loop; + } + } + existing_vtable = this.vtables[i]; + break; + } + } + if (existing_vtable) { + // Found a match: + // Remove the current vtable. + this.space = this.bb.capacity() - vtableloc; + // Point table to existing vtable. + this.bb.writeInt32(this.space, existing_vtable - vtableloc); + } + else { + // No match: + // Add the location of the current vtable to the list of vtables. + this.vtables.push(this.offset()); + // Point table to current vtable. + this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc); + } + this.isNested = false; + return vtableloc; + } + /** + * Finalize a buffer, poiting to the given `root_table`. + */ + finish(root_table, opt_file_identifier, opt_size_prefix) { + const size_prefix = opt_size_prefix ? SIZE_PREFIX_LENGTH : 0; + if (opt_file_identifier) { + const file_identifier = opt_file_identifier; + this.prep(this.minalign, SIZEOF_INT + + FILE_IDENTIFIER_LENGTH + size_prefix); + if (file_identifier.length != FILE_IDENTIFIER_LENGTH) { + throw new TypeError('FlatBuffers: file identifier must be length ' + + FILE_IDENTIFIER_LENGTH); + } + for (let i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) { + this.writeInt8(file_identifier.charCodeAt(i)); + } + } + this.prep(this.minalign, SIZEOF_INT + size_prefix); + this.addOffset(root_table); + if (size_prefix) { + this.addInt32(this.bb.capacity() - this.space); + } + this.bb.setPosition(this.space); + } + /** + * Finalize a size prefixed buffer, pointing to the given `root_table`. + */ + finishSizePrefixed(root_table, opt_file_identifier) { + this.finish(root_table, opt_file_identifier, true); + } + /** + * This checks a required field has been set in a given table that has + * just been constructed. + */ + requiredField(table, field) { + const table_start = this.bb.capacity() - table; + const vtable_start = table_start - this.bb.readInt32(table_start); + const ok = field < this.bb.readInt16(vtable_start) && + this.bb.readInt16(vtable_start + field) != 0; + // If this fails, the caller will show what field needs to be set. + if (!ok) { + throw new TypeError('FlatBuffers: field ' + field + ' must be set'); + } + } + /** + * Start a new array/vector of objects. Users usually will not call + * this directly. The FlatBuffers compiler will create a start/end + * method for vector types in generated code. + * + * @param elem_size The size of each element in the array + * @param num_elems The number of elements in the array + * @param alignment The alignment of the array + */ + startVector(elem_size, num_elems, alignment) { + this.notNested(); + this.vector_num_elems = num_elems; + this.prep(SIZEOF_INT, elem_size * num_elems); + this.prep(alignment, elem_size * num_elems); // Just in case alignment > int. + } + /** + * Finish off the creation of an array and all its elements. The array must be + * created with `startVector`. + * + * @returns The offset at which the newly created array + * starts. + */ + endVector() { + this.writeInt32(this.vector_num_elems); + return this.offset(); + } + /** + * Encode the string `s` in the buffer using UTF-8. If the string passed has + * already been seen, we return the offset of the already written string + * + * @param s The string to encode + * @return The offset in the buffer where the encoded string starts + */ + createSharedString(s) { + if (!s) { + return 0; + } + if (!this.string_maps) { + this.string_maps = new Map(); + } + if (this.string_maps.has(s)) { + return this.string_maps.get(s); + } + const offset = this.createString(s); + this.string_maps.set(s, offset); + return offset; + } + /** + * Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed + * instead of a string, it is assumed to contain valid UTF-8 encoded data. + * + * @param s The string to encode + * @return The offset in the buffer where the encoded string starts + */ + createString(s) { + if (s === null || s === undefined) { + return 0; + } + let utf8; + if (s instanceof Uint8Array) { + utf8 = s; + } + else { + utf8 = this.text_encoder.encode(s); + } + this.addInt8(0); + this.startVector(1, utf8.length, 1); + this.bb.setPosition(this.space -= utf8.length); + for (let i = 0, offset = this.space, bytes = this.bb.bytes(); i < utf8.length; i++) { + bytes[offset++] = utf8[i]; + } + return this.endVector(); + } + /** + * A helper function to pack an object + * + * @returns offset of obj + */ + createObjectOffset(obj) { + if (obj === null) { + return 0; + } + if (typeof obj === 'string') { + return this.createString(obj); + } + else { + return obj.pack(this); + } + } + /** + * A helper function to pack a list of object + * + * @returns list of offsets of each non null object + */ + createObjectOffsetList(list) { + const ret = []; + for (let i = 0; i < list.length; ++i) { + const val = list[i]; + if (val !== null) { + ret.push(this.createObjectOffset(val)); + } + else { + throw new TypeError('FlatBuffers: Argument for createObjectOffsetList cannot contain null.'); + } + } + return ret; + } + createStructOffsetList(list, startFunc) { + startFunc(this, list.length); + this.createObjectOffsetList(list.slice().reverse()); + return this.endVector(); + } +} - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; +// automatically generated by the FlatBuffers compiler, do not modify +class Alignment { + constructor() { + this.bb = null; + this.bb_pos = 0; + } + __init(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; + } + static getRootAsAlignment(bb, obj) { + return (obj || new Alignment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static getSizePrefixedRootAsAlignment(bb, obj) { + bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); + return (obj || new Alignment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + position(index) { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + positionLength() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + positionArray() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + curve(index) { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + curveLength() { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + curveArray() { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + segment(index) { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + segmentLength() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + segmentArray() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + static startAlignment(builder) { + builder.startObject(3); + } + static addPosition(builder, positionOffset) { + builder.addFieldOffset(0, positionOffset, 0); + } + static createPositionVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startPositionVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addCurve(builder, curveOffset) { + builder.addFieldOffset(1, curveOffset, 0); + } + static createCurveVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startCurveVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addSegment(builder, segmentOffset) { + builder.addFieldOffset(2, segmentOffset, 0); + } + static createSegmentVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startSegmentVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static endAlignment(builder) { + const offset = builder.endObject(); + return offset; + } + static createAlignment(builder, positionOffset, curveOffset, segmentOffset) { + Alignment.startAlignment(builder); + Alignment.addPosition(builder, positionOffset); + Alignment.addCurve(builder, curveOffset); + Alignment.addSegment(builder, segmentOffset); + return Alignment.endAlignment(builder); + } +} - if ( cosHalfTheta < 0 ) { +// automatically generated by the FlatBuffers compiler, do not modify +class Civil { + constructor() { + this.bb = null; + this.bb_pos = 0; + } + __init(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; + } + static getRootAsCivil(bb, obj) { + return (obj || new Civil()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static getSizePrefixedRootAsCivil(bb, obj) { + bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); + return (obj || new Civil()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + alignmentHorizontal(obj) { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; + } + alignmentVertical(obj) { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; + } + alignment3d(obj) { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; + } + static startCivil(builder) { + builder.startObject(3); + } + static addAlignmentHorizontal(builder, alignmentHorizontalOffset) { + builder.addFieldOffset(0, alignmentHorizontalOffset, 0); + } + static addAlignmentVertical(builder, alignmentVerticalOffset) { + builder.addFieldOffset(1, alignmentVerticalOffset, 0); + } + static addAlignment3d(builder, alignment3dOffset) { + builder.addFieldOffset(2, alignment3dOffset, 0); + } + static endCivil(builder) { + const offset = builder.endObject(); + return offset; + } +} - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; +// automatically generated by the FlatBuffers compiler, do not modify +class Fragment { + constructor() { + this.bb = null; + this.bb_pos = 0; + } + __init(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; + } + static getRootAsFragment(bb, obj) { + return (obj || new Fragment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static getSizePrefixedRootAsFragment(bb, obj) { + bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); + return (obj || new Fragment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + position(index) { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + positionLength() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + positionArray() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + normal(index) { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + normalLength() { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + normalArray() { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + index(index) { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + indexLength() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + indexArray() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + blockId(index) { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + blockIdLength() { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + blockIdArray() { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + groups(index) { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + groupsLength() { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + groupsArray() { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + materials(index) { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + materialsLength() { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + materialsArray() { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + matrices(index) { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + matricesLength() { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + matricesArray() { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + colors(index) { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + colorsLength() { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + colorsArray() { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + ids(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 20); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + id(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 22); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + composites(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 24); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + static startFragment(builder) { + builder.startObject(11); + } + static addPosition(builder, positionOffset) { + builder.addFieldOffset(0, positionOffset, 0); + } + static createPositionVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startPositionVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addNormal(builder, normalOffset) { + builder.addFieldOffset(1, normalOffset, 0); + } + static createNormalVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startNormalVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addIndex(builder, indexOffset) { + builder.addFieldOffset(2, indexOffset, 0); + } + static createIndexVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startIndexVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addBlockId(builder, blockIdOffset) { + builder.addFieldOffset(3, blockIdOffset, 0); + } + static createBlockIdVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startBlockIdVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addGroups(builder, groupsOffset) { + builder.addFieldOffset(4, groupsOffset, 0); + } + static createGroupsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startGroupsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addMaterials(builder, materialsOffset) { + builder.addFieldOffset(5, materialsOffset, 0); + } + static createMaterialsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startMaterialsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addMatrices(builder, matricesOffset) { + builder.addFieldOffset(6, matricesOffset, 0); + } + static createMatricesVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startMatricesVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addColors(builder, colorsOffset) { + builder.addFieldOffset(7, colorsOffset, 0); + } + static createColorsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startColorsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addIds(builder, idsOffset) { + builder.addFieldOffset(8, idsOffset, 0); + } + static addId(builder, idOffset) { + builder.addFieldOffset(9, idOffset, 0); + } + static addComposites(builder, compositesOffset) { + builder.addFieldOffset(10, compositesOffset, 0); + } + static endFragment(builder) { + const offset = builder.endObject(); + return offset; + } + static createFragment(builder, positionOffset, normalOffset, indexOffset, blockIdOffset, groupsOffset, materialsOffset, matricesOffset, colorsOffset, idsOffset, idOffset, compositesOffset) { + Fragment.startFragment(builder); + Fragment.addPosition(builder, positionOffset); + Fragment.addNormal(builder, normalOffset); + Fragment.addIndex(builder, indexOffset); + Fragment.addBlockId(builder, blockIdOffset); + Fragment.addGroups(builder, groupsOffset); + Fragment.addMaterials(builder, materialsOffset); + Fragment.addMatrices(builder, matricesOffset); + Fragment.addColors(builder, colorsOffset); + Fragment.addIds(builder, idsOffset); + Fragment.addId(builder, idOffset); + Fragment.addComposites(builder, compositesOffset); + return Fragment.endFragment(builder); + } +} - cosHalfTheta = - cosHalfTheta; +// automatically generated by the FlatBuffers compiler, do not modify +let FragmentsGroup$1 = class FragmentsGroup { + constructor() { + this.bb = null; + this.bb_pos = 0; + } + __init(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; + } + static getRootAsFragmentsGroup(bb, obj) { + return (obj || new FragmentsGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static getSizePrefixedRootAsFragmentsGroup(bb, obj) { + bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); + return (obj || new FragmentsGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + items(index, obj) { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? (obj || new Fragment()).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null; + } + itemsLength() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + civil(obj) { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? (obj || new Civil()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; + } + coordinationMatrix(index) { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + coordinationMatrixLength() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + coordinationMatrixArray() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + ids(index) { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + idsLength() { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + idsArray() { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + itemsKeys(index) { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + itemsKeysLength() { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + itemsKeysArray() { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + itemsKeysIndices(index) { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + itemsKeysIndicesLength() { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + itemsKeysIndicesArray() { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + itemsRels(index) { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + itemsRelsLength() { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + itemsRelsArray() { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + itemsRelsIndices(index) { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + itemsRelsIndicesLength() { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + itemsRelsIndicesArray() { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + fragmentKeys(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 20); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + id(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 22); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + name(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 24); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + ifcName(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 26); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + ifcDescription(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 28); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + ifcSchema(optionalEncoding) { + const offset = this.bb.__offset(this.bb_pos, 30); + return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + } + maxExpressId() { + const offset = this.bb.__offset(this.bb_pos, 32); + return offset ? this.bb.readUint32(this.bb_pos + offset) : 0; + } + boundingBox(index) { + const offset = this.bb.__offset(this.bb_pos, 34); + return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + } + boundingBoxLength() { + const offset = this.bb.__offset(this.bb_pos, 34); + return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + } + boundingBoxArray() { + const offset = this.bb.__offset(this.bb_pos, 34); + return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + } + static startFragmentsGroup(builder) { + builder.startObject(16); + } + static addItems(builder, itemsOffset) { + builder.addFieldOffset(0, itemsOffset, 0); + } + static createItemsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]); + } + return builder.endVector(); + } + static startItemsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addCivil(builder, civilOffset) { + builder.addFieldOffset(1, civilOffset, 0); + } + static addCoordinationMatrix(builder, coordinationMatrixOffset) { + builder.addFieldOffset(2, coordinationMatrixOffset, 0); + } + static createCoordinationMatrixVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startCoordinationMatrixVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addIds(builder, idsOffset) { + builder.addFieldOffset(3, idsOffset, 0); + } + static createIdsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startIdsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addItemsKeys(builder, itemsKeysOffset) { + builder.addFieldOffset(4, itemsKeysOffset, 0); + } + static createItemsKeysVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startItemsKeysVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addItemsKeysIndices(builder, itemsKeysIndicesOffset) { + builder.addFieldOffset(5, itemsKeysIndicesOffset, 0); + } + static createItemsKeysIndicesVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startItemsKeysIndicesVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addItemsRels(builder, itemsRelsOffset) { + builder.addFieldOffset(6, itemsRelsOffset, 0); + } + static createItemsRelsVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startItemsRelsVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addItemsRelsIndices(builder, itemsRelsIndicesOffset) { + builder.addFieldOffset(7, itemsRelsIndicesOffset, 0); + } + static createItemsRelsIndicesVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addInt32(data[i]); + } + return builder.endVector(); + } + static startItemsRelsIndicesVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static addFragmentKeys(builder, fragmentKeysOffset) { + builder.addFieldOffset(8, fragmentKeysOffset, 0); + } + static addId(builder, idOffset) { + builder.addFieldOffset(9, idOffset, 0); + } + static addName(builder, nameOffset) { + builder.addFieldOffset(10, nameOffset, 0); + } + static addIfcName(builder, ifcNameOffset) { + builder.addFieldOffset(11, ifcNameOffset, 0); + } + static addIfcDescription(builder, ifcDescriptionOffset) { + builder.addFieldOffset(12, ifcDescriptionOffset, 0); + } + static addIfcSchema(builder, ifcSchemaOffset) { + builder.addFieldOffset(13, ifcSchemaOffset, 0); + } + static addMaxExpressId(builder, maxExpressId) { + builder.addFieldInt32(14, maxExpressId, 0); + } + static addBoundingBox(builder, boundingBoxOffset) { + builder.addFieldOffset(15, boundingBoxOffset, 0); + } + static createBoundingBoxVector(builder, data) { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addFloat32(data[i]); + } + return builder.endVector(); + } + static startBoundingBoxVector(builder, numElems) { + builder.startVector(4, numElems, 4); + } + static endFragmentsGroup(builder) { + const offset = builder.endObject(); + return offset; + } + static finishFragmentsGroupBuffer(builder, offset) { + builder.finish(offset); + } + static finishSizePrefixedFragmentsGroupBuffer(builder, offset) { + builder.finish(offset, undefined, true); + } +}; - } else { +// TODO: Document this +class FragmentsGroup extends THREE$1.Group { + constructor() { + super(...arguments); + this.items = []; + this.boundingBox = new THREE$1.Box3(); + this.coordinationMatrix = new THREE$1.Matrix4(); + this.keyFragments = {}; + // data: [expressID: number]: [keys, rels] + this.data = {}; + this.ifcMetadata = { + name: "", + description: "", + schema: "IFC2X3", + maxExpressID: 0, + }; + } + // TODO: Force all item IDs to be numbers or strings + getFragmentMap(expressIDs) { + const fragmentMap = {}; + for (const expressID of expressIDs) { + const data = this.data[expressID]; + if (!data) + continue; + for (const key of data[0]) { + const fragmentID = this.keyFragments[key]; + if (!fragmentMap[fragmentID]) + fragmentMap[fragmentID] = new Set(); + fragmentMap[fragmentID].add(expressID); + } + } + return fragmentMap; + } + dispose(disposeResources = true) { + for (const fragment of this.items) { + fragment.dispose(disposeResources); + } + this.coordinationMatrix = new THREE$1.Matrix4(); + this.keyFragments = {}; + this.data = {}; + this.properties = {}; + } +} - this.copy( qb ); +class IfcAlignmentData { + constructor() { + this.coordinates = new Float32Array(0); + this.alignmentIndex = []; + this.curveIndex = []; + } + exportData() { + const { coordinates, alignmentIndex, curveIndex } = this; + return { coordinates, alignmentIndex, curveIndex }; + } +} - } +/** + * Object to export and import sets of fragments efficiently using + * [flatbuffers](https://flatbuffers.dev/). + */ +class Serializer { + constructor() { + this.fragmentIDSeparator = "|"; + } + import(bytes) { + const buffer = new ByteBuffer(bytes); + const fbFragmentsGroup = FragmentsGroup$1.getRootAsFragmentsGroup(buffer); + const fragmentsGroup = this.constructFragmentGroup(fbFragmentsGroup); + const length = fbFragmentsGroup.itemsLength(); + for (let i = 0; i < length; i++) { + const fbFragment = fbFragmentsGroup.items(i); + if (!fbFragment) + continue; + const geometry = this.constructGeometry(fbFragment); + const materials = this.constructMaterials(fbFragment); + const { instances, colors } = this.constructInstances(fbFragment); + const fragment = new Fragment$1(geometry, materials, instances.length); + this.getComposites(fbFragment, fragment); + this.setInstances(instances, colors, fragment); + this.setID(fbFragment, fragment); + fragmentsGroup.items.push(fragment); + fragmentsGroup.add(fragment.mesh); + } + return fragmentsGroup; + } + export(group) { + var _a; + const builder = new Builder(1024); + const items = []; + const G = FragmentsGroup$1; + const F = Fragment; + const C = Civil; + let exportedCivil = null; + if ((_a = group.ifcCivil) === null || _a === void 0 ? void 0 : _a.horizontalAlignments) { + const A = Alignment; + const resultH = group.ifcCivil.horizontalAlignments.exportData(); + const posVectorH = A.createPositionVector(builder, resultH.coordinates); + const curveVectorH = A.createSegmentVector(builder, resultH.curveIndex); + const alignVectorH = A.createCurveVector(builder, resultH.alignmentIndex); + A.startAlignment(builder); + A.addPosition(builder, posVectorH); + A.addSegment(builder, curveVectorH); + A.addCurve(builder, alignVectorH); + const exportedH = Alignment.endAlignment(builder); + const resultV = group.ifcCivil.verticalAlignments.exportData(); + const posVectorV = A.createPositionVector(builder, resultV.coordinates); + const curveVectorV = A.createSegmentVector(builder, resultV.curveIndex); + const alignVectorV = A.createCurveVector(builder, resultV.alignmentIndex); + A.startAlignment(builder); + A.addPosition(builder, posVectorV); + A.addSegment(builder, curveVectorV); + A.addCurve(builder, alignVectorV); + const exportedV = Alignment.endAlignment(builder); + const resultR = group.ifcCivil.realAlignments.exportData(); + const posVectorR = A.createPositionVector(builder, resultR.coordinates); + const curveVectorR = A.createSegmentVector(builder, resultR.curveIndex); + const alignVectorR = A.createCurveVector(builder, resultR.alignmentIndex); + A.startAlignment(builder); + A.addPosition(builder, posVectorR); + A.addSegment(builder, curveVectorR); + A.addCurve(builder, alignVectorR); + const exportedR = Alignment.endAlignment(builder); + C.startCivil(builder); + C.addAlignmentHorizontal(builder, exportedH); + C.addAlignmentVertical(builder, exportedV); + C.addAlignment3d(builder, exportedR); + exportedCivil = Civil.endCivil(builder); + } + for (const fragment of group.items) { + const result = fragment.exportData(); + const posVector = F.createPositionVector(builder, result.position); + const normalVector = F.createNormalVector(builder, result.normal); + const blockVector = F.createBlockIdVector(builder, result.blockID); + const indexVector = F.createIndexVector(builder, result.index); + const groupsVector = F.createGroupsVector(builder, result.groups); + const matsVector = F.createMaterialsVector(builder, result.materials); + const matricesVector = F.createMatricesVector(builder, result.matrices); + const colorsVector = F.createColorsVector(builder, result.colors); + const idsStr = builder.createString(result.ids); + const idStr = builder.createString(result.id); + const compositeStr = builder.createString(JSON.stringify(fragment.composites)); + F.startFragment(builder); + F.addPosition(builder, posVector); + F.addNormal(builder, normalVector); + F.addBlockId(builder, blockVector); + F.addIndex(builder, indexVector); + F.addGroups(builder, groupsVector); + F.addMaterials(builder, matsVector); + F.addMatrices(builder, matricesVector); + F.addColors(builder, colorsVector); + F.addIds(builder, idsStr); + F.addId(builder, idStr); + F.addComposites(builder, compositeStr); + const exported = Fragment.endFragment(builder); + items.push(exported); + } + const itemsVector = G.createItemsVector(builder, items); + const matrixVector = G.createCoordinationMatrixVector(builder, group.coordinationMatrix.elements); + let fragmentKeys = ""; + for (const key in group.keyFragments) { + const fragmentID = group.keyFragments[key]; + if (fragmentKeys.length) + fragmentKeys += this.fragmentIDSeparator; + fragmentKeys += fragmentID; + } + const fragmentKeysRef = builder.createString(fragmentKeys); + const keyIndices = []; + const itemsKeys = []; + const relsIndices = []; + const itemsRels = []; + const ids = []; + let keysCounter = 0; + let relsCounter = 0; + for (const expressID in group.data) { + keyIndices.push(keysCounter); + relsIndices.push(relsCounter); + const [keys, rels] = group.data[expressID]; + const id = parseInt(expressID, 10); + ids.push(id); + for (const key of keys) { + itemsKeys.push(key); + } + for (const rel of rels) { + itemsRels.push(rel); + } + keysCounter += keys.length; + relsCounter += rels.length; + } + const groupID = builder.createString(group.uuid); + const groupName = builder.createString(group.name); + const ifcName = builder.createString(group.ifcMetadata.name); + const ifcDescription = builder.createString(group.ifcMetadata.description); + const ifcSchema = builder.createString(group.ifcMetadata.schema); + const keysIVector = G.createItemsKeysIndicesVector(builder, keyIndices); + const keysVector = G.createItemsKeysVector(builder, itemsKeys); + const relsIVector = G.createItemsRelsIndicesVector(builder, relsIndices); + const relsVector = G.createItemsRelsVector(builder, itemsRels); + const idsVector = G.createIdsVector(builder, ids); + const { min, max } = group.boundingBox; + const bbox = [min.x, min.y, min.z, max.x, max.y, max.z]; + const bboxVector = G.createBoundingBoxVector(builder, bbox); + G.startFragmentsGroup(builder); + if (exportedCivil !== null) { + G.addCivil(builder, exportedCivil); + } + G.addId(builder, groupID); + G.addName(builder, groupName); + G.addIfcName(builder, ifcName); + G.addIfcDescription(builder, ifcDescription); + G.addIfcSchema(builder, ifcSchema); + G.addMaxExpressId(builder, group.ifcMetadata.maxExpressID); + G.addItems(builder, itemsVector); + G.addFragmentKeys(builder, fragmentKeysRef); + G.addIds(builder, idsVector); + G.addItemsKeysIndices(builder, keysIVector); + G.addItemsKeys(builder, keysVector); + G.addItemsRelsIndices(builder, relsIVector); + G.addItemsRels(builder, relsVector); + G.addCoordinationMatrix(builder, matrixVector); + G.addBoundingBox(builder, bboxVector); + const result = FragmentsGroup$1.endFragmentsGroup(builder); + builder.finish(result); + return builder.asUint8Array(); + } + getComposites(fbFragment, fragment) { + const composites = fbFragment.composites() || "{}"; + fragment.composites = JSON.parse(composites); + } + setID(fbFragment, fragment) { + const id = fbFragment.id(); + if (id) { + fragment.id = id; + fragment.mesh.uuid = id; + } + } + setInstances(instances, colors, fragment) { + for (let i = 0; i < instances.length; i++) { + fragment.setInstance(i, instances[i]); + if (colors.length) { + fragment.mesh.setColorAt(i, colors[i]); + } + } + } + constructInstances(fragment) { + const matricesData = fragment.matricesArray(); + const colorData = fragment.colorsArray(); + const colors = []; + const idsString = fragment.ids(); + const id = fragment.id(); + if (!matricesData || !idsString) { + throw new Error(`Error: Can't load empty fragment: ${id}`); + } + const ids = idsString.split("|"); + const singleInstance = matricesData.length === 16; + const manyItems = ids.length > 1; + const isMergedFragment = singleInstance && manyItems; + if (isMergedFragment) { + const transform = new THREE$1.Matrix4().fromArray(matricesData); + const instances = [{ ids, transform }]; + return { instances, colors }; + } + // Instanced fragment + const instances = []; + for (let i = 0; i < matricesData.length; i += 16) { + const matrixArray = matricesData.subarray(i, i + 17); + const transform = new THREE$1.Matrix4().fromArray(matrixArray); + const id = ids[i / 16]; + instances.push({ ids: [id], transform }); + } + if (colorData && colorData.length === instances.length * 3) { + for (let i = 0; i < colorData.length; i += 3) { + const [r, g, b] = colorData.subarray(i, i + 4); + const color = new THREE$1.Color(r, g, b); + colors.push(color); + } + } + return { instances, colors }; + } + constructMaterials(fragment) { + const materials = fragment.materialsArray(); + const matArray = []; + if (!materials) + return matArray; + for (let i = 0; i < materials.length; i += 5) { + const opacity = materials[i]; + const transparent = Boolean(materials[i + 1]); + const red = materials[i + 2]; + const green = materials[i + 3]; + const blue = materials[i + 4]; + const color = new THREE$1.Color(red, green, blue); + const material = new THREE$1.MeshLambertMaterial({ + color, + opacity, + transparent, + }); + matArray.push(material); + } + return matArray; + } + constructFragmentGroup(group) { + const fragmentsGroup = new FragmentsGroup(); + const FBcivil = group.civil(); + const horizontalAlignments = new IfcAlignmentData(); + const verticalAlignments = new IfcAlignmentData(); + const realAlignments = new IfcAlignmentData(); + if (FBcivil) { + const FBalignmentH = FBcivil.alignmentHorizontal(); + this.getAlignmentData(FBalignmentH, horizontalAlignments); + const FBalignmentV = FBcivil.alignmentVertical(); + this.getAlignmentData(FBalignmentV, verticalAlignments); + const FBalignment3D = FBcivil.alignment3d(); + this.getAlignmentData(FBalignment3D, realAlignments); + fragmentsGroup.ifcCivil = { + horizontalAlignments, + verticalAlignments, + realAlignments, + }; + } + // fragmentsGroup.ifcCivil?.horizontalAlignments + fragmentsGroup.uuid = group.id() || fragmentsGroup.uuid; + fragmentsGroup.name = group.name() || ""; + fragmentsGroup.ifcMetadata = { + name: group.ifcName() || "", + description: group.ifcDescription() || "", + schema: group.ifcSchema() || "IFC2X3", + maxExpressID: group.maxExpressId() || 0, + }; + const defaultMatrix = new THREE$1.Matrix4().elements; + const matrixArray = group.coordinationMatrixArray() || defaultMatrix; + const ids = group.idsArray() || new Uint32Array(); + const keysIndices = group.itemsKeysIndicesArray() || new Uint32Array(); + const keysArray = group.itemsKeysArray() || new Uint32Array(); + const relsArray = group.itemsRelsArray() || new Uint32Array(); + const relsIndices = group.itemsRelsIndicesArray() || new Uint32Array(); + const keysIdsString = group.fragmentKeys() || ""; + const keysIdsArray = keysIdsString.split(this.fragmentIDSeparator); + this.setGroupData(fragmentsGroup, ids, keysIndices, keysArray, 0); + this.setGroupData(fragmentsGroup, ids, relsIndices, relsArray, 1); + const bbox = group.boundingBoxArray() || [0, 0, 0, 0, 0, 0]; + const [minX, minY, minZ, maxX, maxY, maxZ] = bbox; + fragmentsGroup.boundingBox.min.set(minX, minY, minZ); + fragmentsGroup.boundingBox.max.set(maxX, maxY, maxZ); + for (let i = 0; i < keysIdsArray.length; i++) { + fragmentsGroup.keyFragments[i] = keysIdsArray[i]; + } + if (matrixArray.length === 16) { + fragmentsGroup.coordinationMatrix.fromArray(matrixArray); + } + return fragmentsGroup; + } + getAlignmentData(alignment, result) { + if (alignment) { + if (alignment.positionArray) { + result.coordinates = alignment.positionArray(); + for (let j = 0; j < alignment.curveLength(); j++) { + result.alignmentIndex.push(alignment.curve(j)); + } + for (let j = 0; j < alignment.segmentLength(); j++) { + result.curveIndex.push(alignment.segment(j)); + } + } + } + } + setGroupData(group, ids, indices, array, index) { + for (let i = 0; i < indices.length; i++) { + const expressID = ids[i]; + const currentIndex = indices[i]; + const nextIndex = indices[i + 1] || array.length; + const keys = []; + for (let j = currentIndex; j < nextIndex; j++) { + keys.push(array[j]); + } + if (!group.data[expressID]) { + group.data[expressID] = [[], []]; + } + group.data[expressID][index] = keys; + } + } + constructGeometry(fragment) { + const position = fragment.positionArray(); + const normal = fragment.normalArray(); + const blockID = fragment.blockIdArray(); + const index = fragment.indexArray(); + const groups = fragment.groupsArray(); + if (!index) + throw new Error("Index not found!"); + const geometry = new THREE$1.BufferGeometry(); + geometry.setIndex(Array.from(index)); + this.loadAttribute(geometry, "position", position, 3); + this.loadAttribute(geometry, "normal", normal, 3); + this.loadAttribute(geometry, "blockID", blockID, 1); + this.loadGeometryGroups(groups, geometry); + return geometry; + } + loadGeometryGroups(groups, geometry) { + if (!groups) + return; + for (let i = 0; i < groups.length; i += 3) { + const start = groups[i]; + const count = groups[i + 1]; + const materialIndex = groups[i + 2]; + geometry.addGroup(start, count, materialIndex); + } + } + loadAttribute(geometry, name, data, size) { + if (!data) + return; + geometry.setAttribute(name, new THREE$1.BufferAttribute(data, size)); + } +} - if ( cosHalfTheta >= 1.0 ) { +// +// Thanks to the advice here https://github.com/zalo/TetSim/commit/9696c2e1cd6354fb9bd40dbd299c58f4de0341dd +// +function clientWaitAsync(gl, sync, flags, intervalMilliseconds) { + return new Promise((resolve, reject) => { + function test() { + const res = gl.clientWaitSync(sync, flags, 0); + if (res === gl.WAIT_FAILED) { + reject(); + return; + } + if (res === gl.TIMEOUT_EXPIRED) { + setTimeout(test, intervalMilliseconds); + return; + } + resolve(); + } + test(); + }); +} +async function getBufferSubDataAsync(gl, target, buffer, srcByteOffset, dstBuffer, dstOffset, length) { + const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); + gl.flush(); + await clientWaitAsync(gl, sync, 0, 10); + gl.deleteSync(sync); + gl.bindBuffer(target, buffer); + gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length); + gl.bindBuffer(target, null); +} +async function readPixelsAsync(gl, x, y, w, h, format, type, dest) { + const buf = gl.createBuffer(); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); + gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ); + gl.readPixels(x, y, w, h, format, type, 0); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); + await getBufferSubDataAsync(gl, gl.PIXEL_PACK_BUFFER, buf, 0, dest); + gl.deleteBuffer(buf); + return dest; +} - this._w = w; - this._x = x; - this._y = y; - this._z = z; +/** + * Object that can efficiently load binary files that contain + * [fragment geometry](https://github.com/ifcjs/fragment). + */ +class FragmentManager extends Component { + /** The list of meshes of the created fragments. */ + get meshes() { + const allMeshes = []; + for (const fragID in this.list) { + allMeshes.push(this.list[fragID].mesh); + } + return allMeshes; + } + constructor(components) { + super(components); + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + /** {@link Component.enabled} */ + this.enabled = true; + /** All the created [fragments](https://github.com/ifcjs/fragment). */ + this.list = {}; + this.groups = []; + this.baseCoordinationModel = ""; + this.onFragmentsLoaded = new Event(); + this.onFragmentsDisposed = new Event(); + this.uiElement = new UIElement(); + this.commands = []; + this._loader = new Serializer(); + this._cards = []; + this.components.tools.add(FragmentManager.uuid, this); + if (components.uiEnabled) { + this.setupUI(components); + } + } + /** {@link Component.get} */ + get() { + return Object.values(this.list); + } + /** {@link Component.get} */ + async dispose(disposeUI = false) { + if (disposeUI) { + this.uiElement.dispose(); + } + for (const group of this.groups) { + group.dispose(true); + } + for (const command of this.commands) { + await command.dispose(); + } + for (const card of this._cards) { + await card.dispose(); + } + this.groups = []; + this.list = {}; + this.onFragmentsLoaded.reset(); + this.onFragmentsDisposed.reset(); + await this.onDisposed.trigger(FragmentManager.uuid); + this.onDisposed.reset(); + } + async disposeGroup(group) { + const { uuid: groupID } = group; + const fragmentIDs = group.items.map((fragment) => fragment.id); + for (const fragment of group.items) { + this.removeFragmentMesh(fragment); + delete this.list[fragment.id]; + } + group.dispose(true); + const index = this.groups.indexOf(group); + this.groups.splice(index, 1); + await this.onFragmentsDisposed.trigger({ + groupID, + fragmentIDs, + }); + await this.updateWindow(); + } + /** Disposes all existing fragments */ + reset() { + for (const id in this.list) { + const fragment = this.list[id]; + fragment.dispose(); + } + this.list = {}; + } + /** + * Loads one or many fragments into the scene. + * @param data - the bytes containing the data for the fragments to load. + * @returns the list of IDs of the loaded fragments. + */ + async load(data) { + const group = this._loader.import(data); + const scene = this.components.scene.get(); + const ids = []; + scene.add(group); + for (const fragment of group.items) { + fragment.group = group; + this.list[fragment.id] = fragment; + ids.push(fragment.id); + this.components.meshes.push(fragment.mesh); + } + this.groups.push(group); + await this.onFragmentsLoaded.trigger(group); + return group; + } + /** + * Export the specified fragments. + * @param group - the fragments group to be exported. + * @returns the exported data as binary buffer. + */ + export(group) { + return this._loader.export(group); + } + async updateWindow() { + if (!this.components.uiEnabled) { + return; + } + for (const card of this._cards) { + await card.dispose(); + } + for (const group of this.groups) { + const card = new SimpleUICard(this.components); + // TODO: Make all cards like this? + card.domElement.classList.remove("bg-ifcjs-120"); + card.domElement.classList.remove("border-transparent"); + card.domElement.className += ` min-w-[300px] my-2 border-1 border-solid border-[#3A444E] `; + const buttonContainer = new SimpleUIComponent(this.components); + card.addChild(buttonContainer); + card.title = group.name; + this.uiElement.get("window").addChild(card); + this._cards.push(card); + // TODO: Use command list just like in fragment plans + const commandsButton = new Button(this.components); + commandsButton.materialIcon = "delete"; + buttonContainer.addChild(commandsButton); + commandsButton.onClick.add(() => this.disposeGroup(group)); + } + } + coordinate(models = this.groups) { + const baseModel = this.groups.find((group) => group.uuid === this.baseCoordinationModel); + if (!baseModel) { + console.log("No base model found for coordination!"); + return; + } + for (const model of models) { + if (model === baseModel) { + continue; + } + model.position.set(0, 0, 0); + model.rotation.set(0, 0, 0); + model.scale.set(1, 1, 1); + model.updateMatrix(); + model.applyMatrix4(model.coordinationMatrix.clone().invert()); + model.applyMatrix4(baseModel.coordinationMatrix); + } + } + setupUI(components) { + const window = new FloatingWindow(components); + window.title = "Models"; + window.domElement.style.left = "70px"; + window.domElement.style.top = "100px"; + window.domElement.style.width = "340px"; + window.domElement.style.height = "400px"; + const windowContent = window.slots.content.domElement; + windowContent.classList.remove("overflow-auto"); + windowContent.classList.add("overflow-x-hidden"); + components.ui.add(window); + window.visible = false; + const main = new Button(components); + main.tooltip = "Models"; + main.materialIcon = "inbox"; + main.onClick.add(() => { + window.visible = !window.visible; + }); + this.uiElement.set({ main, window }); + this.onFragmentsLoaded.add(() => this.updateWindow()); + } + removeFragmentMesh(fragment) { + const meshes = this.components.meshes; + const mesh = fragment.mesh; + if (meshes.includes(mesh)) { + meshes.splice(meshes.indexOf(mesh), 1); + } + } +} +FragmentManager.uuid = "fef46874-46a3-461b-8c44-2922ab77c806"; +ToolComponent.libraryUUIDs.add(FragmentManager.uuid); - return this; +// TODO: Work at the instance level instead of the mesh level? +/** + * A tool to handle big scenes efficiently by automatically hiding the objects + * that are not visible to the camera. + */ +class ScreenCuller extends Component { + constructor(components, updateInterval = 1000, rtWidth = 512, rtHeight = 512, autoUpdate = true) { + super(components); + this.updateInterval = updateInterval; + this.rtWidth = rtWidth; + this.rtHeight = rtHeight; + this.autoUpdate = autoUpdate; + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + /** Fires after hiding the objects that were not visible to the camera. */ + this.onViewUpdated = new Event(); + /** {@link Component.enabled} */ + this.enabled = true; + /** + * Needs to check whether there are objects that need to be hidden or shown. + * You can bind this to the camera movement, to a certain interval, etc. + */ + this.needsUpdate = false; + /** + * Render the internal scene used to determine the object visibility. Used + * for debugging purposes. + */ + this.renderDebugFrame = false; + this._meshColorMap = new Map(); + this._visibleMeshes = []; + this._colorMeshes = new Map(); + this._meshes = new Map(); + this._currentVisibleMeshes = new Set(); + this._recentlyHiddenMeshes = new Set(); + this._transparentMat = new THREE$1.MeshBasicMaterial({ + transparent: true, + opacity: 0, + }); + this._colors = { r: 0, g: 0, b: 0, i: 0 }; + // Alternative scene and meshes to make the visibility check + this._scene = new THREE$1.Scene(); + /** + * The function that the culler uses to reprocess the scene. Generally it's + * better to call needsUpdate, but you can also call this to force it. + * @param force if true, it will refresh the scene even if needsUpdate is + * not true. + */ + this.updateVisibility = async (force) => { + if (!this.enabled) + return; + if (!this.needsUpdate && !force) + return; + const camera = this.components.camera.get(); + camera.updateMatrix(); + this.renderer.setSize(this.rtWidth, this.rtHeight); + this.renderer.setRenderTarget(this.renderTarget); + this.renderer.render(this._scene, camera); + const context = this.renderer.getContext(); + await readPixelsAsync(context, 0, 0, this.rtWidth, this.rtHeight, context.RGBA, context.UNSIGNED_BYTE, this._buffer); + this.renderer.setRenderTarget(null); + if (this.renderDebugFrame) { + this.renderer.render(this._scene, camera); + } + this.worker.postMessage({ + buffer: this._buffer, + }); + this.needsUpdate = false; + }; + this.handleWorkerMessage = async (event) => { + const colors = event.data.colors; + this._recentlyHiddenMeshes = new Set(this._currentVisibleMeshes); + this._currentVisibleMeshes.clear(); + this._visibleMeshes = []; + // Make found meshes visible + for (const code of colors.values()) { + const mesh = this._meshColorMap.get(code); + if (mesh) { + this._visibleMeshes.push(mesh); + mesh.visible = true; + this._currentVisibleMeshes.add(mesh.uuid); + this._recentlyHiddenMeshes.delete(mesh.uuid); + } + } + // Hide meshes that were visible before but not anymore + for (const uuid of this._recentlyHiddenMeshes) { + const mesh = this._meshes.get(uuid); + if (mesh === undefined) + continue; + mesh.visible = false; + } + await this.onViewUpdated.trigger(); + }; + components.tools.add(ScreenCuller.uuid, this); + this.renderer = new THREE$1.WebGLRenderer(); + const planes = this.components.renderer.clippingPlanes; + this.renderer.clippingPlanes = planes; + this.renderTarget = new THREE$1.WebGLRenderTarget(rtWidth, rtHeight); + this.bufferSize = rtWidth * rtHeight * 4; + this._buffer = new Uint8Array(this.bufferSize); + this.materialCache = new Map(); + const code = ` + addEventListener("message", (event) => { + const { buffer } = event.data; + const colors = new Set(); + for (let i = 0; i < buffer.length; i += 4) { + const r = buffer[i]; + const g = buffer[i + 1]; + const b = buffer[i + 2]; + const code = "" + r + "-" + g + "-" + b; + colors.add(code); + } + postMessage({ colors }); + }); + `; + const blob = new Blob([code], { type: "application/javascript" }); + this.worker = new Worker(URL.createObjectURL(blob)); + this.worker.addEventListener("message", this.handleWorkerMessage); + if (autoUpdate) + window.setInterval(this.updateVisibility, updateInterval); + } + /** + * {@link Component.get}. + * @returns the map of internal meshes used to determine visibility. + */ + get() { + return this._colorMeshes; + } + /** {@link Disposable.dispose} */ + async dispose() { + this.enabled = false; + this._currentVisibleMeshes.clear(); + this._recentlyHiddenMeshes.clear(); + this._scene.children.length = 0; + this.onViewUpdated.reset(); + this.worker.terminate(); + this.renderer.dispose(); + this.renderTarget.dispose(); + this._buffer = null; + this._transparentMat.dispose(); + this._meshColorMap.clear(); + this._visibleMeshes = []; + for (const id in this.materialCache) { + const material = this.materialCache.get(id); + if (material) { + material.dispose(); + } + } + const disposer = this.components.tools.get(Disposer); + for (const id in this._colorMeshes) { + const mesh = this._colorMeshes.get(id); + if (mesh) { + disposer.destroy(mesh); + } + } + this._colorMeshes.clear(); + this._meshes.clear(); + await this.onDisposed.trigger(ScreenCuller.uuid); + this.onDisposed.reset(); + } + /** + * Adds a new mesh to be processed and managed by the culler. + * @mesh the mesh or instanced mesh to add. + */ + add(mesh) { + if (!this.enabled) + return; + const isInstanced = mesh instanceof THREE$1.InstancedMesh; + const { geometry, material } = mesh; + const { r, g, b, code } = this.getNextColor(); + const colorMaterial = this.getMaterial(r, g, b); + let newMaterial; + if (Array.isArray(material)) { + let transparentOnly = true; + const matArray = []; + for (const mat of material) { + if (this.isTransparent(mat)) { + matArray.push(this._transparentMat); + } + else { + transparentOnly = false; + matArray.push(colorMaterial); + } + } + // If we find that all the materials are transparent then we must remove this from analysis + if (transparentOnly) { + colorMaterial.dispose(); + return; + } + newMaterial = matArray; + } + else if (this.isTransparent(material)) { + // This material is transparent, so we must remove it from analysis + colorMaterial.dispose(); + return; + } + else { + newMaterial = colorMaterial; + } + this._meshColorMap.set(code, mesh); + const count = isInstanced ? mesh.count : 1; + const colorMesh = new THREE$1.InstancedMesh(geometry, newMaterial, count); + if (isInstanced) { + colorMesh.instanceMatrix = mesh.instanceMatrix; + } + else { + colorMesh.setMatrixAt(0, new THREE$1.Matrix4()); + } + mesh.visible = false; + colorMesh.applyMatrix4(mesh.matrix); + colorMesh.updateMatrix(); + const parent = mesh.parent; + if (parent instanceof FragmentsGroup) { + const manager = this.components.tools.get(FragmentManager); + const coordinationModel = manager.groups.find((model) => model.uuid === manager.baseCoordinationModel); + if (coordinationModel) { + colorMesh.applyMatrix4(parent.coordinationMatrix.clone().invert()); + colorMesh.applyMatrix4(coordinationModel.coordinationMatrix); + } + } + this._scene.add(colorMesh); + this._colorMeshes.set(mesh.uuid, colorMesh); + this._meshes.set(mesh.uuid, mesh); + } + getMaterial(r, g, b) { + const colorEnabled = THREE$1.ColorManagement.enabled; + THREE$1.ColorManagement.enabled = false; + const code = `rgb(${r}, ${g}, ${b})`; + const color = new THREE$1.Color(code); + let material = this.materialCache.get(code); + const clippingPlanes = this.components.renderer.clippingPlanes; + if (!material) { + material = new THREE$1.MeshBasicMaterial({ + color, + clippingPlanes, + side: THREE$1.DoubleSide, + }); + this.materialCache.set(code, material); + } + THREE$1.ColorManagement.enabled = colorEnabled; + return material; + } + isTransparent(material) { + return material.transparent && material.opacity < 1; + } + getNextColor() { + if (this._colors.i === 0) { + this._colors.b++; + if (this._colors.b === 256) { + this._colors.b = 0; + this._colors.i = 1; + } + } + if (this._colors.i === 1) { + this._colors.g++; + this._colors.i = 0; + if (this._colors.g === 256) { + this._colors.g = 0; + this._colors.i = 2; + } + } + if (this._colors.i === 2) { + this._colors.r++; + this._colors.i = 1; + if (this._colors.r === 256) { + this._colors.r = 0; + this._colors.i = 0; + } + } + return { + r: this._colors.r, + g: this._colors.g, + b: this._colors.b, + code: `${this._colors.r}-${this._colors.g}-${this._colors.b}`, + }; + } +} +ScreenCuller.uuid = "69f2a50d-c266-44fc-b1bd-fa4d34be89e6"; +ToolComponent.libraryUUIDs.add(ScreenCuller.uuid); - } +/* + * Dexie.js - a minimalistic wrapper for IndexedDB + * =============================================== + * + * By David Fahlander, david.fahlander@gmail.com + * + * Version 3.2.4, Tue May 30 2023 + * + * https://dexie.org + * + * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ + */ + +const _global = typeof globalThis !== 'undefined' ? globalThis : + typeof self !== 'undefined' ? self : + typeof window !== 'undefined' ? window : + global; - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; +const keys = Object.keys; +const isArray = Array.isArray; +if (typeof Promise !== 'undefined' && !_global.Promise) { + _global.Promise = Promise; +} +function extend(obj, extension) { + if (typeof extension !== 'object') + return obj; + keys(extension).forEach(function (key) { + obj[key] = extension[key]; + }); + return obj; +} +const getProto = Object.getPrototypeOf; +const _hasOwn = {}.hasOwnProperty; +function hasOwn(obj, prop) { + return _hasOwn.call(obj, prop); +} +function props(proto, extension) { + if (typeof extension === 'function') + extension = extension(getProto(proto)); + (typeof Reflect === "undefined" ? keys : Reflect.ownKeys)(extension).forEach(key => { + setProp(proto, key, extension[key]); + }); +} +const defineProperty = Object.defineProperty; +function setProp(obj, prop, functionOrGetSet, options) { + defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, "get") && typeof functionOrGetSet.get === 'function' ? + { get: functionOrGetSet.get, set: functionOrGetSet.set, configurable: true } : + { value: functionOrGetSet, configurable: true, writable: true }, options)); +} +function derive(Child) { + return { + from: function (Parent) { + Child.prototype = Object.create(Parent.prototype); + setProp(Child.prototype, "constructor", Child); + return { + extend: props.bind(null, Child.prototype) + }; + } + }; +} +const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +function getPropertyDescriptor(obj, prop) { + const pd = getOwnPropertyDescriptor(obj, prop); + let proto; + return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); +} +const _slice = [].slice; +function slice(args, start, end) { + return _slice.call(args, start, end); +} +function override(origFunc, overridedFactory) { + return overridedFactory(origFunc); +} +function assert(b) { + if (!b) + throw new Error("Assertion Failed"); +} +function asap$1(fn) { + if (_global.setImmediate) + setImmediate(fn); + else + setTimeout(fn, 0); +} +function arrayToObject(array, extractor) { + return array.reduce((result, item, i) => { + var nameAndValue = extractor(item, i); + if (nameAndValue) + result[nameAndValue[0]] = nameAndValue[1]; + return result; + }, {}); +} +function tryCatch(fn, onerror, args) { + try { + fn.apply(null, args); + } + catch (ex) { + onerror && onerror(ex); + } +} +function getByKeyPath(obj, keyPath) { + if (hasOwn(obj, keyPath)) + return obj[keyPath]; + if (!keyPath) + return obj; + if (typeof keyPath !== 'string') { + var rv = []; + for (var i = 0, l = keyPath.length; i < l; ++i) { + var val = getByKeyPath(obj, keyPath[i]); + rv.push(val); + } + return rv; + } + var period = keyPath.indexOf('.'); + if (period !== -1) { + var innerObj = obj[keyPath.substr(0, period)]; + return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); + } + return undefined; +} +function setByKeyPath(obj, keyPath, value) { + if (!obj || keyPath === undefined) + return; + if ('isFrozen' in Object && Object.isFrozen(obj)) + return; + if (typeof keyPath !== 'string' && 'length' in keyPath) { + assert(typeof value !== 'string' && 'length' in value); + for (var i = 0, l = keyPath.length; i < l; ++i) { + setByKeyPath(obj, keyPath[i], value[i]); + } + } + else { + var period = keyPath.indexOf('.'); + if (period !== -1) { + var currentKeyPath = keyPath.substr(0, period); + var remainingKeyPath = keyPath.substr(period + 1); + if (remainingKeyPath === "") + if (value === undefined) { + if (isArray(obj) && !isNaN(parseInt(currentKeyPath))) + obj.splice(currentKeyPath, 1); + else + delete obj[currentKeyPath]; + } + else + obj[currentKeyPath] = value; + else { + var innerObj = obj[currentKeyPath]; + if (!innerObj || !hasOwn(obj, currentKeyPath)) + innerObj = (obj[currentKeyPath] = {}); + setByKeyPath(innerObj, remainingKeyPath, value); + } + } + else { + if (value === undefined) { + if (isArray(obj) && !isNaN(parseInt(keyPath))) + obj.splice(keyPath, 1); + else + delete obj[keyPath]; + } + else + obj[keyPath] = value; + } + } +} +function delByKeyPath(obj, keyPath) { + if (typeof keyPath === 'string') + setByKeyPath(obj, keyPath, undefined); + else if ('length' in keyPath) + [].map.call(keyPath, function (kp) { + setByKeyPath(obj, kp, undefined); + }); +} +function shallowClone(obj) { + var rv = {}; + for (var m in obj) { + if (hasOwn(obj, m)) + rv[m] = obj[m]; + } + return rv; +} +const concat = [].concat; +function flatten(a) { + return concat.apply([], a); +} +const intrinsicTypeNames = "Boolean,String,Date,RegExp,Blob,File,FileList,FileSystemFileHandle,ArrayBuffer,DataView,Uint8ClampedArray,ImageBitmap,ImageData,Map,Set,CryptoKey" + .split(',').concat(flatten([8, 16, 32, 64].map(num => ["Int", "Uint", "Float"].map(t => t + num + "Array")))).filter(t => _global[t]); +const intrinsicTypes = intrinsicTypeNames.map(t => _global[t]); +arrayToObject(intrinsicTypeNames, x => [x, true]); +let circularRefs = null; +function deepClone(any) { + circularRefs = typeof WeakMap !== 'undefined' && new WeakMap(); + const rv = innerDeepClone(any); + circularRefs = null; + return rv; +} +function innerDeepClone(any) { + if (!any || typeof any !== 'object') + return any; + let rv = circularRefs && circularRefs.get(any); + if (rv) + return rv; + if (isArray(any)) { + rv = []; + circularRefs && circularRefs.set(any, rv); + for (var i = 0, l = any.length; i < l; ++i) { + rv.push(innerDeepClone(any[i])); + } + } + else if (intrinsicTypes.indexOf(any.constructor) >= 0) { + rv = any; + } + else { + const proto = getProto(any); + rv = proto === Object.prototype ? {} : Object.create(proto); + circularRefs && circularRefs.set(any, rv); + for (var prop in any) { + if (hasOwn(any, prop)) { + rv[prop] = innerDeepClone(any[prop]); + } + } + } + return rv; +} +const { toString } = {}; +function toStringTag(o) { + return toString.call(o).slice(8, -1); +} +const iteratorSymbol = typeof Symbol !== 'undefined' ? + Symbol.iterator : + '@@iterator'; +const getIteratorOf = typeof iteratorSymbol === "symbol" ? function (x) { + var i; + return x != null && (i = x[iteratorSymbol]) && i.apply(x); +} : function () { return null; }; +const NO_CHAR_ARRAY = {}; +function getArrayOf(arrayLike) { + var i, a, x, it; + if (arguments.length === 1) { + if (isArray(arrayLike)) + return arrayLike.slice(); + if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') + return [arrayLike]; + if ((it = getIteratorOf(arrayLike))) { + a = []; + while ((x = it.next()), !x.done) + a.push(x.value); + return a; + } + if (arrayLike == null) + return [arrayLike]; + i = arrayLike.length; + if (typeof i === 'number') { + a = new Array(i); + while (i--) + a[i] = arrayLike[i]; + return a; + } + return [arrayLike]; + } + i = arguments.length; + a = new Array(i); + while (i--) + a[i] = arguments[i]; + return a; +} +const isAsyncFunction = typeof Symbol !== 'undefined' + ? (fn) => fn[Symbol.toStringTag] === 'AsyncFunction' + : () => false; - if ( sqrSinHalfTheta <= Number.EPSILON ) { +var debug = typeof location !== 'undefined' && + /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); +function setDebug(value, filter) { + debug = value; + libraryFilter = filter; +} +var libraryFilter = () => true; +const NEEDS_THROW_FOR_STACK = !new Error("").stack; +function getErrorWithStack() { + if (NEEDS_THROW_FOR_STACK) + try { + getErrorWithStack.arguments; + throw new Error(); + } + catch (e) { + return e; + } + return new Error(); +} +function prettyStack(exception, numIgnoredFrames) { + var stack = exception.stack; + if (!stack) + return ""; + numIgnoredFrames = (numIgnoredFrames || 0); + if (stack.indexOf(exception.name) === 0) + numIgnoredFrames += (exception.name + exception.message).split('\n').length; + return stack.split('\n') + .slice(numIgnoredFrames) + .filter(libraryFilter) + .map(frame => "\n" + frame) + .join(''); +} - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; +var dexieErrorNames = [ + 'Modify', + 'Bulk', + 'OpenFailed', + 'VersionChange', + 'Schema', + 'Upgrade', + 'InvalidTable', + 'MissingAPI', + 'NoSuchDatabase', + 'InvalidArgument', + 'SubTransaction', + 'Unsupported', + 'Internal', + 'DatabaseClosed', + 'PrematureCommit', + 'ForeignAwait' +]; +var idbDomErrorNames = [ + 'Unknown', + 'Constraint', + 'Data', + 'TransactionInactive', + 'ReadOnly', + 'Version', + 'NotFound', + 'InvalidState', + 'InvalidAccess', + 'Abort', + 'Timeout', + 'QuotaExceeded', + 'Syntax', + 'DataClone' +]; +var errorList = dexieErrorNames.concat(idbDomErrorNames); +var defaultTexts = { + VersionChanged: "Database version changed by other database connection", + DatabaseClosed: "Database has been closed", + Abort: "Transaction aborted", + TransactionInactive: "Transaction has already completed or failed", + MissingAPI: "IndexedDB API missing. Please visit https://tinyurl.com/y2uuvskb" +}; +function DexieError(name, msg) { + this._e = getErrorWithStack(); + this.name = name; + this.message = msg; +} +derive(DexieError).from(Error).extend({ + stack: { + get: function () { + return this._stack || + (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); + } + }, + toString: function () { return this.name + ": " + this.message; } +}); +function getMultiErrorMessage(msg, failures) { + return msg + ". Errors: " + Object.keys(failures) + .map(key => failures[key].toString()) + .filter((v, i, s) => s.indexOf(v) === i) + .join('\n'); +} +function ModifyError(msg, failures, successCount, failedKeys) { + this._e = getErrorWithStack(); + this.failures = failures; + this.failedKeys = failedKeys; + this.successCount = successCount; + this.message = getMultiErrorMessage(msg, failures); +} +derive(ModifyError).from(DexieError); +function BulkError(msg, failures) { + this._e = getErrorWithStack(); + this.name = "BulkError"; + this.failures = Object.keys(failures).map(pos => failures[pos]); + this.failuresByPos = failures; + this.message = getMultiErrorMessage(msg, failures); +} +derive(BulkError).from(DexieError); +var errnames = errorList.reduce((obj, name) => (obj[name] = name + "Error", obj), {}); +const BaseException = DexieError; +var exceptions = errorList.reduce((obj, name) => { + var fullName = name + "Error"; + function DexieError(msgOrInner, inner) { + this._e = getErrorWithStack(); + this.name = fullName; + if (!msgOrInner) { + this.message = defaultTexts[name] || fullName; + this.inner = null; + } + else if (typeof msgOrInner === 'string') { + this.message = `${msgOrInner}${!inner ? '' : '\n ' + inner}`; + this.inner = inner || null; + } + else if (typeof msgOrInner === 'object') { + this.message = `${msgOrInner.name} ${msgOrInner.message}`; + this.inner = msgOrInner; + } + } + derive(DexieError).from(BaseException); + obj[name] = DexieError; + return obj; +}, {}); +exceptions.Syntax = SyntaxError; +exceptions.Type = TypeError; +exceptions.Range = RangeError; +var exceptionMap = idbDomErrorNames.reduce((obj, name) => { + obj[name + "Error"] = exceptions[name]; + return obj; +}, {}); +function mapError(domError, message) { + if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name]) + return domError; + var rv = new exceptionMap[domError.name](message || domError.message, domError); + if ("stack" in domError) { + setProp(rv, "stack", { get: function () { + return this.inner.stack; + } }); + } + return rv; +} +var fullNameExceptions = errorList.reduce((obj, name) => { + if (["Syntax", "Type", "Range"].indexOf(name) === -1) + obj[name + "Error"] = exceptions[name]; + return obj; +}, {}); +fullNameExceptions.ModifyError = ModifyError; +fullNameExceptions.DexieError = DexieError; +fullNameExceptions.BulkError = BulkError; - this.normalize(); - this._onChangeCallback(); +function nop() { } +function mirror(val) { return val; } +function pureFunctionChain(f1, f2) { + if (f1 == null || f1 === mirror) + return f2; + return function (val) { + return f2(f1(val)); + }; +} +function callBoth(on1, on2) { + return function () { + on1.apply(this, arguments); + on2.apply(this, arguments); + }; +} +function hookCreatingChain(f1, f2) { + if (f1 === nop) + return f2; + return function () { + var res = f1.apply(this, arguments); + if (res !== undefined) + arguments[0] = res; + var onsuccess = this.onsuccess, + onerror = this.onerror; + this.onsuccess = null; + this.onerror = null; + var res2 = f2.apply(this, arguments); + if (onsuccess) + this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) + this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + return res2 !== undefined ? res2 : res; + }; +} +function hookDeletingChain(f1, f2) { + if (f1 === nop) + return f2; + return function () { + f1.apply(this, arguments); + var onsuccess = this.onsuccess, + onerror = this.onerror; + this.onsuccess = this.onerror = null; + f2.apply(this, arguments); + if (onsuccess) + this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) + this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + }; +} +function hookUpdatingChain(f1, f2) { + if (f1 === nop) + return f2; + return function (modifications) { + var res = f1.apply(this, arguments); + extend(modifications, res); + var onsuccess = this.onsuccess, + onerror = this.onerror; + this.onsuccess = null; + this.onerror = null; + var res2 = f2.apply(this, arguments); + if (onsuccess) + this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) + this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + return res === undefined ? + (res2 === undefined ? undefined : res2) : + (extend(res, res2)); + }; +} +function reverseStoppableEventChain(f1, f2) { + if (f1 === nop) + return f2; + return function () { + if (f2.apply(this, arguments) === false) + return false; + return f1.apply(this, arguments); + }; +} +function promisableChain(f1, f2) { + if (f1 === nop) + return f2; + return function () { + var res = f1.apply(this, arguments); + if (res && typeof res.then === 'function') { + var thiz = this, i = arguments.length, args = new Array(i); + while (i--) + args[i] = arguments[i]; + return res.then(function () { + return f2.apply(thiz, args); + }); + } + return f2.apply(this, arguments); + }; +} - return this; +var INTERNAL = {}; +const LONG_STACKS_CLIP_LIMIT = 100, +MAX_LONG_STACKS = 20, ZONE_ECHO_LIMIT = 100, [resolvedNativePromise, nativePromiseProto, resolvedGlobalPromise] = typeof Promise === 'undefined' ? + [] : + (() => { + let globalP = Promise.resolve(); + if (typeof crypto === 'undefined' || !crypto.subtle) + return [globalP, getProto(globalP), globalP]; + const nativeP = crypto.subtle.digest("SHA-512", new Uint8Array([0])); + return [ + nativeP, + getProto(nativeP), + globalP + ]; + })(), nativePromiseThen = nativePromiseProto && nativePromiseProto.then; +const NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; +const patchGlobalPromise = !!resolvedGlobalPromise; +var stack_being_generated = false; +var schedulePhysicalTick = resolvedGlobalPromise ? + () => { resolvedGlobalPromise.then(physicalTick); } + : + _global.setImmediate ? + setImmediate.bind(null, physicalTick) : + _global.MutationObserver ? + () => { + var hiddenDiv = document.createElement("div"); + (new MutationObserver(() => { + physicalTick(); + hiddenDiv = null; + })).observe(hiddenDiv, { attributes: true }); + hiddenDiv.setAttribute('i', '1'); + } : + () => { setTimeout(physicalTick, 0); }; +var asap = function (callback, args) { + microtickQueue.push([callback, args]); + if (needsNewPhysicalTick) { + schedulePhysicalTick(); + needsNewPhysicalTick = false; + } +}; +var isOutsideMicroTick = true, +needsNewPhysicalTick = true, +unhandledErrors = [], +rejectingErrors = [], +currentFulfiller = null, rejectionMapper = mirror; +var globalPSD = { + id: 'global', + global: true, + ref: 0, + unhandleds: [], + onunhandled: globalError, + pgp: false, + env: {}, + finalize: function () { + this.unhandleds.forEach(uh => { + try { + globalError(uh[0], uh[1]); + } + catch (e) { } + }); + } +}; +var PSD = globalPSD; +var microtickQueue = []; +var numScheduledCalls = 0; +var tickFinalizers = []; +function DexiePromise(fn) { + if (typeof this !== 'object') + throw new TypeError('Promises must be constructed via new'); + this._listeners = []; + this.onuncatched = nop; + this._lib = false; + var psd = (this._PSD = PSD); + if (debug) { + this._stackHolder = getErrorWithStack(); + this._prev = null; + this._numPrev = 0; + } + if (typeof fn !== 'function') { + if (fn !== INTERNAL) + throw new TypeError('Not a function'); + this._state = arguments[1]; + this._value = arguments[2]; + if (this._state === false) + handleRejection(this, this._value); + return; + } + this._state = null; + this._value = null; + ++psd.ref; + executePromiseTask(this, fn); +} +const thenProp = { + get: function () { + var psd = PSD, microTaskId = totalEchoes; + function then(onFulfilled, onRejected) { + var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); + const cleanup = possibleAwait && !decrementExpectedAwaits(); + var rv = new DexiePromise((resolve, reject) => { + propagateToListener(this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait, cleanup), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait, cleanup), resolve, reject, psd)); + }); + debug && linkToPreviousPromise(rv, this); + return rv; + } + then.prototype = INTERNAL; + return then; + }, + set: function (value) { + setProp(this, 'then', value && value.prototype === INTERNAL ? + thenProp : + { + get: function () { + return value; + }, + set: thenProp.set + }); + } +}; +props(DexiePromise.prototype, { + then: thenProp, + _then: function (onFulfilled, onRejected) { + propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); + }, + catch: function (onRejected) { + if (arguments.length === 1) + return this.then(null, onRejected); + var type = arguments[0], handler = arguments[1]; + return typeof type === 'function' ? this.then(null, err => + err instanceof type ? handler(err) : PromiseReject(err)) + : this.then(null, err => + err && err.name === type ? handler(err) : PromiseReject(err)); + }, + finally: function (onFinally) { + return this.then(value => { + onFinally(); + return value; + }, err => { + onFinally(); + return PromiseReject(err); + }); + }, + stack: { + get: function () { + if (this._stack) + return this._stack; + try { + stack_being_generated = true; + var stacks = getStack(this, [], MAX_LONG_STACKS); + var stack = stacks.join("\nFrom previous: "); + if (this._state !== null) + this._stack = stack; + return stack; + } + finally { + stack_being_generated = false; + } + } + }, + timeout: function (ms, msg) { + return ms < Infinity ? + new DexiePromise((resolve, reject) => { + var handle = setTimeout(() => reject(new exceptions.Timeout(msg)), ms); + this.then(resolve, reject).finally(clearTimeout.bind(null, handle)); + }) : this; + } +}); +if (typeof Symbol !== 'undefined' && Symbol.toStringTag) + setProp(DexiePromise.prototype, Symbol.toStringTag, 'Dexie.Promise'); +globalPSD.env = snapShot(); +function Listener(onFulfilled, onRejected, resolve, reject, zone) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.resolve = resolve; + this.reject = reject; + this.psd = zone; +} +props(DexiePromise, { + all: function () { + var values = getArrayOf.apply(null, arguments) + .map(onPossibleParallellAsync); + return new DexiePromise(function (resolve, reject) { + if (values.length === 0) + resolve([]); + var remaining = values.length; + values.forEach((a, i) => DexiePromise.resolve(a).then(x => { + values[i] = x; + if (!--remaining) + resolve(values); + }, reject)); + }); + }, + resolve: value => { + if (value instanceof DexiePromise) + return value; + if (value && typeof value.then === 'function') + return new DexiePromise((resolve, reject) => { + value.then(resolve, reject); + }); + var rv = new DexiePromise(INTERNAL, true, value); + linkToPreviousPromise(rv, currentFulfiller); + return rv; + }, + reject: PromiseReject, + race: function () { + var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new DexiePromise((resolve, reject) => { + values.map(value => DexiePromise.resolve(value).then(resolve, reject)); + }); + }, + PSD: { + get: () => PSD, + set: value => PSD = value + }, + totalEchoes: { get: () => totalEchoes }, + newPSD: newScope, + usePSD: usePSD, + scheduler: { + get: () => asap, + set: value => { asap = value; } + }, + rejectionMapper: { + get: () => rejectionMapper, + set: value => { rejectionMapper = value; } + }, + follow: (fn, zoneProps) => { + return new DexiePromise((resolve, reject) => { + return newScope((resolve, reject) => { + var psd = PSD; + psd.unhandleds = []; + psd.onunhandled = reject; + psd.finalize = callBoth(function () { + run_at_end_of_this_or_next_physical_tick(() => { + this.unhandleds.length === 0 ? resolve() : reject(this.unhandleds[0]); + }); + }, psd.finalize); + fn(); + }, zoneProps, resolve, reject); + }); + } +}); +if (NativePromise) { + if (NativePromise.allSettled) + setProp(DexiePromise, "allSettled", function () { + const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new DexiePromise(resolve => { + if (possiblePromises.length === 0) + resolve([]); + let remaining = possiblePromises.length; + const results = new Array(remaining); + possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(value => results[i] = { status: "fulfilled", value }, reason => results[i] = { status: "rejected", reason }) + .then(() => --remaining || resolve(results))); + }); + }); + if (NativePromise.any && typeof AggregateError !== 'undefined') + setProp(DexiePromise, "any", function () { + const possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new DexiePromise((resolve, reject) => { + if (possiblePromises.length === 0) + reject(new AggregateError([])); + let remaining = possiblePromises.length; + const failures = new Array(remaining); + possiblePromises.forEach((p, i) => DexiePromise.resolve(p).then(value => resolve(value), failure => { + failures[i] = failure; + if (!--remaining) + reject(new AggregateError(failures)); + })); + }); + }); +} +function executePromiseTask(promise, fn) { + try { + fn(value => { + if (promise._state !== null) + return; + if (value === promise) + throw new TypeError('A promise cannot be resolved with itself.'); + var shouldExecuteTick = promise._lib && beginMicroTickScope(); + if (value && typeof value.then === 'function') { + executePromiseTask(promise, (resolve, reject) => { + value instanceof DexiePromise ? + value._then(resolve, reject) : + value.then(resolve, reject); + }); + } + else { + promise._state = true; + promise._value = value; + propagateAllListeners(promise); + } + if (shouldExecuteTick) + endMicroTickScope(); + }, handleRejection.bind(null, promise)); + } + catch (ex) { + handleRejection(promise, ex); + } +} +function handleRejection(promise, reason) { + rejectingErrors.push(reason); + if (promise._state !== null) + return; + var shouldExecuteTick = promise._lib && beginMicroTickScope(); + reason = rejectionMapper(reason); + promise._state = false; + promise._value = reason; + debug && reason !== null && typeof reason === 'object' && !reason._promise && tryCatch(() => { + var origProp = getPropertyDescriptor(reason, "stack"); + reason._promise = promise; + setProp(reason, "stack", { + get: () => stack_being_generated ? + origProp && (origProp.get ? + origProp.get.apply(reason) : + origProp.value) : + promise.stack + }); + }); + addPossiblyUnhandledError(promise); + propagateAllListeners(promise); + if (shouldExecuteTick) + endMicroTickScope(); +} +function propagateAllListeners(promise) { + var listeners = promise._listeners; + promise._listeners = []; + for (var i = 0, len = listeners.length; i < len; ++i) { + propagateToListener(promise, listeners[i]); + } + var psd = promise._PSD; + --psd.ref || psd.finalize(); + if (numScheduledCalls === 0) { + ++numScheduledCalls; + asap(() => { + if (--numScheduledCalls === 0) + finalizePhysicalTick(); + }, []); + } +} +function propagateToListener(promise, listener) { + if (promise._state === null) { + promise._listeners.push(listener); + return; + } + var cb = promise._state ? listener.onFulfilled : listener.onRejected; + if (cb === null) { + return (promise._state ? listener.resolve : listener.reject)(promise._value); + } + ++listener.psd.ref; + ++numScheduledCalls; + asap(callListener, [cb, promise, listener]); +} +function callListener(cb, promise, listener) { + try { + currentFulfiller = promise; + var ret, value = promise._value; + if (promise._state) { + ret = cb(value); + } + else { + if (rejectingErrors.length) + rejectingErrors = []; + ret = cb(value); + if (rejectingErrors.indexOf(value) === -1) + markErrorAsHandled(promise); + } + listener.resolve(ret); + } + catch (e) { + listener.reject(e); + } + finally { + currentFulfiller = null; + if (--numScheduledCalls === 0) + finalizePhysicalTick(); + --listener.psd.ref || listener.psd.finalize(); + } +} +function getStack(promise, stacks, limit) { + if (stacks.length === limit) + return stacks; + var stack = ""; + if (promise._state === false) { + var failure = promise._value, errorName, message; + if (failure != null) { + errorName = failure.name || "Error"; + message = failure.message || failure; + stack = prettyStack(failure, 0); + } + else { + errorName = failure; + message = ""; + } + stacks.push(errorName + (message ? ": " + message : "") + stack); + } + if (debug) { + stack = prettyStack(promise._stackHolder, 2); + if (stack && stacks.indexOf(stack) === -1) + stacks.push(stack); + if (promise._prev) + getStack(promise._prev, stacks, limit); + } + return stacks; +} +function linkToPreviousPromise(promise, prev) { + var numPrev = prev ? prev._numPrev + 1 : 0; + if (numPrev < LONG_STACKS_CLIP_LIMIT) { + promise._prev = prev; + promise._numPrev = numPrev; + } +} +function physicalTick() { + beginMicroTickScope() && endMicroTickScope(); +} +function beginMicroTickScope() { + var wasRootExec = isOutsideMicroTick; + isOutsideMicroTick = false; + needsNewPhysicalTick = false; + return wasRootExec; +} +function endMicroTickScope() { + var callbacks, i, l; + do { + while (microtickQueue.length > 0) { + callbacks = microtickQueue; + microtickQueue = []; + l = callbacks.length; + for (i = 0; i < l; ++i) { + var item = callbacks[i]; + item[0].apply(null, item[1]); + } + } + } while (microtickQueue.length > 0); + isOutsideMicroTick = true; + needsNewPhysicalTick = true; +} +function finalizePhysicalTick() { + var unhandledErrs = unhandledErrors; + unhandledErrors = []; + unhandledErrs.forEach(p => { + p._PSD.onunhandled.call(null, p._value, p); + }); + var finalizers = tickFinalizers.slice(0); + var i = finalizers.length; + while (i) + finalizers[--i](); +} +function run_at_end_of_this_or_next_physical_tick(fn) { + function finalizer() { + fn(); + tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1); + } + tickFinalizers.push(finalizer); + ++numScheduledCalls; + asap(() => { + if (--numScheduledCalls === 0) + finalizePhysicalTick(); + }, []); +} +function addPossiblyUnhandledError(promise) { + if (!unhandledErrors.some(p => p._value === promise._value)) + unhandledErrors.push(promise); +} +function markErrorAsHandled(promise) { + var i = unhandledErrors.length; + while (i) + if (unhandledErrors[--i]._value === promise._value) { + unhandledErrors.splice(i, 1); + return; + } +} +function PromiseReject(reason) { + return new DexiePromise(INTERNAL, false, reason); +} +function wrap(fn, errorCatcher) { + var psd = PSD; + return function () { + var wasRootExec = beginMicroTickScope(), outerScope = PSD; + try { + switchToZone(psd, true); + return fn.apply(this, arguments); + } + catch (e) { + errorCatcher && errorCatcher(e); + } + finally { + switchToZone(outerScope, false); + if (wasRootExec) + endMicroTickScope(); + } + }; +} +const task = { awaits: 0, echoes: 0, id: 0 }; +var taskCounter = 0; +var zoneStack = []; +var zoneEchoes = 0; +var totalEchoes = 0; +var zone_id_counter = 0; +function newScope(fn, props, a1, a2) { + var parent = PSD, psd = Object.create(parent); + psd.parent = parent; + psd.ref = 0; + psd.global = false; + psd.id = ++zone_id_counter; + var globalEnv = globalPSD.env; + psd.env = patchGlobalPromise ? { + Promise: DexiePromise, + PromiseProp: { value: DexiePromise, configurable: true, writable: true }, + all: DexiePromise.all, + race: DexiePromise.race, + allSettled: DexiePromise.allSettled, + any: DexiePromise.any, + resolve: DexiePromise.resolve, + reject: DexiePromise.reject, + nthen: getPatchedPromiseThen(globalEnv.nthen, psd), + gthen: getPatchedPromiseThen(globalEnv.gthen, psd) + } : {}; + if (props) + extend(psd, props); + ++parent.ref; + psd.finalize = function () { + --this.parent.ref || this.parent.finalize(); + }; + var rv = usePSD(psd, fn, a1, a2); + if (psd.ref === 0) + psd.finalize(); + return rv; +} +function incrementExpectedAwaits() { + if (!task.id) + task.id = ++taskCounter; + ++task.awaits; + task.echoes += ZONE_ECHO_LIMIT; + return task.id; +} +function decrementExpectedAwaits() { + if (!task.awaits) + return false; + if (--task.awaits === 0) + task.id = 0; + task.echoes = task.awaits * ZONE_ECHO_LIMIT; + return true; +} +if (('' + nativePromiseThen).indexOf('[native code]') === -1) { + incrementExpectedAwaits = decrementExpectedAwaits = nop; +} +function onPossibleParallellAsync(possiblePromise) { + if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { + incrementExpectedAwaits(); + return possiblePromise.then(x => { + decrementExpectedAwaits(); + return x; + }, e => { + decrementExpectedAwaits(); + return rejection(e); + }); + } + return possiblePromise; +} +function zoneEnterEcho(targetZone) { + ++totalEchoes; + if (!task.echoes || --task.echoes === 0) { + task.echoes = task.id = 0; + } + zoneStack.push(PSD); + switchToZone(targetZone, true); +} +function zoneLeaveEcho() { + var zone = zoneStack[zoneStack.length - 1]; + zoneStack.pop(); + switchToZone(zone, false); +} +function switchToZone(targetZone, bEnteringZone) { + var currentZone = PSD; + if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (!--zoneEchoes || targetZone !== PSD)) { + enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); + } + if (targetZone === PSD) + return; + PSD = targetZone; + if (currentZone === globalPSD) + globalPSD.env = snapShot(); + if (patchGlobalPromise) { + var GlobalPromise = globalPSD.env.Promise; + var targetEnv = targetZone.env; + nativePromiseProto.then = targetEnv.nthen; + GlobalPromise.prototype.then = targetEnv.gthen; + if (currentZone.global || targetZone.global) { + Object.defineProperty(_global, 'Promise', targetEnv.PromiseProp); + GlobalPromise.all = targetEnv.all; + GlobalPromise.race = targetEnv.race; + GlobalPromise.resolve = targetEnv.resolve; + GlobalPromise.reject = targetEnv.reject; + if (targetEnv.allSettled) + GlobalPromise.allSettled = targetEnv.allSettled; + if (targetEnv.any) + GlobalPromise.any = targetEnv.any; + } + } +} +function snapShot() { + var GlobalPromise = _global.Promise; + return patchGlobalPromise ? { + Promise: GlobalPromise, + PromiseProp: Object.getOwnPropertyDescriptor(_global, "Promise"), + all: GlobalPromise.all, + race: GlobalPromise.race, + allSettled: GlobalPromise.allSettled, + any: GlobalPromise.any, + resolve: GlobalPromise.resolve, + reject: GlobalPromise.reject, + nthen: nativePromiseProto.then, + gthen: GlobalPromise.prototype.then + } : {}; +} +function usePSD(psd, fn, a1, a2, a3) { + var outerScope = PSD; + try { + switchToZone(psd, true); + return fn(a1, a2, a3); + } + finally { + switchToZone(outerScope, false); + } +} +function enqueueNativeMicroTask(job) { + nativePromiseThen.call(resolvedNativePromise, job); +} +function nativeAwaitCompatibleWrap(fn, zone, possibleAwait, cleanup) { + return typeof fn !== 'function' ? fn : function () { + var outerZone = PSD; + if (possibleAwait) + incrementExpectedAwaits(); + switchToZone(zone, true); + try { + return fn.apply(this, arguments); + } + finally { + switchToZone(outerZone, false); + if (cleanup) + enqueueNativeMicroTask(decrementExpectedAwaits); + } + }; +} +function getPatchedPromiseThen(origThen, zone) { + return function (onResolved, onRejected) { + return origThen.call(this, nativeAwaitCompatibleWrap(onResolved, zone), nativeAwaitCompatibleWrap(onRejected, zone)); + }; +} +const UNHANDLEDREJECTION = "unhandledrejection"; +function globalError(err, promise) { + var rv; + try { + rv = promise.onuncatched(err); + } + catch (e) { } + if (rv !== false) + try { + var event, eventData = { promise: promise, reason: err }; + if (_global.document && document.createEvent) { + event = document.createEvent('Event'); + event.initEvent(UNHANDLEDREJECTION, true, true); + extend(event, eventData); + } + else if (_global.CustomEvent) { + event = new CustomEvent(UNHANDLEDREJECTION, { detail: eventData }); + extend(event, eventData); + } + if (event && _global.dispatchEvent) { + dispatchEvent(event); + if (!_global.PromiseRejectionEvent && _global.onunhandledrejection) + try { + _global.onunhandledrejection(event); + } + catch (_) { } + } + if (debug && event && !event.defaultPrevented) { + console.warn(`Unhandled rejection: ${err.stack || err}`); + } + } + catch (e) { } +} +var rejection = DexiePromise.reject; - } +function tempTransaction(db, mode, storeNames, fn) { + if (!db.idbdb || (!db._state.openComplete && (!PSD.letThrough && !db._vip))) { + if (db._state.openComplete) { + return rejection(new exceptions.DatabaseClosed(db._state.dbOpenError)); + } + if (!db._state.isBeingOpened) { + if (!db._options.autoOpen) + return rejection(new exceptions.DatabaseClosed()); + db.open().catch(nop); + } + return db._state.dbReadyPromise.then(() => tempTransaction(db, mode, storeNames, fn)); + } + else { + var trans = db._createTransaction(mode, storeNames, db._dbSchema); + try { + trans.create(); + db._state.PR1398_maxLoop = 3; + } + catch (ex) { + if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { + console.warn('Dexie: Need to reopen db'); + db._close(); + return db.open().then(() => tempTransaction(db, mode, storeNames, fn)); + } + return rejection(ex); + } + return trans._promise(mode, (resolve, reject) => { + return newScope(() => { + PSD.trans = trans; + return fn(resolve, reject, trans); + }); + }).then(result => { + return trans._completion.then(() => result); + }); + } +} - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; +const DEXIE_VERSION = '3.2.4'; +const maxString = String.fromCharCode(65535); +const minKey = -Infinity; +const INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array."; +const STRING_EXPECTED = "String expected."; +const connections = []; +const isIEOrEdge = typeof navigator !== 'undefined' && /(MSIE|Trident|Edge)/.test(navigator.userAgent); +const hasIEDeleteObjectStoreBug = isIEOrEdge; +const hangsOnDeleteLargeKeyRange = isIEOrEdge; +const dexieStackFrameFilter = frame => !/(dexie\.js|dexie\.min\.js)/.test(frame); +const DBNAMES_DB = '__dbnames'; +const READONLY = 'readonly'; +const READWRITE = 'readwrite'; - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); +function combine(filter1, filter2) { + return filter1 ? + filter2 ? + function () { return filter1.apply(this, arguments) && filter2.apply(this, arguments); } : + filter1 : + filter2; +} - this._onChangeCallback(); +const AnyRange = { + type: 3 , + lower: -Infinity, + lowerOpen: false, + upper: [[]], + upperOpen: false +}; - return this; +function workaroundForUndefinedPrimKey(keyPath) { + return typeof keyPath === "string" && !/\./.test(keyPath) + ? (obj) => { + if (obj[keyPath] === undefined && (keyPath in obj)) { + obj = deepClone(obj); + delete obj[keyPath]; + } + return obj; + } + : (obj) => obj; +} - } +let Table$3 = class Table { + _trans(mode, fn, writeLocked) { + const trans = this._tx || PSD.trans; + const tableName = this.name; + function checkTableInTransaction(resolve, reject, trans) { + if (!trans.schema[tableName]) + throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); + return fn(trans.idbtrans, trans); + } + const wasRootExec = beginMicroTickScope(); + try { + return trans && trans.db === this.db ? + trans === PSD.trans ? + trans._promise(mode, checkTableInTransaction, writeLocked) : + newScope(() => trans._promise(mode, checkTableInTransaction, writeLocked), { trans: trans, transless: PSD.transless || PSD }) : + tempTransaction(this.db, mode, [this.name], checkTableInTransaction); + } + finally { + if (wasRootExec) + endMicroTickScope(); + } + } + get(keyOrCrit, cb) { + if (keyOrCrit && keyOrCrit.constructor === Object) + return this.where(keyOrCrit).first(cb); + return this._trans('readonly', (trans) => { + return this.core.get({ trans, key: keyOrCrit }) + .then(res => this.hook.reading.fire(res)); + }).then(cb); + } + where(indexOrCrit) { + if (typeof indexOrCrit === 'string') + return new this.db.WhereClause(this, indexOrCrit); + if (isArray(indexOrCrit)) + return new this.db.WhereClause(this, `[${indexOrCrit.join('+')}]`); + const keyPaths = keys(indexOrCrit); + if (keyPaths.length === 1) + return this + .where(keyPaths[0]) + .equals(indexOrCrit[keyPaths[0]]); + const compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(ix => ix.compound && + keyPaths.every(keyPath => ix.keyPath.indexOf(keyPath) >= 0) && + ix.keyPath.every(keyPath => keyPaths.indexOf(keyPath) >= 0))[0]; + if (compoundIndex && this.db._maxKey !== maxString) + return this + .where(compoundIndex.name) + .equals(compoundIndex.keyPath.map(kp => indexOrCrit[kp])); + if (!compoundIndex && debug) + console.warn(`The query ${JSON.stringify(indexOrCrit)} on ${this.name} would benefit of a ` + + `compound index [${keyPaths.join('+')}]`); + const { idxByName } = this.schema; + const idb = this.db._deps.indexedDB; + function equals(a, b) { + try { + return idb.cmp(a, b) === 0; + } + catch (e) { + return false; + } + } + const [idx, filterFunction] = keyPaths.reduce(([prevIndex, prevFilterFn], keyPath) => { + const index = idxByName[keyPath]; + const value = indexOrCrit[keyPath]; + return [ + prevIndex || index, + prevIndex || !index ? + combine(prevFilterFn, index && index.multi ? + x => { + const prop = getByKeyPath(x, keyPath); + return isArray(prop) && prop.some(item => equals(value, item)); + } : x => equals(value, getByKeyPath(x, keyPath))) + : prevFilterFn + ]; + }, [null, null]); + return idx ? + this.where(idx.name).equals(indexOrCrit[idx.keyPath]) + .filter(filterFunction) : + compoundIndex ? + this.filter(filterFunction) : + this.where(keyPaths).equals(''); + } + filter(filterFunction) { + return this.toCollection().and(filterFunction); + } + count(thenShortcut) { + return this.toCollection().count(thenShortcut); + } + offset(offset) { + return this.toCollection().offset(offset); + } + limit(numRows) { + return this.toCollection().limit(numRows); + } + each(callback) { + return this.toCollection().each(callback); + } + toArray(thenShortcut) { + return this.toCollection().toArray(thenShortcut); + } + toCollection() { + return new this.db.Collection(new this.db.WhereClause(this)); + } + orderBy(index) { + return new this.db.Collection(new this.db.WhereClause(this, isArray(index) ? + `[${index.join('+')}]` : + index)); + } + reverse() { + return this.toCollection().reverse(); + } + mapToClass(constructor) { + this.schema.mappedClass = constructor; + const readHook = obj => { + if (!obj) + return obj; + const res = Object.create(constructor.prototype); + for (var m in obj) + if (hasOwn(obj, m)) + try { + res[m] = obj[m]; + } + catch (_) { } + return res; + }; + if (this.schema.readHook) { + this.hook.reading.unsubscribe(this.schema.readHook); + } + this.schema.readHook = readHook; + this.hook("reading", readHook); + return constructor; + } + defineClass() { + function Class(content) { + extend(this, content); + } + return this.mapToClass(Class); + } + add(obj, key) { + const { auto, keyPath } = this.schema.primKey; + let objToAdd = obj; + if (keyPath && auto) { + objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); + } + return this._trans('readwrite', trans => { + return this.core.mutate({ trans, type: 'add', keys: key != null ? [key] : null, values: [objToAdd] }); + }).then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult) + .then(lastResult => { + if (keyPath) { + try { + setByKeyPath(obj, keyPath, lastResult); + } + catch (_) { } + } + return lastResult; + }); + } + update(keyOrObject, modifications) { + if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) { + const key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); + if (key === undefined) + return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); + try { + if (typeof modifications !== "function") { + keys(modifications).forEach(keyPath => { + setByKeyPath(keyOrObject, keyPath, modifications[keyPath]); + }); + } + else { + modifications(keyOrObject, { value: keyOrObject, primKey: key }); + } + } + catch (_a) { + } + return this.where(":id").equals(key).modify(modifications); + } + else { + return this.where(":id").equals(keyOrObject).modify(modifications); + } + } + put(obj, key) { + const { auto, keyPath } = this.schema.primKey; + let objToAdd = obj; + if (keyPath && auto) { + objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); + } + return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'put', values: [objToAdd], keys: key != null ? [key] : null })) + .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult) + .then(lastResult => { + if (keyPath) { + try { + setByKeyPath(obj, keyPath, lastResult); + } + catch (_) { } + } + return lastResult; + }); + } + delete(key) { + return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'delete', keys: [key] })) + .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined); + } + clear() { + return this._trans('readwrite', trans => this.core.mutate({ trans, type: 'deleteRange', range: AnyRange })) + .then(res => res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined); + } + bulkGet(keys) { + return this._trans('readonly', trans => { + return this.core.getMany({ + keys, + trans + }).then(result => result.map(res => this.hook.reading.fire(res))); + }); + } + bulkAdd(objects, keysOrOptions, options) { + const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; + options = options || (keys ? undefined : keysOrOptions); + const wantResults = options ? options.allKeys : undefined; + return this._trans('readwrite', trans => { + const { auto, keyPath } = this.schema.primKey; + if (keyPath && keys) + throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); + if (keys && keys.length !== objects.length) + throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + const numObjects = objects.length; + let objectsToAdd = keyPath && auto ? + objects.map(workaroundForUndefinedPrimKey(keyPath)) : + objects; + return this.core.mutate({ trans, type: 'add', keys: keys, values: objectsToAdd, wantResults }) + .then(({ numFailures, results, lastResult, failures }) => { + const result = wantResults ? results : lastResult; + if (numFailures === 0) + return result; + throw new BulkError(`${this.name}.bulkAdd(): ${numFailures} of ${numObjects} operations failed`, failures); + }); + }); + } + bulkPut(objects, keysOrOptions, options) { + const keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; + options = options || (keys ? undefined : keysOrOptions); + const wantResults = options ? options.allKeys : undefined; + return this._trans('readwrite', trans => { + const { auto, keyPath } = this.schema.primKey; + if (keyPath && keys) + throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); + if (keys && keys.length !== objects.length) + throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + const numObjects = objects.length; + let objectsToPut = keyPath && auto ? + objects.map(workaroundForUndefinedPrimKey(keyPath)) : + objects; + return this.core.mutate({ trans, type: 'put', keys: keys, values: objectsToPut, wantResults }) + .then(({ numFailures, results, lastResult, failures }) => { + const result = wantResults ? results : lastResult; + if (numFailures === 0) + return result; + throw new BulkError(`${this.name}.bulkPut(): ${numFailures} of ${numObjects} operations failed`, failures); + }); + }); + } + bulkDelete(keys) { + const numKeys = keys.length; + return this._trans('readwrite', trans => { + return this.core.mutate({ trans, type: 'delete', keys: keys }); + }).then(({ numFailures, lastResult, failures }) => { + if (numFailures === 0) + return lastResult; + throw new BulkError(`${this.name}.bulkDelete(): ${numFailures} of ${numKeys} operations failed`, failures); + }); + } +}; - slerpQuaternions( qa, qb, t ) { +function Events(ctx) { + var evs = {}; + var rv = function (eventName, subscriber) { + if (subscriber) { + var i = arguments.length, args = new Array(i - 1); + while (--i) + args[i - 1] = arguments[i]; + evs[eventName].subscribe.apply(null, args); + return ctx; + } + else if (typeof (eventName) === 'string') { + return evs[eventName]; + } + }; + rv.addEventType = add; + for (var i = 1, l = arguments.length; i < l; ++i) { + add(arguments[i]); + } + return rv; + function add(eventName, chainFunction, defaultFunction) { + if (typeof eventName === 'object') + return addConfiguredEvents(eventName); + if (!chainFunction) + chainFunction = reverseStoppableEventChain; + if (!defaultFunction) + defaultFunction = nop; + var context = { + subscribers: [], + fire: defaultFunction, + subscribe: function (cb) { + if (context.subscribers.indexOf(cb) === -1) { + context.subscribers.push(cb); + context.fire = chainFunction(context.fire, cb); + } + }, + unsubscribe: function (cb) { + context.subscribers = context.subscribers.filter(function (fn) { return fn !== cb; }); + context.fire = context.subscribers.reduce(chainFunction, defaultFunction); + } + }; + evs[eventName] = rv[eventName] = context; + return context; + } + function addConfiguredEvents(cfg) { + keys(cfg).forEach(function (eventName) { + var args = cfg[eventName]; + if (isArray(args)) { + add(eventName, cfg[eventName][0], cfg[eventName][1]); + } + else if (args === 'asap') { + var context = add(eventName, mirror, function fire() { + var i = arguments.length, args = new Array(i); + while (i--) + args[i] = arguments[i]; + context.subscribers.forEach(function (fn) { + asap$1(function fireEvent() { + fn.apply(null, args); + }); + }); + }); + } + else + throw new exceptions.InvalidArgument("Invalid event config"); + }); + } +} - return this.copy( qa ).slerp( qb, t ); +function makeClassConstructor(prototype, constructor) { + derive(constructor).from({ prototype }); + return constructor; +} - } +function createTableConstructor(db) { + return makeClassConstructor(Table$3.prototype, function Table(name, tableSchema, trans) { + this.db = db; + this._tx = trans; + this.name = name; + this.schema = tableSchema; + this.hook = db._allTables[name] ? db._allTables[name].hook : Events(null, { + "creating": [hookCreatingChain, nop], + "reading": [pureFunctionChain, mirror], + "updating": [hookUpdatingChain, nop], + "deleting": [hookDeletingChain, nop] + }); + }); +} - random() { +function isPlainKeyRange(ctx, ignoreLimitFilter) { + return !(ctx.filter || ctx.algorithm || ctx.or) && + (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); +} +function addFilter(ctx, fn) { + ctx.filter = combine(ctx.filter, fn); +} +function addReplayFilter(ctx, factory, isLimitFilter) { + var curr = ctx.replayFilter; + ctx.replayFilter = curr ? () => combine(curr(), factory()) : factory; + ctx.justLimit = isLimitFilter && !curr; +} +function addMatchFilter(ctx, fn) { + ctx.isMatch = combine(ctx.isMatch, fn); +} +function getIndexOrStore(ctx, coreSchema) { + if (ctx.isPrimKey) + return coreSchema.primaryKey; + const index = coreSchema.getIndexByKeyPath(ctx.index); + if (!index) + throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + coreSchema.name + " is not indexed"); + return index; +} +function openCursor(ctx, coreTable, trans) { + const index = getIndexOrStore(ctx, coreTable.schema); + return coreTable.openCursor({ + trans, + values: !ctx.keysOnly, + reverse: ctx.dir === 'prev', + unique: !!ctx.unique, + query: { + index, + range: ctx.range + } + }); +} +function iter(ctx, fn, coreTrans, coreTable) { + const filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; + if (!ctx.or) { + return iterate(openCursor(ctx, coreTable, coreTrans), combine(ctx.algorithm, filter), fn, !ctx.keysOnly && ctx.valueMapper); + } + else { + const set = {}; + const union = (item, cursor, advance) => { + if (!filter || filter(cursor, advance, result => cursor.stop(result), err => cursor.fail(err))) { + var primaryKey = cursor.primaryKey; + var key = '' + primaryKey; + if (key === '[object ArrayBuffer]') + key = '' + new Uint8Array(primaryKey); + if (!hasOwn(set, key)) { + set[key] = true; + fn(item, cursor, advance); + } + } + }; + return Promise.all([ + ctx.or._iterate(union, coreTrans), + iterate(openCursor(ctx, coreTable, coreTrans), ctx.algorithm, union, !ctx.keysOnly && ctx.valueMapper) + ]); + } +} +function iterate(cursorPromise, filter, fn, valueMapper) { + var mappedFn = valueMapper ? (x, c, a) => fn(valueMapper(x), c, a) : fn; + var wrappedFn = wrap(mappedFn); + return cursorPromise.then(cursor => { + if (cursor) { + return cursor.start(() => { + var c = () => cursor.continue(); + if (!filter || filter(cursor, advancer => c = advancer, val => { cursor.stop(val); c = nop; }, e => { cursor.fail(e); c = nop; })) + wrappedFn(cursor.value, cursor, advancer => c = advancer); + c(); + }); + } + }); +} - // Derived from http://planning.cs.uiuc.edu/node198.html - // Note, this source uses w, x, y, z ordering, - // so we swap the order below. +function cmp(a, b) { + try { + const ta = type(a); + const tb = type(b); + if (ta !== tb) { + if (ta === 'Array') + return 1; + if (tb === 'Array') + return -1; + if (ta === 'binary') + return 1; + if (tb === 'binary') + return -1; + if (ta === 'string') + return 1; + if (tb === 'string') + return -1; + if (ta === 'Date') + return 1; + if (tb !== 'Date') + return NaN; + return -1; + } + switch (ta) { + case 'number': + case 'Date': + case 'string': + return a > b ? 1 : a < b ? -1 : 0; + case 'binary': { + return compareUint8Arrays(getUint8Array(a), getUint8Array(b)); + } + case 'Array': + return compareArrays(a, b); + } + } + catch (_a) { } + return NaN; +} +function compareArrays(a, b) { + const al = a.length; + const bl = b.length; + const l = al < bl ? al : bl; + for (let i = 0; i < l; ++i) { + const res = cmp(a[i], b[i]); + if (res !== 0) + return res; + } + return al === bl ? 0 : al < bl ? -1 : 1; +} +function compareUint8Arrays(a, b) { + const al = a.length; + const bl = b.length; + const l = al < bl ? al : bl; + for (let i = 0; i < l; ++i) { + if (a[i] !== b[i]) + return a[i] < b[i] ? -1 : 1; + } + return al === bl ? 0 : al < bl ? -1 : 1; +} +function type(x) { + const t = typeof x; + if (t !== 'object') + return t; + if (ArrayBuffer.isView(x)) + return 'binary'; + const tsTag = toStringTag(x); + return tsTag === 'ArrayBuffer' ? 'binary' : tsTag; +} +function getUint8Array(a) { + if (a instanceof Uint8Array) + return a; + if (ArrayBuffer.isView(a)) + return new Uint8Array(a.buffer, a.byteOffset, a.byteLength); + return new Uint8Array(a); +} - const u1 = Math.random(); - const sqrt1u1 = Math.sqrt( 1 - u1 ); - const sqrtu1 = Math.sqrt( u1 ); +class Collection { + _read(fn, cb) { + var ctx = this._ctx; + return ctx.error ? + ctx.table._trans(null, rejection.bind(null, ctx.error)) : + ctx.table._trans('readonly', fn).then(cb); + } + _write(fn) { + var ctx = this._ctx; + return ctx.error ? + ctx.table._trans(null, rejection.bind(null, ctx.error)) : + ctx.table._trans('readwrite', fn, "locked"); + } + _addAlgorithm(fn) { + var ctx = this._ctx; + ctx.algorithm = combine(ctx.algorithm, fn); + } + _iterate(fn, coreTrans) { + return iter(this._ctx, fn, coreTrans, this._ctx.table.core); + } + clone(props) { + var rv = Object.create(this.constructor.prototype), ctx = Object.create(this._ctx); + if (props) + extend(ctx, props); + rv._ctx = ctx; + return rv; + } + raw() { + this._ctx.valueMapper = null; + return this; + } + each(fn) { + var ctx = this._ctx; + return this._read(trans => iter(ctx, fn, trans, ctx.table.core)); + } + count(cb) { + return this._read(trans => { + const ctx = this._ctx; + const coreTable = ctx.table.core; + if (isPlainKeyRange(ctx, true)) { + return coreTable.count({ + trans, + query: { + index: getIndexOrStore(ctx, coreTable.schema), + range: ctx.range + } + }).then(count => Math.min(count, ctx.limit)); + } + else { + var count = 0; + return iter(ctx, () => { ++count; return false; }, trans, coreTable) + .then(() => count); + } + }).then(cb); + } + sortBy(keyPath, cb) { + const parts = keyPath.split('.').reverse(), lastPart = parts[0], lastIndex = parts.length - 1; + function getval(obj, i) { + if (i) + return getval(obj[parts[i]], i - 1); + return obj[lastPart]; + } + var order = this._ctx.dir === "next" ? 1 : -1; + function sorter(a, b) { + var aVal = getval(a, lastIndex), bVal = getval(b, lastIndex); + return aVal < bVal ? -order : aVal > bVal ? order : 0; + } + return this.toArray(function (a) { + return a.sort(sorter); + }).then(cb); + } + toArray(cb) { + return this._read(trans => { + var ctx = this._ctx; + if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + const { valueMapper } = ctx; + const index = getIndexOrStore(ctx, ctx.table.core.schema); + return ctx.table.core.query({ + trans, + limit: ctx.limit, + values: true, + query: { + index, + range: ctx.range + } + }).then(({ result }) => valueMapper ? result.map(valueMapper) : result); + } + else { + const a = []; + return iter(ctx, item => a.push(item), trans, ctx.table.core).then(() => a); + } + }, cb); + } + offset(offset) { + var ctx = this._ctx; + if (offset <= 0) + return this; + ctx.offset += offset; + if (isPlainKeyRange(ctx)) { + addReplayFilter(ctx, () => { + var offsetLeft = offset; + return (cursor, advance) => { + if (offsetLeft === 0) + return true; + if (offsetLeft === 1) { + --offsetLeft; + return false; + } + advance(() => { + cursor.advance(offsetLeft); + offsetLeft = 0; + }); + return false; + }; + }); + } + else { + addReplayFilter(ctx, () => { + var offsetLeft = offset; + return () => (--offsetLeft < 0); + }); + } + return this; + } + limit(numRows) { + this._ctx.limit = Math.min(this._ctx.limit, numRows); + addReplayFilter(this._ctx, () => { + var rowsLeft = numRows; + return function (cursor, advance, resolve) { + if (--rowsLeft <= 0) + advance(resolve); + return rowsLeft >= 0; + }; + }, true); + return this; + } + until(filterFunction, bIncludeStopEntry) { + addFilter(this._ctx, function (cursor, advance, resolve) { + if (filterFunction(cursor.value)) { + advance(resolve); + return bIncludeStopEntry; + } + else { + return true; + } + }); + return this; + } + first(cb) { + return this.limit(1).toArray(function (a) { return a[0]; }).then(cb); + } + last(cb) { + return this.reverse().first(cb); + } + filter(filterFunction) { + addFilter(this._ctx, function (cursor) { + return filterFunction(cursor.value); + }); + addMatchFilter(this._ctx, filterFunction); + return this; + } + and(filter) { + return this.filter(filter); + } + or(indexName) { + return new this.db.WhereClause(this._ctx.table, indexName, this); + } + reverse() { + this._ctx.dir = (this._ctx.dir === "prev" ? "next" : "prev"); + if (this._ondirectionchange) + this._ondirectionchange(this._ctx.dir); + return this; + } + desc() { + return this.reverse(); + } + eachKey(cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { cb(cursor.key, cursor); }); + } + eachUniqueKey(cb) { + this._ctx.unique = "unique"; + return this.eachKey(cb); + } + eachPrimaryKey(cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { cb(cursor.primaryKey, cursor); }); + } + keys(cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.key); + }).then(function () { + return a; + }).then(cb); + } + primaryKeys(cb) { + var ctx = this._ctx; + if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + return this._read(trans => { + var index = getIndexOrStore(ctx, ctx.table.core.schema); + return ctx.table.core.query({ + trans, + values: false, + limit: ctx.limit, + query: { + index, + range: ctx.range + } + }); + }).then(({ result }) => result).then(cb); + } + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.primaryKey); + }).then(function () { + return a; + }).then(cb); + } + uniqueKeys(cb) { + this._ctx.unique = "unique"; + return this.keys(cb); + } + firstKey(cb) { + return this.limit(1).keys(function (a) { return a[0]; }).then(cb); + } + lastKey(cb) { + return this.reverse().firstKey(cb); + } + distinct() { + var ctx = this._ctx, idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; + if (!idx || !idx.multi) + return this; + var set = {}; + addFilter(this._ctx, function (cursor) { + var strKey = cursor.primaryKey.toString(); + var found = hasOwn(set, strKey); + set[strKey] = true; + return !found; + }); + return this; + } + modify(changes) { + var ctx = this._ctx; + return this._write(trans => { + var modifyer; + if (typeof changes === 'function') { + modifyer = changes; + } + else { + var keyPaths = keys(changes); + var numKeys = keyPaths.length; + modifyer = function (item) { + var anythingModified = false; + for (var i = 0; i < numKeys; ++i) { + var keyPath = keyPaths[i], val = changes[keyPath]; + if (getByKeyPath(item, keyPath) !== val) { + setByKeyPath(item, keyPath, val); + anythingModified = true; + } + } + return anythingModified; + }; + } + const coreTable = ctx.table.core; + const { outbound, extractKey } = coreTable.schema.primaryKey; + const limit = this.db._options.modifyChunkSize || 200; + const totalFailures = []; + let successCount = 0; + const failedKeys = []; + const applyMutateResult = (expectedCount, res) => { + const { failures, numFailures } = res; + successCount += expectedCount - numFailures; + for (let pos of keys(failures)) { + totalFailures.push(failures[pos]); + } + }; + return this.clone().primaryKeys().then(keys => { + const nextChunk = (offset) => { + const count = Math.min(limit, keys.length - offset); + return coreTable.getMany({ + trans, + keys: keys.slice(offset, offset + count), + cache: "immutable" + }).then(values => { + const addValues = []; + const putValues = []; + const putKeys = outbound ? [] : null; + const deleteKeys = []; + for (let i = 0; i < count; ++i) { + const origValue = values[i]; + const ctx = { + value: deepClone(origValue), + primKey: keys[offset + i] + }; + if (modifyer.call(ctx, ctx.value, ctx) !== false) { + if (ctx.value == null) { + deleteKeys.push(keys[offset + i]); + } + else if (!outbound && cmp(extractKey(origValue), extractKey(ctx.value)) !== 0) { + deleteKeys.push(keys[offset + i]); + addValues.push(ctx.value); + } + else { + putValues.push(ctx.value); + if (outbound) + putKeys.push(keys[offset + i]); + } + } + } + const criteria = isPlainKeyRange(ctx) && + ctx.limit === Infinity && + (typeof changes !== 'function' || changes === deleteCallback) && { + index: ctx.index, + range: ctx.range + }; + return Promise.resolve(addValues.length > 0 && + coreTable.mutate({ trans, type: 'add', values: addValues }) + .then(res => { + for (let pos in res.failures) { + deleteKeys.splice(parseInt(pos), 1); + } + applyMutateResult(addValues.length, res); + })).then(() => (putValues.length > 0 || (criteria && typeof changes === 'object')) && + coreTable.mutate({ + trans, + type: 'put', + keys: putKeys, + values: putValues, + criteria, + changeSpec: typeof changes !== 'function' + && changes + }).then(res => applyMutateResult(putValues.length, res))).then(() => (deleteKeys.length > 0 || (criteria && changes === deleteCallback)) && + coreTable.mutate({ + trans, + type: 'delete', + keys: deleteKeys, + criteria + }).then(res => applyMutateResult(deleteKeys.length, res))).then(() => { + return keys.length > offset + count && nextChunk(offset + limit); + }); + }); + }; + return nextChunk(0).then(() => { + if (totalFailures.length > 0) + throw new ModifyError("Error modifying one or more objects", totalFailures, successCount, failedKeys); + return keys.length; + }); + }); + }); + } + delete() { + var ctx = this._ctx, range = ctx.range; + if (isPlainKeyRange(ctx) && + ((ctx.isPrimKey && !hangsOnDeleteLargeKeyRange) || range.type === 3 )) + { + return this._write(trans => { + const { primaryKey } = ctx.table.core.schema; + const coreRange = range; + return ctx.table.core.count({ trans, query: { index: primaryKey, range: coreRange } }).then(count => { + return ctx.table.core.mutate({ trans, type: 'deleteRange', range: coreRange }) + .then(({ failures, lastResult, results, numFailures }) => { + if (numFailures) + throw new ModifyError("Could not delete some values", Object.keys(failures).map(pos => failures[pos]), count - numFailures); + return count - numFailures; + }); + }); + }); + } + return this.modify(deleteCallback); + } +} +const deleteCallback = (value, ctx) => ctx.value = null; - const u2 = 2 * Math.PI * Math.random(); +function createCollectionConstructor(db) { + return makeClassConstructor(Collection.prototype, function Collection(whereClause, keyRangeGenerator) { + this.db = db; + let keyRange = AnyRange, error = null; + if (keyRangeGenerator) + try { + keyRange = keyRangeGenerator(); + } + catch (ex) { + error = ex; + } + const whereCtx = whereClause._ctx; + const table = whereCtx.table; + const readingHook = table.hook.reading.fire; + this._ctx = { + table: table, + index: whereCtx.index, + isPrimKey: (!whereCtx.index || (table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name)), + range: keyRange, + keysOnly: false, + dir: "next", + unique: "", + algorithm: null, + filter: null, + replayFilter: null, + justLimit: true, + isMatch: null, + offset: 0, + limit: Infinity, + error: error, + or: whereCtx.or, + valueMapper: readingHook !== mirror ? readingHook : null + }; + }); +} - const u3 = 2 * Math.PI * Math.random(); +function simpleCompare(a, b) { + return a < b ? -1 : a === b ? 0 : 1; +} +function simpleCompareReverse(a, b) { + return a > b ? -1 : a === b ? 0 : 1; +} - return this.set( - sqrt1u1 * Math.cos( u2 ), - sqrtu1 * Math.sin( u3 ), - sqrtu1 * Math.cos( u3 ), - sqrt1u1 * Math.sin( u2 ), - ); +function fail(collectionOrWhereClause, err, T) { + var collection = collectionOrWhereClause instanceof WhereClause ? + new collectionOrWhereClause.Collection(collectionOrWhereClause) : + collectionOrWhereClause; + collection._ctx.error = T ? new T(err) : new TypeError(err); + return collection; +} +function emptyCollection(whereClause) { + return new whereClause.Collection(whereClause, () => rangeEqual("")).limit(0); +} +function upperFactory(dir) { + return dir === "next" ? + (s) => s.toUpperCase() : + (s) => s.toLowerCase(); +} +function lowerFactory(dir) { + return dir === "next" ? + (s) => s.toLowerCase() : + (s) => s.toUpperCase(); +} +function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { + var length = Math.min(key.length, lowerNeedle.length); + var llp = -1; + for (var i = 0; i < length; ++i) { + var lwrKeyChar = lowerKey[i]; + if (lwrKeyChar !== lowerNeedle[i]) { + if (cmp(key[i], upperNeedle[i]) < 0) + return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); + if (cmp(key[i], lowerNeedle[i]) < 0) + return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); + if (llp >= 0) + return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); + return null; + } + if (cmp(key[i], lwrKeyChar) < 0) + llp = i; + } + if (length < lowerNeedle.length && dir === "next") + return key + upperNeedle.substr(key.length); + if (length < key.length && dir === "prev") + return key.substr(0, upperNeedle.length); + return (llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1)); +} +function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { + var upper, lower, compare, upperNeedles, lowerNeedles, direction, nextKeySuffix, needlesLen = needles.length; + if (!needles.every(s => typeof s === 'string')) { + return fail(whereClause, STRING_EXPECTED); + } + function initDirection(dir) { + upper = upperFactory(dir); + lower = lowerFactory(dir); + compare = (dir === "next" ? simpleCompare : simpleCompareReverse); + var needleBounds = needles.map(function (needle) { + return { lower: lower(needle), upper: upper(needle) }; + }).sort(function (a, b) { + return compare(a.lower, b.lower); + }); + upperNeedles = needleBounds.map(function (nb) { return nb.upper; }); + lowerNeedles = needleBounds.map(function (nb) { return nb.lower; }); + direction = dir; + nextKeySuffix = (dir === "next" ? "" : suffix); + } + initDirection("next"); + var c = new whereClause.Collection(whereClause, () => createRange(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix)); + c._ondirectionchange = function (direction) { + initDirection(direction); + }; + var firstPossibleNeedle = 0; + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + if (typeof key !== 'string') + return false; + var lowerKey = lower(key); + if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { + return true; + } + else { + var lowestPossibleCasing = null; + for (var i = firstPossibleNeedle; i < needlesLen; ++i) { + var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); + if (casing === null && lowestPossibleCasing === null) + firstPossibleNeedle = i + 1; + else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { + lowestPossibleCasing = casing; + } + } + if (lowestPossibleCasing !== null) { + advance(function () { cursor.continue(lowestPossibleCasing + nextKeySuffix); }); + } + else { + advance(resolve); + } + return false; + } + }); + return c; +} +function createRange(lower, upper, lowerOpen, upperOpen) { + return { + type: 2 , + lower, + upper, + lowerOpen, + upperOpen + }; +} +function rangeEqual(value) { + return { + type: 1 , + lower: value, + upper: value + }; +} - } +class WhereClause { + get Collection() { + return this._ctx.table.db.Collection; + } + between(lower, upper, includeLower, includeUpper) { + includeLower = includeLower !== false; + includeUpper = includeUpper === true; + try { + if ((this._cmp(lower, upper) > 0) || + (this._cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper))) + return emptyCollection(this); + return new this.Collection(this, () => createRange(lower, upper, !includeLower, !includeUpper)); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + } + equals(value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, () => rangeEqual(value)); + } + above(value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, () => createRange(value, undefined, true)); + } + aboveOrEqual(value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, () => createRange(value, undefined, false)); + } + below(value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, () => createRange(undefined, value, false, true)); + } + belowOrEqual(value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, () => createRange(undefined, value)); + } + startsWith(str) { + if (typeof str !== 'string') + return fail(this, STRING_EXPECTED); + return this.between(str, str + maxString, true, true); + } + startsWithIgnoreCase(str) { + if (str === "") + return this.startsWith(str); + return addIgnoreCaseAlgorithm(this, (x, a) => x.indexOf(a[0]) === 0, [str], maxString); + } + equalsIgnoreCase(str) { + return addIgnoreCaseAlgorithm(this, (x, a) => x === a[0], [str], ""); + } + anyOfIgnoreCase() { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, (x, a) => a.indexOf(x) !== -1, set, ""); + } + startsWithAnyOfIgnoreCase() { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, (x, a) => a.some(n => x.indexOf(n) === 0), set, maxString); + } + anyOf() { + const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + let compare = this._cmp; + try { + set.sort(compare); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + if (set.length === 0) + return emptyCollection(this); + const c = new this.Collection(this, () => createRange(set[0], set[set.length - 1])); + c._ondirectionchange = direction => { + compare = (direction === "next" ? + this._ascending : + this._descending); + set.sort(compare); + }; + let i = 0; + c._addAlgorithm((cursor, advance, resolve) => { + const key = cursor.key; + while (compare(key, set[i]) > 0) { + ++i; + if (i === set.length) { + advance(resolve); + return false; + } + } + if (compare(key, set[i]) === 0) { + return true; + } + else { + advance(() => { cursor.continue(set[i]); }); + return false; + } + }); + return c; + } + notEqual(value) { + return this.inAnyRange([[minKey, value], [value, this.db._maxKey]], { includeLowers: false, includeUppers: false }); + } + noneOf() { + const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return new this.Collection(this); + try { + set.sort(this._ascending); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + const ranges = set.reduce((res, val) => res ? + res.concat([[res[res.length - 1][1], val]]) : + [[minKey, val]], null); + ranges.push([set[set.length - 1], this.db._maxKey]); + return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false }); + } + inAnyRange(ranges, options) { + const cmp = this._cmp, ascending = this._ascending, descending = this._descending, min = this._min, max = this._max; + if (ranges.length === 0) + return emptyCollection(this); + if (!ranges.every(range => range[0] !== undefined && + range[1] !== undefined && + ascending(range[0], range[1]) <= 0)) { + return fail(this, "First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower", exceptions.InvalidArgument); + } + const includeLowers = !options || options.includeLowers !== false; + const includeUppers = options && options.includeUppers === true; + function addRange(ranges, newRange) { + let i = 0, l = ranges.length; + for (; i < l; ++i) { + const range = ranges[i]; + if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { + range[0] = min(range[0], newRange[0]); + range[1] = max(range[1], newRange[1]); + break; + } + } + if (i === l) + ranges.push(newRange); + return ranges; + } + let sortDirection = ascending; + function rangeSorter(a, b) { return sortDirection(a[0], b[0]); } + let set; + try { + set = ranges.reduce(addRange, []); + set.sort(rangeSorter); + } + catch (ex) { + return fail(this, INVALID_KEY_ARGUMENT); + } + let rangePos = 0; + const keyIsBeyondCurrentEntry = includeUppers ? + key => ascending(key, set[rangePos][1]) > 0 : + key => ascending(key, set[rangePos][1]) >= 0; + const keyIsBeforeCurrentEntry = includeLowers ? + key => descending(key, set[rangePos][0]) > 0 : + key => descending(key, set[rangePos][0]) >= 0; + function keyWithinCurrentRange(key) { + return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); + } + let checkKey = keyIsBeyondCurrentEntry; + const c = new this.Collection(this, () => createRange(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers)); + c._ondirectionchange = direction => { + if (direction === "next") { + checkKey = keyIsBeyondCurrentEntry; + sortDirection = ascending; + } + else { + checkKey = keyIsBeforeCurrentEntry; + sortDirection = descending; + } + set.sort(rangeSorter); + }; + c._addAlgorithm((cursor, advance, resolve) => { + var key = cursor.key; + while (checkKey(key)) { + ++rangePos; + if (rangePos === set.length) { + advance(resolve); + return false; + } + } + if (keyWithinCurrentRange(key)) { + return true; + } + else if (this._cmp(key, set[rangePos][1]) === 0 || this._cmp(key, set[rangePos][0]) === 0) { + return false; + } + else { + advance(() => { + if (sortDirection === ascending) + cursor.continue(set[rangePos][0]); + else + cursor.continue(set[rangePos][1]); + }); + return false; + } + }); + return c; + } + startsWithAnyOf() { + const set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (!set.every(s => typeof s === 'string')) { + return fail(this, "startsWithAnyOf() only works with strings"); + } + if (set.length === 0) + return emptyCollection(this); + return this.inAnyRange(set.map((str) => [str, str + maxString])); + } +} - equals( quaternion ) { +function createWhereClauseConstructor(db) { + return makeClassConstructor(WhereClause.prototype, function WhereClause(table, index, orCollection) { + this.db = db; + this._ctx = { + table: table, + index: index === ":id" ? null : index, + or: orCollection + }; + const indexedDB = db._deps.indexedDB; + if (!indexedDB) + throw new exceptions.MissingAPI(); + this._cmp = this._ascending = indexedDB.cmp.bind(indexedDB); + this._descending = (a, b) => indexedDB.cmp(b, a); + this._max = (a, b) => indexedDB.cmp(a, b) > 0 ? a : b; + this._min = (a, b) => indexedDB.cmp(a, b) < 0 ? a : b; + this._IDBKeyRange = db._deps.IDBKeyRange; + }); +} - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); +function eventRejectHandler(reject) { + return wrap(function (event) { + preventDefault(event); + reject(event.target.error); + return false; + }); +} +function preventDefault(event) { + if (event.stopPropagation) + event.stopPropagation(); + if (event.preventDefault) + event.preventDefault(); +} - } +const DEXIE_STORAGE_MUTATED_EVENT_NAME = 'storagemutated'; +const STORAGE_MUTATED_DOM_EVENT_NAME = 'x-storagemutated-1'; +const globalEvents = Events(null, DEXIE_STORAGE_MUTATED_EVENT_NAME); - fromArray( array, offset = 0 ) { +class Transaction { + _lock() { + assert(!PSD.global); + ++this._reculock; + if (this._reculock === 1 && !PSD.global) + PSD.lockOwnerFor = this; + return this; + } + _unlock() { + assert(!PSD.global); + if (--this._reculock === 0) { + if (!PSD.global) + PSD.lockOwnerFor = null; + while (this._blockedFuncs.length > 0 && !this._locked()) { + var fnAndPSD = this._blockedFuncs.shift(); + try { + usePSD(fnAndPSD[1], fnAndPSD[0]); + } + catch (e) { } + } + } + return this; + } + _locked() { + return this._reculock && PSD.lockOwnerFor !== this; + } + create(idbtrans) { + if (!this.mode) + return this; + const idbdb = this.db.idbdb; + const dbOpenError = this.db._state.dbOpenError; + assert(!this.idbtrans); + if (!idbtrans && !idbdb) { + switch (dbOpenError && dbOpenError.name) { + case "DatabaseClosedError": + throw new exceptions.DatabaseClosed(dbOpenError); + case "MissingAPIError": + throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); + default: + throw new exceptions.OpenFailed(dbOpenError); + } + } + if (!this.active) + throw new exceptions.TransactionInactive(); + assert(this._completion._state === null); + idbtrans = this.idbtrans = idbtrans || + (this.db.core + ? this.db.core.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability }) + : idbdb.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability })); + idbtrans.onerror = wrap(ev => { + preventDefault(ev); + this._reject(idbtrans.error); + }); + idbtrans.onabort = wrap(ev => { + preventDefault(ev); + this.active && this._reject(new exceptions.Abort(idbtrans.error)); + this.active = false; + this.on("abort").fire(ev); + }); + idbtrans.oncomplete = wrap(() => { + this.active = false; + this._resolve(); + if ('mutatedParts' in idbtrans) { + globalEvents.storagemutated.fire(idbtrans["mutatedParts"]); + } + }); + return this; + } + _promise(mode, fn, bWriteLock) { + if (mode === 'readwrite' && this.mode !== 'readwrite') + return rejection(new exceptions.ReadOnly("Transaction is readonly")); + if (!this.active) + return rejection(new exceptions.TransactionInactive()); + if (this._locked()) { + return new DexiePromise((resolve, reject) => { + this._blockedFuncs.push([() => { + this._promise(mode, fn, bWriteLock).then(resolve, reject); + }, PSD]); + }); + } + else if (bWriteLock) { + return newScope(() => { + var p = new DexiePromise((resolve, reject) => { + this._lock(); + const rv = fn(resolve, reject, this); + if (rv && rv.then) + rv.then(resolve, reject); + }); + p.finally(() => this._unlock()); + p._lib = true; + return p; + }); + } + else { + var p = new DexiePromise((resolve, reject) => { + var rv = fn(resolve, reject, this); + if (rv && rv.then) + rv.then(resolve, reject); + }); + p._lib = true; + return p; + } + } + _root() { + return this.parent ? this.parent._root() : this; + } + waitFor(promiseLike) { + var root = this._root(); + const promise = DexiePromise.resolve(promiseLike); + if (root._waitingFor) { + root._waitingFor = root._waitingFor.then(() => promise); + } + else { + root._waitingFor = promise; + root._waitingQueue = []; + var store = root.idbtrans.objectStore(root.storeNames[0]); + (function spin() { + ++root._spinCount; + while (root._waitingQueue.length) + (root._waitingQueue.shift())(); + if (root._waitingFor) + store.get(-Infinity).onsuccess = spin; + }()); + } + var currentWaitPromise = root._waitingFor; + return new DexiePromise((resolve, reject) => { + promise.then(res => root._waitingQueue.push(wrap(resolve.bind(null, res))), err => root._waitingQueue.push(wrap(reject.bind(null, err)))).finally(() => { + if (root._waitingFor === currentWaitPromise) { + root._waitingFor = null; + } + }); + }); + } + abort() { + if (this.active) { + this.active = false; + if (this.idbtrans) + this.idbtrans.abort(); + this._reject(new exceptions.Abort()); + } + } + table(tableName) { + const memoizedTables = (this._memoizedTables || (this._memoizedTables = {})); + if (hasOwn(memoizedTables, tableName)) + return memoizedTables[tableName]; + const tableSchema = this.schema[tableName]; + if (!tableSchema) { + throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); + } + const transactionBoundTable = new this.db.Table(tableName, tableSchema, this); + transactionBoundTable.core = this.db.core.table(tableName); + memoizedTables[tableName] = transactionBoundTable; + return transactionBoundTable; + } +} - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; +function createTransactionConstructor(db) { + return makeClassConstructor(Transaction.prototype, function Transaction(mode, storeNames, dbschema, chromeTransactionDurability, parent) { + this.db = db; + this.mode = mode; + this.storeNames = storeNames; + this.schema = dbschema; + this.chromeTransactionDurability = chromeTransactionDurability; + this.idbtrans = null; + this.on = Events(this, "complete", "error", "abort"); + this.parent = parent || null; + this.active = true; + this._reculock = 0; + this._blockedFuncs = []; + this._resolve = null; + this._reject = null; + this._waitingFor = null; + this._waitingQueue = null; + this._spinCount = 0; + this._completion = new DexiePromise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + this._completion.then(() => { + this.active = false; + this.on.complete.fire(); + }, e => { + var wasActive = this.active; + this.active = false; + this.on.error.fire(e); + this.parent ? + this.parent._reject(e) : + wasActive && this.idbtrans && this.idbtrans.abort(); + return rejection(e); + }); + }); +} - this._onChangeCallback(); +function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey) { + return { + name, + keyPath, + unique, + multi, + auto, + compound, + src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + nameFromKeyPath(keyPath) + }; +} +function nameFromKeyPath(keyPath) { + return typeof keyPath === 'string' ? + keyPath : + keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : ""; +} - return this; +function createTableSchema(name, primKey, indexes) { + return { + name, + primKey, + indexes, + mappedClass: null, + idxByName: arrayToObject(indexes, index => [index.name, index]) + }; +} - } +function safariMultiStoreFix(storeNames) { + return storeNames.length === 1 ? storeNames[0] : storeNames; +} +let getMaxKey = (IdbKeyRange) => { + try { + IdbKeyRange.only([[]]); + getMaxKey = () => [[]]; + return [[]]; + } + catch (e) { + getMaxKey = () => maxString; + return maxString; + } +}; - toArray( array = [], offset = 0 ) { +function getKeyExtractor(keyPath) { + if (keyPath == null) { + return () => undefined; + } + else if (typeof keyPath === 'string') { + return getSinglePathKeyExtractor(keyPath); + } + else { + return obj => getByKeyPath(obj, keyPath); + } +} +function getSinglePathKeyExtractor(keyPath) { + const split = keyPath.split('.'); + if (split.length === 1) { + return obj => obj[keyPath]; + } + else { + return obj => getByKeyPath(obj, keyPath); + } +} - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; +function arrayify(arrayLike) { + return [].slice.call(arrayLike); +} +let _id_counter = 0; +function getKeyPathAlias(keyPath) { + return keyPath == null ? + ":id" : + typeof keyPath === 'string' ? + keyPath : + `[${keyPath.join('+')}]`; +} +function createDBCore(db, IdbKeyRange, tmpTrans) { + function extractSchema(db, trans) { + const tables = arrayify(db.objectStoreNames); + return { + schema: { + name: db.name, + tables: tables.map(table => trans.objectStore(table)).map(store => { + const { keyPath, autoIncrement } = store; + const compound = isArray(keyPath); + const outbound = keyPath == null; + const indexByKeyPath = {}; + const result = { + name: store.name, + primaryKey: { + name: null, + isPrimaryKey: true, + outbound, + compound, + keyPath, + autoIncrement, + unique: true, + extractKey: getKeyExtractor(keyPath) + }, + indexes: arrayify(store.indexNames).map(indexName => store.index(indexName)) + .map(index => { + const { name, unique, multiEntry, keyPath } = index; + const compound = isArray(keyPath); + const result = { + name, + compound, + keyPath, + unique, + multiEntry, + extractKey: getKeyExtractor(keyPath) + }; + indexByKeyPath[getKeyPathAlias(keyPath)] = result; + return result; + }), + getIndexByKeyPath: (keyPath) => indexByKeyPath[getKeyPathAlias(keyPath)] + }; + indexByKeyPath[":id"] = result.primaryKey; + if (keyPath != null) { + indexByKeyPath[getKeyPathAlias(keyPath)] = result.primaryKey; + } + return result; + }) + }, + hasGetAll: tables.length > 0 && ('getAll' in trans.objectStore(tables[0])) && + !(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && + !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && + [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) + }; + } + function makeIDBKeyRange(range) { + if (range.type === 3 ) + return null; + if (range.type === 4 ) + throw new Error("Cannot convert never type to IDBKeyRange"); + const { lower, upper, lowerOpen, upperOpen } = range; + const idbRange = lower === undefined ? + upper === undefined ? + null : + IdbKeyRange.upperBound(upper, !!upperOpen) : + upper === undefined ? + IdbKeyRange.lowerBound(lower, !!lowerOpen) : + IdbKeyRange.bound(lower, upper, !!lowerOpen, !!upperOpen); + return idbRange; + } + function createDbCoreTable(tableSchema) { + const tableName = tableSchema.name; + function mutate({ trans, type, keys, values, range }) { + return new Promise((resolve, reject) => { + resolve = wrap(resolve); + const store = trans.objectStore(tableName); + const outbound = store.keyPath == null; + const isAddOrPut = type === "put" || type === "add"; + if (!isAddOrPut && type !== 'delete' && type !== 'deleteRange') + throw new Error("Invalid operation type: " + type); + const { length } = keys || values || { length: 1 }; + if (keys && values && keys.length !== values.length) { + throw new Error("Given keys array must have same length as given values array."); + } + if (length === 0) + return resolve({ numFailures: 0, failures: {}, results: [], lastResult: undefined }); + let req; + const reqs = []; + const failures = []; + let numFailures = 0; + const errorHandler = event => { + ++numFailures; + preventDefault(event); + }; + if (type === 'deleteRange') { + if (range.type === 4 ) + return resolve({ numFailures, failures, results: [], lastResult: undefined }); + if (range.type === 3 ) + reqs.push(req = store.clear()); + else + reqs.push(req = store.delete(makeIDBKeyRange(range))); + } + else { + const [args1, args2] = isAddOrPut ? + outbound ? + [values, keys] : + [values, null] : + [keys, null]; + if (isAddOrPut) { + for (let i = 0; i < length; ++i) { + reqs.push(req = (args2 && args2[i] !== undefined ? + store[type](args1[i], args2[i]) : + store[type](args1[i]))); + req.onerror = errorHandler; + } + } + else { + for (let i = 0; i < length; ++i) { + reqs.push(req = store[type](args1[i])); + req.onerror = errorHandler; + } + } + } + const done = event => { + const lastResult = event.target.result; + reqs.forEach((req, i) => req.error != null && (failures[i] = req.error)); + resolve({ + numFailures, + failures, + results: type === "delete" ? keys : reqs.map(req => req.result), + lastResult + }); + }; + req.onerror = event => { + errorHandler(event); + done(event); + }; + req.onsuccess = done; + }); + } + function openCursor({ trans, values, query, reverse, unique }) { + return new Promise((resolve, reject) => { + resolve = wrap(resolve); + const { index, range } = query; + const store = trans.objectStore(tableName); + const source = index.isPrimaryKey ? + store : + store.index(index.name); + const direction = reverse ? + unique ? + "prevunique" : + "prev" : + unique ? + "nextunique" : + "next"; + const req = values || !('openKeyCursor' in source) ? + source.openCursor(makeIDBKeyRange(range), direction) : + source.openKeyCursor(makeIDBKeyRange(range), direction); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(ev => { + const cursor = req.result; + if (!cursor) { + resolve(null); + return; + } + cursor.___id = ++_id_counter; + cursor.done = false; + const _cursorContinue = cursor.continue.bind(cursor); + let _cursorContinuePrimaryKey = cursor.continuePrimaryKey; + if (_cursorContinuePrimaryKey) + _cursorContinuePrimaryKey = _cursorContinuePrimaryKey.bind(cursor); + const _cursorAdvance = cursor.advance.bind(cursor); + const doThrowCursorIsNotStarted = () => { throw new Error("Cursor not started"); }; + const doThrowCursorIsStopped = () => { throw new Error("Cursor not stopped"); }; + cursor.trans = trans; + cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsNotStarted; + cursor.fail = wrap(reject); + cursor.next = function () { + let gotOne = 1; + return this.start(() => gotOne-- ? this.continue() : this.stop()).then(() => this); + }; + cursor.start = (callback) => { + const iterationPromise = new Promise((resolveIteration, rejectIteration) => { + resolveIteration = wrap(resolveIteration); + req.onerror = eventRejectHandler(rejectIteration); + cursor.fail = rejectIteration; + cursor.stop = value => { + cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsStopped; + resolveIteration(value); + }; + }); + const guardedCallback = () => { + if (req.result) { + try { + callback(); + } + catch (err) { + cursor.fail(err); + } + } + else { + cursor.done = true; + cursor.start = () => { throw new Error("Cursor behind last entry"); }; + cursor.stop(); + } + }; + req.onsuccess = wrap(ev => { + req.onsuccess = guardedCallback; + guardedCallback(); + }); + cursor.continue = _cursorContinue; + cursor.continuePrimaryKey = _cursorContinuePrimaryKey; + cursor.advance = _cursorAdvance; + guardedCallback(); + return iterationPromise; + }; + resolve(cursor); + }, reject); + }); + } + function query(hasGetAll) { + return (request) => { + return new Promise((resolve, reject) => { + resolve = wrap(resolve); + const { trans, values, limit, query } = request; + const nonInfinitLimit = limit === Infinity ? undefined : limit; + const { index, range } = query; + const store = trans.objectStore(tableName); + const source = index.isPrimaryKey ? store : store.index(index.name); + const idbKeyRange = makeIDBKeyRange(range); + if (limit === 0) + return resolve({ result: [] }); + if (hasGetAll) { + const req = values ? + source.getAll(idbKeyRange, nonInfinitLimit) : + source.getAllKeys(idbKeyRange, nonInfinitLimit); + req.onsuccess = event => resolve({ result: event.target.result }); + req.onerror = eventRejectHandler(reject); + } + else { + let count = 0; + const req = values || !('openKeyCursor' in source) ? + source.openCursor(idbKeyRange) : + source.openKeyCursor(idbKeyRange); + const result = []; + req.onsuccess = event => { + const cursor = req.result; + if (!cursor) + return resolve({ result }); + result.push(values ? cursor.value : cursor.primaryKey); + if (++count === limit) + return resolve({ result }); + cursor.continue(); + }; + req.onerror = eventRejectHandler(reject); + } + }); + }; + } + return { + name: tableName, + schema: tableSchema, + mutate, + getMany({ trans, keys }) { + return new Promise((resolve, reject) => { + resolve = wrap(resolve); + const store = trans.objectStore(tableName); + const length = keys.length; + const result = new Array(length); + let keyCount = 0; + let callbackCount = 0; + let req; + const successHandler = event => { + const req = event.target; + if ((result[req._pos] = req.result) != null) + ; + if (++callbackCount === keyCount) + resolve(result); + }; + const errorHandler = eventRejectHandler(reject); + for (let i = 0; i < length; ++i) { + const key = keys[i]; + if (key != null) { + req = store.get(keys[i]); + req._pos = i; + req.onsuccess = successHandler; + req.onerror = errorHandler; + ++keyCount; + } + } + if (keyCount === 0) + resolve(result); + }); + }, + get({ trans, key }) { + return new Promise((resolve, reject) => { + resolve = wrap(resolve); + const store = trans.objectStore(tableName); + const req = store.get(key); + req.onsuccess = event => resolve(event.target.result); + req.onerror = eventRejectHandler(reject); + }); + }, + query: query(hasGetAll), + openCursor, + count({ query, trans }) { + const { index, range } = query; + return new Promise((resolve, reject) => { + const store = trans.objectStore(tableName); + const source = index.isPrimaryKey ? store : store.index(index.name); + const idbKeyRange = makeIDBKeyRange(range); + const req = idbKeyRange ? source.count(idbKeyRange) : source.count(); + req.onsuccess = wrap(ev => resolve(ev.target.result)); + req.onerror = eventRejectHandler(reject); + }); + } + }; + } + const { schema, hasGetAll } = extractSchema(db, tmpTrans); + const tables = schema.tables.map(tableSchema => createDbCoreTable(tableSchema)); + const tableMap = {}; + tables.forEach(table => tableMap[table.name] = table); + return { + stack: "dbcore", + transaction: db.transaction.bind(db), + table(name) { + const result = tableMap[name]; + if (!result) + throw new Error(`Table '${name}' not found`); + return tableMap[name]; + }, + MIN_KEY: -Infinity, + MAX_KEY: getMaxKey(IdbKeyRange), + schema + }; +} - return array; +function createMiddlewareStack(stackImpl, middlewares) { + return middlewares.reduce((down, { create }) => ({ ...down, ...create(down) }), stackImpl); +} +function createMiddlewareStacks(middlewares, idbdb, { IDBKeyRange, indexedDB }, tmpTrans) { + const dbcore = createMiddlewareStack(createDBCore(idbdb, IDBKeyRange, tmpTrans), middlewares.dbcore); + return { + dbcore + }; +} +function generateMiddlewareStacks({ _novip: db }, tmpTrans) { + const idbdb = tmpTrans.db; + const stacks = createMiddlewareStacks(db._middlewares, idbdb, db._deps, tmpTrans); + db.core = stacks.dbcore; + db.tables.forEach(table => { + const tableName = table.name; + if (db.core.schema.tables.some(tbl => tbl.name === tableName)) { + table.core = db.core.table(tableName); + if (db[tableName] instanceof db.Table) { + db[tableName].core = table.core; + } + } + }); +} - } +function setApiOnPlace({ _novip: db }, objs, tableNames, dbschema) { + tableNames.forEach(tableName => { + const schema = dbschema[tableName]; + objs.forEach(obj => { + const propDesc = getPropertyDescriptor(obj, tableName); + if (!propDesc || ("value" in propDesc && propDesc.value === undefined)) { + if (obj === db.Transaction.prototype || obj instanceof db.Transaction) { + setProp(obj, tableName, { + get() { return this.table(tableName); }, + set(value) { + defineProperty(this, tableName, { value, writable: true, configurable: true, enumerable: true }); + } + }); + } + else { + obj[tableName] = new db.Table(tableName, schema); + } + } + }); + }); +} +function removeTablesApi({ _novip: db }, objs) { + objs.forEach(obj => { + for (let key in obj) { + if (obj[key] instanceof db.Table) + delete obj[key]; + } + }); +} +function lowerVersionFirst(a, b) { + return a._cfg.version - b._cfg.version; +} +function runUpgraders(db, oldVersion, idbUpgradeTrans, reject) { + const globalSchema = db._dbSchema; + const trans = db._createTransaction('readwrite', db._storeNames, globalSchema); + trans.create(idbUpgradeTrans); + trans._completion.catch(reject); + const rejectTransaction = trans._reject.bind(trans); + const transless = PSD.transless || PSD; + newScope(() => { + PSD.trans = trans; + PSD.transless = transless; + if (oldVersion === 0) { + keys(globalSchema).forEach(tableName => { + createTable(idbUpgradeTrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); + }); + generateMiddlewareStacks(db, idbUpgradeTrans); + DexiePromise.follow(() => db.on.populate.fire(trans)).catch(rejectTransaction); + } + else + updateTablesAndIndexes(db, oldVersion, trans, idbUpgradeTrans).catch(rejectTransaction); + }); +} +function updateTablesAndIndexes({ _novip: db }, oldVersion, trans, idbUpgradeTrans) { + const queue = []; + const versions = db._versions; + let globalSchema = db._dbSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans); + let anyContentUpgraderHasRun = false; + const versToRun = versions.filter(v => v._cfg.version >= oldVersion); + versToRun.forEach(version => { + queue.push(() => { + const oldSchema = globalSchema; + const newSchema = version._cfg.dbschema; + adjustToExistingIndexNames(db, oldSchema, idbUpgradeTrans); + adjustToExistingIndexNames(db, newSchema, idbUpgradeTrans); + globalSchema = db._dbSchema = newSchema; + const diff = getSchemaDiff(oldSchema, newSchema); + diff.add.forEach(tuple => { + createTable(idbUpgradeTrans, tuple[0], tuple[1].primKey, tuple[1].indexes); + }); + diff.change.forEach(change => { + if (change.recreate) { + throw new exceptions.Upgrade("Not yet support for changing primary key"); + } + else { + const store = idbUpgradeTrans.objectStore(change.name); + change.add.forEach(idx => addIndex(store, idx)); + change.change.forEach(idx => { + store.deleteIndex(idx.name); + addIndex(store, idx); + }); + change.del.forEach(idxName => store.deleteIndex(idxName)); + } + }); + const contentUpgrade = version._cfg.contentUpgrade; + if (contentUpgrade && version._cfg.version > oldVersion) { + generateMiddlewareStacks(db, idbUpgradeTrans); + trans._memoizedTables = {}; + anyContentUpgraderHasRun = true; + let upgradeSchema = shallowClone(newSchema); + diff.del.forEach(table => { + upgradeSchema[table] = oldSchema[table]; + }); + removeTablesApi(db, [db.Transaction.prototype]); + setApiOnPlace(db, [db.Transaction.prototype], keys(upgradeSchema), upgradeSchema); + trans.schema = upgradeSchema; + const contentUpgradeIsAsync = isAsyncFunction(contentUpgrade); + if (contentUpgradeIsAsync) { + incrementExpectedAwaits(); + } + let returnValue; + const promiseFollowed = DexiePromise.follow(() => { + returnValue = contentUpgrade(trans); + if (returnValue) { + if (contentUpgradeIsAsync) { + var decrementor = decrementExpectedAwaits.bind(null, null); + returnValue.then(decrementor, decrementor); + } + } + }); + return (returnValue && typeof returnValue.then === 'function' ? + DexiePromise.resolve(returnValue) : promiseFollowed.then(() => returnValue)); + } + }); + queue.push(idbtrans => { + if (!anyContentUpgraderHasRun || !hasIEDeleteObjectStoreBug) { + const newSchema = version._cfg.dbschema; + deleteRemovedTables(newSchema, idbtrans); + } + removeTablesApi(db, [db.Transaction.prototype]); + setApiOnPlace(db, [db.Transaction.prototype], db._storeNames, db._dbSchema); + trans.schema = db._dbSchema; + }); + }); + function runQueue() { + return queue.length ? DexiePromise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : + DexiePromise.resolve(); + } + return runQueue().then(() => { + createMissingTables(globalSchema, idbUpgradeTrans); + }); +} +function getSchemaDiff(oldSchema, newSchema) { + const diff = { + del: [], + add: [], + change: [] + }; + let table; + for (table in oldSchema) { + if (!newSchema[table]) + diff.del.push(table); + } + for (table in newSchema) { + const oldDef = oldSchema[table], newDef = newSchema[table]; + if (!oldDef) { + diff.add.push([table, newDef]); + } + else { + const change = { + name: table, + def: newDef, + recreate: false, + del: [], + add: [], + change: [] + }; + if (( + '' + (oldDef.primKey.keyPath || '')) !== ('' + (newDef.primKey.keyPath || '')) || + (oldDef.primKey.auto !== newDef.primKey.auto && !isIEOrEdge)) + { + change.recreate = true; + diff.change.push(change); + } + else { + const oldIndexes = oldDef.idxByName; + const newIndexes = newDef.idxByName; + let idxName; + for (idxName in oldIndexes) { + if (!newIndexes[idxName]) + change.del.push(idxName); + } + for (idxName in newIndexes) { + const oldIdx = oldIndexes[idxName], newIdx = newIndexes[idxName]; + if (!oldIdx) + change.add.push(newIdx); + else if (oldIdx.src !== newIdx.src) + change.change.push(newIdx); + } + if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { + diff.change.push(change); + } + } + } + } + return diff; +} +function createTable(idbtrans, tableName, primKey, indexes) { + const store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? + { keyPath: primKey.keyPath, autoIncrement: primKey.auto } : + { autoIncrement: primKey.auto }); + indexes.forEach(idx => addIndex(store, idx)); + return store; +} +function createMissingTables(newSchema, idbtrans) { + keys(newSchema).forEach(tableName => { + if (!idbtrans.db.objectStoreNames.contains(tableName)) { + createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); + } + }); +} +function deleteRemovedTables(newSchema, idbtrans) { + [].slice.call(idbtrans.db.objectStoreNames).forEach(storeName => newSchema[storeName] == null && idbtrans.db.deleteObjectStore(storeName)); +} +function addIndex(store, idx) { + store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi }); +} +function buildGlobalSchema(db, idbdb, tmpTrans) { + const globalSchema = {}; + const dbStoreNames = slice(idbdb.objectStoreNames, 0); + dbStoreNames.forEach(storeName => { + const store = tmpTrans.objectStore(storeName); + let keyPath = store.keyPath; + const primKey = createIndexSpec(nameFromKeyPath(keyPath), keyPath || "", false, false, !!store.autoIncrement, keyPath && typeof keyPath !== "string", true); + const indexes = []; + for (let j = 0; j < store.indexNames.length; ++j) { + const idbindex = store.index(store.indexNames[j]); + keyPath = idbindex.keyPath; + var index = createIndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== "string", false); + indexes.push(index); + } + globalSchema[storeName] = createTableSchema(storeName, primKey, indexes); + }); + return globalSchema; +} +function readGlobalSchema({ _novip: db }, idbdb, tmpTrans) { + db.verno = idbdb.version / 10; + const globalSchema = db._dbSchema = buildGlobalSchema(db, idbdb, tmpTrans); + db._storeNames = slice(idbdb.objectStoreNames, 0); + setApiOnPlace(db, [db._allTables], keys(globalSchema), globalSchema); +} +function verifyInstalledSchema(db, tmpTrans) { + const installedSchema = buildGlobalSchema(db, db.idbdb, tmpTrans); + const diff = getSchemaDiff(installedSchema, db._dbSchema); + return !(diff.add.length || diff.change.some(ch => ch.add.length || ch.change.length)); +} +function adjustToExistingIndexNames({ _novip: db }, schema, idbtrans) { + const storeNames = idbtrans.db.objectStoreNames; + for (let i = 0; i < storeNames.length; ++i) { + const storeName = storeNames[i]; + const store = idbtrans.objectStore(storeName); + db._hasGetAll = 'getAll' in store; + for (let j = 0; j < store.indexNames.length; ++j) { + const indexName = store.indexNames[j]; + const keyPath = store.index(indexName).keyPath; + const dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; + if (schema[storeName]) { + const indexSpec = schema[storeName].idxByName[dexieName]; + if (indexSpec) { + indexSpec.name = indexName; + delete schema[storeName].idxByName[dexieName]; + schema[storeName].idxByName[indexName] = indexSpec; + } + } + } + } + if (typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && + !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && + _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope && + [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) { + db._hasGetAll = false; + } +} +function parseIndexSyntax(primKeyAndIndexes) { + return primKeyAndIndexes.split(',').map((index, indexNum) => { + index = index.trim(); + const name = index.replace(/([&*]|\+\+)/g, ""); + const keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; + return createIndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), indexNum === 0); + }); +} - fromBufferAttribute( attribute, index ) { +class Version { + _parseStoresSpec(stores, outSchema) { + keys(stores).forEach(tableName => { + if (stores[tableName] !== null) { + var indexes = parseIndexSyntax(stores[tableName]); + var primKey = indexes.shift(); + if (primKey.multi) + throw new exceptions.Schema("Primary key cannot be multi-valued"); + indexes.forEach(idx => { + if (idx.auto) + throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)"); + if (!idx.keyPath) + throw new exceptions.Schema("Index must have a name and cannot be an empty string"); + }); + outSchema[tableName] = createTableSchema(tableName, primKey, indexes); + } + }); + } + stores(stores) { + const db = this.db; + this._cfg.storesSource = this._cfg.storesSource ? + extend(this._cfg.storesSource, stores) : + stores; + const versions = db._versions; + const storesSpec = {}; + let dbschema = {}; + versions.forEach(version => { + extend(storesSpec, version._cfg.storesSource); + dbschema = (version._cfg.dbschema = {}); + version._parseStoresSpec(storesSpec, dbschema); + }); + db._dbSchema = dbschema; + removeTablesApi(db, [db._allTables, db, db.Transaction.prototype]); + setApiOnPlace(db, [db._allTables, db, db.Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); + db._storeNames = keys(dbschema); + return this; + } + upgrade(upgradeFunction) { + this._cfg.contentUpgrade = promisableChain(this._cfg.contentUpgrade || nop, upgradeFunction); + return this; + } +} - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); +function createVersionConstructor(db) { + return makeClassConstructor(Version.prototype, function Version(versionNumber) { + this.db = db; + this._cfg = { + version: versionNumber, + storesSource: null, + dbschema: {}, + tables: {}, + contentUpgrade: null + }; + }); +} - return this; +function getDbNamesTable(indexedDB, IDBKeyRange) { + let dbNamesDB = indexedDB["_dbNamesDB"]; + if (!dbNamesDB) { + dbNamesDB = indexedDB["_dbNamesDB"] = new Dexie$1(DBNAMES_DB, { + addons: [], + indexedDB, + IDBKeyRange, + }); + dbNamesDB.version(1).stores({ dbnames: "name" }); + } + return dbNamesDB.table("dbnames"); +} +function hasDatabasesNative(indexedDB) { + return indexedDB && typeof indexedDB.databases === "function"; +} +function getDatabaseNames({ indexedDB, IDBKeyRange, }) { + return hasDatabasesNative(indexedDB) + ? Promise.resolve(indexedDB.databases()).then((infos) => infos + .map((info) => info.name) + .filter((name) => name !== DBNAMES_DB)) + : getDbNamesTable(indexedDB, IDBKeyRange).toCollection().primaryKeys(); +} +function _onDatabaseCreated({ indexedDB, IDBKeyRange }, name) { + !hasDatabasesNative(indexedDB) && + name !== DBNAMES_DB && + getDbNamesTable(indexedDB, IDBKeyRange).put({ name }).catch(nop); +} +function _onDatabaseDeleted({ indexedDB, IDBKeyRange }, name) { + !hasDatabasesNative(indexedDB) && + name !== DBNAMES_DB && + getDbNamesTable(indexedDB, IDBKeyRange).delete(name).catch(nop); +} - } +function vip(fn) { + return newScope(function () { + PSD.letThrough = true; + return fn(); + }); +} - toJSON() { +function idbReady() { + var isSafari = !navigator.userAgentData && + /Safari\//.test(navigator.userAgent) && + !/Chrom(e|ium)\//.test(navigator.userAgent); + if (!isSafari || !indexedDB.databases) + return Promise.resolve(); + var intervalId; + return new Promise(function (resolve) { + var tryIdb = function () { return indexedDB.databases().finally(resolve); }; + intervalId = setInterval(tryIdb, 100); + tryIdb(); + }).finally(function () { return clearInterval(intervalId); }); +} - return this.toArray(); +function dexieOpen(db) { + const state = db._state; + const { indexedDB } = db._deps; + if (state.isBeingOpened || db.idbdb) + return state.dbReadyPromise.then(() => state.dbOpenError ? + rejection(state.dbOpenError) : + db); + debug && (state.openCanceller._stackHolder = getErrorWithStack()); + state.isBeingOpened = true; + state.dbOpenError = null; + state.openComplete = false; + const openCanceller = state.openCanceller; + function throwIfCancelled() { + if (state.openCanceller !== openCanceller) + throw new exceptions.DatabaseClosed('db.open() was cancelled'); + } + let resolveDbReady = state.dbReadyResolve, + upgradeTransaction = null, wasCreated = false; + return DexiePromise.race([openCanceller, (typeof navigator === 'undefined' ? DexiePromise.resolve() : idbReady()).then(() => new DexiePromise((resolve, reject) => { + throwIfCancelled(); + if (!indexedDB) + throw new exceptions.MissingAPI(); + const dbName = db.name; + const req = state.autoSchema ? + indexedDB.open(dbName) : + indexedDB.open(dbName, Math.round(db.verno * 10)); + if (!req) + throw new exceptions.MissingAPI(); + req.onerror = eventRejectHandler(reject); + req.onblocked = wrap(db._fireOnBlocked); + req.onupgradeneeded = wrap(e => { + upgradeTransaction = req.transaction; + if (state.autoSchema && !db._options.allowEmptyDB) { + req.onerror = preventDefault; + upgradeTransaction.abort(); + req.result.close(); + const delreq = indexedDB.deleteDatabase(dbName); + delreq.onsuccess = delreq.onerror = wrap(() => { + reject(new exceptions.NoSuchDatabase(`Database ${dbName} doesnt exist`)); + }); + } + else { + upgradeTransaction.onerror = eventRejectHandler(reject); + var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; + wasCreated = oldVer < 1; + db._novip.idbdb = req.result; + runUpgraders(db, oldVer / 10, upgradeTransaction, reject); + } + }, reject); + req.onsuccess = wrap(() => { + upgradeTransaction = null; + const idbdb = db._novip.idbdb = req.result; + const objectStoreNames = slice(idbdb.objectStoreNames); + if (objectStoreNames.length > 0) + try { + const tmpTrans = idbdb.transaction(safariMultiStoreFix(objectStoreNames), 'readonly'); + if (state.autoSchema) + readGlobalSchema(db, idbdb, tmpTrans); + else { + adjustToExistingIndexNames(db, db._dbSchema, tmpTrans); + if (!verifyInstalledSchema(db, tmpTrans)) { + console.warn(`Dexie SchemaDiff: Schema was extended without increasing the number passed to db.version(). Some queries may fail.`); + } + } + generateMiddlewareStacks(db, tmpTrans); + } + catch (e) { + } + connections.push(db); + idbdb.onversionchange = wrap(ev => { + state.vcFired = true; + db.on("versionchange").fire(ev); + }); + idbdb.onclose = wrap(ev => { + db.on("close").fire(ev); + }); + if (wasCreated) + _onDatabaseCreated(db._deps, dbName); + resolve(); + }, reject); + }))]).then(() => { + throwIfCancelled(); + state.onReadyBeingFired = []; + return DexiePromise.resolve(vip(() => db.on.ready.fire(db.vip))).then(function fireRemainders() { + if (state.onReadyBeingFired.length > 0) { + let remainders = state.onReadyBeingFired.reduce(promisableChain, nop); + state.onReadyBeingFired = []; + return DexiePromise.resolve(vip(() => remainders(db.vip))).then(fireRemainders); + } + }); + }).finally(() => { + state.onReadyBeingFired = null; + state.isBeingOpened = false; + }).then(() => { + return db; + }).catch(err => { + state.dbOpenError = err; + try { + upgradeTransaction && upgradeTransaction.abort(); + } + catch (_a) { } + if (openCanceller === state.openCanceller) { + db._close(); + } + return rejection(err); + }).finally(() => { + state.openComplete = true; + resolveDbReady(); + }); +} - } +function awaitIterator(iterator) { + var callNext = result => iterator.next(result), doThrow = error => iterator.throw(error), onSuccess = step(callNext), onError = step(doThrow); + function step(getNext) { + return (val) => { + var next = getNext(val), value = next.value; + return next.done ? value : + (!value || typeof value.then !== 'function' ? + isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : + value.then(onSuccess, onError)); + }; + } + return step(callNext)(); +} - _onChange( callback ) { +function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { + var i = arguments.length; + if (i < 2) + throw new exceptions.InvalidArgument("Too few arguments"); + var args = new Array(i - 1); + while (--i) + args[i - 1] = arguments[i]; + scopeFunc = args.pop(); + var tables = flatten(args); + return [mode, tables, scopeFunc]; +} +function enterTransactionScope(db, mode, storeNames, parentTransaction, scopeFunc) { + return DexiePromise.resolve().then(() => { + const transless = PSD.transless || PSD; + const trans = db._createTransaction(mode, storeNames, db._dbSchema, parentTransaction); + const zoneProps = { + trans: trans, + transless: transless + }; + if (parentTransaction) { + trans.idbtrans = parentTransaction.idbtrans; + } + else { + try { + trans.create(); + db._state.PR1398_maxLoop = 3; + } + catch (ex) { + if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { + console.warn('Dexie: Need to reopen db'); + db._close(); + return db.open().then(() => enterTransactionScope(db, mode, storeNames, null, scopeFunc)); + } + return rejection(ex); + } + } + const scopeFuncIsAsync = isAsyncFunction(scopeFunc); + if (scopeFuncIsAsync) { + incrementExpectedAwaits(); + } + let returnValue; + const promiseFollowed = DexiePromise.follow(() => { + returnValue = scopeFunc.call(trans, trans); + if (returnValue) { + if (scopeFuncIsAsync) { + var decrementor = decrementExpectedAwaits.bind(null, null); + returnValue.then(decrementor, decrementor); + } + else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { + returnValue = awaitIterator(returnValue); + } + } + }, zoneProps); + return (returnValue && typeof returnValue.then === 'function' ? + DexiePromise.resolve(returnValue).then(x => trans.active ? + x + : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn"))) + : promiseFollowed.then(() => returnValue)).then(x => { + if (parentTransaction) + trans._resolve(); + return trans._completion.then(() => x); + }).catch(e => { + trans._reject(e); + return rejection(e); + }); + }); +} - this._onChangeCallback = callback; +function pad(a, value, count) { + const result = isArray(a) ? a.slice() : [a]; + for (let i = 0; i < count; ++i) + result.push(value); + return result; +} +function createVirtualIndexMiddleware(down) { + return { + ...down, + table(tableName) { + const table = down.table(tableName); + const { schema } = table; + const indexLookup = {}; + const allVirtualIndexes = []; + function addVirtualIndexes(keyPath, keyTail, lowLevelIndex) { + const keyPathAlias = getKeyPathAlias(keyPath); + const indexList = (indexLookup[keyPathAlias] = indexLookup[keyPathAlias] || []); + const keyLength = keyPath == null ? 0 : typeof keyPath === 'string' ? 1 : keyPath.length; + const isVirtual = keyTail > 0; + const virtualIndex = { + ...lowLevelIndex, + isVirtual, + keyTail, + keyLength, + extractKey: getKeyExtractor(keyPath), + unique: !isVirtual && lowLevelIndex.unique + }; + indexList.push(virtualIndex); + if (!virtualIndex.isPrimaryKey) { + allVirtualIndexes.push(virtualIndex); + } + if (keyLength > 1) { + const virtualKeyPath = keyLength === 2 ? + keyPath[0] : + keyPath.slice(0, keyLength - 1); + addVirtualIndexes(virtualKeyPath, keyTail + 1, lowLevelIndex); + } + indexList.sort((a, b) => a.keyTail - b.keyTail); + return virtualIndex; + } + const primaryKey = addVirtualIndexes(schema.primaryKey.keyPath, 0, schema.primaryKey); + indexLookup[":id"] = [primaryKey]; + for (const index of schema.indexes) { + addVirtualIndexes(index.keyPath, 0, index); + } + function findBestIndex(keyPath) { + const result = indexLookup[getKeyPathAlias(keyPath)]; + return result && result[0]; + } + function translateRange(range, keyTail) { + return { + type: range.type === 1 ? + 2 : + range.type, + lower: pad(range.lower, range.lowerOpen ? down.MAX_KEY : down.MIN_KEY, keyTail), + lowerOpen: true, + upper: pad(range.upper, range.upperOpen ? down.MIN_KEY : down.MAX_KEY, keyTail), + upperOpen: true + }; + } + function translateRequest(req) { + const index = req.query.index; + return index.isVirtual ? { + ...req, + query: { + index, + range: translateRange(req.query.range, index.keyTail) + } + } : req; + } + const result = { + ...table, + schema: { + ...schema, + primaryKey, + indexes: allVirtualIndexes, + getIndexByKeyPath: findBestIndex + }, + count(req) { + return table.count(translateRequest(req)); + }, + query(req) { + return table.query(translateRequest(req)); + }, + openCursor(req) { + const { keyTail, isVirtual, keyLength } = req.query.index; + if (!isVirtual) + return table.openCursor(req); + function createVirtualCursor(cursor) { + function _continue(key) { + key != null ? + cursor.continue(pad(key, req.reverse ? down.MAX_KEY : down.MIN_KEY, keyTail)) : + req.unique ? + cursor.continue(cursor.key.slice(0, keyLength) + .concat(req.reverse + ? down.MIN_KEY + : down.MAX_KEY, keyTail)) : + cursor.continue(); + } + const virtualCursor = Object.create(cursor, { + continue: { value: _continue }, + continuePrimaryKey: { + value(key, primaryKey) { + cursor.continuePrimaryKey(pad(key, down.MAX_KEY, keyTail), primaryKey); + } + }, + primaryKey: { + get() { + return cursor.primaryKey; + } + }, + key: { + get() { + const key = cursor.key; + return keyLength === 1 ? + key[0] : + key.slice(0, keyLength); + } + }, + value: { + get() { + return cursor.value; + } + } + }); + return virtualCursor; + } + return table.openCursor(translateRequest(req)) + .then(cursor => cursor && createVirtualCursor(cursor)); + } + }; + return result; + } + }; +} +const virtualIndexMiddleware = { + stack: "dbcore", + name: "VirtualIndexMiddleware", + level: 1, + create: createVirtualIndexMiddleware +}; - return this; +function getObjectDiff(a, b, rv, prfx) { + rv = rv || {}; + prfx = prfx || ''; + keys(a).forEach((prop) => { + if (!hasOwn(b, prop)) { + rv[prfx + prop] = undefined; + } + else { + var ap = a[prop], bp = b[prop]; + if (typeof ap === 'object' && typeof bp === 'object' && ap && bp) { + const apTypeName = toStringTag(ap); + const bpTypeName = toStringTag(bp); + if (apTypeName !== bpTypeName) { + rv[prfx + prop] = b[prop]; + } + else if (apTypeName === 'Object') { + getObjectDiff(ap, bp, rv, prfx + prop + '.'); + } + else if (ap !== bp) { + rv[prfx + prop] = b[prop]; + } + } + else if (ap !== bp) + rv[prfx + prop] = b[prop]; + } + }); + keys(b).forEach((prop) => { + if (!hasOwn(a, prop)) { + rv[prfx + prop] = b[prop]; + } + }); + return rv; +} - } +function getEffectiveKeys(primaryKey, req) { + if (req.type === 'delete') + return req.keys; + return req.keys || req.values.map(primaryKey.extractKey); +} - _onChangeCallback() {} +const hooksMiddleware = { + stack: "dbcore", + name: "HooksMiddleware", + level: 2, + create: (downCore) => ({ + ...downCore, + table(tableName) { + const downTable = downCore.table(tableName); + const { primaryKey } = downTable.schema; + const tableMiddleware = { + ...downTable, + mutate(req) { + const dxTrans = PSD.trans; + const { deleting, creating, updating } = dxTrans.table(tableName).hook; + switch (req.type) { + case 'add': + if (creating.fire === nop) + break; + return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); + case 'put': + if (creating.fire === nop && updating.fire === nop) + break; + return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); + case 'delete': + if (deleting.fire === nop) + break; + return dxTrans._promise('readwrite', () => addPutOrDelete(req), true); + case 'deleteRange': + if (deleting.fire === nop) + break; + return dxTrans._promise('readwrite', () => deleteRange(req), true); + } + return downTable.mutate(req); + function addPutOrDelete(req) { + const dxTrans = PSD.trans; + const keys = req.keys || getEffectiveKeys(primaryKey, req); + if (!keys) + throw new Error("Keys missing"); + req = req.type === 'add' || req.type === 'put' ? + { ...req, keys } : + { ...req }; + if (req.type !== 'delete') + req.values = [...req.values]; + if (req.keys) + req.keys = [...req.keys]; + return getExistingValues(downTable, req, keys).then(existingValues => { + const contexts = keys.map((key, i) => { + const existingValue = existingValues[i]; + const ctx = { onerror: null, onsuccess: null }; + if (req.type === 'delete') { + deleting.fire.call(ctx, key, existingValue, dxTrans); + } + else if (req.type === 'add' || existingValue === undefined) { + const generatedPrimaryKey = creating.fire.call(ctx, key, req.values[i], dxTrans); + if (key == null && generatedPrimaryKey != null) { + key = generatedPrimaryKey; + req.keys[i] = key; + if (!primaryKey.outbound) { + setByKeyPath(req.values[i], primaryKey.keyPath, key); + } + } + } + else { + const objectDiff = getObjectDiff(existingValue, req.values[i]); + const additionalChanges = updating.fire.call(ctx, objectDiff, key, existingValue, dxTrans); + if (additionalChanges) { + const requestedValue = req.values[i]; + Object.keys(additionalChanges).forEach(keyPath => { + if (hasOwn(requestedValue, keyPath)) { + requestedValue[keyPath] = additionalChanges[keyPath]; + } + else { + setByKeyPath(requestedValue, keyPath, additionalChanges[keyPath]); + } + }); + } + } + return ctx; + }); + return downTable.mutate(req).then(({ failures, results, numFailures, lastResult }) => { + for (let i = 0; i < keys.length; ++i) { + const primKey = results ? results[i] : keys[i]; + const ctx = contexts[i]; + if (primKey == null) { + ctx.onerror && ctx.onerror(failures[i]); + } + else { + ctx.onsuccess && ctx.onsuccess(req.type === 'put' && existingValues[i] ? + req.values[i] : + primKey + ); + } + } + return { failures, results, numFailures, lastResult }; + }).catch(error => { + contexts.forEach(ctx => ctx.onerror && ctx.onerror(error)); + return Promise.reject(error); + }); + }); + } + function deleteRange(req) { + return deleteNextChunk(req.trans, req.range, 10000); + } + function deleteNextChunk(trans, range, limit) { + return downTable.query({ trans, values: false, query: { index: primaryKey, range }, limit }) + .then(({ result }) => { + return addPutOrDelete({ type: 'delete', keys: result, trans }).then(res => { + if (res.numFailures > 0) + return Promise.reject(res.failures[0]); + if (result.length < limit) { + return { failures: [], numFailures: 0, lastResult: undefined }; + } + else { + return deleteNextChunk(trans, { ...range, lower: result[result.length - 1], lowerOpen: true }, limit); + } + }); + }); + } + } + }; + return tableMiddleware; + }, + }) +}; +function getExistingValues(table, req, effectiveKeys) { + return req.type === "add" + ? Promise.resolve([]) + : table.getMany({ trans: req.trans, keys: effectiveKeys, cache: "immutable" }); +} - *[ Symbol.iterator ]() { +function getFromTransactionCache(keys, cache, clone) { + try { + if (!cache) + return null; + if (cache.keys.length < keys.length) + return null; + const result = []; + for (let i = 0, j = 0; i < cache.keys.length && j < keys.length; ++i) { + if (cmp(cache.keys[i], keys[j]) !== 0) + continue; + result.push(clone ? deepClone(cache.values[i]) : cache.values[i]); + ++j; + } + return result.length === keys.length ? result : null; + } + catch (_a) { + return null; + } +} +const cacheExistingValuesMiddleware = { + stack: "dbcore", + level: -1, + create: (core) => { + return { + table: (tableName) => { + const table = core.table(tableName); + return { + ...table, + getMany: (req) => { + if (!req.cache) { + return table.getMany(req); + } + const cachedResult = getFromTransactionCache(req.keys, req.trans["_cache"], req.cache === "clone"); + if (cachedResult) { + return DexiePromise.resolve(cachedResult); + } + return table.getMany(req).then((res) => { + req.trans["_cache"] = { + keys: req.keys, + values: req.cache === "clone" ? deepClone(res) : res, + }; + return res; + }); + }, + mutate: (req) => { + if (req.type !== "add") + req.trans["_cache"] = null; + return table.mutate(req); + }, + }; + }, + }; + }, +}; - yield this._x; - yield this._y; - yield this._z; - yield this._w; +function isEmptyRange(node) { + return !("from" in node); +} +const RangeSet = function (fromOrTree, to) { + if (this) { + extend(this, arguments.length ? { d: 1, from: fromOrTree, to: arguments.length > 1 ? to : fromOrTree } : { d: 0 }); + } + else { + const rv = new RangeSet(); + if (fromOrTree && ("d" in fromOrTree)) { + extend(rv, fromOrTree); + } + return rv; + } +}; +props(RangeSet.prototype, { + add(rangeSet) { + mergeRanges(this, rangeSet); + return this; + }, + addKey(key) { + addRange(this, key, key); + return this; + }, + addKeys(keys) { + keys.forEach(key => addRange(this, key, key)); + return this; + }, + [iteratorSymbol]() { + return getRangeSetIterator(this); + } +}); +function addRange(target, from, to) { + const diff = cmp(from, to); + if (isNaN(diff)) + return; + if (diff > 0) + throw RangeError(); + if (isEmptyRange(target)) + return extend(target, { from, to, d: 1 }); + const left = target.l; + const right = target.r; + if (cmp(to, target.from) < 0) { + left + ? addRange(left, from, to) + : (target.l = { from, to, d: 1, l: null, r: null }); + return rebalance(target); + } + if (cmp(from, target.to) > 0) { + right + ? addRange(right, from, to) + : (target.r = { from, to, d: 1, l: null, r: null }); + return rebalance(target); + } + if (cmp(from, target.from) < 0) { + target.from = from; + target.l = null; + target.d = right ? right.d + 1 : 1; + } + if (cmp(to, target.to) > 0) { + target.to = to; + target.r = null; + target.d = target.l ? target.l.d + 1 : 1; + } + const rightWasCutOff = !target.r; + if (left && !target.l) { + mergeRanges(target, left); + } + if (right && rightWasCutOff) { + mergeRanges(target, right); + } +} +function mergeRanges(target, newSet) { + function _addRangeSet(target, { from, to, l, r }) { + addRange(target, from, to); + if (l) + _addRangeSet(target, l); + if (r) + _addRangeSet(target, r); + } + if (!isEmptyRange(newSet)) + _addRangeSet(target, newSet); +} +function rangesOverlap(rangeSet1, rangeSet2) { + const i1 = getRangeSetIterator(rangeSet2); + let nextResult1 = i1.next(); + if (nextResult1.done) + return false; + let a = nextResult1.value; + const i2 = getRangeSetIterator(rangeSet1); + let nextResult2 = i2.next(a.from); + let b = nextResult2.value; + while (!nextResult1.done && !nextResult2.done) { + if (cmp(b.from, a.to) <= 0 && cmp(b.to, a.from) >= 0) + return true; + cmp(a.from, b.from) < 0 + ? (a = (nextResult1 = i1.next(b.from)).value) + : (b = (nextResult2 = i2.next(a.from)).value); + } + return false; +} +function getRangeSetIterator(node) { + let state = isEmptyRange(node) ? null : { s: 0, n: node }; + return { + next(key) { + const keyProvided = arguments.length > 0; + while (state) { + switch (state.s) { + case 0: + state.s = 1; + if (keyProvided) { + while (state.n.l && cmp(key, state.n.from) < 0) + state = { up: state, n: state.n.l, s: 1 }; + } + else { + while (state.n.l) + state = { up: state, n: state.n.l, s: 1 }; + } + case 1: + state.s = 2; + if (!keyProvided || cmp(key, state.n.to) <= 0) + return { value: state.n, done: false }; + case 2: + if (state.n.r) { + state.s = 3; + state = { up: state, n: state.n.r, s: 0 }; + continue; + } + case 3: + state = state.up; + } + } + return { done: true }; + }, + }; +} +function rebalance(target) { + var _a, _b; + const diff = (((_a = target.r) === null || _a === void 0 ? void 0 : _a.d) || 0) - (((_b = target.l) === null || _b === void 0 ? void 0 : _b.d) || 0); + const r = diff > 1 ? "r" : diff < -1 ? "l" : ""; + if (r) { + const l = r === "r" ? "l" : "r"; + const rootClone = { ...target }; + const oldRootRight = target[r]; + target.from = oldRootRight.from; + target.to = oldRootRight.to; + target[r] = oldRootRight[r]; + rootClone[r] = oldRootRight[l]; + target[l] = rootClone; + rootClone.d = computeDepth(rootClone); + } + target.d = computeDepth(target); +} +function computeDepth({ r, l }) { + return (r ? (l ? Math.max(r.d, l.d) : r.d) : l ? l.d : 0) + 1; +} - } +const observabilityMiddleware = { + stack: "dbcore", + level: 0, + create: (core) => { + const dbName = core.schema.name; + const FULL_RANGE = new RangeSet(core.MIN_KEY, core.MAX_KEY); + return { + ...core, + table: (tableName) => { + const table = core.table(tableName); + const { schema } = table; + const { primaryKey } = schema; + const { extractKey, outbound } = primaryKey; + const tableClone = { + ...table, + mutate: (req) => { + const trans = req.trans; + const mutatedParts = trans.mutatedParts || (trans.mutatedParts = {}); + const getRangeSet = (indexName) => { + const part = `idb://${dbName}/${tableName}/${indexName}`; + return (mutatedParts[part] || + (mutatedParts[part] = new RangeSet())); + }; + const pkRangeSet = getRangeSet(""); + const delsRangeSet = getRangeSet(":dels"); + const { type } = req; + let [keys, newObjs] = req.type === "deleteRange" + ? [req.range] + : req.type === "delete" + ? [req.keys] + : req.values.length < 50 + ? [[], req.values] + : []; + const oldCache = req.trans["_cache"]; + return table.mutate(req).then((res) => { + if (isArray(keys)) { + if (type !== "delete") + keys = res.results; + pkRangeSet.addKeys(keys); + const oldObjs = getFromTransactionCache(keys, oldCache); + if (!oldObjs && type !== "add") { + delsRangeSet.addKeys(keys); + } + if (oldObjs || newObjs) { + trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs); + } + } + else if (keys) { + const range = { from: keys.lower, to: keys.upper }; + delsRangeSet.add(range); + pkRangeSet.add(range); + } + else { + pkRangeSet.add(FULL_RANGE); + delsRangeSet.add(FULL_RANGE); + schema.indexes.forEach(idx => getRangeSet(idx.name).add(FULL_RANGE)); + } + return res; + }); + }, + }; + const getRange = ({ query: { index, range }, }) => { + var _a, _b; + return [ + index, + new RangeSet((_a = range.lower) !== null && _a !== void 0 ? _a : core.MIN_KEY, (_b = range.upper) !== null && _b !== void 0 ? _b : core.MAX_KEY), + ]; + }; + const readSubscribers = { + get: (req) => [primaryKey, new RangeSet(req.key)], + getMany: (req) => [primaryKey, new RangeSet().addKeys(req.keys)], + count: getRange, + query: getRange, + openCursor: getRange, + }; + keys(readSubscribers).forEach(method => { + tableClone[method] = function (req) { + const { subscr } = PSD; + if (subscr) { + const getRangeSet = (indexName) => { + const part = `idb://${dbName}/${tableName}/${indexName}`; + return (subscr[part] || + (subscr[part] = new RangeSet())); + }; + const pkRangeSet = getRangeSet(""); + const delsRangeSet = getRangeSet(":dels"); + const [queriedIndex, queriedRanges] = readSubscribers[method](req); + getRangeSet(queriedIndex.name || "").add(queriedRanges); + if (!queriedIndex.isPrimaryKey) { + if (method === "count") { + delsRangeSet.add(FULL_RANGE); + } + else { + const keysPromise = method === "query" && + outbound && + req.values && + table.query({ + ...req, + values: false, + }); + return table[method].apply(this, arguments).then((res) => { + if (method === "query") { + if (outbound && req.values) { + return keysPromise.then(({ result: resultingKeys }) => { + pkRangeSet.addKeys(resultingKeys); + return res; + }); + } + const pKeys = req.values + ? res.result.map(extractKey) + : res.result; + if (req.values) { + pkRangeSet.addKeys(pKeys); + } + else { + delsRangeSet.addKeys(pKeys); + } + } + else if (method === "openCursor") { + const cursor = res; + const wantValues = req.values; + return (cursor && + Object.create(cursor, { + key: { + get() { + delsRangeSet.addKey(cursor.primaryKey); + return cursor.key; + }, + }, + primaryKey: { + get() { + const pkey = cursor.primaryKey; + delsRangeSet.addKey(pkey); + return pkey; + }, + }, + value: { + get() { + wantValues && pkRangeSet.addKey(cursor.primaryKey); + return cursor.value; + }, + }, + })); + } + return res; + }); + } + } + } + return table[method].apply(this, arguments); + }; + }); + return tableClone; + }, + }; + }, +}; +function trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs) { + function addAffectedIndex(ix) { + const rangeSet = getRangeSet(ix.name || ""); + function extractKey(obj) { + return obj != null ? ix.extractKey(obj) : null; + } + const addKeyOrKeys = (key) => ix.multiEntry && isArray(key) + ? key.forEach(key => rangeSet.addKey(key)) + : rangeSet.addKey(key); + (oldObjs || newObjs).forEach((_, i) => { + const oldKey = oldObjs && extractKey(oldObjs[i]); + const newKey = newObjs && extractKey(newObjs[i]); + if (cmp(oldKey, newKey) !== 0) { + if (oldKey != null) + addKeyOrKeys(oldKey); + if (newKey != null) + addKeyOrKeys(newKey); + } + }); + } + schema.indexes.forEach(addAffectedIndex); +} +class Dexie$1 { + constructor(name, options) { + this._middlewares = {}; + this.verno = 0; + const deps = Dexie$1.dependencies; + this._options = options = { + addons: Dexie$1.addons, + autoOpen: true, + indexedDB: deps.indexedDB, + IDBKeyRange: deps.IDBKeyRange, + ...options + }; + this._deps = { + indexedDB: options.indexedDB, + IDBKeyRange: options.IDBKeyRange + }; + const { addons, } = options; + this._dbSchema = {}; + this._versions = []; + this._storeNames = []; + this._allTables = {}; + this.idbdb = null; + this._novip = this; + const state = { + dbOpenError: null, + isBeingOpened: false, + onReadyBeingFired: null, + openComplete: false, + dbReadyResolve: nop, + dbReadyPromise: null, + cancelOpen: nop, + openCanceller: null, + autoSchema: true, + PR1398_maxLoop: 3 + }; + state.dbReadyPromise = new DexiePromise(resolve => { + state.dbReadyResolve = resolve; + }); + state.openCanceller = new DexiePromise((_, reject) => { + state.cancelOpen = reject; + }); + this._state = state; + this.name = name; + this.on = Events(this, "populate", "blocked", "versionchange", "close", { ready: [promisableChain, nop] }); + this.on.ready.subscribe = override(this.on.ready.subscribe, subscribe => { + return (subscriber, bSticky) => { + Dexie$1.vip(() => { + const state = this._state; + if (state.openComplete) { + if (!state.dbOpenError) + DexiePromise.resolve().then(subscriber); + if (bSticky) + subscribe(subscriber); + } + else if (state.onReadyBeingFired) { + state.onReadyBeingFired.push(subscriber); + if (bSticky) + subscribe(subscriber); + } + else { + subscribe(subscriber); + const db = this; + if (!bSticky) + subscribe(function unsubscribe() { + db.on.ready.unsubscribe(subscriber); + db.on.ready.unsubscribe(unsubscribe); + }); + } + }); + }; + }); + this.Collection = createCollectionConstructor(this); + this.Table = createTableConstructor(this); + this.Transaction = createTransactionConstructor(this); + this.Version = createVersionConstructor(this); + this.WhereClause = createWhereClauseConstructor(this); + this.on("versionchange", ev => { + if (ev.newVersion > 0) + console.warn(`Another connection wants to upgrade database '${this.name}'. Closing db now to resume the upgrade.`); + else + console.warn(`Another connection wants to delete database '${this.name}'. Closing db now to resume the delete request.`); + this.close(); + }); + this.on("blocked", ev => { + if (!ev.newVersion || ev.newVersion < ev.oldVersion) + console.warn(`Dexie.delete('${this.name}') was blocked`); + else + console.warn(`Upgrade '${this.name}' blocked by other connection holding version ${ev.oldVersion / 10}`); + }); + this._maxKey = getMaxKey(options.IDBKeyRange); + this._createTransaction = (mode, storeNames, dbschema, parentTransaction) => new this.Transaction(mode, storeNames, dbschema, this._options.chromeTransactionDurability, parentTransaction); + this._fireOnBlocked = ev => { + this.on("blocked").fire(ev); + connections + .filter(c => c.name === this.name && c !== this && !c._state.vcFired) + .map(c => c.on("versionchange").fire(ev)); + }; + this.use(virtualIndexMiddleware); + this.use(hooksMiddleware); + this.use(observabilityMiddleware); + this.use(cacheExistingValuesMiddleware); + this.vip = Object.create(this, { _vip: { value: true } }); + addons.forEach(addon => addon(this)); + } + version(versionNumber) { + if (isNaN(versionNumber) || versionNumber < 0.1) + throw new exceptions.Type(`Given version is not a positive number`); + versionNumber = Math.round(versionNumber * 10) / 10; + if (this.idbdb || this._state.isBeingOpened) + throw new exceptions.Schema("Cannot add version when database is open"); + this.verno = Math.max(this.verno, versionNumber); + const versions = this._versions; + var versionInstance = versions.filter(v => v._cfg.version === versionNumber)[0]; + if (versionInstance) + return versionInstance; + versionInstance = new this.Version(versionNumber); + versions.push(versionInstance); + versions.sort(lowerVersionFirst); + versionInstance.stores({}); + this._state.autoSchema = false; + return versionInstance; + } + _whenReady(fn) { + return (this.idbdb && (this._state.openComplete || PSD.letThrough || this._vip)) ? fn() : new DexiePromise((resolve, reject) => { + if (this._state.openComplete) { + return reject(new exceptions.DatabaseClosed(this._state.dbOpenError)); + } + if (!this._state.isBeingOpened) { + if (!this._options.autoOpen) { + reject(new exceptions.DatabaseClosed()); + return; + } + this.open().catch(nop); + } + this._state.dbReadyPromise.then(resolve, reject); + }).then(fn); + } + use({ stack, create, level, name }) { + if (name) + this.unuse({ stack, name }); + const middlewares = this._middlewares[stack] || (this._middlewares[stack] = []); + middlewares.push({ stack, create, level: level == null ? 10 : level, name }); + middlewares.sort((a, b) => a.level - b.level); + return this; + } + unuse({ stack, name, create }) { + if (stack && this._middlewares[stack]) { + this._middlewares[stack] = this._middlewares[stack].filter(mw => create ? mw.create !== create : + name ? mw.name !== name : + false); + } + return this; + } + open() { + return dexieOpen(this); + } + _close() { + const state = this._state; + const idx = connections.indexOf(this); + if (idx >= 0) + connections.splice(idx, 1); + if (this.idbdb) { + try { + this.idbdb.close(); + } + catch (e) { } + this._novip.idbdb = null; + } + state.dbReadyPromise = new DexiePromise(resolve => { + state.dbReadyResolve = resolve; + }); + state.openCanceller = new DexiePromise((_, reject) => { + state.cancelOpen = reject; + }); + } + close() { + this._close(); + const state = this._state; + this._options.autoOpen = false; + state.dbOpenError = new exceptions.DatabaseClosed(); + if (state.isBeingOpened) + state.cancelOpen(state.dbOpenError); + } + delete() { + const hasArguments = arguments.length > 0; + const state = this._state; + return new DexiePromise((resolve, reject) => { + const doDelete = () => { + this.close(); + var req = this._deps.indexedDB.deleteDatabase(this.name); + req.onsuccess = wrap(() => { + _onDatabaseDeleted(this._deps, this.name); + resolve(); + }); + req.onerror = eventRejectHandler(reject); + req.onblocked = this._fireOnBlocked; + }; + if (hasArguments) + throw new exceptions.InvalidArgument("Arguments not allowed in db.delete()"); + if (state.isBeingOpened) { + state.dbReadyPromise.then(doDelete); + } + else { + doDelete(); + } + }); + } + backendDB() { + return this.idbdb; + } + isOpen() { + return this.idbdb !== null; + } + hasBeenClosed() { + const dbOpenError = this._state.dbOpenError; + return dbOpenError && (dbOpenError.name === 'DatabaseClosed'); + } + hasFailed() { + return this._state.dbOpenError !== null; + } + dynamicallyOpened() { + return this._state.autoSchema; + } + get tables() { + return keys(this._allTables).map(name => this._allTables[name]); + } + transaction() { + const args = extractTransactionArgs.apply(this, arguments); + return this._transaction.apply(this, args); + } + _transaction(mode, tables, scopeFunc) { + let parentTransaction = PSD.trans; + if (!parentTransaction || parentTransaction.db !== this || mode.indexOf('!') !== -1) + parentTransaction = null; + const onlyIfCompatible = mode.indexOf('?') !== -1; + mode = mode.replace('!', '').replace('?', ''); + let idbMode, storeNames; + try { + storeNames = tables.map(table => { + var storeName = table instanceof this.Table ? table.name : table; + if (typeof storeName !== 'string') + throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); + return storeName; + }); + if (mode == "r" || mode === READONLY) + idbMode = READONLY; + else if (mode == "rw" || mode == READWRITE) + idbMode = READWRITE; + else + throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); + if (parentTransaction) { + if (parentTransaction.mode === READONLY && idbMode === READWRITE) { + if (onlyIfCompatible) { + parentTransaction = null; + } + else + throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); + } + if (parentTransaction) { + storeNames.forEach(storeName => { + if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { + if (onlyIfCompatible) { + parentTransaction = null; + } + else + throw new exceptions.SubTransaction("Table " + storeName + + " not included in parent transaction."); + } + }); + } + if (onlyIfCompatible && parentTransaction && !parentTransaction.active) { + parentTransaction = null; + } + } + } + catch (e) { + return parentTransaction ? + parentTransaction._promise(null, (_, reject) => { reject(e); }) : + rejection(e); + } + const enterTransaction = enterTransactionScope.bind(null, this, idbMode, storeNames, parentTransaction, scopeFunc); + return (parentTransaction ? + parentTransaction._promise(idbMode, enterTransaction, "lock") : + PSD.trans ? + usePSD(PSD.transless, () => this._whenReady(enterTransaction)) : + this._whenReady(enterTransaction)); + } + table(tableName) { + if (!hasOwn(this._allTables, tableName)) { + throw new exceptions.InvalidTable(`Table ${tableName} does not exist`); + } + return this._allTables[tableName]; + } } -class Vector3 { - - constructor( x = 0, y = 0, z = 0 ) { - - Vector3.prototype.isVector3 = true; - - this.x = x; - this.y = y; - this.z = z; - - } - - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - - this.x = x; - this.y = y; - this.z = z; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setZ( z ) { - - this.z = z; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y, this.z ); - - } - - copy( v ) { +const symbolObservable = typeof Symbol !== "undefined" && "observable" in Symbol + ? Symbol.observable + : "@@observable"; +class Observable { + constructor(subscribe) { + this._subscribe = subscribe; + } + subscribe(x, error, complete) { + return this._subscribe(!x || typeof x === "function" ? { next: x, error, complete } : x); + } + [symbolObservable]() { + return this; + } +} - this.x = v.x; - this.y = v.y; - this.z = v.z; +function extendObservabilitySet(target, newSet) { + keys(newSet).forEach(part => { + const rangeSet = target[part] || (target[part] = new RangeSet()); + mergeRanges(rangeSet, newSet[part]); + }); + return target; +} - return this; +function liveQuery(querier) { + let hasValue = false; + let currentValue = undefined; + const observable = new Observable((observer) => { + const scopeFuncIsAsync = isAsyncFunction(querier); + function execute(subscr) { + if (scopeFuncIsAsync) { + incrementExpectedAwaits(); + } + const exec = () => newScope(querier, { subscr, trans: null }); + const rv = PSD.trans + ? + usePSD(PSD.transless, exec) + : exec(); + if (scopeFuncIsAsync) { + rv.then(decrementExpectedAwaits, decrementExpectedAwaits); + } + return rv; + } + let closed = false; + let accumMuts = {}; + let currentObs = {}; + const subscription = { + get closed() { + return closed; + }, + unsubscribe: () => { + closed = true; + globalEvents.storagemutated.unsubscribe(mutationListener); + }, + }; + observer.start && observer.start(subscription); + let querying = false, startedListening = false; + function shouldNotify() { + return keys(currentObs).some((key) => accumMuts[key] && rangesOverlap(accumMuts[key], currentObs[key])); + } + const mutationListener = (parts) => { + extendObservabilitySet(accumMuts, parts); + if (shouldNotify()) { + doQuery(); + } + }; + const doQuery = () => { + if (querying || closed) + return; + accumMuts = {}; + const subscr = {}; + const ret = execute(subscr); + if (!startedListening) { + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener); + startedListening = true; + } + querying = true; + Promise.resolve(ret).then((result) => { + hasValue = true; + currentValue = result; + querying = false; + if (closed) + return; + if (shouldNotify()) { + doQuery(); + } + else { + accumMuts = {}; + currentObs = subscr; + observer.next && observer.next(result); + } + }, (err) => { + querying = false; + hasValue = false; + observer.error && observer.error(err); + subscription.unsubscribe(); + }); + }; + doQuery(); + return subscription; + }); + observable.hasValue = () => hasValue; + observable.getValue = () => currentValue; + return observable; +} - } +let domDeps; +try { + domDeps = { + indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, + IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange + }; +} +catch (e) { + domDeps = { indexedDB: null, IDBKeyRange: null }; +} - add( v ) { +const Dexie = Dexie$1; +props(Dexie, { + ...fullNameExceptions, + delete(databaseName) { + const db = new Dexie(databaseName, { addons: [] }); + return db.delete(); + }, + exists(name) { + return new Dexie(name, { addons: [] }).open().then(db => { + db.close(); + return true; + }).catch('NoSuchDatabaseError', () => false); + }, + getDatabaseNames(cb) { + try { + return getDatabaseNames(Dexie.dependencies).then(cb); + } + catch (_a) { + return rejection(new exceptions.MissingAPI()); + } + }, + defineClass() { + function Class(content) { + extend(this, content); + } + return Class; + }, + ignoreTransaction(scopeFunc) { + return PSD.trans ? + usePSD(PSD.transless, scopeFunc) : + scopeFunc(); + }, + vip, + async: function (generatorFn) { + return function () { + try { + var rv = awaitIterator(generatorFn.apply(this, arguments)); + if (!rv || typeof rv.then !== 'function') + return DexiePromise.resolve(rv); + return rv; + } + catch (e) { + return rejection(e); + } + }; + }, + spawn: function (generatorFn, args, thiz) { + try { + var rv = awaitIterator(generatorFn.apply(thiz, args || [])); + if (!rv || typeof rv.then !== 'function') + return DexiePromise.resolve(rv); + return rv; + } + catch (e) { + return rejection(e); + } + }, + currentTransaction: { + get: () => PSD.trans || null + }, + waitFor: function (promiseOrFunction, optionalTimeout) { + const promise = DexiePromise.resolve(typeof promiseOrFunction === 'function' ? + Dexie.ignoreTransaction(promiseOrFunction) : + promiseOrFunction) + .timeout(optionalTimeout || 60000); + return PSD.trans ? + PSD.trans.waitFor(promise) : + promise; + }, + Promise: DexiePromise, + debug: { + get: () => debug, + set: value => { + setDebug(value, value === 'dexie' ? () => true : dexieStackFrameFilter); + } + }, + derive: derive, + extend: extend, + props: props, + override: override, + Events: Events, + on: globalEvents, + liveQuery, + extendObservabilitySet, + getByKeyPath: getByKeyPath, + setByKeyPath: setByKeyPath, + delByKeyPath: delByKeyPath, + shallowClone: shallowClone, + deepClone: deepClone, + getObjectDiff: getObjectDiff, + cmp, + asap: asap$1, + minKey: minKey, + addons: [], + connections: connections, + errnames: errnames, + dependencies: domDeps, + semVer: DEXIE_VERSION, + version: DEXIE_VERSION.split('.') + .map(n => parseInt(n)) + .reduce((p, c, i) => p + (c / Math.pow(10, i * 2))), +}); +Dexie.maxKey = getMaxKey(Dexie.dependencies.IDBKeyRange); - this.x += v.x; - this.y += v.y; - this.z += v.z; +if (typeof dispatchEvent !== 'undefined' && typeof addEventListener !== 'undefined') { + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, updatedParts => { + if (!propagatingLocally) { + let event; + if (isIEOrEdge) { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, true, true, updatedParts); + } + else { + event = new CustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, { + detail: updatedParts + }); + } + propagatingLocally = true; + dispatchEvent(event); + propagatingLocally = false; + } + }); + addEventListener(STORAGE_MUTATED_DOM_EVENT_NAME, ({ detail }) => { + if (!propagatingLocally) { + propagateLocally(detail); + } + }); +} +function propagateLocally(updateParts) { + let wasMe = propagatingLocally; + try { + propagatingLocally = true; + globalEvents.storagemutated.fire(updateParts); + } + finally { + propagatingLocally = wasMe; + } +} +let propagatingLocally = false; - return this; +if (typeof BroadcastChannel !== 'undefined') { + const bc = new BroadcastChannel(STORAGE_MUTATED_DOM_EVENT_NAME); + if (typeof bc.unref === 'function') { + bc.unref(); + } + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, (changedParts) => { + if (!propagatingLocally) { + bc.postMessage(changedParts); + } + }); + bc.onmessage = (ev) => { + if (ev.data) + propagateLocally(ev.data); + }; +} +else if (typeof self !== 'undefined' && typeof navigator !== 'undefined') { + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, (changedParts) => { + try { + if (!propagatingLocally) { + if (typeof localStorage !== 'undefined') { + localStorage.setItem(STORAGE_MUTATED_DOM_EVENT_NAME, JSON.stringify({ + trig: Math.random(), + changedParts, + })); + } + if (typeof self['clients'] === 'object') { + [...self['clients'].matchAll({ includeUncontrolled: true })].forEach((client) => client.postMessage({ + type: STORAGE_MUTATED_DOM_EVENT_NAME, + changedParts, + })); + } + } + } + catch (_a) { } + }); + if (typeof addEventListener !== 'undefined') { + addEventListener('storage', (ev) => { + if (ev.key === STORAGE_MUTATED_DOM_EVENT_NAME) { + const data = JSON.parse(ev.newValue); + if (data) + propagateLocally(data.changedParts); + } + }); + } + const swContainer = self.document && navigator.serviceWorker; + if (swContainer) { + swContainer.addEventListener('message', propagateMessageLocally); + } +} +function propagateMessageLocally({ data }) { + if (data && data.type === STORAGE_MUTATED_DOM_EVENT_NAME) { + propagateLocally(data.changedParts); + } +} - } +DexiePromise.rejectionMapper = mapError; +setDebug(debug, dexieStackFrameFilter); - addScalar( s ) { +class ModelDatabase extends Dexie$1 { + constructor() { + super("ModelDatabase"); + this.version(2).stores({ + models: "id, file", + }); + } +} - this.x += s; - this.y += s; - this.z += s; +// TODO: Implement UI elements (this is probably just for 3d scans) +/** + * A tool to cache files using the browser's IndexedDB API. This might + * save loading time and infrastructure costs for files that need to be + * fetched from the cloud. + */ +class LocalCacher extends Component { + /** The IDs of all the stored files. */ + get ids() { + const serialized = localStorage.getItem(this._storedModels) || "[]"; + return JSON.parse(serialized); + } + constructor(components) { + super(components); + /** Fires when a file has been loaded from cache. */ + this.onFileLoaded = new Event(); + /** Fires when a file has been saved into cache. */ + this.onItemSaved = new Event(); + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + /** {@link Component.enabled} */ + this.enabled = true; + /** {@link UI.uiElement} */ + this.uiElement = new UIElement(); + this.cards = []; + this._storedModels = "open-bim-components-stored-files"; + components.tools.add(LocalCacher.uuid, this); + this._db = new ModelDatabase(); + if (components.uiEnabled) { + this.setUI(components); + } + } + /** + * {@link Component.get}. + * @param id the ID of the file to fetch. + */ + async get(id) { + if (this.exists(id)) { + await this._db.open(); + const result = await this.getModelFromLocalCache(id); + this._db.close(); + return result; + } + return null; + } + /** + * Saves the file with the given ID. + * @param id the ID to assign to the file. + * @param url the URL where the file is located. + */ + async save(id, url) { + this.addStoredID(id); + const rawData = await fetch(url); + const file = await rawData.blob(); + await this._db.open(); + await this._db.models.add({ + id, + file, + }); + this._db.close(); + } + /** + * Checks if there's a file stored with the given ID. + * @param id to check. + */ + exists(id) { + const stored = localStorage.getItem(id); + return stored !== null; + } + /** + * Deletes the files stored in the given ids. + * @param ids the identifiers of the files to delete. + */ + async delete(ids) { + await this._db.open(); + for (const id of ids) { + if (this.exists(id)) { + this.removeStoredID(id); + await this._db.models.where("id").equals(id).delete(); + } + } + this._db.close(); + } + /** Deletes all the stored files. */ + async deleteAll() { + await this._db.open(); + this.clearStoredIDs(); + await this._db.delete(); + this._db = new ModelDatabase(); + this._db.close(); + } + /** {@link Disposable.dispose} */ + async dispose() { + this.onFileLoaded.reset(); + this.onItemSaved.reset(); + for (const card of this.cards) { + await card.dispose(); + } + this.cards = []; + await this.uiElement.dispose(); + this._db = null; + await this.onDisposed.trigger(LocalCacher.uuid); + this.onDisposed.reset(); + } + setUI(components) { + const main = new Button(components); + main.materialIcon = "storage"; + main.tooltip = "Local cacher"; + const saveButton = new Button(components); + saveButton.label = "Save"; + saveButton.materialIcon = "save"; + const loadButton = new Button(components); + loadButton.label = "Download"; + loadButton.materialIcon = "download"; + main.addChild(saveButton, loadButton); + const floatingMenu = new FloatingWindow(components, "file-list-menu"); + this.uiElement.set({ main, loadButton, saveButton, floatingMenu }); + floatingMenu.title = "Saved Files"; + floatingMenu.visible = false; + const savedFilesMenuHTML = floatingMenu.get(); + savedFilesMenuHTML.style.left = "70px"; + savedFilesMenuHTML.style.top = "100px"; + savedFilesMenuHTML.style.width = "340px"; + savedFilesMenuHTML.style.height = "400px"; + const renderer = this.components.renderer.get(); + const viewerContainer = renderer.domElement.parentElement; + viewerContainer.appendChild(floatingMenu.get()); + } + async getModelFromLocalCache(id) { + const found = await this._db.models.where("id").equals(id).toArray(); + return found[0].file; + } + clearStoredIDs() { + const ids = this.ids; + for (const id of ids) { + this.removeStoredID(id); + } + } + removeStoredID(id) { + localStorage.removeItem(id); + const allIDs = this.ids; + const ids = allIDs.filter((savedId) => savedId !== id); + this.setStoredIDs(ids); + } + addStoredID(id) { + const time = performance.now().toString(); + localStorage.setItem(id, time); + const ids = this.ids; + ids.push(id); + this.setStoredIDs(ids); + } + setStoredIDs(ids) { + localStorage.setItem(this._storedModels, JSON.stringify(ids)); + } +} +LocalCacher.uuid = "22ae591a-3a67-4988-86c6-68d7b83febf2"; +ToolComponent.libraryUUIDs.add(LocalCacher.uuid); - return this; +class SimpleSVGViewport extends Component { + get enabled() { + return this._enabled; + } + set enabled(value) { + this._enabled = value; + this.resize(); + this._undoList = []; + this.uiElement.get("toolbar").visible = value; + if (value) { + this._viewport.classList.remove("pointer-events-none"); + } + else { + this.clear(); + this.uiElement.get("settingsWindow").visible = false; + this._viewport.classList.add("pointer-events-none"); + } + } + set config(value) { + this._config = { ...this._config, ...value }; + } + get config() { + return this._config; + } + constructor(components, config) { + super(components); + this.uiElement = new UIElement(); + this.id = generateUUID().toLowerCase(); + this._enabled = false; + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + this._viewport = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + this._size = new Vector2$1(); + this._undoList = []; + this.onResize = () => { + this.resize(); + }; + const defaultConfig = { + fillColor: "transparent", + strokeColor: "#ff0000", + strokeWidth: 4, + }; + this.config = { ...defaultConfig, ...(config !== null && config !== void 0 ? config : {}) }; + this._viewport.classList.add("absolute", "top-0", "right-0"); + // this._viewport.setAttribute("preserveAspectRatio", "xMidYMid") + this._viewport.setAttribute("width", "100%"); + this._viewport.setAttribute("height", "100%"); + // const renderer = this._components.renderer; + // const rendererSize = renderer.getSize(); + // const width = rendererSize.x + // const height = rendererSize.y + // this._viewport.setAttribute("viewBox", `0 0 ${width} ${height}`); + this.setUI(); + this.enabled = false; + this.components.ui.viewerContainer.append(this._viewport); + this.setupEvents(true); + } + async dispose() { + this._undoList = []; + this.uiElement.dispose(); + await this.onDisposed.trigger(); + this.onDisposed.reset(); + } + get() { + return this._viewport; + } + clear() { + const viewport = this.get(); + this._undoList = []; + while (viewport.firstChild) { + viewport.removeChild(viewport.firstChild); + } + } + getDrawing() { + return this.get().childNodes; + } + // setDrawing() { + // if (!this.enabled) { } + // } + /** {@link Resizeable.resize}. */ + resize() { + const renderer = this.components.renderer; + const rendererSize = renderer.getSize(); + const width = this.enabled ? rendererSize.x : 0; + const height = this.enabled ? rendererSize.y : 0; + this._size.set(width, height); + // this._viewport.setAttribute("viewBox", `0 0 ${this._size.x} ${this._size.y}`); + } + /** {@link Resizeable.getSize}. */ + getSize() { + return this._size; + } + setupEvents(active) { + if (active) { + window.addEventListener("resize", this.onResize); + } + else { + window.removeEventListener("resize", this.onResize); + } + } + setUI() { + var _a, _b; + const undoDrawingBtn = new Button(this.components, { + materialIconName: "undo", + }); + undoDrawingBtn.onClick.add(() => { + if (this._viewport.lastChild) { + this._undoList.push(this._viewport.lastChild); + this._viewport.lastChild.remove(); + } + }); + const redoDrawingBtn = new Button(this.components, { + materialIconName: "redo", + }); + redoDrawingBtn.onClick.add(() => { + const childNode = this._undoList[this._undoList.length - 1]; + if (childNode) { + this._undoList.pop(); + this._viewport.append(childNode); + } + }); + const clearDrawingBtn = new Button(this.components, { + materialIconName: "delete", + }); + clearDrawingBtn.onClick.add(() => this.clear()); + // #region Settings window + const settingsWindow = new FloatingWindow(this.components, this.id); + settingsWindow.title = "Drawing Settings"; + settingsWindow.visible = false; + const viewerContainer = this.components.renderer.get().domElement + .parentElement; + viewerContainer.append(settingsWindow.get()); + const strokeWidth = new RangeInput(this.components); + strokeWidth.label = "Stroke Width"; + strokeWidth.min = 2; + strokeWidth.max = 6; + strokeWidth.value = 4; + // strokeWidth.id = this.id; + strokeWidth.onChange.add((value) => { + // @ts-ignore + this.config = { strokeWidth: value }; + }); + const strokeColorInput = new ColorInput(this.components); + strokeColorInput.label = "Stroke Color"; + strokeColorInput.value = (_a = this.config.strokeColor) !== null && _a !== void 0 ? _a : "#BCF124"; + // strokeColorInput.name = "stroke-color"; + // strokeColorInput.id = this.id; + strokeColorInput.onChange.add((value) => { + this.config = { strokeColor: value }; + }); + const fillColorInput = new ColorInput(this.components); + strokeColorInput.label = "Fill Color"; + strokeColorInput.value = (_b = this.config.fillColor) !== null && _b !== void 0 ? _b : "#BCF124"; + // strokeColorInput.name = "fill-color"; + // strokeColorInput.id = this.id; + fillColorInput.onChange.add((value) => { + this.config = { fillColor: value }; + }); + settingsWindow.addChild(strokeColorInput, fillColorInput, strokeWidth); + const settingsBtn = new Button(this.components, { + materialIconName: "settings", + }); + settingsBtn.onClick.add(() => { + settingsWindow.visible = !settingsWindow.visible; + settingsBtn.active = settingsWindow.visible; + }); + settingsWindow.onHidden.add(() => (settingsBtn.active = false)); + const toolbar = new Toolbar(this.components, { position: "right" }); + toolbar.addChild(settingsBtn, undoDrawingBtn, redoDrawingBtn, clearDrawingBtn); + this.uiElement.set({ toolbar, settingsWindow }); + } +} - } +// TODO: Clean up and document +// TODO: Disable / enable instance color for instance meshes +/** + * A tool to easily handle the materials of massive amounts of + * objects and scene background easily. + */ +class MaterialManager extends Component { + constructor(components) { + super(components); + /** {@link Component.enabled} */ + this.enabled = true; + this._originalBackground = null; + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + this._originals = {}; + this._list = {}; + this.components.tools.add(MaterialManager.uuid, this); + } + /** + * {@link Component.get}. + * @return list of created materials. + */ + get() { + return Object.keys(this._list); + } + /** + * Turns the specified material styles on or off. + * + * @param active whether to turn it on or off. + * @param ids the ids of the style to turn on or off. + */ + set(active, ids = Object.keys(this._list)) { + for (const id of ids) { + const { material, meshes } = this._list[id]; + for (const mesh of meshes) { + if (active) { + if (!this._originals[mesh.uuid]) { + this._originals[mesh.uuid] = { material: mesh.material }; + } + if (mesh instanceof THREE$1.InstancedMesh && mesh.instanceColor) { + this._originals[mesh.uuid].instances = mesh.instanceColor; + mesh.instanceColor = null; + } + mesh.material = material; + } + else { + if (!this._originals[mesh.uuid]) + continue; + mesh.material = this._originals[mesh.uuid].material; + const instances = this._originals[mesh.uuid].instances; + if (mesh instanceof THREE$1.InstancedMesh && instances) { + mesh.instanceColor = instances; + } + } + } + } + } + /** {@link Disposable.dispose} */ + async dispose() { + for (const id in this._list) { + const { material } = this._list[id]; + material.dispose(); + } + this._list = {}; + this._originals = {}; + await this.onDisposed.trigger(MaterialManager.uuid); + this.onDisposed.reset(); + } + /** + * Sets the color of the background of the scene. + * + * @param color: the color to apply. + */ + setBackgroundColor(color) { + const scene = this.components.scene.get(); + if (!this._originalBackground) { + this._originalBackground = scene.background; + } + if (this._originalBackground) { + scene.background = color; + } + } + /** + * Resets the scene background to the color that was being used + * before applying the material manager. + */ + resetBackgroundColor() { + const scene = this.components.scene.get(); + if (this._originalBackground) { + scene.background = this._originalBackground; + } + } + /** + * Creates a new material style. + * @param id the identifier of the style to create. + * @param material the material of the style. + */ + addMaterial(id, material) { + if (this._list[id]) { + throw new Error("This ID already exists!"); + } + this._list[id] = { material, meshes: new Set() }; + } + /** + * Assign meshes to a certain style. + * @param id the identifier of the style. + * @param meshes the meshes to assign to the style. + */ + addMeshes(id, meshes) { + if (!this._list[id]) { + throw new Error("This ID doesn't exists!"); + } + for (const mesh of meshes) { + this._list[id].meshes.add(mesh); + } + } +} +MaterialManager.uuid = "24989d27-fa2f-4797-8b08-35918f74e502"; +ToolComponent.libraryUUIDs.add(MaterialManager.uuid); - addVectors( a, b ) { +// OrbitControls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; - return this; +class OrbitControls extends EventDispatcher$1 { - } + constructor( object, domElement ) { - addScaledVector( v, s ) { + super(); - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll - return this; + // Set to false to disable this control + this.enabled = true; - } + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3$1(); - sub( v ) { + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; - return this; + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians - } + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians - subScalar( s ) { + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; - this.x -= s; - this.y -= s; - this.z -= s; + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; - return this; + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; - } + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push - subVectors( a, b ) { + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; + // The four arrow keys + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - return this; + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - } + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - multiply( v ) { + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; + // the target DOM element for key events + this._domElementKeyEvents = null; - return this; + // + // public methods + // - } + this.getPolarAngle = function () { - multiplyScalar( scalar ) { + return spherical.phi; - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; + }; - return this; + this.getAzimuthalAngle = function () { - } + return spherical.theta; - multiplyVectors( a, b ) { + }; - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; + this.getDistance = function () { - return this; + return this.object.position.distanceTo( this.target ); - } + }; - applyEuler( euler ) { + this.listenToKeyEvents = function ( domElement ) { - return this.applyQuaternion( _quaternion.setFromEuler( euler ) ); + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; - } + }; - applyAxisAngle( axis, angle ) { + this.stopListenToKeyEvents = function () { - return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) ); + this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = null; - } + }; - applyMatrix3( m ) { + this.saveState = function () { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + }; - return this; + this.reset = function () { - } + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; - applyNormalMatrix( m ) { + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); - return this.applyMatrix3( m ).normalize(); + scope.update(); - } + state = STATE.NONE; - applyMatrix4( m ) { + }; - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + const offset = new Vector3$1(); - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + // so camera.up is the orbit axis + const quat = new Quaternion$1().setFromUnitVectors( object.up, new Vector3$1( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); - return this; + const lastPosition = new Vector3$1(); + const lastQuaternion = new Quaternion$1(); - } + const twoPI = 2 * Math.PI; - applyQuaternion( q ) { + return function update() { - const x = this.x, y = this.y, z = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + const position = scope.object.position; - // calculate quat * vector + offset.copy( position ).sub( scope.target ); - const ix = qw * x + qy * z - qz * y; - const iy = qw * y + qz * x - qx * z; - const iz = qw * z + qx * y - qy * x; - const iw = - qx * x - qy * y - qz * z; + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); - // calculate result * inverse quat + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); - this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; - this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; - this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; + if ( scope.autoRotate && state === STATE.NONE ) { - return this; + rotateLeft( getAutoRotationAngle() ); - } + } - project( camera ) { + if ( scope.enableDamping ) { - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; - } + } else { - unproject( camera ) { + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); + } - } + // restrict theta to be between desired limits - transformDirection( m ) { + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction + if ( isFinite( min ) && isFinite( max ) ) { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; - 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; + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; - return this.normalize(); + if ( min <= max ) { - } + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); - divide( v ) { + } else { - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); - return this; + } - } + } - divideScalar( scalar ) { + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); - return this.multiplyScalar( 1 / scalar ); + spherical.makeSafe(); - } - min( v ) { + spherical.radius *= scale; - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); - return this; + // move target to panned location - } + if ( scope.enableDamping === true ) { - max( v ) { + scope.target.addScaledVector( panOffset, scope.dampingFactor ); - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); + } else { - return this; + scope.target.add( panOffset ); - } + } - clamp( min, max ) { + offset.setFromSpherical( spherical ); - // assumes min < max, componentwise + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + position.copy( scope.target ).add( offset ); - return this; + scope.object.lookAt( scope.target ); - } + if ( scope.enableDamping === true ) { - clampScalar( minVal, maxVal ) { + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); + panOffset.multiplyScalar( 1 - scope.dampingFactor ); - return this; + } else { - } + sphericalDelta.set( 0, 0, 0 ); - clampLength( min, max ) { + panOffset.set( 0, 0, 0 ); - const length = this.length(); + } - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + scale = 1; - } + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - floor() { + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); + scope.dispatchEvent( _changeEvent ); - return this; + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; - } + return true; - ceil() { + } - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); + return false; - return this; + }; - } + }(); - round() { + this.dispose = function () { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); - return this; + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); - } + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); - roundToZero() { - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + if ( scope._domElementKeyEvents !== null ) { - return this; + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + scope._domElementKeyEvents = null; - } + } - negate() { + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; + }; - return this; + // + // internals + // - } + const scope = this; - dot( v ) { + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; - return this.x * v.x + this.y * v.y + this.z * v.z; + let state = STATE.NONE; - } + const EPS = 0.000001; - // TODO lengthSquared? + // current position in spherical coordinates + const spherical = new Spherical(); + const sphericalDelta = new Spherical(); - lengthSq() { + let scale = 1; + const panOffset = new Vector3$1(); + let zoomChanged = false; - return this.x * this.x + this.y * this.y + this.z * this.z; + const rotateStart = new Vector2$1(); + const rotateEnd = new Vector2$1(); + const rotateDelta = new Vector2$1(); - } + const panStart = new Vector2$1(); + const panEnd = new Vector2$1(); + const panDelta = new Vector2$1(); - length() { + const dollyStart = new Vector2$1(); + const dollyEnd = new Vector2$1(); + const dollyDelta = new Vector2$1(); - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + const pointers = []; + const pointerPositions = {}; - } + function getAutoRotationAngle() { - manhattanLength() { + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + } - } + function getZoomScale() { - normalize() { + return Math.pow( 0.95, scope.zoomSpeed ); - return this.divideScalar( this.length() || 1 ); + } - } + function rotateLeft( angle ) { - setLength( length ) { + sphericalDelta.theta -= angle; - return this.normalize().multiplyScalar( length ); + } - } + function rotateUp( angle ) { - lerp( v, alpha ) { + sphericalDelta.phi -= angle; - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; + } - return this; + const panLeft = function () { - } + const v = new Vector3$1(); - lerpVectors( v1, v2, alpha ) { + return function panLeft( distance, objectMatrix ) { - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); - return this; + panOffset.add( v ); - } + }; - cross( v ) { + }(); - return this.crossVectors( this, v ); + const panUp = function () { - } + const v = new Vector3$1(); - crossVectors( a, b ) { + return function panUp( distance, objectMatrix ) { - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; + if ( scope.screenSpacePanning === true ) { - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; + v.setFromMatrixColumn( objectMatrix, 1 ); - return this; + } else { - } + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); - projectOnVector( v ) { + } - const denominator = v.lengthSq(); + v.multiplyScalar( distance ); - if ( denominator === 0 ) return this.set( 0, 0, 0 ); + panOffset.add( v ); - const scalar = v.dot( this ) / denominator; + }; - return this.copy( v ).multiplyScalar( scalar ); + }(); - } + // deltaX and deltaY are in pixels; right and down are positive + const pan = function () { - projectOnPlane( planeNormal ) { + const offset = new Vector3$1(); - _vector$2.copy( this ).projectOnVector( planeNormal ); + return function pan( deltaX, deltaY ) { - return this.sub( _vector$2 ); + const element = scope.domElement; - } + if ( scope.object.isPerspectiveCamera ) { - reflect( normal ) { + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); - return this.sub( _vector$2.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); - } + } else if ( scope.object.isOrthographicCamera ) { - angleTo( v ) { + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + } else { - if ( denominator === 0 ) return Math.PI / 2; + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; - const theta = this.dot( v ) / denominator; + } - // clamp, to handle numerical problems + }; - return Math.acos( clamp( theta, - 1, 1 ) ); + }(); - } + function dollyOut( dollyScale ) { - distanceTo( v ) { + if ( scope.object.isPerspectiveCamera ) { - return Math.sqrt( this.distanceToSquared( v ) ); + scale /= dollyScale; - } + } else if ( scope.object.isOrthographicCamera ) { - distanceToSquared( v ) { + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + } else { - return dx * dx + dy * dy + dz * dz; + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; - } + } - manhattanDistanceTo( v ) { + } - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + function dollyIn( dollyScale ) { - } + if ( scope.object.isPerspectiveCamera ) { - setFromSpherical( s ) { + scale *= dollyScale; - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); + } else if ( scope.object.isOrthographicCamera ) { - } + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; - setFromSphericalCoords( radius, phi, theta ) { + } else { - const sinPhiRadius = Math.sin( phi ) * radius; + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); + } - return this; + } - } + // + // event callbacks - update the object state + // - setFromCylindrical( c ) { + function handleMouseDownRotate( event ) { - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); + rotateStart.set( event.clientX, event.clientY ); - } + } - setFromCylindricalCoords( radius, theta, y ) { + function handleMouseDownDolly( event ) { - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); + dollyStart.set( event.clientX, event.clientY ); - return this; + } - } + function handleMouseDownPan( event ) { - setFromMatrixPosition( m ) { + panStart.set( event.clientX, event.clientY ); - const e = m.elements; + } - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; + function handleMouseMoveRotate( event ) { - return this; + rotateEnd.set( event.clientX, event.clientY ); - } + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); - setFromMatrixScale( m ) { + const element = scope.domElement; - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height - this.x = sx; - this.y = sy; - this.z = sz; + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); - return this; + rotateStart.copy( rotateEnd ); - } + scope.update(); - setFromMatrixColumn( m, index ) { + } - return this.fromArray( m.elements, index * 4 ); + function handleMouseMoveDolly( event ) { - } + dollyEnd.set( event.clientX, event.clientY ); - setFromMatrix3Column( m, index ) { + dollyDelta.subVectors( dollyEnd, dollyStart ); - return this.fromArray( m.elements, index * 3 ); + if ( dollyDelta.y > 0 ) { - } + dollyOut( getZoomScale() ); - setFromEuler( e ) { + } else if ( dollyDelta.y < 0 ) { - this.x = e._x; - this.y = e._y; - this.z = e._z; + dollyIn( getZoomScale() ); - return this; + } - } + dollyStart.copy( dollyEnd ); - setFromColor( c ) { + scope.update(); - this.x = c.r; - this.y = c.g; - this.z = c.b; + } - return this; + function handleMouseMovePan( event ) { - } + panEnd.set( event.clientX, event.clientY ); - equals( v ) { + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + pan( panDelta.x, panDelta.y ); - } + panStart.copy( panEnd ); - fromArray( array, offset = 0 ) { + scope.update(); - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; + } - return this; + function handleMouseWheel( event ) { - } + if ( event.deltaY < 0 ) { - toArray( array = [], offset = 0 ) { + dollyIn( getZoomScale() ); - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; + } else if ( event.deltaY > 0 ) { - return array; + dollyOut( getZoomScale() ); - } + } - fromBufferAttribute( attribute, index ) { + scope.update(); - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); + } - return this; + function handleKeyDown( event ) { - } + let needsUpdate = false; - random() { + switch ( event.code ) { - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); + case scope.keys.UP: - return this; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } + rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - randomDirection() { + } else { - // Derived from https://mathworld.wolfram.com/SpherePointPicking.html + pan( 0, scope.keyPanSpeed ); - const u = ( Math.random() - 0.5 ) * 2; - const t = Math.random() * Math.PI * 2; - const f = Math.sqrt( 1 - u ** 2 ); + } - this.x = f * Math.cos( t ); - this.y = f * Math.sin( t ); - this.z = u; + needsUpdate = true; + break; - return this; + case scope.keys.BOTTOM: - } + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - *[ Symbol.iterator ]() { + rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - yield this.x; - yield this.y; - yield this.z; + } else { - } + pan( 0, - scope.keyPanSpeed ); -} + } -const _vector$2 = /*@__PURE__*/ new Vector3(); -const _quaternion = /*@__PURE__*/ new Quaternion(); + needsUpdate = true; + break; -class Vector2 { + case scope.keys.LEFT: - constructor( x = 0, y = 0 ) { + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - Vector2.prototype.isVector2 = true; + rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - this.x = x; - this.y = y; + } else { - } + pan( scope.keyPanSpeed, 0 ); - get width() { + } - return this.x; + needsUpdate = true; + break; - } + case scope.keys.RIGHT: - set width( value ) { + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - this.x = value; + rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); - } + } else { - get height() { + pan( - scope.keyPanSpeed, 0 ); - return this.y; + } - } + needsUpdate = true; + break; - set height( value ) { + } - this.y = value; + if ( needsUpdate ) { - } + // prevent the browser from scrolling on cursor keys + event.preventDefault(); - set( x, y ) { + scope.update(); - this.x = x; - this.y = y; + } - return this; - } + } - setScalar( scalar ) { + function handleTouchStartRotate() { - this.x = scalar; - this.y = scalar; + if ( pointers.length === 1 ) { - return this; + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); - } + } else { - setX( x ) { + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); - this.x = x; + rotateStart.set( x, y ); - return this; + } - } + } - setY( y ) { + function handleTouchStartPan() { - this.y = y; + if ( pointers.length === 1 ) { - return this; + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); - } + } else { - setComponent( index, value ) { + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); - switch ( index ) { + panStart.set( x, y ); - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'index is out of range: ' + index ); + } } - return this; - - } + function handleTouchStartDolly() { - getComponent( index ) { + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; - switch ( index ) { + const distance = Math.sqrt( dx * dx + dy * dy ); - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'index is out of range: ' + index ); + dollyStart.set( 0, distance ); } - } + function handleTouchStartDollyPan() { - clone() { + if ( scope.enableZoom ) handleTouchStartDolly(); - return new this.constructor( this.x, this.y ); + if ( scope.enablePan ) handleTouchStartPan(); - } + } - copy( v ) { + function handleTouchStartDollyRotate() { - this.x = v.x; - this.y = v.y; + if ( scope.enableZoom ) handleTouchStartDolly(); - return this; + if ( scope.enableRotate ) handleTouchStartRotate(); - } + } - add( v ) { + function handleTouchMoveRotate( event ) { - this.x += v.x; - this.y += v.y; + if ( pointers.length == 1 ) { - return this; + rotateEnd.set( event.pageX, event.pageY ); - } + } else { - addScalar( s ) { + const position = getSecondPointerPosition( event ); - this.x += s; - this.y += s; + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - return this; + rotateEnd.set( x, y ); - } + } - addVectors( a, b ) { + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); - this.x = a.x + b.x; - this.y = a.y + b.y; + const element = scope.domElement; - return this; + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height - } + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); - addScaledVector( v, s ) { + rotateStart.copy( rotateEnd ); - this.x += v.x * s; - this.y += v.y * s; + } - return this; + function handleTouchMovePan( event ) { - } + if ( pointers.length === 1 ) { - sub( v ) { + panEnd.set( event.pageX, event.pageY ); - this.x -= v.x; - this.y -= v.y; + } else { - return this; + const position = getSecondPointerPosition( event ); - } + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - subScalar( s ) { + panEnd.set( x, y ); - this.x -= s; - this.y -= s; + } - return this; + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); - } + pan( panDelta.x, panDelta.y ); - subVectors( a, b ) { + panStart.copy( panEnd ); - this.x = a.x - b.x; - this.y = a.y - b.y; + } - return this; + function handleTouchMoveDolly( event ) { - } + const position = getSecondPointerPosition( event ); - multiply( v ) { + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - this.x *= v.x; - this.y *= v.y; + const distance = Math.sqrt( dx * dx + dy * dy ); - return this; + dollyEnd.set( 0, distance ); - } + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); - multiplyScalar( scalar ) { + dollyOut( dollyDelta.y ); - this.x *= scalar; - this.y *= scalar; + dollyStart.copy( dollyEnd ); - return this; + } - } + function handleTouchMoveDollyPan( event ) { - divide( v ) { + if ( scope.enableZoom ) handleTouchMoveDolly( event ); - this.x /= v.x; - this.y /= v.y; + if ( scope.enablePan ) handleTouchMovePan( event ); - return this; + } - } + function handleTouchMoveDollyRotate( event ) { - divideScalar( scalar ) { + if ( scope.enableZoom ) handleTouchMoveDolly( event ); - return this.multiplyScalar( 1 / scalar ); + if ( scope.enableRotate ) handleTouchMoveRotate( event ); - } + } - applyMatrix3( m ) { + // + // event handlers - FSM: listen for events and reset state + // - const x = this.x, y = this.y; - const e = m.elements; + function onPointerDown( event ) { - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; + if ( scope.enabled === false ) return; - return this; + if ( pointers.length === 0 ) { - } + scope.domElement.setPointerCapture( event.pointerId ); - min( v ) { + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); + } - return this; + // - } + addPointer( event ); - max( v ) { + if ( event.pointerType === 'touch' ) { - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); + onTouchStart( event ); - return this; + } else { - } + onMouseDown( event ); - clamp( min, max ) { + } - // assumes min < max, componentwise + } - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + function onPointerMove( event ) { - return this; + if ( scope.enabled === false ) return; - } + if ( event.pointerType === 'touch' ) { - clampScalar( minVal, maxVal ) { + onTouchMove( event ); - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + } else { - return this; + onMouseMove( event ); - } + } - clampLength( min, max ) { + } - const length = this.length(); + function onPointerUp( event ) { - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + removePointer( event ); - } + if ( pointers.length === 0 ) { - floor() { + scope.domElement.releasePointerCapture( event.pointerId ); - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); - return this; + } - } + scope.dispatchEvent( _endEvent ); - ceil() { + state = STATE.NONE; - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); + } - return this; + function onMouseDown( event ) { - } + let mouseAction; - round() { + switch ( event.button ) { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); + case 0: - return this; + mouseAction = scope.mouseButtons.LEFT; + break; - } + case 1: - roundToZero() { + mouseAction = scope.mouseButtons.MIDDLE; + break; - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + case 2: - return this; + mouseAction = scope.mouseButtons.RIGHT; + break; - } + default: - negate() { + mouseAction = - 1; - this.x = - this.x; - this.y = - this.y; + } - return this; + switch ( mouseAction ) { - } + case MOUSE.DOLLY: - dot( v ) { + if ( scope.enableZoom === false ) return; - return this.x * v.x + this.y * v.y; + handleMouseDownDolly( event ); - } + state = STATE.DOLLY; - cross( v ) { + break; - return this.x * v.y - this.y * v.x; + case MOUSE.ROTATE: - } + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - lengthSq() { + if ( scope.enablePan === false ) return; - return this.x * this.x + this.y * this.y; + handleMouseDownPan( event ); - } + state = STATE.PAN; - length() { + } else { - return Math.sqrt( this.x * this.x + this.y * this.y ); + if ( scope.enableRotate === false ) return; - } + handleMouseDownRotate( event ); - manhattanLength() { + state = STATE.ROTATE; - return Math.abs( this.x ) + Math.abs( this.y ); + } - } + break; - normalize() { + case MOUSE.PAN: - return this.divideScalar( this.length() || 1 ); + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } + if ( scope.enableRotate === false ) return; - angle() { + handleMouseDownRotate( event ); - // computes the angle in radians with respect to the positive x-axis + state = STATE.ROTATE; - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; + } else { - return angle; + if ( scope.enablePan === false ) return; - } + handleMouseDownPan( event ); - angleTo( v ) { + state = STATE.PAN; - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + } - if ( denominator === 0 ) return Math.PI / 2; + break; - const theta = this.dot( v ) / denominator; + default: - // clamp, to handle numerical problems + state = STATE.NONE; - return Math.acos( clamp( theta, - 1, 1 ) ); + } - } + if ( state !== STATE.NONE ) { - distanceTo( v ) { + scope.dispatchEvent( _startEvent ); - return Math.sqrt( this.distanceToSquared( v ) ); + } - } + } - distanceToSquared( v ) { + function onMouseMove( event ) { - const dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; + switch ( state ) { - } + case STATE.ROTATE: - manhattanDistanceTo( v ) { + if ( scope.enableRotate === false ) return; - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); + handleMouseMoveRotate( event ); - } + break; - setLength( length ) { + case STATE.DOLLY: - return this.normalize().multiplyScalar( length ); + if ( scope.enableZoom === false ) return; - } + handleMouseMoveDolly( event ); - lerp( v, alpha ) { + break; - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; + case STATE.PAN: - return this; + if ( scope.enablePan === false ) return; - } + handleMouseMovePan( event ); - lerpVectors( v1, v2, alpha ) { + break; - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; + } - return this; + } - } + function onMouseWheel( event ) { - equals( v ) { + if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; - return ( ( v.x === this.x ) && ( v.y === this.y ) ); + event.preventDefault(); - } + scope.dispatchEvent( _startEvent ); - fromArray( array, offset = 0 ) { + handleMouseWheel( event ); - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; + scope.dispatchEvent( _endEvent ); - return this; + } - } + function onKeyDown( event ) { - toArray( array = [], offset = 0 ) { + if ( scope.enabled === false || scope.enablePan === false ) return; - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; + handleKeyDown( event ); - return array; + } - } + function onTouchStart( event ) { + + trackPointer( event ); - fromBufferAttribute( attribute, index ) { + switch ( pointers.length ) { - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); + case 1: - return this; + switch ( scope.touches.ONE ) { - } + case TOUCH.ROTATE: - rotateAround( center, angle ) { + if ( scope.enableRotate === false ) return; - const c = Math.cos( angle ), s = Math.sin( angle ); + handleTouchStartRotate(); - const x = this.x - center.x; - const y = this.y - center.y; + state = STATE.TOUCH_ROTATE; - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; + break; - return this; + case TOUCH.PAN: - } + if ( scope.enablePan === false ) return; - random() { + handleTouchStartPan(); - this.x = Math.random(); - this.y = Math.random(); + state = STATE.TOUCH_PAN; - return this; + break; - } + default: - *[ Symbol.iterator ]() { + state = STATE.NONE; - yield this.x; - yield this.y; + } - } + break; -} + case 2: -const StaticDrawUsage = 35044; + switch ( scope.touches.TWO ) { -const _vector$1 = /*@__PURE__*/ new Vector3(); -const _vector2 = /*@__PURE__*/ new Vector2(); + case TOUCH.DOLLY_PAN: -class BufferAttribute { + if ( scope.enableZoom === false && scope.enablePan === false ) return; - constructor( array, itemSize, normalized = false ) { + handleTouchStartDollyPan(); - if ( Array.isArray( array ) ) { + state = STATE.TOUCH_DOLLY_PAN; - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + break; - } + case TOUCH.DOLLY_ROTATE: - this.isBufferAttribute = true; + if ( scope.enableZoom === false && scope.enableRotate === false ) return; - this.name = ''; + handleTouchStartDollyRotate(); - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized; + state = STATE.TOUCH_DOLLY_ROTATE; - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; + break; - this.version = 0; + default: - } + state = STATE.NONE; - onUploadCallback() {} + } - set needsUpdate( value ) { + break; - if ( value === true ) this.version ++; + default: - } + state = STATE.NONE; - setUsage( value ) { + } - this.usage = value; + if ( state !== STATE.NONE ) { - return this; + scope.dispatchEvent( _startEvent ); - } + } - copy( source ) { + } - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; + function onTouchMove( event ) { - this.usage = source.usage; + trackPointer( event ); - return this; + switch ( state ) { - } + case STATE.TOUCH_ROTATE: - copyAt( index1, attribute, index2 ) { + if ( scope.enableRotate === false ) return; - index1 *= this.itemSize; - index2 *= attribute.itemSize; + handleTouchMoveRotate( event ); - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { + scope.update(); - this.array[ index1 + i ] = attribute.array[ index2 + i ]; + break; - } + case STATE.TOUCH_PAN: - return this; + if ( scope.enablePan === false ) return; - } + handleTouchMovePan( event ); - copyArray( array ) { + scope.update(); - this.array.set( array ); + break; - return this; + case STATE.TOUCH_DOLLY_PAN: - } + if ( scope.enableZoom === false && scope.enablePan === false ) return; - applyMatrix3( m ) { + handleTouchMoveDollyPan( event ); - if ( this.itemSize === 2 ) { + scope.update(); - for ( let i = 0, l = this.count; i < l; i ++ ) { + break; - _vector2.fromBufferAttribute( this, i ); - _vector2.applyMatrix3( m ); + case STATE.TOUCH_DOLLY_ROTATE: - this.setXY( i, _vector2.x, _vector2.y ); + if ( scope.enableZoom === false && scope.enableRotate === false ) return; - } + handleTouchMoveDollyRotate( event ); - } else if ( this.itemSize === 3 ) { + scope.update(); - for ( let i = 0, l = this.count; i < l; i ++ ) { + break; - _vector$1.fromBufferAttribute( this, i ); - _vector$1.applyMatrix3( m ); + default: - this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); + state = STATE.NONE; } } - return this; - - } + function onContextMenu( event ) { - applyMatrix4( m ) { + if ( scope.enabled === false ) return; - for ( let i = 0, l = this.count; i < l; i ++ ) { + event.preventDefault(); - _vector$1.fromBufferAttribute( this, i ); + } - _vector$1.applyMatrix4( m ); + function addPointer( event ) { - this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); + pointers.push( event ); } - return this; + function removePointer( event ) { - } + delete pointerPositions[ event.pointerId ]; - applyNormalMatrix( m ) { + for ( let i = 0; i < pointers.length; i ++ ) { - for ( let i = 0, l = this.count; i < l; i ++ ) { + if ( pointers[ i ].pointerId == event.pointerId ) { - _vector$1.fromBufferAttribute( this, i ); + pointers.splice( i, 1 ); + return; - _vector$1.applyNormalMatrix( m ); + } - this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); + } } - return this; - - } + function trackPointer( event ) { - transformDirection( m ) { + let position = pointerPositions[ event.pointerId ]; - for ( let i = 0, l = this.count; i < l; i ++ ) { + if ( position === undefined ) { - _vector$1.fromBufferAttribute( this, i ); + position = new Vector2$1(); + pointerPositions[ event.pointerId ] = position; - _vector$1.transformDirection( m ); + } - this.setXYZ( i, _vector$1.x, _vector$1.y, _vector$1.z ); + position.set( event.pageX, event.pageY ); } - return this; - - } + function getSecondPointerPosition( event ) { - set( value, offset = 0 ) { + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); + return pointerPositions[ pointer.pointerId ]; - return this; + } - } + // - getX( index ) { + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); - let x = this.array[ index * this.itemSize ]; + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerUp ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - if ( this.normalized ) x = denormalize( x, this.array ); + // force an update at start - return x; + this.update(); } - setX( index, x ) { +} - if ( this.normalized ) x = normalize( x, this.array ); +/** + * Full-screen textured quad shader + */ - this.array[ index * this.itemSize ] = x; +const CopyShader = { - return this; + uniforms: { - } + 'tDiffuse': { value: null }, + 'opacity': { value: 1.0 } - getY( index ) { + }, - let y = this.array[ index * this.itemSize + 1 ]; + vertexShader: /* glsl */` - if ( this.normalized ) y = denormalize( y, this.array ); + varying vec2 vUv; - return y; + void main() { - } + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - setY( index, y ) { + }`, - if ( this.normalized ) y = normalize( y, this.array ); + fragmentShader: /* glsl */` - this.array[ index * this.itemSize + 1 ] = y; + uniform float opacity; - return this; + uniform sampler2D tDiffuse; - } + varying vec2 vUv; - getZ( index ) { + void main() { - let z = this.array[ index * this.itemSize + 2 ]; + gl_FragColor = texture2D( tDiffuse, vUv ); + gl_FragColor.a *= opacity; - if ( this.normalized ) z = denormalize( z, this.array ); - return z; + }` - } +}; - setZ( index, z ) { +class Pass { - if ( this.normalized ) z = normalize( z, this.array ); + constructor() { - this.array[ index * this.itemSize + 2 ] = z; + this.isPass = true; - return this; + // if set to true, the pass is processed by the composer + this.enabled = true; - } + // if set to true, the pass indicates to swap read and write buffer after rendering + this.needsSwap = true; - getW( index ) { + // if set to true, the pass clears its buffer before rendering + this.clear = false; - let w = this.array[ index * this.itemSize + 3 ]; + // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. + this.renderToScreen = false; - if ( this.normalized ) w = denormalize( w, this.array ); + } - return w; + setSize( /* width, height */ ) {} + + render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { + + console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); } - setW( index, w ) { + dispose() {} - if ( this.normalized ) w = normalize( w, this.array ); +} - this.array[ index * this.itemSize + 3 ] = w; +// Helper for passes that need to fill the viewport with a single quad. - return this; +const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - } +// https://github.com/mrdoob/three.js/pull/21358 - setXY( index, x, y ) { +const _geometry = new BufferGeometry(); +_geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); +_geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); - index *= this.itemSize; +class FullScreenQuad { - if ( this.normalized ) { + constructor( material ) { - x = normalize( x, this.array ); - y = normalize( y, this.array ); + this._mesh = new Mesh( _geometry, material ); - } + } - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; + dispose() { - return this; + this._mesh.geometry.dispose(); } - setXYZ( index, x, y, z ) { + render( renderer ) { - index *= this.itemSize; + renderer.render( this._mesh, _camera ); - if ( this.normalized ) { + } - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); + get material() { - } + return this._mesh.material; - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; + } - return this; + set material( value ) { + + this._mesh.material = value; } - setXYZW( index, x, y, z, w ) { +} - index *= this.itemSize; +class ShaderPass extends Pass { - if ( this.normalized ) { + constructor( shader, textureID ) { - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); + super(); - } + this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; + if ( shader instanceof ShaderMaterial ) { - return this; + this.uniforms = shader.uniforms; - } + this.material = shader; - onUpload( callback ) { + } else if ( shader ) { - this.onUploadCallback = callback; + this.uniforms = UniformsUtils.clone( shader.uniforms ); - return this; + this.material = new ShaderMaterial( { - } + defines: Object.assign( {}, shader.defines ), + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader - clone() { + } ); - return new this.constructor( this.array, this.itemSize ).copy( this ); + } + + this.fsQuad = new FullScreenQuad( this.material ); } - toJSON() { + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; + if ( this.uniforms[ this.textureID ] ) { - if ( this.name !== '' ) data.name = this.name; - if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange; + this.uniforms[ this.textureID ].value = readBuffer.texture; - return data; + } - } + this.fsQuad.material = this.material; - copyColorsArray() { // @deprecated, r144 + if ( this.renderToScreen ) { - console.error( 'THREE.BufferAttribute: copyColorsArray() was removed in r144.' ); + renderer.setRenderTarget( null ); + this.fsQuad.render( renderer ); - } + } else { - copyVector2sArray() { // @deprecated, r144 + renderer.setRenderTarget( writeBuffer ); + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + this.fsQuad.render( renderer ); - console.error( 'THREE.BufferAttribute: copyVector2sArray() was removed in r144.' ); + } } - copyVector3sArray() { // @deprecated, r144 + dispose() { - console.error( 'THREE.BufferAttribute: copyVector3sArray() was removed in r144.' ); + this.material.dispose(); + + this.fsQuad.dispose(); } - copyVector4sArray() { // @deprecated, r144 +} - console.error( 'THREE.BufferAttribute: copyVector4sArray() was removed in r144.' ); +class MaskPass extends Pass { - } + constructor( scene, camera ) { -} + super(); -class FragmentMesh extends InstancedMesh { - constructor(geometry, material, count, fragment) { - super(geometry, material, count); - this.elementCount = 0; - this.exportOptions = { - trs: false, - onlyVisible: false, - truncateDrawRange: true, - binary: true, - maxTextureSize: 0, - }; - this.exporter = new GLTFExporter(); - this.material = FragmentMesh.newMaterialArray(material); - this.geometry = this.newFragmentGeometry(geometry); - this.fragment = fragment; - } - exportData() { - const position = this.geometry.attributes.position.array; - const normal = this.geometry.attributes.normal.array; - const blockID = Array.from(this.geometry.attributes.blockID.array); - const index = Array.from(this.geometry.index.array); - const groups = []; - for (const group of this.geometry.groups) { - const index = group.materialIndex || 0; - const { start, count } = group; - groups.push(start, count, index); - } - const materials = []; - if (Array.isArray(this.material)) { - for (const material of this.material) { - const opacity = material.opacity; - const transparent = material.transparent ? 1 : 0; - const color = new Color(material.color).toArray(); - materials.push(opacity, transparent, ...color); - } - } - const matrices = Array.from(this.instanceMatrix.array); - let colors; - if (this.instanceColor !== null) { - colors = Array.from(this.instanceColor.array); - } - else { - colors = []; - } - return { - position, - normal, - index, - blockID, - groups, - materials, - matrices, - colors, - }; - } - export() { - const mesh = this; - return new Promise((resolve) => { - this.exporter.parse(mesh, (geometry) => resolve(geometry), (error) => console.log(error), this.exportOptions); - }); - } - newFragmentGeometry(geometry) { - if (!geometry.index) { - throw new Error("The geometry must be indexed!"); - } - if (!geometry.attributes.blockID) { - const vertexSize = geometry.attributes.position.count; - const array = new Uint16Array(vertexSize); - array.fill(this.elementCount++); - geometry.attributes.blockID = new BufferAttribute(array, 1); - } - const size = geometry.index.count; - FragmentMesh.initializeGroups(geometry, size); - return geometry; - } - static initializeGroups(geometry, size) { - if (!geometry.groups.length) { - geometry.groups.push({ - start: 0, - count: size, - materialIndex: 0, - }); - } - } - static newMaterialArray(material) { - if (!Array.isArray(material)) - material = [material]; - return material; - } -} + this.scene = scene; + this.camera = camera; + + this.clear = true; + this.needsSwap = false; + + this.inverse = false; + + } + + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + + const context = renderer.getContext(); + const state = renderer.state; + + // don't update color or depth + + state.buffers.color.setMask( false ); + state.buffers.depth.setMask( false ); + + // lock buffers + + state.buffers.color.setLocked( true ); + state.buffers.depth.setLocked( true ); + + // set up stencil + + let writeValue, clearValue; + + if ( this.inverse ) { + + writeValue = 0; + clearValue = 1; + + } else { + + writeValue = 1; + clearValue = 0; + + } + + state.buffers.stencil.setTest( true ); + state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE ); + state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff ); + state.buffers.stencil.setClear( clearValue ); + state.buffers.stencil.setLocked( true ); + + // draw into the stencil buffer + + renderer.setRenderTarget( readBuffer ); + if ( this.clear ) renderer.clear(); + renderer.render( this.scene, this.camera ); + + renderer.setRenderTarget( writeBuffer ); + if ( this.clear ) renderer.clear(); + renderer.render( this.scene, this.camera ); + + // unlock color and depth buffer for subsequent rendering + + state.buffers.color.setLocked( false ); + state.buffers.depth.setLocked( false ); + + // only render where stencil is set to 1 + + state.buffers.stencil.setLocked( false ); + state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 + state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP ); + state.buffers.stencil.setLocked( true ); + + } -/** - * Contains the logic to get, create and delete geometric subsets of an IFC model. For example, - * this can extract all the items in a specific IfcBuildingStorey and create a new Mesh. - */ -class Blocks { - get count() { - return this.ids.size; - } - constructor(fragment) { - this.fragment = fragment; - this._visibilityInitialized = false; - this._originalIndex = new Map(); - this._idIndexIndexMap = {}; - const rawIds = fragment.mesh.geometry.attributes.blockID.array; - this.ids = new Set(rawIds); - this.visibleIds = new Set(this.ids); - } - setVisibility(visible, itemIDs = new Set(this.fragment.items), isolate = false) { - const geometry = this.fragment.mesh.geometry; - const index = geometry.index; - if (!this._visibilityInitialized) { - this.initializeVisibility(index, geometry); - } - if (isolate) { - index.array.fill(0); - } - for (const id of itemIDs) { - const indices = this._idIndexIndexMap[id]; - if (!indices) - continue; - for (const i of indices) { - const originalIndex = this._originalIndex.get(i); - if (originalIndex === undefined) - continue; - const blockID = geometry.attributes.blockID.getX(originalIndex); - const itemID = this.fragment.items[blockID]; - if (itemIDs.has(itemID)) { - if (visible) { - this.visibleIds.add(blockID); - } - else { - this.visibleIds.delete(blockID); - } - const newIndex = visible ? originalIndex : 0; - index.setX(i, newIndex); - } - } - } - index.needsUpdate = true; - } - initializeVisibility(index, geometry) { - for (let i = 0; i < index.count; i++) { - const foundIndex = index.getX(i); - this._originalIndex.set(i, foundIndex); - const blockID = geometry.attributes.blockID.getX(foundIndex); - const itemID = this.fragment.getItemID(0, blockID); - if (!this._idIndexIndexMap[itemID]) { - this._idIndexIndexMap[itemID] = []; - } - this._idIndexIndexMap[itemID].push(i); - } - this._visibilityInitialized = true; - } - // Use this only for destroying the current Fragment instance - dispose() { - this._idIndexIndexMap = {}; - this.ids.clear(); - this.visibleIds.clear(); - this._originalIndex.clear(); - this.ids = null; - this.visibleIds = null; - this._originalIndex = null; - } } -// Source: https://github.com/gkjohnson/three-mesh-bvh -class BVH { - static apply(geometry) { - if (!BVH.initialized) { - BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; - BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; - Mesh.prototype.raycast = acceleratedRaycast; - BVH.initialized = true; - } - if (!geometry.boundsTree) { - geometry.computeBoundsTree(); - } - } - static dispose(geometry) { - geometry.disposeBoundsTree(); - } +class ClearMaskPass extends Pass { + + constructor() { + + super(); + + this.needsSwap = false; + + } + + render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { + + renderer.state.buffers.stencil.setLocked( false ); + renderer.state.buffers.stencil.setTest( false ); + + } + } -BVH.initialized = false; -/* - * Fragments can contain one or multiple Instances of one or multiple Blocks - * Each Instance is identified by an instanceID (property of THREE.InstancedMesh) - * Each Block identified by a blockID (custom bufferAttribute per vertex) - * Both instanceId and blockId are unsigned integers starting at 0 and going up sequentially - * A specific Block of a specific Instance is an Item, identified by an itemID - * - * For example: - * Imagine a fragment mesh with 8 instances and 2 elements (16 items, identified from A to P) - * It will have instanceIds from 0 to 8, and blockIds from 0 to 2 - * If we raycast it, we will get an instanceId and the index of the found triangle - * We can use the index to get the blockId for that triangle - * Combining instanceId and blockId using the elementMap will give us the itemId - * The items will look like this: - * - * [ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P ] - * - * Where the criteria to sort the items is the following (Y-axis is instance, X-axis is block): - * - * A C E G I K M O - * B D F H J L N P - * */ -let Fragment$1 = class Fragment { - get ids() { - const ids = new Set(); - for (const id of this.items) { - ids.add(id); - } - for (const id in this.hiddenInstances) { - ids.add(id); - } - return ids; - } - constructor(geometry, material, count) { - this.fragments = {}; - this.items = []; - this.hiddenInstances = {}; - // When multiple instances represent the same object - // this allows to create a composite ID for each instance - // E.g. all the steps in a stair are a single thing - // so if the ID of the stair is asdf, then each step could be - // asdf.1, asdf.2, asdf.3, etc - // the value is the number of instances - this.composites = {}; - this.mesh = new FragmentMesh(geometry, material, count, this); - this.id = this.mesh.uuid; - this.capacity = count; - this.blocks = new Blocks(this); - BVH.apply(geometry); - } - dispose(disposeResources = true) { - this.items = null; - this.group = undefined; - if (this.mesh) { - if (disposeResources) { - this.mesh.material.forEach((mat) => mat.dispose()); - this.mesh.material = []; - BVH.dispose(this.mesh.geometry); - this.mesh.geometry.dispose(); - this.mesh.geometry = null; - } - this.mesh.removeFromParent(); - this.mesh.dispose(); - this.mesh.fragment = null; - this.mesh = null; - } - this.disposeNestedFragments(); - } - getItemID(instanceID, blockID) { - const index = this.getItemIndex(instanceID, blockID); - return this.items[index]; - } - getInstanceAndBlockID(itemID) { - const index = this.items.indexOf(itemID); - const instanceID = this.getInstanceIDFromIndex(index); - const blockID = index % this.blocks.count; - return { instanceID, blockID }; - } - getVertexBlockID(geometry, index) { - const blocks = geometry.attributes.blockID; - return blocks.array[index]; - } - getItemData(itemID) { - const index = this.items.indexOf(itemID); - const instanceID = Math.ceil(index / this.blocks.count); - const blockID = index % this.blocks.count; - return { instanceID, blockID }; - } - getInstance(instanceID, matrix) { - return this.mesh.getMatrixAt(instanceID, matrix); - } - setInstance(instanceID, items) { - this.checkIfInstanceExist(instanceID); - this.mesh.setMatrixAt(instanceID, items.transform); - this.mesh.instanceMatrix.needsUpdate = true; - if (items.color && this.mesh.instanceColor) { - this.mesh.setColorAt(instanceID, items.color); - this.mesh.instanceColor.needsUpdate = true; - } - if (items.ids) { - this.saveItemsInMap(items.ids, instanceID); - } - } - addInstances(items) { - this.resizeCapacityIfNeeded(items.length); - const start = this.mesh.count; - this.mesh.count += items.length; - for (let i = 0; i < items.length; i++) { - this.setInstance(start + i, items[i]); - } - } - removeInstances(itemsIDs) { - if (this.mesh.count <= 1) { - this.clear(); - return; - } - this.deleteAndRearrangeInstances(itemsIDs); - this.mesh.count -= itemsIDs.length; - this.mesh.instanceMatrix.needsUpdate = true; - } - clear() { - this.mesh.clear(); - this.mesh.count = 0; - this.items = []; - } - addFragment(id, material = this.mesh.material) { - const newGeometry = this.initializeGeometry(); - if (material === this.mesh.material) { - this.copyGroups(newGeometry); - } - const newFragment = new Fragment(newGeometry, material, this.capacity); - newFragment.mesh.applyMatrix4(this.mesh.matrix); - newFragment.mesh.updateMatrix(); - this.fragments[id] = newFragment; - return this.fragments[id]; - } - removeFragment(id) { - const fragment = this.fragments[id]; - if (fragment) { - fragment.dispose(false); - delete this.fragments[id]; - } - } - resetVisibility() { - if (this.blocks.count > 1) { - this.blocks.setVisibility(true); - } - else { - const hiddenInstances = Object.keys(this.hiddenInstances); - this.makeInstancesVisible(hiddenInstances); - this.hiddenInstances = {}; - } - } - setVisibility(visible, itemIDs = this.ids) { - if (this.blocks.count > 1) { - this.blocks.setVisibility(visible, itemIDs); - } - else { - this.toggleInstanceVisibility(visible, itemIDs); - } - } - resize(size) { - var _a; - const newMesh = this.createFragmentMeshWithNewSize(size); - this.capacity = size; - const oldMesh = this.mesh; - (_a = oldMesh.parent) === null || _a === void 0 ? void 0 : _a.add(newMesh); - oldMesh.removeFromParent(); - this.mesh = newMesh; - oldMesh.dispose(); - } - exportData() { - const geometry = this.mesh.exportData(); - const ids = this.items.join("|"); - const id = this.id; - return { ...geometry, ids, id }; - } - copyGroups(newGeometry) { - newGeometry.groups = []; - for (const group of this.mesh.geometry.groups) { - newGeometry.groups.push({ ...group }); - } - } - initializeGeometry() { - const newGeometry = new THREE$1.BufferGeometry(); - newGeometry.setAttribute("position", this.mesh.geometry.attributes.position); - newGeometry.setAttribute("normal", this.mesh.geometry.attributes.normal); - newGeometry.setAttribute("blockID", this.mesh.geometry.attributes.blockID); - newGeometry.setIndex(Array.from(this.mesh.geometry.index.array)); - return newGeometry; - } - saveItemsInMap(ids, instanceId) { - this.checkBlockNumberValid(ids); - let counter = 0; - for (const id of ids) { - const index = this.getItemIndex(instanceId, counter); - this.items[index] = id; - counter++; - } - } - resizeCapacityIfNeeded(newSize) { - const necessaryCapacity = newSize + this.mesh.count; - if (necessaryCapacity > this.capacity) { - this.resize(necessaryCapacity); - } - } - createFragmentMeshWithNewSize(capacity) { - const newMesh = new FragmentMesh(this.mesh.geometry, this.mesh.material, capacity, this); - newMesh.count = this.mesh.count; - return newMesh; - } - disposeNestedFragments() { - const fragments = Object.values(this.fragments); - for (let i = 0; i < fragments.length; i++) { - fragments[i].dispose(); - } - this.fragments = {}; - } - checkBlockNumberValid(ids) { - if (ids.length > this.blocks.count) { - throw new Error(`You passed more items (${ids.length}) than blocks in this instance (${this.blocks.count})`); - } - } - checkIfInstanceExist(index) { - if (index > this.mesh.count) { - throw new Error(`The given index (${index}) exceeds the instances in this fragment (${this.mesh.count})`); - } - } - // Assigns the index of the removed instance to the last instance - // F.e. let there be 6 instances: (A) (B) (C) (D) (E) (F) - // If instance (C) is removed: -> (A) (B) (F) (D) (E) - deleteAndRearrangeInstances(ids) { - const deletedItems = []; - for (const id of ids) { - const deleted = this.deleteAndRearrange(id); - if (deleted) { - deletedItems.push(deleted); - } - } - for (const id of ids) { - delete this.hiddenInstances[id]; - } - return deletedItems; - } - deleteAndRearrange(id) { - const index = this.items.indexOf(id); - if (index === -1) - return null; - this.mesh.count--; - const isLastElement = index === this.mesh.count; - const instanceId = this.getInstanceIDFromIndex(index); - const tempMatrix = new THREE$1.Matrix4(); - const tempColor = new THREE$1.Color(); - const transform = new THREE$1.Matrix4(); - this.mesh.getMatrixAt(instanceId, transform); - const result = { ids: [id], transform }; - if (this.mesh.instanceColor) { - const color = new THREE$1.Color(); - this.mesh.getColorAt(instanceId, color); - result.color = color; - } - if (isLastElement) { - this.items.pop(); - return result; - } - const lastElement = this.mesh.count; - this.items[index] = this.items[lastElement]; - this.items.pop(); - this.mesh.getMatrixAt(lastElement, tempMatrix); - this.mesh.setMatrixAt(instanceId, tempMatrix); - this.mesh.instanceMatrix.needsUpdate = true; - if (this.mesh.instanceColor) { - this.mesh.getColorAt(lastElement, tempColor); - this.mesh.setColorAt(instanceId, tempColor); - this.mesh.instanceColor.needsUpdate = true; - } - return result; - } - getItemIndex(instanceId, blockId) { - return instanceId * this.blocks.count + blockId; - } - getInstanceIDFromIndex(itemIndex) { - return Math.trunc(itemIndex / this.blocks.count); - } - toggleInstanceVisibility(visible, itemIDs) { - if (visible) { - this.makeInstancesVisible(itemIDs); - } - else { - this.makeInstancesInvisible(itemIDs); - } - } - makeInstancesInvisible(itemIDs) { - itemIDs = this.filterHiddenItems(itemIDs, false); - const deletedItems = this.deleteAndRearrangeInstances(itemIDs); - for (const item of deletedItems) { - if (item.ids) { - this.hiddenInstances[item.ids[0]] = item; - } - } - } - makeInstancesVisible(itemIDs) { - const items = []; - itemIDs = this.filterHiddenItems(itemIDs, true); - for (const id of itemIDs) { - const found = this.hiddenInstances[id]; - if (found !== undefined) { - items.push(found); - delete this.hiddenInstances[id]; - } - } - this.addInstances(items); - } - filterHiddenItems(itemIDs, hidden) { - const hiddenItems = Object.keys(this.hiddenInstances); - const result = []; - for (const id of itemIDs) { - const isHidden = hidden && hiddenItems.includes(id); - const isNotHidden = !hidden && !hiddenItems.includes(id); - if (isHidden || isNotHidden) { - result.push(id); - } - } - return result; - } -}; +class EffectComposer { + + constructor( renderer, renderTarget ) { + + this.renderer = renderer; + + this._pixelRatio = renderer.getPixelRatio(); + + if ( renderTarget === undefined ) { + + const size = renderer.getSize( new Vector2$1() ); + this._width = size.width; + this._height = size.height; + + renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio ); + renderTarget.texture.name = 'EffectComposer.rt1'; -const SIZEOF_SHORT = 2; -const SIZEOF_INT = 4; -const FILE_IDENTIFIER_LENGTH = 4; -const SIZE_PREFIX_LENGTH = 4; + } else { -const int32 = new Int32Array(2); -const float32 = new Float32Array(int32.buffer); -const float64 = new Float64Array(int32.buffer); -const isLittleEndian = new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1; + this._width = renderTarget.width; + this._height = renderTarget.height; -var Encoding; -(function (Encoding) { - Encoding[Encoding["UTF8_BYTES"] = 1] = "UTF8_BYTES"; - Encoding[Encoding["UTF16_STRING"] = 2] = "UTF16_STRING"; -})(Encoding || (Encoding = {})); + } + + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); + this.renderTarget2.texture.name = 'EffectComposer.rt2'; + + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; + + this.renderToScreen = true; + + this.passes = []; + + this.copyPass = new ShaderPass( CopyShader ); + + this.clock = new Clock(); + + } + + swapBuffers() { + + const tmp = this.readBuffer; + this.readBuffer = this.writeBuffer; + this.writeBuffer = tmp; + + } + + addPass( pass ) { + + this.passes.push( pass ); + pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + + } + + insertPass( pass, index ) { + + this.passes.splice( index, 0, pass ); + pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + + } + + removePass( pass ) { + + const index = this.passes.indexOf( pass ); + + if ( index !== - 1 ) { + + this.passes.splice( index, 1 ); + + } + + } + + isLastEnabledPass( passIndex ) { + + for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { + + if ( this.passes[ i ].enabled ) { + + return false; + + } + + } + + return true; + + } + + render( deltaTime ) { + + // deltaTime value is in seconds + + if ( deltaTime === undefined ) { + + deltaTime = this.clock.getDelta(); + + } + + const currentRenderTarget = this.renderer.getRenderTarget(); + + let maskActive = false; + + for ( let i = 0, il = this.passes.length; i < il; i ++ ) { + + const pass = this.passes[ i ]; + + if ( pass.enabled === false ) continue; + + pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); + pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); + + if ( pass.needsSwap ) { + + if ( maskActive ) { + + const context = this.renderer.getContext(); + const stencil = this.renderer.state.buffers.stencil; + + //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); + stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); + + this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); + + //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); + stencil.setFunc( context.EQUAL, 1, 0xffffffff ); + + } + + this.swapBuffers(); + + } + + if ( MaskPass !== undefined ) { + + if ( pass instanceof MaskPass ) { + + maskActive = true; + + } else if ( pass instanceof ClearMaskPass ) { + + maskActive = false; + + } + + } + + } + + this.renderer.setRenderTarget( currentRenderTarget ); + + } + + reset( renderTarget ) { + + if ( renderTarget === undefined ) { + + const size = this.renderer.getSize( new Vector2$1() ); + this._pixelRatio = this.renderer.getPixelRatio(); + this._width = size.width; + this._height = size.height; + + renderTarget = this.renderTarget1.clone(); + renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); + + } + + this.renderTarget1.dispose(); + this.renderTarget2.dispose(); + this.renderTarget1 = renderTarget; + this.renderTarget2 = renderTarget.clone(); + + this.writeBuffer = this.renderTarget1; + this.readBuffer = this.renderTarget2; + + } + + setSize( width, height ) { + + this._width = width; + this._height = height; + + const effectiveWidth = this._width * this._pixelRatio; + const effectiveHeight = this._height * this._pixelRatio; + + this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); + this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); + + for ( let i = 0; i < this.passes.length; i ++ ) { + + this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); + + } + + } + + setPixelRatio( pixelRatio ) { + + this._pixelRatio = pixelRatio; + + this.setSize( this._width, this._height ); + + } + + dispose() { + + this.renderTarget1.dispose(); + this.renderTarget2.dispose(); + + this.copyPass.dispose(); + + } + +} + +class RenderPass extends Pass { + + constructor( scene, camera, overrideMaterial, clearColor, clearAlpha ) { + + super(); + + this.scene = scene; + this.camera = camera; + + this.overrideMaterial = overrideMaterial; + + this.clearColor = clearColor; + this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + + this.clear = true; + this.clearDepth = false; + this.needsSwap = false; + this._oldClearColor = new Color(); + + } + + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + + const oldAutoClear = renderer.autoClear; + renderer.autoClear = false; + + let oldClearAlpha, oldOverrideMaterial; + + if ( this.overrideMaterial !== undefined ) { + + oldOverrideMaterial = this.scene.overrideMaterial; + + this.scene.overrideMaterial = this.overrideMaterial; + + } + + if ( this.clearColor ) { + + renderer.getClearColor( this._oldClearColor ); + oldClearAlpha = renderer.getClearAlpha(); + + renderer.setClearColor( this.clearColor, this.clearAlpha ); + + } + + if ( this.clearDepth ) { + + renderer.clearDepth(); + + } + + renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); + + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + renderer.render( this.scene, this.camera ); + + if ( this.clearColor ) { + + renderer.setClearColor( this._oldClearColor, oldClearAlpha ); + + } + + if ( this.overrideMaterial !== undefined ) { + + this.scene.overrideMaterial = oldOverrideMaterial; + + } + + renderer.autoClear = oldAutoClear; + + } -class ByteBuffer { - /** - * Create a new ByteBuffer with a given array of bytes (`Uint8Array`) - */ - constructor(bytes_) { - this.bytes_ = bytes_; - this.position_ = 0; - this.text_decoder_ = new TextDecoder(); - } - /** - * Create and allocate a new ByteBuffer with a given size. - */ - static allocate(byte_size) { - return new ByteBuffer(new Uint8Array(byte_size)); - } - clear() { - this.position_ = 0; - } - /** - * Get the underlying `Uint8Array`. - */ - bytes() { - return this.bytes_; - } - /** - * Get the buffer's position. - */ - position() { - return this.position_; - } - /** - * Set the buffer's position. - */ - setPosition(position) { - this.position_ = position; - } - /** - * Get the buffer's capacity. - */ - capacity() { - return this.bytes_.length; - } - readInt8(offset) { - return this.readUint8(offset) << 24 >> 24; - } - readUint8(offset) { - return this.bytes_[offset]; - } - readInt16(offset) { - return this.readUint16(offset) << 16 >> 16; - } - readUint16(offset) { - return this.bytes_[offset] | this.bytes_[offset + 1] << 8; - } - readInt32(offset) { - return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24; - } - readUint32(offset) { - return this.readInt32(offset) >>> 0; - } - readInt64(offset) { - return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); - } - readUint64(offset) { - return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32))); - } - readFloat32(offset) { - int32[0] = this.readInt32(offset); - return float32[0]; - } - readFloat64(offset) { - int32[isLittleEndian ? 0 : 1] = this.readInt32(offset); - int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4); - return float64[0]; - } - writeInt8(offset, value) { - this.bytes_[offset] = value; - } - writeUint8(offset, value) { - this.bytes_[offset] = value; - } - writeInt16(offset, value) { - this.bytes_[offset] = value; - this.bytes_[offset + 1] = value >> 8; - } - writeUint16(offset, value) { - this.bytes_[offset] = value; - this.bytes_[offset + 1] = value >> 8; - } - writeInt32(offset, value) { - this.bytes_[offset] = value; - this.bytes_[offset + 1] = value >> 8; - this.bytes_[offset + 2] = value >> 16; - this.bytes_[offset + 3] = value >> 24; - } - writeUint32(offset, value) { - this.bytes_[offset] = value; - this.bytes_[offset + 1] = value >> 8; - this.bytes_[offset + 2] = value >> 16; - this.bytes_[offset + 3] = value >> 24; - } - writeInt64(offset, value) { - this.writeInt32(offset, Number(BigInt.asIntN(32, value))); - this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32)))); - } - writeUint64(offset, value) { - this.writeUint32(offset, Number(BigInt.asUintN(32, value))); - this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32)))); - } - writeFloat32(offset, value) { - float32[0] = value; - this.writeInt32(offset, int32[0]); - } - writeFloat64(offset, value) { - float64[0] = value; - this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]); - this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]); - } - /** - * Return the file identifier. Behavior is undefined for FlatBuffers whose - * schema does not include a file_identifier (likely points at padding or the - * start of a the root vtable). - */ - getBufferIdentifier() { - if (this.bytes_.length < this.position_ + SIZEOF_INT + - FILE_IDENTIFIER_LENGTH) { - throw new Error('FlatBuffers: ByteBuffer is too short to contain an identifier.'); - } - let result = ""; - for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { - result += String.fromCharCode(this.readInt8(this.position_ + SIZEOF_INT + i)); - } - return result; - } - /** - * Look up a field in the vtable, return an offset into the object, or 0 if the - * field is not present. - */ - __offset(bb_pos, vtable_offset) { - const vtable = bb_pos - this.readInt32(bb_pos); - return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0; - } - /** - * Initialize any Table-derived type to point to the union at the given offset. - */ - __union(t, offset) { - t.bb_pos = offset + this.readInt32(offset); - t.bb = this; - return t; - } - /** - * Create a JavaScript string from UTF-8 data stored inside the FlatBuffer. - * This allocates a new string and converts to wide chars upon each access. - * - * To avoid the conversion to string, pass Encoding.UTF8_BYTES as the - * "optionalEncoding" argument. This is useful for avoiding conversion when - * the data will just be packaged back up in another FlatBuffer later on. - * - * @param offset - * @param opt_encoding Defaults to UTF16_STRING - */ - __string(offset, opt_encoding) { - offset += this.readInt32(offset); - const length = this.readInt32(offset); - offset += SIZEOF_INT; - const utf8bytes = this.bytes_.subarray(offset, offset + length); - if (opt_encoding === Encoding.UTF8_BYTES) - return utf8bytes; - else - return this.text_decoder_.decode(utf8bytes); - } - /** - * Handle unions that can contain string as its member, if a Table-derived type then initialize it, - * if a string then return a new one - * - * WARNING: strings are immutable in JS so we can't change the string that the user gave us, this - * makes the behaviour of __union_with_string different compared to __union - */ - __union_with_string(o, offset) { - if (typeof o === 'string') { - return this.__string(offset); - } - return this.__union(o, offset); - } - /** - * Retrieve the relative offset stored at "offset" - */ - __indirect(offset) { - return offset + this.readInt32(offset); - } - /** - * Get the start of data of a vector whose offset is stored at "offset" in this object. - */ - __vector(offset) { - return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length - } - /** - * Get the length of a vector whose offset is stored at "offset" in this object. - */ - __vector_len(offset) { - return this.readInt32(offset + this.readInt32(offset)); - } - __has_identifier(ident) { - if (ident.length != FILE_IDENTIFIER_LENGTH) { - throw new Error('FlatBuffers: file identifier must be length ' + - FILE_IDENTIFIER_LENGTH); - } - for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { - if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) { - return false; - } - } - return true; - } - /** - * A helper function for generating list for obj api - */ - createScalarList(listAccessor, listLength) { - const ret = []; - for (let i = 0; i < listLength; ++i) { - const val = listAccessor(i); - if (val !== null) { - ret.push(val); - } - } - return ret; - } - /** - * A helper function for generating list for obj api - * @param listAccessor function that accepts an index and return data at that index - * @param listLength listLength - * @param res result list - */ - createObjList(listAccessor, listLength) { - const ret = []; - for (let i = 0; i < listLength; ++i) { - const val = listAccessor(i); - if (val !== null) { - ret.push(val.unpack()); - } - } - return ret; - } } -class Builder { - /** - * Create a FlatBufferBuilder. - */ - constructor(opt_initial_size) { - /** Minimum alignment encountered so far. */ - this.minalign = 1; - /** The vtable for the current table. */ - this.vtable = null; - /** The amount of fields we're actually using. */ - this.vtable_in_use = 0; - /** Whether we are currently serializing a table. */ - this.isNested = false; - /** Starting offset of the current struct/table. */ - this.object_start = 0; - /** List of offsets of all vtables. */ - this.vtables = []; - /** For the current vector being built. */ - this.vector_num_elems = 0; - /** False omits default values from the serialized data */ - this.force_defaults = false; - this.string_maps = null; - this.text_encoder = new TextEncoder(); - let initial_size; - if (!opt_initial_size) { - initial_size = 1024; - } - else { - initial_size = opt_initial_size; - } - /** - * @type {ByteBuffer} - * @private - */ - this.bb = ByteBuffer.allocate(initial_size); - this.space = initial_size; - } - clear() { - this.bb.clear(); - this.space = this.bb.capacity(); - this.minalign = 1; - this.vtable = null; - this.vtable_in_use = 0; - this.isNested = false; - this.object_start = 0; - this.vtables = []; - this.vector_num_elems = 0; - this.force_defaults = false; - this.string_maps = null; - } - /** - * In order to save space, fields that are set to their default value - * don't get serialized into the buffer. Forcing defaults provides a - * way to manually disable this optimization. - * - * @param forceDefaults true always serializes default values - */ - forceDefaults(forceDefaults) { - this.force_defaults = forceDefaults; - } - /** - * Get the ByteBuffer representing the FlatBuffer. Only call this after you've - * called finish(). The actual data starts at the ByteBuffer's current position, - * not necessarily at 0. - */ - dataBuffer() { - return this.bb; - } - /** - * Get the bytes representing the FlatBuffer. Only call this after you've - * called finish(). - */ - asUint8Array() { - return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset()); - } - /** - * Prepare to write an element of `size` after `additional_bytes` have been - * written, e.g. if you write a string, you need to align such the int length - * field is aligned to 4 bytes, and the string data follows it directly. If all - * you need to do is alignment, `additional_bytes` will be 0. - * - * @param size This is the of the new element to write - * @param additional_bytes The padding size - */ - prep(size, additional_bytes) { - // Track the biggest thing we've ever aligned to. - if (size > this.minalign) { - this.minalign = size; - } - // Find the amount of alignment needed such that `size` is properly - // aligned after `additional_bytes` - const align_size = ((~(this.bb.capacity() - this.space + additional_bytes)) + 1) & (size - 1); - // Reallocate the buffer if needed. - while (this.space < align_size + size + additional_bytes) { - const old_buf_size = this.bb.capacity(); - this.bb = Builder.growByteBuffer(this.bb); - this.space += this.bb.capacity() - old_buf_size; - } - this.pad(align_size); - } - pad(byte_size) { - for (let i = 0; i < byte_size; i++) { - this.bb.writeInt8(--this.space, 0); - } - } - writeInt8(value) { - this.bb.writeInt8(this.space -= 1, value); - } - writeInt16(value) { - this.bb.writeInt16(this.space -= 2, value); - } - writeInt32(value) { - this.bb.writeInt32(this.space -= 4, value); - } - writeInt64(value) { - this.bb.writeInt64(this.space -= 8, value); - } - writeFloat32(value) { - this.bb.writeFloat32(this.space -= 4, value); - } - writeFloat64(value) { - this.bb.writeFloat64(this.space -= 8, value); - } - /** - * Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `int8` to add the buffer. - */ - addInt8(value) { - this.prep(1, 0); - this.writeInt8(value); - } - /** - * Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `int16` to add the buffer. - */ - addInt16(value) { - this.prep(2, 0); - this.writeInt16(value); - } - /** - * Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `int32` to add the buffer. - */ - addInt32(value) { - this.prep(4, 0); - this.writeInt32(value); - } - /** - * Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `int64` to add the buffer. - */ - addInt64(value) { - this.prep(8, 0); - this.writeInt64(value); - } - /** - * Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `float32` to add the buffer. - */ - addFloat32(value) { - this.prep(4, 0); - this.writeFloat32(value); - } - /** - * Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary). - * @param value The `float64` to add the buffer. - */ - addFloat64(value) { - this.prep(8, 0); - this.writeFloat64(value); - } - addFieldInt8(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addInt8(value); - this.slot(voffset); - } - } - addFieldInt16(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addInt16(value); - this.slot(voffset); - } - } - addFieldInt32(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addInt32(value); - this.slot(voffset); - } - } - addFieldInt64(voffset, value, defaultValue) { - if (this.force_defaults || value !== defaultValue) { - this.addInt64(value); - this.slot(voffset); - } - } - addFieldFloat32(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addFloat32(value); - this.slot(voffset); - } - } - addFieldFloat64(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addFloat64(value); - this.slot(voffset); - } - } - addFieldOffset(voffset, value, defaultValue) { - if (this.force_defaults || value != defaultValue) { - this.addOffset(value); - this.slot(voffset); - } - } - /** - * Structs are stored inline, so nothing additional is being added. `d` is always 0. - */ - addFieldStruct(voffset, value, defaultValue) { - if (value != defaultValue) { - this.nested(value); - this.slot(voffset); - } - } - /** - * Structures are always stored inline, they need to be created right - * where they're used. You'll get this assertion failure if you - * created it elsewhere. - */ - nested(obj) { - if (obj != this.offset()) { - throw new TypeError('FlatBuffers: struct must be serialized inline.'); - } - } - /** - * Should not be creating any other object, string or vector - * while an object is being constructed - */ - notNested() { - if (this.isNested) { - throw new TypeError('FlatBuffers: object serialization must not be nested.'); - } - } - /** - * Set the current vtable at `voffset` to the current location in the buffer. - */ - slot(voffset) { - if (this.vtable !== null) - this.vtable[voffset] = this.offset(); +/** + * postprocessing v6.33.3 build Mon Oct 30 2023 + * https://github.com/pmndrs/postprocessing + * Copyright 2015-2023 Raoul van Rüschen + * @license Zlib + */ + + +// src/utils/BackCompat.js +Number(REVISION.replace(/\D+/g, "")); + +const $e4ca8dcb0218f846$var$_geometry = new BufferGeometry(); +$e4ca8dcb0218f846$var$_geometry.setAttribute("position", new BufferAttribute$1(new Float32Array([ + -1, + -1, + 3, + -1, + -1, + 3 +]), 2)); +$e4ca8dcb0218f846$var$_geometry.setAttribute("uv", new BufferAttribute$1(new Float32Array([ + 0, + 0, + 2, + 0, + 0, + 2 +]), 2)); +// Recent three.js versions break setDrawRange or itemSize <3 position +$e4ca8dcb0218f846$var$_geometry.boundingSphere = new Sphere(); +$e4ca8dcb0218f846$var$_geometry.computeBoundingSphere = function() {}; +const $e4ca8dcb0218f846$var$_camera = new OrthographicCamera(); +class $e4ca8dcb0218f846$export$dcd670d73db751f5 { + constructor(material){ + this._mesh = new Mesh($e4ca8dcb0218f846$var$_geometry, material); + this._mesh.frustumCulled = false; } - /** - * @returns Offset relative to the end of the buffer. - */ - offset() { - return this.bb.capacity() - this.space; + render(renderer) { + renderer.render(this._mesh, $e4ca8dcb0218f846$var$_camera); } - /** - * Doubles the size of the backing ByteBuffer and copies the old data towards - * the end of the new buffer (since we build the buffer backwards). - * - * @param bb The current buffer with the existing data - * @returns A new byte buffer with the old data copied - * to it. The data is located at the end of the buffer. - * - * uint8Array.set() formally takes {Array|ArrayBufferView}, so to pass - * it a uint8Array we need to suppress the type check: - * @suppress {checkTypes} - */ - static growByteBuffer(bb) { - const old_buf_size = bb.capacity(); - // Ensure we don't grow beyond what fits in an int. - if (old_buf_size & 0xC0000000) { - throw new Error('FlatBuffers: cannot grow buffer beyond 2 gigabytes.'); - } - const new_buf_size = old_buf_size << 1; - const nbb = ByteBuffer.allocate(new_buf_size); - nbb.setPosition(new_buf_size - old_buf_size); - nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size); - return nbb; + get material() { + return this._mesh.material; } - /** - * Adds on offset, relative to where it will be written. - * - * @param offset The offset to add. - */ - addOffset(offset) { - this.prep(SIZEOF_INT, 0); // Ensure alignment is already done. - this.writeInt32(this.offset() - offset + SIZEOF_INT); + set material(value) { + this._mesh.material = value; } - /** - * Start encoding a new object in the buffer. Users will not usually need to - * call this directly. The FlatBuffers compiler will generate helper methods - * that call this method internally. - */ - startObject(numfields) { - this.notNested(); - if (this.vtable == null) { - this.vtable = []; - } - this.vtable_in_use = numfields; - for (let i = 0; i < numfields; i++) { - this.vtable[i] = 0; // This will push additional elements as needed - } - this.isNested = true; - this.object_start = this.offset(); + dispose() { + this._mesh.material.dispose(); + this._mesh.geometry.dispose(); } - /** - * Finish off writing the object that is under construction. - * - * @returns The offset to the object inside `dataBuffer` - */ - endObject() { - if (this.vtable == null || !this.isNested) { - throw new Error('FlatBuffers: endObject called without startObject'); - } - this.addInt32(0); - const vtableloc = this.offset(); - // Trim trailing zeroes. - let i = this.vtable_in_use - 1; - // eslint-disable-next-line no-empty - for (; i >= 0 && this.vtable[i] == 0; i--) { } - const trimmed_size = i + 1; - // Write out the current vtable. - for (; i >= 0; i--) { - // Offset relative to the start of the table. - this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0); - } - const standard_fields = 2; // The fields below: - this.addInt16(vtableloc - this.object_start); - const len = (trimmed_size + standard_fields) * SIZEOF_SHORT; - this.addInt16(len); - // Search for an existing vtable that matches the current one. - let existing_vtable = 0; - const vt1 = this.space; - outer_loop: for (i = 0; i < this.vtables.length; i++) { - const vt2 = this.bb.capacity() - this.vtables[i]; - if (len == this.bb.readInt16(vt2)) { - for (let j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) { - if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) { - continue outer_loop; - } - } - existing_vtable = this.vtables[i]; - break; - } - } - if (existing_vtable) { - // Found a match: - // Remove the current vtable. - this.space = this.bb.capacity() - vtableloc; - // Point table to existing vtable. - this.bb.writeInt32(this.space, existing_vtable - vtableloc); - } - else { - // No match: - // Add the location of the current vtable to the list of vtables. - this.vtables.push(this.offset()); - // Point table to current vtable. - this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc); +} + + + +const $1ed45968c1160c3c$export$c9b263b9a17dffd7 = { + uniforms: { + "sceneDiffuse": { + value: null + }, + "sceneDepth": { + value: null + }, + "sceneNormal": { + value: null + }, + "projMat": { + value: new Matrix4() + }, + "viewMat": { + value: new Matrix4() + }, + "projViewMat": { + value: new Matrix4() + }, + "projectionMatrixInv": { + value: new Matrix4() + }, + "viewMatrixInv": { + value: new Matrix4() + }, + "cameraPos": { + value: new Vector3$1() + }, + "resolution": { + value: new Vector2$1() + }, + "time": { + value: 0.0 + }, + "samples": { + value: [] + }, + "bluenoise": { + value: null + }, + "distanceFalloff": { + value: 1.0 + }, + "radius": { + value: 5.0 + }, + "near": { + value: 0.1 + }, + "far": { + value: 1000.0 + }, + "logDepth": { + value: false + }, + "ortho": { + value: false + }, + "screenSpaceRadius": { + value: false } - this.isNested = false; - return vtableloc; + }, + depthWrite: false, + depthTest: false, + vertexShader: /* glsl */ ` +varying vec2 vUv; +void main() { + vUv = uv; + gl_Position = vec4(position, 1); +}`, + fragmentShader: /* glsl */ ` + #define SAMPLES 16 + #define FSAMPLES 16.0 +uniform sampler2D sceneDiffuse; +uniform highp sampler2D sceneNormal; +uniform highp sampler2D sceneDepth; +uniform mat4 projectionMatrixInv; +uniform mat4 viewMatrixInv; +uniform mat4 projMat; +uniform mat4 viewMat; +uniform mat4 projViewMat; +uniform vec3 cameraPos; +uniform vec2 resolution; +uniform float time; +uniform vec3[SAMPLES] samples; +uniform float radius; +uniform float distanceFalloff; +uniform float near; +uniform float far; +uniform bool logDepth; +uniform bool ortho; +uniform bool screenSpaceRadius; +uniform sampler2D bluenoise; + varying vec2 vUv; + highp float linearize_depth(highp float d, highp float zNear,highp float zFar) + { + return (zFar * zNear) / (zFar - d * (zFar - zNear)); } - /** - * Finalize a buffer, poiting to the given `root_table`. - */ - finish(root_table, opt_file_identifier, opt_size_prefix) { - const size_prefix = opt_size_prefix ? SIZE_PREFIX_LENGTH : 0; - if (opt_file_identifier) { - const file_identifier = opt_file_identifier; - this.prep(this.minalign, SIZEOF_INT + - FILE_IDENTIFIER_LENGTH + size_prefix); - if (file_identifier.length != FILE_IDENTIFIER_LENGTH) { - throw new TypeError('FlatBuffers: file identifier must be length ' + - FILE_IDENTIFIER_LENGTH); - } - for (let i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) { - this.writeInt8(file_identifier.charCodeAt(i)); - } - } - this.prep(this.minalign, SIZEOF_INT + size_prefix); - this.addOffset(root_table); - if (size_prefix) { - this.addInt32(this.bb.capacity() - this.space); - } - this.bb.setPosition(this.space); + highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { + return nearZ + (farZ - nearZ) * d; } - /** - * Finalize a size prefixed buffer, pointing to the given `root_table`. - */ - finishSizePrefixed(root_table, opt_file_identifier) { - this.finish(root_table, opt_file_identifier, true); + highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { + float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + return ortho ? linearize_depth_ortho( + linDepth, + nearZ, + farZ + ) :linearize_depth(linDepth, nearZ, farZ); } - /** - * This checks a required field has been set in a given table that has - * just been constructed. - */ - requiredField(table, field) { - const table_start = this.bb.capacity() - table; - const vtable_start = table_start - this.bb.readInt32(table_start); - const ok = field < this.bb.readInt16(vtable_start) && - this.bb.readInt16(vtable_start + field) != 0; - // If this fails, the caller will show what field needs to be set. - if (!ok) { - throw new TypeError('FlatBuffers: field ' + field + ' must be set'); + + vec3 getWorldPosLog(vec3 posS) { + vec2 uv = posS.xy; + float z = posS.z; + float nearZ =near; + float farZ = far; + float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; + vec4 wpos = projectionMatrixInv * clipVec; + return wpos.xyz / wpos.w; + } + vec3 getWorldPos(float depth, vec2 coord) { + #ifdef LOGDEPTH + return getWorldPosLog(vec3(coord, depth)); + #endif + float z = depth * 2.0 - 1.0; + vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); + vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; + // Perspective division + vec4 worldSpacePosition = viewSpacePosition; + worldSpacePosition.xyz /= worldSpacePosition.w; + return worldSpacePosition.xyz; + } + + vec3 computeNormal(vec3 worldPos, vec2 vUv) { + ivec2 p = ivec2(vUv * resolution); + float c0 = texelFetch(sceneDepth, p, 0).x; + float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; + float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; + float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; + float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; + float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; + float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; + float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; + float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; + + float dl = abs((2.0 * l1 - l2) - c0); + float dr = abs((2.0 * r1 - r2) - c0); + float db = abs((2.0 * b1 - b2) - c0); + float dt = abs((2.0 * t1 - t2) - c0); + + vec3 ce = getWorldPos(c0, vUv).xyz; + + vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz + : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; + vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz + : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; + + return normalize(cross(dpdx, dpdy)); +} + +mat3 makeRotationZ(float theta) { + float c = cos(theta); + float s = sin(theta); + return mat3(c, - s, 0, + s, c, 0, + 0, 0, 1); + } + +void main() { + vec4 diffuse = texture2D(sceneDiffuse, vUv); + float depth = texture2D(sceneDepth, vUv).x; + if (depth == 1.0) { + gl_FragColor = vec4(vec3(1.0), 1.0); + return; + } + vec3 worldPos = getWorldPos(depth, vUv); + #ifdef HALFRES + vec3 normal = texture2D(sceneNormal, vUv).rgb; + #else + vec3 normal = computeNormal(worldPos, vUv); + #endif + vec4 noise = texture2D(bluenoise, gl_FragCoord.xy / 128.0); + vec3 helperVec = vec3(0.0, 1.0, 0.0); + if (dot(helperVec, normal) > 0.99) { + helperVec = vec3(1.0, 0.0, 0.0); + } + vec3 tangent = normalize(cross(helperVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 tbn = mat3(tangent, bitangent, normal) * makeRotationZ(noise.r * 2.0 * 3.1415962) ; + + float occluded = 0.0; + float totalWeight = 0.0; + float radiusToUse = screenSpaceRadius ? distance( + worldPos, + getWorldPos(depth, vUv + + vec2(radius, 0.0) / resolution) + ) : radius; + float distanceFalloffToUse =screenSpaceRadius ? + radiusToUse * distanceFalloff + : radiusToUse * distanceFalloff * 0.2; + float bias = (min( + 0.1, + distanceFalloffToUse * 0.1 + ) / near) * fwidth(distance(worldPos, cameraPos)) / radiusToUse; + float phi = 1.61803398875; + float offsetMove = 0.0; + float offsetMoveInv = 1.0 / FSAMPLES; + for(float i = 0.0; i < FSAMPLES; i++) { + vec3 sampleDirection = tbn * samples[int(i)]; + + float moveAmt = fract(noise.g + offsetMove); + offsetMove += offsetMoveInv; + + vec3 samplePos = worldPos + radiusToUse * moveAmt * sampleDirection; + vec4 offset = projMat * vec4(samplePos, 1.0); + offset.xyz /= offset.w; + offset.xyz = offset.xyz * 0.5 + 0.5; + + vec2 diff = gl_FragCoord.xy - floor(offset.xy * resolution); + // From Rabbid76's hbao + vec2 clipRangeCheck = step(vec2(0.0),offset.xy) * step(offset.xy, vec2(1.0)); + float sampleDepth = textureLod(sceneDepth, offset.xy, 0.0).x; + + #ifdef LOGDEPTH + + float distSample = linearize_depth_log(sampleDepth, near, far); + + #else + + float distSample = ortho ? linearize_depth_ortho(sampleDepth, near, far) : linearize_depth(sampleDepth, near, far); + + #endif + + float distWorld = ortho ? linearize_depth_ortho(offset.z, near, far) : linearize_depth(offset.z, near, far); + + float rangeCheck = smoothstep(0.0, 1.0, distanceFalloffToUse / (abs(distSample - distWorld))); + + float sampleValid = (clipRangeCheck.x * clipRangeCheck.y); + occluded += rangeCheck * float(sampleDepth != depth) * float(distSample + bias < distWorld) * step( + 1.0, + dot(diff, diff) + ) * sampleValid; + + totalWeight += sampleValid; + } + float occ = clamp(1.0 - occluded / (totalWeight == 0.0 ? 1.0 : totalWeight), 0.0, 1.0); + gl_FragColor = vec4(0.5 + 0.5 * normal, occ); +}` +}; + + + +const $12b21d24d1192a04$export$a815acccbd2c9a49 = { + uniforms: { + "sceneDiffuse": { + value: null + }, + "sceneDepth": { + value: null + }, + "tDiffuse": { + value: null + }, + "transparencyDWFalse": { + value: null + }, + "transparencyDWTrue": { + value: null + }, + "transparencyDWTrueDepth": { + value: null + }, + "transparencyAware": { + value: false + }, + "projMat": { + value: new Matrix4() + }, + "viewMat": { + value: new Matrix4() + }, + "projectionMatrixInv": { + value: new Matrix4() + }, + "viewMatrixInv": { + value: new Matrix4() + }, + "cameraPos": { + value: new Vector3$1() + }, + "resolution": { + value: new Vector2$1() + }, + "color": { + value: new Vector3$1(0, 0, 0) + }, + "blueNoise": { + value: null + }, + "downsampledDepth": { + value: null + }, + "time": { + value: 0.0 + }, + "intensity": { + value: 10.0 + }, + "renderMode": { + value: 0.0 + }, + "gammaCorrection": { + value: false + }, + "logDepth": { + value: false + }, + "ortho": { + value: false + }, + "near": { + value: 0.1 + }, + "far": { + value: 1000.0 + }, + "screenSpaceRadius": { + value: false + }, + "radius": { + value: 0.0 + }, + "distanceFalloff": { + value: 1.0 + }, + "fog": { + value: false + }, + "fogExp": { + value: false + }, + "fogDensity": { + value: 0.0 + }, + "fogNear": { + value: Infinity + }, + "fogFar": { + value: Infinity + }, + "colorMultiply": { + value: true } + }, + depthWrite: false, + depthTest: false, + vertexShader: /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position, 1); + }`, + fragmentShader: /* glsl */ ` + uniform sampler2D sceneDiffuse; + uniform highp sampler2D sceneDepth; + uniform highp sampler2D downsampledDepth; + uniform highp sampler2D transparencyDWFalse; + uniform highp sampler2D transparencyDWTrue; + uniform highp sampler2D transparencyDWTrueDepth; + uniform sampler2D tDiffuse; + uniform sampler2D blueNoise; + uniform vec2 resolution; + uniform vec3 color; + uniform mat4 projectionMatrixInv; + uniform mat4 viewMatrixInv; + uniform float intensity; + uniform float renderMode; + uniform float near; + uniform float far; + uniform bool gammaCorrection; + uniform bool logDepth; + uniform bool ortho; + uniform bool screenSpaceRadius; + uniform bool fog; + uniform bool fogExp; + uniform bool colorMultiply; + uniform bool transparencyAware; + uniform float fogDensity; + uniform float fogNear; + uniform float fogFar; + uniform float radius; + uniform float distanceFalloff; + uniform vec3 cameraPos; + varying vec2 vUv; + highp float linearize_depth(highp float d, highp float zNear,highp float zFar) + { + return (zFar * zNear) / (zFar - d * (zFar - zNear)); } - /** - * Start a new array/vector of objects. Users usually will not call - * this directly. The FlatBuffers compiler will create a start/end - * method for vector types in generated code. - * - * @param elem_size The size of each element in the array - * @param num_elems The number of elements in the array - * @param alignment The alignment of the array - */ - startVector(elem_size, num_elems, alignment) { - this.notNested(); - this.vector_num_elems = num_elems; - this.prep(SIZEOF_INT, elem_size * num_elems); - this.prep(alignment, elem_size * num_elems); // Just in case alignment > int. + highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { + return nearZ + (farZ - nearZ) * d; } - /** - * Finish off the creation of an array and all its elements. The array must be - * created with `startVector`. - * - * @returns The offset at which the newly created array - * starts. - */ - endVector() { - this.writeInt32(this.vector_num_elems); - return this.offset(); + highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { + float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + return ortho ? linearize_depth_ortho( + linDepth, + nearZ, + farZ + ) :linearize_depth(linDepth, nearZ, farZ); } - /** - * Encode the string `s` in the buffer using UTF-8. If the string passed has - * already been seen, we return the offset of the already written string - * - * @param s The string to encode - * @return The offset in the buffer where the encoded string starts - */ - createSharedString(s) { - if (!s) { - return 0; - } - if (!this.string_maps) { - this.string_maps = new Map(); - } - if (this.string_maps.has(s)) { - return this.string_maps.get(s); - } - const offset = this.createString(s); - this.string_maps.set(s, offset); - return offset; + vec3 getWorldPosLog(vec3 posS) { + vec2 uv = posS.xy; + float z = posS.z; + float nearZ =near; + float farZ = far; + float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; + vec4 wpos = projectionMatrixInv * clipVec; + return wpos.xyz / wpos.w; + } + vec3 getWorldPos(float depth, vec2 coord) { + // if (logDepth) { + #ifdef LOGDEPTH + return getWorldPosLog(vec3(coord, depth)); + #endif + // } + float z = depth * 2.0 - 1.0; + vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); + vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; + // Perspective division + vec4 worldSpacePosition = viewSpacePosition; + worldSpacePosition.xyz /= worldSpacePosition.w; + return worldSpacePosition.xyz; } - /** - * Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed - * instead of a string, it is assumed to contain valid UTF-8 encoded data. - * - * @param s The string to encode - * @return The offset in the buffer where the encoded string starts - */ - createString(s) { - if (s === null || s === undefined) { - return 0; - } - let utf8; - if (s instanceof Uint8Array) { - utf8 = s; - } - else { - utf8 = this.text_encoder.encode(s); + + vec3 computeNormal(vec3 worldPos, vec2 vUv) { + ivec2 p = ivec2(vUv * resolution); + float c0 = texelFetch(sceneDepth, p, 0).x; + float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; + float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; + float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; + float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; + float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; + float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; + float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; + float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; + + float dl = abs((2.0 * l1 - l2) - c0); + float dr = abs((2.0 * r1 - r2) - c0); + float db = abs((2.0 * b1 - b2) - c0); + float dt = abs((2.0 * t1 - t2) - c0); + + vec3 ce = getWorldPos(c0, vUv).xyz; + + vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz + : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; + vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz + : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; + + return normalize(cross(dpdx, dpdy)); + } + + #include + #include + void main() { + //vec4 texel = texture2D(tDiffuse, vUv);//vec3(0.0); + vec4 sceneTexel = texture2D(sceneDiffuse, vUv); + float depth = texture2D( + sceneDepth, + vUv + ).x; + #ifdef HALFRES + vec4 texel; + if (depth == 1.0) { + texel = vec4(0.0, 0.0, 0.0, 1.0); + } else { + vec3 worldPos = getWorldPos(depth, vUv); + vec3 normal = computeNormal(getWorldPos(depth, vUv), vUv); + // vec4 texel = texture2D(tDiffuse, vUv); + // Find closest depth; + float totalWeight = 0.0; + float radiusToUse = screenSpaceRadius ? distance( + worldPos, + getWorldPos(depth, vUv + + vec2(radius, 0.0) / resolution) + ) : radius; + float distanceFalloffToUse =screenSpaceRadius ? + radiusToUse * distanceFalloff + : distanceFalloff; + for(float x = -1.0; x <= 1.0; x++) { + for(float y = -1.0; y <= 1.0; y++) { + vec2 offset = vec2(x, y); + ivec2 p = ivec2( + (vUv * resolution * 0.5) + offset + ); + vec2 pUv = vec2(p) / (resolution * 0.5); + float sampleDepth = texelFetch(downsampledDepth,p, 0).x; + vec4 sampleInfo = texelFetch(tDiffuse, p, 0); + vec3 normalSample = sampleInfo.xyz * 2.0 - 1.0; + vec3 worldPosSample = getWorldPos(sampleDepth, pUv); + float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal)); + float rangeCheck = exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0); + float weight = rangeCheck; + totalWeight += weight; + texel += sampleInfo * weight; + } } - this.addInt8(0); - this.startVector(1, utf8.length, 1); - this.bb.setPosition(this.space -= utf8.length); - for (let i = 0, offset = this.space, bytes = this.bb.bytes(); i < utf8.length; i++) { - bytes[offset++] = utf8[i]; + if (totalWeight == 0.0) { + texel = texture2D(tDiffuse, vUv); + } else { + texel /= totalWeight; } - return this.endVector(); } - /** - * A helper function to pack an object - * - * @returns offset of obj - */ - createObjectOffset(obj) { - if (obj === null) { - return 0; + #else + vec4 texel = texture2D(tDiffuse, vUv); + #endif + + #ifdef LOGDEPTH + texel.a = clamp(texel.a, 0.0, 1.0); + if (texel.a == 0.0) { + texel.a = 1.0; } - if (typeof obj === 'string') { - return this.createString(obj); + #endif + + float finalAo = pow(texel.a, intensity); + float fogFactor; + float fogDepth = distance( + cameraPos, + getWorldPos(depth, vUv) + ); + if (fog) { + if (fogExp) { + fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth ); + } else { + fogFactor = smoothstep( fogNear, fogFar, fogDepth ); + } } - else { - return obj.pack(this); + if (transparencyAware) { + float transparencyDWOff = texture2D(transparencyDWFalse, vUv).a; + float transparencyDWOn = texture2D(transparencyDWTrue, vUv).a; + float adjustmentFactorOff = transparencyDWOff; + float adjustmentFactorOn = (1.0 - transparencyDWOn) * ( + texture2D(transparencyDWTrueDepth, vUv).r == texture2D(sceneDepth, vUv).r ? 1.0 : 0.0 + ); + float adjustmentFactor = max(adjustmentFactorOff, adjustmentFactorOn); + finalAo = mix(finalAo, 1.0, adjustmentFactor); } - } - /** - * A helper function to pack a list of object - * - * @returns list of offsets of each non null object - */ - createObjectOffsetList(list) { - const ret = []; - for (let i = 0; i < list.length; ++i) { - const val = list[i]; - if (val !== null) { - ret.push(this.createObjectOffset(val)); + finalAo = mix(finalAo, 1.0, fogFactor); + vec3 aoApplied = color * mix(vec3(1.0), sceneTexel.rgb, float(colorMultiply)); + if (renderMode == 0.0) { + gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a); + } else if (renderMode == 1.0) { + gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a); + } else if (renderMode == 2.0) { + gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); + } else if (renderMode == 3.0) { + if (vUv.x < 0.5) { + gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); + } else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) { + gl_FragColor = vec4(1.0); + } else { + gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a); } - else { - throw new TypeError('FlatBuffers: Argument for createObjectOffsetList cannot contain null.'); + } else if (renderMode == 4.0) { + if (vUv.x < 0.5) { + gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a); + } else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) { + gl_FragColor = vec4(1.0); + } else { + gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a); } } - return ret; - } - createStructOffsetList(list, startFunc) { - startFunc(this, list.length); - this.createObjectOffsetList(list.slice().reverse()); - return this.endVector(); - } -} - -// automatically generated by the FlatBuffers compiler, do not modify -class Alignment { - constructor() { - this.bb = null; - this.bb_pos = 0; - } - __init(i, bb) { - this.bb_pos = i; - this.bb = bb; - return this; - } - static getRootAsAlignment(bb, obj) { - return (obj || new Alignment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - static getSizePrefixedRootAsAlignment(bb, obj) { - bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); - return (obj || new Alignment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - position(index) { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - positionLength() { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - positionArray() { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - curve(index) { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - curveLength() { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - curveArray() { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - segment(index) { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - segmentLength() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - segmentArray() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - static startAlignment(builder) { - builder.startObject(3); - } - static addPosition(builder, positionOffset) { - builder.addFieldOffset(0, positionOffset, 0); - } - static createPositionVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); - } - return builder.endVector(); - } - static startPositionVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static addCurve(builder, curveOffset) { - builder.addFieldOffset(1, curveOffset, 0); - } - static createCurveVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); - } - return builder.endVector(); - } - static startCurveVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static addSegment(builder, segmentOffset) { - builder.addFieldOffset(2, segmentOffset, 0); - } - static createSegmentVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + #include + if (gammaCorrection) { + gl_FragColor = LinearTosRGB(gl_FragColor); } - return builder.endVector(); - } - static startSegmentVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static endAlignment(builder) { - const offset = builder.endObject(); - return offset; - } - static createAlignment(builder, positionOffset, curveOffset, segmentOffset) { - Alignment.startAlignment(builder); - Alignment.addPosition(builder, positionOffset); - Alignment.addCurve(builder, curveOffset); - Alignment.addSegment(builder, segmentOffset); - return Alignment.endAlignment(builder); } -} + ` +}; -// automatically generated by the FlatBuffers compiler, do not modify -class Civil { - constructor() { - this.bb = null; - this.bb_pos = 0; - } - __init(i, bb) { - this.bb_pos = i; - this.bb = bb; - return this; - } - static getRootAsCivil(bb, obj) { - return (obj || new Civil()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - static getSizePrefixedRootAsCivil(bb, obj) { - bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); - return (obj || new Civil()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - alignmentHorizontal(obj) { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; - } - alignmentVertical(obj) { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; - } - alignment3d(obj) { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? (obj || new Alignment()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; - } - static startCivil(builder) { - builder.startObject(3); - } - static addAlignmentHorizontal(builder, alignmentHorizontalOffset) { - builder.addFieldOffset(0, alignmentHorizontalOffset, 0); - } - static addAlignmentVertical(builder, alignmentVerticalOffset) { - builder.addFieldOffset(1, alignmentVerticalOffset, 0); - } - static addAlignment3d(builder, alignment3dOffset) { - builder.addFieldOffset(2, alignment3dOffset, 0); - } - static endCivil(builder) { - const offset = builder.endObject(); - return offset; - } -} -// automatically generated by the FlatBuffers compiler, do not modify -class Fragment { - constructor() { - this.bb = null; - this.bb_pos = 0; - } - __init(i, bb) { - this.bb_pos = i; - this.bb = bb; - return this; - } - static getRootAsFragment(bb, obj) { - return (obj || new Fragment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - static getSizePrefixedRootAsFragment(bb, obj) { - bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); - return (obj || new Fragment()).__init(bb.readInt32(bb.position()) + bb.position(), bb); - } - position(index) { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - positionLength() { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - positionArray() { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - normal(index) { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - normalLength() { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - normalArray() { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - index(index) { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - indexLength() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - indexArray() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - blockId(index) { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? this.bb.readInt32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - blockIdLength() { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - blockIdArray() { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? new Int32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - groups(index) { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - groupsLength() { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - groupsArray() { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - materials(index) { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - materialsLength() { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - materialsArray() { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - matrices(index) { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - matricesLength() { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - matricesArray() { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - colors(index) { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; - } - colorsLength() { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; - } - colorsArray() { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; - } - ids(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 20); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; - } - id(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 22); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; - } - composites(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 24); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; - } - static startFragment(builder) { - builder.startObject(11); + +const $e52378cd0f5a973d$export$57856b59f317262e = { + uniforms: { + "sceneDiffuse": { + value: null + }, + "sceneDepth": { + value: null + }, + "tDiffuse": { + value: null + }, + "projMat": { + value: new Matrix4() + }, + "viewMat": { + value: new Matrix4() + }, + "projectionMatrixInv": { + value: new Matrix4() + }, + "viewMatrixInv": { + value: new Matrix4() + }, + "cameraPos": { + value: new Vector3$1() + }, + "resolution": { + value: new Vector2$1() + }, + "time": { + value: 0.0 + }, + "r": { + value: 5.0 + }, + "blueNoise": { + value: null + }, + "radius": { + value: 12.0 + }, + "worldRadius": { + value: 5.0 + }, + "index": { + value: 0.0 + }, + "poissonDisk": { + value: [] + }, + "distanceFalloff": { + value: 1.0 + }, + "near": { + value: 0.1 + }, + "far": { + value: 1000.0 + }, + "logDepth": { + value: false + }, + "screenSpaceRadius": { + value: false + } + }, + depthWrite: false, + depthTest: false, + vertexShader: /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position, 1.0); + }`, + fragmentShader: /* glsl */ ` + uniform sampler2D sceneDiffuse; + uniform highp sampler2D sceneDepth; + uniform sampler2D tDiffuse; + uniform sampler2D blueNoise; + uniform mat4 projectionMatrixInv; + uniform mat4 viewMatrixInv; + uniform vec2 resolution; + uniform float r; + uniform float radius; + uniform float worldRadius; + uniform float index; + uniform float near; + uniform float far; + uniform float distanceFalloff; + uniform bool logDepth; + uniform bool screenSpaceRadius; + varying vec2 vUv; + + highp float linearize_depth(highp float d, highp float zNear,highp float zFar) + { + highp float z_n = 2.0 * d - 1.0; + return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); } - static addPosition(builder, positionOffset) { - builder.addFieldOffset(0, positionOffset, 0); + highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) { + float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + return linearize_depth(linDepth, nearZ, farZ); + } + highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) { + return nearZ + (farZ - nearZ) * d; + } + vec3 getWorldPosLog(vec3 posS) { + vec2 uv = posS.xy; + float z = posS.z; + float nearZ =near; + float farZ = far; + float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; + vec4 wpos = projectionMatrixInv * clipVec; + return wpos.xyz / wpos.w; + } + vec3 getWorldPos(float depth, vec2 coord) { + #ifdef LOGDEPTH + return getWorldPosLog(vec3(coord, depth)); + #endif + + float z = depth * 2.0 - 1.0; + vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); + vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; + // Perspective division + vec4 worldSpacePosition = viewSpacePosition; + worldSpacePosition.xyz /= worldSpacePosition.w; + return worldSpacePosition.xyz; } - static createPositionVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + #include + #define NUM_SAMPLES 16 + uniform vec2 poissonDisk[NUM_SAMPLES]; + void main() { + const float pi = 3.14159; + vec2 texelSize = vec2(1.0 / resolution.x, 1.0 / resolution.y); + vec2 uv = vUv; + vec4 data = texture2D(tDiffuse, vUv); + float occlusion = data.a; + float baseOcc = data.a; + vec3 normal = data.rgb * 2.0 - 1.0; + float count = 1.0; + float d = texture2D(sceneDepth, vUv).x; + if (d == 1.0) { + gl_FragColor = data; + return; } - return builder.endVector(); - } - static startPositionVector(builder, numElems) { - builder.startVector(4, numElems, 4); + vec3 worldPos = getWorldPos(d, vUv); + float size = radius; + float angle; + if (index == 0.0) { + angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).x * PI2; + } else if (index == 1.0) { + angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).y * PI2; + } else if (index == 2.0) { + angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).z * PI2; + } else { + angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).w * PI2; + } + + mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); + float radiusToUse = screenSpaceRadius ? distance( + worldPos, + getWorldPos(d, vUv + + vec2(worldRadius, 0.0) / resolution) + ) : worldRadius; + float distanceFalloffToUse =screenSpaceRadius ? + radiusToUse * distanceFalloff + : radiusToUse * distanceFalloff * 0.2; + + + for(int i = 0; i < NUM_SAMPLES; i++) { + vec2 offset = (rotationMatrix * poissonDisk[i]) * texelSize * size; + vec4 dataSample = texture2D(tDiffuse, uv + offset); + float occSample = dataSample.a; + vec3 normalSample = dataSample.rgb * 2.0 - 1.0; + float dSample = texture2D(sceneDepth, uv + offset).x; + vec3 worldPosSample = getWorldPos(dSample, uv + offset); + float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal)); + float rangeCheck = dSample == 1.0 ? 0.0 :exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0) * (1.0 - abs(occSample - baseOcc)); + occlusion += occSample * rangeCheck; + count += rangeCheck; + } + if (count > 0.0) { + occlusion /= count; + } + #ifdef LOGDEPTH + occlusion = clamp(occlusion, 0.0, 1.0); + if (occlusion == 0.0) { + occlusion = 1.0; + } + #endif + gl_FragColor = vec4(0.5 + 0.5 * normal, occlusion); } - static addNormal(builder, normalOffset) { - builder.addFieldOffset(1, normalOffset, 0); + ` +}; + + + +const $26aca173e0984d99$export$1efdf491687cd442 = { + uniforms: { + "sceneDepth": { + value: null + }, + "resolution": { + value: new Vector2$1() + }, + "near": { + value: 0.1 + }, + "far": { + value: 1000.0 + }, + "viewMatrixInv": { + value: new Matrix4() + }, + "projectionMatrixInv": { + value: new Matrix4() + }, + "logDepth": { + value: false + } + }, + depthWrite: false, + depthTest: false, + vertexShader: /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position, 1); + }`, + fragmentShader: /* glsl */ ` + uniform highp sampler2D sceneDepth; + uniform vec2 resolution; + uniform float near; + uniform float far; + uniform bool logDepth; + uniform mat4 viewMatrixInv; + uniform mat4 projectionMatrixInv; + varying vec2 vUv; + layout(location = 1) out vec4 gNormal; + vec3 getWorldPosLog(vec3 posS) { + vec2 uv = posS.xy; + float z = posS.z; + float nearZ =near; + float farZ = far; + float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0; + float a = farZ / (farZ - nearZ); + float b = farZ * nearZ / (nearZ - farZ); + float linDepth = a + b / depth; + vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0; + vec4 wpos = projectionMatrixInv * clipVec; + return wpos.xyz / wpos.w; + } + vec3 getWorldPos(float depth, vec2 coord) { + if (logDepth) { + return getWorldPosLog(vec3(coord, depth)); + } + float z = depth * 2.0 - 1.0; + vec4 clipSpacePosition = vec4(coord * 2.0 - 1.0, z, 1.0); + vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition; + // Perspective division + vec4 worldSpacePosition = viewSpacePosition; + worldSpacePosition.xyz /= worldSpacePosition.w; + return worldSpacePosition.xyz; } - static createNormalVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + + vec3 computeNormal(vec3 worldPos, vec2 vUv) { + ivec2 p = ivec2(vUv * resolution); + float c0 = texelFetch(sceneDepth, p, 0).x; + float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x; + float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x; + float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x; + float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x; + float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x; + float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x; + float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x; + float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x; + + float dl = abs((2.0 * l1 - l2) - c0); + float dr = abs((2.0 * r1 - r2) - c0); + float db = abs((2.0 * b1 - b2) - c0); + float dt = abs((2.0 * t1 - t2) - c0); + + vec3 ce = getWorldPos(c0, vUv).xyz; + + vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz + : -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz; + vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz + : -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz; + + return normalize(cross(dpdx, dpdy)); + } + void main() { + vec2 uv = vUv - vec2(0.5) / resolution; + vec2 pixelSize = vec2(1.0) / resolution; + vec2[] uvSamples = vec2[4]( + uv, + uv + vec2(pixelSize.x, 0.0), + uv + vec2(0.0, pixelSize.y), + uv + pixelSize + ); + float depth00 = texture2D(sceneDepth, uvSamples[0]).r; + float depth10 = texture2D(sceneDepth, uvSamples[1]).r; + float depth01 = texture2D(sceneDepth, uvSamples[2]).r; + float depth11 = texture2D(sceneDepth, uvSamples[3]).r; + float minDepth = min(min(depth00, depth10), min(depth01, depth11)); + float maxDepth = max(max(depth00, depth10), max(depth01, depth11)); + float targetDepth = minDepth; + // Checkerboard pattern to avoid artifacts + if (mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) > 0.5) { + targetDepth = maxDepth; } - return builder.endVector(); + int chosenIndex = 0; + float[] samples = float[4](depth00, depth10, depth01, depth11); + for(int i = 0; i < 4; ++i) { + if (samples[i] == targetDepth) { + chosenIndex = i; + break; + } + } + gl_FragColor = vec4(samples[chosenIndex], 0.0, 0.0, 1.0); + gNormal = vec4(computeNormal( + getWorldPos(samples[chosenIndex], uvSamples[chosenIndex]), uvSamples[chosenIndex] + ), 0.0); + }` +}; + + + + + + + + + +var $06269ad78f3c5fdf$export$2e2bcd8739ae039 = `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`; + + +Uint8Array.from(atob(($06269ad78f3c5fdf$export$2e2bcd8739ae039)), (c)=>c.charCodeAt(0)); + + + +const $05f6997e4b65da14$var$bluenoiseBits = Uint8Array.from(atob(($06269ad78f3c5fdf$export$2e2bcd8739ae039)), (c)=>c.charCodeAt(0)); +/** + * + * @param {*} timerQuery + * @param {THREE.WebGLRenderer} gl + * @param {N8AOPass} pass + */ function $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, pass) { + const available = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT_AVAILABLE); + if (available) { + const elapsedTimeInNs = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT); + const elapsedTimeInMs = elapsedTimeInNs / 1000000; + pass.lastTime = elapsedTimeInMs; + } else // If the result is not available yet, check again after a delay + setTimeout(()=>{ + $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, pass); + }, 1); +} +class $05f6997e4b65da14$export$2d57db20b5eb5e0a extends (Pass) { + /** + * + * @param {THREE.Scene} scene + * @param {THREE.Camera} camera + * @param {number} width + * @param {number} height + * + * @property {THREE.Scene} scene + * @property {THREE.Camera} camera + * @property {number} width + * @property {number} height + */ constructor(scene, camera, width = 512, height = 512){ + super(); + this.width = width; + this.height = height; + this.clear = true; + this.camera = camera; + this.scene = scene; + /** + * @type {Proxy & { + * aoSamples: number, + * aoRadius: number, + * denoiseSamples: number, + * denoiseRadius: number, + * distanceFalloff: number, + * intensity: number, + * denoiseIterations: number, + * renderMode: 0 | 1 | 2 | 3 | 4, + * color: THREE.Color, + * gammaCorrection: boolean, + * logarithmicDepthBuffer: boolean + * screenSpaceRadius: boolean, + * halfRes: boolean, + * depthAwareUpsampling: boolean, + * autoRenderBeauty: boolean + * colorMultiply: boolean + * } + */ this.configuration = new Proxy({ + aoSamples: 16, + aoRadius: 5.0, + denoiseSamples: 8, + denoiseRadius: 12, + distanceFalloff: 1.0, + intensity: 5, + denoiseIterations: 2.0, + renderMode: 0, + color: new Color(0, 0, 0), + gammaCorrection: true, + logarithmicDepthBuffer: false, + screenSpaceRadius: false, + halfRes: false, + depthAwareUpsampling: true, + autoRenderBeauty: true, + colorMultiply: true, + transparencyAware: false, + stencil: false + }, { + set: (target, propName, value)=>{ + const oldProp = target[propName]; + target[propName] = value; + if (propName === "aoSamples" && oldProp !== value) this.configureAOPass(this.configuration.logarithmicDepthBuffer); + if (propName === "denoiseSamples" && oldProp !== value) this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); + if (propName === "halfRes" && oldProp !== value) { + this.configureAOPass(this.configuration.logarithmicDepthBuffer); + this.configureHalfResTargets(); + this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); + this.setSize(this.width, this.height); + } + if (propName === "depthAwareUpsampling" && oldProp !== value) this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); + if (propName === "transparencyAware" && oldProp !== value) { + this.autoDetectTransparency = false; + this.configureTransparencyTarget(); + } + if (propName === "stencil" && oldProp !== value) { + /* this.beautyRenderTarget.stencilBuffer = value; + this.beautyRenderTarget.depthTexture.format = value ? THREE.DepthStencilFormat : THREE.DepthFormat; + this.beautyRenderTarget.depthTexture.type = value ? THREE.UnsignedInt248Type : THREE.UnsignedIntType; + this.beautyRenderTarget.depthTexture.needsUpdate = true; + this.beautyRenderTarget.needsUpdate = true;*/ this.beautyRenderTarget.dispose(); + this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: NearestFilter, + type: HalfFloatType, + format: RGBAFormat, + stencilBuffer: value + }); + this.beautyRenderTarget.depthTexture = new DepthTexture(this.width, this.height, value ? UnsignedInt248Type : UnsignedIntType); + this.beautyRenderTarget.depthTexture.format = value ? DepthStencilFormat : DepthFormat; + } + return true; + } + }); + /** @type {THREE.Vector3[]} */ this.samples = []; + /** @type {THREE.Vector2[]} */ this.samplesDenoise = []; + this.autoDetectTransparency = true; + this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: NearestFilter, + type: HalfFloatType, + format: RGBAFormat, + stencilBuffer: false + }); + this.beautyRenderTarget.depthTexture = new DepthTexture(this.width, this.height, UnsignedIntType); + this.beautyRenderTarget.depthTexture.format = DepthFormat; + this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); + this.configureSampleDependentPasses(); + this.configureHalfResTargets(); + this.detectTransparency(); + this.configureTransparencyTarget(); + this.writeTargetInternal = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: LinearFilter, + depthBuffer: false + }); + this.readTargetInternal = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: LinearFilter, + depthBuffer: false + }); + /** @type {THREE.DataTexture} */ this.bluenoise = new DataTexture($05f6997e4b65da14$var$bluenoiseBits, 128, 128); + this.bluenoise.colorSpace = NoColorSpace; + this.bluenoise.wrapS = RepeatWrapping; + this.bluenoise.wrapT = RepeatWrapping; + this.bluenoise.minFilter = NearestFilter; + this.bluenoise.magFilter = NearestFilter; + this.bluenoise.needsUpdate = true; + this.lastTime = 0; + this._r = new Vector2$1(); + this._c = new Color(); } - static startNormalVector(builder, numElems) { - builder.startVector(4, numElems, 4); + configureHalfResTargets() { + if (this.configuration.halfRes) { + this.depthDownsampleTarget = /*new THREE.WebGLRenderTarget(this.width / 2, this.height / 2, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + depthBuffer: false, + format: THREE.RedFormat, + type: THREE.FloatType + });*/ new WebGLMultipleRenderTargets(this.width / 2, this.height / 2, 2); + this.depthDownsampleTarget.texture[0].format = RedFormat; + this.depthDownsampleTarget.texture[0].type = FloatType; + this.depthDownsampleTarget.texture[0].minFilter = NearestFilter; + this.depthDownsampleTarget.texture[0].magFilter = NearestFilter; + this.depthDownsampleTarget.texture[0].depthBuffer = false; + this.depthDownsampleTarget.texture[1].format = RGBAFormat; + this.depthDownsampleTarget.texture[1].type = HalfFloatType; + this.depthDownsampleTarget.texture[1].minFilter = NearestFilter; + this.depthDownsampleTarget.texture[1].magFilter = NearestFilter; + this.depthDownsampleTarget.texture[1].depthBuffer = false; + this.depthDownsampleQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(($26aca173e0984d99$export$1efdf491687cd442))); + } else { + if (this.depthDownsampleTarget) { + this.depthDownsampleTarget.dispose(); + this.depthDownsampleTarget = null; + } + if (this.depthDownsampleQuad) { + this.depthDownsampleQuad.dispose(); + this.depthDownsampleQuad = null; + } + } } - static addIndex(builder, indexOffset) { - builder.addFieldOffset(2, indexOffset, 0); + detectTransparency() { + if (this.autoDetectTransparency) { + let isTransparency = false; + this.scene.traverse((obj)=>{ + if (obj.material && obj.material.transparent) isTransparency = true; + }); + this.configuration.transparencyAware = isTransparency; + } } - static createIndexVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + configureTransparencyTarget() { + if (this.configuration.transparencyAware) { + this.transparencyRenderTargetDWFalse = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: NearestFilter, + type: HalfFloatType, + format: RGBAFormat + }); + this.transparencyRenderTargetDWTrue = new WebGLRenderTarget(this.width, this.height, { + minFilter: LinearFilter, + magFilter: NearestFilter, + type: HalfFloatType, + format: RGBAFormat + }); + this.transparencyRenderTargetDWTrue.depthTexture = new DepthTexture(this.width, this.height, UnsignedIntType); + this.depthCopyPass = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial({ + uniforms: { + depthTexture: { + value: this.beautyRenderTarget.depthTexture + } + }, + vertexShader: /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = vec4(position, 1); + }`, + fragmentShader: /* glsl */ ` + uniform sampler2D depthTexture; + varying vec2 vUv; + void main() { + gl_FragDepth = texture2D(depthTexture, vUv).r + 0.00001; + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } + ` + })); + } else { + if (this.transparencyRenderTargetDWFalse) { + this.transparencyRenderTargetDWFalse.dispose(); + this.transparencyRenderTargetDWFalse = null; + } + if (this.transparencyRenderTargetDWTrue) { + this.transparencyRenderTargetDWTrue.dispose(); + this.transparencyRenderTargetDWTrue = null; + } + if (this.depthCopyPass) { + this.depthCopyPass.dispose(); + this.depthCopyPass = null; + } } - return builder.endVector(); } - static startIndexVector(builder, numElems) { - builder.startVector(4, numElems, 4); + renderTransparency(renderer) { + const oldBackground = this.scene.background; + const oldClearColor = renderer.getClearColor(new Color()); + const oldClearAlpha = renderer.getClearAlpha(); + const oldVisibility = new Map(); + const oldAutoClearDepth = renderer.autoClearDepth; + this.scene.traverse((obj)=>{ + oldVisibility.set(obj, obj.visible); + }); + // Override the state + this.scene.background = null; + renderer.autoClearDepth = false; + renderer.setClearColor(new Color(0, 0, 0), 0); + this.depthCopyPass.material.uniforms.depthTexture.value = this.beautyRenderTarget.depthTexture; + // Render out transparent objects WITHOUT depth write + renderer.setRenderTarget(this.transparencyRenderTargetDWFalse); + this.scene.traverse((obj)=>{ + if (obj.material) obj.visible = oldVisibility.get(obj) && obj.material.transparent && !obj.material.depthWrite && !obj.userData.treatAsOpaque; + }); + renderer.clear(true, true, true); + this.depthCopyPass.render(renderer); + renderer.render(this.scene, this.camera); + // Render out transparent objects WITH depth write + renderer.setRenderTarget(this.transparencyRenderTargetDWTrue); + this.scene.traverse((obj)=>{ + if (obj.material) obj.visible = oldVisibility.get(obj) && obj.material.transparent && obj.material.depthWrite && !obj.userData.treatAsOpaque; + }); + renderer.clear(true, true, true); + this.depthCopyPass.render(renderer); + renderer.render(this.scene, this.camera); + // Restore + this.scene.traverse((obj)=>{ + obj.visible = oldVisibility.get(obj); + }); + renderer.setClearColor(oldClearColor, oldClearAlpha); + this.scene.background = oldBackground; + renderer.autoClearDepth = oldAutoClearDepth; } - static addBlockId(builder, blockIdOffset) { - builder.addFieldOffset(3, blockIdOffset, 0); + configureSampleDependentPasses() { + this.configureAOPass(this.configuration.logarithmicDepthBuffer); + this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); } - static createBlockIdVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); - } - return builder.endVector(); + configureAOPass(logarithmicDepthBuffer = false) { + this.samples = this.generateHemisphereSamples(this.configuration.aoSamples); + const e = { + ...($1ed45968c1160c3c$export$c9b263b9a17dffd7) + }; + e.fragmentShader = e.fragmentShader.replace("16", this.configuration.aoSamples).replace("16.0", this.configuration.aoSamples + ".0"); + if (logarithmicDepthBuffer) e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader; + if (this.configuration.halfRes) e.fragmentShader = "#define HALFRES\n" + e.fragmentShader; + if (this.effectShaderQuad) { + this.effectShaderQuad.material.dispose(); + this.effectShaderQuad.material = new ShaderMaterial(e); + } else this.effectShaderQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(e)); } - static startBlockIdVector(builder, numElems) { - builder.startVector(4, numElems, 4); + configureDenoisePass(logarithmicDepthBuffer = false) { + this.samplesDenoise = this.generateDenoiseSamples(this.configuration.denoiseSamples, 11); + const p = { + ...($e52378cd0f5a973d$export$57856b59f317262e) + }; + p.fragmentShader = p.fragmentShader.replace("16", this.configuration.denoiseSamples); + if (logarithmicDepthBuffer) p.fragmentShader = "#define LOGDEPTH\n" + p.fragmentShader; + if (this.poissonBlurQuad) { + this.poissonBlurQuad.material.dispose(); + this.poissonBlurQuad.material = new ShaderMaterial(p); + } else this.poissonBlurQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(p)); } - static addGroups(builder, groupsOffset) { - builder.addFieldOffset(4, groupsOffset, 0); + configureEffectCompositer(logarithmicDepthBuffer = false) { + const e = { + ...($12b21d24d1192a04$export$a815acccbd2c9a49) + }; + if (logarithmicDepthBuffer) e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader; + if (this.configuration.halfRes && this.configuration.depthAwareUpsampling) e.fragmentShader = "#define HALFRES\n" + e.fragmentShader; + if (this.effectCompositerQuad) { + this.effectCompositerQuad.material.dispose(); + this.effectCompositerQuad.material = new ShaderMaterial(e); + } else this.effectCompositerQuad = new ($e4ca8dcb0218f846$export$dcd670d73db751f5)(new ShaderMaterial(e)); } - static createGroupsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + /** + * + * @param {Number} n + * @returns {THREE.Vector3[]} + */ generateHemisphereSamples(n) { + const points = []; + for(let k = 0; k < n; k++){ + const theta = 2.399963 * k; + let r = Math.sqrt(k + 0.5) / Math.sqrt(n); + const x = r * Math.cos(theta); + const y = r * Math.sin(theta); + // Project to hemisphere + const z = Math.sqrt(1 - (x * x + y * y)); + points.push(new Vector3$1(x, y, z)); } - return builder.endVector(); + return points; } - static startGroupsVector(builder, numElems) { - builder.startVector(4, numElems, 4); + /** + * + * @param {number} numSamples + * @param {number} numRings + * @returns {THREE.Vector2[]} + */ generateDenoiseSamples(numSamples, numRings) { + const angleStep = 2 * Math.PI * numRings / numSamples; + const invNumSamples = 1.0 / numSamples; + const radiusStep = invNumSamples; + const samples = []; + let radius = invNumSamples; + let angle = 0; + for(let i = 0; i < numSamples; i++){ + samples.push(new Vector2$1(Math.cos(angle), Math.sin(angle)).multiplyScalar(Math.pow(radius, 0.75))); + radius += radiusStep; + angle += angleStep; + } + return samples; } - static addMaterials(builder, materialsOffset) { - builder.addFieldOffset(5, materialsOffset, 0); + setSize(width, height) { + this.width = width; + this.height = height; + const c = this.configuration.halfRes ? 0.5 : 1; + this.beautyRenderTarget.setSize(width, height); + this.writeTargetInternal.setSize(width * c, height * c); + this.readTargetInternal.setSize(width * c, height * c); + if (this.configuration.halfRes) this.depthDownsampleTarget.setSize(width * c, height * c); + if (this.configuration.transparencyAware) { + this.transparencyRenderTargetDWFalse.setSize(width, height); + this.transparencyRenderTargetDWTrue.setSize(width, height); + } } - static createMaterialsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) { + if (renderer.capabilities.logarithmicDepthBuffer !== this.configuration.logarithmicDepthBuffer) { + this.configuration.logarithmicDepthBuffer = renderer.capabilities.logarithmicDepthBuffer; + this.configureAOPass(this.configuration.logarithmicDepthBuffer); + this.configureDenoisePass(this.configuration.logarithmicDepthBuffer); + this.configureEffectCompositer(this.configuration.logarithmicDepthBuffer); } - return builder.endVector(); + this.detectTransparency(); + let gl; + let ext; + let timerQuery; + if (this.debugMode) { + gl = renderer.getContext(); + ext = gl.getExtension("EXT_disjoint_timer_query_webgl2"); + if (ext === null) { + console.error("EXT_disjoint_timer_query_webgl2 not available, disabling debug mode."); + this.debugMode = false; + } + } + if (this.configuration.autoRenderBeauty) { + renderer.setRenderTarget(this.beautyRenderTarget); + renderer.render(this.scene, this.camera); + if (this.configuration.transparencyAware) this.renderTransparency(renderer); + } + if (this.debugMode) { + timerQuery = gl.createQuery(); + gl.beginQuery(ext.TIME_ELAPSED_EXT, timerQuery); + } + const xrEnabled = renderer.xr.enabled; + renderer.xr.enabled = false; + this.camera.updateMatrixWorld(); + this._r.set(this.width, this.height); + let trueRadius = this.configuration.aoRadius; + if (this.configuration.halfRes && this.configuration.screenSpaceRadius) trueRadius *= 0.5; + if (this.configuration.halfRes) { + renderer.setRenderTarget(this.depthDownsampleTarget); + this.depthDownsampleQuad.material.uniforms.sceneDepth.value = this.beautyRenderTarget.depthTexture; + this.depthDownsampleQuad.material.uniforms.resolution.value = this._r; + this.depthDownsampleQuad.material.uniforms["near"].value = this.camera.near; + this.depthDownsampleQuad.material.uniforms["far"].value = this.camera.far; + this.depthDownsampleQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; + this.depthDownsampleQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; + this.depthDownsampleQuad.material.uniforms["logDepth"].value = this.configuration.logarithmicDepthBuffer; + this.depthDownsampleQuad.render(renderer); + } + this.effectShaderQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture; + this.effectShaderQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; + this.effectShaderQuad.material.uniforms["sceneNormal"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[1] : null; + this.effectShaderQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix; + this.effectShaderQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse; + this.effectShaderQuad.material.uniforms["projViewMat"].value = this.camera.projectionMatrix.clone().multiply(this.camera.matrixWorldInverse.clone()); + this.effectShaderQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; + this.effectShaderQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; + this.effectShaderQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); + this.effectShaderQuad.material.uniforms["resolution"].value = this.configuration.halfRes ? this._r.clone().multiplyScalar(0.5).floor() : this._r; + this.effectShaderQuad.material.uniforms["time"].value = performance.now() / 1000; + this.effectShaderQuad.material.uniforms["samples"].value = this.samples; + this.effectShaderQuad.material.uniforms["bluenoise"].value = this.bluenoise; + this.effectShaderQuad.material.uniforms["radius"].value = trueRadius; + this.effectShaderQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; + this.effectShaderQuad.material.uniforms["near"].value = this.camera.near; + this.effectShaderQuad.material.uniforms["far"].value = this.camera.far; + this.effectShaderQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; + this.effectShaderQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera; + this.effectShaderQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; + // Start the AO + renderer.setRenderTarget(this.writeTargetInternal); + this.effectShaderQuad.render(renderer); + // End the AO + // Start the blur + for(let i = 0; i < this.configuration.denoiseIterations; i++){ + [this.writeTargetInternal, this.readTargetInternal] = [ + this.readTargetInternal, + this.writeTargetInternal + ]; + this.poissonBlurQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.texture; + this.poissonBlurQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; + this.poissonBlurQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix; + this.poissonBlurQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse; + this.poissonBlurQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; + this.poissonBlurQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; + this.poissonBlurQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); + this.poissonBlurQuad.material.uniforms["resolution"].value = this.configuration.halfRes ? this._r.clone().multiplyScalar(0.5).floor() : this._r; + this.poissonBlurQuad.material.uniforms["time"].value = performance.now() / 1000; + this.poissonBlurQuad.material.uniforms["blueNoise"].value = this.bluenoise; + this.poissonBlurQuad.material.uniforms["radius"].value = this.configuration.denoiseRadius * (this.configuration.halfRes ? 0.5 : 1); + this.poissonBlurQuad.material.uniforms["worldRadius"].value = trueRadius; + this.poissonBlurQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; + this.poissonBlurQuad.material.uniforms["index"].value = i; + this.poissonBlurQuad.material.uniforms["poissonDisk"].value = this.samplesDenoise; + this.poissonBlurQuad.material.uniforms["near"].value = this.camera.near; + this.poissonBlurQuad.material.uniforms["far"].value = this.camera.far; + this.poissonBlurQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; + this.poissonBlurQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; + renderer.setRenderTarget(this.writeTargetInternal); + this.poissonBlurQuad.render(renderer); + } + // Now, we have the blurred AO in writeTargetInternal + // End the blur + // Start the composition + if (this.configuration.transparencyAware) { + this.effectCompositerQuad.material.uniforms["transparencyDWFalse"].value = this.transparencyRenderTargetDWFalse.texture; + this.effectCompositerQuad.material.uniforms["transparencyDWTrue"].value = this.transparencyRenderTargetDWTrue.texture; + this.effectCompositerQuad.material.uniforms["transparencyDWTrueDepth"].value = this.transparencyRenderTargetDWTrue.depthTexture; + this.effectCompositerQuad.material.uniforms["transparencyAware"].value = true; + } + this.effectCompositerQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture; + this.effectCompositerQuad.material.uniforms["sceneDepth"].value = this.beautyRenderTarget.depthTexture; + this.effectCompositerQuad.material.uniforms["near"].value = this.camera.near; + this.effectCompositerQuad.material.uniforms["far"].value = this.camera.far; + this.effectCompositerQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse; + this.effectCompositerQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld; + this.effectCompositerQuad.material.uniforms["logDepth"].value = renderer.capabilities.logarithmicDepthBuffer; + this.effectCompositerQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera; + this.effectCompositerQuad.material.uniforms["downsampledDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.texture[0] : this.beautyRenderTarget.depthTexture; + this.effectCompositerQuad.material.uniforms["resolution"].value = this._r; + this.effectCompositerQuad.material.uniforms["blueNoise"].value = this.bluenoise; + this.effectCompositerQuad.material.uniforms["intensity"].value = this.configuration.intensity; + this.effectCompositerQuad.material.uniforms["renderMode"].value = this.configuration.renderMode; + this.effectCompositerQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius; + this.effectCompositerQuad.material.uniforms["radius"].value = trueRadius; + this.effectCompositerQuad.material.uniforms["distanceFalloff"].value = this.configuration.distanceFalloff; + this.effectCompositerQuad.material.uniforms["gammaCorrection"].value = this.configuration.gammaCorrection; + this.effectCompositerQuad.material.uniforms["tDiffuse"].value = this.writeTargetInternal.texture; + this.effectCompositerQuad.material.uniforms["color"].value = this._c.copy(this.configuration.color).convertSRGBToLinear(); + this.effectCompositerQuad.material.uniforms["colorMultiply"].value = this.configuration.colorMultiply; + this.effectCompositerQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new Vector3$1()); + this.effectCompositerQuad.material.uniforms["fog"].value = !!this.scene.fog; + if (this.scene.fog) { + if (this.scene.fog.isFog) { + this.effectCompositerQuad.material.uniforms["fogExp"].value = false; + this.effectCompositerQuad.material.uniforms["fogNear"].value = this.scene.fog.near; + this.effectCompositerQuad.material.uniforms["fogFar"].value = this.scene.fog.far; + } else if (this.scene.fog.isFogExp2) { + this.effectCompositerQuad.material.uniforms["fogExp"].value = true; + this.effectCompositerQuad.material.uniforms["fogDensity"].value = this.scene.fog.density; + } else console.error(`Unsupported fog type ${this.scene.fog.constructor.name} in SSAOPass.`); + } + renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer); + this.effectCompositerQuad.render(renderer); + if (this.debugMode) { + gl.endQuery(ext.TIME_ELAPSED_EXT); + $05f6997e4b65da14$var$checkTimerQuery(timerQuery, gl, this); + } + renderer.xr.enabled = xrEnabled; } - static startMaterialsVector(builder, numElems) { - builder.startVector(4, numElems, 4); + /** + * Enables the debug mode of the AO, meaning the lastTime value will be updated. + */ enableDebugMode() { + this.debugMode = true; } - static addMatrices(builder, matricesOffset) { - builder.addFieldOffset(6, matricesOffset, 0); + /** + * Disables the debug mode of the AO, meaning the lastTime value will not be updated. + */ disableDebugMode() { + this.debugMode = false; } - static createMatricesVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + /** + * Sets the display mode of the AO + * @param {"Combined" | "AO" | "No AO" | "Split" | "Split AO"} mode - The display mode. + */ setDisplayMode(mode) { + this.configuration.renderMode = [ + "Combined", + "AO", + "No AO", + "Split", + "Split AO" + ].indexOf(mode); + } + /** + * + * @param {"Performance" | "Low" | "Medium" | "High" | "Ultra"} mode + */ setQualityMode(mode) { + if (mode === "Performance") { + this.configuration.aoSamples = 8; + this.configuration.denoiseSamples = 4; + this.configuration.denoiseRadius = 12; + } else if (mode === "Low") { + this.configuration.aoSamples = 16; + this.configuration.denoiseSamples = 4; + this.configuration.denoiseRadius = 12; + } else if (mode === "Medium") { + this.configuration.aoSamples = 16; + this.configuration.denoiseSamples = 8; + this.configuration.denoiseRadius = 12; + } else if (mode === "High") { + this.configuration.aoSamples = 64; + this.configuration.denoiseSamples = 8; + this.configuration.denoiseRadius = 6; + } else if (mode === "Ultra") { + this.configuration.aoSamples = 64; + this.configuration.denoiseSamples = 16; + this.configuration.denoiseRadius = 6; } - return builder.endVector(); } - static startMatricesVector(builder, numElems) { - builder.startVector(4, numElems, 4); +} + +/** + * Gamma Correction Shader + * http://en.wikipedia.org/wiki/gamma_correction + */ + +const GammaCorrectionShader = { + + uniforms: { + + 'tDiffuse': { value: null } + + }, + + vertexShader: /* glsl */` + + varying vec2 vUv; + + void main() { + + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + + uniform sampler2D tDiffuse; + + varying vec2 vUv; + + void main() { + + vec4 tex = texture2D( tDiffuse, vUv ); + + gl_FragColor = LinearTosRGB( tex ); + + }` + +}; + +/** + * Object to control the {@link CameraProjection} of the {@link OrthoPerspectiveCamera}. + */ +class ProjectionManager { + get projection() { + return this._currentProjection; } - static addColors(builder, colorsOffset) { - builder.addFieldOffset(7, colorsOffset, 0); + constructor(components, camera) { + this.components = components; + this._previousDistance = -1; + this.matchOrthoDistanceEnabled = false; + this._camera = camera; + const perspective = "Perspective"; + this._currentCamera = camera.get(perspective); + this._currentProjection = perspective; } - static createColorsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); + /** + * Sets the {@link CameraProjection} of the {@link OrthoPerspectiveCamera}. + * + * @param projection - the new projection to set. If it is the current projection, + * it will have no effect. + */ + async setProjection(projection) { + if (this.projection === projection) + return; + if (projection === "Orthographic") { + this.setOrthoCamera(); } - return builder.endVector(); + else { + await this.setPerspectiveCamera(); + } + await this.updateActiveCamera(); } - static startColorsVector(builder, numElems) { - builder.startVector(4, numElems, 4); + setOrthoCamera() { + // Matching orthographic camera to perspective camera + // Resource: https://stackoverflow.com/questions/48758959/what-is-required-to-convert-threejs-perspective-camera-to-orthographic + if (this._camera.currentMode.id === "FirstPerson") { + return; + } + this._previousDistance = this._camera.controls.distance; + this._camera.controls.distance = 200; + const { width, height } = this.getDims(); + this.setupOrthoCamera(height, width); + this._currentCamera = this._camera.get("Orthographic"); + this._currentProjection = "Orthographic"; } - static addIds(builder, idsOffset) { - builder.addFieldOffset(8, idsOffset, 0); + // This small delay is needed to hide weirdness during the transition + async updateActiveCamera() { + await new Promise((resolve) => { + setTimeout(() => { + this._camera.activeCamera = this._currentCamera; + resolve(); + }, 50); + }); } - static addId(builder, idOffset) { - builder.addFieldOffset(9, idOffset, 0); + getDims() { + const lineOfSight = new THREE$1.Vector3(); + this._camera.get("Perspective").getWorldDirection(lineOfSight); + const target = new THREE$1.Vector3(); + this._camera.controls.getTarget(target); + const distance = target + .clone() + .sub(this._camera.get("Perspective").position); + const depth = distance.dot(lineOfSight); + const dims = this.components.renderer.getSize(); + const aspect = dims.x / dims.y; + const camera = this._camera.get("Perspective"); + const height = depth * 2 * Math.atan((camera.fov * (Math.PI / 180)) / 2); + const width = height * aspect; + return { width, height }; } - static addComposites(builder, compositesOffset) { - builder.addFieldOffset(10, compositesOffset, 0); + setupOrthoCamera(height, width) { + this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.ZOOM; + this._camera.controls.mouseButtons.middle = CameraControls.ACTION.ZOOM; + const pCamera = this._camera.get("Perspective"); + const oCamera = this._camera.get("Orthographic"); + oCamera.zoom = 1; + oCamera.left = width / -2; + oCamera.right = width / 2; + oCamera.top = height / 2; + oCamera.bottom = height / -2; + oCamera.updateProjectionMatrix(); + oCamera.position.copy(pCamera.position); + oCamera.quaternion.copy(pCamera.quaternion); + this._camera.controls.camera = oCamera; } - static endFragment(builder) { - const offset = builder.endObject(); - return offset; + getDistance() { + // this handles ortho zoom to perpective distance + const pCamera = this._camera.get("Perspective"); + const oCamera = this._camera.get("Orthographic"); + // this is the reverse of + // const height = depth * 2 * Math.atan((pCamera.fov * (Math.PI / 180)) / 2); + // accounting for zoom + const depth = (oCamera.top - oCamera.bottom) / + oCamera.zoom / + (2 * Math.atan((pCamera.fov * (Math.PI / 180)) / 2)); + return depth; } - static createFragment(builder, positionOffset, normalOffset, indexOffset, blockIdOffset, groupsOffset, materialsOffset, matricesOffset, colorsOffset, idsOffset, idOffset, compositesOffset) { - Fragment.startFragment(builder); - Fragment.addPosition(builder, positionOffset); - Fragment.addNormal(builder, normalOffset); - Fragment.addIndex(builder, indexOffset); - Fragment.addBlockId(builder, blockIdOffset); - Fragment.addGroups(builder, groupsOffset); - Fragment.addMaterials(builder, materialsOffset); - Fragment.addMatrices(builder, matricesOffset); - Fragment.addColors(builder, colorsOffset); - Fragment.addIds(builder, idsOffset); - Fragment.addId(builder, idOffset); - Fragment.addComposites(builder, compositesOffset); - return Fragment.endFragment(builder); + async setPerspectiveCamera() { + this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; + this._camera.controls.mouseButtons.middle = CameraControls.ACTION.DOLLY; + const pCamera = this._camera.get("Perspective"); + const oCamera = this._camera.get("Orthographic"); + pCamera.position.copy(oCamera.position); + pCamera.quaternion.copy(oCamera.quaternion); + this._camera.controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; + if (this.matchOrthoDistanceEnabled) { + this._camera.controls.distance = this.getDistance(); + } + else { + this._camera.controls.distance = this._previousDistance; + } + await this._camera.controls.zoomTo(1); + pCamera.updateProjectionMatrix(); + this._camera.controls.camera = pCamera; + this._currentCamera = pCamera; + this._currentProjection = "Perspective"; } } -// automatically generated by the FlatBuffers compiler, do not modify -let FragmentsGroup$1 = class FragmentsGroup { - constructor() { - this.bb = null; - this.bb_pos = 0; - } - __init(i, bb) { - this.bb_pos = i; - this.bb = bb; - return this; - } - static getRootAsFragmentsGroup(bb, obj) { - return (obj || new FragmentsGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +/** + * A {@link NavigationMode} that allows 3D navigation and panning + * like in many 3D and CAD softwares. + */ +class OrbitMode { + constructor(camera) { + this.camera = camera; + /** {@link NavigationMode.enabled} */ + this.enabled = true; + /** {@link NavigationMode.id} */ + this.id = "Orbit"; + /** {@link NavigationMode.projectionChanged} */ + this.projectionChanged = new Event(); + this.activateOrbitControls(); } - static getSizePrefixedRootAsFragmentsGroup(bb, obj) { - bb.setPosition(bb.position() + SIZE_PREFIX_LENGTH); - return (obj || new FragmentsGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + /** {@link NavigationMode.toggle} */ + toggle(active) { + this.enabled = active; + if (active) { + this.activateOrbitControls(); + } } - items(index, obj) { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? (obj || new Fragment()).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos + offset) + index * 4), this.bb) : null; + activateOrbitControls() { + const controls = this.camera.controls; + controls.minDistance = 1; + controls.maxDistance = 300; + const position = new THREE$1.Vector3(); + controls.getPosition(position); + const distance = position.length(); + controls.distance = distance; + controls.truckSpeed = 2; + const { rotation } = this.camera.get(); + const direction = new THREE$1.Vector3(0, 0, -1).applyEuler(rotation); + const target = position.addScaledVector(direction, distance); + controls.moveTo(target.x, target.y, target.z); } - itemsLength() { - const offset = this.bb.__offset(this.bb_pos, 4); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +} + +/** + * A {@link NavigationMode} that allows first person navigation, + * simulating FPS video games. + */ +class FirstPersonMode { + constructor(camera) { + this.camera = camera; + /** {@link NavigationMode.enabled} */ + this.enabled = false; + /** {@link NavigationMode.id} */ + this.id = "FirstPerson"; + /** {@link NavigationMode.projectionChanged} */ + this.projectionChanged = new Event(); } - civil(obj) { - const offset = this.bb.__offset(this.bb_pos, 6); - return offset ? (obj || new Civil()).__init(this.bb.__indirect(this.bb_pos + offset), this.bb) : null; + /** {@link NavigationMode.toggle} */ + toggle(active) { + this.enabled = active; + if (active) { + const projection = this.camera.getProjection(); + if (projection !== "Perspective") { + this.camera.setNavigationMode("Orbit"); + return; + } + this.setupFirstPersonCamera(); + } } - coordinationMatrix(index) { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + setupFirstPersonCamera() { + const controls = this.camera.controls; + const newTargetPosition = new THREE$1.Vector3(); + controls.distance--; + controls.getPosition(newTargetPosition); + controls.minDistance = 1; + controls.maxDistance = 1; + controls.distance = 1; + controls.moveTo(newTargetPosition.x, newTargetPosition.y, newTargetPosition.z); + controls.truckSpeed = 50; + controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; + controls.touches.two = CameraControls.ACTION.TOUCH_ZOOM_TRUCK; } - coordinationMatrixLength() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; +} + +/** + * A {@link NavigationMode} that allows to navigate floorplans in 2D, + * like many BIM tools. + */ +class PlanMode { + constructor(camera) { + this.camera = camera; + /** {@link NavigationMode.enabled} */ + this.enabled = false; + /** {@link NavigationMode.id} */ + this.id = "Plan"; + /** {@link NavigationMode.projectionChanged} */ + this.projectionChanged = new Event(); + this.mouseInitialized = false; + this.defaultAzimuthSpeed = camera.controls.azimuthRotateSpeed; + this.defaultPolarSpeed = camera.controls.polarRotateSpeed; } - coordinationMatrixArray() { - const offset = this.bb.__offset(this.bb_pos, 8); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + /** {@link NavigationMode.toggle} */ + toggle(active) { + this.enabled = active; + const controls = this.camera.controls; + controls.azimuthRotateSpeed = active ? 0 : this.defaultAzimuthSpeed; + controls.polarRotateSpeed = active ? 0 : this.defaultPolarSpeed; + if (!this.mouseInitialized) { + this.mouseAction1 = controls.touches.one; + this.mouseAction2 = controls.touches.two; + this.mouseInitialized = true; + } + if (active) { + controls.mouseButtons.left = CameraControls.ACTION.TRUCK; + controls.touches.one = CameraControls.ACTION.TOUCH_TRUCK; + controls.touches.two = CameraControls.ACTION.TOUCH_ZOOM; + } + else { + controls.mouseButtons.left = CameraControls.ACTION.ROTATE; + controls.touches.one = this.mouseAction1; + controls.touches.two = this.mouseAction2; + } } - ids(index) { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; +} + +/** + * A flexible camera that uses + * [yomotsu's cameracontrols](https://github.com/yomotsu/camera-controls) to + * easily control the camera in 2D and 3D. It supports multiple navigation + * modes, such as 2D floor plan navigation, first person and 3D orbit. + */ +class OrthoPerspectiveCamera extends SimpleCamera { + constructor(components) { + super(components); + /** + * Event that fires when the {@link CameraProjection} changes. + */ + this.projectionChanged = new Event(); + this._userInputButtons = {}; + this._frustumSize = 50; + this._navigationModes = new Map(); + this.uiElement = new UIElement(); + this._orthoCamera = this.newOrthoCamera(); + this._navigationModes.set("Orbit", new OrbitMode(this)); + this._navigationModes.set("FirstPerson", new FirstPersonMode(this)); + this._navigationModes.set("Plan", new PlanMode(this)); + this.currentMode = this._navigationModes.get("Orbit"); + this.currentMode.toggle(true, { preventTargetAdjustment: true }); + this.toggleEvents(true); + this._projectionManager = new ProjectionManager(components, this); + components.onInitialized.add(() => { + if (components.uiEnabled) + this.setUI(); + }); + this.onAspectUpdated.add(() => this.setOrthoCameraAspect()); } - idsLength() { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + setUI() { + const mainButton = new Button(this.components); + mainButton.materialIcon = "video_camera_back"; + mainButton.tooltip = "Camera"; + const projection = new Button(this.components, { + materialIconName: "camera", + name: "Projection", + }); + const perspective = new Button(this.components, { name: "Perspective" }); + perspective.active = true; + perspective.onClick.add(() => this.setProjection("Perspective")); + const orthographic = new Button(this.components, { name: "Orthographic" }); + orthographic.onClick.add(() => this.setProjection("Orthographic")); + projection.addChild(perspective, orthographic); + const navigation = new Button(this.components, { + materialIconName: "open_with", + name: "Navigation", + }); + const orbit = new Button(this.components, { name: "Orbit Around" }); + orbit.onClick.add(() => this.setNavigationMode("Orbit")); + const plan = new Button(this.components, { name: "Plan View" }); + plan.onClick.add(() => this.setNavigationMode("Plan")); + const firstPerson = new Button(this.components, { name: "First person" }); + firstPerson.onClick.add(() => this.setNavigationMode("FirstPerson")); + navigation.addChild(orbit, plan, firstPerson); + mainButton.addChild(navigation, projection); + this.projectionChanged.add((camera) => { + if (camera instanceof THREE$1.PerspectiveCamera) { + perspective.active = true; + orthographic.active = false; + } + else { + perspective.active = false; + orthographic.active = true; + } + }); + this.uiElement.set({ main: mainButton }); } - idsArray() { - const offset = this.bb.__offset(this.bb_pos, 10); - return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + /** {@link Disposable.dispose} */ + async dispose() { + await super.dispose(); + this.toggleEvents(false); + this._orthoCamera.removeFromParent(); } - itemsKeys(index) { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + /** + * Similar to {@link Component.get}, but with an optional argument + * to specify which camera to get. + * + * @param projection - The camera corresponding to the + * {@link CameraProjection} specified. If no projection is specified, + * the active camera will be returned. + */ + get(projection) { + if (!projection) { + return this.activeCamera; + } + return projection === "Orthographic" + ? this._orthoCamera + : this._perspectiveCamera; } - itemsKeysLength() { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + /** Returns the current {@link CameraProjection}. */ + getProjection() { + return this._projectionManager.projection; } - itemsKeysArray() { - const offset = this.bb.__offset(this.bb_pos, 12); - return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + /** Match Ortho zoom with Perspective distance when changing projection mode */ + set matchOrthoDistanceEnabled(value) { + this._projectionManager.matchOrthoDistanceEnabled = value; } - itemsKeysIndices(index) { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + /** + * Changes the current {@link CameraProjection} from Ortographic to Perspective + * and Viceversa. + */ + async toggleProjection() { + const projection = this.getProjection(); + const newProjection = projection === "Perspective" ? "Orthographic" : "Perspective"; + await this.setProjection(newProjection); } - itemsKeysIndicesLength() { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + /** + * Sets the current {@link CameraProjection}. This triggers the event + * {@link projectionChanged}. + * + * @param projection - The new {@link CameraProjection} to set. + */ + async setProjection(projection) { + await this._projectionManager.setProjection(projection); + await this.projectionChanged.trigger(this.activeCamera); } - itemsKeysIndicesArray() { - const offset = this.bb.__offset(this.bb_pos, 14); - return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + /** + * Allows or prevents all user input. + * + * @param active - whether to enable or disable user inputs. + */ + toggleUserInput(active) { + if (active) { + this.enableUserInput(); + } + else { + this.disableUserInput(); + } } - itemsRels(index) { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + /** + * Sets a new {@link NavigationMode} and disables the previous one. + * + * @param mode - The {@link NavigationMode} to set. + */ + setNavigationMode(mode) { + if (this.currentMode.id === mode) + return; + this.currentMode.toggle(false); + if (!this._navigationModes.has(mode)) { + throw new Error("The specified mode does not exist!"); + } + this.currentMode = this._navigationModes.get(mode); + this.currentMode.toggle(true); } - itemsRelsLength() { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + /** + * Make the camera view fit all the specified meshes. + * + * @param meshes the meshes to fit. If it is not defined, it will + * evaluate {@link Components.meshes}. + * @param offset the distance to the fit object + */ + async fit(meshes = this.components.meshes, offset = 1.5) { + if (!this.enabled) + return; + const maxNum = Number.MAX_VALUE; + const minNum = Number.MIN_VALUE; + const min = new THREE$1.Vector3(maxNum, maxNum, maxNum); + const max = new THREE$1.Vector3(minNum, minNum, minNum); + for (const mesh of meshes) { + const box = new THREE$1.Box3().setFromObject(mesh); + if (box.min.x < min.x) + min.x = box.min.x; + if (box.min.y < min.y) + min.y = box.min.y; + if (box.min.z < min.z) + min.z = box.min.z; + if (box.max.x > max.x) + max.x = box.max.x; + if (box.max.y > max.y) + max.y = box.max.y; + if (box.max.z > max.z) + max.z = box.max.z; + } + const box = new THREE$1.Box3(min, max); + const sceneSize = new THREE$1.Vector3(); + box.getSize(sceneSize); + const sceneCenter = new THREE$1.Vector3(); + box.getCenter(sceneCenter); + const radius = Math.max(sceneSize.x, sceneSize.y, sceneSize.z) * offset; + const sphere = new THREE$1.Sphere(sceneCenter, radius); + await this.controls.fitToSphere(sphere, true); } - itemsRelsArray() { - const offset = this.bb.__offset(this.bb_pos, 16); - return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + disableUserInput() { + this._userInputButtons.left = this.controls.mouseButtons.left; + this._userInputButtons.right = this.controls.mouseButtons.right; + this._userInputButtons.middle = this.controls.mouseButtons.middle; + this._userInputButtons.wheel = this.controls.mouseButtons.wheel; + this.controls.mouseButtons.left = 0; + this.controls.mouseButtons.right = 0; + this.controls.mouseButtons.middle = 0; + this.controls.mouseButtons.wheel = 0; } - itemsRelsIndices(index) { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? this.bb.readUint32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + enableUserInput() { + if (Object.keys(this._userInputButtons).length === 0) + return; + this.controls.mouseButtons.left = this._userInputButtons.left; + this.controls.mouseButtons.right = this._userInputButtons.right; + this.controls.mouseButtons.middle = this._userInputButtons.middle; + this.controls.mouseButtons.wheel = this._userInputButtons.wheel; } - itemsRelsIndicesLength() { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + newOrthoCamera() { + const dims = this.components.renderer.getSize(); + const aspect = dims.x / dims.y; + return new THREE$1.OrthographicCamera((this._frustumSize * aspect) / -2, (this._frustumSize * aspect) / 2, this._frustumSize / 2, this._frustumSize / -2, 0.1, 1000); } - itemsRelsIndicesArray() { - const offset = this.bb.__offset(this.bb_pos, 18); - return offset ? new Uint32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + setOrthoCameraAspect() { + const size = this.components.renderer.getSize(); + const aspect = size.x / size.y; + this._orthoCamera.left = (-this._frustumSize * aspect) / 2; + this._orthoCamera.right = (this._frustumSize * aspect) / 2; + this._orthoCamera.top = this._frustumSize / 2; + this._orthoCamera.bottom = -this._frustumSize / 2; + this._orthoCamera.updateProjectionMatrix(); } - fragmentKeys(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 20); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + toggleEvents(active) { + const modes = Object.values(this._navigationModes); + for (const mode of modes) { + if (active) { + mode.projectionChanged.on(this.projectionChanged.trigger); + } + else { + mode.projectionChanged.reset(); + } + } } - id(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 22); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +// Gets the plane information (ax + by + cz = d) of each face, where: +// - (a, b, c) is the normal vector of the plane +// - d is the signed distance to the origin +function getPlaneDistanceMaterial() { + return new THREE$1.ShaderMaterial({ + side: 2, + clipping: true, + uniforms: {}, + vertexShader: ` + varying vec4 vColor; + + #include + + void main() { + #include + + vec4 absPosition = vec4(position, 1.0); + vec3 trueNormal = normal; + + #ifdef USE_INSTANCING + absPosition = instanceMatrix * absPosition; + trueNormal = (instanceMatrix * vec4(normal, 0.)).xyz; + #endif + + absPosition = modelMatrix * absPosition; + trueNormal = (normalize(modelMatrix * vec4(trueNormal, 0.))).xyz; + + vec3 planePosition = absPosition.xyz / 40.; + float d = abs(dot(trueNormal, planePosition)); + vColor = vec4(abs(trueNormal), d); + gl_Position = projectionMatrix * viewMatrix * absPosition; + + #include + #include } - name(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 24); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + `, + fragmentShader: ` + varying vec4 vColor; + + #include + + void main() { + #include + gl_FragColor = vColor; } - ifcName(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 26); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + `, + }); +} + +// Gets the plane information (ax + by + cz = d) of each face, where: +// - (a, b, c) is the normal vector of the plane +// - d is the signed distance to the origin +function getProjectedNormalMaterial() { + return new THREE$1.ShaderMaterial({ + side: 2, + clipping: true, + uniforms: {}, + vertexShader: ` + varying vec3 vCameraPosition; + varying vec3 vPosition; + varying vec3 vNormal; + + #include + + void main() { + #include + + vec4 absPosition = vec4(position, 1.0); + vNormal = normal; + + #ifdef USE_INSTANCING + absPosition = instanceMatrix * absPosition; + vNormal = (instanceMatrix * vec4(normal, 0.)).xyz; + #endif + + absPosition = modelMatrix * absPosition; + vNormal = (normalize(modelMatrix * vec4(vNormal, 0.))).xyz; + + gl_Position = projectionMatrix * viewMatrix * absPosition; + + vCameraPosition = cameraPosition; + vPosition = absPosition.xyz; + + #include + #include } - ifcDescription(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 28); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + `, + fragmentShader: ` + varying vec3 vCameraPosition; + varying vec3 vPosition; + varying vec3 vNormal; + + #include + + void main() { + #include + vec3 cameraPixelVec = normalize(vCameraPosition - vPosition); + float difference = abs(dot(vNormal, cameraPixelVec)); + + // This achieves a double gloss effect: when the surface is perpendicular and when it's parallel + difference = abs((difference * 2.) - 1.); + + gl_FragColor = vec4(difference, difference, difference, 1.); } - ifcSchema(optionalEncoding) { - const offset = this.bb.__offset(this.bb_pos, 30); - return offset ? this.bb.__string(this.bb_pos + offset, optionalEncoding) : null; + `, + }); +} + +// Follows the structure of +// https://github.com/mrdoob/three.js/blob/master/examples/jsm/postprocessing/OutlinePass.js +class CustomEffectsPass extends Pass { + get lineColor() { + return this._lineColor; } - maxExpressId() { - const offset = this.bb.__offset(this.bb_pos, 32); - return offset ? this.bb.readUint32(this.bb_pos + offset) : 0; + set lineColor(lineColor) { + this._lineColor = lineColor; + const material = this.fsQuad.material; + material.uniforms.lineColor.value.set(lineColor); } - boundingBox(index) { - const offset = this.bb.__offset(this.bb_pos, 34); - return offset ? this.bb.readFloat32(this.bb.__vector(this.bb_pos + offset) + index * 4) : 0; + get tolerance() { + return this._tolerance; } - boundingBoxLength() { - const offset = this.bb.__offset(this.bb_pos, 34); - return offset ? this.bb.__vector_len(this.bb_pos + offset) : 0; + set tolerance(value) { + this._tolerance = value; + const material = this.fsQuad.material; + material.uniforms.tolerance.value = value; } - boundingBoxArray() { - const offset = this.bb.__offset(this.bb_pos, 34); - return offset ? new Float32Array(this.bb.bytes().buffer, this.bb.bytes().byteOffset + this.bb.__vector(this.bb_pos + offset), this.bb.__vector_len(this.bb_pos + offset)) : null; + get opacity() { + return this._opacity; } - static startFragmentsGroup(builder) { - builder.startObject(16); + set opacity(value) { + this._opacity = value; + const material = this.fsQuad.material; + material.uniforms.opacity.value = value; } - static addItems(builder, itemsOffset) { - builder.addFieldOffset(0, itemsOffset, 0); + get glossEnabled() { + return this._glossEnabled; } - static createItemsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addOffset(data[i]); - } - return builder.endVector(); + set glossEnabled(active) { + if (active === this._glossEnabled) + return; + this._glossEnabled = active; + const material = this.fsQuad.material; + material.uniforms.glossEnabled.value = active ? 1 : 0; } - static startItemsVector(builder, numElems) { - builder.startVector(4, numElems, 4); + get glossExponent() { + return this._glossExponent; } - static addCivil(builder, civilOffset) { - builder.addFieldOffset(1, civilOffset, 0); + set glossExponent(value) { + this._glossExponent = value; + const material = this.fsQuad.material; + material.uniforms.glossExponent.value = value; } - static addCoordinationMatrix(builder, coordinationMatrixOffset) { - builder.addFieldOffset(2, coordinationMatrixOffset, 0); + get minGloss() { + return this._minGloss; } - static createCoordinationMatrixVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); - } - return builder.endVector(); + set minGloss(value) { + this._minGloss = value; + const material = this.fsQuad.material; + material.uniforms.minGloss.value = value; } - static startCoordinationMatrixVector(builder, numElems) { - builder.startVector(4, numElems, 4); + get maxGloss() { + new THREE$1.MeshBasicMaterial().color.convertLinearToSRGB(); + return this._maxGloss; } - static addIds(builder, idsOffset) { - builder.addFieldOffset(3, idsOffset, 0); + set maxGloss(value) { + this._maxGloss = value; + const material = this.fsQuad.material; + material.uniforms.maxGloss.value = value; } - static createIdsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); - } - return builder.endVector(); + get outlineEnabled() { + return this._outlineEnabled; } - static startIdsVector(builder, numElems) { - builder.startVector(4, numElems, 4); + set outlineEnabled(active) { + if (active === this._outlineEnabled) + return; + this._outlineEnabled = active; + const material = this.fsQuad.material; + material.uniforms.outlineEnabled.value = active ? 1 : 0; } - static addItemsKeys(builder, itemsKeysOffset) { - builder.addFieldOffset(4, itemsKeysOffset, 0); + constructor(resolution, components, scene, camera) { + super(); + this.excludedMeshes = []; + this.outlinedMeshes = {}; + this._outlineScene = new THREE$1.Scene(); + this._outlineEnabled = false; + this._lineColor = 0x999999; + this._opacity = 0.4; + this._tolerance = 3; + this._glossEnabled = true; + this._glossExponent = 1.9; + this._minGloss = -0.1; + this._maxGloss = 0.1; + this._outlinesNeedsUpdate = false; + this.components = components; + this.renderScene = scene; + this.renderCamera = camera; + this.resolution = new THREE$1.Vector2(resolution.x, resolution.y); + this.fsQuad = new FullScreenQuad(); + this.fsQuad.material = this.createOutlinePostProcessMaterial(); + this.planeBuffer = this.newRenderTarget(); + this.glossBuffer = this.newRenderTarget(); + this.outlineBuffer = this.newRenderTarget(); + const normalMaterial = getPlaneDistanceMaterial(); + normalMaterial.clippingPlanes = components.renderer.clippingPlanes; + this.normalOverrideMaterial = normalMaterial; + const glossMaterial = getProjectedNormalMaterial(); + glossMaterial.clippingPlanes = components.renderer.clippingPlanes; + this.glossOverrideMaterial = glossMaterial; } - static createItemsKeysVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + async dispose() { + this.planeBuffer.dispose(); + this.glossBuffer.dispose(); + this.outlineBuffer.dispose(); + this.normalOverrideMaterial.dispose(); + this.glossOverrideMaterial.dispose(); + this.fsQuad.dispose(); + this.excludedMeshes = []; + this._outlineScene.children = []; + const disposer = await this.components.tools.get(Disposer); + for (const name in this.outlinedMeshes) { + const style = this.outlinedMeshes[name]; + for (const mesh of style.meshes) { + disposer.destroy(mesh, true, true); + } + style.material.dispose(); } - return builder.endVector(); - } - static startItemsKeysVector(builder, numElems) { - builder.startVector(4, numElems, 4); } - static addItemsKeysIndices(builder, itemsKeysIndicesOffset) { - builder.addFieldOffset(5, itemsKeysIndicesOffset, 0); + setSize(width, height) { + this.planeBuffer.setSize(width, height); + this.glossBuffer.setSize(width, height); + this.outlineBuffer.setSize(width, height); + this.resolution.set(width, height); + const material = this.fsQuad.material; + material.uniforms.screenSize.value.set(this.resolution.x, this.resolution.y, 1 / this.resolution.x, 1 / this.resolution.y); } - static createItemsKeysIndicesVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + render(renderer, writeBuffer, readBuffer) { + // Turn off writing to the depth buffer + // because we need to read from it in the subsequent passes. + const depthBufferValue = writeBuffer.depthBuffer; + writeBuffer.depthBuffer = false; + // 1. Re-render the scene to capture all normals in a texture. + const previousOverrideMaterial = this.renderScene.overrideMaterial; + const previousBackground = this.renderScene.background; + this.renderScene.background = null; + for (const mesh of this.excludedMeshes) { + mesh.visible = false; } - return builder.endVector(); - } - static startItemsKeysIndicesVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static addItemsRels(builder, itemsRelsOffset) { - builder.addFieldOffset(6, itemsRelsOffset, 0); - } - static createItemsRelsVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + // Render normal pass + renderer.setRenderTarget(this.planeBuffer); + this.renderScene.overrideMaterial = this.normalOverrideMaterial; + renderer.render(this.renderScene, this.renderCamera); + // Render gloss pass + if (this._glossEnabled) { + renderer.setRenderTarget(this.glossBuffer); + this.renderScene.overrideMaterial = this.glossOverrideMaterial; + renderer.render(this.renderScene, this.renderCamera); } - return builder.endVector(); - } - static startItemsRelsVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static addItemsRelsIndices(builder, itemsRelsIndicesOffset) { - builder.addFieldOffset(7, itemsRelsIndicesOffset, 0); - } - static createItemsRelsIndicesVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addInt32(data[i]); + this.renderScene.overrideMaterial = previousOverrideMaterial; + // Render outline pass + if (this._outlineEnabled) { + let outlinedMeshesFound = false; + for (const name in this.outlinedMeshes) { + const style = this.outlinedMeshes[name]; + for (const mesh of style.meshes) { + outlinedMeshesFound = true; + mesh.userData.materialPreOutline = mesh.material; + mesh.material = style.material; + mesh.userData.groupsPreOutline = mesh.geometry.groups; + mesh.geometry.groups = []; + if (mesh instanceof THREE$1.InstancedMesh) { + mesh.userData.colorPreOutline = mesh.instanceColor; + mesh.instanceColor = null; + } + mesh.userData.parentPreOutline = mesh.parent; + this._outlineScene.add(mesh); + } + } + // This way, when there are no outlines meshes, it clears the outlines buffer only once + // and then skips this render + if (outlinedMeshesFound || this._outlinesNeedsUpdate) { + renderer.setRenderTarget(this.outlineBuffer); + renderer.render(this._outlineScene, this.renderCamera); + this._outlinesNeedsUpdate = outlinedMeshesFound; + } + for (const name in this.outlinedMeshes) { + const style = this.outlinedMeshes[name]; + for (const mesh of style.meshes) { + mesh.material = mesh.userData.materialPreOutline; + mesh.geometry.groups = mesh.userData.groupsPreOutline; + if (mesh instanceof THREE$1.InstancedMesh) { + mesh.instanceColor = mesh.userData.colorPreOutline; + } + if (mesh.userData.parentPreOutline) { + mesh.userData.parentPreOutline.add(mesh); + } + mesh.userData.materialPreOutline = undefined; + mesh.userData.groupsPreOutline = undefined; + mesh.userData.colorPreOutline = undefined; + mesh.userData.parentPreOutline = undefined; + } + } } - return builder.endVector(); - } - static startItemsRelsIndicesVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static addFragmentKeys(builder, fragmentKeysOffset) { - builder.addFieldOffset(8, fragmentKeysOffset, 0); - } - static addId(builder, idOffset) { - builder.addFieldOffset(9, idOffset, 0); - } - static addName(builder, nameOffset) { - builder.addFieldOffset(10, nameOffset, 0); - } - static addIfcName(builder, ifcNameOffset) { - builder.addFieldOffset(11, ifcNameOffset, 0); + for (const mesh of this.excludedMeshes) { + mesh.visible = true; + } + this.renderScene.background = previousBackground; + const material = this.fsQuad.material; + material.uniforms.planeBuffer.value = this.planeBuffer.texture; + material.uniforms.glossBuffer.value = this.glossBuffer.texture; + material.uniforms.outlineBuffer.value = this.outlineBuffer.texture; + material.uniforms.sceneColorBuffer.value = readBuffer.texture; + if (this.renderToScreen) { + // If this is the last effect, then renderToScreen is true. + // So we should render to the screen by setting target null + // Otherwise, just render into the writeBuffer that the next effect will use as its read buffer. + renderer.setRenderTarget(null); + this.fsQuad.render(renderer); + } + else { + renderer.setRenderTarget(writeBuffer); + this.fsQuad.render(renderer); + } + // Reset the depthBuffer value so we continue writing to it in the next render. + writeBuffer.depthBuffer = depthBufferValue; } - static addIfcDescription(builder, ifcDescriptionOffset) { - builder.addFieldOffset(12, ifcDescriptionOffset, 0); + get vertexShader() { + return ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `; } - static addIfcSchema(builder, ifcSchemaOffset) { - builder.addFieldOffset(13, ifcSchemaOffset, 0); + get fragmentShader() { + return ` + uniform sampler2D sceneColorBuffer; + uniform sampler2D planeBuffer; + uniform sampler2D glossBuffer; + uniform sampler2D outlineBuffer; + uniform vec4 screenSize; + uniform vec3 lineColor; + + uniform float outlineEnabled; + + uniform int width; + uniform float opacity; + uniform float tolerance; + uniform float glossExponent; + uniform float minGloss; + uniform float maxGloss; + uniform float glossEnabled; + + varying vec2 vUv; + + vec4 getValue(sampler2D buffer, int x, int y) { + return texture2D(buffer, vUv + screenSize.zw * vec2(x, y)); + } + + float normalDiff(vec3 normal1, vec3 normal2) { + return ((dot(normal1, normal2) - 1.) * -1.) / 2.; + } + + // Returns 0 if it's background, 1 if it's not + float getIsBackground(vec3 normal) { + float background = 1.0; + background *= step(normal.x, 0.); + background *= step(normal.y, 0.); + background *= step(normal.z, 0.); + background = (background - 1.) * -1.; + return background; + } + + void main() { + + vec4 sceneColor = getValue(sceneColorBuffer, 0, 0); + vec3 normSceneColor = normalize(sceneColor.rgb); + + vec4 plane = getValue(planeBuffer, 0, 0); + vec3 normal = plane.xyz; + float distance = plane.w; + + vec3 normalTop = getValue(planeBuffer, 0, width).rgb; + vec3 normalBottom = getValue(planeBuffer, 0, -width).rgb; + vec3 normalRight = getValue(planeBuffer, width, 0).rgb; + vec3 normalLeft = getValue(planeBuffer, -width, 0).rgb; + vec3 normalTopRight = getValue(planeBuffer, width, width).rgb; + vec3 normalTopLeft = getValue(planeBuffer, -width, width).rgb; + vec3 normalBottomRight = getValue(planeBuffer, width, -width).rgb; + vec3 normalBottomLeft = getValue(planeBuffer, -width, -width).rgb; + + float distanceTop = getValue(planeBuffer, 0, width).a; + float distanceBottom = getValue(planeBuffer, 0, -width).a; + float distanceRight = getValue(planeBuffer, width, 0).a; + float distanceLeft = getValue(planeBuffer, -width, 0).a; + float distanceTopRight = getValue(planeBuffer, width, width).a; + float distanceTopLeft = getValue(planeBuffer, -width, width).a; + float distanceBottomRight = getValue(planeBuffer, width, -width).a; + float distanceBottomLeft = getValue(planeBuffer, -width, -width).a; + + vec3 sceneColorTop = normalize(getValue(sceneColorBuffer, 1, 0).rgb); + vec3 sceneColorBottom = normalize(getValue(sceneColorBuffer, -1, 0).rgb); + vec3 sceneColorLeft = normalize(getValue(sceneColorBuffer, 0, -1).rgb); + vec3 sceneColorRight = normalize(getValue(sceneColorBuffer, 0, 1).rgb); + vec3 sceneColorTopRight = normalize(getValue(sceneColorBuffer, 1, 1).rgb); + vec3 sceneColorBottomRight = normalize(getValue(sceneColorBuffer, -1, 1).rgb); + vec3 sceneColorTopLeft = normalize(getValue(sceneColorBuffer, 1, 1).rgb); + vec3 sceneColorBottomLeft = normalize(getValue(sceneColorBuffer, -1, 1).rgb); + + // Checks if the planes of this texel and the neighbour texels are different + + float planeDiff = 0.0; + + planeDiff += step(0.001, normalDiff(normal, normalTop)); + planeDiff += step(0.001, normalDiff(normal, normalBottom)); + planeDiff += step(0.001, normalDiff(normal, normalLeft)); + planeDiff += step(0.001, normalDiff(normal, normalRight)); + planeDiff += step(0.001, normalDiff(normal, normalTopRight)); + planeDiff += step(0.001, normalDiff(normal, normalTopLeft)); + planeDiff += step(0.001, normalDiff(normal, normalBottomRight)); + planeDiff += step(0.001, normalDiff(normal, normalBottomLeft)); + + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTop)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottom)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorLeft)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorRight)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTopRight)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorTopLeft)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottomRight)); + planeDiff += step(0.001, normalDiff(normSceneColor, sceneColorBottomLeft)); + + planeDiff += step(0.001, abs(distance - distanceTop)); + planeDiff += step(0.001, abs(distance - distanceBottom)); + planeDiff += step(0.001, abs(distance - distanceLeft)); + planeDiff += step(0.001, abs(distance - distanceRight)); + planeDiff += step(0.001, abs(distance - distanceTopRight)); + planeDiff += step(0.001, abs(distance - distanceTopLeft)); + planeDiff += step(0.001, abs(distance - distanceBottomRight)); + planeDiff += step(0.001, abs(distance - distanceBottomLeft)); + + // Add extra background outline + + int width2 = width + 1; + vec3 normalTop2 = getValue(planeBuffer, 0, width2).rgb; + vec3 normalBottom2 = getValue(planeBuffer, 0, -width2).rgb; + vec3 normalRight2 = getValue(planeBuffer, width2, 0).rgb; + vec3 normalLeft2 = getValue(planeBuffer, -width2, 0).rgb; + vec3 normalTopRight2 = getValue(planeBuffer, width2, width2).rgb; + vec3 normalTopLeft2 = getValue(planeBuffer, -width2, width2).rgb; + vec3 normalBottomRight2 = getValue(planeBuffer, width2, -width2).rgb; + vec3 normalBottomLeft2 = getValue(planeBuffer, -width2, -width2).rgb; + + planeDiff += -(getIsBackground(normalTop2) - 1.); + planeDiff += -(getIsBackground(normalBottom2) - 1.); + planeDiff += -(getIsBackground(normalRight2) - 1.); + planeDiff += -(getIsBackground(normalLeft2) - 1.); + planeDiff += -(getIsBackground(normalTopRight2) - 1.); + planeDiff += -(getIsBackground(normalBottomRight2) - 1.); + planeDiff += -(getIsBackground(normalBottomRight2) - 1.); + planeDiff += -(getIsBackground(normalBottomLeft2) - 1.); + + // Tolerance sets the minimum amount of differences to consider + // this texel an edge + + float line = step(tolerance, planeDiff); + + // Exclude background and apply opacity + + float background = getIsBackground(normal); + line *= background; + line *= opacity; + + // Add gloss + + vec3 gloss = getValue(glossBuffer, 0, 0).xyz; + float diffGloss = abs(maxGloss - minGloss); + vec3 glossExpVector = vec3(glossExponent,glossExponent,glossExponent); + gloss = min(pow(gloss, glossExpVector), vec3(1.,1.,1.)); + gloss *= diffGloss; + gloss += minGloss; + vec4 glossedColor = sceneColor + vec4(gloss, 1.) * glossEnabled; + + vec4 corrected = mix(sceneColor, glossedColor, background); + + // Draw lines + + corrected = mix(corrected, vec4(lineColor, 1.), line); + + // Add outline + + vec4 outlinePreview =getValue(outlineBuffer, 0, 0); + float outlineColorCorrection = 1. / max(0.2, outlinePreview.a); + vec3 outlineColor = outlinePreview.rgb * outlineColorCorrection; + + // thickness between 10 and 2, opacity between 1 and 0.2 + int outlineThickness = int(outlinePreview.a * 10.); + + float outlineDiff = 0.; + + outlineDiff += step(0.1, getValue(outlineBuffer, 0, 0).a); + outlineDiff += step(0.1, getValue(outlineBuffer, 1, 0).a); + outlineDiff += step(0.1, getValue(outlineBuffer, -1, 0).a); + outlineDiff += step(0.1, getValue(outlineBuffer, 0, -1).a); + outlineDiff += step(0.1, getValue(outlineBuffer, 0, 1).a); + outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, 0).a); + outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, 0).a); + outlineDiff += step(0.1, getValue(outlineBuffer, 0, -outlineThickness).a); + outlineDiff += step(0.1, getValue(outlineBuffer, 0, outlineThickness).a); + outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, outlineThickness).a); + outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, outlineThickness).a); + outlineDiff += step(0.1, getValue(outlineBuffer, -outlineThickness, -outlineThickness).a); + outlineDiff += step(0.1, getValue(outlineBuffer, outlineThickness, -outlineThickness).a); + + float outLine = step(4., outlineDiff) * step(outlineDiff, 12.) * outlineEnabled; + corrected = mix(corrected, vec4(outlineColor, 1.), outLine); + + gl_FragColor = corrected; + } + `; } - static addMaxExpressId(builder, maxExpressId) { - builder.addFieldInt32(14, maxExpressId, 0); + createOutlinePostProcessMaterial() { + return new THREE$1.ShaderMaterial({ + uniforms: { + opacity: { value: this._opacity }, + debugVisualize: { value: 0 }, + sceneColorBuffer: { value: null }, + tolerance: { value: this._tolerance }, + planeBuffer: { value: null }, + glossBuffer: { value: null }, + outlineBuffer: { value: null }, + glossEnabled: { value: 1 }, + minGloss: { value: this._minGloss }, + maxGloss: { value: this._maxGloss }, + outlineEnabled: { value: 0 }, + glossExponent: { value: this._glossExponent }, + width: { value: 1 }, + lineColor: { value: new THREE$1.Color(this._lineColor) }, + screenSize: { + value: new THREE$1.Vector4(this.resolution.x, this.resolution.y, 1 / this.resolution.x, 1 / this.resolution.y), + }, + }, + vertexShader: this.vertexShader, + fragmentShader: this.fragmentShader, + }); } - static addBoundingBox(builder, boundingBoxOffset) { - builder.addFieldOffset(15, boundingBoxOffset, 0); + newRenderTarget() { + const planeBuffer = new THREE$1.WebGLRenderTarget(this.resolution.x, this.resolution.y); + planeBuffer.texture.colorSpace = "srgb-linear"; + planeBuffer.texture.format = THREE$1.RGBAFormat; + planeBuffer.texture.type = THREE$1.HalfFloatType; + planeBuffer.texture.minFilter = THREE$1.NearestFilter; + planeBuffer.texture.magFilter = THREE$1.NearestFilter; + planeBuffer.texture.generateMipmaps = false; + planeBuffer.stencilBuffer = false; + return planeBuffer; } - static createBoundingBoxVector(builder, data) { - builder.startVector(4, data.length, 4); - for (let i = data.length - 1; i >= 0; i--) { - builder.addFloat32(data[i]); +} + +// source: https://discourse.threejs.org/t/how-to-render-full-outlines-as-a-post-process-tutorial/22674 +class Postproduction { + get basePass() { + if (!this._basePass) { + throw new Error("Custom effects not initialized!"); } - return builder.endVector(); - } - static startBoundingBoxVector(builder, numElems) { - builder.startVector(4, numElems, 4); - } - static endFragmentsGroup(builder) { - const offset = builder.endObject(); - return offset; - } - static finishFragmentsGroupBuffer(builder, offset) { - builder.finish(offset); - } - static finishSizePrefixedFragmentsGroupBuffer(builder, offset) { - builder.finish(offset, undefined, true); + return this._basePass; } -}; - -// TODO: Document this -class FragmentsGroup extends THREE$1.Group { - constructor() { - super(...arguments); - this.items = []; - this.boundingBox = new THREE$1.Box3(); - this.coordinationMatrix = new THREE$1.Matrix4(); - this.keyFragments = {}; - // data: [expressID: number]: [keys, rels] - this.data = {}; - this.ifcMetadata = { - name: "", - description: "", - schema: "IFC2X3", - maxExpressID: 0, - }; + get gammaPass() { + if (!this._gammaPass) { + throw new Error("Custom effects not initialized!"); + } + return this._gammaPass; } - // TODO: Force all item IDs to be numbers or strings - getFragmentMap(expressIDs) { - const fragmentMap = {}; - for (const expressID of expressIDs) { - const data = this.data[expressID]; - if (!data) - continue; - for (const key of data[0]) { - const fragmentID = this.keyFragments[key]; - if (!fragmentMap[fragmentID]) - fragmentMap[fragmentID] = new Set(); - fragmentMap[fragmentID].add(expressID); - } + get customEffects() { + if (!this._customEffects) { + throw new Error("Custom effects not initialized!"); } - return fragmentMap; + return this._customEffects; } - dispose(disposeResources = true) { - for (const fragment of this.items) { - fragment.dispose(disposeResources); + get n8ao() { + if (!this._n8ao) { + throw new Error("Custom effects not initialized!"); } - this.coordinationMatrix = new THREE$1.Matrix4(); - this.keyFragments = {}; - this.data = {}; - this.properties = {}; + return this._n8ao; } -} - -class IfcAlignmentData { - constructor() { - this.coordinates = new Float32Array(0); - this.alignmentIndex = []; - this.curveIndex = []; + get enabled() { + return this._enabled; } - exportData() { - const { coordinates, alignmentIndex, curveIndex } = this; - return { coordinates, alignmentIndex, curveIndex }; + set enabled(active) { + if (!this._initialized) { + this.initialize(); + } + this._enabled = active; } -} - -/** - * Object to export and import sets of fragments efficiently using - * [flatbuffers](https://flatbuffers.dev/). - */ -class Serializer { - constructor() { - this.fragmentIDSeparator = "|"; + get settings() { + return { ...this._settings }; } - import(bytes) { - const buffer = new ByteBuffer(bytes); - const fbFragmentsGroup = FragmentsGroup$1.getRootAsFragmentsGroup(buffer); - const fragmentsGroup = this.constructFragmentGroup(fbFragmentsGroup); - const length = fbFragmentsGroup.itemsLength(); - for (let i = 0; i < length; i++) { - const fbFragment = fbFragmentsGroup.items(i); - if (!fbFragment) - continue; - const geometry = this.constructGeometry(fbFragment); - const materials = this.constructMaterials(fbFragment); - const { instances, colors } = this.constructInstances(fbFragment); - const fragment = new Fragment$1(geometry, materials, instances.length); - this.getComposites(fbFragment, fragment); - this.setInstances(instances, colors, fragment); - this.setID(fbFragment, fragment); - fragmentsGroup.items.push(fragment); - fragmentsGroup.add(fragment.mesh); - } - return fragmentsGroup; + constructor(components, renderer) { + this.components = components; + this.renderer = renderer; + this.excludedItems = new Set(); + this.overrideClippingPlanes = false; + this._enabled = false; + this._initialized = false; + this._settings = { + gamma: true, + custom: true, + ao: false, + }; + this._renderTarget = new THREE$1.WebGLRenderTarget(window.innerWidth, window.innerHeight); + this._renderTarget.texture.colorSpace = "srgb-linear"; + this.composer = new EffectComposer(this.renderer, this._renderTarget); + this.composer.setSize(window.innerWidth, window.innerHeight); } - export(group) { - var _a; - const builder = new Builder(1024); - const items = []; - const G = FragmentsGroup$1; - const F = Fragment; - const C = Civil; - let exportedCivil = null; - if ((_a = group.ifcCivil) === null || _a === void 0 ? void 0 : _a.horizontalAlignments) { - const A = Alignment; - const resultH = group.ifcCivil.horizontalAlignments.exportData(); - const posVectorH = A.createPositionVector(builder, resultH.coordinates); - const curveVectorH = A.createSegmentVector(builder, resultH.curveIndex); - const alignVectorH = A.createCurveVector(builder, resultH.alignmentIndex); - A.startAlignment(builder); - A.addPosition(builder, posVectorH); - A.addSegment(builder, curveVectorH); - A.addCurve(builder, alignVectorH); - const exportedH = Alignment.endAlignment(builder); - const resultV = group.ifcCivil.verticalAlignments.exportData(); - const posVectorV = A.createPositionVector(builder, resultV.coordinates); - const curveVectorV = A.createSegmentVector(builder, resultV.curveIndex); - const alignVectorV = A.createCurveVector(builder, resultV.alignmentIndex); - A.startAlignment(builder); - A.addPosition(builder, posVectorV); - A.addSegment(builder, curveVectorV); - A.addCurve(builder, alignVectorV); - const exportedV = Alignment.endAlignment(builder); - const resultR = group.ifcCivil.realAlignments.exportData(); - const posVectorR = A.createPositionVector(builder, resultR.coordinates); - const curveVectorR = A.createSegmentVector(builder, resultR.curveIndex); - const alignVectorR = A.createCurveVector(builder, resultR.alignmentIndex); - A.startAlignment(builder); - A.addPosition(builder, posVectorR); - A.addSegment(builder, curveVectorR); - A.addCurve(builder, alignVectorR); - const exportedR = Alignment.endAlignment(builder); - C.startCivil(builder); - C.addAlignmentHorizontal(builder, exportedH); - C.addAlignmentVertical(builder, exportedV); - C.addAlignment3d(builder, exportedR); - exportedCivil = Civil.endCivil(builder); - } - for (const fragment of group.items) { - const result = fragment.exportData(); - const posVector = F.createPositionVector(builder, result.position); - const normalVector = F.createNormalVector(builder, result.normal); - const blockVector = F.createBlockIdVector(builder, result.blockID); - const indexVector = F.createIndexVector(builder, result.index); - const groupsVector = F.createGroupsVector(builder, result.groups); - const matsVector = F.createMaterialsVector(builder, result.materials); - const matricesVector = F.createMatricesVector(builder, result.matrices); - const colorsVector = F.createColorsVector(builder, result.colors); - const idsStr = builder.createString(result.ids); - const idStr = builder.createString(result.id); - const compositeStr = builder.createString(JSON.stringify(fragment.composites)); - F.startFragment(builder); - F.addPosition(builder, posVector); - F.addNormal(builder, normalVector); - F.addBlockId(builder, blockVector); - F.addIndex(builder, indexVector); - F.addGroups(builder, groupsVector); - F.addMaterials(builder, matsVector); - F.addMatrices(builder, matricesVector); - F.addColors(builder, colorsVector); - F.addIds(builder, idsStr); - F.addId(builder, idStr); - F.addComposites(builder, compositeStr); - const exported = Fragment.endFragment(builder); - items.push(exported); + async dispose() { + var _a, _b, _c, _d; + this._renderTarget.dispose(); + (_a = this._depthTexture) === null || _a === void 0 ? void 0 : _a.dispose(); + await ((_b = this._customEffects) === null || _b === void 0 ? void 0 : _b.dispose()); + (_c = this._gammaPass) === null || _c === void 0 ? void 0 : _c.dispose(); + (_d = this._n8ao) === null || _d === void 0 ? void 0 : _d.dispose(); + this.excludedItems.clear(); + } + setPasses(settings) { + // This check can prevent some bugs + let settingsChanged = false; + for (const name in settings) { + const key = name; + if (this.settings[key] !== settings[key]) { + settingsChanged = true; + break; + } } - const itemsVector = G.createItemsVector(builder, items); - const matrixVector = G.createCoordinationMatrixVector(builder, group.coordinationMatrix.elements); - let fragmentKeys = ""; - for (const key in group.keyFragments) { - const fragmentID = group.keyFragments[key]; - if (fragmentKeys.length) - fragmentKeys += this.fragmentIDSeparator; - fragmentKeys += fragmentID; + if (!settingsChanged) { + return; } - const fragmentKeysRef = builder.createString(fragmentKeys); - const keyIndices = []; - const itemsKeys = []; - const relsIndices = []; - const itemsRels = []; - const ids = []; - let keysCounter = 0; - let relsCounter = 0; - for (const expressID in group.data) { - keyIndices.push(keysCounter); - relsIndices.push(relsCounter); - const [keys, rels] = group.data[expressID]; - const id = parseInt(expressID, 10); - ids.push(id); - for (const key of keys) { - itemsKeys.push(key); - } - for (const rel of rels) { - itemsRels.push(rel); + for (const name in settings) { + const key = name; + if (this._settings[key] !== undefined) { + this._settings[key] = settings[key]; } - keysCounter += keys.length; - relsCounter += rels.length; - } - const groupID = builder.createString(group.uuid); - const groupName = builder.createString(group.name); - const ifcName = builder.createString(group.ifcMetadata.name); - const ifcDescription = builder.createString(group.ifcMetadata.description); - const ifcSchema = builder.createString(group.ifcMetadata.schema); - const keysIVector = G.createItemsKeysIndicesVector(builder, keyIndices); - const keysVector = G.createItemsKeysVector(builder, itemsKeys); - const relsIVector = G.createItemsRelsIndicesVector(builder, relsIndices); - const relsVector = G.createItemsRelsVector(builder, itemsRels); - const idsVector = G.createIdsVector(builder, ids); - const { min, max } = group.boundingBox; - const bbox = [min.x, min.y, min.z, max.x, max.y, max.z]; - const bboxVector = G.createBoundingBoxVector(builder, bbox); - G.startFragmentsGroup(builder); - if (exportedCivil !== null) { - G.addCivil(builder, exportedCivil); } - G.addId(builder, groupID); - G.addName(builder, groupName); - G.addIfcName(builder, ifcName); - G.addIfcDescription(builder, ifcDescription); - G.addIfcSchema(builder, ifcSchema); - G.addMaxExpressId(builder, group.ifcMetadata.maxExpressID); - G.addItems(builder, itemsVector); - G.addFragmentKeys(builder, fragmentKeysRef); - G.addIds(builder, idsVector); - G.addItemsKeysIndices(builder, keysIVector); - G.addItemsKeys(builder, keysVector); - G.addItemsRelsIndices(builder, relsIVector); - G.addItemsRels(builder, relsVector); - G.addCoordinationMatrix(builder, matrixVector); - G.addBoundingBox(builder, bboxVector); - const result = FragmentsGroup$1.endFragmentsGroup(builder); - builder.finish(result); - return builder.asUint8Array(); - } - getComposites(fbFragment, fragment) { - const composites = fbFragment.composites() || "{}"; - fragment.composites = JSON.parse(composites); + this.updatePasses(); } - setID(fbFragment, fragment) { - const id = fbFragment.id(); - if (id) { - fragment.id = id; - fragment.mesh.uuid = id; + setSize(width, height) { + if (this._initialized) { + this.composer.setSize(width, height); + this.basePass.setSize(width, height); + this.n8ao.setSize(width, height); + this.customEffects.setSize(width, height); + this.gammaPass.setSize(width, height); } } - setInstances(instances, colors, fragment) { - for (let i = 0; i < instances.length; i++) { - fragment.setInstance(i, instances[i]); - if (colors.length) { - fragment.mesh.setColorAt(i, colors[i]); - } - } + update() { + if (!this._enabled) + return; + this.composer.render(); } - constructInstances(fragment) { - const matricesData = fragment.matricesArray(); - const colorData = fragment.colorsArray(); - const colors = []; - const idsString = fragment.ids(); - const id = fragment.id(); - if (!matricesData || !idsString) { - throw new Error(`Error: Can't load empty fragment: ${id}`); - } - const ids = idsString.split("|"); - const singleInstance = matricesData.length === 16; - const manyItems = ids.length > 1; - const isMergedFragment = singleInstance && manyItems; - if (isMergedFragment) { - const transform = new THREE$1.Matrix4().fromArray(matricesData); - const instances = [{ ids, transform }]; - return { instances, colors }; + updateCamera() { + const camera = this.components.camera.get(); + if (this._n8ao) { + this._n8ao.camera = camera; } - // Instanced fragment - const instances = []; - for (let i = 0; i < matricesData.length; i += 16) { - const matrixArray = matricesData.subarray(i, i + 17); - const transform = new THREE$1.Matrix4().fromArray(matrixArray); - const id = ids[i / 16]; - instances.push({ ids: [id], transform }); + if (this._customEffects) { + this._customEffects.renderCamera = camera; } - if (colorData && colorData.length === instances.length * 3) { - for (let i = 0; i < colorData.length; i += 3) { - const [r, g, b] = colorData.subarray(i, i + 4); - const color = new THREE$1.Color(r, g, b); - colors.push(color); - } + if (this._basePass) { + this._basePass.camera = camera; } - return { instances, colors }; } - constructMaterials(fragment) { - const materials = fragment.materialsArray(); - const matArray = []; - if (!materials) - return matArray; - for (let i = 0; i < materials.length; i += 5) { - const opacity = materials[i]; - const transparent = Boolean(materials[i + 1]); - const red = materials[i + 2]; - const green = materials[i + 3]; - const blue = materials[i + 4]; - const color = new THREE$1.Color(red, green, blue); - const material = new THREE$1.MeshLambertMaterial({ - color, - opacity, - transparent, + initialize() { + const scene = this.overrideScene || this.components.scene.get(); + const camera = this.overrideCamera || this.components.camera.get(); + if (!scene || !camera) + return; + if (this.components.camera instanceof OrthoPerspectiveCamera) { + this.components.camera.projectionChanged.add(() => { + this.updateCamera(); }); - matArray.push(material); } - return matArray; + const renderer = this.components.renderer; + if (!this.overrideClippingPlanes) { + this.renderer.clippingPlanes = renderer.clippingPlanes; + } + this.renderer.outputColorSpace = "srgb"; + this.renderer.toneMapping = THREE$1.NoToneMapping; + this.newBasePass(scene, camera); + this.newSaoPass(scene, camera); + this.newGammaPass(); + this.newCustomPass(scene, camera); + this._initialized = true; + this.updatePasses(); } - constructFragmentGroup(group) { - const fragmentsGroup = new FragmentsGroup(); - const FBcivil = group.civil(); - const horizontalAlignments = new IfcAlignmentData(); - const verticalAlignments = new IfcAlignmentData(); - const realAlignments = new IfcAlignmentData(); - if (FBcivil) { - const FBalignmentH = FBcivil.alignmentHorizontal(); - this.getAlignmentData(FBalignmentH, horizontalAlignments); - const FBalignmentV = FBcivil.alignmentVertical(); - this.getAlignmentData(FBalignmentV, verticalAlignments); - const FBalignment3D = FBcivil.alignment3d(); - this.getAlignmentData(FBalignment3D, realAlignments); - fragmentsGroup.ifcCivil = { - horizontalAlignments, - verticalAlignments, - realAlignments, - }; + updateProjection(camera) { + this.composer.passes.forEach((pass) => { + // @ts-ignore + pass.camera = camera; + }); + this.update(); + } + updatePasses() { + for (const pass of this.composer.passes) { + this.composer.removePass(pass); } - // fragmentsGroup.ifcCivil?.horizontalAlignments - fragmentsGroup.uuid = group.id() || fragmentsGroup.uuid; - fragmentsGroup.name = group.name() || ""; - fragmentsGroup.ifcMetadata = { - name: group.ifcName() || "", - description: group.ifcDescription() || "", - schema: group.ifcSchema() || "IFC2X3", - maxExpressID: group.maxExpressId() || 0, - }; - const defaultMatrix = new THREE$1.Matrix4().elements; - const matrixArray = group.coordinationMatrixArray() || defaultMatrix; - const ids = group.idsArray() || new Uint32Array(); - const keysIndices = group.itemsKeysIndicesArray() || new Uint32Array(); - const keysArray = group.itemsKeysArray() || new Uint32Array(); - const relsArray = group.itemsRelsArray() || new Uint32Array(); - const relsIndices = group.itemsRelsIndicesArray() || new Uint32Array(); - const keysIdsString = group.fragmentKeys() || ""; - const keysIdsArray = keysIdsString.split(this.fragmentIDSeparator); - this.setGroupData(fragmentsGroup, ids, keysIndices, keysArray, 0); - this.setGroupData(fragmentsGroup, ids, relsIndices, relsArray, 1); - const bbox = group.boundingBoxArray() || [0, 0, 0, 0, 0, 0]; - const [minX, minY, minZ, maxX, maxY, maxZ] = bbox; - fragmentsGroup.boundingBox.min.set(minX, minY, minZ); - fragmentsGroup.boundingBox.max.set(maxX, maxY, maxZ); - for (let i = 0; i < keysIdsArray.length; i++) { - fragmentsGroup.keyFragments[i] = keysIdsArray[i]; + if (this._basePass) { + this.composer.addPass(this.basePass); } - if (matrixArray.length === 16) { - fragmentsGroup.coordinationMatrix.fromArray(matrixArray); + if (this._settings.gamma) { + this.composer.addPass(this.gammaPass); } - return fragmentsGroup; + if (this._settings.ao) { + this.composer.addPass(this.n8ao); + } + if (this._settings.custom) { + this.composer.addPass(this.customEffects); + } + } + newCustomPass(scene, camera) { + this._customEffects = new CustomEffectsPass(new THREE$1.Vector2(window.innerWidth, window.innerHeight), this.components, scene, camera); + } + newGammaPass() { + this._gammaPass = new ShaderPass(GammaCorrectionShader); + } + newSaoPass(scene, camera) { + const { width, height } = this.components.renderer.getSize(); + this._n8ao = new $05f6997e4b65da14$export$2d57db20b5eb5e0a(scene, camera, width, height); + // this.composer.addPass(this.n8ao); + const { configuration } = this._n8ao; + configuration.aoSamples = 16; + configuration.denoiseSamples = 1; + configuration.denoiseRadius = 13; + configuration.aoRadius = 1; + configuration.distanceFalloff = 4; + configuration.aoRadius = 1; + configuration.intensity = 4; + configuration.halfRes = true; + configuration.color = new THREE$1.Color().setHex(0xcccccc, "srgb-linear"); + } + newBasePass(scene, camera) { + this._basePass = new RenderPass(scene, camera); + } +} + +/** + * Renderer that uses efficient postproduction effects (e.g. Ambient Occlusion). + */ +class PostproductionRenderer extends SimpleRenderer { + constructor(components, container, parameters) { + super(components, container, parameters); + this.postproduction = new Postproduction(components, this._renderer); + this.setPostproductionSize(); + this.onResize.add((size) => this.resizePostproduction(size)); } - getAlignmentData(alignment, result) { - if (alignment) { - if (alignment.positionArray) { - result.coordinates = alignment.positionArray(); - for (let j = 0; j < alignment.curveLength(); j++) { - result.alignmentIndex.push(alignment.curve(j)); - } - for (let j = 0; j < alignment.segmentLength(); j++) { - result.curveIndex.push(alignment.segment(j)); - } - } + /** {@link Updateable.update} */ + async update() { + if (!this.enabled) + return; + await this.onBeforeUpdate.trigger(); + const scene = this.overrideScene || this.components.scene.get(); + const camera = this.overrideCamera || this.components.camera.get(); + if (!scene || !camera) + return; + if (this.postproduction.enabled) { + this.postproduction.composer.render(); } - } - setGroupData(group, ids, indices, array, index) { - for (let i = 0; i < indices.length; i++) { - const expressID = ids[i]; - const currentIndex = indices[i]; - const nextIndex = indices[i + 1] || array.length; - const keys = []; - for (let j = currentIndex; j < nextIndex; j++) { - keys.push(array[j]); - } - if (!group.data[expressID]) { - group.data[expressID] = [[], []]; - } - group.data[expressID][index] = keys; + else { + this._renderer.render(scene, camera); } + this._renderer2D.render(scene, camera); + await this.onAfterUpdate.trigger(); } - constructGeometry(fragment) { - const position = fragment.positionArray(); - const normal = fragment.normalArray(); - const blockID = fragment.blockIdArray(); - const index = fragment.indexArray(); - const groups = fragment.groupsArray(); - if (!index) - throw new Error("Index not found!"); - const geometry = new THREE$1.BufferGeometry(); - geometry.setIndex(Array.from(index)); - this.loadAttribute(geometry, "position", position, 3); - this.loadAttribute(geometry, "normal", normal, 3); - this.loadAttribute(geometry, "blockID", blockID, 1); - this.loadGeometryGroups(groups, geometry); - return geometry; + /** {@link Disposable.dispose}. */ + async dispose() { + await super.dispose(); + await this.postproduction.dispose(); } - loadGeometryGroups(groups, geometry) { - if (!groups) - return; - for (let i = 0; i < groups.length; i += 3) { - const start = groups[i]; - const count = groups[i + 1]; - const materialIndex = groups[i + 2]; - geometry.addGroup(start, count, materialIndex); + resizePostproduction(size) { + if (this.postproduction) { + this.setPostproductionSize(size); } } - loadAttribute(geometry, name, data, size) { - if (!data) + setPostproductionSize(size) { + if (!this.container) return; - geometry.setAttribute(name, new THREE$1.BufferAttribute(data, size)); + const width = size ? size.x : this.container.clientWidth; + const height = size ? size.y : this.container.clientHeight; + this.postproduction.setSize(width, height); } } /** - * Object that can efficiently load binary files that contain - * [fragment geometry](https://github.com/ifcjs/fragment). + * An infinite lightweight 2D grid that can be used for any + * kind of 2d viewports. */ -class FragmentManager extends Component { - /** The list of meshes of the created fragments. */ - get meshes() { - const allMeshes = []; - for (const fragID in this.list) { - allMeshes.push(this.list[fragID].mesh); - } - return allMeshes; - } - constructor(components) { - super(components); - /** {@link Disposable.onDisposed} */ - this.onDisposed = new Event(); - /** {@link Component.enabled} */ - this.enabled = true; - /** All the created [fragments](https://github.com/ifcjs/fragment). */ - this.list = {}; - this.groups = []; - this.baseCoordinationModel = ""; - this.onFragmentsLoaded = new Event(); - this.onFragmentsDisposed = new Event(); - this.uiElement = new UIElement(); - this.commands = []; - this._loader = new Serializer(); - this._cards = []; - this.components.tools.add(FragmentManager.uuid, this); - if (components.uiEnabled) { - this.setupUI(components); - } +class Infinite2dGrid { + constructor(camera, container) { + this.numbers = new THREE$1.Group(); + this.maxRegenerateRetrys = 4; + this.gridsFactor = 5; + this.scaleX = 1; + this.scaleY = 1; + this._group = new THREE$1.Group(); + this._frustum = new THREE$1.Frustum(); + this._frustumMat = new THREE$1.Matrix4(); + this._regenerateDelay = 200; + this._regenerateCounter = 0; + this._camera = camera; + this._container = container; + const main = this.newGrid(0x222222, -1); + const secondary = this.newGrid(0x111111, -2); + this.grids = { main, secondary }; + this._group.add(secondary, main, this.numbers); } - /** {@link Component.get} */ get() { - return Object.values(this.list); + return this._group; } - /** {@link Component.get} */ - async dispose(disposeUI = false) { - if (disposeUI) { - this.uiElement.dispose(); + dispose() { + const { main, secondary } = this.grids; + main.removeFromParent(); + secondary.removeFromParent(); + main.geometry.dispose(); + const mMat = main.material; + mMat.dispose(); + secondary.geometry.dispose(); + const sMat = secondary.material; + sMat.dispose(); + } + regenerate() { + const isReady = this.isGridReady(); + if (!isReady) { + this._regenerateCounter++; + if (this._regenerateCounter > this.maxRegenerateRetrys) { + throw new Error("Grid could not be regenerated"); + } + setTimeout(() => this.regenerate, this._regenerateDelay); + return; } - for (const group of this.groups) { - group.dispose(true); + this._regenerateCounter = 0; + const matrix = this._frustumMat.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse); + this._frustum.setFromProjectionMatrix(matrix); + // Step 1: find out the distance of the visible area of the 2D scene + // and the translation pixel / 3d unit + const { planes } = this._frustum; + const right = planes[0].constant * -planes[0].normal.x; + const left = planes[1].constant * -planes[1].normal.x; + const bottom = planes[2].constant * -planes[2].normal.y; + const top = planes[3].constant * -planes[3].normal.y; + const horizontalDistance = Math.abs(right - left); + const verticalDistance = Math.abs(top - bottom); + const { clientWidth, clientHeight } = this._container; + const maxPixelDist = Math.max(clientWidth, clientHeight); + const maxUnit3dDist = Math.max(horizontalDistance, verticalDistance); + const unit3dPixelRel = maxUnit3dDist / maxPixelDist; + // Step 2: find out its order of magnitude + const magnitudeX = Math.ceil(Math.log10(horizontalDistance / this.scaleX)); + const magnitudeY = Math.ceil(Math.log10(verticalDistance / this.scaleY)); + // Step 3: represent main grid + const sDistanceHor = 10 ** (magnitudeX - 2) * this.scaleX; + const sDistanceVert = 10 ** (magnitudeY - 2) * this.scaleY; + const mDistanceHor = sDistanceHor * this.gridsFactor; + const mDistanceVert = sDistanceVert * this.gridsFactor; + const mainGridCountVert = Math.ceil(verticalDistance / mDistanceVert); + const mainGridCountHor = Math.ceil(horizontalDistance / mDistanceHor); + const secondaryGridCountVert = Math.ceil(verticalDistance / sDistanceVert); + const secondaryGridCountHor = Math.ceil(horizontalDistance / sDistanceHor); + // Step 4: find out position of first lines + const sTrueLeft = sDistanceHor * Math.ceil(left / sDistanceHor); + const sTrueBottom = sDistanceVert * Math.ceil(bottom / sDistanceVert); + const mTrueLeft = mDistanceHor * Math.ceil(left / mDistanceHor); + const mTrueBottom = mDistanceVert * Math.ceil(bottom / mDistanceVert); + // Step 5: draw lines and texts + const numbers = [...this.numbers.children]; + for (const number of numbers) { + number.removeFromParent(); } - for (const command of this.commands) { - await command.dispose(); + this.numbers.children = []; + const mPoints = []; + for (let i = 0; i < mainGridCountHor; i++) { + const offset = mTrueLeft + i * mDistanceHor; + mPoints.push(offset, top, 0, offset, bottom, 0); + const sign = this.newNumber(offset / this.scaleX); + const textOffsetPixels = 12; + const textOffset = textOffsetPixels * unit3dPixelRel; + sign.position.set(offset, bottom + textOffset, 0); } - for (const card of this._cards) { - await card.dispose(); + for (let i = 0; i < mainGridCountVert; i++) { + const offset = mTrueBottom + i * mDistanceVert; + mPoints.push(left, offset, 0, right, offset, 0); + const sign = this.newNumber(offset / this.scaleY); + let textOffsetPixels = 12; + if (sign.element.textContent) { + textOffsetPixels += 4 * sign.element.textContent.length; + } + const textOffset = textOffsetPixels * unit3dPixelRel; + sign.position.set(left + textOffset, offset, 0); } - this.groups = []; - this.list = {}; - this.onFragmentsLoaded.reset(); - this.onFragmentsDisposed.reset(); - await this.onDisposed.trigger(FragmentManager.uuid); - this.onDisposed.reset(); + const sPoints = []; + for (let i = 0; i < secondaryGridCountHor; i++) { + const offset = sTrueLeft + i * sDistanceHor; + sPoints.push(offset, top, 0, offset, bottom, 0); + } + for (let i = 0; i < secondaryGridCountVert; i++) { + const offset = sTrueBottom + i * sDistanceVert; + sPoints.push(left, offset, 0, right, offset, 0); + } + const mIndices = []; + const sIndices = []; + this.fillIndices(mPoints, mIndices); + this.fillIndices(sPoints, sIndices); + const mBuffer = new THREE$1.BufferAttribute(new Float32Array(mPoints), 3); + const sBuffer = new THREE$1.BufferAttribute(new Float32Array(sPoints), 3); + const { main, secondary } = this.grids; + main.geometry.setAttribute("position", mBuffer); + main.geometry.setIndex(mIndices); + secondary.geometry.setAttribute("position", sBuffer); + secondary.geometry.setIndex(sIndices); } - async disposeGroup(group) { - const { uuid: groupID } = group; - const fragmentIDs = group.items.map((fragment) => fragment.id); - for (const fragment of group.items) { - this.removeFragmentMesh(fragment); - delete this.list[fragment.id]; + fillIndices(points, indices) { + for (let i = 0; i < points.length / 2 - 1; i += 2) { + indices.push(i, i + 1); } - group.dispose(true); - const index = this.groups.indexOf(group); - this.groups.splice(index, 1); - await this.onFragmentsDisposed.trigger({ - groupID, - fragmentIDs, - }); - await this.updateWindow(); } - /** Disposes all existing fragments */ - reset() { - for (const id in this.list) { - const fragment = this.list[id]; - fragment.dispose(); + newNumber(offset) { + const text = document.createElement("div"); + text.textContent = `${offset}`; + if (text.textContent.length > 6) { + text.textContent = text.textContent.slice(0, 6); } - this.list = {}; + text.style.height = "24px"; + text.style.fontSize = "12px"; + const sign = new CSS2DObject(text); + this.numbers.add(sign); + return sign; } - /** - * Loads one or many fragments into the scene. - * @param data - the bytes containing the data for the fragments to load. - * @returns the list of IDs of the loaded fragments. - */ - async load(data) { - const group = this._loader.import(data); - const scene = this.components.scene.get(); - const ids = []; - scene.add(group); - for (const fragment of group.items) { - fragment.group = group; - this.list[fragment.id] = fragment; - ids.push(fragment.id); - this.components.meshes.push(fragment.mesh); + newGrid(color, renderOrder) { + const geometry = new THREE$1.BufferGeometry(); + const material = new THREE$1.LineBasicMaterial({ color }); + const grid = new THREE$1.LineSegments(geometry, material); + grid.frustumCulled = false; + grid.renderOrder = renderOrder; + return grid; + } + isGridReady() { + const nums = this._camera.projectionMatrix.elements; + for (let i = 0; i < nums.length; i++) { + const num = nums[i]; + if (Number.isNaN(num)) { + return false; + } } - this.groups.push(group); - await this.onFragmentsLoaded.trigger(group); - return group; + return true; } - /** - * Export the specified fragments. - * @param group - the fragments group to be exported. - * @returns the exported data as binary buffer. - */ - export(group) { - return this._loader.export(group); +} + +// TODO: Make a scene manager as a Tool (so that it as an UUID) +/** + * A simple floating 2D scene that you can use to easily draw 2D graphics + * with all the power of Three.js. + */ +class Simple2DScene extends Component { + get scaleX() { + return this._scaleX; } - async updateWindow() { - if (!this.components.uiEnabled) { - return; + set scaleX(value) { + this._scaleX = value; + this._root.scale.x = value; + this.grid.scaleX = value; + this.grid.regenerate(); + } + get scaleY() { + return this._scaleY; + } + set scaleY(value) { + this._scaleY = value; + this._root.scale.y = value; + this.grid.scaleY = value; + this.grid.regenerate(); + } + constructor(components, postproduction = false) { + super(components); + /** {@link Updateable.onAfterUpdate} */ + this.onAfterUpdate = new Event(); + /** {@link Updateable.onBeforeUpdate} */ + this.onBeforeUpdate = new Event(); + /** {@link Resizeable.onResize} */ + this.onResize = new Event(); + /** {@link Component.enabled} */ + this.enabled = true; + /** {@link Disposable.onDisposed} */ + this.onDisposed = new Event(); + /** {@link UI.uiElement} */ + this.uiElement = new UIElement(); + this._scaleX = 1; + this._scaleY = 1; + this._root = new THREE$1.Group(); + this._size = new THREE$1.Vector2(); + this._frustumSize = 50; + /** {@link Resizeable.resize} */ + this.resize = () => { + const { height, width } = this._size; + const aspect = width / height; + this.camera.left = (-this._frustumSize * aspect) / 2; + this.camera.right = (this._frustumSize * aspect) / 2; + this.camera.top = this._frustumSize / 2; + this.camera.bottom = -this._frustumSize / 2; + this.camera.updateProjectionMatrix(); + this.camera.updateProjectionMatrix(); + this.renderer.resize(this._size); + }; + if (!components.uiEnabled) { + throw new Error("The Simple2DScene component needs to use UI elements (TODO: Decouple from them)."); } - for (const card of this._cards) { - await card.dispose(); + const container = new SimpleUIComponent(components); + container.domElement.classList.add("relative"); + this.uiElement.set({ container }); + this.scene = new THREE$1.Scene(); + this._size.set(window.innerWidth, window.innerHeight); + const { width, height } = this._size; + // Creates the camera (point of view of the user) + this.camera = new THREE$1.OrthographicCamera(75, width / height); + this.scene.add(this.camera); + this.camera.position.z = 10; + const domContainer = container.domElement; + this.scene.add(this._root); + this.grid = new Infinite2dGrid(this.camera, domContainer); + const gridObject = this.grid.get(); + this.scene.add(gridObject); + if (postproduction) { + this.renderer = new PostproductionRenderer(this.components, domContainer); } - for (const group of this.groups) { - const card = new SimpleUICard(this.components); - // TODO: Make all cards like this? - card.domElement.classList.remove("bg-ifcjs-120"); - card.domElement.classList.remove("border-transparent"); - card.domElement.className += ` min-w-[300px] my-2 border-1 border-solid border-[#3A444E] `; - const buttonContainer = new SimpleUIComponent(this.components); - card.addChild(buttonContainer); - card.title = group.name; - this.uiElement.get("window").addChild(card); - this._cards.push(card); - // TODO: Use command list just like in fragment plans - const commandsButton = new Button(this.components); - commandsButton.materialIcon = "delete"; - buttonContainer.addChild(commandsButton); - commandsButton.onClick.add(() => this.disposeGroup(group)); + else { + this.renderer = new SimpleRenderer(this.components, domContainer); } + const renderer = this.renderer.get(); + renderer.localClippingEnabled = false; + this.renderer.setupEvents(false); + this.renderer.overrideScene = this.scene; + this.renderer.overrideCamera = this.camera; + this.controls = new OrbitControls(this.camera, renderer.domElement); + this.controls.target.set(0, 0, 0); + this.controls.enableRotate = false; + this.controls.enableZoom = true; + this.controls.addEventListener("change", () => this.grid.regenerate()); } - coordinate(models = this.groups) { - const baseModel = this.groups.find((group) => group.uuid === this.baseCoordinationModel); - if (!baseModel) { - console.log("No base model found for coordination!"); - return; - } - for (const model of models) { - if (model === baseModel) { - continue; + /** + * {@link Component.get} + * @returns the 2D scene. + */ + get() { + return this._root; + } + /** {@link Disposable.dispose} */ + async dispose() { + const disposer = this.components.tools.get(Disposer); + for (const child of this.scene.children) { + const item = child; + if (item instanceof THREE$1.Object3D) { + disposer.destroy(item); } - model.position.set(0, 0, 0); - model.rotation.set(0, 0, 0); - model.scale.set(1, 1, 1); - model.updateMatrix(); - model.applyMatrix4(model.coordinationMatrix.clone().invert()); - model.applyMatrix4(baseModel.coordinationMatrix); } + await this.renderer.dispose(); + await this.uiElement.dispose(); + await this.onDisposed.trigger(Simple2DScene.uuid); + this.onDisposed.reset(); } - setupUI(components) { - const window = new FloatingWindow(components); - window.title = "Models"; - window.domElement.style.left = "70px"; - window.domElement.style.top = "100px"; - window.domElement.style.width = "340px"; - window.domElement.style.height = "400px"; - const windowContent = window.slots.content.domElement; - windowContent.classList.remove("overflow-auto"); - windowContent.classList.add("overflow-x-hidden"); - components.ui.add(window); - window.visible = false; - const main = new Button(components); - main.tooltip = "Models"; - main.materialIcon = "inbox"; - main.onClick.add(() => { - window.visible = !window.visible; - }); - this.uiElement.set({ main, window }); - this.onFragmentsLoaded.add(() => this.updateWindow()); + /** {@link Updateable.update} */ + async update() { + await this.onBeforeUpdate.trigger(); + this.controls.update(); + await this.renderer.update(); + await this.onAfterUpdate.trigger(); } - removeFragmentMesh(fragment) { - const meshes = this.components.meshes; - const mesh = fragment.mesh; - if (meshes.includes(mesh)) { - meshes.splice(meshes.indexOf(mesh), 1); - } + /** {@link Resizeable.getSize} */ + getSize() { + return new THREE$1.Vector2(this._size.width, this._size.height); + } + setSize(height, width) { + this._size.width = width; + this._size.height = height; + this.resize(); } } -FragmentManager.uuid = "fef46874-46a3-461b-8c44-2922ab77c806"; -ToolComponent.libraryUUIDs.add(FragmentManager.uuid); +Simple2DScene.uuid = "b48b7194-0f9a-43a4-a718-270b1522595f"; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; diff --git a/src/core/LocalCacher/index.ts b/src/core/LocalCacher/index.ts index 2c83799e9..c9c638446 100644 --- a/src/core/LocalCacher/index.ts +++ b/src/core/LocalCacher/index.ts @@ -1,6 +1,10 @@ import { ModelDatabase } from "./db"; -import { Component, Disposable, UI, Event, UIElement } from "../../base-types"; -import { Button, FloatingWindow, SimpleUICard } from "../../ui"; +import { Component } from "../../base-types/component"; +import { UIElement } from "../../base-types/ui-element"; +import { Disposable, UI, Event } from "../../base-types/base-types"; +import { Button } from "../../ui/ButtonComponent"; +import { FloatingWindow } from "../../ui/FloatingWindow"; +import { SimpleUICard } from "../../ui/SimpleUICard"; import { Components } from "../Components"; import { ToolComponent } from "../ToolsComponent"; diff --git a/src/core/ScreenCuller/index.ts b/src/core/ScreenCuller/index.ts index 67f46493b..707229057 100644 --- a/src/core/ScreenCuller/index.ts +++ b/src/core/ScreenCuller/index.ts @@ -1,10 +1,12 @@ import * as THREE from "three"; import { Material } from "three"; +import { FragmentsGroup } from "bim-fragment"; import { Component, Disposable, Event } from "../../base-types"; import { Components } from "../Components"; import { readPixelsAsync } from "./src/screen-culler-helper"; import { Disposer } from "../Disposer"; import { ToolComponent } from "../ToolsComponent"; +import { FragmentManager } from "../../fragments/FragmentManager"; // TODO: Work at the instance level instead of the mesh level? @@ -204,6 +206,18 @@ export class ScreenCuller colorMesh.applyMatrix4(mesh.matrix); colorMesh.updateMatrix(); + const parent = mesh.parent; + if (parent instanceof FragmentsGroup) { + const manager = this.components.tools.get(FragmentManager); + const coordinationModel = manager.groups.find( + (model) => model.uuid === manager.baseCoordinationModel + ); + if (coordinationModel) { + colorMesh.applyMatrix4(parent.coordinationMatrix.clone().invert()); + colorMesh.applyMatrix4(coordinationModel.coordinationMatrix); + } + } + this._scene.add(colorMesh); this._colorMeshes.set(mesh.uuid, colorMesh); this._meshes.set(mesh.uuid, mesh); diff --git a/src/fragments/FragmentCacher/index.ts b/src/fragments/FragmentCacher/index.ts index df29511f2..f6621b1ae 100644 --- a/src/fragments/FragmentCacher/index.ts +++ b/src/fragments/FragmentCacher/index.ts @@ -1,7 +1,10 @@ import { FragmentsGroup } from "bim-fragment"; -import { Components, LocalCacher } from "../../core"; +import { Components } from "../../core/Components"; +import { LocalCacher } from "../../core/LocalCacher"; import { FragmentManager } from "../FragmentManager"; -import { Button, FloatingWindow, SimpleUICard } from "../../ui"; +import { Button } from "../../ui/ButtonComponent"; +import { FloatingWindow } from "../../ui/FloatingWindow"; +import { SimpleUICard } from "../../ui/SimpleUICard"; // TODO: Clean up // TODO: Improve UI element diff --git a/src/fragments/FragmentManager/index.ts b/src/fragments/FragmentManager/index.ts index f66d70cc5..c5833ddef 100644 --- a/src/fragments/FragmentManager/index.ts +++ b/src/fragments/FragmentManager/index.ts @@ -1,7 +1,10 @@ import { Fragment, FragmentsGroup, Serializer } from "bim-fragment"; import * as THREE from "three"; -import { Component, Disposable, Event, UI, UIElement } from "../../base-types"; -import { Components, ToolComponent } from "../../core"; +import { Component } from "../../base-types/component"; +import { UIElement } from "../../base-types/ui-element"; +import { Disposable, Event, UI } from "../../base-types"; +import { Components } from "../../core/Components"; +import { ToolComponent } from "../../core/ToolsComponent"; import { Button, FloatingWindow,