diff --git a/libs/ff-core/source/Color.ts b/libs/ff-core/source/Color.ts index 0812f588..949129a4 100644 --- a/libs/ff-core/source/Color.ts +++ b/libs/ff-core/source/Color.ts @@ -299,26 +299,20 @@ export default class Color implements IVector4 { color = color.trim().toLowerCase(); color = Color.presets[color] || color; - - let result = color.match(/^#?([0-9a-f]{3})$/i); - if (result) { - const text = result[1]; + let text = /^#?([0-9a-f]{3,8})$/i.exec(color)?.[1]; + if (text && (text.length === 3 || text.length == 4)) { const factor = 1 / 15; this.x = Number.parseInt(text.charAt(0), 16) * factor; this.y = Number.parseInt(text.charAt(1), 16) * factor; this.z = Number.parseInt(text.charAt(2), 16) * factor; - this.w = alpha; + this.w = (text.length == 4)? Number.parseInt(text.charAt(3), 16) * factor:alpha; return this; - } - - result = color.match(/^#?([0-9a-f]{6})$/i); - if (result) { - const text = result[1]; + }else if (text && (text.length === 6 || text.length == 8) ) { const factor = 1 / 255; this.x = Number.parseInt(text.substr(0,2), 16) * factor; this.y = Number.parseInt(text.substr(2,2), 16) * factor; this.z = Number.parseInt(text.substr(4,2), 16) * factor; - this.w = alpha; + this.w = (text.length == 8)? Number.parseInt(text.substr(6,2), 16) * factor:alpha; return this; } @@ -478,15 +472,13 @@ export default class Color implements IVector4 return [ this.r, this.g, this.b, this.a ]; } - toString(includeAlpha: boolean = true): string + toString(includeAlpha?: boolean): string { - if (includeAlpha && this.w < 1) { - return `rgba(${this.redByte}, ${this.greenByte}, ${this.blueByte}, ${this.w})`; - } - else { - const value = (1 << 24) + (this.redByte << 16) + (this.greenByte << 8) + this.blueByte; - return `#${value.toString(16).slice(1)}`; + let bytes = [this.redByte, this.greenByte, this.blueByte]; + if (includeAlpha || (typeof includeAlpha === "undefined" && this.w < 1)) { + bytes.push(this.alphaByte); } + return "#"+bytes.map(b=>b.toString(16).padStart(2,"0")).join(""); } private static presets = { diff --git a/libs/ff-core/test/Color.test.ts b/libs/ff-core/test/Color.test.ts index c769f604..3fa604a1 100644 --- a/libs/ff-core/test/Color.test.ts +++ b/libs/ff-core/test/Color.test.ts @@ -27,6 +27,14 @@ export default function() { assert.approximately(c.w, 0xff / 255, eps, "alpha"); }); + test("fromString - static constructor from 8-digit hex string #4d7e0988", function() { + const c = Color.fromString("#4d7e0908"); + assert.approximately(c.x, 0x4d / 255, eps, "red"); + assert.approximately(c.y, 0x7e / 255, eps, "green"); + assert.approximately(c.z, 0x09 / 255, eps, "blue"); + assert.approximately(c.w, 0x08 / 255, eps, "alpha"); + }); + test("fromString - static constructor from 3-digit hex string #6ea", function() { const c = Color.fromString("#6ea"); assert.approximately(c.x, 0x66 / 255, eps, "red"); @@ -34,6 +42,14 @@ export default function() { assert.approximately(c.z, 0xaa / 255, eps, "blue"); assert.approximately(c.w, 0xff / 255, eps, "alpha"); }); + + test("fromString - static constructor from 4-digit hex string #6ea", function() { + const c = Color.fromString("#6ea3"); + assert.approximately(c.x, 0x66 / 255, eps, "red"); + assert.approximately(c.y, 0xee / 255, eps, "green"); + assert.approximately(c.z, 0xaa / 255, eps, "blue"); + assert.approximately(c.w, 0x33 / 255, eps, "alpha"); + }); test("fromString - static constructor from rgb(47, 25, 243)", function() { const c = Color.fromString("rgb(47, 25, 243)"); diff --git a/libs/ff-scene/source/components/CCamera.ts b/libs/ff-scene/source/components/CCamera.ts index bfe836f5..f064bc44 100644 --- a/libs/ff-scene/source/components/CCamera.ts +++ b/libs/ff-scene/source/components/CCamera.ts @@ -5,7 +5,7 @@ * License: MIT */ -import { Vector3, Euler, Quaternion, EulerOrder } from "three"; +import { Vector3, Euler, Quaternion, EulerOrder, Matrix4 } from "three"; import { Node, types } from "@ff/graph/Component"; @@ -107,7 +107,7 @@ export default class CCamera extends CObject3D * Updating the properties then also updates the matrix of the internal universal camera object. * @param matrix A 4x4 transform matrix. If omitted, properties are updated from the matrix of the internal camera. */ - setPropertiesFromMatrix(matrix?: THREE.Matrix4) + setPropertiesFromMatrix(matrix?: Matrix4) { const silent = !matrix; matrix = matrix || this.object3D.matrix; diff --git a/libs/ff-scene/source/components/CObject3D.ts b/libs/ff-scene/source/components/CObject3D.ts index 9935426a..d76e9587 100644 --- a/libs/ff-scene/source/components/CObject3D.ts +++ b/libs/ff-scene/source/components/CObject3D.ts @@ -35,7 +35,7 @@ export interface IObject3DObjectEvent extends ITypedEvent<"object"> } /** - * Base class for drawable components. Wraps a THREE.Object3D based instance. + * Base class for drawable components. Wraps a Object3D based instance. * If component is added to a node together with a [[Transform]] component, * it is automatically added as a child to the transform. */ @@ -63,7 +63,7 @@ export default class CObject3D extends Component implements ICObject3D outs = this.addOutputs(CObject3D.object3DOuts); - private _object3D: THREE.Object3D = null; + private _object3D: Object3D = null; private _isPickable = false; constructor(node: Node, id: string) @@ -91,17 +91,17 @@ export default class CObject3D extends Component implements ICObject3D const transform = this.transform; return transform ? transform.getParentComponent(CScene, true) : undefined; } - /** The underlying [[THREE.Object3D]] of this component. */ - get object3D(): THREE.Object3D | null { + /** The underlying [[Object3D]] of this component. */ + get object3D(): Object3D | null { return this._object3D; } /** - * Assigns a [[THREE.Object3D]] to this component. The object automatically becomes a child + * Assigns a [[Object3D]] to this component. The object automatically becomes a child * of the parent component's object. * @param object */ - set object3D(object: THREE.Object3D) + set object3D(object: Object3D) { const currentObject = this._object3D; if (currentObject) { @@ -254,33 +254,33 @@ export default class CObject3D extends Component implements ICObject3D return true; } - protected onAddToParent(parent: THREE.Object3D) + protected onAddToParent(parent: Object3D) { parent.add(this._object3D); } - protected onRemoveFromParent(parent: THREE.Object3D) + protected onRemoveFromParent(parent: Object3D) { parent.remove(this._object3D); } /** - * Adds a [[THREE.Object3D]] as a child to this component's object. + * Adds a [[Object3D]] as a child to this component's object. * Registers the object with the picking service to make it pickable. * @param object */ - protected addObject3D(object: THREE.Object3D) + protected addObject3D(object: Object3D) { this._object3D.add(object); this.registerPickableObject3D(object, true); } /** - * Removes a [[THREE.Object3D]] child from this component's object. + * Removes a [[Object3D]] child from this component's object. * Also unregisters the object from the picking service. * @param object */ - protected removeObject3D(object: THREE.Object3D) + protected removeObject3D(object: Object3D) { this.unregisterPickableObject3D(object, true); this._object3D.remove(object); @@ -292,7 +292,7 @@ export default class CObject3D extends Component implements ICObject3D * @param object * @param recursive */ - protected registerPickableObject3D(object: THREE.Object3D, recursive: boolean) + protected registerPickableObject3D(object: Object3D, recursive: boolean) { GPUPicker.add(object, recursive); } @@ -303,14 +303,14 @@ export default class CObject3D extends Component implements ICObject3D * @param object * @param recursive */ - protected unregisterPickableObject3D(object: THREE.Object3D, recursive: boolean) + protected unregisterPickableObject3D(object: Object3D, recursive: boolean) { GPUPicker.remove(object, recursive); } private _onParent(event: IComponentEvent) { - // add this THREE.Object3D to the parent THREE.Object3D + // add this Object3D to the parent Object3D if (this._object3D && !this._object3D.parent && event.add) { this.onAddToParent(event.object.object3D); } diff --git a/libs/ff-three/source/Floor.ts b/libs/ff-three/source/Floor.ts index 95c2aafa..71a7da2c 100644 --- a/libs/ff-three/source/Floor.ts +++ b/libs/ff-three/source/Floor.ts @@ -52,8 +52,8 @@ export interface IFloorMaterialParameters extends MeshPhongMaterialParameters export class FloorMaterial extends MeshPhongMaterial { - isMeshPhongMaterial: boolean; - isFloorMaterial: boolean; + readonly isMeshPhongMaterial = true; + readonly isFloorMaterial = true; uniforms: { }; @@ -69,9 +69,6 @@ export class FloorMaterial extends MeshPhongMaterial this.type = "FloorMaterial"; - this.isMeshPhongMaterial = true; - this.isFloorMaterial = true; - this.defines = {}; this.uniforms = UniformsUtils.merge([ ShaderLib.phong.uniforms diff --git a/libs/ff-three/source/Grid.ts b/libs/ff-three/source/Grid.ts index 95db809e..540a51ad 100644 --- a/libs/ff-three/source/Grid.ts +++ b/libs/ff-three/source/Grid.ts @@ -20,8 +20,8 @@ export interface IGridProps size: number; mainDivisions: number; subDivisions: number; - mainColor: THREE.Color | string | number; - subColor: THREE.Color | string | number; + mainColor: Color | string | number; + subColor: Color | string | number; } export default class Grid extends LineSegments @@ -51,7 +51,7 @@ export default class Grid extends LineSegments this.geometry = Grid.generate(props); } - protected static generate(props: IGridProps): THREE.BufferGeometry + protected static generate(props: IGridProps): BufferGeometry { const mainColor = new Color(props.mainColor as any); const subColor = new Color(props.subColor as any); diff --git a/libs/ff-three/source/HTMLSprite.ts b/libs/ff-three/source/HTMLSprite.ts index 53ac2708..53b8d4f6 100644 --- a/libs/ff-three/source/HTMLSprite.ts +++ b/libs/ff-three/source/HTMLSprite.ts @@ -10,6 +10,7 @@ import { Camera, Vector2, Vector3, + Object3DEventMap, } from "three"; import CustomElement, { customElement, html } from "@ff/ui/CustomElement"; @@ -31,7 +32,7 @@ export enum EQuadrant { TopRight, TopLeft, BottomLeft, BottomRight } * A Three.js Object representing a 3D renderable part and a 2D (HTML) part. * HTML sprites should have a [[HTMLSpriteGroup]] as their parent. */ -export default class HTMLSprite extends Object3D +export default class HTMLSprite extends Object3D { readonly isHTMLSprite = true; diff --git a/libs/ff-three/source/UniversalCamera.ts b/libs/ff-three/source/UniversalCamera.ts index d7ad7248..8fd7465c 100644 --- a/libs/ff-three/source/UniversalCamera.ts +++ b/libs/ff-three/source/UniversalCamera.ts @@ -159,7 +159,7 @@ export default class UniversalCamera extends Camera // TODO: Implement } - moveToView(boundingBox: THREE.Box3) + moveToView(boundingBox: Box3) { this.updateMatrixWorld(false); _box.copy(boundingBox); @@ -278,20 +278,20 @@ export default class UniversalCamera extends Camera toJSON(meta) { const data = super.toJSON(meta); - - data.object.fov = this.fov; - data.object.size = this.size; - data.object.aspect = this.aspect; - data.object.zoom = this.zoom; - data.object.near = this.near; - data.object.far = this.far; - - data.object.focus = this.focus; - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; + Object.assign(data.object, { + fov: this.fov, + size: this.size, + aspect: this.aspect, + zoom: this.zoom, + near: this.near, + far: this.far, + focus: this.focus, + filmGauge: this.filmGauge, + filmOffset: this.filmOffset, + }); if (this.view !== null) { - data.object.view = Object.assign({}, this.view); + (data.object as any).view = Object.assign({}, this.view); } return data; diff --git a/package-lock.json b/package-lock.json index a5a199f6..11700664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "voyager", - "version": "0.42.0", + "version": "0.45.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "voyager", - "version": "0.42.0", + "version": "0.45.0", "license": "Apache-2.0", "dependencies": { "ajv": "^8.6.2", "buffer": "^6.0.3", "client-zip": "^2.0.0", "clone-deep": "^4.0.1", - "express": "^4.18.2", + "express": "^4.21.0", "filenamify": "^4.3.0", "handlebars": "^4.7.7", "hotkeys-js": "^3.8.7", @@ -30,10 +30,10 @@ "simple-dropzone": "^0.8.3", "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", - "three": "^0.158.0", + "three": "^0.168.0", "tinymce": "^6.3.1", "toposort": "^2.0.2", - "webdav": "^5.3.0", + "webdav": "^5.7.1", "webdav-server": "^2.6.2", "xml-js": "^1.6.11" }, @@ -41,7 +41,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/node": "^14.0.26", - "@types/three": "^0.155.1", + "@types/three": "^0.168.0", "chai": "^4.3.8", "concurrently": "^6.2.1", "copy-webpack-plugin": "^11.0.0", @@ -70,9 +70,9 @@ } }, "node_modules/@buttercup/fetch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.1.2.tgz", - "integrity": "sha512-mDBtsysQ0Gnrp4FamlRJGpu7HUHwbyLC4uUav1I7QAqThFAa/4d1cdZCxrV5gKvh6zO1fu95bILNJi4Y2hALhQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.2.1.tgz", + "integrity": "sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==", "optionalDependencies": { "node-fetch": "^3.3.0" } @@ -184,9 +184,9 @@ } }, "node_modules/@tweenjs/tween.js": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", - "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "dev": true }, "node_modules/@types/chai": { @@ -247,16 +247,16 @@ "dev": true }, "node_modules/@types/three": { - "version": "0.155.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.155.1.tgz", - "integrity": "sha512-uNUwnz/wWRxahjIqTtDYQ1qdE1R1py21obxfuILkT+kKrjocMwRLQQA1l8nMxfQU7VXb7CXu04ucMo8OqZt4ZA==", + "version": "0.168.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.168.0.tgz", + "integrity": "sha512-qAGLGzbaYgkkonOBfwOr+TZpOskPfFjrDAj801WQSVkUz0/D9zwir4vhruJ/CC/GteywzR9pqeVVfs5th/2oKw==", "dev": true, "dependencies": { - "@tweenjs/tween.js": "~18.6.4", + "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", - "fflate": "~0.6.9", - "lil-gui": "~0.17.0", + "@webgpu/types": "*", + "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, @@ -397,6 +397,12 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webgpu/types": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.44.tgz", + "integrity": "sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==", + "dev": true + }, "node_modules/@webpack-cli/configtest": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", @@ -690,9 +696,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -702,7 +708,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -712,14 +718,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -1542,7 +1540,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "optional": true, "engines": { "node": ">= 12" } @@ -1626,6 +1623,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -1780,9 +1785,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -1957,36 +1962,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1997,14 +2002,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -2062,17 +2059,17 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-xml-parser": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.1.tgz", - "integrity": "sha512-viVv3xb8D+SiS1W4cv4tva3bni08kAkx0gQnWrykMM8nXPc1FxqZPU00dCEVjkiCg4HoXd2jC4x29Nzg/l2DAA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { @@ -2117,7 +2114,6 @@ "url": "https://paypal.me/jimmywarting" } ], - "optional": true, "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -2127,9 +2123,9 @@ } }, "node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true }, "node_modules/filename-reserved-regex": { @@ -2168,12 +2164,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -2224,7 +2220,6 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "optional": true, "dependencies": { "fetch-blob": "^3.1.2" }, @@ -2486,6 +2481,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, "bin": { "he": "bin/he" } @@ -2593,19 +2589,6 @@ "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2708,9 +2691,9 @@ } }, "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/interpret": { "version": "2.2.0", @@ -2971,9 +2954,9 @@ } }, "node_modules/layerr": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz", - "integrity": "sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz", + "integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==" }, "node_modules/license-checker": { "version": "25.0.1", @@ -3065,12 +3048,6 @@ "nopt": "bin/nopt.js" } }, - "node_modules/lil-gui": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", - "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", - "dev": true - }, "node_modules/lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -3249,9 +3226,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -3550,14 +3530,6 @@ "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3620,7 +3592,6 @@ "url": "https://paypal.me/jimmywarting" } ], - "optional": true, "engines": { "node": ">=10.5.0" } @@ -3629,7 +3600,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "optional": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -3776,9 +3746,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3978,9 +3951,9 @@ "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -4665,11 +4638,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -5207,9 +5180,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -5229,10 +5202,10 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -5263,14 +5236,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -5566,11 +5539,6 @@ "readable-stream": "^3.5.0" } }, - "node_modules/stream-browserify/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5791,9 +5759,9 @@ "dev": true }, "node_modules/three": { - "version": "0.158.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.158.0.tgz", - "integrity": "sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ==" + "version": "0.168.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.168.0.tgz", + "integrity": "sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==" }, "node_modules/timsort": { "version": "0.3.0", @@ -6176,35 +6144,35 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "optional": true, + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "engines": { "node": ">= 8" } }, "node_modules/webdav": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.3.0.tgz", - "integrity": "sha512-xRu/URZGCxDPXmT+9Gu6tNGvlETBwjcuz69lx/6Qlq/0q3Gu2GSVyRt+mP0vTlLFfaY3xZ5O/SPTQ578tC/45Q==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.7.1.tgz", + "integrity": "sha512-JVPn3nLxXJfHSRvennHsOrDYjFLkilZ1Qlw8Ff6hpqp6AvkgF7a//aOh5wA4rMp+sLZ1Km0V+iv0LyO1FIwtXg==", "dependencies": { - "@buttercup/fetch": "^0.1.1", + "@buttercup/fetch": "^0.2.1", "base-64": "^1.0.0", "byte-length": "^1.0.2", - "fast-xml-parser": "^4.2.4", - "he": "^1.2.0", - "hot-patcher": "^2.0.0", - "layerr": "^2.0.1", + "entities": "^5.0.0", + "fast-xml-parser": "^4.4.1", + "hot-patcher": "^2.0.1", + "layerr": "^3.0.0", "md5": "^2.3.0", - "minimatch": "^7.4.6", + "minimatch": "^9.0.5", "nested-property": "^4.0.0", + "node-fetch": "^3.3.2", "path-posix": "^1.0.0", "url-join": "^5.0.0", "url-parse": "^1.5.10" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/webdav-server": { @@ -6227,15 +6195,26 @@ "balanced-match": "^1.0.0" } }, + "node_modules/webdav/node_modules/entities": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", + "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/webdav/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6703,9 +6682,9 @@ }, "dependencies": { "@buttercup/fetch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.1.2.tgz", - "integrity": "sha512-mDBtsysQ0Gnrp4FamlRJGpu7HUHwbyLC4uUav1I7QAqThFAa/4d1cdZCxrV5gKvh6zO1fu95bILNJi4Y2hALhQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.2.1.tgz", + "integrity": "sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==", "requires": { "node-fetch": "^3.3.0" } @@ -6793,9 +6772,9 @@ "dev": true }, "@tweenjs/tween.js": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", - "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "dev": true }, "@types/chai": { @@ -6856,16 +6835,16 @@ "dev": true }, "@types/three": { - "version": "0.155.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.155.1.tgz", - "integrity": "sha512-uNUwnz/wWRxahjIqTtDYQ1qdE1R1py21obxfuILkT+kKrjocMwRLQQA1l8nMxfQU7VXb7CXu04ucMo8OqZt4ZA==", + "version": "0.168.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.168.0.tgz", + "integrity": "sha512-qAGLGzbaYgkkonOBfwOr+TZpOskPfFjrDAj801WQSVkUz0/D9zwir4vhruJ/CC/GteywzR9pqeVVfs5th/2oKw==", "dev": true, "requires": { - "@tweenjs/tween.js": "~18.6.4", + "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", - "fflate": "~0.6.9", - "lil-gui": "~0.17.0", + "@webgpu/types": "*", + "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, @@ -7006,6 +6985,12 @@ "@xtuc/long": "4.2.2" } }, + "@webgpu/types": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.44.tgz", + "integrity": "sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==", + "dev": true + }, "@webpack-cli/configtest": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", @@ -7215,9 +7200,9 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -7227,17 +7212,12 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -7817,8 +7797,7 @@ "data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "optional": true + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, "date-fns": { "version": "2.25.0", @@ -7870,6 +7849,11 @@ "gopd": "^1.0.1" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -7992,9 +7976,9 @@ "dev": true }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "enhanced-resolve": { "version": "5.8.3", @@ -8116,36 +8100,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8153,11 +8137,6 @@ "vary": "~1.1.2" }, "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8197,9 +8176,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-xml-parser": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.1.tgz", - "integrity": "sha512-viVv3xb8D+SiS1W4cv4tva3bni08kAkx0gQnWrykMM8nXPc1FxqZPU00dCEVjkiCg4HoXd2jC4x29Nzg/l2DAA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", "requires": { "strnum": "^1.0.5" } @@ -8229,16 +8208,15 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "optional": true, "requires": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "dev": true }, "filename-reserved-regex": { @@ -8265,12 +8243,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8308,7 +8286,6 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "optional": true, "requires": { "fetch-blob": "^3.1.2" } @@ -8484,7 +8461,8 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true }, "hosted-git-info": { "version": "2.8.9", @@ -8560,18 +8538,6 @@ "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } } }, "human-signals": { @@ -8638,9 +8604,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "interpret": { "version": "2.2.0", @@ -8831,9 +8797,9 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "layerr": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz", - "integrity": "sha512-z0730CwG/JO24evdORnyDkwG1Q7b7mF2Tp1qRQ0YvrMMARbt1DFG694SOv439Gm7hYKolyZyaB49YIrYIfZBdg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz", + "integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==" }, "license-checker": { "version": "25.0.1", @@ -8915,12 +8881,6 @@ } } }, - "lil-gui": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", - "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", - "dev": true - }, "lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -9067,9 +9027,9 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -9278,13 +9238,6 @@ "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.0.2" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } } }, "ms": { @@ -9328,14 +9281,12 @@ "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "optional": true + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, "node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "optional": true, "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -9443,9 +9394,9 @@ "dev": true }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "on-finished": { "version": "2.3.0", @@ -9600,9 +9551,9 @@ "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "path-type": { "version": "4.0.0", @@ -10048,11 +9999,11 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "querystringify": { @@ -10416,9 +10367,9 @@ "dev": true }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -10435,10 +10386,10 @@ "statuses": "2.0.1" }, "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "ms": { "version": "2.1.3", @@ -10465,14 +10416,14 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "set-function-length": { @@ -10708,13 +10659,6 @@ "requires": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } } }, "string_decoder": { @@ -10855,9 +10799,9 @@ } }, "three": { - "version": "0.158.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.158.0.tgz", - "integrity": "sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ==" + "version": "0.168.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.168.0.tgz", + "integrity": "sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==" }, "timsort": { "version": "0.3.0", @@ -11140,26 +11084,26 @@ } }, "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "optional": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, "webdav": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.3.0.tgz", - "integrity": "sha512-xRu/URZGCxDPXmT+9Gu6tNGvlETBwjcuz69lx/6Qlq/0q3Gu2GSVyRt+mP0vTlLFfaY3xZ5O/SPTQ578tC/45Q==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.7.1.tgz", + "integrity": "sha512-JVPn3nLxXJfHSRvennHsOrDYjFLkilZ1Qlw8Ff6hpqp6AvkgF7a//aOh5wA4rMp+sLZ1Km0V+iv0LyO1FIwtXg==", "requires": { - "@buttercup/fetch": "^0.1.1", + "@buttercup/fetch": "^0.2.1", "base-64": "^1.0.0", "byte-length": "^1.0.2", - "fast-xml-parser": "^4.2.4", - "he": "^1.2.0", - "hot-patcher": "^2.0.0", - "layerr": "^2.0.1", + "entities": "^5.0.0", + "fast-xml-parser": "^4.4.1", + "hot-patcher": "^2.0.1", + "layerr": "^3.0.0", "md5": "^2.3.0", - "minimatch": "^7.4.6", + "minimatch": "^9.0.5", "nested-property": "^4.0.0", + "node-fetch": "^3.3.2", "path-posix": "^1.0.0", "url-join": "^5.0.0", "url-parse": "^1.5.10" @@ -11173,10 +11117,15 @@ "balanced-match": "^1.0.0" } }, + "entities": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", + "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==" + }, "minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "requires": { "brace-expansion": "^2.0.1" } diff --git a/package.json b/package.json index ebf74d03..66ad70cb 100755 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "name": "voyager", - "version": "0.44.0", + "version": "0.45.0", "description": "Smithsonian DPO Voyager - 3D Explorer and Tool Suite", "scripts": { "start": "npm run server", "server": "nodemon services/server/bin/index.js", - "watch": "concurrently \"cd source/client && webpack --watch --env=app=story\" \"tsc -b source/server -w\" \"nodemon services/server/bin/index.js\"", + "watch": "concurrently \"cd source/client && webpack --watch --env=app=story\" \"tsc -p source/server -w\" \"nodemon services/server/bin/index.js\"", "build-dev": "cd source/client; webpack --mode=development --env=app=all; cd ../..", "build-dev-local": "cd source/client; webpack --mode=development --env=app=all --local=true; cd ../..", "build-prod": "cd source/client; webpack --mode=production --env=app=all; cd ../..", "build-prod-local": "cd source/client; webpack --mode=production --env=app=all --local=true; cd ../..", - "build-server": "tsc -b source/server", + "build-server": "tsc -p source/server", "build-libs": "tsc -b libs", "docs": "cd docs && jekyll build", "doc": "typedoc --name \"Smithsonian Voyager\" --exclude \"**/node_modules/**\" --ignoreCompilerErrors --mode file --target ES6 --theme minimal --out doc/ source/", @@ -29,7 +29,7 @@ "node_modules/@ff/server" ] }, - "engines":{ + "engines": { "node": ">=14.15.0" }, "repository": { @@ -55,7 +55,7 @@ "buffer": "^6.0.3", "client-zip": "^2.0.0", "clone-deep": "^4.0.1", - "express": "^4.18.2", + "express": "^4.21.0", "filenamify": "^4.3.0", "handlebars": "^4.7.7", "hotkeys-js": "^3.8.7", @@ -72,10 +72,10 @@ "simple-dropzone": "^0.8.3", "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", - "three": "^0.158.0", + "three": "^0.168.0", "tinymce": "^6.3.1", "toposort": "^2.0.2", - "webdav": "^5.3.0", + "webdav": "^5.7.1", "webdav-server": "^2.6.2", "xml-js": "^1.6.11" }, @@ -83,7 +83,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/node": "^14.0.26", - "@types/three": "^0.155.1", + "@types/three": "^0.168.0", "chai": "^4.3.8", "concurrently": "^6.2.1", "copy-webpack-plugin": "^11.0.0", diff --git a/source/client/annotations/AnnotationSprite.ts b/source/client/annotations/AnnotationSprite.ts index a4d96fe0..93999ef6 100755 --- a/source/client/annotations/AnnotationSprite.ts +++ b/source/client/annotations/AnnotationSprite.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Object3D, Camera, ArrayCamera, PerspectiveCamera, Vector3 } from "three"; +import { Object3D, Camera, ArrayCamera, PerspectiveCamera, Vector3, Object3DEventMap } from "three"; import { ITypedEvent } from "@ff/core/Publisher"; import HTMLSprite, { SpriteElement, html } from "@ff/three/HTMLSprite"; @@ -54,6 +54,11 @@ export interface IAnnotationLinkEvent extends ITypedEvent<"link"> link: string; } +export interface IAnnotationEventMap extends Object3DEventMap{ + click: IAnnotationClickEvent; + link: IAnnotationLinkEvent; +} + /** * Defines the visual appearance of an annotation. * An annotation consists of a 3D (WebGL) part and a 2D (HTML) part. @@ -62,7 +67,7 @@ export interface IAnnotationLinkEvent extends ITypedEvent<"link"> * - *"click"* Emitted if the user clicks on the annotation. * - *"link"* Emitted if the user activates a link on the annotation. */ -export default class AnnotationSprite extends HTMLSprite +export default class AnnotationSprite extends HTMLSprite { static readonly typeName: string = "Annotation"; diff --git a/source/client/applications/storyTypes.ts b/source/client/applications/storyTypes.ts index 0bc892de..9d540e8b 100644 --- a/source/client/applications/storyTypes.ts +++ b/source/client/applications/storyTypes.ts @@ -29,6 +29,7 @@ import CVArticlesTask from "../components/CVArticlesTask"; import CVToursTask from "../components/CVToursTask"; import CVDerivativesTask from "../components/CVDerivativesTask"; import CVStandaloneFileManager from "../components/CVStandaloneFileManager"; +import CVOverlayTask from "../components/CVOverlayTask"; import CVAudioTask from "../components/CVAudioTask"; import NVVoyagerStory from "../nodes/NVoyagerStory"; @@ -51,6 +52,7 @@ const types = [ CVArticlesTask, CVToursTask, CVDerivativesTask, + CVOverlayTask, CVAudioTask, NVVoyagerStory, diff --git a/source/client/applications/taskSets.ts b/source/client/applications/taskSets.ts index b31311f0..57510f13 100644 --- a/source/client/applications/taskSets.ts +++ b/source/client/applications/taskSets.ts @@ -22,6 +22,7 @@ import CVAnnotationsTask from "../components/CVAnnotationsTask"; import CVDerivativesTask from "../components/CVDerivativesTask"; import CVArticlesTask from "../components/CVArticlesTask"; import CVToursTask from "../components/CVToursTask"; +import CVOverlayTask from "../components/CVOverlayTask"; import CVAudioTask from "../components/CVAudioTask"; //////////////////////////////////////////////////////////////////////////////// @@ -36,6 +37,7 @@ export default { CVAnnotationsTask, CVArticlesTask, CVToursTask, + //CVOverlayTask, CVAudioTask, CVSettingsTask, ], @@ -59,6 +61,7 @@ export default { CVAnnotationsTask, CVArticlesTask, CVToursTask, + CVOverlayTask, CVAudioTask, CVSettingsTask, ], @@ -69,6 +72,7 @@ export default { CVAnnotationsTask, CVArticlesTask, CVToursTask, + //CVOverlayTask, CVAudioTask, CVSettingsTask, ] diff --git a/source/client/components/CVAnnotationView.ts b/source/client/components/CVAnnotationView.ts index 656e1306..d5b8f2a7 100755 --- a/source/client/components/CVAnnotationView.ts +++ b/source/client/components/CVAnnotationView.ts @@ -20,7 +20,7 @@ import { Dictionary } from "@ff/core/types"; import { ITypedEvent, Node, types } from "@ff/graph/Component"; import Viewport, { IViewportDisposeEvent } from "@ff/three/Viewport"; -import HTMLSpriteGroup, { HTMLSprite } from "@ff/three/HTMLSpriteGroup"; +import HTMLSpriteGroup from "@ff/three/HTMLSpriteGroup"; import CObject3D, { IPointerEvent, IRenderContext } from "@ff/scene/components/CObject3D"; import CRenderer from "@ff/scene/components/CRenderer"; @@ -95,7 +95,7 @@ export default class CVAnnotationView extends CObject3D private _annotations: Dictionary = {}; private _viewports = new Set(); - private _sprites: Dictionary = {}; + private _sprites: Dictionary = {}; private _truncateLock = false; private _activeView = false; diff --git a/source/client/components/CVAnnotationsTask.ts b/source/client/components/CVAnnotationsTask.ts index b9d466f4..504cdde2 100644 --- a/source/client/components/CVAnnotationsTask.ts +++ b/source/client/components/CVAnnotationsTask.ts @@ -34,9 +34,10 @@ import CVAnnotationView, { IAnnotationsUpdateEvent, IAnnotationClickEvent } from import AnnotationsTaskView from "../ui/story/AnnotationsTaskView"; import CVScene from "client/components/CVScene"; -import { ELanguageStringType, ELanguageType, DEFAULT_LANGUAGE } from "client/schema/common"; +import { ELanguageType, DEFAULT_LANGUAGE } from "client/schema/common"; import { getMeshTransform } from "client/utils/Helpers"; import CVSnapshots, { EEasingCurve } from "./CVSnapshots"; +import CPulse from "@ff/graph/components/CPulse"; //////////////////////////////////////////////////////////////////////////////// @@ -67,7 +68,6 @@ export default class CVAnnotationsTask extends CVTask private _activeAnnotations: CVAnnotationView = null; private _defaultScale = 1; private _machine: CVSnapshots = null; - private _state: string = null; constructor(node: Node, id: string) { @@ -90,6 +90,10 @@ export default class CVAnnotationsTask extends CVTask } } + get selectedState() { + return this._activeAnnotations.activeAnnotation.data.viewId; + } + createView() { return new AnnotationsTaskView(this); @@ -100,28 +104,22 @@ export default class CVAnnotationsTask extends CVTask this.startObserving(); super.activateTask(); this.synchAudioOptions(); - - //this.selection.selectedComponents.on(CVAnnotationView, this.onSelectAnnotations, this); - //this.system.on("pointer-up", this.onPointerUp, this); } deactivateTask() { this.stopObserving(); - //this.selection.selectedComponents.off(CVAnnotationView, this.onSelectAnnotations, this); - //this.system.off("pointer-up", this.onPointerUp, this); super.deactivateTask(); } update(context) { - const {ins, outs} = this; + const {ins} = this; if(!this.activeDocument) { return false; } - const languageManager = this.activeDocument.setup.language; if (ins.mode.changed) { this.emitUpdateEvent(); @@ -180,7 +178,6 @@ export default class CVAnnotationsTask extends CVTask threshold: 0.5, }); - this._state = id; const annotation = this._activeAnnotations.activeAnnotation; annotation.set("viewId", id); this._activeAnnotations.updateAnnotation(annotation, true); @@ -190,14 +187,23 @@ export default class CVAnnotationsTask extends CVTask restoreAnnotationView() { const machine = this._machine; - machine.ins.id.setValue(this._state); - machine.ins.tween.set(); + const annotation = this._activeAnnotations.activeAnnotation; + + // If activeAnnotation is being tracked, make sure it is set + const activeIdx = machine.getTargetProperties().findIndex(prop => prop.name == "ActiveId"); + if(activeIdx >= 0) { + const viewState = machine.getState(this.selectedState); + viewState.values[activeIdx] = annotation.data.id; + } + + const pulse = this.getMainComponent(CPulse); + machine.tweenTo(this.selectedState, pulse.context.secondsElapsed); } deleteAnnotationView() { const machine = this._machine; - machine.deleteState(this._state); + machine.deleteState(this.selectedState); const annotation = this._activeAnnotations.activeAnnotation; annotation.set("viewId", ""); this._activeAnnotations.updateAnnotation(annotation, true); diff --git a/source/client/components/CVAssetReader.ts b/source/client/components/CVAssetReader.ts index 0e75ca8c..ab7b7298 100644 --- a/source/client/components/CVAssetReader.ts +++ b/source/client/components/CVAssetReader.ts @@ -25,6 +25,7 @@ import FontReader, { IBitmapFont } from "../io/FontReader"; import CVAssetManager from "./CVAssetManager"; import CRenderer from "@ff/scene/components/CRenderer"; +import { Object3D, BufferGeometry, Texture } from "three"; //////////////////////////////////////////////////////////////////////////////// @@ -103,19 +104,19 @@ export default class CVAssetReader extends Component return this.fileLoader.getText(url); } - async getModel(assetPath: string): Promise + async getModel(assetPath: string): Promise { const url = this.assetManager.getAssetUrl(assetPath); return this.modelLoader.get(url); } - async getGeometry(assetPath: string): Promise + async getGeometry(assetPath: string): Promise { const url = this.assetManager.getAssetUrl(assetPath); return this.geometryLoader.get(url); } - async getTexture(assetPath: string): Promise + async getTexture(assetPath: string): Promise { const url = this.assetManager.getAssetUrl(assetPath); return this.textureLoader.get(url); @@ -127,7 +128,7 @@ export default class CVAssetReader extends Component return this.fontReader.load(url); } - async getSystemTexture(assetPath: string): Promise + async getSystemTexture(assetPath: string): Promise { const url = this.getSystemAssetUrl(assetPath); return this.textureLoader.get(url); diff --git a/source/client/components/CVEnvironmentTool.ts b/source/client/components/CVEnvironmentTool.ts index 442d1dba..18eb9b9d 100644 --- a/source/client/components/CVEnvironmentTool.ts +++ b/source/client/components/CVEnvironmentTool.ts @@ -73,12 +73,12 @@ export class EnvironmentToolView extends ToolView return html`
- - ${!isSolid ? html`` : null} + + ${!isSolid ? html`` : null} - + - +
`; diff --git a/source/client/components/CVLightTool.ts b/source/client/components/CVLightTool.ts index 2bdd5c59..725144c1 100644 --- a/source/client/components/CVLightTool.ts +++ b/source/client/components/CVLightTool.ts @@ -114,7 +114,7 @@ export class LightToolView extends ToolView
- +
` : null; diff --git a/source/client/components/CVModel2.ts b/source/client/components/CVModel2.ts index ac2e12a3..f4d397f4 100644 --- a/source/client/components/CVModel2.ts +++ b/source/client/components/CVModel2.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Vector3, Quaternion, Box3, Mesh, Group, Matrix4, Box3Helper, Object3D, FrontSide, BackSide, DoubleSide } from "three"; +import { Vector3, Quaternion, Box3, Mesh, Group, Matrix4, Box3Helper, Object3D, FrontSide, BackSide, DoubleSide, Texture, CanvasTexture } from "three"; import Notification from "@ff/ui/Notification"; @@ -41,6 +41,7 @@ import CRenderer from "@ff/scene/components/CRenderer"; import CVEnvironment from "./CVEnvironment"; import CVSetup from "./CVSetup"; import { Dictionary } from "client/../../libs/ff-core/source/types"; +import Asset from "client/models/Asset"; //////////////////////////////////////////////////////////////////////////////// @@ -59,6 +60,17 @@ export interface IModelLoadEvent extends ITypedEvent<"model-load"> quality: EDerivativeQuality; } +/** + * Describes an overlay image + */ +export interface IOverlay +{ + texture: Texture; + asset: Asset; + fromFile: boolean; + isDirty: boolean; +} + /** * Graph component rendering a model or model part. * @@ -105,6 +117,7 @@ export default class CVModel2 extends CObject3D unitScale: types.Number("UnitScale", { preset: 1, precision: 5 }), quality: types.Enum("LoadedQuality", EDerivativeQuality), updated: types.Event("Updated"), + overlayMap: types.Option("Material.OverlayMap", ["None"], 0) }; ins = this.addInputs(CVModel2.ins); @@ -142,6 +155,7 @@ export default class CVModel2 extends CObject3D private _prevPosition: Vector3 = new Vector3(0.0,0.0,0.0); private _prevRotation: Vector3 = new Vector3(0.0,0.0,0.0); private _materialCache: Dictionary = {}; + private _overlays: Dictionary = {}; constructor(node: Node, id: string) { @@ -184,6 +198,39 @@ export default class CVModel2 extends CObject3D return this.getMainComponent(CRenderer); } + getOverlay(key: string) : IOverlay { + if(key in this._overlays) { + return this._overlays[key]; + } + else { + const overlayProp = this.ins.overlayMap; + if(!overlayProp.schema.options.includes(key)) { + overlayProp.setOptions(overlayProp.schema.options.concat(key)); + } + + return this._overlays[key] = + { + texture: null, + asset: null, + fromFile: false, + isDirty: false + }; + } + } + getOverlays() { //TODO: This should be more efficient + return Object.keys(this._overlays).filter(key => this.activeDerivative.findAssets(EAssetType.Image).some(image => image.data.uri == key)) + .map(key => this._overlays[key]); + } + deleteOverlay(key: string) { + const overlayProp = this.ins.overlayMap; + const options = overlayProp.schema.options; + options.splice(options.indexOf(key),1); + overlayProp.setOptions(options); + + this._overlays[key].texture.dispose(); + delete this._overlays[key]; + } + create() { super.create(); @@ -327,6 +374,9 @@ export default class CVModel2 extends CObject3D { this.derivatives.clear(); this._activeDerivative = null; + for (let key in this._overlays) { + this._overlays[key].texture.dispose(); + } super.dispose(); } @@ -553,30 +603,44 @@ export default class CVModel2 extends CObject3D }); return; } - const mapURI = this.ins.overlayMap.getOptionText(); - if (mapURI !== "None") { - this.assetReader.getTexture(mapURI).then(texture => { - this.object3D.traverse(object => { - const material = object["material"]; - if (material && material.isUberPBRMaterial) { - texture.flipY = false; - material.zoneMap = texture; - material.enableZoneMap(true); - } + + const overlays = this.getOverlays(); + const currIdx = this.ins.overlayMap.value-1; + if (currIdx >= 0 && overlays.length > currIdx) { + const mapURI = this.getOverlays()[currIdx].asset.data.uri; + const texture = this.getOverlay(mapURI).texture; + if(texture) { + this.updateOverlayMaterial(texture, mapURI); + } + else { + this.assetReader.getTexture(mapURI).then(map => { + this.getOverlay(mapURI).texture = map; + this.updateOverlayMaterial(map, mapURI); }); - }); + } } else { - this.object3D.traverse(object => { - const material = object["material"]; - if (material && material.isUberPBRMaterial) { - material.enableZoneMap(false); - material.zoneMap = null; - } - }); + this.updateOverlayMaterial(null, null); } } + // helper function to update overlay map state + protected updateOverlayMaterial(texture: Texture, uri: string) + { + this.object3D.traverse(object => { + const material = object["material"]; + if (material && material.isUberPBRMaterial) { + if(texture) { + texture.flipY = false; + material.enableOverlayAlpha(uri.endsWith(".jpg")); + } + material.zoneMap = texture; + material.enableZoneMap(texture != null); + } + }); + this.outs.overlayMap.setValue(this.ins.overlayMap.value); + } + protected updateMaterial() { const ins = this.ins; @@ -775,10 +839,16 @@ export default class CVModel2 extends CObject3D if(this.ins.renderOrder.value !== 0) this.updateRenderOrder(this.object3D, this.ins.renderOrder.value); - // set overlay map options - const overlayOptions = this.ins.overlayMap.schema.options || ["None"]; - overlayOptions.push(...derivative.findAssets(EAssetType.Image).filter(image => image.data.mapType === EMapType.Zone).map(image => image.data.uri)); - this.ins.overlayMap.setOptions(overlayOptions); + // load overlays + const overlayProp = this.ins.overlayMap; + overlayProp.setOptions(["None"]); + derivative.findAssets(EAssetType.Image).filter(image => image.data.mapType === EMapType.Zone).forEach(image => { + overlayProp.setOptions(overlayProp.schema.options.concat(image.data.uri)); + const overlay = this.getOverlay(image.data.uri); + overlay.asset = image; + overlay.fromFile = true; + }); + if(this.ins.overlayMap.value !== 0) { this.ins.overlayMap.set(); } diff --git a/source/client/components/CVOverlayTask.ts b/source/client/components/CVOverlayTask.ts new file mode 100644 index 00000000..6e36cbda --- /dev/null +++ b/source/client/components/CVOverlayTask.ts @@ -0,0 +1,566 @@ +/** + * 3D Foundation Project + * Copyright 2024 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Node } from "@ff/graph/Component"; +import { IPointerEvent } from "@ff/scene/RenderView"; +import CRenderer from "@ff/scene/components/CRenderer"; +import Notification from "@ff/ui/Notification"; +import convert from "@ff/browser/convert"; +import { Dictionary } from "@ff/core/types"; + +import CVTask, { types } from "./CVTask"; +import OverlayTaskView from "../ui/story/OverlayTaskView"; + +import CVModel2 from "./CVModel2"; +import CVAssetManager from "./CVAssetManager"; + +import NVNode from "../nodes/NVNode"; +import {Vector2, CanvasTexture, Mesh} from 'three'; +import VGPUPicker from "../utils/VGPUPicker"; +import { EDerivativeQuality, EDerivativeUsage, EAssetType, EMapType } from "client/schema/model"; +import UberPBRMaterial from "client/shaders/UberPBRMaterial"; +import UberPBRAdvMaterial from "client/shaders/UberPBRAdvMaterial"; +import CVAssetReader from "./CVAssetReader"; +import CVStandaloneFileManager from "./CVStandaloneFileManager"; + +//////////////////////////////////////////////////////////////////////////////// + +export interface IGLTFIndexMapExtension +{ + blendFactor: number; + indexTexture: number; +} + +export interface IGLTFExportOptions +{ + binary: boolean; + includeCustomExtensions: boolean; +} + +export enum EPaintMode { Interact, Paint, Erase }; + +export default class CVOverlayTask extends CVTask +{ + static readonly typeName: string = "CVOverlayTask"; + + static readonly text: string = "Overlay"; + static readonly icon: string = "brush"; + + protected static readonly ins = { + activeNode: types.String("Targets.ActiveNode"), + activeIndex: types.Integer("Overlay.Index", -1), + createOverlay: types.Event("Overlay.Create"), + deleteOverlay: types.Event("Overlay.Delete"), + saveOverlays: types.Event("Overlay.Save"), + overlayFill: types.Event("Overlay.Fill"), + overlayClear: types.Event("Overlay.Clear"), + overlayOpacity: types.Percent("Overlay.Opacity", 1.0), + overlayColor: types.ColorRGB("Overlay.Color", [1.0, 0.0, 0.0]), + overlayBrushSize: types.Unit("Overlay.BrushSize", {preset: 10, min: 1, max: 100}), + paintMode: types.Enum("Paint.Mode", EPaintMode, EPaintMode.Interact) + }; + + protected static readonly outs = { + }; + + ins = this.addInputs(CVOverlayTask.ins); + outs = this.addOutputs(CVOverlayTask.outs); + + isPainting: boolean = false; + + //protected qualities: EDerivativeQuality[] = [EDerivativeQuality.Low, EDerivativeQuality.Medium, EDerivativeQuality.High]; + + protected ctx: CanvasRenderingContext2D; + protected activeModel: CVModel2; + protected picker: VGPUPicker; + protected uv: Vector2; + + private _oldColor: number[] = [1.0, 0.0, 0.0]; + private _canvasMap: Dictionary = {}; + private _textureMap: Dictionary = {}; + + protected get assetManager() { + return this.getMainComponent(CVAssetManager); + } + protected get assetReader() { + return this.getMainComponent(CVAssetReader); + } + + get material() { + let mat = null; + if(this,this.activeModel.object3D.type === "Mesh") { + const mesh = this.activeModel.object3D as Mesh; + mat = mesh.material as UberPBRMaterial | UberPBRAdvMaterial; + } + else { + const mesh = this.activeModel.object3D.getObjectByProperty("type", "Mesh") as Mesh; + if(mesh) { + mat = mesh.material as UberPBRMaterial | UberPBRAdvMaterial; + } + } + return mat; + } + + get overlays() { + return this.activeModel ? this.activeModel.getOverlays() : null; + } + + getCanvas(key: string) { + return this._canvasMap[key] ? this._canvasMap[key] : this._canvasMap[key] = this.createZoneCanvas(); + } + + get activeCanvas() { + const idx = this.ins.activeIndex.value; + const key = this.overlays[idx].asset.data.uri; + return idx >= 0 ? this.getCanvas(key) : null; + } + + getTexture(key: string) { + const idx = this.ins.activeIndex.value; + const overlay = this.overlays[idx]; + + // replace Texture from file with CanvasTexture + if(overlay.fromFile) { + overlay.fromFile = false; + overlay.texture.dispose(); + overlay.texture = new CanvasTexture(this.getCanvas(key)); + overlay.texture.flipY = false; + } + + if(!overlay.texture) { + overlay.texture = new CanvasTexture(this.getCanvas(key)); + overlay.texture.flipY = false; + } + + return overlay.texture; + } + setTexture(key: string, texture: CanvasTexture) { + this._textureMap[key] = texture; + } + + get activeTexture() { + const idx = this.ins.activeIndex.value; + return idx >= 0 ? this.getTexture(this.overlays[idx].asset.data.uri) : null; + } + + get colorString() { + return "#" + Math.round(this.ins.overlayColor.value[0]*255).toString(16).padStart(2, '0') + Math.round(this.ins.overlayColor.value[1]*255).toString(16).padStart(2, '0') + + Math.round(this.ins.overlayColor.value[2]*255).toString(16).padStart(2, '0') + Math.round(this.ins.overlayOpacity.value*255).toString(16).padStart(2, '0'); + } + + constructor(node: Node, id: string) + { + super(node, id); + + const configuration = this.configuration; + configuration.bracketsVisible = true; + + this.uv = new Vector2(); + } + + create() + { + super.create(); + this.startObserving(); + } + + dispose() + { + this.stopObserving(); + + for (let key in this._canvasMap) { + this._canvasMap[key] = null; + } + + super.dispose(); + } + + update(context) + { + const ins = this.ins; + const idx = this.ins.activeIndex.value; + const overlays = this.overlays; + const model = this.activeModel; + + if (!overlays) { + return false; + } + + const overlay = overlays[idx]; + + if(ins.activeIndex.changed) { + if(overlay && overlay.fromFile && !overlay.texture) { + // load texture from file if not done yet + this.assetReader.getTexture(overlay.asset.data.uri).then((map) => { + map.flipY = false; + overlay.texture = map; + this.material.zoneMap = map; + this.onOverlayChange(); + }); + } + else { + this.onOverlayChange(); + } + return true; + } + + if(ins.createOverlay.changed) + { + const derivative = model.activeDerivative; + const qualityName = EDerivativeQuality[derivative.data.quality].toLowerCase(); + const newUri = this.getUniqueName(model.node.name, qualityName); + const newAsset = derivative.createAsset(EAssetType.Image, newUri); + newAsset.data.mapType = EMapType.Zone; + + // add new overlay + const newOverlay = model.getOverlay(newUri); + ins.activeIndex.setValue(this.overlays.length - 1); + newOverlay.texture = this.getTexture(newUri); + newOverlay.asset = newAsset; + this.onSave(); + + this.onOverlayChange(); + this.emit("update"); + return true; + } + + if(ins.deleteOverlay.changed) + { + this._canvasMap[overlay.asset.data.uri] = null; + this.activeModel.deleteOverlay(overlay.asset.data.uri); + this.activeModel.activeDerivative.removeAsset(overlay.asset); + ins.activeIndex.setValue(-1); + + this.onOverlayChange(); + this.emit("update"); + return true; + } + + if(ins.saveOverlays.changed) + { + this.onSave(); + return true; + } + + if(ins.overlayColor.changed || ins.overlayOpacity.changed) + { + const newColor = this.colorString; + this.ctx.fillStyle = newColor; + this.ctx.strokeStyle = newColor; + + /*console.log("color change"); + let pixels = this.ctx.getImageData(0,0,this.overlayCanvas.width,this.overlayCanvas.height); + for(let i=0; i("pointer-up", this.onPointerUp, this); + previous.model.off("pointer-down", this.onPointerDown, this); + previous.model.off("pointer-move", this.onPointerMove, this); + previous.model.outs.quality.off("value", this.onQualityChange, this); + previous.model.outs.overlayMap.off("value", this.onUpdateIdx, this); + } + + if(next && next.model) + { + this.ins.activeNode.setValue(next.name); + this.activeModel = next.model; + this.onUpdateIdx(); + + next.model.on("pointer-up", this.onPointerUp, this); + next.model.on("pointer-down", this.onPointerDown, this); + next.model.on("pointer-move", this.onPointerMove, this); + + next.model.outs.quality.on("value", this.onQualityChange, this); + next.model.outs.overlayMap.on("value", this.onUpdateIdx, this); + + // load overlays + const overlayProp = this.activeModel.ins.overlayMap; + overlayProp.setOptions(["None"]); + this.activeModel.activeDerivative.findAssets(EAssetType.Image).filter(image => image.data.mapType === EMapType.Zone).forEach(image => { + overlayProp.setOptions(overlayProp.schema.options.concat(image.data.uri)); + }); + } + + super.onActiveNode(previous, next); + } + + protected onOverlayChange() + { + const idx = this.ins.activeIndex.value + 1; + const isShowing = idx > 0; + + if(this.activeModel.ins.overlayMap.value != idx) { + this.activeModel.ins.overlayMap.setValue(idx); + } + + if(isShowing) { + const compOp = this.ctx ? this.ctx.globalCompositeOperation : "source-over"; + this.ctx = this.activeCanvas.getContext('2d'); + this.ctx.lineWidth = Math.round(this.ins.overlayBrushSize.value); + this.ctx.fillStyle = this.colorString; + this.ctx.globalCompositeOperation = compOp; + } + + if(this.material) { + this.material.zoneMap = this.activeTexture; + this.material.transparent = isShowing; + this.material.enableZoneMap(isShowing); + } + + if(this.activeTexture) { + this.updateOverlayTexture(); + } + } + + protected onUpdateIdx() + { + const idx = this.activeModel.ins.overlayMap.value - 1; + this.ins.activeIndex.setValue(idx); + } + + protected onQualityChange() + { + this.ins.activeIndex.setValue(-1); + } + + protected updateOverlayTexture() { + this.activeTexture.needsUpdate = true; + this.system.getComponent(CRenderer).forceRender(); + } + + protected onPointerDown(event: IPointerEvent) + { + // do not handle event if user is dragging or task is not active + if (event.isDragging || !this.outs.isActive.value) { + return; + } + + // do not handle if overlay not selected + if(this.ins.activeIndex.value < 0) { + return; + } + + if(!this.picker) { + this.picker = new VGPUPicker(event.view.renderer); + } + + const model = this.activeModel; + // if user left-clicked on model in paint or erase mode + if (this.ins.paintMode.value != EPaintMode.Interact && event.component === model && event.originalEvent.button == 0) { + event.stopPropagation = true; + VGPUPicker.add(model.object3D, true); + this.isPainting = true; + this.draw(event); + } + } + + protected onPointerUp(event: IPointerEvent) + { + if(this.isPainting) { + this.setSaveNeeded(true); + } + + this.isPainting = false; + } + + protected onPointerMove(event: IPointerEvent) + { + if (event.isDragging && this.isPainting) { + event.stopPropagation = true; + this.draw(event); + + return; + } + } + + protected draw(event: IPointerEvent) + { + const sceneComponent = this.system.getComponent(CRenderer, true).activeSceneComponent; + const scene = sceneComponent && sceneComponent.scene; + const camera = sceneComponent && sceneComponent.activeCamera; + + let shaderUVs = this.picker.pickUV(scene, camera, event); + this.uv.setX(shaderUVs.x); + this.uv.setY(shaderUVs.y); + this.activeTexture.transformUv( this.uv ); + + const brushWidth = Math.round(this.ins.overlayBrushSize.value); + + const width = Math.floor(this.uv.x*this.activeCanvas.width)-(brushWidth/2); + const height = Math.floor(this.uv.y*this.activeCanvas.height)-(brushWidth/2); + this.ctx.clearRect(width, height, brushWidth, brushWidth); + this.ctx.fillRect(width, height, brushWidth, brushWidth); + this.updateOverlayTexture(); + } + + protected createZoneCanvas() + { + const material = this.material; + const dim = material.map ? material.map.image.width : 4096; // TODO: Adapt to better size maps for models w/o diffuse + + const canvas = document.createElement('canvas') as HTMLCanvasElement; + const ctx = canvas.getContext('2d'); + canvas.width = dim; + canvas.height = dim; + canvas.style.width = "100%"; + canvas.style.height = "100%"; + canvas.style.objectFit = "scale-down"; + canvas.style.boxSizing = "border-box"; + canvas.style.position = "absolute"; + canvas.style.zIndex = "2"; + canvas.style.setProperty("mix-blend-mode", "multiply"); + ctx.lineWidth = 10; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + ctx.globalAlpha = 1.0; + ctx.fillStyle = "#FFFFFF00"; + ctx.strokeStyle = '#FF000000' + + ctx.fillRect(0,0,dim,dim); + + // if we have a pre-loaded overlay texture we need to copy the image + if(this.overlays[this.ins.activeIndex.value].fromFile) { + const map = this.overlays[this.ins.activeIndex.value].texture; + ctx.save(); + ctx.drawImage(map.image,0,0); + ctx.restore(); + } + + return canvas; + } + + protected onSave() + { + const currentCanvas = this.activeCanvas; + const model = this.activeModel; + const derivative = model.activeDerivative; + const quality = derivative.data.quality; + + const imageName = this.overlays[this.ins.activeIndex.value].asset.data.uri; + + const mimeType = imageName.endsWith(".png") ? "image/png" : "image/jpeg"; + const dataURI = currentCanvas.toDataURL(mimeType); + this.saveTexture(imageName, dataURI, quality); + } + + protected saveTexture(filePath: string, uri: string, quality: EDerivativeQuality) + { + + const fileURL = this.assetManager.getAssetUrl(filePath); + const fileName = this.assetManager.getAssetName(filePath); + const blob = convert.dataURItoBlob(uri); + const file = new File([blob], fileName); + const standaloneFM = this.graph.getMainComponent(CVStandaloneFileManager, true); + + if(standaloneFM) { + standaloneFM.addFile(filePath, [blob]); + new Notification(`Saved ${fileName} to scene package.`, "info", 4000); + } + else { + fetch(fileURL, { + method:"PUT", + body: file, + }) + .then(() => { + this.setSaveNeeded(false); + //this.updateImageMeta(quality, this._mimeType, filePath); + new Notification(`Successfully uploaded image to '${fileURL}'`, "info", 4000); + }) + .catch(e => { + new Notification(`Failed to upload image to '${fileURL}'`, "error", 8000); + }); + } + } + + protected setSaveNeeded(isDirty: boolean) + { + const activeOverlay = this.overlays[this.ins.activeIndex.value]; + activeOverlay.isDirty = isDirty; + this.emit("update"); + } + + protected getUniqueName(name: string, quality: string) : string + { + var newName = name + "-overlaymap-" + (this.overlays.length+1) + "-" + quality + ".png"; + var count = 2; + while(this.overlays.some(overlay => { + return overlay.asset.data.uri == newName; + })) + { + newName = name + "-overlaymap-" + (this.overlays.length+count) + "-" + quality + ".png"; + count++; + } + + return newName; + } +} \ No newline at end of file diff --git a/source/client/schema/document.ts b/source/client/schema/document.ts index 2cdb5ce9..5c0ab29d 100644 --- a/source/client/schema/document.ts +++ b/source/client/schema/document.ts @@ -21,6 +21,7 @@ import { EUnitType, TUnitType, Vector3, Quaternion, Matrix4, ColorRGB } from "./ import { IMeta } from "./meta"; import { IModel } from "./model"; import { ISetup } from "./setup"; +import { QuaternionTuple } from "three"; //////////////////////////////////////////////////////////////////////////////// @@ -75,7 +76,7 @@ export interface INode matrix?: Matrix4; translation?: Vector3; - rotation?: Quaternion; + rotation?: QuaternionTuple; scale?: Vector3; camera?: Index; diff --git a/source/client/schema/model.ts b/source/client/schema/model.ts index 8535b1bd..5b8786b2 100644 --- a/source/client/schema/model.ts +++ b/source/client/schema/model.ts @@ -16,7 +16,8 @@ */ import { Dictionary } from "@ff/core/types"; -import { ColorRGBA, EUnitType, TUnitType, Vector3, Vector4 } from "./common"; +import { ColorRGB, EUnitType, TUnitType, Vector3, Vector4 } from "./common"; +import { QuaternionTuple } from "three"; //////////////////////////////////////////////////////////////////////////////// @@ -51,7 +52,7 @@ export interface IModel overlayMap?: number; shadowSide?: TSideType; translation?: Vector3; - rotation?: Vector4; + rotation?: QuaternionTuple; boundingBox?: IBoundingBox; material?: IPBRMaterialSettings; annotations?: IAnnotation[]; @@ -132,7 +133,7 @@ export interface IAsset export interface IPBRMaterialSettings { - color?: ColorRGBA + color?: ColorRGB opacity?: number; hiddenOpacity?: number; roughness?: number; diff --git a/source/client/shaders/UVShader.ts b/source/client/shaders/UVShader.ts new file mode 100644 index 00000000..cedd24c1 --- /dev/null +++ b/source/client/shaders/UVShader.ts @@ -0,0 +1,62 @@ +/** + * 3D Foundation Project + * Copyright 2020 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ShaderMaterial} from "three"; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Renders the UV coordinates + * Based on ff-three position shader : Copyright 2020 Ralph Wiedemeier, Frame Factory GmbH + */ +export default class UVShader extends ShaderMaterial +{ + isUVShader = true; + + uniforms = { + index: { value: 0 } + }; + + vertexShader = [ + "varying vec2 vUv;", + + "void main() {", + " #include ", + " #include ", + " #include ", + " vUv = uv;", + "}", + ].join("\n"); + + fragmentShader = [ + "uniform float index;", + "varying vec2 vUv;", + + "vec4 toVec4(float v) {", + " float vn = v;", + " float b0 = floor(vn * 255.0) / 255.0; vn = (vn - b0) * 256.0;", + " float b1 = floor(vn * 255.0) / 255.0; vn = (vn - b1) * 256.0;", + " float b2 = floor(vn * 255.0) / 255.0; vn = (vn - b2) * 256.0;", + " float b3 = floor(vn * 255.0) / 255.0;", + " return vec4(clamp(b0, 0.0, 1.0), clamp(b1, 0.0, 1.0), clamp(b2, 0.0, 1.0), clamp(b3, 0.0, 1.0));", + "}", + + "void main() {", + " gl_FragColor = index == 0.0 ? toVec4(vUv.x) : toVec4(vUv.y);", + "}" + ].join("\n"); +} \ No newline at end of file diff --git a/source/client/shaders/UberPBRAdvMaterial.ts b/source/client/shaders/UberPBRAdvMaterial.ts index a115d0bb..82b34f5b 100644 --- a/source/client/shaders/UberPBRAdvMaterial.ts +++ b/source/client/shaders/UberPBRAdvMaterial.ts @@ -34,9 +34,9 @@ export interface IUberPBRAdvShaderProps extends MeshPhysicalMaterialParameters export default class UberPBRMaterial extends MeshPhysicalMaterial { - isUberPBRMaterial: boolean; - isMeshStandardMaterial: boolean; - isMeshPhysicalMaterial: boolean; + readonly isUberPBRMaterial = true; + readonly isMeshStandardMaterial = true; + readonly isMeshPhysicalMaterial = true; uniforms: { aoMapMix: { value: Vector3 }, @@ -66,9 +66,6 @@ export default class UberPBRMaterial extends MeshPhysicalMaterial this.type = "UberPBRAdvMaterial"; - this.isUberPBRMaterial = true; - this.isMeshStandardMaterial = true; - this.isMeshPhysicalMaterial = true; this.defines = { "STANDARD": true, @@ -78,6 +75,7 @@ export default class UberPBRMaterial extends MeshPhysicalMaterial "MODE_XRAY": false, "CUT_PLANE": false, "USE_ZONEMAP": false, + "OVERLAY_ALPHA": false }; this.uniforms = UniformsUtils.merge([ @@ -88,7 +86,7 @@ export default class UberPBRMaterial extends MeshPhysicalMaterial cutPlaneColor: { value: new Vector3(1, 0, 0) }, zoneMap: { value: null }, } - ]); + ]) as any; this._aoMapMix = this.uniforms.aoMapMix.value; this._cutPlaneDirection = this.uniforms.cutPlaneDirection.value; @@ -254,7 +252,12 @@ export default class UberPBRMaterial extends MeshPhysicalMaterial } } - enableZoneMap(enabled) { + enableZoneMap(enabled: boolean) { this.defines["USE_ZONEMAP"] = enabled; } + + // enable black-to-alpha blending for overlays + enableOverlayAlpha(enabled: boolean) { + this.defines["OVERLAY_ALPHA"] = enabled; + } } \ No newline at end of file diff --git a/source/client/shaders/UberPBRMaterial.ts b/source/client/shaders/UberPBRMaterial.ts index ba0a26ab..dd857fc0 100644 --- a/source/client/shaders/UberPBRMaterial.ts +++ b/source/client/shaders/UberPBRMaterial.ts @@ -34,9 +34,9 @@ export interface IUberPBRShaderProps extends MeshStandardMaterialParameters export default class UberPBRMaterial extends MeshStandardMaterial { - isUberPBRMaterial: boolean; - isMeshStandardMaterial: boolean; - isMeshPhysicalMaterial: boolean; + readonly isUberPBRMaterial = true; + readonly isMeshStandardMaterial = true; + readonly isMeshPhysicalMaterial = false; uniforms: { aoMapMix: { value: Vector3 }, @@ -66,10 +66,6 @@ export default class UberPBRMaterial extends MeshStandardMaterial this.type = "UberPBRMaterial"; - this.isUberPBRMaterial = true; - this.isMeshStandardMaterial = true; - this.isMeshPhysicalMaterial = false; - this.defines = { "STANDARD": true, "PHYSICAL": false, @@ -78,6 +74,7 @@ export default class UberPBRMaterial extends MeshStandardMaterial "MODE_XRAY": false, "CUT_PLANE": false, "USE_ZONEMAP": false, + "OVERLAY_ALPHA": false }; this.uniforms = UniformsUtils.merge([ @@ -88,7 +85,7 @@ export default class UberPBRMaterial extends MeshStandardMaterial cutPlaneColor: { value: new Vector3(1, 0, 0) }, zoneMap: { value: null }, } - ]); + ]) as any; this._aoMapMix = this.uniforms.aoMapMix.value; this._cutPlaneDirection = this.uniforms.cutPlaneDirection.value; @@ -250,7 +247,12 @@ export default class UberPBRMaterial extends MeshStandardMaterial } } - enableZoneMap(enabled) { + enableZoneMap(enabled: boolean) { this.defines["USE_ZONEMAP"] = enabled; } + + // enable black-to-alpha blending for overlays + enableOverlayAlpha(enabled: boolean) { + this.defines["OVERLAY_ALPHA"] = enabled; + } } \ No newline at end of file diff --git a/source/client/shaders/ZoneShader.ts b/source/client/shaders/ZoneShader.ts new file mode 100644 index 00000000..4102fb8f --- /dev/null +++ b/source/client/shaders/ZoneShader.ts @@ -0,0 +1,53 @@ +/** + * 3D Foundation Project + * Copyright 2020 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ShaderMaterial} from "three"; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Renders the zone colors + * Based on ff-three position shader : Copyright 2020 Ralph Wiedemeier, Frame Factory GmbH + */ +export default class ZoneShader extends ShaderMaterial +{ + isZoneShader = true; + + uniforms = { + zoneMap: { value: null } + }; + + vertexShader = [ + "varying vec2 vUv;", + + "void main() {", + " #include ", + " #include ", + " #include ", + " vUv = uv;", + "}", + ].join("\n"); + + fragmentShader = [ + "varying vec2 vUv;", + "uniform sampler2D zoneMap;", + + "void main() {", + " gl_FragColor = vec4(texture2D(zoneMap, vUv).xyz, 1.0);", + "}" + ].join("\n"); +} \ No newline at end of file diff --git a/source/client/shaders/uberPBRShader.frag b/source/client/shaders/uberPBRShader.frag index 7c75b474..47dfbe7e 100644 --- a/source/client/shaders/uberPBRShader.frag +++ b/source/client/shaders/uberPBRShader.frag @@ -33,6 +33,10 @@ uniform float opacity; uniform float clearcoatRoughness; #endif +#ifdef USE_DISPERSION + uniform float dispersion; +#endif + #ifdef USE_IRIDESCENCE uniform float iridescence; uniform float iridescenceIOR; @@ -128,10 +132,11 @@ void main() { discard; } #endif + + vec4 diffuseColor = vec4( diffuse, opacity ); #include - vec4 diffuseColor = vec4( diffuse, opacity ); ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); vec3 totalEmissiveRadiance = emissive; @@ -240,7 +245,13 @@ void main() { #ifdef USE_ZONEMAP vec4 zoneColor = texture2D(zoneMap, vZoneUv); - gl_FragColor += mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(zoneColor.rgb, 1.0), zoneColor.a); + + #ifdef OVERLAY_ALPHA + gl_FragColor += mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(zoneColor.rgb, 1.0), zoneColor.a); + #endif + #ifndef OVERLAY_ALPHA + gl_FragColor = mix(gl_FragColor, vec4(zoneColor.rgb, 1.0), zoneColor.a); + #endif #endif #include diff --git a/source/client/shaders/uberPBRShader.vert b/source/client/shaders/uberPBRShader.vert index 804df66b..530d1449 100644 --- a/source/client/shaders/uberPBRShader.vert +++ b/source/client/shaders/uberPBRShader.vert @@ -15,6 +15,7 @@ varying vec3 vViewPosition; #endif #include +#include #include #include #include @@ -48,7 +49,9 @@ void main() { #endif #include + #include #include + #include #include #include diff --git a/source/client/ui/PropertyColor.ts b/source/client/ui/PropertyColor.ts index b9b6f712..22cfffb3 100644 --- a/source/client/ui/PropertyColor.ts +++ b/source/client/ui/PropertyColor.ts @@ -22,6 +22,7 @@ import CustomElement, { customElement, property, PropertyValues, html } from "@f import "@ff/ui/Button"; import "@ff/ui/ColorEdit"; +import Notification from "@ff/ui/Notification"; import type { IColorEditChangeEvent } from "@ff/ui/ColorEdit"; import { focusTrap, getFocusableElements } from "client/utils/focusHelpers"; @@ -40,8 +41,17 @@ export default class PropertyColor extends CustomElement @property({attribute: false, type: Boolean}) pickerActive :boolean = false; + @property({type: Boolean}) + compact :boolean = false; + + @property({type: Boolean}) + floating :boolean = true; + protected color: Color = new Color(); + get alphaEnabled(){ + return this.property.elementCount === 4; + } constructor() { @@ -65,8 +75,8 @@ export default class PropertyColor extends CustomElement throw new Error("missing property attribute"); } - if (this.property.type !== "number" || this.property.elementCount !== 3) { - throw new Error(`not an color property: '${this.property.path}'`); + if (this.property.type !== "number" || 4 < this.property.elementCount ||this.property.elementCount < 3) { + throw new Error(`not a color property: '${this.property.path}'`); } if (changedProperties.has("property")) { @@ -96,11 +106,30 @@ export default class PropertyColor extends CustomElement { const property = this.property; const name = this.name || property.name; - const color = this.color.toString(); + const color = this.color.toString(this.alphaEnabled); + + const colorEdit = html`this.onKeyDown(e)} @change=${this.onColorChange}>`; + const popupColorEdit = html`${colorEdit}` return html` - - ${this.pickerActive ? html`this.onKeyDown(e)} @change=${this.onColorChange}>` : null} + + ${this.compact?null:html`{ + try{ + this.color.setString(ev.target.value); + this.onColorChange(); + ev.target.setCustomValidity(""); + }catch(e){ + ev.target.setCustomValidity(e.message); + Notification.show(`Not a valid color: ${ev.target.value}`, "warning", 1000); + } + }} + >`} + + + ${this.pickerActive ? (this.floating ? popupColorEdit : colorEdit) : null} `; } @@ -116,9 +145,10 @@ export default class PropertyColor extends CustomElement this.pickerActive = !this.pickerActive; } - protected onColorChange(event: IColorEditChangeEvent) + protected onColorChange() { - this.property.setValue(this.color.toRGBArray()); + + this.property.setValue( (this.alphaEnabled)? this.color.toRGBAArray() : this.color.toRGBArray() ); } protected onPropertyChange(value: number[]) diff --git a/source/client/ui/SceneView.ts b/source/client/ui/SceneView.ts index 31ef915d..50107feb 100644 --- a/source/client/ui/SceneView.ts +++ b/source/client/ui/SceneView.ts @@ -250,7 +250,7 @@ export default class SceneView extends SystemView } } else if(e.code === "Tab") { - focusTrap(getFocusableElements(this.overlay) as HTMLElement[], e); + focusTrap(getFocusableElements(this.overlay) as HTMLElement[], e, true); } } diff --git a/source/client/ui/explorer/TourMenu.ts b/source/client/ui/explorer/TourMenu.ts index be53e1ec..13daffb1 100644 --- a/source/client/ui/explorer/TourMenu.ts +++ b/source/client/ui/explorer/TourMenu.ts @@ -21,6 +21,7 @@ import "@ff/ui/Button"; import { ITour } from "client/schema/setup"; import { ELanguageType } from "client/schema/common"; import {getFocusableElements, focusTrap} from "../../utils/focusHelpers" +import { unsafeHTML } from "lit-html/directives/unsafe-html"; //////////////////////////////////////////////////////////////////////////////// @@ -57,7 +58,7 @@ export default class TourMenu extends CustomElement

${Object.keys(tour.titles).length > 0 ? tour.titles[ELanguageType[this.activeLanguage]] || "undefined" : tour.title}

-

${Object.keys(tour.leads).length > 0 ? tour.leads[ELanguageType[this.activeLanguage]] : tour.lead}

+

${unsafeHTML(Object.keys(tour.leads).length > 0 ? tour.leads[ELanguageType[this.activeLanguage]] : tour.lead)}

`; } diff --git a/source/client/ui/explorer/styles.scss b/source/client/ui/explorer/styles.scss index 454843b6..f466e811 100644 --- a/source/client/ui/explorer/styles.scss +++ b/source/client/ui/explorer/styles.scss @@ -1248,17 +1248,14 @@ $tour-entry-indent: 12px; position: relative; display: block; - & > .ff-button { - width: 26px; - max-width: 20ch; - height: 26px; - inline-size: 26px; - box-sizing: border-box; - padding: 1px; - background: $color-background; - border-radius: 2px; - border: none; - margin: 2px; + & > .sv-property-field { + display: flex; + + .ff-button{ + min-width: 23px; + border-radius: 2px; + margin: 2px; + } } .ff-color-edit { diff --git a/source/client/ui/story/MainView.ts b/source/client/ui/story/MainView.ts index e5cd95de..63d41505 100644 --- a/source/client/ui/story/MainView.ts +++ b/source/client/ui/story/MainView.ts @@ -62,6 +62,9 @@ Icon.add("upload", html``); Icon.add("trash", html``); Icon.add("cube", html``); +Icon.add("brush", html``); +Icon.add("pointer", html``); +Icon.add("eraser", html``); //Icon.add("lock", html``); //Icon.add("unlock", html``); diff --git a/source/client/ui/story/OverlayTaskView.ts b/source/client/ui/story/OverlayTaskView.ts new file mode 100644 index 00000000..e99d74cb --- /dev/null +++ b/source/client/ui/story/OverlayTaskView.ts @@ -0,0 +1,242 @@ +/** + * 3D Foundation Project + * Copyright 2020 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import List from "@ff/ui/List"; + +import CVOverlayTask, { EPaintMode } from "../../components/CVOverlayTask"; +import { TaskView, customElement, property, html } from "../../components/CVTask"; + +import CVDocument from "../../components/CVDocument"; + +import NVNode from "../../nodes/NVNode"; +import CVModel2, { IOverlay } from "../../components/CVModel2"; +import { EDerivativeQuality } from "client/schema/model"; + +//////////////////////////////////////////////////////////////////////////////// + +@customElement("sv-overlay-task-view") +export default class OverlayTaskView extends TaskView +{ + protected featureConfigMode = false; + + protected sceneview : HTMLElement = null; + protected activeModel: CVModel2; + + protected get snapshots() { + return this.activeDocument.setup.snapshots; + } + + protected connected() + { + super.connected(); + this.task.on("update", this.onUpdate, this); + + // get sceneview for cursor updates + const explorer = (this.getRootNode() as Element).getElementsByTagName("voyager-explorer")[0]; + this.sceneview = explorer.shadowRoot.querySelector(".sv-scene-view") as HTMLElement; + } + + protected disconnected() + { + this.task.off("update", this.onUpdate, this); + this.task.ins.paintMode.setValue(EPaintMode.Interact); + + super.disconnected(); + } + + protected render() + { + const task = this.task; + const overlays = task.overlays; + + if (!this.activeModel) { + return html`
Please select a model to edit its overlays.
`; + } + + const props = task.ins; + const activeOverlay = overlays[props.activeIndex.value]; + const activeQuality = this.activeModel.activeDerivative.data.quality; + + this.sceneview.style.cursor = props.paintMode.value === EPaintMode.Interact ? "grab" : "default"; + + const overlayConfig = activeOverlay ? html`
+
Overlay Editing [${EDerivativeQuality[activeQuality]} Derivative]
+ + +
Painting Tools
+ + + + + + +
+ + +
+
` : null; + + + return html`
+ + + +
+
+
+
Overlay Images
+
+
+ +
+
+ +
+ ${overlayConfig} +
+
+
`; + } + + protected onClickConfig() + { + this.featureConfigMode = true; + this.requestUpdate(); + } + + protected onSelectOverlay(event: ISelectOverlayEvent) + { + if( event.detail.index !== this.task.ins.activeIndex.value ) { + this.task.ins.activeIndex.setValue(event.detail.index); + this.requestUpdate(); + } + } + + protected onActiveDocument(previous: CVDocument, next: CVDocument) + { + this.requestUpdate(); + } + + protected onActiveNode(previous: NVNode, next: NVNode) + { + this.activeModel = null; + + if(previous && previous.model) + { + } + + if(next && next.model) + { + this.activeModel = next.model; + } + + super.onActiveNode(previous, next); + } + + // Handle adding new overlay + protected onClickOverlayCreate() + { + this.task.ins.createOverlay.set(); + } + + // Handle overlay delete + protected onClickOverlayDelete() + { + this.task.ins.deleteOverlay.set(); + } + + // Handle overlay save + protected onClickOverlaySave() + { + this.task.ins.saveOverlays.set(); + } + + // Handle overlay fill + protected onClickFillAll() + { + this.task.ins.overlayFill.set(); + } + + // Handle overlay clear + protected onClickClearAll() + { + this.task.ins.overlayClear.set(); + } + + // Handle overlay mode changes + protected onClickInteract() + { + this.task.ins.paintMode.setValue(EPaintMode.Interact); + } + protected onClickPaint() + { + this.task.ins.paintMode.setValue(EPaintMode.Paint); + } + protected onClickErase() + { + this.task.ins.paintMode.setValue(EPaintMode.Erase); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +interface ISelectOverlayEvent extends CustomEvent +{ + target: OverlayList; + detail: { + overlay: IOverlay; + index: number; + } +} + +@customElement("sv-overlay-list") +export class OverlayList extends List +{ + @property({ attribute: false }) + selectedItem: IOverlay = null; + + protected firstConnected() + { + super.firstConnected(); + this.classList.add("sv-overlay-list"); + } + + protected renderItem(item: IOverlay) + { + return html`${item.isDirty ? "(unsaved) " : null}${item.asset.data.uri}`; + } + + protected isItemSelected(item: IOverlay) + { + return item === this.selectedItem; + } + + protected onClickItem(event: MouseEvent, item: IOverlay, index: number) + { + this.dispatchEvent(new CustomEvent("select", { + detail: { target: item, index } + })); + } + + protected onClickEmpty(event: MouseEvent) + { + this.dispatchEvent(new CustomEvent("select", { + detail: { target: null, index: -1 } + })); + } +} \ No newline at end of file diff --git a/source/client/ui/story/ToursTaskView.ts b/source/client/ui/story/ToursTaskView.ts index e7f17f99..444c466f 100644 --- a/source/client/ui/story/ToursTaskView.ts +++ b/source/client/ui/story/ToursTaskView.ts @@ -182,7 +182,11 @@ export default class ToursTaskView extends TaskView task.ins.tourTitle.setValue(sanitizeHtml(text)); } else if (target.name === "lead") { - task.ins.tourLead.setValue(text); + task.ins.tourLead.setValue(sanitizeHtml(text, + { + allowedTags: [ 'b', 'i', 'em', 'strong', 'sup', 'sub' ], + } + )); } else if (target.name === "tags") { task.ins.tourTags.setValue(text); diff --git a/source/client/ui/story/styles.scss b/source/client/ui/story/styles.scss index 8612f9d4..9509320e 100644 --- a/source/client/ui/story/styles.scss +++ b/source/client/ui/story/styles.scss @@ -392,6 +392,29 @@ $color-component-meta-light: #d9d998; border-left-color: #343434; } +//////////////////////////////////////////////////////////////////////////////// +// TARGETS + +.ff-list .sv-target-list-header { + //background-color: $color-background-darker; + font-weight: bold; + pointer-events: none; +} + +.ff-property-button.sv-property-button { + align-items: center; + padding: 0px; + padding-left: 4px; + padding-right: 4px; + background-color: $color-background-light; +} + +.sv-target-colorbox { + float: right; + width: 1.2em; + height: 1.2em; +} + //////////////////////////////////////////////////////////////////////////////// // PROPERTY VIEW diff --git a/source/client/utils/VGPUPicker.ts b/source/client/utils/VGPUPicker.ts new file mode 100644 index 00000000..323e9da1 --- /dev/null +++ b/source/client/utils/VGPUPicker.ts @@ -0,0 +1,136 @@ +/** + * 3D Foundation Project + * Copyright 2020 Smithsonian Institution + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {WebGLRenderer, Scene, Vector3, Camera, Texture, Color} from 'three'; +import GPUPicker from "@ff/three/GPUPicker"; +import { IBaseEvent } from "@ff/three/Viewport"; + +import UVShader from "../shaders/UVShader"; +import ZoneShader from "../shaders/ZoneShader"; + +const _color = new Color(); + +export default class VGPUPicker extends GPUPicker +{ + protected uvShader: UVShader; + protected zoneShader: ZoneShader; + + constructor(renderer: WebGLRenderer) + { + super(renderer); + + this.uvShader = new UVShader(); + this.zoneShader = new ZoneShader(); + } + + /** + * Picks the uv coordinates on the surface of the object at the screen position of the given UI event. + * @param scene The scene containing the objects available for picking. + * @param camera The active camera. + * @param event A UI event providing the screen position at which to pick. + * @param result A vector containing the picked uv coordinates. + */ + pickUV(scene: Scene, camera: Camera, + event: IBaseEvent, result?: Vector3): Vector3 + { + result = result || new Vector3(); + + const viewport = event.viewport; + camera = viewport.updateCamera(camera); + camera.layers.disable(1); + + const overrideMaterial = scene.overrideMaterial; + const shader = scene.overrideMaterial = this.uvShader; + + const renderer = this.renderer; + const pickTextures = this.pickTextures; + renderer.getClearColor(_color); + + for (let i = 0; i < 2; ++i) { + shader.uniforms.index.value = i; + viewport.applyPickViewport(pickTextures[i], event); + renderer.setRenderTarget(pickTextures[i]); + renderer.clear(); + renderer.render(scene, camera); + } + + renderer.setRenderTarget(null); + renderer.setClearColor(_color); + + scene.overrideMaterial = overrideMaterial; + camera.layers.enable(1); + + const buffer = this.pickBuffer; + + for (let i = 0; i < 3; ++i) { + renderer.readRenderTargetPixels(pickTextures[i], 0, 0, 1, 1, buffer); + result.setComponent(i, + buffer[3] * 2.337437050015319e-10 /* / 255 / 16777216 */ + + buffer[2] * 5.983838848039216e-8 /* / 255 / 65536 */ + + buffer[1] * 1.531862745098039e-5 /* / 255 / 256 */ + + buffer[0] * 0.003921568627451 /* / 255 */ + ); + } + + return result; + } + + + /** + * Picks the color value from the zone texture at the screen position of the given UI event. + * @param scene The scene containing the objects available for picking. + * @param texture The zone texture to pick from + * @param camera The active camera. + * @param event A UI event providing the screen position at which to pick. + * @param result A vector containing the picked uv coordinates. + */ + pickZone(scene: Scene, texture: Texture, camera: Camera, + event: IBaseEvent, result?: Vector3): Vector3 + { + result = result || new Vector3(); + + const viewport = event.viewport; + camera = viewport.updateCamera(camera); + camera.layers.disable(1); + + const overrideMaterial = scene.overrideMaterial; + const shader = scene.overrideMaterial = this.zoneShader; + + const renderer = this.renderer; + const pickTexture = this.pickTextures[0]; + renderer.getClearColor(_color); + + shader.uniforms.zoneMap.value = texture; + viewport.applyPickViewport(pickTexture, event); + renderer.setRenderTarget(pickTexture); + renderer.clear(); + renderer.render(scene, camera); + + renderer.setRenderTarget(null); + renderer.setClearColor(_color); + + scene.overrideMaterial = overrideMaterial; + camera.layers.enable(1); + + const buffer = this.pickBuffer; + + renderer.readRenderTargetPixels(pickTexture, 0, 0, 1, 1, buffer); + return result.set( + buffer[0], buffer[1], buffer[2] + ) + } +} \ No newline at end of file diff --git a/source/client/utils/focusHelpers.ts b/source/client/utils/focusHelpers.ts index 57bca75a..5905e3e6 100644 --- a/source/client/utils/focusHelpers.ts +++ b/source/client/utils/focusHelpers.ts @@ -24,21 +24,27 @@ export function getFocusableElements (element: HTMLElement) .filter(el => !el.hasAttribute('disabled') && !!el.getClientRects().length && (el as HTMLElement).style.visibility !== "hidden" && !el.getAttribute("aria-hidden") && (el.getAttribute("tabindex") !== "-1")) } -export function focusTrap (focusableElements: HTMLElement[], e: KeyboardEvent) +export function focusTrap (focusableElements: HTMLElement[], e: KeyboardEvent, noScroll: boolean = false) { + const idx = focusableElements.findIndex(elem => elem === e.target) const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length-1]; + e.preventDefault(); if(e.shiftKey) { - if(e.target === firstElement) { - e.preventDefault(); - lastElement.focus(); + if(idx === 0) { + lastElement.focus({preventScroll: noScroll}); + } + else { + focusableElements[idx-1].focus({preventScroll: noScroll}); } } else { - if(e.target === lastElement) { - e.preventDefault(); - firstElement.focus(); + if(idx === focusableElements.length-1) { + firstElement.focus({preventScroll: noScroll}); + } + else { + focusableElements[idx+1].focus({preventScroll: noScroll}); } } } \ No newline at end of file diff --git a/source/server/tsconfig.json b/source/server/tsconfig.json index 164a1505..c97da477 100644 --- a/source/server/tsconfig.json +++ b/source/server/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES6", "module": "commonjs", "sourceMap": true, + "types": [], "outDir": "../../services/server/bin", "baseUrl": "../../services/server/bin/node_modules"