Created
April 2, 2019 20:48
-
-
Save sketchpunk/40a8916712fe6adcb1a5742036de3e77 to your computer and use it in GitHub Desktop.
Gerstner Wave
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//################################################################################################# | |
class Water{ | |
constructor( gridSize=500, gridCell=51, style=0, defaultColor=0x676789 ){ | |
this.mat = new GerstnerWaveShader( gridCell ); | |
this.grid = new LineDotGrid( gridSize, gridSize, gridCell, gridCell, 0x676789, 0, 0, 2, this.mat, false ); | |
this._waveCount = 0; | |
//Center the Grid in World Space | |
this.grid.obj3D.position.x = gridSize * -0.5; | |
this.grid.obj3D.position.z = gridSize * -0.5; | |
if( style != -1) this.setStyle( style ); | |
} | |
update( et ){ this.mat.setTime( et ); } | |
/////////////////////////////////////////////////////////////////////////////// | |
// SETTERS - GETTERS | |
/////////////////////////////////////////////////////////////////////////////// | |
toggleGrid(){ | |
this.mat.setStaticXZ( ! this.mat.getStaticXZ() ); | |
return this; | |
} | |
setState( b ){ | |
this.mat.setWaveCount( ( b )? this._waveCount : 0 ); // Can disable waves by just setting the wave count to zero. | |
return this; | |
} | |
setStyle( s ){ | |
// Wave Index, Angle of Direction in Degrees, Steepness (Height of Wave), WaveLength ( Phase Cycle) | |
switch( s ){ | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
case 0: | |
this._waveCount = 3; | |
this.mat .setWaves( 0, 0, 1.0, 20.0 ) | |
.setWaves( 1, 135, 1.5, 10.0 ) | |
.setWaves( 2, 60, 0.5, 4.0 ) | |
.setWaveSpeed( 0.8 ) | |
.setWaveCount( this._waveCount ); | |
break; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
case 1: | |
this._waveCount = 3; | |
this.mat .setWaves( 0, 225, 2.0, 25.0 ) | |
.setWaves( 1, 140, 2.0, 8.0 ) | |
.setWaves( 2, 45, 1.5, 2.0 ) | |
.setWaveSpeed( 2.5 ) | |
.setWaveCount( this._waveCount ); | |
break; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
} | |
return this; | |
} | |
// Gets Y of the specific X,Z grid coord. | |
getGridY( x, z ){ | |
let wCnt = this.mat.uniforms.u_wave_cnt.value, | |
speed = this.mat.uniforms.u_wave_speed.value, | |
time = this.mat.uniforms.u_time.value, | |
w = this.mat.uniforms.u_waves.value, | |
wave = 0, | |
ii; | |
for(let i=0; i < wCnt; i++){ | |
ii = i * 4; | |
wave += gerstnerWaveY( x, z, | |
w[ ii ], | |
w[ ii+1 ], | |
w[ ii+2 ], | |
w[ ii+3 ], | |
time, speed ); | |
} | |
return wave; | |
} | |
// Pass in world space XZ, the function then uses barycentric coords to solve the Y value of the grid cell that the point exists in | |
getY( x, z ){ | |
if( this.mat.getWaveCount() == 0 ) return 0; | |
//Move Coords to Local Space of the grid. | |
x = x - this.grid.obj3D.position.x; | |
z = z - this.grid.obj3D.position.z; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// BaryCentric Coord of Quad, TOP - LEFT = 00, BOTTOM - RIGHT = 11 | |
// d00 - c10 | |
// a01 - b11 | |
// a : gx gy+1 : 00 | |
// b : gx+1 gy+1 : 01 | |
// c : gx+1 gy : 11 | |
// d : gx gy : 10 | |
// Right Triangle : a, b, c | |
// Left Triangle : c, d, a | |
let gx = Math.floor( x / this.grid.xInc ), // Top LEFT Corner of the Quat Grid Coord | |
gy = Math.floor( z / this.grid.yInc ), | |
u = (x % this.grid.xInc) / this.grid.xInc, // BaryCentric Weight of X,Y Coord in relation to quad its in. | |
v = (z % this.grid.yInc) / this.grid.yInc, | |
w = 1 - ( u + v ), | |
y0, y1, y2; | |
if( u <= (1 - v) ){ | |
// d, c, a 00 -> 01 -> 10 | |
y0 = this.getGridY( gx, gy ); | |
y1 = this.getGridY( gx+1, gy ); | |
y2 = this.getGridY( gx, gy+1 ); | |
}else{ | |
// b, a, c 01 -> 10 -> 01 | |
y0 = this.getGridY( gx+1, gy+1 ); | |
y1 = this.getGridY( gx, gy+1 ); | |
y2 = this.getGridY( gx+1, gy ); | |
w = -w; //Right side needs things inverted to work right, Dont know why... trial & error fix. | |
u = 1-u; | |
v = 1-v; | |
} | |
return w * y0 + u * y1 + v * y2; | |
} | |
} | |
//################################################################################################# | |
// Tutorial on the Math of how Gerstner Waves Works - https://catlikecoding.com/unity/tutorials/flow/waves/ | |
function gerstnerWaveY( x, y, wx, wy, steep, waveLen, time, speed ){ | |
let k = 6.283185307179586 / waveLen, // Phase Increment | |
c = Math.sqrt( 9.8 / k ), // Phase Speed. Higher the wave, the faster it moves, Gravity Constant | |
f = k * ( ( wx * x + wy * y ) - c * time * speed ), // Frequency - Specific time in Phase - PhaseInc * ( Angle - PhaseSpeed * Time ) | |
a = steep / k; // Amptitude, Steep=1, aptitude is at max where mesh will start to loop onto self. | |
return a * Math.sin( f ); | |
} | |
class GerstnerWaveShader extends $.RawShaderMaterial{ | |
constructor( gridSize ){ | |
super({ | |
fragmentShader : FRAGMENT_SHADER, | |
vertexShader : VERTEX_SHADER, | |
transparent : true, | |
uniforms : { | |
u_time : { value: 0.0 }, | |
u_pnt_size : { value: 100.0 }, | |
u_wave_speed : { value: 1.0 }, | |
u_wave_cnt : { value: 0 }, | |
u_waves : { value: new Float32Array( 3 * 4 ), type:"fv" }, | |
u_wave_staticXZ : { value: 1 }, | |
u_grid_size : { value: gridSize }, | |
u_grid_size_inv : { value: 1 / gridSize }, | |
} | |
}); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// SETTERS - GETTERS | |
/////////////////////////////////////////////////////////////////////////////// | |
getStaticXZ(){ return this.uniforms.u_wave_staticXZ.value == 1; } | |
setStaticXZ( b ){ this.uniforms.u_wave_staticXZ.value = ( b )? 1 : 0; return this; } | |
setWaveCount( i ){ this.uniforms.u_wave_cnt.value = i; return this; } | |
getWaveCount( i ){ return this.uniforms.u_wave_cnt.value; } | |
setTime( i ){ this.uniforms.u_time.value = i; return this; } | |
setWaveSpeed( i ){ this.uniforms.u_wave_speed.value = i; return this; } | |
/** Set a Specific Wave */ | |
setWaves( i, angle, steepness, wavelength ){ | |
i *= 4; | |
let rad = angle * 0.01745329251, | |
x = Math.cos( rad ), | |
y = Math.sin( rad ), | |
w = this.uniforms.u_waves.value; | |
w[ i++ ] = x; | |
w[ i++ ] = y; | |
w[ i++ ] = steepness; | |
w[ i ] = wavelength; | |
return this; | |
} | |
/** Set Raw Float Data to Wave Array. Can set any X,Y direction for different effects if want to not use a unit direction vector */ | |
setWavesRaw( i ){ | |
let w = this.uniforms.u_waves.value; | |
i *= 4; | |
for(let ii=1; i < arguments.length; ii++) w[ i++ ] = arguments[ ii ]; | |
return this; | |
} | |
} | |
const VERTEX_SHADER = ` | |
attribute vec4 position; | |
attribute vec3 color; | |
uniform mat4 modelViewMatrix; | |
uniform mat4 projectionMatrix; | |
uniform float u_time; | |
uniform float u_pnt_size; | |
uniform vec4 u_waves[3]; | |
uniform int u_wave_cnt; | |
uniform float u_wave_speed; | |
uniform int u_wave_staticXZ; | |
uniform float u_grid_size; | |
uniform float u_grid_size_inv; | |
varying vec3 v_color; | |
const float SCALE = 6.0; | |
const float PI_2 = 6.283185307179586; | |
// Wave = norm( Dir.xy ), Steepness( 0->1 ), WaveLength( PI2 / n ) | |
vec3 gerstnerWave( vec4 wave, vec2 pnt, float time, float speed ){ | |
float k = PI_2 / wave.w, // Phase Increment | |
c = sqrt( 9.8 / k ), // Phase Speed. Higher the wave, the faster it moves, Gravity Constant | |
// Frequency - Specific time in Phase | |
// PhaseInc * ( Angle - PhaseSpeed * Time ) | |
f = k * ( dot( wave.xy, pnt ) - c * time * speed ), | |
a = wave.z / k, // Amptitude, Steep=1, app is at max where mesh will start to loop onto self. | |
cos_af = a * cos( f ); // cache results for multi reuse. | |
/* Save for applying normals to mesh. | |
normal is normalize(cross(binormal, tangent)); | |
tangent = vec3( | |
-wave.x * wave.x * (steepness * sin(f)), | |
wave.x * (steepness * cos(f)), | |
-wave.x * wave.y * (steepness * sin(f)) | |
); | |
binormal = vec3( | |
-wave.x * wave.y * (steepness * sin(f)), | |
wave.y * (steepness * cos(f)), | |
-wave.y * wave.y * (steepness * sin(f)) | |
); | |
*/ | |
return vec3( | |
wave.x * cos_af, | |
a * sin( f ), | |
wave.y * cos_af | |
); | |
} | |
void main(){ | |
vec3 offset = vec3( 0 ); | |
float gy = floor( position.w * u_grid_size_inv ); | |
float gx = floor( position.w - u_grid_size * gy ); | |
vec2 grid = vec2( gx, gy ); | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// Calculate 3 Possible Gerstner Waves | |
if( u_wave_cnt > 0 ) offset += gerstnerWave( u_waves[ 0 ], grid, u_time, u_wave_speed ); | |
if( u_wave_cnt > 1 ) offset += gerstnerWave( u_waves[ 1 ], grid, u_time, u_wave_speed ); | |
if( u_wave_cnt > 2 ) offset += gerstnerWave( u_waves[ 2 ], grid, u_time, u_wave_speed ); | |
// Don't shift X,Z positions to use Barymetric Detection | |
if( u_wave_staticXZ == 1 ){ | |
offset.x = 0.0; | |
offset.z = 0.0; | |
} | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
vec4 ws_pos = modelViewMatrix * vec4( position.xyz + offset, 1.0 ); | |
gl_Position = projectionMatrix * ws_pos; | |
gl_PointSize = u_pnt_size * ( SCALE / -ws_pos.z ); | |
v_color = color; | |
}`; | |
const FRAGMENT_SHADER = `precision mediump float; | |
const vec2 UV_CENTER = vec2( 0.5 ); | |
varying vec3 v_color; | |
void main(){ | |
vec2 coord = gl_PointCoord - UV_CENTER; | |
gl_FragColor = vec4( v_color, smoothstep( 0.5, 0.45, length(coord) ) ); | |
}`; //if( length(coord) > 0.5 ) discard; \ | |
//################################################################################################# | |
export default Water; | |
export { WaterSys }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function getYTest(){ | |
let x = 1.6, y = 1.4; | |
let gx = Math.floor( x / gGrid.xInc ); | |
let gy = Math.floor( y / gGrid.yInc ); | |
let a = new $.Vector3( gx * gGrid.xInc, 0, (gy+1) * gGrid.yInc ), // gx, gy+1 | |
b = new $.Vector3( (gx+1) * gGrid.xInc, 0, (gy+1) * gGrid.yInc ), // gx+1, gy+1 | |
c = new $.Vector3( (gx+1) * gGrid.xInc, 0, gy * gGrid.yInc ), // gx+1, gy | |
d = new $.Vector3( gx * gGrid.xInc, 0, gy * gGrid.yInc ); // gx, gy | |
let vv = gWaveMat.getGridWaveXYZ( gx, gy+1 ); | |
a.y += vv[ 1 ] + 0.01; | |
vv = gWaveMat.getGridWaveXYZ( gx+1, gy+1 ); | |
b.y += vv[ 1 ] + 0.01; | |
vv = gWaveMat.getGridWaveXYZ( gx+1, gy ); | |
c.y += vv[ 1 ] + 0.01; | |
vv = gWaveMat.getGridWaveXYZ( gx, gy ); | |
d.y += vv[ 1 ] + 0.01; | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
gDebug .pointVec( a, 80, 0xff0000 ) | |
.pointVec( b, 80, 0x00ff00 ) | |
.pointVec( c, 80, 0x0000ff ) | |
.pointVec( d, 80, 0xffff00 ); | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// BaryCentric Coord of Quad, TOP - LEFT = 00, BOTTOM - RIGHT = 11 | |
// 00 - 10 | |
// 01 - 11 | |
let u = (x % gGrid.xInc) / gGrid.xInc, //BaryCentric Weight of X,Y Coord in relation to cell its in. | |
v = (y % gGrid.yInc) / gGrid.yInc, | |
w = 1 - ( u + v );; | |
let tri = new Array(); | |
if( u <= (1 - v) ){ | |
console.log( "LEFT" ); | |
tri.push( d, c, a ); // 00 -> 01 -> 10 | |
}else{ | |
console.log( "RIGHT" ); | |
tri.push( b, a, c ); // 01 -> 10 -> 01 | |
w = -w; //Right side needs things inverted to work right. | |
u = 1-u; | |
v = 1-v; | |
} | |
console.log("uvw", u, v, w ); | |
var xxx = w * tri[0].x + u * tri[1].x + v * tri[2].x, | |
yyy = w * tri[0].y + u * tri[1].y + v * tri[2].y, | |
zzz = w * tri[0].z + u * tri[1].z + v * tri[2].z; | |
console.log("final", x, y, "---", xxx, yyy, zzz ); | |
/* | |
this.gridXSize = xSize; | |
this.gridYSize = ySize; | |
this.vertCount = xSize * ySize; | |
this.xInc = xLen / (xSize-1); | |
this.yInc = yLen / (ySize-1); | |
*/ | |
gDebug.point( x, yyy, y, 80, 0xff00ff ); | |
} | |
function getY( x, y ){ | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
// BaryCentric Coord of Quad, TOP - LEFT = 00, BOTTOM - RIGHT = 11 | |
// 00 - 10 | |
// 01 - 11 | |
// a : gx gy+1 : 00 | |
// b : gx+1 gy+1 : 01 | |
// c : gx+1 gy : 11 | |
// d : gx gy : 10 | |
let gx = Math.floor( x / gGrid.xInc ), | |
gy = Math.floor( y / gGrid.yInc ), | |
u = (x % gGrid.xInc) / gGrid.xInc, //BaryCentric Weight of X,Y Coord in relation to cell its in. | |
v = (y % gGrid.yInc) / gGrid.yInc, | |
w = 1 - ( u + v ), | |
y0, y1, y2; | |
if( u <= (1 - v) ){ | |
// d, c, a 00 -> 01 -> 10 | |
y0 = gWaveMat.getGridY( gx, gy ); | |
y1 = gWaveMat.getGridY( gx+1, gy ); | |
y2 = gWaveMat.getGridY( gx, gy+1 ); | |
}else{ | |
// b, a, c 01 -> 10 -> 01 | |
y0 = gWaveMat.getGridY( gx+1, gy+1 ); | |
y1 = gWaveMat.getGridY( gx, gy+1 ); | |
y2 = gWaveMat.getGridY( gx+1, gy ); | |
w = -w; //Right side needs things inverted to work right. | |
u = 1-u; | |
v = 1-v; | |
} | |
let yyy = w * y0 + u * y1 + v * y2; | |
gDebug.point( x, yyy, y, 80, 0xffffff ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment