@guinetik/gcanvas 1.0.4 → 1.0.5
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/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/coordinates.html +698 -0
- package/dist/cube3d.html +23 -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 +517 -0
- 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/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 +398 -0
- package/dist/isometric.html +34 -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/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -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/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 +863 -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/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/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +410 -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/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/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/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/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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fractal Plasma - Generative Art Demo
|
|
3
|
+
*
|
|
4
|
+
* Animated plasma effect using Fractals.applyColorScheme()
|
|
5
|
+
* for trippy color palettes. Fast single-image rendering.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Game, ImageGo, Painter } from "/gcanvas.es.min.js";
|
|
9
|
+
import { Fractals } from "/gcanvas.es.min.js";
|
|
10
|
+
|
|
11
|
+
class PlasmaDemo extends Game {
|
|
12
|
+
constructor(canvas) {
|
|
13
|
+
super(canvas);
|
|
14
|
+
this.backgroundColor = '#000';
|
|
15
|
+
this.enableFluidSize();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
init() {
|
|
19
|
+
super.init();
|
|
20
|
+
|
|
21
|
+
// Render at moderate res for balance of quality and performance
|
|
22
|
+
this.plasmaWidth = 512;
|
|
23
|
+
this.plasmaHeight = 512;
|
|
24
|
+
|
|
25
|
+
// Create plasma data buffer
|
|
26
|
+
this.plasmaData = new Uint8Array(this.plasmaWidth * this.plasmaHeight);
|
|
27
|
+
|
|
28
|
+
// Create ImageData for rendering
|
|
29
|
+
this.imageData = Painter.img.createImageData(this.plasmaWidth, this.plasmaHeight);
|
|
30
|
+
|
|
31
|
+
// Create ImageGo to display (fullscreen from top-left corner)
|
|
32
|
+
this.plasmaImage = new ImageGo(this, this.imageData, {
|
|
33
|
+
x: 0,
|
|
34
|
+
y: 0,
|
|
35
|
+
width: this.width,
|
|
36
|
+
height: this.height,
|
|
37
|
+
anchor: 'top-left',
|
|
38
|
+
});
|
|
39
|
+
this.pipeline.add(this.plasmaImage);
|
|
40
|
+
|
|
41
|
+
// Animation state
|
|
42
|
+
this.baseTime = 0; // Steady clock
|
|
43
|
+
this.time = 0; // Modulated time for fluid speed
|
|
44
|
+
this.colorSchemeIndex = 0;
|
|
45
|
+
// Only use schemes that animate well with hueShift
|
|
46
|
+
this.colorSchemes = ['electric', 'rainbow', 'historic', 'psychedelic', 'neon'];
|
|
47
|
+
this.currentScheme = this.colorSchemes[0];
|
|
48
|
+
this.schemeTime = 0;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// HSL to RGB helper (needed for some color schemes)
|
|
52
|
+
this.hslToRgb = (h, s, l) => {
|
|
53
|
+
h /= 360;
|
|
54
|
+
const a = s * Math.min(l, 1 - l);
|
|
55
|
+
const f = (n) => {
|
|
56
|
+
const k = (n + h * 12) % 12;
|
|
57
|
+
return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
58
|
+
};
|
|
59
|
+
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
update(dt) {
|
|
64
|
+
super.update(dt);
|
|
65
|
+
|
|
66
|
+
this.baseTime += dt;
|
|
67
|
+
this.schemeTime += dt;
|
|
68
|
+
|
|
69
|
+
// Fluid speed modulation - breathing effect (slow -> fast -> slow)
|
|
70
|
+
// Speed varies from 0.3x to 1.7x in smooth waves
|
|
71
|
+
const speedMod = 1 + Math.sin(this.baseTime * 0.4) * 0.7;
|
|
72
|
+
this.time += dt * speedMod;
|
|
73
|
+
|
|
74
|
+
// Cycle color schemes every 5 seconds
|
|
75
|
+
if (this.schemeTime > 5) {
|
|
76
|
+
this.schemeTime = 0;
|
|
77
|
+
this.colorSchemeIndex = (this.colorSchemeIndex + 1) % this.colorSchemes.length;
|
|
78
|
+
this.currentScheme = this.colorSchemes[this.colorSchemeIndex];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Generate animated plasma
|
|
82
|
+
this.generatePlasma();
|
|
83
|
+
|
|
84
|
+
// Apply color scheme (use custom for trippy ones, library for others)
|
|
85
|
+
if (this.currentScheme === 'psychedelic' || this.currentScheme === 'neon') {
|
|
86
|
+
this.applyCustomScheme(this.currentScheme, this.time * 50);
|
|
87
|
+
} else {
|
|
88
|
+
Fractals.applyColorScheme(
|
|
89
|
+
this.plasmaData,
|
|
90
|
+
this.imageData,
|
|
91
|
+
this.currentScheme,
|
|
92
|
+
256, // iterations (for normalization)
|
|
93
|
+
this.time * 50, // hue shift for animation
|
|
94
|
+
this.hslToRgb
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Update the image
|
|
99
|
+
this.plasmaImage.shape.bitmap = this.imageData;
|
|
100
|
+
|
|
101
|
+
// Scale to fit screen (fullscreen from top-left)
|
|
102
|
+
this.plasmaImage.x = 0;
|
|
103
|
+
this.plasmaImage.y = 0;
|
|
104
|
+
this.plasmaImage.width = this.width;
|
|
105
|
+
this.plasmaImage.height = this.height;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
applyCustomScheme(scheme, hueShift) {
|
|
109
|
+
const data = this.imageData.data;
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < this.plasmaData.length; i++) {
|
|
112
|
+
const v = this.plasmaData[i];
|
|
113
|
+
const idx = i * 4;
|
|
114
|
+
|
|
115
|
+
if (scheme === 'psychedelic') {
|
|
116
|
+
// Multi-frequency color cycling for maximum trippy effect
|
|
117
|
+
const h1 = (v * 2 + hueShift) % 360;
|
|
118
|
+
const h2 = (v * 3 + hueShift * 1.5) % 360;
|
|
119
|
+
const h3 = (v * 5 + hueShift * 0.7) % 360;
|
|
120
|
+
|
|
121
|
+
// Blend multiple hues
|
|
122
|
+
const [r1, g1, b1] = this.hslToRgb(h1, 1, 0.5);
|
|
123
|
+
const [r2, g2, b2] = this.hslToRgb(h2, 0.8, 0.6);
|
|
124
|
+
const [r3, g3, b3] = this.hslToRgb(h3, 0.9, 0.4);
|
|
125
|
+
|
|
126
|
+
// Mix based on value bands
|
|
127
|
+
const blend = Math.sin(v * 0.05 + hueShift * 0.01) * 0.5 + 0.5;
|
|
128
|
+
data[idx] = Math.floor(r1 * blend + r2 * (1 - blend));
|
|
129
|
+
data[idx + 1] = Math.floor(g1 * (1 - blend) + g3 * blend);
|
|
130
|
+
data[idx + 2] = Math.floor(b2 * blend + b3 * (1 - blend));
|
|
131
|
+
} else if (scheme === 'neon') {
|
|
132
|
+
// Bright neon colors on dark background
|
|
133
|
+
const phase = (v + hueShift) % 256;
|
|
134
|
+
const intensity = Math.sin(v * 0.1) * 0.5 + 0.5;
|
|
135
|
+
|
|
136
|
+
// Neon pink, cyan, green cycling
|
|
137
|
+
if (phase < 85) {
|
|
138
|
+
const t = phase / 85;
|
|
139
|
+
data[idx] = Math.floor(255 * intensity); // Pink/Magenta
|
|
140
|
+
data[idx + 1] = Math.floor(50 * t * intensity);
|
|
141
|
+
data[idx + 2] = Math.floor(255 * (1 - t * 0.5) * intensity);
|
|
142
|
+
} else if (phase < 170) {
|
|
143
|
+
const t = (phase - 85) / 85;
|
|
144
|
+
data[idx] = Math.floor(50 * (1 - t) * intensity);
|
|
145
|
+
data[idx + 1] = Math.floor(255 * intensity); // Cyan
|
|
146
|
+
data[idx + 2] = Math.floor(255 * intensity);
|
|
147
|
+
} else {
|
|
148
|
+
const t = (phase - 170) / 86;
|
|
149
|
+
data[idx] = Math.floor(50 * t * intensity);
|
|
150
|
+
data[idx + 1] = Math.floor(255 * (1 - t * 0.5) * intensity); // Green
|
|
151
|
+
data[idx + 2] = Math.floor(50 * intensity);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
data[idx + 3] = 255;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
generatePlasma() {
|
|
159
|
+
const w = this.plasmaWidth;
|
|
160
|
+
const h = this.plasmaHeight;
|
|
161
|
+
const t = this.time;
|
|
162
|
+
|
|
163
|
+
// Multiple overlapping sine waves create plasma effect
|
|
164
|
+
for (let y = 0; y < h; y++) {
|
|
165
|
+
for (let x = 0; x < w; x++) {
|
|
166
|
+
// Normalized coordinates
|
|
167
|
+
const nx = x / w;
|
|
168
|
+
const ny = y / h;
|
|
169
|
+
|
|
170
|
+
// Classic plasma formula with multiple sine waves
|
|
171
|
+
let value = 0;
|
|
172
|
+
|
|
173
|
+
// Wave 1: horizontal
|
|
174
|
+
value += Math.sin(nx * 10 + t * 2);
|
|
175
|
+
|
|
176
|
+
// Wave 2: vertical
|
|
177
|
+
value += Math.sin(ny * 10 + t * 1.5);
|
|
178
|
+
|
|
179
|
+
// Wave 3: diagonal
|
|
180
|
+
value += Math.sin((nx + ny) * 10 + t);
|
|
181
|
+
|
|
182
|
+
// Wave 4: radial from center
|
|
183
|
+
const cx = nx - 0.5;
|
|
184
|
+
const cy = ny - 0.5;
|
|
185
|
+
const dist = Math.sqrt(cx * cx + cy * cy);
|
|
186
|
+
value += Math.sin(dist * 20 - t * 3);
|
|
187
|
+
|
|
188
|
+
// Wave 5: spiral
|
|
189
|
+
const angle = Math.atan2(cy, cx);
|
|
190
|
+
value += Math.sin(angle * 5 + dist * 15 - t * 2);
|
|
191
|
+
|
|
192
|
+
// Wave 6: interference pattern
|
|
193
|
+
value += Math.sin(nx * 15 * Math.sin(t * 0.5) + ny * 15 * Math.cos(t * 0.5));
|
|
194
|
+
|
|
195
|
+
// Normalize to 0-255 range
|
|
196
|
+
// value is roughly -6 to +6, normalize to 0-255
|
|
197
|
+
const normalized = Math.floor(((value + 6) / 12) * 255);
|
|
198
|
+
|
|
199
|
+
this.plasmaData[y * w + x] = Math.max(0, Math.min(255, normalized));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
window.addEventListener('load', () => {
|
|
206
|
+
const canvas = document.getElementById('game');
|
|
207
|
+
const demo = new PlasmaDemo(canvas);
|
|
208
|
+
demo.start();
|
|
209
|
+
});
|
package/dist/js/group.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Game,
|
|
3
|
+
GameObject,
|
|
4
|
+
Group,
|
|
5
|
+
Rectangle,
|
|
6
|
+
Circle,
|
|
7
|
+
TextShape,
|
|
8
|
+
FPSCounter,
|
|
9
|
+
} from "/gcanvas.es.min.js";
|
|
10
|
+
|
|
11
|
+
export class MyGame extends Game {
|
|
12
|
+
constructor(canvas) {
|
|
13
|
+
super(canvas);
|
|
14
|
+
this.enableFluidSize();
|
|
15
|
+
this.backgroundColor = "black";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
update(dt) {
|
|
19
|
+
super.update(dt);
|
|
20
|
+
// Use transform API for positioning
|
|
21
|
+
this.groupDemo.transform.position(this.width / 2, this.height / 2);
|
|
22
|
+
this.pipeline.update(dt);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
init() {
|
|
26
|
+
super.init();
|
|
27
|
+
this.groupDemo = new GroupDemo(this);
|
|
28
|
+
this.pipeline.add(this.groupDemo);
|
|
29
|
+
this.pipeline.add(
|
|
30
|
+
new FPSCounter(this, {
|
|
31
|
+
anchor: "bottom-right",
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* GroupDemo
|
|
39
|
+
*
|
|
40
|
+
* Demonstrates the Transform API with groups:
|
|
41
|
+
* - Group-wide rotation & scaling using transform.rotation() and transform.scale()
|
|
42
|
+
* - Child positioning using transform.position()
|
|
43
|
+
* - Batch operations using forEachTransform()
|
|
44
|
+
* - Circles arranged radially, color-shifting via HSL
|
|
45
|
+
*/
|
|
46
|
+
export class GroupDemo extends GameObject {
|
|
47
|
+
constructor(game) {
|
|
48
|
+
super(game);
|
|
49
|
+
// Create a Group and set dimensions using Transform API
|
|
50
|
+
this.group = new Group({ debug: true });
|
|
51
|
+
this.group.transform.size(450, 450);
|
|
52
|
+
|
|
53
|
+
// 1) A central rectangle
|
|
54
|
+
const centerRect = new Rectangle({
|
|
55
|
+
width: 145,
|
|
56
|
+
height: 60,
|
|
57
|
+
color: "#222",
|
|
58
|
+
debug: true,
|
|
59
|
+
});
|
|
60
|
+
this.group.add(centerRect);
|
|
61
|
+
|
|
62
|
+
// 2) Some text in the middle
|
|
63
|
+
const centerText = new TextShape("Transform API Demo!", {
|
|
64
|
+
font: "18px sans-serif",
|
|
65
|
+
color: "#FFF",
|
|
66
|
+
align: "center",
|
|
67
|
+
baseline: "middle",
|
|
68
|
+
opacity: 0.5,
|
|
69
|
+
});
|
|
70
|
+
this.group.add(centerText);
|
|
71
|
+
|
|
72
|
+
// 3) Create a radial pattern of circles around the origin
|
|
73
|
+
this.circles = [];
|
|
74
|
+
this.circleCount = 20;
|
|
75
|
+
const circleRadius = 200;
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < this.circleCount; i++) {
|
|
78
|
+
const angle = (Math.PI * 2 * i) / this.circleCount;
|
|
79
|
+
const x = Math.cos(angle) * circleRadius;
|
|
80
|
+
const y = Math.sin(angle) * circleRadius;
|
|
81
|
+
|
|
82
|
+
const circle = new Circle(20, {
|
|
83
|
+
color: "#ff0",
|
|
84
|
+
stroke: "#FFF",
|
|
85
|
+
lineWidth: 2,
|
|
86
|
+
visible: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Use Transform API to position the circle
|
|
90
|
+
circle.transform.position(x, y);
|
|
91
|
+
|
|
92
|
+
this.circles.push(circle);
|
|
93
|
+
this.group.add(circle);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Initialize group transforms using the Transform API
|
|
97
|
+
this.group.transform
|
|
98
|
+
.rotation(0)
|
|
99
|
+
.scale(1);
|
|
100
|
+
|
|
101
|
+
this.elapsed = 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* update(dt) - Animate using the Transform API:
|
|
106
|
+
* 1) Group rotation & scale using transform.rotation() and transform.scale()
|
|
107
|
+
* 2) Each circle's color via forEachTransform pattern
|
|
108
|
+
* 3) Circle visibility cycling
|
|
109
|
+
* @param {number} dt - Delta time in seconds.
|
|
110
|
+
*/
|
|
111
|
+
update(dt) {
|
|
112
|
+
super.update(dt);
|
|
113
|
+
this.elapsed += dt;
|
|
114
|
+
|
|
115
|
+
// 1) Animate group using Transform API (fluent chaining)
|
|
116
|
+
const pulse = 0.5 * Math.sin(this.elapsed * 2);
|
|
117
|
+
this.group.transform
|
|
118
|
+
.rotation(this.elapsed * 24)
|
|
119
|
+
.scale(1 + pulse);
|
|
120
|
+
|
|
121
|
+
// 2) Animate each circle's color & visibility
|
|
122
|
+
const cycleSpeed = 0.1;
|
|
123
|
+
const flashIndex = Math.floor(this.elapsed / cycleSpeed) % this.circleCount;
|
|
124
|
+
|
|
125
|
+
this.circles.forEach((circle, i) => {
|
|
126
|
+
// Animate color
|
|
127
|
+
const hue = (this.elapsed * 50 + i * 40) % 360;
|
|
128
|
+
circle.color = `hsl(${hue}, 90%, 60%)`;
|
|
129
|
+
// One circle is invisible; all others remain visible
|
|
130
|
+
circle.visible = i !== flashIndex;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
this.group.update(dt);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
draw() {
|
|
137
|
+
this.logger.log("GroupDemo: render");
|
|
138
|
+
this.group.render();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperbolic 001 - Wireframe Flux
|
|
3
|
+
*
|
|
4
|
+
* Inspired by hyperbolic space - a torus wireframe that breathes and
|
|
5
|
+
* deforms with noise. Click to disturb, watch it settle to equilibrium.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Torus geometry with simplex noise deformation
|
|
9
|
+
* - Click to add energy - more clicks = more chaos
|
|
10
|
+
* - Settles to subtle vibration equilibrium
|
|
11
|
+
* - Drag to rotate, auto-rotates when idle
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { gcanvas, Noise, Painter } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
// Torus geometry
|
|
19
|
+
majorRadius: 350,
|
|
20
|
+
minorRadius: 140,
|
|
21
|
+
rows: 50,
|
|
22
|
+
cols: 30,
|
|
23
|
+
|
|
24
|
+
// Noise settings
|
|
25
|
+
noiseScale: 0.005,
|
|
26
|
+
noiseSpeed: 0.5,
|
|
27
|
+
noiseStrengthBase: 15, // Calm equilibrium - subtle breathing
|
|
28
|
+
noiseStrengthMax: 120, // Max when fully energized
|
|
29
|
+
|
|
30
|
+
// Projection
|
|
31
|
+
fov: 800, // Slightly wider FOV for larger object
|
|
32
|
+
|
|
33
|
+
// Colors
|
|
34
|
+
baseColor: { h: 280, s: 100, l: 60 },
|
|
35
|
+
bgColor: "#000000",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 3D Vector helper
|
|
40
|
+
*/
|
|
41
|
+
class Vec3 {
|
|
42
|
+
constructor(x, y, z) {
|
|
43
|
+
this.x = x;
|
|
44
|
+
this.y = y;
|
|
45
|
+
this.z = z;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Main Study Class
|
|
51
|
+
*/
|
|
52
|
+
class WireframeFlux {
|
|
53
|
+
constructor(game) {
|
|
54
|
+
this.game = game;
|
|
55
|
+
this.width = game.width;
|
|
56
|
+
this.height = game.height;
|
|
57
|
+
|
|
58
|
+
// Mesh data
|
|
59
|
+
this.vertices = [];
|
|
60
|
+
this.projected = [];
|
|
61
|
+
this.indices = [];
|
|
62
|
+
|
|
63
|
+
// Animation state
|
|
64
|
+
this.time = 0;
|
|
65
|
+
this.rotation = { x: 0, y: 0 };
|
|
66
|
+
this.targetRotation = { x: 0.5, y: 0.5 };
|
|
67
|
+
this.autoRotate = true;
|
|
68
|
+
|
|
69
|
+
// Interaction state - energy decays toward equilibrium
|
|
70
|
+
this.energy = 0;
|
|
71
|
+
this.noiseStrength = CONFIG.noiseStrengthBase;
|
|
72
|
+
|
|
73
|
+
this.initMesh();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
initMesh() {
|
|
77
|
+
this.vertices = [];
|
|
78
|
+
this.indices = [];
|
|
79
|
+
|
|
80
|
+
// Generate Torus vertices
|
|
81
|
+
for (let i = 0; i < CONFIG.rows; i++) {
|
|
82
|
+
const u = (i / CONFIG.rows) * Math.PI * 2;
|
|
83
|
+
|
|
84
|
+
for (let j = 0; j < CONFIG.cols; j++) {
|
|
85
|
+
const v = (j / CONFIG.cols) * Math.PI * 2;
|
|
86
|
+
|
|
87
|
+
// Torus parametric equation
|
|
88
|
+
// x = (R + r * cos(v)) * cos(u)
|
|
89
|
+
// y = (R + r * cos(v)) * sin(u)
|
|
90
|
+
// z = r * sin(v)
|
|
91
|
+
|
|
92
|
+
const r = CONFIG.minorRadius;
|
|
93
|
+
const R = CONFIG.majorRadius;
|
|
94
|
+
|
|
95
|
+
const x = (R + r * Math.cos(v)) * Math.cos(u);
|
|
96
|
+
const y = (R + r * Math.cos(v)) * Math.sin(u);
|
|
97
|
+
const z = r * Math.sin(v);
|
|
98
|
+
|
|
99
|
+
this.vertices.push(new Vec3(x, y, z));
|
|
100
|
+
|
|
101
|
+
// Generate indices for wireframe (quads)
|
|
102
|
+
const nextI = (i + 1) % CONFIG.rows;
|
|
103
|
+
const nextJ = (j + 1) % CONFIG.cols;
|
|
104
|
+
|
|
105
|
+
const current = i * CONFIG.cols + j;
|
|
106
|
+
const right = nextI * CONFIG.cols + j;
|
|
107
|
+
const down = i * CONFIG.cols + nextJ;
|
|
108
|
+
|
|
109
|
+
// Horizontal line
|
|
110
|
+
this.indices.push([current, right]);
|
|
111
|
+
// Vertical line
|
|
112
|
+
this.indices.push([current, down]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
update(dt) {
|
|
118
|
+
this.time += dt;
|
|
119
|
+
|
|
120
|
+
// Update dimensions in case of resize
|
|
121
|
+
this.width = this.game.width;
|
|
122
|
+
this.height = this.game.height;
|
|
123
|
+
|
|
124
|
+
// Responsive scaling: Adjust radius based on screen size
|
|
125
|
+
const minDim = Math.min(this.width, this.height);
|
|
126
|
+
const scaleFactor = minDim / 1000; // Base scale on ~1000px screen
|
|
127
|
+
|
|
128
|
+
// Smoothly interpolate the actual drawing scale
|
|
129
|
+
// (We'll use a local property for rendering scale to avoid rebuilding mesh)
|
|
130
|
+
this.renderScale = scaleFactor;
|
|
131
|
+
|
|
132
|
+
// Smooth rotation
|
|
133
|
+
this.rotation.x += (this.targetRotation.x - this.rotation.x) * 0.1;
|
|
134
|
+
this.rotation.y += (this.targetRotation.y - this.rotation.y) * 0.1;
|
|
135
|
+
|
|
136
|
+
// Auto rotate
|
|
137
|
+
if (this.autoRotate) {
|
|
138
|
+
this.targetRotation.y += dt * 0.2;
|
|
139
|
+
this.targetRotation.x += dt * 0.1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Decay energy smoothly toward 0
|
|
143
|
+
this.energy *= 0.95;
|
|
144
|
+
if (this.energy < 0.001) this.energy = 0;
|
|
145
|
+
|
|
146
|
+
// Interpolate noise strength: base (calm) + energy * (max - base)
|
|
147
|
+
const targetStrength = CONFIG.noiseStrengthBase + this.energy * (CONFIG.noiseStrengthMax - CONFIG.noiseStrengthBase);
|
|
148
|
+
this.noiseStrength += (targetStrength - this.noiseStrength) * 0.1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
render(ctx, width, height) {
|
|
152
|
+
const cx = width / 2;
|
|
153
|
+
const cy = height / 2;
|
|
154
|
+
|
|
155
|
+
// Clear
|
|
156
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
157
|
+
ctx.fillRect(0, 0, width, height);
|
|
158
|
+
|
|
159
|
+
// Pre-calculate rotation matrices
|
|
160
|
+
const cosX = Math.cos(this.rotation.x);
|
|
161
|
+
const sinX = Math.sin(this.rotation.x);
|
|
162
|
+
const cosY = Math.cos(this.rotation.y);
|
|
163
|
+
const sinY = Math.sin(this.rotation.y);
|
|
164
|
+
|
|
165
|
+
// Transform and project vertices
|
|
166
|
+
const projectedVertices = new Array(this.vertices.length);
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
169
|
+
const v = this.vertices[i];
|
|
170
|
+
|
|
171
|
+
// 1. Apply Noise Displacement
|
|
172
|
+
// We calculate noise based on the original position + time
|
|
173
|
+
const nX = v.x * CONFIG.noiseScale;
|
|
174
|
+
const nY = v.y * CONFIG.noiseScale;
|
|
175
|
+
const nZ = v.z * CONFIG.noiseScale;
|
|
176
|
+
const t = this.time * CONFIG.noiseSpeed;
|
|
177
|
+
|
|
178
|
+
const noiseVal = Noise.simplex3(nX + t, nY + t, nZ);
|
|
179
|
+
const displacement = noiseVal * this.noiseStrength;
|
|
180
|
+
|
|
181
|
+
// Displace along the normal (approximated by position for torus)
|
|
182
|
+
// Or just simple directional displacement for "glitch" look
|
|
183
|
+
let dx = v.x + displacement * (v.x / CONFIG.majorRadius);
|
|
184
|
+
let dy = v.y + displacement * (v.y / CONFIG.majorRadius);
|
|
185
|
+
let dz = v.z + displacement * (v.z / CONFIG.minorRadius);
|
|
186
|
+
|
|
187
|
+
// 2. Rotate
|
|
188
|
+
// Rotate around Y
|
|
189
|
+
let x1 = dx * cosY - dz * sinY;
|
|
190
|
+
let z1 = dz * cosY + dx * sinY;
|
|
191
|
+
let y1 = dy;
|
|
192
|
+
|
|
193
|
+
// Rotate around X
|
|
194
|
+
let y2 = y1 * cosX - z1 * sinX;
|
|
195
|
+
let z2 = z1 * cosX + y1 * sinX;
|
|
196
|
+
let x2 = x1;
|
|
197
|
+
|
|
198
|
+
// 3. Project
|
|
199
|
+
// Apply responsive scale factor to coordinates before projection
|
|
200
|
+
const sx = x2 * (this.renderScale || 1);
|
|
201
|
+
const sy = y2 * (this.renderScale || 1);
|
|
202
|
+
const sz = z2 * (this.renderScale || 1);
|
|
203
|
+
|
|
204
|
+
const scale = CONFIG.fov / (CONFIG.fov + sz + 400);
|
|
205
|
+
const px = sx * scale + cx;
|
|
206
|
+
const py = sy * scale + cy;
|
|
207
|
+
|
|
208
|
+
projectedVertices[i] = { x: px, y: py, z: sz, scale: scale };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Draw edges
|
|
212
|
+
ctx.lineWidth = 2 * (this.renderScale || 1); // Scale line width too
|
|
213
|
+
|
|
214
|
+
// We can batch stroke calls by color for performance
|
|
215
|
+
// Simple depth sorting approach: draw lines based on average Z of endpoints
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i < this.indices.length; i++) {
|
|
218
|
+
const [idx1, idx2] = this.indices[i];
|
|
219
|
+
const p1 = projectedVertices[idx1];
|
|
220
|
+
const p2 = projectedVertices[idx2];
|
|
221
|
+
|
|
222
|
+
// Simple culling
|
|
223
|
+
if (p1.z < -CONFIG.fov + 10 || p2.z < -CONFIG.fov + 10) continue;
|
|
224
|
+
|
|
225
|
+
// Depth color
|
|
226
|
+
const avgZ = (p1.z + p2.z) / 2;
|
|
227
|
+
// Map Z (-200 to 200) to hue/lightness
|
|
228
|
+
// Close = Bright, Far = Dim
|
|
229
|
+
const depthAlpha = Math.max(0.1, Math.min(1, (300 - avgZ) / 500));
|
|
230
|
+
|
|
231
|
+
// Color shift based on position
|
|
232
|
+
const hue = (CONFIG.baseColor.h + avgZ * 0.2 + this.time * 10) % 360;
|
|
233
|
+
|
|
234
|
+
ctx.strokeStyle = `hsla(${hue}, ${CONFIG.baseColor.s}%, ${CONFIG.baseColor.l}%, ${depthAlpha})`;
|
|
235
|
+
|
|
236
|
+
ctx.beginPath();
|
|
237
|
+
ctx.moveTo(p1.x, p1.y);
|
|
238
|
+
ctx.lineTo(p2.x, p2.y);
|
|
239
|
+
ctx.stroke();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Initialize
|
|
245
|
+
window.addEventListener("load", () => {
|
|
246
|
+
const canvas = document.getElementById("game");
|
|
247
|
+
if (!canvas) return;
|
|
248
|
+
|
|
249
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
250
|
+
const gameInstance = game.game;
|
|
251
|
+
|
|
252
|
+
const flux = new WireframeFlux(gameInstance);
|
|
253
|
+
|
|
254
|
+
// Custom render loop
|
|
255
|
+
gameInstance.clear = function() {
|
|
256
|
+
flux.render(this.ctx, this.width, this.height);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
game.on("update", (dt) => {
|
|
260
|
+
flux.update(dt);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Interaction
|
|
264
|
+
let isDragging = false;
|
|
265
|
+
let lastX = 0;
|
|
266
|
+
let lastY = 0;
|
|
267
|
+
|
|
268
|
+
gameInstance.events.on("mousedown", (e) => {
|
|
269
|
+
isDragging = true;
|
|
270
|
+
lastX = e.x;
|
|
271
|
+
lastY = e.y;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
gameInstance.events.on("mouseup", () => {
|
|
275
|
+
isDragging = false;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
gameInstance.events.on("mousemove", (e) => {
|
|
279
|
+
if (isDragging) {
|
|
280
|
+
const dx = (e.x - lastX) * 0.01;
|
|
281
|
+
const dy = (e.y - lastY) * 0.01;
|
|
282
|
+
|
|
283
|
+
flux.targetRotation.y += dx;
|
|
284
|
+
flux.targetRotation.x += dy;
|
|
285
|
+
|
|
286
|
+
// Stop auto-rotation while dragging
|
|
287
|
+
flux.autoRotate = false;
|
|
288
|
+
|
|
289
|
+
lastX = e.x;
|
|
290
|
+
lastY = e.y;
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
gameInstance.events.on("mouseup", () => {
|
|
295
|
+
isDragging = false;
|
|
296
|
+
// Resume auto-rotation after a short delay or immediately?
|
|
297
|
+
// Let's resume it with current momentum perhaps, or just standard auto
|
|
298
|
+
setTimeout(() => { flux.autoRotate = true; }, 500);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Click for glitch intensity
|
|
302
|
+
gameInstance.events.on("click", () => {
|
|
303
|
+
// Add energy on click
|
|
304
|
+
flux.energy = Math.min(flux.energy + 0.4, 1.0);
|
|
305
|
+
CONFIG.baseColor.h = Math.random() * 360;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
game.start();
|
|
309
|
+
});
|
|
310
|
+
|