@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,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperbolic 004 - Dini's Vortex
|
|
3
|
+
*
|
|
4
|
+
* Inspired by hyperbolic space - Dini's Surface, a twisted pseudosphere
|
|
5
|
+
* of constant negative curvature. Click to disturb, watch it settle.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Dini's Surface (hyperbolic vortex)
|
|
9
|
+
* - Click to add energy - more clicks = more chaos
|
|
10
|
+
* - Settles to gentle pulsing equilibrium
|
|
11
|
+
* - Drag to rotate, auto-rotates when idle
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { gcanvas, Noise } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
// Geometry
|
|
19
|
+
scale: 180, // Much larger
|
|
20
|
+
uTurns: 4,
|
|
21
|
+
uSegments: 180,
|
|
22
|
+
vSegments: 40,
|
|
23
|
+
b: 0.15,
|
|
24
|
+
|
|
25
|
+
// Noise
|
|
26
|
+
noiseScale: 0.008,
|
|
27
|
+
noiseSpeed: 0.5,
|
|
28
|
+
noiseStrengthBase: 8,
|
|
29
|
+
noiseStrengthMax: 70,
|
|
30
|
+
|
|
31
|
+
// Projection
|
|
32
|
+
fov: 700,
|
|
33
|
+
|
|
34
|
+
// Colors
|
|
35
|
+
baseColor: { h: 45, s: 100, l: 60 }, // Gold/Orange/Yellow
|
|
36
|
+
bgColor: "#000000", // Pure black
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 3D Vector helper
|
|
41
|
+
*/
|
|
42
|
+
class Vec3 {
|
|
43
|
+
constructor(x, y, z) {
|
|
44
|
+
this.x = x;
|
|
45
|
+
this.y = y;
|
|
46
|
+
this.z = z;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Main Study Class
|
|
52
|
+
*/
|
|
53
|
+
class DiniVortex {
|
|
54
|
+
constructor(game) {
|
|
55
|
+
this.game = game;
|
|
56
|
+
this.width = game.width;
|
|
57
|
+
this.height = game.height;
|
|
58
|
+
|
|
59
|
+
this.vertices = [];
|
|
60
|
+
this.indices = [];
|
|
61
|
+
|
|
62
|
+
// Animation
|
|
63
|
+
this.time = 0;
|
|
64
|
+
this.rotation = { x: 0.2, y: 0 };
|
|
65
|
+
this.targetRotation = { x: 0.2, y: 0.5 };
|
|
66
|
+
this.autoRotate = true;
|
|
67
|
+
|
|
68
|
+
// Interaction state - energy decays toward equilibrium
|
|
69
|
+
this.energy = 0;
|
|
70
|
+
this.noiseStrength = CONFIG.noiseStrengthBase;
|
|
71
|
+
|
|
72
|
+
this.initMesh();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
initMesh() {
|
|
76
|
+
this.vertices = [];
|
|
77
|
+
this.indices = [];
|
|
78
|
+
|
|
79
|
+
// Dini's Surface Equations:
|
|
80
|
+
// x = a * cos(u) * sin(v)
|
|
81
|
+
// y = a * sin(u) * sin(v)
|
|
82
|
+
// z = a * (cos(v) + ln(tan(v/2))) + b*u
|
|
83
|
+
|
|
84
|
+
// Constraints:
|
|
85
|
+
// u: 0 to 4PI (or more for spiraling)
|
|
86
|
+
// v: 0.1 to PI (profile curve)
|
|
87
|
+
|
|
88
|
+
const a = CONFIG.scale;
|
|
89
|
+
const b = CONFIG.b * CONFIG.scale; // Scale the pitch too
|
|
90
|
+
|
|
91
|
+
const uMax = CONFIG.uTurns * Math.PI * 2;
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < CONFIG.uSegments; i++) {
|
|
94
|
+
const uRatio = i / (CONFIG.uSegments - 1);
|
|
95
|
+
const u = uRatio * uMax;
|
|
96
|
+
|
|
97
|
+
for (let j = 0; j < CONFIG.vSegments; j++) {
|
|
98
|
+
const vRatio = j / (CONFIG.vSegments - 1);
|
|
99
|
+
// v must avoid 0 (singularity for ln(tan(0)))
|
|
100
|
+
// Range from small epsilon to just under PI/2 (asymptote) or up to 2 (curl back)
|
|
101
|
+
// Standard Dini profile usually 0.1 to 2.0
|
|
102
|
+
const v = 0.05 + vRatio * 1.8;
|
|
103
|
+
|
|
104
|
+
const sinV = Math.sin(v);
|
|
105
|
+
const cosV = Math.cos(v);
|
|
106
|
+
const tanHalfV = Math.tan(v / 2);
|
|
107
|
+
|
|
108
|
+
const x = a * Math.cos(u) * sinV;
|
|
109
|
+
const y = a * Math.sin(u) * sinV;
|
|
110
|
+
|
|
111
|
+
// Handle singularity gracefully
|
|
112
|
+
let term2 = 0;
|
|
113
|
+
if (tanHalfV > 0) {
|
|
114
|
+
term2 = Math.log(tanHalfV);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const z = a * (cosV + term2) + b * u;
|
|
118
|
+
|
|
119
|
+
// Center the spiral vertically
|
|
120
|
+
// Approximation: z ranges roughly from a*2 to -infinity?
|
|
121
|
+
// Let's shift it down by half the spiral height
|
|
122
|
+
const zShift = z - (b * uMax * 0.5);
|
|
123
|
+
|
|
124
|
+
this.vertices.push(new Vec3(x, y, zShift));
|
|
125
|
+
|
|
126
|
+
// Indices (Grid)
|
|
127
|
+
if (i < CONFIG.uSegments - 1 && j < CONFIG.vSegments - 1) {
|
|
128
|
+
const current = i * CONFIG.vSegments + j;
|
|
129
|
+
const right = (i + 1) * CONFIG.vSegments + j;
|
|
130
|
+
const down = i * CONFIG.vSegments + (j + 1);
|
|
131
|
+
const diag = (i + 1) * CONFIG.vSegments + (j + 1);
|
|
132
|
+
|
|
133
|
+
// Wireframe style: grid lines
|
|
134
|
+
this.indices.push([current, right]);
|
|
135
|
+
this.indices.push([current, down]);
|
|
136
|
+
|
|
137
|
+
// Optional: Add diagonals for triangulation density
|
|
138
|
+
// this.indices.push([current, diag]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
update(dt) {
|
|
145
|
+
this.time += dt;
|
|
146
|
+
|
|
147
|
+
// Update dimensions
|
|
148
|
+
this.width = this.game.width;
|
|
149
|
+
this.height = this.game.height;
|
|
150
|
+
|
|
151
|
+
// Responsive scaling
|
|
152
|
+
const minDim = Math.min(this.width, this.height);
|
|
153
|
+
this.renderScale = minDim / 600; // Adjusted baseline for larger appearance
|
|
154
|
+
|
|
155
|
+
// Smooth rotation
|
|
156
|
+
this.rotation.x += (this.targetRotation.x - this.rotation.x) * 0.1;
|
|
157
|
+
this.rotation.y += (this.targetRotation.y - this.rotation.y) * 0.1;
|
|
158
|
+
|
|
159
|
+
if (this.autoRotate) {
|
|
160
|
+
this.targetRotation.y += dt * 0.2;
|
|
161
|
+
this.targetRotation.x = 0.2 + Math.sin(this.time * 0.3) * 0.1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Decay energy smoothly toward 0
|
|
165
|
+
this.energy *= 0.95;
|
|
166
|
+
if (this.energy < 0.001) this.energy = 0;
|
|
167
|
+
|
|
168
|
+
// Interpolate noise strength toward equilibrium
|
|
169
|
+
const targetStrength = CONFIG.noiseStrengthBase + this.energy * (CONFIG.noiseStrengthMax - CONFIG.noiseStrengthBase);
|
|
170
|
+
this.noiseStrength += (targetStrength - this.noiseStrength) * 0.1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
render(ctx, width, height) {
|
|
174
|
+
const cx = width / 2;
|
|
175
|
+
const cy = height / 2;
|
|
176
|
+
|
|
177
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
178
|
+
ctx.fillRect(0, 0, width, height);
|
|
179
|
+
|
|
180
|
+
const cosX = Math.cos(this.rotation.x);
|
|
181
|
+
const sinX = Math.sin(this.rotation.x);
|
|
182
|
+
const cosY = Math.cos(this.rotation.y);
|
|
183
|
+
const sinY = Math.sin(this.rotation.y);
|
|
184
|
+
|
|
185
|
+
const projectedVertices = new Array(this.vertices.length);
|
|
186
|
+
const scaleFactor = this.renderScale || 1;
|
|
187
|
+
|
|
188
|
+
// Transform Loop
|
|
189
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
190
|
+
const v = this.vertices[i];
|
|
191
|
+
|
|
192
|
+
// Calculate derived polar coords for effect
|
|
193
|
+
const r = Math.sqrt(v.x*v.x + v.y*v.y);
|
|
194
|
+
const angle = Math.atan2(v.y, v.x);
|
|
195
|
+
|
|
196
|
+
// 1. Noise Deformation
|
|
197
|
+
// Flow along the spiral (u direction, roughly Z and Angle)
|
|
198
|
+
const t = this.time * CONFIG.noiseSpeed;
|
|
199
|
+
|
|
200
|
+
// We want the noise to flow "up" the spiral
|
|
201
|
+
const flow = v.z * 0.01 - t;
|
|
202
|
+
|
|
203
|
+
const nX = v.x * CONFIG.noiseScale;
|
|
204
|
+
const nY = v.y * CONFIG.noiseScale;
|
|
205
|
+
const nZ = v.z * CONFIG.noiseScale;
|
|
206
|
+
|
|
207
|
+
const noiseVal = Noise.simplex3(nX + Math.cos(t), nY + Math.sin(t), nZ + flow);
|
|
208
|
+
|
|
209
|
+
// Pulse effect
|
|
210
|
+
const pulse = 1.0 + Math.sin(t * 2 + v.z * 0.02) * 0.1;
|
|
211
|
+
|
|
212
|
+
// Apply deformations
|
|
213
|
+
// Expand/contract radially
|
|
214
|
+
const dr = noiseVal * this.noiseStrength;
|
|
215
|
+
|
|
216
|
+
let dx = v.x + (v.x / (r+0.1)) * dr;
|
|
217
|
+
let dy = v.y + (v.y / (r+0.1)) * dr;
|
|
218
|
+
let dz = v.z * pulse;
|
|
219
|
+
|
|
220
|
+
// 2. Rotation
|
|
221
|
+
let x1 = dx * cosY - dz * sinY;
|
|
222
|
+
let z1 = dz * cosY + dx * sinY;
|
|
223
|
+
let y1 = dy;
|
|
224
|
+
|
|
225
|
+
let y2 = y1 * cosX - z1 * sinX;
|
|
226
|
+
let z2 = z1 * cosX + y1 * sinX;
|
|
227
|
+
let x2 = x1;
|
|
228
|
+
|
|
229
|
+
// 3. Projection
|
|
230
|
+
const s = this.renderScale || 1;
|
|
231
|
+
const sx = x2 * s;
|
|
232
|
+
const sy = y2 * s;
|
|
233
|
+
const sz = z2 * s;
|
|
234
|
+
|
|
235
|
+
const scale = CONFIG.fov / (CONFIG.fov + sz + 400);
|
|
236
|
+
const px = sx * scale + cx;
|
|
237
|
+
const py = sy * scale + cy;
|
|
238
|
+
|
|
239
|
+
projectedVertices[i] = {
|
|
240
|
+
x: px,
|
|
241
|
+
y: py,
|
|
242
|
+
z: sz,
|
|
243
|
+
scale: scale,
|
|
244
|
+
origZ: v.z // Store original Z for gradient coloring
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Draw Edges
|
|
249
|
+
const lineWidth = 1.5 * (this.renderScale || 1);
|
|
250
|
+
ctx.lineWidth = lineWidth;
|
|
251
|
+
|
|
252
|
+
// Batch stroke? No, color changes per segment
|
|
253
|
+
for (let i = 0; i < this.indices.length; i++) {
|
|
254
|
+
const [idx1, idx2] = this.indices[i];
|
|
255
|
+
const p1 = projectedVertices[idx1];
|
|
256
|
+
const p2 = projectedVertices[idx2];
|
|
257
|
+
|
|
258
|
+
// Culling
|
|
259
|
+
if (p1.z < -CONFIG.fov + 10 || p2.z < -CONFIG.fov + 10) continue;
|
|
260
|
+
|
|
261
|
+
const avgZ = (p1.z + p2.z) / 2;
|
|
262
|
+
const avgOrigZ = (p1.origZ + p2.origZ) / 2;
|
|
263
|
+
|
|
264
|
+
// Depth fading
|
|
265
|
+
const depthAlpha = Math.max(0.1, Math.min(1, (600 - avgZ) / 800));
|
|
266
|
+
|
|
267
|
+
// Color Gradient along the spiral height
|
|
268
|
+
// Map origZ (-height/2 to height/2) to Hue
|
|
269
|
+
// Height is roughly scale * b * uMax = 60 * 0.15 * 8PI ~= 226
|
|
270
|
+
const heightRange = 300;
|
|
271
|
+
const normalizedH = (avgOrigZ + heightRange/2) / heightRange;
|
|
272
|
+
|
|
273
|
+
// Gold to Red to Purple
|
|
274
|
+
const hue = (CONFIG.baseColor.h + normalizedH * 60) % 360;
|
|
275
|
+
const light = 40 + normalizedH * 40; // Brighter at top
|
|
276
|
+
|
|
277
|
+
ctx.strokeStyle = `hsla(${hue}, ${CONFIG.baseColor.s}%, ${light}%, ${depthAlpha})`;
|
|
278
|
+
|
|
279
|
+
ctx.beginPath();
|
|
280
|
+
ctx.moveTo(p1.x, p1.y);
|
|
281
|
+
ctx.lineTo(p2.x, p2.y);
|
|
282
|
+
ctx.stroke();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Initialize
|
|
288
|
+
window.addEventListener("load", () => {
|
|
289
|
+
const canvas = document.getElementById("game");
|
|
290
|
+
if (!canvas) return;
|
|
291
|
+
|
|
292
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
293
|
+
const gameInstance = game.game;
|
|
294
|
+
|
|
295
|
+
const vortex = new DiniVortex(gameInstance);
|
|
296
|
+
|
|
297
|
+
// Loop
|
|
298
|
+
gameInstance.clear = function() {
|
|
299
|
+
vortex.render(this.ctx, this.width, this.height);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
game.on("update", (dt) => {
|
|
303
|
+
vortex.update(dt);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Interaction
|
|
307
|
+
let isDragging = false;
|
|
308
|
+
let lastX = 0, lastY = 0;
|
|
309
|
+
|
|
310
|
+
gameInstance.events.on("mousedown", (e) => {
|
|
311
|
+
isDragging = true;
|
|
312
|
+
lastX = e.x;
|
|
313
|
+
lastY = e.y;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
gameInstance.events.on("mouseup", () => {
|
|
317
|
+
isDragging = false;
|
|
318
|
+
setTimeout(() => { vortex.autoRotate = true; }, 500);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
gameInstance.events.on("mousemove", (e) => {
|
|
322
|
+
if (isDragging) {
|
|
323
|
+
const dx = (e.x - lastX) * 0.01;
|
|
324
|
+
const dy = (e.y - lastY) * 0.01;
|
|
325
|
+
|
|
326
|
+
vortex.targetRotation.y += dx;
|
|
327
|
+
vortex.targetRotation.x += dy;
|
|
328
|
+
vortex.autoRotate = false;
|
|
329
|
+
|
|
330
|
+
lastX = e.x;
|
|
331
|
+
lastY = e.y;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
gameInstance.events.on("click", () => {
|
|
336
|
+
if(!isDragging) {
|
|
337
|
+
// Add energy on click
|
|
338
|
+
vortex.energy = Math.min(vortex.energy + 0.4, 1.0);
|
|
339
|
+
CONFIG.baseColor.h = Math.random() * 360;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
game.start();
|
|
344
|
+
});
|
|
345
|
+
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperbolic 005 - Fractal Terrain
|
|
3
|
+
*
|
|
4
|
+
* Inspired by hyperbolic space - a Julia set rendered as 3D terrain.
|
|
5
|
+
* The iteration counts become elevation, creating fractal mountains.
|
|
6
|
+
* Click to disturb, watch it settle to equilibrium.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Julia set as heightmap
|
|
10
|
+
* - Animated c parameter morphs the fractal
|
|
11
|
+
* - Click to add energy - more clicks = more chaos
|
|
12
|
+
* - Settles to gentle breathing equilibrium
|
|
13
|
+
* - Drag to rotate
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { gcanvas, Noise, Fractals } from "/gcanvas.es.min.js";
|
|
17
|
+
|
|
18
|
+
const CONFIG = {
|
|
19
|
+
// Mesh resolution (lower = faster, higher = more detail)
|
|
20
|
+
gridSize: 100,
|
|
21
|
+
|
|
22
|
+
// Julia parameters
|
|
23
|
+
maxIterations: 30,
|
|
24
|
+
cReal: -0.7,
|
|
25
|
+
cImag: 0.27015,
|
|
26
|
+
cAnimSpeed: 0.15,
|
|
27
|
+
cAnimRadius: 0.1,
|
|
28
|
+
|
|
29
|
+
// Height scaling
|
|
30
|
+
heightScale: 200,
|
|
31
|
+
heightScaleBase: 120,
|
|
32
|
+
heightScaleMax: 350,
|
|
33
|
+
|
|
34
|
+
// Noise
|
|
35
|
+
noiseScale: 0.02,
|
|
36
|
+
noiseSpeed: 0.5,
|
|
37
|
+
noiseStrengthBase: 5,
|
|
38
|
+
noiseStrengthMax: 40,
|
|
39
|
+
|
|
40
|
+
// View
|
|
41
|
+
fov: 600,
|
|
42
|
+
baseRotationX: 0.8,
|
|
43
|
+
baseRotationY: 0,
|
|
44
|
+
|
|
45
|
+
// Colors
|
|
46
|
+
baseHue: 280,
|
|
47
|
+
bgColor: "#000",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
class Vec3 {
|
|
51
|
+
constructor(x, y, z) {
|
|
52
|
+
this.x = x;
|
|
53
|
+
this.y = y;
|
|
54
|
+
this.z = z;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate fractal heightmap using library's Fractals class
|
|
60
|
+
*/
|
|
61
|
+
function generateFractalHeightmap(size, cReal, cImag, maxIter) {
|
|
62
|
+
// Use the library's Julia set generator
|
|
63
|
+
const rawData = Fractals.julia(
|
|
64
|
+
size,
|
|
65
|
+
size,
|
|
66
|
+
maxIter,
|
|
67
|
+
cReal,
|
|
68
|
+
cImag,
|
|
69
|
+
1.0, // zoom
|
|
70
|
+
0, // offsetX
|
|
71
|
+
0 // offsetY
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// The library returns iteration counts:
|
|
75
|
+
// - 0 = interior points (never escaped)
|
|
76
|
+
// - 1 to maxIter-1 = escaped after that many iterations
|
|
77
|
+
|
|
78
|
+
const data = new Float32Array(size * size);
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < rawData.length; i++) {
|
|
81
|
+
const iter = rawData[i];
|
|
82
|
+
|
|
83
|
+
if (iter === 0) {
|
|
84
|
+
// Interior points - these are the "mountains" (highest)
|
|
85
|
+
data[i] = 1.0;
|
|
86
|
+
} else {
|
|
87
|
+
// Escaped points - lower iterations = further from set = valleys
|
|
88
|
+
// Higher iterations = closer to boundary = foothills
|
|
89
|
+
// Use inverse + power curve for dramatic terrain
|
|
90
|
+
const t = iter / maxIter;
|
|
91
|
+
// Invert: high iterations = high terrain (near boundary)
|
|
92
|
+
// Apply power curve for more dramatic relief
|
|
93
|
+
const height = Math.pow(1 - t, 0.5); // sqrt gives more midrange detail
|
|
94
|
+
data[i] = height * 0.9; // Scale to leave room for interior peaks
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class FractalTerrain {
|
|
102
|
+
constructor(game) {
|
|
103
|
+
this.game = game;
|
|
104
|
+
this.time = 0;
|
|
105
|
+
|
|
106
|
+
this.vertices = [];
|
|
107
|
+
this.indices = [];
|
|
108
|
+
this.heightData = null;
|
|
109
|
+
|
|
110
|
+
this.rotation = { x: CONFIG.baseRotationX, y: 0 };
|
|
111
|
+
this.targetRotation = { x: CONFIG.baseRotationX, y: 0.3 };
|
|
112
|
+
this.autoRotate = true;
|
|
113
|
+
|
|
114
|
+
// Energy system
|
|
115
|
+
this.energy = 0;
|
|
116
|
+
this.noiseStrength = CONFIG.noiseStrengthBase;
|
|
117
|
+
this.heightScale = CONFIG.heightScaleBase;
|
|
118
|
+
|
|
119
|
+
// Animated c parameter
|
|
120
|
+
this.cPhase = 0;
|
|
121
|
+
|
|
122
|
+
this.initMesh();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
initMesh() {
|
|
126
|
+
const size = CONFIG.gridSize;
|
|
127
|
+
const spacing = 900 / size;
|
|
128
|
+
|
|
129
|
+
this.vertices = [];
|
|
130
|
+
this.indices = [];
|
|
131
|
+
|
|
132
|
+
// Create grid vertices
|
|
133
|
+
for (let y = 0; y < size; y++) {
|
|
134
|
+
for (let x = 0; x < size; x++) {
|
|
135
|
+
const px = (x - size / 2) * spacing;
|
|
136
|
+
const py = (y - size / 2) * spacing;
|
|
137
|
+
this.vertices.push(new Vec3(px, py, 0));
|
|
138
|
+
|
|
139
|
+
// Create edges
|
|
140
|
+
if (x < size - 1) {
|
|
141
|
+
const current = y * size + x;
|
|
142
|
+
const right = y * size + x + 1;
|
|
143
|
+
this.indices.push([current, right]);
|
|
144
|
+
}
|
|
145
|
+
if (y < size - 1) {
|
|
146
|
+
const current = y * size + x;
|
|
147
|
+
const down = (y + 1) * size + x;
|
|
148
|
+
this.indices.push([current, down]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.regenerateFractal();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
regenerateFractal() {
|
|
157
|
+
// Animate c parameter in a circle
|
|
158
|
+
const cReal = CONFIG.cReal + Math.cos(this.cPhase) * CONFIG.cAnimRadius;
|
|
159
|
+
const cImag = CONFIG.cImag + Math.sin(this.cPhase) * CONFIG.cAnimRadius;
|
|
160
|
+
|
|
161
|
+
this.heightData = generateFractalHeightmap(
|
|
162
|
+
CONFIG.gridSize,
|
|
163
|
+
cReal,
|
|
164
|
+
cImag,
|
|
165
|
+
CONFIG.maxIterations
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
update(dt) {
|
|
170
|
+
this.time += dt;
|
|
171
|
+
|
|
172
|
+
// Animate c parameter slowly
|
|
173
|
+
this.cPhase += dt * CONFIG.cAnimSpeed;
|
|
174
|
+
this.regenerateFractal();
|
|
175
|
+
|
|
176
|
+
// Responsive scaling
|
|
177
|
+
const minDim = Math.min(this.game.width, this.game.height);
|
|
178
|
+
this.renderScale = minDim / 800;
|
|
179
|
+
|
|
180
|
+
// Smooth rotation
|
|
181
|
+
this.rotation.x += (this.targetRotation.x - this.rotation.x) * 0.08;
|
|
182
|
+
this.rotation.y += (this.targetRotation.y - this.rotation.y) * 0.08;
|
|
183
|
+
|
|
184
|
+
if (this.autoRotate) {
|
|
185
|
+
this.targetRotation.y += dt * 0.15;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Decay energy
|
|
189
|
+
this.energy *= 0.95;
|
|
190
|
+
if (this.energy < 0.001) this.energy = 0;
|
|
191
|
+
|
|
192
|
+
// Interpolate noise and height scale
|
|
193
|
+
const targetNoise = CONFIG.noiseStrengthBase +
|
|
194
|
+
this.energy * (CONFIG.noiseStrengthMax - CONFIG.noiseStrengthBase);
|
|
195
|
+
this.noiseStrength += (targetNoise - this.noiseStrength) * 0.1;
|
|
196
|
+
|
|
197
|
+
const targetHeight = CONFIG.heightScaleBase +
|
|
198
|
+
this.energy * (CONFIG.heightScaleMax - CONFIG.heightScaleBase);
|
|
199
|
+
this.heightScale += (targetHeight - this.heightScale) * 0.1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
render(ctx, width, height) {
|
|
203
|
+
const cx = width / 2;
|
|
204
|
+
const cy = height / 2;
|
|
205
|
+
const size = CONFIG.gridSize;
|
|
206
|
+
|
|
207
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
208
|
+
ctx.fillRect(0, 0, width, height);
|
|
209
|
+
|
|
210
|
+
const cosX = Math.cos(this.rotation.x);
|
|
211
|
+
const sinX = Math.sin(this.rotation.x);
|
|
212
|
+
const cosY = Math.cos(this.rotation.y);
|
|
213
|
+
const sinY = Math.sin(this.rotation.y);
|
|
214
|
+
|
|
215
|
+
const projected = new Array(this.vertices.length);
|
|
216
|
+
const scale = this.renderScale || 1;
|
|
217
|
+
const t = this.time * CONFIG.noiseSpeed;
|
|
218
|
+
|
|
219
|
+
// Transform vertices
|
|
220
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
221
|
+
const v = this.vertices[i];
|
|
222
|
+
const h = this.heightData[i];
|
|
223
|
+
|
|
224
|
+
// Base height from fractal
|
|
225
|
+
let z = h * this.heightScale;
|
|
226
|
+
|
|
227
|
+
// Add noise disturbance
|
|
228
|
+
const noiseVal = Noise.simplex3(
|
|
229
|
+
v.x * CONFIG.noiseScale + t,
|
|
230
|
+
v.y * CONFIG.noiseScale,
|
|
231
|
+
t * 0.5
|
|
232
|
+
);
|
|
233
|
+
z += noiseVal * this.noiseStrength;
|
|
234
|
+
|
|
235
|
+
// Rotate Y
|
|
236
|
+
let x1 = v.x * cosY - z * sinY;
|
|
237
|
+
let z1 = z * cosY + v.x * sinY;
|
|
238
|
+
let y1 = v.y;
|
|
239
|
+
|
|
240
|
+
// Rotate X
|
|
241
|
+
let y2 = y1 * cosX - z1 * sinX;
|
|
242
|
+
let z2 = z1 * cosX + y1 * sinX;
|
|
243
|
+
let x2 = x1;
|
|
244
|
+
|
|
245
|
+
// Scale and project
|
|
246
|
+
const sx = x2 * scale;
|
|
247
|
+
const sy = y2 * scale;
|
|
248
|
+
const sz = z2 * scale;
|
|
249
|
+
|
|
250
|
+
const projScale = CONFIG.fov / (CONFIG.fov + sz + 300);
|
|
251
|
+
const px = sx * projScale + cx;
|
|
252
|
+
const py = sy * projScale + cy;
|
|
253
|
+
|
|
254
|
+
projected[i] = { x: px, y: py, z: sz, h, scale: projScale };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Draw edges
|
|
258
|
+
ctx.lineWidth = 1.5 * scale;
|
|
259
|
+
|
|
260
|
+
for (const [i1, i2] of this.indices) {
|
|
261
|
+
const p1 = projected[i1];
|
|
262
|
+
const p2 = projected[i2];
|
|
263
|
+
|
|
264
|
+
if (p1.z < -CONFIG.fov + 50 || p2.z < -CONFIG.fov + 50) continue;
|
|
265
|
+
|
|
266
|
+
const avgZ = (p1.z + p2.z) / 2;
|
|
267
|
+
const avgH = (p1.h + p2.h) / 2;
|
|
268
|
+
|
|
269
|
+
// Depth alpha
|
|
270
|
+
const depthAlpha = Math.max(0.1, Math.min(1, (400 - avgZ) / 600));
|
|
271
|
+
|
|
272
|
+
// Color based on height
|
|
273
|
+
const hue = (CONFIG.baseHue + avgH * 120 + this.time * 20) % 360;
|
|
274
|
+
const light = 30 + avgH * 50;
|
|
275
|
+
|
|
276
|
+
ctx.strokeStyle = `hsla(${hue}, 90%, ${light}%, ${depthAlpha})`;
|
|
277
|
+
ctx.beginPath();
|
|
278
|
+
ctx.moveTo(p1.x, p1.y);
|
|
279
|
+
ctx.lineTo(p2.x, p2.y);
|
|
280
|
+
ctx.stroke();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Initialize
|
|
286
|
+
window.addEventListener("load", () => {
|
|
287
|
+
const canvas = document.getElementById("game");
|
|
288
|
+
if (!canvas) return;
|
|
289
|
+
|
|
290
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
291
|
+
const gameInstance = game.game;
|
|
292
|
+
|
|
293
|
+
const terrain = new FractalTerrain(gameInstance);
|
|
294
|
+
|
|
295
|
+
gameInstance.clear = function () {
|
|
296
|
+
terrain.render(this.ctx, this.width, this.height);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
game.on("update", (dt) => {
|
|
300
|
+
terrain.update(dt);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Interaction
|
|
304
|
+
let isDragging = false;
|
|
305
|
+
let lastX = 0, lastY = 0;
|
|
306
|
+
|
|
307
|
+
gameInstance.events.on("mousedown", (e) => {
|
|
308
|
+
isDragging = true;
|
|
309
|
+
lastX = e.x;
|
|
310
|
+
lastY = e.y;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
gameInstance.events.on("mouseup", () => {
|
|
314
|
+
isDragging = false;
|
|
315
|
+
setTimeout(() => { terrain.autoRotate = true; }, 500);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
gameInstance.events.on("mousemove", (e) => {
|
|
319
|
+
if (isDragging) {
|
|
320
|
+
const dx = (e.x - lastX) * 0.01;
|
|
321
|
+
const dy = (e.y - lastY) * 0.01;
|
|
322
|
+
|
|
323
|
+
terrain.targetRotation.y += dx;
|
|
324
|
+
terrain.targetRotation.x += dy;
|
|
325
|
+
terrain.autoRotate = false;
|
|
326
|
+
|
|
327
|
+
lastX = e.x;
|
|
328
|
+
lastY = e.y;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
gameInstance.events.on("click", () => {
|
|
333
|
+
if (!isDragging) {
|
|
334
|
+
terrain.energy = Math.min(terrain.energy + 0.4, 1.0);
|
|
335
|
+
CONFIG.baseHue = Math.random() * 360;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
game.start();
|
|
340
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-injects a toggle button for #info overlay on mobile devices.
|
|
3
|
+
* Include this script in demo pages that have an #info element.
|
|
4
|
+
*/
|
|
5
|
+
(function() {
|
|
6
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
7
|
+
const info = document.getElementById("info");
|
|
8
|
+
if (!info) return;
|
|
9
|
+
|
|
10
|
+
// Create toggle button
|
|
11
|
+
const toggle = document.createElement("button");
|
|
12
|
+
toggle.id = "info-toggle";
|
|
13
|
+
toggle.textContent = "?";
|
|
14
|
+
toggle.setAttribute("aria-label", "Toggle info panel");
|
|
15
|
+
|
|
16
|
+
// Insert button before info element
|
|
17
|
+
info.parentNode.insertBefore(toggle, info);
|
|
18
|
+
|
|
19
|
+
// Toggle functionality
|
|
20
|
+
toggle.addEventListener("click", function() {
|
|
21
|
+
info.classList.toggle("open");
|
|
22
|
+
toggle.textContent = info.classList.contains("open") ? "×" : "?";
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
})();
|