@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,175 @@
1
+ import { Painter } from "../../painter/painter.js";
2
+ import { ZOrderedCollection } from "../../util/zindex.js";
3
+ import { GameObject } from "./go.js";
4
+
5
+ /**
6
+ * Scene - A Hierarchical Container for Game Objects
7
+ *
8
+ * ### Conceptual Overview
9
+ *
10
+ * The Scene class represents a complex spatial management system that goes beyond
11
+ * simple object containment. It serves as a specialized GameObject that:
12
+ *
13
+ * - Creates a local coordinate system
14
+ * - Manages spatial relationships between game objects
15
+ * - Provides dynamic bounds calculation
16
+ * - Handles rendering and updating of child objects
17
+ *
18
+ * ### Rendering Pipeline Integration
19
+ *
20
+ * The Scene operates as a crucial middleware in the rendering pipeline:
21
+ * 1. Inherits transformation capabilities from GameObject
22
+ * 2. Applies collective transformations to child objects
23
+ * 3. Calculates composite bounds dynamically
24
+ *
25
+ * @extends GameObject
26
+ */
27
+ export class Scene extends GameObject {
28
+ constructor(game, options = {}) {
29
+ super(game, options);
30
+ // Create the z-ordered collection
31
+ this._collection = new ZOrderedCollection({
32
+ sortByZIndex: options.sortByZIndex || true,
33
+ });
34
+ this._collection._owner = this;
35
+ // Initialize dimensions and override properties
36
+ this._width = options.width ?? 0;
37
+ this._height = options.height ?? 0;
38
+ this.forceWidth = null; // CRITICAL: Initialize with null
39
+ this.forceHeight = null; // CRITICAL: Initialize with null
40
+ // Cache for natural dimensions
41
+ this._naturalWidth = null;
42
+ this._naturalHeight = null;
43
+ this.userDefinedDimensions = false;
44
+ if (options.width != undefined && options.height != undefined) {
45
+ this.userDefinedWidth = options.width;
46
+ this.userDefinedHeight = options.height;
47
+ this.userDefinedDimensions = true;
48
+ }
49
+ }
50
+
51
+ // Update method - update children and recalculate natural dimensions
52
+ update(dt) {
53
+ this.logger.groupCollapsed(
54
+ "Scene.update: " +
55
+ (this.name == undefined ? this.constructor.name : this.name)
56
+ );
57
+ // Update all active children
58
+ for (let i = 0; i < this.children.length; i++) {
59
+ const child = this.children[i];
60
+ if (child.active && child.update) {
61
+ child.update(dt);
62
+ }
63
+ }
64
+ super.update(dt);
65
+ this.logger.groupEnd();
66
+ }
67
+
68
+ // Add a GameObject to the scene and invalidate dimensions
69
+ add(go) {
70
+ if (go == null || go == undefined) {
71
+ throw new Error("GameObject is null or undefined");
72
+ }
73
+ if (go.parent != null) {
74
+ console.warn(
75
+ "This GameObject already has a parent. Consider removing it first."
76
+ );
77
+ }
78
+ go.parent = this;
79
+ this._collection.add(go);
80
+ this.markBoundsDirty();
81
+ // if the game object has an init method, call it
82
+ if (go.init) {
83
+ go.init();
84
+ }
85
+ return go;
86
+ }
87
+
88
+ markBoundsDirty() {
89
+ super.markBoundsDirty();
90
+ // Dirty the children's bounds
91
+ this.children.forEach((child) => {
92
+ child.markBoundsDirty();
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Removes a GameObject from the Scene
98
+ *
99
+ * ### Removal Strategy
100
+ *
101
+ * #### Why Use Filter?
102
+ * - Creates a new array (immutable approach)
103
+ * - Ensures no direct mutation of the children array
104
+ *
105
+ * #### Parent Reference Clearing
106
+ * - Breaks the parent-child relationship
107
+ * - Allows the object to be re-parented elsewhere
108
+ *
109
+ * @param {GameObject} go - Game object to remove
110
+ */
111
+ remove(go) {
112
+ const result = this._collection.remove(go);
113
+ if (result) {
114
+ go.parent = null;
115
+ this.markBoundsDirty();
116
+ }
117
+ return result;
118
+ }
119
+
120
+ draw() {
121
+ super.draw();
122
+ this.logger.log("Scene.draw chilren:");
123
+ this._collection
124
+ .getSortedChildren()
125
+ .filter((obj) => obj.visible)
126
+ .map(function (obj) {
127
+ Painter.save();
128
+ obj.render();
129
+ Painter.restore();
130
+ return obj;
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Returns debug bounds in local space (centered at origin).
136
+ * Used for debug drawing after transforms have been applied.
137
+ * @returns {{x: number, y: number, width: number, height: number}}
138
+ */
139
+ getDebugBounds() {
140
+ // Return bounds centered at local origin (0, 0)
141
+ // This works because debug is drawn after translation to scene's position
142
+ return {
143
+ width: this.width,
144
+ height: this.height,
145
+ x: -this.width / 2,
146
+ y: -this.height / 2,
147
+ };
148
+ }
149
+
150
+ bringToFront(go) {
151
+ return this._collection.bringToFront(go);
152
+ }
153
+
154
+ sendToBack(go) {
155
+ return this._collection.sendToBack(go);
156
+ }
157
+
158
+ bringForward(go) {
159
+ return this._collection.bringForward(go);
160
+ }
161
+
162
+ sendBackward(go) {
163
+ return this._collection.sendBackward(go);
164
+ }
165
+
166
+ clear() {
167
+ this._collection.children.forEach((go) => this.remove(go));
168
+ return this._collection.clear();
169
+ }
170
+
171
+ // Getter to access children
172
+ get children() {
173
+ return this._collection.children;
174
+ }
175
+ }
@@ -0,0 +1,118 @@
1
+ import { Scene } from "./scene.js";
2
+ import { Painter } from "../../painter/painter.js";
3
+
4
+ /**
5
+ * Scene3D - A Scene that projects children through Camera3D
6
+ *
7
+ * Bridges the GameObject/Scene system with Camera3D, allowing GameObjects
8
+ * to be positioned in 3D space and automatically projected through the camera.
9
+ *
10
+ * Children can have an optional `z` property (defaults to 0 if not present).
11
+ *
12
+ * @example
13
+ * const scene3d = new Scene3D(this, {
14
+ * x: this.width / 2,
15
+ * y: this.height / 2,
16
+ * camera: this.camera,
17
+ * depthSort: true,
18
+ * });
19
+ *
20
+ * const box = new MyBox(this, { x: -100, y: 0 });
21
+ * box.z = -50; // Behind center plane
22
+ * scene3d.add(box);
23
+ *
24
+ * this.pipeline.add(scene3d);
25
+ *
26
+ * @extends Scene
27
+ */
28
+ export class Scene3D extends Scene {
29
+ /**
30
+ * @param {Game} game - Game instance
31
+ * @param {Object} options - Configuration
32
+ * @param {Camera3D} options.camera - Required Camera3D for projection
33
+ * @param {boolean} [options.depthSort=true] - Sort children by depth (back-to-front)
34
+ * @param {boolean} [options.scaleByDepth=true] - Scale children by perspective
35
+ */
36
+ constructor(game, options = {}) {
37
+ super(game, options);
38
+
39
+ if (!options.camera) {
40
+ throw new Error("Scene3D requires a camera option");
41
+ }
42
+
43
+ this.camera = options.camera;
44
+ this.depthSort = options.depthSort ?? true;
45
+ this.scaleByDepth = options.scaleByDepth ?? true;
46
+ }
47
+
48
+ /**
49
+ * Override Scene's draw to project children through Camera3D
50
+ */
51
+ render() {
52
+ // Do NOT call super.render() as it triggers the 2D Scene.draw() pass,
53
+ // causing double rendering (one 2D, one 3D).
54
+
55
+ if (!this.visible) return;
56
+
57
+ Painter.save();
58
+ // Translate to the Scene3D's position (e.g., center of screen)
59
+ // This defines the "origin" (0,0) of the 3D projection on the 2D canvas.
60
+ Painter.translateTo(this.x, this.y);
61
+
62
+ // Build render list with projections
63
+ const renderList = [];
64
+
65
+ for (const child of this._collection.getSortedChildren()) {
66
+ if (!child.visible) continue;
67
+
68
+ const z = child.z ?? 0;
69
+ const projected = this.camera.project(child.x, child.y, z);
70
+
71
+ // Cull if behind camera
72
+ if (projected.z < -this.camera.perspective + 10) continue;
73
+
74
+ renderList.push({
75
+ child,
76
+ x: projected.x,
77
+ y: projected.y,
78
+ z: projected.z,
79
+ scale: projected.scale,
80
+ });
81
+ }
82
+
83
+ // Sort order: respect zIndex first, then depth for subtle overlap stability.
84
+ // Higher zIndex should render later (on top).
85
+ if (this.depthSort) {
86
+ renderList.sort((a, b) => {
87
+ const za = a.child.zIndex ?? 0;
88
+ const zb = b.child.zIndex ?? 0;
89
+ if (za !== zb) return za - zb;
90
+ return b.z - a.z; // back-to-front by depth as tie-breaker
91
+ });
92
+ }
93
+
94
+ // Render each child at projected position
95
+ for (const item of renderList) {
96
+ Painter.save();
97
+
98
+ // 1. Move to the projected screen position
99
+ Painter.translateTo(item.x, item.y);
100
+
101
+ // 2. Apply perspective scale
102
+ if (this.scaleByDepth) {
103
+ Painter.ctx.scale(item.scale, item.scale);
104
+ }
105
+
106
+ // 3. Neutralize the child's own translation logic
107
+ // GameObject.render() will translate by (child.x, child.y).
108
+ // By applying (-child.x, -child.y) first (in the scaled context),
109
+ // we ensure the net visual position is exactly at (item.x, item.y).
110
+ Painter.translateTo(-item.child.x, -item.child.y);
111
+
112
+ item.child.render();
113
+ Painter.restore();
114
+ }
115
+
116
+ Painter.restore();
117
+ }
118
+ }
@@ -0,0 +1,221 @@
1
+ /************************************************************
2
+ * Text.js
3
+ *
4
+ * A simple GameObject for drawing styled text on the canvas.
5
+ * Supports optional stroke, alignment, and baseline.
6
+ ************************************************************/
7
+
8
+ import { GameObject } from "./go.js";
9
+ import { GameObjectShapeWrapper } from "./wrapper.js";
10
+ import { Painter } from "../../painter/painter.js";
11
+ import { TextShape } from "../../shapes/text.js";
12
+
13
+ /**
14
+ * Text - Renders a string onto the canvas using the Painter API.
15
+ * @extends GameObjectShapeWrapper
16
+ *
17
+ * Creates a GameObject that wraps a TextShape, providing a convenient API
18
+ * for text rendering with support for styling, alignment, and measurement.
19
+ *
20
+ * @example
21
+ * ```js
22
+ * const myText = new Text(game, "Hello World!", {
23
+ * x: 100,
24
+ * y: 50,
25
+ * font: "20px Arial",
26
+ * color: "#ff0",
27
+ * align: "center",
28
+ * baseline: "middle",
29
+ * stroke: true,
30
+ * strokeColor: "#000",
31
+ * lineWidth: 2,
32
+ * });
33
+ * game.pipeline.add(myText);
34
+ * ```
35
+ */
36
+ export class Text extends GameObjectShapeWrapper {
37
+ /**
38
+ * Create a Text component
39
+ * @param {Game} game - The main game instance
40
+ * @param {string} text - The text content to display
41
+ * @param {object} [options={}] - Configuration options
42
+ * @param {number} [options.x=0] - X-position
43
+ * @param {number} [options.y=0] - Y-position
44
+ * @param {string} [options.font="16px monospace"] - CSS-style font string
45
+ * @param {string} [options.color="#fff"] - Text color
46
+ * @param {string} [options.align="left"] - Text alignment
47
+ * @param {string} [options.baseline="top"] - Text baseline
48
+ * @param {boolean} [options.stroke=false] - Whether to stroke the text
49
+ * @param {string} [options.strokeColor="#000"] - Stroke color
50
+ * @param {number} [options.lineWidth=1] - Stroke width
51
+ * @param {boolean} [options.interactive=false] - Whether the text should be interactive
52
+ * @param {string} [options.anchor] - Optional anchor position (e.g., "top-left", "center")
53
+ * @param {number} [options.padding] - Padding when using anchors
54
+ */
55
+ constructor(game, text, options = {}) {
56
+ // Create the TextShape
57
+ const textShape = new TextShape(text, {
58
+ font: options.font || "16px monospace",
59
+ color: options.color || "yellow",
60
+ align: options.align || "left",
61
+ baseline: options.baseline || "top",
62
+ strokeColor: options.strokeColor || "#000",
63
+ lineWidth: options.lineWidth || 1,
64
+ debugColor: options.debugColor || "yellow",
65
+ });
66
+ // Pass the shape to the parent GameObjectShapeWrapper
67
+ super(game, textShape, options);
68
+ // Store reference to text-specific options for direct access
69
+ this._textOptions = {
70
+ font: options.font || "16px monospace",
71
+ color: options.color || "yellow",
72
+ align: options.align || "left",
73
+ baseline: options.baseline || "top",
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Get the text content
79
+ * @returns {string} Current text
80
+ */
81
+ get text() {
82
+ return this.shape.text;
83
+ }
84
+
85
+ /**
86
+ * Set the text content
87
+ * @param {string} value - New text to display
88
+ */
89
+ set text(value) {
90
+ this.shape.text = value;
91
+ this.markBoundsDirty();
92
+ }
93
+
94
+ /**
95
+ * Get the font style
96
+ * @returns {string} Current font
97
+ */
98
+ get font() {
99
+ return this.shape.font;
100
+ }
101
+
102
+ /**
103
+ * Set the font style
104
+ * @param {string} value - New font style
105
+ */
106
+ set font(value) {
107
+ this.shape.font = value;
108
+ this._textOptions.font = value;
109
+ this.markBoundsDirty();
110
+ }
111
+
112
+ /**
113
+ * Get the text color
114
+ * @returns {string} Current color
115
+ */
116
+ get color() {
117
+ return this.shape.color;
118
+ }
119
+
120
+ /**
121
+ * Set the text color
122
+ * @param {string} value - New color
123
+ */
124
+ set color(value) {
125
+ this.shape.color = value;
126
+ this._textOptions.color = value;
127
+ }
128
+
129
+ /**
130
+ * Get text alignment
131
+ * @returns {string} Current alignment
132
+ */
133
+ get align() {
134
+ return this.shape.align;
135
+ }
136
+
137
+ /**
138
+ * Set text alignment
139
+ * @param {string} value - New alignment
140
+ */
141
+ set align(value) {
142
+ this.shape.align = value;
143
+ this._textOptions.align = value;
144
+ this.markBoundsDirty();
145
+ }
146
+
147
+ /**
148
+ * Get text baseline
149
+ * @returns {string} Current baseline
150
+ */
151
+ get baseline() {
152
+ return this.shape.baseline;
153
+ }
154
+
155
+ /**
156
+ * Set text baseline
157
+ * @param {string} value - New baseline
158
+ */
159
+ set baseline(value) {
160
+ this.shape.baseline = value;
161
+ this._textOptions.baseline = value;
162
+ this.markBoundsDirty();
163
+ }
164
+
165
+ /**
166
+ * Calculate the width based on the text content
167
+ * @returns {number} Approximate width of the text
168
+ */
169
+ measureWidth() {
170
+ if (!Painter.ctx) return 0;
171
+ const width = Painter.text.measureTextWidth(this.text, this.font);
172
+ return width;
173
+ }
174
+
175
+ /**
176
+ * Calculate the height based on the font size
177
+ * @returns {number} Approximate height of the text
178
+ */
179
+ measureHeight() {
180
+ if (!this.font) return 16; // Default font size as fallback
181
+
182
+ // Extract font size from font string (e.g., "16px Arial" → 16)
183
+ const fontSize = parseInt(this.font);
184
+ return isNaN(fontSize) ? 16 : fontSize;
185
+ }
186
+
187
+ /**
188
+ * Gets the text bounds accounting for alignment and baseline
189
+ * @returns {Object} Bounds object with { x, y, width, height }
190
+ */
191
+ getBounds() {
192
+ const bounds = super.getBounds();
193
+
194
+ // If the shape has alignment-aware bounds, use those
195
+ if (this.shape.getTextBounds) {
196
+ const textBounds = this.shape.getTextBounds();
197
+ return {
198
+ x: this.x,
199
+ y: this.y,
200
+ width: textBounds.width,
201
+ height: textBounds.height,
202
+ };
203
+ }
204
+
205
+ return bounds;
206
+ }
207
+
208
+ /**
209
+ * Updates the GameObject and the wrapped TextShape
210
+ * @param {number} dt - Delta time in seconds
211
+ */
212
+ update(dt) {
213
+ super.update(dt);
214
+
215
+ // Sync dimensions from the text shape
216
+ if (this.shape) {
217
+ this.width = this.shape.width || this.measureWidth();
218
+ this.height = this.shape.height || this.measureHeight();
219
+ }
220
+ }
221
+ }