@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
package/dist/js/schrodinger.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Ψ(x,t) = A * e^(-(x-vt)²/4a²) * e^(i(kx-ωt))
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Game, Painter, Camera3D, Text, applyAnchor, Position, Scene, verticalLayout, applyLayout } from "/gcanvas.es.min.js";
|
|
10
|
+
import { Game, Painter, Camera3D, Text, applyAnchor, Position, Scene, verticalLayout, applyLayout, Gesture, Screen } from "/gcanvas.es.min.js";
|
|
11
11
|
import { gaussianWavePacket } from "/gcanvas.es.min.js";
|
|
12
12
|
|
|
13
13
|
// Configuration
|
|
@@ -16,8 +16,8 @@ const CONFIG = {
|
|
|
16
16
|
amplitude: 1.0,
|
|
17
17
|
sigma: 0.8, // Width of Gaussian envelope
|
|
18
18
|
k: 8.0, // Wave number (controls oscillation frequency)
|
|
19
|
-
omega:
|
|
20
|
-
velocity: 0.5, // Group velocity of packet
|
|
19
|
+
omega: 8.0, // Angular frequency (phase velocity = ω/k = 1.0)
|
|
20
|
+
velocity: 0.5, // Group velocity of packet (envelope moves slower than phase)
|
|
21
21
|
|
|
22
22
|
// Visualization
|
|
23
23
|
numPoints: 300, // Points along the wave
|
|
@@ -38,6 +38,17 @@ const CONFIG = {
|
|
|
38
38
|
// Animation
|
|
39
39
|
timeScale: 1.0,
|
|
40
40
|
|
|
41
|
+
// Zoom
|
|
42
|
+
minZoom: 0.3,
|
|
43
|
+
maxZoom: 3.0,
|
|
44
|
+
zoomSpeed: 0.5, // Zoom sensitivity (increased)
|
|
45
|
+
zoomEasing: 0.12, // Easing interpolation speed (0-1)
|
|
46
|
+
baseScreenSize: 600, // Reference screen size for zoom calculation
|
|
47
|
+
|
|
48
|
+
// Collapse interaction
|
|
49
|
+
collapseHoldTime: 200, // ms to hold before collapse triggers
|
|
50
|
+
collapseDragThreshold: 8, // px movement to cancel collapse (it's a drag)
|
|
51
|
+
|
|
41
52
|
// Colors
|
|
42
53
|
waveColor: [80, 160, 255], // Cyan-blue for the helix
|
|
43
54
|
waveGlow: [150, 200, 255], // Brighter for the center
|
|
@@ -57,23 +68,59 @@ class SchrodingerDemo extends Game {
|
|
|
57
68
|
super.init();
|
|
58
69
|
this.time = 0;
|
|
59
70
|
|
|
60
|
-
//
|
|
71
|
+
// Calculate initial zoom based on screen size to fill canvas better
|
|
72
|
+
const initialZoom = Math.min(
|
|
73
|
+
CONFIG.maxZoom,
|
|
74
|
+
Math.max(CONFIG.minZoom, Screen.minDimension() / CONFIG.baseScreenSize)
|
|
75
|
+
);
|
|
76
|
+
this.zoom = initialZoom;
|
|
77
|
+
this.targetZoom = initialZoom;
|
|
78
|
+
this.defaultZoom = initialZoom;
|
|
79
|
+
|
|
80
|
+
// Create 3D camera with mouse controls and inertia
|
|
61
81
|
this.camera = new Camera3D({
|
|
62
82
|
rotationX: CONFIG.rotationX,
|
|
63
83
|
rotationY: CONFIG.rotationY,
|
|
64
84
|
perspective: CONFIG.perspective,
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
clampX: false, // Allow free rotation without limits
|
|
86
|
+
inertia: true, // Enable momentum after drag release
|
|
87
|
+
friction: 0.94, // Velocity decay (higher = longer drift)
|
|
88
|
+
velocityScale: 1.2, // Multiplier for throw velocity
|
|
67
89
|
});
|
|
68
90
|
|
|
69
91
|
// Enable mouse/touch rotation
|
|
70
92
|
this.camera.enableMouseControl(this.canvas);
|
|
71
93
|
|
|
72
|
-
//
|
|
94
|
+
// Enable zoom via mouse wheel and pinch gesture
|
|
95
|
+
this.gesture = new Gesture(this.canvas, {
|
|
96
|
+
onZoom: (delta) => {
|
|
97
|
+
// Update target zoom (eases smoothly in update loop)
|
|
98
|
+
this.targetZoom *= 1 + delta * CONFIG.zoomSpeed;
|
|
99
|
+
this.targetZoom = Math.max(CONFIG.minZoom, Math.min(CONFIG.maxZoom, this.targetZoom));
|
|
100
|
+
},
|
|
101
|
+
// Don't handle pan - Camera3D handles rotation via drag
|
|
102
|
+
onPan: null,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Override double-click to also reset time and zoom
|
|
73
106
|
this.canvas.addEventListener("dblclick", () => {
|
|
74
107
|
this.time = 0;
|
|
108
|
+
this.targetZoom = this.defaultZoom;
|
|
75
109
|
});
|
|
76
110
|
|
|
111
|
+
// Collapse interaction state
|
|
112
|
+
this.isCollapsed = false;
|
|
113
|
+
this.collapseAmount = 0; // 0 = normal, 1 = fully collapsed (animated)
|
|
114
|
+
this.collapseOffset = 0; // Current offset (tweening)
|
|
115
|
+
this.collapseTargetOffset = 0; // Target offset to tween toward
|
|
116
|
+
this.collapseTimer = null;
|
|
117
|
+
this.collapseSampleTimer = null; // Timer for periodic resampling
|
|
118
|
+
this.pointerStartX = 0;
|
|
119
|
+
this.pointerStartY = 0;
|
|
120
|
+
|
|
121
|
+
// Setup collapse detection (hold without dragging = measure/collapse)
|
|
122
|
+
this.setupCollapseInteraction();
|
|
123
|
+
|
|
77
124
|
// Create info panel container anchored to top center
|
|
78
125
|
this.infoPanel = new Scene(this, { x: 0, y: 0 });
|
|
79
126
|
applyAnchor(this.infoPanel, {
|
|
@@ -137,16 +184,157 @@ class SchrodingerDemo extends Game {
|
|
|
137
184
|
}
|
|
138
185
|
|
|
139
186
|
/**
|
|
140
|
-
*
|
|
187
|
+
* Setup collapse interaction - hold without moving triggers wave function collapse
|
|
188
|
+
*/
|
|
189
|
+
setupCollapseInteraction() {
|
|
190
|
+
const startCollapse = (x, y) => {
|
|
191
|
+
this.pointerStartX = x;
|
|
192
|
+
this.pointerStartY = y;
|
|
193
|
+
|
|
194
|
+
// Start timer - if we hold long enough without moving, collapse
|
|
195
|
+
this.collapseTimer = setTimeout(() => {
|
|
196
|
+
if (!this.isCollapsed) {
|
|
197
|
+
this.collapse();
|
|
198
|
+
}
|
|
199
|
+
}, CONFIG.collapseHoldTime);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const checkDrag = (x, y) => {
|
|
203
|
+
// If moved beyond threshold, cancel collapse timer (it's a drag)
|
|
204
|
+
const dx = Math.abs(x - this.pointerStartX);
|
|
205
|
+
const dy = Math.abs(y - this.pointerStartY);
|
|
206
|
+
if (dx > CONFIG.collapseDragThreshold || dy > CONFIG.collapseDragThreshold) {
|
|
207
|
+
this.cancelCollapseTimer();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const endCollapse = () => {
|
|
212
|
+
this.cancelCollapseTimer();
|
|
213
|
+
this.stopCollapseSampling();
|
|
214
|
+
this.isCollapsed = false;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Mouse events
|
|
218
|
+
this.canvas.addEventListener("mousedown", (e) => {
|
|
219
|
+
startCollapse(e.clientX, e.clientY);
|
|
220
|
+
});
|
|
221
|
+
this.canvas.addEventListener("mousemove", (e) => {
|
|
222
|
+
checkDrag(e.clientX, e.clientY);
|
|
223
|
+
});
|
|
224
|
+
this.canvas.addEventListener("mouseup", endCollapse);
|
|
225
|
+
this.canvas.addEventListener("mouseleave", endCollapse);
|
|
226
|
+
|
|
227
|
+
// Touch events
|
|
228
|
+
this.canvas.addEventListener("touchstart", (e) => {
|
|
229
|
+
if (e.touches.length === 1) {
|
|
230
|
+
startCollapse(e.touches[0].clientX, e.touches[0].clientY);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
this.canvas.addEventListener("touchmove", (e) => {
|
|
234
|
+
if (e.touches.length === 1) {
|
|
235
|
+
checkDrag(e.touches[0].clientX, e.touches[0].clientY);
|
|
236
|
+
} else {
|
|
237
|
+
// Multi-touch (pinch) cancels collapse
|
|
238
|
+
this.cancelCollapseTimer();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
this.canvas.addEventListener("touchend", endCollapse);
|
|
242
|
+
this.canvas.addEventListener("touchcancel", endCollapse);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Cancel any pending collapse timer
|
|
247
|
+
*/
|
|
248
|
+
cancelCollapseTimer() {
|
|
249
|
+
if (this.collapseTimer) {
|
|
250
|
+
clearTimeout(this.collapseTimer);
|
|
251
|
+
this.collapseTimer = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Sample a new random offset from Gaussian distribution
|
|
257
|
+
*/
|
|
258
|
+
sampleCollapseOffset() {
|
|
259
|
+
const u1 = Math.random();
|
|
260
|
+
const u2 = Math.random();
|
|
261
|
+
const gaussian = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
262
|
+
return gaussian * CONFIG.sigma;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Collapse the wave function - start sampling positions periodically
|
|
267
|
+
*/
|
|
268
|
+
collapse() {
|
|
269
|
+
this.isCollapsed = true;
|
|
270
|
+
|
|
271
|
+
// Initial sample
|
|
272
|
+
this.collapseOffset = this.sampleCollapseOffset();
|
|
273
|
+
this.collapseTargetOffset = this.collapseOffset;
|
|
274
|
+
|
|
275
|
+
// Start periodic resampling while collapsed
|
|
276
|
+
this.startCollapseSampling();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Start periodic position sampling while collapsed
|
|
281
|
+
*/
|
|
282
|
+
startCollapseSampling() {
|
|
283
|
+
this.stopCollapseSampling();
|
|
284
|
+
|
|
285
|
+
const resample = () => {
|
|
286
|
+
if (this.isCollapsed) {
|
|
287
|
+
// Set new target position
|
|
288
|
+
this.collapseTargetOffset = this.sampleCollapseOffset();
|
|
289
|
+
// Schedule next sample (300-600ms random interval)
|
|
290
|
+
this.collapseSampleTimer = setTimeout(resample, 300 + Math.random() * 300);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// First resample after initial delay
|
|
295
|
+
this.collapseSampleTimer = setTimeout(resample, 400);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Stop periodic sampling
|
|
300
|
+
*/
|
|
301
|
+
stopCollapseSampling() {
|
|
302
|
+
if (this.collapseSampleTimer) {
|
|
303
|
+
clearTimeout(this.collapseSampleTimer);
|
|
304
|
+
this.collapseSampleTimer = null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 3D rotation and projection - delegates to Camera3D with zoom applied
|
|
141
310
|
*/
|
|
142
311
|
project3D(x, y, z) {
|
|
143
|
-
|
|
312
|
+
const proj = this.camera.project(x, y, z);
|
|
313
|
+
return {
|
|
314
|
+
x: proj.x * this.zoom,
|
|
315
|
+
y: proj.y * this.zoom,
|
|
316
|
+
z: proj.z,
|
|
317
|
+
scale: proj.scale * this.zoom,
|
|
318
|
+
};
|
|
144
319
|
}
|
|
145
320
|
|
|
146
321
|
update(dt) {
|
|
147
322
|
super.update(dt);
|
|
148
323
|
this.time += dt * CONFIG.timeScale;
|
|
149
324
|
|
|
325
|
+
// Update camera for inertia physics
|
|
326
|
+
this.camera.update(dt);
|
|
327
|
+
|
|
328
|
+
// Ease zoom towards target
|
|
329
|
+
this.zoom += (this.targetZoom - this.zoom) * CONFIG.zoomEasing;
|
|
330
|
+
|
|
331
|
+
// Animate collapse amount
|
|
332
|
+
const targetCollapse = this.isCollapsed ? 1 : 0;
|
|
333
|
+
this.collapseAmount += (targetCollapse - this.collapseAmount) * 0.15;
|
|
334
|
+
|
|
335
|
+
// Tween collapse offset toward target (smooth bezier-like easing)
|
|
336
|
+
this.collapseOffset += (this.collapseTargetOffset - this.collapseOffset) * 0.08;
|
|
337
|
+
|
|
150
338
|
// Loop when wave packet exits the visible range
|
|
151
339
|
const packetCenter = CONFIG.velocity * this.time;
|
|
152
340
|
if (packetCenter > CONFIG.xRange * 0.6) {
|
|
@@ -161,6 +349,14 @@ class SchrodingerDemo extends Game {
|
|
|
161
349
|
}
|
|
162
350
|
}
|
|
163
351
|
|
|
352
|
+
onResize() {
|
|
353
|
+
// Recalculate default zoom for new screen size
|
|
354
|
+
this.defaultZoom = Math.min(
|
|
355
|
+
CONFIG.maxZoom,
|
|
356
|
+
Math.max(CONFIG.minZoom, Screen.minDimension() / CONFIG.baseScreenSize)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
164
360
|
render() {
|
|
165
361
|
const w = this.width;
|
|
166
362
|
const h = this.height;
|
|
@@ -173,6 +369,12 @@ class SchrodingerDemo extends Game {
|
|
|
173
369
|
const points = [];
|
|
174
370
|
const { numPoints, xRange, helixRadius, zScale } = CONFIG;
|
|
175
371
|
|
|
372
|
+
// Collapsed target position = sampled position (center + offset)
|
|
373
|
+
const packetCenter = CONFIG.velocity * this.time;
|
|
374
|
+
const collapsedX = packetCenter + this.collapseOffset;
|
|
375
|
+
const collapsedZ = collapsedX * zScale;
|
|
376
|
+
const collapse = this.collapseAmount;
|
|
377
|
+
|
|
176
378
|
for (let i = 0; i < numPoints; i++) {
|
|
177
379
|
const t_param = i / (numPoints - 1);
|
|
178
380
|
const x = (t_param - 0.5) * xRange;
|
|
@@ -180,9 +382,16 @@ class SchrodingerDemo extends Game {
|
|
|
180
382
|
const { psi, envelope } = this.computeWavePacket(x, this.time);
|
|
181
383
|
|
|
182
384
|
// 3D coordinates: helix in Re/Im plane, extending along Z
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
385
|
+
let px = psi.real * helixRadius; // Re(Ψ) -> X
|
|
386
|
+
let py = psi.imag * helixRadius; // Im(Ψ) -> Y
|
|
387
|
+
let pz = x * zScale; // position -> Z
|
|
388
|
+
|
|
389
|
+
// Collapse animation: lerp all points toward the collapsed position
|
|
390
|
+
if (collapse > 0.01) {
|
|
391
|
+
px = px * (1 - collapse); // X collapses to 0
|
|
392
|
+
py = py * (1 - collapse); // Y collapses to 0
|
|
393
|
+
pz = pz + (collapsedZ - pz) * collapse; // Z collapses to measured position
|
|
394
|
+
}
|
|
186
395
|
|
|
187
396
|
const projected = this.project3D(px, py, pz);
|
|
188
397
|
|
|
@@ -218,10 +427,93 @@ class SchrodingerDemo extends Game {
|
|
|
218
427
|
// Draw projection on grid (2D wave)
|
|
219
428
|
this.drawProjection(cx, cy, points);
|
|
220
429
|
|
|
430
|
+
// Draw collapse indicator if collapsed
|
|
431
|
+
if (this.isCollapsed) {
|
|
432
|
+
this.drawCollapseIndicator(cx, cy);
|
|
433
|
+
}
|
|
434
|
+
|
|
221
435
|
// Info text
|
|
222
436
|
this.drawInfo(w, h);
|
|
223
437
|
}
|
|
224
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Draw squiggly line from collapsed position to red envelope curve
|
|
441
|
+
*/
|
|
442
|
+
drawCollapseIndicator(cx, cy) {
|
|
443
|
+
const { zScale, gridY, helixRadius } = CONFIG;
|
|
444
|
+
const collapse = this.collapseAmount;
|
|
445
|
+
|
|
446
|
+
// Sampled collapse position (center + random offset, travels with envelope)
|
|
447
|
+
const packetCenter = CONFIG.velocity * this.time;
|
|
448
|
+
const collapsedX = packetCenter + this.collapseOffset;
|
|
449
|
+
|
|
450
|
+
// Get the envelope height at the sampled position
|
|
451
|
+
const { envelope } = this.computeWavePacket(collapsedX, this.time);
|
|
452
|
+
const envHeight = envelope * helixRadius * 0.8;
|
|
453
|
+
|
|
454
|
+
// Project points: collapsed position on axis and on envelope
|
|
455
|
+
const axisProj = this.project3D(0, 0, collapsedX * zScale);
|
|
456
|
+
const envelopeProj = this.project3D(0, gridY - envHeight, collapsedX * zScale);
|
|
457
|
+
|
|
458
|
+
// Draw squiggly line from axis to envelope (quantum fluctuations)
|
|
459
|
+
Painter.useCtx((ctx) => {
|
|
460
|
+
ctx.strokeStyle = `rgba(255, 255, 100, ${0.8 * collapse})`;
|
|
461
|
+
ctx.lineWidth = 2;
|
|
462
|
+
ctx.lineCap = "round";
|
|
463
|
+
ctx.lineJoin = "round";
|
|
464
|
+
|
|
465
|
+
const startX = cx + axisProj.x;
|
|
466
|
+
const startY = cy + axisProj.y;
|
|
467
|
+
const endX = cx + envelopeProj.x;
|
|
468
|
+
const endY = cy + envelopeProj.y;
|
|
469
|
+
|
|
470
|
+
// Number of segments and wave properties
|
|
471
|
+
const segments = 20;
|
|
472
|
+
const waveAmplitude = 8 * collapse;
|
|
473
|
+
const waveFrequency = 3;
|
|
474
|
+
const timeOffset = this.time * 10; // Animate the squiggle
|
|
475
|
+
|
|
476
|
+
ctx.moveTo(startX, startY);
|
|
477
|
+
|
|
478
|
+
for (let i = 1; i <= segments; i++) {
|
|
479
|
+
const t = i / segments;
|
|
480
|
+
// Linear interpolation along the line
|
|
481
|
+
const baseX = startX + (endX - startX) * t;
|
|
482
|
+
const baseY = startY + (endY - startY) * t;
|
|
483
|
+
|
|
484
|
+
// Perpendicular offset for squiggle (sine wave)
|
|
485
|
+
const angle = Math.atan2(endY - startY, endX - startX) + Math.PI / 2;
|
|
486
|
+
const wave = Math.sin(t * Math.PI * waveFrequency * 2 + timeOffset) * waveAmplitude * (1 - Math.abs(t - 0.5) * 2);
|
|
487
|
+
|
|
488
|
+
const squiggleX = baseX + Math.cos(angle) * wave;
|
|
489
|
+
const squiggleY = baseY + Math.sin(angle) * wave;
|
|
490
|
+
|
|
491
|
+
ctx.lineTo(squiggleX, squiggleY);
|
|
492
|
+
}
|
|
493
|
+
ctx.stroke();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Draw particle dot on axis (where it collapsed to)
|
|
497
|
+
Painter.useCtx((ctx) => {
|
|
498
|
+
ctx.fillStyle = `rgba(255, 255, 100, ${collapse})`;
|
|
499
|
+
ctx.shadowColor = "#ffff66";
|
|
500
|
+
ctx.shadowBlur = 12 * collapse;
|
|
501
|
+
ctx.arc(cx + axisProj.x, cy + axisProj.y, 5 * axisProj.scale, 0, Math.PI * 2);
|
|
502
|
+
ctx.fill();
|
|
503
|
+
ctx.shadowBlur = 0;
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Draw dot on the envelope curve
|
|
507
|
+
Painter.useCtx((ctx) => {
|
|
508
|
+
ctx.fillStyle = `rgba(255, 255, 100, ${collapse})`;
|
|
509
|
+
ctx.shadowColor = "#ffff66";
|
|
510
|
+
ctx.shadowBlur = 10 * collapse;
|
|
511
|
+
ctx.arc(cx + envelopeProj.x, cy + envelopeProj.y, 5 * envelopeProj.scale, 0, Math.PI * 2);
|
|
512
|
+
ctx.fill();
|
|
513
|
+
ctx.shadowBlur = 0;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
225
517
|
drawGrid(cx, cy) {
|
|
226
518
|
const { gridSize, gridSpacing, gridY } = CONFIG;
|
|
227
519
|
const halfGrid = (gridSize * gridSpacing) / 2;
|
|
@@ -260,28 +552,32 @@ class SchrodingerDemo extends Game {
|
|
|
260
552
|
|
|
261
553
|
drawAxis(cx, cy) {
|
|
262
554
|
const { zScale, xRange } = CONFIG;
|
|
555
|
+
|
|
556
|
+
// Fade out when collapsed (losing momentum information)
|
|
557
|
+
const fade = 1 - this.collapseAmount;
|
|
558
|
+
if (fade < 0.01) return; // Fully collapsed, don't draw
|
|
263
559
|
|
|
264
560
|
// Main position axis (Z direction in 3D space)
|
|
265
561
|
const p1 = this.project3D(0, 0, -xRange / 2 * zScale * 1.2);
|
|
266
562
|
const p2 = this.project3D(0, 0, xRange / 2 * zScale * 1.2);
|
|
267
563
|
|
|
268
|
-
// Glowing axis line
|
|
564
|
+
// Glowing axis line (fades with collapse)
|
|
269
565
|
Painter.useCtx((ctx) => {
|
|
270
|
-
ctx.strokeStyle =
|
|
566
|
+
ctx.strokeStyle = `rgba(100, 150, 200, ${0.6 * fade})`;
|
|
271
567
|
ctx.lineWidth = 2;
|
|
272
568
|
ctx.moveTo(cx + p1.x, cy + p1.y);
|
|
273
569
|
ctx.lineTo(cx + p2.x, cy + p2.y);
|
|
274
570
|
ctx.stroke();
|
|
275
571
|
});
|
|
276
572
|
|
|
277
|
-
// Bright center dot where wave packet is
|
|
573
|
+
// Bright center dot where wave packet is (fades with collapse)
|
|
278
574
|
const packetCenter = CONFIG.velocity * this.time;
|
|
279
575
|
const centerProj = this.project3D(0, 0, packetCenter * zScale);
|
|
280
576
|
|
|
281
577
|
Painter.useCtx((ctx) => {
|
|
282
|
-
ctx.fillStyle =
|
|
578
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${fade})`;
|
|
283
579
|
ctx.shadowColor = "#88ccff";
|
|
284
|
-
ctx.shadowBlur = 20;
|
|
580
|
+
ctx.shadowBlur = 20 * fade;
|
|
285
581
|
ctx.arc(cx + centerProj.x, cy + centerProj.y, 4, 0, Math.PI * 2);
|
|
286
582
|
ctx.fill();
|
|
287
583
|
ctx.shadowBlur = 0;
|
|
@@ -394,7 +690,7 @@ class SchrodingerDemo extends Game {
|
|
|
394
690
|
ctx.fillStyle = "#445";
|
|
395
691
|
ctx.font = "10px monospace";
|
|
396
692
|
ctx.textAlign = "right";
|
|
397
|
-
ctx.fillText("drag to rotate | double-click to reset", w - 15, h - 30);
|
|
693
|
+
ctx.fillText("drag to rotate | scroll to zoom | hold to collapse | double-click to reset", w - 15, h - 30);
|
|
398
694
|
|
|
399
695
|
// Legend
|
|
400
696
|
ctx.fillText("Helix = \u03A8 | Blue = Re(\u03A8) | Red = |\u03A8|\u00B2", w - 15, h - 15);
|