├── .github └── workflows │ └── gh-pages.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── example ├── README.md ├── core │ ├── drawing.ts │ ├── materials.ts │ ├── patches.ts │ ├── postprocessing.ts │ ├── storage.ts │ ├── viewport.ts │ └── walk.ts ├── favicon.ico ├── index.html ├── main.css ├── main.ts ├── rollup.config.js ├── tsconfig.json └── ui │ ├── color.ts │ ├── exporter.ts │ ├── icons.ts │ ├── orientation.ts │ ├── size.ts │ └── snapshot.ts ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js ├── src ├── chunks │ ├── chunk.ts │ ├── triangles.ts │ ├── types.ts │ └── voxels.ts ├── index.ts ├── storage.ts ├── workers │ ├── compile.sh │ ├── mesher.c │ ├── mesher.js │ ├── mesher.wasm │ ├── program.js │ └── worker.ts └── world.ts └── tsconfig.json /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: PNPM 14 | run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 15 | - name: Dependencies 16 | run: pnpm install 17 | - name: Module 18 | run: pnpm build 19 | - name: Build 20 | run: pnpm build:example 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: 'example/dist' 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.eol": "\n", 4 | "search.exclude": { 5 | "**/.github": true, 6 | "**/.vscode": true, 7 | "**/dist": true, 8 | "**/node_modules": true, 9 | }, 10 | "files.exclude": { 11 | "**/.github": true, 12 | "**/.vscode": true, 13 | "**/dist": true, 14 | "**/node_modules": true, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2023 Daniel Esteban Nombela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [sculpty](https://github.com/danielesteban/sculpty) 2 | [![npm-version](https://img.shields.io/npm/v/sculpty.svg)](https://www.npmjs.com/package/sculpty) 3 | == 4 | 5 | ### Installation 6 | 7 | ```bash 8 | npm install sculpty 9 | ``` 10 | 11 | ### Basic usage 12 | 13 | ```js 14 | import { World } from 'sculpty'; 15 | import { PerspectiveCamera, Scene, sRGBEncoding, WebGLRenderer } from 'three'; 16 | 17 | const aspect = window.innerWidth / window.innerHeight; 18 | const camera = new PerspectiveCamera(75, aspect, 0.1, 1000); 19 | const renderer = new WebGLRenderer({ antialias: true }); 20 | renderer.outputEncoding = sRGBEncoding; 21 | renderer.setSize(window.innerWidth, window.innerHeight); 22 | document.getElementById('renderer').appendChild(renderer.domElement); 23 | 24 | const scene = new Scene(); 25 | const world = new World(); 26 | world.update(Array.from({ length: 20 }, (_, i) => ({ 27 | x: i - 10, y: Math.floor(Math.sin(i * 0.5) * 2), z: -10, 28 | r: 255, g: 255, b: 255, 29 | value: 255, 30 | }))); 31 | scene.add(world); 32 | 33 | renderer.setAnimationLoop(() => { 34 | renderer.render(scene, camera); 35 | }); 36 | ``` 37 | 38 | ### Examples 39 | 40 | * Basic: [sculpty.glitch.me](https://sculpty.glitch.me) ([source](https://glitch.com/edit/#!/sculpty)) 41 | * Editor: [sculpty.gatunes.com](https://sculpty.gatunes.com) ([source](example)) 42 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | # clone this repo 3 | git clone https://github.com/danielesteban/sculpty.git 4 | cd sculpty 5 | # install dependencies 6 | pnpm install 7 | # build the module 8 | pnpm build 9 | # start the dev environment 10 | pnpm start 11 | # open http://localhost:8080/ in your browser 12 | ``` 13 | -------------------------------------------------------------------------------- /example/core/drawing.ts: -------------------------------------------------------------------------------- 1 | import { World } from 'sculpty'; 2 | import { 3 | Mesh, 4 | PerspectiveCamera, 5 | PlaneGeometry, 6 | Raycaster, 7 | Vector3 8 | } from 'three'; 9 | import { Pointer } from './viewport'; 10 | import Color from '../ui/color'; 11 | import Orientation, { OrientationMode } from '../ui/orientation'; 12 | import Size from '../ui/size'; 13 | 14 | const _center = new Vector3(0.5, 0.5, 0.5); 15 | const _vector = new Vector3(); 16 | 17 | class Drawing { 18 | private action: number = 0; 19 | private isEnabled: boolean = false; 20 | private isEraser: boolean = false; 21 | private isPaint: boolean = false; 22 | private readonly lastPosition: Vector3 = new Vector3(); 23 | private readonly plane: Mesh = new Mesh(new PlaneGeometry(10000, 10000, 1, 1)); 24 | private readonly raycaster: Raycaster = new Raycaster(undefined, undefined, 0, 128); 25 | private readonly camera: PerspectiveCamera; 26 | private readonly color: Color; 27 | private readonly orientation: Orientation; 28 | private readonly size: Size; 29 | private readonly world: World; 30 | 31 | constructor( 32 | camera: PerspectiveCamera, 33 | color: Color, 34 | orientation: Orientation, 35 | size: Size, 36 | world: World 37 | ) { 38 | this.camera = camera; 39 | this.color = color; 40 | this.orientation = orientation; 41 | this.size = size; 42 | this.world = world; 43 | } 44 | 45 | start(pointer: Pointer, ctrlKey: boolean, shiftKey: boolean) { 46 | if ( 47 | (ctrlKey && pointer.button !== 1) 48 | || (shiftKey && pointer.button !== 1) 49 | ) { 50 | return; 51 | } 52 | 53 | const { camera, color, orientation, plane, raycaster, world } = this; 54 | raycaster.setFromCamera(pointer.position, camera); 55 | const hit = raycaster.intersectObject(world, true)[0]; 56 | if (!hit || !hit.face?.normal) { 57 | return; 58 | } 59 | if (pointer.button === 4) { 60 | const hex = (hit.object as any).getColor(hit.instanceId).getHex(); 61 | color.setValue( 62 | (hex >> 16) & 0xFF, 63 | (hex >> 8) & 0xFF, 64 | hex & 0xFF 65 | ); 66 | return; 67 | } 68 | 69 | this.action++; 70 | this.isEnabled = true; 71 | this.isEraser = ctrlKey || pointer.button === 2; 72 | this.isPaint = shiftKey; 73 | hit.point 74 | .addScaledVector(hit.face.normal, 0.25 * ((this.isEraser || this.isPaint) ? -1 : 1)) 75 | .floor(); 76 | this.draw(hit.point); 77 | 78 | plane.position.copy(hit.point).add(_center); 79 | switch (orientation.mode) { 80 | default: 81 | plane.quaternion.copy(camera.quaternion); 82 | break; 83 | case OrientationMode.surface: 84 | plane.lookAt(_vector.addVectors(plane.position, hit.face.normal)); 85 | break; 86 | } 87 | plane.updateMatrixWorld(); 88 | } 89 | 90 | move(pointer: Pointer) { 91 | const { camera, isEnabled, isEraser, isPaint, lastPosition, plane, raycaster, world } = this; 92 | if (!isEnabled) { 93 | return; 94 | } 95 | raycaster.setFromCamera(pointer.position, camera); 96 | let hit; 97 | if (isPaint) { 98 | hit = raycaster.intersectObject(world, true)[0]; 99 | } else { 100 | hit = raycaster.intersectObject(plane)[0]; 101 | } 102 | if (!hit || !hit.face?.normal) { 103 | return; 104 | } 105 | if (isPaint) { 106 | hit.point 107 | .addScaledVector(hit.face.normal, 0.25 * ((isEraser || isPaint) ? -1 : 1)); 108 | } 109 | hit.point.floor(); 110 | if ( 111 | lastPosition.equals(hit.point) 112 | || ( 113 | isPaint && lastPosition.distanceTo(hit.point) > 8 114 | ) 115 | ) { 116 | return; 117 | } 118 | this.draw(hit.point); 119 | } 120 | 121 | end() { 122 | this.isEnabled = false; 123 | } 124 | 125 | private draw(position: Vector3) { 126 | const { action, color, isEraser, isPaint, lastPosition, size, world } = this; 127 | let value; 128 | let r, g, b; 129 | if (isEraser) { 130 | value = 0; 131 | } else { 132 | if (!isPaint) { 133 | value = 64 + Math.floor(Math.random() * 192); 134 | } 135 | r = color.value.r; 136 | g = color.value.g; 137 | b = color.value.b; 138 | } 139 | const s = size.value; 140 | const { x, y, z } = position; 141 | const updates = []; 142 | for (let bz = -s; bz <= s; bz++) { 143 | for (let by = -s; by <= s; by++) { 144 | for (let bx = -s; bx <= s; bx++) { 145 | updates.push({ 146 | x: x + bx, y: y + by, z: z + bz, 147 | r, g, b, 148 | value, 149 | }); 150 | } 151 | } 152 | } 153 | world.update(updates, action); 154 | lastPosition.copy(position); 155 | } 156 | } 157 | 158 | export default Drawing; 159 | -------------------------------------------------------------------------------- /example/core/materials.ts: -------------------------------------------------------------------------------- 1 | import { MeshStandardMaterial } from 'three'; 2 | 3 | export default () => { 4 | const triangles = new MeshStandardMaterial({ 5 | envMapIntensity: 0.5, 6 | vertexColors: true, 7 | metalness: 0.2, 8 | roughness: 0.8, 9 | }); 10 | triangles.defines = { 11 | USE_OUTPUT_NORMAL: 1, 12 | USE_OUTPUT_POSITION: 1, 13 | }; 14 | 15 | const voxels = new MeshStandardMaterial({ 16 | envMapIntensity: 0.5, 17 | vertexColors: true, 18 | visible: false, 19 | }); 20 | voxels.defines = { 21 | USE_INSTANCED_POSITION: 1, 22 | USE_OUTPUT_NORMAL: 1, 23 | USE_OUTPUT_POSITION: 1, 24 | }; 25 | 26 | return { triangles, voxels }; 27 | }; 28 | -------------------------------------------------------------------------------- /example/core/patches.ts: -------------------------------------------------------------------------------- 1 | import { ShaderChunk } from 'three'; 2 | 3 | const _pars_vertex = /* glsl */` 4 | #ifdef USE_INSTANCED_POSITION 5 | attribute vec3 instance; 6 | #endif 7 | #ifdef USE_OUTPUT_NORMAL 8 | varying vec3 fragNormal; 9 | #endif 10 | #ifdef USE_OUTPUT_POSITION 11 | varying vec3 fragPosition; 12 | #endif 13 | `; 14 | 15 | const _vertex = /* glsl */` 16 | #ifdef USE_INSTANCED_POSITION 17 | transformed += instance; 18 | #endif 19 | #ifdef USE_OUTPUT_NORMAL 20 | vec3 outputNormal = vec3(normal); 21 | #ifdef USE_INSTANCING 22 | mat3 nm = mat3(instanceMatrix); 23 | outputNormal /= vec3(dot(nm[0], nm[0]), dot(nm[1], nm[1]), dot(nm[2], nm[2])); 24 | outputNormal = nm * outputNormal; 25 | #endif 26 | fragNormal = normalMatrix * outputNormal; 27 | #endif 28 | #ifdef USE_OUTPUT_POSITION 29 | vec4 outputPosition = vec4(transformed, 1.0); 30 | #ifdef USE_INSTANCING 31 | outputPosition = instanceMatrix * outputPosition; 32 | #endif 33 | outputPosition = modelMatrix * outputPosition; 34 | fragPosition = outputPosition.xyz; 35 | #endif 36 | `; 37 | 38 | const _pars_fragment= /* glsl */` 39 | #ifdef USE_OUTPUT_NORMAL 40 | layout(location = 1) out vec4 pc_fragNormal; 41 | varying vec3 fragNormal; 42 | #endif 43 | #ifdef USE_OUTPUT_POSITION 44 | layout(location = 2) out vec4 pc_fragPosition; 45 | varying vec3 fragPosition; 46 | #endif 47 | `; 48 | 49 | const _fragment = /* glsl */` 50 | #ifdef USE_OUTPUT_NORMAL 51 | pc_fragNormal = vec4(normalize(fragNormal), 0.0); 52 | #endif 53 | #ifdef USE_OUTPUT_POSITION 54 | pc_fragPosition = vec4(fragPosition, 0.0); 55 | #endif 56 | `; 57 | 58 | export default () => { 59 | ShaderChunk.skinning_pars_vertex += _pars_vertex; 60 | ShaderChunk.skinning_vertex += _vertex; 61 | ShaderChunk.clipping_planes_pars_fragment += _pars_fragment; 62 | ShaderChunk.clipping_planes_fragment += _fragment; 63 | }; 64 | -------------------------------------------------------------------------------- /example/core/postprocessing.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | GLSL3, 4 | Mesh, 5 | HalfFloatType, 6 | PerspectiveCamera, 7 | PlaneGeometry, 8 | RawShaderMaterial, 9 | Scene, 10 | Vector2, 11 | Vector3, 12 | WebGLMultipleRenderTargets, 13 | WebGLRenderer, 14 | WebGLRenderTarget, 15 | } from 'three'; 16 | 17 | const _clearColor = new Color(); 18 | const _vertex = /* glsl */` 19 | precision highp int; 20 | precision highp float; 21 | in vec3 position; 22 | out vec2 uv; 23 | uniform int flipY; 24 | void main() { 25 | gl_Position = vec4(position.xy, 0, 1); 26 | uv = position.xy * 0.5 + 0.5; 27 | if (flipY == 1) { 28 | uv.y = 1.0 - uv.y; 29 | } 30 | } 31 | `; 32 | 33 | const _fragment = /* glsl */` 34 | precision highp int; 35 | precision highp float; 36 | in vec2 uv; 37 | out vec4 fragColor; 38 | uniform sampler2D colorTexture; 39 | uniform sampler2D normalTexture; 40 | uniform sampler2D positionTexture; 41 | uniform vec2 resolution; 42 | uniform float cameraNear; 43 | uniform float cameraFar; 44 | uniform vec3 cameraPosition; 45 | uniform float intensity; 46 | uniform float thickness; 47 | uniform float depthBias; 48 | uniform float depthScale; 49 | uniform float normalBias; 50 | uniform float normalScale; 51 | #define saturate(a) clamp(a, 0.0, 1.0) 52 | float getDepth(const in vec3 position) { 53 | float depth = length(position - cameraPosition); 54 | return (depth - cameraNear) / (cameraFar - cameraNear); 55 | } 56 | vec3 LinearToSRGB(const in vec3 value) { 57 | return vec3(mix(pow(value.rgb, vec3(0.41666)) * 1.055 - vec3(0.055), value.rgb * 12.92, vec3(lessThanEqual(value.rgb, vec3(0.0031308))))); 58 | } 59 | vec3 SobelSample(const in sampler2D tex, const in vec2 uv, const in vec3 offset) { 60 | vec3 pixelCenter = texture(tex, uv).rgb; 61 | vec3 pixelLeft = texture(tex, uv - offset.xz).rgb; 62 | vec3 pixelRight = texture(tex, uv + offset.xz).rgb; 63 | vec3 pixelUp = texture(tex, uv + offset.zy).rgb; 64 | vec3 pixelDown = texture(tex, uv - offset.zy).rgb; 65 | return ( 66 | abs(pixelLeft - pixelCenter) 67 | + abs(pixelRight - pixelCenter) 68 | + abs(pixelUp - pixelCenter) 69 | + abs(pixelDown - pixelCenter) 70 | ); 71 | } 72 | float SobelSampleDepth(const in sampler2D tex, const in vec2 uv, const in vec3 offset) { 73 | float pixelCenter = getDepth(texture(tex, uv).xyz); 74 | float pixelLeft = getDepth(texture(tex, uv - offset.xz).xyz); 75 | float pixelRight = getDepth(texture(tex, uv + offset.xz).xyz); 76 | float pixelUp = getDepth(texture(tex, uv + offset.zy).xyz); 77 | float pixelDown = getDepth(texture(tex, uv - offset.zy).xyz); 78 | return ( 79 | abs(pixelLeft - pixelCenter) 80 | + abs(pixelRight - pixelCenter) 81 | + abs(pixelUp - pixelCenter) 82 | + abs(pixelDown - pixelCenter) 83 | ); 84 | } 85 | float edge(const in vec2 uv) { 86 | vec3 offset = vec3((1.0 / resolution.x), (1.0 / resolution.y), 0.0) * thickness; 87 | float sobelDepth = SobelSampleDepth(positionTexture, uv, offset); 88 | sobelDepth = pow(saturate(sobelDepth) * depthScale, depthBias); 89 | vec3 sobelNormalVec = SobelSample(normalTexture, uv, offset); 90 | float sobelNormal = sobelNormalVec.x + sobelNormalVec.y + sobelNormalVec.z; 91 | sobelNormal = pow(sobelNormal * normalScale, normalBias); 92 | return saturate(max(sobelDepth, sobelNormal)) * intensity; 93 | } 94 | const vec3 background = vec3(0.06666666666666667, 0.13333333333333333, 0.2); 95 | void main() { 96 | vec3 color = texture(colorTexture, uv).rgb; 97 | float depth = length(texture(positionTexture, uv).xyz * vec3(1.0, 0.5, 1.0)); 98 | float decay = (1.0 - exp(-0.02 * 0.02 * depth * depth)); 99 | color = mix(color, background, decay); 100 | decay = (1.0 - exp(-0.015 * 0.015 * depth * depth)); 101 | color = mix(color, vec3(0.0), edge(uv) * (1.0 - decay)); 102 | fragColor = vec4(LinearToSRGB(color), 1.0); 103 | } 104 | `; 105 | 106 | class PostProcessing { 107 | private readonly target: WebGLMultipleRenderTargets; 108 | private readonly screen: Mesh; 109 | 110 | constructor({ samples }: { samples: number; }) { 111 | const plane = new PlaneGeometry(2, 2, 1, 1); 112 | plane.deleteAttribute('normal'); 113 | plane.deleteAttribute('uv'); 114 | this.target = new WebGLMultipleRenderTargets(window.innerWidth, window.innerHeight, 3, { 115 | samples, 116 | type: HalfFloatType, 117 | }); 118 | this.screen = new Mesh( 119 | plane, 120 | new RawShaderMaterial({ 121 | glslVersion: GLSL3, 122 | uniforms: { 123 | colorTexture: { value: this.target.texture[0] }, 124 | normalTexture: { value: this.target.texture[1] }, 125 | positionTexture: { value: this.target.texture[2] }, 126 | resolution: { value: new Vector2((this.target as any).width, (this.target as any).height) }, 127 | cameraNear: { value: 0 }, 128 | cameraFar: { value: 0 }, 129 | cameraPosition: { value: new Vector3() }, 130 | flipY: { value: 0 }, 131 | intensity: { value: 0.8 }, 132 | thickness: { value: 1 }, 133 | depthBias: { value: 1 }, 134 | depthScale: { value: 40 }, 135 | normalBias: { value: 40 }, 136 | normalScale: { value: 1 }, 137 | }, 138 | vertexShader: _vertex, 139 | fragmentShader: _fragment, 140 | }) 141 | ); 142 | this.screen.frustumCulled = false; 143 | this.screen.matrixAutoUpdate = false; 144 | } 145 | 146 | setSize(width: number, height: number, flipY: boolean = false, pixelRatio: number = 1) { 147 | const { screen, target } = this; 148 | target.setSize(width, height); 149 | (screen.material as RawShaderMaterial).uniforms.resolution.value.set(width, height).divideScalar(pixelRatio); 150 | (screen.material as RawShaderMaterial).uniforms.flipY.value = flipY ? 1 : 0; 151 | } 152 | 153 | render( 154 | renderer: WebGLRenderer, 155 | camera: PerspectiveCamera, 156 | scene: Scene, 157 | screenTarget: WebGLRenderTarget | null = null, 158 | ) { 159 | const { screen, target } = this; 160 | renderer.setClearColor(_clearColor.setRGB(camera.far, camera.far, camera.far), 1); 161 | renderer.setRenderTarget(target); 162 | renderer.render(scene, camera); 163 | const { uniforms } = (screen.material as RawShaderMaterial); 164 | uniforms.cameraNear.value = camera.near; 165 | uniforms.cameraFar.value = camera.far; 166 | uniforms.cameraPosition.value.setFromMatrixPosition(camera.matrixWorld); 167 | renderer.setClearColor(_clearColor.setRGB(0, 0, 0), 1); 168 | renderer.setRenderTarget(screenTarget); 169 | renderer.render(screen, camera); 170 | } 171 | } 172 | 173 | export default PostProcessing; 174 | -------------------------------------------------------------------------------- /example/core/storage.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import FastNoise from 'fastnoise-lite'; 3 | import { deflate, inflateSync } from 'fflate'; 4 | import { Storage } from 'sculpty'; 5 | 6 | class LocalStorage implements Storage { 7 | public readonly chunkSize: number; 8 | private readonly data: Map; 9 | private readonly noise: FastNoise; 10 | private readonly queue: Map; 11 | 12 | constructor({ chunkSize }: { 13 | chunkSize: number; 14 | }) { 15 | this.chunkSize = chunkSize; 16 | this.data = new Map(); 17 | this.noise = new FastNoise(); 18 | this.noise.SetFrequency(0.03); 19 | this.noise.SetFractalType(FastNoise.FractalType.FBm); 20 | this.queue = new Map(); 21 | } 22 | 23 | private generate(cx: number, cy: number, cz: number): Uint32Array { 24 | const { chunkSize, noise } = this; 25 | const data = new Uint32Array(chunkSize * chunkSize * chunkSize); 26 | for (let i = 0, z = 0; z < chunkSize; z++) { 27 | for (let y = 0; y < chunkSize; y++) { 28 | for (let x = 0; x < chunkSize; x++, i++) { 29 | const vx = cx * chunkSize + x; 30 | const vy = cy * chunkSize + y; 31 | const vz = cz * chunkSize + z; 32 | if ( 33 | vy < -8 34 | || ( 35 | vy < 8 36 | && vy < noise.GetNoise(vx, vy, vz) * Math.sqrt(vx * vx + vz * vz) * 0.1 37 | ) 38 | ) { 39 | const l = Math.floor(255 - Math.random() * 64); 40 | data[i] = ( 41 | ((l / 2) << 24) 42 | ^ (l << 16) 43 | ^ (l << 8) 44 | ^ (32 + Math.floor(Math.random() * 128)) 45 | ); 46 | } 47 | } 48 | } 49 | } 50 | return data; 51 | } 52 | 53 | get(cx: number, cy: number, cz: number): Uint32Array { 54 | const key: string = `${cx}:${cy}:${cz}`; 55 | let data = this.data.get(key); 56 | if (!data) { 57 | const stored = localStorage.getItem(`chunk:${key}`); 58 | if (stored) { 59 | data = new Uint32Array( 60 | inflateSync(new Uint8Array(atob(stored).split('').map((c) => c.charCodeAt(0)))).buffer 61 | ); 62 | } else { 63 | data = this.generate(cx, cy, cz); 64 | } 65 | this.data.set(key, data); 66 | } 67 | return data; 68 | } 69 | 70 | save(cx: number, cy: number, cz: number) { 71 | const { queue } = this; 72 | const key: string = `${cx}:${cy}:${cz}`; 73 | const data = this.data.get(key); 74 | if (!data) { 75 | return; 76 | } 77 | const queued = queue.get(key); 78 | if (queued) { 79 | queued.aborted = true; 80 | clearTimeout(queued.timer); 81 | } 82 | const request = { 83 | aborted: false, 84 | timer: setTimeout(() => ( 85 | deflate(new Uint8Array(data.buffer), (err, data) => { 86 | if (err || request.aborted) { 87 | return; 88 | } 89 | localStorage.setItem( 90 | `chunk:${key}`, 91 | btoa([...data].map((c) => String.fromCharCode(c)).join('')) 92 | ); 93 | }) 94 | ), 1000), 95 | }; 96 | queue.set(key, request); 97 | } 98 | 99 | listStored() { 100 | return Object.keys(localStorage) 101 | .filter((key) => key.slice(0, 6) === 'chunk:') 102 | .map((key) => { 103 | const [x, y, z] = key.slice(6).split(':'); 104 | return { x: parseInt(x, 10), y: parseInt(y, 10), z: parseInt(z, 10) }; 105 | }); 106 | } 107 | } 108 | 109 | export default LocalStorage; 110 | -------------------------------------------------------------------------------- /example/core/viewport.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Clock, 3 | EventDispatcher, 4 | PMREMGenerator, 5 | PerspectiveCamera, 6 | Scene, 7 | Vector2, 8 | WebGLRenderer, 9 | } from 'three'; 10 | import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'; 11 | import PostProcessing from './postprocessing'; 12 | 13 | export type Pointer = { 14 | id: number; 15 | button: number; 16 | position: Vector2; 17 | }; 18 | 19 | class Viewport extends EventDispatcher { 20 | private readonly animate: (delta: number) => void; 21 | private readonly clock: Clock; 22 | public readonly dom: HTMLElement; 23 | public readonly camera: PerspectiveCamera; 24 | private readonly pointer: Pointer; 25 | public readonly postprocessing: PostProcessing; 26 | public readonly renderer: WebGLRenderer; 27 | public readonly scene: Scene; 28 | public needsUpdate: boolean = false; 29 | 30 | constructor(animate: (delta: number) => void) { 31 | super(); 32 | const dom = document.getElementById('viewport'); 33 | if (!dom) { 34 | throw new Error('Couldn\'t get viewport'); 35 | } 36 | dom.addEventListener('contextmenu', (e) => e.preventDefault()); 37 | dom.addEventListener('touchstart', (e) => e.preventDefault()); 38 | dom.addEventListener('pointerdown', this.pointerdown.bind(this)); 39 | dom.addEventListener('pointermove', this.pointermove.bind(this)); 40 | dom.addEventListener('pointerup', this.pointerup.bind(this)); 41 | this.dom = dom; 42 | 43 | this.animate = animate; 44 | this.camera = new PerspectiveCamera(75, 1, 0.1, 1000); 45 | this.clock = new Clock(); 46 | this.pointer = { 47 | id: -1, 48 | button: -1, 49 | position: new Vector2() 50 | }; 51 | this.postprocessing = new PostProcessing({ samples: 4 }); 52 | this.renderer = new WebGLRenderer({ 53 | antialias: false, 54 | powerPreference: 'high-performance', 55 | stencil: false, 56 | }); 57 | this.renderer.setPixelRatio(window.devicePixelRatio || 1); 58 | this.scene = new Scene(); 59 | this.scene.environment = (new PMREMGenerator(this.renderer)).fromScene(new RoomEnvironment(), 0.04).texture; 60 | 61 | this.resize(); 62 | window.addEventListener('resize', this.resize.bind(this)); 63 | dom.appendChild(this.renderer.domElement); 64 | 65 | document.addEventListener('visibilitychange', () => ( 66 | document.visibilityState === 'visible' && this.clock.start() 67 | )); 68 | this.renderer.setAnimationLoop(this.render.bind(this)); 69 | } 70 | 71 | render() { 72 | const { camera, clock, postprocessing, renderer, scene } = this; 73 | this.animate(Math.min(clock.getDelta(), 1)); 74 | if (this.needsUpdate) { 75 | this.needsUpdate = false; 76 | postprocessing.render(renderer, camera, scene); 77 | } 78 | } 79 | 80 | resize() { 81 | const { camera, postprocessing, renderer } = this; 82 | renderer.setSize(window.innerWidth, window.innerHeight); 83 | postprocessing.setSize(window.innerWidth, window.innerHeight); 84 | camera.aspect = window.innerWidth / window.innerHeight; 85 | camera.updateProjectionMatrix(); 86 | this.needsUpdate = true; 87 | } 88 | 89 | private getPointer({ buttons, clientX, clientY, pointerId }: PointerEvent) { 90 | this.pointer.id = pointerId; 91 | this.pointer.button = buttons; 92 | this.pointer.position.set( 93 | (clientX / window.innerWidth) * 2 - 1, 94 | -(clientY / window.innerHeight) * 2 + 1 95 | ); 96 | return this.pointer; 97 | } 98 | 99 | private pointerdown(e: PointerEvent) { 100 | (e.target as HTMLElement).setPointerCapture(e.pointerId); 101 | if (this.pointer.id === -1) { 102 | const pointer = this.getPointer(e); 103 | this.dispatchEvent({ type: 'dragstart', pointer, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey }); 104 | } 105 | } 106 | 107 | private pointermove(e: PointerEvent) { 108 | if (e.pointerId !== this.pointer.id) { 109 | return; 110 | } 111 | (('getCoalescedEvents' in e) ? e.getCoalescedEvents() : [e]).forEach((e) => { 112 | const pointer = this.getPointer(e); 113 | this.dispatchEvent({ type: 'dragmove', pointer }); 114 | }); 115 | } 116 | 117 | private pointerup(e: PointerEvent) { 118 | (e.target as HTMLElement).releasePointerCapture(e.pointerId); 119 | if (e.pointerId === this.pointer.id) { 120 | const pointer = this.getPointer(e); 121 | this.dispatchEvent({ type: 'dragend', pointer }); 122 | this.pointer.id = -1; 123 | } 124 | } 125 | } 126 | 127 | export default Viewport; 128 | -------------------------------------------------------------------------------- /example/core/walk.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Box3, 3 | Group, 4 | Line3, 5 | PerspectiveCamera, 6 | Vector3 7 | } from 'three'; 8 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; 9 | import { MeshBVH, StaticGeometryGenerator } from 'three-mesh-bvh'; 10 | 11 | const _tempBox = new Box3(); 12 | const _tempSegment = new Line3(); 13 | const _tempVector = new Vector3(); 14 | const _tempVector2 = new Vector3(); 15 | const _upVector = new Vector3(0, 1, 0); 16 | 17 | class Walk { 18 | private enabled: boolean = false; 19 | private camera: PerspectiveCamera; 20 | private controls: OrbitControls; 21 | private world: Group; 22 | private bvh?: MeshBVH; 23 | private gravity: number = -30; 24 | private capsule: { 25 | radius: number; 26 | segment: Line3; 27 | } = { 28 | radius: 0.5, 29 | segment: new Line3(new Vector3(), new Vector3(0, -2.0, 0.0)) 30 | }; 31 | private playerPosition: Vector3 = new Vector3(); 32 | private playerSpeed: number = 10; 33 | private playerVelocity: Vector3 = new Vector3(); 34 | private playerIsOnGround: boolean = false; 35 | private fwdPressed: boolean = false; 36 | private bkdPressed: boolean = false; 37 | private lftPressed: boolean = false; 38 | private rgtPressed: boolean = false; 39 | 40 | constructor(camera: PerspectiveCamera, controls: OrbitControls, world: Group) { 41 | this.camera = camera; 42 | this.controls = controls; 43 | this.world = world; 44 | this.blur = this.blur.bind(this); 45 | this.keydown = this.keydown.bind(this); 46 | this.keyup = this.keyup.bind(this); 47 | window.addEventListener('blur', this.blur); 48 | document.addEventListener('keydown', this.keydown); 49 | document.addEventListener('keyup', this.keyup); 50 | } 51 | 52 | isEnabled(): boolean { 53 | return this.enabled; 54 | } 55 | 56 | private blur() { 57 | const { playerVelocity } = this; 58 | playerVelocity.set(0, 0, 0); 59 | this.fwdPressed = this.bkdPressed = this.rgtPressed = this.lftPressed = false; 60 | } 61 | 62 | private keydown(e: KeyboardEvent) { 63 | const { playerIsOnGround, playerVelocity } = this; 64 | switch (e.code) { 65 | case 'KeyW': this.fwdPressed = true; break; 66 | case 'KeyS': this.bkdPressed = true; break; 67 | case 'KeyD': this.rgtPressed = true; break; 68 | case 'KeyA': this.lftPressed = true; break; 69 | case 'Space': 70 | if (playerIsOnGround) { 71 | playerVelocity.y = 10.0; 72 | } 73 | break; 74 | } 75 | } 76 | 77 | private keyup(e: KeyboardEvent) { 78 | switch (e.code) { 79 | case 'KeyW': this.fwdPressed = false; break; 80 | case 'KeyS': this.bkdPressed = false; break; 81 | case 'KeyD': this.rgtPressed = false; break; 82 | case 'KeyA': this.lftPressed = false; break; 83 | } 84 | } 85 | 86 | toggle(): boolean { 87 | const { camera, controls, playerPosition, playerVelocity, world } = this; 88 | this.enabled = !this.enabled; 89 | if (this.enabled) { 90 | controls.maxDistance = 1e-4; 91 | controls.minDistance = 1e-4; 92 | controls.enableRotate = true; 93 | controls.enablePan = false; 94 | playerPosition.copy(camera.position); 95 | const staticGenerator = new StaticGeometryGenerator( 96 | world.children.filter(({ visible }) => visible).map(({ triangles }: any) => triangles) 97 | ); 98 | staticGenerator.attributes = ['position']; 99 | this.bvh = new MeshBVH(staticGenerator.generate()); 100 | } else { 101 | controls.maxDistance = 96; 102 | controls.minDistance = 4; 103 | controls.enableRotate = false; 104 | controls.enablePan = false; 105 | controls.target.set(0, 8, 0); 106 | camera.position.set(0, 16, 32); 107 | playerVelocity.set(0, 0, 0); 108 | this.fwdPressed = this.bkdPressed = this.rgtPressed = this.lftPressed = false; 109 | } 110 | return this.enabled; 111 | } 112 | 113 | update(delta: number) { 114 | const { 115 | camera, controls, bvh, 116 | enabled, 117 | capsule, gravity, playerPosition, playerSpeed, playerVelocity, 118 | fwdPressed, bkdPressed, lftPressed, rgtPressed, 119 | } = this; 120 | 121 | if (!enabled || !bvh) { 122 | return; 123 | } 124 | 125 | playerVelocity.y += this.playerIsOnGround ? 0 : delta * gravity; 126 | playerPosition.addScaledVector(playerVelocity, delta); 127 | 128 | const angle = controls.getAzimuthalAngle(); 129 | if (fwdPressed) { 130 | _tempVector.set(0, 0, -1).applyAxisAngle(_upVector, angle); 131 | playerPosition.addScaledVector(_tempVector, playerSpeed * delta); 132 | } 133 | if (bkdPressed) { 134 | _tempVector.set(0, 0, 1).applyAxisAngle(_upVector, angle); 135 | playerPosition.addScaledVector(_tempVector, playerSpeed * delta); 136 | } 137 | if (lftPressed) { 138 | _tempVector.set(-1, 0, 0).applyAxisAngle(_upVector, angle); 139 | playerPosition.addScaledVector(_tempVector, playerSpeed * delta); 140 | } 141 | if (rgtPressed) { 142 | _tempVector.set(1, 0, 0).applyAxisAngle(_upVector, angle); 143 | playerPosition.addScaledVector(_tempVector, playerSpeed * delta); 144 | } 145 | 146 | _tempBox.makeEmpty(); 147 | _tempSegment.copy(capsule.segment); 148 | _tempSegment.start.add(playerPosition); 149 | _tempSegment.end.add(playerPosition); 150 | _tempBox.expandByPoint(_tempSegment.start); 151 | _tempBox.expandByPoint(_tempSegment.end); 152 | _tempBox.min.addScalar(-capsule.radius); 153 | _tempBox.max.addScalar(capsule.radius); 154 | 155 | bvh.shapecast({ 156 | intersectsBounds: (box) => box.intersectsBox(_tempBox), 157 | intersectsTriangle: (tri) => { 158 | const triPoint = _tempVector; 159 | const capsulePoint = _tempVector2; 160 | const distance = tri.closestPointToSegment(_tempSegment, triPoint, capsulePoint); 161 | if (distance < capsule.radius) { 162 | const depth = capsule.radius - distance; 163 | const direction = capsulePoint.sub( triPoint ).normalize(); 164 | _tempSegment.start.addScaledVector( direction, depth ); 165 | _tempSegment.end.addScaledVector( direction, depth ); 166 | } 167 | } 168 | }); 169 | 170 | const newPosition = _tempVector; 171 | newPosition.copy(_tempSegment.start); 172 | const deltaVector = _tempVector2; 173 | deltaVector.subVectors(newPosition, playerPosition); 174 | this.playerIsOnGround = deltaVector.y > Math.abs(delta * playerVelocity.y * 0.25); 175 | 176 | const offset = Math.max(0.0, deltaVector.length() - 1e-5); 177 | deltaVector.normalize().multiplyScalar(offset); 178 | playerPosition.add(deltaVector); 179 | 180 | if (!this.playerIsOnGround) { 181 | deltaVector.normalize(); 182 | playerVelocity.addScaledVector(deltaVector, - deltaVector.dot(playerVelocity)); 183 | } else { 184 | playerVelocity.set(0, 0, 0); 185 | } 186 | 187 | camera.position.sub(controls.target); 188 | controls.target.copy(playerPosition); 189 | camera.position.add(playerPosition); 190 | } 191 | } 192 | 193 | export default Walk; 194 | -------------------------------------------------------------------------------- /example/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielesteban/sculpty/ccd8b7a14136c58a075d8fda884c22727c3ecd59/example/favicon.ico -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sculpty 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 73 |
74 | dani@gatunes © 2023 75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /example/main.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-size: 16px; 3 | width: 100vh; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | background: #000; 10 | color: #eee; 11 | cursor: default; 12 | user-select: none; 13 | overflow: hidden; 14 | font-family: 'Roboto Condensed', monospace; 15 | font-size: 0.75rem; 16 | line-height: 1.125rem; 17 | width: 100vh; 18 | height: 100%; 19 | touch-action: none; 20 | -webkit-touch-callout: none; 21 | -webkit-text-size-adjust: none; 22 | } 23 | 24 | canvas { 25 | vertical-align: middle; 26 | } 27 | 28 | #viewport { 29 | width: 100vh; 30 | height: 100%; 31 | } 32 | 33 | #ui { 34 | position: fixed; 35 | bottom: 1rem; 36 | left: 50%; 37 | transform: translate(-50%, 0); 38 | padding: 0 0.5rem; 39 | height: 2rem; 40 | border-radius: 0.5rem; 41 | background: rgba(0, 0, 0, 0.5); 42 | display: flex; 43 | gap: 0.5rem; 44 | } 45 | 46 | #ui button { 47 | border: 0; 48 | margin: 0; 49 | padding: 0; 50 | outline: 0; 51 | width: 1rem; 52 | height: 1rem; 53 | font-family: inherit; 54 | font-weight: inherit; 55 | color: inherit; 56 | box-sizing: content-box; 57 | background: transparent; 58 | border: 1px solid #666; 59 | display: flex; 60 | align-items: center; 61 | justify-content: center; 62 | border-radius: 0.5rem; 63 | cursor: pointer; 64 | } 65 | 66 | #ui button > svg { 67 | height: 0.875rem; 68 | } 69 | 70 | #brush { 71 | display: flex; 72 | gap: 0.5rem; 73 | } 74 | 75 | #color { 76 | position: relative; 77 | display: flex; 78 | align-items: center; 79 | justify-content: center; 80 | cursor: pointer; 81 | } 82 | 83 | #color > div { 84 | width: 1rem; 85 | height: 1rem; 86 | border: 1px solid #666; 87 | border-radius: 0.625rem; 88 | } 89 | 90 | #color > input { 91 | border: 0; 92 | margin: 0; 93 | padding: 0; 94 | outline: 0; 95 | width: 0; 96 | height: 0; 97 | visibility: hidden; 98 | position: absolute; 99 | top: 0; 100 | left: -0.5rem; 101 | } 102 | 103 | #size { 104 | display: flex; 105 | gap: 0.25rem; 106 | } 107 | 108 | #size > div > button > span { 109 | display: block; 110 | aspect-ratio: 1; 111 | border-radius: 50%; 112 | background-color: #666; 113 | transition: background-color 0.2s ease-in-out; 114 | } 115 | 116 | #size > div > button.active { 117 | cursor: default; 118 | } 119 | 120 | #size > div > button.active > span { 121 | background-color: #eee; 122 | } 123 | 124 | #actions { 125 | display: flex; 126 | gap: 0.25rem; 127 | } 128 | 129 | .action { 130 | position: relative; 131 | display: flex; 132 | align-items: center; 133 | justify-content: center; 134 | gap: 0.25rem; 135 | } 136 | 137 | .toggle { 138 | width: 1rem; 139 | height: 1rem; 140 | border: 1px solid #666; 141 | border-radius: 0.5rem; 142 | display: flex; 143 | align-items: center; 144 | flex-direction: column; 145 | } 146 | 147 | .tooltip { 148 | display: none; 149 | position: absolute; 150 | bottom: calc(100% + 0.5rem); 151 | left: 50%; 152 | transform: translate(-50%, 0); 153 | padding: 0.25rem 0.5rem; 154 | border-radius: 0.5rem; 155 | background: rgba(0, 0, 0, 0.5); 156 | color: #bbb; 157 | opacity: 0; 158 | pointer-events: none; 159 | white-space: nowrap; 160 | } 161 | 162 | .tooltip::before { 163 | position: absolute; 164 | display: block; 165 | top: 100%; 166 | left: 50%; 167 | transform: translate(-50%, 0); 168 | content: ""; 169 | width: 0; 170 | height: 0; 171 | border-left: 0.5rem solid transparent; 172 | border-right: 0.5rem solid transparent; 173 | border-top: 0.5rem solid rgba(0, 0, 0, 0.5); 174 | } 175 | 176 | @keyframes fade { 177 | from { 178 | opacity: 0; 179 | } 180 | to { 181 | opacity: 1; 182 | } 183 | } 184 | 185 | .action:hover .tooltip { 186 | display: block; 187 | animation: 0.2s ease-in 0.1s forwards fade; 188 | } 189 | 190 | #help { 191 | cursor: help; 192 | } 193 | 194 | #help .tooltip > div { 195 | display: flex; 196 | } 197 | 198 | #help .tooltip > div > div:nth-child(1) { 199 | width: 5.5rem; 200 | } 201 | 202 | #help .tooltip > div > div:nth-child(2) { 203 | color: #eee; 204 | } 205 | 206 | #info { 207 | position: absolute; 208 | bottom: 1rem; 209 | left: 1rem; 210 | opacity: 0.5; 211 | color: #111; 212 | } 213 | 214 | #info > a { 215 | color: inherit; 216 | } 217 | -------------------------------------------------------------------------------- /example/main.ts: -------------------------------------------------------------------------------- 1 | import './main.css'; 2 | import { World } from 'sculpty'; 3 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; 4 | import Drawing from './core/drawing'; 5 | import Materials from './core/materials'; 6 | import PatchShaders from './core/patches'; 7 | import Storage from './core/storage'; 8 | import Viewport from './core/viewport'; 9 | import Walk from './core/walk'; 10 | import Color from './ui/color'; 11 | import Exporter from './ui/exporter'; 12 | import Orientation from './ui/orientation'; 13 | import Size from './ui/size'; 14 | import Snapshot from './ui/snapshot'; 15 | 16 | PatchShaders(); 17 | 18 | const viewport = new Viewport((delta) => { 19 | controls.update(); 20 | for (let i = 0; i < 4; i++) { 21 | walk.update(delta / 4); 22 | } 23 | }); 24 | 25 | const materials = Materials(); 26 | const storage = new Storage({ chunkSize: 32 }); 27 | const world = new World({ history: true, materials, storage }); 28 | world.addEventListener('change', () => { 29 | viewport.needsUpdate = true; 30 | }); 31 | viewport.scene.add(world); 32 | 33 | const ui = document.getElementById('ui'); 34 | if (!ui) { 35 | throw new Error('Couldn\'t get ui'); 36 | } 37 | const color = new Color(); 38 | const size = new Size(); 39 | const orientation = new Orientation(); 40 | new Exporter(world); 41 | new Snapshot(viewport); 42 | ui.style.display = ''; 43 | 44 | const controls = new OrbitControls(viewport.camera, viewport.dom); 45 | controls.addEventListener('change', () => { 46 | viewport.needsUpdate = true; 47 | }); 48 | controls.enableDamping = true; 49 | controls.enablePan = controls.enableRotate = false; 50 | controls.dampingFactor = 0.1; 51 | controls.maxDistance = 96; 52 | controls.minDistance = 4; 53 | controls.mouseButtons.MIDDLE = undefined; 54 | controls.target.set(0, 8, 0); 55 | viewport.camera.position.set(0, 16, 32); 56 | const walk = new Walk(viewport.camera, controls, world); 57 | 58 | const drawing = new Drawing(viewport.camera, color, orientation, size, world); 59 | viewport.addEventListener('dragstart', ({ pointer, ctrlKey, shiftKey }: any) => { 60 | if (!controls.enablePan && !walk.isEnabled()) { 61 | drawing.start(pointer, ctrlKey, shiftKey); 62 | } 63 | }); 64 | viewport.addEventListener('dragmove', ({ pointer }: any) => ( 65 | drawing.move(pointer) 66 | )); 67 | viewport.addEventListener('dragend', () => ( 68 | drawing.end() 69 | )); 70 | document.addEventListener('keydown', (e) => { 71 | const { ctrlKey, code, repeat, shiftKey } = e; 72 | if (!repeat && code === 'Escape') { 73 | ui.style.display = walk.toggle() ? 'none' : ''; 74 | } 75 | if (!repeat && code === 'Tab') { 76 | e.preventDefault(); 77 | materials.triangles.visible = !materials.triangles.visible; 78 | materials.voxels.visible = !materials.triangles.visible; 79 | viewport.needsUpdate = true; 80 | } 81 | if (!repeat && ctrlKey && code === 'Backspace') { 82 | e.preventDefault(); 83 | localStorage.clear(); 84 | location.reload(); 85 | } 86 | if (walk.isEnabled()) { 87 | return; 88 | } 89 | if (!repeat && code === 'Space') { 90 | controls.enablePan = controls.enableRotate = true; 91 | } 92 | if (!repeat && ['Digit1', 'Digit2', 'Digit3'].includes(code)) { 93 | size.setValue(['Digit1', 'Digit2', 'Digit3'].indexOf(code)); 94 | } 95 | if (!repeat && code === 'KeyE') { 96 | orientation.toggleMode(); 97 | } 98 | if (ctrlKey && code === 'KeyZ') { 99 | e.preventDefault(); 100 | if (shiftKey) { 101 | world.redo(); 102 | } else { 103 | world.undo(); 104 | } 105 | } 106 | }); 107 | document.addEventListener('keyup', ({ code }) => { 108 | if (walk.isEnabled()) { 109 | return; 110 | } 111 | if (code === 'Space') { 112 | controls.enablePan = controls.enableRotate = false; 113 | } 114 | }); 115 | 116 | { 117 | const chunks: { x: number; y: number; z: number; d: number; }[] = []; 118 | const maxY = Math.min( 119 | 1 + storage.listStored().reduce((max, { y }) => Math.max(max, y), 0), 120 | 3 121 | ); 122 | for (let z = -3; z <= 3; z++) { 123 | for (let y = 0; y <= maxY; y++) { 124 | for (let x = -3; x <= 3; x++) { 125 | chunks.push({ x, y, z, d: Math.sqrt(x * x + y * y + z * z)}); 126 | } 127 | } 128 | } 129 | chunks.sort(({ d: a }, { d: b }) => a - b); 130 | chunks.forEach(({ x, y, z }) => ( 131 | world.updateChunk(x, y, z) 132 | )); 133 | } 134 | -------------------------------------------------------------------------------- /example/rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import alias from '@rollup/plugin-alias'; 4 | import copy from 'rollup-plugin-copy'; 5 | import html from '@rollup/plugin-html'; 6 | import livereload from 'rollup-plugin-livereload'; 7 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 8 | import postcss from 'rollup-plugin-postcss'; 9 | import serve from 'rollup-plugin-serve'; 10 | import terser from '@rollup/plugin-terser'; 11 | import typescript from '@rollup/plugin-typescript'; 12 | 13 | const outputPath = path.resolve(__dirname, 'dist'); 14 | const production = !process.env.ROLLUP_WATCH; 15 | 16 | export default { 17 | input: path.join(__dirname, 'main.ts'), 18 | output: { 19 | dir: outputPath, 20 | format: 'iife', 21 | sourcemap: !production, 22 | }, 23 | plugins: [ 24 | alias({ 25 | entries: { 'sculpty': path.join(__dirname, '..', 'dist') }, 26 | }), 27 | nodeResolve({ extensions: ['.js', '.ts'] }), 28 | typescript({ sourceMap: !production }), 29 | postcss({ 30 | extract: 'main.css', 31 | minimize: production, 32 | }), 33 | html({ 34 | template: ({ files }) => ( 35 | fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8') 36 | .replace( 37 | '', 38 | (files.css || []) 39 | .map(({ fileName }) => ``) 40 | ) 41 | .replace( 42 | '', 43 | (files.js || []) 44 | .map(({ fileName }) => ``) 45 | ) 46 | .replace(/( |\n)/g, '') 47 | ), 48 | }), 49 | copy({ 50 | copyOnce: true, 51 | targets: [ 52 | { src: 'favicon.ico', dest: 'dist' }, 53 | ], 54 | }), 55 | ...(production ? [ 56 | terser({ format: { comments: false } }), 57 | { 58 | writeBundle() { 59 | fs.writeFileSync(path.join(outputPath, 'CNAME'), 'sculpty.gatunes.com'); 60 | }, 61 | }, 62 | ] : [ 63 | serve({ 64 | contentBase: outputPath, 65 | port: 8080, 66 | host: '0.0.0.0', 67 | }), 68 | livereload(outputPath), 69 | ]), 70 | ], 71 | watch: { clearScreen: false }, 72 | }; 73 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "sculpty": ["../dist"], 7 | }, 8 | }, 9 | "include": ["."] 10 | } 11 | -------------------------------------------------------------------------------- /example/ui/color.ts: -------------------------------------------------------------------------------- 1 | class Color { 2 | private readonly display: HTMLDivElement; 3 | private readonly input: HTMLInputElement; 4 | public readonly value: { r: number; g: number; b: number; }; 5 | 6 | constructor() { 7 | const container = document.getElementById('brush'); 8 | if (!container) { 9 | throw new Error('Couldn\'t get UI container'); 10 | } 11 | const color = { r: 0, g: 0, b: 0 }; 12 | const toggle = document.createElement('div'); 13 | toggle.id = 'color'; 14 | toggle.addEventListener('click', () => input.click()); 15 | const display = document.createElement('div'); 16 | toggle.appendChild(display); 17 | const input = document.createElement('input'); 18 | input.type = 'color'; 19 | input.addEventListener('input', () => { 20 | const hex = input.value.slice(1); 21 | if (hex.length === 6) { 22 | color.r = parseInt(hex.slice(0, 2), 16); 23 | color.g = parseInt(hex.slice(2, 4), 16); 24 | color.b = parseInt(hex.slice(4, 6), 16); 25 | display.style.backgroundColor = input.value; 26 | } 27 | }); 28 | toggle.appendChild(input); 29 | container.appendChild(toggle); 30 | 31 | this.display = display; 32 | this.input = input; 33 | this.value = color; 34 | this.setValue(0xFF, 0xFF, 0x66); 35 | } 36 | 37 | setValue(r: number, g: number, b: number) { 38 | const { input, display, value } = this; 39 | value.r = r; 40 | value.g = g; 41 | value.b = b; 42 | input.value = `#${('000000' + (value.r << 16 ^ value.g << 8 ^ value.b).toString(16)).slice(-6)}`; 43 | display.style.backgroundColor = input.value; 44 | } 45 | } 46 | 47 | export default Color; 48 | -------------------------------------------------------------------------------- /example/ui/exporter.ts: -------------------------------------------------------------------------------- 1 | import { Group } from 'three'; 2 | import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'; 3 | import { Download } from './icons'; 4 | 5 | class Exporter { 6 | constructor(world: Group) { 7 | const container = document.getElementById('actions'); 8 | if (!container) { 9 | throw new Error('Couldn\'t get UI container'); 10 | } 11 | const exporter = new GLTFExporter(); 12 | const downloader = document.createElement('a'); 13 | downloader.download = 'sculpty.glb'; 14 | downloader.style.display = 'none'; 15 | const action = document.createElement('div'); 16 | action.className = 'action'; 17 | const button = document.createElement('button'); 18 | button.appendChild(Download()); 19 | button.addEventListener('click', () => ( 20 | exporter.parse( 21 | world.children.filter(({ visible }) => visible).map((chunk) => { 22 | const mesh: any = chunk.children[0].clone(); 23 | mesh.position.copy(chunk.position); 24 | mesh.updateMatrix(); 25 | return mesh; 26 | }), 27 | (buffer) => { 28 | const blob = new Blob([buffer as ArrayBuffer]); 29 | if (downloader.href) { 30 | URL.revokeObjectURL(downloader.href); 31 | } 32 | downloader.href = URL.createObjectURL(blob); 33 | downloader.click(); 34 | }, 35 | () => {}, 36 | { binary: true } 37 | ) 38 | )); 39 | action.appendChild(button); 40 | action.appendChild(downloader); 41 | const tooltip = document.createElement('div'); 42 | tooltip.className = 'tooltip'; 43 | tooltip.appendChild(document.createTextNode('Export GLTF')); 44 | action.appendChild(tooltip); 45 | container.appendChild(action); 46 | } 47 | } 48 | 49 | export default Exporter; 50 | -------------------------------------------------------------------------------- /example/ui/icons.ts: -------------------------------------------------------------------------------- 1 | const Icon = (paths: any[], width: number = 24, height: number = 24, x: number = 0, y: number = 0) => () => { 2 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 3 | svg.setAttribute('fill', 'none'); 4 | svg.setAttribute('viewBox', `${x} ${y} ${width} ${height}`); 5 | paths.forEach((attributes) => { 6 | const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 7 | Object.keys(attributes).forEach((attribute) => ( 8 | path.setAttribute(attribute, attributes[attribute]) 9 | )); 10 | svg.appendChild(path); 11 | }); 12 | return svg; 13 | }; 14 | 15 | export const Download = Icon( 16 | [ 17 | { 18 | d: 'M17 12L12 17M12 17L7 12M12 17V4M17 20H7', 19 | stroke: 'currentColor', 20 | strokeWidth: '2', 21 | strokeLinecap: 'round', 22 | strokeLinejoin: 'round' 23 | }, 24 | ], 25 | ); 26 | 27 | export const Surface = Icon( 28 | [ 29 | { 30 | d: `M 34,39.1716L 34,22.4583L 28,28.0833L 28,22.5833L 36,15.0833L 44,22.5833L 44,28.0833L 38,22.4583L 38,38L 54.625,38L 49,32L 54.5,32L 62,40L 54.5,48L 49,48L 54.625,42L 36.8284,42L 25.8284,53L 34,53L 30,57L 19,57L 19,46L 23,42L 23,50.1716L 34,39.1716 Z`, 31 | fill: 'currentColor', 32 | strokeLinejoin: 'round', 33 | }, 34 | ], 35 | 60, 60, 8, 8 36 | ); 37 | 38 | export const Camera = Icon( 39 | [ 40 | { 41 | d: 'M17 11V8.5C17 7.67157 16.3284 7 15.5 7H5.5C4.67157 7 4 7.67157 4 8.5V16.5C4 17.3284 4.67157 18 5.5 18H15.5C16.3284 18 17 17.3284 17 16.5V14.5', 42 | stroke: 'currentColor', 43 | strokeWidth: '2', 44 | strokeLinecap: 'round', 45 | }, 46 | { 47 | d: 'M17 11L20.2764 9.3618C20.6088 9.19558 21 9.43733 21 9.80902V15.2785C21 15.6276 20.6513 15.8692 20.3244 15.7467L17 14.5', 48 | stroke: 'currentColor', 49 | strokeWidth: '2', 50 | strokeLinecap: 'round', 51 | }, 52 | ], 53 | ); 54 | 55 | export const Snapshot = Icon( 56 | [ 57 | { 58 | d: 'M15.0858 3.58579C14.7107 3.21071 14.202 3 13.6716 3H10.3284C9.79799 3 9.28929 3.21071 8.91421 3.58579L8.08579 4.41421C7.71071 4.78929 7.20201 5 6.67157 5H5C3.89543 5 3 5.89543 3 7L3 17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V7C21 5.89543 20.1046 5 19 5H17.3284C16.798 5 16.2893 4.78929 15.9142 4.41421L15.0858 3.58579Z', 59 | stroke: 'currentColor', 60 | strokeWidth: '2', 61 | strokeLinecap: 'round', 62 | strokeLinejoin: 'round', 63 | }, 64 | { 65 | d: 'M12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z', 66 | stroke: 'currentColor', 67 | strokeWidth: '2', 68 | strokeLinecap: 'round', 69 | strokeLinejoin: 'round', 70 | fillRule: 'evenodd', 71 | clipRule: 'evenodd', 72 | }, 73 | ], 74 | ); 75 | -------------------------------------------------------------------------------- /example/ui/orientation.ts: -------------------------------------------------------------------------------- 1 | import { Camera, Surface } from './icons'; 2 | 3 | export enum OrientationMode { 4 | camera = 1, 5 | surface = 2, 6 | } 7 | 8 | const icons = { 9 | [OrientationMode.camera]: Camera(), 10 | [OrientationMode.surface]: Surface(), 11 | }; 12 | const tooltips = { 13 | [OrientationMode.camera]: 'Draw paralell to camera', 14 | [OrientationMode.surface]: 'Draw paralell to surface', 15 | }; 16 | 17 | class Orientation { 18 | private readonly button: HTMLButtonElement; 19 | private readonly tooltip: HTMLDivElement; 20 | public mode: OrientationMode = OrientationMode.camera; 21 | 22 | constructor() { 23 | const container = document.getElementById('brush'); 24 | if (!container) { 25 | throw new Error('Couldn\'t get UI container'); 26 | } 27 | const action = document.createElement('div'); 28 | action.className = 'action'; 29 | const button = document.createElement('button'); 30 | const tooltip = document.createElement('div'); 31 | tooltip.className = 'tooltip'; 32 | button.addEventListener('click', () => this.toggleMode()); 33 | action.appendChild(button); 34 | container.appendChild(action); 35 | action.appendChild(tooltip); 36 | 37 | this.button = button; 38 | this.tooltip = tooltip; 39 | this.setMode(this.mode); 40 | } 41 | 42 | setMode(mode: OrientationMode) { 43 | const { button, tooltip } = this; 44 | this.mode = mode; 45 | if (button.firstChild) button.removeChild(button.firstChild); 46 | button.appendChild(icons[mode]); 47 | if (tooltip.firstChild) tooltip.removeChild(tooltip.firstChild); 48 | tooltip.appendChild(document.createTextNode(tooltips[this.mode])); 49 | } 50 | 51 | toggleMode() { 52 | this.setMode( 53 | this.mode === OrientationMode.camera ? OrientationMode.surface : OrientationMode.camera 54 | ); 55 | } 56 | } 57 | 58 | export default Orientation; 59 | -------------------------------------------------------------------------------- /example/ui/size.ts: -------------------------------------------------------------------------------- 1 | class Size { 2 | private readonly buttons: HTMLButtonElement[]; 3 | public value: number = 0; 4 | 5 | constructor() { 6 | const container = document.getElementById('brush'); 7 | if (!container) { 8 | throw new Error('Couldn\'t get UI container'); 9 | } 10 | const wrapper = document.createElement('div'); 11 | wrapper.id = 'size'; 12 | this.buttons = ['Small', 'Medium', 'Large'].map((size, i) => { 13 | const action = document.createElement('div'); 14 | action.className = 'action'; 15 | const button = document.createElement('button'); 16 | const circle = document.createElement('span'); 17 | circle.style.height = `${(i + 1) / 3 * 80}%`; 18 | button.appendChild(circle); 19 | button.addEventListener('click', () => this.setValue(i)); 20 | action.appendChild(button); 21 | const tooltip = document.createElement('div'); 22 | tooltip.className = 'tooltip'; 23 | tooltip.appendChild(document.createTextNode(`${size} brush`)); 24 | action.appendChild(tooltip); 25 | wrapper.appendChild(action); 26 | return button; 27 | }); 28 | container.appendChild(wrapper); 29 | 30 | this.setValue(0); 31 | } 32 | 33 | setValue(value: number) { 34 | const { buttons } = this; 35 | this.value = value; 36 | buttons.forEach((button, i) => { 37 | button.classList.remove('active'); 38 | if (i === value) { 39 | button.classList.add('active'); 40 | } 41 | }); 42 | this.value = value; 43 | } 44 | } 45 | 46 | export default Size; 47 | -------------------------------------------------------------------------------- /example/ui/snapshot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PerspectiveCamera, 3 | Scene, 4 | WebGLRenderer, 5 | WebGLRenderTarget, 6 | } from 'three'; 7 | import PostProcessing from '../core/postprocessing'; 8 | import { Snapshot as Icon } from './icons'; 9 | 10 | class Snapshot { 11 | constructor({ 12 | camera, 13 | postprocessing, 14 | renderer, 15 | scene, 16 | width = 3840, 17 | height = 2160 18 | }: { 19 | camera: PerspectiveCamera; 20 | postprocessing: PostProcessing; 21 | renderer: WebGLRenderer; 22 | scene: Scene; 23 | width?: number; 24 | height?: number; 25 | }) { 26 | const container = document.getElementById('actions'); 27 | if (!container) { 28 | throw new Error('Couldn\'t get UI container'); 29 | } 30 | const canvas = document.createElement('canvas'); 31 | const ctx = canvas.getContext('2d'); 32 | if (!ctx) { 33 | throw new Error('Couldn\'t get 2D context'); 34 | } 35 | const output = document.createElement('canvas'); 36 | const outputCtx = output.getContext('2d'); 37 | if (!outputCtx) { 38 | throw new Error('Couldn\'t get 2D context'); 39 | } 40 | const downloader = document.createElement('a'); 41 | downloader.download = 'sculpty.png'; 42 | downloader.style.display = 'none'; 43 | const pixels = new ImageData(width, height); 44 | const target = new WebGLRenderTarget(width, height); 45 | const action = document.createElement('div'); 46 | action.className = 'action'; 47 | const button = document.createElement('button'); 48 | button.appendChild(Icon()); 49 | button.addEventListener('click', () => { 50 | renderer.setSize(width, height); 51 | postprocessing.setSize(width, height, true, 2); 52 | camera.aspect = width / height; 53 | camera.updateProjectionMatrix(); 54 | postprocessing.render(renderer, camera, scene, target); 55 | renderer.setSize(window.innerWidth, window.innerHeight); 56 | postprocessing.setSize(window.innerWidth, window.innerHeight); 57 | camera.aspect = window.innerWidth / window.innerHeight; 58 | camera.updateProjectionMatrix(); 59 | renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels.data); 60 | canvas.width = width; 61 | canvas.height = height; 62 | ctx.putImageData(pixels, 0, 0); 63 | output.width = width * 0.5; 64 | output.height = height * 0.5; 65 | outputCtx.imageSmoothingQuality = 'high'; 66 | outputCtx.drawImage(canvas, 0, 0, width, height, 0, 0, output.width, output.height); 67 | output.toBlob((blob) => { 68 | if (!blob) { 69 | return; 70 | } 71 | if (downloader.href) { 72 | URL.revokeObjectURL(downloader.href); 73 | } 74 | downloader.href = URL.createObjectURL(blob); 75 | downloader.click(); 76 | }); 77 | }); 78 | action.appendChild(button); 79 | action.appendChild(downloader); 80 | const tooltip = document.createElement('div'); 81 | tooltip.className = 'tooltip'; 82 | tooltip.appendChild(document.createTextNode('Take snapshot')); 83 | action.appendChild(tooltip); 84 | container.appendChild(action); 85 | } 86 | } 87 | 88 | export default Snapshot; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sculpty", 3 | "scripts": { 4 | "build": "rollup -c rollup.config.js", 5 | "build:example": "cd example && rollup -c rollup.config.js", 6 | "compile:mesher": "sh src/workers/compile.sh mesher", 7 | "watch": "pnpm build -w", 8 | "start": "pnpm build:example -w" 9 | }, 10 | "dependencies": { 11 | "fastnoise-lite": "^0.0.1", 12 | "fflate": "^0.7.4", 13 | "three": "^0.149.0", 14 | "three-mesh-bvh": "^0.5.22" 15 | }, 16 | "devDependencies": { 17 | "@rollup/plugin-alias": "^4.0.3", 18 | "@rollup/plugin-html": "^1.0.2", 19 | "@rollup/plugin-node-resolve": "^15.0.1", 20 | "@rollup/plugin-terser": "^0.4.0", 21 | "@rollup/plugin-typescript": "^11.0.0", 22 | "@rollup/plugin-wasm": "^6.1.2", 23 | "@types/three": "^0.148.1", 24 | "postcss": "^8.4.21", 25 | "rollup": "^2.79.1", 26 | "rollup-plugin-copy": "^3.4.0", 27 | "rollup-plugin-livereload": "^2.0.5", 28 | "rollup-plugin-postcss": "^4.0.2", 29 | "rollup-plugin-serve": "^2.0.2", 30 | "rollup-plugin-web-worker-loader": "^1.6.1", 31 | "tslib": "^2.5.0", 32 | "typescript": "^4.9.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | '@rollup/plugin-alias': ^4.0.3 5 | '@rollup/plugin-html': ^1.0.2 6 | '@rollup/plugin-node-resolve': ^15.0.1 7 | '@rollup/plugin-terser': ^0.4.0 8 | '@rollup/plugin-typescript': ^11.0.0 9 | '@rollup/plugin-wasm': ^6.1.2 10 | '@types/three': ^0.148.1 11 | fastnoise-lite: ^0.0.1 12 | fflate: ^0.7.4 13 | postcss: ^8.4.21 14 | rollup: ^2.79.1 15 | rollup-plugin-copy: ^3.4.0 16 | rollup-plugin-livereload: ^2.0.5 17 | rollup-plugin-postcss: ^4.0.2 18 | rollup-plugin-serve: ^2.0.2 19 | rollup-plugin-web-worker-loader: ^1.6.1 20 | three: ^0.149.0 21 | three-mesh-bvh: ^0.5.22 22 | tslib: ^2.5.0 23 | typescript: ^4.9.4 24 | 25 | dependencies: 26 | fastnoise-lite: 0.0.1 27 | fflate: 0.7.4 28 | three: 0.149.0 29 | three-mesh-bvh: 0.5.22_three@0.149.0 30 | 31 | devDependencies: 32 | '@rollup/plugin-alias': 4.0.3_rollup@2.79.1 33 | '@rollup/plugin-html': 1.0.2_rollup@2.79.1 34 | '@rollup/plugin-node-resolve': 15.0.1_rollup@2.79.1 35 | '@rollup/plugin-terser': 0.4.0_rollup@2.79.1 36 | '@rollup/plugin-typescript': 11.0.0_bgwi2fgwyi362qsw25gfipc3ca 37 | '@rollup/plugin-wasm': 6.1.2_rollup@2.79.1 38 | '@types/three': 0.148.1 39 | postcss: 8.4.21 40 | rollup: 2.79.1 41 | rollup-plugin-copy: 3.4.0 42 | rollup-plugin-livereload: 2.0.5 43 | rollup-plugin-postcss: 4.0.2_postcss@8.4.21 44 | rollup-plugin-serve: 2.0.2 45 | rollup-plugin-web-worker-loader: 1.6.1_rollup@2.79.1 46 | tslib: 2.5.0 47 | typescript: 4.9.4 48 | 49 | packages: 50 | 51 | /@jridgewell/gen-mapping/0.3.2: 52 | resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} 53 | engines: {node: '>=6.0.0'} 54 | dependencies: 55 | '@jridgewell/set-array': 1.1.2 56 | '@jridgewell/sourcemap-codec': 1.4.14 57 | '@jridgewell/trace-mapping': 0.3.17 58 | dev: true 59 | 60 | /@jridgewell/resolve-uri/3.1.0: 61 | resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} 62 | engines: {node: '>=6.0.0'} 63 | dev: true 64 | 65 | /@jridgewell/set-array/1.1.2: 66 | resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} 67 | engines: {node: '>=6.0.0'} 68 | dev: true 69 | 70 | /@jridgewell/source-map/0.3.2: 71 | resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} 72 | dependencies: 73 | '@jridgewell/gen-mapping': 0.3.2 74 | '@jridgewell/trace-mapping': 0.3.17 75 | dev: true 76 | 77 | /@jridgewell/sourcemap-codec/1.4.14: 78 | resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} 79 | dev: true 80 | 81 | /@jridgewell/trace-mapping/0.3.17: 82 | resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} 83 | dependencies: 84 | '@jridgewell/resolve-uri': 3.1.0 85 | '@jridgewell/sourcemap-codec': 1.4.14 86 | dev: true 87 | 88 | /@nodelib/fs.scandir/2.1.5: 89 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 90 | engines: {node: '>= 8'} 91 | dependencies: 92 | '@nodelib/fs.stat': 2.0.5 93 | run-parallel: 1.2.0 94 | dev: true 95 | 96 | /@nodelib/fs.stat/2.0.5: 97 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 98 | engines: {node: '>= 8'} 99 | dev: true 100 | 101 | /@nodelib/fs.walk/1.2.8: 102 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 103 | engines: {node: '>= 8'} 104 | dependencies: 105 | '@nodelib/fs.scandir': 2.1.5 106 | fastq: 1.15.0 107 | dev: true 108 | 109 | /@rollup/plugin-alias/4.0.3_rollup@2.79.1: 110 | resolution: {integrity: sha512-ZuDWE1q4PQDhvm/zc5Prun8sBpLJy41DMptYrS6MhAy9s9kL/doN1613BWfEchGVfKxzliJ3BjbOPizXX38DbQ==} 111 | engines: {node: '>=14.0.0'} 112 | peerDependencies: 113 | rollup: ^1.20.0||^2.0.0||^3.0.0 114 | peerDependenciesMeta: 115 | rollup: 116 | optional: true 117 | dependencies: 118 | rollup: 2.79.1 119 | slash: 4.0.0 120 | dev: true 121 | 122 | /@rollup/plugin-html/1.0.2_rollup@2.79.1: 123 | resolution: {integrity: sha512-jGqb45BPj5kwvb/bq1jIzUDLebsm1xmfnY1rwgTIZyjpsMyMKLuQO27n4z5qv6kZmxqxh+CBRD7d1rjAu85Uzg==} 124 | engines: {node: '>=14.0.0'} 125 | peerDependencies: 126 | rollup: ^1.20.0||^2.0.0||^3.0.0 127 | peerDependenciesMeta: 128 | rollup: 129 | optional: true 130 | dependencies: 131 | rollup: 2.79.1 132 | dev: true 133 | 134 | /@rollup/plugin-node-resolve/15.0.1_rollup@2.79.1: 135 | resolution: {integrity: sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==} 136 | engines: {node: '>=14.0.0'} 137 | peerDependencies: 138 | rollup: ^2.78.0||^3.0.0 139 | peerDependenciesMeta: 140 | rollup: 141 | optional: true 142 | dependencies: 143 | '@rollup/pluginutils': 5.0.2_rollup@2.79.1 144 | '@types/resolve': 1.20.2 145 | deepmerge: 4.2.2 146 | is-builtin-module: 3.2.0 147 | is-module: 1.0.0 148 | resolve: 1.22.1 149 | rollup: 2.79.1 150 | dev: true 151 | 152 | /@rollup/plugin-terser/0.4.0_rollup@2.79.1: 153 | resolution: {integrity: sha512-Ipcf3LPNerey1q9ZMjiaWHlNPEHNU/B5/uh9zXLltfEQ1lVSLLeZSgAtTPWGyw8Ip1guOeq+mDtdOlEj/wNxQw==} 154 | engines: {node: '>=14.0.0'} 155 | peerDependencies: 156 | rollup: ^2.x || ^3.x 157 | peerDependenciesMeta: 158 | rollup: 159 | optional: true 160 | dependencies: 161 | rollup: 2.79.1 162 | serialize-javascript: 6.0.1 163 | smob: 0.0.6 164 | terser: 5.16.1 165 | dev: true 166 | 167 | /@rollup/plugin-typescript/11.0.0_bgwi2fgwyi362qsw25gfipc3ca: 168 | resolution: {integrity: sha512-goPyCWBiimk1iJgSTgsehFD5OOFHiAknrRJjqFCudcW8JtWiBlK284Xnn4flqMqg6YAjVG/EE+3aVzrL5qNSzQ==} 169 | engines: {node: '>=14.0.0'} 170 | peerDependencies: 171 | rollup: ^2.14.0||^3.0.0 172 | tslib: '*' 173 | typescript: '>=3.7.0' 174 | peerDependenciesMeta: 175 | rollup: 176 | optional: true 177 | tslib: 178 | optional: true 179 | dependencies: 180 | '@rollup/pluginutils': 5.0.2_rollup@2.79.1 181 | resolve: 1.22.1 182 | rollup: 2.79.1 183 | tslib: 2.5.0 184 | typescript: 4.9.4 185 | dev: true 186 | 187 | /@rollup/plugin-wasm/6.1.2_rollup@2.79.1: 188 | resolution: {integrity: sha512-YdrQ7zfnZ54Y+6raCev3tR1PrhQGxYKSTajGylhyP0oBacouuNo6KcNCk+pYKw9M98jxRWLFFca/udi76IDXzg==} 189 | engines: {node: '>=14.0.0'} 190 | peerDependencies: 191 | rollup: ^1.20.0||^2.0.0||^3.0.0 192 | peerDependenciesMeta: 193 | rollup: 194 | optional: true 195 | dependencies: 196 | rollup: 2.79.1 197 | dev: true 198 | 199 | /@rollup/pluginutils/5.0.2_rollup@2.79.1: 200 | resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} 201 | engines: {node: '>=14.0.0'} 202 | peerDependencies: 203 | rollup: ^1.20.0||^2.0.0||^3.0.0 204 | peerDependenciesMeta: 205 | rollup: 206 | optional: true 207 | dependencies: 208 | '@types/estree': 1.0.0 209 | estree-walker: 2.0.2 210 | picomatch: 2.3.1 211 | rollup: 2.79.1 212 | dev: true 213 | 214 | /@trysound/sax/0.2.0: 215 | resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} 216 | engines: {node: '>=10.13.0'} 217 | dev: true 218 | 219 | /@types/estree/1.0.0: 220 | resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} 221 | dev: true 222 | 223 | /@types/fs-extra/8.1.2: 224 | resolution: {integrity: sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==} 225 | dependencies: 226 | '@types/node': 18.11.18 227 | dev: true 228 | 229 | /@types/glob/7.2.0: 230 | resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} 231 | dependencies: 232 | '@types/minimatch': 5.1.2 233 | '@types/node': 18.11.18 234 | dev: true 235 | 236 | /@types/minimatch/5.1.2: 237 | resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} 238 | dev: true 239 | 240 | /@types/node/18.11.18: 241 | resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} 242 | dev: true 243 | 244 | /@types/resolve/1.20.2: 245 | resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} 246 | dev: true 247 | 248 | /@types/three/0.148.1: 249 | resolution: {integrity: sha512-gZwIyTBMxKXqJHXmZ0dzvDieuQ4hz8MPNHtkRrAwER/xPlAh9eP2WIfaolvQY+wJAzlNV5a9ceS4JT+i/jybsw==} 250 | dependencies: 251 | '@types/webxr': 0.5.1 252 | dev: true 253 | 254 | /@types/webxr/0.5.1: 255 | resolution: {integrity: sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==} 256 | dev: true 257 | 258 | /acorn/8.8.2: 259 | resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} 260 | engines: {node: '>=0.4.0'} 261 | hasBin: true 262 | dev: true 263 | 264 | /ansi-styles/4.3.0: 265 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 266 | engines: {node: '>=8'} 267 | dependencies: 268 | color-convert: 2.0.1 269 | dev: true 270 | 271 | /anymatch/3.1.3: 272 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 273 | engines: {node: '>= 8'} 274 | dependencies: 275 | normalize-path: 3.0.0 276 | picomatch: 2.3.1 277 | dev: true 278 | 279 | /array-union/2.1.0: 280 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 281 | engines: {node: '>=8'} 282 | dev: true 283 | 284 | /balanced-match/1.0.2: 285 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 286 | dev: true 287 | 288 | /binary-extensions/2.2.0: 289 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 290 | engines: {node: '>=8'} 291 | dev: true 292 | 293 | /boolbase/1.0.0: 294 | resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} 295 | dev: true 296 | 297 | /brace-expansion/1.1.11: 298 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 299 | dependencies: 300 | balanced-match: 1.0.2 301 | concat-map: 0.0.1 302 | dev: true 303 | 304 | /braces/3.0.2: 305 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 306 | engines: {node: '>=8'} 307 | dependencies: 308 | fill-range: 7.0.1 309 | dev: true 310 | 311 | /browserslist/4.21.4: 312 | resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} 313 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 314 | hasBin: true 315 | dependencies: 316 | caniuse-lite: 1.0.30001447 317 | electron-to-chromium: 1.4.284 318 | node-releases: 2.0.8 319 | update-browserslist-db: 1.0.10_browserslist@4.21.4 320 | dev: true 321 | 322 | /buffer-from/1.1.2: 323 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 324 | dev: true 325 | 326 | /builtin-modules/3.3.0: 327 | resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} 328 | engines: {node: '>=6'} 329 | dev: true 330 | 331 | /caniuse-api/3.0.0: 332 | resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} 333 | dependencies: 334 | browserslist: 4.21.4 335 | caniuse-lite: 1.0.30001447 336 | lodash.memoize: 4.1.2 337 | lodash.uniq: 4.5.0 338 | dev: true 339 | 340 | /caniuse-lite/1.0.30001447: 341 | resolution: {integrity: sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==} 342 | dev: true 343 | 344 | /chalk/4.1.2: 345 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 346 | engines: {node: '>=10'} 347 | dependencies: 348 | ansi-styles: 4.3.0 349 | supports-color: 7.2.0 350 | dev: true 351 | 352 | /chokidar/3.5.3: 353 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 354 | engines: {node: '>= 8.10.0'} 355 | dependencies: 356 | anymatch: 3.1.3 357 | braces: 3.0.2 358 | glob-parent: 5.1.2 359 | is-binary-path: 2.1.0 360 | is-glob: 4.0.3 361 | normalize-path: 3.0.0 362 | readdirp: 3.6.0 363 | optionalDependencies: 364 | fsevents: 2.3.2 365 | dev: true 366 | 367 | /color-convert/2.0.1: 368 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 369 | engines: {node: '>=7.0.0'} 370 | dependencies: 371 | color-name: 1.1.4 372 | dev: true 373 | 374 | /color-name/1.1.4: 375 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 376 | dev: true 377 | 378 | /colord/2.9.3: 379 | resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} 380 | dev: true 381 | 382 | /colorette/1.4.0: 383 | resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} 384 | dev: true 385 | 386 | /commander/2.20.3: 387 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 388 | dev: true 389 | 390 | /commander/7.2.0: 391 | resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} 392 | engines: {node: '>= 10'} 393 | dev: true 394 | 395 | /concat-map/0.0.1: 396 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 397 | dev: true 398 | 399 | /concat-with-sourcemaps/1.1.0: 400 | resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} 401 | dependencies: 402 | source-map: 0.6.1 403 | dev: true 404 | 405 | /css-declaration-sorter/6.3.1_postcss@8.4.21: 406 | resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} 407 | engines: {node: ^10 || ^12 || >=14} 408 | peerDependencies: 409 | postcss: ^8.0.9 410 | dependencies: 411 | postcss: 8.4.21 412 | dev: true 413 | 414 | /css-select/4.3.0: 415 | resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} 416 | dependencies: 417 | boolbase: 1.0.0 418 | css-what: 6.1.0 419 | domhandler: 4.3.1 420 | domutils: 2.8.0 421 | nth-check: 2.1.1 422 | dev: true 423 | 424 | /css-tree/1.1.3: 425 | resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} 426 | engines: {node: '>=8.0.0'} 427 | dependencies: 428 | mdn-data: 2.0.14 429 | source-map: 0.6.1 430 | dev: true 431 | 432 | /css-what/6.1.0: 433 | resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} 434 | engines: {node: '>= 6'} 435 | dev: true 436 | 437 | /cssesc/3.0.0: 438 | resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 439 | engines: {node: '>=4'} 440 | hasBin: true 441 | dev: true 442 | 443 | /cssnano-preset-default/5.2.13_postcss@8.4.21: 444 | resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==} 445 | engines: {node: ^10 || ^12 || >=14.0} 446 | peerDependencies: 447 | postcss: ^8.2.15 448 | dependencies: 449 | css-declaration-sorter: 6.3.1_postcss@8.4.21 450 | cssnano-utils: 3.1.0_postcss@8.4.21 451 | postcss: 8.4.21 452 | postcss-calc: 8.2.4_postcss@8.4.21 453 | postcss-colormin: 5.3.0_postcss@8.4.21 454 | postcss-convert-values: 5.1.3_postcss@8.4.21 455 | postcss-discard-comments: 5.1.2_postcss@8.4.21 456 | postcss-discard-duplicates: 5.1.0_postcss@8.4.21 457 | postcss-discard-empty: 5.1.1_postcss@8.4.21 458 | postcss-discard-overridden: 5.1.0_postcss@8.4.21 459 | postcss-merge-longhand: 5.1.7_postcss@8.4.21 460 | postcss-merge-rules: 5.1.3_postcss@8.4.21 461 | postcss-minify-font-values: 5.1.0_postcss@8.4.21 462 | postcss-minify-gradients: 5.1.1_postcss@8.4.21 463 | postcss-minify-params: 5.1.4_postcss@8.4.21 464 | postcss-minify-selectors: 5.2.1_postcss@8.4.21 465 | postcss-normalize-charset: 5.1.0_postcss@8.4.21 466 | postcss-normalize-display-values: 5.1.0_postcss@8.4.21 467 | postcss-normalize-positions: 5.1.1_postcss@8.4.21 468 | postcss-normalize-repeat-style: 5.1.1_postcss@8.4.21 469 | postcss-normalize-string: 5.1.0_postcss@8.4.21 470 | postcss-normalize-timing-functions: 5.1.0_postcss@8.4.21 471 | postcss-normalize-unicode: 5.1.1_postcss@8.4.21 472 | postcss-normalize-url: 5.1.0_postcss@8.4.21 473 | postcss-normalize-whitespace: 5.1.1_postcss@8.4.21 474 | postcss-ordered-values: 5.1.3_postcss@8.4.21 475 | postcss-reduce-initial: 5.1.1_postcss@8.4.21 476 | postcss-reduce-transforms: 5.1.0_postcss@8.4.21 477 | postcss-svgo: 5.1.0_postcss@8.4.21 478 | postcss-unique-selectors: 5.1.1_postcss@8.4.21 479 | dev: true 480 | 481 | /cssnano-utils/3.1.0_postcss@8.4.21: 482 | resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} 483 | engines: {node: ^10 || ^12 || >=14.0} 484 | peerDependencies: 485 | postcss: ^8.2.15 486 | dependencies: 487 | postcss: 8.4.21 488 | dev: true 489 | 490 | /cssnano/5.1.14_postcss@8.4.21: 491 | resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==} 492 | engines: {node: ^10 || ^12 || >=14.0} 493 | peerDependencies: 494 | postcss: ^8.2.15 495 | dependencies: 496 | cssnano-preset-default: 5.2.13_postcss@8.4.21 497 | lilconfig: 2.0.6 498 | postcss: 8.4.21 499 | yaml: 1.10.2 500 | dev: true 501 | 502 | /csso/4.2.0: 503 | resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} 504 | engines: {node: '>=8.0.0'} 505 | dependencies: 506 | css-tree: 1.1.3 507 | dev: true 508 | 509 | /deepmerge/4.2.2: 510 | resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} 511 | engines: {node: '>=0.10.0'} 512 | dev: true 513 | 514 | /dir-glob/3.0.1: 515 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 516 | engines: {node: '>=8'} 517 | dependencies: 518 | path-type: 4.0.0 519 | dev: true 520 | 521 | /dom-serializer/1.4.1: 522 | resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} 523 | dependencies: 524 | domelementtype: 2.3.0 525 | domhandler: 4.3.1 526 | entities: 2.2.0 527 | dev: true 528 | 529 | /domelementtype/2.3.0: 530 | resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} 531 | dev: true 532 | 533 | /domhandler/4.3.1: 534 | resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} 535 | engines: {node: '>= 4'} 536 | dependencies: 537 | domelementtype: 2.3.0 538 | dev: true 539 | 540 | /domutils/2.8.0: 541 | resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} 542 | dependencies: 543 | dom-serializer: 1.4.1 544 | domelementtype: 2.3.0 545 | domhandler: 4.3.1 546 | dev: true 547 | 548 | /electron-to-chromium/1.4.284: 549 | resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} 550 | dev: true 551 | 552 | /entities/2.2.0: 553 | resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} 554 | dev: true 555 | 556 | /escalade/3.1.1: 557 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 558 | engines: {node: '>=6'} 559 | dev: true 560 | 561 | /estree-walker/0.6.1: 562 | resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} 563 | dev: true 564 | 565 | /estree-walker/2.0.2: 566 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 567 | dev: true 568 | 569 | /eventemitter3/4.0.7: 570 | resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} 571 | dev: true 572 | 573 | /fast-glob/3.2.12: 574 | resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} 575 | engines: {node: '>=8.6.0'} 576 | dependencies: 577 | '@nodelib/fs.stat': 2.0.5 578 | '@nodelib/fs.walk': 1.2.8 579 | glob-parent: 5.1.2 580 | merge2: 1.4.1 581 | micromatch: 4.0.5 582 | dev: true 583 | 584 | /fastnoise-lite/0.0.1: 585 | resolution: {integrity: sha512-NqvFnDOB+F8SpsK3dNNU0JSchNujV/O5taEXleEE4jTsxjBIfYQTYx9nXyTlKZKHteHYoIi67zBAnmurI2YTvw==} 586 | dev: false 587 | 588 | /fastq/1.15.0: 589 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 590 | dependencies: 591 | reusify: 1.0.4 592 | dev: true 593 | 594 | /fflate/0.7.4: 595 | resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} 596 | dev: false 597 | 598 | /fill-range/7.0.1: 599 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 600 | engines: {node: '>=8'} 601 | dependencies: 602 | to-regex-range: 5.0.1 603 | dev: true 604 | 605 | /fs-extra/8.1.0: 606 | resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 607 | engines: {node: '>=6 <7 || >=8'} 608 | dependencies: 609 | graceful-fs: 4.2.10 610 | jsonfile: 4.0.0 611 | universalify: 0.1.2 612 | dev: true 613 | 614 | /fs.realpath/1.0.0: 615 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 616 | dev: true 617 | 618 | /fsevents/2.3.2: 619 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 620 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 621 | os: [darwin] 622 | requiresBuild: true 623 | dev: true 624 | optional: true 625 | 626 | /function-bind/1.1.1: 627 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 628 | dev: true 629 | 630 | /generic-names/4.0.0: 631 | resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} 632 | dependencies: 633 | loader-utils: 3.2.1 634 | dev: true 635 | 636 | /glob-parent/5.1.2: 637 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 638 | engines: {node: '>= 6'} 639 | dependencies: 640 | is-glob: 4.0.3 641 | dev: true 642 | 643 | /glob/7.2.3: 644 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 645 | dependencies: 646 | fs.realpath: 1.0.0 647 | inflight: 1.0.6 648 | inherits: 2.0.4 649 | minimatch: 3.1.2 650 | once: 1.4.0 651 | path-is-absolute: 1.0.1 652 | dev: true 653 | 654 | /globby/10.0.1: 655 | resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==} 656 | engines: {node: '>=8'} 657 | dependencies: 658 | '@types/glob': 7.2.0 659 | array-union: 2.1.0 660 | dir-glob: 3.0.1 661 | fast-glob: 3.2.12 662 | glob: 7.2.3 663 | ignore: 5.2.4 664 | merge2: 1.4.1 665 | slash: 3.0.0 666 | dev: true 667 | 668 | /graceful-fs/4.2.10: 669 | resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} 670 | dev: true 671 | 672 | /has-flag/4.0.0: 673 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 674 | engines: {node: '>=8'} 675 | dev: true 676 | 677 | /has/1.0.3: 678 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 679 | engines: {node: '>= 0.4.0'} 680 | dependencies: 681 | function-bind: 1.1.1 682 | dev: true 683 | 684 | /icss-replace-symbols/1.1.0: 685 | resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} 686 | dev: true 687 | 688 | /icss-utils/5.1.0_postcss@8.4.21: 689 | resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} 690 | engines: {node: ^10 || ^12 || >= 14} 691 | peerDependencies: 692 | postcss: ^8.1.0 693 | dependencies: 694 | postcss: 8.4.21 695 | dev: true 696 | 697 | /ignore/5.2.4: 698 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 699 | engines: {node: '>= 4'} 700 | dev: true 701 | 702 | /import-cwd/3.0.0: 703 | resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} 704 | engines: {node: '>=8'} 705 | dependencies: 706 | import-from: 3.0.0 707 | dev: true 708 | 709 | /import-from/3.0.0: 710 | resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} 711 | engines: {node: '>=8'} 712 | dependencies: 713 | resolve-from: 5.0.0 714 | dev: true 715 | 716 | /inflight/1.0.6: 717 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 718 | dependencies: 719 | once: 1.4.0 720 | wrappy: 1.0.2 721 | dev: true 722 | 723 | /inherits/2.0.4: 724 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 725 | dev: true 726 | 727 | /is-binary-path/2.1.0: 728 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 729 | engines: {node: '>=8'} 730 | dependencies: 731 | binary-extensions: 2.2.0 732 | dev: true 733 | 734 | /is-builtin-module/3.2.0: 735 | resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==} 736 | engines: {node: '>=6'} 737 | dependencies: 738 | builtin-modules: 3.3.0 739 | dev: true 740 | 741 | /is-core-module/2.11.0: 742 | resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} 743 | dependencies: 744 | has: 1.0.3 745 | dev: true 746 | 747 | /is-extglob/2.1.1: 748 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 749 | engines: {node: '>=0.10.0'} 750 | dev: true 751 | 752 | /is-glob/4.0.3: 753 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 754 | engines: {node: '>=0.10.0'} 755 | dependencies: 756 | is-extglob: 2.1.1 757 | dev: true 758 | 759 | /is-module/1.0.0: 760 | resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} 761 | dev: true 762 | 763 | /is-number/7.0.0: 764 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 765 | engines: {node: '>=0.12.0'} 766 | dev: true 767 | 768 | /is-plain-object/3.0.1: 769 | resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} 770 | engines: {node: '>=0.10.0'} 771 | dev: true 772 | 773 | /jsonfile/4.0.0: 774 | resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 775 | optionalDependencies: 776 | graceful-fs: 4.2.10 777 | dev: true 778 | 779 | /lilconfig/2.0.6: 780 | resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} 781 | engines: {node: '>=10'} 782 | dev: true 783 | 784 | /livereload-js/3.4.1: 785 | resolution: {integrity: sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==} 786 | dev: true 787 | 788 | /livereload/0.9.3: 789 | resolution: {integrity: sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==} 790 | engines: {node: '>=8.0.0'} 791 | hasBin: true 792 | dependencies: 793 | chokidar: 3.5.3 794 | livereload-js: 3.4.1 795 | opts: 2.0.2 796 | ws: 7.5.9 797 | transitivePeerDependencies: 798 | - bufferutil 799 | - utf-8-validate 800 | dev: true 801 | 802 | /loader-utils/3.2.1: 803 | resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==} 804 | engines: {node: '>= 12.13.0'} 805 | dev: true 806 | 807 | /lodash.camelcase/4.3.0: 808 | resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} 809 | dev: true 810 | 811 | /lodash.memoize/4.1.2: 812 | resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} 813 | dev: true 814 | 815 | /lodash.uniq/4.5.0: 816 | resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} 817 | dev: true 818 | 819 | /mdn-data/2.0.14: 820 | resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} 821 | dev: true 822 | 823 | /merge2/1.4.1: 824 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 825 | engines: {node: '>= 8'} 826 | dev: true 827 | 828 | /micromatch/4.0.5: 829 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 830 | engines: {node: '>=8.6'} 831 | dependencies: 832 | braces: 3.0.2 833 | picomatch: 2.3.1 834 | dev: true 835 | 836 | /mime/3.0.0: 837 | resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 838 | engines: {node: '>=10.0.0'} 839 | hasBin: true 840 | dev: true 841 | 842 | /minimatch/3.1.2: 843 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 844 | dependencies: 845 | brace-expansion: 1.1.11 846 | dev: true 847 | 848 | /nanoid/3.3.4: 849 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} 850 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 851 | hasBin: true 852 | dev: true 853 | 854 | /node-releases/2.0.8: 855 | resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} 856 | dev: true 857 | 858 | /normalize-path/3.0.0: 859 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 860 | engines: {node: '>=0.10.0'} 861 | dev: true 862 | 863 | /normalize-url/6.1.0: 864 | resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} 865 | engines: {node: '>=10'} 866 | dev: true 867 | 868 | /nth-check/2.1.1: 869 | resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} 870 | dependencies: 871 | boolbase: 1.0.0 872 | dev: true 873 | 874 | /once/1.4.0: 875 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 876 | dependencies: 877 | wrappy: 1.0.2 878 | dev: true 879 | 880 | /opener/1.5.2: 881 | resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} 882 | hasBin: true 883 | dev: true 884 | 885 | /opts/2.0.2: 886 | resolution: {integrity: sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==} 887 | dev: true 888 | 889 | /p-finally/1.0.0: 890 | resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} 891 | engines: {node: '>=4'} 892 | dev: true 893 | 894 | /p-queue/6.6.2: 895 | resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} 896 | engines: {node: '>=8'} 897 | dependencies: 898 | eventemitter3: 4.0.7 899 | p-timeout: 3.2.0 900 | dev: true 901 | 902 | /p-timeout/3.2.0: 903 | resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} 904 | engines: {node: '>=8'} 905 | dependencies: 906 | p-finally: 1.0.0 907 | dev: true 908 | 909 | /path-is-absolute/1.0.1: 910 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 911 | engines: {node: '>=0.10.0'} 912 | dev: true 913 | 914 | /path-parse/1.0.7: 915 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 916 | dev: true 917 | 918 | /path-type/4.0.0: 919 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 920 | engines: {node: '>=8'} 921 | dev: true 922 | 923 | /picocolors/1.0.0: 924 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 925 | dev: true 926 | 927 | /picomatch/2.3.1: 928 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 929 | engines: {node: '>=8.6'} 930 | dev: true 931 | 932 | /pify/5.0.0: 933 | resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} 934 | engines: {node: '>=10'} 935 | dev: true 936 | 937 | /postcss-calc/8.2.4_postcss@8.4.21: 938 | resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} 939 | peerDependencies: 940 | postcss: ^8.2.2 941 | dependencies: 942 | postcss: 8.4.21 943 | postcss-selector-parser: 6.0.11 944 | postcss-value-parser: 4.2.0 945 | dev: true 946 | 947 | /postcss-colormin/5.3.0_postcss@8.4.21: 948 | resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} 949 | engines: {node: ^10 || ^12 || >=14.0} 950 | peerDependencies: 951 | postcss: ^8.2.15 952 | dependencies: 953 | browserslist: 4.21.4 954 | caniuse-api: 3.0.0 955 | colord: 2.9.3 956 | postcss: 8.4.21 957 | postcss-value-parser: 4.2.0 958 | dev: true 959 | 960 | /postcss-convert-values/5.1.3_postcss@8.4.21: 961 | resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} 962 | engines: {node: ^10 || ^12 || >=14.0} 963 | peerDependencies: 964 | postcss: ^8.2.15 965 | dependencies: 966 | browserslist: 4.21.4 967 | postcss: 8.4.21 968 | postcss-value-parser: 4.2.0 969 | dev: true 970 | 971 | /postcss-discard-comments/5.1.2_postcss@8.4.21: 972 | resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} 973 | engines: {node: ^10 || ^12 || >=14.0} 974 | peerDependencies: 975 | postcss: ^8.2.15 976 | dependencies: 977 | postcss: 8.4.21 978 | dev: true 979 | 980 | /postcss-discard-duplicates/5.1.0_postcss@8.4.21: 981 | resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} 982 | engines: {node: ^10 || ^12 || >=14.0} 983 | peerDependencies: 984 | postcss: ^8.2.15 985 | dependencies: 986 | postcss: 8.4.21 987 | dev: true 988 | 989 | /postcss-discard-empty/5.1.1_postcss@8.4.21: 990 | resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} 991 | engines: {node: ^10 || ^12 || >=14.0} 992 | peerDependencies: 993 | postcss: ^8.2.15 994 | dependencies: 995 | postcss: 8.4.21 996 | dev: true 997 | 998 | /postcss-discard-overridden/5.1.0_postcss@8.4.21: 999 | resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} 1000 | engines: {node: ^10 || ^12 || >=14.0} 1001 | peerDependencies: 1002 | postcss: ^8.2.15 1003 | dependencies: 1004 | postcss: 8.4.21 1005 | dev: true 1006 | 1007 | /postcss-load-config/3.1.4_postcss@8.4.21: 1008 | resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} 1009 | engines: {node: '>= 10'} 1010 | peerDependencies: 1011 | postcss: '>=8.0.9' 1012 | ts-node: '>=9.0.0' 1013 | peerDependenciesMeta: 1014 | postcss: 1015 | optional: true 1016 | ts-node: 1017 | optional: true 1018 | dependencies: 1019 | lilconfig: 2.0.6 1020 | postcss: 8.4.21 1021 | yaml: 1.10.2 1022 | dev: true 1023 | 1024 | /postcss-merge-longhand/5.1.7_postcss@8.4.21: 1025 | resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} 1026 | engines: {node: ^10 || ^12 || >=14.0} 1027 | peerDependencies: 1028 | postcss: ^8.2.15 1029 | dependencies: 1030 | postcss: 8.4.21 1031 | postcss-value-parser: 4.2.0 1032 | stylehacks: 5.1.1_postcss@8.4.21 1033 | dev: true 1034 | 1035 | /postcss-merge-rules/5.1.3_postcss@8.4.21: 1036 | resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==} 1037 | engines: {node: ^10 || ^12 || >=14.0} 1038 | peerDependencies: 1039 | postcss: ^8.2.15 1040 | dependencies: 1041 | browserslist: 4.21.4 1042 | caniuse-api: 3.0.0 1043 | cssnano-utils: 3.1.0_postcss@8.4.21 1044 | postcss: 8.4.21 1045 | postcss-selector-parser: 6.0.11 1046 | dev: true 1047 | 1048 | /postcss-minify-font-values/5.1.0_postcss@8.4.21: 1049 | resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} 1050 | engines: {node: ^10 || ^12 || >=14.0} 1051 | peerDependencies: 1052 | postcss: ^8.2.15 1053 | dependencies: 1054 | postcss: 8.4.21 1055 | postcss-value-parser: 4.2.0 1056 | dev: true 1057 | 1058 | /postcss-minify-gradients/5.1.1_postcss@8.4.21: 1059 | resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} 1060 | engines: {node: ^10 || ^12 || >=14.0} 1061 | peerDependencies: 1062 | postcss: ^8.2.15 1063 | dependencies: 1064 | colord: 2.9.3 1065 | cssnano-utils: 3.1.0_postcss@8.4.21 1066 | postcss: 8.4.21 1067 | postcss-value-parser: 4.2.0 1068 | dev: true 1069 | 1070 | /postcss-minify-params/5.1.4_postcss@8.4.21: 1071 | resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} 1072 | engines: {node: ^10 || ^12 || >=14.0} 1073 | peerDependencies: 1074 | postcss: ^8.2.15 1075 | dependencies: 1076 | browserslist: 4.21.4 1077 | cssnano-utils: 3.1.0_postcss@8.4.21 1078 | postcss: 8.4.21 1079 | postcss-value-parser: 4.2.0 1080 | dev: true 1081 | 1082 | /postcss-minify-selectors/5.2.1_postcss@8.4.21: 1083 | resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} 1084 | engines: {node: ^10 || ^12 || >=14.0} 1085 | peerDependencies: 1086 | postcss: ^8.2.15 1087 | dependencies: 1088 | postcss: 8.4.21 1089 | postcss-selector-parser: 6.0.11 1090 | dev: true 1091 | 1092 | /postcss-modules-extract-imports/3.0.0_postcss@8.4.21: 1093 | resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} 1094 | engines: {node: ^10 || ^12 || >= 14} 1095 | peerDependencies: 1096 | postcss: ^8.1.0 1097 | dependencies: 1098 | postcss: 8.4.21 1099 | dev: true 1100 | 1101 | /postcss-modules-local-by-default/4.0.0_postcss@8.4.21: 1102 | resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} 1103 | engines: {node: ^10 || ^12 || >= 14} 1104 | peerDependencies: 1105 | postcss: ^8.1.0 1106 | dependencies: 1107 | icss-utils: 5.1.0_postcss@8.4.21 1108 | postcss: 8.4.21 1109 | postcss-selector-parser: 6.0.11 1110 | postcss-value-parser: 4.2.0 1111 | dev: true 1112 | 1113 | /postcss-modules-scope/3.0.0_postcss@8.4.21: 1114 | resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} 1115 | engines: {node: ^10 || ^12 || >= 14} 1116 | peerDependencies: 1117 | postcss: ^8.1.0 1118 | dependencies: 1119 | postcss: 8.4.21 1120 | postcss-selector-parser: 6.0.11 1121 | dev: true 1122 | 1123 | /postcss-modules-values/4.0.0_postcss@8.4.21: 1124 | resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} 1125 | engines: {node: ^10 || ^12 || >= 14} 1126 | peerDependencies: 1127 | postcss: ^8.1.0 1128 | dependencies: 1129 | icss-utils: 5.1.0_postcss@8.4.21 1130 | postcss: 8.4.21 1131 | dev: true 1132 | 1133 | /postcss-modules/4.3.1_postcss@8.4.21: 1134 | resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} 1135 | peerDependencies: 1136 | postcss: ^8.0.0 1137 | dependencies: 1138 | generic-names: 4.0.0 1139 | icss-replace-symbols: 1.1.0 1140 | lodash.camelcase: 4.3.0 1141 | postcss: 8.4.21 1142 | postcss-modules-extract-imports: 3.0.0_postcss@8.4.21 1143 | postcss-modules-local-by-default: 4.0.0_postcss@8.4.21 1144 | postcss-modules-scope: 3.0.0_postcss@8.4.21 1145 | postcss-modules-values: 4.0.0_postcss@8.4.21 1146 | string-hash: 1.1.3 1147 | dev: true 1148 | 1149 | /postcss-normalize-charset/5.1.0_postcss@8.4.21: 1150 | resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} 1151 | engines: {node: ^10 || ^12 || >=14.0} 1152 | peerDependencies: 1153 | postcss: ^8.2.15 1154 | dependencies: 1155 | postcss: 8.4.21 1156 | dev: true 1157 | 1158 | /postcss-normalize-display-values/5.1.0_postcss@8.4.21: 1159 | resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} 1160 | engines: {node: ^10 || ^12 || >=14.0} 1161 | peerDependencies: 1162 | postcss: ^8.2.15 1163 | dependencies: 1164 | postcss: 8.4.21 1165 | postcss-value-parser: 4.2.0 1166 | dev: true 1167 | 1168 | /postcss-normalize-positions/5.1.1_postcss@8.4.21: 1169 | resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} 1170 | engines: {node: ^10 || ^12 || >=14.0} 1171 | peerDependencies: 1172 | postcss: ^8.2.15 1173 | dependencies: 1174 | postcss: 8.4.21 1175 | postcss-value-parser: 4.2.0 1176 | dev: true 1177 | 1178 | /postcss-normalize-repeat-style/5.1.1_postcss@8.4.21: 1179 | resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} 1180 | engines: {node: ^10 || ^12 || >=14.0} 1181 | peerDependencies: 1182 | postcss: ^8.2.15 1183 | dependencies: 1184 | postcss: 8.4.21 1185 | postcss-value-parser: 4.2.0 1186 | dev: true 1187 | 1188 | /postcss-normalize-string/5.1.0_postcss@8.4.21: 1189 | resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} 1190 | engines: {node: ^10 || ^12 || >=14.0} 1191 | peerDependencies: 1192 | postcss: ^8.2.15 1193 | dependencies: 1194 | postcss: 8.4.21 1195 | postcss-value-parser: 4.2.0 1196 | dev: true 1197 | 1198 | /postcss-normalize-timing-functions/5.1.0_postcss@8.4.21: 1199 | resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} 1200 | engines: {node: ^10 || ^12 || >=14.0} 1201 | peerDependencies: 1202 | postcss: ^8.2.15 1203 | dependencies: 1204 | postcss: 8.4.21 1205 | postcss-value-parser: 4.2.0 1206 | dev: true 1207 | 1208 | /postcss-normalize-unicode/5.1.1_postcss@8.4.21: 1209 | resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} 1210 | engines: {node: ^10 || ^12 || >=14.0} 1211 | peerDependencies: 1212 | postcss: ^8.2.15 1213 | dependencies: 1214 | browserslist: 4.21.4 1215 | postcss: 8.4.21 1216 | postcss-value-parser: 4.2.0 1217 | dev: true 1218 | 1219 | /postcss-normalize-url/5.1.0_postcss@8.4.21: 1220 | resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} 1221 | engines: {node: ^10 || ^12 || >=14.0} 1222 | peerDependencies: 1223 | postcss: ^8.2.15 1224 | dependencies: 1225 | normalize-url: 6.1.0 1226 | postcss: 8.4.21 1227 | postcss-value-parser: 4.2.0 1228 | dev: true 1229 | 1230 | /postcss-normalize-whitespace/5.1.1_postcss@8.4.21: 1231 | resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} 1232 | engines: {node: ^10 || ^12 || >=14.0} 1233 | peerDependencies: 1234 | postcss: ^8.2.15 1235 | dependencies: 1236 | postcss: 8.4.21 1237 | postcss-value-parser: 4.2.0 1238 | dev: true 1239 | 1240 | /postcss-ordered-values/5.1.3_postcss@8.4.21: 1241 | resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} 1242 | engines: {node: ^10 || ^12 || >=14.0} 1243 | peerDependencies: 1244 | postcss: ^8.2.15 1245 | dependencies: 1246 | cssnano-utils: 3.1.0_postcss@8.4.21 1247 | postcss: 8.4.21 1248 | postcss-value-parser: 4.2.0 1249 | dev: true 1250 | 1251 | /postcss-reduce-initial/5.1.1_postcss@8.4.21: 1252 | resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==} 1253 | engines: {node: ^10 || ^12 || >=14.0} 1254 | peerDependencies: 1255 | postcss: ^8.2.15 1256 | dependencies: 1257 | browserslist: 4.21.4 1258 | caniuse-api: 3.0.0 1259 | postcss: 8.4.21 1260 | dev: true 1261 | 1262 | /postcss-reduce-transforms/5.1.0_postcss@8.4.21: 1263 | resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} 1264 | engines: {node: ^10 || ^12 || >=14.0} 1265 | peerDependencies: 1266 | postcss: ^8.2.15 1267 | dependencies: 1268 | postcss: 8.4.21 1269 | postcss-value-parser: 4.2.0 1270 | dev: true 1271 | 1272 | /postcss-selector-parser/6.0.11: 1273 | resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} 1274 | engines: {node: '>=4'} 1275 | dependencies: 1276 | cssesc: 3.0.0 1277 | util-deprecate: 1.0.2 1278 | dev: true 1279 | 1280 | /postcss-svgo/5.1.0_postcss@8.4.21: 1281 | resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} 1282 | engines: {node: ^10 || ^12 || >=14.0} 1283 | peerDependencies: 1284 | postcss: ^8.2.15 1285 | dependencies: 1286 | postcss: 8.4.21 1287 | postcss-value-parser: 4.2.0 1288 | svgo: 2.8.0 1289 | dev: true 1290 | 1291 | /postcss-unique-selectors/5.1.1_postcss@8.4.21: 1292 | resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} 1293 | engines: {node: ^10 || ^12 || >=14.0} 1294 | peerDependencies: 1295 | postcss: ^8.2.15 1296 | dependencies: 1297 | postcss: 8.4.21 1298 | postcss-selector-parser: 6.0.11 1299 | dev: true 1300 | 1301 | /postcss-value-parser/4.2.0: 1302 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 1303 | dev: true 1304 | 1305 | /postcss/8.4.21: 1306 | resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} 1307 | engines: {node: ^10 || ^12 || >=14} 1308 | dependencies: 1309 | nanoid: 3.3.4 1310 | picocolors: 1.0.0 1311 | source-map-js: 1.0.2 1312 | dev: true 1313 | 1314 | /promise.series/0.2.0: 1315 | resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} 1316 | engines: {node: '>=0.12'} 1317 | dev: true 1318 | 1319 | /queue-microtask/1.2.3: 1320 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1321 | dev: true 1322 | 1323 | /randombytes/2.1.0: 1324 | resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} 1325 | dependencies: 1326 | safe-buffer: 5.2.1 1327 | dev: true 1328 | 1329 | /readdirp/3.6.0: 1330 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1331 | engines: {node: '>=8.10.0'} 1332 | dependencies: 1333 | picomatch: 2.3.1 1334 | dev: true 1335 | 1336 | /resolve-from/5.0.0: 1337 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 1338 | engines: {node: '>=8'} 1339 | dev: true 1340 | 1341 | /resolve/1.22.1: 1342 | resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} 1343 | hasBin: true 1344 | dependencies: 1345 | is-core-module: 2.11.0 1346 | path-parse: 1.0.7 1347 | supports-preserve-symlinks-flag: 1.0.0 1348 | dev: true 1349 | 1350 | /reusify/1.0.4: 1351 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1352 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1353 | dev: true 1354 | 1355 | /rollup-plugin-copy/3.4.0: 1356 | resolution: {integrity: sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==} 1357 | engines: {node: '>=8.3'} 1358 | dependencies: 1359 | '@types/fs-extra': 8.1.2 1360 | colorette: 1.4.0 1361 | fs-extra: 8.1.0 1362 | globby: 10.0.1 1363 | is-plain-object: 3.0.1 1364 | dev: true 1365 | 1366 | /rollup-plugin-livereload/2.0.5: 1367 | resolution: {integrity: sha512-vqQZ/UQowTW7VoiKEM5ouNW90wE5/GZLfdWuR0ELxyKOJUIaj+uismPZZaICU4DnWPVjnpCDDxEqwU7pcKY/PA==} 1368 | engines: {node: '>=8.3'} 1369 | dependencies: 1370 | livereload: 0.9.3 1371 | transitivePeerDependencies: 1372 | - bufferutil 1373 | - utf-8-validate 1374 | dev: true 1375 | 1376 | /rollup-plugin-postcss/4.0.2_postcss@8.4.21: 1377 | resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} 1378 | engines: {node: '>=10'} 1379 | peerDependencies: 1380 | postcss: 8.x 1381 | dependencies: 1382 | chalk: 4.1.2 1383 | concat-with-sourcemaps: 1.1.0 1384 | cssnano: 5.1.14_postcss@8.4.21 1385 | import-cwd: 3.0.0 1386 | p-queue: 6.6.2 1387 | pify: 5.0.0 1388 | postcss: 8.4.21 1389 | postcss-load-config: 3.1.4_postcss@8.4.21 1390 | postcss-modules: 4.3.1_postcss@8.4.21 1391 | promise.series: 0.2.0 1392 | resolve: 1.22.1 1393 | rollup-pluginutils: 2.8.2 1394 | safe-identifier: 0.4.2 1395 | style-inject: 0.3.0 1396 | transitivePeerDependencies: 1397 | - ts-node 1398 | dev: true 1399 | 1400 | /rollup-plugin-serve/2.0.2: 1401 | resolution: {integrity: sha512-ALqyTbPhlf7FZ5RzlbDvMYvbKuCHWginJkTo6dMsbgji/a78IbsXox+pC83HENdkTRz8OXrTj+aShp3+3ratpg==} 1402 | dependencies: 1403 | mime: 3.0.0 1404 | opener: 1.5.2 1405 | dev: true 1406 | 1407 | /rollup-plugin-web-worker-loader/1.6.1_rollup@2.79.1: 1408 | resolution: {integrity: sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A==} 1409 | peerDependencies: 1410 | rollup: ^1.9.2 || ^2.0.0 1411 | dependencies: 1412 | rollup: 2.79.1 1413 | dev: true 1414 | 1415 | /rollup-pluginutils/2.8.2: 1416 | resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} 1417 | dependencies: 1418 | estree-walker: 0.6.1 1419 | dev: true 1420 | 1421 | /rollup/2.79.1: 1422 | resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} 1423 | engines: {node: '>=10.0.0'} 1424 | hasBin: true 1425 | optionalDependencies: 1426 | fsevents: 2.3.2 1427 | dev: true 1428 | 1429 | /run-parallel/1.2.0: 1430 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1431 | dependencies: 1432 | queue-microtask: 1.2.3 1433 | dev: true 1434 | 1435 | /safe-buffer/5.2.1: 1436 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1437 | dev: true 1438 | 1439 | /safe-identifier/0.4.2: 1440 | resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} 1441 | dev: true 1442 | 1443 | /serialize-javascript/6.0.1: 1444 | resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} 1445 | dependencies: 1446 | randombytes: 2.1.0 1447 | dev: true 1448 | 1449 | /slash/3.0.0: 1450 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1451 | engines: {node: '>=8'} 1452 | dev: true 1453 | 1454 | /slash/4.0.0: 1455 | resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} 1456 | engines: {node: '>=12'} 1457 | dev: true 1458 | 1459 | /smob/0.0.6: 1460 | resolution: {integrity: sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==} 1461 | dev: true 1462 | 1463 | /source-map-js/1.0.2: 1464 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 1465 | engines: {node: '>=0.10.0'} 1466 | dev: true 1467 | 1468 | /source-map-support/0.5.21: 1469 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 1470 | dependencies: 1471 | buffer-from: 1.1.2 1472 | source-map: 0.6.1 1473 | dev: true 1474 | 1475 | /source-map/0.6.1: 1476 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1477 | engines: {node: '>=0.10.0'} 1478 | dev: true 1479 | 1480 | /stable/0.1.8: 1481 | resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} 1482 | deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' 1483 | dev: true 1484 | 1485 | /string-hash/1.1.3: 1486 | resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} 1487 | dev: true 1488 | 1489 | /style-inject/0.3.0: 1490 | resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} 1491 | dev: true 1492 | 1493 | /stylehacks/5.1.1_postcss@8.4.21: 1494 | resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} 1495 | engines: {node: ^10 || ^12 || >=14.0} 1496 | peerDependencies: 1497 | postcss: ^8.2.15 1498 | dependencies: 1499 | browserslist: 4.21.4 1500 | postcss: 8.4.21 1501 | postcss-selector-parser: 6.0.11 1502 | dev: true 1503 | 1504 | /supports-color/7.2.0: 1505 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1506 | engines: {node: '>=8'} 1507 | dependencies: 1508 | has-flag: 4.0.0 1509 | dev: true 1510 | 1511 | /supports-preserve-symlinks-flag/1.0.0: 1512 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1513 | engines: {node: '>= 0.4'} 1514 | dev: true 1515 | 1516 | /svgo/2.8.0: 1517 | resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} 1518 | engines: {node: '>=10.13.0'} 1519 | hasBin: true 1520 | dependencies: 1521 | '@trysound/sax': 0.2.0 1522 | commander: 7.2.0 1523 | css-select: 4.3.0 1524 | css-tree: 1.1.3 1525 | csso: 4.2.0 1526 | picocolors: 1.0.0 1527 | stable: 0.1.8 1528 | dev: true 1529 | 1530 | /terser/5.16.1: 1531 | resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==} 1532 | engines: {node: '>=10'} 1533 | hasBin: true 1534 | dependencies: 1535 | '@jridgewell/source-map': 0.3.2 1536 | acorn: 8.8.2 1537 | commander: 2.20.3 1538 | source-map-support: 0.5.21 1539 | dev: true 1540 | 1541 | /three-mesh-bvh/0.5.22_three@0.149.0: 1542 | resolution: {integrity: sha512-vi9X78CoEz1VVfkKRpUZQa34SEc0QGDd9Hmyis0aeBRAmjp4BpHQ2URcirBsOvFMNBXqB9Klp8sQ9NCRsUEW0w==} 1543 | peerDependencies: 1544 | three: '>= 0.123.0' 1545 | dependencies: 1546 | three: 0.149.0 1547 | dev: false 1548 | 1549 | /three/0.149.0: 1550 | resolution: {integrity: sha512-tohpUxPDht0qExRLDTM8sjRLc5d9STURNrdnK3w9A+V4pxaTBfKWWT/IqtiLfg23Vfc3Z+ImNfvRw1/0CtxrkQ==} 1551 | dev: false 1552 | 1553 | /to-regex-range/5.0.1: 1554 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1555 | engines: {node: '>=8.0'} 1556 | dependencies: 1557 | is-number: 7.0.0 1558 | dev: true 1559 | 1560 | /tslib/2.5.0: 1561 | resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} 1562 | dev: true 1563 | 1564 | /typescript/4.9.4: 1565 | resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} 1566 | engines: {node: '>=4.2.0'} 1567 | hasBin: true 1568 | dev: true 1569 | 1570 | /universalify/0.1.2: 1571 | resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 1572 | engines: {node: '>= 4.0.0'} 1573 | dev: true 1574 | 1575 | /update-browserslist-db/1.0.10_browserslist@4.21.4: 1576 | resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} 1577 | hasBin: true 1578 | peerDependencies: 1579 | browserslist: '>= 4.21.0' 1580 | dependencies: 1581 | browserslist: 4.21.4 1582 | escalade: 3.1.1 1583 | picocolors: 1.0.0 1584 | dev: true 1585 | 1586 | /util-deprecate/1.0.2: 1587 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1588 | dev: true 1589 | 1590 | /wrappy/1.0.2: 1591 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1592 | dev: true 1593 | 1594 | /ws/7.5.9: 1595 | resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} 1596 | engines: {node: '>=8.3.0'} 1597 | peerDependencies: 1598 | bufferutil: ^4.0.1 1599 | utf-8-validate: ^5.0.2 1600 | peerDependenciesMeta: 1601 | bufferutil: 1602 | optional: true 1603 | utf-8-validate: 1604 | optional: true 1605 | dev: true 1606 | 1607 | /yaml/1.10.2: 1608 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 1609 | engines: {node: '>= 6'} 1610 | dev: true 1611 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import copy from 'rollup-plugin-copy'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | import terser from '@rollup/plugin-terser'; 6 | import typescript from '@rollup/plugin-typescript'; 7 | import wasm from '@rollup/plugin-wasm'; 8 | import webWorkerLoader from 'rollup-plugin-web-worker-loader'; 9 | 10 | const outputPath = path.resolve(__dirname, 'dist'); 11 | 12 | export default { 13 | external: ['three'], 14 | input: path.join(__dirname, 'src'), 15 | output: { 16 | file: path.join(outputPath, 'sculpty.js'), 17 | format: 'esm', 18 | }, 19 | plugins: [ 20 | nodeResolve({ extensions: ['.js', '.ts'] }), 21 | typescript({ declaration: true, declarationDir: 'types' }), 22 | wasm({ maxFileSize: Infinity }), 23 | webWorkerLoader(), 24 | terser({ format: { comments: false } }), 25 | copy({ 26 | copyOnce: true, 27 | targets: [ 28 | { src: 'LICENSE', dest: 'dist' }, 29 | { src: 'README.md', dest: 'dist' }, 30 | ], 31 | }), 32 | { 33 | writeBundle() { 34 | fs.writeFileSync(path.join(outputPath, 'package.json'), JSON.stringify({ 35 | name: 'sculpty', 36 | author: 'Daniel Esteban Nombela', 37 | license: 'MIT', 38 | version: '0.0.4', 39 | module: './sculpty.js', 40 | types: './types', 41 | homepage: 'https://sculpty.gatunes.com', 42 | repository: { 43 | type: 'git', 44 | url: 'https://github.com/danielesteban/sculpty', 45 | }, 46 | peerDependencies: { 47 | three: '>=0.149.0', 48 | }, 49 | }, null, ' ')); 50 | }, 51 | }, 52 | ], 53 | watch: { clearScreen: false }, 54 | }; 55 | -------------------------------------------------------------------------------- /src/chunks/chunk.ts: -------------------------------------------------------------------------------- 1 | import { Group, Vector3 } from 'three'; 2 | import { Geometry, Materials } from './types'; 3 | import TriangleChunk from './triangles'; 4 | import VoxelChunk from './voxels'; 5 | 6 | class Chunk extends Group { 7 | private readonly triangles: TriangleChunk; 8 | private readonly voxels: VoxelChunk; 9 | public request: number = 0; 10 | public version: number = 0; 11 | 12 | constructor({ 13 | geometry, 14 | materials, 15 | position, 16 | }: { 17 | geometry?: Geometry; 18 | materials: Materials; 19 | position: Vector3; 20 | }) { 21 | super(); 22 | this.position.copy(position); 23 | this.updateMatrixWorld(); 24 | this.matrixAutoUpdate = false; 25 | this.triangles = new TriangleChunk(materials.triangles); 26 | this.add(this.triangles); 27 | this.voxels = new VoxelChunk(materials.voxels); 28 | this.add(this.voxels); 29 | this.update(geometry); 30 | } 31 | 32 | dispose() { 33 | const { triangles, voxels } = this; 34 | triangles.dispose(); 35 | voxels.dispose(); 36 | } 37 | 38 | update(geometry?: Geometry) { 39 | if (!geometry) { 40 | this.visible = false; 41 | return; 42 | } 43 | const { triangles, voxels } = this; 44 | triangles.update(geometry); 45 | voxels.update(geometry); 46 | voxels.geometry.boundingBox = triangles.geometry.boundingBox; 47 | voxels.geometry.boundingSphere = triangles.geometry.boundingSphere; 48 | this.visible = true; 49 | } 50 | } 51 | 52 | export default Chunk; 53 | -------------------------------------------------------------------------------- /src/chunks/triangles.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Box3, 3 | BufferGeometry, 4 | InterleavedBuffer, 5 | InterleavedBufferAttribute, 6 | Mesh, 7 | Material, 8 | Sphere, 9 | } from 'three'; 10 | import { Geometry } from './types'; 11 | 12 | class TriangleChunk extends Mesh { 13 | constructor(material: Material) { 14 | super(new BufferGeometry(), material); 15 | this.castShadow = true; 16 | this.receiveShadow = true; 17 | this.matrixAutoUpdate = false; 18 | } 19 | 20 | dispose() { 21 | const { geometry } = this; 22 | geometry.dispose(); 23 | } 24 | 25 | override raycast() { 26 | return; 27 | } 28 | 29 | update({ bounds, vertices }: Geometry) { 30 | const { geometry } = this; 31 | const buffer = new InterleavedBuffer(vertices, 9); 32 | geometry.setAttribute('position', new InterleavedBufferAttribute(buffer, 3, 0)); 33 | geometry.setAttribute('normal', new InterleavedBufferAttribute(buffer, 3, 3)); 34 | geometry.setAttribute('color', new InterleavedBufferAttribute(buffer, 3, 6)); 35 | if (!geometry.boundingBox) { 36 | geometry.boundingBox = new Box3(); 37 | } 38 | geometry.boundingBox.min.set(bounds[0], bounds[1], bounds[2]); 39 | geometry.boundingBox.max.set(bounds[3], bounds[4], bounds[5]); 40 | if (!geometry.boundingSphere) { 41 | geometry.boundingSphere = new Sphere(); 42 | } 43 | geometry.boundingBox.getBoundingSphere(geometry.boundingSphere); 44 | } 45 | } 46 | 47 | export default TriangleChunk; 48 | -------------------------------------------------------------------------------- /src/chunks/types.ts: -------------------------------------------------------------------------------- 1 | import { Material } from 'three'; 2 | 3 | export type Geometry = { 4 | bounds: Float32Array; 5 | vertices: Float32Array; 6 | voxels: Float32Array; 7 | }; 8 | 9 | export type Materials = { 10 | triangles: Material; 11 | voxels: Material; 12 | }; 13 | -------------------------------------------------------------------------------- /src/chunks/voxels.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxGeometry, 3 | Color, 4 | InstancedBufferGeometry, 5 | InstancedInterleavedBuffer, 6 | InterleavedBufferAttribute, 7 | Intersection, 8 | Matrix4, 9 | Mesh, 10 | Material, 11 | Object3D, 12 | Raycaster, 13 | Sphere, 14 | Vector3, 15 | } from 'three'; 16 | import { Geometry } from './types'; 17 | 18 | const _color = new Color(); 19 | const _instance = new Mesh(new BoxGeometry(1, 1, 1)); 20 | const _intersects: Intersection>[] = []; 21 | const _sphere = new Sphere(); 22 | const _translation = new Matrix4(); 23 | const _voxel = new Vector3(); 24 | 25 | class Chunk extends Mesh { 26 | constructor(material: Material) { 27 | const geometry = new InstancedBufferGeometry(); 28 | geometry.boundingSphere = new Sphere(); 29 | geometry.setIndex(_instance.geometry.getIndex()); 30 | geometry.setAttribute('position', _instance.geometry.getAttribute('position')); 31 | geometry.setAttribute('normal', _instance.geometry.getAttribute('normal')); 32 | super(geometry, material); 33 | this.castShadow = true; 34 | this.receiveShadow = true; 35 | this.matrixAutoUpdate = false; 36 | } 37 | 38 | dispose() { 39 | const { geometry } = this; 40 | geometry.dispose(); 41 | } 42 | 43 | getColor(instance: number) { 44 | const { geometry } = this; 45 | const color = geometry.getAttribute('color'); 46 | return _color.fromBufferAttribute(color as any, instance).convertLinearToSRGB(); 47 | } 48 | 49 | override raycast(raycaster: Raycaster, intersects: Intersection>[]) { 50 | const { geometry, matrixWorld, parent } = this; 51 | if (!parent?.visible || !geometry.boundingSphere) { 52 | return; 53 | } 54 | _sphere.copy(geometry.boundingSphere); 55 | _sphere.applyMatrix4(matrixWorld); 56 | if (!raycaster.ray.intersectsSphere(_sphere)) { 57 | return; 58 | } 59 | const instance = geometry.getAttribute('instance'); 60 | for (let i = 0, l = (geometry as InstancedBufferGeometry).instanceCount; i < l; i++) { 61 | _voxel.fromBufferAttribute(instance, i); 62 | _instance.matrixWorld 63 | .multiplyMatrices(matrixWorld, _translation.makeTranslation(_voxel.x, _voxel.y, _voxel.z)); 64 | _instance.raycast(raycaster, _intersects); 65 | _intersects.forEach((intersect) => { 66 | intersect.object = this as any; 67 | intersect.instanceId = i; 68 | intersects.push(intersect); 69 | }); 70 | _intersects.length = 0; 71 | } 72 | } 73 | 74 | update({ voxels }: Geometry) { 75 | const { geometry } = this; 76 | const buffer = new InstancedInterleavedBuffer(voxels, 6, 1); 77 | geometry.setAttribute('instance', new InterleavedBufferAttribute(buffer, 3, 0)); 78 | geometry.setAttribute('color', new InterleavedBufferAttribute(buffer, 3, 3)); 79 | (geometry as InstancedBufferGeometry).instanceCount = (geometry as any)._maxInstanceCount = buffer.count; 80 | } 81 | } 82 | 83 | export default Chunk; 84 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storage'; 2 | export { default as World } from './world'; 3 | -------------------------------------------------------------------------------- /src/storage.ts: -------------------------------------------------------------------------------- 1 | export interface Storage { 2 | chunkSize: number; 3 | get: (x: number, y: number, z: number) => Uint32Array; 4 | save: (x: number, y: number, z: number) => void; 5 | } 6 | 7 | export class MemoryStorage implements Storage { 8 | public readonly chunkSize: number; 9 | private readonly data: Map; 10 | 11 | constructor(chunkSize: number = 32) { 12 | this.chunkSize = chunkSize; 13 | this.data = new Map(); 14 | } 15 | 16 | get(x: number, y: number, z: number): Uint32Array { 17 | const { chunkSize } = this; 18 | const key: string = `${x}:${y}:${z}`; 19 | let data = this.data.get(key); 20 | if (!data) { 21 | data = new Uint32Array(chunkSize * chunkSize * chunkSize); 22 | this.data.set(key, data); 23 | } 24 | return data; 25 | } 26 | 27 | save() { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/workers/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd "${0%/*}" 3 | WASI_SDK_PATH=../../../wasi-sdk-17.0 4 | ${WASI_SDK_PATH}/bin/clang \ 5 | --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot \ 6 | -nostartfiles \ 7 | -flto \ 8 | -Ofast \ 9 | -Wl,--no-entry \ 10 | -Wl,--lto-O3 \ 11 | -Wl,--import-memory \ 12 | -Wl,--export=malloc \ 13 | -Wl,--export=run \ 14 | -o ./${1}.wasm ${1}.c 15 | -------------------------------------------------------------------------------- /src/workers/mesher.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int edgeTable[256]; 4 | int triTable[256][16]; 5 | 6 | typedef struct { 7 | unsigned char x; 8 | unsigned char y; 9 | unsigned char z; 10 | } iVector; 11 | 12 | typedef struct { 13 | const unsigned char value; 14 | const iVector color; 15 | } Voxel; 16 | 17 | typedef struct { 18 | const iVector position; 19 | const iVector color; 20 | const float value; 21 | } Cell; 22 | 23 | typedef struct { 24 | float x; 25 | float y; 26 | float z; 27 | } Vector; 28 | 29 | typedef struct { 30 | Vector position; 31 | Vector color; 32 | } Point; 33 | 34 | typedef struct { 35 | Vector position; 36 | Vector normal; 37 | Vector color; 38 | } Vertex; 39 | 40 | typedef struct { 41 | Vector min; 42 | Vector max; 43 | } Box; 44 | 45 | static void computeNormal(Vector* normal, Vector** triangle) { 46 | const Vector cb = { 47 | triangle[2]->x - triangle[1]->x, 48 | triangle[2]->y - triangle[1]->y, 49 | triangle[2]->z - triangle[1]->z 50 | }; 51 | const Vector ab = { 52 | triangle[0]->x - triangle[1]->x, 53 | triangle[0]->y - triangle[1]->y, 54 | triangle[0]->z - triangle[1]->z 55 | }; 56 | normal->x = cb.y * ab.z - cb.z * ab.y; 57 | normal->y = cb.z * ab.x - cb.x * ab.z; 58 | normal->z = cb.x * ab.y - cb.y * ab.x; 59 | const float length = sqrt(normal->x * normal->x + normal->y * normal->y + normal->z * normal->z); 60 | if (length != 0.0) { 61 | normal->x /= length; 62 | normal->y /= length; 63 | normal->z /= length; 64 | } 65 | } 66 | 67 | static const Voxel* getVoxel( 68 | const Voxel* chunks, 69 | const unsigned char chunkSize, 70 | const int x, 71 | const int y, 72 | const int z 73 | ) { 74 | int chunkX = 0; 75 | int voxelX = x + chunkSize / 2; 76 | if (voxelX >= chunkSize) { 77 | chunkX = 1; 78 | voxelX -= chunkSize; 79 | } 80 | int chunkY = 0; 81 | int voxelY = y + chunkSize / 2; 82 | if (voxelY >= chunkSize) { 83 | chunkY = 1; 84 | voxelY -= chunkSize; 85 | } 86 | int chunkZ = 0; 87 | int voxelZ = z + chunkSize / 2; 88 | if (voxelZ >= chunkSize) { 89 | chunkZ = 1; 90 | voxelZ -= chunkSize; 91 | } 92 | const unsigned int offset = (chunkZ * 4 + chunkY * 2 + chunkX) * chunkSize * chunkSize * chunkSize; 93 | return &chunks[offset + voxelZ * chunkSize * chunkSize + voxelY * chunkSize + voxelX]; 94 | } 95 | 96 | static const Cell get( 97 | const Voxel* chunks, 98 | const unsigned char chunkSize, 99 | const unsigned char x, 100 | const unsigned char y, 101 | const unsigned char z 102 | ) { 103 | int r = 0; 104 | int g = 0; 105 | int b = 0; 106 | float value = 0.0f; 107 | int c = 0; 108 | for (int nz = -1; nz <= 0; nz++) { 109 | for (int ny = -1; ny <= 0; ny++) { 110 | for (int nx = -1; nx <= 0; nx++) { 111 | const Voxel* voxel = getVoxel(chunks, chunkSize, x + nx, y + ny, z + nz); 112 | if (voxel->value > 0) { 113 | c++; 114 | r += voxel->color.x; 115 | g += voxel->color.y; 116 | b += voxel->color.z; 117 | value += 4.0f + ((float) voxel->value / 255.0f) * 4.0f; 118 | } 119 | } 120 | } 121 | } 122 | if (c > 0) { 123 | r /= c; 124 | g /= c; 125 | b /= c; 126 | } 127 | value /= 8.0; 128 | const Cell cell = { { x, y, z }, { r, g, b }, value }; 129 | return cell; 130 | } 131 | 132 | static void growBox( 133 | Box* box, 134 | const Vector* point 135 | ) { 136 | if (box->min.x > point->x) box->min.x = point->x; 137 | if (box->min.y > point->y) box->min.y = point->y; 138 | if (box->min.z > point->z) box->min.z = point->z; 139 | if (box->max.x < point->x) box->max.x = point->x; 140 | if (box->max.y < point->y) box->max.y = point->y; 141 | if (box->max.z < point->z) box->max.z = point->z; 142 | } 143 | 144 | static const float SRGBToLinear(const unsigned char c) { 145 | const float n = (float) c / 255.0f; 146 | return (n < 0.04045f) ? n * 0.0773993808f : pow(n * 0.9478672986f + 0.0521327014f, 2.4f); 147 | } 148 | 149 | static void interpolate( 150 | Point* point, 151 | const Cell* p1, 152 | const Cell* p2 153 | ) { 154 | const float step = (1.0 - p1->value) / (p2->value - p1->value); 155 | point->position.x = (float) p1->position.x + step * ((float) p2->position.x - p1->position.x); 156 | point->position.y = (float) p1->position.y + step * ((float) p2->position.y - p1->position.y); 157 | point->position.z = (float) p1->position.z + step * ((float) p2->position.z - p1->position.z); 158 | const float r = SRGBToLinear((p1->value ? p1 : p2)->color.x); 159 | const float g = SRGBToLinear((p1->value ? p1 : p2)->color.y); 160 | const float b = SRGBToLinear((p1->value ? p1 : p2)->color.z); 161 | point->color.x = r + step * (SRGBToLinear((p2->value ? p2 : p1)->color.x) - r); 162 | point->color.y = g + step * (SRGBToLinear((p2->value ? p2 : p1)->color.y) - g); 163 | point->color.z = b + step * (SRGBToLinear((p2->value ? p2 : p1)->color.z) - b); 164 | } 165 | 166 | void run( 167 | const Voxel* chunks, 168 | unsigned int* counts, 169 | Vertex* vertices, 170 | Point* voxels, 171 | Box* bounds, 172 | const unsigned char chunkSize 173 | ) { 174 | bounds->min.x = bounds->min.y = bounds->min.z = chunkSize; 175 | bounds->max.x = bounds->max.y = bounds->max.z = 0; 176 | unsigned int triangles = 0; 177 | unsigned int offset = 0; 178 | unsigned int voxel = 0; 179 | for (unsigned char z = 0; z < chunkSize; z++) { 180 | for (unsigned char y = 0; y < chunkSize; y++) { 181 | for (unsigned char x = 0; x < chunkSize; x++) { 182 | const Voxel* cell = getVoxel(chunks, chunkSize, x, y, z); 183 | if ( 184 | cell->value != 0.0 185 | && ( 186 | getVoxel(chunks, chunkSize, x + 1, y, z)->value == 0.0 187 | || getVoxel(chunks, chunkSize, x, y + 1, z)->value == 0.0 188 | || getVoxel(chunks, chunkSize, x, y, z + 1)->value == 0.0 189 | || getVoxel(chunks, chunkSize, x - 1, y, z)->value == 0.0 190 | || getVoxel(chunks, chunkSize, x, y - 1, z)->value == 0.0 191 | || getVoxel(chunks, chunkSize, x, y, z - 1)->value == 0.0 192 | ) 193 | ) { 194 | voxels[voxel].position.x = (float) x + 0.5; 195 | voxels[voxel].position.y = (float) y + 0.5; 196 | voxels[voxel].position.z = (float) z + 0.5; 197 | voxels[voxel].color.x = SRGBToLinear(cell->color.x); 198 | voxels[voxel].color.y = SRGBToLinear(cell->color.y); 199 | voxels[voxel].color.z = SRGBToLinear(cell->color.z); 200 | voxel++; 201 | } 202 | 203 | int cubeindex = 0; 204 | const Cell cells[8] = { 205 | get(chunks, chunkSize, x, y, z), 206 | get(chunks, chunkSize, x, y + 1, z), 207 | get(chunks, chunkSize, x + 1, y + 1, z), 208 | get(chunks, chunkSize, x + 1, y, z), 209 | get(chunks, chunkSize, x, y, z + 1), 210 | get(chunks, chunkSize, x, y + 1, z + 1), 211 | get(chunks, chunkSize, x + 1, y + 1, z + 1), 212 | get(chunks, chunkSize, x + 1, y, z + 1) 213 | }; 214 | if (cells[0].value >= 1.0) cubeindex |= 1; 215 | if (cells[1].value >= 1.0) cubeindex |= 2; 216 | if (cells[2].value >= 1.0) cubeindex |= 4; 217 | if (cells[3].value >= 1.0) cubeindex |= 8; 218 | if (cells[4].value >= 1.0) cubeindex |= 16; 219 | if (cells[5].value >= 1.0) cubeindex |= 32; 220 | if (cells[6].value >= 1.0) cubeindex |= 64; 221 | if (cells[7].value >= 1.0) cubeindex |= 128; 222 | 223 | if (edgeTable[cubeindex] == 0) { 224 | continue; 225 | } 226 | 227 | Point points[12]; 228 | if (edgeTable[cubeindex] & 1) 229 | interpolate(&points[0], &cells[0], &cells[1]); 230 | if (edgeTable[cubeindex] & 2) 231 | interpolate(&points[1], &cells[1], &cells[2]); 232 | if (edgeTable[cubeindex] & 4) 233 | interpolate(&points[2], &cells[2], &cells[3]); 234 | if (edgeTable[cubeindex] & 8) 235 | interpolate(&points[3], &cells[3], &cells[0]); 236 | if (edgeTable[cubeindex] & 16) 237 | interpolate(&points[4], &cells[4], &cells[5]); 238 | if (edgeTable[cubeindex] & 32) 239 | interpolate(&points[5], &cells[5], &cells[6]); 240 | if (edgeTable[cubeindex] & 64) 241 | interpolate(&points[6], &cells[6], &cells[7]); 242 | if (edgeTable[cubeindex] & 128) 243 | interpolate(&points[7], &cells[7], &cells[4]); 244 | if (edgeTable[cubeindex] & 256) 245 | interpolate(&points[8], &cells[0], &cells[4]); 246 | if (edgeTable[cubeindex] & 512) 247 | interpolate(&points[9], &cells[1], &cells[5]); 248 | if (edgeTable[cubeindex] & 1024) 249 | interpolate(&points[10], &cells[2], &cells[6]); 250 | if (edgeTable[cubeindex] & 2048) 251 | interpolate(&points[11], &cells[3], &cells[7]); 252 | 253 | Vector* triangle[3]; 254 | Vector normal; 255 | for (int i = 0; triTable[cubeindex][i] != -1; i += 3) { 256 | for (int v = 0; v < 3; v += 1) { 257 | triangle[v] = &points[triTable[cubeindex][i + v]].position; 258 | growBox(bounds, triangle[v]); 259 | } 260 | computeNormal(&normal, triangle); 261 | for (int v = 0; v < 3; v += 1) { 262 | const Point* point = &points[triTable[cubeindex][i + v]]; 263 | Vertex* vertex = &vertices[offset++]; 264 | vertex->position = point->position; 265 | vertex->normal = normal; 266 | vertex->color = point->color; 267 | } 268 | triangles++; 269 | } 270 | } 271 | } 272 | } 273 | counts[0] = triangles; 274 | counts[1] = voxel; 275 | } 276 | 277 | int edgeTable[256] = { 278 | 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 279 | 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 280 | 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 281 | 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 282 | 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 283 | 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 284 | 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 285 | 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 286 | 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 287 | 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 288 | 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 289 | 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 290 | 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 291 | 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 292 | 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 293 | 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 294 | 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 295 | 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 296 | 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 297 | 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 298 | 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 299 | 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 300 | 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 301 | 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, 302 | 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 303 | 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, 304 | 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 305 | 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, 306 | 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 307 | 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, 308 | 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 309 | 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 310 | }; 311 | 312 | int triTable[256][16] = { 313 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 314 | {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 315 | {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 316 | {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 317 | {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 318 | {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 319 | {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 320 | {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, 321 | {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 322 | {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 323 | {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 324 | {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, 325 | {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 326 | {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, 327 | {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, 328 | {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 329 | {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 330 | {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 331 | {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 332 | {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, 333 | {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 334 | {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, 335 | {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, 336 | {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, 337 | {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 338 | {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, 339 | {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, 340 | {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, 341 | {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, 342 | {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, 343 | {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, 344 | {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, 345 | {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 346 | {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 347 | {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 348 | {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, 349 | {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 350 | {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, 351 | {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, 352 | {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, 353 | {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 354 | {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, 355 | {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, 356 | {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, 357 | {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, 358 | {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, 359 | {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, 360 | {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, 361 | {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 362 | {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, 363 | {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, 364 | {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 365 | {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, 366 | {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, 367 | {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, 368 | {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, 369 | {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, 370 | {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, 371 | {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, 372 | {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, 373 | {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, 374 | {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, 375 | {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, 376 | {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 377 | {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 378 | {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 379 | {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 380 | {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, 381 | {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 382 | {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, 383 | {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, 384 | {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, 385 | {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 386 | {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, 387 | {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, 388 | {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, 389 | {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, 390 | {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, 391 | {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, 392 | {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, 393 | {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 394 | {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, 395 | {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, 396 | {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, 397 | {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, 398 | {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, 399 | {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, 400 | {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, 401 | {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, 402 | {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, 403 | {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, 404 | {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, 405 | {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, 406 | {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, 407 | {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, 408 | {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, 409 | {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 410 | {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, 411 | {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, 412 | {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, 413 | {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, 414 | {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, 415 | {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 416 | {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, 417 | {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, 418 | {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, 419 | {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, 420 | {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, 421 | {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, 422 | {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, 423 | {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, 424 | {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 425 | {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, 426 | {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, 427 | {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, 428 | {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, 429 | {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, 430 | {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, 431 | {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, 432 | {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 433 | {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, 434 | {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, 435 | {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, 436 | {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, 437 | {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, 438 | {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 439 | {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, 440 | {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 441 | {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 442 | {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 443 | {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 444 | {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, 445 | {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 446 | {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, 447 | {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, 448 | {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, 449 | {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 450 | {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, 451 | {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, 452 | {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, 453 | {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, 454 | {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, 455 | {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, 456 | {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, 457 | {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 458 | {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, 459 | {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, 460 | {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, 461 | {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, 462 | {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, 463 | {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, 464 | {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, 465 | {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, 466 | {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 467 | {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, 468 | {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, 469 | {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, 470 | {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, 471 | {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, 472 | {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 473 | {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 474 | {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, 475 | {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, 476 | {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, 477 | {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, 478 | {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, 479 | {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, 480 | {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, 481 | {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, 482 | {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, 483 | {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, 484 | {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, 485 | {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, 486 | {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, 487 | {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, 488 | {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, 489 | {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, 490 | {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, 491 | {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, 492 | {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, 493 | {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, 494 | {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, 495 | {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, 496 | {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, 497 | {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, 498 | {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, 499 | {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, 500 | {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 501 | {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, 502 | {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, 503 | {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 504 | {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 505 | {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 506 | {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, 507 | {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, 508 | {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, 509 | {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, 510 | {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, 511 | {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, 512 | {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, 513 | {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, 514 | {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, 515 | {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, 516 | {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, 517 | {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 518 | {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, 519 | {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, 520 | {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 521 | {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, 522 | {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, 523 | {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, 524 | {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, 525 | {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, 526 | {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, 527 | {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, 528 | {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 529 | {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, 530 | {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, 531 | {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, 532 | {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, 533 | {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, 534 | {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 535 | {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, 536 | {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 537 | {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, 538 | {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, 539 | {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, 540 | {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, 541 | {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, 542 | {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, 543 | {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, 544 | {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, 545 | {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, 546 | {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, 547 | {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, 548 | {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 549 | {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, 550 | {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, 551 | {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 552 | {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 553 | {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 554 | {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, 555 | {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, 556 | {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 557 | {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, 558 | {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, 559 | {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 560 | {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 561 | {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, 562 | {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 563 | {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, 564 | {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 565 | {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 566 | {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 567 | {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 568 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} 569 | }; 570 | -------------------------------------------------------------------------------- /src/workers/mesher.js: -------------------------------------------------------------------------------- 1 | import instantiate from './program.js'; 2 | 3 | let program; 4 | 5 | const onLoad = ({ data: { options: { chunkSize }, program: module } }) => { 6 | instantiate({ 7 | memory: [ 8 | { id: 'chunks', type: Uint32Array, size: chunkSize * chunkSize * chunkSize * 8 }, 9 | { id: 'counts', type: Int32Array, size: 2 }, 10 | { id: 'vertices', type: Float32Array, size: chunkSize * chunkSize * chunkSize * 3 * 9 * 5 }, 11 | { id: 'voxels', type: Float32Array, size: chunkSize * chunkSize * chunkSize * 0.5 * 6 }, 12 | { id: 'bounds', type: Float32Array, size: 6 }, 13 | ], 14 | program: module, 15 | }) 16 | .then(({ memory, run }) => { 17 | program = { 18 | chunkSize, 19 | memory, 20 | run, 21 | }; 22 | self.removeEventListener('message', onLoad); 23 | self.addEventListener('message', onData); 24 | self.postMessage(true); 25 | }); 26 | }; 27 | self.addEventListener('message', onLoad); 28 | 29 | const onData = ({ data: chunks }) => { 30 | program.memory.chunks.view.set(chunks); 31 | program.run( 32 | program.memory.chunks.address, 33 | program.memory.counts.address, 34 | program.memory.vertices.address, 35 | program.memory.voxels.address, 36 | program.memory.bounds.address, 37 | program.chunkSize 38 | ); 39 | const [triangles, instances] = program.memory.counts.view; 40 | if (triangles === 0) { 41 | self.postMessage({ buffer: chunks, data: false }, [chunks.buffer]); 42 | return; 43 | } 44 | const bounds = program.memory.bounds.view.slice(0); 45 | const vertices = program.memory.vertices.view.slice(0, triangles * 3 * 9); 46 | const voxels = program.memory.voxels.view.slice(0, instances * 6); 47 | self.postMessage({ buffer: chunks, data: { bounds, vertices, voxels } }, [chunks.buffer, bounds.buffer, vertices.buffer, voxels.buffer]); 48 | }; 49 | -------------------------------------------------------------------------------- /src/workers/mesher.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielesteban/sculpty/ccd8b7a14136c58a075d8fda884c22727c3ecd59/src/workers/mesher.wasm -------------------------------------------------------------------------------- /src/workers/program.js: -------------------------------------------------------------------------------- 1 | export default ({ 2 | memory: layout, 3 | program, 4 | }) => { 5 | const pages = Math.ceil(layout.reduce((total, { type, size }) => ( 6 | total + size * type.BYTES_PER_ELEMENT 7 | ), 0) / 65536) + 2; 8 | const memory = new WebAssembly.Memory({ initial: pages, maximum: pages }); 9 | return WebAssembly 10 | .instantiate(program, { env: { memory } }) 11 | .then((instance) => ({ 12 | memory: layout.reduce((layout, { id, type, size }) => { 13 | const address = instance.exports.malloc(size * type.BYTES_PER_ELEMENT); 14 | layout[id] = { 15 | address, 16 | view: new type(memory.buffer, address, size), 17 | }; 18 | return layout; 19 | }, {}), 20 | run: instance.exports.run, 21 | })); 22 | } 23 | -------------------------------------------------------------------------------- /src/workers/worker.ts: -------------------------------------------------------------------------------- 1 | type Job = { 2 | buffers: Uint32Array[], 3 | resolve: (value: any) => void, 4 | }; 5 | 6 | type Response = { 7 | data: { 8 | buffer: Uint32Array; 9 | data: Result; 10 | } 11 | }; 12 | 13 | class Worker { 14 | private instances?: any[]; 15 | private readonly queue: Job[]; 16 | 17 | constructor({ 18 | buffer, 19 | concurrency = navigator.hardwareConcurrency || 1, 20 | options, 21 | program, 22 | script, 23 | }: { 24 | buffer: number; 25 | concurrency?: number; 26 | options: any; 27 | program: any; 28 | script: any; 29 | }) { 30 | this.queue = []; 31 | program().then((program: WebAssembly.Module) => { 32 | this.instances = Array.from({ length: concurrency }, () => { 33 | const worker = new script(); 34 | worker.buffer = new Uint32Array(buffer); 35 | worker.isBusy = true; 36 | worker.run = ({ buffers, resolve }: Job) => { 37 | worker.isBusy = true; 38 | worker.resolve = resolve; 39 | const stride = buffers[0].length; 40 | buffers.forEach((buffer: Uint32Array, i: number) => { 41 | worker.buffer.set(buffer, stride * i) 42 | }); 43 | worker.postMessage(worker.buffer, [worker.buffer.buffer]); 44 | }; 45 | const onLoad = () => { 46 | worker.removeEventListener('message', onLoad); 47 | worker.addEventListener('message', onData); 48 | const queued = this.queue.shift(); 49 | if (queued) { 50 | worker.run(queued); 51 | } else { 52 | worker.isBusy = false; 53 | } 54 | }; 55 | const onData = ({ data: { buffer, data } }: Response) => { 56 | worker.buffer = buffer; 57 | const { resolve } = worker; 58 | delete worker.resolve; 59 | resolve(data); 60 | const queued = this.queue.shift(); 61 | if (queued) { 62 | worker.run(queued); 63 | } else { 64 | worker.isBusy = false; 65 | } 66 | }; 67 | worker.addEventListener('message', onLoad); 68 | worker.postMessage({ options, program }); 69 | return worker; 70 | }); 71 | }); 72 | } 73 | 74 | dispose() { 75 | const { instances } = this; 76 | if (!instances) { 77 | return; 78 | } 79 | instances.forEach((instance) => instance.terminate()); 80 | } 81 | 82 | run(buffers: Uint32Array[]) { 83 | const { instances, queue } = this; 84 | return new Promise((resolve) => { 85 | if (!instances) { 86 | queue.push({ buffers, resolve }); 87 | return; 88 | } 89 | let worker; 90 | for (let i = 0, l = instances.length; i < l; i++) { 91 | if (!instances[i].isBusy) { 92 | worker = instances[i]; 93 | break; 94 | } 95 | } 96 | if (!worker) { 97 | queue.push({ buffers, resolve }); 98 | return; 99 | } 100 | worker.run({ buffers, resolve }); 101 | }); 102 | } 103 | } 104 | 105 | export default Worker; 106 | -------------------------------------------------------------------------------- /src/world.ts: -------------------------------------------------------------------------------- 1 | import { Group, Material, MeshBasicMaterial, Vector3 } from 'three'; 2 | import { Geometry, Materials } from './chunks/types'; 3 | import Chunk from './chunks/chunk'; 4 | import { Storage, MemoryStorage } from './storage'; 5 | import Worker from './workers/worker'; 6 | // @ts-ignore 7 | import MesherProgram from './workers/mesher.wasm'; 8 | // @ts-ignore 9 | import MesherWorker from 'web-worker:./workers/mesher.js'; 10 | 11 | const _queueMicrotask = (typeof self.queueMicrotask === 'function') ? ( 12 | self.queueMicrotask 13 | ) : (callback: () => void) => { 14 | Promise.resolve() 15 | .then(callback) 16 | .catch(e => setTimeout(() => { throw e; })); 17 | }; 18 | 19 | type Action = { x: number; y: number; z: number; undo: number; redo: number; }; 20 | 21 | class World extends Group { 22 | private readonly chunks: Map; 23 | private readonly queue: Map; 24 | private readonly materials: Materials; 25 | private readonly mesher: Worker; 26 | private readonly history: { 27 | enabled: boolean; 28 | last: number; 29 | undo: Action[][]; 30 | redo: Action[][]; 31 | }; 32 | private readonly storage: Storage; 33 | 34 | constructor({ 35 | history = false, 36 | materials = {}, 37 | storage = new MemoryStorage(), 38 | }: { 39 | history?: boolean; 40 | materials?: { triangles?: Material; voxels?: Material; }; 41 | storage?: Storage; 42 | } = {}) { 43 | super(); 44 | this.chunks = new Map(); 45 | this.queue = new Map(); 46 | this.materials = { 47 | triangles: materials.triangles || new MeshBasicMaterial({ vertexColors: true }), 48 | voxels: materials.voxels || new MeshBasicMaterial({ visible: false }), 49 | }; 50 | const { chunkSize } = storage; 51 | this.mesher = new Worker({ 52 | buffer: chunkSize * chunkSize * chunkSize * 8, 53 | options: { chunkSize }, 54 | program: MesherProgram, 55 | script: MesherWorker, 56 | }); 57 | this.history = { enabled: history, last: -1, undo: [], redo: [] }; 58 | this.storage = storage; 59 | } 60 | 61 | dispose() { 62 | const { chunks, mesher, queue } = this; 63 | chunks.forEach((chunk) => chunk.dispose()); 64 | mesher.dispose(); 65 | queue.clear(); 66 | } 67 | 68 | undo() { 69 | const { history } = this; 70 | const actions = history.undo.pop(); 71 | if (!actions) { 72 | return; 73 | } 74 | history.last = -1; 75 | history.redo.push(actions); 76 | this.update( 77 | actions.map(({ x, y, z, undo }) => ({ 78 | x, y, z, 79 | r: (undo >> 8) & 0xFF, g: (undo >> 16) & 0xFF, b: (undo >> 24) & 0xFF, 80 | value: undo & 0xFF, 81 | })), 82 | -1, 83 | true 84 | ); 85 | } 86 | 87 | redo() { 88 | const { history } = this; 89 | const actions = history.redo.pop(); 90 | if (!actions) { 91 | return; 92 | } 93 | history.last = -1; 94 | history.undo.push(actions); 95 | this.update( 96 | actions.map(({ x, y, z, redo }) => ({ 97 | x, y, z, 98 | r: (redo >> 8) & 0xFF, g: (redo >> 16) & 0xFF, b: (redo >> 24) & 0xFF, 99 | value: redo & 0xFF, 100 | })), 101 | -1, 102 | true 103 | ); 104 | } 105 | 106 | updateChunk(x: number, y: number, z: number) { 107 | const { materials, mesher, storage } = this; 108 | const { chunkSize } = storage; 109 | const key: string = `${x}:${y}:${z}`; 110 | let chunk = this.chunks.get(key); 111 | if (!chunk) { 112 | chunk = new Chunk({ 113 | materials, 114 | position: new Vector3( 115 | x * chunkSize + chunkSize * -0.5, 116 | y * chunkSize + chunkSize * -0.5, 117 | z * chunkSize + chunkSize * -0.5 118 | ), 119 | }); 120 | this.add(chunk); 121 | this.chunks.set(key, chunk); 122 | } 123 | const chunks = []; 124 | for (let cz = z - 1; cz <= z; cz++) { 125 | for (let cy = y - 1; cy <= y; cy++) { 126 | for (let cx = x - 1; cx <= x; cx++) { 127 | chunks.push(storage.get(cx, cy, cz)); 128 | } 129 | } 130 | } 131 | const version = ++chunk.request; 132 | mesher.run(chunks).then((geometry) => { 133 | if (chunk && chunk.version < version) { 134 | chunk.update(geometry); 135 | chunk.version = version; 136 | this.dispatchEvent({ type: 'change' }); 137 | } 138 | }); 139 | } 140 | 141 | update( 142 | updates: { 143 | x: number, y: number, z: number, 144 | r?: number, g?: number, b?: number, 145 | value?: number, 146 | }[], 147 | id: number = -1, 148 | isFromHistory: boolean = false 149 | ) { 150 | const { history, queue, storage } = this; 151 | const { chunkSize } = storage; 152 | const halfChunkSize = chunkSize * 0.5; 153 | const hasQueuedUpdate = !!queue.size; 154 | 155 | const affected = new Map(); 156 | const actions = updates.reduce((actions: Action[], { x, y, z, value, r = 0, g = 0, b = 0 }) => { 157 | const cx = Math.floor(x / chunkSize); 158 | const cy = Math.floor(y / chunkSize); 159 | const cz = Math.floor(z / chunkSize); 160 | const vx = x - cx * chunkSize; 161 | const vy = y - cy * chunkSize; 162 | const vz = z - cz * chunkSize; 163 | const vi = vz * chunkSize * chunkSize + vy * chunkSize + vx; 164 | const data = storage.get(cx, cy, cz); 165 | const current = data[vi]; 166 | if (value === undefined) { 167 | value = current & 0xFF; 168 | if (value === 0) { 169 | return actions; 170 | } 171 | } 172 | const updated = ( 173 | (b << 24) 174 | ^ (g << 16) 175 | ^ (r << 8) 176 | ^ value 177 | ); 178 | data[vi] = updated; 179 | 180 | for (let nz = 0; nz < 2; nz++) { 181 | for (let ny = 0; ny < 2; ny++) { 182 | for (let nx = 0; nx < 2; nx++) { 183 | if ( 184 | vx >= ((nx * halfChunkSize) - 1) && vx <= (nx + 1) * halfChunkSize 185 | && vy >= ((ny * halfChunkSize) - 1) && vy <= (ny + 1) * halfChunkSize 186 | && vz >= ((nz * halfChunkSize) - 1) && vz <= (nz + 1) * halfChunkSize 187 | ) { 188 | const ncx = cx + nx; 189 | const ncy = cy + ny; 190 | const ncz = cz + nz; 191 | const nkey = `${ncx}:${ncy}:${ncz}`; 192 | if (!queue.has(nkey)) { 193 | queue.set(nkey, { x: ncx, y: ncy, z: ncz }); 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | const key = `${cx}:${cy}:${cz}`; 201 | if (!affected.has(key)) { 202 | affected.set(key, { x: cx, y: cy, z: cz }); 203 | } 204 | 205 | if (history.enabled && !isFromHistory) { 206 | actions.push({ x, y, z, undo: current, redo: updated }); 207 | } 208 | return actions; 209 | }, []); 210 | 211 | if (history.enabled && !isFromHistory) { 212 | const last = history.undo[history.undo.length - 1]; 213 | if (id !== -1 && history.last === id && last.length < 10000) { 214 | actions.forEach((action) => { 215 | const existing = last.find(({ x, y, z }) => ( 216 | x === action.x 217 | && y === action.y 218 | && z === action.z 219 | )); 220 | if (existing) { 221 | existing.redo = action.redo; 222 | } else { 223 | last.push(action); 224 | } 225 | }); 226 | } else { 227 | history.undo.push(actions); 228 | } 229 | history.last = id; 230 | history.redo.length = 0; 231 | } 232 | 233 | affected.forEach(({ x, y, z }) => storage.save(x, y, z)); 234 | 235 | if (!hasQueuedUpdate && queue.size) { 236 | _queueMicrotask(() => { 237 | queue.forEach(({ x, y, z }) => this.updateChunk(x, y, z)); 238 | queue.clear(); 239 | }); 240 | } 241 | } 242 | } 243 | 244 | export default World; 245 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "noFallthroughCasesInSwitch": true, 7 | "noImplicitReturns": true, 8 | "noImplicitOverride": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "strict": true, 12 | "target": "esnext" 13 | }, 14 | "exclude": ["node_modules", "dist"], 15 | "include": ["src"] 16 | } 17 | --------------------------------------------------------------------------------