Gerstner water
This commit is contained in:
parent
72d3afa812
commit
28b57c37d5
123
index.html
123
index.html
@ -17,6 +17,129 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script id="vertexShader" type="x-shader/x-vertex">
|
||||||
|
uniform mat4 textureMatrix;
|
||||||
|
uniform float time;
|
||||||
|
|
||||||
|
varying vec4 mirrorCoord;
|
||||||
|
varying vec4 worldPosition;
|
||||||
|
|
||||||
|
#include <common>
|
||||||
|
#include <fog_pars_vertex>
|
||||||
|
#include <shadowmap_pars_vertex>
|
||||||
|
#include <logdepthbuf_pars_vertex>
|
||||||
|
|
||||||
|
uniform vec4 waveA;
|
||||||
|
uniform vec4 waveB;
|
||||||
|
uniform vec4 waveC;
|
||||||
|
|
||||||
|
vec3 GerstnerWave (vec4 wave, vec3 p) {
|
||||||
|
float steepness = wave.z;
|
||||||
|
float wavelength = wave.w;
|
||||||
|
float k = 2.0 * PI / wavelength;
|
||||||
|
float c = sqrt(9.8 / k);
|
||||||
|
vec2 d = normalize(wave.xy);
|
||||||
|
float f = k * (dot(d, p.xy) - c * time);
|
||||||
|
float a = steepness / k;
|
||||||
|
|
||||||
|
return vec3(
|
||||||
|
d.x * (a * cos(f)),
|
||||||
|
d.y * (a * cos(f)),
|
||||||
|
a * sin(f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
mirrorCoord = modelMatrix * vec4( position, 1.0 );
|
||||||
|
worldPosition = mirrorCoord.xyzw;
|
||||||
|
mirrorCoord = textureMatrix * mirrorCoord;
|
||||||
|
|
||||||
|
vec3 p = position.xyz;
|
||||||
|
p += GerstnerWave(waveA, position.xyz);
|
||||||
|
p += GerstnerWave(waveB, position.xyz);
|
||||||
|
p += GerstnerWave(waveC, position.xyz);
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( p.x, p.y, p.z, 1.0);
|
||||||
|
|
||||||
|
#include <beginnormal_vertex>
|
||||||
|
#include <defaultnormal_vertex>
|
||||||
|
#include <logdepthbuf_vertex>
|
||||||
|
#include <fog_vertex>
|
||||||
|
#include <shadowmap_vertex>
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script id="fragmentShader" type="x-shader/x-fragment">
|
||||||
|
uniform sampler2D mirrorSampler;
|
||||||
|
uniform float alpha;
|
||||||
|
uniform float time;
|
||||||
|
uniform float size;
|
||||||
|
uniform float distortionScale;
|
||||||
|
uniform sampler2D normalSampler;
|
||||||
|
uniform vec3 sunColor;
|
||||||
|
uniform vec3 sunDirection;
|
||||||
|
uniform vec3 eye;
|
||||||
|
uniform vec3 waterColor;
|
||||||
|
|
||||||
|
varying vec4 mirrorCoord;
|
||||||
|
varying vec4 worldPosition;
|
||||||
|
|
||||||
|
vec4 getNoise( vec2 uv ) {
|
||||||
|
vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);
|
||||||
|
vec2 uv1 = uv / 107.0-vec2( time / -19.0, time / 31.0 );
|
||||||
|
vec2 uv2 = uv / vec2( 8907.0, 9803.0 ) + vec2( time / 101.0, time / 97.0 );
|
||||||
|
vec2 uv3 = uv / vec2( 1091.0, 1027.0 ) - vec2( time / 109.0, time / -113.0 );
|
||||||
|
vec4 noise = texture2D( normalSampler, uv0 ) +
|
||||||
|
texture2D( normalSampler, uv1 ) +
|
||||||
|
texture2D( normalSampler, uv2 ) +
|
||||||
|
texture2D( normalSampler, uv3 );
|
||||||
|
return noise * 0.5 - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor ) {
|
||||||
|
vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );
|
||||||
|
float direction = max( 0.0, dot( eyeDirection, reflection ) );
|
||||||
|
specularColor += pow( direction, shiny ) * sunColor * spec;
|
||||||
|
diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <common>
|
||||||
|
#include <packing>
|
||||||
|
#include <bsdfs>
|
||||||
|
#include <fog_pars_fragment>
|
||||||
|
#include <logdepthbuf_pars_fragment>
|
||||||
|
#include <lights_pars_begin>
|
||||||
|
#include <shadowmap_pars_fragment>
|
||||||
|
#include <shadowmask_pars_fragment>
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
#include <logdepthbuf_fragment>
|
||||||
|
vec4 noise = getNoise( worldPosition.xz * size );
|
||||||
|
vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );
|
||||||
|
|
||||||
|
vec3 diffuseLight = vec3(0.0);
|
||||||
|
vec3 specularLight = vec3(0.0);
|
||||||
|
|
||||||
|
vec3 worldToEye = eye-worldPosition.xyz;
|
||||||
|
vec3 eyeDirection = normalize( worldToEye );
|
||||||
|
sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );
|
||||||
|
|
||||||
|
float distance = length(worldToEye);
|
||||||
|
|
||||||
|
vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;
|
||||||
|
vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.w + distortion ) );
|
||||||
|
|
||||||
|
float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );
|
||||||
|
float rf0 = 0.3;
|
||||||
|
float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );
|
||||||
|
vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;
|
||||||
|
vec3 albedo = mix( ( sunColor * diffuseLight * 0.3 + scatter ) * getShadowMask(), ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance);
|
||||||
|
vec3 outgoingLight = albedo;
|
||||||
|
gl_FragColor = vec4( outgoingLight, alpha );
|
||||||
|
|
||||||
|
#include <tonemapping_fragment>
|
||||||
|
#include <fog_fragment>
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
|
588
main.js
588
main.js
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
import Stats from 'three/addons/libs/stats.module.js';
|
import Stats from 'three/addons/libs/stats.module.js';
|
||||||
|
|
||||||
import { Octree } from 'three/addons/math/Octree.js';
|
import { Octree } from 'three/addons/math/Octree.js';
|
||||||
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
|
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
|
||||||
|
|
||||||
import { Capsule } from 'three/addons/math/Capsule.js';
|
import { Capsule } from 'three/addons/math/Capsule.js';
|
||||||
|
|
||||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||||||
|
|
||||||
import { Water } from 'three/addons/objects/Water.js';
|
import { Water } from 'three/addons/objects/Water.js';
|
||||||
import { Sky } from 'three/addons/objects/Sky.js';
|
import { Sky } from 'three/addons/objects/Sky.js';
|
||||||
|
|
||||||
@ -16,11 +14,20 @@ import MazeMesh from './MazeMesh.js';
|
|||||||
|
|
||||||
const mazeLength = 23
|
const mazeLength = 23
|
||||||
const mazeWidth = 23
|
const mazeWidth = 23
|
||||||
const latitude = THREE.MathUtils.degToRad(35)
|
|
||||||
const longitude = THREE.MathUtils.degToRad(25)
|
|
||||||
|
|
||||||
let showGUI = window.location.search.includes("debug")
|
const parameters = {
|
||||||
let showStats = window.location.search.includes("stats")
|
elevation: 90 * Math.random(),
|
||||||
|
azimuth: 180 * Math.random(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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 showParam = window.location.search.includes("param")
|
||||||
|
const showStats = window.location.search.includes("stats")
|
||||||
|
|
||||||
const ambiance = new Audio("snd/ambiance.mp3")
|
const ambiance = new Audio("snd/ambiance.mp3")
|
||||||
ambiance.loop = true
|
ambiance.loop = true
|
||||||
@ -29,15 +36,24 @@ piano.loop = false
|
|||||||
|
|
||||||
const loadMngr = new THREE.LoadingManager();
|
const loadMngr = new THREE.LoadingManager();
|
||||||
const loader = new THREE.TextureLoader(loadMngr);
|
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');
|
|
||||||
loadMngr.onLoad = () => {
|
loadMngr.onLoad = () => {
|
||||||
animate();
|
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 scene = new THREE.Scene();
|
||||||
|
|
||||||
@ -48,38 +64,82 @@ camera.position.set( 0, 25, 0 );
|
|||||||
const worldOctree = new Octree();
|
const worldOctree = new Octree();
|
||||||
const raftOctree = 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 );
|
|
||||||
|
|
||||||
// Water
|
// Water
|
||||||
waterTexture.wrapS = waterTexture.wrapT = THREE.RepeatWrapping;
|
|
||||||
const ocean = new Water(
|
const waterGeometry = new THREE.PlaneGeometry(2048, 2048, 512, 512);
|
||||||
new THREE.PlaneGeometry( 1000, 1000 ),
|
|
||||||
{
|
const ocean = new Water(waterGeometry, {
|
||||||
textureWidth: 512,
|
textureWidth: 512,
|
||||||
textureHeight: 512,
|
textureHeight: 512,
|
||||||
waterNormals : waterTexture,
|
waterNormals: loader.load(
|
||||||
|
'img/waternormals.jpg',
|
||||||
|
function (texture) {
|
||||||
|
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
||||||
|
}
|
||||||
|
),
|
||||||
sunDirection: new THREE.Vector3(),
|
sunDirection: new THREE.Vector3(),
|
||||||
sunColor: 0xffffff,
|
sunColor: 0xffffff,
|
||||||
waterColor: 0x001e0f,
|
waterColor: 0x001e0f,
|
||||||
distortionScale: 3.7,
|
distortionScale: 3.7,
|
||||||
fog: scene.fog !== undefined,
|
fog: scene.fog !== undefined,
|
||||||
alpha : 0.7
|
alpha: 0.9
|
||||||
}
|
});
|
||||||
);
|
ocean.rotation.x = - Math.PI / 2;
|
||||||
ocean.rotation.x = - Math.PI * 0.5;
|
ocean.position.y = -0.2;
|
||||||
ocean.position.y = -.01
|
|
||||||
ocean.receiveShadow = true;
|
|
||||||
ocean.material.transparent = true;
|
ocean.material.transparent = true;
|
||||||
|
ocean.material.onBeforeCompile = function (shader) {
|
||||||
|
|
||||||
|
shader.uniforms.size = { value: 6 }
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
scene.add(ocean);
|
scene.add(ocean);
|
||||||
|
|
||||||
|
// Skybox
|
||||||
|
|
||||||
|
const sun = new THREE.Vector3();
|
||||||
|
|
||||||
|
const sky = new 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;
|
||||||
|
|
||||||
|
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
||||||
|
|
||||||
// Lights
|
// Lights
|
||||||
|
|
||||||
const ambientLight = new THREE.AmbientLight(0x404040, 1); // soft white light
|
const ambientLight = new THREE.AmbientLight(0x404040, 1); // soft white light
|
||||||
@ -99,119 +159,12 @@ sunLight.shadow.radius = 4;
|
|||||||
sunLight.target = camera
|
sunLight.target = camera
|
||||||
scene.add(sunLight);
|
scene.add(sunLight);
|
||||||
|
|
||||||
const torchLight = new THREE.SpotLight(0xffffe8, 1, mazeLength/2, .45, 1)
|
|
||||||
scene.add( torchLight );
|
|
||||||
scene.add( torchLight.target );
|
|
||||||
|
|
||||||
// Skybox
|
|
||||||
|
|
||||||
const sun = new THREE.Vector3();
|
|
||||||
|
|
||||||
const sky = new 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;
|
|
||||||
|
|
||||||
const parameters = {
|
|
||||||
elevation: 70,
|
|
||||||
azimuth: 160
|
|
||||||
};
|
|
||||||
|
|
||||||
const pmremGenerator = new THREE.PMREMGenerator( renderer );
|
|
||||||
let renderTarget;
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSun();
|
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
|
// Raft
|
||||||
|
|
||||||
const raftGeometry = new THREE.BoxGeometry(1.8, .1, .9, 1, 1, 8)
|
const raftGeometry = new THREE.BoxGeometry(1.8, .1, .9, 1, 1, 8)
|
||||||
|
const woodTexture = loader.load('img/wood.jpg');
|
||||||
const raftMaterial = new THREE.MeshPhongMaterial({
|
const raftMaterial = new THREE.MeshPhongMaterial({
|
||||||
map: woodTexture,
|
map: woodTexture,
|
||||||
color: 0xFFFFFF,
|
color: 0xFFFFFF,
|
||||||
@ -227,18 +180,16 @@ const raftMaterial = new THREE.MeshPhongMaterial( {
|
|||||||
displacementScale: -0.08
|
displacementScale: -0.08
|
||||||
})
|
})
|
||||||
const raft = new THREE.Mesh(raftGeometry, raftMaterial)
|
const raft = new THREE.Mesh(raftGeometry, raftMaterial)
|
||||||
raft.position.set( .2, 0, -1 - mazeWidth/2 )
|
raft.position.set(.2, ocean.position.y, -1 - mazeWidth / 2)
|
||||||
raft.rotation.y = 1.4
|
raft.rotation.y = 1.4
|
||||||
raft.rotation.order = 'ZXY';
|
|
||||||
raft.castShadow = true;
|
raft.castShadow = true;
|
||||||
scene.add(raft)
|
|
||||||
worldOctree.fromGraphNode(raft)
|
worldOctree.fromGraphNode(raft)
|
||||||
raftOctree.fromGraphNode(raft)
|
raftOctree.fromGraphNode(raft)
|
||||||
|
scene.add(raft)
|
||||||
camera.lookAt( raft.position.x, raft.position.y, raft.position.z );
|
|
||||||
|
|
||||||
// Maze
|
// Maze
|
||||||
|
|
||||||
|
const wallTexture = loader.load('img/wall.jpg');
|
||||||
const wallMaterial = new THREE.MeshPhongMaterial({
|
const wallMaterial = new THREE.MeshPhongMaterial({
|
||||||
map: wallTexture,
|
map: wallTexture,
|
||||||
color: 0xFCF8E5,
|
color: 0xFCF8E5,
|
||||||
@ -252,6 +203,8 @@ const wallMaterial = new THREE.MeshPhongMaterial( {
|
|||||||
depthWrite: true
|
depthWrite: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Maze
|
||||||
|
|
||||||
const maze = new MazeMesh(mazeLength, mazeWidth, wallMaterial);
|
const maze = new MazeMesh(mazeLength, mazeWidth, wallMaterial);
|
||||||
maze.castShadow = true;
|
maze.castShadow = true;
|
||||||
maze.receiveShadow = true;
|
maze.receiveShadow = true;
|
||||||
@ -269,55 +222,201 @@ for ( let i=0; i<maze.count; i++ ) {
|
|||||||
worldOctree.fromGraphNode(clone)
|
worldOctree.fromGraphNode(clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug
|
// Ground
|
||||||
|
|
||||||
let stats, octreeHelper, gui
|
const pavementTexture = loader.load(
|
||||||
if ( showGUI ) {
|
'img/pavement.jpg',
|
||||||
|
texture => {
|
||||||
|
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)
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const stats = new Stats();
|
||||||
|
if (showStats) container.appendChild(stats.dom);
|
||||||
|
|
||||||
|
// GUI
|
||||||
|
|
||||||
|
if (showParam) {
|
||||||
|
|
||||||
|
const gui = new GUI();
|
||||||
|
|
||||||
octreeHelper = new OctreeHelper( worldOctree );
|
|
||||||
octreeHelper.visible = false;
|
|
||||||
scene.add( octreeHelper );
|
|
||||||
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;
|
||||||
scene.add( lightHelper );
|
|
||||||
var cameraHelper = new THREE.CameraHelper(sunLight.shadow.camera);
|
const octreeHelper = new OctreeHelper(worldOctree);
|
||||||
cameraHelper.visible = false;
|
octreeHelper.visible = false;
|
||||||
scene.add(cameraHelper)
|
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) {
|
||||||
|
|
||||||
octreeHelper.visible = value;
|
|
||||||
lightHelper.visible = value;
|
lightHelper.visible = value;
|
||||||
cameraHelper.visible = value;
|
octreeHelper.visible = value;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folderSky = gui.addFolder('Sky');
|
const folderSky = gui.addFolder('Sky');
|
||||||
folderSky.add( parameters, 'elevation', -90, 90, 0.1 ).onChange( updateSun );
|
folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun);
|
||||||
folderSky.add(parameters, 'azimuth', - 180, 180, 0.1).onChange(updateSun);
|
folderSky.add(parameters, 'azimuth', - 180, 180, 0.1).onChange(updateSun);
|
||||||
folderSky.open();
|
folderSky.open();
|
||||||
|
|
||||||
const waterUniforms = ocean.material.uniforms;
|
const waterUniforms = ocean.material.uniforms;
|
||||||
|
|
||||||
const folderWater = gui.addFolder('Water');
|
const folderWater = gui.addFolder('Water');
|
||||||
folderWater.add( waterUniforms.distortionScale, 'value', 0, 8, 0.1 ).name( 'distortionScale' );
|
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(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size');
|
||||||
|
folderWater.add(ocean.material, 'wireframe');
|
||||||
folderWater.open();
|
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';
|
waveAFolder
|
||||||
stats.domElement.style.top = '0px';
|
.add(waves.A, 'steepness', 0, 1, 0.01)
|
||||||
container.appendChild( stats.domElement );
|
.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
|
// Controls
|
||||||
|
|
||||||
const GRAVITY = 30;
|
const GRAVITY = 30;
|
||||||
@ -379,17 +478,6 @@ document.body.addEventListener( 'mousemove', ( event ) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener( 'resize', onWindowResize );
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function playerCollisions() {
|
function playerCollisions() {
|
||||||
|
|
||||||
if (!escaped && raftOctree.capsuleIntersect(playerCollider)) {
|
if (!escaped && raftOctree.capsuleIntersect(playerCollider)) {
|
||||||
@ -428,13 +516,11 @@ function gameEnd() {
|
|||||||
|
|
||||||
addEventListener("animationend", (event) => {
|
addEventListener("animationend", (event) => {
|
||||||
|
|
||||||
clearInterval( updateSunIntervalId );
|
|
||||||
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;
|
||||||
@ -537,58 +623,87 @@ function teleportPlayerIfOob() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
function getWaveInfo(x, z, time) {
|
||||||
const pos = new THREE.Vector3()
|
|
||||||
const tangent = new THREE.Vector3(1, 0, 0)
|
const pos = new THREE.Vector3();
|
||||||
const binormal = new THREE.Vector3(0, 0, 1)
|
const tangent = new THREE.Vector3(1, 0, 0);
|
||||||
Object.keys(waves).forEach(function (wave) {
|
const binormal = new THREE.Vector3(0, 0, 1);
|
||||||
const w = waves[wave]
|
Object.keys(waves).forEach((wave) => {
|
||||||
const k = (Math.PI * 2) / w.wavelength
|
|
||||||
const c = Math.sqrt(9.8 / k)
|
const w = waves[wave];
|
||||||
|
const k = (Math.PI * 2) / w.wavelength;
|
||||||
|
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.y += a * Math.sin(f)
|
pos.x += d.y * (a * Math.cos(f));
|
||||||
pos.z += d.x * (a * Math.cos(f))
|
pos.y += a * Math.sin(f);
|
||||||
tangent.x += -d.x * d.x * (w.steepness * Math.sin(f))
|
pos.z += d.x * (a * Math.cos(f));
|
||||||
tangent.y += d.x * (w.steepness * Math.cos(f))
|
|
||||||
tangent.z += -d.x * d.y * (w.steepness * Math.sin(f))
|
tangent.x += - d.x * d.x * (w.steepness * Math.sin(f));
|
||||||
binormal.x += -d.x * d.y * (w.steepness * Math.sin(f))
|
tangent.y += d.x * (w.steepness * Math.cos(f));
|
||||||
binormal.y += d.y * (w.steepness * Math.cos(f))
|
tangent.z += - d.x * d.y * (w.steepness * Math.sin(f));
|
||||||
binormal.z += -d.y * d.y * (w.steepness * Math.sin(f))
|
|
||||||
})
|
binormal.x += - d.x * d.y * (w.steepness * Math.sin(f));
|
||||||
const normal = binormal.cross(tangent).normalize()
|
binormal.y += d.y * (w.steepness * Math.cos(f));
|
||||||
return {
|
binormal.z += - d.y * d.y * (w.steepness * Math.sin(f));
|
||||||
position: pos,
|
|
||||||
normal: normal,
|
});
|
||||||
}
|
|
||||||
|
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() {
|
function animate() {
|
||||||
|
|
||||||
|
requestAnimationFrame(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;
|
||||||
|
|
||||||
@ -605,35 +720,16 @@ function animate() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if ( sunLight.visible ) {
|
|
||||||
|
|
||||||
sunLight.position.copy(sun).add(camera.position)
|
sunLight.position.copy(sun).add(camera.position)
|
||||||
|
|
||||||
}
|
ocean.material.uniforms['time'].value += delta;
|
||||||
|
updateRaft(delta);
|
||||||
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);
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
if (showStats) stats.update();
|
if (showStats) stats.update();
|
||||||
|
|
||||||
requestAnimationFrame( animate );
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user