123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- import * as RE from 'rogue-engine'
- import * as THREE from 'three'
- // https://codesandbox.io/s/prototypes-pygsc7?file=/prototypes/water/001_gerstner.html:0-10387
- // https://catlikecoding.com/unity/tutorials/flow/waves/
- // Old Fungi Version
- // https://sketchpunk.bitbucket.io/src/fungi_v4/002_gerstner_waves.html
- // https://bitbucket.org/sketchpunk/sketchpunk.bitbucket.io/src/c036dfb4b5a93425220b4bb482c0e96e1edc77cf/src/fungi_v4/002_gerstner_waves.html#lines-304
- export default class GerstnerWavesComponent extends RE.Component {
- wave: GerstnerWave = new GerstnerWave(2, 12)
- vertexShader = `#version 300 es
- in vec3 position;
- in vec3 normal;
- in vec2 uv;
-
- uniform mat4 modelMatrix;
- uniform mat4 viewMatrix;
- uniform mat4 projectionMatrix;
-
- uniform float clock;
- out vec3 fragNorm;
- out vec3 fragWPos;
- /////////////////////////////////////////////////////////////////
- // #region GERSTNER WAVE
- const float PI_2 = 6.283185307179586;
- uniform vec4 Waves[3]; // XY : Direction, Z: Steepness, W: Wavelength
- uniform int WaveCnt;
- uniform float WaveSpeed;
- uniform int WaveXZ;
- uniform float GridSize;
- uniform float GridRes;
- // https://catlikecoding.com/unity/tutorials/flow/waves/
- // XY : norm( Dir.xy ), Z : Steepness( 0->1 ), W : 0 to Total Grid Divisions, Max gives one wave
- vec3 gerstnerWave( vec2 dir, float steepness, float wavelength, vec2 pnt, float time, out vec3 tangent, out vec3 binormal ){
- float k = PI_2 / wavelength; // Phase Increment
- float c = sqrt( 9.8 / k ); // Phase Speed. Higher the wave, the faster it moves, Gravity Constant
- float f = k * ( dot( dir, pnt ) - c * time ); // Frequency - Specific time in Phase : PhaseInc * ( Angle - PhaseSpeed * Time )
- float a = steepness / k; // Amptitude, Steep=1, app is at max where mesh will start to loop onto self.
-
- // cache results for multi reuse.
- float sin_f = sin( f );
- float cos_f = cos( f );
- float acos_f = a * cos_f;
- float scos_f = steepness * cos_f;
- float ssin_f = steepness * sin_f;
- /*====================================
- Must Init outside function as
- vec3 tangent = vec3( 1.0, 0.0, 0.0 );
- vec3 binormal = vec3( 0.0, 0.0, 1.0 );
- normal is normalize(cross(binormal, tangent)); */
- tangent += vec3(
- -dir.x * dir.x * ssin_f,
- dir.x * scos_f,
- -dir.x * dir.y * ssin_f
- );
- binormal += vec3(
- -dir.x * dir.y * ssin_f,
- dir.y * scos_f,
- -dir.y * dir.y * ssin_f
- );
-
- return vec3(
- dir.x * acos_f,
- a * sin_f,
- dir.y * acos_f
- );
- }
- // #endregion
- /////////////////////////////////////////////////////////////////
- // #region GRID
- const float FLOOR = 0.001;
- vec2 gridCenterCoord( vec2 p ){
- float inc = GridSize / GridRes;
- p += vec2( GridSize * 0.5 ); // Shift so top Left is at origin
- return floor( p / vec2( inc ) );
- }
- vec2 gridCoord( vec2 p ){
- float inc = GridSize / GridRes;
- return floor( p / vec2( inc ) );
- // return p / vec2( inc );
- }
- // #endregion
-
- /////////////////////////////////////////////////////////////////
- void main(){
- vec3 pos = position;
- fragNorm = normal;
- if( pos.y > FLOOR ){
- vec2 coord = gridCoord( pos.xz );
- vec3 offset = vec3( 0.0 );
- vec3 tangent = vec3( 1.0, 0.0, 0.0 ); // Normal is Up, So tangent is Right
- vec3 binormal = vec3( 0.0, 0.0, 1.0 ); // ... and Binormal is Forward
- float time = clock * WaveSpeed;
- if( WaveCnt > 0 ) offset += gerstnerWave( normalize(Waves[0].xy), Waves[0].z, Waves[0].w, coord, time, tangent, binormal );
- if( WaveCnt > 1 ) offset += gerstnerWave( normalize(Waves[1].xy), Waves[1].z, Waves[1].w, coord, time, tangent, binormal );
- if( WaveCnt > 2 ) offset += gerstnerWave( normalize(Waves[2].xy), Waves[2].z, Waves[2].w, coord, time, tangent, binormal );
-
- if( WaveXZ == 1 ){
- // Only keep height change
- offset.x = 0.0;
- offset.z = 0.0;
- }else{
- // keep Wave in cube bounds
- if( coord.x < 1.0 || coord.x >= GridRes ) offset.x = 0.0;
- if( coord.y < 1.0 || coord.y >= GridRes ) offset.z = 0.0;
- }
- pos += offset;
- // Only Apply Wave Normals on top face, use normal to find it
- if( normal.x < 0.001 && normal.z < 0.001 && normal.y > 0.9 ){
- fragNorm = normalize( cross( binormal, tangent ) );
- }
- }
- vec4 wPos = modelMatrix * vec4( pos, 1.0 );
- fragWPos = wPos.xyz;
- gl_Position = projectionMatrix * viewMatrix * wPos;
- }`
- fragmentShader = `#version 300 es
- precision mediump float;
- in vec3 fragNorm;
- in vec3 fragWPos;
- uniform vec3 cameraPosition;
- out vec4 outColor;
- ////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////
- const vec3 lightWPos = vec3( 10.0, 10.0, 10.0 );
- void main(){
- // vec3 N = normalize( cross( dFdx(fragWPos), dFdy(fragWPos) ) );
- vec3 N = normalize( fragNorm );
-
- // float ns = 6.0; // Normal is to light, scale it up to improve renderering
- // vec3 N = normalize( vec3( fragNorm.x * ns, fragNorm.y, fragNorm.z * ns ) );
- vec3 L = normalize( lightWPos - fragWPos );
- float NdL = clamp( dot( L, N ), 0.0, 1.0 );
- outColor = vec4( vec3( NdL ), 1.0 );
- // if( NdL <= 0.0 ) outColor.rgb = vec3( 1.0, 0.0, 0.0 );
- }`
- elapsed = 0
- awake() {
- }
- start() {
- this.elapsed = 0;
- let objectThree = (this.object3d as any)
- let geometry: THREE.BoxGeometry = objectThree.geometry;
- let gridSize = geometry.parameters.width//2
- let gridRes = geometry.parameters.width * 6//12
- objectThree.material = new THREE.RawShaderMaterial(
- { depthTest: true,
- transparent: false,
- uniforms: {
- clock : { value : 0 },
- Waves : { value : new Float32Array( [ 0.5,0.5,0.09,10, 0,1,0.05,5, 0.3,0.7,0.01,2, ]) },
- WaveCnt : { value : 3 },
- WaveSpeed : { value : 1.0 },
- WaveXZ : { value : 1 },
- GridSize : { value : gridSize },
- GridRes : { value : gridRes },
- },
- vertexShader: this.vertexShader,
- fragmentShader: this.fragmentShader,
- }
- )
- this.wave = new GerstnerWave(gridSize, gridRes)
- }
- update() {
- this.elapsed += RE.Runtime.deltaTime;
- (this.object3d as any).material.uniforms.clock.value = this.elapsed
- }
- getHeightAt(position: THREE.Vector3) {
- let height = 0
- let coords = new THREE.Vector2(position.x, position.z)
- this.wave.waves.forEach((wave) => {
- height += this.wave.calcWaveH(wave, coords, this.elapsed)
- })
- return height
- }
- }
- RE.registerComponent(GerstnerWavesComponent);
- // Compute height of the position
- class GerstnerWave {
- minH = 1;
- gridSize = 2;
- gridRes = 12;
- waveSpeed = 1.0;
- constructor(gridSize, gridRes) {
- this.gridSize = gridSize//2
- this.gridRes = gridRes//12
- }
- waves = [
- { dir:[ 0.5,0.5 ], steep:0.09, wavelen:10 },
- { dir:[ 0,1 ], steep:0.05, wavelen:5 },
- { dir:[ 0.3,0.7 ], steep:0.01, wavelen:2 },
- ];
- static dot(a: THREE.Vector2, b: THREE.Vector2) {
- return a.x * b.x + a.y * b.y;
- }
- calcWaveH( w, coord, time ){
- const dir = new THREE.Vector2( w.dir[0], w.dir[1] ).normalize();
- const k = 6.283185307179586 / w.wavelen; // Phase Increment
- const c = Math.sqrt( 9.8 / k ); // Phase Speed. Higher the wave, the faster it moves, Gravity Constant
- const f = k * ( GerstnerWave.dot( dir, coord ) - c * time ); // Frequency - Specific time in Phase : PhaseInc * ( Angle - PhaseSpeed * Time )
- const a = w.steep / k; // Amptitude, Steep=1, app is at max where mesh will start to loop onto self.
-
- // cache results for multi reuse.
- const sin_f = Math.sin( f );
- // const cos_f = Math.cos( f );
- // const acos_f = a * cos_f;
- // Gerstner Position
- // return [
- // dir.x * acos_f,
- // a * sin_f,
- // dir.y * acos_f
- // ];
- // Just the Y value
- return a * sin_f;
- }
- getHit( p, clock=0 ){
- const coord = this.gridCoord( p );
- const time = clock * this.waveSpeed;
- let h = 0;
- for( const w of this.waves ){
- h += this.calcWaveH( w, coord, time );
- }
- return [ p[0], this.minH + h, p[2] ];
- }
- gridCoord( p ){
- const inc = this.gridSize / this.gridRes;
- return {x: p[0] / inc , y: p[2] / inc };
- }
- }
|