Skip to content

Instantly share code, notes, and snippets.

@sketchpunk
Created April 2, 2019 20:48
Show Gist options
  • Save sketchpunk/40a8916712fe6adcb1a5742036de3e77 to your computer and use it in GitHub Desktop.
Save sketchpunk/40a8916712fe6adcb1a5742036de3e77 to your computer and use it in GitHub Desktop.
Gerstner Wave
//#################################################################################################
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 };
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