diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5dc4aaf..f32237e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,7 @@ module.exports = { env: { browser: true, node: true, + es6: true, }, extends: [ 'eslint:recommended', diff --git a/.github/workflows/deploy-github-pages.yml b/.github/workflows/deploy-github-pages.yml index 9aa4563..7c04de4 100644 --- a/.github/workflows/deploy-github-pages.yml +++ b/.github/workflows/deploy-github-pages.yml @@ -6,7 +6,7 @@ on: - master jobs: - build: + deploy: runs-on: ubuntu-latest steps: - name: Checkout repo @@ -16,9 +16,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 - - - name: Cache node_modules - uses: c-hive/gha-yarn-cache@v2 + cache: 'yarn' - name: Install Dependencies run: yarn diff --git a/README.md b/README.md index 61bb24f..5b2bd99 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It is inspired from [mattdesl](https://twitter.com/mattdesl)'s [threejs-app](htt ## Features -- All the **three.js boilerplate code is tucked away** in a file, the exported `WebGLApp` is easily configurable from the outside, for example you can enable [postprocessing](https://github.com/vanruesc/postprocessing), orbit controls, [FPS stats](https://github.com/marcofugaro/stats.js/), [Detect GPU](https://github.com/TimvanScherpenzeel/detect-gpu), a [controls-gui](https://github.com/rreusser/controls-gui) and use the save screenshot or record mp4 functionality. It also has built-in support for [cannon-es](https://github.com/pmndrs/cannon-es). [[Read more](#webglapp)] +- All the **three.js boilerplate code is tucked away** in a file, the exported `WebGLApp` is easily configurable from the outside, for example you can enable [postprocessing](https://github.com/vanruesc/postprocessing), orbit controls, a [gui](https://github.com/georgealways/lil-gui), [FPS stats](https://github.com/marcofugaro/stats.js/), [Detect GPU](https://github.com/TimvanScherpenzeel/detect-gpu), and use the save screenshot or record mp4 functionality. It also has built-in support for [cannon-es](https://github.com/pmndrs/cannon-es). [[Read more](#webglapp)] - A **scalable three.js component structure** where each component is a class which extends `THREE.Group`, so you can add any object to it. The class also has update, resize, and pointer hooks. [[Read more](#component-structure)] - An **asset manager** which handles the preloading of `.gltf` models, images, audios, videos and can be easily extended to support other files. It also automatically uploads a texture to the GPU, loads cube env maps or parses equirectangular projection images. [[Read more](#asset-manager)] - global `window.DEBUG` flag which is true when the url contains `?debug` as a query parameter. So you can enable **debug mode** both locally and in production. [[Read more](#debug-mode)] @@ -48,29 +48,27 @@ https://github.com/marcofugaro/threejs-modern-app/blob/bd303c968c0b0ef56a40046e0 You can pass the class the options you would pass to the [THREE.WebGLRenderer](https://threejs.org/docs/#api/en/renderers/WebGLRenderer), and also some more options: -| Option | Default | Description | -| --------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `background` | `'#111'` | The background of the scene. | -| `backgroundAlpha` | 1 | The transparency of the background. | -| `maxPixelRatio` | 2 | You can clamp the pixelRatio. Often the pixelRatio is clamped for performance reasons. | -| `width` | `window.innerWidth` | The canvas width. | -| `height` | `window.innerHeight` | The canvas height. | -| `orthographic` | false | Use an [OrthographicCamera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera) instead of the default [PerspectiveCamera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera). | -| `cameraPosition` | `new THREE.Vector3(0, 0, 4)` | Set the initial camera position. The camera will always look at [0, 0, 0]. | -| `fov` | 45 | The field of view of the PerspectiveCamera. It is ignored if the option `orthographic` is true. | -| `frustumSize` | 3 | Defines the size of the OrthographicCamera frustum. It is ignored if the option `orthographic` is false. | -| `near` | 0.01 | The camera near plane. | -| `far` | 100 | The camera far plane. | -| `postprocessing` | false | Enable the [postprocessing library](https://github.com/vanruesc/postprocessing). The composer gets exposed as `webgl.composer`. | -| `xr` | false | Enable three.js WebXR mode. The update function now will have a `xrframe` object passed as a third parameter. | -| `gamma` | false | Turn on gamma correction. Remember to turn on gamma corrections also for textures and colors as stated in [this guide](https://www.donmccurdy.com/2020/06/17/color-management-in-threejs/). | -| `showFps` | false | Show the [stats.js](https://github.com/mrdoob/stats.js/) fps counter. | -| `orbitControls` | undefined | Set this to `true` to enable OrbitControls. You can also pass an object of [OrbitControls properties](https://threejs.org/docs/index.html#examples/en/controls/OrbitControls) to set. | -| `controls` | undefined | Accepts an object with the [controls-gui](https://github.com/rreusser/controls-gui) configuration. Exposed ad `webgl.controls`. | -| `hideControls` | false | Set this to `true` to hide the controls-gui panel. | -| `closeControls` | false | Set this to `true` to initialize the controls-gui panel closed. | -| `world` | undefined | Accepts an instance of the [cannon-es](https://github.com/pmndrs/cannon-es) world (`new CANNON.World()`). Exposed as `webgl.world`. | -| `showWorldWireframes` | false | Set this to `true` to show the wireframes of every body in the world. Uses [cannon-es-debugger](https://github.com/pmndrs/cannon-es-debugger). | +| Option | Default | Description | +| --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `background` | `'#111'` | The background of the scene. | +| `backgroundAlpha` | 1 | The transparency of the background. | +| `maxPixelRatio` | 2 | You can clamp the pixelRatio. Often the pixelRatio is clamped for performance reasons. | +| `width` | `window.innerWidth` | The canvas width. | +| `height` | `window.innerHeight` | The canvas height. | +| `orthographic` | false | Use an [OrthographicCamera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera) instead of the default [PerspectiveCamera](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera). | +| `cameraPosition` | `new Vector3(0, 0, 4)` | Set the initial camera position. The camera will always look at [0, 0, 0]. | +| `fov` | 45 | The field of view of the PerspectiveCamera. It is ignored if the option `orthographic` is true. | +| `frustumSize` | 3 | Defines the size of the OrthographicCamera frustum. It is ignored if the option `orthographic` is false. | +| `near` | 0.01 | The camera near plane. | +| `far` | 100 | The camera far plane. | +| `postprocessing` | false | Enable the [postprocessing library](https://github.com/vanruesc/postprocessing). The composer gets exposed as `webgl.composer`. | +| `xr` | false | Enable three.js WebXR mode. The update function now will have a `xrframe` object passed as a third parameter. | +| `showFps` | false | Show the [stats.js](https://github.com/mrdoob/stats.js/) fps counter. | +| `orbitControls` | undefined | Set this to `true` to enable OrbitControls. You can also pass an object of [OrbitControls properties](https://threejs.org/docs/index.html#examples/en/controls/OrbitControls) to set. | +| `gui` | undefined | Wether or not to initialize the gui using [lil-gui](https://github.com/georgealways/lil-gui). Exposed ad `webgl.gui`. | +| `guiClosed` | false | Set this to `true` to initialize the gui panel closed. | +| `world` | undefined | Accepts an instance of the [cannon-es](https://github.com/pmndrs/cannon-es) world (`new CANNON.World()`). Exposed as `webgl.world`. | +| `showWorldWireframes` | false | Set this to `true` to show the wireframes of every body in the world. Uses [cannon-es-debugger](https://github.com/pmndrs/cannon-es-debugger). | The `webgl` instance will contain all the three.js elements such as `webgl.scene`, `webgl.renderer`, `webgl.camera` or `webgl.canvas`. It also exposes some useful properties and methods: @@ -157,6 +155,26 @@ Subscribe a function to the `pointerup` event on the canvas without having to cr | `event` | The native event. | | `position` | An object containing the `x` and the `y` position from the top left of the canvas. The object contains also the `dragX` and `dragY` distances from the drag start point. | +### webgl.gui.wireUniforms('My Material', material.uniforms) + +Automatically adds the exposed uniforms of a material to the gui, as a folder. +Internally it adds the uniforms using [`gui.addSmart()`](#webgl-gui-add-smart-object-property) + +| Argument | Description | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `folderName` | The name of the folder the gui will make. | +| `uniforms` | The uniforms of the material. They are exposed by default in a [ShaderMaterial](https://threejs.org/docs/#api/en/materials/ShaderMaterial) or [RawShaderMaterial](https://threejs.org/docs/#api/en/materials/RawShaderMaterial). Or if you use the `addUniforms` function. | + +### webgl.gui.addSmart(object, 'property') + +Like [`gui.add()`](https://lil-gui.georgealways.com/#GUI#add) but tries to be more smart about it. +For example if the number is between 0 and 1, it sets the slider min to 0 and max to 1. Otherwise the slider uses an exponential mapping for easy iteration. + +| Argument | Description | +| ------------ | -------------------------------------------------------------------------- | +| `object` | The object the gui will modify. For example it can be any material. | +| `'property'` | The name of the property in the object. The gui will modify this property. | + ## Component structure Rather than writing all of your three.js app in one file instruction after instruction, you can split your app into thhree.js components". This makes it easier to manage the app as it grows. Here is a basic component: @@ -176,7 +194,7 @@ webgl.scene.add(webgl.scene.birds) And in the component, you can use the options like this. ```js -export default class Birds extends THREE.Group { +export default class Birds extends Group { constructor(webgl, options = {}) { super(options) // these can be used also in other methods @@ -243,11 +261,11 @@ If you don't need any of the previous methods, you can use functional components ```js export function addLights(webgl) { - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6) + const directionalLight = new DirectionalLight(0xffffff, 0.6) directionalLight.position.copy(position) webgl.scene.add(directionalLight) - const ambientLight = new THREE.AmbientLight(0xffffff, 0.5) + const ambientLight = new AmbientLight(0xffffff, 0.5) webgl.scene.add(ambientLight) } @@ -334,9 +352,9 @@ const bricksKeys = assets.queueStandardMaterial( aoMap: `assets/bricks/ambientocclusion.jpg`, }, { - repeat: new THREE.Vector2().setScalar(0.5), - wrapS: THREE.RepeatWrapping, - wrapT: THREE.RepeatWrapping, + repeat: new Vector2().setScalar(0.5), + wrapS: RepeatWrapping, + wrapT: RepeatWrapping, } ) ``` @@ -395,7 +413,7 @@ It returns an object of the loaded textures that can be fed directly into [MeshS ```js const textures = assets.getStandardMaterial(keys) -const material = new THREE.MeshStandardMaterial({ ...textures }) +const material = new MeshStandardMaterial({ ...textures }) ``` | Option | Default | Description | @@ -464,7 +482,7 @@ import passVertexShader from '../shaders/pass.vert' // ... -const material = new THREE.ShaderMaterial({ +const material = new ShaderMaterial({ // it's a string vertexShader: passVert, @@ -489,7 +507,7 @@ if (webgl.gpu.tier > 1) { webgl.renderer.shadowMap.enabled = true // soft shadows - webgl.renderer.shadowMap.type = THREE.PCFSoftShadowMap + webgl.renderer.shadowMap.type = PCFSoftShadowMap } ``` diff --git a/esbuild.js b/esbuild.js index a927f10..12483b7 100644 --- a/esbuild.js +++ b/esbuild.js @@ -38,7 +38,7 @@ const result = await esbuild .build({ entryPoints: ['src/index.js'], bundle: true, - format: 'iife', + format: 'esm', logLevel: 'silent', // sssh... legalComments: 'none', // don't include licenses txt file sourcemap: true, diff --git a/package.json b/package.json index ff7a690..e59bcd3 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,9 @@ "dependencies": { "cannon-es": "^0.20.0", "cannon-es-debugger": "^1.0.0", - "controls-gui": "^2.0.0", - "controls-state": "^2.0.0", "detect-gpu": "^5.0.4", "image-promise": "^7.0.1", + "lil-gui": "^0.17.0", "lodash-es": "^4.17.21", "mp4-wasm": "marcofugaro/mp4-wasm#build-embedded", "p-map": "^5.5.0", diff --git a/public/index.html b/public/index.html index 5b49acc..91aa2da 100644 --- a/public/index.html +++ b/public/index.html @@ -11,6 +11,6 @@ - + diff --git a/src/index.js b/src/index.js index f9756e8..275a9a6 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ import WebGLApp from './utils/WebGLApp' import assets from './utils/AssetManager' import Suzanne from './scene/Suzanne' import { addNaturalLight } from './scene/lights' -import { addScreenshotButton, addRecordButton } from './scene/screenshot-record-buttons' +import { addScreenshotButton, addRecordButton } from './screenshot-record-buttons' // true if the url has the `?debug` parameter, otherwise false window.DEBUG = window.location.search.includes('debug') @@ -23,20 +23,8 @@ const webgl = new WebGLApp({ showFps: window.DEBUG, // enable OrbitControls orbitControls: window.DEBUG, - // Add the controls pane inputs - controls: { - roughness: 0.5, - movement: { - speed: { - value: 1.5, - max: 100, - scale: 'exp', - }, - frequency: { value: 0.5, max: 5 }, - amplitude: { value: 0.7, max: 2 }, - }, - }, - hideControls: !window.DEBUG, + // show the GUI + gui: window.DEBUG, // enable cannon-es // world: new CANNON.World(), }) @@ -50,29 +38,29 @@ if (window.DEBUG) { webgl.canvas.style.visibility = 'hidden' // load any queued assets -assets.load({ renderer: webgl.renderer }).then(() => { - // add any "WebGL components" here... - // append them to the scene so you can - // use them from other components easily - webgl.scene.suzanne = new Suzanne(webgl) - webgl.scene.add(webgl.scene.suzanne) +await assets.load({ renderer: webgl.renderer }) - // lights and other scene related stuff - addNaturalLight(webgl) +// add any "WebGL components" here... +// append them to the scene so you can +// use them from other components easily +webgl.scene.suzanne = new Suzanne(webgl) +webgl.scene.add(webgl.scene.suzanne) - // postprocessing - // add an existing effect from the postprocessing library - webgl.composer.addPass(new EffectPass(webgl.camera, new VignetteEffect())) +// lights and other scene related stuff +addNaturalLight(webgl) - // add the save screenshot and save gif buttons - if (window.DEBUG) { - addScreenshotButton(webgl) - addRecordButton(webgl) - } +// postprocessing +// add an existing effect from the postprocessing library +webgl.composer.addPass(new EffectPass(webgl.camera, new VignetteEffect())) - // show canvas - webgl.canvas.style.visibility = '' +// add the save screenshot and save gif buttons +if (window.DEBUG) { + addScreenshotButton(webgl) + addRecordButton(webgl) +} - // start animation loop - webgl.start() -}) +// show canvas +webgl.canvas.style.visibility = '' + +// start animation loop +webgl.start() diff --git a/src/scene/Box.js b/src/scene/Box.js index fcac202..3b4e557 100644 --- a/src/scene/Box.js +++ b/src/scene/Box.js @@ -1,8 +1,8 @@ -import * as THREE from 'three' +import { BoxGeometry, Group, Mesh, MeshBasicMaterial } from 'three' // basic three.js component example -export default class Box extends THREE.Group { +export default class Box extends Group { constructor(webgl, options = {}) { super(options) // these can be used also in other methods @@ -12,9 +12,9 @@ export default class Box extends THREE.Group { // destructure and default values like you do in React const { color = 0x00ff00 } = this.options - const geometry = new THREE.BoxGeometry(1, 1, 1) - const material = new THREE.MeshBasicMaterial({ color, wireframe: true }) - this.box = new THREE.Mesh(geometry, material) + const geometry = new BoxGeometry(1, 1, 1) + const material = new MeshBasicMaterial({ color, wireframe: true }) + this.box = new Mesh(geometry, material) // add it to the group, // later the group will be added to the scene diff --git a/src/scene/CannonSphere.js b/src/scene/CannonSphere.js index 820b85b..1fce4fc 100644 --- a/src/scene/CannonSphere.js +++ b/src/scene/CannonSphere.js @@ -1,5 +1,5 @@ -import * as THREE from 'three' -import * as CANNON from 'cannon-es' +import { Group, Mesh, MeshStandardMaterial, SphereGeometry } from 'three' +import { Body, Sphere } from 'cannon-es' // remember to add the body to the CANNON world and // the mesh to the three.js scene or to some component @@ -8,8 +8,8 @@ import * as CANNON from 'cannon-es' // webgl.world.addBody(sphere) // webgl.scene.add(sphere.mesh) -export default class CannonSphere extends CANNON.Body { - mesh = new THREE.Group() +export default class CannonSphere extends Body { + mesh = new Group() constructor(webgl, options = {}) { super(options) @@ -18,13 +18,13 @@ export default class CannonSphere extends CANNON.Body { const { radius = 1 } = this.options - this.addShape(new CANNON.Sphere(radius)) + this.addShape(new Sphere(radius)) // add corresponding geometry and material this.mesh.add( - new THREE.Mesh( - new THREE.SphereGeometry(radius, 32, 32), - new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff }) + new Mesh( + new SphereGeometry(radius, 32, 32), + new MeshStandardMaterial({ color: Math.random() * 0xffffff }) ) ) diff --git a/src/scene/Suzanne.js b/src/scene/Suzanne.js index 29455dd..6ccf023 100644 --- a/src/scene/Suzanne.js +++ b/src/scene/Suzanne.js @@ -1,7 +1,6 @@ -import * as THREE from 'three' +import { Group, MeshStandardMaterial, Raycaster, Vector2 } from 'three' import glsl from 'glslify' import assets from '../utils/AssetManager' -import { wireValue, wireUniform } from '../utils/Controls' import { addUniforms, customizeVertexShader } from '../utils/customizeShader' // elaborated three.js component example @@ -45,7 +44,7 @@ const hdrKey = assets.queue({ type: 'env-map', }) -export default class Suzanne extends THREE.Group { +export default class Suzanne extends Group { constructor(webgl, options = {}) { super(options) this.webgl = webgl @@ -55,26 +54,26 @@ export default class Suzanne extends THREE.Group { const suzanne = suzanneGltf.scene.clone() const envMap = assets.get(hdrKey) - const material = new THREE.MeshStandardMaterial({ + const material = new MeshStandardMaterial({ map: assets.get(albedoKey), metalnessMap: assets.get(metalnessKey), roughnessMap: assets.get(roughnessKey), normalMap: assets.get(normalKey), - normalScale: new THREE.Vector2(2, 2), + normalScale: new Vector2(2, 2), envMap, - roughness: webgl.controls.roughness, + roughness: 0.5, metalness: 1, }) + webgl.gui?.addSmart(material, 'roughness') this.material = material - wireValue(material, () => webgl.controls.roughness) - // add new unifroms and expose current uniforms addUniforms(material, { time: { value: 0 }, - frequency: wireUniform(material, () => webgl.controls.movement.frequency), - amplitude: wireUniform(material, () => webgl.controls.movement.amplitude), + frequency: { value: 0.5 }, + amplitude: { value: 0.7 }, }) + webgl.gui?.wireUniforms('movement', material.uniforms, { blacklist: ['time'] }) customizeVertexShader(material, { head: glsl` @@ -83,7 +82,7 @@ export default class Suzanne extends THREE.Group { uniform float amplitude; // you could import glsl packages like this - // #pragma glslify: noise = require(glsl-noise/simplex/3d) + // #pragma glslify: noise3d = require(glsl-noise/simplex/3d) `, main: glsl` float theta = sin(position.z * frequency + time) * amplitude; @@ -111,6 +110,10 @@ export default class Suzanne extends THREE.Group { // make it a little bigger suzanne.scale.multiplyScalar(1.2) + // incremental speed, we can change it through the GUI + this.speed = 1.5 + webgl.gui?.folders.find((f) => f._title === 'movement').addSmart(this, 'speed') + this.add(suzanne) // set the background as the hdr @@ -120,11 +123,11 @@ export default class Suzanne extends THREE.Group { onPointerDown(event, { x, y }) { // for example, check of we clicked on an // object with raycasting - const coords = new THREE.Vector2().set( + const coords = new Vector2().set( (x / this.webgl.width) * 2 - 1, (-y / this.webgl.height) * 2 + 1 ) - const raycaster = new THREE.Raycaster() + const raycaster = new Raycaster() raycaster.setFromCamera(coords, this.webgl.camera) const hits = raycaster.intersectObject(this, true) console.log(hits.length > 0 ? `Hit ${hits[0].object.name}!` : 'No hit') @@ -133,6 +136,6 @@ export default class Suzanne extends THREE.Group { } update(dt, time) { - this.material.uniforms.time.value += dt * this.webgl.controls.movement.speed + this.material.uniforms.time.value += dt * this.speed } } diff --git a/src/scene/lights.js b/src/scene/lights.js index 62f7e7b..40b2caa 100644 --- a/src/scene/lights.js +++ b/src/scene/lights.js @@ -1,15 +1,15 @@ -import * as THREE from 'three' +import { DirectionalLight, HemisphereLight } from 'three' // natural hemisphere light from // https://threejs.org/examples/#webgl_lights_hemisphere export function addNaturalLight(webgl) { - const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6) + const hemiLight = new HemisphereLight(0xffffff, 0xffffff, 0.6) hemiLight.color.setHSL(0.6, 1, 0.6) hemiLight.groundColor.setHSL(0.095, 1, 0.75) hemiLight.position.set(0, 50, 0) webgl.scene.add(hemiLight) - const dirLight = new THREE.DirectionalLight(0xffffff, 1) + const dirLight = new DirectionalLight(0xffffff, 1) dirLight.color.setHSL(0.1, 1, 0.95) dirLight.position.set(3, 5, 1) dirLight.position.multiplyScalar(50) diff --git a/src/scene/screenshot-record-buttons.js b/src/screenshot-record-buttons.js similarity index 100% rename from src/scene/screenshot-record-buttons.js rename to src/screenshot-record-buttons.js diff --git a/src/utils/AssetManager.js b/src/utils/AssetManager.js index 28e6931..b58be13 100644 --- a/src/utils/AssetManager.js +++ b/src/utils/AssetManager.js @@ -5,6 +5,7 @@ import omit from 'lodash/omit' import loadTexture from './loadTexture' import loadEnvMap from './loadEnvMap' import loadGLTF from './loadGLTF' +import { mapValues } from 'lodash-es' class AssetManager { #queue = [] @@ -42,6 +43,46 @@ class AssetManager { return url } + // Add a MeshStandardMaterial to be queued, + // input: { map, metalnessMap, roughnessMap, normalMap, ... } + queueStandardMaterial(maps, options = {}) { + const keys = {} + + // These textures are non-color and they don't + // need gamma correction + const linearTextures = [ + 'pbrMap', + 'alphaMap', + 'aoMap', + 'bumpMap', + 'displacementMap', + 'lightMap', + 'metalnessMap', + 'normalMap', + 'roughnessMap', + 'clearcoatMap', + 'clearcoatNormalMap', + 'clearcoatRoughnessMap', + 'sheenRoughnessMap', + 'sheenColorMap', + 'specularIntensityMap', + 'specularColorMap', + 'thicknessMap', + 'transmissionMap', + ] + + Object.keys(maps).forEach((map) => { + keys[map] = this.queue({ + url: maps[map], + type: 'texture', + ...options, + ...(linearTextures.includes(map) && { linear: true }), + }) + }) + + return keys + } + _getQueued(url) { return this.#queue.find((item) => item.url === url) } @@ -74,6 +115,11 @@ class AssetManager { return this.#loaded[key] } + // Fetch a loaded MeshStandardMaterial object + getStandardMaterial = (keys) => { + return mapValues(keys, (key) => this.get(key)) + } + // Loads a single asset on demand. async loadSingle({ renderer, ...item }) { // renderer is used to load textures and env maps, diff --git a/src/utils/Controls.js b/src/utils/Controls.js deleted file mode 100644 index fe59e7f..0000000 --- a/src/utils/Controls.js +++ /dev/null @@ -1,295 +0,0 @@ -import * as THREE from 'three' -import State from 'controls-state' -import wrapGUI from 'controls-gui' - -let controls - -// https://github.com/mrdoob/three.js/blob/670b1e9e85356d98efa4c702e93c85dd52f01e1e/src/math/Color.js#L3 -const _colorKeywords = { - aliceblue: 0xf0f8ff, - antiquewhite: 0xfaebd7, - aqua: 0x00ffff, - aquamarine: 0x7fffd4, - azure: 0xf0ffff, - beige: 0xf5f5dc, - bisque: 0xffe4c4, - black: 0x000000, - blanchedalmond: 0xffebcd, - blue: 0x0000ff, - blueviolet: 0x8a2be2, - brown: 0xa52a2a, - burlywood: 0xdeb887, - cadetblue: 0x5f9ea0, - chartreuse: 0x7fff00, - chocolate: 0xd2691e, - coral: 0xff7f50, - cornflowerblue: 0x6495ed, - cornsilk: 0xfff8dc, - crimson: 0xdc143c, - cyan: 0x00ffff, - darkblue: 0x00008b, - darkcyan: 0x008b8b, - darkgoldenrod: 0xb8860b, - darkgray: 0xa9a9a9, - darkgreen: 0x006400, - darkgrey: 0xa9a9a9, - darkkhaki: 0xbdb76b, - darkmagenta: 0x8b008b, - darkolivegreen: 0x556b2f, - darkorange: 0xff8c00, - darkorchid: 0x9932cc, - darkred: 0x8b0000, - darksalmon: 0xe9967a, - darkseagreen: 0x8fbc8f, - darkslateblue: 0x483d8b, - darkslategray: 0x2f4f4f, - darkslategrey: 0x2f4f4f, - darkturquoise: 0x00ced1, - darkviolet: 0x9400d3, - deeppink: 0xff1493, - deepskyblue: 0x00bfff, - dimgray: 0x696969, - dimgrey: 0x696969, - dodgerblue: 0x1e90ff, - firebrick: 0xb22222, - floralwhite: 0xfffaf0, - forestgreen: 0x228b22, - fuchsia: 0xff00ff, - gainsboro: 0xdcdcdc, - ghostwhite: 0xf8f8ff, - gold: 0xffd700, - goldenrod: 0xdaa520, - gray: 0x808080, - green: 0x008000, - greenyellow: 0xadff2f, - grey: 0x808080, - honeydew: 0xf0fff0, - hotpink: 0xff69b4, - indianred: 0xcd5c5c, - indigo: 0x4b0082, - ivory: 0xfffff0, - khaki: 0xf0e68c, - lavender: 0xe6e6fa, - lavenderblush: 0xfff0f5, - lawngreen: 0x7cfc00, - lemonchiffon: 0xfffacd, - lightblue: 0xadd8e6, - lightcoral: 0xf08080, - lightcyan: 0xe0ffff, - lightgoldenrodyellow: 0xfafad2, - lightgray: 0xd3d3d3, - lightgreen: 0x90ee90, - lightgrey: 0xd3d3d3, - lightpink: 0xffb6c1, - lightsalmon: 0xffa07a, - lightseagreen: 0x20b2aa, - lightskyblue: 0x87cefa, - lightslategray: 0x778899, - lightslategrey: 0x778899, - lightsteelblue: 0xb0c4de, - lightyellow: 0xffffe0, - lime: 0x00ff00, - limegreen: 0x32cd32, - linen: 0xfaf0e6, - magenta: 0xff00ff, - maroon: 0x800000, - mediumaquamarine: 0x66cdaa, - mediumblue: 0x0000cd, - mediumorchid: 0xba55d3, - mediumpurple: 0x9370db, - mediumseagreen: 0x3cb371, - mediumslateblue: 0x7b68ee, - mediumspringgreen: 0x00fa9a, - mediumturquoise: 0x48d1cc, - mediumvioletred: 0xc71585, - midnightblue: 0x191970, - mintcream: 0xf5fffa, - mistyrose: 0xffe4e1, - moccasin: 0xffe4b5, - navajowhite: 0xffdead, - navy: 0x000080, - oldlace: 0xfdf5e6, - olive: 0x808000, - olivedrab: 0x6b8e23, - orange: 0xffa500, - orangered: 0xff4500, - orchid: 0xda70d6, - palegoldenrod: 0xeee8aa, - palegreen: 0x98fb98, - paleturquoise: 0xafeeee, - palevioletred: 0xdb7093, - papayawhip: 0xffefd5, - peachpuff: 0xffdab9, - peru: 0xcd853f, - pink: 0xffc0cb, - plum: 0xdda0dd, - powderblue: 0xb0e0e6, - purple: 0x800080, - rebeccapurple: 0x663399, - red: 0xff0000, - rosybrown: 0xbc8f8f, - royalblue: 0x4169e1, - saddlebrown: 0x8b4513, - salmon: 0xfa8072, - sandybrown: 0xf4a460, - seagreen: 0x2e8b57, - seashell: 0xfff5ee, - sienna: 0xa0522d, - silver: 0xc0c0c0, - skyblue: 0x87ceeb, - slateblue: 0x6a5acd, - slategray: 0x708090, - slategrey: 0x708090, - snow: 0xfffafa, - springgreen: 0x00ff7f, - steelblue: 0x4682b4, - tan: 0xd2b48c, - teal: 0x008080, - thistle: 0xd8bfd8, - tomato: 0xff6347, - turquoise: 0x40e0d0, - violet: 0xee82ee, - wheat: 0xf5deb3, - white: 0xffffff, - whitesmoke: 0xf5f5f5, - yellow: 0xffff00, - yellowgreen: 0x9acd32, -} - -function mapValues(obj, fn) { - return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])) -} - -function fromObjectToSlider(object) { - return State.Slider(object.value, { - min: object.min, - max: object.max, - step: object.step || 0.01, - ...(object.scale === 'exp' && { - min: object.min || 0.001, - step: object.step || 0.001, - mapping: (x) => Math.pow(10, x), - inverseMapping: Math.log10, - }), - }) -} - -export function initControls(object, options = {}) { - const stateObject = mapValues(object, (value) => { - if ( - typeof value === 'object' && - (value.hasOwnProperty('value') || - value.hasOwnProperty('max') || - value.hasOwnProperty('min') || - value.hasOwnProperty('step')) - ) { - return fromObjectToSlider(value) - } - - if (typeof value === 'object') { - return mapValues(value, (v) => { - if ( - typeof v === 'object' && - (v.hasOwnProperty('value') || - v.hasOwnProperty('max') || - v.hasOwnProperty('min') || - v.hasOwnProperty('step')) - ) { - return fromObjectToSlider(v) - } - - return v - }) - } - - return value - }) - - const controlsState = State(stateObject) - const controlsInstance = options.hideControls - ? controlsState - : wrapGUI(controlsState, { expanded: !options.closeControls }) - - // add the custom controls-gui styles - if (!options.hideControls) { - const styles = ` - [class^="controlPanel-"] [class*="__field"]::before { - content: initial !important; - } - [class^="controlPanel-"] [class*="__labelText"] { - text-indent: 6px !important; - } - [class^="controlPanel-"] [class*="__field--button"] > button::before { - content: initial !important; - } - ` - const style = document.createElement('style') - style.type = 'text/css' - style.innerHTML = styles - document.head.appendChild(style) - } - - controls = controlsInstance - return controlsInstance -} - -function extractAccessor(fnString) { - if (fnString.slice(-1) === '}') { - fnString = fnString.slice(0, -1) - } - - const accessorStart = fnString.indexOf('.controls.') + '.controls.'.length - fnString = fnString.slice(accessorStart) - - return fnString.trim() -} - -export function wireValue(object, fn) { - const fnString = fn.toString() - const accessor = extractAccessor(fnString) - - const key = accessor.includes('.') ? accessor.slice(accessor.lastIndexOf('.') + 1) : accessor - - controls.$onChanges((cons) => { - if (cons[accessor]) { - if (object[key].isColor) { - object[key].set(cons[accessor].value) - } else { - object[key] = cons[accessor].value - } - } - }) - - let value = fn() - - if (typeof value === 'string' && (value.startsWith('#') || value in _colorKeywords)) { - value = new THREE.Color(value) - } - - return value -} - -export function wireUniform(object, fn) { - const fnString = fn.toString() - const accessor = extractAccessor(fnString) - - const key = accessor.includes('.') ? accessor.slice(accessor.lastIndexOf('.') + 1) : accessor - - controls.$onChanges((cons) => { - if (cons[accessor]) { - if (object.uniforms[key].value.isColor) { - object.uniforms[key].value.set(cons[accessor].value) - } else { - object.uniforms[key].value = cons[accessor].value - } - } - }) - - let value = fn() - - if (typeof value === 'string' && (value.startsWith('#') || value in _colorKeywords)) { - value = new THREE.Color(value) - } - - return { value } -} diff --git a/src/utils/ExponentialNumberController.js b/src/utils/ExponentialNumberController.js new file mode 100644 index 0000000..9b3cabe --- /dev/null +++ b/src/utils/ExponentialNumberController.js @@ -0,0 +1,85 @@ +import { NumberController } from 'lil-gui' + +// Exponential slider for lil-gui. +// Only for numbers > 0 + +const mapping = (x) => Math.pow(10, x) +const inverseMapping = Math.log10 + +export class ExponentialNumberController extends NumberController { + updateDisplay() { + super.updateDisplay() + + if (this._hasSlider) { + const value = inverseMapping(this.getValue()) + const min = inverseMapping(this._min) + const max = inverseMapping(this._max) + let percent = (value - min) / (max - min) + percent = Math.max(0, Math.min(percent, 1)) + + this.$fill.style.width = percent * 100 + '%' + } + + return this + } + + _initSlider() { + this._hasSlider = true + + // Build DOM + // --------------------------------------------------------------------- + + this.$slider = document.createElement('div') + this.$slider.classList.add('slider') + + this.$fill = document.createElement('div') + this.$fill.classList.add('fill') + + this.$slider.appendChild(this.$fill) + this.$widget.insertBefore(this.$slider, this.$input) + + this.domElement.classList.add('hasSlider') + + // Map clientX to value + // --------------------------------------------------------------------- + + const min = inverseMapping(this._min) + const max = inverseMapping(this._max) + + const clamp = (value) => { + if (value < min) value = min + if (value > max) value = max + return value + } + + const map = (v, a, b, c, d) => { + return ((v - a) / (b - a)) * (d - c) + c + } + + const setValueFromX = (clientX) => { + const rect = this.$slider.getBoundingClientRect() + let value = map(clientX, rect.left, rect.right, min, max) + this.setValue(mapping(clamp(this._snap(value)))) + } + + const mouseDown = (e) => { + this._setDraggingStyle(true) + setValueFromX(e.clientX) + window.addEventListener('pointermove', mouseMove) + window.addEventListener('pointerup', mouseUp) + } + + const mouseMove = (e) => { + setValueFromX(e.clientX) + } + + const mouseUp = () => { + this._callOnFinishChange() + this._setDraggingStyle(false) + window.removeEventListener('pointermove', mouseMove) + window.removeEventListener('pointerup', mouseUp) + } + + this.$slider.addEventListener('pointerdown', mouseDown) + } +} diff --git a/src/utils/WebGLApp.js b/src/utils/WebGLApp.js index 06d3850..c726b62 100644 --- a/src/utils/WebGLApp.js +++ b/src/utils/WebGLApp.js @@ -1,11 +1,21 @@ -import * as THREE from 'three' -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' +import { + Color, + HalfFloatType, + OrthographicCamera, + PerspectiveCamera, + Scene, + sRGBEncoding, + Vector3, + WebGLRenderer, +} from 'three' +import { OrbitControls } from 'three/addons/controls/OrbitControls.js' import Stats from 'stats.js' import { getGPUTier } from 'detect-gpu' import { EffectComposer, RenderPass } from 'postprocessing' import CannonDebugger from 'cannon-es-debugger' import loadMP4Module, { isWebCodecsSupported } from 'mp4-wasm' -import { initControls } from './Controls' +import GUI from 'lil-gui' +import { ExponentialNumberController } from '../utils/ExponentialNumberController' export default class WebGLApp { #width @@ -26,7 +36,7 @@ export default class WebGLApp { #frames = [] get background() { - return this.renderer.getClearColor(new THREE.Color()) + return this.renderer.getClearColor(new Color()) } get backgroundAlpha() { @@ -52,11 +62,9 @@ export default class WebGLApp { frustumSize = 3, near = 0.01, far = 100, - gamma = true, - physicallyCorrectLights = true, ...options } = {}) { - this.renderer = new THREE.WebGLRenderer({ + this.renderer = new WebGLRenderer({ antialias: !options.postprocessing, alpha: backgroundAlpha !== 1, // enabled for recording gifs or videos, @@ -64,17 +72,16 @@ export default class WebGLApp { preserveDrawingBuffer: true, ...options, }) + // enable gamma correction, read more about it here: + // https://www.donmccurdy.com/2020/06/17/color-management-in-threejs/ + this.renderer.outputEncoding = sRGBEncoding + // this will be the default in the future + // https://github.com/mrdoob/three.js/issues/23614 + this.renderer.physicallyCorrectLights = true + if (options.sortObjects !== undefined) { this.renderer.sortObjects = options.sortObjects } - if (gamma) { - // enable gamma correction, read more about it here: - // https://www.donmccurdy.com/2020/06/17/color-management-in-threejs/ - this.renderer.outputEncoding = THREE.sRGBEncoding - } - if (physicallyCorrectLights) { - this.renderer.physicallyCorrectLights = true - } if (options.xr) { this.renderer.xr.enabled = true } @@ -95,9 +102,9 @@ export default class WebGLApp { // setup the camera const aspect = this.#width / this.#height if (!options.orthographic) { - this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far) + this.camera = new PerspectiveCamera(fov, aspect, near, far) } else { - this.camera = new THREE.OrthographicCamera( + this.camera = new OrthographicCamera( -(frustumSize * aspect) / 2, (frustumSize * aspect) / 2, frustumSize / 2, @@ -107,10 +114,10 @@ export default class WebGLApp { ) this.camera.frustumSize = frustumSize } - this.camera.position.copy(options.cameraPosition || new THREE.Vector3(0, 0, 4)) - this.camera.lookAt(0, 0, 0) + this.camera.position.copy(options.cameraPosition || new Vector3(0, 0, 4)) + this.camera.lookAt(options.cameraTarget || new Vector3()) - this.scene = new THREE.Scene() + this.scene = new Scene() this.gl = this.renderer.getContext() @@ -187,7 +194,7 @@ export default class WebGLApp { const maxMultisampling = this.gl.getParameter(this.gl.MAX_SAMPLES) this.composer = new EffectComposer(this.renderer, { multisampling: Math.min(8, maxMultisampling), - frameBufferType: gamma ? THREE.HalfFloatType : undefined, + frameBufferType: HalfFloatType, ...options, }) this.composer.addPass(new RenderPass(this.scene, this.camera)) @@ -223,9 +230,67 @@ export default class WebGLApp { document.body.appendChild(this.stats.dom) } - // initialize the controls-state - if (options.controls) { - this.controls = initControls(options.controls, options) + // initialize the gui + if (options.gui) { + this.gui = new GUI() + + if (options.guiClosed) { + this.gui.close() + } + + Object.assign(Object.getPrototypeOf(this.gui), { + // let's try to be smart + addSmart(object, key, name = '') { + const value = object[key] + switch (typeof value) { + case 'number': { + if (value === 0) { + return this.add(object, key, -10, 10, 0.01) + } else if ( + 0 < value && + value < 1 && + !['f', 'a', 'frequency', 'amplitude'].includes(name) + ) { + return this.add(object, key, 0, 1, 0.01) + } else if (value > 0) { + return new ExponentialNumberController( + this, + object, + key, + 0.01, + value < 100 ? 100 : 1000, + 0.01 + ) + } else { + return this.add(object, key, -10, 0, 0.01) + } + } + case 'object': { + return this.addColor(object, key) + } + default: { + return this.add(object, key) + } + } + }, + // specifically for three.js exposed uniforms + wireUniforms(folderName, uniforms, { blacklist = [] } = {}) { + const folder = this.addFolder(folderName) + + Object.keys(uniforms).forEach((key) => { + if (blacklist.includes(key)) return + const uniformObject = uniforms[key] + folder.addSmart(uniformObject, 'value', key).name(key) + }) + }, + }) + + if (typeof options.gui === 'object') { + this.guiState = options.gui + Object.keys(options.gui).forEach((key) => { + this.gui.addSmart(this.guiState, key) + }) + } } // detect the gpu info diff --git a/src/utils/loadEnvMap.js b/src/utils/loadEnvMap.js index eae203d..07b238c 100644 --- a/src/utils/loadEnvMap.js +++ b/src/utils/loadEnvMap.js @@ -1,8 +1,15 @@ -import * as THREE from 'three' +import { + CubeTextureLoader, + EquirectangularReflectionMapping, + PMREMGenerator, + sRGBEncoding, + TextureLoader, + UnsignedByteType, +} from 'three' // TODO lazy load these, or put them in different files -import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' -import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js' -import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js' +import { RGBELoader } from 'three/addons/loaders/RGBELoader.js' +import { EXRLoader } from 'three/addons/loaders/EXRLoader.js' +import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js' export default function loadEnvMap(url, { renderer, ...options }) { if (!renderer) { @@ -17,18 +24,18 @@ export default function loadEnvMap(url, { renderer, ...options }) { switch (extension) { case 'hdr': { - loader = new RGBELoader().setDataType(THREE.UnsignedByteType).loadAsync(url) + loader = new RGBELoader().setDataType(UnsignedByteType).loadAsync(url) break } case 'exr': { - loader = new EXRLoader().setDataType(THREE.UnsignedByteType).loadAsync(url) + loader = new EXRLoader().setDataType(UnsignedByteType).loadAsync(url) break } case 'png': case 'jpg': { - loader = new THREE.TextureLoader().loadAsync(url).then((texture) => { - if (renderer.outputEncoding === THREE.sRGBEncoding && !options.linear) { - texture.encoding = THREE.sRGBEncoding + loader = new TextureLoader().loadAsync(url).then((texture) => { + if (renderer.outputEncoding === sRGBEncoding && !options.linear) { + texture.encoding = sRGBEncoding } return texture }) @@ -51,14 +58,14 @@ export default function loadEnvMap(url, { renderer, ...options }) { switch (extension) { case 'hdr': { - loader = new HDRCubeTextureLoader().setDataType(THREE.UnsignedByteType).loadAsync(url) + loader = new HDRCubeTextureLoader().setDataType(UnsignedByteType).loadAsync(url) break } case 'png': case 'jpg': { - loader = new THREE.CubeTextureLoader().loadAsync(url).then((texture) => { - if (renderer.outputEncoding === THREE.sRGBEncoding && !options.linear) { - texture.encoding = THREE.sRGBEncoding + loader = new CubeTextureLoader().loadAsync(url).then((texture) => { + if (renderer.outputEncoding === sRGBEncoding && !options.linear) { + texture.encoding = sRGBEncoding } return texture }) @@ -94,7 +101,7 @@ export default function loadEnvMap(url, { renderer, ...options }) { // prefilter the equirectangular environment map for irradiance function equirectangularToPMREMCube(texture, renderer) { - const pmremGenerator = new THREE.PMREMGenerator(renderer) + const pmremGenerator = new PMREMGenerator(renderer) pmremGenerator.compileEquirectangularShader() const cubeRenderTarget = pmremGenerator.fromEquirectangular(texture) @@ -108,7 +115,7 @@ function equirectangularToPMREMCube(texture, renderer) { // prefilter the cubemap environment map for irradiance function cubeToPMREMCube(texture, renderer) { - const pmremGenerator = new THREE.PMREMGenerator(renderer) + const pmremGenerator = new PMREMGenerator(renderer) pmremGenerator.compileCubemapShader() const cubeRenderTarget = pmremGenerator.fromCubemap(texture) @@ -123,6 +130,6 @@ function cubeToPMREMCube(texture, renderer) { // transform an equirectangular texture to a cubetexture that // can be used as an envmap or scene background function equirectangularToCube(texture) { - texture.mapping = THREE.EquirectangularReflectionMapping + texture.mapping = EquirectangularReflectionMapping return texture } diff --git a/src/utils/loadGLTF.js b/src/utils/loadGLTF.js index e4678f5..8415fac 100644 --- a/src/utils/loadGLTF.js +++ b/src/utils/loadGLTF.js @@ -1,5 +1,5 @@ -import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' -import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js' export default function loadGLTF(url, options = {}) { return new Promise((resolve, reject) => { diff --git a/src/utils/loadTexture.js b/src/utils/loadTexture.js index 5d5596e..82a3d5b 100644 --- a/src/utils/loadTexture.js +++ b/src/utils/loadTexture.js @@ -1,4 +1,4 @@ -import * as THREE from 'three' +import { sRGBEncoding, TextureLoader } from 'three' export default function loadTexture(url, { renderer, ...options }) { if (!renderer) { @@ -6,12 +6,12 @@ export default function loadTexture(url, { renderer, ...options }) { } return new Promise((resolve, reject) => { - new THREE.TextureLoader().load( + new TextureLoader().load( url, (texture) => { // apply eventual gamma encoding - if (renderer.outputEncoding === THREE.sRGBEncoding && !options.linear) { - texture.encoding = THREE.sRGBEncoding + if (renderer.outputEncoding === sRGBEncoding && !options.linear) { + texture.encoding = sRGBEncoding } // apply eventual texture options, such as wrap, repeat... diff --git a/yarn.lock b/yarn.lock index 45b67a7..c260ff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -818,24 +818,6 @@ connect@3.6.6: parseurl "~1.3.2" utils-merge "1.0.1" -controls-gui@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/controls-gui/-/controls-gui-2.0.0.tgz#8f6ad1f0435ffeba931f7398597f496c0f7c7ff6" - integrity sha512-biqTR+5fqXnQvXiQ54fA7Ta85O31eG7RxMxSCpwD0/yhAm7ipGNqv9+NLM48BTWsyJXsgC5PDR3wHbKN1i793g== - dependencies: - defaults "^1.0.3" - insert-css "^2.0.0" - preact "^8.4.2" - -controls-state@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/controls-state/-/controls-state-2.0.0.tgz#b7e18625e4f8d7ab1c51143ae6c03b09303b5958" - integrity sha512-9tXQ4fgmXQCLgYlEQ58G+xab97vCnIL2+tlzbEBrErxzt2GipdaRXTQZfD2b3M8VS5fY0aF5mGSM950Q9HcYPw== - dependencies: - event-emitter "^0.3.5" - global "^4.3.2" - raf "^3.4.1" - cookie@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" @@ -884,14 +866,6 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - debug@2.6.9, debug@^2.2.0, debug@^2.6.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -989,11 +963,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-walk@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" - integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -1102,32 +1071,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - esbuild-plugin-glslify-inline@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/esbuild-plugin-glslify-inline/-/esbuild-plugin-glslify-inline-1.1.0.tgz#a0d7279ee8dc730b08ccb3ae6ab3a71fb120c1f5" @@ -1330,14 +1273,6 @@ etag@1.8.1, etag@^1.8.1, etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -1348,13 +1283,6 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - falafel@^2.1.0: version "2.2.4" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" @@ -1638,14 +1566,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@^4.3.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - globals@^13.19.0: version "13.19.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" @@ -1961,11 +1881,6 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -insert-css@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4" - integrity sha1-610Ql7dUL0x56jBg067gfQU4gPQ= - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2152,6 +2067,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lil-gui@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/lil-gui/-/lil-gui-0.17.0.tgz#b41ae55d0023fcd9185f7395a218db0f58189663" + integrity sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ== + limiter@^1.0.5: version "1.1.5" resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" @@ -2289,13 +2209,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= - dependencies: - dom-walk "^0.1.0" - minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -2368,11 +2281,6 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - node-releases@^2.0.6: version "2.0.8" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" @@ -2593,11 +2501,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -2633,11 +2536,6 @@ postprocessing@^6.29.1: resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.29.1.tgz#169b48cca498a52d3ee4b8960645b089cc7f64b3" integrity sha512-w+XoL2Z7YpVDsHbIeg6sldMMmDqZS4Hul1FDq3TjOeXZ0Wnp0Qy1EvR7QmYJxyWBux1cKQx/k3XO7LxfvPsZyg== -preact@^8.4.2: - version "8.5.3" - resolved "https://registry.yarnpkg.com/preact/-/preact-8.5.3.tgz#78c2a5562fcecb1fed1d0055fa4ac1e27bde17c1" - integrity sha512-O3kKP+1YdgqHOFsZF2a9JVdtqD+RPzCQc3rP+Ualf7V6rmRDchZ9MJbiGTT7LuyqFKZqlHSOyO/oMFmI2lVTsw== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -2665,11 +2563,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -2690,13 +2583,6 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - range-parser@~1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -3304,16 +3190,6 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" - integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"