@guinetik/gcanvas 1.0.2 → 1.0.4

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 (217) hide show
  1. package/dist/gcanvas.es.js +25656 -0
  2. package/dist/gcanvas.es.min.js +1 -0
  3. package/dist/gcanvas.umd.js +1 -0
  4. package/dist/gcanvas.umd.min.js +1 -0
  5. package/package.json +23 -6
  6. package/src/game/objects/index.js +1 -0
  7. package/src/game/objects/spritesheet.js +260 -0
  8. package/src/game/ui/theme.js +6 -0
  9. package/src/io/keys.js +9 -1
  10. package/src/math/boolean.js +481 -0
  11. package/src/math/index.js +1 -0
  12. package/.github/workflows/release.yaml +0 -70
  13. package/.jshintrc +0 -4
  14. package/.vscode/settings.json +0 -22
  15. package/CLAUDE.md +0 -310
  16. package/blackhole.jpg +0 -0
  17. package/demo.png +0 -0
  18. package/demos/CNAME +0 -1
  19. package/demos/animations.html +0 -31
  20. package/demos/basic.html +0 -38
  21. package/demos/baskara.html +0 -31
  22. package/demos/bezier.html +0 -35
  23. package/demos/beziersignature.html +0 -29
  24. package/demos/blackhole.html +0 -28
  25. package/demos/blob.html +0 -35
  26. package/demos/coordinates.html +0 -698
  27. package/demos/cube3d.html +0 -23
  28. package/demos/demos.css +0 -303
  29. package/demos/dino.html +0 -42
  30. package/demos/easing.html +0 -28
  31. package/demos/events.html +0 -195
  32. package/demos/fluent.html +0 -647
  33. package/demos/fluid-simple.html +0 -22
  34. package/demos/fluid.html +0 -37
  35. package/demos/fractals.html +0 -36
  36. package/demos/gameobjects.html +0 -626
  37. package/demos/genart.html +0 -26
  38. package/demos/gendream.html +0 -26
  39. package/demos/group.html +0 -36
  40. package/demos/home.html +0 -587
  41. package/demos/index.html +0 -376
  42. package/demos/isometric.html +0 -34
  43. package/demos/js/animations.js +0 -452
  44. package/demos/js/basic.js +0 -204
  45. package/demos/js/baskara.js +0 -751
  46. package/demos/js/bezier.js +0 -692
  47. package/demos/js/beziersignature.js +0 -241
  48. package/demos/js/blackhole/accretiondisk.obj.js +0 -379
  49. package/demos/js/blackhole/blackhole.obj.js +0 -318
  50. package/demos/js/blackhole/index.js +0 -409
  51. package/demos/js/blackhole/particle.js +0 -56
  52. package/demos/js/blackhole/starfield.obj.js +0 -218
  53. package/demos/js/blob.js +0 -2276
  54. package/demos/js/coordinates.js +0 -840
  55. package/demos/js/cube3d.js +0 -789
  56. package/demos/js/dino.js +0 -1420
  57. package/demos/js/easing.js +0 -477
  58. package/demos/js/fluent.js +0 -183
  59. package/demos/js/fluid-simple.js +0 -253
  60. package/demos/js/fluid.js +0 -527
  61. package/demos/js/fractals.js +0 -931
  62. package/demos/js/fractalworker.js +0 -93
  63. package/demos/js/gameobjects.js +0 -176
  64. package/demos/js/genart.js +0 -268
  65. package/demos/js/gendream.js +0 -209
  66. package/demos/js/group.js +0 -140
  67. package/demos/js/info-toggle.js +0 -25
  68. package/demos/js/isometric.js +0 -863
  69. package/demos/js/kerr.js +0 -1556
  70. package/demos/js/lavalamp.js +0 -590
  71. package/demos/js/layout.js +0 -354
  72. package/demos/js/mondrian.js +0 -285
  73. package/demos/js/opacity.js +0 -275
  74. package/demos/js/painter.js +0 -484
  75. package/demos/js/particles-showcase.js +0 -514
  76. package/demos/js/particles.js +0 -299
  77. package/demos/js/patterns.js +0 -397
  78. package/demos/js/penrose/artifact.js +0 -69
  79. package/demos/js/penrose/blackhole.js +0 -121
  80. package/demos/js/penrose/constants.js +0 -73
  81. package/demos/js/penrose/game.js +0 -943
  82. package/demos/js/penrose/lore.js +0 -278
  83. package/demos/js/penrose/penrosescene.js +0 -892
  84. package/demos/js/penrose/ship.js +0 -216
  85. package/demos/js/penrose/sounds.js +0 -211
  86. package/demos/js/penrose/voidparticle.js +0 -55
  87. package/demos/js/penrose/voidscene.js +0 -258
  88. package/demos/js/penrose/voidship.js +0 -144
  89. package/demos/js/penrose/wormhole.js +0 -46
  90. package/demos/js/pipeline.js +0 -555
  91. package/demos/js/plane3d.js +0 -256
  92. package/demos/js/platformer.js +0 -1579
  93. package/demos/js/scene.js +0 -304
  94. package/demos/js/scenes.js +0 -320
  95. package/demos/js/schrodinger.js +0 -410
  96. package/demos/js/schwarzschild.js +0 -1023
  97. package/demos/js/shapes.js +0 -628
  98. package/demos/js/space/alien.js +0 -171
  99. package/demos/js/space/boom.js +0 -98
  100. package/demos/js/space/boss.js +0 -353
  101. package/demos/js/space/buff.js +0 -73
  102. package/demos/js/space/bullet.js +0 -102
  103. package/demos/js/space/constants.js +0 -85
  104. package/demos/js/space/game.js +0 -1884
  105. package/demos/js/space/hud.js +0 -112
  106. package/demos/js/space/laserbeam.js +0 -179
  107. package/demos/js/space/lightning.js +0 -277
  108. package/demos/js/space/minion.js +0 -192
  109. package/demos/js/space/missile.js +0 -212
  110. package/demos/js/space/player.js +0 -430
  111. package/demos/js/space/powerup.js +0 -90
  112. package/demos/js/space/starfield.js +0 -58
  113. package/demos/js/space/starpower.js +0 -90
  114. package/demos/js/spacetime.js +0 -559
  115. package/demos/js/sphere3d.js +0 -229
  116. package/demos/js/sprite.js +0 -473
  117. package/demos/js/svgtween.js +0 -204
  118. package/demos/js/tde/accretiondisk.js +0 -471
  119. package/demos/js/tde/blackhole.js +0 -219
  120. package/demos/js/tde/blackholescene.js +0 -209
  121. package/demos/js/tde/config.js +0 -59
  122. package/demos/js/tde/index.js +0 -820
  123. package/demos/js/tde/jets.js +0 -290
  124. package/demos/js/tde/lensedstarfield.js +0 -154
  125. package/demos/js/tde/tdestar.js +0 -297
  126. package/demos/js/tde/tidalstream.js +0 -372
  127. package/demos/js/tde_old/blackhole.obj.js +0 -354
  128. package/demos/js/tde_old/debris.obj.js +0 -791
  129. package/demos/js/tde_old/flare.obj.js +0 -239
  130. package/demos/js/tde_old/index.js +0 -448
  131. package/demos/js/tde_old/star.obj.js +0 -812
  132. package/demos/js/tiles.js +0 -312
  133. package/demos/js/tweendemo.js +0 -79
  134. package/demos/js/visibility.js +0 -102
  135. package/demos/kerr.html +0 -28
  136. package/demos/lavalamp.html +0 -27
  137. package/demos/layouts.html +0 -37
  138. package/demos/logo.svg +0 -4
  139. package/demos/loop.html +0 -84
  140. package/demos/mondrian.html +0 -32
  141. package/demos/og_image.png +0 -0
  142. package/demos/opacity.html +0 -36
  143. package/demos/painter.html +0 -39
  144. package/demos/particles-showcase.html +0 -28
  145. package/demos/particles.html +0 -24
  146. package/demos/patterns.html +0 -33
  147. package/demos/penrose-game.html +0 -31
  148. package/demos/pipeline.html +0 -737
  149. package/demos/plane3d.html +0 -24
  150. package/demos/platformer.html +0 -43
  151. package/demos/scene.html +0 -33
  152. package/demos/scenes.html +0 -96
  153. package/demos/schrodinger.html +0 -27
  154. package/demos/schwarzschild.html +0 -27
  155. package/demos/shapes.html +0 -16
  156. package/demos/space.html +0 -85
  157. package/demos/spacetime.html +0 -27
  158. package/demos/sphere3d.html +0 -24
  159. package/demos/sprite.html +0 -18
  160. package/demos/svgtween.html +0 -29
  161. package/demos/tde.html +0 -28
  162. package/demos/tiles.html +0 -28
  163. package/demos/transforms.html +0 -400
  164. package/demos/tween.html +0 -45
  165. package/demos/visibility.html +0 -33
  166. package/docs/README.md +0 -230
  167. package/docs/api/FluidSystem.md +0 -173
  168. package/docs/concepts/architecture-overview.md +0 -204
  169. package/docs/concepts/coordinate-system.md +0 -384
  170. package/docs/concepts/lifecycle.md +0 -255
  171. package/docs/concepts/rendering-pipeline.md +0 -279
  172. package/docs/concepts/shapes-vs-gameobjects.md +0 -187
  173. package/docs/concepts/tde-zorder.md +0 -106
  174. package/docs/concepts/two-layer-architecture.md +0 -229
  175. package/docs/fluid-dynamics.md +0 -99
  176. package/docs/getting-started/first-game.md +0 -354
  177. package/docs/getting-started/hello-world.md +0 -269
  178. package/docs/getting-started/installation.md +0 -175
  179. package/docs/modules/collision/README.md +0 -453
  180. package/docs/modules/fluent/README.md +0 -1075
  181. package/docs/modules/game/README.md +0 -303
  182. package/docs/modules/isometric-camera.md +0 -210
  183. package/docs/modules/isometric.md +0 -275
  184. package/docs/modules/painter/README.md +0 -328
  185. package/docs/modules/particle/README.md +0 -559
  186. package/docs/modules/shapes/README.md +0 -221
  187. package/docs/modules/shapes/base/euclidian.md +0 -123
  188. package/docs/modules/shapes/base/geometry2d.md +0 -204
  189. package/docs/modules/shapes/base/renderable.md +0 -215
  190. package/docs/modules/shapes/base/shape.md +0 -262
  191. package/docs/modules/shapes/base/transformable.md +0 -243
  192. package/docs/modules/shapes/hierarchy.md +0 -218
  193. package/docs/modules/state/README.md +0 -577
  194. package/docs/modules/util/README.md +0 -99
  195. package/docs/modules/util/camera3d.md +0 -412
  196. package/docs/modules/util/scene3d.md +0 -395
  197. package/index.html +0 -17
  198. package/jsdoc.json +0 -50
  199. package/scripts/build-demo.js +0 -69
  200. package/scripts/bundle4llm.js +0 -276
  201. package/scripts/clearconsole.js +0 -48
  202. package/test/math/orbital.test.js +0 -61
  203. package/test/math/tensor.test.js +0 -114
  204. package/test/particle/emitter.test.js +0 -204
  205. package/test/particle/particle-system.test.js +0 -310
  206. package/test/particle/particle.test.js +0 -116
  207. package/test/particle/updaters.test.js +0 -386
  208. package/test/setup.js +0 -120
  209. package/test/shapes/euclidian.test.js +0 -44
  210. package/test/shapes/geometry.test.js +0 -86
  211. package/test/shapes/group.test.js +0 -86
  212. package/test/shapes/rectangle.test.js +0 -64
  213. package/test/shapes/transform.test.js +0 -379
  214. package/test/util/camera3d.test.js +0 -428
  215. package/test/util/scene3d.test.js +0 -352
  216. package/vite.config.js +0 -50
  217. package/vitest.config.js +0 -13
@@ -1,559 +0,0 @@
1
- /**
2
- * Spacetime Curvature - Math & Physics Demo
3
- *
4
- * Visualization of how mass curves spacetime, based on Einstein's
5
- * general relativity. The "rubber sheet" analogy where massive objects
6
- * create wells/depressions in the fabric of spacetime.
7
- *
8
- * Uses gravitational potential: Φ(r) = -GM/r
9
- * Grid deformation: y = Σ(-M_i / |r - r_i|)
10
- */
11
-
12
- import {
13
- Game,
14
- Painter,
15
- Camera3D,
16
- Text,
17
- applyAnchor,
18
- Position,
19
- Scene,
20
- verticalLayout,
21
- applyLayout,
22
- } from "../../src/index.js";
23
-
24
- // Configuration
25
- const CONFIG = {
26
- // Grid parameters
27
- gridSize: 20, // Grid extends from -gridSize to +gridSize
28
- gridResolution: 40, // Number of grid lines
29
- gridScale: 15, // Scale factor for grid spacing
30
-
31
- // Physics - Gaussian well profile for smooth falloff to flat edges
32
- wellDepth: 75, // Base depth (scaled by sqrt of mass)
33
- wellWidth: 4.0, // Base width (scaled by mass)
34
-
35
- // 3D view
36
- rotationX: 0.7, // Initial tilt (looking down at grid)
37
- rotationY: 0.3, // Initial rotation
38
- perspective: 1000, // Perspective depth
39
-
40
- // Stellar body
41
- initialBody: { x: 0, z: 0, mass: 3.0, type: "blackhole" },
42
-
43
- // Body properties by type
44
- bodyTypes: {
45
- blackhole: {
46
- color: "#111",
47
- glowColor: "rgba(100, 50, 150, 0.8)",
48
- minMass: 2.0,
49
- maxMass: 5.0,
50
- },
51
- star: {
52
- color: "#ff8800",
53
- glowColor: "rgba(255, 200, 50, 0.6)",
54
- minMass: 0.5,
55
- maxMass: 2.0,
56
- },
57
- neutron: {
58
- color: "#88ccff",
59
- glowColor: "rgba(150, 200, 255, 0.7)",
60
- minMass: 1.5,
61
- maxMass: 3.0,
62
- },
63
- },
64
-
65
- // Visual
66
- gridColor: "rgba(0, 180, 255, 0.4)",
67
- gridHighlight: "rgba(100, 220, 255, 0.6)",
68
- wellGradientStart: "rgba(80, 0, 120, 0.3)",
69
- wellGradientEnd: "rgba(0, 100, 200, 0.1)",
70
-
71
- // Animation
72
- autoRotateSpeed: 0.15, // Auto-rotate speed (radians per second)
73
- pulseSpeed: 2.0, // Glow pulse speed
74
- wellPulseSpeed: 1.5, // Well breathing speed
75
- wellPulseAmount: 0.08, // How much the well pulses (0-1)
76
-
77
- // Orbiting body
78
- orbitRadiusMultiplier: 2.0, // Orbit at this multiple of well width (sigma)
79
- orbitSpeed: 0.8, // Base orbit speed (faster for heavier central mass)
80
- orbiterSize: 4, // Size of orbiting body
81
- orbiterColor: "#4af", // Color of orbiter
82
- orbiterGlow: "rgba(100, 180, 255, 0.6)",
83
- };
84
-
85
- class SpacetimeDemo extends Game {
86
- constructor(canvas) {
87
- super(canvas);
88
- this.backgroundColor = "#000008";
89
- this.enableFluidSize();
90
- }
91
-
92
- init() {
93
- super.init();
94
- this.time = 0;
95
-
96
- // Create 3D camera with mouse controls and auto-rotate
97
- this.camera = new Camera3D({
98
- rotationX: CONFIG.rotationX,
99
- rotationY: CONFIG.rotationY,
100
- perspective: CONFIG.perspective,
101
- minRotationX: 0.2, // Don't allow looking from below
102
- maxRotationX: 1.3, // Don't flip over
103
- autoRotate: true,
104
- autoRotateSpeed: CONFIG.autoRotateSpeed,
105
- autoRotateAxis: "y",
106
- });
107
- this.camera.enableMouseControl(this.canvas);
108
-
109
- // Initialize single stellar body
110
- this.body = {
111
- ...CONFIG.initialBody,
112
- orbitPhase: Math.random() * Math.PI * 2,
113
- };
114
-
115
- // Initialize orbiting test particle
116
- this.orbiterAngle = 0;
117
-
118
- // Precompute grid vertex positions (flat)
119
- this.initGrid();
120
-
121
- // Click to add bodies
122
- this.canvas.addEventListener("click", (e) => this.handleClick(e));
123
-
124
- // Setup info panel
125
- this.setupInfoPanel();
126
- }
127
-
128
- setupInfoPanel() {
129
- this.infoPanel = new Scene(this, { x: 0, y: 0 });
130
- applyAnchor(this.infoPanel, {
131
- anchor: Position.TOP_CENTER,
132
- anchorOffsetY: 150,
133
- });
134
- this.pipeline.add(this.infoPanel);
135
-
136
- this.titleText = new Text(this, "Spacetime Curvature", {
137
- font: "bold 16px monospace",
138
- color: "#7af",
139
- align: "center",
140
- baseline: "middle",
141
- });
142
-
143
- this.equationText = new Text(
144
- this,
145
- "g\u03BC\u03BD = \u03B7\u03BC\u03BD + h\u03BC\u03BD | R\u03BC\u03BD - \u00BDRg\u03BC\u03BD = 8\u03C0GT\u03BC\u03BD",
146
- {
147
- font: "12px monospace",
148
- color: "#888",
149
- align: "center",
150
- baseline: "middle",
151
- },
152
- );
153
-
154
- this.statsText = new Text(this, "Blackhole | Mass: 3.0 M\u2609", {
155
- font: "12px monospace",
156
- color: "#6d8",
157
- align: "center",
158
- baseline: "middle",
159
- });
160
-
161
- const textItems = [this.titleText, this.equationText, this.statsText];
162
- const layout = verticalLayout(textItems, { spacing: 18, align: "center" });
163
- applyLayout(textItems, layout.positions);
164
- textItems.forEach((item) => this.infoPanel.add(item));
165
- }
166
-
167
- initGrid() {
168
- const { gridSize, gridResolution } = CONFIG;
169
- this.gridVertices = [];
170
-
171
- // Create grid of vertices
172
- for (let i = 0; i <= gridResolution; i++) {
173
- const row = [];
174
- for (let j = 0; j <= gridResolution; j++) {
175
- const x = (i / gridResolution - 0.5) * 2 * gridSize;
176
- const z = (j / gridResolution - 0.5) * 2 * gridSize;
177
- row.push({ x, y: 0, z });
178
- }
179
- this.gridVertices.push(row);
180
- }
181
- }
182
-
183
- handleClick(e) {
184
- if (this.camera.isDragging()) return;
185
- this.shuffleBody();
186
- }
187
-
188
- shuffleBody() {
189
- // Randomly choose body type (weighted toward black holes for dramatic effect)
190
- const rand = Math.random();
191
- let type = "blackhole";
192
- if (rand > 0.5) type = "star";
193
- else if (rand > 0.3) type = "neutron";
194
-
195
- const typeConfig = CONFIG.bodyTypes[type];
196
- const mass =
197
- typeConfig.minMass +
198
- Math.random() * (typeConfig.maxMass - typeConfig.minMass);
199
-
200
- // Always centered
201
- this.body = {
202
- x: 0,
203
- z: 0,
204
- mass,
205
- type,
206
- orbitPhase: Math.random() * Math.PI * 2,
207
- };
208
- }
209
-
210
- /**
211
- * Calculate well depth at a point using Gaussian profile
212
- * Returns positive value (depth of well)
213
- * Gaussian naturally falls to 0 at edges - no clamping needed
214
- * Includes subtle pulsing animation
215
- */
216
- calculateWellDepth(x, z) {
217
- const dx = x - this.body.x;
218
- const dz = z - this.body.z;
219
- const rSquared = dx * dx + dz * dz;
220
-
221
- // Gaussian well: depth = A * exp(-r²/2σ²)
222
- // More mass = wider crater (sigma increases)
223
- // More mass = deeper but with diminishing returns (sqrt)
224
- const sigma = CONFIG.wellWidth * Math.sqrt(this.body.mass);
225
- const baseAmplitude = CONFIG.wellDepth * Math.sqrt(this.body.mass);
226
-
227
- // Pulsing animation - well "breathes"
228
- const pulse =
229
- 1 + CONFIG.wellPulseAmount * Math.sin(this.time * CONFIG.wellPulseSpeed);
230
- const amplitude = baseAmplitude * pulse;
231
-
232
- return amplitude * Math.exp(-rSquared / (2 * sigma * sigma));
233
- }
234
-
235
- update(dt) {
236
- super.update(dt);
237
- this.time += dt;
238
-
239
- // Update camera (handles auto-rotate when not dragging)
240
- this.camera.update(dt);
241
-
242
- // Update grid vertices based on gravitational wells
243
- // Positive Y = wells curving DOWN (with current camera angle)
244
- const { gridResolution } = CONFIG;
245
- for (let i = 0; i <= gridResolution; i++) {
246
- for (let j = 0; j <= gridResolution; j++) {
247
- const vertex = this.gridVertices[i][j];
248
- vertex.y = this.calculateWellDepth(vertex.x, vertex.z);
249
- }
250
- }
251
-
252
- // Update orbiter - faster orbit for heavier central mass (Kepler's law)
253
- const orbitSpeed = CONFIG.orbitSpeed * Math.sqrt(this.body.mass);
254
- this.orbiterAngle += orbitSpeed * dt;
255
-
256
- // Update stats text
257
- if (this.statsText) {
258
- const typeName =
259
- this.body.type.charAt(0).toUpperCase() + this.body.type.slice(1);
260
- this.statsText.text = `${typeName} | Mass: ${this.body.mass.toFixed(1)} M\u2609`;
261
- }
262
- }
263
-
264
- render() {
265
- const w = this.width;
266
- const h = this.height;
267
- const cx = w / 2;
268
- const cy = h / 2 + 50;
269
-
270
- super.render();
271
-
272
- // Draw grid lines
273
- this.drawGrid(cx, cy);
274
-
275
- // Draw orbiting body (behind or in front based on position)
276
- this.drawOrbiter(cx, cy);
277
-
278
- // Draw stellar body
279
- this.drawBody(cx, cy);
280
-
281
- // Draw controls hint
282
- this.drawControls(w, h);
283
- }
284
-
285
- drawGrid(cx, cy) {
286
- const { gridResolution, gridScale, gridColor, gridHighlight } = CONFIG;
287
-
288
- // Project all vertices
289
- const projected = this.gridVertices.map((row) =>
290
- row.map((v) => {
291
- const p = this.camera.project(v.x * gridScale, v.y, v.z * gridScale);
292
- return {
293
- x: cx + p.x,
294
- y: cy + p.y,
295
- z: p.z,
296
- depth: v.y,
297
- };
298
- }),
299
- );
300
-
301
- // Draw grid lines along X direction
302
- for (let i = 0; i <= gridResolution; i++) {
303
- const isMainLine = i % 5 === 0;
304
- Painter.useCtx((ctx) => {
305
- ctx.strokeStyle = isMainLine ? gridHighlight : gridColor;
306
- ctx.lineWidth = isMainLine ? 1.5 : 0.8;
307
- ctx.beginPath();
308
-
309
- for (let j = 0; j <= gridResolution; j++) {
310
- const p = projected[i][j];
311
- if (j === 0) {
312
- ctx.moveTo(p.x, p.y);
313
- } else {
314
- ctx.lineTo(p.x, p.y);
315
- }
316
- }
317
- ctx.stroke();
318
- });
319
- }
320
-
321
- // Draw grid lines along Z direction
322
- for (let j = 0; j <= gridResolution; j++) {
323
- const isMainLine = j % 5 === 0;
324
- Painter.useCtx((ctx) => {
325
- ctx.strokeStyle = isMainLine ? gridHighlight : gridColor;
326
- ctx.lineWidth = isMainLine ? 1.5 : 0.8;
327
- ctx.beginPath();
328
-
329
- for (let i = 0; i <= gridResolution; i++) {
330
- const p = projected[i][j];
331
- if (i === 0) {
332
- ctx.moveTo(p.x, p.y);
333
- } else {
334
- ctx.lineTo(p.x, p.y);
335
- }
336
- }
337
- ctx.stroke();
338
- });
339
- }
340
- }
341
-
342
- drawOrbiter(cx, cy) {
343
- // Orbit radius scales with well width (sigma)
344
- const sigma = CONFIG.wellWidth * Math.sqrt(this.body.mass);
345
- const orbitRadius = sigma * CONFIG.orbitRadiusMultiplier;
346
-
347
- // Calculate orbiter position on the grid
348
- const orbiterX = this.body.x + Math.cos(this.orbiterAngle) * orbitRadius;
349
- const orbiterZ = this.body.z + Math.sin(this.orbiterAngle) * orbitRadius;
350
-
351
- // Get well depth at orbiter position (follows the curvature)
352
- const wellDepth = this.calculateWellDepth(orbiterX, orbiterZ);
353
-
354
- // Project to screen
355
- const p = this.camera.project(
356
- orbiterX * CONFIG.gridScale,
357
- wellDepth,
358
- orbiterZ * CONFIG.gridScale,
359
- );
360
-
361
- const screenX = cx + p.x;
362
- const screenY = cy + p.y;
363
- const size = CONFIG.orbiterSize * p.scale;
364
-
365
- // Draw glow
366
- Painter.useCtx((ctx) => {
367
- const gradient = ctx.createRadialGradient(
368
- screenX,
369
- screenY,
370
- 0,
371
- screenX,
372
- screenY,
373
- size * 3,
374
- );
375
- gradient.addColorStop(0, CONFIG.orbiterGlow);
376
- gradient.addColorStop(1, "transparent");
377
-
378
- ctx.fillStyle = gradient;
379
- ctx.beginPath();
380
- ctx.arc(screenX, screenY, size * 3, 0, Math.PI * 2);
381
- ctx.fill();
382
- });
383
-
384
- // Draw orbiter body
385
- Painter.useCtx((ctx) => {
386
- const gradient = ctx.createRadialGradient(
387
- screenX - size * 0.3,
388
- screenY - size * 0.3,
389
- 0,
390
- screenX,
391
- screenY,
392
- size,
393
- );
394
- gradient.addColorStop(0, "#fff");
395
- gradient.addColorStop(0.5, CONFIG.orbiterColor);
396
- gradient.addColorStop(1, CONFIG.orbiterGlow);
397
-
398
- ctx.fillStyle = gradient;
399
- ctx.beginPath();
400
- ctx.arc(screenX, screenY, size, 0, Math.PI * 2);
401
- ctx.fill();
402
- });
403
-
404
- // Draw trail (fading arc showing recent path)
405
- this.drawOrbiterTrail(cx, cy);
406
- }
407
-
408
- drawOrbiterTrail(cx, cy) {
409
- // Orbit radius scales with well width (sigma)
410
- const sigma = CONFIG.wellWidth * Math.sqrt(this.body.mass);
411
- const orbitRadius = sigma * CONFIG.orbitRadiusMultiplier;
412
-
413
- const trailLength = 40; // Number of trail segments
414
- const trailArc = Math.PI * 0.8; // How much of the orbit to show as trail
415
-
416
- Painter.useCtx((ctx) => {
417
- ctx.lineCap = "round";
418
-
419
- for (let i = 0; i < trailLength; i++) {
420
- const t = i / trailLength;
421
- const angle = this.orbiterAngle - t * trailArc;
422
-
423
- const trailX = this.body.x + Math.cos(angle) * orbitRadius;
424
- const trailZ = this.body.z + Math.sin(angle) * orbitRadius;
425
- const wellDepth = this.calculateWellDepth(trailX, trailZ);
426
-
427
- const p = this.camera.project(
428
- trailX * CONFIG.gridScale,
429
- wellDepth,
430
- trailZ * CONFIG.gridScale,
431
- );
432
-
433
- if (i === 0) continue;
434
-
435
- const prevAngle =
436
- this.orbiterAngle - ((i - 1) / trailLength) * trailArc;
437
- const prevX = this.body.x + Math.cos(prevAngle) * orbitRadius;
438
- const prevZ = this.body.z + Math.sin(prevAngle) * orbitRadius;
439
- const prevDepth = this.calculateWellDepth(prevX, prevZ);
440
- const prevP = this.camera.project(
441
- prevX * CONFIG.gridScale,
442
- prevDepth,
443
- prevZ * CONFIG.gridScale,
444
- );
445
-
446
- const alpha = (1 - t) * 0.5;
447
- ctx.strokeStyle = `rgba(100, 180, 255, ${alpha})`;
448
- ctx.lineWidth = (1 - t) * 2 * p.scale;
449
- ctx.beginPath();
450
- ctx.moveTo(cx + prevP.x, cy + prevP.y);
451
- ctx.lineTo(cx + p.x, cy + p.y);
452
- ctx.stroke();
453
- }
454
- });
455
- }
456
-
457
- drawBody(cx, cy) {
458
- const body = this.body;
459
- const typeConfig = CONFIG.bodyTypes[body.type];
460
-
461
- const wellDepth = this.calculateWellDepth(body.x, body.z);
462
- const p = this.camera.project(
463
- body.x * CONFIG.gridScale,
464
- wellDepth * 0.7, // Place body in the well
465
- body.z * CONFIG.gridScale,
466
- );
467
-
468
- const screenX = cx + p.x;
469
- const screenY = cy + p.y;
470
-
471
- // Size based on mass and perspective
472
- const baseSize = 8 + body.mass * 6;
473
- const size = baseSize * p.scale;
474
-
475
- // Pulsing glow effect
476
- const pulse =
477
- 0.8 + 0.2 * Math.sin(this.time * CONFIG.pulseSpeed + body.orbitPhase);
478
-
479
- // Draw glow
480
- Painter.useCtx((ctx) => {
481
- const gradient = ctx.createRadialGradient(
482
- screenX,
483
- screenY,
484
- 0,
485
- screenX,
486
- screenY,
487
- size * 3 * pulse,
488
- );
489
- gradient.addColorStop(0, typeConfig.glowColor);
490
- gradient.addColorStop(1, "transparent");
491
-
492
- ctx.fillStyle = gradient;
493
- ctx.beginPath();
494
- ctx.arc(screenX, screenY, size * 3 * pulse, 0, Math.PI * 2);
495
- ctx.fill();
496
- });
497
-
498
- // Draw body
499
- Painter.useCtx((ctx) => {
500
- if (body.type === "blackhole") {
501
- // Black hole: dark center with bright accretion disk effect
502
- ctx.fillStyle = "#000";
503
- ctx.beginPath();
504
- ctx.arc(screenX, screenY, size, 0, Math.PI * 2);
505
- ctx.fill();
506
-
507
- // Event horizon ring
508
- ctx.strokeStyle = "rgba(150, 100, 200, 0.8)";
509
- ctx.lineWidth = 2;
510
- ctx.beginPath();
511
- ctx.arc(screenX, screenY, size * 1.3, 0, Math.PI * 2);
512
- ctx.stroke();
513
- } else {
514
- // Stars and neutron stars: bright center
515
- const gradient = ctx.createRadialGradient(
516
- screenX - size * 0.3,
517
- screenY - size * 0.3,
518
- 0,
519
- screenX,
520
- screenY,
521
- size,
522
- );
523
- gradient.addColorStop(0, "#fff");
524
- gradient.addColorStop(0.3, typeConfig.color);
525
- gradient.addColorStop(1, typeConfig.glowColor);
526
-
527
- ctx.fillStyle = gradient;
528
- ctx.beginPath();
529
- ctx.arc(screenX, screenY, size, 0, Math.PI * 2);
530
- ctx.fill();
531
- }
532
- });
533
- }
534
-
535
- drawControls(w, h) {
536
- Painter.useCtx((ctx) => {
537
- ctx.fillStyle = "#445";
538
- ctx.font = "10px monospace";
539
- ctx.textAlign = "right";
540
- ctx.fillText(
541
- "click to shuffle | drag to rotate | double-click to reset",
542
- w - 15,
543
- h - 30,
544
- );
545
- ctx.fillText(
546
- "Mass curves spacetime | Objects follow geodesics",
547
- w - 15,
548
- h - 15,
549
- );
550
- ctx.textAlign = "left";
551
- });
552
- }
553
- }
554
-
555
- window.addEventListener("load", () => {
556
- const canvas = document.getElementById("game");
557
- const demo = new SpacetimeDemo(canvas);
558
- demo.start();
559
- });