@guinetik/gcanvas 1.0.0 → 1.0.1
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/demos/fluid-simple.html +22 -0
- package/demos/fluid.html +37 -0
- package/demos/index.html +2 -0
- package/demos/js/blob.js +18 -5
- package/demos/js/fluid-simple.js +253 -0
- package/demos/js/fluid.js +527 -0
- package/demos/js/tde/accretiondisk.js +64 -11
- package/demos/js/tde/blackholescene.js +2 -2
- package/demos/js/tde/config.js +2 -2
- package/demos/js/tde/index.js +152 -27
- package/demos/js/tde/lensedstarfield.js +32 -25
- package/demos/js/tde/tdestar.js +78 -98
- package/demos/js/tde/tidalstream.js +23 -7
- package/docs/README.md +230 -222
- package/docs/api/FluidSystem.md +173 -0
- package/docs/concepts/architecture-overview.md +204 -204
- package/docs/concepts/rendering-pipeline.md +279 -279
- package/docs/concepts/two-layer-architecture.md +229 -229
- package/docs/fluid-dynamics.md +97 -0
- package/docs/getting-started/first-game.md +354 -354
- package/docs/getting-started/installation.md +175 -157
- package/docs/modules/collision/README.md +2 -2
- package/docs/modules/fluent/README.md +6 -6
- package/docs/modules/game/README.md +303 -303
- package/docs/modules/isometric-camera.md +2 -2
- package/docs/modules/isometric.md +1 -1
- package/docs/modules/painter/README.md +328 -328
- package/docs/modules/particle/README.md +3 -3
- package/docs/modules/shapes/README.md +221 -221
- package/docs/modules/shapes/base/euclidian.md +123 -123
- package/docs/modules/shapes/base/shape.md +262 -262
- package/docs/modules/shapes/base/transformable.md +243 -243
- package/docs/modules/state/README.md +2 -2
- package/docs/modules/util/README.md +1 -1
- package/docs/modules/util/camera3d.md +3 -3
- package/docs/modules/util/scene3d.md +1 -1
- package/package.json +3 -1
- package/readme.md +19 -5
- package/src/collision/collision.js +75 -0
- package/src/game/index.js +2 -1
- package/src/game/pipeline.js +3 -3
- package/src/game/systems/FluidSystem.js +835 -0
- package/src/game/systems/index.js +11 -0
- package/src/game/ui/button.js +39 -18
- package/src/game/ui/cursor.js +14 -0
- package/src/game/ui/fps.js +12 -4
- package/src/game/ui/index.js +2 -0
- package/src/game/ui/stepper.js +549 -0
- package/src/game/ui/theme.js +121 -0
- package/src/game/ui/togglebutton.js +9 -3
- package/src/game/ui/tooltip.js +11 -4
- package/src/math/fluid.js +507 -0
- package/src/math/index.js +2 -0
- package/src/mixins/anchor.js +17 -7
- package/src/motion/tweenetik.js +16 -0
- package/src/shapes/index.js +1 -0
- package/src/util/camera3d.js +218 -12
- package/types/fluent.d.ts +361 -0
- package/types/game.d.ts +303 -0
- package/types/index.d.ts +144 -5
- package/types/math.d.ts +361 -0
- package/types/motion.d.ts +271 -0
- package/types/particle.d.ts +373 -0
- package/types/shapes.d.ts +107 -9
- package/types/util.d.ts +353 -0
- package/types/webgl.d.ts +109 -0
- package/disk_example.png +0 -0
- package/tde.png +0 -0
package/src/util/camera3d.js
CHANGED
|
@@ -32,6 +32,9 @@ export class Camera3D {
|
|
|
32
32
|
* @param {number} [options.rotationX=0] - Initial X rotation (tilt up/down) in radians
|
|
33
33
|
* @param {number} [options.rotationY=0] - Initial Y rotation (spin left/right) in radians
|
|
34
34
|
* @param {number} [options.rotationZ=0] - Initial Z rotation (roll) in radians
|
|
35
|
+
* @param {number} [options.x=0] - Initial X position in world space
|
|
36
|
+
* @param {number} [options.y=0] - Initial Y position in world space
|
|
37
|
+
* @param {number} [options.z=0] - Initial Z position in world space
|
|
35
38
|
* @param {number} [options.perspective=800] - Perspective distance (higher = less distortion)
|
|
36
39
|
* @param {number} [options.sensitivity=0.005] - Mouse drag sensitivity
|
|
37
40
|
* @param {number} [options.minRotationX=-1.5] - Minimum X rotation limit
|
|
@@ -49,10 +52,18 @@ export class Camera3D {
|
|
|
49
52
|
this.rotationY = options.rotationY ?? 0;
|
|
50
53
|
this.rotationZ = options.rotationZ ?? 0;
|
|
51
54
|
|
|
55
|
+
// Position state (camera location in world space)
|
|
56
|
+
this.x = options.x ?? 0;
|
|
57
|
+
this.y = options.y ?? 0;
|
|
58
|
+
this.z = options.z ?? 0;
|
|
59
|
+
|
|
52
60
|
// Store initial values for reset
|
|
53
61
|
this._initialRotationX = this.rotationX;
|
|
54
62
|
this._initialRotationY = this.rotationY;
|
|
55
63
|
this._initialRotationZ = this.rotationZ;
|
|
64
|
+
this._initialX = this.x;
|
|
65
|
+
this._initialY = this.y;
|
|
66
|
+
this._initialZ = this.z;
|
|
56
67
|
|
|
57
68
|
// Perspective
|
|
58
69
|
this.perspective = options.perspective ?? 800;
|
|
@@ -86,6 +97,20 @@ export class Camera3D {
|
|
|
86
97
|
this._lastMouseY = 0;
|
|
87
98
|
this._canvas = null;
|
|
88
99
|
this._boundHandlers = null;
|
|
100
|
+
|
|
101
|
+
// Follow target state
|
|
102
|
+
this._followTarget = null;
|
|
103
|
+
this._followOffset = { x: 0, y: 0, z: 0 };
|
|
104
|
+
this._followLookAt = true; // Auto-orient to look at target's forward direction
|
|
105
|
+
this._followLerp = 0.1; // Interpolation speed (0-1, higher = snappier)
|
|
106
|
+
|
|
107
|
+
// Position animation state
|
|
108
|
+
this._targetX = null;
|
|
109
|
+
this._targetY = null;
|
|
110
|
+
this._targetZ = null;
|
|
111
|
+
this._targetRotationX = null;
|
|
112
|
+
this._targetRotationY = null;
|
|
113
|
+
this._positionLerp = 0.05; // Default smooth movement speed
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
/**
|
|
@@ -96,6 +121,12 @@ export class Camera3D {
|
|
|
96
121
|
* @returns {{x: number, y: number, z: number, scale: number}} Projected 2D coordinates and depth info
|
|
97
122
|
*/
|
|
98
123
|
project(x, y, z) {
|
|
124
|
+
// Translate world relative to camera position
|
|
125
|
+
// (Moving camera right = moving world left)
|
|
126
|
+
x -= this.x;
|
|
127
|
+
y -= this.y;
|
|
128
|
+
z -= this.z;
|
|
129
|
+
|
|
99
130
|
// Rotate around Z axis (roll)
|
|
100
131
|
if (this.rotationZ !== 0) {
|
|
101
132
|
const cosZ = Math.cos(this.rotationZ);
|
|
@@ -141,12 +172,78 @@ export class Camera3D {
|
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
/**
|
|
144
|
-
* Update camera for auto-rotation and
|
|
175
|
+
* Update camera for auto-rotation, inertia, and follow mode (call in update loop)
|
|
145
176
|
* @param {number} dt - Delta time in seconds
|
|
146
177
|
*/
|
|
147
178
|
update(dt) {
|
|
148
|
-
//
|
|
149
|
-
if (this.
|
|
179
|
+
// Follow target mode - position camera relative to target
|
|
180
|
+
if (this._followTarget) {
|
|
181
|
+
const target = this._followTarget;
|
|
182
|
+
const targetX = (target.x ?? 0) + this._followOffset.x;
|
|
183
|
+
const targetY = (target.y ?? 0) + this._followOffset.y;
|
|
184
|
+
const targetZ = (target.z ?? 0) + this._followOffset.z;
|
|
185
|
+
|
|
186
|
+
// Smooth interpolation to target position
|
|
187
|
+
this.x += (targetX - this.x) * this._followLerp;
|
|
188
|
+
this.y += (targetY - this.y) * this._followLerp;
|
|
189
|
+
this.z += (targetZ - this.z) * this._followLerp;
|
|
190
|
+
|
|
191
|
+
// Auto-orient to look toward origin (or specified look target)
|
|
192
|
+
if (this._followLookAt) {
|
|
193
|
+
const lookX = this._followLookAtTarget?.x ?? 0;
|
|
194
|
+
const lookY = this._followLookAtTarget?.y ?? 0;
|
|
195
|
+
const lookZ = this._followLookAtTarget?.z ?? 0;
|
|
196
|
+
|
|
197
|
+
// Calculate direction from camera to look target
|
|
198
|
+
const dx = lookX - this.x;
|
|
199
|
+
const dy = lookY - this.y;
|
|
200
|
+
const dz = lookZ - this.z;
|
|
201
|
+
const distXZ = Math.sqrt(dx * dx + dz * dz);
|
|
202
|
+
|
|
203
|
+
// Target rotation to face the look target
|
|
204
|
+
const targetRotY = Math.atan2(dx, dz);
|
|
205
|
+
const targetRotX = Math.atan2(-dy, distXZ);
|
|
206
|
+
|
|
207
|
+
// Smooth rotation interpolation
|
|
208
|
+
this.rotationY += this._angleDiff(this.rotationY, targetRotY) * this._followLerp;
|
|
209
|
+
this.rotationX += (targetRotX - this.rotationX) * this._followLerp;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Animated position movement (moveTo)
|
|
213
|
+
else if (this._targetX !== null) {
|
|
214
|
+
const lerp = this._positionLerp;
|
|
215
|
+
|
|
216
|
+
// Interpolate position
|
|
217
|
+
this.x += (this._targetX - this.x) * lerp;
|
|
218
|
+
this.y += (this._targetY - this.y) * lerp;
|
|
219
|
+
this.z += (this._targetZ - this.z) * lerp;
|
|
220
|
+
|
|
221
|
+
// Interpolate rotation if targets set
|
|
222
|
+
if (this._targetRotationX !== null) {
|
|
223
|
+
this.rotationX += (this._targetRotationX - this.rotationX) * lerp;
|
|
224
|
+
}
|
|
225
|
+
if (this._targetRotationY !== null) {
|
|
226
|
+
this.rotationY += this._angleDiff(this.rotationY, this._targetRotationY) * lerp;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if we've arrived (close enough)
|
|
230
|
+
const posDist = Math.abs(this._targetX - this.x) +
|
|
231
|
+
Math.abs(this._targetY - this.y) +
|
|
232
|
+
Math.abs(this._targetZ - this.z);
|
|
233
|
+
if (posDist < 0.1) {
|
|
234
|
+
this.x = this._targetX;
|
|
235
|
+
this.y = this._targetY;
|
|
236
|
+
this.z = this._targetZ;
|
|
237
|
+
this._targetX = null;
|
|
238
|
+
this._targetY = null;
|
|
239
|
+
this._targetZ = null;
|
|
240
|
+
this._targetRotationX = null;
|
|
241
|
+
this._targetRotationY = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Apply inertia when not dragging and not following
|
|
246
|
+
if (this.inertia && !this._isDragging && !this._followTarget) {
|
|
150
247
|
// Apply velocity to rotation
|
|
151
248
|
if (Math.abs(this._velocityX) > 0.0001 || Math.abs(this._velocityY) > 0.0001) {
|
|
152
249
|
this.rotationY += this._velocityY;
|
|
@@ -167,8 +264,8 @@ export class Camera3D {
|
|
|
167
264
|
}
|
|
168
265
|
}
|
|
169
266
|
|
|
170
|
-
// Auto-rotate when not dragging and no significant velocity
|
|
171
|
-
if (this.autoRotate && !this._isDragging) {
|
|
267
|
+
// Auto-rotate when not dragging, not following, and no significant velocity
|
|
268
|
+
if (this.autoRotate && !this._isDragging && !this._followTarget) {
|
|
172
269
|
const hasVelocity = Math.abs(this._velocityX) > 0.001 || Math.abs(this._velocityY) > 0.001;
|
|
173
270
|
if (!hasVelocity) {
|
|
174
271
|
const delta = this.autoRotateSpeed * dt;
|
|
@@ -187,6 +284,17 @@ export class Camera3D {
|
|
|
187
284
|
}
|
|
188
285
|
}
|
|
189
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Calculate shortest angle difference (handles wraparound)
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
_angleDiff(from, to) {
|
|
292
|
+
let diff = to - from;
|
|
293
|
+
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
294
|
+
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
295
|
+
return diff;
|
|
296
|
+
}
|
|
297
|
+
|
|
190
298
|
/**
|
|
191
299
|
* Enable mouse/touch drag rotation on a canvas
|
|
192
300
|
* @param {HTMLCanvasElement} canvas - The canvas element to attach controls to
|
|
@@ -360,15 +468,22 @@ export class Camera3D {
|
|
|
360
468
|
}
|
|
361
469
|
|
|
362
470
|
/**
|
|
363
|
-
* Reset rotation to initial values
|
|
471
|
+
* Reset rotation and position to initial values, stop inertia and following
|
|
364
472
|
* @returns {Camera3D} Returns this for chaining
|
|
365
473
|
*/
|
|
366
474
|
reset() {
|
|
367
475
|
this.rotationX = this._initialRotationX;
|
|
368
476
|
this.rotationY = this._initialRotationY;
|
|
369
477
|
this.rotationZ = this._initialRotationZ;
|
|
478
|
+
this.x = this._initialX;
|
|
479
|
+
this.y = this._initialY;
|
|
480
|
+
this.z = this._initialZ;
|
|
370
481
|
this._velocityX = 0;
|
|
371
482
|
this._velocityY = 0;
|
|
483
|
+
this._followTarget = null;
|
|
484
|
+
this._targetX = null;
|
|
485
|
+
this._targetY = null;
|
|
486
|
+
this._targetZ = null;
|
|
372
487
|
return this;
|
|
373
488
|
}
|
|
374
489
|
|
|
@@ -382,6 +497,91 @@ export class Camera3D {
|
|
|
382
497
|
return this;
|
|
383
498
|
}
|
|
384
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Set camera position in world space
|
|
502
|
+
* @param {number} x - X position
|
|
503
|
+
* @param {number} y - Y position
|
|
504
|
+
* @param {number} z - Z position
|
|
505
|
+
* @returns {Camera3D} Returns this for chaining
|
|
506
|
+
*/
|
|
507
|
+
setPosition(x, y, z) {
|
|
508
|
+
this.x = x;
|
|
509
|
+
this.y = y;
|
|
510
|
+
this.z = z;
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Animate camera to a new position (and optionally rotation)
|
|
516
|
+
* @param {number} x - Target X position
|
|
517
|
+
* @param {number} y - Target Y position
|
|
518
|
+
* @param {number} z - Target Z position
|
|
519
|
+
* @param {object} [options] - Animation options
|
|
520
|
+
* @param {number} [options.rotationX] - Target X rotation
|
|
521
|
+
* @param {number} [options.rotationY] - Target Y rotation
|
|
522
|
+
* @param {number} [options.lerp=0.05] - Interpolation speed (0-1)
|
|
523
|
+
* @returns {Camera3D} Returns this for chaining
|
|
524
|
+
*/
|
|
525
|
+
moveTo(x, y, z, options = {}) {
|
|
526
|
+
this._targetX = x;
|
|
527
|
+
this._targetY = y;
|
|
528
|
+
this._targetZ = z;
|
|
529
|
+
this._targetRotationX = options.rotationX ?? null;
|
|
530
|
+
this._targetRotationY = options.rotationY ?? null;
|
|
531
|
+
this._positionLerp = options.lerp ?? 0.05;
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Follow a target object (position camera relative to target)
|
|
537
|
+
* @param {object} target - Object with x, y, z properties to follow
|
|
538
|
+
* @param {object} [options] - Follow options
|
|
539
|
+
* @param {number} [options.offsetX=0] - X offset from target
|
|
540
|
+
* @param {number} [options.offsetY=0] - Y offset from target
|
|
541
|
+
* @param {number} [options.offsetZ=0] - Z offset from target
|
|
542
|
+
* @param {boolean} [options.lookAt=true] - Auto-orient to look at lookAtTarget
|
|
543
|
+
* @param {object} [options.lookAtTarget=null] - Point to look at (default: origin)
|
|
544
|
+
* @param {number} [options.lerp=0.1] - Interpolation speed (0-1, higher = snappier)
|
|
545
|
+
* @returns {Camera3D} Returns this for chaining
|
|
546
|
+
*/
|
|
547
|
+
follow(target, options = {}) {
|
|
548
|
+
this._followTarget = target;
|
|
549
|
+
this._followOffset = {
|
|
550
|
+
x: options.offsetX ?? 0,
|
|
551
|
+
y: options.offsetY ?? 0,
|
|
552
|
+
z: options.offsetZ ?? 0,
|
|
553
|
+
};
|
|
554
|
+
this._followLookAt = options.lookAt ?? true;
|
|
555
|
+
this._followLookAtTarget = options.lookAtTarget ?? null;
|
|
556
|
+
this._followLerp = options.lerp ?? 0.1;
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Stop following target and optionally reset to initial position
|
|
562
|
+
* @param {boolean} [resetPosition=false] - Whether to animate back to initial position
|
|
563
|
+
* @returns {Camera3D} Returns this for chaining
|
|
564
|
+
*/
|
|
565
|
+
unfollow(resetPosition = false) {
|
|
566
|
+
this._followTarget = null;
|
|
567
|
+
if (resetPosition) {
|
|
568
|
+
this.moveTo(this._initialX, this._initialY, this._initialZ, {
|
|
569
|
+
rotationX: this._initialRotationX,
|
|
570
|
+
rotationY: this._initialRotationY,
|
|
571
|
+
lerp: 0.05,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return this;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Check if camera is currently following a target
|
|
579
|
+
* @returns {boolean} True if following a target
|
|
580
|
+
*/
|
|
581
|
+
isFollowing() {
|
|
582
|
+
return this._followTarget !== null;
|
|
583
|
+
}
|
|
584
|
+
|
|
385
585
|
/**
|
|
386
586
|
* Set rotation angles
|
|
387
587
|
* @param {number} x - X rotation in radians
|
|
@@ -424,15 +624,21 @@ export class Camera3D {
|
|
|
424
624
|
}
|
|
425
625
|
|
|
426
626
|
/**
|
|
427
|
-
* Look at a specific point (sets rotation to face that direction)
|
|
428
|
-
* @param {number} x - Target X
|
|
429
|
-
* @param {number} y - Target Y
|
|
430
|
-
* @param {number} z - Target Z
|
|
627
|
+
* Look at a specific point (sets rotation to face that direction from current position)
|
|
628
|
+
* @param {number} x - Target X in world space
|
|
629
|
+
* @param {number} y - Target Y in world space
|
|
630
|
+
* @param {number} z - Target Z in world space
|
|
431
631
|
* @returns {Camera3D} Returns this for chaining
|
|
432
632
|
*/
|
|
433
633
|
lookAt(x, y, z) {
|
|
434
|
-
|
|
435
|
-
|
|
634
|
+
// Calculate direction from camera position to target
|
|
635
|
+
const dx = x - this.x;
|
|
636
|
+
const dy = y - this.y;
|
|
637
|
+
const dz = z - this.z;
|
|
638
|
+
const distXZ = Math.sqrt(dx * dx + dz * dz);
|
|
639
|
+
|
|
640
|
+
this.rotationY = Math.atan2(dx, dz);
|
|
641
|
+
this.rotationX = Math.atan2(-dy, distXZ);
|
|
436
642
|
return this;
|
|
437
643
|
}
|
|
438
644
|
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/// <reference lib="es2015" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GCanvas Fluent API Module
|
|
5
|
+
* Declarative, chainable API for rapid game and creative coding development.
|
|
6
|
+
* @module fluent
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Game, Scene, GameObject, GameObjectOptions, SceneOptions } from './game';
|
|
10
|
+
import { Shape } from './shapes';
|
|
11
|
+
import { Easing, Motion, Tweenetik } from './motion';
|
|
12
|
+
import { Keys, Mouse } from './io';
|
|
13
|
+
|
|
14
|
+
// ==========================================================================
|
|
15
|
+
// Main Entry Points
|
|
16
|
+
// ==========================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Main entry point for the fluent API.
|
|
20
|
+
* Creates a FluentGame instance with automatic canvas setup.
|
|
21
|
+
*
|
|
22
|
+
* @param options - Game configuration
|
|
23
|
+
* @returns FluentGame builder instance
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* gcanvas({ bg: 'black' })
|
|
27
|
+
* .scene('game')
|
|
28
|
+
* .go({ x: 400, y: 300, name: 'player' })
|
|
29
|
+
* .circle({ radius: 30, fill: 'lime' })
|
|
30
|
+
* .start();
|
|
31
|
+
*/
|
|
32
|
+
export function gcanvas(options?: FluentGameOptions): FluentGame;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ultra-simple sketch mode for quick creative coding prototypes.
|
|
36
|
+
*
|
|
37
|
+
* @param w - Canvas width (default: 800)
|
|
38
|
+
* @param h - Canvas height (default: 600)
|
|
39
|
+
* @param bg - Background color (default: 'black')
|
|
40
|
+
* @returns SketchAPI builder instance
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* sketch(800, 600, 'black')
|
|
44
|
+
* .circle(400, 300, 50, 'lime')
|
|
45
|
+
* .update((dt, ctx) => {
|
|
46
|
+
* ctx.shapes[0].x += Math.sin(ctx.time) * 2;
|
|
47
|
+
* })
|
|
48
|
+
* .start();
|
|
49
|
+
*/
|
|
50
|
+
export function sketch(w?: number, h?: number, bg?: string): SketchAPI;
|
|
51
|
+
|
|
52
|
+
// ==========================================================================
|
|
53
|
+
// FluentGame Options
|
|
54
|
+
// ==========================================================================
|
|
55
|
+
|
|
56
|
+
/** Options for FluentGame constructor */
|
|
57
|
+
export interface FluentGameOptions {
|
|
58
|
+
/** Canvas element to use (auto-creates if not provided) */
|
|
59
|
+
canvas?: HTMLCanvasElement;
|
|
60
|
+
/** Canvas width when auto-creating (default: 800) */
|
|
61
|
+
width?: number;
|
|
62
|
+
/** Canvas height when auto-creating (default: 600) */
|
|
63
|
+
height?: number;
|
|
64
|
+
/** Background color */
|
|
65
|
+
bg?: string;
|
|
66
|
+
/** Enable fluid/responsive sizing (default: true for auto-created canvas) */
|
|
67
|
+
fluid?: boolean;
|
|
68
|
+
/** Container for auto-created canvas (default: document.body) */
|
|
69
|
+
container?: HTMLElement;
|
|
70
|
+
/** Target FPS (default: 60) */
|
|
71
|
+
fps?: number;
|
|
72
|
+
/** Pixel ratio for HiDPI displays */
|
|
73
|
+
pixelRatio?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ==========================================================================
|
|
77
|
+
// FluentGame Class
|
|
78
|
+
// ==========================================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Root builder class for the fluent API.
|
|
82
|
+
* Wraps the Game class with a declarative, chainable interface.
|
|
83
|
+
*/
|
|
84
|
+
export class FluentGame {
|
|
85
|
+
constructor(options?: FluentGameOptions);
|
|
86
|
+
|
|
87
|
+
// Scene Management
|
|
88
|
+
/**
|
|
89
|
+
* Create or switch to a scene.
|
|
90
|
+
* @param name - Scene identifier
|
|
91
|
+
* @param options - Scene options
|
|
92
|
+
*/
|
|
93
|
+
scene(name: string, options?: FluentSceneOptions): FluentScene;
|
|
94
|
+
/**
|
|
95
|
+
* Create scene with custom Scene class.
|
|
96
|
+
* @param name - Scene identifier
|
|
97
|
+
* @param sceneClass - Custom Scene class
|
|
98
|
+
* @param options - Scene options
|
|
99
|
+
*/
|
|
100
|
+
scene(name: string, sceneClass: new (...args: any[]) => Scene, options?: FluentSceneOptions): FluentScene;
|
|
101
|
+
/**
|
|
102
|
+
* Create scene with custom Scene class (name derived from class).
|
|
103
|
+
* @param sceneClass - Custom Scene class
|
|
104
|
+
* @param options - Scene options
|
|
105
|
+
*/
|
|
106
|
+
scene(sceneClass: new (...args: any[]) => Scene, options?: FluentSceneOptions): FluentScene;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Switch context to an existing scene (does not create).
|
|
110
|
+
* @param name - Scene name
|
|
111
|
+
*/
|
|
112
|
+
inScene(name: string): FluentScene;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Shortcut: create GameObject in current/default scene.
|
|
116
|
+
* @param options - GameObject options
|
|
117
|
+
*/
|
|
118
|
+
go(options?: GameObjectOptions): FluentGO;
|
|
119
|
+
/**
|
|
120
|
+
* Create GameObject with custom class.
|
|
121
|
+
* @param goClass - Custom GameObject class
|
|
122
|
+
* @param options - GameObject options
|
|
123
|
+
*/
|
|
124
|
+
go(goClass: new (...args: any[]) => GameObject, options?: GameObjectOptions): FluentGO;
|
|
125
|
+
|
|
126
|
+
// Scene Visibility
|
|
127
|
+
/** Show a scene by name */
|
|
128
|
+
showScene(name: string): FluentGame;
|
|
129
|
+
/** Hide a scene by name */
|
|
130
|
+
hideScene(name: string): FluentGame;
|
|
131
|
+
/** Transition between scenes */
|
|
132
|
+
transition(from: string, to: string, options?: TransitionOptions): FluentGame;
|
|
133
|
+
|
|
134
|
+
// State Management
|
|
135
|
+
/** Set initial state */
|
|
136
|
+
state(initialState: Record<string, any>): FluentGame;
|
|
137
|
+
/** Get a state value */
|
|
138
|
+
getState<T = any>(key: string): T;
|
|
139
|
+
/** Set a state value */
|
|
140
|
+
setState(key: string, value: any): FluentGame;
|
|
141
|
+
|
|
142
|
+
// Events & Lifecycle
|
|
143
|
+
/**
|
|
144
|
+
* Register event handler.
|
|
145
|
+
* @param event - Event name (update, keydown:escape, click, etc.)
|
|
146
|
+
* @param handler - Handler function
|
|
147
|
+
*/
|
|
148
|
+
on(event: string, handler: (ctx: FluentContext, e?: any) => void): FluentGame;
|
|
149
|
+
on(event: 'update', handler: (dt: number, ctx: FluentContext) => void): FluentGame;
|
|
150
|
+
|
|
151
|
+
// Plugins
|
|
152
|
+
/** Use a plugin or scene builder function */
|
|
153
|
+
use(plugin: (game: FluentGame) => void): FluentGame;
|
|
154
|
+
|
|
155
|
+
// Lifecycle
|
|
156
|
+
/** Start the game loop */
|
|
157
|
+
start(): FluentGame;
|
|
158
|
+
/** Stop the game loop */
|
|
159
|
+
stop(): FluentGame;
|
|
160
|
+
/** Restart the game */
|
|
161
|
+
restart(): FluentGame;
|
|
162
|
+
|
|
163
|
+
// Accessors
|
|
164
|
+
/** Underlying Game instance */
|
|
165
|
+
readonly game: Game;
|
|
166
|
+
/** Named object references */
|
|
167
|
+
readonly refs: Record<string, GameObject>;
|
|
168
|
+
/** All scenes */
|
|
169
|
+
readonly scenes: Map<string, Scene>;
|
|
170
|
+
/** Canvas element */
|
|
171
|
+
readonly canvas: HTMLCanvasElement;
|
|
172
|
+
/** Canvas width */
|
|
173
|
+
readonly width: number;
|
|
174
|
+
/** Canvas height */
|
|
175
|
+
readonly height: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ==========================================================================
|
|
179
|
+
// FluentScene Class
|
|
180
|
+
// ==========================================================================
|
|
181
|
+
|
|
182
|
+
/** Options for fluent scene creation */
|
|
183
|
+
export interface FluentSceneOptions extends SceneOptions {
|
|
184
|
+
/** Scene z-index (default: 0) */
|
|
185
|
+
zIndex?: number;
|
|
186
|
+
/** Whether scene is visible (default: true) */
|
|
187
|
+
active?: boolean;
|
|
188
|
+
/** Scene enter callback */
|
|
189
|
+
onEnter?: (ctx: FluentContext) => void;
|
|
190
|
+
/** Scene exit callback */
|
|
191
|
+
onExit?: (ctx: FluentContext) => void;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Transition options */
|
|
195
|
+
export interface TransitionOptions {
|
|
196
|
+
/** Fade duration in seconds */
|
|
197
|
+
fade?: number;
|
|
198
|
+
/** Completion callback */
|
|
199
|
+
onComplete?: () => void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Scene builder for the fluent API.
|
|
204
|
+
*/
|
|
205
|
+
export class FluentScene {
|
|
206
|
+
/**
|
|
207
|
+
* Create a GameObject in this scene.
|
|
208
|
+
* @param options - GameObject options
|
|
209
|
+
*/
|
|
210
|
+
go(options?: GameObjectOptions): FluentGO;
|
|
211
|
+
/**
|
|
212
|
+
* Create GameObject with custom class.
|
|
213
|
+
* @param goClass - Custom GameObject class
|
|
214
|
+
* @param options - GameObject options
|
|
215
|
+
*/
|
|
216
|
+
go(goClass: new (...args: any[]) => GameObject, options?: GameObjectOptions): FluentGO;
|
|
217
|
+
|
|
218
|
+
/** Return to FluentGame context */
|
|
219
|
+
end(): FluentGame;
|
|
220
|
+
|
|
221
|
+
/** Access the underlying scene */
|
|
222
|
+
readonly sceneInstance: Scene;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ==========================================================================
|
|
226
|
+
// FluentGO Class
|
|
227
|
+
// ==========================================================================
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* GameObject builder for the fluent API.
|
|
231
|
+
* Provides chainable methods for adding shapes and behaviors.
|
|
232
|
+
*/
|
|
233
|
+
export class FluentGO {
|
|
234
|
+
// Shape methods
|
|
235
|
+
circle(options?: { radius?: number; fill?: string; stroke?: string }): FluentGO;
|
|
236
|
+
rect(options?: { width?: number; height?: number; fill?: string; stroke?: string }): FluentGO;
|
|
237
|
+
square(options?: { size?: number; fill?: string; stroke?: string }): FluentGO;
|
|
238
|
+
star(options?: { points?: number; radius?: number; innerRadius?: number; fill?: string }): FluentGO;
|
|
239
|
+
triangle(options?: { size?: number; fill?: string }): FluentGO;
|
|
240
|
+
hexagon(options?: { radius?: number; fill?: string }): FluentGO;
|
|
241
|
+
line(options?: { x2?: number; y2?: number; stroke?: string; lineWidth?: number }): FluentGO;
|
|
242
|
+
ring(options?: { innerRadius?: number; outerRadius?: number; fill?: string }): FluentGO;
|
|
243
|
+
text(content: string, options?: { fill?: string; font?: string }): FluentGO;
|
|
244
|
+
|
|
245
|
+
// Motion methods (apply behaviors)
|
|
246
|
+
pulse(options?: { min?: number; max?: number; duration?: number }): FluentGO;
|
|
247
|
+
orbit(options?: { radius?: number; speed?: number; centerX?: number; centerY?: number }): FluentGO;
|
|
248
|
+
oscillate(options?: { axis?: 'x' | 'y'; amplitude?: number; frequency?: number }): FluentGO;
|
|
249
|
+
bounce(options?: { height?: number; duration?: number }): FluentGO;
|
|
250
|
+
shake(options?: { intensity?: number; duration?: number }): FluentGO;
|
|
251
|
+
float(options?: { amplitude?: number; frequency?: number }): FluentGO;
|
|
252
|
+
spin(options?: { speed?: number }): FluentGO;
|
|
253
|
+
|
|
254
|
+
/** Return to FluentScene context */
|
|
255
|
+
end(): FluentScene;
|
|
256
|
+
|
|
257
|
+
/** Access the underlying GameObject */
|
|
258
|
+
readonly goInstance: GameObject;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ==========================================================================
|
|
262
|
+
// FluentLayer Class
|
|
263
|
+
// ==========================================================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Layer builder for organizing GameObjects at different z-levels.
|
|
267
|
+
*/
|
|
268
|
+
export class FluentLayer {
|
|
269
|
+
/** Add a GameObject to this layer */
|
|
270
|
+
go(options?: GameObjectOptions): FluentGO;
|
|
271
|
+
/** Return to parent context */
|
|
272
|
+
end(): FluentScene;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ==========================================================================
|
|
276
|
+
// Sketch API
|
|
277
|
+
// ==========================================================================
|
|
278
|
+
|
|
279
|
+
/** Context passed to sketch update function */
|
|
280
|
+
export interface SketchContext {
|
|
281
|
+
/** All created GameObjects */
|
|
282
|
+
shapes: GameObject[];
|
|
283
|
+
/** Elapsed time in seconds */
|
|
284
|
+
time: number;
|
|
285
|
+
/** Current frame number */
|
|
286
|
+
frame: number;
|
|
287
|
+
/** Canvas width */
|
|
288
|
+
width: number;
|
|
289
|
+
/** Canvas height */
|
|
290
|
+
height: number;
|
|
291
|
+
/** Mouse position */
|
|
292
|
+
mouse: { x: number; y: number };
|
|
293
|
+
/** Named object references */
|
|
294
|
+
refs: Record<string, GameObject>;
|
|
295
|
+
/** Underlying Game instance */
|
|
296
|
+
game: Game;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Ultra-simple sketch API for quick prototypes.
|
|
301
|
+
*/
|
|
302
|
+
export interface SketchAPI {
|
|
303
|
+
// Shapes
|
|
304
|
+
circle(x: number, y: number, r: number, fill?: string): SketchAPI;
|
|
305
|
+
rect(x: number, y: number, w: number, h: number, fill?: string): SketchAPI;
|
|
306
|
+
square(x: number, y: number, size: number, fill?: string): SketchAPI;
|
|
307
|
+
star(x: number, y: number, points: number, r: number, fill?: string): SketchAPI;
|
|
308
|
+
triangle(x: number, y: number, size: number, fill?: string): SketchAPI;
|
|
309
|
+
hexagon(x: number, y: number, r: number, fill?: string): SketchAPI;
|
|
310
|
+
line(x1: number, y1: number, x2: number, y2: number, stroke?: string, lineWidth?: number): SketchAPI;
|
|
311
|
+
ring(x: number, y: number, innerRadius: number, outerRadius: number, fill?: string): SketchAPI;
|
|
312
|
+
text(content: string, x: number, y: number, opts?: { fill?: string; font?: string }): SketchAPI;
|
|
313
|
+
|
|
314
|
+
// Bulk creation
|
|
315
|
+
grid(cols: number, rows: number, spacing: number, shapeFn: (api: SketchAPI, x: number, y: number, col: number, row: number) => void): SketchAPI;
|
|
316
|
+
repeat(count: number, shapeFn: (api: SketchAPI, index: number, total: number) => void): SketchAPI;
|
|
317
|
+
radial(cx: number, cy: number, radius: number, count: number, shapeFn: (api: SketchAPI, x: number, y: number, angle: number, index: number) => void): SketchAPI;
|
|
318
|
+
|
|
319
|
+
// Lifecycle
|
|
320
|
+
setup(fn: (api: SketchAPI) => void): SketchAPI;
|
|
321
|
+
update(fn: (dt: number, ctx: SketchContext) => void): SketchAPI;
|
|
322
|
+
start(): FluentGame;
|
|
323
|
+
|
|
324
|
+
// Accessors
|
|
325
|
+
readonly width: number;
|
|
326
|
+
readonly height: number;
|
|
327
|
+
readonly game: FluentGame | null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ==========================================================================
|
|
331
|
+
// Fluent Context
|
|
332
|
+
// ==========================================================================
|
|
333
|
+
|
|
334
|
+
/** Context object passed to event handlers */
|
|
335
|
+
export interface FluentContext {
|
|
336
|
+
/** Named object references */
|
|
337
|
+
refs: Record<string, GameObject>;
|
|
338
|
+
/** Game state */
|
|
339
|
+
state: Record<string, any>;
|
|
340
|
+
/** All scenes by name */
|
|
341
|
+
scenes: Record<string, Scene>;
|
|
342
|
+
/** Underlying Game instance */
|
|
343
|
+
game: Game;
|
|
344
|
+
/** Canvas width */
|
|
345
|
+
width: number;
|
|
346
|
+
/** Canvas height */
|
|
347
|
+
height: number;
|
|
348
|
+
/** Show a scene */
|
|
349
|
+
showScene: (name: string) => void;
|
|
350
|
+
/** Hide a scene */
|
|
351
|
+
hideScene: (name: string) => void;
|
|
352
|
+
/** Transition between scenes */
|
|
353
|
+
transition: (from: string, to: string, opts?: TransitionOptions) => void;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ==========================================================================
|
|
357
|
+
// Re-exports for convenience
|
|
358
|
+
// ==========================================================================
|
|
359
|
+
|
|
360
|
+
export { Motion, Easing, Tweenetik, Keys, Mouse };
|
|
361
|
+
|