@guinetik/gcanvas 1.0.0 → 1.0.2

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 (102) hide show
  1. package/demos/coordinates.html +698 -0
  2. package/demos/cube3d.html +23 -0
  3. package/demos/demos.css +17 -3
  4. package/demos/dino.html +42 -0
  5. package/demos/fluid-simple.html +22 -0
  6. package/demos/fluid.html +37 -0
  7. package/demos/gameobjects.html +626 -0
  8. package/demos/index.html +19 -7
  9. package/demos/js/blob.js +18 -5
  10. package/demos/js/coordinates.js +840 -0
  11. package/demos/js/cube3d.js +789 -0
  12. package/demos/js/dino.js +1420 -0
  13. package/demos/js/fluid-simple.js +253 -0
  14. package/demos/js/fluid.js +527 -0
  15. package/demos/js/gameobjects.js +176 -0
  16. package/demos/js/plane3d.js +256 -0
  17. package/demos/js/platformer.js +1579 -0
  18. package/demos/js/sphere3d.js +229 -0
  19. package/demos/js/sprite.js +473 -0
  20. package/demos/js/tde/accretiondisk.js +65 -12
  21. package/demos/js/tde/blackholescene.js +2 -2
  22. package/demos/js/tde/config.js +2 -2
  23. package/demos/js/tde/index.js +152 -27
  24. package/demos/js/tde/lensedstarfield.js +32 -25
  25. package/demos/js/tde/tdestar.js +78 -98
  26. package/demos/js/tde/tidalstream.js +24 -8
  27. package/demos/plane3d.html +24 -0
  28. package/demos/platformer.html +43 -0
  29. package/demos/sphere3d.html +24 -0
  30. package/demos/sprite.html +18 -0
  31. package/docs/README.md +230 -222
  32. package/docs/api/FluidSystem.md +173 -0
  33. package/docs/concepts/architecture-overview.md +204 -204
  34. package/docs/concepts/coordinate-system.md +384 -0
  35. package/docs/concepts/rendering-pipeline.md +279 -279
  36. package/docs/concepts/shapes-vs-gameobjects.md +187 -0
  37. package/docs/concepts/two-layer-architecture.md +229 -229
  38. package/docs/fluid-dynamics.md +99 -0
  39. package/docs/getting-started/first-game.md +354 -354
  40. package/docs/getting-started/installation.md +175 -157
  41. package/docs/modules/collision/README.md +2 -2
  42. package/docs/modules/fluent/README.md +6 -6
  43. package/docs/modules/game/README.md +303 -303
  44. package/docs/modules/isometric-camera.md +2 -2
  45. package/docs/modules/isometric.md +1 -1
  46. package/docs/modules/painter/README.md +328 -328
  47. package/docs/modules/particle/README.md +3 -3
  48. package/docs/modules/shapes/README.md +221 -221
  49. package/docs/modules/shapes/base/euclidian.md +123 -123
  50. package/docs/modules/shapes/base/shape.md +262 -262
  51. package/docs/modules/shapes/base/transformable.md +243 -243
  52. package/docs/modules/state/README.md +2 -2
  53. package/docs/modules/util/README.md +1 -1
  54. package/docs/modules/util/camera3d.md +3 -3
  55. package/docs/modules/util/scene3d.md +1 -1
  56. package/package.json +3 -1
  57. package/readme.md +19 -5
  58. package/src/collision/collision.js +75 -0
  59. package/src/game/game.js +11 -5
  60. package/src/game/index.js +2 -1
  61. package/src/game/objects/index.js +3 -0
  62. package/src/game/objects/platformer-scene.js +411 -0
  63. package/src/game/objects/scene.js +14 -0
  64. package/src/game/objects/sprite.js +529 -0
  65. package/src/game/pipeline.js +20 -16
  66. package/src/game/systems/FluidSystem.js +835 -0
  67. package/src/game/systems/index.js +11 -0
  68. package/src/game/ui/button.js +39 -18
  69. package/src/game/ui/cursor.js +14 -0
  70. package/src/game/ui/fps.js +12 -4
  71. package/src/game/ui/index.js +2 -0
  72. package/src/game/ui/stepper.js +549 -0
  73. package/src/game/ui/theme.js +123 -0
  74. package/src/game/ui/togglebutton.js +9 -3
  75. package/src/game/ui/tooltip.js +11 -4
  76. package/src/io/input.js +75 -45
  77. package/src/io/mouse.js +44 -19
  78. package/src/io/touch.js +35 -12
  79. package/src/math/fluid.js +507 -0
  80. package/src/math/index.js +2 -0
  81. package/src/mixins/anchor.js +17 -7
  82. package/src/motion/tweenetik.js +16 -0
  83. package/src/shapes/cube3d.js +599 -0
  84. package/src/shapes/index.js +3 -0
  85. package/src/shapes/plane3d.js +687 -0
  86. package/src/shapes/sphere3d.js +75 -6
  87. package/src/util/camera2d.js +315 -0
  88. package/src/util/camera3d.js +218 -12
  89. package/src/util/index.js +1 -0
  90. package/src/webgl/shaders/plane-shaders.js +332 -0
  91. package/src/webgl/shaders/sphere-shaders.js +4 -2
  92. package/types/fluent.d.ts +361 -0
  93. package/types/game.d.ts +303 -0
  94. package/types/index.d.ts +144 -5
  95. package/types/math.d.ts +361 -0
  96. package/types/motion.d.ts +271 -0
  97. package/types/particle.d.ts +373 -0
  98. package/types/shapes.d.ts +107 -9
  99. package/types/util.d.ts +353 -0
  100. package/types/webgl.d.ts +109 -0
  101. package/disk_example.png +0 -0
  102. package/tde.png +0 -0
@@ -0,0 +1,384 @@
1
+ # Coordinate System
2
+
3
+ GCanvas uses a **center-based coordinate system** where `x` and `y` refer to an object's center point, not its top-left corner. This guide explains how positioning works throughout the library.
4
+
5
+ ## TL;DR
6
+
7
+ | Situation | How It Works |
8
+ |-----------|--------------|
9
+ | Object at `(100, 100)` | Center is at screen position (100, 100) |
10
+ | Child at `(10, 20)` in a Scene at `(100, 100)` | Child center is at screen position (110, 120) |
11
+ | Camera3D `project()` returns `(0, 0)` | Object is at the center of the 3D view |
12
+ | Scene3D at `(width/2, height/2)` | 3D origin appears at canvas center |
13
+
14
+ ---
15
+
16
+ ## 1. Center-Based Positioning
17
+
18
+ Unlike many graphics libraries that use top-left corners, GCanvas positions objects by their **center point**. This makes rotation and scaling more intuitive since transforms happen around the object's center.
19
+
20
+ ```javascript
21
+ // This circle's CENTER is at (100, 100)
22
+ const circle = new Circle(50, { x: 100, y: 100, color: "#0f0" });
23
+
24
+ // The circle extends from:
25
+ // - Left edge: 50 (100 - radius)
26
+ // - Right edge: 150 (100 + radius)
27
+ // - Top edge: 50 (100 - radius)
28
+ // - Bottom edge: 150 (100 + radius)
29
+ ```
30
+
31
+ ### Comparison with Top-Left Systems
32
+
33
+ | System | `x: 100, y: 100` means... | Rotation pivot |
34
+ |--------|---------------------------|----------------|
35
+ | **GCanvas (center)** | Center at (100, 100) | Around center |
36
+ | Top-left systems | Top-left at (100, 100) | Around top-left corner |
37
+
38
+ **Why center-based?** When you rotate or scale an object, it transforms around its center naturally. No need to manually adjust the pivot point.
39
+
40
+ ---
41
+
42
+ ## 2. Basic Positioning
43
+
44
+ When you add an object directly to the pipeline, its coordinates are **absolute screen coordinates**.
45
+
46
+ ```javascript
47
+ class MyGame extends Game {
48
+ init() {
49
+ super.init();
50
+
51
+ // Circle centered at screen position (200, 150)
52
+ const circle = new Circle(30, { x: 200, y: 150, color: "#0ff" });
53
+ this.pipeline.add(circle);
54
+
55
+ // Rectangle centered at screen position (400, 300)
56
+ const rect = new Rectangle({
57
+ x: 400, y: 300,
58
+ width: 100, height: 60,
59
+ color: "#f0f"
60
+ });
61
+ this.pipeline.add(rect);
62
+ }
63
+ }
64
+ ```
65
+
66
+ Canvas origin `(0, 0)` is the **top-left corner**:
67
+ - **X increases** going right
68
+ - **Y increases** going down
69
+
70
+ ---
71
+
72
+ ## 3. Parent-Child Relationships
73
+
74
+ When you add objects to a **Scene**, their coordinates become **relative to the parent's center**.
75
+
76
+ ```javascript
77
+ // Scene centered at (200, 200)
78
+ const scene = new Scene(this, { x: 200, y: 200 });
79
+
80
+ // Child at (50, 30) relative to scene
81
+ const child = new Circle(20, { x: 50, y: 30, color: "#0f0" });
82
+ scene.add(child);
83
+
84
+ // Child's screen position: (200 + 50, 200 + 30) = (250, 230)
85
+ ```
86
+
87
+ ### Visual Diagram
88
+
89
+ ```
90
+ Canvas (0,0)───────────────────────────────►X
91
+
92
+ │ Scene at (200, 200)
93
+ │ ┌─────────────────────┐
94
+ │ │ Scene's local │
95
+ │ │ origin (0, 0) │
96
+ │ │ ●───────────┼──► Scene's X
97
+ │ │ │ │
98
+ │ │ │ Child at │
99
+ │ │ │ (50, 30) │
100
+ │ │ ▼ ◉ │
101
+ │ │ Scene's Y │
102
+ │ └─────────────────────┘
103
+
104
+
105
+ Y
106
+ ```
107
+
108
+ When the Scene renders:
109
+ 1. Canvas translates to `(200, 200)` — Scene's position
110
+ 2. Child renders at `(50, 30)` — relative to Scene's origin
111
+ 3. Final screen position: `(250, 230)`
112
+
113
+ ### Nested Scenes
114
+
115
+ Coordinates stack through the hierarchy:
116
+
117
+ ```javascript
118
+ const outer = new Scene(this, { x: 100, y: 100 });
119
+ const inner = new Scene(this, { x: 50, y: 50 });
120
+ const shape = new Circle(10, { x: 25, y: 25, color: "#ff0" });
121
+
122
+ inner.add(shape);
123
+ outer.add(inner);
124
+ this.pipeline.add(outer);
125
+
126
+ // Screen position calculation:
127
+ // outer.x + inner.x + shape.x = 100 + 50 + 25 = 175
128
+ // outer.y + inner.y + shape.y = 100 + 50 + 25 = 175
129
+ // Shape appears at screen (175, 175)
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 4. Camera3D and Scene3D
135
+
136
+ When using 3D projection, an additional coordinate transformation layer is introduced.
137
+
138
+ ### How Camera3D.project() Works
139
+
140
+ `Camera3D.project(x, y, z)` transforms a 3D world position to 2D screen coordinates:
141
+
142
+ ```javascript
143
+ const camera = new Camera3D({ perspective: 800 });
144
+
145
+ // Object at 3D position (100, 0, 200)
146
+ const projected = camera.project(100, 0, 200);
147
+ // Returns: { x: 80, y: 0, z: 200, scale: 0.8 }
148
+ ```
149
+
150
+ **Key insight:** The returned `x` and `y` are offsets **from the center of the view**, not absolute screen coordinates. An object at world origin `(0, 0, 0)` projects to screen `(0, 0)`.
151
+
152
+ ### Why Scene3D Centers the View
153
+
154
+ Since Camera3D returns origin-centered coordinates, Scene3D must translate to the canvas center:
155
+
156
+ ```javascript
157
+ // Scene3D at canvas center
158
+ const scene3d = new Scene3D(this, {
159
+ x: this.width / 2, // 400 for 800px canvas
160
+ y: this.height / 2, // 300 for 600px canvas
161
+ camera: this.camera
162
+ });
163
+ ```
164
+
165
+ This means:
166
+ - 3D origin `(0, 0, 0)` appears at screen center
167
+ - Objects with positive X move right of center
168
+ - Objects with positive Y move down from center
169
+ - Objects with positive Z are further away (smaller due to perspective)
170
+
171
+ ### Code Example: Basic Scene3D
172
+
173
+ ```javascript
174
+ import { Game, Scene3D, Sphere3D, Camera3D } from "gcanvas";
175
+
176
+ class My3DDemo extends Game {
177
+ init() {
178
+ super.init();
179
+
180
+ // Create camera with perspective
181
+ this.camera = new Camera3D({
182
+ perspective: 800,
183
+ rotationY: 0.3
184
+ });
185
+
186
+ // Scene3D MUST be centered for 3D projection to work correctly
187
+ this.scene = new Scene3D(this, {
188
+ x: this.width / 2,
189
+ y: this.height / 2,
190
+ camera: this.camera
191
+ });
192
+
193
+ // Sphere at 3D origin - appears at screen center
194
+ const sphere1 = new Sphere3D(50, {
195
+ x: 0, y: 0, z: 0,
196
+ color: "#0ff",
197
+ camera: this.camera
198
+ });
199
+
200
+ // Sphere offset in 3D space - appears right of center
201
+ const sphere2 = new Sphere3D(30, {
202
+ x: 150, y: 0, z: 0,
203
+ color: "#f0f",
204
+ camera: this.camera
205
+ });
206
+
207
+ this.scene.add(sphere1);
208
+ this.scene.add(sphere2);
209
+ this.pipeline.add(this.scene);
210
+ }
211
+
212
+ update(dt) {
213
+ super.update(dt);
214
+ this.camera.update(dt); // For auto-rotation/inertia
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Common Gotcha: Manual Centering
220
+
221
+ If you're rendering 3D-projected content **outside** of Scene3D, you must manually center:
222
+
223
+ ```javascript
224
+ // In ParticleSystem.renderWithDepthSort():
225
+ // When NOT inside a Scene3D, we must translate to center ourselves
226
+ if (!this.worldSpace && !isInsideScene3D) {
227
+ ctx.save();
228
+ ctx.translate(this.game.width / 2, this.game.height / 2);
229
+ // ... render particles at projected positions ...
230
+ ctx.restore();
231
+ }
232
+ ```
233
+
234
+ Similarly, `Sphere3D` with shader rendering extracts the current transform to find the scene center:
235
+
236
+ ```javascript
237
+ // From sphere3d.js - getting scene center for WebGL compositing
238
+ const transform = ctx.getTransform();
239
+ const sceneX = transform.e; // Translation X (scene center)
240
+ const sceneY = transform.f; // Translation Y (scene center)
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 5. Anchoring and Layouts
246
+
247
+ ### Position Anchoring
248
+
249
+ The `applyAnchor` mixin positions objects relative to the game canvas or a parent container:
250
+
251
+ ```javascript
252
+ import { Text, applyAnchor, Position } from "gcanvas";
253
+
254
+ // Anchor text to top-center of canvas
255
+ const title = new Text(this, "Game Title", { font: "24px sans-serif" });
256
+ applyAnchor(title, {
257
+ anchor: Position.TOP_CENTER,
258
+ anchorMargin: 20 // 20px from top edge
259
+ });
260
+ this.pipeline.add(title);
261
+ ```
262
+
263
+ Available positions:
264
+ - `Position.TOP_LEFT`, `Position.TOP_CENTER`, `Position.TOP_RIGHT`
265
+ - `Position.CENTER_LEFT`, `Position.CENTER`, `Position.CENTER_RIGHT`
266
+ - `Position.BOTTOM_LEFT`, `Position.BOTTOM_CENTER`, `Position.BOTTOM_RIGHT`
267
+
268
+ ### Relative Anchoring
269
+
270
+ Anchor relative to another object:
271
+
272
+ ```javascript
273
+ const panel = new Scene(this, { x: 200, y: 200, width: 300, height: 200 });
274
+
275
+ const label = new Text(this, "Panel Title", {});
276
+ applyAnchor(label, {
277
+ anchor: Position.TOP_CENTER,
278
+ anchorRelative: panel, // Position relative to panel
279
+ anchorMargin: 10
280
+ });
281
+
282
+ panel.add(label);
283
+ ```
284
+
285
+ ### Layout Utilities
286
+
287
+ Layouts automatically position multiple items:
288
+
289
+ ```javascript
290
+ import { verticalLayout, applyLayout, Scene, Text } from "gcanvas";
291
+
292
+ const items = [
293
+ new Text(this, "Option 1", {}),
294
+ new Text(this, "Option 2", {}),
295
+ new Text(this, "Option 3", {})
296
+ ];
297
+
298
+ // Compute vertical layout
299
+ const layout = verticalLayout(items, {
300
+ spacing: 15,
301
+ padding: 10,
302
+ align: "center"
303
+ });
304
+
305
+ // Apply positions to items
306
+ applyLayout(items, layout.positions);
307
+
308
+ // Add to scene (layout is centered by offset)
309
+ const menu = new Scene(this, { x: this.width / 2, y: this.height / 2 });
310
+ items.forEach(item => menu.add(item));
311
+ ```
312
+
313
+ **How layouts work internally:**
314
+ 1. Layouts compute positions in a top-left coordinate system starting at `(0, 0)`
315
+ 2. `applyLayout` applies these positions plus any offset
316
+ 3. LayoutScene subclasses (like `VerticalLayout`) apply a centering offset:
317
+ - Vertical: `offsetY = -totalHeight / 2`
318
+ - Horizontal: `offsetX = -totalWidth / 2`
319
+
320
+ ---
321
+
322
+ ## 6. Practical Tips
323
+
324
+ ### Quick Reference
325
+
326
+ | I want to... | Do this |
327
+ |--------------|---------|
328
+ | Place object at screen position | Set `x`, `y` directly, add to pipeline |
329
+ | Place object relative to parent | Add to Scene, set local `x`, `y` |
330
+ | Center object on screen | `x: game.width / 2, y: game.height / 2` |
331
+ | Anchor to screen edge | Use `applyAnchor` with `Position` constant |
332
+ | Use 3D projection | Use `Scene3D` centered at canvas center |
333
+ | Position particles with Camera3D | Set `camera` and `depthSort: true` on ParticleSystem |
334
+
335
+ ### Common Mistakes
336
+
337
+ **1. Forgetting to center Scene3D**
338
+ ```javascript
339
+ // WRONG - 3D origin appears at top-left
340
+ const scene = new Scene3D(this, { x: 0, y: 0, camera });
341
+
342
+ // CORRECT - 3D origin appears at canvas center
343
+ const scene = new Scene3D(this, {
344
+ x: this.width / 2,
345
+ y: this.height / 2,
346
+ camera
347
+ });
348
+ ```
349
+
350
+ **2. Confusing local and screen coordinates**
351
+ ```javascript
352
+ const scene = new Scene(this, { x: 100, y: 100 });
353
+ const child = new Circle(20, { x: 50, y: 50 });
354
+ scene.add(child);
355
+
356
+ // WRONG assumption: child is at screen (50, 50)
357
+ // CORRECT: child is at screen (150, 150)
358
+ ```
359
+
360
+ **3. Not updating Camera3D in game loop**
361
+ ```javascript
362
+ update(dt) {
363
+ super.update(dt);
364
+ // If using auto-rotation or inertia, camera needs update
365
+ this.camera.update(dt);
366
+ }
367
+ ```
368
+
369
+ **4. Using world coordinates with Camera3D outside Scene3D**
370
+ ```javascript
371
+ // If using Camera3D projection directly (not in Scene3D),
372
+ // remember to translate to canvas center before drawing
373
+ Painter.useCtx((ctx) => {
374
+ ctx.translate(this.width / 2, this.height / 2);
375
+ // Now draw at projected coordinates
376
+ });
377
+ ```
378
+
379
+ ---
380
+
381
+ ## See Also
382
+
383
+ - [Shapes vs GameObjects](./shapes-vs-gameobjects.md) - Understanding the two hierarchies
384
+ - [Rendering Pipeline](./rendering-pipeline.md) - How objects are rendered each frame