@guinetik/gcanvas 1.0.4 → 2.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/dist/CNAME +1 -0
- package/dist/aizawa.html +27 -0
- package/dist/animations.html +31 -0
- package/dist/basic.html +38 -0
- package/dist/baskara.html +31 -0
- package/dist/bezier.html +35 -0
- package/dist/beziersignature.html +29 -0
- package/dist/blackhole.html +28 -0
- package/dist/blob.html +35 -0
- package/dist/clifford.html +25 -0
- package/dist/cmb.html +24 -0
- package/dist/coordinates.html +698 -0
- package/dist/cube3d.html +23 -0
- package/dist/dadras.html +26 -0
- package/dist/dejong.html +25 -0
- package/dist/demos.css +303 -0
- package/dist/dino.html +42 -0
- package/dist/easing.html +28 -0
- package/dist/events.html +195 -0
- package/dist/fluent.html +647 -0
- package/dist/fluid-simple.html +22 -0
- package/dist/fluid.html +37 -0
- package/dist/fractals.html +36 -0
- package/dist/gameobjects.html +626 -0
- package/dist/gcanvas.es.js +14368 -9093
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/genart.html +26 -0
- package/dist/gendream.html +26 -0
- package/dist/group.html +36 -0
- package/dist/halvorsen.html +27 -0
- package/dist/home.html +587 -0
- package/dist/hyperbolic001.html +23 -0
- package/dist/hyperbolic002.html +23 -0
- package/dist/hyperbolic003.html +23 -0
- package/dist/hyperbolic004.html +23 -0
- package/dist/hyperbolic005.html +22 -0
- package/dist/index.html +446 -0
- package/dist/isometric.html +34 -0
- package/dist/js/aizawa.js +425 -0
- package/dist/js/animations.js +452 -0
- package/dist/js/basic.js +204 -0
- package/dist/js/baskara.js +751 -0
- package/dist/js/bezier.js +692 -0
- package/dist/js/beziersignature.js +241 -0
- package/dist/js/blackhole/accretiondisk.obj.js +379 -0
- package/dist/js/blackhole/blackhole.obj.js +318 -0
- package/dist/js/blackhole/index.js +409 -0
- package/dist/js/blackhole/particle.js +56 -0
- package/dist/js/blackhole/starfield.obj.js +218 -0
- package/dist/js/blob.js +2276 -0
- package/dist/js/clifford.js +236 -0
- package/dist/js/cmb.js +594 -0
- package/dist/js/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -0
- package/dist/js/dadras.js +405 -0
- package/dist/js/dejong.js +257 -0
- package/dist/js/dino.js +1420 -0
- package/dist/js/easing.js +477 -0
- package/dist/js/fluent.js +183 -0
- package/dist/js/fluid-simple.js +253 -0
- package/dist/js/fluid.js +527 -0
- package/dist/js/fractals.js +932 -0
- package/dist/js/fractalworker.js +93 -0
- package/dist/js/gameobjects.js +176 -0
- package/dist/js/genart.js +268 -0
- package/dist/js/gendream.js +209 -0
- package/dist/js/group.js +140 -0
- package/dist/js/halvorsen.js +405 -0
- package/dist/js/hyperbolic001.js +310 -0
- package/dist/js/hyperbolic002.js +388 -0
- package/dist/js/hyperbolic003.js +319 -0
- package/dist/js/hyperbolic004.js +345 -0
- package/dist/js/hyperbolic005.js +340 -0
- package/dist/js/info-toggle.js +25 -0
- package/dist/js/isometric.js +851 -0
- package/dist/js/kerr.js +1547 -0
- package/dist/js/lavalamp.js +590 -0
- package/dist/js/layout.js +354 -0
- package/dist/js/lorenz.js +425 -0
- package/dist/js/mondrian.js +285 -0
- package/dist/js/opacity.js +275 -0
- package/dist/js/painter.js +484 -0
- package/dist/js/particles-showcase.js +514 -0
- package/dist/js/particles.js +299 -0
- package/dist/js/patterns.js +397 -0
- package/dist/js/penrose/artifact.js +69 -0
- package/dist/js/penrose/blackhole.js +121 -0
- package/dist/js/penrose/constants.js +73 -0
- package/dist/js/penrose/game.js +943 -0
- package/dist/js/penrose/lore.js +278 -0
- package/dist/js/penrose/penrosescene.js +892 -0
- package/dist/js/penrose/ship.js +216 -0
- package/dist/js/penrose/sounds.js +211 -0
- package/dist/js/penrose/voidparticle.js +55 -0
- package/dist/js/penrose/voidscene.js +258 -0
- package/dist/js/penrose/voidship.js +144 -0
- package/dist/js/penrose/wormhole.js +46 -0
- package/dist/js/pipeline.js +555 -0
- package/dist/js/plane3d.js +256 -0
- package/dist/js/platformer.js +1579 -0
- package/dist/js/rossler.js +480 -0
- package/dist/js/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +706 -0
- package/dist/js/schwarzschild.js +1015 -0
- package/dist/js/shapes.js +628 -0
- package/dist/js/space/alien.js +171 -0
- package/dist/js/space/boom.js +98 -0
- package/dist/js/space/boss.js +353 -0
- package/dist/js/space/buff.js +73 -0
- package/dist/js/space/bullet.js +102 -0
- package/dist/js/space/constants.js +85 -0
- package/dist/js/space/game.js +1884 -0
- package/dist/js/space/hud.js +112 -0
- package/dist/js/space/laserbeam.js +179 -0
- package/dist/js/space/lightning.js +277 -0
- package/dist/js/space/minion.js +192 -0
- package/dist/js/space/missile.js +212 -0
- package/dist/js/space/player.js +430 -0
- package/dist/js/space/powerup.js +90 -0
- package/dist/js/space/starfield.js +58 -0
- package/dist/js/space/starpower.js +90 -0
- package/dist/js/spacetime.js +559 -0
- package/dist/js/sphere3d.js +229 -0
- package/dist/js/sprite.js +473 -0
- package/dist/js/starfaux/config.js +118 -0
- package/dist/js/starfaux/enemy.js +353 -0
- package/dist/js/starfaux/hud.js +78 -0
- package/dist/js/starfaux/index.js +482 -0
- package/dist/js/starfaux/laser.js +182 -0
- package/dist/js/starfaux/player.js +468 -0
- package/dist/js/starfaux/terrain.js +560 -0
- package/dist/js/study001.js +275 -0
- package/dist/js/study002.js +366 -0
- package/dist/js/study003.js +331 -0
- package/dist/js/study004.js +389 -0
- package/dist/js/study005.js +209 -0
- package/dist/js/study006.js +194 -0
- package/dist/js/study007.js +192 -0
- package/dist/js/study008.js +413 -0
- package/dist/js/svgtween.js +204 -0
- package/dist/js/tde/accretiondisk.js +471 -0
- package/dist/js/tde/blackhole.js +219 -0
- package/dist/js/tde/blackholescene.js +209 -0
- package/dist/js/tde/config.js +59 -0
- package/dist/js/tde/index.js +820 -0
- package/dist/js/tde/jets.js +290 -0
- package/dist/js/tde/lensedstarfield.js +154 -0
- package/dist/js/tde/tdestar.js +297 -0
- package/dist/js/tde/tidalstream.js +372 -0
- package/dist/js/tde_old/blackhole.obj.js +354 -0
- package/dist/js/tde_old/debris.obj.js +791 -0
- package/dist/js/tde_old/flare.obj.js +239 -0
- package/dist/js/tde_old/index.js +448 -0
- package/dist/js/tde_old/star.obj.js +812 -0
- package/dist/js/tetris/config.js +157 -0
- package/dist/js/tetris/grid.js +286 -0
- package/dist/js/tetris/index.js +1195 -0
- package/dist/js/tetris/renderer.js +634 -0
- package/dist/js/tetris/tetrominos.js +280 -0
- package/dist/js/thomas.js +394 -0
- package/dist/js/tiles.js +312 -0
- package/dist/js/tweendemo.js +79 -0
- package/dist/js/visibility.js +102 -0
- package/dist/kerr.html +28 -0
- package/dist/lavalamp.html +27 -0
- package/dist/layouts.html +37 -0
- package/dist/logo.svg +4 -0
- package/dist/loop.html +84 -0
- package/dist/lorenz.html +27 -0
- package/dist/mondrian.html +32 -0
- package/dist/og_image.png +0 -0
- package/dist/opacity.html +36 -0
- package/dist/painter.html +39 -0
- package/dist/particles-showcase.html +28 -0
- package/dist/particles.html +24 -0
- package/dist/patterns.html +33 -0
- package/dist/penrose-game.html +31 -0
- package/dist/pipeline.html +737 -0
- package/dist/plane3d.html +24 -0
- package/dist/platformer.html +43 -0
- package/dist/rossler.html +27 -0
- package/dist/scene-interactivity-test.html +220 -0
- package/dist/scene.html +33 -0
- package/dist/scenes.html +96 -0
- package/dist/schrodinger.html +27 -0
- package/dist/schwarzschild.html +27 -0
- package/dist/shapes.html +16 -0
- package/dist/space.html +85 -0
- package/dist/spacetime.html +27 -0
- package/dist/sphere3d.html +24 -0
- package/dist/sprite.html +18 -0
- package/dist/starfaux.html +22 -0
- package/dist/study001.html +23 -0
- package/dist/study002.html +23 -0
- package/dist/study003.html +23 -0
- package/dist/study004.html +23 -0
- package/dist/study005.html +22 -0
- package/dist/study006.html +24 -0
- package/dist/study007.html +24 -0
- package/dist/study008.html +22 -0
- package/dist/svgtween.html +29 -0
- package/dist/tde.html +28 -0
- package/dist/tetris3d.html +25 -0
- package/dist/thomas.html +27 -0
- package/dist/tiles.html +28 -0
- package/dist/transforms.html +400 -0
- package/dist/tween.html +45 -0
- package/dist/visibility.html +33 -0
- package/package.json +1 -1
- package/readme.md +30 -22
- package/src/game/objects/go.js +7 -0
- package/src/game/objects/index.js +2 -0
- package/src/game/objects/isometric-scene.js +53 -3
- package/src/game/objects/layoutscene.js +57 -0
- package/src/game/objects/mask.js +241 -0
- package/src/game/objects/scene.js +19 -0
- package/src/game/objects/wrapper.js +14 -2
- package/src/game/pipeline.js +17 -0
- package/src/game/ui/button.js +101 -16
- package/src/game/ui/theme.js +0 -6
- package/src/game/ui/togglebutton.js +25 -14
- package/src/game/ui/tooltip.js +12 -4
- package/src/index.js +3 -0
- package/src/io/gesture.js +409 -0
- package/src/io/index.js +4 -1
- package/src/io/keys.js +9 -1
- package/src/io/screen.js +476 -0
- package/src/math/attractors.js +664 -0
- package/src/math/heat.js +106 -0
- package/src/math/index.js +1 -0
- package/src/mixins/draggable.js +15 -19
- package/src/painter/painter.shapes.js +11 -5
- package/src/particle/particle-system.js +165 -1
- package/src/physics/index.js +26 -0
- package/src/physics/physics-updaters.js +333 -0
- package/src/physics/physics.js +375 -0
- package/src/shapes/image.js +5 -5
- package/src/shapes/index.js +2 -0
- package/src/shapes/parallelogram.js +147 -0
- package/src/shapes/righttriangle.js +115 -0
- package/src/shapes/svg.js +281 -100
- package/src/shapes/text.js +22 -6
- package/src/shapes/transformable.js +5 -0
- package/src/sound/effects.js +807 -0
- package/src/sound/index.js +13 -0
- package/src/webgl/index.js +7 -0
- package/src/webgl/shaders/clifford-point-shaders.js +131 -0
- package/src/webgl/shaders/dejong-point-shaders.js +131 -0
- package/src/webgl/shaders/point-sprite-shaders.js +152 -0
- package/src/webgl/webgl-clifford-renderer.js +477 -0
- package/src/webgl/webgl-dejong-renderer.js +472 -0
- package/src/webgl/webgl-line-renderer.js +391 -0
- package/src/webgl/webgl-particle-renderer.js +410 -0
- package/types/index.d.ts +30 -2
- package/types/io.d.ts +217 -0
- package/types/physics.d.ts +299 -0
- package/types/shapes.d.ts +8 -0
- package/types/webgl.d.ts +188 -109
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Game,
|
|
3
|
+
Scene,
|
|
4
|
+
FPSCounter,
|
|
5
|
+
Button,
|
|
6
|
+
HorizontalLayout,
|
|
7
|
+
Painter,
|
|
8
|
+
ToggleButton,
|
|
9
|
+
Cursor,
|
|
10
|
+
TextShape,
|
|
11
|
+
Circle,
|
|
12
|
+
GameObject,
|
|
13
|
+
Position,
|
|
14
|
+
} from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The PaintScene:
|
|
18
|
+
* - Holds an array of "strokes" (either lines or freehand paths).
|
|
19
|
+
* - Each stroke is a plain JS object with fields: type, points, color, etc.
|
|
20
|
+
*/
|
|
21
|
+
class PaintScene extends GameObject {
|
|
22
|
+
constructor(game, options = {}) {
|
|
23
|
+
super(game, options);
|
|
24
|
+
this.MARGIN = 0;
|
|
25
|
+
this.currentTool = "pencil"; // 'line', 'pencil', or 'eraser'
|
|
26
|
+
this.lineStart = null; // used for line tool
|
|
27
|
+
this.activeStroke = null; // stroke being drawn right now
|
|
28
|
+
this.strokes = []; // all finalized strokes
|
|
29
|
+
this.pencilCursor = new TextShape("✏️", {
|
|
30
|
+
font: "30px monospace",
|
|
31
|
+
color: "green",
|
|
32
|
+
});
|
|
33
|
+
this.eraserCursor = new TextShape(" 🦯", { font: "30px monospace" });
|
|
34
|
+
this.lineCursor = new TextShape("✒️", { font: "30px monospace" });
|
|
35
|
+
this.lineCursorP = new Circle(5, { color: "red" });
|
|
36
|
+
this.cursor = new Cursor(game, this.pencilCursor, this.pencilCursor, {
|
|
37
|
+
x: 0,
|
|
38
|
+
y: 0,
|
|
39
|
+
});
|
|
40
|
+
this.cursor.offsetX = -10;
|
|
41
|
+
this.cursor.offsetY = -25;
|
|
42
|
+
//
|
|
43
|
+
this.totalPoints = 0; // Track total points across all strokes
|
|
44
|
+
this.MAX_POINTS = 10000; // Maximum number of points to keep
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set the current tool to use.
|
|
49
|
+
*/
|
|
50
|
+
setTool(tool) {
|
|
51
|
+
this.currentTool = tool;
|
|
52
|
+
this.lineStart = null;
|
|
53
|
+
this.activeStroke = null;
|
|
54
|
+
if (tool === "eraser") {
|
|
55
|
+
this.cursor.normalShape = this.cursor.pressedShape = this.eraserCursor;
|
|
56
|
+
// this.cursor.offsetX = 10;
|
|
57
|
+
this.cursor.offsetY = -25;
|
|
58
|
+
} else if (tool === "line") {
|
|
59
|
+
this.cursor.normalShape = this.cursor.pressedShape = this.lineCursor;
|
|
60
|
+
this.cursor.offsetX = 12;
|
|
61
|
+
this.cursor.offsetY = -25;
|
|
62
|
+
} else {
|
|
63
|
+
this.cursor.normalShape = this.cursor.pressedShape = this.pencilCursor;
|
|
64
|
+
this.cursor.offsetX = -10;
|
|
65
|
+
this.cursor.offsetY = -25;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#prevWidth = 0;
|
|
70
|
+
#prevHeight = 0;
|
|
71
|
+
|
|
72
|
+
update(dt) {
|
|
73
|
+
// Update scene dimensions based on margin
|
|
74
|
+
this.width = this.game.width - this.MARGIN * 2;
|
|
75
|
+
this.height = this.game.height - this.MARGIN * 2;
|
|
76
|
+
// Center the scene in the game
|
|
77
|
+
this.x = this.game.width / 2;
|
|
78
|
+
this.y = this.game.height / 2;
|
|
79
|
+
super.update(dt);
|
|
80
|
+
if (this.#prevWidth !== this.width || this.#prevHeight !== this.height) {
|
|
81
|
+
this.markBoundsDirty();
|
|
82
|
+
}
|
|
83
|
+
this.#prevWidth = this.width;
|
|
84
|
+
this.#prevHeight = this.height;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* This is called every frame after update() to draw everything.
|
|
89
|
+
*/
|
|
90
|
+
draw() {
|
|
91
|
+
super.draw();
|
|
92
|
+
this.logger.log("PaintScene.draw", Painter.ctx.fillStyle);
|
|
93
|
+
// Draw each finalized stroke
|
|
94
|
+
for (let s of this.strokes) {
|
|
95
|
+
this.drawStroke(s, false); // false = not active
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Draw the active stroke differently
|
|
99
|
+
if (this.activeStroke) {
|
|
100
|
+
this.drawStroke(this.activeStroke, true); // true = active
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Draw line preview if needed
|
|
104
|
+
if (this.lineStart && this.currentTool === "line") {
|
|
105
|
+
this.drawLinePreview(this.lineStart, this.currentMousePos);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static gco = [
|
|
110
|
+
"source-over",
|
|
111
|
+
"multiply",
|
|
112
|
+
"screen",
|
|
113
|
+
"overlay",
|
|
114
|
+
"darken",
|
|
115
|
+
"lighten",
|
|
116
|
+
"color-dodge",
|
|
117
|
+
"color-burn",
|
|
118
|
+
"hard-light",
|
|
119
|
+
"soft-light",
|
|
120
|
+
"difference",
|
|
121
|
+
"exclusion",
|
|
122
|
+
"hue",
|
|
123
|
+
"saturation",
|
|
124
|
+
"color",
|
|
125
|
+
"luminosity",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
drawStroke(stroke, isActive = false) {
|
|
129
|
+
// Common setup
|
|
130
|
+
Painter.ctx.save();
|
|
131
|
+
Painter.ctx.globalAlpha = 1;
|
|
132
|
+
|
|
133
|
+
// Set line properties explicitly
|
|
134
|
+
Painter.ctx.lineWidth = stroke.lineWidth || 2;
|
|
135
|
+
Painter.ctx.strokeStyle = stroke.color || "#fff";
|
|
136
|
+
|
|
137
|
+
// IMPORTANT: Different handling for active strokes
|
|
138
|
+
if (isActive) {
|
|
139
|
+
// For active strokes, ensure we're using regular luminosity compositing
|
|
140
|
+
Painter.ctx.globalCompositeOperation =
|
|
141
|
+
stroke.compositeOp || "source-over";
|
|
142
|
+
} else {
|
|
143
|
+
// For completed strokes, use the specified composite operation
|
|
144
|
+
Painter.ctx.globalCompositeOperation =
|
|
145
|
+
stroke.compositeOp || "source-over";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Begin the path
|
|
149
|
+
Painter.ctx.beginPath();
|
|
150
|
+
|
|
151
|
+
const pts = stroke.points;
|
|
152
|
+
if (!pts || pts.length < 2) {
|
|
153
|
+
Painter.ctx.restore();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Start at the first point
|
|
157
|
+
Painter.ctx.moveTo(pts[0].x, pts[0].y);
|
|
158
|
+
//Apply effects
|
|
159
|
+
if (stroke.type === "eraser") {
|
|
160
|
+
Painter.ctx.globalAlpha = 1;
|
|
161
|
+
Painter.ctx.fillStyle = "black";
|
|
162
|
+
Painter.ctx.fill();
|
|
163
|
+
} else {
|
|
164
|
+
Painter.ctx.fillStyle = "transparent";
|
|
165
|
+
Painter.ctx.globalAlpha = Math.random();
|
|
166
|
+
Painter.ctx.globalCompositeOperation =
|
|
167
|
+
PaintScene.gco[Math.floor(Math.random() * PaintScene.gco.length)];
|
|
168
|
+
}
|
|
169
|
+
// Draw lines to each point
|
|
170
|
+
for (let i = 1; i < pts.length; i++) {
|
|
171
|
+
Painter.ctx.lineTo(pts[i].x, pts[i].y);
|
|
172
|
+
}
|
|
173
|
+
Painter.ctx.stroke();
|
|
174
|
+
// Restore the context
|
|
175
|
+
Painter.ctx.restore();
|
|
176
|
+
Painter.ctx.globalAlpha = 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Draw a preview line from start point to current mouse position
|
|
181
|
+
*/
|
|
182
|
+
drawLinePreview(start, end) {
|
|
183
|
+
Painter.ctx.save();
|
|
184
|
+
|
|
185
|
+
// Use a different style for the preview
|
|
186
|
+
Painter.ctx.lineWidth = 2;
|
|
187
|
+
Painter.ctx.strokeStyle = "#FFFFFF"; // Lighter color for preview
|
|
188
|
+
Painter.ctx.setLineDash([5, 3]); // Dashed line for preview
|
|
189
|
+
|
|
190
|
+
Painter.ctx.beginPath();
|
|
191
|
+
Painter.ctx.moveTo(start.x, start.y);
|
|
192
|
+
Painter.ctx.lineTo(end.x, end.y);
|
|
193
|
+
Painter.ctx.stroke();
|
|
194
|
+
|
|
195
|
+
// Draw small circles at the start and end points
|
|
196
|
+
Painter.ctx.fillStyle = "#fff";
|
|
197
|
+
Painter.ctx.beginPath();
|
|
198
|
+
Painter.ctx.arc(start.x, start.y, 4, 0, Math.PI * 2);
|
|
199
|
+
Painter.ctx.fill();
|
|
200
|
+
|
|
201
|
+
Painter.ctx.beginPath();
|
|
202
|
+
Painter.ctx.arc(end.x, end.y, 4, 0, Math.PI * 2);
|
|
203
|
+
Painter.ctx.fill();
|
|
204
|
+
|
|
205
|
+
Painter.ctx.restore();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The scene can handle pointer events directly.
|
|
210
|
+
* We'll do "line" logic vs "pencil"/"eraser" logic.
|
|
211
|
+
*/
|
|
212
|
+
pointerDown(x, y) {
|
|
213
|
+
this.activeStroke = null;
|
|
214
|
+
this.game.uiScene.visible = false;
|
|
215
|
+
this.game.uiScene.active = false;
|
|
216
|
+
// If user has chosen "line":
|
|
217
|
+
if (this.currentTool === "line") {
|
|
218
|
+
if (this.lineStart == null) {
|
|
219
|
+
// first click: store start
|
|
220
|
+
this.lineStart = { x, y };
|
|
221
|
+
this.currentMousePos = { x, y }; // Initialize current mouse position
|
|
222
|
+
} else {
|
|
223
|
+
// second click: finalize line stroke
|
|
224
|
+
const stroke = {
|
|
225
|
+
type: "line",
|
|
226
|
+
lineWidth: 4,
|
|
227
|
+
color: Painter.colors.randomColorHSL(),
|
|
228
|
+
compositeOp: "luminosity",
|
|
229
|
+
points: [
|
|
230
|
+
{ x: this.lineStart.x, y: this.lineStart.y },
|
|
231
|
+
{ x, y },
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
this.strokes.push(stroke);
|
|
235
|
+
this.lineStart = null; // Reset line start for next line
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// If "pencil" or "eraser", start a new active stroke
|
|
241
|
+
const erasing = this.currentTool === "eraser";
|
|
242
|
+
this.activeStroke = {
|
|
243
|
+
type: erasing ? "eraser" : "pencil",
|
|
244
|
+
lineWidth: erasing ? 50 : 8, // eraser is wider
|
|
245
|
+
color: erasing ? "#000" : Painter.colors.randomColorHSL(), // color doesn't matter for eraser, but we'll use #000
|
|
246
|
+
compositeOp: erasing ? "destination-out" : "source-over",
|
|
247
|
+
points: [{ x, y }],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
pointerMove(x, y) {
|
|
252
|
+
// Always update current mouse position for preview
|
|
253
|
+
this.currentMousePos = { x, y };
|
|
254
|
+
|
|
255
|
+
// If we're currently drawing a stroke (pencil/eraser),
|
|
256
|
+
// add the new point.
|
|
257
|
+
if (this.activeStroke) {
|
|
258
|
+
this.activeStroke.points.push({ x, y });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
pointerUp(x, y) {
|
|
263
|
+
this.game.uiScene.visible = true;
|
|
264
|
+
this.game.uiScene.active = true;
|
|
265
|
+
// If we have an active stroke, finalize it
|
|
266
|
+
if (this.activeStroke) {
|
|
267
|
+
// Make sure it has at least 2 points
|
|
268
|
+
if (this.activeStroke.points.length < 2) {
|
|
269
|
+
// Add the current point if needed
|
|
270
|
+
this.activeStroke.points.push({ x, y });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Make a fresh copy to avoid reference issues
|
|
274
|
+
const finalStroke = {
|
|
275
|
+
type: this.activeStroke.type,
|
|
276
|
+
lineWidth: this.activeStroke.lineWidth,
|
|
277
|
+
color: this.activeStroke.color,
|
|
278
|
+
compositeOp: this.activeStroke.compositeOp,
|
|
279
|
+
points: this.activeStroke.points.map((p) => ({ ...p })), // Deep copy points
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
this.strokes.push(finalStroke);
|
|
283
|
+
this.activeStroke = null;
|
|
284
|
+
}
|
|
285
|
+
this.enforceStrokeLimit();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
enforceStrokeLimit() {
|
|
289
|
+
// First check total point count
|
|
290
|
+
this.totalPoints = this.strokes.reduce(
|
|
291
|
+
(sum, stroke) => sum + stroke.points.length,
|
|
292
|
+
0,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// If too many points, start removing oldest strokes
|
|
296
|
+
while (this.totalPoints > this.MAX_POINTS && this.strokes.length > 0) {
|
|
297
|
+
const removedStroke = this.strokes.shift(); // Remove oldest
|
|
298
|
+
this.totalPoints -= removedStroke.points.length;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Also enforce max stroke count
|
|
302
|
+
if (this.strokes.length > this.MAX_STROKES) {
|
|
303
|
+
// Get points in strokes to be removed
|
|
304
|
+
const pointsToRemove = this.strokes
|
|
305
|
+
.slice(0, this.REMOVE_BATCH)
|
|
306
|
+
.reduce((sum, stroke) => sum + stroke.points.length, 0);
|
|
307
|
+
|
|
308
|
+
// Remove the oldest REMOVE_BATCH strokes
|
|
309
|
+
this.strokes = this.strokes.slice(this.REMOVE_BATCH);
|
|
310
|
+
this.totalPoints -= pointsToRemove;
|
|
311
|
+
|
|
312
|
+
console.log(
|
|
313
|
+
`Removed ${this.REMOVE_BATCH} oldest strokes. ${this.strokes.length} strokes remaining.`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* A small UI scene that has 3 buttons in a horizontal layout:
|
|
321
|
+
* "Line", "Pencil", "Eraser."
|
|
322
|
+
* When clicked, they call paintScene.setTool(...).
|
|
323
|
+
*/
|
|
324
|
+
class UIScene extends Scene {
|
|
325
|
+
constructor(game, paintScene, options = {}) {
|
|
326
|
+
super(game, options);
|
|
327
|
+
this.debug = false;
|
|
328
|
+
this.debugColor = "yellow";
|
|
329
|
+
this.paintScene = paintScene;
|
|
330
|
+
this.layout = new HorizontalLayout(game, {
|
|
331
|
+
x: 0,
|
|
332
|
+
y: 0,
|
|
333
|
+
spacing: 8,
|
|
334
|
+
padding: 0,
|
|
335
|
+
});
|
|
336
|
+
this.toolPencil = this.layout.add(
|
|
337
|
+
new ToggleButton(game, {
|
|
338
|
+
text: "✏️Pencil",
|
|
339
|
+
width: 80,
|
|
340
|
+
height: 32,
|
|
341
|
+
colorHoverBg: "transparent",
|
|
342
|
+
colorDefaultBg: "transparent",
|
|
343
|
+
colorPressedBg: "transparent",
|
|
344
|
+
colorDefaultText: "white",
|
|
345
|
+
startToggled: true,
|
|
346
|
+
onToggle: (active) => {
|
|
347
|
+
if (currentTool) {
|
|
348
|
+
currentTool.toggle(false);
|
|
349
|
+
}
|
|
350
|
+
if (active) {
|
|
351
|
+
paintScene.setTool("pencil");
|
|
352
|
+
currentTool = this.toolPencil;
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
this.toolEraser = this.layout.add(
|
|
358
|
+
new ToggleButton(game, {
|
|
359
|
+
text: "🦯Eraser",
|
|
360
|
+
width: 80,
|
|
361
|
+
height: 32,
|
|
362
|
+
colorHoverBg: "transparent",
|
|
363
|
+
colorDefaultBg: "transparent",
|
|
364
|
+
colorPressedBg: "transparent",
|
|
365
|
+
colorDefaultText: "white",
|
|
366
|
+
onToggle: (active) => {
|
|
367
|
+
if (currentTool) {
|
|
368
|
+
currentTool.toggle(false);
|
|
369
|
+
}
|
|
370
|
+
if (active) {
|
|
371
|
+
paintScene.setTool("eraser");
|
|
372
|
+
currentTool = this.toolEraser;
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
this.toolLine = this.layout.add(
|
|
378
|
+
new ToggleButton(game, {
|
|
379
|
+
text: "✒️Line",
|
|
380
|
+
width: 80,
|
|
381
|
+
height: 32,
|
|
382
|
+
colorHoverBg: "transparent",
|
|
383
|
+
colorDefaultBg: "transparent",
|
|
384
|
+
colorPressedBg: "transparent",
|
|
385
|
+
colorDefaultText: "white",
|
|
386
|
+
onToggle: (active) => {
|
|
387
|
+
if (currentTool) {
|
|
388
|
+
currentTool.toggle(false);
|
|
389
|
+
}
|
|
390
|
+
if (active) {
|
|
391
|
+
currentTool = this.toolLine;
|
|
392
|
+
paintScene.setTool("line");
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
this.layout.add(
|
|
398
|
+
new Button(game, {
|
|
399
|
+
text: "🧼Clear",
|
|
400
|
+
height: 32,
|
|
401
|
+
width: 80,
|
|
402
|
+
colorHoverBg: "transparent",
|
|
403
|
+
colorDefaultBg: "transparent",
|
|
404
|
+
colorPressedBg: "transparent",
|
|
405
|
+
colorDefaultText: "white",
|
|
406
|
+
onClick: () => {
|
|
407
|
+
this.paintScene.strokes = [];
|
|
408
|
+
this.paintScene.activeStroke = null;
|
|
409
|
+
this.paintScene.lineStart = null;
|
|
410
|
+
},
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
413
|
+
let currentTool = this.toolPencil;
|
|
414
|
+
this.add(this.layout);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Our main Game class.
|
|
420
|
+
* We create two Scenes: one for painting, one for UI.
|
|
421
|
+
* We forward pointer events to the paint scene.
|
|
422
|
+
*/
|
|
423
|
+
class DemoGame extends Game {
|
|
424
|
+
constructor(canvas) {
|
|
425
|
+
super(canvas);
|
|
426
|
+
this.enableFluidSize();
|
|
427
|
+
this.backgroundColor = "black";
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
addFPSCounter() {
|
|
431
|
+
this.pipeline.add(
|
|
432
|
+
new FPSCounter(this, {
|
|
433
|
+
anchor: "bottom-right",
|
|
434
|
+
width: 20,
|
|
435
|
+
height: 20,
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
createUI() {
|
|
441
|
+
// Create the UI scene
|
|
442
|
+
this.uiScene = new UIScene(this, this.paintScene, {
|
|
443
|
+
debug: true,
|
|
444
|
+
debugColor: "yellow",
|
|
445
|
+
anchor: Position.BOTTOM_CENTER,
|
|
446
|
+
anchorRelative: this.paintScene,
|
|
447
|
+
width: 80 + 80 + 80 + 80 + 32,
|
|
448
|
+
height: 40,
|
|
449
|
+
});
|
|
450
|
+
this.pipeline.add(this.uiScene);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
init() {
|
|
454
|
+
super.init();
|
|
455
|
+
// Create the paint scene
|
|
456
|
+
this.paintScene = new PaintScene(this, { debug: true, anchor: "center" });
|
|
457
|
+
this.pipeline.add(this.paintScene);
|
|
458
|
+
// Add them to the pipeline
|
|
459
|
+
this.createUI();
|
|
460
|
+
this.addFPSCounter();
|
|
461
|
+
this.cursor = this.paintScene.cursor;
|
|
462
|
+
// Listen for pointer events global ly and hand them to paintScene
|
|
463
|
+
this.events.on("inputdown", (e) => {
|
|
464
|
+
// translate canvas point to scene point (center is 0,0)
|
|
465
|
+
const x = e.x - this.width / 2;
|
|
466
|
+
const y = e.y - this.height / 2;
|
|
467
|
+
this.paintScene.pointerDown(x, y);
|
|
468
|
+
});
|
|
469
|
+
this.events.on("inputmove", (e) => {
|
|
470
|
+
// translate canvas point to scene point (center is 0,0)
|
|
471
|
+
const x = e.x - this.width / 2;
|
|
472
|
+
const y = e.y - this.height / 2;
|
|
473
|
+
this.paintScene.pointerMove(x, y);
|
|
474
|
+
});
|
|
475
|
+
this.events.on("inputup", (e) => {
|
|
476
|
+
// translate canvas point to scene point (center is 0,0)
|
|
477
|
+
const x = e.x - this.width / 2;
|
|
478
|
+
const y = e.y - this.height / 2;
|
|
479
|
+
this.paintScene.pointerUp(x, y);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export { DemoGame };
|