@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,220 @@
1
+ import { Transformable } from "../../shapes/transformable.js";
2
+ import { EventEmitter } from "../../io/index.js";
3
+ import { applyAnchor } from "../../mixins/anchor.js";
4
+ /**
5
+ * GameObject
6
+ * ----------
7
+ *
8
+ * A dynamic canvas entity with interactivity, state, and update/render lifecycle.
9
+ * Builds upon `Transformable` and adds:
10
+ * - Event handling via `EventEmitter`
11
+ * - Bounding box-based hit detection
12
+ * - Game instance linkage
13
+ * - Parent reference for scene graph traversal
14
+ *
15
+ * ### Modernized Interactivity
16
+ *
17
+ * Previous versions used a `Shape` for hit testing. This version uses
18
+ * the object's bounding box (`getBounds()`) for collision/interactivity.
19
+ *
20
+ * ### Event Model
21
+ * Events are managed via the internal emitter. Use:
22
+ * ```js
23
+ * obj.on("click", cb)
24
+ * obj.off("mouseover", cb)
25
+ * ```
26
+ *
27
+ * ### Typical Usage
28
+ * ```js
29
+ * const box = new GameObject(game, { x: 100, y: 100, width: 200, height: 80 });
30
+ * box.on("click", () => console.log("clicked"));
31
+ * ```
32
+ *
33
+ * @abstract
34
+ * @extends Transformable
35
+ */
36
+ export class GameObject extends Transformable {
37
+ /**
38
+ * @param {Game} game - Game instance reference
39
+ * @param {Object} [options={}] - Configuration and styling
40
+ */
41
+ constructor(game, options = {}) {
42
+ super(options);
43
+ this.game = game;
44
+ this.parent = null;
45
+ /** @type {EventEmitter} */
46
+ this.events = new EventEmitter();
47
+ this._interactive = options.interactive ?? false;
48
+ this._hovered = false;
49
+ if (options.anchor) {
50
+ applyAnchor(this, options);
51
+ }
52
+ }
53
+
54
+ update(dt) {
55
+ this.logger.groupCollapsed(
56
+ "GameObject.update: " +
57
+ (this.name == undefined ? this.constructor.name : this.name)
58
+ );
59
+ super.update(dt);
60
+ this.logger.groupEnd();
61
+ }
62
+
63
+ /**
64
+ * Enable or disable hit-testing for this GameObject.
65
+ * When enabled, _hitTest() will run during interaction checks.
66
+ * @type {boolean}
67
+ */
68
+ get interactive() {
69
+ return this._interactive;
70
+ }
71
+
72
+ set interactive(value) {
73
+ const newValue = Boolean(value);
74
+
75
+ // Only proceed if there's an actual change
76
+ if (this._interactive !== newValue) {
77
+ // Store the new state
78
+ this._interactive = newValue;
79
+
80
+ if (newValue === true) {
81
+ // Object is becoming interactive
82
+ this._enableEvents();
83
+ } else {
84
+ // Object is becoming non-interactive
85
+ this._disableEvents();
86
+
87
+ // Also reset hover state if it was previously hovered
88
+ if (this._hovered) {
89
+ this._hovered = false;
90
+ this.events.emit("mouseout");
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Enable event handling for this GameObject.
98
+ * @private
99
+ */
100
+ _enableEvents() {
101
+ // No need to create event handlers here
102
+ // The Pipeline class already handles dispatching events to interactive objects
103
+ // We just need to mark the object as interactive
104
+ this.logger.log(`${this.constructor.name} is now interactive`);
105
+ }
106
+
107
+ /**
108
+ * Disable event handling for this GameObject.
109
+ * @private
110
+ */
111
+ _disableEvents() {
112
+ // Clean up any specific event state
113
+ this.logger.log(`${this.constructor.name} is no longer interactive`);
114
+ }
115
+
116
+ /**
117
+ * True if the pointer is currently hovering over the object.
118
+ * @type {boolean}
119
+ * @readonly
120
+ */
121
+ get hovered() {
122
+ return this._hovered;
123
+ }
124
+
125
+ set hovered(value) {
126
+ this._hovered = Boolean(value);
127
+ }
128
+
129
+ /** Internal use by input system */
130
+ _setHovered(state) {
131
+ this._hovered = Boolean(state);
132
+ }
133
+
134
+ /**
135
+ * Test whether a given point lies inside the object's bounds,
136
+ * taking into account the full transformation hierarchy (position, rotation, scale).
137
+ *
138
+ * @param {number} x - X screen coordinate
139
+ * @param {number} y - Y screen coordinate
140
+ * @returns {boolean} True if the point is inside this object's bounds
141
+ */
142
+ _hitTest(x, y) {
143
+ if (!this._interactive) return false;
144
+
145
+ const bounds = this.getBounds?.();
146
+ if (!bounds) return false;
147
+
148
+ // Transform point from screen space to object's local space
149
+ let localX = x;
150
+ let localY = y;
151
+
152
+ // Build transform chain (from root to this object)
153
+ const transformChain = [];
154
+ let current = this;
155
+ while (current) {
156
+ transformChain.unshift(current);
157
+ current = current.parent;
158
+ }
159
+
160
+ // Apply inverse transforms in sequence (from root to object)
161
+ for (const obj of transformChain) {
162
+ // Translation: subtract object position
163
+ localX -= obj.x || 0;
164
+ localY -= obj.y || 0;
165
+
166
+ // Rotation: apply inverse rotation if needed
167
+ if (obj.rotation) {
168
+ const cos = Math.cos(-obj.rotation);
169
+ const sin = Math.sin(-obj.rotation);
170
+ const tempX = localX;
171
+ localX = tempX * cos - localY * sin;
172
+ localY = tempX * sin + localY * cos;
173
+ }
174
+
175
+ // Scale: apply inverse scale if needed
176
+ if (obj.scaleX !== undefined && obj.scaleX !== 0) {
177
+ localX /= obj.scaleX;
178
+ }
179
+ if (obj.scaleY !== undefined && obj.scaleY !== 0) {
180
+ localY /= obj.scaleY;
181
+ }
182
+ }
183
+
184
+ // Now check if the point is inside our local bounds
185
+ // Use bounds from getBounds() if available, otherwise fall back to this.width/height
186
+ const halfW = (bounds.width || this.width || 0) / 2;
187
+ const halfH = (bounds.height || this.height || 0) / 2;
188
+
189
+ return (
190
+ localX >= -halfW && localX <= halfW && localY >= -halfH && localY <= halfH
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Attach an event handler.
196
+ * @param {string} event - Event name
197
+ * @param {Function} callback - Callback function
198
+ */
199
+ on(event, callback) {
200
+ this.events.on(event, callback);
201
+ }
202
+
203
+ /**
204
+ * Remove an event handler.
205
+ * @param {string} event - Event name
206
+ * @param {Function} callback - Callback function
207
+ */
208
+ off(event, callback) {
209
+ this.events.off(event, callback);
210
+ }
211
+
212
+ /**
213
+ * Dispatch an event manually.
214
+ * @param {string} event - Event name
215
+ * @param {...any} args - Event arguments
216
+ */
217
+ emit(event, ...args) {
218
+ this.events.emit(event, ...args);
219
+ }
220
+ }
@@ -0,0 +1,30 @@
1
+ import { ImageShape } from "../../shapes/image";
2
+ import { GameObjectShapeWrapper } from ".";
3
+
4
+ export class ImageGo extends GameObjectShapeWrapper {
5
+ /**
6
+ * Quickly drop a bitmap into the pipeline.
7
+ *
8
+ * @param {Game} game – the main Game instance
9
+ * @param {ImageData|HTMLImageElement|HTMLCanvasElement|Uint8ClampedArray} bitmap
10
+ * @param {object} [options] – BitmapShape & GO options (x, y, scale, anchor, …)
11
+ *
12
+ * The constructor:
13
+ * 1. creates / re‑uses a BitmapShape,
14
+ * 2. hands it to GameObjectShapeWrapper,
15
+ * 3. propagates any extra options (anchor, padding, debug …).
16
+ */
17
+ constructor(game, bitmap, options = {}) {
18
+ // If the user passed an existing BitmapShape we keep it,
19
+ // otherwise we fabricate one.
20
+ const shape =
21
+ bitmap instanceof ImageShape ? bitmap : new ImageShape(bitmap, options);
22
+ // GameObjectShapeWrapper keeps transform + render in sync with the shape
23
+ super(game, shape, options);
24
+ //console.log("imagego", this);
25
+ }
26
+
27
+ reset() {
28
+ this.shape.reset();
29
+ }
30
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @module GameObjects
3
+ * @description Specialized GameObjects providing common functionality for game development.
4
+ *
5
+ * This module exports a collection of pre-built GameObject classes that provide
6
+ * specific functionality commonly needed in games:
7
+ * - {@link Scene}: A container for organizing hierarchies of GameObjects
8
+ * - {@link Text}: A GameObject for rendering text with various styling options
9
+ *
10
+ * All classes in this module extend the core {@link GameObject} class and inherit
11
+ * its properties and lifecycle methods while adding specialized behaviors.
12
+ *
13
+ * @example
14
+ * // Using Scene to organize a group of related objects
15
+ * import { Game } from '../core/game.js';
16
+ * import { Scene, Text } from '../core/objects';
17
+ *
18
+ * class MyGame extends Game {
19
+ * init() {
20
+ * // Create a UI layer as a Scene
21
+ * const uiLayer = new Scene(this, {
22
+ * x: 0,
23
+ * y: 0
24
+ * });
25
+ *
26
+ * // Add text elements to the scene
27
+ * const scoreText = new Text(this, "Score: 0", {
28
+ * x: 20,
29
+ * y: 20,
30
+ * font: "24px Arial",
31
+ * color: "#ffffff"
32
+ * });
33
+ *
34
+ * uiLayer.add(scoreText);
35
+ *
36
+ * // Add the entire scene to the pipeline
37
+ * this.pipeline.add(uiLayer);
38
+ * }
39
+ * }
40
+ *
41
+ * @see {@link GameObject} The base class for all objects in this module
42
+ */
43
+
44
+ // Interactive abstracts
45
+ export { GameObject } from "./go.js";
46
+ export { GameObjectShapeWrapper, ShapeGOFactory } from "./wrapper.js";
47
+ // Interactive Groups
48
+ export { Scene } from "./scene.js";
49
+ export { Scene3D } from "./scene3d.js";
50
+ export { IsometricScene } from "./isometric-scene.js";
51
+ export * from "./layoutscene.js";
52
+ // Specialized GameObjects
53
+ export { Text } from "./text.js";
54
+ export { ImageGo } from "./imagego.js";
@@ -0,0 +1,260 @@
1
+ import { Scene } from "./scene.js";
2
+ import { Painter } from "../../painter/painter.js";
3
+
4
+ /**
5
+ * IsometricScene - A Scene that projects children using isometric projection
6
+ *
7
+ * Bridges the GameObject/Scene system with isometric rendering, allowing GameObjects
8
+ * to be positioned in 3D grid space (x, y, z) and automatically projected to 2D
9
+ * isometric view using diamond projection.
10
+ *
11
+ * Children can have an optional `z` property for height above the ground plane.
12
+ * The scene handles depth sorting automatically based on isometric depth (x + y).
13
+ *
14
+ * Supports optional IsometricCamera for animated view rotation.
15
+ *
16
+ * @example
17
+ * // Basic usage
18
+ * const isoScene = new IsometricScene(this, {
19
+ * x: this.width / 2,
20
+ * y: this.height / 2,
21
+ * tileWidth: 64,
22
+ * tileHeight: 32,
23
+ * depthSort: true,
24
+ * });
25
+ *
26
+ * // With camera for rotatable view
27
+ * const camera = new IsometricCamera({ rotationStep: Math.PI / 4 });
28
+ * isoScene.setCamera(camera);
29
+ * camera.rotateRight(); // Rotate view 45°
30
+ *
31
+ * const box = new IsometricBox(this, { x: 2, y: 3 });
32
+ * box.z = 0; // ground level
33
+ * isoScene.add(box);
34
+ *
35
+ * this.pipeline.add(isoScene);
36
+ *
37
+ * @extends Scene
38
+ */
39
+ export class IsometricScene extends Scene {
40
+ /**
41
+ * @param {Game} game - Game instance
42
+ * @param {Object} options - Configuration
43
+ * @param {number} [options.tileWidth=64] - Width of a tile in the isometric grid (pixels)
44
+ * @param {number} [options.tileHeight] - Height of a tile (defaults to tileWidth / 2)
45
+ * @param {boolean} [options.depthSort=true] - Sort children by depth (back-to-front)
46
+ * @param {boolean} [options.scaleByDepth=false] - Scale children by perspective distance
47
+ * @param {number} [options.gridSize=10] - Size of the grid in tiles (used for scale calculations)
48
+ * @param {number} [options.elevationScale=1] - Multiplier for z-axis visual offset
49
+ * @param {IsometricCamera} [options.camera=null] - Optional camera for view rotation
50
+ */
51
+ constructor(game, options = {}) {
52
+ super(game, options);
53
+
54
+ /** @type {number} Width of a tile in pixels */
55
+ this.tileWidth = options.tileWidth ?? 64;
56
+
57
+ /** @type {number} Height of a tile in pixels (typically tileWidth / 2 for standard isometric) */
58
+ this.tileHeight = options.tileHeight ?? this.tileWidth / 2;
59
+
60
+ /** @type {boolean} Whether to sort children by depth (back-to-front) */
61
+ this.depthSort = options.depthSort ?? true;
62
+
63
+ /** @type {boolean} Whether to scale children by perspective distance */
64
+ this.scaleByDepth = options.scaleByDepth ?? false;
65
+
66
+ /** @type {number} Size of the grid in tiles (for scale calculations) */
67
+ this.gridSize = options.gridSize ?? 10;
68
+
69
+ /** @type {number} Multiplier for z-axis visual offset */
70
+ this.elevationScale = options.elevationScale ?? 1;
71
+
72
+ /** @type {IsometricCamera|null} Camera for view rotation */
73
+ this.camera = options.camera ?? null;
74
+ }
75
+
76
+ /**
77
+ * Set or update the camera reference
78
+ * @param {IsometricCamera} camera - Camera instance
79
+ * @returns {IsometricScene} this for chaining
80
+ */
81
+ setCamera(camera) {
82
+ this.camera = camera;
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Convert 3D grid coordinates (x, y, z) to 2D isometric screen position.
88
+ *
89
+ * Uses the standard "diamond" isometric projection with camera rotation.
90
+ * Note: For best visual results, use 90° rotation steps (Math.PI/2).
91
+ * 45° rotations can cause visual flattening at certain angles.
92
+ *
93
+ * @param {number} x - Grid X coordinate
94
+ * @param {number} y - Grid Y coordinate
95
+ * @param {number} [z=0] - Height above ground plane
96
+ * @returns {{x: number, y: number, depth: number}} Screen position and depth for sorting
97
+ */
98
+ toIsometric(x, y, z = 0) {
99
+ // Apply camera rotation if present
100
+ let rotatedX = x;
101
+ let rotatedY = y;
102
+
103
+ if (this.camera) {
104
+ const angle = this.camera.angle;
105
+ const cos = Math.cos(angle);
106
+ const sin = Math.sin(angle);
107
+ rotatedX = x * cos - y * sin;
108
+ rotatedY = x * sin + y * cos;
109
+ }
110
+
111
+ const isoX = (rotatedX - rotatedY) * (this.tileWidth / 2);
112
+ const isoY = (rotatedX + rotatedY) * (this.tileHeight / 2) - z * this.elevationScale;
113
+
114
+ // Depth for sorting
115
+ const depth = rotatedX + rotatedY - z * 0.01;
116
+
117
+ return { x: isoX, y: isoY, depth };
118
+ }
119
+
120
+ /**
121
+ * Convert screen coordinates back to grid coordinates.
122
+ *
123
+ * Useful for mouse picking and tile selection.
124
+ * Note: This assumes z = 0 (ground plane).
125
+ * If a camera is attached, the inverse rotation is applied.
126
+ *
127
+ * @param {number} screenX - Screen X relative to scene center
128
+ * @param {number} screenY - Screen Y relative to scene center
129
+ * @returns {{x: number, y: number}} Grid coordinates
130
+ */
131
+ fromIsometric(screenX, screenY) {
132
+ // Inverse of the isometric transform (assuming z = 0)
133
+ const halfTileW = this.tileWidth / 2;
134
+ const halfTileH = this.tileHeight / 2;
135
+
136
+ // Get rotated grid coordinates
137
+ let rotatedX = (screenX / halfTileW + screenY / halfTileH) / 2;
138
+ let rotatedY = (screenY / halfTileH - screenX / halfTileW) / 2;
139
+
140
+ // Apply inverse camera rotation if present
141
+ if (this.camera) {
142
+ const angle = -this.camera.angle; // Negative for inverse
143
+ const cos = Math.cos(angle);
144
+ const sin = Math.sin(angle);
145
+ const x = rotatedX * cos - rotatedY * sin;
146
+ const y = rotatedX * sin + rotatedY * cos;
147
+ return { x, y };
148
+ }
149
+
150
+ return { x: rotatedX, y: rotatedY };
151
+ }
152
+
153
+ /**
154
+ * Update method - updates camera if attached
155
+ * @param {number} dt - Delta time in seconds
156
+ */
157
+ update(dt) {
158
+ super.update(dt);
159
+
160
+ // Update camera animation
161
+ if (this.camera) {
162
+ this.camera.update(dt);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get the tile coordinates at a given screen position (floored to integers).
168
+ *
169
+ * @param {number} screenX - Screen X relative to scene center
170
+ * @param {number} screenY - Screen Y relative to scene center
171
+ * @returns {{x: number, y: number}} Tile coordinates (integers)
172
+ */
173
+ getTileAt(screenX, screenY) {
174
+ const grid = this.fromIsometric(screenX, screenY);
175
+ return {
176
+ x: Math.floor(grid.x),
177
+ y: Math.floor(grid.y),
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Calculate scale factor based on Y position (for perspective effect).
183
+ *
184
+ * Objects further "back" (higher y in grid) appear smaller.
185
+ *
186
+ * @param {number} y - Grid Y position
187
+ * @returns {number} Scale factor (0.7 to 1.3 range)
188
+ */
189
+ getDepthScale(y) {
190
+ const distanceFromCenter = Math.abs(y);
191
+ const maxDistance = this.gridSize;
192
+ // Range from 0.7 (far) to 1.3 (near)
193
+ return 0.7 + (distanceFromCenter / maxDistance) * 0.6;
194
+ }
195
+
196
+ /**
197
+ * Override Scene's render to provide isometric coordinate system.
198
+ *
199
+ * This method:
200
+ * 1. Translates to the scene's position (center of projection)
201
+ * 2. Depth-sorts children back-to-front by their grid position
202
+ * 3. Renders each child (children use toIsometric() for their own projection)
203
+ *
204
+ * Note: Children are responsible for calling toIsometric() in their render()
205
+ * method. This gives them full control over complex rendering (shadows, etc).
206
+ */
207
+ render() {
208
+ if (!this.visible) return;
209
+
210
+ Painter.save();
211
+
212
+ // Translate to the IsometricScene's position (e.g., center of screen)
213
+ // This defines the origin (0,0) of the isometric projection on the 2D canvas
214
+ Painter.translateTo(this.x, this.y);
215
+
216
+ // Build render list with depth info for sorting
217
+ const renderList = [];
218
+
219
+ for (const child of this._collection.getSortedChildren()) {
220
+ if (!child.visible) continue;
221
+
222
+ // Use custom isoDepth if available, otherwise calculate
223
+ let depth;
224
+ if (child.isoDepth !== undefined) {
225
+ depth = child.isoDepth;
226
+ } else {
227
+ // For moving objects, use z as height
228
+ const height = child.z ?? 0;
229
+ // Higher (x + y) = closer to viewer = higher depth = render later
230
+ // Higher z = on top = higher depth = render later
231
+ depth = (child.x + child.y) + height * 0.05;
232
+ }
233
+
234
+ renderList.push({
235
+ child,
236
+ depth,
237
+ });
238
+ }
239
+
240
+ // Depth sort: respect zIndex first, then isometric depth
241
+ // Lower depth (back) renders first, higher depth (front) renders last
242
+ if (this.depthSort) {
243
+ renderList.sort((a, b) => {
244
+ const za = a.child.zIndex ?? 0;
245
+ const zb = b.child.zIndex ?? 0;
246
+ if (za !== zb) return za - zb;
247
+ return a.depth - b.depth; // back-to-front by position
248
+ });
249
+ }
250
+
251
+ // Render each child - they handle their own projection via toIsometric()
252
+ for (const item of renderList) {
253
+ Painter.save();
254
+ item.child.render();
255
+ Painter.restore();
256
+ }
257
+
258
+ Painter.restore();
259
+ }
260
+ }