@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,175 @@
|
|
|
1
|
+
import { Painter } from "../../painter/painter.js";
|
|
2
|
+
import { ZOrderedCollection } from "../../util/zindex.js";
|
|
3
|
+
import { GameObject } from "./go.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scene - A Hierarchical Container for Game Objects
|
|
7
|
+
*
|
|
8
|
+
* ### Conceptual Overview
|
|
9
|
+
*
|
|
10
|
+
* The Scene class represents a complex spatial management system that goes beyond
|
|
11
|
+
* simple object containment. It serves as a specialized GameObject that:
|
|
12
|
+
*
|
|
13
|
+
* - Creates a local coordinate system
|
|
14
|
+
* - Manages spatial relationships between game objects
|
|
15
|
+
* - Provides dynamic bounds calculation
|
|
16
|
+
* - Handles rendering and updating of child objects
|
|
17
|
+
*
|
|
18
|
+
* ### Rendering Pipeline Integration
|
|
19
|
+
*
|
|
20
|
+
* The Scene operates as a crucial middleware in the rendering pipeline:
|
|
21
|
+
* 1. Inherits transformation capabilities from GameObject
|
|
22
|
+
* 2. Applies collective transformations to child objects
|
|
23
|
+
* 3. Calculates composite bounds dynamically
|
|
24
|
+
*
|
|
25
|
+
* @extends GameObject
|
|
26
|
+
*/
|
|
27
|
+
export class Scene extends GameObject {
|
|
28
|
+
constructor(game, options = {}) {
|
|
29
|
+
super(game, options);
|
|
30
|
+
// Create the z-ordered collection
|
|
31
|
+
this._collection = new ZOrderedCollection({
|
|
32
|
+
sortByZIndex: options.sortByZIndex || true,
|
|
33
|
+
});
|
|
34
|
+
this._collection._owner = this;
|
|
35
|
+
// Initialize dimensions and override properties
|
|
36
|
+
this._width = options.width ?? 0;
|
|
37
|
+
this._height = options.height ?? 0;
|
|
38
|
+
this.forceWidth = null; // CRITICAL: Initialize with null
|
|
39
|
+
this.forceHeight = null; // CRITICAL: Initialize with null
|
|
40
|
+
// Cache for natural dimensions
|
|
41
|
+
this._naturalWidth = null;
|
|
42
|
+
this._naturalHeight = null;
|
|
43
|
+
this.userDefinedDimensions = false;
|
|
44
|
+
if (options.width != undefined && options.height != undefined) {
|
|
45
|
+
this.userDefinedWidth = options.width;
|
|
46
|
+
this.userDefinedHeight = options.height;
|
|
47
|
+
this.userDefinedDimensions = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Update method - update children and recalculate natural dimensions
|
|
52
|
+
update(dt) {
|
|
53
|
+
this.logger.groupCollapsed(
|
|
54
|
+
"Scene.update: " +
|
|
55
|
+
(this.name == undefined ? this.constructor.name : this.name)
|
|
56
|
+
);
|
|
57
|
+
// Update all active children
|
|
58
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
59
|
+
const child = this.children[i];
|
|
60
|
+
if (child.active && child.update) {
|
|
61
|
+
child.update(dt);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
super.update(dt);
|
|
65
|
+
this.logger.groupEnd();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add a GameObject to the scene and invalidate dimensions
|
|
69
|
+
add(go) {
|
|
70
|
+
if (go == null || go == undefined) {
|
|
71
|
+
throw new Error("GameObject is null or undefined");
|
|
72
|
+
}
|
|
73
|
+
if (go.parent != null) {
|
|
74
|
+
console.warn(
|
|
75
|
+
"This GameObject already has a parent. Consider removing it first."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
go.parent = this;
|
|
79
|
+
this._collection.add(go);
|
|
80
|
+
this.markBoundsDirty();
|
|
81
|
+
// if the game object has an init method, call it
|
|
82
|
+
if (go.init) {
|
|
83
|
+
go.init();
|
|
84
|
+
}
|
|
85
|
+
return go;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
markBoundsDirty() {
|
|
89
|
+
super.markBoundsDirty();
|
|
90
|
+
// Dirty the children's bounds
|
|
91
|
+
this.children.forEach((child) => {
|
|
92
|
+
child.markBoundsDirty();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Removes a GameObject from the Scene
|
|
98
|
+
*
|
|
99
|
+
* ### Removal Strategy
|
|
100
|
+
*
|
|
101
|
+
* #### Why Use Filter?
|
|
102
|
+
* - Creates a new array (immutable approach)
|
|
103
|
+
* - Ensures no direct mutation of the children array
|
|
104
|
+
*
|
|
105
|
+
* #### Parent Reference Clearing
|
|
106
|
+
* - Breaks the parent-child relationship
|
|
107
|
+
* - Allows the object to be re-parented elsewhere
|
|
108
|
+
*
|
|
109
|
+
* @param {GameObject} go - Game object to remove
|
|
110
|
+
*/
|
|
111
|
+
remove(go) {
|
|
112
|
+
const result = this._collection.remove(go);
|
|
113
|
+
if (result) {
|
|
114
|
+
go.parent = null;
|
|
115
|
+
this.markBoundsDirty();
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
draw() {
|
|
121
|
+
super.draw();
|
|
122
|
+
this.logger.log("Scene.draw chilren:");
|
|
123
|
+
this._collection
|
|
124
|
+
.getSortedChildren()
|
|
125
|
+
.filter((obj) => obj.visible)
|
|
126
|
+
.map(function (obj) {
|
|
127
|
+
Painter.save();
|
|
128
|
+
obj.render();
|
|
129
|
+
Painter.restore();
|
|
130
|
+
return obj;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Returns debug bounds in local space (centered at origin).
|
|
136
|
+
* Used for debug drawing after transforms have been applied.
|
|
137
|
+
* @returns {{x: number, y: number, width: number, height: number}}
|
|
138
|
+
*/
|
|
139
|
+
getDebugBounds() {
|
|
140
|
+
// Return bounds centered at local origin (0, 0)
|
|
141
|
+
// This works because debug is drawn after translation to scene's position
|
|
142
|
+
return {
|
|
143
|
+
width: this.width,
|
|
144
|
+
height: this.height,
|
|
145
|
+
x: -this.width / 2,
|
|
146
|
+
y: -this.height / 2,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
bringToFront(go) {
|
|
151
|
+
return this._collection.bringToFront(go);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
sendToBack(go) {
|
|
155
|
+
return this._collection.sendToBack(go);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bringForward(go) {
|
|
159
|
+
return this._collection.bringForward(go);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
sendBackward(go) {
|
|
163
|
+
return this._collection.sendBackward(go);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
clear() {
|
|
167
|
+
this._collection.children.forEach((go) => this.remove(go));
|
|
168
|
+
return this._collection.clear();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Getter to access children
|
|
172
|
+
get children() {
|
|
173
|
+
return this._collection.children;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Scene } from "./scene.js";
|
|
2
|
+
import { Painter } from "../../painter/painter.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scene3D - A Scene that projects children through Camera3D
|
|
6
|
+
*
|
|
7
|
+
* Bridges the GameObject/Scene system with Camera3D, allowing GameObjects
|
|
8
|
+
* to be positioned in 3D space and automatically projected through the camera.
|
|
9
|
+
*
|
|
10
|
+
* Children can have an optional `z` property (defaults to 0 if not present).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const scene3d = new Scene3D(this, {
|
|
14
|
+
* x: this.width / 2,
|
|
15
|
+
* y: this.height / 2,
|
|
16
|
+
* camera: this.camera,
|
|
17
|
+
* depthSort: true,
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const box = new MyBox(this, { x: -100, y: 0 });
|
|
21
|
+
* box.z = -50; // Behind center plane
|
|
22
|
+
* scene3d.add(box);
|
|
23
|
+
*
|
|
24
|
+
* this.pipeline.add(scene3d);
|
|
25
|
+
*
|
|
26
|
+
* @extends Scene
|
|
27
|
+
*/
|
|
28
|
+
export class Scene3D extends Scene {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Game} game - Game instance
|
|
31
|
+
* @param {Object} options - Configuration
|
|
32
|
+
* @param {Camera3D} options.camera - Required Camera3D for projection
|
|
33
|
+
* @param {boolean} [options.depthSort=true] - Sort children by depth (back-to-front)
|
|
34
|
+
* @param {boolean} [options.scaleByDepth=true] - Scale children by perspective
|
|
35
|
+
*/
|
|
36
|
+
constructor(game, options = {}) {
|
|
37
|
+
super(game, options);
|
|
38
|
+
|
|
39
|
+
if (!options.camera) {
|
|
40
|
+
throw new Error("Scene3D requires a camera option");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.camera = options.camera;
|
|
44
|
+
this.depthSort = options.depthSort ?? true;
|
|
45
|
+
this.scaleByDepth = options.scaleByDepth ?? true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Override Scene's draw to project children through Camera3D
|
|
50
|
+
*/
|
|
51
|
+
render() {
|
|
52
|
+
// Do NOT call super.render() as it triggers the 2D Scene.draw() pass,
|
|
53
|
+
// causing double rendering (one 2D, one 3D).
|
|
54
|
+
|
|
55
|
+
if (!this.visible) return;
|
|
56
|
+
|
|
57
|
+
Painter.save();
|
|
58
|
+
// Translate to the Scene3D's position (e.g., center of screen)
|
|
59
|
+
// This defines the "origin" (0,0) of the 3D projection on the 2D canvas.
|
|
60
|
+
Painter.translateTo(this.x, this.y);
|
|
61
|
+
|
|
62
|
+
// Build render list with projections
|
|
63
|
+
const renderList = [];
|
|
64
|
+
|
|
65
|
+
for (const child of this._collection.getSortedChildren()) {
|
|
66
|
+
if (!child.visible) continue;
|
|
67
|
+
|
|
68
|
+
const z = child.z ?? 0;
|
|
69
|
+
const projected = this.camera.project(child.x, child.y, z);
|
|
70
|
+
|
|
71
|
+
// Cull if behind camera
|
|
72
|
+
if (projected.z < -this.camera.perspective + 10) continue;
|
|
73
|
+
|
|
74
|
+
renderList.push({
|
|
75
|
+
child,
|
|
76
|
+
x: projected.x,
|
|
77
|
+
y: projected.y,
|
|
78
|
+
z: projected.z,
|
|
79
|
+
scale: projected.scale,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Sort order: respect zIndex first, then depth for subtle overlap stability.
|
|
84
|
+
// Higher zIndex should render later (on top).
|
|
85
|
+
if (this.depthSort) {
|
|
86
|
+
renderList.sort((a, b) => {
|
|
87
|
+
const za = a.child.zIndex ?? 0;
|
|
88
|
+
const zb = b.child.zIndex ?? 0;
|
|
89
|
+
if (za !== zb) return za - zb;
|
|
90
|
+
return b.z - a.z; // back-to-front by depth as tie-breaker
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Render each child at projected position
|
|
95
|
+
for (const item of renderList) {
|
|
96
|
+
Painter.save();
|
|
97
|
+
|
|
98
|
+
// 1. Move to the projected screen position
|
|
99
|
+
Painter.translateTo(item.x, item.y);
|
|
100
|
+
|
|
101
|
+
// 2. Apply perspective scale
|
|
102
|
+
if (this.scaleByDepth) {
|
|
103
|
+
Painter.ctx.scale(item.scale, item.scale);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. Neutralize the child's own translation logic
|
|
107
|
+
// GameObject.render() will translate by (child.x, child.y).
|
|
108
|
+
// By applying (-child.x, -child.y) first (in the scaled context),
|
|
109
|
+
// we ensure the net visual position is exactly at (item.x, item.y).
|
|
110
|
+
Painter.translateTo(-item.child.x, -item.child.y);
|
|
111
|
+
|
|
112
|
+
item.child.render();
|
|
113
|
+
Painter.restore();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Painter.restore();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/************************************************************
|
|
2
|
+
* Text.js
|
|
3
|
+
*
|
|
4
|
+
* A simple GameObject for drawing styled text on the canvas.
|
|
5
|
+
* Supports optional stroke, alignment, and baseline.
|
|
6
|
+
************************************************************/
|
|
7
|
+
|
|
8
|
+
import { GameObject } from "./go.js";
|
|
9
|
+
import { GameObjectShapeWrapper } from "./wrapper.js";
|
|
10
|
+
import { Painter } from "../../painter/painter.js";
|
|
11
|
+
import { TextShape } from "../../shapes/text.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Text - Renders a string onto the canvas using the Painter API.
|
|
15
|
+
* @extends GameObjectShapeWrapper
|
|
16
|
+
*
|
|
17
|
+
* Creates a GameObject that wraps a TextShape, providing a convenient API
|
|
18
|
+
* for text rendering with support for styling, alignment, and measurement.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```js
|
|
22
|
+
* const myText = new Text(game, "Hello World!", {
|
|
23
|
+
* x: 100,
|
|
24
|
+
* y: 50,
|
|
25
|
+
* font: "20px Arial",
|
|
26
|
+
* color: "#ff0",
|
|
27
|
+
* align: "center",
|
|
28
|
+
* baseline: "middle",
|
|
29
|
+
* stroke: true,
|
|
30
|
+
* strokeColor: "#000",
|
|
31
|
+
* lineWidth: 2,
|
|
32
|
+
* });
|
|
33
|
+
* game.pipeline.add(myText);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class Text extends GameObjectShapeWrapper {
|
|
37
|
+
/**
|
|
38
|
+
* Create a Text component
|
|
39
|
+
* @param {Game} game - The main game instance
|
|
40
|
+
* @param {string} text - The text content to display
|
|
41
|
+
* @param {object} [options={}] - Configuration options
|
|
42
|
+
* @param {number} [options.x=0] - X-position
|
|
43
|
+
* @param {number} [options.y=0] - Y-position
|
|
44
|
+
* @param {string} [options.font="16px monospace"] - CSS-style font string
|
|
45
|
+
* @param {string} [options.color="#fff"] - Text color
|
|
46
|
+
* @param {string} [options.align="left"] - Text alignment
|
|
47
|
+
* @param {string} [options.baseline="top"] - Text baseline
|
|
48
|
+
* @param {boolean} [options.stroke=false] - Whether to stroke the text
|
|
49
|
+
* @param {string} [options.strokeColor="#000"] - Stroke color
|
|
50
|
+
* @param {number} [options.lineWidth=1] - Stroke width
|
|
51
|
+
* @param {boolean} [options.interactive=false] - Whether the text should be interactive
|
|
52
|
+
* @param {string} [options.anchor] - Optional anchor position (e.g., "top-left", "center")
|
|
53
|
+
* @param {number} [options.padding] - Padding when using anchors
|
|
54
|
+
*/
|
|
55
|
+
constructor(game, text, options = {}) {
|
|
56
|
+
// Create the TextShape
|
|
57
|
+
const textShape = new TextShape(text, {
|
|
58
|
+
font: options.font || "16px monospace",
|
|
59
|
+
color: options.color || "yellow",
|
|
60
|
+
align: options.align || "left",
|
|
61
|
+
baseline: options.baseline || "top",
|
|
62
|
+
strokeColor: options.strokeColor || "#000",
|
|
63
|
+
lineWidth: options.lineWidth || 1,
|
|
64
|
+
debugColor: options.debugColor || "yellow",
|
|
65
|
+
});
|
|
66
|
+
// Pass the shape to the parent GameObjectShapeWrapper
|
|
67
|
+
super(game, textShape, options);
|
|
68
|
+
// Store reference to text-specific options for direct access
|
|
69
|
+
this._textOptions = {
|
|
70
|
+
font: options.font || "16px monospace",
|
|
71
|
+
color: options.color || "yellow",
|
|
72
|
+
align: options.align || "left",
|
|
73
|
+
baseline: options.baseline || "top",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the text content
|
|
79
|
+
* @returns {string} Current text
|
|
80
|
+
*/
|
|
81
|
+
get text() {
|
|
82
|
+
return this.shape.text;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set the text content
|
|
87
|
+
* @param {string} value - New text to display
|
|
88
|
+
*/
|
|
89
|
+
set text(value) {
|
|
90
|
+
this.shape.text = value;
|
|
91
|
+
this.markBoundsDirty();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the font style
|
|
96
|
+
* @returns {string} Current font
|
|
97
|
+
*/
|
|
98
|
+
get font() {
|
|
99
|
+
return this.shape.font;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set the font style
|
|
104
|
+
* @param {string} value - New font style
|
|
105
|
+
*/
|
|
106
|
+
set font(value) {
|
|
107
|
+
this.shape.font = value;
|
|
108
|
+
this._textOptions.font = value;
|
|
109
|
+
this.markBoundsDirty();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the text color
|
|
114
|
+
* @returns {string} Current color
|
|
115
|
+
*/
|
|
116
|
+
get color() {
|
|
117
|
+
return this.shape.color;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Set the text color
|
|
122
|
+
* @param {string} value - New color
|
|
123
|
+
*/
|
|
124
|
+
set color(value) {
|
|
125
|
+
this.shape.color = value;
|
|
126
|
+
this._textOptions.color = value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get text alignment
|
|
131
|
+
* @returns {string} Current alignment
|
|
132
|
+
*/
|
|
133
|
+
get align() {
|
|
134
|
+
return this.shape.align;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Set text alignment
|
|
139
|
+
* @param {string} value - New alignment
|
|
140
|
+
*/
|
|
141
|
+
set align(value) {
|
|
142
|
+
this.shape.align = value;
|
|
143
|
+
this._textOptions.align = value;
|
|
144
|
+
this.markBoundsDirty();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get text baseline
|
|
149
|
+
* @returns {string} Current baseline
|
|
150
|
+
*/
|
|
151
|
+
get baseline() {
|
|
152
|
+
return this.shape.baseline;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Set text baseline
|
|
157
|
+
* @param {string} value - New baseline
|
|
158
|
+
*/
|
|
159
|
+
set baseline(value) {
|
|
160
|
+
this.shape.baseline = value;
|
|
161
|
+
this._textOptions.baseline = value;
|
|
162
|
+
this.markBoundsDirty();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Calculate the width based on the text content
|
|
167
|
+
* @returns {number} Approximate width of the text
|
|
168
|
+
*/
|
|
169
|
+
measureWidth() {
|
|
170
|
+
if (!Painter.ctx) return 0;
|
|
171
|
+
const width = Painter.text.measureTextWidth(this.text, this.font);
|
|
172
|
+
return width;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Calculate the height based on the font size
|
|
177
|
+
* @returns {number} Approximate height of the text
|
|
178
|
+
*/
|
|
179
|
+
measureHeight() {
|
|
180
|
+
if (!this.font) return 16; // Default font size as fallback
|
|
181
|
+
|
|
182
|
+
// Extract font size from font string (e.g., "16px Arial" → 16)
|
|
183
|
+
const fontSize = parseInt(this.font);
|
|
184
|
+
return isNaN(fontSize) ? 16 : fontSize;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Gets the text bounds accounting for alignment and baseline
|
|
189
|
+
* @returns {Object} Bounds object with { x, y, width, height }
|
|
190
|
+
*/
|
|
191
|
+
getBounds() {
|
|
192
|
+
const bounds = super.getBounds();
|
|
193
|
+
|
|
194
|
+
// If the shape has alignment-aware bounds, use those
|
|
195
|
+
if (this.shape.getTextBounds) {
|
|
196
|
+
const textBounds = this.shape.getTextBounds();
|
|
197
|
+
return {
|
|
198
|
+
x: this.x,
|
|
199
|
+
y: this.y,
|
|
200
|
+
width: textBounds.width,
|
|
201
|
+
height: textBounds.height,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return bounds;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Updates the GameObject and the wrapped TextShape
|
|
210
|
+
* @param {number} dt - Delta time in seconds
|
|
211
|
+
*/
|
|
212
|
+
update(dt) {
|
|
213
|
+
super.update(dt);
|
|
214
|
+
|
|
215
|
+
// Sync dimensions from the text shape
|
|
216
|
+
if (this.shape) {
|
|
217
|
+
this.width = this.shape.width || this.measureWidth();
|
|
218
|
+
this.height = this.shape.height || this.measureHeight();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|