Skip to content

Commit 503b987

Browse files
committed
Flower falling renderer added.
1 parent ed1ab3e commit 503b987

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<button id="space_button" class="menu_button">Space</button>
1616
<button id="github_button" class="menu_button"><a href="https://github.com/bugcaptor/bugcaptor.github.io">About Me<br/><img src="/github-mark.png" width="16"/></a></button>
1717
<button id="rain_button" class="menu_button">Rain</button>
18+
<button id="flower_falling_button" class="menu_button">Flower Falling</button>
1819
</div>
1920
<script type="module" src="/src/main.ts"></script>
2021
</body>

src/flower_falling_renderer.ts

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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+
}

src/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import './style.css';
22
import { SpaceRenderer } from './space_renderer';
33
import { RendererInterface } from './renderer';
44
import { RainRenderer } from './rain_renderer';
5+
import { FlowerFallingRenderer } from './flower_falling_renderer';
56
import { pickWeightedRandom } from './utility';
67

78
function hideBottomMenu() {
@@ -58,6 +59,7 @@ function startApp() {
5859
const rendererMap = new Map<string, RendererInterface>();
5960
rendererMap.set('space', new SpaceRenderer(mainCanvas));
6061
rendererMap.set('rain', new RainRenderer(mainCanvas));
62+
rendererMap.set('flower_falling', new FlowerFallingRenderer(mainCanvas));
6163

6264
let currentRendererName = 'space';
6365
// random pick evenly.
@@ -67,6 +69,7 @@ function startApp() {
6769

6870
const spaceButton = document.getElementById('space_button') as HTMLButtonElement;
6971
const rainButton = document.getElementById('rain_button') as HTMLButtonElement;
72+
const flowerFallingButton = document.getElementById('flower_falling_button') as HTMLButtonElement;
7073

7174
spaceButton.addEventListener('click', () => {
7275
currentRendererName = 'space';
@@ -76,6 +79,10 @@ function startApp() {
7679
currentRendererName = 'rain';
7780
});
7881

82+
flowerFallingButton.addEventListener('click', () => {
83+
currentRendererName = 'flower_falling';
84+
});
85+
7986
let prevTimeStamp = performance.now();
8087
let deltaTimeSec = 0.001;
8188

src/style.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ body {
2828
width: 100%;
2929
flex-direction: row;
3030
display: none;
31+
background-color: rgba(0.5, 0.5, 0.5, 0.5);
32+
3133
}
3234

3335
.menu_button {

0 commit comments

Comments
 (0)