1
+ import { RendererInterface } from './renderer' ;
2
+ import { clampValue , createShader , degreesToRadians , rangedRandom } from './utility' ;
3
+ import { mat4 , vec3 } from 'gl-matrix' ;
4
+
5
+ interface FlowerLeaf {
6
+ position : vec3 ;
7
+ rotation : vec3 ;
8
+ fallingSpeedRatio : number ;
9
+ }
10
+
11
+ const FLOWER_LEAF_COUNT = 1000 ;
12
+ const FLOWER_LEAF_RANGE = 20 ;
13
+ const FLOWER_LEAF_FALLING_HEIGHT = 20 ;
14
+ const FLOWER_FALLING_SPEED = 5.1 ;
15
+ const FLOWER_SCALING = 0.1 ;
16
+ const WIND_VECTOR = vec3 . fromValues ( - 3.5 , 0 , 2.85 ) ;
17
+
18
+ export class FlowerFallingRenderer implements RendererInterface {
19
+ canvas : HTMLCanvasElement ;
20
+ context : WebGLRenderingContext ;
21
+
22
+ // webgl default is x(right), y(up), z(out of screen = towards the viewer) = right-handed coordinate system.
23
+ cameraTargetPosition : vec3 = vec3 . fromValues ( 0 , 9 , 0 ) ;
24
+ cameraDistance : number = 10 ;
25
+ cameraPitchRadians : number = degreesToRadians ( - 15 ) ;
26
+ flowerLeaves : FlowerLeaf [ ] = [ ] ;
27
+ groundY : number = 0 ;
28
+
29
+ shaderProgram : WebGLProgram ;
30
+ vertexBuffer : WebGLBuffer ;
31
+ lastVertexCount : number = 0 ;
32
+
33
+ alpha : number = 0.0 ;
34
+
35
+ constructor ( canvasElement : HTMLCanvasElement ) {
36
+ this . canvas = canvasElement ;
37
+ this . canvas . width = this . canvas . clientWidth ;
38
+ this . canvas . height = this . canvas . clientHeight ;
39
+ this . context = this . canvas . getContext ( 'webgl' ) ! ;
40
+ this . context . viewport ( 0 , 0 , this . canvas . width , this . canvas . height ) ;
41
+ this . shaderProgram = this . context . createProgram ( ) ! ;
42
+ this . vertexBuffer = this . context . createBuffer ( ) ! ;
43
+ }
44
+
45
+ stop ( ) {
46
+ }
47
+
48
+ start ( ) {
49
+ this . initiateRaindrops ( ) ;
50
+
51
+ // Vertex shader source code
52
+ const vertexShaderSource = `
53
+ attribute vec3 position;
54
+ uniform mat4 viewMatrix;
55
+ uniform mat4 projectionMatrix;
56
+ void main() {
57
+ gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);
58
+ }
59
+ ` ;
60
+
61
+ // Fragment shader source code
62
+ const fragmentShaderSource = `
63
+ precision mediump float;
64
+ void main() {
65
+ gl_FragColor = vec4(1, 0.75, 0.79, 1);
66
+ }
67
+ ` ;
68
+
69
+ const gl = this . context ;
70
+
71
+ // Create shader program
72
+ const program = gl . createProgram ( ) ! ;
73
+ const vertexShader = createShader ( gl , gl . VERTEX_SHADER , vertexShaderSource ) ;
74
+ const fragmentShader = createShader ( gl , gl . FRAGMENT_SHADER , fragmentShaderSource ) ;
75
+ gl . attachShader ( program , vertexShader ) ;
76
+ gl . attachShader ( program , fragmentShader ) ;
77
+ gl . linkProgram ( program ) ;
78
+
79
+ // Check for linking errors
80
+ if ( ! gl . getProgramParameter ( program , gl . LINK_STATUS ) ) {
81
+ const error = gl . getProgramInfoLog ( program ) ;
82
+ console . error ( 'Failed to link program:' , error ) ;
83
+ gl . deleteProgram ( program ) ;
84
+ return null ;
85
+ }
86
+
87
+ gl . useProgram ( program ) ;
88
+
89
+ this . shaderProgram = program ;
90
+ }
91
+
92
+ initiateRaindrops ( ) {
93
+ this . alpha = 0.0 ;
94
+ this . flowerLeaves = [ ] ;
95
+
96
+ for ( let iMake = 0 ; iMake < FLOWER_LEAF_COUNT ; iMake ++ ) {
97
+ const flowerLeaf = {
98
+ position : vec3 . fromValues (
99
+ rangedRandom ( - 1 , 1 ) * FLOWER_LEAF_RANGE ,
100
+ rangedRandom ( 0.1 , 1.5 ) * FLOWER_LEAF_FALLING_HEIGHT ,
101
+ rangedRandom ( - 1 , 1 ) * FLOWER_LEAF_RANGE
102
+ ) ,
103
+ rotation : vec3 . fromValues ( rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) ) ,
104
+ fallingSpeedRatio : rangedRandom ( 0.5 , 1.2 ) ,
105
+ } ;
106
+ this . flowerLeaves . push ( flowerLeaf ) ;
107
+ }
108
+ }
109
+
110
+ update ( dt : number ) {
111
+ this . updateFlowerFalling ( dt ) ;
112
+ }
113
+
114
+ updateFlowerFalling ( dt : number ) {
115
+ const dropHeight = FLOWER_FALLING_SPEED * dt ;
116
+
117
+ this . flowerLeaves . forEach ( leaf => {
118
+ let position = leaf . position ;
119
+
120
+ position [ 1 ] -= dropHeight * leaf . fallingSpeedRatio ;
121
+
122
+ // apply wind.
123
+ position [ 0 ] += WIND_VECTOR [ 0 ] * dt ;
124
+ position [ 1 ] += WIND_VECTOR [ 1 ] * dt ;
125
+ position [ 2 ] += WIND_VECTOR [ 2 ] * dt ;
126
+
127
+ //
128
+ if ( position [ 1 ] < this . groundY ) {
129
+ // reset the leaf.
130
+ position [ 0 ] = rangedRandom ( - 1 , 1 ) * FLOWER_LEAF_RANGE ;
131
+ position [ 1 ] = rangedRandom ( 0.9 , 1.5 ) * FLOWER_LEAF_FALLING_HEIGHT ;
132
+ position [ 2 ] = rangedRandom ( - 1 , 1 ) * FLOWER_LEAF_RANGE ;
133
+ leaf . fallingSpeedRatio = rangedRandom ( 0.5 , 1.2 ) ;
134
+ leaf . rotation = vec3 . fromValues ( rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) , rangedRandom ( 0 , 2 * Math . PI ) ) ;
135
+ }
136
+
137
+ // rotate the leaf along the speed.
138
+ const rotationSpeed = leaf . fallingSpeedRatio * dt ;
139
+ leaf . rotation [ 0 ] += rotationSpeed ;
140
+ leaf . rotation [ 1 ] += rotationSpeed ;
141
+ leaf . rotation [ 2 ] += rotationSpeed ;
142
+ } ) ;
143
+ }
144
+
145
+ updateVertexBuffer ( ) {
146
+ const leafVertices = [
147
+ vec3 . fromValues ( - 0.25 , 0.5 , 0 ) ,
148
+ vec3 . fromValues ( 0.0 , 0.45 , 0 ) ,
149
+ vec3 . fromValues ( 0.25 , 0.5 , 0 ) ,
150
+ vec3 . fromValues ( 0.25 , 0 , 0 ) ,
151
+ vec3 . fromValues ( 0 , - 0.2 , 0 ) ,
152
+ vec3 . fromValues ( - 0.25 , 0 , 0 ) ,
153
+ ] ;
154
+
155
+ const vertexStride = 3 ; // lines from 2 vertices.
156
+ const leafStride = vertexStride * leafVertices . length * 2 ;
157
+ const gl = this . context ;
158
+ const vertexBuffer = this . vertexBuffer ;
159
+ gl . bindBuffer ( gl . ARRAY_BUFFER , vertexBuffer ) ;
160
+
161
+ const vertexData = new Float32Array ( this . flowerLeaves . length * leafStride ) ;
162
+ this . flowerLeaves . forEach ( ( leaf , index ) => {
163
+ // scale the leaf.
164
+ const scaledVertices = leafVertices . map ( leafVertex => {
165
+ const scaledVertex = vec3 . create ( ) ;
166
+ vec3 . scale ( scaledVertex , leafVertex , FLOWER_SCALING ) ;
167
+ return scaledVertex ;
168
+ } ) ;
169
+ // rotate the leaf.
170
+ const rotation = leaf . rotation ;
171
+ const rotatedVertices = scaledVertices . map ( leafVertex => {
172
+ const rotatedVertex = vec3 . create ( ) ;
173
+ vec3 . rotateX ( rotatedVertex , leafVertex , [ 0 , 0 , 0 ] , rotation [ 0 ] ) ;
174
+ vec3 . rotateY ( rotatedVertex , rotatedVertex , [ 0 , 0 , 0 ] , rotation [ 1 ] ) ;
175
+ vec3 . rotateZ ( rotatedVertex , rotatedVertex , [ 0 , 0 , 0 ] , rotation [ 2 ] ) ;
176
+ return rotatedVertex ;
177
+ } ) ;
178
+ // translate the leaf.
179
+ for ( let i = 0 ; i < rotatedVertices . length ; i ++ ) {
180
+ const rotatedVertex = rotatedVertices [ i ] ;
181
+ rotatedVertex [ 0 ] = rotatedVertex [ 0 ] + leaf . position [ 0 ] ;
182
+ rotatedVertex [ 1 ] = rotatedVertex [ 1 ] + leaf . position [ 1 ] ;
183
+ rotatedVertex [ 2 ] = rotatedVertex [ 2 ] + leaf . position [ 2 ] ;
184
+ }
185
+
186
+ // add the leaf to the vertex buffer (lines)
187
+ for ( let i = 0 ; i < rotatedVertices . length ; i ++ ) {
188
+ const vertex0 = rotatedVertices [ i ] ;
189
+ const vertex1 = rotatedVertices [ ( i + 1 ) % rotatedVertices . length ] ;
190
+
191
+ vertexData [ index * leafStride + i * vertexStride * 2 + 0 ] = vertex0 [ 0 ] ;
192
+ vertexData [ index * leafStride + i * vertexStride * 2 + 1 ] = vertex0 [ 1 ] ;
193
+ vertexData [ index * leafStride + i * vertexStride * 2 + 2 ] = vertex0 [ 2 ] ;
194
+
195
+ vertexData [ index * leafStride + i * vertexStride * 2 + 3 ] = vertex1 [ 0 ] ;
196
+ vertexData [ index * leafStride + i * vertexStride * 2 + 4 ] = vertex1 [ 1 ] ;
197
+ vertexData [ index * leafStride + i * vertexStride * 2 + 5 ] = vertex1 [ 2 ] ;
198
+ }
199
+
200
+ } ) ;
201
+
202
+ this . lastVertexCount = vertexData . length / vertexStride ;
203
+
204
+ gl . bufferData ( gl . ARRAY_BUFFER , vertexData , gl . STATIC_DRAW ) ;
205
+ }
206
+
207
+ getViewMatrix ( ) {
208
+ // make look at view matrix. (cameraDistance, cameraPitchRadians, cameraTargetPosition)
209
+ const cameraPosition = vec3 . fromValues (
210
+ 0 ,
211
+ this . cameraTargetPosition [ 1 ] + this . cameraDistance * Math . sin ( this . cameraPitchRadians ) ,
212
+ this . cameraTargetPosition [ 2 ] + this . cameraDistance * Math . cos ( this . cameraPitchRadians ) ,
213
+ ) ;
214
+ let viewMatrix = mat4 . create ( ) ;
215
+ mat4 . lookAt ( viewMatrix , cameraPosition , this . cameraTargetPosition , [ 0 , 1 , 0 ] ) ;
216
+ return viewMatrix ;
217
+ }
218
+
219
+ getProjectionMatrix ( ) {
220
+ // perspective projection.
221
+ const fovy = 60.0 / 180.0 * Math . PI ;
222
+ const aspect = this . canvas . clientWidth / this . canvas . clientHeight ;
223
+ const near = 0.1 ;
224
+ const far = 100 ;
225
+
226
+ const perspectiveMatrix = mat4 . create ( ) ;
227
+ mat4 . perspective ( perspectiveMatrix , fovy , aspect , near , far ) ;
228
+
229
+ return perspectiveMatrix ;
230
+ }
231
+
232
+ render ( ) {
233
+ this . updateVertexBuffer ( ) ;
234
+
235
+ // clear white.
236
+ const gl = this . context ;
237
+ gl . clearColor ( 1 , 1 , 1 , 1 ) ;
238
+ gl . clear ( this . context . COLOR_BUFFER_BIT | this . context . DEPTH_BUFFER_BIT ) ;
239
+
240
+ // alpha blend
241
+ gl . enable ( gl . BLEND ) ;
242
+ gl . blendFunc ( gl . SRC_ALPHA , gl . ONE_MINUS_SRC_ALPHA ) ;
243
+
244
+ // update vertex buffer.
245
+ const program = this . shaderProgram ;
246
+ gl . useProgram ( program ) ;
247
+
248
+ const viewMatrixLocation = gl . getUniformLocation ( program , 'viewMatrix' ) ;
249
+ gl . uniformMatrix4fv ( viewMatrixLocation , false , this . getViewMatrix ( ) ) ;
250
+
251
+ const projectionMatrixLocation = gl . getUniformLocation ( program , 'projectionMatrix' ) ;
252
+ gl . uniformMatrix4fv ( projectionMatrixLocation , false , this . getProjectionMatrix ( ) ) ;
253
+
254
+ // Set up attribute pointers
255
+ const vertexStride = 3 * Float32Array . BYTES_PER_ELEMENT ;
256
+ const positionAttributeLocation = gl . getAttribLocation ( program , 'position' ) ;
257
+ gl . enableVertexAttribArray ( positionAttributeLocation ) ;
258
+ gl . vertexAttribPointer ( positionAttributeLocation , 3 , gl . FLOAT , false , vertexStride , 0 ) ;
259
+
260
+ // Draw lines
261
+ gl . drawArrays ( gl . LINES , 0 , this . lastVertexCount ) ;
262
+ }
263
+ }
0 commit comments