|
@@ -0,0 +1,271 @@
|
|
|
+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
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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 ).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 };
|
|
|
+ }
|
|
|
+}
|