| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Camera UiConfig</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,23 @@ | |||
| import {_testFinish, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.appendChild(viewer.scene.mainCamera.uiConfig) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -1,4 +1,4 @@ | |||
| import {_testFinish, DropzonePlugin, ThreeViewer} from 'threepipe' | |||
| import {_testFinish, DropzonePlugin, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| @@ -21,14 +21,17 @@ async function init() { | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| const dropzone = viewer.getPlugin(DropzonePlugin) | |||
| dropzone?.addEventListener('drop', (e: any) => { | |||
| const dropzone = viewer.getPlugin(DropzonePlugin)! | |||
| dropzone.addEventListener('drop', (e: any) => { | |||
| if (!e.assets?.length) return // no assets imported | |||
| console.log('Dropped Event:', e) | |||
| const promptDiv = document.getElementById('prompt-div')! | |||
| promptDiv.style.display = 'none' | |||
| }) | |||
| const ui = viewer.addPluginSync(TweakpaneUiPlugin, true) | |||
| ui.appendChild(dropzone.uiConfig) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -12,6 +12,7 @@ setupCodePreview( | |||
| scripts.map(s=>(typeof s === 'string' && s.endsWith('.js')) ? s : 'https://github.com/repalash/threepipe/tree/master/examples/'+ window.location.pathname.split('/examples/').pop().replace('index.html', '')+(s.textContent ? 'index.html' : s)), // todo: github link | |||
| (c)=>c | |||
| .replaceAll(" from 'threepipe'", " from 'https://threepipe.org/dist/index.mjs'") | |||
| .replaceAll(" from 'uiconfig-tweakpane'", " from 'https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs'") | |||
| .replaceAll(" from '../", " from 'https://threepipe.org/examples/"), | |||
| { | |||
| title: 'ThreePipe: ' + document.title, | |||
| @@ -215,39 +215,47 @@ | |||
| <h1><a href="https://github.com/repalash/threepipe">ThreePipe</a> Examples</h1> | |||
| <h2 class="category">Plugins</h2> | |||
| <ul> | |||
| <li><a href="./depth-buffer-plugin">Depth Buffer Plugin </a></li> | |||
| <li><a href="./render-target-preview">Render Target Preview </a></li> | |||
| <li><a href="./dropzone-plugin">Dropzone (Drag & Drop) </a></li> | |||
| <li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | |||
| <li><a href="./render-target-preview/">Render Target Preview </a></li> | |||
| <li><a href="./dropzone-plugin/">Dropzone (Drag & Drop) </a></li> | |||
| </ul> | |||
| <h2 class="category">Import/Export</h2> | |||
| <ul> | |||
| <li><a href="./fbx-load">FBX Load </a></li> | |||
| <li><a href="./hdr-load">HDR Load </a></li> | |||
| <li><a href="./obj-mtl-load">OBJ MTL Load </a></li> | |||
| <li><a class="selected" href="./gltf-load">GLTF Load </a></li> | |||
| <li><a href="./drc-load">DRACO(DRC) Load </a></li> | |||
| <li><a href="./glb-export">GLB Export </a></li> | |||
| <li><a href="./pmat-material-export">PMAT Material export </a></li> | |||
| <li><a href="./fbx-load/">FBX Load </a></li> | |||
| <li><a href="./hdr-load/">HDR Load </a></li> | |||
| <li><a href="./obj-mtl-load/">OBJ MTL Load </a></li> | |||
| <li><a class="selected" href="./gltf-load/">GLTF Load </a></li> | |||
| <li><a href="./drc-load/">DRACO(DRC) Load </a></li> | |||
| <li><a href="./glb-export/">GLB Export </a></li> | |||
| <li><a href="./pmat-material-export/">PMAT Material export </a></li> | |||
| </ul> | |||
| <h2 class="category">Rendering</h2> | |||
| <ul> | |||
| <li><a href="./custom-pipeline">Custom Pipeline specification </a></li> | |||
| <li><a href="./custom-pipeline/">Custom Pipeline specification </a></li> | |||
| </ul> | |||
| <h2 class="category">UI Config</h2> | |||
| <ul> | |||
| <li><a href="./material-uiconfig/">Material UI </a></li> | |||
| <li><a href="./object-uiconfig/">Object UI </a></li> | |||
| <li><a href="./camera-uiconfig/">Camera UI </a></li> | |||
| <li><a href="./scene-uiconfig/">Scene UI </a></li> | |||
| <li><a href="./viewer-uiconfig/">Viewer UI </a></li> | |||
| </ul> | |||
| <h2 class="category">Utils</h2> | |||
| <ul> | |||
| <li><a href="./obj-to-glb">Convert OBJ to GLB </a></li> | |||
| <li><a href="./obj-to-glb/">Convert OBJ to GLB </a></li> | |||
| </ul> | |||
| <h2 class="category">Tests</h2> | |||
| <ul> | |||
| <li><a href="./import-test">Import Test</a></li> | |||
| <li><a href="./sphere-rgbm-test">Sphere RGBM Test </a></li> | |||
| <li><a href="./sphere-half-float-test">Sphere Half Float Test </a></li> | |||
| <li><a href="./sphere-msaa-test">Sphere MSAA Test </a></li> | |||
| <li><a href="./z-prepass">Z-Prepass Test </a></li> | |||
| <li><a href="./import-test/">Import Test</a></li> | |||
| <li><a href="./sphere-rgbm-test/">Sphere RGBM Test </a></li> | |||
| <li><a href="./sphere-half-float-test/">Sphere Half Float Test </a></li> | |||
| <li><a href="./sphere-msaa-test/">Sphere MSAA Test </a></li> | |||
| <li><a href="./z-prepass/">Z-Prepass Test </a></li> | |||
| </ul> | |||
| </div> | |||
| <div class="iframe-container"> | |||
| <iframe id="example-iframe" src="./gltf-load" frameborder="0" allowfullscreen="allowfullscreen" | |||
| <iframe id="example-iframe" src="./gltf-load/" frameborder="0" allowfullscreen="allowfullscreen" | |||
| allow="accelerometer *; ambient-light-sensor *; autoplay *; camera *; clipboard-read *; clipboard-write *; encrypted-media *; fullscreen *; geolocation *; gyroscope *; magnetometer *; microphone *; midi *; payment *; picture-in-picture *; screen-wake-lock *; speaker *; sync-xhr *; usb *; web-share *; vibrate *; vr *"> | |||
| </iframe> | |||
| </div> | |||
| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Material UiConfig</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,29 @@ | |||
| import {_testFinish, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| const model = result?.getObjectByName('node_damagedHelmet_-6514') | |||
| const materials = model?.materials || [] | |||
| for (const material of materials) { | |||
| const config = material.uiConfig | |||
| if (!config) continue | |||
| ui.appendChild(config) | |||
| } | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Object UiConfig</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,26 @@ | |||
| import {_testFinish, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| const model = result?.getObjectByName('node_damagedHelmet_-6514') | |||
| const config = model?.uiConfig | |||
| if (config) ui.appendChild(config) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Scene UiConfig</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,23 @@ | |||
| import {_testFinish, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.appendChild(viewer.scene.uiConfig) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -3,6 +3,7 @@ | |||
| "baseUrl": "./", | |||
| "allowJs": false, | |||
| "checkJs": false, | |||
| "skipLibCheck": true, | |||
| "allowSyntheticDefaultImports": true, | |||
| "experimentalDecorators": true, | |||
| "isolatedModules": false, | |||
| @@ -0,0 +1,35 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Viewer UiConfig</title> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "uiconfig-tweakpane": "https://unpkg.com/uiconfig-tweakpane@latest/dist/index.mjs" | |||
| } | |||
| } | |||
| </script> | |||
| <style id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||
| </head> | |||
| <body> | |||
| <div id="canvas-container"> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,22 @@ | |||
| import {_testFinish, IObject3D, ThreeViewer, TweakpaneUiPlugin} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| }) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| ui.appendChild(viewer.uiConfig) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -1,12 +1,12 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.1", | |||
| "version": "0.0.2", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "threepipe", | |||
| "version": "0.0.1", | |||
| "version": "0.0.2", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.0004/package.tgz", | |||
| @@ -17,10 +17,11 @@ | |||
| "@rollup/plugin-commonjs": "^25.0.0", | |||
| "@rollup/plugin-json": "^6.0.0", | |||
| "@rollup/plugin-node-resolve": "^15.0.2", | |||
| "@rollup/plugin-terser": "^0.4.1", | |||
| "@rollup/plugin-terser": "^0.4.3", | |||
| "@rollup/plugin-typescript": "^11.1.1", | |||
| "@tweakpane/core": "^1.1.8", | |||
| "@types/stats.js": "^0.17.0", | |||
| "@typescript-eslint/eslint-plugin": "^5.59.5", | |||
| "@typescript-eslint/eslint-plugin": "^5.59.7", | |||
| "@typescript-eslint/parser": "^5.59.5", | |||
| "clean-package": "^2.2.0", | |||
| "eslint": "^8.40.0", | |||
| @@ -31,44 +32,20 @@ | |||
| "local-web-server": "^5.3.0", | |||
| "markdown-to-html-cli": "^3.7.0", | |||
| "rimraf": "^5.0.1", | |||
| "rollup": "^3.21.7", | |||
| "rollup": "^3.23.0", | |||
| "rollup-plugin-license": "^3.0.1", | |||
| "rollup-plugin-postcss": "^4.0.2", | |||
| "stats.js": "^0.17.0", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2005/package.tgz", | |||
| "ts-browser-helpers": "^0.4.0", | |||
| "ts-browser-helpers": "^0.5.0", | |||
| "tslib": "^2.5.0", | |||
| "tweakpane": "^3.1.9", | |||
| "tweakpane-image-plugin": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.403/package.tgz", | |||
| "typedoc": "^0.24.7", | |||
| "typescript": "^5.0.4", | |||
| "typescript-plugin-css-modules": "^5.0.1", | |||
| "uiconfig.js": "^0.0.3" | |||
| }, | |||
| "optionalDependencies": { | |||
| "win-node-env": "^0.6.1" | |||
| } | |||
| }, | |||
| "../ts-browser-helpers": { | |||
| "version": "0.4.0", | |||
| "extraneous": true, | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "@types/wicg-file-system-access": "^2020.9.5" | |||
| }, | |||
| "devDependencies": { | |||
| "@rollup/plugin-commonjs": "^23.0.2", | |||
| "@rollup/plugin-json": "^5.0.1", | |||
| "@rollup/plugin-node-resolve": "^15.0.1", | |||
| "@rollup/plugin-terser": "^0.1.0", | |||
| "@rollup/plugin-typescript": "^9.0.2", | |||
| "clean-package": "^2.2.0", | |||
| "local-web-server": "^5.2.1", | |||
| "rimraf": "^5.0.1", | |||
| "rollup": "^3.4.0", | |||
| "rollup-plugin-license": "^3.0.1", | |||
| "tslib": "^2.4.1", | |||
| "typedoc": "^0.24.7", | |||
| "typedoc-plugin-markdown": "^3.15.3", | |||
| "typescript": "^4.9.3" | |||
| "uiconfig-tweakpane": "^0.0.3", | |||
| "uiconfig.js": "^0.0.4" | |||
| }, | |||
| "optionalDependencies": { | |||
| "win-node-env": "^0.6.1" | |||
| @@ -212,9 +189,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/@isaacs/cliui/node_modules/strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -555,6 +532,12 @@ | |||
| "node": ">=10.13.0" | |||
| } | |||
| }, | |||
| "node_modules/@tweakpane/core": { | |||
| "version": "1.1.8", | |||
| "resolved": "https://registry.npmjs.org/@tweakpane/core/-/core-1.1.8.tgz", | |||
| "integrity": "sha512-psvBf6Cbm3YSZOTmDFWkcGzHYMnw7gVZM3jw+TfbzErIC+sMXPQb85h4ayW04w2u7AGg8jD0gHXSCg5wd+rafg==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/@tweenjs/tween.js": { | |||
| "version": "18.6.4", | |||
| "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", | |||
| @@ -594,9 +577,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/@types/json-schema": { | |||
| "version": "7.0.11", | |||
| "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", | |||
| "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", | |||
| "version": "7.0.12", | |||
| "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", | |||
| "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/@types/json5": { | |||
| @@ -633,9 +616,9 @@ | |||
| "dev": true | |||
| }, | |||
| "node_modules/@types/node": { | |||
| "version": "20.2.4", | |||
| "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.4.tgz", | |||
| "integrity": "sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==", | |||
| "version": "20.2.5", | |||
| "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", | |||
| "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/@types/parse5": { | |||
| @@ -1280,9 +1263,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/browserslist": { | |||
| "version": "4.21.5", | |||
| "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", | |||
| "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", | |||
| "version": "4.21.6", | |||
| "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.6.tgz", | |||
| "integrity": "sha512-PF07dKGXKR+/bljJzCB6rAYtHEu21TthLxmJagtQizx+rwiqdRDBO5971Xu1N7MgcMLi4+mr4Cnl76x7O3DHtA==", | |||
| "dev": true, | |||
| "funding": [ | |||
| { | |||
| @@ -1292,13 +1275,17 @@ | |||
| { | |||
| "type": "tidelift", | |||
| "url": "https://tidelift.com/funding/github/npm/browserslist" | |||
| }, | |||
| { | |||
| "type": "github", | |||
| "url": "https://github.com/sponsors/ai" | |||
| } | |||
| ], | |||
| "dependencies": { | |||
| "caniuse-lite": "^1.0.30001449", | |||
| "electron-to-chromium": "^1.4.284", | |||
| "node-releases": "^2.0.8", | |||
| "update-browserslist-db": "^1.0.10" | |||
| "caniuse-lite": "^1.0.30001489", | |||
| "electron-to-chromium": "^1.4.411", | |||
| "node-releases": "^2.0.12", | |||
| "update-browserslist-db": "^1.0.11" | |||
| }, | |||
| "bin": { | |||
| "browserslist": "cli.js" | |||
| @@ -1736,12 +1723,12 @@ | |||
| } | |||
| }, | |||
| "node_modules/commander": { | |||
| "version": "7.2.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", | |||
| "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", | |||
| "version": "8.3.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | |||
| "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | |||
| "dev": true, | |||
| "engines": { | |||
| "node": ">= 10" | |||
| "node": ">= 12" | |||
| } | |||
| }, | |||
| "node_modules/commenting": { | |||
| @@ -2387,9 +2374,9 @@ | |||
| "dev": true | |||
| }, | |||
| "node_modules/electron-to-chromium": { | |||
| "version": "1.4.405", | |||
| "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.405.tgz", | |||
| "integrity": "sha512-JdDgnwU69FMZURoesf9gNOej2Cms1XJFfLk24y1IBtnAdhTcJY/mXnokmpmxHN59PcykBP4bgUU98vLY44Lhuw==", | |||
| "version": "1.4.411", | |||
| "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.411.tgz", | |||
| "integrity": "sha512-5VXLW4Qw89vM2WTICHua/y8v7fKGDRVa2VPOtBB9IpLvW316B+xd8yD1wTmLPY2ot/00P/qt87xdolj4aG/Lzg==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/emoji-regex": { | |||
| @@ -2408,9 +2395,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/enhanced-resolve": { | |||
| "version": "5.14.0", | |||
| "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", | |||
| "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", | |||
| "version": "5.14.1", | |||
| "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", | |||
| "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "graceful-fs": "^4.2.4", | |||
| @@ -4744,15 +4731,6 @@ | |||
| "katex": "cli.js" | |||
| } | |||
| }, | |||
| "node_modules/katex/node_modules/commander": { | |||
| "version": "8.3.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | |||
| "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | |||
| "dev": true, | |||
| "engines": { | |||
| "node": ">= 12" | |||
| } | |||
| }, | |||
| "node_modules/keygrip": { | |||
| "version": "1.1.0", | |||
| "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", | |||
| @@ -7059,9 +7037,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/postcss": { | |||
| "version": "8.4.23", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", | |||
| "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", | |||
| "version": "8.4.24", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | |||
| "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", | |||
| "dev": true, | |||
| "funding": [ | |||
| { | |||
| @@ -8811,9 +8789,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/smob": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmjs.org/smob/-/smob-1.1.1.tgz", | |||
| "integrity": "sha512-i5aqEBPnDv9d77+NDxfjROtywxzNdAVNyaOr+RsLhM28Ts+Ar7luIp/Q+SBYa6wv/7BBcOpEkrhtDxsl2WA9Jg==", | |||
| "version": "1.3.0", | |||
| "resolved": "https://registry.npmjs.org/smob/-/smob-1.3.0.tgz", | |||
| "integrity": "sha512-/fIjILUZrqpd2v9GWqaULJ1Gjw4BnWmPnQcqx8aFrSt97C2/ALamtVl9q1XRkuUg9TDkrXzfTsppQnm9DCJH8w==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/source-map": { | |||
| @@ -9099,9 +9077,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/string-width/node_modules/strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -9348,6 +9326,15 @@ | |||
| "node": ">=10.13.0" | |||
| } | |||
| }, | |||
| "node_modules/svgo/node_modules/commander": { | |||
| "version": "7.2.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", | |||
| "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", | |||
| "dev": true, | |||
| "engines": { | |||
| "node": ">= 10" | |||
| } | |||
| }, | |||
| "node_modules/synckit": { | |||
| "version": "0.8.5", | |||
| "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", | |||
| @@ -9524,9 +9511,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/ts-browser-helpers": { | |||
| "version": "0.4.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.4.0.tgz", | |||
| "integrity": "sha512-9XoLdIL5f61ht3UvJLAonPAam4EkCfjosLoF6xayOGxc0HnT0y8EzIdk/jx+MuFc3hT28TCzOgP9Va4C8ky/9Q==", | |||
| "version": "0.5.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.5.0.tgz", | |||
| "integrity": "sha512-seKCLyEIzNfjaVSYMMhYvvQlj0OHfgHbtbeOJNrFufhalmQOJcj5NP/PSMCcIU1qrKgcXNYuAolgtTmsPG6Aaw==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "@types/wicg-file-system-access": "^2020.9.5" | |||
| @@ -9580,6 +9567,22 @@ | |||
| "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/tweakpane": { | |||
| "version": "3.1.9", | |||
| "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-3.1.9.tgz", | |||
| "integrity": "sha512-vMzh3X8uHo9HDY+9S9V0bc+UBScs8VYmMeOEW+BvynczV0aiLHweYv4eKpyoqpcRrQlkLhUsx8Dvv/1/qiCESg==", | |||
| "dev": true, | |||
| "funding": { | |||
| "url": "https://github.com/sponsors/cocopon" | |||
| } | |||
| }, | |||
| "node_modules/tweakpane-image-plugin": { | |||
| "version": "1.1.403", | |||
| "resolved": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.403/package.tgz", | |||
| "integrity": "sha512-CR1f5HFXqs/wqibb6W+Wxfg6xDJsYLoTgHMPKHmLsjr3CyFMROYHH4kDnBEhRroudiMA6PYG7SLq5/9N8HS0sQ==", | |||
| "dev": true, | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/type-check": { | |||
| "version": "0.4.0", | |||
| "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", | |||
| @@ -9751,10 +9754,20 @@ | |||
| "node": ">=12.17" | |||
| } | |||
| }, | |||
| "node_modules/uiconfig.js": { | |||
| "node_modules/uiconfig-tweakpane": { | |||
| "version": "0.0.3", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.3.tgz", | |||
| "integrity": "sha512-RZidKh6+6SJKrQa2xXHkZb8iY5UjYMJC651eIzWl7RzSuxDKvH+QahJkjtVULj/C103LH5Y6DOcC93/6WM3+gQ==", | |||
| "resolved": "https://registry.npmjs.org/uiconfig-tweakpane/-/uiconfig-tweakpane-0.0.3.tgz", | |||
| "integrity": "sha512-QDWiXMh5+pwr4jLOv7GV5nI/9RJdNAQGEwD503ShIxW9oB7eFdq9bRDvq46ncXHelWxiq2j3gqxvCnLhwfMx5A==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "@types/three": "^0.152.1", | |||
| "uiconfig.js": "^0.0.4" | |||
| } | |||
| }, | |||
| "node_modules/uiconfig.js": { | |||
| "version": "0.0.4", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.4.tgz", | |||
| "integrity": "sha512-uFrcLIey/vx2+bYJwf3VBinHMRaSlTKaYOxYLFFqvu1jLj57OBJiuUosNfXKMxliSNMx4ptuNOhePFRSswumiQ==", | |||
| "dev": true | |||
| }, | |||
| "node_modules/unbox-primitive": { | |||
| @@ -10262,9 +10275,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/wrap-ansi/node_modules/strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "dependencies": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -10428,9 +10441,9 @@ | |||
| "dev": true | |||
| }, | |||
| "strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "requires": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -10652,6 +10665,12 @@ | |||
| "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", | |||
| "dev": true | |||
| }, | |||
| "@tweakpane/core": { | |||
| "version": "1.1.8", | |||
| "resolved": "https://registry.npmjs.org/@tweakpane/core/-/core-1.1.8.tgz", | |||
| "integrity": "sha512-psvBf6Cbm3YSZOTmDFWkcGzHYMnw7gVZM3jw+TfbzErIC+sMXPQb85h4ayW04w2u7AGg8jD0gHXSCg5wd+rafg==", | |||
| "dev": true | |||
| }, | |||
| "@tweenjs/tween.js": { | |||
| "version": "18.6.4", | |||
| "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", | |||
| @@ -10691,9 +10710,9 @@ | |||
| } | |||
| }, | |||
| "@types/json-schema": { | |||
| "version": "7.0.11", | |||
| "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", | |||
| "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", | |||
| "version": "7.0.12", | |||
| "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", | |||
| "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", | |||
| "dev": true | |||
| }, | |||
| "@types/json5": { | |||
| @@ -10730,9 +10749,9 @@ | |||
| "dev": true | |||
| }, | |||
| "@types/node": { | |||
| "version": "20.2.4", | |||
| "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.4.tgz", | |||
| "integrity": "sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==", | |||
| "version": "20.2.5", | |||
| "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", | |||
| "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", | |||
| "dev": true | |||
| }, | |||
| "@types/parse5": { | |||
| @@ -11197,15 +11216,15 @@ | |||
| } | |||
| }, | |||
| "browserslist": { | |||
| "version": "4.21.5", | |||
| "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", | |||
| "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", | |||
| "version": "4.21.6", | |||
| "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.6.tgz", | |||
| "integrity": "sha512-PF07dKGXKR+/bljJzCB6rAYtHEu21TthLxmJagtQizx+rwiqdRDBO5971Xu1N7MgcMLi4+mr4Cnl76x7O3DHtA==", | |||
| "dev": true, | |||
| "requires": { | |||
| "caniuse-lite": "^1.0.30001449", | |||
| "electron-to-chromium": "^1.4.284", | |||
| "node-releases": "^2.0.8", | |||
| "update-browserslist-db": "^1.0.10" | |||
| "caniuse-lite": "^1.0.30001489", | |||
| "electron-to-chromium": "^1.4.411", | |||
| "node-releases": "^2.0.12", | |||
| "update-browserslist-db": "^1.0.11" | |||
| } | |||
| }, | |||
| "buffer-from": { | |||
| @@ -11515,9 +11534,9 @@ | |||
| } | |||
| }, | |||
| "commander": { | |||
| "version": "7.2.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", | |||
| "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", | |||
| "version": "8.3.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | |||
| "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | |||
| "dev": true | |||
| }, | |||
| "commenting": { | |||
| @@ -11981,9 +12000,9 @@ | |||
| "dev": true | |||
| }, | |||
| "electron-to-chromium": { | |||
| "version": "1.4.405", | |||
| "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.405.tgz", | |||
| "integrity": "sha512-JdDgnwU69FMZURoesf9gNOej2Cms1XJFfLk24y1IBtnAdhTcJY/mXnokmpmxHN59PcykBP4bgUU98vLY44Lhuw==", | |||
| "version": "1.4.411", | |||
| "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.411.tgz", | |||
| "integrity": "sha512-5VXLW4Qw89vM2WTICHua/y8v7fKGDRVa2VPOtBB9IpLvW316B+xd8yD1wTmLPY2ot/00P/qt87xdolj4aG/Lzg==", | |||
| "dev": true | |||
| }, | |||
| "emoji-regex": { | |||
| @@ -11999,9 +12018,9 @@ | |||
| "dev": true | |||
| }, | |||
| "enhanced-resolve": { | |||
| "version": "5.14.0", | |||
| "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", | |||
| "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", | |||
| "version": "5.14.1", | |||
| "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", | |||
| "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", | |||
| "dev": true, | |||
| "requires": { | |||
| "graceful-fs": "^4.2.4", | |||
| @@ -13706,14 +13725,6 @@ | |||
| "dev": true, | |||
| "requires": { | |||
| "commander": "^8.3.0" | |||
| }, | |||
| "dependencies": { | |||
| "commander": { | |||
| "version": "8.3.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | |||
| "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | |||
| "dev": true | |||
| } | |||
| } | |||
| }, | |||
| "keygrip": { | |||
| @@ -15364,9 +15375,9 @@ | |||
| "dev": true | |||
| }, | |||
| "postcss": { | |||
| "version": "8.4.23", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", | |||
| "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", | |||
| "version": "8.4.24", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | |||
| "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", | |||
| "dev": true, | |||
| "requires": { | |||
| "nanoid": "^3.3.6", | |||
| @@ -16611,9 +16622,9 @@ | |||
| "dev": true | |||
| }, | |||
| "smob": { | |||
| "version": "1.1.1", | |||
| "resolved": "https://registry.npmjs.org/smob/-/smob-1.1.1.tgz", | |||
| "integrity": "sha512-i5aqEBPnDv9d77+NDxfjROtywxzNdAVNyaOr+RsLhM28Ts+Ar7luIp/Q+SBYa6wv/7BBcOpEkrhtDxsl2WA9Jg==", | |||
| "version": "1.3.0", | |||
| "resolved": "https://registry.npmjs.org/smob/-/smob-1.3.0.tgz", | |||
| "integrity": "sha512-/fIjILUZrqpd2v9GWqaULJ1Gjw4BnWmPnQcqx8aFrSt97C2/ALamtVl9q1XRkuUg9TDkrXzfTsppQnm9DCJH8w==", | |||
| "dev": true | |||
| }, | |||
| "source-map": { | |||
| @@ -16837,9 +16848,9 @@ | |||
| "dev": true | |||
| }, | |||
| "strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "requires": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -17024,6 +17035,14 @@ | |||
| "csso": "^4.2.0", | |||
| "picocolors": "^1.0.0", | |||
| "stable": "^0.1.8" | |||
| }, | |||
| "dependencies": { | |||
| "commander": { | |||
| "version": "7.2.0", | |||
| "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", | |||
| "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", | |||
| "dev": true | |||
| } | |||
| } | |||
| }, | |||
| "synckit": { | |||
| @@ -17157,9 +17176,9 @@ | |||
| "dev": true | |||
| }, | |||
| "ts-browser-helpers": { | |||
| "version": "0.4.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.4.0.tgz", | |||
| "integrity": "sha512-9XoLdIL5f61ht3UvJLAonPAam4EkCfjosLoF6xayOGxc0HnT0y8EzIdk/jx+MuFc3hT28TCzOgP9Va4C8ky/9Q==", | |||
| "version": "0.5.0", | |||
| "resolved": "https://registry.npmjs.org/ts-browser-helpers/-/ts-browser-helpers-0.5.0.tgz", | |||
| "integrity": "sha512-seKCLyEIzNfjaVSYMMhYvvQlj0OHfgHbtbeOJNrFufhalmQOJcj5NP/PSMCcIU1qrKgcXNYuAolgtTmsPG6Aaw==", | |||
| "dev": true, | |||
| "requires": { | |||
| "@types/wicg-file-system-access": "^2020.9.5" | |||
| @@ -17206,6 +17225,17 @@ | |||
| } | |||
| } | |||
| }, | |||
| "tweakpane": { | |||
| "version": "3.1.9", | |||
| "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-3.1.9.tgz", | |||
| "integrity": "sha512-vMzh3X8uHo9HDY+9S9V0bc+UBScs8VYmMeOEW+BvynczV0aiLHweYv4eKpyoqpcRrQlkLhUsx8Dvv/1/qiCESg==", | |||
| "dev": true | |||
| }, | |||
| "tweakpane-image-plugin": { | |||
| "version": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.403/package.tgz", | |||
| "integrity": "sha512-CR1f5HFXqs/wqibb6W+Wxfg6xDJsYLoTgHMPKHmLsjr3CyFMROYHH4kDnBEhRroudiMA6PYG7SLq5/9N8HS0sQ==", | |||
| "dev": true | |||
| }, | |||
| "type-check": { | |||
| "version": "0.4.0", | |||
| "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", | |||
| @@ -17329,10 +17359,20 @@ | |||
| "integrity": "sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==", | |||
| "dev": true | |||
| }, | |||
| "uiconfig.js": { | |||
| "uiconfig-tweakpane": { | |||
| "version": "0.0.3", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.3.tgz", | |||
| "integrity": "sha512-RZidKh6+6SJKrQa2xXHkZb8iY5UjYMJC651eIzWl7RzSuxDKvH+QahJkjtVULj/C103LH5Y6DOcC93/6WM3+gQ==", | |||
| "resolved": "https://registry.npmjs.org/uiconfig-tweakpane/-/uiconfig-tweakpane-0.0.3.tgz", | |||
| "integrity": "sha512-QDWiXMh5+pwr4jLOv7GV5nI/9RJdNAQGEwD503ShIxW9oB7eFdq9bRDvq46ncXHelWxiq2j3gqxvCnLhwfMx5A==", | |||
| "dev": true, | |||
| "requires": { | |||
| "@types/three": "^0.152.1", | |||
| "uiconfig.js": "^0.0.4" | |||
| } | |||
| }, | |||
| "uiconfig.js": { | |||
| "version": "0.0.4", | |||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.4.tgz", | |||
| "integrity": "sha512-uFrcLIey/vx2+bYJwf3VBinHMRaSlTKaYOxYLFFqvu1jLj57OBJiuUosNfXKMxliSNMx4ptuNOhePFRSswumiQ==", | |||
| "dev": true | |||
| }, | |||
| "unbox-primitive": { | |||
| @@ -17654,9 +17694,9 @@ | |||
| "dev": true | |||
| }, | |||
| "strip-ansi": { | |||
| "version": "7.0.1", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", | |||
| "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", | |||
| "version": "7.1.0", | |||
| "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | |||
| "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | |||
| "dev": true, | |||
| "requires": { | |||
| "ansi-regex": "^6.0.1" | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.1", | |||
| "version": "0.0.2", | |||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | |||
| "main": "src/index.ts", | |||
| "module": "dist/index.mjs", | |||
| @@ -67,14 +67,13 @@ | |||
| }, | |||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||
| "devDependencies": { | |||
| "rimraf": "^5.0.1", | |||
| "@rollup/plugin-commonjs": "^25.0.0", | |||
| "@rollup/plugin-json": "^6.0.0", | |||
| "@rollup/plugin-node-resolve": "^15.0.2", | |||
| "@rollup/plugin-terser": "^0.4.1", | |||
| "@rollup/plugin-terser": "^0.4.3", | |||
| "@rollup/plugin-typescript": "^11.1.1", | |||
| "@types/stats.js": "^0.17.0", | |||
| "@typescript-eslint/eslint-plugin": "^5.59.5", | |||
| "@typescript-eslint/eslint-plugin": "^5.59.7", | |||
| "@typescript-eslint/parser": "^5.59.5", | |||
| "clean-package": "^2.2.0", | |||
| "eslint": "^8.40.0", | |||
| @@ -83,18 +82,23 @@ | |||
| "eslint-plugin-html": "^7.1.0", | |||
| "eslint-plugin-import": "^2.27.5", | |||
| "local-web-server": "^5.3.0", | |||
| "rollup": "^3.21.7", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2005/package.tgz", | |||
| "markdown-to-html-cli": "^3.7.0", | |||
| "rimraf": "^5.0.1", | |||
| "rollup": "^3.23.0", | |||
| "rollup-plugin-license": "^3.0.1", | |||
| "ts-browser-helpers": "^0.4.0", | |||
| "rollup-plugin-postcss": "^4.0.2", | |||
| "stats.js": "^0.17.0", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2005/package.tgz", | |||
| "ts-browser-helpers": "^0.5.0", | |||
| "tslib": "^2.5.0", | |||
| "tweakpane": "^3.1.9", | |||
| "@tweakpane/core": "^1.1.8", | |||
| "typedoc": "^0.24.7", | |||
| "typescript": "^5.0.4", | |||
| "uiconfig.js": "^0.0.3", | |||
| "typescript-plugin-css-modules": "^5.0.1", | |||
| "rollup-plugin-postcss": "^4.0.2", | |||
| "markdown-to-html-cli": "^3.7.0" | |||
| "uiconfig-tweakpane": "^0.0.3", | |||
| "uiconfig.js": "^0.0.4", | |||
| "tweakpane-image-plugin": "https://github.com/repalash/tweakpane-image-plugin/releases/download/v1.1.403/package.tgz" | |||
| }, | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.0004/package.tgz", | |||
| @@ -103,14 +107,19 @@ | |||
| }, | |||
| "//": { | |||
| "dependencies": { | |||
| "ts-browser-helpers": "^0.4.0", | |||
| "uiconfig.js": "^0.0.4", | |||
| "ts-browser-helpers": "^0.5.0", | |||
| "uiconfig-tweakpane": "^0.0.3", | |||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2005/package.tgz", | |||
| "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.152.2005.tar.gz", | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.0004/package.tgz", | |||
| "@types/three-f": "https://github.com/repalash/three-ts-types/archive/refs/tags/v0.152.0004.tar.gz", | |||
| "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three" | |||
| "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three", | |||
| "tweakpane-image-plugin": "git+ssh://github.com/repalash/tweakpane-image-plugin.git#52d5542047fd07d2e7225b5b67c9f7620366f2c7" | |||
| }, | |||
| "local_dependencies": { | |||
| "uiconfig-tweakpane": "^file:./../uiconfig-tweakpane", | |||
| "uiconfig.js": "^file:./../uiconfig", | |||
| "ts-browser-helpers": "file:./../ts-browser-helpers", | |||
| "three": "file:./../three.js", | |||
| "@types/three": "file:./../three-ts-types/types/three" | |||
| @@ -128,8 +137,12 @@ | |||
| "description": "A new way to work with three.js, 3D models and rendering on the web.", | |||
| "style": "body { padding: 4rem; } @media (max-width: 768px) { body { padding: 2.5rem 1rem; } }", | |||
| "meta": [ | |||
| { "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility" }, | |||
| { "keywords": "3d,three.js,typescript,javascipt,browser,esm,rendering,viewer,webgl,webgi,canvas" } | |||
| { | |||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility" | |||
| }, | |||
| { | |||
| "keywords": "3d,three.js,typescript,javascipt,browser,esm,rendering,viewer,webgl,webgi,canvas" | |||
| } | |||
| ] | |||
| }, | |||
| "favicon": "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌐</text></svg>", | |||
| @@ -2,6 +2,7 @@ import {IMaterial} from './IMaterial' | |||
| import {Texture} from 'three' | |||
| export interface ITextureUserData{ | |||
| mimeType?: string | |||
| disposeOnIdle?: boolean // automatically dispose when added to a material and then not used in any material | |||
| __appliedMaterials?: Set<IMaterial> | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import {Camera, Event, IUniform, Object3D, PerspectiveCamera, Vector3} from 'three' | |||
| import {generateUiConfig, UiObjectConfig, uiSlider, uiVector} from 'uiconfig.js' | |||
| import {onChange, serialize} from 'ts-browser-helpers' | |||
| import {generateUiConfig, uiInput, UiObjectConfig, uiSlider, uiVector} from 'uiconfig.js' | |||
| import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' | |||
| import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | |||
| import {ICameraSetDirtyOptions} from '../ICamera' | |||
| import type {ICameraControls, TControlsCtor} from './ICameraControls' | |||
| @@ -16,10 +16,12 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| return this._controls | |||
| } | |||
| @uiInput('Name') name: string | |||
| @serialize('camControls') | |||
| private _controls?: ICameraControls | |||
| private _currentControlsMode: TCameraControlsMode = '' | |||
| @onChange(PerspectiveCamera2.prototype.refreshCameraControls) | |||
| @onChange2(PerspectiveCamera2.prototype.refreshCameraControls) | |||
| controlsMode: TCameraControlsMode | |||
| /** | |||
| * It should be the canvas actually | |||
| @@ -33,38 +35,38 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| @serialize() | |||
| userData: ICameraUserData = {} | |||
| @onChange(PerspectiveCamera2.prototype.setDirty) | |||
| @onChange3(PerspectiveCamera2.prototype.setDirty) | |||
| @uiSlider('Field Of View', [1, 180], 0.001) | |||
| @serialize() fov: number | |||
| @onChange(PerspectiveCamera2.prototype.setDirty) | |||
| @onChange3(PerspectiveCamera2.prototype.setDirty) | |||
| @serialize() focus: number | |||
| @onChange(PerspectiveCamera2.prototype.setDirty) | |||
| @onChange3(PerspectiveCamera2.prototype.setDirty) | |||
| // @uiSlider('Zoom', [0.001, 20], 0.001) | |||
| @serialize() zoom: number | |||
| @uiVector('Position') | |||
| @serialize() readonly position: Vector3 | |||
| @onChange(PerspectiveCamera2.prototype.setDirty) | |||
| @onChange3(PerspectiveCamera2.prototype.setDirty) | |||
| @uiVector('Target') | |||
| @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | |||
| @serialize() | |||
| @onChange(PerspectiveCamera2.prototype.refreshAspect) | |||
| @onChange2(PerspectiveCamera2.prototype.refreshAspect) | |||
| autoAspect: boolean | |||
| /** | |||
| * Near clipping plane. This is managed by RootScene for active cameras | |||
| */ | |||
| @onChange(PerspectiveCamera2.prototype._nearFarChanged) | |||
| @onChange2(PerspectiveCamera2.prototype._nearFarChanged) | |||
| near = 0.01 | |||
| /** | |||
| * Far clipping plane. This is managed by RootScene for active cameras | |||
| */ | |||
| @onChange(PerspectiveCamera2.prototype._nearFarChanged) | |||
| @onChange2(PerspectiveCamera2.prototype._nearFarChanged) | |||
| far = 50 | |||
| constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, fov?: number, aspect?: number) { | |||
| @@ -129,6 +131,8 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| setDirty(options?: ICameraSetDirtyOptions|Event): void { | |||
| if (!this._positionWorld) return // class not initialized | |||
| if (options?.key === 'fov') this.updateProjectionMatrix() | |||
| this.getWorldPosition(this._positionWorld) | |||
| iCameraCommons.setDirty.call(this, options) | |||
| @@ -317,8 +321,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| uiConfig: UiObjectConfig = { | |||
| type: 'folder', | |||
| label: 'Camera', | |||
| limitedUi: true, | |||
| label: ()=>this.name || 'Camera', | |||
| children: [ | |||
| ...this._camUi, | |||
| // todo hack for zoom in and out for now. | |||
| @@ -1,5 +1,6 @@ | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | |||
| import {toIndexedGeometry} from '../../three' | |||
| export const iGeometryCommons = { | |||
| setDirty: function(this: IGeometry, options?: IGeometrySetDirtyOptions): void { | |||
| @@ -21,18 +22,74 @@ export const iGeometryCommons = { | |||
| property: [this, 'uuid'], | |||
| disabled: true, | |||
| }, | |||
| // { | |||
| // type: 'input', | |||
| // property: [this, 'name'], | |||
| // }, | |||
| { | |||
| type: 'input', | |||
| property: [this, 'name'], | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Center Geometry', | |||
| value: () => { | |||
| this.center() | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Compute vertex normals', | |||
| value: () => { | |||
| if (this.hasAttribute('normal') && !confirm('Normals already exist, replace with computed normals?')) return | |||
| this.computeVertexNormals() | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Compute vertex tangents', | |||
| value: () => { | |||
| if (this.hasAttribute('tangent') && !confirm('Tangents already exist, replace with computed tangents?')) return | |||
| this.computeTangents() | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Normalize normals', | |||
| value: () => { | |||
| this.normalizeNormals() | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Convert to indexed', | |||
| hidden: () => !!this.index, | |||
| value: () => { | |||
| if (this.attributes.index) return | |||
| const tolerance = parseFloat(prompt('Tolerance', '-1') ?? '-1') | |||
| toIndexedGeometry(this, tolerance) | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Convert to non-indexed', | |||
| hidden: () => !this.index, | |||
| value: () => { | |||
| if (!this.attributes.index) return | |||
| this.toNonIndexed() | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Create uv2 from uv', | |||
| label: 'Create uv1 from uv', | |||
| value: () => { | |||
| if (this.hasAttribute('uv2')) { | |||
| if (!confirm('uv2 already exists, replace with uv data?')) return | |||
| if (this.hasAttribute('uv1')) { | |||
| if (!confirm('uv1 already exists, replace with uv data?')) return | |||
| } | |||
| this.setAttribute('uv2', this.getAttribute('uv')) | |||
| this.setAttribute('uv1', this.getAttribute('uv')) | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| { | |||
| @@ -46,27 +103,9 @@ export const iGeometryCommons = { | |||
| } | |||
| if (!confirm('Remove color attribute?')) return | |||
| this.deleteAttribute('color') | |||
| this.setDirty() | |||
| }, | |||
| }, | |||
| // { | |||
| // type: 'button', | |||
| // label: 'Invert eigen vectors', | |||
| // value: () => { | |||
| // console.log(geometry) | |||
| // const offsets = geometry.userData.normalsCaptureOffsets | |||
| // if (!offsets) return | |||
| // const m = offsets.offsetMatrix as Matrix4 | |||
| // console.log(offsets.offsetMatrix.toArray()) | |||
| // console.log(m.determinant()) | |||
| // | |||
| // const m1 = new Matrix4().makeRotationX(Math.PI / 2) | |||
| // m.multiply(m1) | |||
| // | |||
| // console.log(m.determinant()) | |||
| // offsets.offsetMatrixInv.copy(m).invert() | |||
| // console.log(offsets.offsetMatrix.toArray()) | |||
| // }, | |||
| // }, | |||
| { | |||
| type: 'input', | |||
| label: 'Mesh count', | |||
| @@ -88,9 +88,11 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| label: 'Auto Scale', | |||
| hidden: ()=>!this.autoScale, | |||
| prompt: ['Auto Scale Radius: Object will be scaled to the given radius', this.userData.autoScaleRadius || '2', true], | |||
| value: (res: string|null)=>{ | |||
| if (!res) return | |||
| const rad = parseFloat(res) | |||
| value: ()=>{ | |||
| const def = (this.userData.autoScaleRadius || 2) + '' | |||
| const res = prompt('Auto Scale Radius: Object will be scaled to the given radius', def) | |||
| if (res === null) return | |||
| const rad = parseFloat(res || def) | |||
| if (Math.abs(rad) > 0) this.autoScale?.(rad) | |||
| }, | |||
| }, | |||
| @@ -8,20 +8,24 @@ import { | |||
| UVMapping, | |||
| Vector3, | |||
| } from 'three' | |||
| import {IObject3D, IObjectProcessor} from '../IObject' | |||
| import type {IObject3D, IObjectProcessor} from '../IObject' | |||
| import {type ICamera} from '../ICamera' | |||
| import {Box3B} from '../../three/math/Box3B' | |||
| import {AnyOptions, onChange, serialize} from 'ts-browser-helpers' | |||
| import {Box3B} from '../../three' | |||
| import {AnyOptions, onChange2, onChange3, serialize} from 'ts-browser-helpers' | |||
| import {PerspectiveCamera2} from '../camera/PerspectiveCamera2' | |||
| import {ThreeSerialization} from '../../utils/serialization' | |||
| import {ThreeSerialization} from '../../utils' | |||
| import {ITexture} from '../ITexture' | |||
| import {AddObjectOptions, IScene, ISceneEvent, ISceneEventTypes, ISceneSetDirtyOptions} from '../IScene' | |||
| import {iObjectCommons} from './iObjectCommons' | |||
| import {RootSceneImportResult} from '../../assetmanager/IAssetImporter' | |||
| import {RootSceneImportResult} from '../../assetmanager' | |||
| import {uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| @uiFolderContainer('Root Scene') | |||
| export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements IScene<ISceneEvent, ISceneEventTypes> { | |||
| isRootScene = true | |||
| readonly isRootScene = true | |||
| assetType = 'model' as const | |||
| uiConfig!: UiObjectConfig | |||
| // private _processors = new ObjectProcessorMap<'environment' | 'background'>() | |||
| // private _sceneObjects: ISceneObject[] = [] | |||
| @@ -31,26 +35,43 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| */ | |||
| readonly modelRoot: IObject3D | |||
| @uiColor<RootScene>('Background Color', (s)=>({ | |||
| onChange: ()=>s?._onBackgroundChange(), | |||
| })) | |||
| @serialize() @onChange2(RootScene.prototype._onBackgroundChange) | |||
| backgroundColor: Color | null = null // read in three.js WebGLBackground | |||
| @onChange2(RootScene.prototype._onBackgroundChange) | |||
| @serialize() @uiImage('Background Image') | |||
| background: null | Color | ITexture | 'environment' = null | |||
| /** | |||
| * The default camera in the scene | |||
| * The intensity for the environment light. | |||
| */ | |||
| @serialize() readonly defaultCamera: ICamera | |||
| @serialize() @onChange3(RootScene.prototype.setDirty) | |||
| @uiSlider('Background Intensity', [0, 10], 0.01) | |||
| backgroundIntensity = 1 | |||
| @uiImage('Environment') | |||
| @serialize() @onChange2(RootScene.prototype._onEnvironmentChange) | |||
| environment: ITexture | null = null | |||
| /** | |||
| * The intensity for the environment light. | |||
| */ | |||
| @onChange(RootScene.prototype.setDirty) // todo: fix options that get passed to setDirty | |||
| @serialize() envMapIntensity = 1 | |||
| @uiSlider('Environment Intensity', [0, 10], 0.01) | |||
| @serialize() @onChange3(RootScene.prototype.setDirty) | |||
| envMapIntensity = 1 | |||
| /** | |||
| * Fixed direction environment reflections irrespective of camera position. | |||
| */ | |||
| @onChange(RootScene.prototype.setDirty) // todo: fix options that get passed to setDirty | |||
| @serialize() fixedEnvMapDirection = false | |||
| @uiToggle('Fixed Env Direction') | |||
| @serialize() @onChange3(RootScene.prototype.setDirty) | |||
| fixedEnvMapDirection = false | |||
| /** | |||
| * The intensity for the environment light. | |||
| * The default camera in the scene | |||
| */ | |||
| @onChange(RootScene.prototype.setDirty) // todo: fix options that get passed to setDirty | |||
| @serialize() backgroundIntensity = 1 | |||
| @uiConfig() @serialize() readonly defaultCamera: ICamera | |||
| // private _environmentLight?: IEnvironmentLight | |||
| @@ -80,14 +101,6 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| this.setDirty() | |||
| } | |||
| addEventListener<T extends ISceneEventTypes>(type: T, listener: EventListener<ISceneEvent, T, this>): void { | |||
| if (type === 'activeCameraChange') console.error('activeCameraChange is deprecated. Use mainCameraChange instead.') | |||
| if (type === 'activeCameraUpdate') console.error('activeCameraUpdate is deprecated. Use mainCameraUpdate instead.') | |||
| if (type === 'sceneMaterialUpdate') console.error('sceneMaterialUpdate is deprecated. Use materialUpdate instead.') | |||
| if (type === 'update') console.error('update is deprecated. Use sceneUpdate instead.') | |||
| super.addEventListener(type, listener) | |||
| } | |||
| /** | |||
| * Create a scene instance. This is done automatically in the {@link ThreeViewer} and must not be created separately. | |||
| * @param camera | |||
| @@ -102,6 +115,8 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| // this is called from parentDispatch since scene is a parent. | |||
| this.addEventListener('materialUpdate', ()=>this.dispatchEvent({type: 'sceneMaterialUpdate'})) | |||
| this.addEventListener('objectUpdate', this.refreshScene) | |||
| this.addEventListener('geometryUpdate', this.refreshScene) | |||
| this.addEventListener('geometryChanged', this.refreshScene) | |||
| this.defaultCamera = camera | |||
| this.modelRoot = new Object3D() as IObject3D | |||
| @@ -240,10 +255,6 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| if (setDirty) this.setDirty({refreshScene: true}) | |||
| } | |||
| @serialize() | |||
| @onChange(RootScene.prototype._onEnvironmentChange.name) // cannot do this as updateShadow in shadowBaker resets it every frame temporarily, todo: why was that needed? | |||
| public environment: ITexture | null = null | |||
| private _onEnvironmentChange() { | |||
| // console.warn('environment changed') | |||
| if (this.environment?.mapping === UVMapping) { | |||
| @@ -252,20 +263,15 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| } | |||
| this.dispatchEvent({type: 'environmentChanged', environment: this.environment}) | |||
| this.setDirty({refreshScene: true, geometryChanged: false}) | |||
| this.refreshUi?.() | |||
| } | |||
| private _onBackgroundChange() { | |||
| this.dispatchEvent({type: 'backgroundChanged', background: this.background, backgroundColor: this.backgroundColor}) | |||
| this.setDirty({refreshScene: true, geometryChanged: false}) | |||
| this.refreshUi?.() | |||
| } | |||
| @serialize() | |||
| @onChange(RootScene.prototype._onBackgroundChange) | |||
| public background: null | Color | ITexture | 'environment' = null | |||
| @serialize() | |||
| @onChange(RootScene.prototype._onBackgroundChange) | |||
| public backgroundColor: Color | null = null // read in three.js WebGLBackground | |||
| /** | |||
| * @deprecated Use {@link addObject} | |||
| */ | |||
| @@ -284,7 +290,8 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| * @param options - set sceneUpdate to true to to mark that any object transformations have changed. It might trigger effects like frame fade depening on plugins. | |||
| * @returns {this} | |||
| */ | |||
| setDirty(options?: ISceneSetDirtyOptions): this { // todo;;; | |||
| setDirty(options?: ISceneSetDirtyOptions): this { | |||
| // todo: for onChange calls -> check options.key for specific key that's changed and use it to determine refreshScene | |||
| if (options?.sceneUpdate) { | |||
| console.warn('sceneUpdate is deprecated, use refreshScene instead.') | |||
| options.refreshScene = true | |||
| @@ -302,7 +309,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| private _mainCameraUpdate = () => { | |||
| this.setDirty({refreshScene: false}) | |||
| this.refreshActiveCameraNearFar() | |||
| this.dispatchEvent({type: 'mainCameraUpdate'}) // this sets dirty in the viewer | |||
| this.dispatchEvent({type: 'mainCameraUpdate'}) | |||
| this.dispatchEvent({type: 'activeCameraUpdate'}) // deprecated | |||
| } | |||
| @@ -328,7 +335,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| return this | |||
| } | |||
| refreshUi = iObjectCommons.refreshUi | |||
| refreshUi = iObjectCommons.refreshUi.bind(this) | |||
| /** | |||
| * Dispose the scene and clear all resources. | |||
| @@ -461,6 +468,14 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| return this | |||
| } | |||
| addEventListener<T extends ISceneEventTypes>(type: T, listener: EventListener<ISceneEvent, T, this>): void { | |||
| if (type === 'activeCameraChange') console.error('activeCameraChange is deprecated. Use mainCameraChange instead.') | |||
| if (type === 'activeCameraUpdate') console.error('activeCameraUpdate is deprecated. Use mainCameraUpdate instead.') | |||
| if (type === 'sceneMaterialUpdate') console.error('sceneMaterialUpdate is deprecated. Use materialUpdate instead.') | |||
| if (type === 'update') console.error('update is deprecated. Use sceneUpdate instead.') | |||
| super.addEventListener(type, listener) | |||
| } | |||
| /** | |||
| * Minimum Camera near plane | |||
| * @deprecated - use camera.userData.minNearPlane instead | |||
| @@ -469,7 +484,6 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| console.error('minNearDistance is deprecated. Use camera.userData.minNearPlane instead') | |||
| return this.mainCamera.userData.minNearPlane ?? 0.02 | |||
| } | |||
| /** | |||
| * @deprecated - use camera.userData.minNearPlane instead | |||
| */ | |||
| @@ -2,4 +2,5 @@ export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | |||
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | |||
| export {PipelinePassPlugin} from './base/PipelinePassPlugin' | |||
| export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | |||
| export {TweakpaneUiPlugin} from './ui/tweakpane/TweakpaneUiPlugin' | |||
| export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | |||
| @@ -1,8 +1,9 @@ | |||
| import {AViewerPluginSync} from '../../viewer/AViewerPlugin' | |||
| import {type ThreeViewer} from '../../viewer/' | |||
| import {Dropzone} from '../../utils' | |||
| import {uiButton, uiConfig, uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| import {uiButton, uiConfig, uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js' | |||
| import type {AddAssetOptions, ImportFilesOptions, ImportResult} from '../../assetmanager' | |||
| import {serialize} from 'ts-browser-helpers' | |||
| export interface DropzonePluginOptions { | |||
| domElement?: HTMLElement | |||
| @@ -15,7 +16,8 @@ export interface DropzonePluginOptions { | |||
| @uiFolderContainer('Dropzone') | |||
| export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||
| static readonly PluginType = 'Dropzone' | |||
| @uiToggle() enabled = true | |||
| uiConfig!: UiObjectConfig | |||
| @uiToggle() @serialize() enabled = true | |||
| private _inputEl?: HTMLInputElement | |||
| private _dropzone?: Dropzone | |||
| private _allowedExtensions: string[]|undefined = undefined // undefined and empty array is different. | |||
| @@ -23,17 +25,17 @@ export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||
| /** | |||
| * Automatically import assets when dropped. | |||
| */ | |||
| autoImport = true | |||
| @serialize() autoImport = true | |||
| /** | |||
| * Automatically add dropped and imported assets to the scene. | |||
| * Works only if {@link autoImport} is true. | |||
| Works only if {@link autoImport} is true. | |||
| */ | |||
| @uiToggle() autoAdd = true | |||
| @uiToggle() @serialize() autoAdd = true | |||
| /** | |||
| * Import options for the {@link AssetImporter.importFiles} | |||
| */ | |||
| @uiConfig() importOptions: ImportFilesOptions = { | |||
| @uiConfig() @serialize() importOptions: ImportFilesOptions = { | |||
| autoImportZipContents: true, | |||
| forceImporterReprocess: false, | |||
| } | |||
| @@ -41,7 +43,7 @@ export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||
| /** | |||
| * Add options for the {@link RootScene.addObject} | |||
| */ | |||
| @uiConfig() addOptions: AddAssetOptions = { | |||
| @uiConfig() @serialize() addOptions: AddAssetOptions = { | |||
| autoCenter: true, | |||
| importConfig: true, | |||
| autoScale: true, | |||
| @@ -47,23 +47,3 @@ | |||
| content: '+'; | |||
| line-height: 20px; | |||
| } | |||
| .RenderTargetPreviewPluginContextMenu{ | |||
| position: absolute; | |||
| background: #333e; | |||
| min-width: 8rem; | |||
| color: white; | |||
| font-size: 0.8rem; | |||
| overflow: hidden; | |||
| border-radius: 8px; | |||
| box-shadow: 2px 2px 10px #6666; | |||
| z-index: 100000; | |||
| font-family: monospace; | |||
| } | |||
| .RenderTargetPreviewPluginContextMenu div{ | |||
| cursor: pointer; | |||
| padding: 6px 10px; | |||
| transition: background-color 0.25s ease-in-out; | |||
| } | |||
| .RenderTargetPreviewPluginContextMenu div:hover{ | |||
| background: #1a1a1c; | |||
| } | |||
| @@ -3,6 +3,7 @@ import {IRenderTarget} from '../../rendering' | |||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | |||
| import {Vector4, WebGLRenderTarget} from 'three' | |||
| import styles from './RenderTargetPreviewPlugin.css' | |||
| import {CustomContextMenu} from '../../utils' | |||
| export class RenderTargetPreviewPlugin <TEvent extends string> extends AViewerPluginSync<TEvent> { | |||
| static readonly PluginType = 'RenderTargetPreviewPlugin' | |||
| @@ -87,31 +88,10 @@ export class RenderTargetPreviewPlugin <TEvent extends string> extends AViewerPl | |||
| header.oncontextmenu = (e) => { | |||
| e.preventDefault() | |||
| e.stopPropagation() | |||
| const menu = document.createElement('div') | |||
| menu.classList.add('RenderTargetPreviewPluginContextMenu') | |||
| menu.style.left = e.clientX + 'px' | |||
| menu.style.top = e.clientY + 'px' | |||
| const download = document.createElement('div') | |||
| download.innerText = 'Download' | |||
| download.onclick = () => { | |||
| this.downloadTarget(target) | |||
| menu.remove() | |||
| } | |||
| const remove = document.createElement('div') | |||
| remove.innerText = 'Remove' | |||
| remove.onclick = () => { | |||
| this.removeTarget(target) | |||
| menu.remove() | |||
| } | |||
| const cancel = document.createElement('div') | |||
| cancel.innerText = 'Cancel' | |||
| cancel.onclick = () => { | |||
| menu.remove() | |||
| } | |||
| menu.appendChild(download) | |||
| menu.appendChild(remove) | |||
| menu.appendChild(cancel) | |||
| document.body.appendChild(menu) | |||
| CustomContextMenu.Create({ | |||
| 'Download': () => this.downloadTarget(target), | |||
| 'Remove': () => this.removeTarget(target), | |||
| }, e.clientX, e.clientY) | |||
| } | |||
| div.appendChild(header) | |||
| this.mainDiv.appendChild(div) | |||
| @@ -0,0 +1,138 @@ | |||
| import {FolderApi} from 'tweakpane' | |||
| import * as TweakpaneImagePlugin from 'tweakpane-image-plugin' | |||
| import {UiConfigRendererTweakpane} from 'uiconfig-tweakpane' | |||
| import {IViewerPlugin, IViewerPluginSync, ThreeViewer} from '../../../viewer' | |||
| import styles from './tpTheme.css' | |||
| import {Class, createDiv, createStyles, downloadBlob, getOrCall, IEvent, uploadFile} from 'ts-browser-helpers' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {CustomContextMenu} from '../../../utils' | |||
| import {Color, Vector2, Vector3, Vector4} from 'three' | |||
| import {tpImageInputGenerator} from './tpImageInputGenerator' | |||
| export class TweakpaneUiPlugin extends UiConfigRendererTweakpane implements IViewerPluginSync { | |||
| declare ['constructor']: typeof TweakpaneUiPlugin | |||
| static readonly PluginType = 'TweakpaneUi' | |||
| enabled = true | |||
| constructor(expanded = false, bigTheme = true, container: HTMLElement = document.body) { | |||
| super(container, { | |||
| expanded, autoPostFrame: false, | |||
| }) | |||
| this.THREE = {Color, Vector4, Vector3, Vector2} as any | |||
| this._root!.registerPlugin(TweakpaneImagePlugin) | |||
| if (bigTheme) createStyles(styles, container) | |||
| } | |||
| protected _viewer?: ThreeViewer | |||
| onAdded(viewer: ThreeViewer): void { | |||
| this._viewer = viewer | |||
| this.typeGenerators.image = tpImageInputGenerator(this._viewer) | |||
| viewer.addEventListener('preRender', this._preRender) | |||
| viewer.addEventListener('postRender', this._postRender) | |||
| viewer.addEventListener('preFrame', this._preFrame) | |||
| viewer.addEventListener('postFrame', this._postFrame) | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| this._viewer = undefined | |||
| viewer.removeEventListener('preRender', this._preRender) | |||
| viewer.removeEventListener('postRender', this._postRender) | |||
| viewer.removeEventListener('preFrame', this._preFrame) | |||
| viewer.removeEventListener('postFrame', this._postFrame) | |||
| this.dispose() | |||
| } | |||
| private _plugins: IViewerPlugin[] = [] | |||
| setupPlugins<T extends IViewerPlugin>(...plugins: Class<T>[]): void { | |||
| plugins.forEach(plugin => this.setupPluginUi(plugin)) | |||
| } | |||
| setupPluginUi<T extends IViewerPlugin>(plugin: T|Class<T>): UiObjectConfig | undefined { | |||
| const p = (<Class<IViewerPlugin>>plugin).prototype ? this._viewer?.getPlugin<T>(<Class<T>>plugin) : <T>plugin | |||
| if (!p) { | |||
| console.warn('plugin not found:', plugin) | |||
| return undefined | |||
| } | |||
| this._plugins.push(p) | |||
| if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now | |||
| this.appendChild(p) | |||
| const ui = p.uiConfig | |||
| this._setupPluginSerializationContext(ui, p) | |||
| return ui | |||
| } | |||
| private _setupPluginSerializationContext(ui: any, p: IViewerPlugin) { | |||
| // serialization | |||
| if (!(ui?.uiRef && p.toJSON)) return; | |||
| (p as any)._defaultState = typeof p.toJSON === 'function' ? p.toJSON() : null | |||
| ;(p as any).resetDefaults = async() => { | |||
| if (!(p as any)._defaultState) return | |||
| await p.fromJSON?.((p as any)._defaultState) | |||
| ui.uiRefresh?.(true, 'postFrame') | |||
| } | |||
| const topBtn = (ui.uiRef as FolderApi).controller_.view.element | |||
| const opBtn = createDiv({ | |||
| innerHTML: '⋮', | |||
| classList: ['pluginOptionsButton'], | |||
| elementTag: 'button', | |||
| }) | |||
| opBtn.onclick = (ev) => { | |||
| const ops = {} as any | |||
| if (typeof p.toJSON === 'function') { | |||
| ops['download preset'] = async() => { | |||
| if (!this._viewer) return | |||
| const json = this._viewer.exportPluginConfig(p) | |||
| await downloadBlob(new Blob([JSON.stringify(json, null, 2)], {type: 'application/json'}), 'preset.' + (p.constructor as any).PluginType + '.json') | |||
| } | |||
| } | |||
| if (typeof p.fromJSON === 'function') { | |||
| ops['upload preset'] = async() => { | |||
| const files = await uploadFile(false, false) | |||
| if (files.length === 0) return | |||
| const file = files[0] | |||
| const text = await file.text() | |||
| const json = JSON.parse(text) | |||
| await this._viewer?.importPluginConfig(json, p) | |||
| ui.uiRefresh?.(true, 'postFrame') | |||
| } | |||
| if ((p as any)._defaultState) ops['reset defaults'] = () => (p as any).resetDefaults?.() | |||
| } | |||
| const menu = CustomContextMenu.Create(ops, topBtn.clientWidth - 120, 12) | |||
| topBtn.append(menu) | |||
| ev.preventDefault() | |||
| } | |||
| topBtn.appendChild(opBtn) | |||
| } | |||
| refreshPluginsEnabled() { | |||
| this._plugins.forEach(p=>{ | |||
| const config = p.uiConfig | |||
| if (config) { | |||
| // const enabled = (p as any).enabled ?? true | |||
| // safeSetProperty(config, 'hidden', !enabled, true) | |||
| // if (config.expanded) | |||
| // safeSetProperty(config, 'expanded', config.expanded && enabled, true) | |||
| if (getOrCall(config.hidden) !== true) | |||
| config.uiRefresh?.(true, 'postFrame') | |||
| else if (config.uiRef) { | |||
| config.uiRef.hidden = true | |||
| } | |||
| } | |||
| }) | |||
| } | |||
| private _preRender = () => this.refreshQueue('preRender') | |||
| private _postRender = () => this.refreshQueue('postRender') | |||
| private _postFrame = (e: IEvent<'postFrame'>) => { | |||
| this.dispatchEvent(e) | |||
| this.refreshQueue('postFrame') | |||
| } | |||
| private _preFrame = () => this.refreshQueue('preFrame') | |||
| alert = async(message?: string): Promise<void> =>this._viewer ? this._viewer.dialog.alert(message) : window?.alert(message) | |||
| confirm = async(message?: string): Promise<boolean> =>this._viewer ? this._viewer.dialog.confirm(message) : window?.confirm(message) | |||
| prompt = async(message?: string, _default?: string, cancel = true): Promise<string | null> =>this._viewer ? this._viewer.dialog.prompt(message, _default, cancel) : window?.prompt(message, _default) | |||
| } | |||
| @@ -0,0 +1,252 @@ | |||
| import {ThreeViewer} from '../../../viewer' | |||
| import type {FolderApi} from 'tweakpane' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {imageBitmapToBase64, makeTextSvg} from 'ts-browser-helpers' | |||
| import {generateUUID} from '../../../three' | |||
| import {ITexture, upgradeTexture} from '../../../core' | |||
| import {LinearSRGBColorSpace, RepeatWrapping, SRGBColorSpace, Texture} from 'three' | |||
| import {CustomContextMenu} from '../../../utils' | |||
| import {TweakpaneUiPlugin} from './TweakpaneUiPlugin' | |||
| const staticData = { | |||
| placeholderVal: 'placeholder', | |||
| renderTarImage: makeTextSvg('Render Target'), | |||
| dataTexImage: makeTextSvg('Data Texture'), | |||
| lutCubeTexImage: makeTextSvg('CUBE Texture'), | |||
| compressedTexImage: makeTextSvg('Compressed Texture'), | |||
| imageMap: {} as any, | |||
| tempMap: {} as any, | |||
| } | |||
| function proxyGetValue(cc: any) { | |||
| if (cc?.get) cc = cc.get() | |||
| let ret: any = undefined | |||
| if (!cc) return staticData.placeholderVal | |||
| if (cc.isRenderTargetTexture && !cc.image.tp_src) { | |||
| cc.image.tp_src = staticData.renderTarImage | |||
| } | |||
| if (cc.isDataTexture && !cc.image.tp_src) { | |||
| cc.image.tp_src = staticData.dataTexImage | |||
| } | |||
| if (cc.isCompressedTexture && !cc.image.tp_src) { | |||
| cc.image.tp_src = staticData.compressedTexImage | |||
| } | |||
| // todo: video is not playing | |||
| // if (cc.isVideoTexture && !cc.image.tp_src) { | |||
| // cc.image.tp_src = dataTexImage | |||
| // } | |||
| if (cc.isTexture) { | |||
| // console.warn('here') | |||
| if (cc.image && (cc.image instanceof ImageBitmap || cc.image instanceof HTMLImageElement || cc.image instanceof HTMLVideoElement) && !cc.image.tp_src) { | |||
| cc.image.tp_src = imageBitmapToBase64(cc.image, 160) | |||
| } | |||
| if (cc.image) { | |||
| ret = cc.image.tp_src_uuid | |||
| ret = ret ? staticData.imageMap[ret] : undefined | |||
| if (!ret) ret = cc.image.tp_src || cc.image.src | |||
| } | |||
| } else if (typeof cc === 'string') { | |||
| ret = cc | |||
| } else if (cc.domainMin) { // for lut CUBE files. | |||
| ret = cc.texture | |||
| if (cc.texture.image && !cc.texture.image.tp_src) { | |||
| cc.texture.image.tp_src = staticData.lutCubeTexImage | |||
| } | |||
| if (cc.texture.image) { | |||
| ret = cc.texture.image.tp_src_uuid | |||
| ret = ret ? staticData.imageMap[ret] : undefined | |||
| if (!ret) ret = cc.texture.image.tp_src || cc.texture.image.src | |||
| } | |||
| } else if (cc) { | |||
| console.error('unknown value', cc) | |||
| } | |||
| if (!ret) ret = staticData.placeholderVal | |||
| if (cc.image && !cc.image.tp_src_uuid) { | |||
| const uuid = generateUUID() | |||
| cc.image.tp_src_uuid = uuid | |||
| staticData.tempMap[ret] = uuid | |||
| } | |||
| // console.log(ret, cc, tar, key) | |||
| if (typeof ret === 'string') | |||
| ret = staticData.imageMap[ret] ?? ret // Note: this will be a bottleneck if the length of src is too long. | |||
| return ret | |||
| } | |||
| const setterTex = (v1: any, config: UiObjectConfig, renderer: TweakpaneUiPlugin)=>{ | |||
| if (v1?.isTexture) { | |||
| if (!v1.isDataTexture) { | |||
| const key = renderer.methods.getBinding(config)[1] + '' | |||
| const isLinear = ['normalMap', 'aoMap', 'emissiveMap', 'roughnessMap', 'metalnessMap', 'displacementMap', 'bumpMap', 'alphaMap'].includes(key) | |||
| v1.colorSpace = isLinear ? LinearSRGBColorSpace : SRGBColorSpace | |||
| v1.wrapS = RepeatWrapping | |||
| v1.wrapT = RepeatWrapping | |||
| v1.flipY = config.__proxy.value_?.flipY ?? true // todo: figure out flipY | |||
| } else { | |||
| v1.needsUpdate = true | |||
| } | |||
| } | |||
| config.__proxy.value_ = v1 | |||
| renderer.methods.setValue(config, v1, {last: true}, false) | |||
| config.uiRefresh?.(false, 'postFrame') | |||
| } | |||
| function proxySetValue(v: any, cc: any, config: UiObjectConfig, viewer: ThreeViewer, renderer: TweakpaneUiPlugin) { | |||
| if (typeof v === 'string') { | |||
| if (typeof cc === 'string') setterTex(v, config, renderer) | |||
| return | |||
| } | |||
| v = v || staticData.placeholderVal | |||
| if ((v as any).isPlaceholder || v === staticData.placeholderVal) { | |||
| if (cc) setterTex(typeof cc === 'string' ? '' : null, config, renderer) | |||
| return | |||
| } | |||
| let iMapKey = v.tp_src_uuid | |||
| if (!iMapKey) { | |||
| iMapKey = v.src ?? v.tp_src | |||
| iMapKey = staticData.tempMap[iMapKey] ?? iMapKey | |||
| delete staticData.tempMap[iMapKey] | |||
| v.tp_src_uuid = iMapKey | |||
| } | |||
| if (iMapKey) | |||
| staticData.imageMap[iMapKey] = v | |||
| // todo: dispose textures if not used. | |||
| if (typeof cc === 'string') { | |||
| setterTex(iMapKey, config, renderer) | |||
| return | |||
| } | |||
| if (cc?.image === v | |||
| || cc?.image?.src === v.src | |||
| || cc?.image?.tp_src === v.tp_src && v.tp_src != null | |||
| || cc?.image?.tp_src === v.src && v.src != null | |||
| || cc?.image?.src === v.tp_src && v.tp_src != null | |||
| ) return | |||
| if (v instanceof File) { // v.src must be from createObjectURL. | |||
| viewer.assetManager.importer.importSingle<ITexture>({file: v, path: (v as any).src}).then(texture => { | |||
| if (!texture) return | |||
| if (texture.isDataTexture) texture.needsUpdate = true | |||
| const ext = (v as any).src?.split('?')?.[0]?.split('.').pop() | |||
| if ((texture as any).userData) { | |||
| if (!(texture as any).userData.mimeType) | |||
| (texture as any).userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | |||
| } | |||
| setterTex(texture, config, renderer) | |||
| }) | |||
| } else { // HTMLImageElement, ImageBitmap, HTMLVideoElement | |||
| const tex: ITexture = new Texture(v) | |||
| upgradeTexture.call(tex) | |||
| tex.assetType = 'texture' | |||
| tex.needsUpdate = true | |||
| // set userData.mimeType for GLTFExporter | |||
| const ext = v.src?.split('?')?.[0]?.split('.').pop() | |||
| if (!tex.userData.mimeType) | |||
| tex.userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') | |||
| setterTex(tex, config, renderer) | |||
| // todo: make normal maps jpeg always? jpg is lossy | |||
| } | |||
| } | |||
| function removeImage(config: UiObjectConfig, renderer: TweakpaneUiPlugin) { | |||
| const vc = config.uiRef.controller_.valueController as any | |||
| vc.value.setRawValue('') | |||
| const isStr = typeof config.__proxy.value_ === 'string' | |||
| setterTex(isStr ? '' : null, config, renderer) | |||
| } | |||
| function downloadImage(config: UiObjectConfig) { | |||
| const cc = config.__proxy.value_ | |||
| let vcv = cc?.image ?? config.uiRef.controller_.valueController.value.rawValue | |||
| if (vcv && (vcv instanceof ImageBitmap || vcv instanceof HTMLImageElement || vcv instanceof HTMLVideoElement) && !(vcv as any).src) | |||
| vcv = imageBitmapToBase64(vcv) | |||
| const link = document.createElement('a') | |||
| document.body.appendChild(link) | |||
| link.style.display = 'none' | |||
| link.href = vcv?.src ?? vcv | |||
| link.download = 'image.png' | |||
| // link.target = '_blank' | |||
| link.click() | |||
| document.body.removeChild(link) | |||
| } | |||
| async function imageFromUrl(renderer: TweakpaneUiPlugin, config: UiObjectConfig, viewer: ThreeViewer) { | |||
| // let url: string|null = navigator.clipboard ? await navigator.clipboard.readText() : '' | |||
| let url: string | null = '' | |||
| // if (!url || !url.startsWith('http') && !url.startsWith('data:image')) { | |||
| // url = '' | |||
| // } | |||
| url = await renderer.prompt('Load texture: Enter Image/Texture URL', url, true) | |||
| if (!url || !url.startsWith('http') && !url.startsWith('data:image')) { | |||
| if (url !== null) await renderer.alert('Loading Image: Invalid URL') | |||
| return | |||
| } else { | |||
| url = url.trim() | |||
| } | |||
| const cc = config.__proxy.value_ | |||
| const isStr = typeof cc === 'string' | |||
| if (isStr) { | |||
| setterTex(url, config, renderer) | |||
| } else { // texture | |||
| viewer.assetManager.importer.importSingle<ITexture>(url).then(texture => { | |||
| if (!texture) { | |||
| console.warn('Failed to load texture', url) | |||
| return | |||
| } | |||
| setterTex(texture, config, renderer) | |||
| }) | |||
| } | |||
| } | |||
| export const tpImageInputGenerator = (viewer: ThreeViewer) => (parent: FolderApi, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => { | |||
| // if (config.value !== undefined) throw 'Not supported yet' | |||
| if (!config.__proxy) { | |||
| config.__proxy = { | |||
| listedOnChange: false, | |||
| } | |||
| Object.defineProperty(config.__proxy, 'value', { | |||
| get: () => { | |||
| config.__proxy.value_ = renderer.methods.getValue(config) | |||
| return proxyGetValue(config.__proxy.value_) | |||
| }, | |||
| set: (v: any) => { | |||
| config.__proxy.value_ = renderer.methods.getValue(config) | |||
| proxySetValue(v, config.__proxy.value_, config, viewer, renderer) | |||
| }, | |||
| }) | |||
| } | |||
| config.__proxy.value_ = renderer.methods.getValue(config) | |||
| params = params ?? {} | |||
| params.extensions = ['.jpg', '.png', '.svg', '.hdr', | |||
| '.exr', /* '.mp4', '.ogg', '.mov',*/ '.jpeg', | |||
| '.bmp', '.gif', '.webp', '.cube'] | |||
| if (typeof params.imageFit === 'undefined') params.imageFit = 'contain' | |||
| if (typeof params.clickCallback === 'undefined') params.clickCallback = (ev: MouseEvent, inp: HTMLInputElement) => { | |||
| const target = ev?.target as HTMLElement | |||
| const rect = target?.getBoundingClientRect() | |||
| if (!rect) { | |||
| inp.click() | |||
| return | |||
| } | |||
| const cv = config.uiRef.controller_.valueController.value.rawValue | |||
| const isPlaceholder = cv === staticData.placeholderVal || cv?.isPlaceholder | |||
| const items: any = isPlaceholder ? {} : { | |||
| ['remove image']: () => removeImage(config, renderer), | |||
| ['download image']: () => downloadImage(config), | |||
| } | |||
| const menu = CustomContextMenu.Create({ | |||
| ...items, | |||
| ['set/replace image']: () => inp.click(), | |||
| ['from url']: async() => imageFromUrl(renderer, config, viewer), | |||
| 'cancel': () => {return}, | |||
| }, 2, rect.height + 8, false, true) | |||
| target.parentElement?.appendChild(menu) | |||
| if (rect.y > document.body.clientHeight * 0.7) { | |||
| menu.style.top = 'auto' | |||
| menu.style.bottom = rect.height + 8 + 'px' | |||
| } | |||
| } | |||
| params.view = 'input-image' | |||
| return renderer.typeGenerators.input(parent, config, renderer, params) | |||
| } | |||
| @@ -0,0 +1,140 @@ | |||
| #tweakpaneUiContainer { | |||
| /*padding-right: 0.25rem;*/ | |||
| } | |||
| :root { | |||
| --tp-blade-unit-size: 24px; | |||
| /* | |||
| --tp-base-shadow-color: #00000000; | |||
| --tp-base-background-color: #00000000; | |||
| --tp-container-background-color: rgba(32, 32, 32, 0.8); | |||
| --tp-container-background-color-hover: rgba(32, 32, 32, 0.9); | |||
| --tp-container-background-color-active: rgba(32, 32, 32, 1.0); | |||
| --tp-container-background-color-focus: rgba(32, 32, 32, 1.0); | |||
| --tp-base-border-radius: 0.4rem; | |||
| --tp-button-background-color: hsla(0, 0%, 70%, 1.00); | |||
| --tp-button-background-color-active: hsla(0, 0%, 85%, 1.00); | |||
| --tp-button-background-color-focus: hsla(0, 0%, 80%, 1.00); | |||
| --tp-button-background-color-hover: hsla(0, 0%, 75%, 1.00); | |||
| --tp-button-foreground-color: hsla(0, 0%, 5%, 1.00); | |||
| --tp-container-foreground-color: hsla(0, 0%, 95%, 1.00); | |||
| --tp-groove-foreground-color: hsla(0, 0%, 10%, 1.00); | |||
| --tp-input-background-color: hsla(0, 0%, 10%, 1.00); | |||
| --tp-input-background-color-active: hsla(0, 0%, 25%, 1.00); | |||
| --tp-input-background-color-focus: hsla(0, 0%, 20%, 1.00); | |||
| --tp-input-background-color-hover: hsla(0, 0%, 15%, 1.00); | |||
| --tp-input-foreground-color: hsla(0, 0%, 90%, 1.00); | |||
| --tp-label-foreground-color: hsla(0, 0%, 85%, 1.00); | |||
| --tp-monitor-background-color: hsla(0, 0%, 8%, 1.00); | |||
| --tp-monitor-foreground-color: hsla(0, 0%, 48%, 1.00); | |||
| */ | |||
| --tp-element-border-radius: 0.25rem; | |||
| --tp-base-background-color: #28223C; | |||
| /*--tp-base-background-color: hsla(235, 21%, 31%, 0.5);*/ | |||
| --tp-base-shadow-color: hsla(0, 0%, 0%, 0.2); | |||
| --tp-button-background-color: hsla(230, 10%, 80%, 1.00); | |||
| --tp-button-background-color-active: hsla(230, 10%, 95%, 1.00); | |||
| --tp-button-background-color-focus: hsla(230, 10%, 90%, 1.00); | |||
| --tp-button-background-color-hover: hsla(230, 10%, 85%, 1.00); | |||
| --tp-button-foreground-color: hsla(230, 20%, 11%, 1.00); | |||
| --tp-container-background-color: hsla(230, 25%, 16%, 0.65); | |||
| --tp-container-background-color-active: hsla(230, 25%, 31%, 0.65); | |||
| --tp-container-background-color-focus: hsla(230, 25%, 26%, 0.65); | |||
| --tp-container-background-color-hover: hsla(230, 25%, 21%, 0.65); | |||
| --tp-container-foreground-color: hsl(240, 10%, 92%); | |||
| --tp-groove-foreground-color: hsla(230, 20%, 8%, 1.00); | |||
| --tp-input-background-color: hsla(230, 20%, 8%, 1.00); | |||
| --tp-input-background-color-active: hsla(230, 28%, 23%, 1.00); | |||
| --tp-input-background-color-focus: hsla(230, 28%, 18%, 1.00); | |||
| --tp-input-background-color-hover: hsla(230, 20%, 13%, 1.00); | |||
| --tp-input-foreground-color: hsla(230, 10%, 80%, 1.00); | |||
| /*--tp-label-foreground-color: hsl(229, 100%, 97%);*/ | |||
| --tp-monitor-background-color: hsla(230, 20%, 8%, 1.00); | |||
| --tp-monitor-foreground-color: hsla(230, 12%, 48%, 1.00); | |||
| --tp-label-foreground-color: #E4E2ED; | |||
| /*--tp-font-family: 'Inter';*/ | |||
| --tp-font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; | |||
| } | |||
| .tp-fldv { | |||
| margin-top: 0.25rem; | |||
| margin-bottom: 0.25rem; | |||
| background-blend-mode: luminosity; | |||
| position: relative; | |||
| } | |||
| .tp-fldv .tp-fldv { | |||
| margin-top: 0.5rem; | |||
| margin-bottom: 0.5rem; | |||
| } | |||
| .tp-fldv .tp-brkv { | |||
| background-color: rgba(32, 32, 50, 0.85); | |||
| } | |||
| .tp-fldv .tp-fldv .tp-brkv { | |||
| background-color: rgba(32, 32, 50, 0.25) !important; | |||
| } | |||
| .tp-fldv-expanded > .tp-fldv_b { | |||
| background-color: rgba(32, 32, 50, 0.80) !important; | |||
| } | |||
| .tp-fldv_b { | |||
| height: calc(var(--bld-us) * 1.5 + 4px) !important; | |||
| font-size: 0.85rem !important; | |||
| } | |||
| .tp-fldv_b + .tp-brkv .tp-fldv_b { | |||
| height: calc(var(--bld-us) * 1.1 + 4px) !important; | |||
| font-size: 0.65rem !important; | |||
| } | |||
| .tp-fldv_b + .tp-brkv .tp-fldv-expanded > .tp-fldv_b { | |||
| } | |||
| .tp-lblv_l { | |||
| font-size: 0.7rem !important; | |||
| font-weight: 400 !important; | |||
| flex-grow: 1 !important; | |||
| flex-basis: 20% !important; | |||
| } | |||
| .tp-lblv_v { | |||
| flex-grow: 1 !important; | |||
| flex-basis: 50% !important; | |||
| } | |||
| .tp-txtv_i { | |||
| font-size: 0.7rem !important; | |||
| font-weight: 400 !important; | |||
| } | |||
| .tp-fldv_t { | |||
| font-weight: 400 !important; | |||
| padding-left: 1.5rem !important; | |||
| } | |||
| .tp-fldv_m { | |||
| right: auto !important; | |||
| left: 0.75rem; | |||
| opacity: 1.0 !important; | |||
| } | |||
| .pluginOptionsButton{ | |||
| position: absolute; | |||
| right: 0; | |||
| top: 0.75rem; | |||
| padding-left: 0.5rem; | |||
| padding-right: 0.5rem; | |||
| height: min-content; | |||
| background: transparent; | |||
| color: #eeeeee; | |||
| border: none; | |||
| } | |||
| @@ -12,7 +12,7 @@ import { | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderTarget, | |||
| } from 'three' | |||
| import {uiToggle} from 'uiconfig.js' | |||
| import {generateUiConfig, UiObjectConfig, uiToggle} from 'uiconfig.js' | |||
| import {serialize} from 'ts-browser-helpers' | |||
| import {GenericBlendTexturePass} from './GenericBlendTexturePass' | |||
| import {IRenderTarget} from '../rendering' | |||
| @@ -296,6 +296,12 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| this.camera = camera | |||
| } | |||
| uiConfig: UiObjectConfig = { | |||
| label: 'Render Pass', | |||
| type: 'folder', | |||
| children: generateUiConfig(this), | |||
| } | |||
| // legacy | |||
| @@ -3,10 +3,12 @@ import {Shader} from 'three' | |||
| import {ShaderMaterial2} from '../core' | |||
| import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | |||
| import {IPassID, IPipelinePass} from './Pass' | |||
| import {uiFolderContainer} from 'uiconfig.js' | |||
| export type TViewerScreenShaderFrag = string | [string, string] | {pars?: string, main: string} | |||
| export type TViewerScreenShader = TViewerScreenShaderFrag | Shader | ShaderMaterial2 | |||
| @uiFolderContainer('Screen Pass') | |||
| export class ScreenPass extends ExtendedShaderPass implements IPipelinePass<'screen'> { | |||
| readonly passId = 'screen' | |||
| after: IPassID[] = ['render'] | |||
| @@ -25,16 +25,20 @@ import { | |||
| IWebGLRenderer, | |||
| upgradeWebGLRenderer, | |||
| } from '../core' | |||
| import {onChange, serializable, serialize} from 'ts-browser-helpers' | |||
| import {onChange2, serializable, serialize} from 'ts-browser-helpers' | |||
| import {uiConfig, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js' | |||
| @serializable('RenderManager') | |||
| @uiFolderContainer('Render Manager') | |||
| export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRenderManagerEventTypes> implements IShaderPropertiesUpdater, IRenderManager { | |||
| private readonly _isWebGL2: boolean | |||
| private readonly _composer: EffectComposer2 | |||
| private readonly _context: WebGLRenderingContext | |||
| @uiMonitor('Render Size') | |||
| private readonly _renderSize = new Vector2(512, 512) // this is updated automatically. | |||
| protected readonly _renderer: IWebGLRenderer<this> | |||
| private _renderScale = 1. | |||
| @uiConfig(undefined, {label: 'Passes'}) | |||
| private _passes: IPipelinePass[] = [] | |||
| private _pipeline: IPassID[] = [] | |||
| private _passesNeedsUpdate = true | |||
| @@ -50,7 +54,7 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| * Use total frame count, if this is set to true, then frameCount won't be reset when the viewer is set to dirty. | |||
| * Which will generate different random numbers for each frame during postprocessing steps. With TAA set properly, this will give a smoother result. | |||
| */ | |||
| @serialize() stableNoise = false | |||
| @uiToggle() @serialize() stableNoise = false | |||
| public frameWaitTime = 0 // time to wait before next frame // used by canvas recorder //todo/ | |||
| @@ -59,7 +63,7 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| /** | |||
| * Set autoBuildPipeline = false to be able to set the pipeline manually. | |||
| */ | |||
| @onChange(RenderManager.prototype.rebuildPipeline) | |||
| @onChange2(RenderManager.prototype.rebuildPipeline) | |||
| public autoBuildPipeline = true | |||
| rebuildPipeline(setDirty = true): void { | |||
| @@ -331,6 +335,7 @@ export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRen | |||
| get renderSize(): Vector2 { | |||
| return this._renderSize | |||
| } | |||
| @uiSlider('Render Scale', [0.1, 8], 0.05) | |||
| get renderScale(): number { | |||
| return this._renderScale | |||
| } | |||
| @@ -4,7 +4,7 @@ import {mergeVertices} from 'three/examples/jsm/utils/BufferGeometryUtils.js' | |||
| /** | |||
| * Convert geometry to BufferGeometry with indexed attributes. | |||
| */ | |||
| export function toIndexedGeometry(geometry: BufferGeometry, tolerance = -1) { | |||
| export function toIndexedGeometry(geometry: BufferGeometry<any, any, any>, tolerance = -1) { | |||
| return mergeVertices(geometry, tolerance) | |||
| } | |||
| @@ -24,42 +24,32 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| type: 'folder', | |||
| label: <string>map + ' Sampler', | |||
| hidden: ()=>!mat[map], | |||
| onChange: ()=>mat.setDirty(), | |||
| children: [ | |||
| ()=>({ | |||
| type: 'vec2', | |||
| label: 'Repeat', | |||
| // hidden: ()=>!im && mat.map, | |||
| bounds: [-100, 100], | |||
| stepSize: 0.001, | |||
| property: [mat[map], 'repeat'], | |||
| onChange: mat?.setDirty, | |||
| }), | |||
| ()=>({ | |||
| type: 'vec2', | |||
| label: 'Offset', | |||
| // hidden: ()=>!im && mat.map, | |||
| bounds: [-2, 2], | |||
| stepSize: 0.001, | |||
| property: [mat[map], 'offset'], | |||
| onChange: mat?.setDirty, | |||
| }), | |||
| ()=>({ | |||
| type: 'vec2', | |||
| label: 'Center', | |||
| // hidden: ()=>!im && mat.map, | |||
| bounds: [-2, 2], | |||
| stepSize: 0.001, | |||
| property: [mat[map], 'center'], | |||
| onChange: mat?.setDirty, | |||
| }), | |||
| ()=>({ | |||
| type: 'input', | |||
| label: 'Rotation', | |||
| stepSize: 0.001, | |||
| // hidden: ()=>!im && mat.map, | |||
| bounds: [-Math.PI, Math.PI], | |||
| property: [mat[map], 'rotation'], | |||
| onChange: mat?.setDirty, | |||
| }), | |||
| ()=>({ | |||
| type: 'dropdown', | |||
| @@ -81,7 +71,17 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| // mat[map] = tex.clone() // it doesn't work with just setting needsUpdate = true | |||
| // ;(mat[map] as any).uuid = tex.uuid | |||
| // tex!.dispose() | |||
| }, mat?.setDirty], | |||
| }], | |||
| }), | |||
| ()=>({ | |||
| type: 'dropdown', | |||
| label: 'UV Channel', | |||
| property: [mat[map], 'channel'], | |||
| children: [0, 1, 2, 3].map(value => ({label: value.toString(), value})), | |||
| onChange: ()=>{ | |||
| const tex = mat[map] as any | |||
| if (tex) tex.needsUpdate = true | |||
| }, | |||
| }), | |||
| ()=>({ | |||
| type: 'checkbox', | |||
| @@ -134,7 +134,7 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| label: value[0], | |||
| value: value[1], | |||
| })), | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}, mat?.setDirty], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}], | |||
| }), | |||
| ()=>({ | |||
| type: 'dropdown', | |||
| @@ -148,7 +148,7 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| label: value[0], | |||
| value: value[1], | |||
| })), | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}, mat?.setDirty], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}], | |||
| }), | |||
| ()=>({ | |||
| type: 'input', | |||
| @@ -156,7 +156,7 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| bounds: [1, 6], | |||
| stepSize: 1, | |||
| property: [mat[map], 'anisotropy'], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true; mat.needsUpdate = true}, mat?.setDirty], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true; mat.needsUpdate = true}], | |||
| }), | |||
| ()=>({ | |||
| type: 'dropdown', | |||
| @@ -173,7 +173,7 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| label: value[0], | |||
| value: value[1], | |||
| })), | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}, mat?.setDirty], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}], | |||
| }), | |||
| ()=>({ | |||
| type: 'dropdown', | |||
| @@ -186,7 +186,7 @@ export function makeSamplerUi<T extends IMaterial>(mat: T, map: keyof T) { | |||
| label: value[0], | |||
| value: value[1], | |||
| })), | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}, mat?.setDirty], | |||
| onChange: [()=>{if (mat[map])(mat[map] as any)!.needsUpdate = true}], | |||
| }), | |||
| ], | |||
| @@ -0,0 +1,55 @@ | |||
| .CustomContextMenu{ | |||
| position: absolute; | |||
| background: #333e; | |||
| min-width: 8rem; | |||
| color: white; | |||
| font-size: 0.8rem; | |||
| overflow: hidden; | |||
| border-radius: 8px; | |||
| box-shadow: 2px 2px 10px #6666; | |||
| z-index: 100000; | |||
| } | |||
| .CustomContextMenu div{ | |||
| cursor: pointer; | |||
| padding: 6px 10px; | |||
| transition: background-color 0.25s ease-in-out; | |||
| } | |||
| .CustomContextMenu div:hover{ | |||
| background: #1a1a1c; | |||
| } | |||
| #customContextMenu { | |||
| background: #333e; | |||
| backdrop-filter: blur(8px); | |||
| border: 0.5px solid rgba(20, 20, 20, 0.3); | |||
| width: auto; | |||
| height: auto; | |||
| position: absolute; | |||
| display: flex; | |||
| flex-direction: column; | |||
| z-index: 9999; | |||
| padding: 0.4rem 0.25rem; | |||
| border-radius: 0.375rem; | |||
| min-width: 8rem; | |||
| pointer-events: auto; | |||
| box-shadow: 0 2px 10px rgba(12, 12, 12, 0.2); | |||
| } | |||
| .customContextMenuItems { | |||
| color: white; | |||
| font-size: 0.8rem; | |||
| font-family: monospace; | |||
| background-color: transparent; | |||
| cursor: pointer; | |||
| padding: 4px 8px; | |||
| border-radius: 0.25rem; | |||
| line-height: 1rem; | |||
| font-weight: 500; | |||
| transition: background-color 0.25s ease-in-out; | |||
| } | |||
| .customContextMenuItems:hover { | |||
| color: white; | |||
| background-color: #017AFF; | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import styles from './CustomContextMenu.css' | |||
| export class CustomContextMenu { | |||
| public static Element: HTMLDivElement | undefined = undefined | |||
| private static _inited = false | |||
| private static _initialize(): void { | |||
| this._inited = true | |||
| document.addEventListener('pointerdown', (e) => { | |||
| if (this.Element && !this.Element.contains(e.target as any)) { | |||
| this.Remove() | |||
| } | |||
| }) | |||
| } | |||
| public static Create(items: Record<string, () => void>, x: number, y: number, show = true, removeOnSelect = true): HTMLDivElement { | |||
| if (!this._inited) this._initialize() | |||
| if (this.Element) this.Remove() | |||
| const container = document.createElement('div') | |||
| container.id = 'customContextMenu' | |||
| container.style.top = y + 'px' | |||
| container.style.left = x + 'px' | |||
| container.innerHTML = '<style>' + styles + '</style>' | |||
| for (const [key, func] of Object.entries(items)) { | |||
| const d = document.createElement('div') | |||
| d.classList.add('customContextMenuItems') | |||
| d.innerHTML = key | |||
| container.appendChild(d) | |||
| d.onclick = async() => { | |||
| await func() | |||
| if (removeOnSelect) this.Remove() | |||
| } | |||
| } | |||
| this.Element = container | |||
| if (show) document.body.appendChild(container) | |||
| return container | |||
| } | |||
| public static Remove(): void { | |||
| this.Element?.remove() | |||
| this.Element = undefined | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| export * from './browser-helpers' | |||
| export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | |||
| export {GLStatsJS} from './GLStatsJS' | |||
| export {CustomContextMenu} from './CustomContextMenu' | |||
| export {Dropzone, type DropFile, type ListenerCallback, type DropEventType} from './Dropzone' | |||
| export {ThreeSerialization, type SerializationMetaType, type SerializationResourcesType, MetaImporter} from './serialization' | |||
| export {shaderReplaceString} from './shader-helpers' | |||
| @@ -1,4 +1,3 @@ | |||
| import {BaseEvent, EventDispatcher} from 'three' | |||
| import {IUiConfigContainer} from 'uiconfig.js' | |||
| import {Class, IDisposable, IJSONSerializable} from 'ts-browser-helpers' | |||
| import {SerializationMetaType} from '../utils' | |||
| @@ -9,7 +8,7 @@ import {ISerializedConfig, ThreeViewer} from './ThreeViewer' | |||
| * @category Viewer | |||
| */ | |||
| export interface IViewerPlugin<TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> | |||
| extends EventDispatcher<BaseEvent, string>, IUiConfigContainer, Partial<IJSONSerializable<ISerializedConfig, SerializationMetaType>>, IDisposable { | |||
| extends IUiConfigContainer, Partial<IJSONSerializable<ISerializedConfig, SerializationMetaType>>, IDisposable { | |||
| // all classes must have this static property with a unique identifier value for this plugin | |||
| constructor: { | |||
| PluginType: string | |||
| @@ -35,6 +35,7 @@ import { | |||
| import {GLStatsJS, IDialogWrapper, windowDialogWrapper} from '../utils' | |||
| import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | |||
| import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | |||
| import {uiConfig, uiFolderContainer, UiObjectConfig} from 'uiconfig.js' | |||
| export type IViewerEvent = BaseEvent & { | |||
| type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin' | |||
| @@ -125,9 +126,11 @@ const VIEWER_VERSION = '0.0.1' | |||
| * The ThreeViewer is the main class in the framework. | |||
| * @category Viewer | |||
| */ | |||
| @uiFolderContainer('Viewer') | |||
| export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes> { | |||
| public static readonly VERSION = VIEWER_VERSION | |||
| public static readonly ConfigTypeSlug = 'vjson' | |||
| uiConfig!: UiObjectConfig | |||
| static Console: IConsoleWrapper = console | |||
| static Dialog: IDialogWrapper = windowDialogWrapper | |||
| @@ -150,14 +153,14 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| // this can be used by other plugins to add ui elements alongside the canvas | |||
| private readonly _container: HTMLElement // todo: add a way to move the canvas to a new container... and dispatch event... | |||
| @serialize('renderManager') | |||
| @uiConfig() @serialize('renderManager') | |||
| readonly renderManager: ViewerRenderManager | |||
| /** | |||
| * The Scene attached to the viewer, this cannot be changed. | |||
| * @type {RootScene} | |||
| */ | |||
| @serialize('scene') | |||
| @uiConfig() @serialize('scene') | |||
| private readonly _scene: RootScene | |||
| public readonly plugins: Record<string, IViewerPlugin> = {} | |||
| @@ -221,6 +224,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| // camera | |||
| const camera = new PerspectiveCamera2('orbit', this._canvas) | |||
| camera.name = 'Default Camera' | |||
| camera.position.set(0, 0, 5) | |||
| camera.userData.autoLookAtTarget = true | |||
| this.addEventListener('postFrame', () => { // todo: move inside RootScene. | |||
| @@ -2,6 +2,7 @@ import {IRenderTarget, RenderManager} from '../rendering' | |||
| import {HalfFloatType, NoColorSpace, RGBM16ColorSpace, UnsignedByteType} from 'three' | |||
| import {IRenderManagerOptions} from '../core' | |||
| import {ExtendedRenderPass, ScreenPass, TViewerScreenShader} from '../postprocessing' | |||
| import {uiFolderContainer} from 'uiconfig.js' | |||
| export interface ViewerRenderManagerOptions extends IRenderManagerOptions { | |||
| rgbm?: boolean, | |||
| @@ -11,6 +12,7 @@ export interface ViewerRenderManagerOptions extends IRenderManagerOptions { | |||
| screenShader?: TViewerScreenShader | |||
| } | |||
| @uiFolderContainer('Render Manager') | |||
| export class ViewerRenderManager extends RenderManager { | |||
| readonly rgbm: boolean | |||
| readonly msaa: boolean | |||