GerstnerWavesComponent.re.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import * as RE from 'rogue-engine'
  2. import * as THREE from 'three'
  3. // https://codesandbox.io/s/prototypes-pygsc7?file=/prototypes/water/001_gerstner.html:0-10387
  4. // https://catlikecoding.com/unity/tutorials/flow/waves/
  5. // Old Fungi Version
  6. // https://sketchpunk.bitbucket.io/src/fungi_v4/002_gerstner_waves.html
  7. // https://bitbucket.org/sketchpunk/sketchpunk.bitbucket.io/src/c036dfb4b5a93425220b4bb482c0e96e1edc77cf/src/fungi_v4/002_gerstner_waves.html#lines-304
  8. export default class GerstnerWavesComponent extends RE.Component {
  9. wave: GerstnerWave = new GerstnerWave(2, 12)
  10. vertexShader = `#version 300 es
  11. in vec3 position;
  12. in vec3 normal;
  13. in vec2 uv;
  14. uniform mat4 modelMatrix;
  15. uniform mat4 viewMatrix;
  16. uniform mat4 projectionMatrix;
  17. uniform float clock;
  18. out vec3 fragNorm;
  19. out vec3 fragWPos;
  20. /////////////////////////////////////////////////////////////////
  21. // #region GERSTNER WAVE
  22. const float PI_2 = 6.283185307179586;
  23. uniform vec4 Waves[3]; // XY : Direction, Z: Steepness, W: Wavelength
  24. uniform int WaveCnt;
  25. uniform float WaveSpeed;
  26. uniform int WaveXZ;
  27. uniform float GridSize;
  28. uniform float GridRes;
  29. // https://catlikecoding.com/unity/tutorials/flow/waves/
  30. // XY : norm( Dir.xy ), Z : Steepness( 0->1 ), W : 0 to Total Grid Divisions, Max gives one wave
  31. vec3 gerstnerWave( vec2 dir, float steepness, float wavelength, vec2 pnt, float time, out vec3 tangent, out vec3 binormal ){
  32. float k = PI_2 / wavelength; // Phase Increment
  33. float c = sqrt( 9.8 / k ); // Phase Speed. Higher the wave, the faster it moves, Gravity Constant
  34. float f = k * ( dot( dir, pnt ) - c * time ); // Frequency - Specific time in Phase : PhaseInc * ( Angle - PhaseSpeed * Time )
  35. float a = steepness / k; // Amptitude, Steep=1, app is at max where mesh will start to loop onto self.
  36. // cache results for multi reuse.
  37. float sin_f = sin( f );
  38. float cos_f = cos( f );
  39. float acos_f = a * cos_f;
  40. float scos_f = steepness * cos_f;
  41. float ssin_f = steepness * sin_f;
  42. /*====================================
  43. Must Init outside function as
  44. vec3 tangent = vec3( 1.0, 0.0, 0.0 );
  45. vec3 binormal = vec3( 0.0, 0.0, 1.0 );
  46. normal is normalize(cross(binormal, tangent)); */
  47. tangent += vec3(
  48. -dir.x * dir.x * ssin_f,
  49. dir.x * scos_f,
  50. -dir.x * dir.y * ssin_f
  51. );
  52. binormal += vec3(
  53. -dir.x * dir.y * ssin_f,
  54. dir.y * scos_f,
  55. -dir.y * dir.y * ssin_f
  56. );
  57. return vec3(
  58. dir.x * acos_f,
  59. a * sin_f,
  60. dir.y * acos_f
  61. );
  62. }
  63. // #endregion
  64. /////////////////////////////////////////////////////////////////
  65. // #region GRID
  66. const float FLOOR = 0.001;
  67. vec2 gridCenterCoord( vec2 p ){
  68. float inc = GridSize / GridRes;
  69. p += vec2( GridSize * 0.5 ); // Shift so top Left is at origin
  70. return floor( p / vec2( inc ) );
  71. }
  72. vec2 gridCoord( vec2 p ){
  73. float inc = GridSize / GridRes;
  74. return floor( p / vec2( inc ) );
  75. // return p / vec2( inc );
  76. }
  77. // #endregion
  78. /////////////////////////////////////////////////////////////////
  79. void main(){
  80. vec3 pos = position;
  81. fragNorm = normal;
  82. if( pos.y > FLOOR ){
  83. vec2 coord = gridCoord( pos.xz );
  84. vec3 offset = vec3( 0.0 );
  85. vec3 tangent = vec3( 1.0, 0.0, 0.0 ); // Normal is Up, So tangent is Right
  86. vec3 binormal = vec3( 0.0, 0.0, 1.0 ); // ... and Binormal is Forward
  87. float time = clock * WaveSpeed;
  88. if( WaveCnt > 0 ) offset += gerstnerWave( normalize(Waves[0].xy), Waves[0].z, Waves[0].w, coord, time, tangent, binormal );
  89. if( WaveCnt > 1 ) offset += gerstnerWave( normalize(Waves[1].xy), Waves[1].z, Waves[1].w, coord, time, tangent, binormal );
  90. if( WaveCnt > 2 ) offset += gerstnerWave( normalize(Waves[2].xy), Waves[2].z, Waves[2].w, coord, time, tangent, binormal );
  91. if( WaveXZ == 1 ){
  92. // Only keep height change
  93. offset.x = 0.0;
  94. offset.z = 0.0;
  95. }else{
  96. // keep Wave in cube bounds
  97. if( coord.x < 1.0 || coord.x >= GridRes ) offset.x = 0.0;
  98. if( coord.y < 1.0 || coord.y >= GridRes ) offset.z = 0.0;
  99. }
  100. pos += offset;
  101. // Only Apply Wave Normals on top face, use normal to find it
  102. if( normal.x < 0.001 && normal.z < 0.001 && normal.y > 0.9 ){
  103. fragNorm = normalize( cross( binormal, tangent ) );
  104. }
  105. }
  106. vec4 wPos = modelMatrix * vec4( pos, 1.0 );
  107. fragWPos = wPos.xyz;
  108. gl_Position = projectionMatrix * viewMatrix * wPos;
  109. }`
  110. fragmentShader = `#version 300 es
  111. precision mediump float;
  112. in vec3 fragNorm;
  113. in vec3 fragWPos;
  114. uniform vec3 cameraPosition;
  115. out vec4 outColor;
  116. ////////////////////////////////////////////////////////////////////////
  117. ////////////////////////////////////////////////////////////////////////
  118. const vec3 lightWPos = vec3( 10.0, 10.0, 10.0 );
  119. void main(){
  120. // vec3 N = normalize( cross( dFdx(fragWPos), dFdy(fragWPos) ) );
  121. vec3 N = normalize( fragNorm );
  122. // float ns = 6.0; // Normal is to light, scale it up to improve renderering
  123. // vec3 N = normalize( vec3( fragNorm.x * ns, fragNorm.y, fragNorm.z * ns ) );
  124. vec3 L = normalize( lightWPos - fragWPos );
  125. float NdL = clamp( dot( L, N ), 0.0, 1.0 );
  126. outColor = vec4( vec3( NdL ), 1.0 );
  127. // if( NdL <= 0.0 ) outColor.rgb = vec3( 1.0, 0.0, 0.0 );
  128. }`
  129. elapsed = 0
  130. awake() {
  131. }
  132. start() {
  133. this.elapsed = 0;
  134. let objectThree = (this.object3d as any)
  135. let geometry: THREE.BoxGeometry = objectThree.geometry;
  136. let gridSize = geometry.parameters.width//2
  137. let gridRes = geometry.parameters.width * 6//12
  138. objectThree.material = new THREE.RawShaderMaterial(
  139. { depthTest: true,
  140. transparent: false,
  141. uniforms: {
  142. clock : { value : 0 },
  143. Waves : { value : new Float32Array( [ 0.5,0.5,0.09,10, 0,1,0.05,5, 0.3,0.7,0.01,2, ]) },
  144. WaveCnt : { value : 3 },
  145. WaveSpeed : { value : 1.0 },
  146. WaveXZ : { value : 1 },
  147. GridSize : { value : gridSize },
  148. GridRes : { value : gridRes },
  149. },
  150. vertexShader: this.vertexShader,
  151. fragmentShader: this.fragmentShader,
  152. }
  153. )
  154. this.wave = new GerstnerWave(gridSize, gridRes)
  155. }
  156. update() {
  157. this.elapsed += RE.Runtime.deltaTime;
  158. (this.object3d as any).material.uniforms.clock.value = this.elapsed
  159. }
  160. getHeightAt(position: THREE.Vector3) {
  161. let height = 0
  162. let coords = new THREE.Vector2(position.x, position.z)
  163. this.wave.waves.forEach((wave) => {
  164. height += this.wave.calcWaveH(wave, coords, this.elapsed)
  165. })
  166. return height
  167. }
  168. }
  169. RE.registerComponent(GerstnerWavesComponent);
  170. // Compute height of the position
  171. class GerstnerWave {
  172. minH = 1;
  173. gridSize = 2;
  174. gridRes = 12;
  175. waveSpeed = 1.0;
  176. constructor(gridSize, gridRes) {
  177. this.gridSize = gridSize//2
  178. this.gridRes = gridRes//12
  179. }
  180. waves = [
  181. { dir:[ 0.5,0.5 ], steep:0.09, wavelen:10 },
  182. { dir:[ 0,1 ], steep:0.05, wavelen:5 },
  183. { dir:[ 0.3,0.7 ], steep:0.01, wavelen:2 },
  184. ];
  185. static dot(a: THREE.Vector2, b: THREE.Vector2) {
  186. return a.x * b.x + a.y * b.y;
  187. }
  188. calcWaveH( w, coord, time ){
  189. const dir = new THREE.Vector2( w.dir[0], w.dir[1] ).normalize();
  190. const k = 6.283185307179586 / w.wavelen; // Phase Increment
  191. const c = Math.sqrt( 9.8 / k ); // Phase Speed. Higher the wave, the faster it moves, Gravity Constant
  192. const f = k * ( GerstnerWave.dot( dir, coord ) - c * time ); // Frequency - Specific time in Phase : PhaseInc * ( Angle - PhaseSpeed * Time )
  193. const a = w.steep / k; // Amptitude, Steep=1, app is at max where mesh will start to loop onto self.
  194. // cache results for multi reuse.
  195. const sin_f = Math.sin( f );
  196. // const cos_f = Math.cos( f );
  197. // const acos_f = a * cos_f;
  198. // Gerstner Position
  199. // return [
  200. // dir.x * acos_f,
  201. // a * sin_f,
  202. // dir.y * acos_f
  203. // ];
  204. // Just the Y value
  205. return a * sin_f;
  206. }
  207. getHit( p, clock=0 ){
  208. const coord = this.gridCoord( p );
  209. const time = clock * this.waveSpeed;
  210. let h = 0;
  211. for( const w of this.waves ){
  212. h += this.calcWaveH( w, coord, time );
  213. }
  214. return [ p[0], this.minH + h, p[2] ];
  215. }
  216. gridCoord( p ){
  217. const inc = this.gridSize / this.gridRes;
  218. return {x: p[0] / inc , y: p[2] / inc };
  219. }
  220. }