diff --git a/app.js b/app.js index 1b845f9..baf9863 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,6 @@ import * as THREE from 'three' import { scheduler } from './jsm/scheduler.js' -import { TRANSLATION, ROTATION, environnement, Mino, Playfield, HoldQueue, NextQueue } from './jsm/gamelogic.js' +import { TRANSLATION, ROTATION, environment, Mino, Playfield, HoldQueue, NextQueue } from './jsm/Tetrominoes.js' import { Settings } from './jsm/Settings.js' import { Stats } from './jsm/Stats.js' import { TetraGUI } from './jsm/TetraGUI.js' @@ -29,7 +29,7 @@ let game = { gui.settings.close() gui.stats.show() - Mino.mesh.clear() + Mino.meshes.clear() nextQueue.init() holdQueue.piece = undefined @@ -298,17 +298,18 @@ const stats = new Stats() const settings = new Settings() const scene = new TetraScene(settings, loadingManager) const controls = new TetraControls(scene.camera, renderer.domElement) -const gui = new TetraGUI(game, settings, stats, scene, controls) - -scene.add(Mino.mesh) +scene.add(Mino.meshes) const holdQueue = new HoldQueue() scene.add(holdQueue) -const playfield = new Playfield() +const playfield = new Playfield(loadingManager) scene.add(playfield) const nextQueue = new NextQueue() scene.add(nextQueue) +const gui = new TetraGUI(game, settings, stats, scene, controls, playfield) +gui.load() + messagesSpan.onanimationend = function (event) { event.target.remove() } @@ -321,12 +322,12 @@ function animate() { scene.updateMatrixWorld() scene.update(delta) playfield.update(delta) - Mino.mesh.update() + Mino.meshes.update() controls.update() gui.update() renderer.render(scene, scene.camera) - environnement.camera.update(renderer, scene) + environment.camera.update(renderer, scene) } window.addEventListener("resize", () => { diff --git a/audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3 b/audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3 new file mode 100644 index 0000000..4f81749 Binary files /dev/null and b/audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3 differ diff --git a/loading.css b/css/loading.css similarity index 100% rename from loading.css rename to css/loading.css diff --git a/style.css b/css/style.css similarity index 100% rename from style.css rename to css/style.css diff --git a/images/edge.png b/images/edge.png new file mode 100644 index 0000000..70f2dd9 Binary files /dev/null and b/images/edge.png differ diff --git a/images/sprites.png b/images/sprites.png new file mode 100644 index 0000000..424448a Binary files /dev/null and b/images/sprites.png differ diff --git a/index.html b/index.html index 0a40ac6..fdb76b2 100644 --- a/index.html +++ b/index.html @@ -6,8 +6,8 @@ teTra - - + + - + \ No newline at end of file diff --git a/jsm/Stats.js b/jsm/Stats.js index 7b47c08..1a4db3e 100644 --- a/jsm/Stats.js +++ b/jsm/Stats.js @@ -1,5 +1,5 @@ import { Clock } from 'three' -import { T_SPIN } from './gamelogic.js' +import { T_SPIN } from './Tetrominoes.js' // score = AWARDED_LINE_CLEARS[tSpin][nbClearedLines] diff --git a/jsm/TetraControls.js b/jsm/TetraControls.js index 778ba69..620043b 100644 --- a/jsm/TetraControls.js +++ b/jsm/TetraControls.js @@ -10,8 +10,8 @@ class TetraControls extends OrbitControls { this.dampingFactor = 0.04 this.maxDistance = 21 this.keys = {} - this.minPolarAngle = 0.9 - this.maxPolarAngle = 2.14 + this.minPolarAngle = 1 + this.maxPolarAngle = 2.1 this.minAzimuthAngle = 0.9 - Math.PI / 2 this.maxAzimuthAngle = 2.14 - Math.PI / 2 this.target.set(5, 7, 0) diff --git a/jsm/TetraGUI.js b/jsm/TetraGUI.js index ba735b0..f4bf72f 100644 --- a/jsm/TetraGUI.js +++ b/jsm/TetraGUI.js @@ -1,92 +1,15 @@ 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 { Mino, environnement } from './gamelogic.js' - - -let jsKeyRenamer = new Proxy({ - ["←"]: "ArrowLeft", - ["→"]: "ArrowRight", - ["↑"]: "ArrowUp", - ["↓"]: "ArrowDown", - ["Espace"]: " ", - ["Échap."]: "Escape", - ["Ret. arrière"]: "Backspace", - ["Entrée"]: "Enter", -}, { - get(obj, keyName) { - return keyName in obj ? obj[keyName] : keyName - } -}) -let friendyKeyRenamer = new Proxy({ - ["ArrowLeft"]: "←", - ["ArrowRight"]: "→", - ["ArrowUp"]: "↑", - ["ArrowDown"]: "↓", - [" "]: "Espace", - ["Escape"]: "Échap.", - ["Backspace"]: "Ret. arrière", - ["Enter"]: "Entrée", -}, { - get(obj, keyName) { - return keyName in obj ? obj[keyName] : keyName.toUpperCase() - } -}) +import { Mino, environment } from './Tetrominoes.js' export class TetraGUI extends GUI { - constructor(game, settings, stats, scene, controls) { + constructor(game, settings, stats, scene, controls, playfield, loadingManager) { super({title: "teTra"}) - this.startLevel = 1 - - let keyMaps = { - key: {}, - action: {} - } - - this.key = new Proxy(keyMaps, { - set(km, action, key) { - key = jsKeyRenamer[key] - km.action[key.toLowerCase()] = action - return km.key[action] = key - }, - has(km, action) { - return action in km.key - }, - get(km, action) { - return friendyKeyRenamer[km.key[action]] - } - }) - this.action = new Proxy(keyMaps, { - set(km, key, action) { - km.key[action] = key - return km.action[key.toLowerCase()] = action - }, - has(km, key) { - return key.toLowerCase() in km.action - }, - get(km, key) { - return km.action[key.toLowerCase()] - } - }) - - this.key.moveLeft = "ArrowLeft" - this.key.moveRight = "ArrowRight" - this.key.rotateCCW = "w" - this.key.rotateCW = "ArrowUp" - this.key.softDrop = "ArrowDown" - this.key.hardDrop = " " - this.key.hold = "c" - this.key.pause = "Escape" - - this.arrDelay = 50 - this.dasDelay = 300 - - this.musicVolume = 50 - this.sfxVolume = 50 - - this.startButton = this.add(game, "start").name("Jouer").hide() - this.pauseButton = this.add(game, "pause").name("Pause").hide() + + this.startButton = this.add(game, "start").name("Jouer").hide() + this.pauseButton = this.add(game, "pause").name("Pause").hide() this.resumeButton = this.add(game, "resume").name("Reprendre").hide() this.stats = this.addFolder("Stats").hide() @@ -104,25 +27,21 @@ export class TetraGUI extends GUI { this.settings = this.addFolder("Options").open() this.settings.add(settings, "startLevel").name("Niveau initial").min(1).max(15).step(1) - this.settings.add(settings, "theme", ["Plasma", "Espace"]).name("Thème").onChange(theme => { - scene.vortex.theme = theme - switch (theme) { - case "Plasma": - scene.ambientLight.intensity = 0.6 - scene.directionalLight.intensity = 5 - - Mino.mesh.material.opacity = 0.7 - Mino.mesh.material.roughness = 0.48 - Mino.mesh.material.metalness = 0.9 - break - case "Espace": - scene.ambientLight.intensity = 20 - scene.directionalLight.intensity = 10 - - Mino.mesh.material.opacity = 0.6 - Mino.mesh.material.roughness = 0.05 - Mino.mesh.material.metalness = 0.997 - break + + this.settings.add(settings, "theme", ["Plasma", "Espace", "Rétro"]).name("Thème").onChange(theme => { + scene.theme = theme + Mino.meshes.material = Mino.materials[theme] + if (theme == "Rétro") { + playfield.edge.visible = false + playfield.retroEdge.visible = true + Mino.meshes.resetColor() + Mino.meshes.update = Mino.meshes.updateOffset + music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3" + } else { + playfield.edge.visible = true + playfield.retroEdge.visible = false + Mino.meshes.update = Mino.meshes.updateColor + music.src = "audio/benevolence.m4a" } }) @@ -184,11 +103,11 @@ export class TetraGUI extends GUI { function changeMaterial(type) { material?.destroy() material = dev.addFolder("minoes material") - material.add(Mino.mesh.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).name("type").onChange(changeMaterial) + material.add(Mino.meshes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).name("type").onChange(changeMaterial) switch(type) { case "MeshBasicMaterial": - Mino.mesh.material = new THREE.MeshBasicMaterial({ - envMap: environnement, + Mino.meshes.material = new THREE.MeshBasicMaterial({ + envMap: environment, side: THREE.DoubleSide, transparent: true, opacity: 0.5, @@ -196,8 +115,8 @@ export class TetraGUI extends GUI { }) break case "MeshStandardMaterial": - Mino.mesh.material = new THREE.MeshStandardMaterial({ - envMap: environnement, + Mino.meshes.material = new THREE.MeshStandardMaterial({ + envMap: environment, side: THREE.DoubleSide, transparent: true, opacity: 0.7, @@ -206,8 +125,8 @@ export class TetraGUI extends GUI { }) break case "MeshPhysicalMaterial": - Mino.mesh.material = new THREE.MeshPhysicalMaterial({ - envMap: environnement, + Mino.meshes.material = new THREE.MeshPhysicalMaterial({ + envMap: environment, side: THREE.DoubleSide, transparent: true, opacity: 0.6, @@ -223,17 +142,17 @@ export class TetraGUI extends GUI { }) break } - if ("opacity" in Mino.mesh.material) material.add(Mino.mesh.material, "opacity" ).min(0).max(1).listen() - if ("reflectivity" in Mino.mesh.material) material.add(Mino.mesh.material, "reflectivity" ).min(0).max(1).listen() - if ("roughness" in Mino.mesh.material) material.add(Mino.mesh.material, "roughness" ).min(0).max(1).listen() - if ("metalness" in Mino.mesh.material) material.add(Mino.mesh.material, "metalness" ).min(0).max(1).listen() - if ("attenuationDistance" in Mino.mesh.material) material.add(Mino.mesh.material, "attenuationDistance").min(0).listen() - if ("ior" in Mino.mesh.material) material.add(Mino.mesh.material, "ior" ).min(1).max(2).listen() - if ("sheen" in Mino.mesh.material) material.add(Mino.mesh.material, "sheen" ).min(0).max(1).listen() - if ("sheenRoughness" in Mino.mesh.material) material.add(Mino.mesh.material, "sheenRoughness" ).min(0).max(1).listen() - if ("specularIntensity" in Mino.mesh.material) material.add(Mino.mesh.material, "specularIntensity" ).min(0).max(1).listen() - if ("thickness" in Mino.mesh.material) material.add(Mino.mesh.material, "thickness" ).min(0).max(5).listen() - if ("transmission" in Mino.mesh.material) material.add(Mino.mesh.material, "transmission" ).min(0).max(1).listen() + if ("opacity" in Mino.meshes.material) material.add(Mino.meshes.material, "opacity" ).min(0).max(1).listen() + if ("reflectivity" in Mino.meshes.material) material.add(Mino.meshes.material, "reflectivity" ).min(0).max(1).listen() + if ("roughness" in Mino.meshes.material) material.add(Mino.meshes.material, "roughness" ).min(0).max(1).listen() + if ("metalness" in Mino.meshes.material) material.add(Mino.meshes.material, "metalness" ).min(0).max(1).listen() + if ("attenuationDistance" in Mino.meshes.material) material.add(Mino.meshes.material, "attenuationDistance").min(0).listen() + if ("ior" in Mino.meshes.material) material.add(Mino.meshes.material, "ior" ).min(1).max(2).listen() + if ("sheen" in Mino.meshes.material) material.add(Mino.meshes.material, "sheen" ).min(0).max(1).listen() + if ("sheenRoughness" in Mino.meshes.material) material.add(Mino.meshes.material, "sheenRoughness" ).min(0).max(1).listen() + if ("specularIntensity" in Mino.meshes.material) material.add(Mino.meshes.material, "specularIntensity" ).min(0).max(1).listen() + if ("thickness" in Mino.meshes.material) material.add(Mino.meshes.material, "thickness" ).min(0).max(5).listen() + if ("transmission" in Mino.meshes.material) material.add(Mino.meshes.material, "transmission" ).min(0).max(1).listen() } changeMaterial(this.materialType) material.close() @@ -250,8 +169,6 @@ export class TetraGUI extends GUI { })) } - - this.load() } load() { diff --git a/jsm/TetraScene.js b/jsm/TetraScene.js index 1fc618b..3e7efdd 100644 --- a/jsm/TetraScene.js +++ b/jsm/TetraScene.js @@ -44,6 +44,24 @@ export class TetraScene extends THREE.Scene { }.bind(this)) } + set theme(theme) { + switch (theme) { + case "Plasma": + this.ambientLight.intensity = 0.6 + this.directionalLight.intensity = 5 + break + case "Espace": + this.ambientLight.intensity = 20 + this.directionalLight.intensity = 10 + break + case "Rétro": + this.ambientLight.intensity = 1 + this.directionalLight.intensity = 10 + break + } + this.vortex.theme = theme + } + update(delta) { this.vortex.update(delta) } diff --git a/jsm/gamelogic.js b/jsm/Tetrominoes.js similarity index 76% rename from jsm/gamelogic.js rename to jsm/Tetrominoes.js index e2ced7e..a1321f0 100644 --- a/jsm/gamelogic.js +++ b/jsm/Tetrominoes.js @@ -1,5 +1,6 @@ import * as THREE from 'three' import { scheduler } from './scheduler.js' +import { TileMaterial } from './TileMaterial.js' Array.prototype.pick = function () { return this.splice(Math.floor(Math.random() * this.length), 1)[0] } @@ -54,10 +55,17 @@ const COLUMNS = 10 const envRenderTarget = new THREE.WebGLCubeRenderTarget(256) -const environnement = envRenderTarget.texture -environnement.type = THREE.HalfFloatType -environnement.camera = new THREE.CubeCamera(1, 1000, envRenderTarget) -environnement.camera.position.set(5, 10, 0) +const environment = envRenderTarget.texture +environment.type = THREE.HalfFloatType +environment.camera = new THREE.CubeCamera(1, 1000, envRenderTarget) +environment.camera.position.set(5, 10, 0) + + +const sideMaterial = new THREE.MeshStandardMaterial({ + color: 0x222222, + roughness: 0.8, + metalness: 0.8, +}) class InstancedMino extends THREE.InstancedMesh { @@ -65,6 +73,8 @@ class InstancedMino extends THREE.InstancedMesh { super(geometry, material, count) this.instances = new Set() this.count = 0 + this.offsets = new Uint8Array(2*count) + this.update = this.updateColor } add(instance) { @@ -79,26 +89,83 @@ class InstancedMino extends THREE.InstancedMesh { this.instances.clear() } - update() { + setOffsetAt(index, offset) { + this.offsets[index * 2] = offset + } + + resetColor() { + if (this.instanceColor) { + this.instanceColor.array.fill(0xffffff) + this.instanceColor.needsUpdate = true + } + } + + updateColor() { this.count = 0 this.instances.forEach(mino => { if (mino.parent?.visible) { - this.setColorAt(this.count, mino.color) this.setMatrixAt(this.count, mino.matrixWorld) + this.setColorAt(this.count, mino.color) this.count++ } }) if (this.count) { - this.instanceColor.needsUpdate = true 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 instances = new Set() - static mesh + static materials = { + Plasma: new THREE.MeshStandardMaterial({ + envMap: environment, + side: THREE.DoubleSide, + transparent: true, + opacity: 0.7, + roughness: 0.48, + metalness: 0.67, + }), + Espace: new THREE.MeshStandardMaterial({ + envMap: environment, + side: THREE.DoubleSide, + transparent: true, + opacity: 0.8, + roughness: 0.05, + metalness: 0.997, + }), + Rétro: [sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial] + } + static { + new THREE.TextureLoader().load("images/sprites.png", (texture) => { + this.materials.Rétro[0] = new TileMaterial({ + color: 0xd0d4c1, + map: texture, + bumpMap: texture, + bumpScale: 4, + roughness: 0.25, + metalness: 0.8, + transparent: true, + }, 8, 8) + }) + } + static meshes static { let minoFaceShape = new THREE.Shape() minoFaceShape.moveTo(.1, .1) @@ -116,24 +183,17 @@ class Mino extends THREE.Object3D { bevelSegments: 1 } let minoGeometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings) - let minoMaterial = new THREE.MeshStandardMaterial({ - envMap: environnement, - side: THREE.DoubleSide, - transparent: true, - opacity: 0.7, - roughness: 0.48, - metalness: 0.67, - }) - this.mesh = new InstancedMino(minoGeometry, minoMaterial, 2*ROWS*COLUMNS) + this.meshes = new InstancedMino(minoGeometry, this.materials.Plasma, 2*ROWS*COLUMNS) } - constructor(color) { + constructor(color, offset) { super() this.color = color + this.offset = offset 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() - this.constructor.mesh.add(this) + this.constructor.meshes.add(this) } explode(delta) { @@ -149,7 +209,7 @@ class Mino extends THREE.Object3D { } dispose() { - this.constructor.mesh.delete(this) + this.constructor.meshes.delete(this) } } @@ -164,7 +224,7 @@ class Tetromino extends THREE.Group { constructor(position) { super() if (position) this.position.copy(position) - this.minoesPosition[FACING.NORTH].forEach(() => this.add(new Mino(this.freeColor))) + this.minoesPosition[FACING.NORTH].forEach(() => this.add(new Mino(this.freeColor, this.offset))) this.facing = FACING.NORTH this.rotatedLast = false this.rotationPoint4Used = false @@ -251,6 +311,7 @@ class Ghost extends Tetromino { copy(piece) { this.position.copy(piece.position) this.minoesPosition = piece.minoesPosition + //this.children.forEach(mino => mino.offset = piece.offset) this.facing = piece.facing this.visible = true while (this.canMove(TRANSLATION.DOWN)) this.position.y-- @@ -260,6 +321,7 @@ 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)], ] +Ghost.prototype.offset = 0 class I extends Tetromino { } @@ -276,6 +338,7 @@ I.prototype.srs = [ { [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.freeColor = new THREE.Color(COLORS.I) +I.prototype.offset = 1 class J extends Tetromino { } J.prototype.minoesPosition = [ @@ -285,8 +348,10 @@ J.prototype.minoesPosition = [ [P(0, 1), P(-1, -1), P(0, 0), P(0, -1)], ] J.prototype.freeColor = new THREE.Color(COLORS.J) +J.prototype.offset = 2 -class L extends Tetromino { } +class L extends Tetromino { +} 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)], @@ -294,6 +359,7 @@ L.prototype.minoesPosition = [ [P(0, 1), P(0, 0), P(0, -1), P(-1, 1)], ] L.prototype.freeColor = new THREE.Color(COLORS.L) +L.prototype.offset = 3 class O extends Tetromino { } O.prototype.minoesPosition = [ @@ -303,6 +369,7 @@ O.prototype.srs = [ { [ROTATION.CW]: [], [ROTATION.CCW]: [] } ] O.prototype.freeColor = new THREE.Color(COLORS.O) +O.prototype.offset = 4 class S extends Tetromino { } S.prototype.minoesPosition = [ @@ -312,6 +379,7 @@ S.prototype.minoesPosition = [ [P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)], ] S.prototype.freeColor = new THREE.Color(COLORS.S) +S.prototype.offset = 5 class T extends Tetromino { get tSpin() { @@ -339,6 +407,7 @@ T.prototype.tSlots = [ [P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)], ] T.prototype.freeColor = new THREE.Color(COLORS.T) +T.prototype.offset = 6 class Z extends Tetromino { } Z.prototype.minoesPosition = [ @@ -348,18 +417,19 @@ Z.prototype.minoesPosition = [ [P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)] ] Z.prototype.freeColor = new THREE.Color(COLORS.Z) +Z.prototype.offset = 7 class Playfield extends THREE.Group { - constructor() { + constructor(loadingManager) { super() //this.visible = false const edgeMaterial = new THREE.MeshStandardMaterial({ color: COLORS.EDGE, - envMap: environnement, + envMap: environment, transparent: true, - opacity: 0.2, + opacity: 0.3, roughness: 0.1, metalness: 0.67, }) @@ -373,14 +443,56 @@ class Playfield extends THREE.Group { .lineTo(COLUMNS + .3, -.3) .lineTo(-.3, -.3) .moveTo(-.3, SKYLINE) - const edge = new THREE.Mesh( + this.edge = new THREE.Mesh( new THREE.ExtrudeGeometry(edgeShape, { depth: 1, bevelEnabled: false, }), edgeMaterial ) - this.add(edge) + this.add(this.edge) + + const retroEdgeShape = new THREE.Shape() + .moveTo(-1, SKYLINE) + .lineTo(0, SKYLINE) + .lineTo(0, 0) + .lineTo(COLUMNS, 0) + .lineTo(COLUMNS, SKYLINE) + .lineTo(COLUMNS + 1, SKYLINE) + .lineTo(COLUMNS + 1, -1) + .lineTo(-1, -1) + .moveTo(-1, SKYLINE) + const retroEdgeTexture = new THREE.TextureLoader(loadingManager).load("images/edge.png", (texture) => { + texture.wrapS = THREE.RepeatWrapping + texture.wrapT = THREE.RepeatWrapping + }) + const retroEdgeMaterial = new THREE.MeshStandardMaterial({ + color: 0xd0d4c1, + map: retroEdgeTexture, + bumpMap: retroEdgeTexture, + bumpScale: 0.5, + roughness: 0.25, + metalness: 0.8, + }) + this.retroEdge = new THREE.Mesh( + new THREE.ExtrudeGeometry(retroEdgeShape, { + depth: 1, + bevelEnabled: false, + }), + [retroEdgeMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial], + ) + this.add(this.retroEdge) + const back = new THREE.Mesh( + new THREE.PlaneGeometry(COLUMNS, SKYLINE), + new THREE.MeshStandardMaterial({ + color: 0xc5d0a1, + roughness: 0.9, + metalness: 0.9, + }) + ) + back.position.set(COLUMNS/2, SKYLINE/2, 0) + this.retroEdge.add(back) + this.retroEdge.visible = false const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 0, -0.2, 0, 0, 0, 0]) const clip = new THREE.AnimationClip('HardDrop', 3, [positionKF]) @@ -515,4 +627,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, Mino, Tetromino, Playfield, HoldQueue, NextQueue } \ No newline at end of file +export { T_SPIN, FACING, TRANSLATION, ROTATION, COLORS, environment, Mino, Tetromino, Playfield, HoldQueue, NextQueue } \ No newline at end of file diff --git a/jsm/TileMaterial.js b/jsm/TileMaterial.js new file mode 100644 index 0000000..02e1615 --- /dev/null +++ b/jsm/TileMaterial.js @@ -0,0 +1,93 @@ +import { MeshStandardMaterial, Texture, Vector2 } from 'three'; + +export class TileMaterial extends MeshStandardMaterial { + constructor(params, tileSizeX, tileSizeY) { + super(params); + this.tileSize = { value: new Vector2(tileSizeX / this.map.image.width, tileSizeY / this.map.image.height) }; + } + + onBeforeCompile(shader) { + shader.uniforms.tileSize = this.tileSize + shader.vertexShader = shader.vertexShader.replace( + `void main() {`, + `varying vec2 vUv; + varying vec2 vOffset; + attribute vec2 offset; + + void main() { + vUv = uv; + vOffset = offset; + gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);` + ) + shader.fragmentShader = `varying vec2 vUv; + varying vec2 vOffset; + //uniform sampler2D map; + uniform vec2 tileSize; + varying vec2 vBumpMapUvOffset; + ` + shader.fragmentShader + shader.fragmentShader = shader.fragmentShader.replace( + `#include `, + `#ifdef USE_MAP + + vec4 sampledDiffuseColor = texture2D(map, vUv * tileSize + vOffset * tileSize); + + #ifdef DECODE_VIDEO_TEXTURE + + // use inline sRGB decode until browsers properly support SRGB8_ALPHA8 with video textures (#26516) + + sampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w ); + + #endif + + diffuseColor *= sampledDiffuseColor; + + #endif` + ) + shader.fragmentShader = shader.fragmentShader.replace( + `#include `, + `#ifdef USE_BUMPMAP + + uniform sampler2D bumpMap; + uniform float bumpScale; + + // Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen + // https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf + + // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) + + vec2 dHdxy_fwd() { + + vec2 vBumpMapUvOffset = vBumpMapUv * tileSize + vOffset * tileSize; + + vec2 dSTdx = dFdx( vBumpMapUvOffset ); + vec2 dSTdy = dFdy( vBumpMapUvOffset ); + + float Hll = bumpScale * texture2D( bumpMap, vBumpMapUvOffset ).x; + float dBx = bumpScale * texture2D( bumpMap, vBumpMapUvOffset + dSTdx ).x - Hll; + float dBy = bumpScale * texture2D( bumpMap, vBumpMapUvOffset + dSTdy ).x - Hll; + + return vec2( dBx, dBy ); + + } + + vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) { + + // normalize is done to ensure that the bump map looks the same regardless of the texture's scale + vec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) ); + vec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) ); + vec3 vN = surf_norm; // normalized + + vec3 R1 = cross( vSigmaY, vN ); + vec3 R2 = cross( vN, vSigmaX ); + + float fDet = dot( vSigmaX, R1 ) * faceDirection; + + vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 ); + return normalize( abs( fDet ) * surf_norm - vGrad ); + + } + + #endif` + ) + } +} diff --git a/jsm/Vortex.js b/jsm/Vortex.js index 365ef55..7880a8a 100644 --- a/jsm/Vortex.js +++ b/jsm/Vortex.js @@ -15,7 +15,7 @@ export class Vortex extends THREE.Group { this.colorFullTextureRotation = 0.006 this.colorFullMoveForward = 0.025 - const commonCylinderGeometry = new THREE.CylinderGeometry(35, 35, 500, 12, 1, true) + const commonCylinderGeometry = new THREE.CylinderGeometry(35, 35, 1000, 12, 1, true) this.darkCylinder = new THREE.Mesh( commonCylinderGeometry, @@ -24,6 +24,7 @@ export class Vortex extends THREE.Group { blending: THREE.AdditiveBlending, }) ) + this.darkCylinder.position.y = -100 this.add(this.darkCylinder) this.colorFullCylinder = new THREE.Mesh( @@ -33,6 +34,7 @@ export class Vortex extends THREE.Group { blending: THREE.AdditiveBlending, }) ) + this.colorFullCylinder.position.y = -100 this.add(this.colorFullCylinder) this.position.set(5, 10, -10) @@ -44,7 +46,7 @@ export class Vortex extends THREE.Group { new THREE.TextureLoader(this.loadingManager).load("./images/plasma.jpg", texture => { texture.wrapS = THREE.RepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping - texture.repeat.set(1, 1) + texture.repeat.set(1, 2) this.darkCylinder.material.map = texture }) this.darkCylinder.material.opacity = 0.17 @@ -52,7 +54,7 @@ export class Vortex extends THREE.Group { new THREE.TextureLoader(this.loadingManager).load("./images/plasma2.jpg", texture => { texture.wrapS = THREE.RepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping - texture.repeat.set(2, 1) + texture.repeat.set(2, 2) this.colorFullCylinder.material.map = texture }) this.colorFullCylinder.material.opacity = 0.7 @@ -62,6 +64,8 @@ export class Vortex extends THREE.Group { this.darkMoveForward = 0.009 this.colorFullTextureRotation = 0.006 this.colorFullMoveForward = 0.025 + + this.visible = true break case "Espace": @@ -86,17 +90,29 @@ export class Vortex extends THREE.Group { this.darkMoveForward = 0.03 this.colorFullTextureRotation = 0.006 this.colorFullMoveForward = 0.012 + + this.visible = true + break + + case "Rétro": + this.visible = false break } } update(delta) { - this.rotation.y += this.globalRotation * delta - - this.darkCylinder.material.map.offset.y += this.darkMoveForward * delta - this.darkCylinder.material.map.offset.x += this.darkTextureRotation * delta - - this.colorFullCylinder.material.map.offset.y += this.colorFullMoveForward * delta - this.colorFullCylinder.material.map.offset.x += this.colorFullTextureRotation * delta + if (this.visible) { + this.rotation.y += this.globalRotation * delta + + if (this.darkCylinder.material.map) { + this.darkCylinder.material.map.offset.y += this.darkMoveForward * delta + this.darkCylinder.material.map.offset.x += this.darkTextureRotation * delta + } + + if (this.colorFullCylinder.material.map) { + this.colorFullCylinder.material.map.offset.y += this.colorFullMoveForward * delta + this.colorFullCylinder.material.map.offset.x += this.colorFullTextureRotation * delta + } + } } } \ No newline at end of file diff --git a/jsm/scheduler.js b/jsm/scheduler.js index da29b7e..df3d861 100644 --- a/jsm/scheduler.js +++ b/jsm/scheduler.js @@ -33,7 +33,7 @@ class Scheduler { } -const scheduler = new Scheduler +const scheduler = new Scheduler() export { scheduler } \ No newline at end of file