@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,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
|
+
}
|