pixospritz-core 0.10.1 → 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.
Files changed (157) hide show
  1. package/README.md +36 -286
  2. package/dist/bundle.js +13 -3
  3. package/dist/bundle.js.map +1 -1
  4. package/dist/style.css +1 -0
  5. package/package.json +43 -44
  6. package/src/components/WebGLView.jsx +318 -0
  7. package/src/css/pixos.css +372 -0
  8. package/src/engine/actions/animate.js +41 -0
  9. package/src/engine/actions/changezone.js +135 -0
  10. package/src/engine/actions/chat.js +109 -0
  11. package/src/engine/actions/dialogue.js +90 -0
  12. package/src/engine/actions/face.js +22 -0
  13. package/src/engine/actions/greeting.js +28 -0
  14. package/src/engine/actions/interact.js +86 -0
  15. package/src/engine/actions/move.js +67 -0
  16. package/src/engine/actions/patrol.js +109 -0
  17. package/src/engine/actions/prompt.js +185 -0
  18. package/src/engine/actions/script.js +42 -0
  19. package/src/engine/core/audio/AudioSystem.js +543 -0
  20. package/src/engine/core/cutscene/PxcPlayer.js +956 -0
  21. package/src/engine/core/cutscene/manager.js +243 -0
  22. package/src/engine/core/database/index.js +75 -0
  23. package/src/engine/core/debug/index.js +371 -0
  24. package/src/engine/core/hud/index.js +765 -0
  25. package/src/engine/core/index.js +540 -0
  26. package/src/engine/core/input/gamepad/Controller.js +71 -0
  27. package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
  28. package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
  29. package/src/engine/core/input/gamepad/index.js +592 -0
  30. package/src/engine/core/input/keyboard.js +196 -0
  31. package/src/engine/core/input/manager.js +485 -0
  32. package/src/engine/core/input/mouse.js +203 -0
  33. package/src/engine/core/input/touch.js +175 -0
  34. package/src/engine/core/mode/manager.js +199 -0
  35. package/src/engine/core/net/manager.js +535 -0
  36. package/src/engine/core/queue/action.js +83 -0
  37. package/src/engine/core/queue/event.js +82 -0
  38. package/src/engine/core/queue/index.js +44 -0
  39. package/src/engine/core/queue/loadable.js +33 -0
  40. package/src/engine/core/render/CameraEffects.js +494 -0
  41. package/src/engine/core/render/FrustumCuller.js +417 -0
  42. package/src/engine/core/render/LODManager.js +285 -0
  43. package/src/engine/core/render/ParticleManager.js +529 -0
  44. package/src/engine/core/render/TextureAtlas.js +465 -0
  45. package/src/engine/core/render/camera.js +338 -0
  46. package/src/engine/core/render/light.js +197 -0
  47. package/src/engine/core/render/manager.js +1079 -0
  48. package/src/engine/core/render/shaders.js +110 -0
  49. package/src/engine/core/render/skybox.js +342 -0
  50. package/src/engine/core/resource/manager.js +133 -0
  51. package/src/engine/core/resource/object.js +611 -0
  52. package/src/engine/core/resource/texture.js +103 -0
  53. package/src/engine/core/resource/tileset.js +177 -0
  54. package/src/engine/core/scene/avatar.js +215 -0
  55. package/src/engine/core/scene/speech.js +138 -0
  56. package/src/engine/core/scene/sprite.js +702 -0
  57. package/src/engine/core/scene/spritz.js +189 -0
  58. package/src/engine/core/scene/world.js +681 -0
  59. package/src/engine/core/scene/zone.js +1167 -0
  60. package/src/engine/core/store/index.js +110 -0
  61. package/src/engine/dynamic/animatedSprite.js +64 -0
  62. package/src/engine/dynamic/animatedTile.js +98 -0
  63. package/src/engine/dynamic/avatar.js +110 -0
  64. package/src/engine/dynamic/map.js +174 -0
  65. package/src/engine/dynamic/sprite.js +255 -0
  66. package/src/engine/dynamic/spritz.js +119 -0
  67. package/src/engine/events/EventSystem.js +609 -0
  68. package/src/engine/events/camera.js +142 -0
  69. package/src/engine/events/chat.js +75 -0
  70. package/src/engine/events/menu.js +186 -0
  71. package/src/engine/scripting/CallbackManager.js +514 -0
  72. package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
  73. package/src/engine/scripting/PixoScriptLibrary.js +704 -0
  74. package/src/engine/shaders/effects/index.js +450 -0
  75. package/src/engine/shaders/fs.js +222 -0
  76. package/src/engine/shaders/particles/fs.js +41 -0
  77. package/src/engine/shaders/particles/vs.js +61 -0
  78. package/src/engine/shaders/picker/fs.js +34 -0
  79. package/src/engine/shaders/picker/init.js +62 -0
  80. package/src/engine/shaders/picker/vs.js +42 -0
  81. package/src/engine/shaders/pxsl/README.md +250 -0
  82. package/src/engine/shaders/pxsl/index.js +25 -0
  83. package/src/engine/shaders/pxsl/library.js +608 -0
  84. package/src/engine/shaders/pxsl/manager.js +338 -0
  85. package/src/engine/shaders/pxsl/specification.js +363 -0
  86. package/src/engine/shaders/pxsl/transpiler.js +753 -0
  87. package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
  88. package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
  89. package/src/engine/shaders/skybox/matrix/fs.js +127 -0
  90. package/src/engine/shaders/skybox/matrix/vs.js +23 -0
  91. package/src/engine/shaders/skybox/morning/fs.js +109 -0
  92. package/src/engine/shaders/skybox/morning/vs.js +23 -0
  93. package/src/engine/shaders/skybox/neon/fs.js +119 -0
  94. package/src/engine/shaders/skybox/neon/vs.js +23 -0
  95. package/src/engine/shaders/skybox/sky/fs.js +114 -0
  96. package/src/engine/shaders/skybox/sky/vs.js +23 -0
  97. package/src/engine/shaders/skybox/sunset/fs.js +101 -0
  98. package/src/engine/shaders/skybox/sunset/vs.js +23 -0
  99. package/src/engine/shaders/transition/blur/fs.js +42 -0
  100. package/src/engine/shaders/transition/blur/vs.js +26 -0
  101. package/src/engine/shaders/transition/cross/fs.js +36 -0
  102. package/src/engine/shaders/transition/cross/vs.js +26 -0
  103. package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
  104. package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
  105. package/src/engine/shaders/transition/dissolve/fs.js +78 -0
  106. package/src/engine/shaders/transition/dissolve/vs.js +24 -0
  107. package/src/engine/shaders/transition/fade/fs.js +31 -0
  108. package/src/engine/shaders/transition/fade/vs.js +27 -0
  109. package/src/engine/shaders/transition/iris/fs.js +52 -0
  110. package/src/engine/shaders/transition/iris/vs.js +24 -0
  111. package/src/engine/shaders/transition/pixelate/fs.js +44 -0
  112. package/src/engine/shaders/transition/pixelate/vs.js +24 -0
  113. package/src/engine/shaders/transition/slide/fs.js +53 -0
  114. package/src/engine/shaders/transition/slide/vs.js +24 -0
  115. package/src/engine/shaders/transition/swirl/fs.js +39 -0
  116. package/src/engine/shaders/transition/swirl/vs.js +26 -0
  117. package/src/engine/shaders/transition/wipe/fs.js +50 -0
  118. package/src/engine/shaders/transition/wipe/vs.js +24 -0
  119. package/src/engine/shaders/vs.js +60 -0
  120. package/src/engine/utils/CameraController.js +506 -0
  121. package/src/engine/utils/ObjHelper.js +551 -0
  122. package/src/engine/utils/debug-logger.js +110 -0
  123. package/src/engine/utils/enums.js +305 -0
  124. package/src/engine/utils/generator.js +156 -0
  125. package/src/engine/utils/index.js +21 -0
  126. package/src/engine/utils/loaders/ActionLoader.js +77 -0
  127. package/src/engine/utils/loaders/AudioLoader.js +157 -0
  128. package/src/engine/utils/loaders/EventLoader.js +66 -0
  129. package/src/engine/utils/loaders/ObjectLoader.js +67 -0
  130. package/src/engine/utils/loaders/SpriteLoader.js +77 -0
  131. package/src/engine/utils/loaders/TilesetLoader.js +103 -0
  132. package/src/engine/utils/loaders/index.js +21 -0
  133. package/src/engine/utils/math/matrix4.js +367 -0
  134. package/src/engine/utils/math/vector.js +458 -0
  135. package/src/engine/utils/obj/_old_js/index.js +46 -0
  136. package/src/engine/utils/obj/_old_js/layout.js +308 -0
  137. package/src/engine/utils/obj/_old_js/material.js +711 -0
  138. package/src/engine/utils/obj/_old_js/mesh.js +761 -0
  139. package/src/engine/utils/obj/_old_js/utils.js +647 -0
  140. package/src/engine/utils/obj/index.js +24 -0
  141. package/src/engine/utils/obj/js/index.js +277 -0
  142. package/src/engine/utils/obj/js/loader.js +232 -0
  143. package/src/engine/utils/obj/layout.js +246 -0
  144. package/src/engine/utils/obj/material.js +665 -0
  145. package/src/engine/utils/obj/mesh.js +657 -0
  146. package/src/engine/utils/obj/ts/index.ts +72 -0
  147. package/src/engine/utils/obj/ts/layout.ts +265 -0
  148. package/src/engine/utils/obj/ts/material.ts +760 -0
  149. package/src/engine/utils/obj/ts/mesh.ts +785 -0
  150. package/src/engine/utils/obj/ts/utils.ts +501 -0
  151. package/src/engine/utils/obj/utils.js +428 -0
  152. package/src/engine/utils/resources.js +18 -0
  153. package/src/index.jsx +55 -0
  154. package/src/spritz/player.js +18 -0
  155. package/src/spritz/readme.md +18 -0
  156. package/LICENSE +0 -437
  157. package/dist/bundle.js.LICENSE.txt +0 -31
@@ -0,0 +1,44 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ /**
15
+ * ActionQueue - Manages a queue of actions or events for sequential execution.
16
+ */
17
+ export default class ActionQueue {
18
+ /**
19
+ * Creates an instance of ActionQueue.
20
+ */
21
+ constructor() {
22
+ /** @type {Array<Function>} */
23
+ this.actions = [];
24
+ }
25
+
26
+ /**
27
+ * Adds an action to the queue.
28
+ * @param {Function} action - The action function to add.
29
+ */
30
+ add(action) {
31
+ this.actions.push(action);
32
+ }
33
+
34
+ /**
35
+ * Runs all actions in the queue, filtering out completed ones.
36
+ * @param {...any} args - Arguments to pass to each action.
37
+ */
38
+ run() {
39
+ let args = arguments;
40
+ this.actions = this.actions.filter((action) => {
41
+ return action(args);
42
+ });
43
+ }
44
+ }
@@ -0,0 +1,33 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+ /**
14
+ * Loadable - Base class for objects that can be loaded asynchronously, with support for queuing actions until loaded.
15
+ */
16
+ export default class Loadable {
17
+ /**
18
+ * Runs an action immediately if loaded, otherwise adds it to the onLoadActions queue.
19
+ * @param {Function} action - The action to run or queue.
20
+ */
21
+ runWhenLoaded(action) {
22
+ if (this.loaded) action();
23
+ else this.onLoadActions.add(action);
24
+ }
25
+
26
+ /**
27
+ * Updates the object by assigning new properties.
28
+ * @param {*} data - The data object to merge into this instance.
29
+ */
30
+ update(data) {
31
+ Object.assign(this, data);
32
+ }
33
+ }
@@ -0,0 +1,494 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ /**
15
+ * CameraEffects - Visual effects system for the camera.
16
+ * Includes screen shake, smooth follow, and other camera-based effects.
17
+ */
18
+
19
+ import { Vector } from '../../utils/math/vector.js';
20
+
21
+ /**
22
+ * Easing functions for smooth animations
23
+ */
24
+ const Easing = {
25
+ linear: t => t,
26
+ easeInQuad: t => t * t,
27
+ easeOutQuad: t => t * (2 - t),
28
+ easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
29
+ easeInCubic: t => t * t * t,
30
+ easeOutCubic: t => (--t) * t * t + 1,
31
+ easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
32
+ easeOutElastic: t => {
33
+ const p = 0.3;
34
+ return Math.pow(2, -10 * t) * Math.sin((t - p / 4) * (2 * Math.PI) / p) + 1;
35
+ },
36
+ easeOutBounce: t => {
37
+ if (t < 1 / 2.75) return 7.5625 * t * t;
38
+ if (t < 2 / 2.75) return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
39
+ if (t < 2.5 / 2.75) return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
40
+ return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
41
+ },
42
+ };
43
+
44
+ /**
45
+ * CameraEffects - Manages camera visual effects
46
+ */
47
+ export default class CameraEffects {
48
+ /**
49
+ * @param {Object} camera - Reference to the Camera instance
50
+ */
51
+ constructor(camera) {
52
+ this.camera = camera;
53
+
54
+ // Screen shake state
55
+ this.shake = {
56
+ active: false,
57
+ intensity: 0,
58
+ decay: 0.9,
59
+ duration: 0,
60
+ elapsed: 0,
61
+ offset: new Vector(0, 0, 0),
62
+ type: 'random', // 'random', 'horizontal', 'vertical', 'rotational'
63
+ frequency: 1,
64
+ };
65
+
66
+ // Smooth follow state
67
+ this.follow = {
68
+ active: false,
69
+ target: null,
70
+ offset: new Vector(0, 0, 0),
71
+ smoothness: 0.1, // 0-1, lower = smoother
72
+ deadzone: new Vector(0, 0, 0),
73
+ bounds: null, // { min: Vector, max: Vector }
74
+ };
75
+
76
+ // Zoom effect state
77
+ this.zoom = {
78
+ active: false,
79
+ targetZoom: 1,
80
+ currentZoom: 1,
81
+ speed: 0.1,
82
+ };
83
+
84
+ // Flash effect state
85
+ this.flash = {
86
+ active: false,
87
+ color: [1, 1, 1, 1],
88
+ duration: 0,
89
+ elapsed: 0,
90
+ };
91
+
92
+ // Fade effect state
93
+ this.fade = {
94
+ active: false,
95
+ color: [0, 0, 0, 1],
96
+ targetAlpha: 0,
97
+ currentAlpha: 0,
98
+ duration: 0,
99
+ elapsed: 0,
100
+ easing: 'easeInOutQuad',
101
+ onComplete: null,
102
+ };
103
+
104
+ // Punch effect (quick offset then return)
105
+ this.punch = {
106
+ active: false,
107
+ direction: new Vector(0, 0, 0),
108
+ intensity: 0,
109
+ duration: 0,
110
+ elapsed: 0,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Update all active effects (call once per frame)
116
+ * @param {number} deltaTime - Time since last frame in seconds
117
+ */
118
+ update(deltaTime) {
119
+ this._updateShake(deltaTime);
120
+ this._updateFollow(deltaTime);
121
+ this._updateZoom(deltaTime);
122
+ this._updateFlash(deltaTime);
123
+ this._updateFade(deltaTime);
124
+ this._updatePunch(deltaTime);
125
+ }
126
+
127
+ // ===================== SCREEN SHAKE =====================
128
+
129
+ /**
130
+ * Start a screen shake effect
131
+ * @param {Object} options - Shake configuration
132
+ * @param {number} options.intensity - Shake intensity (pixels/units)
133
+ * @param {number} options.duration - Duration in seconds
134
+ * @param {number} [options.decay=0.9] - Decay rate per frame (0-1)
135
+ * @param {string} [options.type='random'] - Shake type
136
+ * @param {number} [options.frequency=1] - Shake frequency multiplier
137
+ */
138
+ startShake(options) {
139
+ this.shake.active = true;
140
+ this.shake.intensity = options.intensity || 1;
141
+ this.shake.duration = options.duration || 0.5;
142
+ this.shake.elapsed = 0;
143
+ this.shake.decay = options.decay ?? 0.9;
144
+ this.shake.type = options.type || 'random';
145
+ this.shake.frequency = options.frequency || 1;
146
+ }
147
+
148
+ /**
149
+ * Stop the current screen shake
150
+ */
151
+ stopShake() {
152
+ this.shake.active = false;
153
+ this.shake.offset = new Vector(0, 0, 0);
154
+ }
155
+
156
+ /**
157
+ * Get current shake offset (apply to camera position)
158
+ * @returns {Vector}
159
+ */
160
+ getShakeOffset() {
161
+ return this.shake.offset;
162
+ }
163
+
164
+ _updateShake(deltaTime) {
165
+ if (!this.shake.active) return;
166
+
167
+ this.shake.elapsed += deltaTime;
168
+
169
+ if (this.shake.elapsed >= this.shake.duration) {
170
+ this.stopShake();
171
+ return;
172
+ }
173
+
174
+ // Calculate current intensity with decay
175
+ const progress = this.shake.elapsed / this.shake.duration;
176
+ const currentIntensity = this.shake.intensity * (1 - progress) * this.shake.decay;
177
+
178
+ // Generate shake offset based on type
179
+ const time = this.shake.elapsed * this.shake.frequency * 10;
180
+
181
+ switch (this.shake.type) {
182
+ case 'horizontal':
183
+ this.shake.offset = new Vector(
184
+ (Math.sin(time) * 2 - 1) * currentIntensity,
185
+ 0,
186
+ 0
187
+ );
188
+ break;
189
+ case 'vertical':
190
+ this.shake.offset = new Vector(
191
+ 0,
192
+ (Math.sin(time) * 2 - 1) * currentIntensity,
193
+ 0
194
+ );
195
+ break;
196
+ case 'rotational':
197
+ // Apply rotation shake (stored as z offset for now)
198
+ this.shake.offset = new Vector(
199
+ 0,
200
+ 0,
201
+ (Math.sin(time) * 2 - 1) * currentIntensity * 0.1
202
+ );
203
+ break;
204
+ case 'random':
205
+ default:
206
+ this.shake.offset = new Vector(
207
+ (Math.random() * 2 - 1) * currentIntensity,
208
+ (Math.random() * 2 - 1) * currentIntensity,
209
+ 0
210
+ );
211
+ break;
212
+ }
213
+ }
214
+
215
+ // ===================== SMOOTH FOLLOW =====================
216
+
217
+ /**
218
+ * Start smooth camera follow
219
+ * @param {Object} target - Object to follow (must have position property)
220
+ * @param {Object} options - Follow configuration
221
+ * @param {Vector} [options.offset] - Offset from target
222
+ * @param {number} [options.smoothness=0.1] - Follow smoothness (0-1)
223
+ * @param {Vector} [options.deadzone] - Deadzone before camera moves
224
+ * @param {{min: Vector, max: Vector}} [options.bounds] - Camera bounds
225
+ */
226
+ startFollow(target, options = {}) {
227
+ this.follow.active = true;
228
+ this.follow.target = target;
229
+ this.follow.offset = options.offset || new Vector(0, 0, 0);
230
+ this.follow.smoothness = options.smoothness ?? 0.1;
231
+ this.follow.deadzone = options.deadzone || new Vector(0, 0, 0);
232
+ this.follow.bounds = options.bounds || null;
233
+ }
234
+
235
+ /**
236
+ * Stop following
237
+ */
238
+ stopFollow() {
239
+ this.follow.active = false;
240
+ this.follow.target = null;
241
+ }
242
+
243
+ /**
244
+ * Get the target position for smooth follow
245
+ * @returns {Vector|null}
246
+ */
247
+ getFollowTarget() {
248
+ if (!this.follow.active || !this.follow.target) return null;
249
+
250
+ const targetPos = this.follow.target.position || this.follow.target;
251
+ return targetPos.add ? targetPos.add(this.follow.offset) :
252
+ new Vector(targetPos.x + this.follow.offset.x,
253
+ targetPos.y + this.follow.offset.y,
254
+ targetPos.z + this.follow.offset.z);
255
+ }
256
+
257
+ _updateFollow(deltaTime) {
258
+ if (!this.follow.active || !this.follow.target) return;
259
+
260
+ const targetPos = this.getFollowTarget();
261
+ if (!targetPos) return;
262
+
263
+ const currentPos = this.camera.cameraTarget || this.camera.cameraPosition;
264
+
265
+ // Calculate difference
266
+ let dx = targetPos.x - currentPos.x;
267
+ let dy = targetPos.y - currentPos.y;
268
+ let dz = targetPos.z - currentPos.z;
269
+
270
+ // Apply deadzone
271
+ if (Math.abs(dx) < this.follow.deadzone.x) dx = 0;
272
+ if (Math.abs(dy) < this.follow.deadzone.y) dy = 0;
273
+ if (Math.abs(dz) < this.follow.deadzone.z) dz = 0;
274
+
275
+ // Smooth interpolation
276
+ const smoothFactor = 1 - Math.pow(1 - this.follow.smoothness, deltaTime * 60);
277
+
278
+ let newX = currentPos.x + dx * smoothFactor;
279
+ let newY = currentPos.y + dy * smoothFactor;
280
+ let newZ = currentPos.z + dz * smoothFactor;
281
+
282
+ // Apply bounds
283
+ if (this.follow.bounds) {
284
+ newX = Math.max(this.follow.bounds.min.x, Math.min(this.follow.bounds.max.x, newX));
285
+ newY = Math.max(this.follow.bounds.min.y, Math.min(this.follow.bounds.max.y, newY));
286
+ newZ = Math.max(this.follow.bounds.min.z, Math.min(this.follow.bounds.max.z, newZ));
287
+ }
288
+
289
+ // Update camera target
290
+ if (this.camera.setTarget) {
291
+ this.camera.setTarget(new Vector(newX, newY, newZ));
292
+ } else {
293
+ this.camera.cameraTarget = new Vector(newX, newY, newZ);
294
+ }
295
+ }
296
+
297
+ // ===================== ZOOM EFFECT =====================
298
+
299
+ /**
300
+ * Smoothly zoom to a target level
301
+ * @param {number} targetZoom - Target zoom level
302
+ * @param {number} [speed=0.1] - Zoom speed
303
+ */
304
+ zoomTo(targetZoom, speed = 0.1) {
305
+ this.zoom.active = true;
306
+ this.zoom.targetZoom = targetZoom;
307
+ this.zoom.speed = speed;
308
+ }
309
+
310
+ _updateZoom(deltaTime) {
311
+ if (!this.zoom.active) return;
312
+
313
+ const diff = this.zoom.targetZoom - this.zoom.currentZoom;
314
+ if (Math.abs(diff) < 0.001) {
315
+ this.zoom.currentZoom = this.zoom.targetZoom;
316
+ this.zoom.active = false;
317
+ return;
318
+ }
319
+
320
+ this.zoom.currentZoom += diff * this.zoom.speed * deltaTime * 60;
321
+
322
+ // Apply zoom to camera
323
+ if (this.camera.zoom) {
324
+ this.camera.cameraDistance = 15 / this.zoom.currentZoom;
325
+ this.camera.updateViewFromAngles?.();
326
+ }
327
+ }
328
+
329
+ // ===================== FLASH EFFECT =====================
330
+
331
+ /**
332
+ * Flash the screen with a color
333
+ * @param {Object} options - Flash configuration
334
+ * @param {number[]} [options.color=[1,1,1,1]] - RGBA color
335
+ * @param {number} [options.duration=0.1] - Duration in seconds
336
+ */
337
+ flash(options = {}) {
338
+ this.flash.active = true;
339
+ this.flash.color = options.color || [1, 1, 1, 1];
340
+ this.flash.duration = options.duration || 0.1;
341
+ this.flash.elapsed = 0;
342
+ }
343
+
344
+ /**
345
+ * Get flash overlay color (apply as post-process)
346
+ * @returns {number[]|null} RGBA color or null if not flashing
347
+ */
348
+ getFlashColor() {
349
+ if (!this.flash.active) return null;
350
+
351
+ const progress = this.flash.elapsed / this.flash.duration;
352
+ const alpha = this.flash.color[3] * (1 - progress);
353
+ return [this.flash.color[0], this.flash.color[1], this.flash.color[2], alpha];
354
+ }
355
+
356
+ _updateFlash(deltaTime) {
357
+ if (!this.flash.active) return;
358
+
359
+ this.flash.elapsed += deltaTime;
360
+ if (this.flash.elapsed >= this.flash.duration) {
361
+ this.flash.active = false;
362
+ }
363
+ }
364
+
365
+ // ===================== FADE EFFECT =====================
366
+
367
+ /**
368
+ * Fade the screen to/from a color
369
+ * @param {Object} options - Fade configuration
370
+ * @param {number} options.targetAlpha - Target alpha (0 = transparent, 1 = opaque)
371
+ * @param {number} [options.duration=1] - Duration in seconds
372
+ * @param {number[]} [options.color=[0,0,0,1]] - RGB color
373
+ * @param {string} [options.easing='easeInOutQuad'] - Easing function
374
+ * @param {Function} [options.onComplete] - Callback when complete
375
+ */
376
+ fadeTo(options) {
377
+ this.fade.active = true;
378
+ this.fade.targetAlpha = options.targetAlpha;
379
+ this.fade.duration = options.duration || 1;
380
+ this.fade.elapsed = 0;
381
+ this.fade.color = options.color || [0, 0, 0, 1];
382
+ this.fade.easing = options.easing || 'easeInOutQuad';
383
+ this.fade.onComplete = options.onComplete || null;
384
+ this.fade.startAlpha = this.fade.currentAlpha;
385
+ }
386
+
387
+ /**
388
+ * Convenience method for fade to black
389
+ */
390
+ fadeToBlack(duration = 1, onComplete = null) {
391
+ this.fadeTo({ targetAlpha: 1, duration, color: [0, 0, 0, 1], onComplete });
392
+ }
393
+
394
+ /**
395
+ * Convenience method for fade from black
396
+ */
397
+ fadeFromBlack(duration = 1, onComplete = null) {
398
+ this.fade.currentAlpha = 1;
399
+ this.fadeTo({ targetAlpha: 0, duration, color: [0, 0, 0, 1], onComplete });
400
+ }
401
+
402
+ /**
403
+ * Get fade overlay color
404
+ * @returns {number[]|null}
405
+ */
406
+ getFadeColor() {
407
+ if (this.fade.currentAlpha <= 0) return null;
408
+ return [this.fade.color[0], this.fade.color[1], this.fade.color[2], this.fade.currentAlpha];
409
+ }
410
+
411
+ _updateFade(deltaTime) {
412
+ if (!this.fade.active) return;
413
+
414
+ this.fade.elapsed += deltaTime;
415
+
416
+ if (this.fade.elapsed >= this.fade.duration) {
417
+ this.fade.currentAlpha = this.fade.targetAlpha;
418
+ this.fade.active = false;
419
+ if (this.fade.onComplete) {
420
+ this.fade.onComplete();
421
+ }
422
+ return;
423
+ }
424
+
425
+ const progress = this.fade.elapsed / this.fade.duration;
426
+ const easingFn = Easing[this.fade.easing] || Easing.linear;
427
+ const easedProgress = easingFn(progress);
428
+
429
+ const startAlpha = this.fade.startAlpha ?? 0;
430
+ this.fade.currentAlpha = startAlpha + (this.fade.targetAlpha - startAlpha) * easedProgress;
431
+ }
432
+
433
+ // ===================== PUNCH EFFECT =====================
434
+
435
+ /**
436
+ * Quick punch effect (offset then return)
437
+ * @param {Vector} direction - Punch direction
438
+ * @param {number} intensity - Punch intensity
439
+ * @param {number} duration - Duration in seconds
440
+ */
441
+ punch(direction, intensity = 1, duration = 0.2) {
442
+ this.punch.active = true;
443
+ this.punch.direction = direction.normal ? direction.normal() : direction;
444
+ this.punch.intensity = intensity;
445
+ this.punch.duration = duration;
446
+ this.punch.elapsed = 0;
447
+ }
448
+
449
+ /**
450
+ * Get punch offset (apply to camera position)
451
+ * @returns {Vector}
452
+ */
453
+ getPunchOffset() {
454
+ if (!this.punch.active) return new Vector(0, 0, 0);
455
+
456
+ const progress = this.punch.elapsed / this.punch.duration;
457
+ const easedProgress = Easing.easeOutElastic(progress);
458
+ const currentIntensity = this.punch.intensity * (1 - easedProgress);
459
+
460
+ return new Vector(
461
+ this.punch.direction.x * currentIntensity,
462
+ this.punch.direction.y * currentIntensity,
463
+ this.punch.direction.z * currentIntensity
464
+ );
465
+ }
466
+
467
+ _updatePunch(deltaTime) {
468
+ if (!this.punch.active) return;
469
+
470
+ this.punch.elapsed += deltaTime;
471
+ if (this.punch.elapsed >= this.punch.duration) {
472
+ this.punch.active = false;
473
+ }
474
+ }
475
+
476
+ // ===================== COMBINED OFFSET =====================
477
+
478
+ /**
479
+ * Get combined camera offset from all effects
480
+ * @returns {Vector}
481
+ */
482
+ getTotalOffset() {
483
+ const shake = this.getShakeOffset();
484
+ const punch = this.getPunchOffset();
485
+
486
+ return new Vector(
487
+ shake.x + punch.x,
488
+ shake.y + punch.y,
489
+ shake.z + punch.z
490
+ );
491
+ }
492
+ }
493
+
494
+ export { Easing };