From 28b57c37d5592f6726c7852e0fb554410c186be1 Mon Sep 17 00:00:00 2001 From: adrien Date: Sat, 10 Jun 2023 19:58:37 +0200 Subject: [PATCH] Gerstner water --- index.html | 123 ++++++++ main.js | 848 +++++++++++++++++++++++++++++------------------------ 2 files changed, 595 insertions(+), 376 deletions(-) diff --git a/index.html b/index.html index 673b689..94e112b 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,129 @@ } } + +
diff --git a/main.js b/main.js index 8653414..3aca914 100644 --- a/main.js +++ b/main.js @@ -1,323 +1,422 @@ + import * as THREE from 'three'; import Stats from 'three/addons/libs/stats.module.js'; import { Octree } from 'three/addons/math/Octree.js'; import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js'; - import { Capsule } from 'three/addons/math/Capsule.js'; - import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { Water } from 'three/addons/objects/Water.js'; import { Sky } from 'three/addons/objects/Sky.js'; import MazeMesh from './MazeMesh.js'; const mazeLength = 23 -const mazeWidth = 23 -const latitude = THREE.MathUtils.degToRad(35) -const longitude = THREE.MathUtils.degToRad(25) +const mazeWidth = 23 -let showGUI = window.location.search.includes("debug") -let showStats = window.location.search.includes("stats") +const parameters = { + elevation: 90 * Math.random(), + azimuth: 180 * Math.random(), +}; -const ambiance = new Audio("snd/ambiance.mp3") -ambiance.loop = true -const piano = new Audio("snd/waves-and-tears.mp3") -piano.loop = false +const waves = { + A: { direction: 0, steepness: 0.05, wavelength: 3 }, + B: { direction: 30, steepness: 0.10, wavelength: 6 }, + C: { direction: 60, steepness: 0.05, wavelength: 1.5 }, +}; -const loadMngr = new THREE.LoadingManager(); -const loader = new THREE.TextureLoader(loadMngr); -const waterTexture = loader.load('img/waternormals.jpg'); -const groundTexture = loader.load('img/pavement.jpg'); -const wallTexture = loader.load('img/wall.jpg'); -const woodTexture = loader.load('img/wood.jpg'); +const showParam = window.location.search.includes("param") +const showStats = window.location.search.includes("stats") + +const ambiance = new Audio("snd/ambiance.mp3") +ambiance.loop = true +const piano = new Audio("snd/waves-and-tears.mp3") +piano.loop = false + +const loadMngr = new THREE.LoadingManager(); +const loader = new THREE.TextureLoader(loadMngr); loadMngr.onLoad = () => { animate(); }; -const clock = new THREE.Clock(); +// + +const container = document.getElementById('container'); + +const renderer = new THREE.WebGLRenderer({ + powerPreference: "high-performance", + antialias: true, +}); +renderer.setPixelRatio(window.devicePixelRatio); +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFShadowMap; +container.appendChild(renderer.domElement); const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 ); +const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000); camera.rotation.order = 'YXZ'; -camera.position.set( 0, 25, 0 ); +camera.position.set(0, 25, 0); const worldOctree = new Octree(); -const raftOctree = new Octree(); - -const container = document.getElementById( 'container' ); - -const renderer = new THREE.WebGLRenderer( { antialias: true } ); -renderer.setPixelRatio( window.devicePixelRatio ); -renderer.setSize( window.innerWidth, window.innerHeight ); -renderer.shadowMap.enabled = true; -renderer.shadowMap.type = THREE.PCFShadowMap ; -renderer.toneMapping = THREE.ACESFilmicToneMapping; -container.appendChild( renderer.domElement ); +const raftOctree = new Octree(); // Water -waterTexture.wrapS = waterTexture.wrapT = THREE.RepeatWrapping; -const ocean = new Water( - new THREE.PlaneGeometry( 1000, 1000 ), - { - textureWidth : 512, - textureHeight : 512, - waterNormals : waterTexture, - sunDirection : new THREE.Vector3(), - sunColor : 0xffffff, - waterColor : 0x001e0f, - distortionScale: 3.7, - fog : scene.fog !== undefined, - alpha : 0.7 - } -); -ocean.rotation.x = - Math.PI * 0.5; -ocean.position.y = -.01 -ocean.receiveShadow = true; + +const waterGeometry = new THREE.PlaneGeometry(2048, 2048, 512, 512); + +const ocean = new Water(waterGeometry, { + textureWidth: 512, + textureHeight: 512, + waterNormals: loader.load( + 'img/waternormals.jpg', + function (texture) { + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + } + ), + sunDirection: new THREE.Vector3(), + sunColor: 0xffffff, + waterColor: 0x001e0f, + distortionScale: 3.7, + fog: scene.fog !== undefined, + alpha: 0.9 +}); +ocean.rotation.x = - Math.PI / 2; +ocean.position.y = -0.2; + ocean.material.transparent = true; -scene.add( ocean ); +ocean.material.onBeforeCompile = function (shader) { -// Lights + shader.uniforms.size = { value: 6 } -const ambientLight = new THREE.AmbientLight( 0x404040 , 1 ); // soft white light -scene.add( ambientLight ); + shader.uniforms.waveA = { + value: [ + Math.sin((waves.A.direction * Math.PI) / 180), + Math.cos((waves.A.direction * Math.PI) / 180), + waves.A.steepness, + waves.A.wavelength, + ], + }; + shader.uniforms.waveB = { + value: [ + Math.sin((waves.B.direction * Math.PI) / 180), + Math.cos((waves.B.direction * Math.PI) / 180), + waves.B.steepness, + waves.B.wavelength, + ], + }; + shader.uniforms.waveC = { + value: [ + Math.sin((waves.C.direction * Math.PI) / 180), + Math.cos((waves.C.direction * Math.PI) / 180), + waves.C.steepness, + waves.C.wavelength, + ], + }; + shader.vertexShader = document.getElementById('vertexShader').textContent; + shader.fragmentShader = document.getElementById('fragmentShader').textContent; -const sunLight = new THREE.DirectionalLight( 0xffffff, 0.3 ); -sunLight.castShadow = true; -sunLight.shadow.camera.near = 20; -sunLight.shadow.camera.far = 200; -sunLight.shadow.camera.right = 10; -sunLight.shadow.camera.left = -10; -sunLight.shadow.camera.top = 10; -sunLight.shadow.camera.bottom = -10; -sunLight.shadow.mapSize.width = 4096; -sunLight.shadow.mapSize.height = 4096; -sunLight.shadow.radius = 4; -sunLight.target = camera -scene.add( sunLight ); +}; -const torchLight = new THREE.SpotLight(0xffffe8, 1, mazeLength/2, .45, 1) -scene.add( torchLight ); -scene.add( torchLight.target ); +scene.add(ocean); // Skybox const sun = new THREE.Vector3(); const sky = new Sky(); -sky.scale.setScalar( 10000 ); -scene.add( sky ); +sky.scale.setScalar(10000); +scene.add(sky); const skyUniforms = sky.material.uniforms; -skyUniforms[ 'turbidity' ].value = 10; -skyUniforms[ 'rayleigh' ].value = 2; -skyUniforms[ 'mieCoefficient' ].value = 0.005; -skyUniforms[ 'mieDirectionalG' ].value = 0.8; +skyUniforms['turbidity'].value = 10; +skyUniforms['rayleigh'].value = 2; +skyUniforms['mieCoefficient'].value = 0.005; +skyUniforms['mieDirectionalG'].value = 0.8; -const parameters = { - elevation: 70, - azimuth: 160 -}; +const pmremGenerator = new THREE.PMREMGenerator(renderer); -const pmremGenerator = new THREE.PMREMGenerator( renderer ); -let renderTarget; +// Lights -const today = new Date() -const startOfYear = new Date(today.getFullYear(), 0, 0); -const diff = today - startOfYear; -const oneDay = 1000 * 60 * 60 * 24; -const dayOfYear = Math.floor(diff / oneDay); -const declination = 0.40928 * Math.sin(2*Math.PI*(dayOfYear+284)/365) -const startHour = 24 * Math.random() +const ambientLight = new THREE.AmbientLight(0x404040, 1); // soft white light +scene.add(ambientLight); -function updateSun() { - - let elevation, azimuth - if ( showGUI ) { - - elevation = THREE.MathUtils.degToRad( parameters.elevation ); - azimuth = THREE.MathUtils.degToRad( parameters.azimuth ); - - } else { - - const time = clock.elapsedTime ; - const hour = ( startHour + time / 1440 ) % 24 - const hourAngle = Math.PI * (1-hour/12) - elevation = Math.asin( Math.sin(declination)*Math.sin(latitude) + Math.cos(declination)*Math.cos(latitude)*Math.cos(hourAngle) ) - azimuth = -Math.PI/2 + Math.asin( Math.cos(declination)*Math.sin(hourAngle)/Math.cos(elevation) ) - - } - - const phi = Math.PI/2 - elevation - const theta = azimuth - - sun.setFromSphericalCoords( 100, phi, theta ); - - sky.material.uniforms[ 'sunPosition' ].value.copy( sun ); - ocean.material.uniforms[ 'sunDirection' ].value.copy( sun ).normalize(); - ambientLight.intensity = 0.5 + Math.max( elevation, 0 )/Math.PI; - - if ( elevation >= 0 ) { - - sunLight.visible = true - torchLight.visible = false - - } else { - - sunLight.visible = false - torchLight.visible = true - - } - - if ( renderTarget !== undefined ) renderTarget.dispose(); - - renderTarget = pmremGenerator.fromScene( sky ); - - scene.environment = renderTarget.texture; - -} +const sunLight = new THREE.DirectionalLight(0xffffff, 0.3); +sunLight.castShadow = true; +sunLight.shadow.camera.near = 20; +sunLight.shadow.camera.far = 200; +sunLight.shadow.camera.right = 10; +sunLight.shadow.camera.left = -10; +sunLight.shadow.camera.top = 10; +sunLight.shadow.camera.bottom = -10; +sunLight.shadow.mapSize.width = 4096; +sunLight.shadow.mapSize.height = 4096; +sunLight.shadow.radius = 4; +sunLight.target = camera +scene.add(sunLight); updateSun(); -const updateSunIntervalId = setInterval( updateSun, 100 ); - -// Ground - -const groundGeometry = new THREE.PlaneGeometry(mazeLength, mazeWidth) -groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping -groundTexture.repeat.set(mazeLength/2, mazeWidth/2) -const groundMaterial = new THREE.MeshPhongMaterial( { - map : groundTexture, - color : 0xFFFFFF, - emissive : 0, - specular : 0x000000, - shininess : 5, - bumpMap : groundTexture, - bumpScale : .02, - depthFunc : 3, - depthTest : true, - depthWrite: true -} ) -const ground = new THREE.Mesh( groundGeometry, groundMaterial ) -ground.rotation.x = - Math.PI / 2; -ground.receiveShadow = true; -ground.matrixAutoUpdate = false -ground.updateMatrix(); -scene.add(ground) -worldOctree.fromGraphNode( ground ) // Raft -const raftGeometry = new THREE.BoxGeometry( 1.8, .1, .9, 1, 1, 8 ) -const raftMaterial = new THREE.MeshPhongMaterial( { - map : woodTexture, - color : 0xFFFFFF, - emissive : 0, - specular : 0x505050, - shininess : 1, - bumpMap : woodTexture, - bumpScale : .1, - depthFunc : 3, - depthTest : true, - depthWrite : true, - displacementMap : woodTexture, +const raftGeometry = new THREE.BoxGeometry(1.8, .1, .9, 1, 1, 8) +const woodTexture = loader.load('img/wood.jpg'); +const raftMaterial = new THREE.MeshPhongMaterial({ + map: woodTexture, + color: 0xFFFFFF, + emissive: 0, + specular: 0x505050, + shininess: 1, + bumpMap: woodTexture, + bumpScale: .1, + depthFunc: 3, + depthTest: true, + depthWrite: true, + displacementMap: woodTexture, displacementScale: -0.08 -} ) -const raft = new THREE.Mesh( raftGeometry, raftMaterial ) -raft.position.set( .2, 0, -1 - mazeWidth/2 ) -raft.rotation.y = 1.4 -raft.rotation.order = 'ZXY'; -raft.castShadow = true; +}) +const raft = new THREE.Mesh(raftGeometry, raftMaterial) +raft.position.set(.2, ocean.position.y, -1 - mazeWidth / 2) +raft.rotation.y = 1.4 +raft.castShadow = true; +worldOctree.fromGraphNode(raft) +raftOctree.fromGraphNode(raft) scene.add(raft) -worldOctree.fromGraphNode( raft ) -raftOctree.fromGraphNode( raft ) - -camera.lookAt( raft.position.x, raft.position.y, raft.position.z ); // Maze -const wallMaterial = new THREE.MeshPhongMaterial( { - map : wallTexture, - color : 0xFCF8E5, - emissive : 0, - specular : 0x505050, - shininess : 4, - bumpMap : wallTexture, - bumpScale : .01, - depthFunc : 3, - depthTest : true, +const wallTexture = loader.load('img/wall.jpg'); +const wallMaterial = new THREE.MeshPhongMaterial({ + map: wallTexture, + color: 0xFCF8E5, + emissive: 0, + specular: 0x505050, + shininess: 4, + bumpMap: wallTexture, + bumpScale: .01, + depthFunc: 3, + depthTest: true, depthWrite: true -} ) +}) -const maze = new MazeMesh( mazeLength, mazeWidth, wallMaterial ); -maze.castShadow = true; -maze.receiveShadow = true; +// Maze + +const maze = new MazeMesh(mazeLength, mazeWidth, wallMaterial); +maze.castShadow = true; +maze.receiveShadow = true; maze.matrixAutoUpdate = false scene.add(maze) console.log(String(maze)) -const wall = new THREE.Mesh( maze.geometry ); +const wall = new THREE.Mesh(maze.geometry); let matrix = new THREE.Matrix4() -for ( let i=0; i { + texture.wrapS = texture.wrapT = THREE.RepeatWrapping + texture.repeat.set(mazeLength / 2, mazeWidth / 2) + } +); +const groundGeometry = new THREE.BoxGeometry(mazeLength, mazeWidth, 1) +const groundMaterial = new THREE.MeshPhongMaterial({ + map: pavementTexture, + color: 0xFFFFFF, + emissive: 0, + specular: 0x000000, + shininess: 5, + bumpMap: pavementTexture, + bumpScale: .02, + depthFunc: 3, + depthTest: true, + depthWrite: true +}) +const sideGroundTexture = wallTexture.clone() +sideGroundTexture.wrapS = sideGroundTexture.wrapT = THREE.RepeatWrapping +sideGroundTexture.repeat.set(mazeLength, 1) +const sideGroundMaterial = new THREE.MeshPhongMaterial({ + map: sideGroundTexture, + color: 0xFCF8E5, + emissive: 0, + specular: 0x505050, + shininess: 4, + bumpMap: sideGroundTexture, + bumpScale: .01, + depthFunc: 3, + depthTest: true, + depthWrite: true +}) +const ground = new THREE.Mesh( + groundGeometry, + [ + sideGroundMaterial, + sideGroundMaterial, + sideGroundMaterial, + sideGroundMaterial, + groundMaterial, + groundMaterial, + ] +) +ground.rotation.x = - Math.PI / 2; +ground.position.y = -0.5 +ground.receiveShadow = true; +ground.matrixAutoUpdate = false +ground.updateMatrix(); +scene.add(ground) - gui = new GUI( { width: 200 } ); +const groundCollisioner = new THREE.Mesh( + new THREE.PlaneGeometry(mazeLength, mazeWidth) +) +groundCollisioner.rotation.x = - Math.PI / 2; +worldOctree.fromGraphNode(groundCollisioner) - octreeHelper = new OctreeHelper( worldOctree ); - octreeHelper.visible = false; - scene.add( octreeHelper ); - const lightHelper = new THREE.DirectionalLightHelper( sunLight, .5 ) +// + +const stats = new Stats(); +if (showStats) container.appendChild(stats.dom); + +// GUI + +if (showParam) { + + const gui = new GUI(); + + const lightHelper = new THREE.DirectionalLightHelper(sunLight, .5) lightHelper.position.copy(maze.start) lightHelper.visible = false; - scene.add( lightHelper ); - var cameraHelper = new THREE.CameraHelper(sunLight.shadow.camera); - cameraHelper.visible = false; - scene.add(cameraHelper) - const showHelper = gui.add( { helpers: false }, "helpers" ) - showHelper.onChange( function ( value ) { + const octreeHelper = new OctreeHelper(worldOctree); + octreeHelper.visible = false; + scene.add(octreeHelper); + const showHelper = gui.add({ helpers: false }, "helpers") + showHelper.onChange(function (value) { + + lightHelper.visible = value; octreeHelper.visible = value; - lightHelper.visible = value; - cameraHelper.visible = value; - } ); + }); - const folderSky = gui.addFolder( 'Sky' ); - folderSky.add( parameters, 'elevation', -90, 90, 0.1 ).onChange( updateSun ); - folderSky.add( parameters, 'azimuth', - 180, 180, 0.1 ).onChange( updateSun ); + const folderSky = gui.addFolder('Sky'); + folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun); + folderSky.add(parameters, 'azimuth', - 180, 180, 0.1).onChange(updateSun); folderSky.open(); const waterUniforms = ocean.material.uniforms; - const folderWater = gui.addFolder( 'Water' ); - folderWater.add( waterUniforms.distortionScale, 'value', 0, 8, 0.1 ).name( 'distortionScale' ); - folderWater.add( waterUniforms.size, 'value', 0.1, 10, 0.1 ).name( 'size' ); + const folderWater = gui.addFolder('Water'); + folderWater + .add(waterUniforms.distortionScale, 'value', 0, 8, 0.1) + .name('distortionScale'); + folderWater.add(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size'); + folderWater.add(ocean.material, 'wireframe'); folderWater.open(); -} + const waveAFolder = gui.addFolder('Wave A'); + waveAFolder + .add(waves.A, 'direction', 0, 359) + .name('Direction') + .onChange((v) => { -if ( showStats ) { + const x = (v * Math.PI) / 180; + ocean.material.uniforms.waveA.value[0] = Math.sin(x); + ocean.material.uniforms.waveA.value[1] = Math.cos(x); - stats = new Stats(); - stats.domElement.style.position = 'absolute'; - stats.domElement.style.top = '0px'; - container.appendChild( stats.domElement ); + }); + waveAFolder + .add(waves.A, 'steepness', 0, 1, 0.01) + .name('Steepness') + .onChange((v) => { + + ocean.material.uniforms.waveA.value[2] = v; + + }); + waveAFolder + .add(waves.A, 'wavelength', 1, 100) + .name('Wavelength') + .onChange((v) => { + + ocean.material.uniforms.waveA.value[3] = v; + + }); + waveAFolder.open(); + + const waveBFolder = gui.addFolder('Wave B'); + waveBFolder + .add(waves.B, 'direction', 0, 359) + .name('Direction') + .onChange((v) => { + + const x = (v * Math.PI) / 180; + ocean.material.uniforms.waveB.value[0] = Math.sin(x); + ocean.material.uniforms.waveB.value[1] = Math.cos(x); + + }); + waveBFolder + .add(waves.B, 'steepness', 0, 1, 0.01) + .name('Steepness') + .onChange((v) => { + + ocean.material.uniforms.waveB.value[2] = v; + + }); + waveBFolder + .add(waves.B, 'wavelength', 1, 100) + .name('Wavelength') + .onChange((v) => { + + ocean.material.uniforms.waveB.value[3] = v; + + }); + waveBFolder.open(); + + const waveCFolder = gui.addFolder('Wave C'); + waveCFolder + .add(waves.C, 'direction', 0, 359) + .name('Direction') + .onChange((v) => { + + const x = (v * Math.PI) / 180; + ocean.material.uniforms.waveC.value[0] = Math.sin(x); + ocean.material.uniforms.waveC.value[1] = Math.cos(x); + + }); + waveCFolder + .add(waves.C, 'steepness', 0, 1, 0.01) + .name('Steepness') + .onChange((v) => { + + ocean.material.uniforms.waveC.value[2] = v; + + }); + waveCFolder + .add(waves.C, 'wavelength', 1, 100) + .name('Wavelength') + .onChange((v) => { + + ocean.material.uniforms.waveC.value[3] = v; + + }); + waveCFolder.open(); } +// + +const clock = new THREE.Clock(); + // Controls const GRAVITY = 30; @@ -325,94 +424,83 @@ const GRAVITY = 30; const STEPS_PER_FRAME = 5; const playerCollider = new Capsule( - new THREE.Vector3( 0, 25, 0 ), - new THREE.Vector3( 0, 25.5, 0 ), + new THREE.Vector3(0, 25, 0), + new THREE.Vector3(0, 25.5, 0), 0.3 ); -const playerVelocity = new THREE.Vector3(); +const playerVelocity = new THREE.Vector3(); const playerDirection = new THREE.Vector3(); let playerOnFloor = false; -let jumping = false; -let escaped = false; +let jumping = false; +let escaped = false; 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; - if ( event.code == 'Space' ) jumping = false + keyStates[event.code] = false; + if (event.code == 'Space') jumping = false -} ); +}); -container.addEventListener( 'mousedown', () => { +container.addEventListener('mousedown', () => { document.body.requestPointerLock(); -} ); +}); function lockChangeAlert() { - if (document.pointerLockElement === document.body) { - ambiance.play() - } else { - ambiance.pause() - } + if (document.pointerLockElement === document.body) { + ambiance.play() + } else { + ambiance.pause() + } } document.addEventListener("pointerlockchange", lockChangeAlert, false); -document.body.addEventListener( 'mousemove', ( event ) => { +document.body.addEventListener('mousemove', (event) => { - if ( document.pointerLockElement === document.body ) { + if (document.pointerLockElement === document.body) { camera.rotation.y -= event.movementX / 500; camera.rotation.x -= event.movementY / 500; } -} ); - -window.addEventListener( 'resize', onWindowResize ); - -function onWindowResize() { - - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - - renderer.setSize( window.innerWidth, window.innerHeight ); - -} +}); function playerCollisions() { - if ( !escaped && raftOctree.capsuleIntersect( playerCollider ) ) { + if (!escaped && raftOctree.capsuleIntersect(playerCollider)) { gameEnd() } - const result = worldOctree.capsuleIntersect( playerCollider ); + const result = worldOctree.capsuleIntersect(playerCollider); playerOnFloor = false; - if ( result ) { + if (result) { 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)); } @@ -423,23 +511,21 @@ function gameEnd() { escaped = true; message.className = "escaped"; piano.play(); - + } addEventListener("animationend", (event) => { - clearInterval( updateSunIntervalId ); document.exitPointerLock(); 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; @@ -448,20 +534,20 @@ function updatePlayer( deltaTime ) { } - playerVelocity.addScaledVector( playerVelocity, damping ); + playerVelocity.addScaledVector(playerVelocity, damping); - const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime ); - playerCollider.translate( deltaPosition ); + const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime); + playerCollider.translate(deltaPosition); playerCollisions(); - camera.position.copy( playerCollider.end ); + camera.position.copy(playerCollider.end); } function getForwardVector() { - camera.getWorldDirection( playerDirection ); + camera.getWorldDirection(playerDirection); playerDirection.y = 0; playerDirection.normalize(); @@ -471,47 +557,47 @@ function getForwardVector() { function getSideVector() { - camera.getWorldDirection( playerDirection ); + camera.getWorldDirection(playerDirection); playerDirection.y = 0; playerDirection.normalize(); - playerDirection.cross( camera.up ); + playerDirection.cross(camera.up); return playerDirection; } -function controls( deltaTime ) { +function controls(deltaTime) { // gives a bit of air control - const speedDelta = deltaTime * ( playerOnFloor ? 10 : 2 ); + const speedDelta = deltaTime * (playerOnFloor ? 10 : 2); - if ( keyStates["ArrowUp"] || keyStates[ 'KeyW' ] ) { + if (keyStates["ArrowUp"] || keyStates['KeyW']) { - playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) ); + playerVelocity.add(getForwardVector().multiplyScalar(speedDelta)); } - if ( keyStates["ArrowDown"] || keyStates[ 'KeyS' ] ) { + if (keyStates["ArrowDown"] || keyStates['KeyS']) { - playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) ); + playerVelocity.add(getForwardVector().multiplyScalar(- speedDelta)); } - if ( keyStates["ArrowLeft"] || keyStates[ 'KeyA' ] ) { + if (keyStates["ArrowLeft"] || keyStates['KeyA']) { - playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) ); + playerVelocity.add(getSideVector().multiplyScalar(- speedDelta)); } - if ( keyStates["ArrowRight"] || keyStates[ 'KeyD' ] ) { + if (keyStates["ArrowRight"] || keyStates['KeyD']) { - playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) ); + playerVelocity.add(getSideVector().multiplyScalar(speedDelta)); } - if ( playerOnFloor && jumping == false ) { + if (playerOnFloor && jumping == false) { - if ( keyStates[ 'Space' ] ) { + if (keyStates['Space']) { playerVelocity.y = 9; jumping = true @@ -524,116 +610,126 @@ function controls( deltaTime ) { function teleportPlayerIfOob() { - if ( camera.position.y <= - 25 ) { + if (camera.position.y <= - 25) { - playerCollider.start.set( 0, 25, 0 ); - playerCollider.end.set( 0, 25.5, 0 ); + playerCollider.start.set(0, 25, 0); + playerCollider.end.set(0, 25.5, 0); playerCollider.radius = 0.3; - camera.position.copy( playerCollider.end ); - camera.rotation.set( 0, 0, 0 ); - message.className= "" + camera.position.copy(playerCollider.end); + camera.rotation.set(0, 0, 0); + message.className = "" } } -const waves = { - A: { - direction: 0, - steepness: 0.015, - wavelength: 10, - }, - B: { - direction: 30, - steepness: 0.015, - wavelength: 5, - }, - C: { - direction: 60, - steepness: 0.015, - wavelength: 3, - }, -} - function getWaveInfo(x, z, time) { - const pos = new THREE.Vector3() - const tangent = new THREE.Vector3(1, 0, 0) - const binormal = new THREE.Vector3(0, 0, 1) - Object.keys(waves).forEach(function (wave) { - const w = waves[wave] - const k = (Math.PI * 2) / w.wavelength - const c = Math.sqrt(9.8 / k) + + const pos = new THREE.Vector3(); + const tangent = new THREE.Vector3(1, 0, 0); + const binormal = new THREE.Vector3(0, 0, 1); + Object.keys(waves).forEach((wave) => { + + const w = waves[wave]; + const k = (Math.PI * 2) / w.wavelength; + const c = Math.sqrt(9.8 / k); const d = new THREE.Vector2( Math.sin((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 a = w.steepness / k - pos.x += d.y * (a * Math.cos(f)) - pos.y += a * Math.sin(f) - pos.z += d.x * (a * Math.cos(f)) - tangent.x += -d.x * d.x * (w.steepness * Math.sin(f)) - tangent.y += d.x * (w.steepness * Math.cos(f)) - tangent.z += -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.z += -d.y * d.y * (w.steepness * Math.sin(f)) - }) - const normal = binormal.cross(tangent).normalize() - return { - position: pos, - normal: normal, - } + - Math.cos((w.direction * Math.PI) / 180) + ); + const f = k * (d.dot(new THREE.Vector2(x, z)) - c * time); + const a = w.steepness / k; + + pos.x += d.y * (a * Math.cos(f)); + pos.y += a * Math.sin(f); + pos.z += d.x * (a * Math.cos(f)); + + tangent.x += - d.x * d.x * (w.steepness * Math.sin(f)); + tangent.y += d.x * (w.steepness * Math.cos(f)); + tangent.z += - 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.z += - d.y * d.y * (w.steepness * Math.sin(f)); + + }); + + const normal = binormal.cross(tangent).normalize(); + + return { position: pos, normal: normal }; + } +function updateRaft(delta) { + + const t = ocean.material.uniforms['time'].value; + + const waveInfo = getWaveInfo(raft.position.x, raft.position.z, t); + raft.position.y = ocean.position.y + waveInfo.position.y; + const quat = new THREE.Quaternion().setFromEuler( + new THREE.Euler().setFromVector3(waveInfo.normal) + ); + raft.quaternion.rotateTowards(quat, delta * 0.5); + +} + +function updateSun() { + + const phi = THREE.MathUtils.degToRad(90 - parameters.elevation); + const theta = THREE.MathUtils.degToRad(parameters.azimuth); + + sun.setFromSphericalCoords(100, phi, theta); + + sky.material.uniforms['sunPosition'].value.copy(sun); + ocean.material.uniforms['sunDirection'].value.copy(sun).normalize(); + + ambientLight.intensity = 0.5 + Math.sin(Math.max(THREE.MathUtils.degToRad(parameters.elevation), 0)); + + scene.environment = pmremGenerator.fromScene(sky).texture; + +} + +window.addEventListener('resize', onWindowResize); + +function onWindowResize() { + + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); + +} function animate() { - const delta = Math.min( 0.05, clock.getDelta() ) + requestAnimationFrame(animate); + + const delta = Math.min(0.05, clock.getDelta()) const deltaTime = delta / STEPS_PER_FRAME; // we look for collisions in substeps to mitigate the risk of // 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); teleportPlayerIfOob(); } - const time = clock.elapsedTime; + if (camera.position.y > 3.5) + camera.lookAt(raft.position.x, raft.position.y, raft.position.z); - ocean.material.uniforms[ 'time' ].value += 1.0 / 100.0; - const waveInfo = getWaveInfo(raft.position.x, raft.position.z, time) - raft.position.y = waveInfo.position.y - const quat = new THREE.Quaternion().setFromEuler( - new THREE.Euler(waveInfo.normal.x, waveInfo.normal.y, waveInfo.normal.z) - ) - raft.quaternion.rotateTowards(quat, delta * 0.5) + sunLight.position.copy(sun).add(camera.position) - if ( sunLight.visible ) { - - sunLight.position.copy( sun ).add( camera.position ) + ocean.material.uniforms['time'].value += delta; + updateRaft(delta); - } + renderer.render(scene, camera); - if ( torchLight.visible ) { - - torchLight.position.copy(camera.position) - torchLight.position.y -= .2 - const targetDirection = camera.getWorldDirection(camera.up).add(camera.position) - torchLight.target.position.copy(targetDirection) - - } - - renderer.render( scene, camera ); - - if ( showStats ) stats.update(); - - requestAnimationFrame( animate ); + if (showStats) stats.update(); } \ No newline at end of file