@guinetik/gcanvas 1.0.5 → 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/aizawa.html +27 -0
- package/dist/clifford.html +25 -0
- package/dist/cmb.html +24 -0
- package/dist/dadras.html +26 -0
- package/dist/dejong.html +25 -0
- package/dist/gcanvas.es.js +5130 -372
- 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/halvorsen.html +27 -0
- package/dist/index.html +96 -48
- package/dist/js/aizawa.js +425 -0
- package/dist/js/bezier.js +5 -5
- package/dist/js/clifford.js +236 -0
- package/dist/js/cmb.js +594 -0
- package/dist/js/dadras.js +405 -0
- package/dist/js/dejong.js +257 -0
- package/dist/js/halvorsen.js +405 -0
- package/dist/js/isometric.js +34 -46
- package/dist/js/lorenz.js +425 -0
- package/dist/js/painter.js +8 -8
- package/dist/js/rossler.js +480 -0
- package/dist/js/schrodinger.js +314 -18
- package/dist/js/thomas.js +394 -0
- package/dist/lorenz.html +27 -0
- package/dist/rossler.html +27 -0
- package/dist/scene-interactivity-test.html +220 -0
- package/dist/thomas.html +27 -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,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lorenz Attractor 3D Visualization
|
|
3
|
+
*
|
|
4
|
+
* The classic "butterfly effect" attractor discovered by Edward Lorenz (1963)
|
|
5
|
+
* while studying atmospheric convection. Particles follow the chaotic
|
|
6
|
+
* trajectories colored by velocity (blue=slow, red=fast).
|
|
7
|
+
*
|
|
8
|
+
* Uses the Attractors module for pure math functions and WebGL for
|
|
9
|
+
* high-performance line rendering.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Game, Gesture, Screen, Attractors } from "/gcanvas.es.min.js";
|
|
13
|
+
import { Camera3D } from "/gcanvas.es.min.js";
|
|
14
|
+
import { WebGLLineRenderer } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// CONFIGURATION
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const CONFIG = {
|
|
21
|
+
// Attractor settings (uses Attractors.lorenz for equations)
|
|
22
|
+
attractor: {
|
|
23
|
+
dt: 0.005, // Integration time step
|
|
24
|
+
scale: 12, // Scale factor for display (Lorenz is larger)
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Particle settings
|
|
28
|
+
particles: {
|
|
29
|
+
count: 400,
|
|
30
|
+
trailLength: 250,
|
|
31
|
+
spawnRange: 2, // Initial position range around origin
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Center offset - Lorenz attractor orbits around z≈27 (ρ-1)
|
|
35
|
+
center: {
|
|
36
|
+
x: 5,
|
|
37
|
+
y: 0,
|
|
38
|
+
z: 27,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Camera settings - angled to show butterfly shape
|
|
42
|
+
camera: {
|
|
43
|
+
perspective: 800,
|
|
44
|
+
rotationX: -2, // Tilt to see butterfly spread
|
|
45
|
+
rotationY: -3, // Rotated to face the wings
|
|
46
|
+
inertia: true,
|
|
47
|
+
friction: 0.95,
|
|
48
|
+
clampX: false,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Visual settings
|
|
52
|
+
visual: {
|
|
53
|
+
minHue: 30, // Orange-red (fast)
|
|
54
|
+
maxHue: 200, // Cyan-blue (slow)
|
|
55
|
+
maxSpeed: 50, // Speed normalization threshold
|
|
56
|
+
saturation: 85,
|
|
57
|
+
lightness: 55,
|
|
58
|
+
maxAlpha: 0.85,
|
|
59
|
+
hueShiftSpeed: 15, // Degrees per second (0 to disable)
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Glitch/blink effect
|
|
63
|
+
blink: {
|
|
64
|
+
chance: 0.015,
|
|
65
|
+
minDuration: 0.05,
|
|
66
|
+
maxDuration: 0.25,
|
|
67
|
+
intensityBoost: 1.4,
|
|
68
|
+
saturationBoost: 1.15,
|
|
69
|
+
alphaBoost: 1.25,
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Zoom settings
|
|
73
|
+
zoom: {
|
|
74
|
+
min: 0.2,
|
|
75
|
+
max: 2.5,
|
|
76
|
+
speed: 0.5,
|
|
77
|
+
easing: 0.12,
|
|
78
|
+
baseScreenSize: 600,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// HELPER FUNCTIONS
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert HSL to RGB
|
|
88
|
+
*/
|
|
89
|
+
function hslToRgb(h, s, l) {
|
|
90
|
+
s /= 100;
|
|
91
|
+
l /= 100;
|
|
92
|
+
const k = (n) => (n + h / 30) % 12;
|
|
93
|
+
const a = s * Math.min(l, 1 - l);
|
|
94
|
+
const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
95
|
+
return {
|
|
96
|
+
r: Math.round(255 * f(0)),
|
|
97
|
+
g: Math.round(255 * f(8)),
|
|
98
|
+
b: Math.round(255 * f(4)),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
// ATTRACTOR PARTICLE
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A particle following attractor dynamics
|
|
108
|
+
*/
|
|
109
|
+
class AttractorParticle {
|
|
110
|
+
/**
|
|
111
|
+
* @param {Function} stepFn - Attractor step function
|
|
112
|
+
* @param {number} spawnRange - Initial position range
|
|
113
|
+
*/
|
|
114
|
+
constructor(stepFn, spawnRange) {
|
|
115
|
+
this.stepFn = stepFn;
|
|
116
|
+
this.position = {
|
|
117
|
+
x: (Math.random() - 0.5) * spawnRange,
|
|
118
|
+
y: (Math.random() - 0.5) * spawnRange,
|
|
119
|
+
z: (Math.random() - 0.5) * spawnRange + CONFIG.center.z, // Start near attractor
|
|
120
|
+
};
|
|
121
|
+
this.trail = [];
|
|
122
|
+
this.speed = 0;
|
|
123
|
+
|
|
124
|
+
// Blink/glitch state
|
|
125
|
+
this.blinkTime = 0;
|
|
126
|
+
this.blinkIntensity = 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update blink state
|
|
131
|
+
*/
|
|
132
|
+
updateBlink(dt) {
|
|
133
|
+
const { chance, minDuration, maxDuration } = CONFIG.blink;
|
|
134
|
+
|
|
135
|
+
if (this.blinkTime > 0) {
|
|
136
|
+
this.blinkTime -= dt;
|
|
137
|
+
this.blinkIntensity = Math.max(
|
|
138
|
+
0,
|
|
139
|
+
this.blinkTime > 0
|
|
140
|
+
? Math.sin((this.blinkTime / ((minDuration + maxDuration) * 0.5)) * Math.PI)
|
|
141
|
+
: 0
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
if (Math.random() < chance) {
|
|
145
|
+
this.blinkTime = minDuration + Math.random() * (maxDuration - minDuration);
|
|
146
|
+
this.blinkIntensity = 1;
|
|
147
|
+
} else {
|
|
148
|
+
this.blinkIntensity = 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Update particle position using attractor
|
|
155
|
+
*/
|
|
156
|
+
update(dt, scale) {
|
|
157
|
+
// Use the attractor step function
|
|
158
|
+
const result = this.stepFn(this.position, dt);
|
|
159
|
+
|
|
160
|
+
// Update position
|
|
161
|
+
this.position = result.position;
|
|
162
|
+
this.speed = result.speed;
|
|
163
|
+
|
|
164
|
+
// Add to trail (scaled and centered for display)
|
|
165
|
+
// Subtract center offset so attractor rotates around its center
|
|
166
|
+
this.trail.unshift({
|
|
167
|
+
x: (this.position.x - CONFIG.center.x) * scale,
|
|
168
|
+
y: (this.position.y - CONFIG.center.y) * scale,
|
|
169
|
+
z: (this.position.z - CONFIG.center.z) * scale,
|
|
170
|
+
speed: this.speed,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Trim trail
|
|
174
|
+
if (this.trail.length > CONFIG.particles.trailLength) {
|
|
175
|
+
this.trail.pop();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
// DEMO CLASS
|
|
182
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Lorenz Attractor Demo
|
|
186
|
+
*/
|
|
187
|
+
class LorenzDemo extends Game {
|
|
188
|
+
constructor(canvas) {
|
|
189
|
+
super(canvas);
|
|
190
|
+
this.backgroundColor = "#000";
|
|
191
|
+
this.enableFluidSize();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
init() {
|
|
195
|
+
super.init();
|
|
196
|
+
|
|
197
|
+
// Get attractor info for display
|
|
198
|
+
this.attractor = Attractors.lorenz;
|
|
199
|
+
console.log(`Attractor: ${this.attractor.name}`);
|
|
200
|
+
console.log(`Equations:`, this.attractor.equations);
|
|
201
|
+
|
|
202
|
+
// Create stepper function with default params
|
|
203
|
+
this.stepFn = this.attractor.createStepper();
|
|
204
|
+
|
|
205
|
+
// Calculate initial zoom
|
|
206
|
+
const { min, max, baseScreenSize } = CONFIG.zoom;
|
|
207
|
+
const initialZoom = Math.min(max, Math.max(min, Screen.minDimension() / baseScreenSize));
|
|
208
|
+
this.zoom = initialZoom;
|
|
209
|
+
this.targetZoom = initialZoom;
|
|
210
|
+
this.defaultZoom = initialZoom;
|
|
211
|
+
|
|
212
|
+
// Camera with mouse control
|
|
213
|
+
this.camera = new Camera3D({
|
|
214
|
+
perspective: CONFIG.camera.perspective,
|
|
215
|
+
rotationX: CONFIG.camera.rotationX,
|
|
216
|
+
rotationY: CONFIG.camera.rotationY,
|
|
217
|
+
inertia: CONFIG.camera.inertia,
|
|
218
|
+
friction: CONFIG.camera.friction,
|
|
219
|
+
clampX: CONFIG.camera.clampX,
|
|
220
|
+
});
|
|
221
|
+
this.camera.enableMouseControl(this.canvas);
|
|
222
|
+
|
|
223
|
+
// Gesture handler for zoom
|
|
224
|
+
this.gesture = new Gesture(this.canvas, {
|
|
225
|
+
onZoom: (delta) => {
|
|
226
|
+
this.targetZoom *= 1 + delta * CONFIG.zoom.speed;
|
|
227
|
+
},
|
|
228
|
+
onPan: null,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Double-click to reset
|
|
232
|
+
this.canvas.addEventListener("dblclick", () => {
|
|
233
|
+
this.targetZoom = this.defaultZoom;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Log camera params on mouse release (for finding good starting angle)
|
|
237
|
+
this.canvas.addEventListener("mouseup", () => {
|
|
238
|
+
console.log(`Camera: rotationX: ${this.camera.rotationX.toFixed(3)}, rotationY: ${this.camera.rotationY.toFixed(3)}`);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Initialize particles using the attractor step function
|
|
242
|
+
this.particles = [];
|
|
243
|
+
for (let i = 0; i < CONFIG.particles.count; i++) {
|
|
244
|
+
this.particles.push(
|
|
245
|
+
new AttractorParticle(this.stepFn, CONFIG.particles.spawnRange)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// WebGL line renderer
|
|
250
|
+
const maxSegments = CONFIG.particles.count * CONFIG.particles.trailLength;
|
|
251
|
+
this.lineRenderer = new WebGLLineRenderer(maxSegments, {
|
|
252
|
+
width: this.width,
|
|
253
|
+
height: this.height,
|
|
254
|
+
blendMode: "additive",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.segments = [];
|
|
258
|
+
|
|
259
|
+
if (!this.lineRenderer.isAvailable()) {
|
|
260
|
+
console.warn("WebGL not available, falling back to Canvas 2D");
|
|
261
|
+
this.useWebGL = false;
|
|
262
|
+
} else {
|
|
263
|
+
this.useWebGL = true;
|
|
264
|
+
console.log(`WebGL enabled, ${maxSegments} max segments`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.time = 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
onResize() {
|
|
271
|
+
if (this.lineRenderer?.isAvailable()) {
|
|
272
|
+
this.lineRenderer.resize(this.width, this.height);
|
|
273
|
+
}
|
|
274
|
+
const { min, max, baseScreenSize } = CONFIG.zoom;
|
|
275
|
+
this.defaultZoom = Math.min(max, Math.max(min, Screen.minDimension() / baseScreenSize));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
update(dt) {
|
|
279
|
+
super.update(dt);
|
|
280
|
+
this.camera.update(dt);
|
|
281
|
+
|
|
282
|
+
// Normalize rotation to prevent unbounded values
|
|
283
|
+
const TAU = Math.PI * 2;
|
|
284
|
+
this.camera.rotationY = ((this.camera.rotationY % TAU) + TAU) % TAU;
|
|
285
|
+
|
|
286
|
+
this.zoom += (this.targetZoom - this.zoom) * CONFIG.zoom.easing;
|
|
287
|
+
this.time += dt;
|
|
288
|
+
|
|
289
|
+
for (const particle of this.particles) {
|
|
290
|
+
particle.update(CONFIG.attractor.dt, CONFIG.attractor.scale);
|
|
291
|
+
particle.updateBlink(dt);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
collectSegments(cx, cy) {
|
|
296
|
+
const { minHue, maxHue, maxSpeed, saturation, lightness, maxAlpha, hueShiftSpeed } =
|
|
297
|
+
CONFIG.visual;
|
|
298
|
+
const { intensityBoost, saturationBoost, alphaBoost } = CONFIG.blink;
|
|
299
|
+
const hueOffset = (this.time * hueShiftSpeed) % 360;
|
|
300
|
+
|
|
301
|
+
this.segments.length = 0;
|
|
302
|
+
|
|
303
|
+
for (const particle of this.particles) {
|
|
304
|
+
if (particle.trail.length < 2) continue;
|
|
305
|
+
|
|
306
|
+
const blink = particle.blinkIntensity;
|
|
307
|
+
|
|
308
|
+
for (let i = 1; i < particle.trail.length; i++) {
|
|
309
|
+
const curr = particle.trail[i];
|
|
310
|
+
const prev = particle.trail[i - 1];
|
|
311
|
+
|
|
312
|
+
const p1 = this.camera.project(prev.x, prev.y, prev.z);
|
|
313
|
+
const p2 = this.camera.project(curr.x, curr.y, curr.z);
|
|
314
|
+
|
|
315
|
+
if (p1.scale <= 0 || p2.scale <= 0) continue;
|
|
316
|
+
|
|
317
|
+
const age = i / particle.trail.length;
|
|
318
|
+
const speedNorm = Math.min(curr.speed / maxSpeed, 1);
|
|
319
|
+
const baseHue = maxHue - speedNorm * (maxHue - minHue);
|
|
320
|
+
const hue = (baseHue + hueOffset) % 360;
|
|
321
|
+
|
|
322
|
+
const sat = Math.min(100, saturation * (1 + blink * (saturationBoost - 1)));
|
|
323
|
+
const lit = Math.min(100, lightness * (1 + blink * (intensityBoost - 1)));
|
|
324
|
+
const rgb = hslToRgb(hue, sat, lit);
|
|
325
|
+
const alpha = Math.min(1, (1 - age) * maxAlpha * (1 + blink * (alphaBoost - 1)));
|
|
326
|
+
|
|
327
|
+
this.segments.push({
|
|
328
|
+
x1: cx + p1.x * this.zoom,
|
|
329
|
+
y1: cy + p1.y * this.zoom,
|
|
330
|
+
x2: cx + p2.x * this.zoom,
|
|
331
|
+
y2: cy + p2.y * this.zoom,
|
|
332
|
+
r: rgb.r,
|
|
333
|
+
g: rgb.g,
|
|
334
|
+
b: rgb.b,
|
|
335
|
+
a: alpha,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return this.segments.length;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
renderCanvas2D(cx, cy) {
|
|
344
|
+
const { minHue, maxHue, maxSpeed, saturation, lightness, maxAlpha, hueShiftSpeed } =
|
|
345
|
+
CONFIG.visual;
|
|
346
|
+
const { intensityBoost, saturationBoost, alphaBoost } = CONFIG.blink;
|
|
347
|
+
const hueOffset = (this.time * hueShiftSpeed) % 360;
|
|
348
|
+
|
|
349
|
+
const ctx = this.ctx;
|
|
350
|
+
ctx.save();
|
|
351
|
+
ctx.globalCompositeOperation = "lighter";
|
|
352
|
+
ctx.lineCap = "round";
|
|
353
|
+
|
|
354
|
+
for (const particle of this.particles) {
|
|
355
|
+
if (particle.trail.length < 2) continue;
|
|
356
|
+
|
|
357
|
+
const blink = particle.blinkIntensity;
|
|
358
|
+
|
|
359
|
+
for (let i = 1; i < particle.trail.length; i++) {
|
|
360
|
+
const curr = particle.trail[i];
|
|
361
|
+
const prev = particle.trail[i - 1];
|
|
362
|
+
|
|
363
|
+
const p1 = this.camera.project(prev.x, prev.y, prev.z);
|
|
364
|
+
const p2 = this.camera.project(curr.x, curr.y, curr.z);
|
|
365
|
+
|
|
366
|
+
if (p1.scale <= 0 || p2.scale <= 0) continue;
|
|
367
|
+
|
|
368
|
+
const age = i / particle.trail.length;
|
|
369
|
+
const speedNorm = Math.min(curr.speed / maxSpeed, 1);
|
|
370
|
+
const baseHue = maxHue - speedNorm * (maxHue - minHue);
|
|
371
|
+
const hue = (baseHue + hueOffset) % 360;
|
|
372
|
+
|
|
373
|
+
const sat = Math.min(100, saturation * (1 + blink * (saturationBoost - 1)));
|
|
374
|
+
const lit = Math.min(100, lightness * (1 + blink * (intensityBoost - 1)));
|
|
375
|
+
const alpha = Math.min(1, (1 - age) * maxAlpha * (1 + blink * (alphaBoost - 1)));
|
|
376
|
+
|
|
377
|
+
ctx.strokeStyle = `hsla(${hue}, ${sat}%, ${lit}%, ${alpha})`;
|
|
378
|
+
ctx.lineWidth = 1;
|
|
379
|
+
|
|
380
|
+
ctx.beginPath();
|
|
381
|
+
ctx.moveTo(cx + p1.x * this.zoom, cy + p1.y * this.zoom);
|
|
382
|
+
ctx.lineTo(cx + p2.x * this.zoom, cy + p2.y * this.zoom);
|
|
383
|
+
ctx.stroke();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
ctx.restore();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
render() {
|
|
391
|
+
super.render();
|
|
392
|
+
if (!this.particles) return;
|
|
393
|
+
|
|
394
|
+
const cx = this.width / 2;
|
|
395
|
+
const cy = this.height / 2;
|
|
396
|
+
|
|
397
|
+
if (this.useWebGL && this.lineRenderer.isAvailable()) {
|
|
398
|
+
const segmentCount = this.collectSegments(cx, cy);
|
|
399
|
+
if (segmentCount > 0) {
|
|
400
|
+
this.lineRenderer.clear();
|
|
401
|
+
this.lineRenderer.updateLines(this.segments);
|
|
402
|
+
this.lineRenderer.render(segmentCount);
|
|
403
|
+
this.lineRenderer.compositeOnto(this.ctx, 0, 0);
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
this.renderCanvas2D(cx, cy);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
destroy() {
|
|
411
|
+
this.gesture?.destroy();
|
|
412
|
+
this.lineRenderer?.destroy();
|
|
413
|
+
super.destroy?.();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
418
|
+
// INITIALIZATION
|
|
419
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
window.addEventListener("load", () => {
|
|
422
|
+
const canvas = document.getElementById("game");
|
|
423
|
+
const demo = new LorenzDemo(canvas);
|
|
424
|
+
demo.start();
|
|
425
|
+
});
|
package/dist/js/painter.js
CHANGED
|
@@ -289,7 +289,7 @@ class PaintScene extends GameObject {
|
|
|
289
289
|
// First check total point count
|
|
290
290
|
this.totalPoints = this.strokes.reduce(
|
|
291
291
|
(sum, stroke) => sum + stroke.points.length,
|
|
292
|
-
0
|
|
292
|
+
0,
|
|
293
293
|
);
|
|
294
294
|
|
|
295
295
|
// If too many points, start removing oldest strokes
|
|
@@ -310,7 +310,7 @@ class PaintScene extends GameObject {
|
|
|
310
310
|
this.totalPoints -= pointsToRemove;
|
|
311
311
|
|
|
312
312
|
console.log(
|
|
313
|
-
`Removed ${this.REMOVE_BATCH} oldest strokes. ${this.strokes.length} strokes remaining
|
|
313
|
+
`Removed ${this.REMOVE_BATCH} oldest strokes. ${this.strokes.length} strokes remaining.`,
|
|
314
314
|
);
|
|
315
315
|
}
|
|
316
316
|
}
|
|
@@ -324,7 +324,7 @@ class PaintScene extends GameObject {
|
|
|
324
324
|
class UIScene extends Scene {
|
|
325
325
|
constructor(game, paintScene, options = {}) {
|
|
326
326
|
super(game, options);
|
|
327
|
-
this.debug =
|
|
327
|
+
this.debug = false;
|
|
328
328
|
this.debugColor = "yellow";
|
|
329
329
|
this.paintScene = paintScene;
|
|
330
330
|
this.layout = new HorizontalLayout(game, {
|
|
@@ -352,7 +352,7 @@ class UIScene extends Scene {
|
|
|
352
352
|
currentTool = this.toolPencil;
|
|
353
353
|
}
|
|
354
354
|
},
|
|
355
|
-
})
|
|
355
|
+
}),
|
|
356
356
|
);
|
|
357
357
|
this.toolEraser = this.layout.add(
|
|
358
358
|
new ToggleButton(game, {
|
|
@@ -372,7 +372,7 @@ class UIScene extends Scene {
|
|
|
372
372
|
currentTool = this.toolEraser;
|
|
373
373
|
}
|
|
374
374
|
},
|
|
375
|
-
})
|
|
375
|
+
}),
|
|
376
376
|
);
|
|
377
377
|
this.toolLine = this.layout.add(
|
|
378
378
|
new ToggleButton(game, {
|
|
@@ -392,7 +392,7 @@ class UIScene extends Scene {
|
|
|
392
392
|
paintScene.setTool("line");
|
|
393
393
|
}
|
|
394
394
|
},
|
|
395
|
-
})
|
|
395
|
+
}),
|
|
396
396
|
);
|
|
397
397
|
this.layout.add(
|
|
398
398
|
new Button(game, {
|
|
@@ -408,7 +408,7 @@ class UIScene extends Scene {
|
|
|
408
408
|
this.paintScene.activeStroke = null;
|
|
409
409
|
this.paintScene.lineStart = null;
|
|
410
410
|
},
|
|
411
|
-
})
|
|
411
|
+
}),
|
|
412
412
|
);
|
|
413
413
|
let currentTool = this.toolPencil;
|
|
414
414
|
this.add(this.layout);
|
|
@@ -433,7 +433,7 @@ class DemoGame extends Game {
|
|
|
433
433
|
anchor: "bottom-right",
|
|
434
434
|
width: 20,
|
|
435
435
|
height: 20,
|
|
436
|
-
})
|
|
436
|
+
}),
|
|
437
437
|
);
|
|
438
438
|
}
|
|
439
439
|
|