INSTANCED MESHES

This commit is contained in:
Adrien MALINGREY 2023-07-14 02:27:21 +02:00
parent a4be8e064c
commit eadae0205f
4 changed files with 220 additions and 299 deletions

58
app.js
View File

@ -1,6 +1,6 @@
import * as THREE from 'three'
import { scheduler } from './jsm/scheduler.js'
import { TRANSLATION, ROTATION, environnement, Matrix, HoldQueue, NextQueue } from './jsm/gamelogic.js'
import { TRANSLATION, ROTATION, environnement, Playfield, HoldQueue, NextQueue } from './jsm/gamelogic.js'
import { Settings } from './jsm/Settings.js'
import { Stats } from './jsm/Stats.js'
import { TetraGUI } from './jsm/TetraGUI.js'
@ -31,13 +31,13 @@ let game = {
holdQueue.remove(holdQueue.piece)
holdQueue.piece = undefined
if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
matrix.init()
playfield.init()
scene.remove(matrix.piece)
if (matrix.piece) matrix.remove(matrix.piece)
matrix.piece = null
scene.remove(playfield.piece)
if (playfield.piece) playfield.remove(playfield.piece)
playfield.piece = null
scene.music.currentTime = 0
matrix.visible = true
playfield.visible = true
this.playing = true
stats.clock.start()
@ -65,16 +65,16 @@ let game = {
stats.clock.elapsedTime = stats.elapsedTime
scene.music.play()
if (matrix.piece) scheduler.setInterval(game.fall, stats.fallPeriod)
if (playfield.piece) scheduler.setInterval(game.fall, stats.fallPeriod)
else this.generate()
},
generate: function(nextPiece=nextQueue.shift()) {
nextPiece.lockDelay = stats.lockDelay
matrix.piece = nextPiece
matrix.piece.onLockDown = game.lockDown
playfield.piece = nextPiece
playfield.piece.onLockDown = game.lockDown
if (matrix.piece.canMove(TRANSLATION.NONE)) {
if (playfield.piece.canMove(TRANSLATION.NONE)) {
scheduler.setInterval(game.fall, stats.fallPeriod)
} else {
game.over() // block out
@ -82,17 +82,17 @@ let game = {
},
fall: function() {
matrix.piece.move(TRANSLATION.DOWN)
playfield.piece.move(TRANSLATION.DOWN)
},
lockDown: function() {
scheduler.clearTimeout(game.lockDown)
scheduler.clearInterval(game.fall)
if (matrix.lock(matrix.piece)) {
let tSpin = matrix.piece.tSpin
let nbClearedLines = matrix.clearLines()
matrix.remove(matrix.piece)
if (playfield.lock(playfield.piece)) {
let tSpin = playfield.piece.tSpin
let nbClearedLines = playfield.clearLines()
playfield.remove(playfield.piece)
if (settings.sfxVolume) {
if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
scene.tetrisSound.currentTime = 0
@ -130,7 +130,7 @@ let game = {
},
over: function() {
matrix.piece.locking = false
playfield.piece.locking = false
document.onkeydown = null
window.onblur = null
@ -151,16 +151,16 @@ let game = {
/* Handle player inputs */
let playerActions = {
moveLeft: () => matrix.piece.move(TRANSLATION.LEFT),
moveLeft: () => playfield.piece.move(TRANSLATION.LEFT),
moveRight: () => matrix.piece.move(TRANSLATION.RIGHT),
moveRight: () => playfield.piece.move(TRANSLATION.RIGHT),
rotateCW: () => matrix.piece.rotate(ROTATION.CW),
rotateCW: () => playfield.piece.rotate(ROTATION.CW),
rotateCCW: () => matrix.piece.rotate(ROTATION.CCW),
rotateCCW: () => playfield.piece.rotate(ROTATION.CCW),
softDrop: function () {
if (matrix.piece.move(TRANSLATION.DOWN)) stats.score++
if (playfield.piece.move(TRANSLATION.DOWN)) stats.score++
},
hardDrop: function () {
@ -170,19 +170,19 @@ let playerActions = {
scene.hardDropSound.currentTime = 0
scene.hardDropSound.play()
}
while (matrix.piece.move(TRANSLATION.DOWN)) stats.score += 2
while (playfield.piece.move(TRANSLATION.DOWN)) stats.score += 2
game.lockDown()
matrix.hardDropAnimation.reset()
matrix.hardDropAnimation.play()
playfield.hardDropAnimation.reset()
playfield.hardDropAnimation.play()
},
hold: function () {
if (matrix.piece.holdEnabled) {
if (playfield.piece.holdEnabled) {
scheduler.clearInterval(game.fall)
scheduler.clearTimeout(game.lockDown)
let heldpiece = holdQueue.piece
holdQueue.piece = matrix.piece
holdQueue.piece = playfield.piece
game.generate(heldpiece)
}
},
@ -290,8 +290,8 @@ const clock = new THREE.Clock()
const holdQueue = new HoldQueue()
scene.add(holdQueue)
const matrix = new Matrix()
scene.add(matrix)
const playfield = new Playfield()
scene.add(playfield)
const nextQueue = new NextQueue()
scene.add(nextQueue)
@ -306,7 +306,7 @@ function animate() {
const delta = clock.getDelta()
scene.update(delta)
matrix.update(delta)
playfield.update(delta)
controls.update()
gui.update()

View File

@ -1,10 +1,10 @@
import * as THREE from 'three'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import * as FPS from 'three/addons/libs/stats.module.js'
import { COLORS, environnement, I, J, L, O, S, T, Z } from './gamelogic.js'
import { COLORS, environnement, minoMaterial, I, J, L, O, S, T, Z, Tetromino } from './gamelogic.js'
class TetraGUI extends GUI {
export class TetraGUI extends GUI {
constructor(game, settings, stats, scene) {
super({title: "teTra"})
this.domElement.tabIndex = 1
@ -135,6 +135,11 @@ class TetraGUI extends GUI {
directionalLightPosition.add(scene.directionalLight.position, "x")
directionalLightPosition.add(scene.directionalLight.position, "y")
directionalLightPosition.add(scene.directionalLight.position, "z")
let directionalLightTargetPosition = this.debug.addFolder("directionalLight.target").close()
directionalLightTargetPosition.add(scene.directionalLight.target.position, "x")
directionalLightTargetPosition.add(scene.directionalLight.target.position, "y")
directionalLightTargetPosition.add(scene.directionalLight.target.position, "z")
let light = this.debug.addFolder("lights intensity").close()
light.add(scene.ambientLight, "intensity").name("ambient").min(0).max(20)
@ -144,125 +149,18 @@ class TetraGUI extends GUI {
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)
let materialParams = {
type: "MeshBasicMaterial",
opacity: 0.95,
reflectivity: 0.8,
roughness: 0.1,
metalness: 0.5,
attenuationDistance: 0.5,
ior: 2,
sheen: 0,
sheenRoughness: 1,
specularIntensity: 1,
thickness: 5,
transmission: 1,
}
let material = this.debug.addFolder("minoes material").close()
let type = material.add(materialParams, "type", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"])
let opacity = material.add(materialParams, "opacity").min(0).max(1)
let reflectivity = material.add(materialParams, "reflectivity").min(0).max(1)
let roughness = material.add(materialParams, "roughness").min(0).max(1).hide()
let metalness = material.add(materialParams, "metalness").min(0).max(1).hide()
let attenuationDistance = material.add(materialParams, "attenuationDistance").min(0).max(1).hide()
let ior = material.add(materialParams, "ior").min(1).max(2).hide()
let sheen = material.add(materialParams, "sheen").min(0).max(1).hide()
let sheenRoughness = material.add(materialParams, "sheenRoughness").min(0).max(1).hide()
let specularIntensity = material.add(materialParams, "specularIntensity").min(0).max(1).hide()
let thickness = material.add(materialParams, "thickness").min(0).max(5).hide()
let transmission = material.add(materialParams, "transmission").min(0).max(1).hide()
type.onChange(type => {
switch(type) {
case "MeshBasicMaterial":
reflectivity.show()
roughness.hide()
metalness.hide()
attenuationDistance.hide()
ior.hide()
sheen.hide()
sheenRoughness.hide()
specularIntensity.hide()
thickness.hide()
transmission.hide()
break
case "MeshStandardMaterial":
reflectivity.hide()
roughness.show()
metalness.show()
attenuationDistance.hide()
ior.hide()
sheen.hide()
sheenRoughness.hide()
specularIntensity.hide()
thickness.hide()
transmission.hide()
break
case "MeshPhysicalMaterial":
reflectivity.hide()
roughness.show()
metalness.show()
attenuationDistance.show()
ior.show()
sheen.show()
sheenRoughness.show()
specularIntensity.show()
thickness.show()
transmission.show()
break
}
})
material.onChange(() => {
let minoMaterialFactory
switch(materialParams.type) {
case "MeshBasicMaterial":
minoMaterialFactory = color => new THREE.MeshBasicMaterial({
color : color,
envMap : environnement,
reflectivity: materialParams.reflectivity,
transparent : true,
opacity : materialParams.opacity,
side : THREE.DoubleSide,
})
break
case "MeshStandardMaterial":
minoMaterialFactory = color => new THREE.MeshStandardMaterial({
color : color,
envMap : environnement,
transparent: true,
opacity : materialParams.opacity,
side : THREE.DoubleSide,
roughness : materialParams.roughness,
metalness : materialParams.metalness,
})
break
case "MeshPhysicalMaterial":
minoMaterialFactory = color => new THREE.MeshPhysicalMaterial({
color : "white",
envMap : environnement,
transparent : true,
opacity : materialParams.opacity,
side : THREE.DoubleSide,
roughness : materialParams.roughness,
metalness : materialParams.metalness,
attenuationColor : color,
attenuationDistance: materialParams.attenuationDistance,
ior : materialParams.ior,
sheen : materialParams.sheen,
sheenRoughness : materialParams.sheenRoughness,
specularIntensity : materialParams.specularIntensity,
thickness : materialParams.thickness,
transmission : materialParams.transmission,
})
break
}
I.prototype.material = minoMaterialFactory(COLORS.I)
J.prototype.material = minoMaterialFactory(COLORS.J)
L.prototype.material = minoMaterialFactory(COLORS.L)
O.prototype.material = minoMaterialFactory(COLORS.O)
S.prototype.material = minoMaterialFactory(COLORS.S)
T.prototype.material = minoMaterialFactory(COLORS.T)
Z.prototype.material = minoMaterialFactory(COLORS.Z)
})
material.add(minoMaterial, "opacity").min(0).max(1)
//material.add(minoMaterial, "reflectivity").min(0).max(1)
material.add(minoMaterial, "roughness").min(0).max(1)
material.add(minoMaterial, "metalness").min(0).max(1)
//material.add(minoMaterial, "attenuationDistance").min(0).max(1).hide()
//material.add(minoMaterial, "ior").min(1).max(2).hide()
//material.add(minoMaterial, "sheen").min(0).max(1).hide()
//material.add(minoMaterial, "sheenRoughness").min(0).max(1).hide()
//material.add(minoMaterial, "specularIntensity").min(0).max(1).hide()
//material.add(minoMaterial, "thickness").min(0).max(5).hide()
//material.add(minoMaterial, "transmission").min(0).max(1).hide()
this.fps = new FPS.default()
document.body.appendChild(this.fps.dom)
@ -294,7 +192,4 @@ class TetraGUI extends GUI {
update() {
this.fps?.update()
}
}
export { TetraGUI }
}

View File

@ -2,7 +2,7 @@ import * as THREE from 'three'
import { Vortex } from './Vortex.js'
class TetraScene extends THREE.Scene {
export class TetraScene extends THREE.Scene {
constructor(loadingManager, settings) {
super()
@ -12,12 +12,15 @@ class TetraScene extends THREE.Scene {
this.vortex = new Vortex(loadingManager)
this.add(this.vortex)
this.ambientLight = new THREE.AmbientLight(0xffffff, 1)
this.ambientLight = new THREE.AmbientLight(0xffffff, .5)
this.add(this.ambientLight)
this.directionalLight = new THREE.DirectionalLight(0xffffff, 20)
this.directionalLight.position.set(5, -100, -20)
this.directionalLight = new THREE.DirectionalLight(0xffffff, 6)
this.directionalLight.position.set(5, -100, 0)
this.add(this.directionalLight)
this.directionalLight.target = new THREE.Object3D()
this.directionalLight.target.position.set(5, -50, 20)
this.add(this.directionalLight.target)
/* Sounds */
@ -53,7 +56,4 @@ class TetraScene extends THREE.Scene {
update(delta) {
this.vortex.update(delta)
}
}
export { TetraScene }
}

View File

@ -16,6 +16,8 @@ const COLORS = {
S: 0xC8FBA8,
T: 0xedb2ff,
Z: 0xffb8c5,
LOCKING: "white",
GHOST: "white",
}
const TRANSLATION = {
@ -52,20 +54,6 @@ environnement.camera = new THREE.CubeCamera(1, 1000, envRenderTarget)
environnement.camera.position.set(5, 10)
class Mino extends THREE.Mesh {
constructor() {
super(Mino.prototype.geometry)
this.velocity = P(50 - 100 * Math.random(), 50 - 100 * Math.random(), 50 - 100 * Math.random())
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
this.angularVelocity = 5 - 10 * Math.random()
}
update(delta) {
this.velocity.y += delta * GRAVITY
this.position.addScaledVector(this.velocity, delta)
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
}
}
const minoFaceShape = new THREE.Shape()
minoFaceShape.moveTo(.1, .1)
minoFaceShape.lineTo(.1, .9)
@ -81,67 +69,46 @@ const minoExtrudeSettings = {
bevelOffset: 0,
bevelSegments: 1
}
Mino.prototype.geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
let minoGeometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
let minoMaterial = new THREE.MeshStandardMaterial({
envMap: environnement,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8,
//reflectivity: 0.8,
roughness: 0.1,
metalness: 0.9,
//attenuationDistance: 0.5,
//ior: 2,
//sheen: 0,
//sheenRoughness: 1,
//specularIntensity: 1,
//thickness: 5,
//transmission: 1,
})
class MinoMaterial extends THREE.MeshBasicMaterial {
constructor(color) {
super({
color: color,
envMap: environnement,
reflectivity: 0.9,
transparent: true,
opacity: 0.8,
side: THREE.DoubleSide,
})
}
}
class GhostMaterial extends THREE.MeshBasicMaterial {
constructor(color) {
super({
color: color,
envMap: environnement,
reflectivity: 0.9,
transparent: true,
opacity: 0.15,
side: THREE.DoubleSide,
})
}
}
class AbstractTetromino extends THREE.Group {
constructor() {
class Mino extends THREE.Object3D {
constructor(color, x, y, z=0) {
super()
for (let i = 0; i < 4; i++) {
this.add(new Mino())
}
this.color = color
this.position.set(x, y, z)
this.velocity = P(50 - 100 * Math.random(), 50 - 100 * Math.random(), 50 - 100 * Math.random())
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
this.angularVelocity = 5 - 10 * Math.random()
}
set facing(facing) {
this._facing = facing
this.minoesPosition[this.facing].forEach(
(position, i) => this.children[i].position.copy(position)
)
}
get facing() {
return this._facing
}
canMove(translation, facing=this.facing) {
let testPosition = this.position.clone().add(translation)
return this.minoesPosition[facing].every(minoPosition => this.parent.cellIsEmpty(minoPosition.clone().add(testPosition)))
update(delta) {
this.velocity.y += delta * GRAVITY
this.position.addScaledVector(this.velocity, delta)
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
this.updateMatrix()
}
}
class Ghost extends AbstractTetromino {}
Ghost.prototype.minoesPosition = [
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
]
class Tetromino extends AbstractTetromino {
class Tetromino extends THREE.InstancedMesh {
static randomBag = []
static get random() {
if (!this.randomBag.length) this.randomBag = [I, J, L, O, S, T, Z]
@ -149,14 +116,49 @@ class Tetromino extends AbstractTetromino {
}
constructor() {
super()
super(minoGeometry, undefined, 4)
this.material = this.minoMaterial
this.facing = FACING.NORTH
this.rotatedLast = false
this.rotationPoint4Used = false
this.holdEnabled = true
this.facing = 0
this.locking = false
}
set facing(facing) {
this._facing = facing
let matrix4 = new THREE.Matrix4()
this.minoesPosition[this.facing].forEach((position, i) => {
matrix4.setPosition(position)
this.setMatrixAt(i, matrix4)
})
this.instanceMatrix.needsUpdate = true
}
get facing() {
return this._facing
}
set locking(locking) {
if (locking) {
this.color = this.lockingColor
} else {
this.color = this.freeColor
}
}
set color(color) {
for (let i = 0; i < this.count; i++) {
this.setColorAt(i, color)
}
this.instanceColor.needsUpdate = true
}
canMove(translation, facing=this.facing) {
let testPosition = this.position.clone().add(translation)
return this.minoesPosition[facing].every(minoPosition => this.parent.cellIsEmpty(minoPosition.clone().add(testPosition)))
}
move(translation, rotatedFacing, rotationPoint) {
if (this.canMove(translation, rotatedFacing)) {
this.position.add(translation)
@ -167,14 +169,16 @@ class Tetromino extends AbstractTetromino {
}
if (this.canMove(TRANSLATION.DOWN)) {
this.locking = false
this.parent.ghost.visible = true
this.parent.ghost.copy(this)
scheduler.clearTimeout(this.onLockDown)
} else {
scheduler.resetTimeout(this.onLockDown, this.lockDelay)
this.locking = true
this.parent.ghost.visible = false
}
if (this.ghost.visible) this.updateGhost()
return true
} else {
} else if (translation == TRANSLATION.DOWN) {
this.locked = true
if (!scheduler.timeoutTasks.has(this.onLockDown))
scheduler.setTimeout(this.onLockDown, this.lockDelay)
@ -189,27 +193,19 @@ class Tetromino extends AbstractTetromino {
)
}
set locking(locking) {
if (locking) {
this.children.forEach(mino => mino.material = this.lockedMaterial)
this.ghost.visible = false
} else {
this.children.forEach(mino => mino.material = this.material)
this.ghost.visible = true
}
}
updateGhost() {
this.ghost.position.copy(this.position)
this.ghost.minoesPosition = this.minoesPosition
this.ghost.facing = this.facing
while (this.ghost.canMove(TRANSLATION.DOWN)) this.ghost.position.y--
}
get tSpin() {
return T_SPIN.NONE
}
copy(piece) {
this.position.copy(piece.position)
this.minoesPosition = piece.minoesPosition
this.facing = piece.facing
while (this.canMove(TRANSLATION.DOWN)) this.position.y--
}
}
Tetromino.prototype.minoMaterial = minoMaterial
Tetromino.prototype.lockingColor = new THREE.Color(COLORS.LOCKING)
// Super Rotation System
// freedom of movement = srs[this.parent.piece.facing][rotation]
Tetromino.prototype.srs = [
@ -218,9 +214,21 @@ Tetromino.prototype.srs = [
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)] },
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)] },
]
Tetromino.prototype.lockedMaterial = new MinoMaterial(0xffffff)
Tetromino.prototype.lockDelay = 500
Tetromino.prototype.ghost = new Ghost()
class Ghost extends Tetromino {}
Ghost.prototype.minoMaterial = new THREE.MeshBasicMaterial({
envMap: environnement,
reflectivity: 0.9,
transparent: true,
opacity: 0.15,
side: THREE.DoubleSide,
})
Ghost.prototype.freeColor = new THREE.Color(COLORS.GHOST)
Ghost.prototype.minoesPosition = [
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
]
class I extends Tetromino { }
@ -236,8 +244,7 @@ I.prototype.srs = [
{ [ROTATION.CW]: [P(0, 0), P(2, 0), P(-1, 0), P(2, 1), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)] },
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)], [ROTATION.CCW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)] },
]
I.prototype.material = new MinoMaterial(COLORS.I)
I.prototype.ghostMaterial = new GhostMaterial(COLORS.I)
I.prototype.freeColor = new THREE.Color(COLORS.I)
class J extends Tetromino { }
J.prototype.minoesPosition = [
@ -246,8 +253,7 @@ J.prototype.minoesPosition = [
[P(1, -1), P(-1, 0), P(0, 0), P(1, 0)],
[P(0, 1), P(-1, -1), P(0, 0), P(0, -1)],
]
J.prototype.material = new MinoMaterial(COLORS.J)
J.prototype.ghostMaterial = new GhostMaterial(COLORS.J)
J.prototype.freeColor = new THREE.Color(COLORS.J)
class L extends Tetromino { }
L.prototype.minoesPosition = [
@ -256,8 +262,7 @@ L.prototype.minoesPosition = [
[P(-1, 0), P(0, 0), P(1, 0), P(-1, -1)],
[P(0, 1), P(0, 0), P(0, -1), P(-1, 1)],
]
L.prototype.material = new MinoMaterial(COLORS.L)
L.prototype.ghostMaterial = new GhostMaterial(COLORS.L)
L.prototype.freeColor = new THREE.Color(COLORS.L)
class O extends Tetromino { }
O.prototype.minoesPosition = [
@ -266,8 +271,7 @@ O.prototype.minoesPosition = [
O.prototype.srs = [
{ [ROTATION.CW]: [], [ROTATION.CCW]: [] }
]
O.prototype.material = new MinoMaterial(COLORS.O)
O.prototype.ghostMaterial = new GhostMaterial(COLORS.O)
O.prototype.freeColor = new THREE.Color(COLORS.O)
class S extends Tetromino { }
S.prototype.minoesPosition = [
@ -276,8 +280,7 @@ S.prototype.minoesPosition = [
[P(-1, -1), P(0, 0), P(1, 0), P(0, -1)],
[P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)],
]
S.prototype.material = new MinoMaterial(COLORS.S)
S.prototype.ghostMaterial = new GhostMaterial(COLORS.S)
S.prototype.freeColor = new THREE.Color(COLORS.S)
class T extends Tetromino {
get tSpin() {
@ -304,8 +307,7 @@ T.prototype.tSlots = [
[P(1, -1), P(-1, -1), P(-1, 1), P(1, 1)],
[P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)],
]
T.prototype.material = new MinoMaterial(COLORS.T)
T.prototype.ghostMaterial = new GhostMaterial(COLORS.T)
T.prototype.freeColor = new THREE.Color(COLORS.T)
class Z extends Tetromino { }
Z.prototype.minoesPosition = [
@ -314,8 +316,7 @@ Z.prototype.minoesPosition = [
[P(-1, 0), P(0, 0), P(0, -1), P(1, -1)],
[P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)]
]
Z.prototype.material = new MinoMaterial(COLORS.Z)
Z.prototype.ghostMaterial = new GhostMaterial(COLORS.Z)
Z.prototype.freeColor = new THREE.Color(COLORS.Z)
const ROWS = 24
@ -323,7 +324,7 @@ const SKYLINE = 20
const COLUMNS = 10
class Matrix extends THREE.Group {
class Playfield extends THREE.Group {
constructor() {
super()
this.visible = false
@ -364,13 +365,24 @@ class Matrix extends THREE.Group {
this.hardDropAnimation.loop = THREE.LoopOnce
this.hardDropAnimation.setDuration(0.2)
this.ghost = new Ghost()
this.add(this.ghost)
this.ghost.visible = false
this.lockedMeshes = new THREE.InstancedMesh(minoGeometry, minoMaterial, 200)
this.add(this.lockedMeshes)
this.freedMinoes = []
this.freedMeshes = new THREE.InstancedMesh(minoGeometry, minoMaterial, 200)
this.freedMeshes.count = 0
this.add(this.freedMeshes)
this.init()
}
init() {
while(this.children.length > 1 ) this.remove(this.children[1])
this.cells = Array(ROWS).fill().map(() => Array(COLUMNS))
this.unlockedMinoes = new Set()
this.lockedMeshes.count = 0
}
cellIsEmpty(p) {
@ -383,11 +395,9 @@ class Matrix extends THREE.Group {
if (piece) {
this.add(piece)
piece.position.set(4, SKYLINE)
this.add(piece.ghost)
piece.ghost.children.forEach((mino) => {
mino.material = piece.ghostMaterial
})
piece.updateGhost()
this.ghost.color = piece.freeColor
this.ghost.copy(piece)
this.ghost.visible = true
}
this._piece = piece
}
@ -397,50 +407,66 @@ class Matrix extends THREE.Group {
}
lock() {
this.piece.locking = false
let minoes = Array.from(this.piece.children)
minoes.forEach(mino => {
mino.position.add(this.piece.position)
this.add(mino)
if (this.cellIsEmpty(mino.position)) {
this.cells[mino.position.y][mino.position.x] = mino
this.piece.minoesPosition[this.piece.facing].forEach(position => {
position = position.clone()
position.add(this.piece.position)
if (this.cellIsEmpty(position)) {
this.cells[position.y][position.x] = this.piece.freeColor
}
})
return minoes.some(mino => mino.position.y < SKYLINE)
this.updateLockedMinoes()
return this.piece.minoesPosition[this.piece.facing].every(position => position.y + this.piece.position.y < SKYLINE)
}
clearLines() {
let nbClearedLines = this.cells.reduceRight((nbClearedLines, row, y) => {
if (row.filter(mino => mino).length == COLUMNS) {
row.forEach(mino => this.unlockedMinoes.add(mino))
if (row.filter(color => color).length == COLUMNS) {
row.forEach((color, x) => {
this.freedMinoes.push(new Mino(color, x, y))
})
this.cells.splice(y, 1)
this.cells.push(Array(COLUMNS))
return ++nbClearedLines
}
return nbClearedLines
}, 0)
if (nbClearedLines) {
this.cells.forEach((rows, y) => {
rows.forEach((mino, x) => {
mino.position.set(x, y)
})
})
}
this.updateLockedMinoes()
return nbClearedLines
}
updateUnlockedMinoes(delta) {
this.unlockedMinoes.forEach(mino => {
mino.update(delta)
if (Math.sqrt(mino.position.x * mino.position.x + mino.position.z * mino.position.z) > 40 || mino.position.y < -50) {
this.remove(mino)
this.unlockedMinoes.delete(mino)
}
})
updateLockedMinoes() {
let i = 0
let matrix4 = new THREE.Matrix4()
this.cells.forEach((row, y) => row.forEach((color, x) => {
matrix4.setPosition(x, y, 0)
this.lockedMeshes.setMatrixAt(i, matrix4)
this.lockedMeshes.setColorAt(i, color)
i++
}))
this.lockedMeshes.count = i
this.lockedMeshes.instanceMatrix.needsUpdate = true
this.lockedMeshes.instanceColor.needsUpdate = true
}
updateFreedMinoes(delta) {
this.freedMinoes.forEach(mino => mino.update(delta))
this.freedMinoes = this.freedMinoes.filter(mino =>
Math.sqrt(mino.position.x * mino.position.x + mino.position.z * mino.position.z) <= 40 && mino.position.y > -50
) || []
this.freedMeshes.count = this.freedMinoes.length
if (this.freedMeshes.count) {
this.freedMinoes.forEach((mino, i) => {
this.freedMeshes.setMatrixAt(i, mino.matrix)
this.freedMeshes.setColorAt(i, mino.color)
})
this.freedMeshes.instanceMatrix.needsUpdate = true
this.freedMeshes.instanceColor.needsUpdate = true
}
}
update(delta) {
this.updateUnlockedMinoes(delta)
this.updateFreedMinoes(delta)
this.mixer?.update(delta)
}
}
@ -499,4 +525,4 @@ class NextQueue extends THREE.Group {
NextQueue.prototype.positions = [P(0, 0), P(0, -3), P(0, -6), P(0, -9), P(0, -12), P(0, -15), P(0, -18)]
export { T_SPIN, FACING, TRANSLATION, ROTATION, COLORS, environnement, Tetromino, I, J, L, O, S, T, Z, Matrix, HoldQueue, NextQueue }
export { T_SPIN, FACING, TRANSLATION, ROTATION, COLORS, environnement, minoMaterial, Tetromino, I, J, L, O, S, T, Z, Playfield, HoldQueue, NextQueue }