├── .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 |
27 |
28 |
29 |
30 |
31 |
32 | Shoot!
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 |
--------------------------------------------------------------------------------