@guinetik/gcanvas 1.0.5 → 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.
Files changed (78) hide show
  1. package/dist/aizawa.html +27 -0
  2. package/dist/clifford.html +25 -0
  3. package/dist/cmb.html +24 -0
  4. package/dist/dadras.html +26 -0
  5. package/dist/dejong.html +25 -0
  6. package/dist/gcanvas.es.js +5130 -372
  7. package/dist/gcanvas.es.min.js +1 -1
  8. package/dist/gcanvas.umd.js +1 -1
  9. package/dist/gcanvas.umd.min.js +1 -1
  10. package/dist/halvorsen.html +27 -0
  11. package/dist/index.html +96 -48
  12. package/dist/js/aizawa.js +425 -0
  13. package/dist/js/bezier.js +5 -5
  14. package/dist/js/clifford.js +236 -0
  15. package/dist/js/cmb.js +594 -0
  16. package/dist/js/dadras.js +405 -0
  17. package/dist/js/dejong.js +257 -0
  18. package/dist/js/halvorsen.js +405 -0
  19. package/dist/js/isometric.js +34 -46
  20. package/dist/js/lorenz.js +425 -0
  21. package/dist/js/painter.js +8 -8
  22. package/dist/js/rossler.js +480 -0
  23. package/dist/js/schrodinger.js +314 -18
  24. package/dist/js/thomas.js +394 -0
  25. package/dist/lorenz.html +27 -0
  26. package/dist/rossler.html +27 -0
  27. package/dist/scene-interactivity-test.html +220 -0
  28. package/dist/thomas.html +27 -0
  29. package/package.json +1 -1
  30. package/readme.md +30 -22
  31. package/src/game/objects/go.js +7 -0
  32. package/src/game/objects/index.js +2 -0
  33. package/src/game/objects/isometric-scene.js +53 -3
  34. package/src/game/objects/layoutscene.js +57 -0
  35. package/src/game/objects/mask.js +241 -0
  36. package/src/game/objects/scene.js +19 -0
  37. package/src/game/objects/wrapper.js +14 -2
  38. package/src/game/pipeline.js +17 -0
  39. package/src/game/ui/button.js +101 -16
  40. package/src/game/ui/theme.js +0 -6
  41. package/src/game/ui/togglebutton.js +25 -14
  42. package/src/game/ui/tooltip.js +12 -4
  43. package/src/index.js +3 -0
  44. package/src/io/gesture.js +409 -0
  45. package/src/io/index.js +4 -1
  46. package/src/io/keys.js +9 -1
  47. package/src/io/screen.js +476 -0
  48. package/src/math/attractors.js +664 -0
  49. package/src/math/heat.js +106 -0
  50. package/src/math/index.js +1 -0
  51. package/src/mixins/draggable.js +15 -19
  52. package/src/painter/painter.shapes.js +11 -5
  53. package/src/particle/particle-system.js +165 -1
  54. package/src/physics/index.js +26 -0
  55. package/src/physics/physics-updaters.js +333 -0
  56. package/src/physics/physics.js +375 -0
  57. package/src/shapes/image.js +5 -5
  58. package/src/shapes/index.js +2 -0
  59. package/src/shapes/parallelogram.js +147 -0
  60. package/src/shapes/righttriangle.js +115 -0
  61. package/src/shapes/svg.js +281 -100
  62. package/src/shapes/text.js +22 -6
  63. package/src/shapes/transformable.js +5 -0
  64. package/src/sound/effects.js +807 -0
  65. package/src/sound/index.js +13 -0
  66. package/src/webgl/index.js +7 -0
  67. package/src/webgl/shaders/clifford-point-shaders.js +131 -0
  68. package/src/webgl/shaders/dejong-point-shaders.js +131 -0
  69. package/src/webgl/shaders/point-sprite-shaders.js +152 -0
  70. package/src/webgl/webgl-clifford-renderer.js +477 -0
  71. package/src/webgl/webgl-dejong-renderer.js +472 -0
  72. package/src/webgl/webgl-line-renderer.js +391 -0
  73. package/src/webgl/webgl-particle-renderer.js +410 -0
  74. package/types/index.d.ts +30 -2
  75. package/types/io.d.ts +217 -0
  76. package/types/physics.d.ts +299 -0
  77. package/types/shapes.d.ts +8 -0
  78. package/types/webgl.d.ts +188 -109
package/src/io/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * - {@link Keys}: Keyboard input with logical key mapping and state tracking
11
11
  * - {@link Touch}: Touch input for mobile devices
12
12
  * - {@link Input}: Unified input system that normalizes mouse and touch events
13
+ * - {@link Screen}: Screen/device detection and responsive utilities
13
14
  *
14
15
  * The IO module serves as the intermediary between raw browser events and your game logic,
15
16
  * providing consistent, normalized events regardless of input source.
@@ -83,4 +84,6 @@ export {EventEmitter} from "./events.js";
83
84
  export {Input} from "./input.js";
84
85
  export {Mouse} from "./mouse.js";
85
86
  export {Keys} from "./keys.js";
86
- export {Touch} from "./touch.js";
87
+ export {Touch} from "./touch.js";
88
+ export {Screen} from "./screen.js";
89
+ export {Gesture} from "./gesture.js";
package/src/io/keys.js CHANGED
@@ -34,6 +34,10 @@ export class Keys {
34
34
  static E = "E";
35
35
  static R = "R";
36
36
  static F = "F";
37
+ static G = "G";
38
+ static J = "J";
39
+ static K = "K";
40
+ static L = "L";
37
41
  static Z = "Z";
38
42
  static C = "C";
39
43
  static UP = "UP";
@@ -50,7 +54,7 @@ export class Keys {
50
54
  * Customize this list as needed for your game.
51
55
  */
52
56
  static _codeMap = {
53
- // WASD + QE + RFZC
57
+ // WASD + QE + RFZC + GJKL
54
58
  KeyW: Keys.W,
55
59
  KeyA: Keys.A,
56
60
  KeyS: Keys.S,
@@ -59,6 +63,10 @@ export class Keys {
59
63
  KeyE: Keys.E,
60
64
  KeyR: Keys.R,
61
65
  KeyF: Keys.F,
66
+ KeyG: Keys.G,
67
+ KeyJ: Keys.J,
68
+ KeyK: Keys.K,
69
+ KeyL: Keys.L,
62
70
  KeyZ: Keys.Z,
63
71
  KeyC: Keys.C,
64
72
 
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Screen.js
3
+ *
4
+ * Provides an abstraction layer over screen/device information and responsive
5
+ * utilities. Detects device type, orientation, pixel density, and emits events
6
+ * on changes.
7
+ *
8
+ * Example usage:
9
+ * // Initialization (usually in your Game constructor):
10
+ * Screen.init(this);
11
+ *
12
+ * // Check device type:
13
+ * if (Screen.isMobile) {
14
+ * this.scaleFactor = 1.5; // Higher quality for smaller screens
15
+ * }
16
+ *
17
+ * // Listen for orientation changes:
18
+ * this.events.on("orientationchange", () => {
19
+ * this.handleOrientationChange();
20
+ * });
21
+ *
22
+ * // Get responsive values:
23
+ * const fontSize = Screen.responsive(12, 16, 20); // mobile, tablet, desktop
24
+ */
25
+ export class Screen {
26
+ // Breakpoints (can be customized)
27
+ static MOBILE_BREAKPOINT = 768;
28
+ static TABLET_BREAKPOINT = 1024;
29
+
30
+ /**
31
+ * Reference to the main game instance for event emission.
32
+ * @type {Game}
33
+ */
34
+ static game = null;
35
+
36
+ /**
37
+ * Whether Screen has been initialized with a game instance.
38
+ * @type {boolean}
39
+ */
40
+ static _initialized = false;
41
+
42
+ /**
43
+ * Current screen/window width.
44
+ * @type {number}
45
+ */
46
+ static width = 0;
47
+
48
+ /**
49
+ * Current screen/window height.
50
+ * @type {number}
51
+ */
52
+ static height = 0;
53
+
54
+ /**
55
+ * Device pixel ratio (for high-DPI displays).
56
+ * @type {number}
57
+ */
58
+ static pixelRatio = 1;
59
+
60
+ /**
61
+ * Whether the device is considered mobile (width <= MOBILE_BREAKPOINT).
62
+ * @type {boolean}
63
+ */
64
+ static isMobile = false;
65
+
66
+ /**
67
+ * Whether the device is considered tablet (MOBILE_BREAKPOINT < width <= TABLET_BREAKPOINT).
68
+ * @type {boolean}
69
+ */
70
+ static isTablet = false;
71
+
72
+ /**
73
+ * Whether the device is considered desktop (width > TABLET_BREAKPOINT).
74
+ * @type {boolean}
75
+ */
76
+ static isDesktop = false;
77
+
78
+ /**
79
+ * Whether the device has touch capability.
80
+ * @type {boolean}
81
+ */
82
+ static hasTouch = false;
83
+
84
+ /**
85
+ * Current orientation: 'portrait' or 'landscape'.
86
+ * @type {string}
87
+ */
88
+ static orientation = "landscape";
89
+
90
+ /**
91
+ * Whether the screen is in portrait mode.
92
+ * @type {boolean}
93
+ */
94
+ static isPortrait = false;
95
+
96
+ /**
97
+ * Whether the screen is in landscape mode.
98
+ * @type {boolean}
99
+ */
100
+ static isLandscape = true;
101
+
102
+ /**
103
+ * Current wake lock sentinel (if active).
104
+ * @type {WakeLockSentinel|null}
105
+ * @private
106
+ */
107
+ static _wakeLock = null;
108
+
109
+ /**
110
+ * Whether wake lock is currently enabled (requested by user).
111
+ * @type {boolean}
112
+ */
113
+ static wakeLockEnabled = false;
114
+
115
+ /**
116
+ * Whether the Wake Lock API is supported.
117
+ * @type {boolean}
118
+ */
119
+ static wakeLockSupported = false;
120
+
121
+ /**
122
+ * Static initializer - runs once when the class is first loaded.
123
+ * Ensures properties are populated even without calling init().
124
+ */
125
+ static {
126
+ // Only run in browser environment
127
+ if (typeof window !== "undefined") {
128
+ Screen._detect();
129
+ Screen.wakeLockSupported = "wakeLock" in navigator;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Initialize screen detection and event handling.
135
+ * Attaches resize and orientation change listeners.
136
+ *
137
+ * @param {Game} game - Your main Game instance with event emitter.
138
+ */
139
+ static init(game) {
140
+ Screen.game = game;
141
+
142
+ // Initial detection (in case static initializer didn't run)
143
+ Screen._detect();
144
+
145
+ // Only attach listeners once
146
+ if (Screen._initialized) return;
147
+ Screen._initialized = true;
148
+
149
+ // Attach listeners
150
+ window.addEventListener("resize", Screen._onResize);
151
+ window.addEventListener("orientationchange", Screen._onOrientationChange);
152
+
153
+ // Also listen for media query changes for more accurate detection
154
+ if (window.matchMedia) {
155
+ const mobileQuery = window.matchMedia(
156
+ `(max-width: ${Screen.MOBILE_BREAKPOINT}px)`
157
+ );
158
+ const tabletQuery = window.matchMedia(
159
+ `(max-width: ${Screen.TABLET_BREAKPOINT}px)`
160
+ );
161
+
162
+ // Modern browsers
163
+ if (mobileQuery.addEventListener) {
164
+ mobileQuery.addEventListener("change", Screen._onMediaChange);
165
+ tabletQuery.addEventListener("change", Screen._onMediaChange);
166
+ }
167
+ }
168
+
169
+ // Re-acquire wake lock when page becomes visible again
170
+ document.addEventListener("visibilitychange", Screen._onVisibilityChange);
171
+ }
172
+
173
+ /**
174
+ * Detect current screen properties.
175
+ * @private
176
+ */
177
+ static _detect() {
178
+ // Dimensions
179
+ Screen.width = window.innerWidth;
180
+ Screen.height = window.innerHeight;
181
+ Screen.pixelRatio = window.devicePixelRatio || 1;
182
+
183
+ // Device type detection
184
+ Screen.isMobile = Screen.width <= Screen.MOBILE_BREAKPOINT;
185
+ Screen.isTablet =
186
+ Screen.width > Screen.MOBILE_BREAKPOINT &&
187
+ Screen.width <= Screen.TABLET_BREAKPOINT;
188
+ Screen.isDesktop = Screen.width > Screen.TABLET_BREAKPOINT;
189
+
190
+ // Touch capability
191
+ Screen.hasTouch =
192
+ "ontouchstart" in window ||
193
+ navigator.maxTouchPoints > 0 ||
194
+ // @ts-ignore - msMaxTouchPoints for older IE
195
+ navigator.msMaxTouchPoints > 0;
196
+
197
+ // Orientation
198
+ Screen.isPortrait = Screen.height > Screen.width;
199
+ Screen.isLandscape = !Screen.isPortrait;
200
+ Screen.orientation = Screen.isPortrait ? "portrait" : "landscape";
201
+ }
202
+
203
+ /**
204
+ * Handle window resize.
205
+ * @private
206
+ */
207
+ static _onResize = () => {
208
+ const prevMobile = Screen.isMobile;
209
+ const prevTablet = Screen.isTablet;
210
+ const prevOrientation = Screen.orientation;
211
+
212
+ Screen._detect();
213
+
214
+ // Emit resize event
215
+ if (Screen.game) {
216
+ Screen.game.events.emit("screenresize", {
217
+ width: Screen.width,
218
+ height: Screen.height,
219
+ isMobile: Screen.isMobile,
220
+ isTablet: Screen.isTablet,
221
+ isDesktop: Screen.isDesktop,
222
+ });
223
+
224
+ // Emit device type change if breakpoint crossed
225
+ if (prevMobile !== Screen.isMobile || prevTablet !== Screen.isTablet) {
226
+ Screen.game.events.emit("devicechange", {
227
+ isMobile: Screen.isMobile,
228
+ isTablet: Screen.isTablet,
229
+ isDesktop: Screen.isDesktop,
230
+ previous: {
231
+ isMobile: prevMobile,
232
+ isTablet: prevTablet,
233
+ isDesktop: !prevMobile && !prevTablet,
234
+ },
235
+ });
236
+ }
237
+
238
+ // Emit orientation change if changed
239
+ if (prevOrientation !== Screen.orientation) {
240
+ Screen.game.events.emit("orientationchange", {
241
+ orientation: Screen.orientation,
242
+ isPortrait: Screen.isPortrait,
243
+ isLandscape: Screen.isLandscape,
244
+ });
245
+ }
246
+ }
247
+ };
248
+
249
+ /**
250
+ * Handle orientation change event.
251
+ * @private
252
+ */
253
+ static _onOrientationChange = () => {
254
+ // Delay detection slightly to allow browser to update dimensions
255
+ setTimeout(() => {
256
+ Screen._onResize();
257
+ }, 100);
258
+ };
259
+
260
+ /**
261
+ * Handle media query change.
262
+ * @private
263
+ */
264
+ static _onMediaChange = () => {
265
+ Screen._onResize();
266
+ };
267
+
268
+ /**
269
+ * Get a responsive value based on device type.
270
+ * Returns different values for mobile, tablet, and desktop.
271
+ *
272
+ * @param {number|any} mobile - Value to use on mobile devices.
273
+ * @param {number|any} [tablet] - Value to use on tablet devices. Defaults to mobile.
274
+ * @param {number|any} [desktop] - Value to use on desktop devices. Defaults to tablet.
275
+ * @returns {number|any} The appropriate value for the current device.
276
+ *
277
+ * @example
278
+ * const scaleFactor = Screen.responsive(1.5, 2, 3);
279
+ * const fontSize = Screen.responsive(12, 14, 16);
280
+ */
281
+ static responsive(mobile, tablet, desktop) {
282
+ // Ensure detection has run (for environments without static initializer support)
283
+ if (Screen.width === 0 && typeof window !== "undefined") {
284
+ Screen._detect();
285
+ }
286
+
287
+ if (tablet === undefined) tablet = mobile;
288
+ if (desktop === undefined) desktop = tablet;
289
+
290
+ if (Screen.isMobile) return mobile;
291
+ if (Screen.isTablet) return tablet;
292
+ return desktop;
293
+ }
294
+
295
+ /**
296
+ * Get a value scaled by pixel ratio for high-DPI displays.
297
+ *
298
+ * @param {number} value - The base value to scale.
299
+ * @returns {number} The value multiplied by device pixel ratio.
300
+ *
301
+ * @example
302
+ * const lineWidth = Screen.scaled(2); // 4 on 2x displays
303
+ */
304
+ static scaled(value) {
305
+ return value * Screen.pixelRatio;
306
+ }
307
+
308
+ /**
309
+ * Check if the current device is likely a touch-primary device.
310
+ * This is different from hasTouch - some laptops have touch screens
311
+ * but are primarily used with mouse/keyboard.
312
+ *
313
+ * @returns {boolean} True if device is likely touch-primary.
314
+ */
315
+ static isTouchPrimary() {
316
+ return Screen.hasTouch && (Screen.isMobile || Screen.isTablet);
317
+ }
318
+
319
+ /**
320
+ * Get the smaller dimension (useful for responsive sizing).
321
+ *
322
+ * @returns {number} The smaller of width or height.
323
+ */
324
+ static minDimension() {
325
+ return Math.min(Screen.width, Screen.height);
326
+ }
327
+
328
+ /**
329
+ * Get the larger dimension.
330
+ *
331
+ * @returns {number} The larger of width or height.
332
+ */
333
+ static maxDimension() {
334
+ return Math.max(Screen.width, Screen.height);
335
+ }
336
+
337
+ /**
338
+ * Get the aspect ratio (width / height).
339
+ *
340
+ * @returns {number} The aspect ratio.
341
+ */
342
+ static aspectRatio() {
343
+ return Screen.width / Screen.height;
344
+ }
345
+
346
+ /**
347
+ * Check if screen matches a media query.
348
+ *
349
+ * @param {string} query - CSS media query string.
350
+ * @returns {boolean} True if the query matches.
351
+ *
352
+ * @example
353
+ * if (Screen.matches('(prefers-color-scheme: dark)')) {
354
+ * // Use dark theme
355
+ * }
356
+ */
357
+ static matches(query) {
358
+ if (!window.matchMedia) return false;
359
+ return window.matchMedia(query).matches;
360
+ }
361
+
362
+ /**
363
+ * Check if the user prefers reduced motion.
364
+ *
365
+ * @returns {boolean} True if user prefers reduced motion.
366
+ */
367
+ static prefersReducedMotion() {
368
+ return Screen.matches("(prefers-reduced-motion: reduce)");
369
+ }
370
+
371
+ /**
372
+ * Check if the user prefers dark color scheme.
373
+ *
374
+ * @returns {boolean} True if user prefers dark mode.
375
+ */
376
+ static prefersDarkMode() {
377
+ return Screen.matches("(prefers-color-scheme: dark)");
378
+ }
379
+
380
+ // ─────────────────────────────────────────────────────────────────────────────
381
+ // WAKE LOCK API - Prevent screen from sleeping during gameplay
382
+ // ─────────────────────────────────────────────────────────────────────────────
383
+
384
+ /**
385
+ * Handle visibility change - re-acquire wake lock when page becomes visible.
386
+ * @private
387
+ */
388
+ static _onVisibilityChange = async () => {
389
+ if (Screen.wakeLockEnabled && document.visibilityState === "visible") {
390
+ await Screen._acquireWakeLock();
391
+ }
392
+ };
393
+
394
+ /**
395
+ * Internal method to acquire wake lock.
396
+ * @private
397
+ * @returns {Promise<boolean>} True if lock was acquired.
398
+ */
399
+ static async _acquireWakeLock() {
400
+ if (!Screen.wakeLockSupported) return false;
401
+
402
+ try {
403
+ Screen._wakeLock = await navigator.wakeLock.request("screen");
404
+ Screen._wakeLock.addEventListener("release", () => {
405
+ // Lock was released (page hidden or manual release)
406
+ if (Screen.game) {
407
+ Screen.game.events.emit("wakelockrelease");
408
+ }
409
+ });
410
+ if (Screen.game) {
411
+ Screen.game.events.emit("wakelockacquire");
412
+ }
413
+ return true;
414
+ } catch (err) {
415
+ // Wake lock request failed (e.g., low battery, browser policy)
416
+ console.warn("[Screen] Wake lock request failed:", err.message);
417
+ return false;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Request a wake lock to prevent the screen from sleeping.
423
+ * Useful for games/simulations that should keep the display on.
424
+ * The lock is automatically re-acquired when the page becomes visible.
425
+ *
426
+ * @returns {Promise<boolean>} True if wake lock was successfully acquired.
427
+ *
428
+ * @example
429
+ * // In your game's init():
430
+ * await Screen.requestWakeLock();
431
+ *
432
+ * // In your game's stop():
433
+ * Screen.releaseWakeLock();
434
+ */
435
+ static async requestWakeLock() {
436
+ if (!Screen.wakeLockSupported) {
437
+ console.warn("[Screen] Wake Lock API not supported in this browser");
438
+ return false;
439
+ }
440
+
441
+ Screen.wakeLockEnabled = true;
442
+ return await Screen._acquireWakeLock();
443
+ }
444
+
445
+ /**
446
+ * Release the wake lock, allowing the screen to sleep normally.
447
+ * Call this when your game/simulation stops or pauses.
448
+ *
449
+ * @returns {Promise<void>}
450
+ *
451
+ * @example
452
+ * // When game stops:
453
+ * Screen.releaseWakeLock();
454
+ */
455
+ static async releaseWakeLock() {
456
+ Screen.wakeLockEnabled = false;
457
+
458
+ if (Screen._wakeLock) {
459
+ try {
460
+ await Screen._wakeLock.release();
461
+ Screen._wakeLock = null;
462
+ } catch (err) {
463
+ console.warn("[Screen] Wake lock release failed:", err.message);
464
+ }
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Check if wake lock is currently active.
470
+ *
471
+ * @returns {boolean} True if wake lock is held.
472
+ */
473
+ static isWakeLockActive() {
474
+ return Screen._wakeLock !== null && !Screen._wakeLock.released;
475
+ }
476
+ }