@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,62 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+ /**
4
+ * Circle - A drawable canvas circle shape.
5
+ *
6
+ * Responsibilities:
7
+ * - Draws a circle using the Painter API
8
+ * - Supports fill and stroke styles
9
+ * - Supports canvas transforms (position, rotation, scale)
10
+ * - Optional drawing constraints (bounds)
11
+ *
12
+ * Limitations:
13
+ * - Not interactive or self-animated (wrap in a GameObject for that)
14
+ */
15
+ export class Circle extends Shape {
16
+ constructor(radius, options = {}) {
17
+ super(options);
18
+ this._radius = radius;
19
+ this.width = radius * 2;
20
+ this.height = radius * 2;
21
+ }
22
+
23
+ /**
24
+ * Renders the circle using the Painter API.
25
+ */
26
+ draw() {
27
+ super.draw();
28
+ if (this.color) {
29
+ Painter.shapes.fillCircle(0, 0, this._radius, this.color);
30
+ }
31
+ if (this.stroke) {
32
+ Painter.shapes.strokeCircle(
33
+ 0,
34
+ 0,
35
+ this._radius,
36
+ this.stroke,
37
+ this.lineWidth
38
+ );
39
+ }
40
+ }
41
+
42
+ calculateBounds() {
43
+ const size = this._radius * 2;
44
+ this.trace("Circle.calculateBounds:" + size);
45
+ return { x: this.x, y: this.y, width: size, height: size };
46
+ }
47
+
48
+ get radius() {
49
+ return this._radius;
50
+ }
51
+
52
+ set radius(v) {
53
+ this.validateProp(v, "radius");
54
+ if (v != this._radius) {
55
+ this._radius = v;
56
+ this.width = v * 2;
57
+ this.height = v * 2;
58
+ this._boundsDirty = true;
59
+ this.calculateBounds();
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,56 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+
4
+ export class Cloud extends Shape {
5
+ constructor(size = 40, options = {}) {
6
+ super(options);
7
+ this.size = size;
8
+ this.width = size * 2;
9
+ this.height = size * 2;
10
+ }
11
+
12
+ draw() {
13
+ super.draw();
14
+ const s = this.size;
15
+ const ctx = Painter.ctx;
16
+
17
+ // Draw cloud as overlapping filled circles
18
+ const circles = [
19
+ { x: -s * 0.5, y: 0, r: s * 0.4 }, // left
20
+ { x: -s * 0.2, y: -s * 0.3, r: s * 0.35 }, // top-left
21
+ { x: s * 0.2, y: -s * 0.35, r: s * 0.4 }, // top-right
22
+ { x: s * 0.5, y: 0, r: s * 0.35 }, // right
23
+ { x: 0, y: s * 0.15, r: s * 0.5 }, // bottom center (base)
24
+ ];
25
+
26
+ if (this.color) {
27
+ ctx.fillStyle = this.color;
28
+ for (const c of circles) {
29
+ ctx.beginPath();
30
+ ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2);
31
+ ctx.fill();
32
+ }
33
+ }
34
+
35
+ if (this.stroke) {
36
+ // For stroke, we'd need a more complex outline - skip for now
37
+ ctx.strokeStyle = this.stroke;
38
+ ctx.lineWidth = this.lineWidth;
39
+ for (const c of circles) {
40
+ ctx.beginPath();
41
+ ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2);
42
+ ctx.stroke();
43
+ }
44
+ }
45
+ }
46
+
47
+ getBounds() {
48
+ const s = this.size * 2;
49
+ return {
50
+ x: this.x,
51
+ y: this.y,
52
+ width: s,
53
+ height: s,
54
+ };
55
+ }
56
+ }
@@ -0,0 +1,219 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+
4
+ /**
5
+ * Cone - A 3D-looking isometric cone with rotation support.
6
+ *
7
+ * Supports:
8
+ * - Bottom circular face and side triangular segments
9
+ * - Full bounding box
10
+ * - Face visibility control
11
+ * - Rotation around X, Y, and Z axes
12
+ * - Adjustable segment count for smoother curves
13
+ *
14
+ * Note: This is a 2.5D visual illusion — not actual 3D rendering.
15
+ */
16
+ export class Cone extends Shape {
17
+ /**
18
+ * Create a cone
19
+ * @param {number} radius - Radius of the cone base
20
+ * @param {number} height - Height of the cone
21
+ * @param {object} options - Customization options
22
+ * @param {string} [options.bottomColor] - Color of the bottom face
23
+ * @param {string} [options.sideColor] - Color of the side face(s)
24
+ * @param {number} [options.segments] - Number of segments to approximate the curved surface
25
+ * @param {Array<string>} [options.visibleFaces] - Array of face keys to render
26
+ * @param {string} [options.stroke] - Optional stroke around each face
27
+ * @param {number} [options.lineWidth] - Stroke width
28
+ * @param {number} [options.rotationX] - Rotation around X axis in radians
29
+ * @param {number} [options.rotationY] - Rotation around Y axis in radians
30
+ * @param {number} [options.rotationZ] - Rotation around Z axis in radians
31
+ */
32
+ constructor(radius = 50, height = 100, options = {}) {
33
+ super(options);
34
+ this.radius = radius;
35
+ this.height = height || options.height || 100;
36
+
37
+ // Number of segments used to approximate the circle
38
+ this.segments = options.segments || 24;
39
+
40
+ // Colors for each face
41
+ this.bottomColor = options.bottomColor || "#eee";
42
+ this.sideColor = options.sideColor || "#aaa";
43
+
44
+ this.stroke = options.stroke || null;
45
+ this.lineWidth = options.lineWidth || 1;
46
+
47
+ // Rotation angles (in radians)
48
+ this.rotationX = options.rotationX || 0;
49
+ this.rotationY = options.rotationY || 0;
50
+ this.rotationZ = options.rotationZ || 0;
51
+
52
+ /** @type {Array<'bottom'|'side'>} */
53
+ this.visibleFaces = options.visibleFaces || ["bottom", "side"];
54
+ }
55
+
56
+ /**
57
+ * Set rotation angles
58
+ * @param {number} x - Rotation around X axis in radians
59
+ * @param {number} y - Rotation around Y axis in radians
60
+ * @param {number} z - Rotation around Z axis in radians
61
+ */
62
+ setRotation(x, y, z) {
63
+ this.rotationX = x;
64
+ this.rotationY = y;
65
+ this.rotationZ = z;
66
+ return this; // Enable method chaining
67
+ }
68
+
69
+ /**
70
+ * Rotate the cone incrementally
71
+ * @param {number} x - Increment for X rotation in radians
72
+ * @param {number} y - Increment for Y rotation in radians
73
+ * @param {number} z - Increment for Z rotation in radians
74
+ */
75
+ rotate(x, y, z) {
76
+ this.rotationX += x;
77
+ this.rotationY += y;
78
+ this.rotationZ += z;
79
+ return this; // Enable method chaining
80
+ }
81
+
82
+ /**
83
+ * Internal draw logic
84
+ */
85
+ draw() {
86
+ super.draw();
87
+ const r = this.radius;
88
+ const h = this.height; // Height from base to apex
89
+ const hh = h / 2; // Half height for positioning
90
+
91
+ /**
92
+ * Apply 3D rotation to a point
93
+ * @param {number} x
94
+ * @param {number} y
95
+ * @param {number} z
96
+ * @returns {{x: number, y: number, z: number}}
97
+ */
98
+ const rotate3D = (x, y, z) => {
99
+ // Apply X-axis rotation
100
+ let y1 = y;
101
+ let z1 = z;
102
+ y = y1 * Math.cos(this.rotationX) - z1 * Math.sin(this.rotationX);
103
+ z = y1 * Math.sin(this.rotationX) + z1 * Math.cos(this.rotationX);
104
+
105
+ // Apply Y-axis rotation
106
+ let x1 = x;
107
+ z1 = z;
108
+ x = x1 * Math.cos(this.rotationY) + z1 * Math.sin(this.rotationY);
109
+ z = -x1 * Math.sin(this.rotationY) + z1 * Math.cos(this.rotationY);
110
+
111
+ // Apply Z-axis rotation
112
+ x1 = x;
113
+ y1 = y;
114
+ x = x1 * Math.cos(this.rotationZ) - y1 * Math.sin(this.rotationZ);
115
+ y = x1 * Math.sin(this.rotationZ) + y1 * Math.cos(this.rotationZ);
116
+
117
+ return { x, y, z };
118
+ };
119
+
120
+ /**
121
+ * Isometric projection of 3D point
122
+ * @param {number} x
123
+ * @param {number} y
124
+ * @param {number} z
125
+ * @returns {{x: number, y: number, z: number}}
126
+ */
127
+ const iso = (x, y, z) => {
128
+ // Apply rotations first
129
+ const rotated = rotate3D(x, y, z);
130
+
131
+ // Then apply isometric projection
132
+ const isoX = (rotated.x - rotated.y) * Math.cos(Math.PI / 6);
133
+ const isoY = (rotated.x + rotated.y) * Math.sin(Math.PI / 6) - rotated.z;
134
+ return { x: isoX, y: isoY, z: rotated.z }; // Include z for depth sorting
135
+ };
136
+
137
+ // Apex of the cone (top point)
138
+ const apex = iso(0, 0, hh);
139
+
140
+ // Generate points for the base circle
141
+ const basePoints = [];
142
+
143
+ // Calculate segment angle
144
+ const angleStep = (Math.PI * 2) / this.segments;
145
+
146
+ // Generate base circle points
147
+ for (let i = 0; i < this.segments; i++) {
148
+ const angle = i * angleStep;
149
+ const x = Math.cos(angle) * r;
150
+ const y = Math.sin(angle) * r;
151
+
152
+ // Project 3D points to 2D
153
+ basePoints.push(iso(x, y, -hh)); // Base circle at -halfHeight
154
+ }
155
+
156
+ // Create side faces (triangles from apex to base)
157
+ const sideFaces = [];
158
+ for (let i = 0; i < this.segments; i++) {
159
+ const nextIdx = (i + 1) % this.segments;
160
+
161
+ // Each side face is a triangle: apex, current base point, next base point
162
+ sideFaces.push({
163
+ points: [apex, basePoints[i], basePoints[nextIdx]],
164
+ // Calculate depth as average Z-value of the three points
165
+ z: (apex.z + basePoints[i].z + basePoints[nextIdx].z) / 3,
166
+ });
167
+ }
168
+
169
+ // Prepare faces for depth sorting
170
+ const facesWithDepth = [];
171
+
172
+ // Add bottom face if visible
173
+ if (this.visibleFaces.includes("bottom")) {
174
+ facesWithDepth.push({
175
+ type: "bottom",
176
+ points: [...basePoints].reverse(), // Reverse for correct winding
177
+ z: -hh, // Z-value of the base
178
+ });
179
+ }
180
+
181
+ // Add side faces if visible
182
+ if (this.visibleFaces.includes("side")) {
183
+ facesWithDepth.push(
184
+ ...sideFaces.map((face) => ({
185
+ type: "side",
186
+ points: face.points,
187
+ z: face.z,
188
+ }))
189
+ );
190
+ }
191
+
192
+ // Sort faces by depth (back to front)
193
+ facesWithDepth.sort((a, b) => b.z - a.z);
194
+
195
+ // Draw faces in depth order
196
+ for (const face of facesWithDepth) {
197
+ const color = face.type === "bottom" ? this.bottomColor : this.sideColor;
198
+ Painter.shapes.polygon(face.points, color, this.stroke, this.lineWidth);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Compute bounding box for interactivity and layout
204
+ * @returns {{x: number, y: number, width: number, height: number}}
205
+ */
206
+ getBounds() {
207
+ // Calculate actual bounds based on isometric projection
208
+ const projectionFactor = 1.5; // Approximation for isometric projection
209
+ const maxDimension = Math.max(this.radius * 2, this.height);
210
+ const adjustedSize = maxDimension * projectionFactor;
211
+
212
+ return {
213
+ x: this.x - adjustedSize / 2,
214
+ y: this.y - adjustedSize / 2,
215
+ width: adjustedSize,
216
+ height: adjustedSize,
217
+ };
218
+ }
219
+ }
@@ -0,0 +1,70 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+
4
+ /**
5
+ * Cross - A cross shape (plus or X), useful for UI, health, or icons.
6
+ */
7
+ export class Cross extends Shape {
8
+ /**
9
+ * @param {number} x - Center X
10
+ * @param {number} y - Center Y
11
+ * @param {number} size - Full size of the cross (width/height of bounding square)
12
+ * @param {number} thickness - Width of the cross arms
13
+ * @param {Object} [options] - Fill/stroke/transform options
14
+ * @param {boolean} [options.diagonal=false] - Whether to draw a rotated X instead of a + shape
15
+ */
16
+ constructor(size, thickness, options = {}) {
17
+ super(options);
18
+ this.size = size;
19
+ this.thickness = thickness;
20
+ this.diagonal = options.diagonal || false;
21
+ }
22
+
23
+ draw() {
24
+ super.draw();
25
+ const s = this.size / 2;
26
+ const t = this.thickness / 2;
27
+
28
+ if (this.diagonal) {
29
+ // Draw an X (rotated +)
30
+ Painter.lines.beginPath();
31
+ Painter.lines.moveTo(-s, -s + t);
32
+ Painter.lines.lineTo(-s + t, -s);
33
+ Painter.lines.lineTo(0, -t);
34
+ Painter.lines.lineTo(s - t, -s);
35
+ Painter.lines.lineTo(s, -s + t);
36
+ Painter.lines.lineTo(t, 0);
37
+ Painter.lines.lineTo(s, s - t);
38
+ Painter.lines.lineTo(s - t, s);
39
+ Painter.lines.lineTo(0, t);
40
+ Painter.lines.lineTo(-s + t, s);
41
+ Painter.lines.lineTo(-s, s - t);
42
+ Painter.lines.lineTo(-t, 0);
43
+ Painter.lines.closePath();
44
+ } else {
45
+ // Draw a + shape
46
+ Painter.lines.beginPath();
47
+ Painter.lines.moveTo(-t, -s);
48
+ Painter.lines.lineTo(t, -s);
49
+ Painter.lines.lineTo(t, -t);
50
+ Painter.lines.lineTo(s, -t);
51
+ Painter.lines.lineTo(s, t);
52
+ Painter.lines.lineTo(t, t);
53
+ Painter.lines.lineTo(t, s);
54
+ Painter.lines.lineTo(-t, s);
55
+ Painter.lines.lineTo(-t, t);
56
+ Painter.lines.lineTo(-s, t);
57
+ Painter.lines.lineTo(-s, -t);
58
+ Painter.lines.lineTo(-t, -t);
59
+ Painter.lines.closePath();
60
+ }
61
+
62
+ if (this.color) {
63
+ Painter.colors.fill(this.color);
64
+ }
65
+
66
+ if (this.stroke) {
67
+ Painter.colors.stroke(this.stroke, this.lineWidth);
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,244 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+
4
+ /**
5
+ * Cube - A 3D-looking isometric cube built from 6 square faces with rotation support.
6
+ *
7
+ * Supports:
8
+ * - Individual face colors
9
+ * - Full bounding box
10
+ * - Face visibility control (top, bottom, left, right, front, back)
11
+ * - Rotation around X, Y, and Z axes
12
+ *
13
+ * Note: This is a 2.5D visual illusion — not actual 3D rendering.
14
+ */
15
+ export class Cube extends Shape {
16
+ /**
17
+ * Create a cube
18
+ * @param {number} x - X position (center of the cube)
19
+ * @param {number} y - Y position (center of the cube)
20
+ * @param {number} size - Size of the cube (edge length)
21
+ * @param {object} options - Customization options
22
+ * @param {string} [options.faceTopColor] - Color of the top face
23
+ * @param {string} [options.faceBottomColor] - Color of the bottom face
24
+ * @param {string} [options.faceLeftColor] - Color of the left face
25
+ * @param {string} [options.faceRightColor] - Color of the right face
26
+ * @param {string} [options.faceFrontColor] - Color of the front face
27
+ * @param {string} [options.faceBackColor] - Color of the back face
28
+ * @param {Array<string>} [options.visibleFaces] - Array of face keys to render
29
+ * @param {string} [options.strokeColor] - Optional stroke around each face
30
+ * @param {number} [options.lineWidth] - Stroke width
31
+ * @param {number} [options.rotationX] - Rotation around X axis in radians
32
+ * @param {number} [options.rotationY] - Rotation around Y axis in radians
33
+ * @param {number} [options.rotationZ] - Rotation around Z axis in radians
34
+ */
35
+ constructor(size = 50, options = {}) {
36
+ super(options);
37
+ this.size = size;
38
+
39
+ this.faceTopColor = options.faceTopColor || "#eee";
40
+ this.faceBottomColor = options.faceBottomColor || "#ccc";
41
+ this.faceLeftColor = options.faceLeftColor || "#aaa";
42
+ this.faceRightColor = options.faceRightColor || "#888";
43
+ this.faceFrontColor = options.faceFrontColor || "#666";
44
+ this.faceBackColor = options.faceBackColor || "#444";
45
+
46
+ this.strokeColor = options.strokeColor || null;
47
+ this.lineWidth = options.lineWidth || 1;
48
+
49
+ // Rotation angles (in radians)
50
+ this.rotationX = options.rotationX || 0;
51
+ this.rotationY = options.rotationY || 0;
52
+ this.rotationZ = options.rotationZ || 0;
53
+
54
+ /** @type {Array<'top'|'bottom'|'left'|'right'|'front'|'back'>} */
55
+ this.visibleFaces = options.visibleFaces || [
56
+ "top",
57
+ "left",
58
+ "right",
59
+ "front",
60
+ "back",
61
+ "bottom",
62
+ ];
63
+ }
64
+
65
+ /**
66
+ * Set rotation angles
67
+ * @param {number} x - Rotation around X axis in radians
68
+ * @param {number} y - Rotation around Y axis in radians
69
+ * @param {number} z - Rotation around Z axis in radians
70
+ */
71
+ setRotation(x, y, z) {
72
+ this.rotationX = x;
73
+ this.rotationY = y;
74
+ this.rotationZ = z;
75
+ return this; // Enable method chaining
76
+ }
77
+
78
+ /**
79
+ * Rotate the cube incrementally
80
+ * @param {number} x - Increment for X rotation in radians
81
+ * @param {number} y - Increment for Y rotation in radians
82
+ * @param {number} z - Increment for Z rotation in radians
83
+ */
84
+ rotate(x, y, z) {
85
+ this.rotationX += x;
86
+ this.rotationY += y;
87
+ this.rotationZ += z;
88
+ return this; // Enable method chaining
89
+ }
90
+
91
+ /**
92
+ * Internal draw logic
93
+ */
94
+ draw() {
95
+ super.draw();
96
+ const s = this.size;
97
+ // Half size for positioning around center point
98
+ const hs = s / 2;
99
+
100
+ /**
101
+ * Apply 3D rotation to a point
102
+ * @param {number} x
103
+ * @param {number} y
104
+ * @param {number} z
105
+ * @returns {{x: number, y: number, z: number}}
106
+ */
107
+ const rotate3D = (x, y, z) => {
108
+ // Apply X-axis rotation
109
+ let y1 = y;
110
+ let z1 = z;
111
+ y = y1 * Math.cos(this.rotationX) - z1 * Math.sin(this.rotationX);
112
+ z = y1 * Math.sin(this.rotationX) + z1 * Math.cos(this.rotationX);
113
+
114
+ // Apply Y-axis rotation
115
+ let x1 = x;
116
+ z1 = z;
117
+ x = x1 * Math.cos(this.rotationY) + z1 * Math.sin(this.rotationY);
118
+ z = -x1 * Math.sin(this.rotationY) + z1 * Math.cos(this.rotationY);
119
+
120
+ // Apply Z-axis rotation
121
+ x1 = x;
122
+ y1 = y;
123
+ x = x1 * Math.cos(this.rotationZ) - y1 * Math.sin(this.rotationZ);
124
+ y = x1 * Math.sin(this.rotationZ) + y1 * Math.cos(this.rotationZ);
125
+
126
+ return { x, y, z };
127
+ };
128
+
129
+ /**
130
+ * Isometric projection of 3D point
131
+ * @param {number} x
132
+ * @param {number} y
133
+ * @param {number} z
134
+ * @returns {{x: number, y: number}}
135
+ */
136
+ const iso = (x, y, z) => {
137
+ // Apply rotations first
138
+ const rotated = rotate3D(x, y, z);
139
+
140
+ // Then apply isometric projection
141
+ const isoX = (rotated.x - rotated.y) * Math.cos(Math.PI / 6);
142
+ const isoY = (rotated.x + rotated.y) * Math.sin(Math.PI / 6) - rotated.z;
143
+ return { x: isoX, y: isoY };
144
+ };
145
+
146
+ // 8 corners of the cube, centered on the origin
147
+ const p = {
148
+ p0: iso(-hs, -hs, -hs), // bottom front left
149
+ p1: iso(hs, -hs, -hs), // bottom front right
150
+ p2: iso(hs, hs, -hs), // bottom back right
151
+ p3: iso(-hs, hs, -hs), // bottom back left
152
+ p4: iso(-hs, -hs, hs), // top front left
153
+ p5: iso(hs, -hs, hs), // top front right
154
+ p6: iso(hs, hs, hs), // top back right
155
+ p7: iso(-hs, hs, hs), // top back left
156
+ };
157
+
158
+ // Faces mapped to corner points
159
+ const faces = {
160
+ top: {
161
+ points: [p.p4, p.p5, p.p6, p.p7],
162
+ color: this.faceTopColor,
163
+ normal: [0, 0, 1],
164
+ },
165
+ bottom: {
166
+ points: [p.p0, p.p1, p.p2, p.p3],
167
+ color: this.faceBottomColor,
168
+ normal: [0, 0, -1],
169
+ },
170
+ left: {
171
+ points: [p.p0, p.p4, p.p7, p.p3],
172
+ color: this.faceLeftColor,
173
+ normal: [-1, 0, 0],
174
+ },
175
+ right: {
176
+ points: [p.p1, p.p5, p.p6, p.p2],
177
+ color: this.faceRightColor,
178
+ normal: [1, 0, 0],
179
+ },
180
+ front: {
181
+ points: [p.p0, p.p1, p.p5, p.p4],
182
+ color: this.faceFrontColor,
183
+ normal: [0, -1, 0],
184
+ },
185
+ back: {
186
+ points: [p.p3, p.p2, p.p6, p.p7],
187
+ color: this.faceBackColor,
188
+ normal: [0, 1, 0],
189
+ },
190
+ };
191
+
192
+ // Calculate visibility based on face normals after rotation
193
+ const visibleFacesWithDepth = this.visibleFaces
194
+ .map((key) => {
195
+ const face = faces[key];
196
+ if (!face) return null;
197
+
198
+ // Calculate face center for z-ordering
199
+ const center = face.points.reduce(
200
+ (acc, pt) => ({ x: acc.x + pt.x, y: acc.y + pt.y }),
201
+ { x: 0, y: 0 }
202
+ );
203
+ center.x /= face.points.length;
204
+ center.y /= face.points.length;
205
+
206
+ // Calculate approximate depth
207
+ // Higher value = farther back
208
+ const depth = center.x * center.x + center.y * center.y;
209
+
210
+ return { key, face, depth };
211
+ })
212
+ .filter((item) => item !== null)
213
+ .sort((a, b) => b.depth - a.depth); // Sort by depth (back to front)
214
+
215
+ // Draw faces in depth order
216
+ visibleFacesWithDepth.forEach(({ key, face }) => {
217
+ if (face?.color) {
218
+ Painter.shapes.polygon(
219
+ face.points,
220
+ face.color,
221
+ this.strokeColor,
222
+ this.lineWidth
223
+ );
224
+ }
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Compute bounding box for interactivity and layout
230
+ * @returns {{x: number, y: number, width: number, height: number}}
231
+ */
232
+ getBounds() {
233
+ // Calculate actual bounds based on isometric projection
234
+ const projectionFactor = 1.5; // Approximation for isometric projection
235
+ const adjustedSize = this.size * projectionFactor;
236
+
237
+ return {
238
+ x: this.x - adjustedSize / 2,
239
+ y: this.y - adjustedSize / 2,
240
+ width: adjustedSize,
241
+ height: adjustedSize,
242
+ };
243
+ }
244
+ }