Compare commits

...

13 Commits

Author SHA1 Message Date
c52a604f0f add README 2025-05-21 10:21:03 +02:00
8e9a089d34 meta 2025-05-20 17:01:59 +02:00
d5893eb8ef fix changeKey 2025-04-08 00:31:52 +02:00
1e006d46b9 little more bumpScale 2024-10-05 15:23:18 +02:00
0f84f90e05 tweak space theme 2024-10-05 15:22:15 +02:00
4287edab71 rename TetraControls 2024-10-03 22:26:37 +02:00
5c2eaca35a remove import 2024-10-03 00:38:10 +02:00
e7dc780173 T class 2024-10-03 00:37:53 +02:00
9721b311eb move mino materials to InstancedMino.prototype 2024-10-03 00:31:12 +02:00
d3f6cf9b71 refactoring 2024-10-03 00:18:42 +02:00
5b058a58b3 load mino retro texture if needed 2024-10-02 22:38:37 +02:00
9fca05ae6e loading... 2024-10-02 14:48:19 +02:00
1a7628bb42 hide screen on loading 2024-10-02 02:17:23 +02:00
9 changed files with 233 additions and 223 deletions

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# teTra
Falling blocks web game made with three.js librairy
![screenshot](https://git.malingrey.fr/adrien/teTra/raw/branch/master/thumbnail.png)

17
app.js
View File

@ -1,10 +1,10 @@
import * as THREE from 'three' import * as THREE from 'three'
import { scheduler } from './jsm/scheduler.js' import { scheduler } from './jsm/scheduler.js'
import { TRANSLATION, ROTATION, environment, Mino, Playfield, HoldQueue, NextQueue } from './jsm/Tetrominoes.js' import { TRANSLATION, ROTATION, environment, InstancedMino, Mino, Playfield, HoldQueue, NextQueue } from './jsm/Tetrominoes.js'
import Settings from './jsm/Settings.js' import Settings from './jsm/Settings.js'
import { Stats } from './jsm/Stats.js' import { Stats } from './jsm/Stats.js'
import { Menu } from './jsm/Menu.js' import { Menu } from './jsm/Menu.js'
import TetraControls from './jsm/TetraControls.js' import CameraControls from './jsm/CameraControls.js'
import { TetraScene } from './jsm/TetraScene.js' import { TetraScene } from './jsm/TetraScene.js'
import * as FPS from 'three/addons/libs/stats.module.js' import * as FPS from 'three/addons/libs/stats.module.js'
@ -30,7 +30,7 @@ let game = {
menu.stats.show() menu.stats.show()
menu.settings.close() menu.settings.close()
Mino.meshes.clear() Mino.instances.clear()
nextQueue.init() nextQueue.init()
holdQueue.piece = undefined holdQueue.piece = undefined
@ -291,15 +291,16 @@ let loadingManager = new THREE.LoadingManager(
) )
loadingManager.onStart = function (url, itemsLoaded, itemsTotal) { loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
loadingPercent.innerText = "0%" loadingPercent.innerText = "0%"
loadingDiv.style.display = "block" loadingDiv.style.display = "flex"
} }
const stats = new Stats() const stats = new Stats()
const settings = new Settings() const settings = new Settings()
const scene = new TetraScene(settings, loadingManager) const scene = new TetraScene(settings, loadingManager)
const controls = new TetraControls(scene.camera, renderer.domElement) const controls = new CameraControls(scene.camera, renderer.domElement)
scene.add(Mino.meshes) const minoes = new InstancedMino()
scene.add(minoes)
const holdQueue = new HoldQueue() const holdQueue = new HoldQueue()
scene.add(holdQueue) scene.add(holdQueue)
const playfield = new Playfield(loadingManager) const playfield = new Playfield(loadingManager)
@ -307,7 +308,7 @@ scene.add(playfield)
const nextQueue = new NextQueue() const nextQueue = new NextQueue()
scene.add(nextQueue) scene.add(nextQueue)
const menu = new Menu(game, settings, stats, scene, controls, playfield) const menu = new Menu(game, settings, stats, scene, minoes, playfield)
menu.load() menu.load()
let fps let fps
@ -328,7 +329,7 @@ function animate() {
scene.updateMatrixWorld() scene.updateMatrixWorld()
scene.update(delta) scene.update(delta)
playfield.update(delta) playfield.update(delta)
Mino.meshes.update() minoes.update()
controls.update() controls.update()
renderer.render(scene, scene.camera) renderer.render(scene, scene.camera)

View File

@ -1,64 +1,70 @@
body { background-color: black; }
#loadingDiv { #loadingDiv {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-flow: column;
justify-content: center;
box-sizing: border-box; box-sizing: border-box;
font-family: "Open Sans", sans-serif; font-family: "Open Sans", sans-serif;
font-size: 1.4rem; font-size: 1.4rem;
color: lightsteelblue; color: lightsteelblue;
text-align: center; text-align: center;
position: absolute; width: 100vw;
top: 50%; height: 100vh;
left: 50%; padding: 30vh;
transform: translate(-50%, -50%); background-color: black;
z-index: 1;
} }
.scene { .scene {
width: 200px; width: 200px;
height: 200px; height: 200px;
margin: auto; margin: 0 auto;
perspective: 200px; perspective: 200px;
font-size: 40px;
} }
.cube { .tetromino {
width: 40px;
height: 40px;
position: relative; position: relative;
top: 2em;
left: 2em;
width: 1em;
height: 1em;
transform-style: preserve-3d; transform-style: preserve-3d;
transform: translateZ(20px); transform: translateZ(0.5em);
left: 80px;
top: 80px;
}
.cube.is-spinning {
animation: spinCube 5s infinite ease-in-out; animation: spinCube 5s infinite ease-in-out;
} }
@keyframes spinCube { @keyframes spinCube {
0% { transform: translateZ(20px) rotateX( 0deg) rotateY( 0deg); } 0% { transform: translateZ(0.5em) rotateX( 0deg) rotateY( 0deg); }
100% { transform: translateZ(20px) rotateX(360deg) rotateY(360deg); } 100% { transform: translateZ(0.5em) rotateX(360deg) rotateY(360deg); }
} }
.mino {
width: 1em;
height: 1em;
position: absolute;
transform-style: preserve-3d;
}
.T.tetromino .first.mino { top: -0.5em; left: -1em; }
.T.tetromino .second.mino { top: -0.5em; left: 0em; }
.T.tetromino .third.mino { top: -0.5em; left: 1em; }
.T.tetromino .fourth.mino { top: 0.5em; left: 0em; }
.face { .face {
position: absolute; position: absolute;
width: 40px; width: 1em;
height: 40px; height: 1em;
padding: 0; padding: 0;
background: hsla(240, 100%, 0%, 0.4); background: hsla(240, 100%, 0%, 0.4);
border: 1px solid hsla(240, 100%, 70%, 0.6); border: 1px solid hsla(240, 100%, 70%, 0.6);
} }
.front { transform: rotateY( 0deg) translateZ(20px); } .front.face { transform: rotateY( 0deg) translateZ(0.5em); }
.right { transform: rotateY( 90deg) translateZ(20px); } .right.face { transform: rotateY( 90deg) translateZ(0.5em); }
.back { transform: rotateY(180deg) translateZ(20px); } .back.face { transform: rotateY(180deg) translateZ(0.5em); }
.left { transform: rotateY(-90deg) translateZ(20px); } .left.face { transform: rotateY(-90deg) translateZ(0.5em); }
.top { transform: rotateX( 90deg) translateZ(20px); } .top.face { transform: rotateX( 90deg) translateZ(0.5em); }
.bottom { transform: rotateX(-90deg) translateZ(20px); } .bottom.face { transform: rotateX(-90deg) translateZ(0.5em); }
.cube.is-backface-hidden .face {
backface-visibility: hidden;
}
.mino0 { left: -40px; top: -20px; }
.mino1 { left: 0px; top: -20px; }
.mino2 { left: 40px; top: -20px; }
.mino3 { left: 0px; top: 20px; }

View File

@ -9,6 +9,8 @@ body {
span { span {
position: absolute; position: absolute;
top: 0;
left: 0;
} }
.lil-menu { .lil-menu {
@ -171,10 +173,10 @@ h1 {
} }
.pause #pauseSpan { .pause #pauseSpan {
display: flex; position: absolute;
position:absolute;
top: 0; top: 0;
left: 0; left: 0;
display: flex;
filter: blur(2px); filter: blur(2px);
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -8,6 +8,15 @@
<link rel="icon" href="favicon.ico"> <link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/loading.css"> <link rel="stylesheet" href="css/loading.css">
<meta property="og:title" content="ᵀᴱTᴿᴬ"/>
<meta property="og:type" content="game"/>
<meta property="og:url" content="https://adrien.malingrey.fr/jeux/tetra/"/>
<meta property="og:image" content="https://adrien.malingrey.fr/jeux/tetra/thumbnail.png"/>
<meta property="og:image:width" content="250"/>
<meta property="og:image:height" content="250"/>
<meta property="og:description" content="Des blocs qui tombent en 3D"/>
<meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="adrien.malingrey.fr"/>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap"> <script type="importmap">
{ {
@ -20,40 +29,48 @@
</head> </head>
<body> <body>
<div id="loadingDiv"> <span id="loadingDiv">
<div class="scene"> <div class="scene">
<div class="cube is-spinning"> <div class="T tetromino">
<div class="mino0 front face"></div> <div class="first mino">
<div class="mino0 back face"></div> <div class="front face"></div>
<div class="mino0 right face"></div> <div class="back face"></div>
<div class="mino0 left face"></div> <div class="left face"></div>
<div class="mino0 top face"></div> <div class="right face"></div>
<div class="mino0 bottom face"></div> <div class="top face"></div>
<div class="mino1 front face"></div> <div class="bottom face"></div>
<div class="mino1 back face"></div> </div>
<div class="mino1 right face"></div> <div class="second mino">
<div class="mino1 left face"></div> <div class="front face"></div>
<div class="mino1 top face"></div> <div class="back face"></div>
<div class="mino1 bottom face"></div> <div class="left face"></div>
<div class="mino2 front face"></div> <div class="right face"></div>
<div class="mino2 back face"></div> <div class="top face"></div>
<div class="mino2 right face"></div> <div class="bottom face"></div>
<div class="mino2 left face"></div> </div>
<div class="mino2 top face"></div> <div class="third mino">
<div class="mino2 bottom face"></div> <div class="front face"></div>
<div class="mino3 front face"></div> <div class="back face"></div>
<div class="mino3 back face"></div> <div class="left face"></div>
<div class="mino3 right face"></div> <div class="right face"></div>
<div class="mino3 left face"></div> <div class="top face"></div>
<div class="mino3 top face"></div> <div class="bottom face"></div>
<div class="mino3 bottom face"></div> </div>
<div class="fourth mino">
<div class="front face"></div>
<div class="back face"></div>
<div class="left face"></div>
<div class="right face"></div>
<div class="top face"></div>
<div class="bottom face"></div>
</div>
</div> </div>
</div> </div>
<div> <div>
<div>Chargement</div> <div>Chargement</div>
<div id="loadingPercent">0%</div> <div id="loadingPercent">0%</div>
</div> </div>
</div> </span>
<span id="messagesSpan"></span> <span id="messagesSpan"></span>
<span id="pauseSpan" tabindex="1">II</span> <span id="pauseSpan" tabindex="1">II</span>
<audio id="music" src="audio/benevolence.m4a" loop></audio> <audio id="music" src="audio/benevolence.m4a" loop></audio>

View File

@ -1,7 +1,7 @@
import { OrbitControls } from 'three/addons/controls/OrbitControls.js' import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default class TetraControls extends OrbitControls { export default class CameraControls extends OrbitControls {
constructor(camera, domElement) { constructor(camera, domElement) {
super(camera, domElement) super(camera, domElement)
this.autoRotate this.autoRotate

View File

@ -1,10 +1,10 @@
import * as THREE from 'three' import * as THREE from 'three'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js' import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import { Mino, environment } from './Tetrominoes.js' import { environment } from './Tetrominoes.js'
export class Menu extends GUI { export class Menu extends GUI {
constructor(game, settings, stats, scene, controls, playfield) { constructor(game, settings, stats, scene, minoes, playfield) {
super({title: "ᵀᴱTᴿᴬ"}) super({title: "ᵀᴱTᴿᴬ"})
this.startButton = this.add(game, "start").name("Jouer").hide() this.startButton = this.add(game, "start").name("Jouer").hide()
@ -28,7 +28,7 @@ export class Menu extends GUI {
this.settings.add(settings, "theme", ["Plasma", "Espace", "Rétro"]).name("Thème").onChange(theme => { this.settings.add(settings, "theme", ["Plasma", "Espace", "Rétro"]).name("Thème").onChange(theme => {
scene.theme = theme scene.theme = theme
Mino.meshes.theme = theme minoes.theme = theme
if (theme == "Rétro") { if (theme == "Rétro") {
playfield.edge.visible = false playfield.edge.visible = false
playfield.retroEdge.visible = true playfield.retroEdge.visible = true
@ -43,21 +43,21 @@ export class Menu extends GUI {
this.settings.key = this.settings.addFolder("Commandes").open() this.settings.key = this.settings.addFolder("Commandes").open()
let moveLeftKeyController = this.settings.key.add(settings.key, "moveLeft").name('Gauche') let moveLeftKeyController = this.settings.key.add(settings.key, "moveLeft").name('Gauche')
moveLeftKeyController.domElement.onclick = this.changeKey.bind(moveLeftKeyController) moveLeftKeyController.domElement.onclick = this.changeKey(moveLeftKeyController)
let moveRightKeyController = this.settings.key.add(settings.key, "moveRight").name('Droite') let moveRightKeyController = this.settings.key.add(settings.key, "moveRight").name('Droite')
moveRightKeyController.domElement.onclick = this.changeKey.bind(moveRightKeyController) moveRightKeyController.domElement.onclick = this.changeKey(moveRightKeyController)
let rotateCWKeyController = this.settings.key.add(settings.key, "rotateCW").name('Rotation horaire') let rotateCWKeyController = this.settings.key.add(settings.key, "rotateCW").name('Rotation horaire')
rotateCWKeyController.domElement.onclick = this.changeKey.bind(rotateCWKeyController) rotateCWKeyController.domElement.onclick = this.changeKey(rotateCWKeyController)
let rotateCCWKeyController = this.settings.key.add(settings.key, "rotateCCW").name('anti-horaire') let rotateCCWKeyController = this.settings.key.add(settings.key, "rotateCCW").name('anti-horaire')
rotateCCWKeyController.domElement.onclick = this.changeKey.bind(rotateCCWKeyController) rotateCCWKeyController.domElement.onclick = this.changeKey(rotateCCWKeyController)
let softDropKeyController = this.settings.key.add(settings.key, "softDrop").name('Chute lente') let softDropKeyController = this.settings.key.add(settings.key, "softDrop").name('Chute lente')
softDropKeyController.domElement.onclick = this.changeKey.bind(softDropKeyController) softDropKeyController.domElement.onclick = this.changeKey(softDropKeyController)
let hardDropKeyController = this.settings.key.add(settings.key, "hardDrop").name('Chute rapide') let hardDropKeyController = this.settings.key.add(settings.key, "hardDrop").name('Chute rapide')
hardDropKeyController.domElement.onclick = this.changeKey.bind(hardDropKeyController) hardDropKeyController.domElement.onclick = this.changeKey(hardDropKeyController)
let holdKeyController = this.settings.key.add(settings.key, "hold").name('Garder') let holdKeyController = this.settings.key.add(settings.key, "hold").name('Garder')
holdKeyController.domElement.onclick = this.changeKey.bind(holdKeyController) holdKeyController.domElement.onclick = this.changeKey(holdKeyController)
let pauseKeyController = this.settings.key.add(settings.key, "pause").name('Pause') let pauseKeyController = this.settings.key.add(settings.key, "pause").name('Pause')
pauseKeyController.domElement.onclick = this.changeKey.bind(pauseKeyController) pauseKeyController.domElement.onclick = this.changeKey(pauseKeyController)
this.settings.delay = this.settings.addFolder("Répétition automatique").open() this.settings.delay = this.settings.addFolder("Répétition automatique").open()
this.settings.delay.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200).step(1); this.settings.delay.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200).step(1);
@ -77,10 +77,10 @@ export class Menu extends GUI {
function changeMaterial() { function changeMaterial() {
material?.destroy() material?.destroy()
material = dev.addFolder("minoes material") material = dev.addFolder("minoes material")
material.add(Mino.meshes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).listen().onChange(type => { material.add(minoes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).listen().onChange(type => {
switch(type) { switch(type) {
case "MeshBasicMaterial": case "MeshBasicMaterial":
Mino.meshes.material = new THREE.MeshBasicMaterial({ minoes.material = new THREE.MeshBasicMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@ -89,7 +89,7 @@ export class Menu extends GUI {
}) })
break break
case "MeshStandardMaterial": case "MeshStandardMaterial":
Mino.meshes.material = new THREE.MeshStandardMaterial({ minoes.material = new THREE.MeshStandardMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@ -99,7 +99,7 @@ export class Menu extends GUI {
}) })
break break
case "MeshPhysicalMaterial": case "MeshPhysicalMaterial":
Mino.meshes.material = new THREE.MeshPhysicalMaterial({ minoes.material = new THREE.MeshPhysicalMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@ -111,12 +111,11 @@ export class Menu extends GUI {
}) })
break break
} }
Mino.meshes.update = Mino.meshes.updateColor minoes.update = minoes.updateColor
changeMaterial() changeMaterial()
}) })
console.log("lnlnl")
let minoMaterial = Mino.meshes.material instanceof Array ? Mino.meshes.material[0] : Mino.meshes.material let minoMaterial = minoes.material instanceof Array ? minoes.material[0] : minoes.material
if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1) if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1)
if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1) if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1)
if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1) if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1)
@ -153,7 +152,7 @@ export class Menu extends GUI {
vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1) vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1)
vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1) vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1)
changeMaterial(Mino.meshes.material.constructor.name) changeMaterial(minoes.material.constructor.name)
material.close() material.close()
} }
} }
@ -168,8 +167,8 @@ export class Menu extends GUI {
localStorage["teTraSettings"] = JSON.stringify(this.settings.save()) localStorage["teTraSettings"] = JSON.stringify(this.settings.save())
} }
changeKey() { changeKey(settings) {
let controller = this.settings let controller = settings
let input = controller.domElement.getElementsByTagName("input")[0] let input = controller.domElement.getElementsByTagName("input")[0]
input.select() input.select()
input.onkeydown = function (event) { input.onkeydown = function (event) {

View File

@ -69,129 +69,8 @@ const sideMaterial = new THREE.MeshStandardMaterial({
}) })
class InstancedMino extends THREE.InstancedMesh { export class InstancedMino extends THREE.InstancedMesh {
constructor(geometry, material, count) { constructor() {
super(geometry, material, count)
this.instances = new Set()
this.count = 0
this.offsets = new Uint8Array(2*count)
this.update = this.updateColor
}
add(instance) {
this.instances.add(instance)
}
delete(instance) {
this.instances.delete(instance)
}
clear() {
this.instances.clear()
}
set theme(theme) {
this._theme = theme
this.material = Mino.materials[theme]
if (theme == "Rétro") {
this.resetColor()
this.update = this.updateOffset
} else {
this.update = this.updateColor
}
}
get theme() {
return this._theme
}
setOffsetAt(index, offset) {
this.offsets[2*index] = offset.x
this.offsets[2*index + 1] = offset.y
}
resetColor() {
this.instanceColor = null
}
updateColor() {
this.count = 0
this.instances.forEach(mino => {
if (mino.parent?.visible) {
this.setMatrixAt(this.count, mino.matrixWorld)
this.setColorAt(this.count, mino.color)
this.count++
}
})
if (this.count) {
this.instanceMatrix.needsUpdate = true
this.instanceColor.needsUpdate = true
}
}
updateOffset() {
this.count = 0
this.instances.forEach(mino => {
if (mino.parent?.visible) {
this.setMatrixAt(this.count, mino.matrixWorld)
this.setOffsetAt(this.count, mino.offset)
this.count++
}
})
if (this.count) {
this.instanceMatrix.needsUpdate = true
this.geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(this.offsets, 2))
}
}
}
class Mino extends THREE.Object3D {
static materials = {
Plasma: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
roughness: 0.6,
metalness: 1,
}),
Espace: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8,
roughness: 0.1,
metalness: 0.99,
}),
Rétro: [sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial]
}
static {
new THREE.TextureLoader().load("images/sprites.png", (texture) => {
this.materials.Rétro[0] = this.materials.Rétro[2] = new TileMaterial({
color: COLORS.RETRO,
map: texture,
bumpMap: texture,
bumpScale: 1.5,
roughness: 0.25,
metalness: 0.9,
transparent: true,
}, 8, 8)
})
new THREE.TextureLoader().load("images/edges.png", (texture) => {
this.materials.Rétro[1] = this.materials.Rétro[3] =this.materials.Rétro[4] = this.materials.Rétro[4] = new TileMaterial({
color: COLORS.RETRO,
map: texture,
bumpMap: texture,
bumpScale: 1.5,
roughness: 0.25,
metalness: 0.9,
transparent: true,
}, 1, 1)
})
}
static meshes
static {
let minoFaceShape = new THREE.Shape() let minoFaceShape = new THREE.Shape()
minoFaceShape.moveTo(.1, .1) minoFaceShape.moveTo(.1, .1)
minoFaceShape.lineTo(.1, .9) minoFaceShape.lineTo(.1, .9)
@ -208,9 +87,110 @@ class Mino extends THREE.Object3D {
bevelSegments: 1 bevelSegments: 1
} }
const geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings) const geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
this.meshes = new InstancedMino(geometry, this.materials.Plasma, 2*ROWS*COLUMNS) super(geometry, undefined, 2*ROWS*COLUMNS)
this.offsets = new Uint8Array(2*this.count)
} }
set theme(theme) {
if (theme == "Rétro") {
this.resetColor()
this.update = this.updateOffset
if (this.materials["Rétro"]) {
this.material = this.materials["Rétro"]
} else {
this.materials["Rétro"] = []
const loadingManager = new THREE.LoadingManager(() => this.material = this.materials["Rétro"])
new THREE.TextureLoader(loadingManager).load("images/sprites.png", (texture) => {
this.materials.Rétro[0] = this.materials.Rétro[2] = new TileMaterial({
color: COLORS.RETRO,
map: texture,
bumpMap: texture,
bumpScale: 1.5,
roughness: 0.25,
metalness: 0.9,
transparent: true,
}, 8, 8)
})
new THREE.TextureLoader(loadingManager).load("images/edges.png", (texture) => {
this.materials.Rétro[1] = this.materials.Rétro[3] = this.materials.Rétro[4] = this.materials.Rétro[5] = new TileMaterial({
color: COLORS.RETRO,
map: texture,
bumpMap: texture,
bumpScale: 1.5,
roughness: 0.25,
metalness: 0.9,
transparent: true,
}, 1, 1)
})
}
} else {
this.update = this.updateColor
this.material = this.materials[theme]
}
}
setOffsetAt(index, offset) {
this.offsets[2*index] = offset.x
this.offsets[2*index + 1] = offset.y
}
resetColor() {
this.instanceColor = null
}
updateColor() {
this.count = 0
Mino.instances.forEach(mino => {
if (mino.parent?.visible) {
this.setMatrixAt(this.count, mino.matrixWorld)
this.setColorAt(this.count, mino.color)
this.count++
}
})
if (this.count) {
this.instanceMatrix.needsUpdate = true
this.instanceColor.needsUpdate = true
}
}
updateOffset() {
this.count = 0
Mino.instances.forEach(mino => {
if (mino.parent?.visible) {
this.setMatrixAt(this.count, mino.matrixWorld)
this.setOffsetAt(this.count, mino.offset)
this.count++
}
})
if (this.count) {
this.instanceMatrix.needsUpdate = true
this.geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(this.offsets, 2))
}
}
}
InstancedMino.prototype.materials = {
Plasma: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
roughness: 0.6,
metalness: 1,
}),
Espace: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8,
roughness: 0.1,
metalness: 0.99,
})
}
class Mino extends THREE.Object3D {
static instances = new Set()
constructor(color, offset) { constructor(color, offset) {
super() super()
this.color = color this.color = color
@ -218,7 +198,7 @@ class Mino extends THREE.Object3D {
this.velocity = P(50 - 100 * Math.random(), 60 - 100 * Math.random(), 50 - 100 * Math.random()) this.velocity = P(50 - 100 * Math.random(), 60 - 100 * Math.random(), 50 - 100 * Math.random())
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize() this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
this.angularVelocity = 5 - 10 * Math.random() this.angularVelocity = 5 - 10 * Math.random()
this.constructor.meshes.add(this) this.constructor.instances.add(this)
} }
explode(delta) { explode(delta) {
@ -234,7 +214,7 @@ class Mino extends THREE.Object3D {
} }
dispose() { dispose() {
this.constructor.meshes.delete(this) this.constructor.instances.delete(this)
} }
} }

View File

@ -70,10 +70,10 @@ export class Vortex extends THREE.Group {
new THREE.TextureLoader(this.loadingManager).load("./images/dark.jpg", texture => { new THREE.TextureLoader(this.loadingManager).load("./images/dark.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.MirroredRepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(2, 2) texture.repeat.set(2, 4)
this.darkCylinder.material.map = texture this.darkCylinder.material.map = texture
}) })
this.darkCylinder.material.opacity = 0.05 this.darkCylinder.material.opacity = 0.08
new THREE.TextureLoader(this.loadingManager).load("./images/colorfull.jpg", texture => { new THREE.TextureLoader(this.loadingManager).load("./images/colorfull.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.RepeatWrapping
@ -81,7 +81,7 @@ export class Vortex extends THREE.Group {
texture.repeat.set(2, 2) texture.repeat.set(2, 2)
this.colorFullCylinder.material.map = texture this.colorFullCylinder.material.map = texture
}) })
this.colorFullCylinder.material.opacity = 0.14 this.colorFullCylinder.material.opacity = 0.15
this.globalRotation = 0.028 this.globalRotation = 0.028
this.darkTextureRotation = 0.006 this.darkTextureRotation = 0.006