@guinetik/gcanvas 1.0.0 → 1.0.2
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/coordinates.html +698 -0
- package/demos/cube3d.html +23 -0
- package/demos/demos.css +17 -3
- package/demos/dino.html +42 -0
- package/demos/fluid-simple.html +22 -0
- package/demos/fluid.html +37 -0
- package/demos/gameobjects.html +626 -0
- package/demos/index.html +19 -7
- package/demos/js/blob.js +18 -5
- package/demos/js/coordinates.js +840 -0
- package/demos/js/cube3d.js +789 -0
- package/demos/js/dino.js +1420 -0
- package/demos/js/fluid-simple.js +253 -0
- package/demos/js/fluid.js +527 -0
- package/demos/js/gameobjects.js +176 -0
- package/demos/js/plane3d.js +256 -0
- package/demos/js/platformer.js +1579 -0
- package/demos/js/sphere3d.js +229 -0
- package/demos/js/sprite.js +473 -0
- package/demos/js/tde/accretiondisk.js +65 -12
- 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 +24 -8
- package/demos/plane3d.html +24 -0
- package/demos/platformer.html +43 -0
- package/demos/sphere3d.html +24 -0
- package/demos/sprite.html +18 -0
- package/docs/README.md +230 -222
- package/docs/api/FluidSystem.md +173 -0
- package/docs/concepts/architecture-overview.md +204 -204
- package/docs/concepts/coordinate-system.md +384 -0
- package/docs/concepts/rendering-pipeline.md +279 -279
- package/docs/concepts/shapes-vs-gameobjects.md +187 -0
- package/docs/concepts/two-layer-architecture.md +229 -229
- package/docs/fluid-dynamics.md +99 -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/game.js +11 -5
- package/src/game/index.js +2 -1
- package/src/game/objects/index.js +3 -0
- package/src/game/objects/platformer-scene.js +411 -0
- package/src/game/objects/scene.js +14 -0
- package/src/game/objects/sprite.js +529 -0
- package/src/game/pipeline.js +20 -16
- 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 +123 -0
- package/src/game/ui/togglebutton.js +9 -3
- package/src/game/ui/tooltip.js +11 -4
- package/src/io/input.js +75 -45
- package/src/io/mouse.js +44 -19
- package/src/io/touch.js +35 -12
- 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/cube3d.js +599 -0
- package/src/shapes/index.js +3 -0
- package/src/shapes/plane3d.js +687 -0
- package/src/shapes/sphere3d.js +75 -6
- package/src/util/camera2d.js +315 -0
- package/src/util/camera3d.js +218 -12
- package/src/util/index.js +1 -0
- package/src/webgl/shaders/plane-shaders.js +332 -0
- package/src/webgl/shaders/sphere-shaders.js +4 -2
- 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/shapes/sphere3d.js
CHANGED
|
@@ -78,6 +78,9 @@ export class Sphere3D extends Shape {
|
|
|
78
78
|
* @param {boolean} [options.useShader=false] - Use WebGL shader rendering
|
|
79
79
|
* @param {string} [options.shaderType='star'] - Shader type: 'star', 'blackHole', 'rockyPlanet', 'gasGiant'
|
|
80
80
|
* @param {Object} [options.shaderUniforms={}] - Custom shader uniforms
|
|
81
|
+
* @param {number} [options.selfRotationX=0] - Self-rotation around X axis (radians)
|
|
82
|
+
* @param {number} [options.selfRotationY=0] - Self-rotation around Y axis (radians)
|
|
83
|
+
* @param {number} [options.selfRotationZ=0] - Self-rotation around Z axis (radians)
|
|
81
84
|
*/
|
|
82
85
|
constructor(radius, options = {}) {
|
|
83
86
|
super(options);
|
|
@@ -93,6 +96,11 @@ export class Sphere3D extends Shape {
|
|
|
93
96
|
this.shaderUniforms = options.shaderUniforms ?? {};
|
|
94
97
|
this._shaderInitialized = false;
|
|
95
98
|
|
|
99
|
+
// Self-rotation (for Canvas 2D mode - shader uses uRotationSpeed)
|
|
100
|
+
this.selfRotationX = options.selfRotationX ?? 0;
|
|
101
|
+
this.selfRotationY = options.selfRotationY ?? 0;
|
|
102
|
+
this.selfRotationZ = options.selfRotationZ ?? 0;
|
|
103
|
+
|
|
96
104
|
// Generate sphere geometry (for Canvas 2D fallback)
|
|
97
105
|
this._generateGeometry();
|
|
98
106
|
}
|
|
@@ -282,6 +290,48 @@ export class Sphere3D extends Shape {
|
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Apply self-rotation to a point (vertex or normal)
|
|
295
|
+
* @param {number} x - X component
|
|
296
|
+
* @param {number} y - Y component
|
|
297
|
+
* @param {number} z - Z component
|
|
298
|
+
* @returns {{x: number, y: number, z: number}} Rotated point
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_applySelfRotation(x, y, z) {
|
|
302
|
+
// Rotate around Y axis first (most common for spinning objects)
|
|
303
|
+
if (this.selfRotationY !== 0) {
|
|
304
|
+
const cosY = Math.cos(this.selfRotationY);
|
|
305
|
+
const sinY = Math.sin(this.selfRotationY);
|
|
306
|
+
const x1 = x * cosY - z * sinY;
|
|
307
|
+
const z1 = x * sinY + z * cosY;
|
|
308
|
+
x = x1;
|
|
309
|
+
z = z1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Rotate around X axis
|
|
313
|
+
if (this.selfRotationX !== 0) {
|
|
314
|
+
const cosX = Math.cos(this.selfRotationX);
|
|
315
|
+
const sinX = Math.sin(this.selfRotationX);
|
|
316
|
+
const y1 = y * cosX - z * sinX;
|
|
317
|
+
const z1 = y * sinX + z * cosX;
|
|
318
|
+
y = y1;
|
|
319
|
+
z = z1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Rotate around Z axis
|
|
323
|
+
if (this.selfRotationZ !== 0) {
|
|
324
|
+
const cosZ = Math.cos(this.selfRotationZ);
|
|
325
|
+
const sinZ = Math.sin(this.selfRotationZ);
|
|
326
|
+
const x1 = x * cosZ - y * sinZ;
|
|
327
|
+
const y1 = x * sinZ + y * cosZ;
|
|
328
|
+
x = x1;
|
|
329
|
+
y = y1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { x, y, z };
|
|
333
|
+
}
|
|
334
|
+
|
|
285
335
|
/**
|
|
286
336
|
* Calculate lighting intensity based on surface normal
|
|
287
337
|
* @param {number} nx - Normal x component
|
|
@@ -398,18 +448,37 @@ export class Sphere3D extends Shape {
|
|
|
398
448
|
|
|
399
449
|
// Project all vertices and normals through the camera
|
|
400
450
|
// Add position offset so sphere appears at correct world position
|
|
451
|
+
const hasSelfRotation = this.selfRotationX !== 0 || this.selfRotationY !== 0 || this.selfRotationZ !== 0;
|
|
452
|
+
|
|
401
453
|
const projectedVertices = this.vertices.map((v) => {
|
|
454
|
+
// Apply self-rotation to vertex position
|
|
455
|
+
let vx = v.x;
|
|
456
|
+
let vy = v.y;
|
|
457
|
+
let vz = v.z;
|
|
458
|
+
let nx = v.nx;
|
|
459
|
+
let ny = v.ny;
|
|
460
|
+
let nz = v.nz;
|
|
461
|
+
|
|
462
|
+
if (hasSelfRotation) {
|
|
463
|
+
const rotatedPos = this._applySelfRotation(vx, vy, vz);
|
|
464
|
+
vx = rotatedPos.x;
|
|
465
|
+
vy = rotatedPos.y;
|
|
466
|
+
vz = rotatedPos.z;
|
|
467
|
+
|
|
468
|
+
const rotatedNormal = this._applySelfRotation(nx, ny, nz);
|
|
469
|
+
nx = rotatedNormal.x;
|
|
470
|
+
ny = rotatedNormal.y;
|
|
471
|
+
nz = rotatedNormal.z;
|
|
472
|
+
}
|
|
473
|
+
|
|
402
474
|
const projected = this.camera.project(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
475
|
+
vx + (this.x || 0),
|
|
476
|
+
vy + (this.y || 0),
|
|
477
|
+
vz + (this.z || 0)
|
|
406
478
|
);
|
|
407
479
|
|
|
408
480
|
// Rotate normals using the same rotation sequence as Camera3D.project
|
|
409
481
|
// (Z, then Y, then X)
|
|
410
|
-
let nx = v.nx;
|
|
411
|
-
let ny = v.ny;
|
|
412
|
-
let nz = v.nz;
|
|
413
482
|
|
|
414
483
|
// Rotate around Z axis (roll)
|
|
415
484
|
if (this.camera.rotationZ !== 0) {
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Camera2D - 2D camera with smooth following, deadzone, zoom, and bounds
|
|
3
|
+
*
|
|
4
|
+
* Provides viewport management for platformer-style games with smooth
|
|
5
|
+
* target following, configurable deadzone, zoom support, and screen shake.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Basic usage with target following
|
|
9
|
+
* const camera = new Camera2D({
|
|
10
|
+
* target: player,
|
|
11
|
+
* viewportWidth: 800,
|
|
12
|
+
* viewportHeight: 600,
|
|
13
|
+
* lerp: 0.1,
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // With deadzone (player can move freely inside without camera moving)
|
|
17
|
+
* const camera = new Camera2D({
|
|
18
|
+
* target: player,
|
|
19
|
+
* deadzone: { width: 100, height: 50 },
|
|
20
|
+
* bounds: { minX: 0, maxX: 2000, minY: 0, maxY: 600 },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // In update loop
|
|
24
|
+
* camera.update(dt);
|
|
25
|
+
* const offset = camera.getOffset();
|
|
26
|
+
* // Use offset.x, offset.y for rendering
|
|
27
|
+
*/
|
|
28
|
+
export class Camera2D {
|
|
29
|
+
/**
|
|
30
|
+
* Create a new Camera2D instance
|
|
31
|
+
* @param {Object} options - Configuration options
|
|
32
|
+
* @param {Object} [options.target=null] - Target object with x, y to follow
|
|
33
|
+
* @param {Object} [options.deadzone=null] - Deadzone { width, height } where target can move freely
|
|
34
|
+
* @param {number} [options.lerp=0.1] - Smooth follow speed (0-1, higher = snappier)
|
|
35
|
+
* @param {number} [options.zoom=1] - Zoom scale factor
|
|
36
|
+
* @param {Object} [options.bounds=null] - Scroll bounds { minX, maxX, minY, maxY }
|
|
37
|
+
* @param {number} [options.viewportWidth=800] - Viewport width in pixels
|
|
38
|
+
* @param {number} [options.viewportHeight=600] - Viewport height in pixels
|
|
39
|
+
* @param {number} [options.offsetX=0] - Fixed X offset from target
|
|
40
|
+
* @param {number} [options.offsetY=0] - Fixed Y offset from target
|
|
41
|
+
*/
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
/** @type {number} Current camera X position (top-left of viewport in world space) */
|
|
44
|
+
this.x = 0;
|
|
45
|
+
|
|
46
|
+
/** @type {number} Current camera Y position (top-left of viewport in world space) */
|
|
47
|
+
this.y = 0;
|
|
48
|
+
|
|
49
|
+
/** @type {Object|null} Target object with x, y properties to follow */
|
|
50
|
+
this.target = options.target ?? null;
|
|
51
|
+
|
|
52
|
+
/** @type {Object|null} Deadzone dimensions { width, height } */
|
|
53
|
+
this.deadzone = options.deadzone ?? null;
|
|
54
|
+
|
|
55
|
+
/** @type {number} Smooth follow interpolation speed (0-1) */
|
|
56
|
+
this.lerp = options.lerp ?? 0.1;
|
|
57
|
+
|
|
58
|
+
/** @type {number} Zoom scale factor */
|
|
59
|
+
this.zoom = options.zoom ?? 1;
|
|
60
|
+
|
|
61
|
+
/** @type {Object|null} Scroll bounds { minX, maxX, minY, maxY } */
|
|
62
|
+
this.bounds = options.bounds ?? null;
|
|
63
|
+
|
|
64
|
+
/** @type {number} Viewport width in pixels */
|
|
65
|
+
this.viewportWidth = options.viewportWidth ?? 800;
|
|
66
|
+
|
|
67
|
+
/** @type {number} Viewport height in pixels */
|
|
68
|
+
this.viewportHeight = options.viewportHeight ?? 600;
|
|
69
|
+
|
|
70
|
+
/** @type {number} Fixed X offset from target */
|
|
71
|
+
this.offsetX = options.offsetX ?? 0;
|
|
72
|
+
|
|
73
|
+
/** @type {number} Fixed Y offset from target */
|
|
74
|
+
this.offsetY = options.offsetY ?? 0;
|
|
75
|
+
|
|
76
|
+
// Shake state
|
|
77
|
+
/** @private */
|
|
78
|
+
this._shakeIntensity = 0;
|
|
79
|
+
/** @private */
|
|
80
|
+
this._shakeDuration = 0;
|
|
81
|
+
/** @private */
|
|
82
|
+
this._shakeTime = 0;
|
|
83
|
+
/** @private */
|
|
84
|
+
this._shakeOffsetX = 0;
|
|
85
|
+
/** @private */
|
|
86
|
+
this._shakeOffsetY = 0;
|
|
87
|
+
|
|
88
|
+
// Store initial values for reset
|
|
89
|
+
this._initialX = this.x;
|
|
90
|
+
this._initialY = this.y;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Update camera position to follow target
|
|
95
|
+
* @param {number} dt - Delta time in seconds
|
|
96
|
+
*/
|
|
97
|
+
update(dt) {
|
|
98
|
+
if (!this.target) return;
|
|
99
|
+
|
|
100
|
+
// Target center position in world space
|
|
101
|
+
const targetX = this.target.x + this.offsetX;
|
|
102
|
+
const targetY = this.target.y + this.offsetY;
|
|
103
|
+
|
|
104
|
+
// Current camera center position
|
|
105
|
+
const cameraCenterX = this.x + this.viewportWidth / 2;
|
|
106
|
+
const cameraCenterY = this.y + this.viewportHeight / 2;
|
|
107
|
+
|
|
108
|
+
// Calculate desired camera position
|
|
109
|
+
let desiredX = this.x;
|
|
110
|
+
let desiredY = this.y;
|
|
111
|
+
|
|
112
|
+
if (this.deadzone) {
|
|
113
|
+
// Only move camera if target leaves deadzone
|
|
114
|
+
const halfDeadW = this.deadzone.width / 2;
|
|
115
|
+
const halfDeadH = this.deadzone.height / 2;
|
|
116
|
+
|
|
117
|
+
// Check horizontal deadzone
|
|
118
|
+
if (targetX < cameraCenterX - halfDeadW) {
|
|
119
|
+
desiredX = targetX + halfDeadW - this.viewportWidth / 2;
|
|
120
|
+
} else if (targetX > cameraCenterX + halfDeadW) {
|
|
121
|
+
desiredX = targetX - halfDeadW - this.viewportWidth / 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check vertical deadzone
|
|
125
|
+
if (targetY < cameraCenterY - halfDeadH) {
|
|
126
|
+
desiredY = targetY + halfDeadH - this.viewportHeight / 2;
|
|
127
|
+
} else if (targetY > cameraCenterY + halfDeadH) {
|
|
128
|
+
desiredY = targetY - halfDeadH - this.viewportHeight / 2;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// Center on target
|
|
132
|
+
desiredX = targetX - this.viewportWidth / 2;
|
|
133
|
+
desiredY = targetY - this.viewportHeight / 2;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Smooth lerp toward desired position
|
|
137
|
+
this.x += (desiredX - this.x) * this.lerp;
|
|
138
|
+
this.y += (desiredY - this.y) * this.lerp;
|
|
139
|
+
|
|
140
|
+
// Apply bounds constraints
|
|
141
|
+
if (this.bounds) {
|
|
142
|
+
const maxScrollX = this.bounds.maxX - this.viewportWidth;
|
|
143
|
+
const maxScrollY = this.bounds.maxY - this.viewportHeight;
|
|
144
|
+
|
|
145
|
+
this.x = Math.max(this.bounds.minX, Math.min(this.x, maxScrollX));
|
|
146
|
+
this.y = Math.max(this.bounds.minY, Math.min(this.y, maxScrollY));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Update shake effect
|
|
150
|
+
this._updateShake(dt);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Update shake effect
|
|
155
|
+
* @private
|
|
156
|
+
* @param {number} dt - Delta time in seconds
|
|
157
|
+
*/
|
|
158
|
+
_updateShake(dt) {
|
|
159
|
+
if (this._shakeDuration > 0 && this._shakeTime < this._shakeDuration) {
|
|
160
|
+
this._shakeTime += dt;
|
|
161
|
+
const progress = this._shakeTime / this._shakeDuration;
|
|
162
|
+
const decay = 1 - progress; // Linear decay
|
|
163
|
+
|
|
164
|
+
this._shakeOffsetX = (Math.random() - 0.5) * 2 * this._shakeIntensity * decay;
|
|
165
|
+
this._shakeOffsetY = (Math.random() - 0.5) * 2 * this._shakeIntensity * decay;
|
|
166
|
+
|
|
167
|
+
if (this._shakeTime >= this._shakeDuration) {
|
|
168
|
+
this._shakeOffsetX = 0;
|
|
169
|
+
this._shakeOffsetY = 0;
|
|
170
|
+
this._shakeDuration = 0;
|
|
171
|
+
this._shakeTime = 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the camera offset for rendering (includes shake)
|
|
178
|
+
* Override this in subclasses for custom scroll behavior
|
|
179
|
+
* @returns {{x: number, y: number}} Camera offset
|
|
180
|
+
*/
|
|
181
|
+
getOffset() {
|
|
182
|
+
return {
|
|
183
|
+
x: this.x + this._shakeOffsetX,
|
|
184
|
+
y: this.y + this._shakeOffsetY,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set camera position directly (bypasses follow)
|
|
190
|
+
* @param {number} x - X position
|
|
191
|
+
* @param {number} y - Y position
|
|
192
|
+
* @returns {Camera2D} this for chaining
|
|
193
|
+
*/
|
|
194
|
+
setPosition(x, y) {
|
|
195
|
+
this.x = x;
|
|
196
|
+
this.y = y;
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Set new target to follow
|
|
202
|
+
* @param {Object} target - Object with x, y properties
|
|
203
|
+
* @returns {Camera2D} this for chaining
|
|
204
|
+
*/
|
|
205
|
+
setTarget(target) {
|
|
206
|
+
this.target = target;
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Apply camera shake effect
|
|
212
|
+
* @param {number} intensity - Shake amount in pixels
|
|
213
|
+
* @param {number} duration - Shake duration in seconds
|
|
214
|
+
* @returns {Camera2D} this for chaining
|
|
215
|
+
*/
|
|
216
|
+
shake(intensity, duration) {
|
|
217
|
+
this._shakeIntensity = intensity;
|
|
218
|
+
this._shakeDuration = duration;
|
|
219
|
+
this._shakeTime = 0;
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Stop any active shake immediately
|
|
225
|
+
* @returns {Camera2D} this for chaining
|
|
226
|
+
*/
|
|
227
|
+
stopShake() {
|
|
228
|
+
this._shakeIntensity = 0;
|
|
229
|
+
this._shakeDuration = 0;
|
|
230
|
+
this._shakeTime = 0;
|
|
231
|
+
this._shakeOffsetX = 0;
|
|
232
|
+
this._shakeOffsetY = 0;
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if a point/bounds is visible in the viewport
|
|
238
|
+
* @param {Object} bounds - Object with x, y, width, height (world coordinates)
|
|
239
|
+
* @returns {boolean} True if any part of bounds is visible
|
|
240
|
+
*/
|
|
241
|
+
isVisible(bounds) {
|
|
242
|
+
if (!bounds) return false;
|
|
243
|
+
|
|
244
|
+
const cameraRight = this.x + this.viewportWidth;
|
|
245
|
+
const cameraBottom = this.y + this.viewportHeight;
|
|
246
|
+
const boundsRight = bounds.x + (bounds.width || 0);
|
|
247
|
+
const boundsBottom = bounds.y + (bounds.height || 0);
|
|
248
|
+
|
|
249
|
+
// AABB intersection test
|
|
250
|
+
return !(
|
|
251
|
+
bounds.x > cameraRight ||
|
|
252
|
+
boundsRight < this.x ||
|
|
253
|
+
bounds.y > cameraBottom ||
|
|
254
|
+
boundsBottom < this.y
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Convert screen coordinates to world coordinates
|
|
260
|
+
* @param {number} screenX - Screen X position
|
|
261
|
+
* @param {number} screenY - Screen Y position
|
|
262
|
+
* @returns {{x: number, y: number}} World coordinates
|
|
263
|
+
*/
|
|
264
|
+
screenToWorld(screenX, screenY) {
|
|
265
|
+
return {
|
|
266
|
+
x: screenX / this.zoom + this.x,
|
|
267
|
+
y: screenY / this.zoom + this.y,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Convert world coordinates to screen coordinates
|
|
273
|
+
* @param {number} worldX - World X position
|
|
274
|
+
* @param {number} worldY - World Y position
|
|
275
|
+
* @returns {{x: number, y: number}} Screen coordinates
|
|
276
|
+
*/
|
|
277
|
+
worldToScreen(worldX, worldY) {
|
|
278
|
+
return {
|
|
279
|
+
x: (worldX - this.x) * this.zoom,
|
|
280
|
+
y: (worldY - this.y) * this.zoom,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get the camera's world bounds (what's currently visible)
|
|
286
|
+
* @returns {{x: number, y: number, width: number, height: number}}
|
|
287
|
+
*/
|
|
288
|
+
getWorldBounds() {
|
|
289
|
+
return {
|
|
290
|
+
x: this.x,
|
|
291
|
+
y: this.y,
|
|
292
|
+
width: this.viewportWidth / this.zoom,
|
|
293
|
+
height: this.viewportHeight / this.zoom,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Reset camera to initial position and clear shake
|
|
299
|
+
* @returns {Camera2D} this for chaining
|
|
300
|
+
*/
|
|
301
|
+
reset() {
|
|
302
|
+
this.x = this._initialX;
|
|
303
|
+
this.y = this._initialY;
|
|
304
|
+
this.stopShake();
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if camera is currently shaking
|
|
310
|
+
* @returns {boolean}
|
|
311
|
+
*/
|
|
312
|
+
isShaking() {
|
|
313
|
+
return this._shakeDuration > 0 && this._shakeTime < this._shakeDuration;
|
|
314
|
+
}
|
|
315
|
+
}
|