├── .github └── FUNDING.yml ├── .gitignore ├── .nvmrc ├── 3d-models ├── shield.blend └── shield.blend1 ├── LICENSE ├── README.md ├── index.html ├── package.json ├── public ├── .gitkeep ├── perlin-noise.png └── shield.glb ├── src ├── Debug.js ├── const.js ├── index.js ├── loaders │ ├── GLTFLoader.js │ ├── TextureLoader.js │ └── index.js ├── materials │ ├── BulletMaterial │ │ ├── fragment.glsl │ │ ├── index.js │ │ └── vertex.glsl │ ├── FloorMaterial │ │ ├── fragment.glsl │ │ ├── index.js │ │ └── vertex.glsl │ ├── SampleShaderMaterial │ │ ├── fragment.glsl │ │ ├── index.js │ │ └── vertex.glsl │ └── ShieldMaterial │ │ ├── fragment.glsl │ │ ├── index.js │ │ └── vertex.glsl ├── physics │ ├── Body.js │ ├── Bullet.js │ ├── Shield.js │ └── Simulation.js └── style.css ├── vite.config.js └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: fra_michelini 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.11.0 2 | -------------------------------------------------------------------------------- /3d-models/shield.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekkorider/threejs-shield/b9e8edab93ef73d7090a36dd35815486dcfb392b/3d-models/shield.blend -------------------------------------------------------------------------------- /3d-models/shield.blend1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekkorider/threejs-shield/b9e8edab93ef73d7090a36dd35815486dcfb392b/3d-models/shield.blend1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Francesco Michelini 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThreeJS starter 2 | 3 | This is a general template for ThreeJS applications. It uses [ViteJS](https://vitejs.dev/) to create the bundle and [Tweakpane](https://github.com/cocopon/tweakpane) for live updates. 4 | 5 | # Before we start 6 | This has been developed with NodeJS `16.11.0`; it should work with other versions too, but in case something doesn't work I recommend to switch to version `16.11.0` with [nvm](https://github.com/nvm-sh/nvm). 7 | 8 | ## Setup 9 | ```shell 10 | $ yarn install 11 | ``` 12 | 13 | ## Develop 14 | 15 | Run 16 | 17 | ```shell 18 | $ yarn dev 19 | ``` 20 | 21 | then open a new browser window and navigate to `http://localhost:1234` 22 | 23 | ## Debug 24 | The template uses dynamic imports to include the code to run the debug panel. To display it, simply append `#debug` to the URL, i.e. `http://localhost:1234#debug`. 25 | 26 | ## Build 27 | 28 | ```shell 29 | $ yarn build 30 | ``` 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ThreeJS Shield Shader 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

15 | ThreeJS Shield Shader 16 |

17 | 18 |

19 | Click the "Shoot" button to see the effect. 20 |

21 | 22 |

23 | Developed by Francesco Michelini using ThreeJS.
24 | Check out the GitHub repository. 25 |

26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-starter", 3 | "private": false, 4 | "version": "1.4.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.1.0", 13 | "vite-plugin-glsl": "^0.3.0" 14 | }, 15 | "dependencies": { 16 | "cannon-es": "^0.20.0", 17 | "cannon-es-debugger": "^1.0.0", 18 | "gsap": "^3.11.1", 19 | "postprocessing": "^6.28.7", 20 | "three": "^0.144.0", 21 | "tweakpane": "^3.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekkorider/threejs-shield/b9e8edab93ef73d7090a36dd35815486dcfb392b/public/.gitkeep -------------------------------------------------------------------------------- /public/perlin-noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekkorider/threejs-shield/b9e8edab93ef73d7090a36dd35815486dcfb392b/public/perlin-noise.png -------------------------------------------------------------------------------- /public/shield.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekkorider/threejs-shield/b9e8edab93ef73d7090a36dd35815486dcfb392b/public/shield.glb -------------------------------------------------------------------------------- /src/Debug.js: -------------------------------------------------------------------------------- 1 | import { Pane } from 'tweakpane' 2 | import { Color, Vector4 } from 'three' 3 | import { gsap } from 'gsap' 4 | 5 | export class Debug { 6 | constructor(app) { 7 | this.app = app 8 | 9 | this.#createPanel() 10 | this.#createSceneConfig() 11 | this.#createBloomConfig() 12 | this.#createControlsConfig() 13 | this.#createShieldConfig() 14 | this.#createHitPointConfig() 15 | } 16 | 17 | refresh() { 18 | this.pane.refresh() 19 | } 20 | 21 | #createPanel() { 22 | this.pane = new Pane({ 23 | container: document.querySelector('#debug') 24 | }) 25 | } 26 | 27 | #createSceneConfig() { 28 | const folder = this.pane.addFolder({ title: 'Scene' }) 29 | 30 | const params = { 31 | background: { r: 18, g: 18, b: 18 } 32 | } 33 | 34 | folder.addInput(params, 'background', { label: 'Background Color' }).on('change', e => { 35 | this.app.renderer.setClearColor(new Color(e.value.r / 255, e.value.g / 255, e.value.b / 255)) 36 | }) 37 | 38 | folder.addSeparator() 39 | 40 | folder.addMonitor(this.app, 'currentHitPointIndex', { label: 'Hit point index' }) 41 | 42 | folder.addSeparator() 43 | 44 | folder.addButton({ title: 'Toggle Physics Debug' }).on('click', () => { 45 | window.dispatchEvent(new CustomEvent('togglePhysicsDebug')) 46 | }) 47 | 48 | folder.addButton({ title: 'Spawn Bullet from random position' }).on('click', () => { 49 | this.app.spawnBullet({ 50 | x: gsap.utils.random(2, 5) * gsap.utils.random([-1, 1]), 51 | y: gsap.utils.random(-3, 3), 52 | z: gsap.utils.random(2, 5) * gsap.utils.random([-1, 1]) 53 | }) 54 | }) 55 | } 56 | 57 | #createBloomConfig() { 58 | const folder = this.pane.addFolder({ title: 'Postprocess - Bloom' }) 59 | 60 | folder.addInput(this.app.bloomPass.effects[0], 'intensity', { label: 'Intensity', min: 0, max: 5 }) 61 | folder.addInput(this.app.bloomPass.effects[0].mipmapBlurPass, 'radius', { label: 'Radius', min: 0, max: 2 }) 62 | } 63 | 64 | #createControlsConfig() { 65 | const folder = this.pane.addFolder({ title: 'Controls' }) 66 | 67 | folder.addButton({ title: 'Hide' }).on('click', () => { 68 | this.app.transformControls.detach() 69 | }) 70 | 71 | folder.addSeparator() 72 | 73 | const items = ['Plane'] 74 | 75 | items.forEach(name => { 76 | folder.addButton({ title: `Attach to ${name}` }).on('click', () => { 77 | this.app.transformControls.attach(this.app.scene.getObjectByName(name)) 78 | }) 79 | }) 80 | } 81 | 82 | #createShieldConfig() { 83 | const folder = this.pane.addFolder({ title: 'Shield' }) 84 | const mesh = this.app.scene.getObjectByName('Shield') 85 | 86 | folder.addInput(mesh.material.uniforms.u_FresnelFalloff, 'value', { label: 'Fresnel falloff', min: 0, max: 3 }) 87 | folder.addInput(mesh.material.uniforms.u_FresnelStrength, 'value', { label: 'Fresnel strength', min: 0, max: 1 }) 88 | 89 | this.#createColorUniformControl(mesh, folder, 'u_FresnelColor', 'Fresnel color') 90 | } 91 | 92 | #createHitPointConfig() { 93 | const folder = this.pane.addFolder({ title: 'Hit Point' }) 94 | const mesh = this.app.scene.getObjectByName('Shield') 95 | 96 | folder.addInput(mesh.material.uniforms.u_HitPoints.value[0], 'size', { label: 'Hit point size', min: 0, max: 3, step: 0.01 }) 97 | .on('change', ({ value }) => { 98 | mesh.material.uniforms.u_HitPoints.value[0].size = value 99 | }) 100 | 101 | folder.addInput(mesh.material.uniforms.u_HitPoints.value[0], 'thickness', { label: 'Hit point thickness', min: 0, max: 1, step: 0.01 }) 102 | .on('change', ({ value }) => { 103 | mesh.material.uniforms.u_HitPoints.value[0].thickness = value 104 | }) 105 | 106 | folder.addSeparator() 107 | 108 | this.#createColorUniformAlphaControl(mesh, folder, 'u_HitPointColorA', 'Color A') 109 | this.#createColorUniformAlphaControl(mesh, folder, 'u_HitPointColorB', 'Color B') 110 | } 111 | 112 | /** 113 | * Adds a color control for the given object to the given folder. 114 | * 115 | * @param {*} obj Any THREE object with a color property 116 | * @param {*} folder The folder to add the control to 117 | * @param {*} label The label of the control 118 | */ 119 | #createColorControl(obj, folder, label = 'Color') { 120 | const baseColor255 = obj.color.clone().multiplyScalar(255) 121 | const params = { color: { r: baseColor255.r, g: baseColor255.g, b: baseColor255.b } } 122 | 123 | folder.addInput(params, 'color', { label }).on('change', e => { 124 | obj.color.setRGB(e.value.r, e.value.g, e.value.b).multiplyScalar(1 / 255) 125 | }) 126 | } 127 | 128 | /** 129 | * Adds a color control for a custom uniform to the given object in the given folder. 130 | * 131 | * @param {THREE.Mesh} obj A `THREE.Mesh` object 132 | * @param {*} folder The folder to add the control to 133 | * @param {String} uniformName The name of the uniform to control 134 | * @param {String} label The label to use for the control 135 | */ 136 | #createColorUniformControl(obj, folder, uniformName, label = 'Color') { 137 | const baseColor255 = obj.material.uniforms[uniformName].value.clone().multiplyScalar(255) 138 | const { r, g, b } = baseColor255 139 | const params = { color: { r, g, b } } 140 | 141 | folder.addInput(params, 'color', { label, view: 'color' }).on('change', ({ value }) => { 142 | obj.material.uniforms[uniformName].value.setRGB(value.r, value.g, value.b).multiplyScalar(1 / 255) 143 | }) 144 | } 145 | 146 | /** 147 | * Adds a color control for a custom uniform to the given object in the given folder. 148 | * 149 | * @param {THREE.Mesh} obj A `THREE.Mesh` object 150 | * @param {*} folder The folder to add the control to 151 | * @param {String} uniformName The name of the uniform to control 152 | * @param {String} label The label to use for the control 153 | */ 154 | #createColorUniformAlphaControl(obj, folder, uniformName, label = 'Color') { 155 | const preMultVector = new Vector4(255, 255, 255, 1) 156 | const postMultVector = new Vector4(1 / 255, 1 / 255, 1 / 255, 1) 157 | 158 | const baseColor255 = obj.material.uniforms[uniformName].value.clone().multiply(preMultVector) 159 | const params = { color: { r: baseColor255.x, g: baseColor255.y, b: baseColor255.z, a: baseColor255.w } } 160 | 161 | folder.addInput(params, 'color', { label, view: 'color', color: { alpha: true } }).on('change', e => { 162 | obj.material.uniforms[uniformName].value.set(e.value.r, e.value.g, e.value.b, e.value.a).multiply(postMultVector) 163 | }) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | export const hitPointsNum = 20 2 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Scene, 3 | WebGLRenderer, 4 | PerspectiveCamera, 5 | Clock, 6 | Vector2, 7 | Plane, 8 | Mesh, 9 | PlaneGeometry, 10 | CylinderGeometry, 11 | Vector3, 12 | RepeatWrapping 13 | } from 'three' 14 | 15 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 16 | import { TransformControls } from 'three/examples/jsm/controls/TransformControls' 17 | 18 | import { BloomEffect, EffectComposer, EffectPass, RenderPass } from "postprocessing" 19 | 20 | import { gsap } from 'gsap' 21 | 22 | import { gltfLoader, textureLoader } from './loaders' 23 | import { hitPointsNum } from './const' 24 | 25 | import { ShieldMaterial } from './materials/ShieldMaterial' 26 | import { FloorMaterial } from './materials/FloorMaterial' 27 | import { BulletMaterial } from './materials/BulletMaterial' 28 | 29 | import { Simulation } from './physics/Simulation' 30 | import { PhysicsShield } from './physics/Shield' 31 | import { PhysicsBullet } from './physics/Bullet' 32 | 33 | class App { 34 | #resizeCallback = () => this.#onResize() 35 | 36 | constructor(container) { 37 | this.container = document.querySelector(container) 38 | this.screen = new Vector2(this.container.clientWidth, this.container.clientHeight) 39 | 40 | this.currentHitPointIndex = 0 41 | 42 | this.bulletGeometry = new CylinderGeometry(0.02, 0.02, 0.4, 12, 1) 43 | } 44 | 45 | async init() { 46 | this.#createScene() 47 | this.#createCamera() 48 | this.#createRenderer() 49 | this.#createClock() 50 | this.#createSimulation() 51 | this.#createPlane() 52 | this.#createClippingPlane() 53 | 54 | await this.#loadTextures() 55 | await this.#createShield() 56 | 57 | this.#addListeners() 58 | this.#createControls() 59 | this.#createPostprocess() 60 | 61 | if (window.location.hash.includes('#debug')) { 62 | const panel = await import('./Debug.js') 63 | new panel.Debug(this) 64 | } 65 | 66 | this.renderer.setAnimationLoop(() => { 67 | this.#update() 68 | this.#render() 69 | }) 70 | 71 | console.log(this) 72 | } 73 | 74 | destroy() { 75 | this.renderer.dispose() 76 | this.#removeListeners() 77 | } 78 | 79 | spawnBullet(position) { 80 | // Add bullet mesh to scene 81 | const bullet = new Mesh(this.bulletGeometry, BulletMaterial) 82 | 83 | bullet.position.copy(position) 84 | bullet.lookAt(this.shield.position) 85 | bullet.rotateX(Math.PI * 0.5) 86 | 87 | this.scene.add(bullet) 88 | 89 | // Add physics body to the physics world 90 | const body = new PhysicsBullet(bullet, this.scene) 91 | this.simulation.addItem(body) 92 | 93 | // Shoot the bullet towards the shield 94 | const dirVector = new Vector3() 95 | dirVector.subVectors(this.shield.position, bullet.position).normalize().multiplyScalar(10) 96 | body.physicsBody.velocity.set(dirVector.x, dirVector.y, dirVector.z) 97 | } 98 | 99 | #update() { 100 | const elapsed = this.clock.getElapsedTime() 101 | 102 | this.orbitControls.maxDistance = this.orbitControls.minDistance = this.screen.x < 1024 ? 5 : 3 103 | this.camera.position.y = this.screen.x < 1024 ? 0.7 : 1 104 | 105 | this.shield.material.uniforms.u_Time.value = elapsed 106 | 107 | this.orbitControls.update() 108 | this.simulation.update() 109 | } 110 | 111 | #render() { 112 | this.composer.render() 113 | } 114 | 115 | #createScene() { 116 | this.scene = new Scene() 117 | } 118 | 119 | #createCamera() { 120 | this.camera = new PerspectiveCamera(75, this.screen.x / this.screen.y, 0.1, 100) 121 | this.camera.position.set(-0.7, 1, 3) 122 | } 123 | 124 | #createRenderer() { 125 | this.renderer = new WebGLRenderer({ 126 | alpha: true, 127 | antialias: false, 128 | stencil: false, 129 | depth: false 130 | }) 131 | 132 | this.container.appendChild(this.renderer.domElement) 133 | 134 | this.renderer.setSize(this.screen.x, this.screen.y) 135 | this.renderer.setPixelRatio(Math.min(1.5, window.devicePixelRatio)) 136 | this.renderer.setClearColor(0x121212) 137 | this.renderer.physicallyCorrectLights = false 138 | this.renderer.localClippingEnabled = true 139 | } 140 | 141 | #createPostprocess() { 142 | this.composer = new EffectComposer(this.renderer) 143 | 144 | this.composer.addPass(new RenderPass(this.scene, this.camera)) 145 | 146 | this.bloomPass = new EffectPass(this.camera, new BloomEffect({ intensity: 3 })) 147 | this.composer.addPass(this.bloomPass) 148 | } 149 | 150 | #createControls() { 151 | this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement) 152 | this.orbitControls.target = new Vector3(0, 0.5, 0) 153 | this.orbitControls.autoRotate = true 154 | this.orbitControls.enablePan = false 155 | this.orbitControls.enableRotate = false 156 | 157 | this.transformControls = new TransformControls(this.camera, this.renderer.domElement) 158 | 159 | this.transformControls.addEventListener('dragging-changed', event => { 160 | this.orbitControls.enabled = !event.value 161 | }) 162 | 163 | this.transformControls.addEventListener('change', event => { 164 | this.shield.material.clippingPlanes[0].constant = -this.plane.position.y 165 | }) 166 | 167 | this.scene.add(this.transformControls) 168 | } 169 | 170 | #createClock() { 171 | this.clock = new Clock() 172 | } 173 | 174 | #createClippingPlane() { 175 | this.clippingPlane = new Plane(new Vector3(0, 1, 0), -this.plane.position.y) 176 | } 177 | 178 | async #loadTextures() { 179 | const [noise] = await textureLoader.load(['/perlin-noise.png']) 180 | 181 | noise.wrapS = noise.wrapT = RepeatWrapping 182 | 183 | this.textures = { 184 | noise 185 | } 186 | } 187 | 188 | async #createShield() { 189 | const gltf = await gltfLoader.load('/shield.glb') 190 | 191 | this.shield = gltf.scene.getObjectByName('Shield') 192 | 193 | this.shield.material = ShieldMaterial 194 | this.shield.material.uniforms.t_Noise.value = this.textures.noise 195 | this.shield.material.clippingPlanes = [this.clippingPlane] 196 | this.shield.position.y = 0.35 197 | 198 | this.scene.add(this.shield) 199 | 200 | const body = new PhysicsShield(this.shield, this.scene) 201 | this.simulation.addItem(body) 202 | } 203 | 204 | #createPlane() { 205 | const geometry = new PlaneGeometry(50, 50, 1, 1) 206 | geometry.rotateX(-Math.PI * 0.5) 207 | 208 | this.plane = new Mesh(geometry, FloorMaterial) 209 | this.plane.position.y -= 0.2 210 | this.plane.name = 'Plane' 211 | 212 | this.scene.add(this.plane) 213 | } 214 | 215 | #createSimulation() { 216 | this.simulation = new Simulation(this.scene) 217 | } 218 | 219 | #addListeners() { 220 | document.querySelector('#shoot').addEventListener('click', () => this.spawnBullet({ 221 | x: gsap.utils.random(2, 5) * gsap.utils.random([-1, 1]), 222 | y: gsap.utils.random(-3, 3), 223 | z: gsap.utils.random(2, 5) * gsap.utils.random([-1, 1]) 224 | }), { passive: true }) 225 | 226 | window.addEventListener('resize', this.#resizeCallback, { passive: true }) 227 | 228 | window.addEventListener('collide', e => { 229 | this.shield.material.uniforms.u_HitPoints.value[this.currentHitPointIndex] = { 230 | position: e.detail.hitPoint, 231 | size: 0, 232 | thickness: 0 233 | } 234 | 235 | const hitPoint = this.shield.material.uniforms.u_HitPoints.value[this.currentHitPointIndex] 236 | 237 | const tl = new gsap.timeline() 238 | 239 | tl 240 | .addLabel('start') 241 | .to(hitPoint, { thickness: 0.45, duration: 0.4 }, 'start') 242 | .to(hitPoint, { size: 1, duration: 1 }, 'start') 243 | .to(hitPoint, { thickness: 0, duration: 0.5 }, 'start+=0.4') 244 | 245 | const item = this.simulation.items.find(item => item.physicsBody === e.detail.body) 246 | this.scene.remove(item.mesh) 247 | this.simulation.removeItem(item) 248 | 249 | this.currentHitPointIndex = gsap.utils.wrap(0, hitPointsNum, this.currentHitPointIndex + 1) 250 | }) 251 | } 252 | 253 | #removeListeners() { 254 | window.removeEventListener('resize', this.#resizeCallback, { passive: true }) 255 | } 256 | 257 | #onResize() { 258 | this.screen.set(this.container.clientWidth, this.container.clientHeight) 259 | 260 | this.camera.aspect = this.screen.x / this.screen.y 261 | this.camera.updateProjectionMatrix() 262 | 263 | this.renderer.setSize(this.screen.x, this.screen.y) 264 | } 265 | } 266 | 267 | const app = new App('#app') 268 | app.init() 269 | -------------------------------------------------------------------------------- /src/loaders/GLTFLoader.js: -------------------------------------------------------------------------------- 1 | import { GLTFLoader as ThreeGLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' 2 | 3 | export class GLTFLoader { 4 | constructor(manager) { 5 | this.loader = new ThreeGLTFLoader(manager) 6 | } 7 | 8 | /** 9 | * Load a single model or an array of models. 10 | * 11 | * @param {String|String[]} resources Single URL or array of URLs of the model(s) to load. 12 | * @returns Object|Object[] 13 | */ 14 | async load(resources) { 15 | if (Array.isArray(resources)) { 16 | const promises = resources.map(url => this.#loadModel(url)) 17 | return await Promise.all(promises) 18 | } else { 19 | return await this.#loadModel(resources) 20 | } 21 | } 22 | 23 | /** 24 | * Load a single model. 25 | * 26 | * @param {String} url The URL of the model to load 27 | * @returns Promise 28 | */ 29 | #loadModel(url) { 30 | return new Promise(resolve => { 31 | this.loader.load(url, model => { 32 | resolve(model) 33 | }) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/loaders/TextureLoader.js: -------------------------------------------------------------------------------- 1 | import { TextureLoader as ThreeTextureLoader } from 'three' 2 | 3 | export class TextureLoader { 4 | constructor(manager) { 5 | this.loader = new ThreeTextureLoader(manager) 6 | } 7 | 8 | /** 9 | * Load a single texture or an array of textures. 10 | * 11 | * @param {String|String[]} resources Single URL or array of URLs of the texture(s) to load. 12 | * @returns Texture|Texture[] 13 | */ 14 | async load(resources) { 15 | if (Array.isArray(resources)) { 16 | const promises = resources.map(url => this.#loadTexture(url)) 17 | return await Promise.all(promises) 18 | } else { 19 | return await this.#loadTexture(resources) 20 | } 21 | } 22 | 23 | /** 24 | * Load a single texture. 25 | * 26 | * @param {String} url The URL of the texture to load 27 | * @returns Promise 28 | */ 29 | #loadTexture(url) { 30 | return new Promise(resolve => { 31 | this.loader.load(url, texture => { 32 | resolve(texture) 33 | }) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/loaders/index.js: -------------------------------------------------------------------------------- 1 | import { LoadingManager } from 'three' 2 | import { TextureLoader } from './TextureLoader' 3 | import { GLTFLoader } from './GLTFLoader' 4 | 5 | /** 6 | * Loading manager 7 | */ 8 | const loadingManager = new LoadingManager() 9 | 10 | loadingManager.onProgress = (url, loaded, total) => { 11 | // In case the progress count is not correct, see this: 12 | // https://discourse.threejs.org/t/gltf-file-loaded-twice-when-loading-is-initiated-in-loadingmanager-inside-onprogress-callback/27799/2 13 | console.log(`Loaded ${loaded} resources out of ${total} -> ${url}`) 14 | } 15 | 16 | /** 17 | * Texture Loader 18 | */ 19 | export const textureLoader = new TextureLoader(loadingManager) 20 | 21 | /** 22 | * GLTF Models 23 | */ 24 | export const gltfLoader = new GLTFLoader(loadingManager) 25 | -------------------------------------------------------------------------------- /src/materials/BulletMaterial/fragment.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | vec3 color = vec3(0.0235, 0.5922, 0.9686); 3 | color += color; 4 | 5 | gl_FragColor = vec4(color, 1.0); 6 | } 7 | -------------------------------------------------------------------------------- /src/materials/BulletMaterial/index.js: -------------------------------------------------------------------------------- 1 | import { ShaderMaterial } from 'three' 2 | 3 | import vertexShader from './vertex.glsl' 4 | import fragmentShader from './fragment.glsl' 5 | 6 | export const BulletMaterial = new ShaderMaterial({ 7 | vertexShader, 8 | fragmentShader, 9 | transparent: true 10 | }) 11 | -------------------------------------------------------------------------------- /src/materials/BulletMaterial/vertex.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 3 | } 4 | -------------------------------------------------------------------------------- /src/materials/FloorMaterial/fragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | #define black vec3(0.0, 0.0, 0.0) 4 | #define white vec3(1.0, 1.0, 1.0) 5 | #define borderThickness 0.01 6 | 7 | void main() { 8 | vec2 gv = fract(vUv * 20.0); 9 | 10 | vec3 color = step(borderThickness, gv.x) * step(borderThickness, gv.y) * white + (1.0 - step(borderThickness, gv.x) * step(borderThickness, gv.y)) * black; 11 | color = 1.0 - color; 12 | 13 | gl_FragColor = vec4(color*0.15, color.r); 14 | } 15 | -------------------------------------------------------------------------------- /src/materials/FloorMaterial/index.js: -------------------------------------------------------------------------------- 1 | import { ShaderMaterial } from 'three' 2 | 3 | import vertexShader from './vertex.glsl' 4 | import fragmentShader from './fragment.glsl' 5 | 6 | export const FloorMaterial = new ShaderMaterial({ 7 | vertexShader, 8 | fragmentShader, 9 | transparent: true 10 | }) 11 | -------------------------------------------------------------------------------- /src/materials/FloorMaterial/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 5 | 6 | vUv = uv; 7 | } 8 | -------------------------------------------------------------------------------- /src/materials/SampleShaderMaterial/fragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | vec3 color = vec3(vUv, 0.5); 5 | 6 | gl_FragColor = vec4(color, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /src/materials/SampleShaderMaterial/index.js: -------------------------------------------------------------------------------- 1 | import { ShaderMaterial } from 'three' 2 | 3 | import vertexShader from './vertex.glsl' 4 | import fragmentShader from './fragment.glsl' 5 | 6 | export const SampleShaderMaterial = new ShaderMaterial({ 7 | vertexShader, 8 | fragmentShader, 9 | transparent: true 10 | }) 11 | -------------------------------------------------------------------------------- /src/materials/SampleShaderMaterial/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 5 | 6 | vUv = uv; 7 | } 8 | -------------------------------------------------------------------------------- /src/materials/ShieldMaterial/fragment.glsl: -------------------------------------------------------------------------------- 1 | struct HitPoint { 2 | vec3 position; 3 | float thickness; 4 | float size; 5 | }; 6 | 7 | varying vec2 vUv; 8 | varying vec3 vWorldPosition; 9 | varying vec3 vNormal; 10 | 11 | uniform sampler2D t_Noise; 12 | 13 | uniform HitPoint u_HitPoints[HIT_POINTS_NUM]; 14 | uniform vec4 u_HitPointColorA; 15 | uniform vec4 u_HitPointColorB; 16 | 17 | uniform float u_FresnelFalloff; 18 | uniform float u_FresnelStrength; 19 | uniform vec3 u_FresnelColor; 20 | 21 | uniform float u_Time; 22 | 23 | #include 24 | 25 | void main() { 26 | #include 27 | 28 | vec4 noise = texture2D(t_Noise, vUv*3.0 + u_Time*vec2(0.127, 0.178)); 29 | 30 | vec3 normal = normalize(vNormal); 31 | vec3 viewDirection = normalize(cameraPosition - vWorldPosition); 32 | 33 | // Draw a ring on the surface of the sphere 34 | float hits = 0.0; 35 | 36 | for (int i = 0; i < HIT_POINTS_NUM; i++) { 37 | float dOuter = distance(u_HitPoints[i].position, vWorldPosition); 38 | dOuter = smoothstep(u_HitPoints[i].size, u_HitPoints[i].size+u_HitPoints[i].thickness, dOuter); 39 | dOuter = 1.0 - dOuter; 40 | 41 | float dInner = distance(u_HitPoints[i].position, vWorldPosition); 42 | dInner = smoothstep(u_HitPoints[i].size-u_HitPoints[i].thickness, u_HitPoints[i].size, dInner); 43 | 44 | float d = dOuter*dInner; 45 | 46 | hits += d; 47 | } 48 | 49 | // Fresnel effect 50 | float fresnel = 1.0 - max(0.0, dot(viewDirection, normal)); 51 | fresnel = pow(fresnel, u_FresnelFalloff); 52 | fresnel = smoothstep(0.45, 0.65, fresnel); 53 | fresnel *= u_FresnelStrength; 54 | 55 | vec4 color = mix(u_HitPointColorA, u_HitPointColorB, hits*smoothstep(0.3, 0.6, noise.r)); 56 | 57 | #if !defined(FLIP_SIDED) 58 | color.rgb += fresnel*u_FresnelColor; 59 | #endif 60 | 61 | gl_FragColor = color; 62 | } 63 | -------------------------------------------------------------------------------- /src/materials/ShieldMaterial/index.js: -------------------------------------------------------------------------------- 1 | import { AdditiveBlending, Color, DoubleSide, ShaderMaterial, Vector3, Vector4 } from 'three' 2 | import { hitPointsNum } from '../../const' 3 | 4 | import vertexShader from './vertex.glsl' 5 | import fragmentShader from './fragment.glsl' 6 | 7 | export const ShieldMaterial = new ShaderMaterial({ 8 | vertexShader, 9 | fragmentShader, 10 | transparent: true, 11 | side: DoubleSide, 12 | blending: AdditiveBlending, 13 | clipIntersection: true, 14 | clipping: true, 15 | defines: { 16 | HIT_POINTS_NUM: hitPointsNum 17 | }, 18 | uniforms: { 19 | t_Noise: { value: null }, 20 | u_Time: { value: 0 }, 21 | u_HitPoints: { value: new Array(hitPointsNum).fill({ position: new Vector3(999, 999, 999), size: 0, thickness: 0 }) }, 22 | u_HitPointColorA: { value: new Vector4(0, 168 / 255, 245 / 255, 0.04) }, 23 | u_HitPointColorB: { value: new Vector4(53 / 255, 175 / 255, 90 / 255, 0.8) }, 24 | u_FresnelFalloff: { value: 0.75 }, 25 | u_FresnelStrength: { value: 0.85 }, 26 | u_FresnelColor: { value: new Color(1, 144 / 255, 0) } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/materials/ShieldMaterial/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying vec3 vWorldPosition; 3 | varying vec3 vNormal; 4 | 5 | #include 6 | 7 | void main() { 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | vUv = uv; 14 | vWorldPosition = (modelMatrix * vec4(transformed, 1.0)).xyz; 15 | vNormal = normal; 16 | } 17 | -------------------------------------------------------------------------------- /src/physics/Body.js: -------------------------------------------------------------------------------- 1 | export class PhysicsBody { 2 | constructor(mesh, scene) { 3 | this.scene = scene 4 | this.mesh = mesh 5 | 6 | this.physicsBody = null 7 | } 8 | 9 | update() { 10 | if (!!!this.physicsBody) return 11 | 12 | this.mesh.position.copy(this.physicsBody.position) 13 | this.mesh.quaternion.copy(this.physicsBody.quaternion) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/physics/Bullet.js: -------------------------------------------------------------------------------- 1 | import { Cylinder, Body, Vec3 } from 'cannon-es' 2 | import { PhysicsBody } from './Body' 3 | 4 | export class PhysicsBullet extends PhysicsBody { 5 | constructor(mesh, scene) { 6 | super(mesh, scene) 7 | 8 | this.addBody() 9 | } 10 | 11 | addBody() { 12 | const { position, quaternion } = this.mesh 13 | const { radiusTop, radiusBottom, height, radialSegments: numSegments } = this.mesh.geometry.parameters 14 | 15 | this.physicsBody = new Body({ 16 | mass: 1, 17 | position, 18 | quaternion, 19 | shape: new Cylinder(radiusTop, radiusBottom, height, numSegments) 20 | }) 21 | 22 | this.physicsBody.addEventListener('collide', this.#onCollide) 23 | } 24 | 25 | #onCollide(e) { 26 | const data = { 27 | body: e.target, 28 | hitPoint: new Vec3( 29 | e.contact.ni.x + e.body.position.x, 30 | e.contact.ni.y + e.body.position.y, 31 | e.contact.ni.z + e.body.position.z 32 | ) 33 | } 34 | 35 | window.dispatchEvent(new CustomEvent('collide', { detail: data })) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/physics/Shield.js: -------------------------------------------------------------------------------- 1 | import { Sphere, Body } from 'cannon-es' 2 | import { PhysicsBody } from './Body' 3 | 4 | export class PhysicsShield extends PhysicsBody { 5 | constructor(mesh, scene) { 6 | super(mesh, scene) 7 | 8 | this.addBody() 9 | } 10 | 11 | addBody() { 12 | this.mesh.geometry.computeBoundingSphere() 13 | 14 | const { position } = this.mesh 15 | const { radius } = this.mesh.geometry.boundingSphere 16 | 17 | this.physicsBody = new Body({ 18 | mass: 0, 19 | position, 20 | shape: new Sphere(radius + 0.01), 21 | type: Body.STATIC 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/physics/Simulation.js: -------------------------------------------------------------------------------- 1 | import { World, Vec3, SAPBroadphase } from 'cannon-es' 2 | 3 | export class Simulation { 4 | constructor(scene) { 5 | this.scene = scene 6 | this.items = [] 7 | 8 | this.init() 9 | } 10 | 11 | async init() { 12 | this.world = new World({ 13 | gravity: new Vec3(0, 0, 0), 14 | broadphase: new SAPBroadphase() 15 | }) 16 | 17 | if (window.location.hash.includes('#debug')) { 18 | const module = await import('cannon-es-debugger') 19 | 20 | this.debugger = new module.default(this.scene, this.world, { 21 | color: 0x005500, 22 | onInit: (body, mesh) => { 23 | window.addEventListener('togglePhysicsDebug', () => { 24 | mesh.visible = !mesh.visible 25 | }) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | addItem(item) { 32 | this.items.push(item) 33 | this.world.addBody(item.physicsBody) 34 | } 35 | 36 | removeItem(item) { 37 | setTimeout(() => { 38 | this.items = this.items.filter((b) => b !== item) 39 | this.world.removeBody(item.physicsBody) 40 | }, 0) 41 | } 42 | 43 | update() { 44 | this.world.fixedStep() 45 | 46 | for (const item of this.items) { 47 | item.update() 48 | } 49 | 50 | this.debugger?.update() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | overflow: hidden; 4 | height: 100vh; 5 | margin: 0; 6 | padding: 0; 7 | font-size: 16px; 8 | } 9 | 10 | #app { 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | #app canvas { 16 | outline: none; 17 | } 18 | 19 | #shoot { 20 | background: #aded0f; 21 | bottom: 40px; 22 | border: none; 23 | clip-path: polygon(0% 0%, 93% 0%, 100% 20%, 100% 100%, 7% 100%, 0% 80%); 24 | color: white; 25 | text-shadow: 2px 2px 0px black; 26 | cursor: pointer; 27 | font-family: "Press Start 2P", cursive; 28 | font-size: 1.2rem; 29 | padding: 0.7em 1.2em; 30 | position: fixed; 31 | right: 40px; 32 | text-transform: uppercase; 33 | z-index: 10; 34 | } 35 | 36 | #shoot:active { 37 | background: white; 38 | color: #aded0f; 39 | } 40 | 41 | #debug { 42 | position: fixed; 43 | right: 10px; 44 | top: 10px; 45 | width: 350px; 46 | z-index: 10; 47 | } 48 | 49 | #credits { 50 | bottom: 100px; 51 | color: white; 52 | left: 24px; 53 | position: fixed; 54 | z-index: 1; 55 | width: calc(100% - 40px); 56 | 57 | @media screen and (min-width: 800px) { 58 | max-width: 60%; 59 | } 60 | 61 | @media screen and (min-width: 1024px) { 62 | max-width: 35%; 63 | } 64 | } 65 | 66 | .title { 67 | font-family: 'Antonio', sans-serif; 68 | font-size: 2.5rem; 69 | margin: 0; 70 | text-transform: uppercase; 71 | } 72 | 73 | .copy { 74 | line-height: 1.4; 75 | font-size: 1rem; 76 | } 77 | 78 | .copy a:hover { 79 | text-decoration: none; 80 | } 81 | 82 | .highlight { 83 | color: #aded0f; 84 | font-weight: 600; 85 | } 86 | 87 | @media screen and (min-width: 1024px) { 88 | 89 | .title { 90 | font-size: 4.6rem; 91 | } 92 | 93 | .copy { 94 | font-size: 1rem; 95 | } 96 | 97 | #credits { 98 | bottom: 24px; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import glsl from 'vite-plugin-glsl' 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | server: { 6 | port: 1234 7 | }, 8 | plugins: [ 9 | glsl() 10 | ] 11 | }) 12 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/linux-loong64@0.15.7": 6 | version "0.15.7" 7 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz#1ec4af4a16c554cbd402cc557ccdd874e3f7be53" 8 | integrity sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw== 9 | 10 | "@rollup/pluginutils@^4.2.1": 11 | version "4.2.1" 12 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" 13 | integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== 14 | dependencies: 15 | estree-walker "^2.0.1" 16 | picomatch "^2.2.2" 17 | 18 | cannon-es-debugger@^1.0.0: 19 | version "1.0.0" 20 | resolved "https://registry.yarnpkg.com/cannon-es-debugger/-/cannon-es-debugger-1.0.0.tgz#28c6679b30dcf608e978225239f447a8fe55ccb4" 21 | integrity sha512-sE9lDOBAYFKlh+0w+cvWKwUhJef8HYnUSVPWPL0jD15MAuVRQKno4QYZSGxgOoJkMR3mQqxL4bxys2b3RSWH8g== 22 | 23 | cannon-es@^0.20.0: 24 | version "0.20.0" 25 | resolved "https://registry.yarnpkg.com/cannon-es/-/cannon-es-0.20.0.tgz#bf2f62a674701f37e3d861c90668a32d4d32f082" 26 | integrity sha512-eZhWTZIkFOnMAJOgfXJa9+b3kVlvG+FX4mdkpePev/w/rP5V8NRquGyEozcjPfEoXUlb+p7d9SUcmDSn14prOA== 27 | 28 | esbuild-android-64@0.15.7: 29 | version "0.15.7" 30 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz#a521604d8c4c6befc7affedc897df8ccde189bea" 31 | integrity sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w== 32 | 33 | esbuild-android-arm64@0.15.7: 34 | version "0.15.7" 35 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz#307b81f1088bf1e81dfe5f3d1d63a2d2a2e3e68e" 36 | integrity sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ== 37 | 38 | esbuild-darwin-64@0.15.7: 39 | version "0.15.7" 40 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz#270117b0c4ec6bcbc5cf3a297a7d11954f007e11" 41 | integrity sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg== 42 | 43 | esbuild-darwin-arm64@0.15.7: 44 | version "0.15.7" 45 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz#97851eacd11dacb7719713602e3319e16202fc77" 46 | integrity sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ== 47 | 48 | esbuild-freebsd-64@0.15.7: 49 | version "0.15.7" 50 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz#1de15ffaf5ae916aa925800aa6d02579960dd8c4" 51 | integrity sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ== 52 | 53 | esbuild-freebsd-arm64@0.15.7: 54 | version "0.15.7" 55 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz#0f160dbf5c9a31a1d8dd87acbbcb1a04b7031594" 56 | integrity sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q== 57 | 58 | esbuild-linux-32@0.15.7: 59 | version "0.15.7" 60 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz#422eb853370a5e40bdce8b39525380de11ccadec" 61 | integrity sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg== 62 | 63 | esbuild-linux-64@0.15.7: 64 | version "0.15.7" 65 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz#f89c468453bb3194b14f19dc32e0b99612e81d2b" 66 | integrity sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ== 67 | 68 | esbuild-linux-arm64@0.15.7: 69 | version "0.15.7" 70 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz#68a79d6eb5e032efb9168a0f340ccfd33d6350a1" 71 | integrity sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw== 72 | 73 | esbuild-linux-arm@0.15.7: 74 | version "0.15.7" 75 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz#2b7c784d0b3339878013dfa82bf5eaf82c7ce7d3" 76 | integrity sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ== 77 | 78 | esbuild-linux-mips64le@0.15.7: 79 | version "0.15.7" 80 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz#bb8330a50b14aa84673816cb63cc6c8b9beb62cc" 81 | integrity sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw== 82 | 83 | esbuild-linux-ppc64le@0.15.7: 84 | version "0.15.7" 85 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz#52544e7fa992811eb996674090d0bc41f067a14b" 86 | integrity sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw== 87 | 88 | esbuild-linux-riscv64@0.15.7: 89 | version "0.15.7" 90 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz#a43ae60697992b957e454cbb622f7ee5297e8159" 91 | integrity sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g== 92 | 93 | esbuild-linux-s390x@0.15.7: 94 | version "0.15.7" 95 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz#8c76a125dd10a84c166294d77416caaf5e1c7b64" 96 | integrity sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ== 97 | 98 | esbuild-netbsd-64@0.15.7: 99 | version "0.15.7" 100 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz#19b2e75449d7d9c32b5d8a222bac2f1e0c3b08fd" 101 | integrity sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ== 102 | 103 | esbuild-openbsd-64@0.15.7: 104 | version "0.15.7" 105 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz#1357b2bf72fd037d9150e751420a1fe4c8618ad7" 106 | integrity sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ== 107 | 108 | esbuild-sunos-64@0.15.7: 109 | version "0.15.7" 110 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz#87ab2c604592a9c3c763e72969da0d72bcde91d2" 111 | integrity sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag== 112 | 113 | esbuild-windows-32@0.15.7: 114 | version "0.15.7" 115 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz#c81e688c0457665a8d463a669e5bf60870323e99" 116 | integrity sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA== 117 | 118 | esbuild-windows-64@0.15.7: 119 | version "0.15.7" 120 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz#2421d1ae34b0561a9d6767346b381961266c4eff" 121 | integrity sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q== 122 | 123 | esbuild-windows-arm64@0.15.7: 124 | version "0.15.7" 125 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz#7d5e9e060a7b454cb2f57f84a3f3c23c8f30b7d2" 126 | integrity sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw== 127 | 128 | esbuild@^0.15.6: 129 | version "0.15.7" 130 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.7.tgz#8a1f1aff58671a3199dd24df95314122fc1ddee8" 131 | integrity sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw== 132 | optionalDependencies: 133 | "@esbuild/linux-loong64" "0.15.7" 134 | esbuild-android-64 "0.15.7" 135 | esbuild-android-arm64 "0.15.7" 136 | esbuild-darwin-64 "0.15.7" 137 | esbuild-darwin-arm64 "0.15.7" 138 | esbuild-freebsd-64 "0.15.7" 139 | esbuild-freebsd-arm64 "0.15.7" 140 | esbuild-linux-32 "0.15.7" 141 | esbuild-linux-64 "0.15.7" 142 | esbuild-linux-arm "0.15.7" 143 | esbuild-linux-arm64 "0.15.7" 144 | esbuild-linux-mips64le "0.15.7" 145 | esbuild-linux-ppc64le "0.15.7" 146 | esbuild-linux-riscv64 "0.15.7" 147 | esbuild-linux-s390x "0.15.7" 148 | esbuild-netbsd-64 "0.15.7" 149 | esbuild-openbsd-64 "0.15.7" 150 | esbuild-sunos-64 "0.15.7" 151 | esbuild-windows-32 "0.15.7" 152 | esbuild-windows-64 "0.15.7" 153 | esbuild-windows-arm64 "0.15.7" 154 | 155 | estree-walker@^2.0.1: 156 | version "2.0.2" 157 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 158 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 159 | 160 | fsevents@~2.3.2: 161 | version "2.3.2" 162 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 163 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 164 | 165 | function-bind@^1.1.1: 166 | version "1.1.1" 167 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 168 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 169 | 170 | gsap@^3.11.1: 171 | version "3.11.1" 172 | resolved "https://registry.yarnpkg.com/gsap/-/gsap-3.11.1.tgz#2d99acf9c9ad02f3b6668bfab7567d31f217a91e" 173 | integrity sha512-UKuJ0UPhntFHMwT6URFQ4cTQv88xc7Kd9Dhxt7qX9IPhC+d+/a5wKW5E5Vn33hZ53nBI1JfApcEbzKgXkcuPZw== 174 | 175 | has@^1.0.3: 176 | version "1.0.3" 177 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 178 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 179 | dependencies: 180 | function-bind "^1.1.1" 181 | 182 | is-core-module@^2.9.0: 183 | version "2.10.0" 184 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" 185 | integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== 186 | dependencies: 187 | has "^1.0.3" 188 | 189 | nanoid@^3.3.4: 190 | version "3.3.4" 191 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 192 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 193 | 194 | path-parse@^1.0.7: 195 | version "1.0.7" 196 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 197 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 198 | 199 | picocolors@^1.0.0: 200 | version "1.0.0" 201 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 202 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 203 | 204 | picomatch@^2.2.2: 205 | version "2.3.1" 206 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 207 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 208 | 209 | postcss@^8.4.16: 210 | version "8.4.16" 211 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" 212 | integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== 213 | dependencies: 214 | nanoid "^3.3.4" 215 | picocolors "^1.0.0" 216 | source-map-js "^1.0.2" 217 | 218 | postprocessing@^6.28.7: 219 | version "6.28.7" 220 | resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.28.7.tgz#ab1104585c3372e576ccf1910a4ea577be379266" 221 | integrity sha512-norsBlNIhDoNG2R0nki9C0bNEARIunE/s5W/4qH/Ozlg3m0dOOmBoY8JZhnmPild/+Jvq/UFEeHQRBvTc11WUg== 222 | 223 | resolve@^1.22.1: 224 | version "1.22.1" 225 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 226 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 227 | dependencies: 228 | is-core-module "^2.9.0" 229 | path-parse "^1.0.7" 230 | supports-preserve-symlinks-flag "^1.0.0" 231 | 232 | rollup@~2.78.0: 233 | version "2.78.1" 234 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f" 235 | integrity sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg== 236 | optionalDependencies: 237 | fsevents "~2.3.2" 238 | 239 | source-map-js@^1.0.2: 240 | version "1.0.2" 241 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 242 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 243 | 244 | supports-preserve-symlinks-flag@^1.0.0: 245 | version "1.0.0" 246 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 247 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 248 | 249 | three@^0.144.0: 250 | version "0.144.0" 251 | resolved "https://registry.yarnpkg.com/three/-/three-0.144.0.tgz#2818517169f8ff94eea5f664f6ff1fcdcd436cc8" 252 | integrity sha512-R8AXPuqfjfRJKkYoTQcTK7A6i3AdO9++2n8ubya/GTU+fEHhYKu1ZooRSCPkx69jbnzT7dD/xEo6eROQTt2lJw== 253 | 254 | tweakpane@^3.1.0: 255 | version "3.1.0" 256 | resolved "https://registry.yarnpkg.com/tweakpane/-/tweakpane-3.1.0.tgz#3ecaecf09c6234e27d0d8f101e16128b65fecc86" 257 | integrity sha512-PGAp/LPQdHwzL7/iAW4lV1p9iPQTti7YMjMWO48CoYjvZRS59RmgQnhEGzKzqST1JnmOYmQUjTe8bdhlZRJs5A== 258 | 259 | vite-plugin-glsl@^0.3.0: 260 | version "0.3.0" 261 | resolved "https://registry.yarnpkg.com/vite-plugin-glsl/-/vite-plugin-glsl-0.3.0.tgz#118197e5aed1725a46dcc3a8f4c4906739a7644d" 262 | integrity sha512-cH7ni+Y3Vz1yOumvrP/71hXVdyTxZIaYYNDeK7KMvO5nzo0uGZZrrkwEVUaESS8/5dYemb/6G0YBj3jsP2sW1A== 263 | dependencies: 264 | "@rollup/pluginutils" "^4.2.1" 265 | 266 | vite@^3.1.0: 267 | version "3.1.0" 268 | resolved "https://registry.yarnpkg.com/vite/-/vite-3.1.0.tgz#3138b279072941d57e76bcf7f66f272fc6a17fe2" 269 | integrity sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g== 270 | dependencies: 271 | esbuild "^0.15.6" 272 | postcss "^8.4.16" 273 | resolve "^1.22.1" 274 | rollup "~2.78.0" 275 | optionalDependencies: 276 | fsevents "~2.3.2" 277 | --------------------------------------------------------------------------------