@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,117 @@
1
+ import { Painter } from "../painter/painter.js";
2
+ import { Transformable } from "./transformable.js";
3
+
4
+ /**
5
+ * Shape
6
+ * ------
7
+ *
8
+ * A base class for drawable geometric primitives (e.g., rectangles, circles).
9
+ *
10
+ * ### Role in the Engine
11
+ *
12
+ * Shape is the first class in the gcanvas hierarchy to express **canvas styling intent**.
13
+ * It does not define geometry — that's up to the subclass — but it introduces:
14
+ *
15
+ * - Fill & stroke control
16
+ * - Line width & join styles
17
+ * - Shadow blur (inherited from `Renderable`)
18
+ *
19
+ * ### Styling Options
20
+ *
21
+ * Most canvas drawing operations respect the following:
22
+ * - `color`
23
+ * - `stroke`
24
+ * - `lineWidth`
25
+ * - `lineJoin`
26
+ * - `lineCap`
27
+ * - `miterLimit`
28
+ * - `globalAlpha` (inherited via `Renderable.opacity`)
29
+ *
30
+ * Subclasses must implement their own `.draw()` logic using these styles.
31
+ *
32
+ * @abstract
33
+ * @extends Transformable
34
+ */
35
+ export class Shape extends Transformable {
36
+ /**
37
+ * @param {number} x - X center of the shape
38
+ * @param {number} y - Y center of the shape
39
+ * @param {Object} [options={}] - Styling and layout options
40
+ * @param {string|null} [options.color=null] - Fill color (CSS color string)
41
+ * @param {string|null} [options.stroke=null] - Stroke color (CSS color string)
42
+ * @param {number} [options.lineWidth=1] - Line width in pixels
43
+ * @param {string} [options.lineJoin="miter"] - "miter", "round", or "bevel"
44
+ * @param {string} [options.lineCap="butt"] - "butt", "round", or "square"
45
+ * @param {number} [options.miterLimit=10] - Maximum miter length
46
+ */
47
+ constructor(options = {}) {
48
+ super(options);
49
+ this._color = options.color ?? null;
50
+ this._stroke = options.stroke ?? null;
51
+ this._lineWidth = options.lineWidth ?? 1;
52
+ this._lineJoin = options.lineJoin ?? "miter";
53
+ this._lineCap = options.lineCap ?? "butt";
54
+ this._miterLimit = options.miterLimit ?? 10;
55
+ this.logger.log("Shape", this.x, this.y, this.width, this.height);
56
+ }
57
+
58
+ /** @type {string|null} Fill style for canvas fill operations */
59
+ get color() {
60
+ return this._color;
61
+ }
62
+
63
+ set color(v) {
64
+ this._color = v;
65
+ this.invalidateCache();
66
+ }
67
+
68
+ /** @type {string|null} Stroke style for canvas stroke operations */
69
+ get stroke() {
70
+ return this._stroke;
71
+ }
72
+
73
+ set stroke(v) {
74
+ this._stroke = v;
75
+ this.invalidateCache();
76
+ }
77
+
78
+ /** @type {number} Width of the stroke in pixels */
79
+ get lineWidth() {
80
+ return this._lineWidth;
81
+ }
82
+
83
+ set lineWidth(v) {
84
+ this._lineWidth = Math.max(0, v);
85
+ this.invalidateCache();
86
+ }
87
+
88
+ /** @type {"miter"|"round"|"bevel"} Style of line joins */
89
+ get lineJoin() {
90
+ return this._lineJoin;
91
+ }
92
+
93
+ set lineJoin(v) {
94
+ this._lineJoin = v;
95
+ this.invalidateCache();
96
+ }
97
+
98
+ /** @type {"butt"|"round"|"square"} Style of line caps */
99
+ get lineCap() {
100
+ return this._lineCap;
101
+ }
102
+
103
+ set lineCap(v) {
104
+ this._lineCap = v;
105
+ this.invalidateCache();
106
+ }
107
+
108
+ /** @type {number} Maximum miter length before switching to bevel */
109
+ get miterLimit() {
110
+ return this._miterLimit;
111
+ }
112
+
113
+ set miterLimit(v) {
114
+ this._miterLimit = v;
115
+ this.invalidateCache();
116
+ }
117
+ }
@@ -0,0 +1,26 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+ export class PieSlice extends Shape {
4
+ constructor(radius, startAngle, endAngle, options = {}) {
5
+ super(options);
6
+ this.radius = radius;
7
+ this.startAngle = startAngle;
8
+ this.endAngle = endAngle;
9
+ }
10
+
11
+ draw() {
12
+ super.draw();
13
+ Painter.lines.beginPath();
14
+ Painter.lines.moveTo(0, 0);
15
+ Painter.shapes.arc(0, 0, this.radius, this.startAngle, this.endAngle);
16
+ Painter.lines.closePath();
17
+
18
+ if (this.color) {
19
+ Painter.colors.fill(this.color);
20
+ }
21
+
22
+ if (this.stroke) {
23
+ Painter.colors.stroke(this.stroke, this.lineWidth);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,314 @@
1
+ import { Shape } from "./shape.js";
2
+ import { Painter } from "../painter/painter.js";
3
+
4
+ /**
5
+ * Sphere - A 3D-looking isometric sphere with rotation support.
6
+ *
7
+ * Supports:
8
+ * - Customizable colors with gradient options
9
+ * - Full bounding box
10
+ * - Rotation around X, Y, and Z axes
11
+ * - Adjustable segment count for smoother appearance
12
+ *
13
+ * Note: This is a 2.5D visual illusion — not actual 3D rendering.
14
+ */
15
+ export class Sphere extends Shape {
16
+ /**
17
+ * Create a sphere
18
+ * @param {number} x - X position (center of the sphere)
19
+ * @param {number} y - Y position (center of the sphere)
20
+ * @param {number} radius - Radius of the sphere
21
+ * @param {object} options - Customization options
22
+ * @param {string} [options.color] - Main color of the sphere
23
+ * @param {string} [options.highlightColor] - Optional highlight color for lighting effect
24
+ * @param {number} [options.hSegments] - Number of horizontal segments
25
+ * @param {number} [options.vSegments] - Number of vertical segments
26
+ * @param {boolean} [options.wireframe] - Whether to render as wireframe
27
+ * @param {string} [options.stroke] - Color of wireframe or outline
28
+ * @param {number} [options.lineWidth] - Stroke width
29
+ * @param {number} [options.rotationX] - Rotation around X axis in radians
30
+ * @param {number} [options.rotationY] - Rotation around Y axis in radians
31
+ * @param {number} [options.rotationZ] - Rotation around Z axis in radians
32
+ */
33
+ constructor(radius = 50, options = {}) {
34
+ super(options);
35
+ this.radius = radius;
36
+
37
+ // Number of segments used to approximate the sphere
38
+ this.hSegments = options.hSegments || 16; // Horizontal segments (longitude)
39
+ this.vSegments = options.vSegments || 12; // Vertical segments (latitude)
40
+
41
+ // Colors
42
+ this.color = options.color || "#6495ED";
43
+ this.highlightColor = options.highlightColor || "#FFFFFF"; // For gradient effect
44
+ this.wireframe = options.wireframe || false;
45
+ this.stroke = options.stroke || "#333333";
46
+ this.lineWidth = options.lineWidth || 1;
47
+
48
+ // Rotation angles (in radians)
49
+ this.rotationX = options.rotationX || 0;
50
+ this.rotationY = options.rotationY || 0;
51
+ this.rotationZ = options.rotationZ || 0;
52
+ }
53
+
54
+ /**
55
+ * Set rotation angles
56
+ * @param {number} x - Rotation around X axis in radians
57
+ * @param {number} y - Rotation around Y axis in radians
58
+ * @param {number} z - Rotation around Z axis in radians
59
+ */
60
+ setRotation(x, y, z) {
61
+ this.rotationX = x;
62
+ this.rotationY = y;
63
+ this.rotationZ = z;
64
+ return this; // Enable method chaining
65
+ }
66
+
67
+ /**
68
+ * Rotate the sphere incrementally
69
+ * @param {number} x - Increment for X rotation in radians
70
+ * @param {number} y - Increment for Y rotation in radians
71
+ * @param {number} z - Increment for Z rotation in radians
72
+ */
73
+ rotate(x, y, z) {
74
+ this.rotationX += x;
75
+ this.rotationY += y;
76
+ this.rotationZ += z;
77
+ return this; // Enable method chaining
78
+ }
79
+
80
+ /**
81
+ * Calculate color based on surface normal direction for lighting effect
82
+ * @param {number} x - Normal x component
83
+ * @param {number} y - Normal y component
84
+ * @param {number} z - Normal z component
85
+ * @returns {string} - Color in hex or rgba format
86
+ */
87
+ calculateSurfaceColor(x, y, z) {
88
+ // Simple lighting model
89
+ // Light source is at (1, 1, 1) normalized
90
+ const lightDir = {
91
+ x: 1 / Math.sqrt(3),
92
+ y: 1 / Math.sqrt(3),
93
+ z: 1 / Math.sqrt(3),
94
+ };
95
+
96
+ // Dot product between normal and light direction gives lighting intensity
97
+ let intensity = x * lightDir.x + y * lightDir.y + z * lightDir.z;
98
+ intensity = Math.max(0.3, intensity); // Ambient light level
99
+
100
+ // If we have a highlight color, blend based on intensity
101
+ if (this.highlightColor) {
102
+ // Simple hex color blending based on intensity
103
+ const baseColor = this.hexToRgb(this.color);
104
+ const highlightColor = this.hexToRgb(this.highlightColor);
105
+
106
+ // Blend colors
107
+ const r = Math.round(
108
+ baseColor.r * (1 - intensity) + highlightColor.r * intensity
109
+ );
110
+ const g = Math.round(
111
+ baseColor.g * (1 - intensity) + highlightColor.g * intensity
112
+ );
113
+ const b = Math.round(
114
+ baseColor.b * (1 - intensity) + highlightColor.b * intensity
115
+ );
116
+
117
+ return `rgb(${r}, ${g}, ${b})`;
118
+ }
119
+
120
+ // Simple intensity adjustment of base color
121
+ const baseColor = this.hexToRgb(this.color);
122
+ const r = Math.min(255, Math.round(baseColor.r * intensity));
123
+ const g = Math.min(255, Math.round(baseColor.g * intensity));
124
+ const b = Math.min(255, Math.round(baseColor.b * intensity));
125
+
126
+ return `rgb(${r}, ${g}, ${b})`;
127
+ }
128
+
129
+ /**
130
+ * Helper to convert hex color to RGB
131
+ * @param {string} hex - Color in hex format
132
+ * @returns {object} - RGB components
133
+ */
134
+ hexToRgb(hex) {
135
+ // Default fallback color
136
+ const defaultColor = { r: 100, g: 100, b: 255 };
137
+
138
+ // Handle non-hex inputs
139
+ if (!hex || typeof hex !== "string") return defaultColor;
140
+
141
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
142
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
143
+ const fullHex = hex.replace(
144
+ shorthandRegex,
145
+ (m, r, g, b) => r + r + g + g + b + b
146
+ );
147
+
148
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
149
+ return result
150
+ ? {
151
+ r: parseInt(result[1], 16),
152
+ g: parseInt(result[2], 16),
153
+ b: parseInt(result[3], 16),
154
+ }
155
+ : defaultColor;
156
+ }
157
+
158
+ /**
159
+ * Internal draw logic
160
+ */
161
+ draw() {
162
+ super.draw();
163
+ const r = this.radius;
164
+ /**
165
+ * Apply 3D rotation to a point
166
+ * @param {number} x
167
+ * @param {number} y
168
+ * @param {number} z
169
+ * @returns {{x: number, y: number, z: number}}
170
+ */
171
+ const rotate3D = (x, y, z) => {
172
+ // Apply X-axis rotation
173
+ let y1 = y;
174
+ let z1 = z;
175
+ y = y1 * Math.cos(this.rotationX) - z1 * Math.sin(this.rotationX);
176
+ z = y1 * Math.sin(this.rotationX) + z1 * Math.cos(this.rotationX);
177
+
178
+ // Apply Y-axis rotation
179
+ let x1 = x;
180
+ z1 = z;
181
+ x = x1 * Math.cos(this.rotationY) + z1 * Math.sin(this.rotationY);
182
+ z = -x1 * Math.sin(this.rotationY) + z1 * Math.cos(this.rotationY);
183
+
184
+ // Apply Z-axis rotation
185
+ x1 = x;
186
+ y1 = y;
187
+ x = x1 * Math.cos(this.rotationZ) - y1 * Math.sin(this.rotationZ);
188
+ y = x1 * Math.sin(this.rotationZ) + y1 * Math.cos(this.rotationZ);
189
+
190
+ return { x, y, z };
191
+ };
192
+
193
+ /**
194
+ * Isometric projection of 3D point
195
+ * @param {number} x
196
+ * @param {number} y
197
+ * @param {number} z
198
+ * @returns {{x: number, y: number, z: number}}
199
+ */
200
+ const iso = (x, y, z) => {
201
+ // Apply rotations first
202
+ const rotated = rotate3D(x, y, z);
203
+
204
+ // Then apply isometric projection
205
+ const isoX = (rotated.x - rotated.y) * Math.cos(Math.PI / 6);
206
+ const isoY = (rotated.x + rotated.y) * Math.sin(Math.PI / 6) - rotated.z;
207
+ return {
208
+ x: isoX,
209
+ y: isoY,
210
+ z: rotated.z,
211
+ nx: rotated.x / r, // Normalized x for normal vector
212
+ ny: rotated.y / r, // Normalized y for normal vector
213
+ nz: rotated.z / r, // Normalized z for normal vector
214
+ };
215
+ };
216
+
217
+ // Generate sphere points
218
+ const spherePoints = [];
219
+
220
+ // Generate grid of points on the sphere
221
+ for (let vi = 0; vi <= this.vSegments; vi++) {
222
+ const row = [];
223
+ const v = vi / this.vSegments;
224
+ const phi = Math.PI * v - Math.PI / 2; // Vertical angle (-PI/2 to PI/2)
225
+
226
+ for (let hi = 0; hi <= this.hSegments; hi++) {
227
+ const u = hi / this.hSegments;
228
+ const theta = 2 * Math.PI * u; // Horizontal angle (0 to 2*PI)
229
+
230
+ // Convert spherical coordinates to Cartesian
231
+ const x = r * Math.cos(phi) * Math.cos(theta);
232
+ const y = r * Math.cos(phi) * Math.sin(theta);
233
+ const z = r * Math.sin(phi);
234
+
235
+ // Add projected point to row
236
+ row.push(iso(x, y, z));
237
+ }
238
+
239
+ spherePoints.push(row);
240
+ }
241
+
242
+ // Create faces from point grid
243
+ const faces = [];
244
+
245
+ for (let vi = 0; vi < this.vSegments; vi++) {
246
+ for (let hi = 0; hi < this.hSegments; hi++) {
247
+ // Get the four corners of this grid cell
248
+ const p00 = spherePoints[vi][hi];
249
+ const p10 = spherePoints[vi][hi + 1];
250
+ const p01 = spherePoints[vi + 1][hi];
251
+ const p11 = spherePoints[vi + 1][hi + 1];
252
+
253
+ // Calculate average Z value for depth sorting
254
+ const avgZ = (p00.z + p10.z + p01.z + p11.z) / 4;
255
+
256
+ // Calculate average normal for color computation
257
+ const avgNx = (p00.nx + p10.nx + p01.nx + p11.nx) / 4;
258
+ const avgNy = (p00.ny + p10.ny + p01.ny + p11.ny) / 4;
259
+ const avgNz = (p00.nz + p10.nz + p01.nz + p11.nz) / 4;
260
+
261
+ faces.push({
262
+ points: [p00, p10, p11, p01],
263
+ z: avgZ,
264
+ color: this.calculateSurfaceColor(avgNx, avgNy, avgNz),
265
+ });
266
+ }
267
+ }
268
+ // Sort faces by depth (back to front)
269
+ faces.sort((a, b) => b.z - a.z);
270
+ // Draw faces in depth order
271
+ if (this.wireframe) {
272
+ // Draw as wireframe
273
+ for (const face of faces) {
274
+ const pts = face.points;
275
+ for (let i = 0; i < pts.length; i++) {
276
+ const j = (i + 1) % pts.length;
277
+ Painter.lines.line(
278
+ pts[i].x,
279
+ pts[i].y,
280
+ pts[j].x,
281
+ pts[j].y,
282
+ this.stroke,
283
+ this.lineWidth
284
+ );
285
+ }
286
+ }
287
+ }
288
+ for (const face of faces) {
289
+ Painter.shapes.polygon(
290
+ face.points,
291
+ face.color,
292
+ this.stroke,
293
+ this.lineWidth
294
+ );
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Compute bounding box for interactivity and layout
300
+ * @returns {{x: number, y: number, width: number, height: number}}
301
+ */
302
+ getBounds() {
303
+ // For a sphere, the bounding box is straightforward
304
+ const projectionFactor = 1.5; // Approximation for isometric projection
305
+ const diameter = this.radius * 2 * projectionFactor;
306
+
307
+ return {
308
+ x: this.x - diameter / 2,
309
+ y: this.y - diameter / 2,
310
+ width: diameter,
311
+ height: diameter,
312
+ };
313
+ }
314
+ }