@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,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 008 - 3D Logo Extrusion
|
|
3
|
+
*
|
|
4
|
+
* Parses the GCanvas SVG logo path, tesselates it, and extrudes it into 3D.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - SVG Path parsing and triangulation
|
|
8
|
+
* - 3D Extrusion (front/back faces + sides)
|
|
9
|
+
* - Wireframe rendering
|
|
10
|
+
* - Mouse rotation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { gcanvas } from "/gcanvas.es.min.js";
|
|
14
|
+
|
|
15
|
+
// Hardcoded path data from logo.svg
|
|
16
|
+
const LOGO_PATH_1 = "M 57.971 224.292 L 57.971 203.374 L 57.971 194.861 L 75.109 194.861 L 75.109 188.769 L 63.16 188.769 L 63.16 174.743 L 57.971 174.743 L 57.971 189.041 L 57.971 194.861 L 32.9 194.861 L 32.9 203.773 L 50.377 203.773 L 50.377 224.292 L 57.971 224.292 Z";
|
|
17
|
+
const LOGO_PATH_2 = "M 79.717 238.319 L 79.717 224.02 L 79.717 218.2 L 104.788 218.2 L 104.788 209.287 L 87.31 209.287 L 87.31 188.769 L 79.717 188.769 L 79.717 209.686 L 79.717 218.2 L 62.579 218.2 L 62.579 224.293 L 74.526 224.293 L 74.526 238.319 L 79.717 238.319 Z";
|
|
18
|
+
|
|
19
|
+
// Configuration
|
|
20
|
+
const CONFIG = {
|
|
21
|
+
extrusionDepth: 25,
|
|
22
|
+
scale: 12,
|
|
23
|
+
fov: 600,
|
|
24
|
+
// Change baseColor to HSL object for easier lighting calcs
|
|
25
|
+
baseColor: { h: 120, s: 100, l: 50 },
|
|
26
|
+
bgColor: "#000000",
|
|
27
|
+
faceOpacity: 0.8, // More solid
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Minimal Vector class
|
|
32
|
+
*/
|
|
33
|
+
class Vec3 {
|
|
34
|
+
constructor(x, y, z) {
|
|
35
|
+
this.x = x;
|
|
36
|
+
this.y = y;
|
|
37
|
+
this.z = z;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add some helper methods
|
|
41
|
+
sub(v) { return new Vec3(this.x - v.x, this.y - v.y, this.z - v.z); }
|
|
42
|
+
cross(v) { return new Vec3(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x); }
|
|
43
|
+
normalize() {
|
|
44
|
+
const len = Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
|
|
45
|
+
return new Vec3(this.x/len, this.y/len, this.z/len);
|
|
46
|
+
}
|
|
47
|
+
dot(v) { return this.x * v.x + this.y * v.y + this.z * v.z; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 3D Logo Object
|
|
52
|
+
*/
|
|
53
|
+
class Logo3D {
|
|
54
|
+
constructor(game) {
|
|
55
|
+
this.game = game;
|
|
56
|
+
this.rotation = { x: 0, y: 0 };
|
|
57
|
+
this.targetRotation = { x: 0.2, y: 0.2 };
|
|
58
|
+
this.autoRotate = true;
|
|
59
|
+
|
|
60
|
+
// Interaction state
|
|
61
|
+
this.energy = 0;
|
|
62
|
+
this.baseScale = CONFIG.scale;
|
|
63
|
+
|
|
64
|
+
this.vertices = [];
|
|
65
|
+
this.edges = []; // array of [index1, index2]
|
|
66
|
+
this.faces = []; // array of { indices: number[], color: string, z: number }
|
|
67
|
+
|
|
68
|
+
this.buildMesh();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Simple parser for "M x y L x y ... Z" style paths (polygons)
|
|
73
|
+
*/
|
|
74
|
+
parsePath(pathStr) {
|
|
75
|
+
const commands = pathStr.split(" ");
|
|
76
|
+
const points = [];
|
|
77
|
+
let i = 0;
|
|
78
|
+
while(i < commands.length) {
|
|
79
|
+
const cmd = commands[i];
|
|
80
|
+
if (cmd === "M" || cmd === "L") {
|
|
81
|
+
const x = parseFloat(commands[i+1]);
|
|
82
|
+
const y = parseFloat(commands[i+2]);
|
|
83
|
+
points.push({x, y});
|
|
84
|
+
i += 3;
|
|
85
|
+
} else if (cmd === "Z") {
|
|
86
|
+
i += 1;
|
|
87
|
+
} else {
|
|
88
|
+
// Just skip unknown
|
|
89
|
+
i += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return points;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
buildMesh() {
|
|
96
|
+
this.vertices = [];
|
|
97
|
+
this.edges = [];
|
|
98
|
+
this.faces = [];
|
|
99
|
+
|
|
100
|
+
// Center the geometry
|
|
101
|
+
// Calculate bounds roughly
|
|
102
|
+
const allPoints = [...this.parsePath(LOGO_PATH_1), ...this.parsePath(LOGO_PATH_2)];
|
|
103
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
104
|
+
|
|
105
|
+
for(const p of allPoints) {
|
|
106
|
+
minX = Math.min(minX, p.x);
|
|
107
|
+
maxX = Math.max(maxX, p.x);
|
|
108
|
+
minY = Math.min(minY, p.y);
|
|
109
|
+
maxY = Math.max(maxY, p.y);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const centerX = (minX + maxX) / 2;
|
|
113
|
+
const centerY = (minY + maxY) / 2;
|
|
114
|
+
|
|
115
|
+
const shapes = [LOGO_PATH_1, LOGO_PATH_2];
|
|
116
|
+
|
|
117
|
+
// Precompute normals in object space
|
|
118
|
+
// Front face normal: [0, 0, -1] (facing camera initially)
|
|
119
|
+
// Back face normal: [0, 0, 1]
|
|
120
|
+
|
|
121
|
+
shapes.forEach((path, shapeIdx) => {
|
|
122
|
+
const points = this.parsePath(path);
|
|
123
|
+
const startIndex = this.vertices.length;
|
|
124
|
+
|
|
125
|
+
const frontIndices = [];
|
|
126
|
+
const backIndices = [];
|
|
127
|
+
|
|
128
|
+
// Add Front Face vertices
|
|
129
|
+
points.forEach((p, i) => {
|
|
130
|
+
this.vertices.push(new Vec3(
|
|
131
|
+
(p.x - centerX) * CONFIG.scale,
|
|
132
|
+
(p.y - centerY) * CONFIG.scale,
|
|
133
|
+
-CONFIG.extrusionDepth * CONFIG.scale
|
|
134
|
+
));
|
|
135
|
+
frontIndices.push(startIndex + i);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const frontCount = points.length;
|
|
139
|
+
const backStartIndex = this.vertices.length;
|
|
140
|
+
|
|
141
|
+
// Add Back Face vertices
|
|
142
|
+
points.forEach((p, i) => {
|
|
143
|
+
this.vertices.push(new Vec3(
|
|
144
|
+
(p.x - centerX) * CONFIG.scale,
|
|
145
|
+
(p.y - centerY) * CONFIG.scale,
|
|
146
|
+
CONFIG.extrusionDepth * CONFIG.scale
|
|
147
|
+
));
|
|
148
|
+
backIndices.push(backStartIndex + i);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Create Edges and Side Faces
|
|
152
|
+
for (let i = 0; i < frontCount; i++) {
|
|
153
|
+
const next = (i + 1) % frontCount;
|
|
154
|
+
|
|
155
|
+
// Front face edges
|
|
156
|
+
this.edges.push([startIndex + i, startIndex + next]);
|
|
157
|
+
|
|
158
|
+
// Back face edges
|
|
159
|
+
this.edges.push([backStartIndex + i, backStartIndex + next]);
|
|
160
|
+
|
|
161
|
+
// Connecting edges (sides)
|
|
162
|
+
this.edges.push([startIndex + i, backStartIndex + i]);
|
|
163
|
+
|
|
164
|
+
// Side Faces (Quads)
|
|
165
|
+
// Vertices: Front[i], Front[next], Back[next], Back[i]
|
|
166
|
+
// Calculate normal for this side
|
|
167
|
+
// v1 = Front[i], v2 = Front[next], v3 = Back[next]
|
|
168
|
+
// U = v2 - v1, V = v3 - v1
|
|
169
|
+
const idx1 = startIndex + i;
|
|
170
|
+
const idx2 = startIndex + next;
|
|
171
|
+
const idx3 = backStartIndex + next;
|
|
172
|
+
const idx4 = backStartIndex + i;
|
|
173
|
+
|
|
174
|
+
// We defer normal calculation to render time or store just indices?
|
|
175
|
+
// Storing computed normal now is better as vertices are static in object space
|
|
176
|
+
const v1 = this.vertices[idx1];
|
|
177
|
+
const v2 = this.vertices[idx2];
|
|
178
|
+
const v3 = this.vertices[idx3];
|
|
179
|
+
|
|
180
|
+
const U = v2.sub(v1);
|
|
181
|
+
const V = v3.sub(v1);
|
|
182
|
+
const normal = U.cross(V).normalize();
|
|
183
|
+
|
|
184
|
+
this.faces.push({
|
|
185
|
+
indices: [idx1, idx2, idx3, idx4],
|
|
186
|
+
type: 'side',
|
|
187
|
+
normal: normal
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Store Face definitions
|
|
192
|
+
this.faces.push({
|
|
193
|
+
indices: frontIndices,
|
|
194
|
+
type: 'front',
|
|
195
|
+
normal: new Vec3(0, 0, -1)
|
|
196
|
+
});
|
|
197
|
+
this.faces.push({
|
|
198
|
+
indices: backIndices,
|
|
199
|
+
type: 'back',
|
|
200
|
+
normal: new Vec3(0, 0, 1)
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
update(dt) {
|
|
206
|
+
this.rotation.x += (this.targetRotation.x - this.rotation.x) * 0.1;
|
|
207
|
+
this.rotation.y += (this.targetRotation.y - this.rotation.y) * 0.1;
|
|
208
|
+
|
|
209
|
+
if (this.autoRotate) {
|
|
210
|
+
this.targetRotation.y += dt * 0.5;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Decay energy
|
|
214
|
+
this.energy *= 0.9;
|
|
215
|
+
|
|
216
|
+
// Pulse scale based on energy
|
|
217
|
+
CONFIG.scale = this.baseScale + this.energy * 2;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
render(ctx, width, height) {
|
|
221
|
+
const cx = width / 2;
|
|
222
|
+
const cy = height / 2;
|
|
223
|
+
|
|
224
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
225
|
+
ctx.fillRect(0, 0, width, height);
|
|
226
|
+
|
|
227
|
+
const cosX = Math.cos(this.rotation.x);
|
|
228
|
+
const sinX = Math.sin(this.rotation.x);
|
|
229
|
+
const cosY = Math.cos(this.rotation.y);
|
|
230
|
+
const sinY = Math.sin(this.rotation.y);
|
|
231
|
+
|
|
232
|
+
const projected = [];
|
|
233
|
+
|
|
234
|
+
// Transform Vertices
|
|
235
|
+
for(let i=0; i<this.vertices.length; i++) {
|
|
236
|
+
const v = this.vertices[i];
|
|
237
|
+
|
|
238
|
+
// Rotate Y
|
|
239
|
+
let x1 = v.x * cosY - v.z * sinY;
|
|
240
|
+
let z1 = v.z * cosY + v.x * sinY;
|
|
241
|
+
let y1 = v.y;
|
|
242
|
+
|
|
243
|
+
// Rotate X
|
|
244
|
+
let y2 = y1 * cosX - z1 * sinX;
|
|
245
|
+
let z2 = z1 * cosX + y1 * sinX;
|
|
246
|
+
let x2 = x1;
|
|
247
|
+
|
|
248
|
+
// Project
|
|
249
|
+
const scale = CONFIG.fov / (CONFIG.fov + z2 + 200);
|
|
250
|
+
const px = x2 * scale + cx;
|
|
251
|
+
const py = y2 * scale + cy;
|
|
252
|
+
|
|
253
|
+
projected.push({x: px, y: py, z: z2, scale});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Calculate Face Depths for Sorting
|
|
257
|
+
const renderFaces = this.faces.map(face => {
|
|
258
|
+
let avgZ = 0;
|
|
259
|
+
face.indices.forEach(idx => {
|
|
260
|
+
avgZ += projected[idx].z;
|
|
261
|
+
});
|
|
262
|
+
avgZ /= face.indices.length;
|
|
263
|
+
return { ...face, avgZ };
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Sort faces: furthest first (Painter's algorithm)
|
|
267
|
+
renderFaces.sort((a, b) => b.avgZ - a.avgZ);
|
|
268
|
+
|
|
269
|
+
// Light source vector (normalized)
|
|
270
|
+
// Light coming from top-left-front
|
|
271
|
+
const lightDir = new Vec3(-0.5, -0.5, -1).normalize();
|
|
272
|
+
|
|
273
|
+
// Render Faces (Fill)
|
|
274
|
+
renderFaces.forEach(face => {
|
|
275
|
+
// Rotate normal
|
|
276
|
+
const n = face.normal;
|
|
277
|
+
// Rotate Y
|
|
278
|
+
let nx1 = n.x * cosY - n.z * sinY;
|
|
279
|
+
let nz1 = n.z * cosY + n.x * sinY;
|
|
280
|
+
let ny1 = n.y;
|
|
281
|
+
|
|
282
|
+
// Rotate X
|
|
283
|
+
let ny2 = ny1 * cosX - nz1 * sinX;
|
|
284
|
+
let nz2 = nz1 * cosX + ny1 * sinX;
|
|
285
|
+
let nx2 = nx1;
|
|
286
|
+
|
|
287
|
+
const rotatedNormal = new Vec3(nx2, ny2, nz2);
|
|
288
|
+
|
|
289
|
+
// Calculate lighting intensity
|
|
290
|
+
// Dot product gives cosine of angle. 1.0 = facing light, -1.0 = away
|
|
291
|
+
// We clamp to [0, 1] or use absolute? Usually clamp for directional.
|
|
292
|
+
// But let's add some ambient.
|
|
293
|
+
const dot = rotatedNormal.dot(lightDir);
|
|
294
|
+
|
|
295
|
+
// Simple lighting model: Ambient + Diffuse
|
|
296
|
+
// Ambient 0.3, Diffuse 0.7
|
|
297
|
+
// Map dot [-1, 1] to [0, 1] roughly for visibility
|
|
298
|
+
// Actually standard is max(0, dot)
|
|
299
|
+
const intensity = Math.max(0, dot);
|
|
300
|
+
|
|
301
|
+
// Vary lightness based on intensity
|
|
302
|
+
// Base L is 50. Range 20-80
|
|
303
|
+
const l = 20 + intensity * 60;
|
|
304
|
+
|
|
305
|
+
ctx.fillStyle = `hsla(${CONFIG.baseColor.h}, ${CONFIG.baseColor.s}%, ${l}%, ${CONFIG.faceOpacity})`;
|
|
306
|
+
|
|
307
|
+
ctx.beginPath();
|
|
308
|
+
const first = projected[face.indices[0]];
|
|
309
|
+
ctx.moveTo(first.x, first.y);
|
|
310
|
+
for(let i=1; i<face.indices.length; i++) {
|
|
311
|
+
const p = projected[face.indices[i]];
|
|
312
|
+
ctx.lineTo(p.x, p.y);
|
|
313
|
+
}
|
|
314
|
+
ctx.closePath();
|
|
315
|
+
ctx.fill();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
ctx.globalAlpha = 1.0;
|
|
319
|
+
|
|
320
|
+
// Draw Edges with Depth Fading
|
|
321
|
+
ctx.lineWidth = 2;
|
|
322
|
+
ctx.lineJoin = "round";
|
|
323
|
+
|
|
324
|
+
for(const edge of this.edges) {
|
|
325
|
+
const p1 = projected[edge[0]];
|
|
326
|
+
const p2 = projected[edge[1]];
|
|
327
|
+
|
|
328
|
+
// Culling
|
|
329
|
+
if(p1.z < -CONFIG.fov + 10) continue;
|
|
330
|
+
|
|
331
|
+
// Depth color
|
|
332
|
+
const avgZ = (p1.z + p2.z) / 2;
|
|
333
|
+
// Map Z (-600 to 600) to opacity
|
|
334
|
+
// Close (negative z in our coord system? No, z increases away usually, but here z2 is depth)
|
|
335
|
+
// Standard: z positive is into screen?
|
|
336
|
+
// In this projection: scale = fov / (fov + z). So larger Z = further away.
|
|
337
|
+
|
|
338
|
+
const depthAlpha = Math.max(0.1, Math.min(1, 1 - (avgZ / 800)));
|
|
339
|
+
|
|
340
|
+
ctx.strokeStyle = `rgba(0, 255, 0, ${depthAlpha})`;
|
|
341
|
+
|
|
342
|
+
ctx.beginPath();
|
|
343
|
+
ctx.moveTo(p1.x, p1.y);
|
|
344
|
+
ctx.lineTo(p2.x, p2.y);
|
|
345
|
+
ctx.stroke();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Draw vertices (dots)
|
|
349
|
+
ctx.fillStyle = "#fff";
|
|
350
|
+
for(const p of projected) {
|
|
351
|
+
if(p.z < -CONFIG.fov + 10) continue;
|
|
352
|
+
const size = Math.max(1, 3 * p.scale);
|
|
353
|
+
|
|
354
|
+
// Dim dots too
|
|
355
|
+
const depthAlpha = Math.max(0.1, Math.min(1, 1 - (p.z / 800)));
|
|
356
|
+
ctx.globalAlpha = depthAlpha;
|
|
357
|
+
|
|
358
|
+
ctx.beginPath();
|
|
359
|
+
ctx.arc(p.x, p.y, size, 0, Math.PI*2);
|
|
360
|
+
ctx.fill();
|
|
361
|
+
}
|
|
362
|
+
ctx.globalAlpha = 1.0;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Initialize
|
|
367
|
+
window.addEventListener("load", () => {
|
|
368
|
+
const canvas = document.getElementById("game");
|
|
369
|
+
if (!canvas) return;
|
|
370
|
+
|
|
371
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
372
|
+
const gameInstance = game.game;
|
|
373
|
+
|
|
374
|
+
const logo = new Logo3D(gameInstance);
|
|
375
|
+
|
|
376
|
+
// Render loop
|
|
377
|
+
gameInstance.clear = function() {
|
|
378
|
+
logo.render(this.ctx, this.width, this.height);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
game.on("update", (dt) => {
|
|
382
|
+
logo.update(dt);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Interaction
|
|
386
|
+
let isDragging = false;
|
|
387
|
+
let lastX = 0, lastY = 0;
|
|
388
|
+
|
|
389
|
+
gameInstance.events.on("mousedown", (e) => {
|
|
390
|
+
isDragging = true;
|
|
391
|
+
lastX = e.x;
|
|
392
|
+
lastY = e.y;
|
|
393
|
+
logo.autoRotate = false;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
gameInstance.events.on("mouseup", () => {
|
|
397
|
+
isDragging = false;
|
|
398
|
+
setTimeout(() => { logo.autoRotate = true; }, 1000);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
gameInstance.events.on("mousemove", (e) => {
|
|
402
|
+
if(isDragging) {
|
|
403
|
+
const dx = (e.x - lastX) * 0.01;
|
|
404
|
+
const dy = (e.y - lastY) * 0.01;
|
|
405
|
+
logo.targetRotation.y += dx;
|
|
406
|
+
logo.targetRotation.x += dy;
|
|
407
|
+
lastX = e.x;
|
|
408
|
+
lastY = e.y;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
game.start();
|
|
413
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Circle,
|
|
3
|
+
Easing,
|
|
4
|
+
FPSCounter,
|
|
5
|
+
Game,
|
|
6
|
+
GameObject,
|
|
7
|
+
Motion,
|
|
8
|
+
Painter,
|
|
9
|
+
Scene,
|
|
10
|
+
SVGShape,
|
|
11
|
+
Tween,
|
|
12
|
+
} from "/gcanvas.es.min.js";
|
|
13
|
+
class MyGame extends Game {
|
|
14
|
+
constructor(canvas) {
|
|
15
|
+
super(canvas);
|
|
16
|
+
this.enableFluidSize();
|
|
17
|
+
this.backgroundColor = "black";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init() {
|
|
21
|
+
super.init();
|
|
22
|
+
// Set up scenes
|
|
23
|
+
console.groupCollapsed("init");
|
|
24
|
+
this.scene = new Scene(this, { debug: true, debugColor: "#0f0", anchor: "center" });
|
|
25
|
+
this.ui = new Scene(this, { debug: true, debugColor: "#0f0", anchor: "center" });
|
|
26
|
+
this.pipeline.add(this.scene); // game layer
|
|
27
|
+
this.pipeline.add(this.ui); // UI layer
|
|
28
|
+
console.groupEnd();
|
|
29
|
+
// Add SVG path animation
|
|
30
|
+
console.groupCollapsed("add SVGPathAnimation");
|
|
31
|
+
const svg = new SVGPathAnimation(this, {
|
|
32
|
+
width: 210,
|
|
33
|
+
height: 250,
|
|
34
|
+
offsetX: -70,
|
|
35
|
+
offsetY: -35,
|
|
36
|
+
path: "M 0 30.276 L 0 9.358 L 0 0.845 L 17.139 0.845 L 17.139 -5.247 L 5.189 -5.247 L 5.189 -19.273 L 0 -19.273 L 0 -4.975 L 0 0.845 L -8.618 0.845 L -25.071 0.845 L -25.071 9.757 L -7.593 9.757 L -7.593 30.276 L 0 30.276 Z",
|
|
37
|
+
});
|
|
38
|
+
this.scene.add(svg);
|
|
39
|
+
console.groupEnd();
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
console.groupCollapsed("add SVGPathAnimation");
|
|
42
|
+
this.scene.add(
|
|
43
|
+
new SVGPathAnimation(this, {
|
|
44
|
+
width: 210,
|
|
45
|
+
height: 250,
|
|
46
|
+
offsetX: 70,
|
|
47
|
+
offsetY: 35,
|
|
48
|
+
path: "M -0.003 20.33 L -0.003 6.031 L -0.003 0.211 L 25.068 0.211 L 25.068 -8.702 L 7.59 -8.702 L 7.59 -29.22 L -0.003 -29.22 L -0.003 -8.303 L -0.003 0.211 L -17.141 0.211 L -17.141 6.304 L -5.194 6.304 L -5.194 20.33 L -0.003 20.33 Z",
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
console.groupEnd();
|
|
52
|
+
}, 200);
|
|
53
|
+
// Add FPS counter in the UI scene
|
|
54
|
+
console.groupCollapsed("add FPSCounter");
|
|
55
|
+
this.ui.add(new FPSCounter(this, { anchor: "bottom-right" }));
|
|
56
|
+
console.groupEnd();
|
|
57
|
+
this.glow = Painter.effects.createGlow('rgba(0, 255, 0, 1)', 100, {
|
|
58
|
+
pulseSpeed: 1,
|
|
59
|
+
pulseMin: 0,
|
|
60
|
+
pulseMax: 50,
|
|
61
|
+
colorShift: 0.5
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
update(dt) {
|
|
66
|
+
this.scene.width = this.width - 20;
|
|
67
|
+
this.scene.height = this.height - 20;
|
|
68
|
+
this.glow.update({ pulseSpeed: 1 });
|
|
69
|
+
super.update(dt);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
render() {
|
|
73
|
+
super.render();
|
|
74
|
+
// Instructions text
|
|
75
|
+
Painter.text.setFont("18px monospace");
|
|
76
|
+
Painter.text.setTextAlign("center");
|
|
77
|
+
Painter.text.setTextBaseline("bottom");
|
|
78
|
+
Painter.text.fillText(
|
|
79
|
+
"Click anywhere to restart the SVG path animation",
|
|
80
|
+
this.width / 2,
|
|
81
|
+
this.height - 100,
|
|
82
|
+
"#0f0"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// SVG Path Animation - An animated SVG path drawing
|
|
88
|
+
class SVGPathAnimation extends GameObject {
|
|
89
|
+
constructor(game, options = {}) {
|
|
90
|
+
super(game, options);
|
|
91
|
+
// My Logo as an SVG
|
|
92
|
+
//
|
|
93
|
+
//
|
|
94
|
+
this.offsetX = options.offsetX ?? 0;
|
|
95
|
+
this.offsetY = options.offsetY ?? 0;
|
|
96
|
+
this.animTime = 0;
|
|
97
|
+
const path =
|
|
98
|
+
options.path ??
|
|
99
|
+
"M 50,10 L 50,40 L 20,40 L 20,60 L 50,60 L 50,90 L 70,90 L 70,60 L 100,60 L 100,40 L 70,40 L 70,10 Z";
|
|
100
|
+
// Initialize state
|
|
101
|
+
this.progress = 0;
|
|
102
|
+
this.speed = 0.6; // Speed of animation
|
|
103
|
+
this.complete = false;
|
|
104
|
+
// Create SVG shape with initial 0 progress
|
|
105
|
+
this.svgShape = new SVGShape(path, {
|
|
106
|
+
stroke: "#0f0", // Green color
|
|
107
|
+
lineWidth: 3,
|
|
108
|
+
color: "rgba(0, 255, 0, 0.1)",
|
|
109
|
+
scale: 5,
|
|
110
|
+
animationProgress: 1,
|
|
111
|
+
// debug:true,
|
|
112
|
+
//debugColor:"yellow",
|
|
113
|
+
x: options.offsetX ?? 0,
|
|
114
|
+
y: options.offsetY ?? 0,
|
|
115
|
+
width: 210,
|
|
116
|
+
height: 250,
|
|
117
|
+
|
|
118
|
+
});
|
|
119
|
+
// Create a circle to represent the drawing point
|
|
120
|
+
this.drawingPoint = new Circle(6, {
|
|
121
|
+
x: 0,
|
|
122
|
+
y: 0,
|
|
123
|
+
color: "#fff",
|
|
124
|
+
shadowColor: "rgba(0, 255, 0, 1)",
|
|
125
|
+
shadowBlur: 15,
|
|
126
|
+
shadowOffsetX: 0,
|
|
127
|
+
shadowOffsetY: 0,
|
|
128
|
+
});
|
|
129
|
+
// Canvas click handler to restart animation
|
|
130
|
+
game.canvas.addEventListener("click", () => this.restart());
|
|
131
|
+
console.log("SVGPathAnimation", this.x, this.y);
|
|
132
|
+
this.jittery = Math.random() * 0.2 + 0.2;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Restart the animation
|
|
136
|
+
restart() {
|
|
137
|
+
this.progress = 0;
|
|
138
|
+
this.complete = false;
|
|
139
|
+
this.x = 0;
|
|
140
|
+
this.y = 0;
|
|
141
|
+
this.animTime = 0;
|
|
142
|
+
this.jittery = Math.random() * 0.2 + 0.2;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
update(dt) {
|
|
146
|
+
//console.log(this.x, this.y);
|
|
147
|
+
// Update progress if animation not complete
|
|
148
|
+
if (!this.complete) {
|
|
149
|
+
this.progress += dt * this.speed;
|
|
150
|
+
if (this.progress >= 1) {
|
|
151
|
+
this.progress = 1;
|
|
152
|
+
this.complete = true;
|
|
153
|
+
this.floatState = null;
|
|
154
|
+
}
|
|
155
|
+
// Apply easing for more natural movement
|
|
156
|
+
const easedProgress = Easing.easeInOutQuad(this.progress);
|
|
157
|
+
// Update SVG shape animation progress
|
|
158
|
+
this.svgShape.setAnimationProgress(easedProgress);
|
|
159
|
+
}
|
|
160
|
+
let x = 0;
|
|
161
|
+
let y = 0;
|
|
162
|
+
// Add gentle bouncing motion when complete
|
|
163
|
+
if (this.complete) {
|
|
164
|
+
this.animTime = this.complete ? (this.animTime ?? 0) + (dt) : 0;
|
|
165
|
+
const floatResult = Motion.float(
|
|
166
|
+
{x:-5,y:-55},
|
|
167
|
+
this.animTime, // elapsed time
|
|
168
|
+
1, // duration (seconds per full loop)
|
|
169
|
+
1, // speed multiplier
|
|
170
|
+
this.jittery,
|
|
171
|
+
50, // radius
|
|
172
|
+
true, // loop
|
|
173
|
+
Easing.easeInOutSine, // optional easing
|
|
174
|
+
{},
|
|
175
|
+
this.floatState // persistent state
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
this.floatState = floatResult.state;
|
|
179
|
+
x = floatResult.x;
|
|
180
|
+
y = floatResult.y;
|
|
181
|
+
this.drawingPoint.visible = false;
|
|
182
|
+
} else {
|
|
183
|
+
// Show the drawing point during animation
|
|
184
|
+
this.drawingPoint.visible = true;
|
|
185
|
+
// Update drawing point position to follow the current path position
|
|
186
|
+
const currentPoint = this.svgShape.getCurrentPoint();
|
|
187
|
+
this.drawingPoint.x = currentPoint.x + this.offsetX;
|
|
188
|
+
this.drawingPoint.y = currentPoint.y + this.offsetY;
|
|
189
|
+
}
|
|
190
|
+
this.x = x;
|
|
191
|
+
this.y = y;
|
|
192
|
+
super.update(dt);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
draw() {
|
|
196
|
+
super.draw();
|
|
197
|
+
// Draw SVG path
|
|
198
|
+
this.svgShape.render();
|
|
199
|
+
// Draw drawing point
|
|
200
|
+
this.drawingPoint.render();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { MyGame };
|