@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.
Files changed (193) hide show
  1. package/dist/CNAME +1 -0
  2. package/dist/animations.html +31 -0
  3. package/dist/basic.html +38 -0
  4. package/dist/baskara.html +31 -0
  5. package/dist/bezier.html +35 -0
  6. package/dist/beziersignature.html +29 -0
  7. package/dist/blackhole.html +28 -0
  8. package/dist/blob.html +35 -0
  9. package/dist/coordinates.html +698 -0
  10. package/dist/cube3d.html +23 -0
  11. package/dist/demos.css +303 -0
  12. package/dist/dino.html +42 -0
  13. package/dist/easing.html +28 -0
  14. package/dist/events.html +195 -0
  15. package/dist/fluent.html +647 -0
  16. package/dist/fluid-simple.html +22 -0
  17. package/dist/fluid.html +37 -0
  18. package/dist/fractals.html +36 -0
  19. package/dist/gameobjects.html +626 -0
  20. package/dist/gcanvas.es.js +517 -0
  21. package/dist/gcanvas.es.min.js +1 -1
  22. package/dist/gcanvas.umd.js +1 -1
  23. package/dist/gcanvas.umd.min.js +1 -1
  24. package/dist/genart.html +26 -0
  25. package/dist/gendream.html +26 -0
  26. package/dist/group.html +36 -0
  27. package/dist/home.html +587 -0
  28. package/dist/hyperbolic001.html +23 -0
  29. package/dist/hyperbolic002.html +23 -0
  30. package/dist/hyperbolic003.html +23 -0
  31. package/dist/hyperbolic004.html +23 -0
  32. package/dist/hyperbolic005.html +22 -0
  33. package/dist/index.html +398 -0
  34. package/dist/isometric.html +34 -0
  35. package/dist/js/animations.js +452 -0
  36. package/dist/js/basic.js +204 -0
  37. package/dist/js/baskara.js +751 -0
  38. package/dist/js/bezier.js +692 -0
  39. package/dist/js/beziersignature.js +241 -0
  40. package/dist/js/blackhole/accretiondisk.obj.js +379 -0
  41. package/dist/js/blackhole/blackhole.obj.js +318 -0
  42. package/dist/js/blackhole/index.js +409 -0
  43. package/dist/js/blackhole/particle.js +56 -0
  44. package/dist/js/blackhole/starfield.obj.js +218 -0
  45. package/dist/js/blob.js +2276 -0
  46. package/dist/js/coordinates.js +840 -0
  47. package/dist/js/cube3d.js +789 -0
  48. package/dist/js/dino.js +1420 -0
  49. package/dist/js/easing.js +477 -0
  50. package/dist/js/fluent.js +183 -0
  51. package/dist/js/fluid-simple.js +253 -0
  52. package/dist/js/fluid.js +527 -0
  53. package/dist/js/fractals.js +932 -0
  54. package/dist/js/fractalworker.js +93 -0
  55. package/dist/js/gameobjects.js +176 -0
  56. package/dist/js/genart.js +268 -0
  57. package/dist/js/gendream.js +209 -0
  58. package/dist/js/group.js +140 -0
  59. package/dist/js/hyperbolic001.js +310 -0
  60. package/dist/js/hyperbolic002.js +388 -0
  61. package/dist/js/hyperbolic003.js +319 -0
  62. package/dist/js/hyperbolic004.js +345 -0
  63. package/dist/js/hyperbolic005.js +340 -0
  64. package/dist/js/info-toggle.js +25 -0
  65. package/dist/js/isometric.js +863 -0
  66. package/dist/js/kerr.js +1547 -0
  67. package/dist/js/lavalamp.js +590 -0
  68. package/dist/js/layout.js +354 -0
  69. package/dist/js/mondrian.js +285 -0
  70. package/dist/js/opacity.js +275 -0
  71. package/dist/js/painter.js +484 -0
  72. package/dist/js/particles-showcase.js +514 -0
  73. package/dist/js/particles.js +299 -0
  74. package/dist/js/patterns.js +397 -0
  75. package/dist/js/penrose/artifact.js +69 -0
  76. package/dist/js/penrose/blackhole.js +121 -0
  77. package/dist/js/penrose/constants.js +73 -0
  78. package/dist/js/penrose/game.js +943 -0
  79. package/dist/js/penrose/lore.js +278 -0
  80. package/dist/js/penrose/penrosescene.js +892 -0
  81. package/dist/js/penrose/ship.js +216 -0
  82. package/dist/js/penrose/sounds.js +211 -0
  83. package/dist/js/penrose/voidparticle.js +55 -0
  84. package/dist/js/penrose/voidscene.js +258 -0
  85. package/dist/js/penrose/voidship.js +144 -0
  86. package/dist/js/penrose/wormhole.js +46 -0
  87. package/dist/js/pipeline.js +555 -0
  88. package/dist/js/plane3d.js +256 -0
  89. package/dist/js/platformer.js +1579 -0
  90. package/dist/js/scene.js +304 -0
  91. package/dist/js/scenes.js +320 -0
  92. package/dist/js/schrodinger.js +410 -0
  93. package/dist/js/schwarzschild.js +1015 -0
  94. package/dist/js/shapes.js +628 -0
  95. package/dist/js/space/alien.js +171 -0
  96. package/dist/js/space/boom.js +98 -0
  97. package/dist/js/space/boss.js +353 -0
  98. package/dist/js/space/buff.js +73 -0
  99. package/dist/js/space/bullet.js +102 -0
  100. package/dist/js/space/constants.js +85 -0
  101. package/dist/js/space/game.js +1884 -0
  102. package/dist/js/space/hud.js +112 -0
  103. package/dist/js/space/laserbeam.js +179 -0
  104. package/dist/js/space/lightning.js +277 -0
  105. package/dist/js/space/minion.js +192 -0
  106. package/dist/js/space/missile.js +212 -0
  107. package/dist/js/space/player.js +430 -0
  108. package/dist/js/space/powerup.js +90 -0
  109. package/dist/js/space/starfield.js +58 -0
  110. package/dist/js/space/starpower.js +90 -0
  111. package/dist/js/spacetime.js +559 -0
  112. package/dist/js/sphere3d.js +229 -0
  113. package/dist/js/sprite.js +473 -0
  114. package/dist/js/starfaux/config.js +118 -0
  115. package/dist/js/starfaux/enemy.js +353 -0
  116. package/dist/js/starfaux/hud.js +78 -0
  117. package/dist/js/starfaux/index.js +482 -0
  118. package/dist/js/starfaux/laser.js +182 -0
  119. package/dist/js/starfaux/player.js +468 -0
  120. package/dist/js/starfaux/terrain.js +560 -0
  121. package/dist/js/study001.js +275 -0
  122. package/dist/js/study002.js +366 -0
  123. package/dist/js/study003.js +331 -0
  124. package/dist/js/study004.js +389 -0
  125. package/dist/js/study005.js +209 -0
  126. package/dist/js/study006.js +194 -0
  127. package/dist/js/study007.js +192 -0
  128. package/dist/js/study008.js +413 -0
  129. package/dist/js/svgtween.js +204 -0
  130. package/dist/js/tde/accretiondisk.js +471 -0
  131. package/dist/js/tde/blackhole.js +219 -0
  132. package/dist/js/tde/blackholescene.js +209 -0
  133. package/dist/js/tde/config.js +59 -0
  134. package/dist/js/tde/index.js +820 -0
  135. package/dist/js/tde/jets.js +290 -0
  136. package/dist/js/tde/lensedstarfield.js +154 -0
  137. package/dist/js/tde/tdestar.js +297 -0
  138. package/dist/js/tde/tidalstream.js +372 -0
  139. package/dist/js/tde_old/blackhole.obj.js +354 -0
  140. package/dist/js/tde_old/debris.obj.js +791 -0
  141. package/dist/js/tde_old/flare.obj.js +239 -0
  142. package/dist/js/tde_old/index.js +448 -0
  143. package/dist/js/tde_old/star.obj.js +812 -0
  144. package/dist/js/tetris/config.js +157 -0
  145. package/dist/js/tetris/grid.js +286 -0
  146. package/dist/js/tetris/index.js +1195 -0
  147. package/dist/js/tetris/renderer.js +634 -0
  148. package/dist/js/tetris/tetrominos.js +280 -0
  149. package/dist/js/tiles.js +312 -0
  150. package/dist/js/tweendemo.js +79 -0
  151. package/dist/js/visibility.js +102 -0
  152. package/dist/kerr.html +28 -0
  153. package/dist/lavalamp.html +27 -0
  154. package/dist/layouts.html +37 -0
  155. package/dist/logo.svg +4 -0
  156. package/dist/loop.html +84 -0
  157. package/dist/mondrian.html +32 -0
  158. package/dist/og_image.png +0 -0
  159. package/dist/opacity.html +36 -0
  160. package/dist/painter.html +39 -0
  161. package/dist/particles-showcase.html +28 -0
  162. package/dist/particles.html +24 -0
  163. package/dist/patterns.html +33 -0
  164. package/dist/penrose-game.html +31 -0
  165. package/dist/pipeline.html +737 -0
  166. package/dist/plane3d.html +24 -0
  167. package/dist/platformer.html +43 -0
  168. package/dist/scene.html +33 -0
  169. package/dist/scenes.html +96 -0
  170. package/dist/schrodinger.html +27 -0
  171. package/dist/schwarzschild.html +27 -0
  172. package/dist/shapes.html +16 -0
  173. package/dist/space.html +85 -0
  174. package/dist/spacetime.html +27 -0
  175. package/dist/sphere3d.html +24 -0
  176. package/dist/sprite.html +18 -0
  177. package/dist/starfaux.html +22 -0
  178. package/dist/study001.html +23 -0
  179. package/dist/study002.html +23 -0
  180. package/dist/study003.html +23 -0
  181. package/dist/study004.html +23 -0
  182. package/dist/study005.html +22 -0
  183. package/dist/study006.html +24 -0
  184. package/dist/study007.html +24 -0
  185. package/dist/study008.html +22 -0
  186. package/dist/svgtween.html +29 -0
  187. package/dist/tde.html +28 -0
  188. package/dist/tetris3d.html +25 -0
  189. package/dist/tiles.html +28 -0
  190. package/dist/transforms.html +400 -0
  191. package/dist/tween.html +45 -0
  192. package/dist/visibility.html +33 -0
  193. 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 };