Skip to content

Commit 8fc068e

Browse files
committed
부서지는 파도 렌더러 껍데기만. 아직은 표현법 연구중.
1 parent 8251128 commit 8fc068e

File tree

3 files changed

+327
-1
lines changed

3 files changed

+327
-1
lines changed

index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
</div>
1414
<div id="bottom_menu">
1515
<button id="space_button" class="menu_button">Space</button>
16-
<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>
16+
<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" alt="about me button"/></a></button>
1717
<button id="rain_button" class="menu_button">Rain</button>
1818
<button id="flower_falling_button" class="menu_button">Flower Falling</button>
19+
<!--<button id="wave_breaking_button" class="menu_button">Wave Breaking</button>-->
1920
</div>
2021
<script type="module" src="/src/main.ts"></script>
2122
</body>

src/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RendererInterface } from './renderer';
44
import { RainRenderer } from './rain_renderer';
55
import { FlowerFallingRenderer } from './flower_falling_renderer';
66
import { pickWeightedRandom } from './utility';
7+
import { WaveBreakingRenderer } from './wave_break_renderer';
78

89
function hideBottomMenu() {
910
const bottomMenu = document.getElementById('bottom_menu') as HTMLDivElement;
@@ -60,6 +61,7 @@ function startApp() {
6061
rendererMap.set('space', new SpaceRenderer(mainCanvas));
6162
rendererMap.set('rain', new RainRenderer(mainCanvas));
6263
rendererMap.set('flower_falling', new FlowerFallingRenderer(mainCanvas));
64+
rendererMap.set('wave_breaking', new WaveBreakingRenderer(mainCanvas));
6365

6466
let currentRendererName = 'space';
6567
// random pick evenly.
@@ -70,6 +72,7 @@ function startApp() {
7072
const spaceButton = document.getElementById('space_button') as HTMLButtonElement;
7173
const rainButton = document.getElementById('rain_button') as HTMLButtonElement;
7274
const flowerFallingButton = document.getElementById('flower_falling_button') as HTMLButtonElement;
75+
const waveBreakingButton = document.getElementById('wave_breaking_button') as HTMLButtonElement;
7376

7477
spaceButton.addEventListener('click', () => {
7578
currentRendererName = 'space';
@@ -83,6 +86,12 @@ function startApp() {
8386
currentRendererName = 'flower_falling';
8487
});
8588

89+
if (waveBreakingButton != null) {
90+
waveBreakingButton.addEventListener('click', () => {
91+
currentRendererName = 'wave_breaking';
92+
});
93+
}
94+
8695
let prevTimeStamp = performance.now();
8796
let deltaTimeSec = 0.001;
8897

src/wave_break_renderer.ts

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

Comments
 (0)