@guinetik/gcanvas 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGLRenderer - Lightweight WebGL utility for gcanvas
|
|
3
|
+
*
|
|
4
|
+
* Provides WebGL rendering capabilities for shapes that need shader effects.
|
|
5
|
+
* Renders to an offscreen canvas that can be composited onto the main 2D canvas.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Shader compilation and caching
|
|
9
|
+
* - Uniform management
|
|
10
|
+
* - Offscreen rendering with compositing
|
|
11
|
+
* - Fallback detection for systems without WebGL
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const renderer = new WebGLRenderer(800, 600);
|
|
15
|
+
* renderer.useProgram('sphere', vertexShader, fragmentShader);
|
|
16
|
+
* renderer.setUniforms({ uTime: performance.now() / 1000 });
|
|
17
|
+
* renderer.render();
|
|
18
|
+
* renderer.compositeOnto(mainCtx, x, y);
|
|
19
|
+
*/
|
|
20
|
+
export class WebGLRenderer {
|
|
21
|
+
/**
|
|
22
|
+
* Create a WebGL renderer
|
|
23
|
+
* @param {number} width - Canvas width
|
|
24
|
+
* @param {number} height - Canvas height
|
|
25
|
+
*/
|
|
26
|
+
constructor(width, height) {
|
|
27
|
+
this.width = width;
|
|
28
|
+
this.height = height;
|
|
29
|
+
|
|
30
|
+
// Create offscreen canvas
|
|
31
|
+
this.canvas = document.createElement("canvas");
|
|
32
|
+
this.canvas.width = width;
|
|
33
|
+
this.canvas.height = height;
|
|
34
|
+
|
|
35
|
+
// Get WebGL context
|
|
36
|
+
// Use premultipliedAlpha: true for correct compositing onto Canvas 2D
|
|
37
|
+
this.gl = this.canvas.getContext("webgl", {
|
|
38
|
+
alpha: true,
|
|
39
|
+
premultipliedAlpha: true,
|
|
40
|
+
antialias: true,
|
|
41
|
+
preserveDrawingBuffer: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!this.gl) {
|
|
45
|
+
console.warn("WebGL not available, falling back to Canvas 2D");
|
|
46
|
+
this.available = false;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.available = true;
|
|
51
|
+
|
|
52
|
+
// Enable alpha blending for premultiplied alpha
|
|
53
|
+
// With premultiplied alpha: output = src + dest * (1 - src_alpha)
|
|
54
|
+
const gl = this.gl;
|
|
55
|
+
gl.enable(gl.BLEND);
|
|
56
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
57
|
+
|
|
58
|
+
// Set initial viewport (important - WebGL doesn't always default correctly)
|
|
59
|
+
gl.viewport(0, 0, width, height);
|
|
60
|
+
|
|
61
|
+
// Program cache
|
|
62
|
+
this.programs = new Map();
|
|
63
|
+
this.currentProgram = null;
|
|
64
|
+
|
|
65
|
+
// Uniform locations cache
|
|
66
|
+
this.uniformLocations = new Map();
|
|
67
|
+
|
|
68
|
+
// Track if attributes need rebinding (after resize)
|
|
69
|
+
this._needsAttributeRebind = false;
|
|
70
|
+
|
|
71
|
+
// Create fullscreen quad for rendering
|
|
72
|
+
this._createQuad();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if WebGL is available
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
isAvailable() {
|
|
80
|
+
return this.available;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resize the renderer
|
|
85
|
+
* @param {number} width - New width
|
|
86
|
+
* @param {number} height - New height
|
|
87
|
+
*/
|
|
88
|
+
resize(width, height) {
|
|
89
|
+
this.width = width;
|
|
90
|
+
this.height = height;
|
|
91
|
+
this.canvas.width = width;
|
|
92
|
+
this.canvas.height = height;
|
|
93
|
+
if (this.gl) {
|
|
94
|
+
this.gl.viewport(0, 0, width, height);
|
|
95
|
+
// Flag that attributes need rebinding after resize
|
|
96
|
+
this._needsAttributeRebind = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a fullscreen quad for rendering
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
_createQuad() {
|
|
105
|
+
const gl = this.gl;
|
|
106
|
+
|
|
107
|
+
// Vertex positions (fullscreen quad as two triangles)
|
|
108
|
+
const positions = new Float32Array([
|
|
109
|
+
-1, -1,
|
|
110
|
+
1, -1,
|
|
111
|
+
-1, 1,
|
|
112
|
+
-1, 1,
|
|
113
|
+
1, -1,
|
|
114
|
+
1, 1,
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
// UV coordinates
|
|
118
|
+
const uvs = new Float32Array([
|
|
119
|
+
0, 0,
|
|
120
|
+
1, 0,
|
|
121
|
+
0, 1,
|
|
122
|
+
0, 1,
|
|
123
|
+
1, 0,
|
|
124
|
+
1, 1,
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
// Position buffer
|
|
128
|
+
this.positionBuffer = gl.createBuffer();
|
|
129
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
130
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
|
131
|
+
|
|
132
|
+
// UV buffer
|
|
133
|
+
this.uvBuffer = gl.createBuffer();
|
|
134
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
|
|
135
|
+
gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compile a shader
|
|
140
|
+
* @param {number} type - gl.VERTEX_SHADER or gl.FRAGMENT_SHADER
|
|
141
|
+
* @param {string} source - Shader source code
|
|
142
|
+
* @returns {WebGLShader|null}
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
_compileShader(type, source) {
|
|
146
|
+
const gl = this.gl;
|
|
147
|
+
const shader = gl.createShader(type);
|
|
148
|
+
gl.shaderSource(shader, source);
|
|
149
|
+
gl.compileShader(shader);
|
|
150
|
+
|
|
151
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
152
|
+
console.error("Shader compile error:", gl.getShaderInfoLog(shader));
|
|
153
|
+
console.error("Source:", source);
|
|
154
|
+
gl.deleteShader(shader);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return shader;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create or get a shader program
|
|
163
|
+
* @param {string} name - Program name for caching
|
|
164
|
+
* @param {string} vertexSource - Vertex shader source
|
|
165
|
+
* @param {string} fragmentSource - Fragment shader source
|
|
166
|
+
* @returns {WebGLProgram|null}
|
|
167
|
+
*/
|
|
168
|
+
useProgram(name, vertexSource, fragmentSource) {
|
|
169
|
+
if (!this.available) return null;
|
|
170
|
+
|
|
171
|
+
const gl = this.gl;
|
|
172
|
+
|
|
173
|
+
// Check cache
|
|
174
|
+
if (this.programs.has(name)) {
|
|
175
|
+
const program = this.programs.get(name);
|
|
176
|
+
gl.useProgram(program);
|
|
177
|
+
this.currentProgram = name;
|
|
178
|
+
|
|
179
|
+
// Rebind attributes if needed (after resize or context change)
|
|
180
|
+
if (this._needsAttributeRebind) {
|
|
181
|
+
this._bindAttributes(program);
|
|
182
|
+
this._needsAttributeRebind = false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return program;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Compile shaders
|
|
189
|
+
const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexSource);
|
|
190
|
+
const fragmentShader = this._compileShader(gl.FRAGMENT_SHADER, fragmentSource);
|
|
191
|
+
|
|
192
|
+
if (!vertexShader || !fragmentShader) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Link program
|
|
197
|
+
const program = gl.createProgram();
|
|
198
|
+
gl.attachShader(program, vertexShader);
|
|
199
|
+
gl.attachShader(program, fragmentShader);
|
|
200
|
+
gl.linkProgram(program);
|
|
201
|
+
|
|
202
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
203
|
+
console.error("Program link error:", gl.getProgramInfoLog(program));
|
|
204
|
+
gl.deleteProgram(program);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Cache program
|
|
209
|
+
this.programs.set(name, program);
|
|
210
|
+
this.uniformLocations.set(name, new Map());
|
|
211
|
+
|
|
212
|
+
// Use the program
|
|
213
|
+
gl.useProgram(program);
|
|
214
|
+
this.currentProgram = name;
|
|
215
|
+
|
|
216
|
+
// Setup attribute locations
|
|
217
|
+
this._bindAttributes(program);
|
|
218
|
+
|
|
219
|
+
return program;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Bind vertex attributes for a program
|
|
224
|
+
* @param {WebGLProgram} program - The program to bind attributes for
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_bindAttributes(program) {
|
|
228
|
+
const gl = this.gl;
|
|
229
|
+
const positionLoc = gl.getAttribLocation(program, "aPosition");
|
|
230
|
+
const uvLoc = gl.getAttribLocation(program, "aUv");
|
|
231
|
+
|
|
232
|
+
if (positionLoc !== -1) {
|
|
233
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
234
|
+
gl.enableVertexAttribArray(positionLoc);
|
|
235
|
+
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (uvLoc !== -1) {
|
|
239
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
|
|
240
|
+
gl.enableVertexAttribArray(uvLoc);
|
|
241
|
+
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get uniform location (cached)
|
|
247
|
+
* @param {string} name - Uniform name
|
|
248
|
+
* @returns {WebGLUniformLocation|null}
|
|
249
|
+
* @private
|
|
250
|
+
*/
|
|
251
|
+
_getUniformLocation(name) {
|
|
252
|
+
const gl = this.gl;
|
|
253
|
+
const program = this.programs.get(this.currentProgram);
|
|
254
|
+
const cache = this.uniformLocations.get(this.currentProgram);
|
|
255
|
+
|
|
256
|
+
if (!cache.has(name)) {
|
|
257
|
+
cache.set(name, gl.getUniformLocation(program, name));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return cache.get(name);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Set uniforms for the current program
|
|
265
|
+
* @param {Object} uniforms - Object of uniform name -> value pairs
|
|
266
|
+
*/
|
|
267
|
+
setUniforms(uniforms) {
|
|
268
|
+
if (!this.available || !this.currentProgram) return;
|
|
269
|
+
|
|
270
|
+
const gl = this.gl;
|
|
271
|
+
|
|
272
|
+
for (const [name, value] of Object.entries(uniforms)) {
|
|
273
|
+
const location = this._getUniformLocation(name);
|
|
274
|
+
if (location === null) continue;
|
|
275
|
+
|
|
276
|
+
if (typeof value === "number") {
|
|
277
|
+
gl.uniform1f(location, value);
|
|
278
|
+
} else if (Array.isArray(value)) {
|
|
279
|
+
switch (value.length) {
|
|
280
|
+
case 2:
|
|
281
|
+
gl.uniform2fv(location, value);
|
|
282
|
+
break;
|
|
283
|
+
case 3:
|
|
284
|
+
gl.uniform3fv(location, value);
|
|
285
|
+
break;
|
|
286
|
+
case 4:
|
|
287
|
+
gl.uniform4fv(location, value);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
} else if (value instanceof Float32Array) {
|
|
291
|
+
if (value.length === 9) {
|
|
292
|
+
gl.uniformMatrix3fv(location, false, value);
|
|
293
|
+
} else if (value.length === 16) {
|
|
294
|
+
gl.uniformMatrix4fv(location, false, value);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Set a color uniform (converts hex to RGB floats)
|
|
302
|
+
* @param {string} name - Uniform name
|
|
303
|
+
* @param {string} color - Hex color string (e.g., "#FF8800")
|
|
304
|
+
*/
|
|
305
|
+
setColorUniform(name, color) {
|
|
306
|
+
if (!this.available || !this.currentProgram) return;
|
|
307
|
+
|
|
308
|
+
const hex = color.replace("#", "");
|
|
309
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
310
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
311
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
312
|
+
|
|
313
|
+
const location = this._getUniformLocation(name);
|
|
314
|
+
if (location !== null) {
|
|
315
|
+
this.gl.uniform3f(location, r, g, b);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Clear the canvas
|
|
321
|
+
* @param {number} r - Red (0-1)
|
|
322
|
+
* @param {number} g - Green (0-1)
|
|
323
|
+
* @param {number} b - Blue (0-1)
|
|
324
|
+
* @param {number} a - Alpha (0-1)
|
|
325
|
+
*/
|
|
326
|
+
clear(r = 0, g = 0, b = 0, a = 0) {
|
|
327
|
+
if (!this.available) return;
|
|
328
|
+
const gl = this.gl;
|
|
329
|
+
gl.clearColor(r, g, b, a);
|
|
330
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Render the current program to the canvas
|
|
335
|
+
*/
|
|
336
|
+
render() {
|
|
337
|
+
if (!this.available || !this.currentProgram) return;
|
|
338
|
+
const gl = this.gl;
|
|
339
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Composite the WebGL canvas onto a 2D canvas context
|
|
344
|
+
* @param {CanvasRenderingContext2D} ctx - Target 2D context
|
|
345
|
+
* @param {number} x - X position
|
|
346
|
+
* @param {number} y - Y position
|
|
347
|
+
* @param {number} [width] - Optional width (defaults to canvas width)
|
|
348
|
+
* @param {number} [height] - Optional height (defaults to canvas height)
|
|
349
|
+
*/
|
|
350
|
+
compositeOnto(ctx, x, y, width, height) {
|
|
351
|
+
if (!this.available) return;
|
|
352
|
+
ctx.drawImage(
|
|
353
|
+
this.canvas,
|
|
354
|
+
x, y,
|
|
355
|
+
width ?? this.canvas.width,
|
|
356
|
+
height ?? this.canvas.height
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get the WebGL canvas element
|
|
362
|
+
* @returns {HTMLCanvasElement}
|
|
363
|
+
*/
|
|
364
|
+
getCanvas() {
|
|
365
|
+
return this.canvas;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Destroy the renderer and free resources
|
|
370
|
+
*/
|
|
371
|
+
destroy() {
|
|
372
|
+
if (!this.available) return;
|
|
373
|
+
|
|
374
|
+
const gl = this.gl;
|
|
375
|
+
|
|
376
|
+
// Delete programs
|
|
377
|
+
for (const program of this.programs.values()) {
|
|
378
|
+
gl.deleteProgram(program);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Delete buffers
|
|
382
|
+
gl.deleteBuffer(this.positionBuffer);
|
|
383
|
+
gl.deleteBuffer(this.uvBuffer);
|
|
384
|
+
|
|
385
|
+
this.programs.clear();
|
|
386
|
+
this.uniformLocations.clear();
|
|
387
|
+
}
|
|
388
|
+
}
|
package/tde.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { decayingOrbitalRadius, getTerminalTrajectory } from "../../src/math/orbital";
|
|
3
|
+
|
|
4
|
+
describe("Orbital Math Utilities", () => {
|
|
5
|
+
describe("decayingOrbitalRadius", () => {
|
|
6
|
+
it("should return the initial radius when t=0", () => {
|
|
7
|
+
const r0 = 100;
|
|
8
|
+
const decay = 0.5;
|
|
9
|
+
const r = decayingOrbitalRadius(r0, decay, 0);
|
|
10
|
+
expect(r).toBe(r0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should decay the radius over time", () => {
|
|
14
|
+
const r0 = 100;
|
|
15
|
+
const decay = 0.5;
|
|
16
|
+
const r1 = decayingOrbitalRadius(r0, decay, 1);
|
|
17
|
+
const r2 = decayingOrbitalRadius(r0, decay, 2);
|
|
18
|
+
|
|
19
|
+
expect(r1).toBeLessThan(r0);
|
|
20
|
+
expect(r2).toBeLessThan(r1);
|
|
21
|
+
expect(r1).toBeCloseTo(100 * Math.exp(-0.5), 5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should handle zero decay factor", () => {
|
|
25
|
+
const r0 = 100;
|
|
26
|
+
const r = decayingOrbitalRadius(r0, 0, 10);
|
|
27
|
+
expect(r).toBe(r0);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("getTerminalTrajectory", () => {
|
|
32
|
+
it("should return start position at progress=0", () => {
|
|
33
|
+
const start = { x: 100, y: 50, z: 25 };
|
|
34
|
+
const pos = getTerminalTrajectory(start.x, start.y, start.z, 0);
|
|
35
|
+
expect(pos).toEqual(start);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return origin at progress=1", () => {
|
|
39
|
+
const start = { x: 100, y: 50, z: 25 };
|
|
40
|
+
const pos = getTerminalTrajectory(start.x, start.y, start.z, 1);
|
|
41
|
+
expect(pos).toEqual({ x: 0, y: 0, z: 0 });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should interpolate linearly by default", () => {
|
|
45
|
+
const start = { x: 100, y: 100, z: 100 };
|
|
46
|
+
const pos = getTerminalTrajectory(start.x, start.y, start.z, 0.5);
|
|
47
|
+
expect(pos).toEqual({ x: 50, y: 50, z: 50 });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should apply easing function if provided", () => {
|
|
51
|
+
const start = { x: 100, y: 100, z: 100 };
|
|
52
|
+
const easeInQuad = (t) => t * t;
|
|
53
|
+
const pos = getTerminalTrajectory(start.x, start.y, start.z, 0.5, easeInQuad);
|
|
54
|
+
// 0.5 * 0.5 = 0.25
|
|
55
|
+
// 100 * (1 - 0.25) = 75
|
|
56
|
+
expect(pos.x).toBe(75);
|
|
57
|
+
expect(pos.y).toBe(75);
|
|
58
|
+
expect(pos.z).toBe(75);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Tensor } from "../../src/math/tensor";
|
|
3
|
+
|
|
4
|
+
describe("Tensor class", () => {
|
|
5
|
+
describe("Basic Operations", () => {
|
|
6
|
+
it("should create a tensor from components", () => {
|
|
7
|
+
const components = [
|
|
8
|
+
[1, 2],
|
|
9
|
+
[3, 4],
|
|
10
|
+
];
|
|
11
|
+
const t = new Tensor(components);
|
|
12
|
+
expect(t.get(0, 0)).toBe(1);
|
|
13
|
+
expect(t.get(1, 1)).toBe(4);
|
|
14
|
+
expect(t.dimension).toBe(2);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should be immutable", () => {
|
|
18
|
+
const components = [[1, 2], [3, 4]];
|
|
19
|
+
const t1 = new Tensor(components);
|
|
20
|
+
const t2 = t1.set(0, 0, 9);
|
|
21
|
+
|
|
22
|
+
expect(t1.get(0, 0)).toBe(1);
|
|
23
|
+
expect(t2.get(0, 0)).toBe(9);
|
|
24
|
+
expect(t1).not.toBe(t2);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should add two tensors", () => {
|
|
28
|
+
const t1 = new Tensor([[1, 0], [0, 1]]);
|
|
29
|
+
const t2 = new Tensor([[1, 2], [3, 4]]);
|
|
30
|
+
const sum = t1.add(t2);
|
|
31
|
+
expect(sum.get(0, 1)).toBe(2);
|
|
32
|
+
expect(sum.get(1, 1)).toBe(5);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should scale a tensor", () => {
|
|
36
|
+
const t = new Tensor([[1, 2], [3, 4]]);
|
|
37
|
+
const scaled = t.scale(2);
|
|
38
|
+
expect(scaled.get(0, 0)).toBe(2);
|
|
39
|
+
expect(scaled.get(1, 1)).toBe(8);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("Inversion & Determinant (Diagonal Optimizations)", () => {
|
|
44
|
+
it("should compute inverse of a diagonal tensor (fast path)", () => {
|
|
45
|
+
const t = Tensor.diagonal([-1, 0.5, 2, 4]);
|
|
46
|
+
const inv = t.inverse();
|
|
47
|
+
expect(inv.get(0, 0)).toBe(-1);
|
|
48
|
+
expect(inv.get(1, 1)).toBe(2);
|
|
49
|
+
expect(inv.get(2, 2)).toBe(0.5);
|
|
50
|
+
expect(inv.get(3, 3)).toBe(0.25);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should compute determinant of a diagonal matrix (fast path)", () => {
|
|
54
|
+
const t = Tensor.diagonal([-1, 1, 1, 1]);
|
|
55
|
+
expect(t.determinant()).toBe(-1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should compute inverse of a non-diagonal matrix (Gaussian elimination)", () => {
|
|
59
|
+
const t = new Tensor([
|
|
60
|
+
[1, 2],
|
|
61
|
+
[3, 4]
|
|
62
|
+
]);
|
|
63
|
+
const inv = t.inverse();
|
|
64
|
+
// det = 1*4 - 2*3 = -2
|
|
65
|
+
// inv = (-1/2) * [4, -2; -3, 1] = [-2, 1; 1.5, -0.5]
|
|
66
|
+
expect(inv.get(0, 0)).toBeCloseTo(-2, 10);
|
|
67
|
+
expect(inv.get(0, 1)).toBeCloseTo(1, 10);
|
|
68
|
+
expect(inv.get(1, 0)).toBeCloseTo(1.5, 10);
|
|
69
|
+
expect(inv.get(1, 1)).toBeCloseTo(-0.5, 10);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("GR Metrics", () => {
|
|
74
|
+
it("should create Schwarzschild metric", () => {
|
|
75
|
+
const g = Tensor.schwarzschild(10, 2);
|
|
76
|
+
expect(g.name).toBe("Schwarzschild");
|
|
77
|
+
// factor = 1 - 2/10 = 0.8
|
|
78
|
+
expect(g.get(0, 0)).toBe(-0.8);
|
|
79
|
+
expect(g.get(1, 1)).toBeCloseTo(1 / 0.8, 5);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should create contravariant Schwarzschild metric directly", () => {
|
|
83
|
+
const gInv = Tensor.schwarzschildContravariant(10, 2);
|
|
84
|
+
expect(gInv.get(0, 0)).toBeCloseTo(-1 / 0.8, 5);
|
|
85
|
+
expect(gInv.get(1, 1)).toBe(0.8);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should match numerical inverse for Kerr metric", () => {
|
|
89
|
+
const r = 10, theta = Math.PI / 4, M = 1, a = 0.6;
|
|
90
|
+
const g = Tensor.kerr(r, theta, M, a);
|
|
91
|
+
const gInvNumerical = g.inverse();
|
|
92
|
+
const gInvAnalytical = Tensor.kerrContravariant(r, theta, M, a);
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < 4; i++) {
|
|
95
|
+
for (let j = 0; j < 4; j++) {
|
|
96
|
+
expect(gInvAnalytical.get(i, j)).toBeCloseTo(gInvNumerical.get(i, j), 8);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should compute analytical Christoffel symbols for Schwarzschild", () => {
|
|
102
|
+
const r = 10, rs = 2, theta = Math.PI / 2;
|
|
103
|
+
const pos = [0, r, theta, 0];
|
|
104
|
+
pos._rs = rs;
|
|
105
|
+
|
|
106
|
+
const gamma = Tensor.christoffel((p) => Tensor.schwarzschild(p[1], rs, p[2]), pos);
|
|
107
|
+
|
|
108
|
+
// factor = 0.8
|
|
109
|
+
// Gamma^t_tr = rs / (2r^2 * factor) = 2 / (200 * 0.8) = 2 / 160 = 0.0125
|
|
110
|
+
expect(gamma[0][0][1]).toBeCloseTo(0.0125, 8);
|
|
111
|
+
expect(gamma[0][1][0]).toBeCloseTo(0.0125, 8);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|