@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.
Files changed (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. 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
+ });