@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,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schrodinger Wave Packet - Math & Physics Demo
|
|
3
|
+
*
|
|
4
|
+
* 3D visualization of a Gaussian wave packet showing the complex
|
|
5
|
+
* wave function as a helix/spiral traveling through space.
|
|
6
|
+
*
|
|
7
|
+
* Ψ(x,t) = A * e^(-(x-vt)²/4a²) * e^(i(kx-ωt))
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Game, Painter, Camera3D, Text, applyAnchor, Position, Scene, verticalLayout, applyLayout, Gesture, Screen } from "/gcanvas.es.min.js";
|
|
11
|
+
import { gaussianWavePacket } from "/gcanvas.es.min.js";
|
|
12
|
+
|
|
13
|
+
// Configuration
|
|
14
|
+
const CONFIG = {
|
|
15
|
+
// Wave packet parameters
|
|
16
|
+
amplitude: 1.0,
|
|
17
|
+
sigma: 0.8, // Width of Gaussian envelope
|
|
18
|
+
k: 8.0, // Wave number (controls oscillation frequency)
|
|
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
|
+
|
|
22
|
+
// Visualization
|
|
23
|
+
numPoints: 300, // Points along the wave
|
|
24
|
+
xRange: 12, // Total x range (-xRange/2 to +xRange/2)
|
|
25
|
+
helixRadius: 80, // Radius of the helix (Re/Im amplitude)
|
|
26
|
+
zScale: 40, // Scale for z-axis (position)
|
|
27
|
+
|
|
28
|
+
// 3D view
|
|
29
|
+
rotationX: 0.3, // Tilt angle
|
|
30
|
+
rotationY: -0.4, // Side rotation
|
|
31
|
+
perspective: 800, // Perspective depth
|
|
32
|
+
|
|
33
|
+
// Grid
|
|
34
|
+
gridSize: 16, // Grid lines count
|
|
35
|
+
gridSpacing: 30, // Spacing between grid lines
|
|
36
|
+
gridY: 120, // Y offset for grid plane
|
|
37
|
+
|
|
38
|
+
// Animation
|
|
39
|
+
timeScale: 1.0,
|
|
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
|
+
|
|
52
|
+
// Colors
|
|
53
|
+
waveColor: [80, 160, 255], // Cyan-blue for the helix
|
|
54
|
+
waveGlow: [150, 200, 255], // Brighter for the center
|
|
55
|
+
gridColor: "rgba(60, 80, 120, 0.4)",
|
|
56
|
+
axisColor: "rgba(100, 150, 200, 0.6)",
|
|
57
|
+
envelopeColor: "rgba(255, 100, 100, 0.5)", // Red envelope line
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
class SchrodingerDemo extends Game {
|
|
61
|
+
constructor(canvas) {
|
|
62
|
+
super(canvas);
|
|
63
|
+
this.backgroundColor = "#000000";
|
|
64
|
+
this.enableFluidSize();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
init() {
|
|
68
|
+
super.init();
|
|
69
|
+
this.time = 0;
|
|
70
|
+
|
|
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
|
|
81
|
+
this.camera = new Camera3D({
|
|
82
|
+
rotationX: CONFIG.rotationX,
|
|
83
|
+
rotationY: CONFIG.rotationY,
|
|
84
|
+
perspective: CONFIG.perspective,
|
|
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
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Enable mouse/touch rotation
|
|
92
|
+
this.camera.enableMouseControl(this.canvas);
|
|
93
|
+
|
|
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
|
|
106
|
+
this.canvas.addEventListener("dblclick", () => {
|
|
107
|
+
this.time = 0;
|
|
108
|
+
this.targetZoom = this.defaultZoom;
|
|
109
|
+
});
|
|
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
|
+
|
|
124
|
+
// Create info panel container anchored to top center
|
|
125
|
+
this.infoPanel = new Scene(this, { x: 0, y: 0 });
|
|
126
|
+
applyAnchor(this.infoPanel, {
|
|
127
|
+
anchor: Position.TOP_CENTER,
|
|
128
|
+
anchorOffsetY: 150,
|
|
129
|
+
});
|
|
130
|
+
this.pipeline.add(this.infoPanel);
|
|
131
|
+
|
|
132
|
+
// Create all text items
|
|
133
|
+
const { amplitude, sigma, k, omega, velocity } = CONFIG;
|
|
134
|
+
|
|
135
|
+
this.titleText = new Text(this, "Gaussian Wave Packet", {
|
|
136
|
+
font: "bold 16px monospace",
|
|
137
|
+
color: "#7af",
|
|
138
|
+
align: "center",
|
|
139
|
+
baseline: "middle",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.equationText = new Text(this, "\u03A8(x,t) = A\u00B7e^(-(x-vt)\u00B2/4\u03C3\u00B2) \u00B7 e^(i(kx-\u03C9t))", {
|
|
143
|
+
font: "14px monospace",
|
|
144
|
+
color: "#fff",
|
|
145
|
+
align: "center",
|
|
146
|
+
baseline: "middle",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.paramsText = new Text(this, `A=${amplitude} \u03C3=${sigma} k=${k} \u03C9=${omega} v=${velocity}`, {
|
|
150
|
+
font: "12px monospace",
|
|
151
|
+
color: "#6d8",
|
|
152
|
+
align: "center",
|
|
153
|
+
baseline: "middle",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.liveText = new Text(this, "t=0.00s x\u2080=0.00", {
|
|
157
|
+
font: "12px monospace",
|
|
158
|
+
color: "#fa6",
|
|
159
|
+
align: "center",
|
|
160
|
+
baseline: "middle",
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Use vertical layout to position items
|
|
164
|
+
const textItems = [this.titleText, this.equationText, this.paramsText, this.liveText];
|
|
165
|
+
const layout = verticalLayout(textItems, { spacing: 20, align: "center" });
|
|
166
|
+
applyLayout(textItems, layout.positions);
|
|
167
|
+
|
|
168
|
+
// Add all to panel
|
|
169
|
+
textItems.forEach(item => this.infoPanel.add(item));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Compute Gaussian wave packet using quantum.js module.
|
|
174
|
+
* Ψ(x,t) = A * e^(-(x-vt)²/4σ²) * e^(i(kx-ωt))
|
|
175
|
+
*/
|
|
176
|
+
computeWavePacket(x, t) {
|
|
177
|
+
return gaussianWavePacket(x, t, {
|
|
178
|
+
amplitude: CONFIG.amplitude,
|
|
179
|
+
sigma: CONFIG.sigma,
|
|
180
|
+
k: CONFIG.k,
|
|
181
|
+
omega: CONFIG.omega,
|
|
182
|
+
velocity: CONFIG.velocity,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
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
|
|
310
|
+
*/
|
|
311
|
+
project3D(x, y, z) {
|
|
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
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
update(dt) {
|
|
322
|
+
super.update(dt);
|
|
323
|
+
this.time += dt * CONFIG.timeScale;
|
|
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
|
+
|
|
338
|
+
// Loop when wave packet exits the visible range
|
|
339
|
+
const packetCenter = CONFIG.velocity * this.time;
|
|
340
|
+
if (packetCenter > CONFIG.xRange * 0.6) {
|
|
341
|
+
this.time = -CONFIG.xRange * 0.6 / CONFIG.velocity;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Update live text values
|
|
345
|
+
if (this.liveText) {
|
|
346
|
+
const t = this.time.toFixed(2);
|
|
347
|
+
const x0 = (CONFIG.velocity * this.time).toFixed(2);
|
|
348
|
+
this.liveText.text = `t=${t}s x\u2080=${x0}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
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
|
+
|
|
360
|
+
render() {
|
|
361
|
+
const w = this.width;
|
|
362
|
+
const h = this.height;
|
|
363
|
+
const cx = w / 2;
|
|
364
|
+
const cy = h / 2 - 30;
|
|
365
|
+
|
|
366
|
+
super.render();
|
|
367
|
+
|
|
368
|
+
// Compute wave packet points
|
|
369
|
+
const points = [];
|
|
370
|
+
const { numPoints, xRange, helixRadius, zScale } = CONFIG;
|
|
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
|
+
|
|
378
|
+
for (let i = 0; i < numPoints; i++) {
|
|
379
|
+
const t_param = i / (numPoints - 1);
|
|
380
|
+
const x = (t_param - 0.5) * xRange;
|
|
381
|
+
|
|
382
|
+
const { psi, envelope } = this.computeWavePacket(x, this.time);
|
|
383
|
+
|
|
384
|
+
// 3D coordinates: helix in Re/Im plane, extending along Z
|
|
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
|
+
}
|
|
395
|
+
|
|
396
|
+
const projected = this.project3D(px, py, pz);
|
|
397
|
+
|
|
398
|
+
points.push({
|
|
399
|
+
x: cx + projected.x,
|
|
400
|
+
y: cy + projected.y,
|
|
401
|
+
z: projected.z,
|
|
402
|
+
scale: projected.scale,
|
|
403
|
+
envelope,
|
|
404
|
+
psi,
|
|
405
|
+
worldX: x,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Draw grid plane
|
|
410
|
+
this.drawGrid(cx, cy);
|
|
411
|
+
|
|
412
|
+
// Draw axis line (position axis)
|
|
413
|
+
this.drawAxis(cx, cy);
|
|
414
|
+
|
|
415
|
+
// Draw envelope curves (Gaussian bell on grid)
|
|
416
|
+
this.drawEnvelope(cx, cy, points);
|
|
417
|
+
|
|
418
|
+
// Sort points by depth for proper rendering
|
|
419
|
+
const sortedIndices = points
|
|
420
|
+
.map((p, i) => ({ z: p.z, i }))
|
|
421
|
+
.sort((a, b) => a.z - b.z)
|
|
422
|
+
.map(item => item.i);
|
|
423
|
+
|
|
424
|
+
// Draw the helix wave
|
|
425
|
+
this.drawHelix(points, sortedIndices);
|
|
426
|
+
|
|
427
|
+
// Draw projection on grid (2D wave)
|
|
428
|
+
this.drawProjection(cx, cy, points);
|
|
429
|
+
|
|
430
|
+
// Draw collapse indicator if collapsed
|
|
431
|
+
if (this.isCollapsed) {
|
|
432
|
+
this.drawCollapseIndicator(cx, cy);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Info text
|
|
436
|
+
this.drawInfo(w, h);
|
|
437
|
+
}
|
|
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
|
+
|
|
517
|
+
drawGrid(cx, cy) {
|
|
518
|
+
const { gridSize, gridSpacing, gridY } = CONFIG;
|
|
519
|
+
const halfGrid = (gridSize * gridSpacing) / 2;
|
|
520
|
+
|
|
521
|
+
// Draw grid lines
|
|
522
|
+
for (let i = -gridSize / 2; i <= gridSize / 2; i++) {
|
|
523
|
+
// Lines along Z
|
|
524
|
+
const x1 = i * gridSpacing;
|
|
525
|
+
const z1 = -halfGrid;
|
|
526
|
+
const z2 = halfGrid;
|
|
527
|
+
|
|
528
|
+
const p1 = this.project3D(x1, gridY, z1);
|
|
529
|
+
const p2 = this.project3D(x1, gridY, z2);
|
|
530
|
+
|
|
531
|
+
Painter.useCtx((ctx) => {
|
|
532
|
+
ctx.strokeStyle = CONFIG.gridColor;
|
|
533
|
+
ctx.lineWidth = 1;
|
|
534
|
+
ctx.moveTo(cx + p1.x, cy + p1.y);
|
|
535
|
+
ctx.lineTo(cx + p2.x, cy + p2.y);
|
|
536
|
+
ctx.stroke();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Lines along X
|
|
540
|
+
const p3 = this.project3D(-halfGrid, gridY, i * gridSpacing);
|
|
541
|
+
const p4 = this.project3D(halfGrid, gridY, i * gridSpacing);
|
|
542
|
+
|
|
543
|
+
Painter.useCtx((ctx) => {
|
|
544
|
+
ctx.strokeStyle = CONFIG.gridColor;
|
|
545
|
+
ctx.lineWidth = 1;
|
|
546
|
+
ctx.moveTo(cx + p3.x, cy + p3.y);
|
|
547
|
+
ctx.lineTo(cx + p4.x, cy + p4.y);
|
|
548
|
+
ctx.stroke();
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
drawAxis(cx, cy) {
|
|
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
|
|
559
|
+
|
|
560
|
+
// Main position axis (Z direction in 3D space)
|
|
561
|
+
const p1 = this.project3D(0, 0, -xRange / 2 * zScale * 1.2);
|
|
562
|
+
const p2 = this.project3D(0, 0, xRange / 2 * zScale * 1.2);
|
|
563
|
+
|
|
564
|
+
// Glowing axis line (fades with collapse)
|
|
565
|
+
Painter.useCtx((ctx) => {
|
|
566
|
+
ctx.strokeStyle = `rgba(100, 150, 200, ${0.6 * fade})`;
|
|
567
|
+
ctx.lineWidth = 2;
|
|
568
|
+
ctx.moveTo(cx + p1.x, cy + p1.y);
|
|
569
|
+
ctx.lineTo(cx + p2.x, cy + p2.y);
|
|
570
|
+
ctx.stroke();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Bright center dot where wave packet is (fades with collapse)
|
|
574
|
+
const packetCenter = CONFIG.velocity * this.time;
|
|
575
|
+
const centerProj = this.project3D(0, 0, packetCenter * zScale);
|
|
576
|
+
|
|
577
|
+
Painter.useCtx((ctx) => {
|
|
578
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${fade})`;
|
|
579
|
+
ctx.shadowColor = "#88ccff";
|
|
580
|
+
ctx.shadowBlur = 20 * fade;
|
|
581
|
+
ctx.arc(cx + centerProj.x, cy + centerProj.y, 4, 0, Math.PI * 2);
|
|
582
|
+
ctx.fill();
|
|
583
|
+
ctx.shadowBlur = 0;
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
drawEnvelope(cx, cy, points) {
|
|
588
|
+
const { gridY } = CONFIG;
|
|
589
|
+
|
|
590
|
+
// Precompute envelope path points
|
|
591
|
+
const pathPoints = points.map(p => {
|
|
592
|
+
const envHeight = p.envelope * CONFIG.helixRadius * 0.8;
|
|
593
|
+
return this.project3D(0, gridY - envHeight, p.worldX * CONFIG.zScale);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Draw Gaussian envelope on the grid plane
|
|
597
|
+
Painter.useCtx((ctx) => {
|
|
598
|
+
ctx.strokeStyle = CONFIG.envelopeColor;
|
|
599
|
+
ctx.lineWidth = 2;
|
|
600
|
+
|
|
601
|
+
for (let i = 0; i < pathPoints.length; i++) {
|
|
602
|
+
const proj = pathPoints[i];
|
|
603
|
+
if (i === 0) {
|
|
604
|
+
ctx.moveTo(cx + proj.x, cy + proj.y);
|
|
605
|
+
} else {
|
|
606
|
+
ctx.lineTo(cx + proj.x, cy + proj.y);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
ctx.stroke();
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
drawHelix(points, sortedIndices) {
|
|
614
|
+
const { waveColor, waveGlow } = CONFIG;
|
|
615
|
+
|
|
616
|
+
// Draw helix as connected line segments with varying thickness
|
|
617
|
+
for (let j = 0; j < sortedIndices.length - 1; j++) {
|
|
618
|
+
const i = sortedIndices[j];
|
|
619
|
+
const p1 = points[i];
|
|
620
|
+
const p2 = points[i + 1] || points[i];
|
|
621
|
+
|
|
622
|
+
if (Math.abs(i - (sortedIndices[j + 1] || i)) > 2) continue;
|
|
623
|
+
|
|
624
|
+
// Thickness based on envelope (thicker where amplitude is higher)
|
|
625
|
+
const thickness = 1 + p1.envelope * 6;
|
|
626
|
+
|
|
627
|
+
// Color intensity based on envelope
|
|
628
|
+
const intensity = 0.3 + p1.envelope * 0.7;
|
|
629
|
+
|
|
630
|
+
const r = Math.floor(waveColor[0] + (waveGlow[0] - waveColor[0]) * p1.envelope);
|
|
631
|
+
const g = Math.floor(waveColor[1] + (waveGlow[1] - waveColor[1]) * p1.envelope);
|
|
632
|
+
const b = Math.floor(waveColor[2] + (waveGlow[2] - waveColor[2]) * p1.envelope);
|
|
633
|
+
|
|
634
|
+
Painter.useCtx((ctx) => {
|
|
635
|
+
ctx.lineCap = "round";
|
|
636
|
+
ctx.lineJoin = "round";
|
|
637
|
+
ctx.strokeStyle = `rgba(${r},${g},${b},${intensity})`;
|
|
638
|
+
ctx.lineWidth = thickness * p1.scale;
|
|
639
|
+
ctx.moveTo(p1.x, p1.y);
|
|
640
|
+
ctx.lineTo(p2.x, p2.y);
|
|
641
|
+
ctx.stroke();
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Add glow effect at high-amplitude regions
|
|
646
|
+
for (const p of points) {
|
|
647
|
+
if (p.envelope > 0.5) {
|
|
648
|
+
const size = p.envelope * 4 * p.scale;
|
|
649
|
+
Painter.useCtx((ctx) => {
|
|
650
|
+
ctx.shadowColor = "rgba(100, 180, 255, 0.8)";
|
|
651
|
+
ctx.shadowBlur = 15;
|
|
652
|
+
ctx.fillStyle = `rgba(200, 230, 255, ${p.envelope * 0.5})`;
|
|
653
|
+
ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
|
|
654
|
+
ctx.fill();
|
|
655
|
+
ctx.shadowBlur = 0;
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
drawProjection(cx, cy, points) {
|
|
662
|
+
const { gridY, zScale } = CONFIG;
|
|
663
|
+
|
|
664
|
+
// Precompute projection path points
|
|
665
|
+
const pathPoints = points.map(p => {
|
|
666
|
+
const waveHeight = p.psi.real * CONFIG.helixRadius * 0.5;
|
|
667
|
+
return this.project3D(waveHeight, gridY, p.worldX * zScale);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Draw 2D wave projection on grid (Re(Ψ) only)
|
|
671
|
+
Painter.useCtx((ctx) => {
|
|
672
|
+
ctx.strokeStyle = "rgba(80, 160, 255, 0.6)";
|
|
673
|
+
ctx.lineWidth = 1.5;
|
|
674
|
+
|
|
675
|
+
for (let i = 0; i < pathPoints.length; i++) {
|
|
676
|
+
const proj = pathPoints[i];
|
|
677
|
+
if (i === 0) {
|
|
678
|
+
ctx.moveTo(cx + proj.x, cy + proj.y);
|
|
679
|
+
} else {
|
|
680
|
+
ctx.lineTo(cx + proj.x, cy + proj.y);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
ctx.stroke();
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
drawInfo(w, h) {
|
|
688
|
+
Painter.useCtx((ctx) => {
|
|
689
|
+
// Controls hint (bottom right)
|
|
690
|
+
ctx.fillStyle = "#445";
|
|
691
|
+
ctx.font = "10px monospace";
|
|
692
|
+
ctx.textAlign = "right";
|
|
693
|
+
ctx.fillText("drag to rotate | scroll to zoom | hold to collapse | double-click to reset", w - 15, h - 30);
|
|
694
|
+
|
|
695
|
+
// Legend
|
|
696
|
+
ctx.fillText("Helix = \u03A8 | Blue = Re(\u03A8) | Red = |\u03A8|\u00B2", w - 15, h - 15);
|
|
697
|
+
ctx.textAlign = "left";
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
window.addEventListener("load", () => {
|
|
703
|
+
const canvas = document.getElementById("game");
|
|
704
|
+
const demo = new SchrodingerDemo(canvas);
|
|
705
|
+
demo.start();
|
|
706
|
+
});
|