Skip to content

Commit 64da282

Browse files
committed
separated webgl tools into a reusable framework.
1 parent e60544a commit 64da282

File tree

11 files changed

+323
-275
lines changed

11 files changed

+323
-275
lines changed

.eslintrc.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"env": {
3+
"browser": true,
4+
"es6": true
5+
},
6+
"extends": "eslint:recommended",
7+
"parserOptions": {
8+
"ecmaVersion": 2018,
9+
"sourceType": "module"
10+
}
11+
}

js/gl.js renamed to js/lib/gl.js

Lines changed: 36 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"use strict";
1+
"use strict"
22

33
import { FragmentShader, VertexShader, ShaderProgram } from './shader.js';
44
import { QuadPrimitive } from './quad-primitive.js';
@@ -21,120 +21,14 @@ export class GL {
2121
this.gl = canvas.getContext("webgl2");
2222
this.gl.get_super = () => this;
2323

24+
this.render_hooks = [];
25+
2426
this.objects = [];
2527

2628
this.projection_matrix = Matrix.identity();
2729
this.view_matrix = Matrix.identity();
2830

2931
this.camera_position = new Vector(0, 0, 0, 1);
30-
this.mouse_position = new Vector(0, 0, 0, 1);
31-
32-
this.zoom_target = new Vector(0, 0, 0, 1);
33-
this.zoom_speed = 0;
34-
this.zoom_level = 1;
35-
36-
this.desired_julia = 0;
37-
this.current_julia = -1;
38-
39-
this.color_cycle = 0;
40-
41-
this.extreme_mode = false;
42-
43-
this.drag_active = false;
44-
this.drag_point = new Vector(0, 0, 0, 1);
45-
46-
// Add resize listener
47-
window.addEventListener('resize', () => {
48-
this.resize();
49-
});
50-
51-
// Add keyboard event listener
52-
window.addEventListener('keypress', (event) => {
53-
switch (event.key) {
54-
case 'z':
55-
this.desired_julia = (this.desired_julia + 1) % 10;
56-
event.preventDefault();
57-
break;
58-
case 'x':
59-
this.extreme_mode = !this.extreme_mode;
60-
event.preventDefault();
61-
break;
62-
case 'c':
63-
this.zoom_target = this.mouse_position;
64-
if (this.zoom_speed > 0) {
65-
this.zoom_speed += 1;
66-
} else {
67-
this.zoom_speed = 1;
68-
}
69-
event.preventDefault();
70-
break;
71-
case 'v':
72-
this.zoom_target = this.mouse_position;
73-
if (this.zoom_speed > 0) {
74-
this.zoom_speed = -1;
75-
} else {
76-
this.zoom_speed -= 1;
77-
}
78-
event.preventDefault();
79-
default:
80-
break;
81-
}
82-
});
83-
84-
// Add click listeners
85-
canvas.addEventListener('mousedown', (event) => {
86-
this.drag_active = true;
87-
88-
this.drag_point = this.view_matrix.inverse().multiply_vector(
89-
this.unproject(
90-
event.layerX,
91-
this.canvas.clientHeight - event.layerY,
92-
0
93-
)
94-
);
95-
});
96-
canvas.addEventListener('mouseup', (event) => {
97-
this.drag_active = false;
98-
});
99-
canvas.addEventListener('mousemove', (event) => {
100-
// Unproject mouse coords into scene coords.
101-
this.mouse_position = this.view_matrix.inverse().multiply_vector(
102-
this.unproject(event.layerX, this.canvas.clientHeight - event.layerY, 0.5)
103-
);
104-
105-
// Only move if the mouse is down.
106-
if (!this.drag_active) {
107-
return;
108-
}
109-
110-
// Stop any zooming going on.
111-
this.zoom_speed = 0;
112-
113-
// Set the camera position to: current pos - mouse pos + initial click pos
114-
this.set_camera_position(
115-
this.camera_position.x - this.mouse_position.x + this.drag_point.x,
116-
this.camera_position.y - this.mouse_position.y + this.drag_point.y,
117-
this.camera_position.z
118-
);
119-
});
120-
121-
// Add mouse wheel listener
122-
canvas.addEventListener('wheel', (event) => {
123-
this.zoom_target = this.mouse_position;
124-
if (event.deltaY > 0) {
125-
if (this.zoom_speed > 0) {
126-
this.zoom_speed += 1;
127-
} else {
128-
this.zoom_speed = 1;
129-
}
130-
} else {
131-
if (this.zoom_speed > 0) {
132-
this.zoom_speed = -1;
133-
} else {
134-
this.zoom_speed -= 1;
135-
}
136-
}
137-
});
13832
}
13933

14034
/**
@@ -144,6 +38,14 @@ export class GL {
14438
return this.gl;
14539
}
14640

41+
/**
42+
* Add a callable hook to be run on each render pass.
43+
*
44+
* @param {callable} hook
45+
*/
46+
add_render_hook(hook) {
47+
this.render_hooks.push(hook);
48+
}
14749

14850
/**
14951
* Create a vertex shader from the given source.
@@ -247,14 +149,31 @@ export class GL {
247149
* @param {float} z
248150
*/
249151
set_camera_position(x, y, z) {
250-
// Stay within the bounds of the quad.
251-
if (x < -2 || x > 1 || y < -1 || y > 1) {
252-
return;
253-
}
254152
this.camera_position = new Vector(x, y, z);
255153
this.view_matrix = Matrix.identity().translate(-x, -y, -z);
256154
}
257155

156+
/**
157+
* Translates the position of the camera.
158+
*
159+
* @param {float} x
160+
* @param {float} y
161+
* @param {float} z
162+
*/
163+
translate_camera_position(x, y, z) {
164+
this.camera_position = new Vector(
165+
this.camera_position.x + x,
166+
this.camera_position.y + y,
167+
this.camera_position.z + z,
168+
0
169+
);
170+
this.view_matrix = Matrix.identity().translate(
171+
-this.camera_position.x,
172+
-this.camera_position.y,
173+
-this.camera_position.z
174+
);
175+
}
176+
258177
/**
259178
* Computes world coordinates from given screen coordinates.
260179
*
@@ -352,125 +271,6 @@ export class GL {
352271

353272
this.objects.forEach((object) => object.render(Matrix.identity()));
354273
}
355-
356-
/**
357-
* Perform zoom calculations.
358-
*
359-
* @param {integer} frame_delta
360-
*/
361-
zoom(frame_delta) {
362-
if (this.zoom_speed === 0) {
363-
return false;
364-
}
365-
366-
// Find the vector from the current camera position to the zoom target.
367-
// Scale by the frame_delta so that the movement would occur in 1 / zoom_speed seconds.
368-
const movement_vector = new Vector(
369-
this.zoom_target.x - this.camera_position.x,
370-
this.zoom_target.y - this.camera_position.y,
371-
this.zoom_target.z - this.camera_position.z,
372-
-this.camera_position.w,
373-
).multiply(frame_delta * Math.abs(this.zoom_speed) / 1000);
374-
375-
// Update the camera position along the movement vector.
376-
this.set_camera_position(
377-
this.camera_position.x + movement_vector.x,
378-
this.camera_position.y + movement_vector.y,
379-
this.camera_position.z + movement_vector.z,
380-
);
381-
382-
// Scale the projection matrix for the zoom effect.
383-
if ((this.zoom_level < 30000 && this.zoom_speed < 0)
384-
|| (this.zoom_level > 1 && this.zoom_speed > 0)) {
385-
const scale_factor = 1 + (frame_delta * -this.zoom_speed / 1000);
386-
this.projection_matrix = this.projection_matrix.scale(
387-
scale_factor,
388-
scale_factor,
389-
1
390-
);
391-
392-
this.zoom_level *= scale_factor;
393-
}
394-
395-
// Reduce the zoom speed over time.
396-
this.zoom_speed *= 1 - (frame_delta / 1000);
397-
if (Math.abs(this.zoom_speed) < 0.005) {
398-
this.zoom_speed = 0;
399-
}
400-
401-
return true;
402-
}
403-
404-
/**
405-
* Cycle the color rotation float.
406-
*
407-
* @param {integer} frame_delta
408-
*/
409-
cycle(frame_delta) {
410-
let speed = 200;
411-
if (this.extreme_mode) {
412-
speed = 10;
413-
}
414-
this.color_cycle = (this.color_cycle + frame_delta / speed) % 1024.0;
415-
416-
this.get_shader_program().set_uniform_float(
417-
'continuous_cycle',
418-
this.color_cycle
419-
);
420-
421-
return true;
422-
}
423-
424-
/**
425-
* Switch the rendered set.
426-
*/
427-
switch() {
428-
if (this.current_julia === this.desired_julia) {
429-
return false;
430-
}
431-
432-
this.current_julia = this.desired_julia;
433-
434-
let c;
435-
switch (this.current_julia) {
436-
case 0:
437-
c = new Vector(0, 0, 0, 0);
438-
break;
439-
case 1:
440-
c = new Vector(-0.4, 0.6, 0, 0);
441-
break;
442-
case 2:
443-
c = new Vector(0.285, 0, 0, 0);
444-
break;
445-
case 3:
446-
c = new Vector(0.285, 0.01, 0, 0);
447-
break;
448-
case 4:
449-
c = new Vector(0.45, 0.1428, 0, 0);
450-
break;
451-
case 5:
452-
c = new Vector(-0.70176, -0.3842, 0, 0);
453-
break;
454-
case 6:
455-
c = new Vector(-0.835, -0.2321, 0, 0);
456-
break;
457-
case 7:
458-
c = new Vector(-0.8, 0.156, 0, 0);
459-
break;
460-
case 8:
461-
c = new Vector(-0.7269, 0.1889, 0, 0);
462-
break;
463-
case 9:
464-
default:
465-
c = new Vector(0, -0.8, 0, 0);
466-
break;
467-
}
468-
469-
this.get_shader_program().set_uniform_vec2('julia_constant', c);
470-
471-
return true;
472-
}
473-
474274
/**
475275
* The event loop executed for each tick.
476276
*/
@@ -487,9 +287,10 @@ export class GL {
487287
let scene_dirty = false;
488288

489289
scene_dirty |= this.resize();
490-
scene_dirty |= this.zoom(frame_delta);
491-
scene_dirty |= this.cycle(frame_delta);
492-
scene_dirty |= this.switch();
290+
291+
this.render_hooks.forEach((hook) => {
292+
scene_dirty |= hook(frame_delta);
293+
});
493294

494295
if (scene_dirty) {
495296
this.render();

js/matrix.js renamed to js/lib/matrix.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,17 +277,6 @@ export class Matrix {
277277
])
278278
}
279279

280-
/**
281-
* Prints the matrix to the debug console.
282-
*/
283-
debug() {
284-
console.log(this.r1);
285-
console.log(this.r2);
286-
console.log(this.r3);
287-
console.log(this.r4);
288-
console.log('---------------');
289-
}
290-
291280
/**
292281
* Return the identity matrix.
293282
*
File renamed without changes.
File renamed without changes.

js/quad.js renamed to js/lib/quad.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use strict";
22

33
import { Object } from './object.js';
4-
import { Matrix } from './matrix.js';
54

65
/**
76
* Represents a renderable quad.
File renamed without changes.
File renamed without changes.

js/main.js

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,5 @@
11
"use strict";
22

3-
import { GL } from './gl.js';
3+
import { Mandelbrot } from './mandelbrot.js';
44

5-
/**
6-
* Main entrypoint function that creates resources and performs a render onto the given canvas.
7-
*/
8-
async function mandelbrot(canvas) {
9-
const gl = new GL(canvas);
10-
11-
const vertex_shader_source = await fetch('shaders/vertex.glsl').then((res) => res.text());
12-
const fragment_shader_source = await fetch('shaders/fragment.glsl').then((res) => res.text());
13-
14-
const fragment_shader = gl.create_fragment_shader(fragment_shader_source);
15-
const vertex_shader = gl.create_vertex_shader(vertex_shader_source);
16-
const shader_program = gl.create_shader_program(vertex_shader, fragment_shader);
17-
18-
gl.set_shader_program(shader_program);
19-
20-
const quad = gl.create_quad(-5, -3, 10, 6);
21-
const mesh = gl.create_mesh([quad]);
22-
gl.add_object_to_scene(mesh);
23-
24-
gl.set_camera_position(-0.5, 0, 0);
25-
26-
gl.event_loop();
27-
}
28-
29-
// Start main entrypoint.
30-
const canvas = document.getElementById("c");
31-
mandelbrot(canvas);
5+
new Mandelbrot(document.getElementById("c"));

0 commit comments

Comments
 (0)