@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.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- 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
|
+
}
|