- new loading screen

- huge loading time reducing by simplifiying ocean
This commit is contained in:
Adrien MALINGREY 2024-03-13 02:29:02 +01:00
parent 4f20dce37f
commit dd8361cf33
3 changed files with 367 additions and 307 deletions

View File

@ -143,15 +143,17 @@
</script> </script>
</head> </head>
<body> <body>
<div id="container"></div> <div id="loading">
<span id="message" class="loading"> <table id="labyTable"></table>
<div id="progressCircle" style="--progress: 0deg;">0%</div> <div id="loadingMessage">Construction du labyrinthe : <span id="progress">0</span>%</div>
<div> <div>
Se déplacer : ↑←↓→, ZQSD ou clic<br/> Se déplacer : ↑←↓→, ZQSD ou clic<br/>
Sauter : ESPACE<br/> Sauter : ESPACE<br/>
Regarder : Souris Regarder : Souris
</div> </div>
</span> </div>
<div id="container"></div>
<span id="message"></span>
<script type="module" src="./main.js"></script> <script type="module" src="./main.js"></script>
</body> </body>

579
main.js
View File

@ -7,48 +7,90 @@ import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js' import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js'
import Stats from 'three/addons/libs/stats.module.js' import Stats from 'three/addons/libs/stats.module.js'
//import 'three-hex-tiling'
import MazeMesh from './MazeMesh.js' import MazeMesh from './MazeMesh.js'
//import 'three-hex-tiling'
const playerHeight = 0.5;
const mazeWidth = 23
const parameters = { // LOADING
elevation: 48,
azimuth : 53,
};
const waves = { const labyWidth =23
A: { direction: 0, steepness: 0.05, wavelength: 3 }, const labyHeight = 23
B: { direction: 30, steepness: 0.10, wavelength: 6 },
C: { direction: 60, steepness: 0.05, wavelength: 1.5 },
};
const dev = window.location.search.includes("dev") for(let y=0; y < labyHeight; y++) {
let tr = document.createElement("tr")
labyTable.appendChild(tr)
for(let x=0; x < labyHeight; x++) {
let td = document.createElement("td")
tr.appendChild(td)
}
}
const ambiance = new Audio("snd/ambiance.mp3") let walls
ambiance.loop = true
const piano = new Audio("snd/waves-and-tears.mp3")
piano.loop = false
const loadMngr = new THREE.LoadingManager(); function dig(x, y) {
const loader = new THREE.TextureLoader(loadMngr); walls[y][x] = false
window.requestAnimationFrame(() => labyTable.children[y].children[x].className = "ground")
}
const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
function* build(x, y) {
for (let direction of Array.from(directions).sort(x => .5 - Math.random())) {
let [dx, dy] = direction
let x1 = x + dx
let y1 = y + dy
let x2 = x1 + dx
let y2 = y1 + dy
if (0 <= x2 && x2 < labyWidth && 0 <= y2 && y2 < labyHeight && walls[y2][x2]) {
dig(x1, y1)
yield x1, y1
dig(x2, y2)
yield x2, y2
yield* build(x2, y2)
}
}
}
function* endlessLaby() {
while (true) {
for (const tr of labyTable.children) {
for (const td of tr.children) {
td.className = "wall"
}
}
walls = Array(labyHeight).fill(true).map(row => Array(labyWidth).fill(true))
//let x0 = 2 * Math.floor(labyWidth * Math.random() / 2) + 1
//let y0 = 2 * Math.floor(labyHeight * Math.random() / 2) + 1
let x0 = Math.floor(labyWidth / 2)
let y0 = Math.floor(labyHeight / 2)
dig(x0, y0)
yield* build(x0, y0)
}
}
let labyIterator = endlessLaby()
let interval = window.setInterval(() => labyIterator.next(), 200)
const loadMngr = new THREE.LoadingManager()
const loader = new THREE.TextureLoader(loadMngr)
loader.setPath("textures/")
loadMngr.onStart = function (url, itemsLoaded, itemsTotal) { loadMngr.onStart = function (url, itemsLoaded, itemsTotal) {
progressCircle.innerText = "0%" progress.innerText = "0"
progressCircle.style.setProperty("--progress", "0deg")
} }
loadMngr.onProgress = function (url, itemsLoaded, itemsTotal) { loadMngr.onProgress = function (url, itemsLoaded, itemsTotal) {
progressCircle.innerText = Math.floor(100 * itemsLoaded / itemsTotal) + "%" progress.innerText = Math.floor(100 * itemsLoaded / itemsTotal)
progressCircle.style.setProperty("--progress", Math.floor(360 * itemsLoaded / itemsTotal)+"deg")
} }
loadMngr.onError = function (url) { loadMngr.onError = function (url) {
message.innerHTML = `Erreur de chargement :<br/>${url}` loadingMessage.innerHTML = `Erreur de chargement :<br/>${url}`
} }
loadMngr.onLoad = function (url, itemsLoaded, itemsTotal) { loadMngr.onLoad = function (url, itemsLoaded, itemsTotal) {
message.innerHTML = "" loading.style.display = "none"
message.className = "" window.clearInterval(interval)
renderer.setAnimationLoop(animate) renderer.setAnimationLoop(animate)
@ -56,26 +98,45 @@ loadMngr.onLoad = function (url, itemsLoaded, itemsTotal) {
let x = Math.floor(8 + camera.position.x * 16 / mazeWidth) let x = Math.floor(8 + camera.position.x * 16 / mazeWidth)
let y = Math.floor(8 + camera.position.z * 16 / mazeWidth) let y = Math.floor(8 + camera.position.z * 16 / mazeWidth)
favicon.href = `favicon.php?x=${x}&y=${y}` favicon.href = `favicon.php?x=${x}&y=${y}`
}, 1000); }, 1000)
}; }
//
const container = document.getElementById('container'); // GAME
const playerHeight = 0.5
const mazeWidth = 23
const parameters = {
elevation: 48,
azimuth : 53,
}
const waves = {
A: { direction: 0, steepness: 0.06, wavelength: 4 },
B: { direction: 30, steepness: 0.10, wavelength: 6 },
C: { direction: 60, steepness: 0.05, wavelength: 1.5 },
}
const ambiance = new Audio("snd/ambiance.mp3")
ambiance.loop = true
const piano = new Audio("snd/waves-and-tears.mp3")
piano.loop = false
const container = document.getElementById('container')
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
powerPreference: "high-performance", powerPreference: "high-performance",
antialias: true, })
}); renderer.setPixelRatio(window.devicePixelRatio)
renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setSize(window.innerWidth, window.innerHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.shadowMap.enabled = true
renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement); container.appendChild(renderer.domElement)
const scene = new THREE.Scene(); const scene = new THREE.Scene()
scene.background = new THREE.CubeTextureLoader(loadMngr) scene.background = new THREE.CubeTextureLoader(loadMngr)
.setPath( 'textures/calm-sea-skybox/' ) .setPath( 'textures/calm-sea-skybox/' )
@ -86,48 +147,50 @@ scene.background = new THREE.CubeTextureLoader(loadMngr)
'dn.webp', 'dn.webp',
'rt.webp', 'rt.webp',
'lf.webp', 'lf.webp',
] ); ] )
scene.backgroundBlurriness = 0.03; scene.backgroundBlurriness = 0.03
scene.backgroundIntensity = 1.4; scene.backgroundIntensity = 1.4
scene.environment = scene.background; scene.environment = scene.background
window.scene = scene; window.scene = scene
const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000); const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.rotation.order = 'YXZ'; camera.rotation.order = 'YXZ'
camera.position.set(0, 25 + playerHeight, 0); camera.position.set(0, 25 + playerHeight, 0)
const mazeCollisionner = new THREE.Group(); const mazeCollisionner = new THREE.Group()
// Maze // Maze
const wallMaterial = new THREE.MeshStandardMaterial({ const wallMaterial = new THREE.MeshStandardMaterial({
map : loader.load('textures/Poly-cobblestone-wall/color_map.webp'), map : loader.load('Poly-cobblestone-wall/color_map.webp'),
normalMap : loader.load('textures/Poly-cobblestone-wall/normal_map_opengl.webp'), normalMap : loader.load('Poly-cobblestone-wall/normal_map_opengl.webp'),
aoMap : loader.load('textures/Poly-cobblestone-wall/ao_map.webp'), aoMap : loader.load('Poly-cobblestone-wall/ao_map.webp'),
roughnessMap : loader.load('textures/Poly-cobblestone-wall/roughness_map.webp'), roughnessMap : loader.load('Poly-cobblestone-wall/roughness_map.webp'),
roughness : 1 roughness : 1
}) })
const maze = new MazeMesh(mazeWidth, mazeWidth, 1, wallMaterial); const maze = new MazeMesh(mazeWidth, mazeWidth, 1, wallMaterial)
maze.castShadow = true; maze.castShadow = true
maze.receiveShadow = true; maze.receiveShadow = true
maze.matrixAutoUpdate = false maze.matrixAutoUpdate = false
scene.add(maze) scene.add(maze)
console.log(String(maze)) console.log(String(maze))
const dev = window.location.search.includes("dev")
if (!dev) { if (!dev) {
const invisibleWall = new THREE.Mesh(new THREE.BoxGeometry( .9, 1.8, .9 )); const invisibleWall = new THREE.Mesh(new THREE.BoxGeometry( .9, 1.8, .9 ))
invisibleWall.material.visible = false; invisibleWall.material.visible = false
let matrix = new THREE.Matrix4() let matrix = new THREE.Matrix4()
for (let i = 0; i < maze.count; i++) { for (let i = 0; i < maze.count; i++) {
maze.getMatrixAt(i, matrix) maze.getMatrixAt(i, matrix)
const clone = invisibleWall.clone() const clone = invisibleWall.clone()
clone.position.setFromMatrixPosition(matrix); clone.position.setFromMatrixPosition(matrix)
clone.position.y = 1; clone.position.y = 1
mazeCollisionner.add(clone); mazeCollisionner.add(clone)
} }
} }
@ -139,11 +202,11 @@ function repeatGroundMaterial (texture) {
texture.repeat.set(mazeWidth / 4, mazeWidth / 4) texture.repeat.set(mazeWidth / 4, mazeWidth / 4)
} }
const groundMaterial = new THREE.MeshStandardMaterial({ const groundMaterial = new THREE.MeshStandardMaterial({
map : loader.load('textures/angled-blocks-vegetation/albedo.webp', repeatGroundMaterial), map : loader.load('angled-blocks-vegetation/albedo.webp', repeatGroundMaterial),
aoMap : loader.load('textures/angled-blocks-vegetation/ao.webp', repeatGroundMaterial), aoMap : loader.load('angled-blocks-vegetation/ao.webp', repeatGroundMaterial),
metalnessMap: loader.load('textures/angled-blocks-vegetation/metallic.webp', repeatGroundMaterial), metalnessMap: loader.load('angled-blocks-vegetation/metallic.webp', repeatGroundMaterial),
normalMap : loader.load('textures/angled-blocks-vegetation/normal-dx.webp', repeatGroundMaterial), normalMap : loader.load('angled-blocks-vegetation/normal-dx.webp', repeatGroundMaterial),
roughnessMap: loader.load('textures/angled-blocks-vegetation/roughness.webp', repeatGroundMaterial), roughnessMap: loader.load('angled-blocks-vegetation/roughness.webp', repeatGroundMaterial),
/*hexTiling : { /*hexTiling : {
patchScale: 1, patchScale: 1,
useContrastCorrectedBlending: true, useContrastCorrectedBlending: true,
@ -184,29 +247,29 @@ const ground = new THREE.Mesh(
groundMaterial, groundMaterial,
] ]
) )
ground.rotation.x = -Math.PI / 2; ground.rotation.x = -Math.PI / 2
ground.position.y = -10 ground.position.y = -10
ground.receiveShadow = true; ground.receiveShadow = true
ground.matrixAutoUpdate = false ground.matrixAutoUpdate = false
ground.updateMatrix(); ground.updateMatrix()
mazeCollisionner.add(ground) mazeCollisionner.add(ground)
scene.add(mazeCollisionner); scene.add(mazeCollisionner)
const mazeOctree = new Octree().fromGraphNode(mazeCollisionner); const mazeOctree = new Octree().fromGraphNode(mazeCollisionner)
// Water // Water
const waterGeometry = new THREE.PlaneGeometry(1024, 1024, 512, 512); const waterGeometry = new THREE.PlaneGeometry(512, 512, 128, 128)
const ocean = new Water(waterGeometry, { const ocean = new Water(waterGeometry, {
textureWidth : 512, textureWidth : 256,
textureHeight: 512, textureHeight: 256,
waterNormals : loader.load( waterNormals : loader.load(
'textures/waternormals.webp', 'waternormals.webp',
function (texture) { function (texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.wrapS = texture.wrapT = THREE.RepeatWrapping
} }
), ),
sunDirection : new THREE.Vector3(), sunDirection : new THREE.Vector3(),
@ -214,12 +277,12 @@ const ocean = new Water(waterGeometry, {
waterColor : 0x001e0f, waterColor : 0x001e0f,
distortionScale: 3.7, distortionScale: 3.7,
fog : scene.fog !== undefined, fog : scene.fog !== undefined,
alpha : 0.9 alpha : 0.7
}); })
ocean.rotation.x = - Math.PI / 2; ocean.rotation.x = - Math.PI / 2
ocean.position.y = -0.2; ocean.position.y = -0.2
ocean.material.transparent = true; ocean.material.transparent = true
ocean.material.onBeforeCompile = function (shader) { ocean.material.onBeforeCompile = function (shader) {
shader.uniforms.size = { value: 6 } shader.uniforms.size = { value: 6 }
@ -231,7 +294,7 @@ ocean.material.onBeforeCompile = function (shader) {
waves.A.steepness, waves.A.steepness,
waves.A.wavelength, waves.A.wavelength,
], ],
}; }
shader.uniforms.waveB = { shader.uniforms.waveB = {
value: [ value: [
Math.sin((waves.B.direction * Math.PI) / 180), Math.sin((waves.B.direction * Math.PI) / 180),
@ -239,7 +302,7 @@ ocean.material.onBeforeCompile = function (shader) {
waves.B.steepness, waves.B.steepness,
waves.B.wavelength, waves.B.wavelength,
], ],
}; }
shader.uniforms.waveC = { shader.uniforms.waveC = {
value: [ value: [
Math.sin((waves.C.direction * Math.PI) / 180), Math.sin((waves.C.direction * Math.PI) / 180),
@ -247,50 +310,50 @@ ocean.material.onBeforeCompile = function (shader) {
waves.C.steepness, waves.C.steepness,
waves.C.wavelength, waves.C.wavelength,
], ],
}; }
shader.vertexShader = document.getElementById('vertexShader').textContent; shader.vertexShader = document.getElementById('vertexShader').textContent
shader.fragmentShader = document.getElementById('fragmentShader').textContent; shader.fragmentShader = document.getElementById('fragmentShader').textContent
}; }
scene.add(ocean); scene.add(ocean)
const oceanOctree = new Octree().fromGraphNode(ocean); const oceanOctree = new Octree().fromGraphNode(ocean)
// Lights // Lights
const sun = new THREE.Vector3(); const sun = new THREE.Vector3()
const ambientLight = new THREE.AmbientLight(0x404040, 5); const ambientLight = new THREE.AmbientLight(0x404040, 5)
scene.add(ambientLight); scene.add(ambientLight)
const sunLight = new THREE.DirectionalLight(0xffffff, 1); const sunLight = new THREE.DirectionalLight(0xffffff, 1)
sunLight.castShadow = true; sunLight.castShadow = true
sunLight.shadow.camera.near = 0.1; sunLight.shadow.camera.near = 0.1
sunLight.shadow.camera.far = 1.4 * mazeWidth; sunLight.shadow.camera.far = 1.4 * mazeWidth
sunLight.shadow.camera.left = -1.4 * mazeWidth/2; sunLight.shadow.camera.left = -1.4 * mazeWidth/2
sunLight.shadow.camera.right = 1.4 * mazeWidth/2; sunLight.shadow.camera.right = 1.4 * mazeWidth/2
sunLight.shadow.camera.bottom = -1.4 * mazeWidth/2; sunLight.shadow.camera.bottom = -1.4 * mazeWidth/2
sunLight.shadow.camera.top = 1.4 * mazeWidth/2; sunLight.shadow.camera.top = 1.4 * mazeWidth/2
sunLight.shadow.mapSize.width = 1024; sunLight.shadow.mapSize.width = 1024
sunLight.shadow.mapSize.height = 1024; sunLight.shadow.mapSize.height = 1024
//sunLight.shadow.radius = 0.01; sunLight.shadow.radius = 0.01
sunLight.shadow.bias = 0.0001; sunLight.shadow.bias = 0.0001
sunLight.target = maze sunLight.target = maze
scene.add(sunLight); scene.add(sunLight)
updateSun(); updateSun()
function updateSun() { function updateSun() {
const phi = THREE.MathUtils.degToRad(90 - parameters.elevation); const phi = THREE.MathUtils.degToRad(90 - parameters.elevation)
const theta = THREE.MathUtils.degToRad(parameters.azimuth); const theta = THREE.MathUtils.degToRad(parameters.azimuth)
sun.setFromSphericalCoords(1.4 * mazeWidth/2, phi, theta); sun.setFromSphericalCoords(1.4 * mazeWidth/2, phi, theta)
ocean.material.uniforms['sunDirection'].value.copy(sun).normalize(); ocean.material.uniforms['sunDirection'].value.copy(sun).normalize()
sunLight.position.copy(sun) sunLight.position.copy(sun)
//ambientLight.intensity = 5 + 5 * Math.sin(Math.max(THREE.MathUtils.degToRad(parameters.elevation), 0)); //ambientLight.intensity = 5 + 5 * Math.sin(Math.max(THREE.MathUtils.degToRad(parameters.elevation), 0))
} }
@ -302,49 +365,49 @@ function repeatRaftMaterial(texture) {
texture.repeat.set(2, 1) texture.repeat.set(2, 1)
} }
const raftMaterial = new THREE.MeshStandardMaterial({ const raftMaterial = new THREE.MeshStandardMaterial({
map: loader.load("textures/Poly-wood/color_map.webp", repeatRaftMaterial), map: loader.load("Poly-wood/color_map.webp", repeatRaftMaterial),
aoMap: loader.load("textures/Poly-wood/ao_map.webp", repeatRaftMaterial), aoMap: loader.load("Poly-wood/ao_map.webp", repeatRaftMaterial),
normalMap: loader.load("textures/Poly-wood/normal_map_opengl.webp", repeatRaftMaterial), normalMap: loader.load("Poly-wood/normal_map_opengl.webp", repeatRaftMaterial),
normalScale : new THREE.Vector2(2, 2), normalScale : new THREE.Vector2(2, 2),
roughnessMap: loader.load("textures/Poly-wood/roughness_map.webp", repeatRaftMaterial), roughnessMap: loader.load("Poly-wood/roughness_map.webp", repeatRaftMaterial),
depthFunc: 3, depthFunc: 3,
depthTest: true, depthTest: true,
depthWrite: true, depthWrite: true,
displacementMap: loader.load("textures/Poly-wood/displacement_map.webp", repeatRaftMaterial), displacementMap: loader.load("Poly-wood/displacement_map.webp", repeatRaftMaterial),
displacementScale: -0.3, displacementScale: -0.3,
displacementBias: 0.15, displacementBias: 0.15,
}) })
const raft = new THREE.Mesh(raftGeometry, raftMaterial) const raft = new THREE.Mesh(raftGeometry, raftMaterial)
raft.position.set( .25, ocean.position.y, -mazeWidth/2 - 1.1 ) raft.position.set( .25, ocean.position.y, -mazeWidth/2 - 1.1 )
raft.castShadow = true; raft.castShadow = true
scene.add(raft); scene.add(raft)
const raftOctree = new Octree().fromGraphNode(raft); const raftOctree = new Octree().fromGraphNode(raft)
// GUI // GUI
const stats = new Stats(); const stats = new Stats()
if (dev) { if (dev) {
container.appendChild(stats.dom); container.appendChild(stats.dom)
const gui = new GUI(); const gui = new GUI()
const lightHelper = new THREE.DirectionalLightHelper(sunLight, .5) const lightHelper = new THREE.DirectionalLightHelper(sunLight, .5)
lightHelper.position.copy(maze.start) lightHelper.position.copy(maze.start)
lightHelper.visible = false; lightHelper.visible = false
const octreeHelper = new OctreeHelper(mazeOctree); const octreeHelper = new OctreeHelper(mazeOctree)
octreeHelper.visible = false; octreeHelper.visible = false
scene.add(octreeHelper); scene.add(octreeHelper)
const showHelper = gui.add({ helpers: false }, "helpers") const showHelper = gui.add({ helpers: false }, "helpers")
showHelper.onChange(function (value) { showHelper.onChange(function (value) {
lightHelper.visible = value; lightHelper.visible = value
octreeHelper.visible = value; octreeHelper.visible = value
}); })
const cameraFolder = gui.addFolder("camera") const cameraFolder = gui.addFolder("camera")
cameraFolder.add(camera, "focus", 0, 200).onChange(() => camera.updateProjectionMatrix()) cameraFolder.add(camera, "focus", 0, 200).onChange(() => camera.updateProjectionMatrix())
@ -360,91 +423,91 @@ if (dev) {
raftRotationFolder.add(raft.rotation, "x") raftRotationFolder.add(raft.rotation, "x")
raftRotationFolder.add(raft.rotation, "y") raftRotationFolder.add(raft.rotation, "y")
raftRotationFolder.add(raft.rotation, "z") raftRotationFolder.add(raft.rotation, "z")
raftFolder.close(); raftFolder.close()
const skyFolder = gui.addFolder('Sky'); const skyFolder = gui.addFolder('Sky')
skyFolder.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun); skyFolder.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun)
skyFolder.add(parameters, 'azimuth', - 180, 180, 0.1).onChange(updateSun); skyFolder.add(parameters, 'azimuth', - 180, 180, 0.1).onChange(updateSun)
skyFolder.close(); skyFolder.close()
const waterUniforms = ocean.material.uniforms; const waterUniforms = ocean.material.uniforms
const waterFolder = gui.addFolder('Water'); const waterFolder = gui.addFolder('Water')
waterFolder waterFolder
.add(waterUniforms.distortionScale, 'value', 0, 8, 0.1) .add(waterUniforms.distortionScale, 'value', 0, 8, 0.1)
.name('distortionScale'); .name('distortionScale')
waterFolder.add(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size'); waterFolder.add(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size')
waterFolder.add(ocean.material, 'wireframe'); waterFolder.add(ocean.material, 'wireframe')
waterFolder.close(); waterFolder.close()
const waveAFolder = waterFolder.addFolder('Wave A'); const waveAFolder = waterFolder.addFolder('Wave A')
waveAFolder waveAFolder
.add(waves.A, 'direction', 0, 359) .add(waves.A, 'direction', 0, 359)
.name('Direction') .name('Direction')
.onChange((v) => { .onChange((v) => {
const x = (v * Math.PI) / 180; const x = (v * Math.PI) / 180
ocean.material.uniforms.waveA.value[0] = Math.sin(x); ocean.material.uniforms.waveA.value[0] = Math.sin(x)
ocean.material.uniforms.waveA.value[1] = Math.cos(x); ocean.material.uniforms.waveA.value[1] = Math.cos(x)
}); })
waveAFolder waveAFolder
.add(waves.A, 'steepness', 0, 1, 0.01) .add(waves.A, 'steepness', 0, 1, 0.01)
.name('Steepness') .name('Steepness')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveA.value[2] = v; ocean.material.uniforms.waveA.value[2] = v
}); })
waveAFolder waveAFolder
.add(waves.A, 'wavelength', 1, 100) .add(waves.A, 'wavelength', 1, 100)
.name('Wavelength') .name('Wavelength')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveA.value[3] = v; ocean.material.uniforms.waveA.value[3] = v
}); })
waveAFolder.open(); waveAFolder.open()
const waveBFolder = waterFolder.addFolder('Wave B'); const waveBFolder = waterFolder.addFolder('Wave B')
waveBFolder waveBFolder
.add(waves.B, 'direction', 0, 359) .add(waves.B, 'direction', 0, 359)
.name('Direction') .name('Direction')
.onChange((v) => { .onChange((v) => {
const x = (v * Math.PI) / 180; const x = (v * Math.PI) / 180
ocean.material.uniforms.waveB.value[0] = Math.sin(x); ocean.material.uniforms.waveB.value[0] = Math.sin(x)
ocean.material.uniforms.waveB.value[1] = Math.cos(x); ocean.material.uniforms.waveB.value[1] = Math.cos(x)
}); })
waveBFolder waveBFolder
.add(waves.B, 'steepness', 0, 1, 0.01) .add(waves.B, 'steepness', 0, 1, 0.01)
.name('Steepness') .name('Steepness')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveB.value[2] = v; ocean.material.uniforms.waveB.value[2] = v
}); })
waveBFolder waveBFolder
.add(waves.B, 'wavelength', 1, 100) .add(waves.B, 'wavelength', 1, 100)
.name('Wavelength') .name('Wavelength')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveB.value[3] = v; ocean.material.uniforms.waveB.value[3] = v
}); })
waveBFolder.open(); waveBFolder.open()
const waveCFolder = waterFolder.addFolder('Wave C'); const waveCFolder = waterFolder.addFolder('Wave C')
waveCFolder waveCFolder
.add(waves.C, 'direction', 0, 359) .add(waves.C, 'direction', 0, 359)
.name('Direction') .name('Direction')
.onChange((v) => { .onChange((v) => {
const x = (v * Math.PI) / 180; const x = (v * Math.PI) / 180
ocean.material.uniforms.waveC.value[0] = Math.sin(x); ocean.material.uniforms.waveC.value[0] = Math.sin(x)
ocean.material.uniforms.waveC.value[1] = Math.cos(x); ocean.material.uniforms.waveC.value[1] = Math.cos(x)
}); })
waveCFolder waveCFolder
.add(waves.C, 'steepness', 0, 1, 0.01) .add(waves.C, 'steepness', 0, 1, 0.01)
.name('Steepness') .name('Steepness')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveC.value[2] = v; ocean.material.uniforms.waveC.value[2] = v
}); })
waveCFolder waveCFolder
.add(waves.C, 'wavelength', 1, 100) .add(waves.C, 'wavelength', 1, 100)
.name('Wavelength') .name('Wavelength')
.onChange((v) => { .onChange((v) => {
ocean.material.uniforms.waveC.value[3] = v; ocean.material.uniforms.waveC.value[3] = v
}); })
waveCFolder.open(); waveCFolder.open()
const hexTilingFolder = gui.addFolder('Hex Tiling') const hexTilingFolder = gui.addFolder('Hex Tiling')
if (wallMaterial?.hexTiling?.patchScale) { if (wallMaterial?.hexTiling?.patchScale) {
@ -466,66 +529,66 @@ if (dev) {
// //
const clock = new THREE.Clock(); const clock = new THREE.Clock()
// Controls // Controls
const GRAVITY = 30; const GRAVITY = 30
const STEPS_PER_FRAME = 10; const STEPS_PER_FRAME = 10
const playerCollider = new Capsule( const playerCollider = new Capsule(
new THREE.Vector3(0, 25.0, 0), new THREE.Vector3(0, 25.0, 0),
new THREE.Vector3(0, 25 + playerHeight, 0), new THREE.Vector3(0, 25 + playerHeight, 0),
0.3 0.3
); )
const playerVelocity = new THREE.Vector3(); const playerVelocity = new THREE.Vector3()
const playerDirection = new THREE.Vector3(); const playerDirection = new THREE.Vector3()
let playerOnFloor = false; let playerOnFloor = false
let jumping = false; let jumping = false
let escaped = false; let escaped = false
const pointerLockControls = new PointerLockControls(camera, document.body); const pointerLockControls = new PointerLockControls(camera, document.body)
pointerLockControls.pointerSpeed = 0.7; pointerLockControls.pointerSpeed = 0.7
const keyStates = {}; const keyStates = {}
document.addEventListener('keydown', (event) => { document.addEventListener('keydown', (event) => {
keyStates[event.code] = true; keyStates[event.code] = true
}); })
document.addEventListener('keyup', (event) => { document.addEventListener('keyup', (event) => {
keyStates[event.code] = false; keyStates[event.code] = false
if (event.code == 'Space') jumping = false if (event.code == 'Space') jumping = false
}); })
var mouseButtonsStates = []; let mouseButtonsStates = []
function onMouseChange(event) { function onMouseChange(event) {
for(var i=0; i < mouseButtonsStates.length || i <= Math.log2(event.buttons); i++) { for(let i=0; i < mouseButtonsStates.length || i <= Math.log2(event.buttons); i++) {
mouseButtonsStates[i] = (event.buttons & (1 << i)) > 0 mouseButtonsStates[i] = (event.buttons & (1 << i)) > 0
} }
} }
container.addEventListener('click', function () { container.addEventListener('click', function () {
pointerLockControls.lock(); pointerLockControls.lock()
}) })
pointerLockControls.addEventListener('lock', function () { pointerLockControls.addEventListener('lock', function () {
ambiance.play(); ambiance.play()
document.addEventListener('mousedown', onMouseChange) document.addEventListener('mousedown', onMouseChange)
document.addEventListener('mouseup', onMouseChange) document.addEventListener('mouseup', onMouseChange)
}) })
pointerLockControls.addEventListener('unlock', function () { pointerLockControls.addEventListener('unlock', function () {
ambiance.pause(); ambiance.pause()
document.removeEventListener('mousedown', onMouseChange) document.removeEventListener('mousedown', onMouseChange)
document.removeEventListener('mouseup', onMouseChange) document.removeEventListener('mouseup', onMouseChange)
}) })
scene.add(pointerLockControls.getObject()); scene.add(pointerLockControls.getObject())
function playerCollisions() { function playerCollisions() {
@ -535,23 +598,23 @@ function playerCollisions() {
const result = playerOnMaze || playerOnRaft || playerOnWater const result = playerOnMaze || playerOnRaft || playerOnWater
playerOnFloor = false; playerOnFloor = false
if ( result ) { if ( result ) {
playerOnFloor = result.normal.y > 0; playerOnFloor = result.normal.y > 0
if (!playerOnFloor) { if (!playerOnFloor) {
playerVelocity.addScaledVector(result.normal, - result.normal.dot(playerVelocity)); playerVelocity.addScaledVector(result.normal, - result.normal.dot(playerVelocity))
} }
playerCollider.translate(result.normal.multiplyScalar(result.depth)); playerCollider.translate(result.normal.multiplyScalar(result.depth))
if (playerOnRaft) { if (playerOnRaft) {
camera.position.y = playerCollider.end.y + raft.position.y camera.position.y = playerCollider.end.y + raft.position.y
if (!escaped) gameEnd() if (!escaped) gameEnd()
} else if (playerOnWater) { } else if (playerOnWater) {
const t = ocean.material.uniforms['time'].value; const t = ocean.material.uniforms['time'].value
const waveInfo = getWaveInfo(playerCollider.end.x, playerCollider.end.z, t) const waveInfo = getWaveInfo(playerCollider.end.x, playerCollider.end.z, t)
camera.position.y = ocean.position.y + waveInfo.position.y + 0.2 camera.position.y = ocean.position.y + waveInfo.position.y + 0.2
} }
@ -561,61 +624,61 @@ function playerCollisions() {
function gameEnd() { function gameEnd() {
escaped = true; escaped = true
message.innerHTML = '<h2>Libre !</h2><a href="">Rejouer</a>'; message.innerHTML = '<h2>Libre !</h2><a href="">Rejouer</a>'
message.className = "escaped"; message.className = "escaped"
piano.play(); piano.play()
document.exitPointerLock(); document.exitPointerLock()
//container.style.cursor = "default"; //container.style.cursor = "default"
} }
function updatePlayer(deltaTime) { function updatePlayer(deltaTime) {
let damping = Math.exp(- 4 * deltaTime) - 1; let damping = Math.exp(- 4 * deltaTime) - 1
if (!playerOnFloor) { if (!playerOnFloor) {
playerVelocity.y -= GRAVITY * deltaTime; playerVelocity.y -= GRAVITY * deltaTime
damping *= 0.1; // small air resistance damping *= 0.1; // small air resistance
} }
playerVelocity.addScaledVector(playerVelocity, damping); playerVelocity.addScaledVector(playerVelocity, damping)
const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime); const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime)
playerCollider.translate(deltaPosition); playerCollider.translate(deltaPosition)
camera.position.copy(playerCollider.end); camera.position.copy(playerCollider.end)
playerCollisions(); playerCollisions()
} }
function getForwardVector() { function getForwardVector() {
camera.getWorldDirection(playerDirection); camera.getWorldDirection(playerDirection)
playerDirection.y = 0; playerDirection.y = 0
playerDirection.normalize(); playerDirection.normalize()
return playerDirection; return playerDirection
} }
function getSideVector() { function getSideVector() {
camera.getWorldDirection(playerDirection); camera.getWorldDirection(playerDirection)
playerDirection.y = 0; playerDirection.y = 0
playerDirection.normalize(); playerDirection.normalize()
playerDirection.cross(camera.up); playerDirection.cross(camera.up)
return playerDirection; return playerDirection
} }
function controls(deltaTime) { function controls(deltaTime) {
// gives a bit of air control // gives a bit of air control
const speedDelta = deltaTime * (playerOnFloor ? 100 : 20) / STEPS_PER_FRAME; const speedDelta = deltaTime * (playerOnFloor ? 100 : 20) / STEPS_PER_FRAME
if (keyStates["ArrowUp"] || keyStates['KeyW'] || mouseButtonsStates[0]) { if (keyStates["ArrowUp"] || keyStates['KeyW'] || mouseButtonsStates[0]) {
playerVelocity.add(getForwardVector().multiplyScalar(speedDelta)) playerVelocity.add(getForwardVector().multiplyScalar(speedDelta))
@ -631,7 +694,7 @@ function controls(deltaTime) {
} }
if (playerOnFloor && jumping == false) { if (playerOnFloor && jumping == false) {
if (keyStates['Space']) { if (keyStates['Space']) {
playerVelocity.y = 9; playerVelocity.y = 9
jumping = true jumping = true
} }
} }
@ -639,88 +702,88 @@ function controls(deltaTime) {
function getWaveInfo(x, z, time) { function getWaveInfo(x, z, time) {
const pos = new THREE.Vector3(); const pos = new THREE.Vector3()
const tangent = new THREE.Vector3(1, 0, 0); const tangent = new THREE.Vector3(1, 0, 0)
const binormal = new THREE.Vector3(0, 0, 1); const binormal = new THREE.Vector3(0, 0, 1)
Object.keys(waves).forEach((wave) => { Object.keys(waves).forEach((wave) => {
const w = waves[wave]; const w = waves[wave]
const k = (Math.PI * 2) / w.wavelength; const k = (Math.PI * 2) / w.wavelength
const c = Math.sqrt(9.8 / k); const c = Math.sqrt(9.8 / k)
const d = new THREE.Vector2( const d = new THREE.Vector2(
Math.sin((w.direction * Math.PI) / 180), Math.sin((w.direction * Math.PI) / 180),
- Math.cos((w.direction * Math.PI) / 180) - Math.cos((w.direction * Math.PI) / 180)
); )
const f = k * (d.dot(new THREE.Vector2(x, z)) - c * time); const f = k * (d.dot(new THREE.Vector2(x, z)) - c * time)
const a = w.steepness / k; const a = w.steepness / k
pos.x += d.y * (a * Math.cos(f)); pos.x += d.y * (a * Math.cos(f))
pos.y += a * Math.sin(f); pos.y += a * Math.sin(f)
pos.z += d.x * (a * Math.cos(f)); pos.z += d.x * (a * Math.cos(f))
tangent.x += - d.x * d.x * (w.steepness * Math.sin(f)); tangent.x += - d.x * d.x * (w.steepness * Math.sin(f))
tangent.y += d.x * (w.steepness * Math.cos(f)); tangent.y += d.x * (w.steepness * Math.cos(f))
tangent.z += - d.x * d.y * (w.steepness * Math.sin(f)); tangent.z += - d.x * d.y * (w.steepness * Math.sin(f))
binormal.x += - d.x * d.y * (w.steepness * Math.sin(f)); binormal.x += - d.x * d.y * (w.steepness * Math.sin(f))
binormal.y += d.y * (w.steepness * Math.cos(f)); binormal.y += d.y * (w.steepness * Math.cos(f))
binormal.z += - d.y * d.y * (w.steepness * Math.sin(f)); binormal.z += - d.y * d.y * (w.steepness * Math.sin(f))
}) })
const normal = binormal.cross(tangent).normalize(); const normal = binormal.cross(tangent).normalize()
return { position: pos, normal: normal }; return { position: pos, normal: normal }
} }
function updateRaft(delta) { function updateRaft(delta) {
const t = ocean.material.uniforms['time'].value; const t = ocean.material.uniforms['time'].value
const waveInfo = getWaveInfo(raft.position.x, raft.position.z, t); const waveInfo = getWaveInfo(raft.position.x, raft.position.z, t)
raft.position.y = ocean.position.y + waveInfo.position.y; raft.position.y = ocean.position.y + waveInfo.position.y
const quat = new THREE.Quaternion().setFromEuler( const quat = new THREE.Quaternion().setFromEuler(
new THREE.Euler().setFromVector3(waveInfo.normal) new THREE.Euler().setFromVector3(waveInfo.normal)
); )
raft.quaternion.rotateTowards(quat, delta * 0.5); raft.quaternion.rotateTowards(quat, delta * 0.5)
} }
window.addEventListener('resize', onWindowResize); window.addEventListener('resize', onWindowResize)
function onWindowResize() { function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix(); camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight)
} }
function animate() { function animate() {
const delta = Math.min(0.05, clock.getDelta()) const delta = Math.min(0.05, clock.getDelta())
const deltaTime = delta / STEPS_PER_FRAME; const deltaTime = delta / STEPS_PER_FRAME
ocean.material.uniforms['time'].value += delta; ocean.material.uniforms['time'].value += delta
updateRaft(delta); updateRaft(delta)
// we look for collisions in substeps to mitigate the risk of // we look for collisions in substeps to mitigate the risk of
// an object traversing another too quickly for detection. // an object traversing another too quickly for detection.
for (let i = 0; i < STEPS_PER_FRAME; i++) { for (let i = 0; i < STEPS_PER_FRAME; i++) {
controls(deltaTime); controls(deltaTime)
updatePlayer(deltaTime); updatePlayer(deltaTime)
} }
if (camera.position.y > 3.5) if (camera.position.y > 3.5)
camera.lookAt(raft.position.x, raft.position.y, raft.position.z); camera.lookAt(raft.position.x, raft.position.y, raft.position.z)
renderer.render(scene, camera); renderer.render(scene, camera)
if (dev) stats.update(); if (dev) stats.update()
} }

View File

@ -1,9 +1,45 @@
body { body {
margin: 0; margin: 0;
background-color: #000; background-color: #041626;
color: #fff; font-size: 1.3em;
font-family: Georgia, serif;
overscroll-behavior: none; overscroll-behavior: none;
cursor: wait;
}
#loading {
width: fit-content;
color: #2c5c88;
font-size: 1.3em;
top: 20vh;
margin: auto;
}
#loadingMessage {
margin-bottom: 0.5em;
}
#labyTable {
width: 230px;
height: 230px;
margin-left: auto;
margin-right: auto;
margin-top: 20vh;
margin-bottom: 5vh;
border-collapse: collapse;
}
td {
width: 10px;
height: 10px;
transition: background-color 1s;
}
.wall {
background-color: transparent;
}
.ground {
background-color: #214464;
} }
#container { #container {
@ -32,48 +68,7 @@ body {
justify-content: center; justify-content: center;
z-index: 1; z-index: 1;
color: gray; color: gray;
} font-family: Times, "Times New Roman", Georgia, serif;
#message.loading {
display: flex;
flex-direction: column;
top: 20vh;
width: 100%;
margin: auto;
align-items: center;
gap: 5rem;
text-align: center;
font-size: 1.7em;
cursor: progress;
}
#progressCircle {
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: 200px;
height: 200px;
border: 4px solid dimgray;
border-radius: 50%;
font-size: 0;
font-size: 3vh;
font-weight: 700;
font-family: system-ui;
text-align: center;
}
#progressCircle::after {
content: "";
display: flex;
position: absolute;
width: 200px;
height: 200px;
top: -4px;
left: -4px;
border: 4px solid #1da8b7;
border-radius: 50%;
mask: conic-gradient(black var(--progress), transparent var(--progress));
} }
#message a { #message a {