1
+ import { RendererInterface } from './renderer' ;
2
+ import { createShader , degreesToRadians , rangedRandom } from './utility' ;
3
+ import { mat4 , vec2 , vec3 } from 'gl-matrix' ;
4
+
5
+ interface Wave {
6
+ progress : number ; // [0, 1]
7
+ xBias : number ;
8
+ }
9
+
10
+ interface WaveParticle {
11
+ position : vec3 ;
12
+ rotation : vec3 ;
13
+ age : number ; // [0, 1]
14
+ }
15
+
16
+ const WAVE_COUNT = 3 ;
17
+ const WAVE_BIAS_MIN = 0.01 ;
18
+ const WAVE_BIAS_MAX = 0.03 ;
19
+ const WAVE_PROGRESS_SPEED = 0.3 ;
20
+
21
+ const WAVE_BREAKING_START_PROGRESS = 0.9 ;
22
+ const WAVE_PARTICLE_SAMPLING_COUNT = 10 ;
23
+
24
+ const WAVE_WIDTH = 10 ;
25
+ const WAVE_DISTANCE_DEPTH = 30 ;
26
+
27
+ const WAVE_PARTICLE_AGE_RANDOM = 0.1 ;
28
+ const WAVE_PARTICLE_ROTATION_SPEED = 0.1 ;
29
+ const WAVE_PARTICLE_AGING_SPEED = 1 ;
30
+
31
+ const WAVEPARTICLE_SCALING = 0.15 ;
32
+
33
+ export class WaveBreakingRenderer implements RendererInterface {
34
+ canvas : HTMLCanvasElement ;
35
+ context : WebGLRenderingContext ;
36
+
37
+ // webgl default is x(right), y(up), z(out of screen = towards the viewer) = right-handed coordinate system.
38
+ cameraDistance : number = 10 ;
39
+ cameraPitchRadians : number = degreesToRadians ( - 15 ) ;
40
+ waveParticles : WaveParticle [ ] = [ ] ;
41
+ waves : Wave [ ] = [ ] ;
42
+ groundY : number = 0 ;
43
+
44
+ shaderProgram : WebGLProgram ;
45
+ vertexBuffer : WebGLBuffer ;
46
+ lastVertexCount : number = 0 ;
47
+
48
+ alpha : number = 0.0 ;
49
+
50
+ constructor ( canvasElement : HTMLCanvasElement ) {
51
+ this . canvas = canvasElement ;
52
+ this . canvas . width = this . canvas . clientWidth ;
53
+ this . canvas . height = this . canvas . clientHeight ;
54
+ this . context = this . canvas . getContext ( 'webgl' ) ! ;
55
+ this . context . viewport ( 0 , 0 , this . canvas . width , this . canvas . height ) ;
56
+ this . shaderProgram = this . context . createProgram ( ) ! ;
57
+ this . vertexBuffer = this . context . createBuffer ( ) ! ;
58
+ }
59
+
60
+ stop ( ) {
61
+ }
62
+
63
+ start ( ) {
64
+ this . initiateWaveBreaking ( ) ;
65
+
66
+ // Vertex shader source code
67
+ const vertexShaderSource = `
68
+ attribute vec3 position;
69
+ uniform mat4 viewMatrix;
70
+ uniform mat4 projectionMatrix;
71
+ void main() {
72
+ gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);
73
+ }
74
+ ` ;
75
+
76
+ // Fragment shader source code
77
+ const fragmentShaderSource = `
78
+ precision mediump float;
79
+ void main() {
80
+ gl_FragColor = vec4(1, 1, 1, 1);
81
+ }
82
+ ` ;
83
+
84
+ const gl = this . context ;
85
+
86
+ // Create shader program
87
+ const program = gl . createProgram ( ) ! ;
88
+ const vertexShader = createShader ( gl , gl . VERTEX_SHADER , vertexShaderSource ) ;
89
+ const fragmentShader = createShader ( gl , gl . FRAGMENT_SHADER , fragmentShaderSource ) ;
90
+ gl . attachShader ( program , vertexShader ) ;
91
+ gl . attachShader ( program , fragmentShader ) ;
92
+ gl . linkProgram ( program ) ;
93
+
94
+ // Check for linking errors
95
+ if ( ! gl . getProgramParameter ( program , gl . LINK_STATUS ) ) {
96
+ const error = gl . getProgramInfoLog ( program ) ;
97
+ console . error ( 'Failed to link program:' , error ) ;
98
+ gl . deleteProgram ( program ) ;
99
+ return null ;
100
+ }
101
+
102
+ gl . useProgram ( program ) ;
103
+
104
+ this . shaderProgram = program ;
105
+ }
106
+
107
+ initiateWaveBreaking ( ) {
108
+ this . alpha = 0.0 ;
109
+ this . waveParticles = [ ] ;
110
+
111
+ for ( let iMake = 0 ; iMake < WAVE_COUNT ; iMake ++ ) {
112
+ const wave = {
113
+ progress : iMake / WAVE_COUNT + rangedRandom ( - 0.25 , 0.25 ) / WAVE_COUNT ,
114
+ xBias : rangedRandom ( WAVE_BIAS_MIN , WAVE_BIAS_MAX ) ,
115
+ } ;
116
+ this . waves . push ( wave ) ;
117
+ }
118
+ }
119
+
120
+ update ( dt : number ) {
121
+ this . updateWave ( dt ) ;
122
+ this . updateWaveBreaking ( dt ) ;
123
+ }
124
+
125
+ updateWave ( dt : number ) {
126
+ for ( let iWave = 0 ; iWave < this . waves . length ; iWave ++ ) {
127
+ const wave = this . waves [ iWave ] ;
128
+ wave . progress += WAVE_PROGRESS_SPEED * dt ;
129
+ if ( wave . progress > 1 ) {
130
+ wave . progress = 0 ;
131
+ wave . xBias = rangedRandom ( WAVE_BIAS_MIN , WAVE_BIAS_MAX ) ;
132
+ }
133
+ }
134
+ }
135
+
136
+ updateWaveBreaking ( dt : number ) {
137
+ // spawn new wave particles.
138
+ for ( let iWave = 0 ; iWave < this . waves . length ; iWave ++ ) {
139
+ const wave = this . waves [ iWave ] ;
140
+ if ( wave . progress < WAVE_BREAKING_START_PROGRESS ) {
141
+ continue ;
142
+ }
143
+
144
+ for ( let iSample = 0 ; iSample < WAVE_PARTICLE_SAMPLING_COUNT ; iSample ++ ) {
145
+ const sampleX = rangedRandom ( 0 , 1 ) ;
146
+ const biasedProgress = wave . progress - sampleX * wave . xBias ;
147
+ if ( biasedProgress < 0 || biasedProgress > 1 ) {
148
+ continue ;
149
+ }
150
+ const waveZ = ( 1 - biasedProgress ) * - WAVE_DISTANCE_DEPTH ;
151
+ // time to spawn!
152
+ const newWaveParticle = {
153
+ position : vec3 . fromValues ( ( sampleX - 0.5 ) * WAVE_WIDTH , 0 , waveZ ) ,
154
+ rotation : vec3 . fromValues ( rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) ) ,
155
+ age : 0 + rangedRandom ( 0 , WAVE_PARTICLE_AGE_RANDOM ) ,
156
+ } ;
157
+ this . waveParticles . push ( newWaveParticle ) ;
158
+ }
159
+ }
160
+
161
+ // update wave particles.
162
+ for ( let iParticle = 0 ; iParticle < this . waveParticles . length ; iParticle ++ ) {
163
+ const particle = this . waveParticles [ iParticle ] ;
164
+ // rotate
165
+ const rotationAmount = WAVE_PARTICLE_ROTATION_SPEED * dt ;
166
+ particle . rotation [ 0 ] += rotationAmount ;
167
+ particle . rotation [ 1 ] += rotationAmount ;
168
+ particle . rotation [ 2 ] += rotationAmount ;
169
+
170
+ particle . age += dt * WAVE_PARTICLE_AGING_SPEED ;
171
+
172
+ if ( particle . age > 1 ) {
173
+ this . waveParticles . splice ( iParticle , 1 ) ;
174
+ iParticle -- ;
175
+ }
176
+ }
177
+ }
178
+
179
+ static equilateralTriangleVertices ( centerX : number , centerY : number , height : number ) : vec2 [ ] {
180
+ // Calculate the distance from the center to each vertex (the radius of the circumcircle)
181
+ const radius : number = height * Math . sqrt ( 3 ) / 3 ;
182
+
183
+ // Calculate the coordinates of the vertices
184
+ const vertex1X : number = centerX ;
185
+ const vertex1Y : number = centerY + radius ;
186
+
187
+ const vertex2X : number = centerX + ( radius * Math . cos ( 120 * Math . PI / 180 ) ) ;
188
+ const vertex2Y : number = centerY - ( radius * Math . sin ( 120 * Math . PI / 180 ) ) ;
189
+
190
+ const vertex3X : number = centerX + ( radius * Math . cos ( 240 * Math . PI / 180 ) ) ;
191
+ const vertex3Y : number = centerY - ( radius * Math . sin ( 240 * Math . PI / 180 ) ) ;
192
+
193
+ // Return the coordinates of the vertices
194
+ return [ vec2 . fromValues ( vertex1X , vertex1Y ) , vec2 . fromValues ( vertex2X , vertex2Y ) , vec2 . fromValues ( vertex3X , vertex3Y ) ] ;
195
+ }
196
+
197
+ updateVertexBuffer ( ) {
198
+ const heightOfTriangle = 1 ;
199
+ const trianglePoints = WaveBreakingRenderer . equilateralTriangleVertices ( 0 , 0 , heightOfTriangle ) ;
200
+
201
+ const particleVertices = [
202
+ vec3 . fromValues ( trianglePoints [ 0 ] [ 0 ] , trianglePoints [ 0 ] [ 1 ] , 0 ) ,
203
+ vec3 . fromValues ( trianglePoints [ 1 ] [ 0 ] , trianglePoints [ 1 ] [ 1 ] , 0 ) ,
204
+ vec3 . fromValues ( trianglePoints [ 2 ] [ 0 ] , trianglePoints [ 2 ] [ 1 ] , 0 ) ,
205
+ ] ;
206
+
207
+ const vertexStride = 3 ; // one vertex stride
208
+ const waveParticleStride = vertexStride * particleVertices . length * 2 ; // line primitive stride
209
+ const gl = this . context ;
210
+ const vertexBuffer = this . vertexBuffer ;
211
+ gl . bindBuffer ( gl . ARRAY_BUFFER , vertexBuffer ) ;
212
+
213
+ const vertexData = new Float32Array ( this . waveParticles . length * waveParticleStride ) ;
214
+ this . waveParticles . forEach ( ( particle , index ) => {
215
+ // scale the particle.
216
+ const scaledVertices = particleVertices . map ( particleVertex => {
217
+ const scaledVertex = vec3 . create ( ) ;
218
+ vec3 . scale ( scaledVertex , particleVertex , WAVEPARTICLE_SCALING ) ;
219
+ return scaledVertex ;
220
+ } ) ;
221
+ // rotate the particle.
222
+ const rotation = particle . rotation ;
223
+ const rotatedVertices = scaledVertices . map ( particleVertex => {
224
+ const rotatedVertex = vec3 . create ( ) ;
225
+ vec3 . rotateX ( rotatedVertex , particleVertex , [ 0 , 0 , 0 ] , rotation [ 0 ] ) ;
226
+ vec3 . rotateY ( rotatedVertex , rotatedVertex , [ 0 , 0 , 0 ] , rotation [ 1 ] ) ;
227
+ vec3 . rotateZ ( rotatedVertex , rotatedVertex , [ 0 , 0 , 0 ] , rotation [ 2 ] ) ;
228
+ return rotatedVertex ;
229
+ } ) ;
230
+ // translate the particle.
231
+ for ( let i = 0 ; i < rotatedVertices . length ; i ++ ) {
232
+ const rotatedVertex = rotatedVertices [ i ] ;
233
+ rotatedVertex [ 0 ] = rotatedVertex [ 0 ] + particle . position [ 0 ] ;
234
+ rotatedVertex [ 1 ] = rotatedVertex [ 1 ] + particle . position [ 1 ] ;
235
+ rotatedVertex [ 2 ] = rotatedVertex [ 2 ] + particle . position [ 2 ] ;
236
+ }
237
+
238
+ // add the leaf to the vertex buffer (lines)
239
+ for ( let i = 0 ; i < rotatedVertices . length ; i ++ ) {
240
+ const vertex0 = rotatedVertices [ i ] ;
241
+ const vertex1 = rotatedVertices [ ( i + 1 ) % rotatedVertices . length ] ;
242
+
243
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 0 ] = vertex0 [ 0 ] ;
244
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 1 ] = vertex0 [ 1 ] ;
245
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 2 ] = vertex0 [ 2 ] ;
246
+
247
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 3 ] = vertex1 [ 0 ] ;
248
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 4 ] = vertex1 [ 1 ] ;
249
+ vertexData [ index * waveParticleStride + i * vertexStride * 2 + 5 ] = vertex1 [ 2 ] ;
250
+ }
251
+
252
+ } ) ;
253
+
254
+ this . lastVertexCount = vertexData . length / vertexStride ;
255
+
256
+ gl . bufferData ( gl . ARRAY_BUFFER , vertexData , gl . STATIC_DRAW ) ;
257
+ }
258
+
259
+ getViewMatrix ( ) {
260
+ // make look at view matrix. (cameraDistance, cameraPitchRadians, cameraTargetPosition)
261
+ const cameraPosition = vec3 . fromValues (
262
+ 0 ,
263
+ 1 ,
264
+ 3 ,
265
+ ) ;
266
+ const cameraTargetPosition = vec3 . fromValues ( 0 , 0 , 0 ) ;
267
+ let viewMatrix = mat4 . create ( ) ;
268
+ mat4 . lookAt ( viewMatrix , cameraPosition , cameraTargetPosition , [ 0 , 1 , 0 ] ) ;
269
+ return viewMatrix ;
270
+ }
271
+
272
+ getProjectionMatrix ( ) {
273
+ // perspective projection.
274
+ const fovy = 60.0 / 180.0 * Math . PI ;
275
+ const aspect = this . canvas . clientWidth / this . canvas . clientHeight ;
276
+ const near = 0.1 ;
277
+ const far = 100 ;
278
+
279
+ const perspectiveMatrix = mat4 . create ( ) ;
280
+ mat4 . perspective ( perspectiveMatrix , fovy , aspect , near , far ) ;
281
+
282
+ return perspectiveMatrix ;
283
+ }
284
+
285
+ render ( ) {
286
+ this . updateVertexBuffer ( ) ;
287
+
288
+ // clear white.
289
+ const gl = this . context ;
290
+ gl . clearColor ( 0 , 0 , 0 , 1 ) ;
291
+ gl . clear ( this . context . COLOR_BUFFER_BIT | this . context . DEPTH_BUFFER_BIT ) ;
292
+
293
+ // alpha blend
294
+ gl . enable ( gl . BLEND ) ;
295
+ gl . blendFunc ( gl . SRC_ALPHA , gl . ONE_MINUS_SRC_ALPHA ) ;
296
+
297
+ // update vertex buffer.
298
+ const program = this . shaderProgram ;
299
+ gl . useProgram ( program ) ;
300
+
301
+ const viewMatrixLocation = gl . getUniformLocation ( program , 'viewMatrix' ) ;
302
+ gl . uniformMatrix4fv ( viewMatrixLocation , false , this . getViewMatrix ( ) ) ;
303
+
304
+ const projectionMatrixLocation = gl . getUniformLocation ( program , 'projectionMatrix' ) ;
305
+ gl . uniformMatrix4fv ( projectionMatrixLocation , false , this . getProjectionMatrix ( ) ) ;
306
+
307
+ // Set up attribute pointers
308
+ const vertexStride = 3 * Float32Array . BYTES_PER_ELEMENT ;
309
+ const positionAttributeLocation = gl . getAttribLocation ( program , 'position' ) ;
310
+ gl . enableVertexAttribArray ( positionAttributeLocation ) ;
311
+ gl . vertexAttribPointer ( positionAttributeLocation , 3 , gl . FLOAT , false , vertexStride , 0 ) ;
312
+
313
+ // Draw lines
314
+ gl . drawArrays ( gl . LINES , 0 , this . lastVertexCount ) ;
315
+ }
316
+ }
0 commit comments