@series-inc/rundot-3d-engine 0.3.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 (38) hide show
  1. package/LICENSE.txt +6 -0
  2. package/README.md +80 -0
  3. package/dist/ComponentRegistry-V_7WauAE.d.ts +448 -0
  4. package/dist/chunk-ZNDJR3RD.js +5623 -0
  5. package/dist/chunk-ZNDJR3RD.js.map +1 -0
  6. package/dist/index.d.ts +1484 -0
  7. package/dist/index.js +1390 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/systems/index.d.ts +3356 -0
  10. package/dist/systems/index.js +9652 -0
  11. package/dist/systems/index.js.map +1 -0
  12. package/docs/core/Component.md +321 -0
  13. package/docs/core/GameObject.md +204 -0
  14. package/docs/core/VenusGame.md +316 -0
  15. package/docs/patterns/ComponentCommunication.md +337 -0
  16. package/docs/patterns/CreatingGameObjects.md +290 -0
  17. package/docs/patterns/MeshColliders.md +338 -0
  18. package/docs/patterns/MeshLoading.md +316 -0
  19. package/docs/physics/Colliders.md +249 -0
  20. package/docs/physics/PhysicsSystem.md +151 -0
  21. package/docs/physics/RigidBodyComponent.md +201 -0
  22. package/docs/rendering/AssetManager.md +308 -0
  23. package/docs/rendering/InstancedRenderer.md +286 -0
  24. package/docs/rendering/MeshRenderer.md +286 -0
  25. package/docs/rendering/SkeletalRenderer.md +308 -0
  26. package/docs/systems/AnimationSystem.md +75 -0
  27. package/docs/systems/AudioSystem.md +79 -0
  28. package/docs/systems/InputManager.md +101 -0
  29. package/docs/systems/LightingSystem.md +101 -0
  30. package/docs/systems/NavigationSystem.md +246 -0
  31. package/docs/systems/ParticleSystem.md +44 -0
  32. package/docs/systems/PrefabSystem.md +60 -0
  33. package/docs/systems/SplineSystem.md +194 -0
  34. package/docs/systems/StowKitSystem.md +77 -0
  35. package/docs/systems/TweenSystem.md +132 -0
  36. package/docs/systems/UISystem.md +73 -0
  37. package/package.json +62 -0
  38. package/scripts/postinstall.mjs +51 -0
package/dist/index.js ADDED
@@ -0,0 +1,1390 @@
1
+ import {
2
+ AssetManager,
3
+ Component,
4
+ ComponentUpdater,
5
+ GameObject,
6
+ InstancedMeshManager,
7
+ PrefabComponent,
8
+ PrefabInstance,
9
+ RigidBodyComponentThree,
10
+ SkeletonCache,
11
+ StowKitSystem,
12
+ VenusGame,
13
+ __decorateClass
14
+ } from "./chunk-ZNDJR3RD.js";
15
+
16
+ // src/engine/interaction/InteractionZone.ts
17
+ import * as THREE from "three";
18
+ var InteractionZone = class extends Component {
19
+ id;
20
+ active = true;
21
+ onEnterCallback;
22
+ onExitCallback;
23
+ entitiesInZone = /* @__PURE__ */ new Set();
24
+ rigidBody = null;
25
+ visualMesh = null;
26
+ options;
27
+ constructor(onEnter, onExit, options = {}) {
28
+ super();
29
+ this.id = `interaction_${Math.random().toString(36).substr(2, 9)}`;
30
+ this.onEnterCallback = onEnter;
31
+ this.onExitCallback = onExit;
32
+ this.options = {
33
+ width: 2,
34
+ depth: 2,
35
+ active: true,
36
+ show: true,
37
+ ...options
38
+ };
39
+ this.active = this.options.active ?? true;
40
+ }
41
+ /**
42
+ * Set interaction callbacks
43
+ */
44
+ setCallbacks(callbacks) {
45
+ this.onEnterCallback = callbacks.onEnter;
46
+ this.onExitCallback = callbacks.onExit;
47
+ }
48
+ /**
49
+ * Called when component is attached to GameObject
50
+ */
51
+ onCreate() {
52
+ if (this.options.show) {
53
+ this.createVisualMesh();
54
+ }
55
+ this.createTriggerCollider();
56
+ this.setActive(this.active);
57
+ }
58
+ onEnabled() {
59
+ }
60
+ /**
61
+ * Create the visual mesh for the interaction zone
62
+ */
63
+ createVisualMesh() {
64
+ const width = this.options.width;
65
+ const height = 0.1;
66
+ const depth = this.options.depth;
67
+ const geometry = new THREE.BoxGeometry(width, height, depth);
68
+ const material = new THREE.MeshBasicMaterial({
69
+ color: 0,
70
+ // Black like the original
71
+ transparent: true,
72
+ opacity: 0.15,
73
+ // Original opacity
74
+ side: THREE.DoubleSide
75
+ });
76
+ this.visualMesh = new THREE.Mesh(geometry, material);
77
+ this.gameObject.add(this.visualMesh);
78
+ this.visualMesh.position.y += 0.1;
79
+ }
80
+ /**
81
+ * Create the physics trigger collider
82
+ */
83
+ createTriggerCollider() {
84
+ this.rigidBody = new RigidBodyComponentThree({
85
+ type: "static" /* STATIC */,
86
+ shape: "box" /* BOX */,
87
+ size: new THREE.Vector3(this.options.width, 0.1, this.options.depth),
88
+ // Fixed height of 0.1
89
+ isSensor: true
90
+ // This makes it a trigger collider
91
+ // No collision groups = default behavior (can detect anything that wants to hit it)
92
+ });
93
+ if (this.options.centerOffset) {
94
+ this.gameObject.position.x += this.options.centerOffset.x;
95
+ this.gameObject.position.z += this.options.centerOffset.y;
96
+ }
97
+ this.gameObject.addComponent(this.rigidBody);
98
+ this.rigidBody.registerOnTriggerEnter(this.onTriggerEnter.bind(this));
99
+ this.rigidBody.registerOnTriggerExit(this.onTriggerExit.bind(this));
100
+ }
101
+ /**
102
+ * Handle trigger enter event (to be called by physics system)
103
+ */
104
+ onTriggerEnter(other) {
105
+ if (!this.active) {
106
+ console.warn(`\u{1F3AF} InteractionZone ${this.id}: Ignoring enter event - zone is inactive`);
107
+ return;
108
+ }
109
+ if (!this.gameObject.isEnabled()) {
110
+ return;
111
+ }
112
+ if (!this.entitiesInZone.has(other)) {
113
+ this.entitiesInZone.add(other);
114
+ if (this.onEnterCallback) {
115
+ this.onEnterCallback(other);
116
+ }
117
+ }
118
+ }
119
+ /**
120
+ * Handle trigger exit event (to be called by physics system)
121
+ */
122
+ onTriggerExit(other) {
123
+ if (!this.active) {
124
+ return;
125
+ }
126
+ if (!this.gameObject.isEnabled()) {
127
+ return;
128
+ }
129
+ if (this.entitiesInZone.has(other)) {
130
+ this.entitiesInZone.delete(other);
131
+ if (this.onExitCallback) {
132
+ this.onExitCallback(other);
133
+ }
134
+ }
135
+ }
136
+ /**
137
+ * Get all entities currently in the zone
138
+ */
139
+ getEntitiesInZone() {
140
+ return Array.from(this.entitiesInZone);
141
+ }
142
+ /**
143
+ * Check if a specific entity is in the zone
144
+ */
145
+ hasEntity(entity) {
146
+ return this.entitiesInZone.has(entity);
147
+ }
148
+ /**
149
+ * Set active state
150
+ */
151
+ setActive(active) {
152
+ this.active = active;
153
+ if (this.visualMesh) {
154
+ this.visualMesh.visible = active;
155
+ }
156
+ if (this.rigidBody) {
157
+ }
158
+ if (!active) {
159
+ this.entitiesInZone.clear();
160
+ }
161
+ }
162
+ /**
163
+ * Check if the interaction zone is active
164
+ */
165
+ isActive() {
166
+ return this.active;
167
+ }
168
+ /**
169
+ * Get the visual mesh
170
+ */
171
+ getVisualMesh() {
172
+ return this.visualMesh;
173
+ }
174
+ /**
175
+ * Get the collider component
176
+ */
177
+ getCollider() {
178
+ return this.rigidBody;
179
+ }
180
+ /**
181
+ * Get the GameObject this zone is attached to
182
+ */
183
+ getGameObject() {
184
+ return this.gameObject;
185
+ }
186
+ /**
187
+ * Component cleanup
188
+ */
189
+ onCleanup() {
190
+ this.entitiesInZone.clear();
191
+ if (this.visualMesh) {
192
+ this.visualMesh.geometry.dispose();
193
+ if (this.visualMesh.material instanceof THREE.Material) {
194
+ this.visualMesh.material.dispose();
195
+ }
196
+ this.gameObject.remove(this.visualMesh);
197
+ this.visualMesh = null;
198
+ }
199
+ }
200
+ };
201
+
202
+ // src/engine/mobile/VirtualJoystickThree.ts
203
+ import * as THREE2 from "three";
204
+ var VirtualJoystickThree = class _VirtualJoystickThree extends Component {
205
+ // Configuration
206
+ options;
207
+ // Mobile detection
208
+ static isMobileDevice() {
209
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || "ontouchstart" in window || navigator.maxTouchPoints > 0;
210
+ }
211
+ // UI Elements
212
+ joystickContainer = null;
213
+ joystickBase = null;
214
+ joystickKnob = null;
215
+ mobileHint = null;
216
+ // State
217
+ isActive = false;
218
+ startPosition = new THREE2.Vector2();
219
+ currentPosition = new THREE2.Vector2();
220
+ direction = new THREE2.Vector2();
221
+ magnitude = 0;
222
+ // Input tracking
223
+ joystickPointerId = null;
224
+ isDragging = false;
225
+ joystickRadius = 0;
226
+ // Event handlers (need to bind them for proper cleanup)
227
+ boundPointerDown = this.onPointerDown.bind(this);
228
+ boundPointerMove = this.onPointerMove.bind(this);
229
+ boundPointerUp = this.onPointerUp.bind(this);
230
+ boundTouchStart = this.onTouchStart.bind(this);
231
+ boundTouchMove = this.onTouchMove.bind(this);
232
+ boundTouchEnd = this.onTouchEnd.bind(this);
233
+ constructor(options = {}) {
234
+ super();
235
+ this.options = {
236
+ size: options.size ?? 120,
237
+ knobSize: options.knobSize ?? 40,
238
+ deadZone: options.deadZone ?? 0.15,
239
+ maxDistance: options.maxDistance ?? 50,
240
+ color: options.color ?? "white",
241
+ visible: options.visible ?? true,
242
+ opacity: options.opacity ?? 0.2
243
+ };
244
+ this.joystickRadius = this.options.maxDistance;
245
+ }
246
+ onCreate() {
247
+ this.createJoystickUI();
248
+ this.createMobileHint();
249
+ this.setupInputHandlers();
250
+ }
251
+ onCleanup() {
252
+ this.cleanupUI();
253
+ this.removeInputHandlers();
254
+ }
255
+ /**
256
+ * Create the joystick UI elements using HTML/CSS
257
+ */
258
+ createJoystickUI() {
259
+ this.joystickContainer = document.createElement("div");
260
+ this.joystickContainer.id = "virtual-joystick-container";
261
+ this.joystickContainer.style.cssText = `
262
+ position: fixed;
263
+ width: ${this.options.size + this.options.knobSize}px;
264
+ height: ${this.options.size + this.options.knobSize}px;
265
+ display: none;
266
+ pointer-events: none;
267
+ z-index: 1000;
268
+ user-select: none;
269
+ touch-action: none;
270
+ `;
271
+ this.joystickBase = document.createElement("div");
272
+ this.joystickBase.style.cssText = `
273
+ position: absolute;
274
+ width: ${this.options.size}px;
275
+ height: ${this.options.size}px;
276
+ border: 4px solid ${this.options.color};
277
+ border-radius: 50%;
278
+ background: rgba(255, 255, 255, 0.2);
279
+ left: 50%;
280
+ top: 50%;
281
+ transform: translate(-50%, -50%);
282
+ box-sizing: border-box;
283
+ opacity: ${this.options.opacity};
284
+ `;
285
+ this.joystickKnob = document.createElement("div");
286
+ this.joystickKnob.style.cssText = `
287
+ position: absolute;
288
+ width: ${this.options.knobSize}px;
289
+ height: ${this.options.knobSize}px;
290
+ border: 2px solid ${this.options.color};
291
+ border-radius: 50%;
292
+ background: rgba(255, 255, 255, 0.8);
293
+ left: 50%;
294
+ top: 50%;
295
+ transform: translate(-50%, -50%);
296
+ transition: none;
297
+ box-sizing: border-box;
298
+ opacity: ${this.options.opacity};
299
+ `;
300
+ this.joystickContainer.appendChild(this.joystickBase);
301
+ this.joystickContainer.appendChild(this.joystickKnob);
302
+ document.body.appendChild(this.joystickContainer);
303
+ }
304
+ /**
305
+ * Create mobile hint for touch controls (only shows on mobile devices)
306
+ */
307
+ createMobileHint() {
308
+ if (!_VirtualJoystickThree.isMobileDevice()) {
309
+ return;
310
+ }
311
+ this.mobileHint = document.createElement("div");
312
+ this.mobileHint.id = "mobile-joystick-hint";
313
+ this.mobileHint.textContent = "Touch & drag to move";
314
+ this.mobileHint.style.cssText = `
315
+ position: fixed;
316
+ bottom: 20px;
317
+ left: 50%;
318
+ transform: translateX(-50%);
319
+ background: rgba(0, 0, 0, 0.8);
320
+ color: white;
321
+ padding: 10px 20px;
322
+ border-radius: 20px;
323
+ font-family: Arial, sans-serif;
324
+ font-size: 14px;
325
+ z-index: 999;
326
+ pointer-events: none;
327
+ user-select: none;
328
+ border: 2px solid rgba(255, 255, 255, 0.3);
329
+ animation: fadeInOut 4s ease-in-out;
330
+ `;
331
+ if (!document.querySelector("#mobile-hint-styles")) {
332
+ const style = document.createElement("style");
333
+ style.id = "mobile-hint-styles";
334
+ style.textContent = `
335
+ @keyframes fadeInOut {
336
+ 0% { opacity: 0; transform: translateX(-50%) translateY(20px); }
337
+ 20% { opacity: 1; transform: translateX(-50%) translateY(0px); }
338
+ 80% { opacity: 1; transform: translateX(-50%) translateY(0px); }
339
+ 100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
340
+ }
341
+ `;
342
+ document.head.appendChild(style);
343
+ }
344
+ document.body.appendChild(this.mobileHint);
345
+ setTimeout(() => {
346
+ if (this.mobileHint && this.mobileHint.parentNode) {
347
+ this.mobileHint.parentNode.removeChild(this.mobileHint);
348
+ this.mobileHint = null;
349
+ }
350
+ }, 4e3);
351
+ }
352
+ /**
353
+ * Setup input handlers for pointer and touch events
354
+ */
355
+ setupInputHandlers() {
356
+ document.addEventListener("pointerdown", this.boundPointerDown, {
357
+ passive: false
358
+ });
359
+ document.addEventListener("pointermove", this.boundPointerMove, {
360
+ passive: false
361
+ });
362
+ document.addEventListener("pointerup", this.boundPointerUp, {
363
+ passive: false
364
+ });
365
+ document.addEventListener("touchstart", this.boundTouchStart, {
366
+ passive: false
367
+ });
368
+ document.addEventListener("touchmove", this.boundTouchMove, {
369
+ passive: false
370
+ });
371
+ document.addEventListener("touchend", this.boundTouchEnd, {
372
+ passive: false
373
+ });
374
+ }
375
+ /**
376
+ * Remove input handlers
377
+ */
378
+ removeInputHandlers() {
379
+ document.removeEventListener("pointerdown", this.boundPointerDown);
380
+ document.removeEventListener("pointermove", this.boundPointerMove);
381
+ document.removeEventListener("pointerup", this.boundPointerUp);
382
+ document.removeEventListener("touchstart", this.boundTouchStart);
383
+ document.removeEventListener("touchmove", this.boundTouchMove);
384
+ document.removeEventListener("touchend", this.boundTouchEnd);
385
+ }
386
+ /**
387
+ * Handle pointer down event
388
+ */
389
+ onPointerDown(event) {
390
+ if (this.isActive || !event.isPrimary) return;
391
+ this.startJoystick(event.clientX, event.clientY, event.pointerId);
392
+ event.preventDefault();
393
+ }
394
+ /**
395
+ * Handle touch start event (fallback)
396
+ */
397
+ onTouchStart(event) {
398
+ if (this.isActive || event.touches.length === 0) return;
399
+ const touch = event.touches[0];
400
+ this.startJoystick(touch.clientX, touch.clientY, touch.identifier);
401
+ event.preventDefault();
402
+ }
403
+ /**
404
+ * Start the joystick at the given position
405
+ */
406
+ startJoystick(x, y, pointerId) {
407
+ this.isActive = true;
408
+ this.isDragging = true;
409
+ this.joystickPointerId = pointerId;
410
+ if (this.mobileHint && this.mobileHint.parentNode) {
411
+ this.mobileHint.parentNode.removeChild(this.mobileHint);
412
+ this.mobileHint = null;
413
+ }
414
+ this.startPosition.set(x, y);
415
+ this.currentPosition.set(x, y);
416
+ if (this.joystickContainer) {
417
+ this.joystickContainer.style.display = this.options.visible ? "block" : "none";
418
+ this.joystickContainer.style.left = `${x - (this.options.size + this.options.knobSize) / 2}px`;
419
+ this.joystickContainer.style.top = `${y - (this.options.size + this.options.knobSize) / 2}px`;
420
+ }
421
+ if (this.joystickKnob) {
422
+ this.joystickKnob.style.transform = "translate(-50%, -50%)";
423
+ }
424
+ this.updateDirection();
425
+ }
426
+ /**
427
+ * Handle pointer move event
428
+ */
429
+ onPointerMove(event) {
430
+ if (!this.isActive || !this.isDragging || event.pointerId !== this.joystickPointerId) {
431
+ return;
432
+ }
433
+ this.updateJoystick(event.clientX, event.clientY);
434
+ event.preventDefault();
435
+ }
436
+ /**
437
+ * Handle touch move event (fallback)
438
+ */
439
+ onTouchMove(event) {
440
+ if (!this.isActive || !this.isDragging) return;
441
+ for (let i = 0; i < event.touches.length; i++) {
442
+ const touch = event.touches[i];
443
+ if (touch.identifier === this.joystickPointerId) {
444
+ this.updateJoystick(touch.clientX, touch.clientY);
445
+ event.preventDefault();
446
+ break;
447
+ }
448
+ }
449
+ }
450
+ /**
451
+ * Update joystick position and direction
452
+ */
453
+ updateJoystick(x, y) {
454
+ this.currentPosition.set(x, y);
455
+ this.updateKnobPosition();
456
+ this.updateDirection();
457
+ }
458
+ /**
459
+ * Handle pointer up event
460
+ */
461
+ onPointerUp(event) {
462
+ if (!this.isActive || event.pointerId !== this.joystickPointerId) {
463
+ return;
464
+ }
465
+ this.endJoystick();
466
+ event.preventDefault();
467
+ }
468
+ /**
469
+ * Handle touch end event (fallback)
470
+ */
471
+ onTouchEnd(event) {
472
+ if (!this.isActive) return;
473
+ let touchEnded = true;
474
+ for (let i = 0; i < event.touches.length; i++) {
475
+ if (event.touches[i].identifier === this.joystickPointerId) {
476
+ touchEnded = false;
477
+ break;
478
+ }
479
+ }
480
+ if (touchEnded) {
481
+ this.endJoystick();
482
+ event.preventDefault();
483
+ }
484
+ }
485
+ /**
486
+ * End the joystick interaction
487
+ */
488
+ endJoystick() {
489
+ this.isActive = false;
490
+ this.isDragging = false;
491
+ this.joystickPointerId = null;
492
+ if (this.joystickContainer) {
493
+ this.joystickContainer.style.display = "none";
494
+ }
495
+ this.direction.set(0, 0);
496
+ this.magnitude = 0;
497
+ }
498
+ /**
499
+ * Update knob position based on current pointer position
500
+ */
501
+ updateKnobPosition() {
502
+ if (!this.joystickKnob) return;
503
+ const offset = this.currentPosition.clone().sub(this.startPosition);
504
+ const distance = offset.length();
505
+ if (distance > this.joystickRadius) {
506
+ offset.normalize().multiplyScalar(this.joystickRadius);
507
+ }
508
+ this.joystickKnob.style.transform = `translate(calc(-50% + ${offset.x}px), calc(-50% + ${offset.y}px))`;
509
+ }
510
+ /**
511
+ * Update direction vector based on knob position
512
+ */
513
+ updateDirection() {
514
+ const offset = this.currentPosition.clone().sub(this.startPosition);
515
+ const distance = offset.length();
516
+ if (distance < this.options.deadZone * this.joystickRadius) {
517
+ this.direction.set(0, 0);
518
+ this.magnitude = 0;
519
+ return;
520
+ }
521
+ const normalizedDistance = Math.min(distance / this.joystickRadius, 1);
522
+ this.direction = offset.normalize();
523
+ this.magnitude = normalizedDistance;
524
+ }
525
+ /**
526
+ * Get the current input direction as a Vector3 (Y=0 for movement)
527
+ */
528
+ getDirection() {
529
+ if (!this.isActive || this.magnitude === 0) {
530
+ return null;
531
+ }
532
+ return new THREE2.Vector3(
533
+ this.direction.x * this.magnitude,
534
+ 0,
535
+ -this.direction.y * this.magnitude
536
+ );
537
+ }
538
+ /**
539
+ * Get the current input magnitude (0-1)
540
+ */
541
+ getMagnitude() {
542
+ return this.magnitude;
543
+ }
544
+ /**
545
+ * Check if the joystick is currently active
546
+ */
547
+ isActiveJoystick() {
548
+ return this.isActive;
549
+ }
550
+ /**
551
+ * Toggle joystick visual visibility (joystick remains functional)
552
+ */
553
+ setVisible(visible) {
554
+ this.options.visible = visible;
555
+ if (this.joystickContainer && this.isActive) {
556
+ this.joystickContainer.style.display = visible ? "block" : "none";
557
+ }
558
+ }
559
+ /**
560
+ * Get current visibility state
561
+ */
562
+ isVisible() {
563
+ return this.options.visible;
564
+ }
565
+ /**
566
+ * Set joystick opacity (0-1)
567
+ */
568
+ setOpacity(opacity) {
569
+ this.options.opacity = Math.max(0, Math.min(1, opacity));
570
+ if (this.joystickBase) {
571
+ this.joystickBase.style.opacity = `${this.options.opacity}`;
572
+ }
573
+ if (this.joystickKnob) {
574
+ this.joystickKnob.style.opacity = `${this.options.opacity}`;
575
+ }
576
+ }
577
+ /**
578
+ * Get current opacity
579
+ */
580
+ getOpacity() {
581
+ return this.options.opacity;
582
+ }
583
+ /**
584
+ * Clean up UI resources
585
+ */
586
+ cleanupUI() {
587
+ if (this.joystickContainer && this.joystickContainer.parentNode) {
588
+ this.joystickContainer.parentNode.removeChild(this.joystickContainer);
589
+ }
590
+ if (this.mobileHint && this.mobileHint.parentNode) {
591
+ this.mobileHint.parentNode.removeChild(this.mobileHint);
592
+ }
593
+ this.joystickContainer = null;
594
+ this.joystickBase = null;
595
+ this.joystickKnob = null;
596
+ this.mobileHint = null;
597
+ }
598
+ };
599
+
600
+ // src/engine/movement/MovementController.ts
601
+ import * as THREE3 from "three";
602
+ var MovementController = class extends Component {
603
+ // Public configuration properties
604
+ maxMoveSpeed = 8;
605
+ acceleration = 40;
606
+ turnSpeed = 12;
607
+ // How fast to rotate (radians per second)
608
+ rigidBodyComponent = null;
609
+ targetRotationY = 0;
610
+ currentRotationY = 0;
611
+ // Pre-allocated vector for velocity queries to avoid GC pressure
612
+ _currentVelocity = new THREE3.Vector3();
613
+ /**
614
+ * Called when the component is created and attached to a GameObject
615
+ */
616
+ onCreate() {
617
+ this.findRigidBodyComponentThree();
618
+ this.currentRotationY = this.gameObject.rotation.y;
619
+ this.targetRotationY = this.currentRotationY;
620
+ }
621
+ /**
622
+ * Find the rigid body component on this GameObject
623
+ */
624
+ findRigidBodyComponentThree() {
625
+ this.rigidBodyComponent = this.gameObject.getComponent(RigidBodyComponentThree) || null;
626
+ if (!this.rigidBodyComponent) {
627
+ console.warn("MovementController: No RigidBodyComponentThree found on GameObject");
628
+ }
629
+ }
630
+ /**
631
+ * Set the rigid body component this controller should manage
632
+ */
633
+ setRigidBodyComponentThree(rigidBodyComponent) {
634
+ this.rigidBodyComponent = rigidBodyComponent;
635
+ }
636
+ /**
637
+ * Move the entity based on input direction
638
+ * @param inputDirection Normalized direction vector (or null for no movement)
639
+ * @param deltaTime Time since last frame in seconds
640
+ */
641
+ move(inputDirection, deltaTime) {
642
+ if (!this.rigidBodyComponent) return;
643
+ const targetVelocity = this.calculateTargetVelocity(inputDirection);
644
+ const smoothedVelocity = this.smoothVelocity(targetVelocity, deltaTime);
645
+ this.rigidBodyComponent.setVelocity(smoothedVelocity);
646
+ this.updateRotation(inputDirection, deltaTime);
647
+ }
648
+ /**
649
+ * Calculate target velocity based on input direction
650
+ */
651
+ calculateTargetVelocity(inputDirection) {
652
+ const targetVelocity = new THREE3.Vector3(0, 0, 0);
653
+ if (inputDirection && inputDirection.length() > 0.01) {
654
+ targetVelocity.x = inputDirection.x * this.maxMoveSpeed;
655
+ targetVelocity.z = inputDirection.z * this.maxMoveSpeed;
656
+ }
657
+ targetVelocity.y = 0;
658
+ return targetVelocity;
659
+ }
660
+ /**
661
+ * Smooth velocity towards target using acceleration
662
+ */
663
+ smoothVelocity(targetVelocity, deltaTime) {
664
+ if (!this.rigidBodyComponent) return targetVelocity;
665
+ this.rigidBodyComponent.getVelocity(this._currentVelocity);
666
+ const maxDelta = this.acceleration * deltaTime;
667
+ const smoothedVelocity = new THREE3.Vector3();
668
+ smoothedVelocity.x = this.moveTowards(this._currentVelocity.x, targetVelocity.x, maxDelta);
669
+ smoothedVelocity.z = this.moveTowards(this._currentVelocity.z, targetVelocity.z, maxDelta);
670
+ smoothedVelocity.y = 0;
671
+ return smoothedVelocity;
672
+ }
673
+ /**
674
+ * Update rotation smoothly towards movement direction using quaternion slerp
675
+ */
676
+ updateRotation(inputDirection, deltaTime) {
677
+ if (this.rigidBodyComponent) {
678
+ const rigidBody = this.rigidBodyComponent.getRigidBody();
679
+ if (rigidBody) {
680
+ this.rigidBodyComponent.setAngularVelocity(new THREE3.Vector3(0, 0, 0));
681
+ }
682
+ }
683
+ if (!inputDirection || inputDirection.length() < 0.01) {
684
+ return;
685
+ }
686
+ const targetRotationY = Math.atan2(inputDirection.x, inputDirection.z);
687
+ const targetQuaternion = new THREE3.Quaternion();
688
+ targetQuaternion.setFromAxisAngle(new THREE3.Vector3(0, 1, 0), targetRotationY);
689
+ const currentQuaternion = new THREE3.Quaternion();
690
+ currentQuaternion.setFromAxisAngle(new THREE3.Vector3(0, 1, 0), this.currentRotationY);
691
+ const slerpFactor = Math.min(1, this.turnSpeed * deltaTime);
692
+ const resultQuaternion = new THREE3.Quaternion();
693
+ resultQuaternion.slerpQuaternions(currentQuaternion, targetQuaternion, slerpFactor);
694
+ const euler = new THREE3.Euler();
695
+ euler.setFromQuaternion(resultQuaternion, "YXZ");
696
+ this.currentRotationY = euler.y;
697
+ if (this.rigidBodyComponent) {
698
+ const rigidBody = this.rigidBodyComponent.getRigidBody();
699
+ if (rigidBody) {
700
+ const rapierQuat = {
701
+ x: resultQuaternion.x,
702
+ y: resultQuaternion.y,
703
+ z: resultQuaternion.z,
704
+ w: resultQuaternion.w
705
+ };
706
+ rigidBody.setRotation(rapierQuat, true);
707
+ } else {
708
+ this.gameObject.rotation.y = this.currentRotationY;
709
+ }
710
+ } else {
711
+ this.gameObject.rotation.y = this.currentRotationY;
712
+ }
713
+ }
714
+ /**
715
+ * Utility function to move a value towards a target at a given rate
716
+ */
717
+ moveTowards(current, target, maxDelta) {
718
+ const delta = target - current;
719
+ if (Math.abs(delta) <= maxDelta) {
720
+ return target;
721
+ }
722
+ return current + Math.sign(delta) * maxDelta;
723
+ }
724
+ /**
725
+ * Get current movement state for debugging
726
+ */
727
+ getMovementState() {
728
+ return {
729
+ maxMoveSpeed: this.maxMoveSpeed,
730
+ acceleration: this.acceleration,
731
+ turnSpeed: this.turnSpeed,
732
+ currentRotationY: this.currentRotationY,
733
+ targetRotationY: this.targetRotationY,
734
+ hasRigidBody: !!this.rigidBodyComponent
735
+ };
736
+ }
737
+ /**
738
+ * Clean up resources when the component is removed
739
+ */
740
+ onCleanup() {
741
+ this.rigidBodyComponent = null;
742
+ }
743
+ };
744
+
745
+ // src/engine/player/PlayerControllerThree.ts
746
+ import * as THREE4 from "three";
747
+ var PlayerControllerThree = class extends Component {
748
+ // Movement parameters
749
+ moveSpeed = 5;
750
+ runSpeed = 8;
751
+ rotationSpeed = 2;
752
+ // Controls configuration
753
+ controls = {
754
+ forward: "KeyW",
755
+ backward: "KeyS",
756
+ left: "KeyA",
757
+ right: "KeyD",
758
+ run: "ShiftLeft",
759
+ interact: "KeyE"
760
+ };
761
+ // Input state
762
+ keys = /* @__PURE__ */ new Set();
763
+ mouseX = 0;
764
+ mouseY = 0;
765
+ mouseSensitivity = 2e-3;
766
+ isPointerLocked = false;
767
+ // Camera reference
768
+ camera;
769
+ cameraHeight = 1.7;
770
+ // Eye level height
771
+ // Movement state
772
+ velocity = new THREE4.Vector3();
773
+ direction = new THREE4.Vector3();
774
+ constructor(camera) {
775
+ super();
776
+ this.camera = camera;
777
+ }
778
+ onCreate() {
779
+ this.setupEventListeners();
780
+ this.setupPointerLock();
781
+ this.updateCameraPosition();
782
+ console.log("\u{1F3AE} Player controller initialized");
783
+ console.log("\u{1F4CB} Controls: WASD to move, Shift to run, E to interact, Click to look around");
784
+ }
785
+ /**
786
+ * Set up keyboard and mouse event listeners
787
+ */
788
+ setupEventListeners() {
789
+ document.addEventListener("keydown", this.onKeyDown.bind(this));
790
+ document.addEventListener("keyup", this.onKeyUp.bind(this));
791
+ document.addEventListener("mousemove", this.onMouseMove.bind(this));
792
+ document.addEventListener("click", this.onClick.bind(this));
793
+ document.addEventListener("pointerlockchange", this.onPointerLockChange.bind(this));
794
+ }
795
+ /**
796
+ * Set up pointer lock for mouse look
797
+ */
798
+ setupPointerLock() {
799
+ const canvas = document.getElementById("renderCanvas");
800
+ if (canvas) {
801
+ canvas.addEventListener("click", () => {
802
+ if (!this.isPointerLocked) {
803
+ canvas.requestPointerLock();
804
+ }
805
+ });
806
+ }
807
+ }
808
+ /**
809
+ * Handle key down events
810
+ */
811
+ onKeyDown(event) {
812
+ this.keys.add(event.code);
813
+ if (event.code === this.controls.interact) {
814
+ this.onInteract();
815
+ }
816
+ }
817
+ /**
818
+ * Handle key up events
819
+ */
820
+ onKeyUp(event) {
821
+ this.keys.delete(event.code);
822
+ }
823
+ /**
824
+ * Handle mouse movement for looking around
825
+ */
826
+ onMouseMove(event) {
827
+ if (!this.isPointerLocked) return;
828
+ this.mouseX += event.movementX * this.mouseSensitivity;
829
+ this.mouseY += event.movementY * this.mouseSensitivity;
830
+ this.mouseY = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.mouseY));
831
+ this.camera.rotation.order = "YXZ";
832
+ this.camera.rotation.y = -this.mouseX;
833
+ this.camera.rotation.x = -this.mouseY;
834
+ }
835
+ /**
836
+ * Handle canvas clicks for pointer lock
837
+ */
838
+ onClick(event) {
839
+ const canvas = document.getElementById("renderCanvas");
840
+ if (event.target === canvas && !this.isPointerLocked) {
841
+ canvas.requestPointerLock();
842
+ }
843
+ }
844
+ /**
845
+ * Handle pointer lock state changes
846
+ */
847
+ onPointerLockChange() {
848
+ this.isPointerLocked = document.pointerLockElement !== null;
849
+ if (this.isPointerLocked) {
850
+ console.log("\u{1F5B1}\uFE0F Mouse locked - look around!");
851
+ } else {
852
+ console.log("\u{1F5B1}\uFE0F Mouse unlocked - click canvas to lock again");
853
+ }
854
+ }
855
+ /**
856
+ * Handle interaction key press
857
+ */
858
+ onInteract() {
859
+ console.log("\u{1F91D} Player trying to interact...");
860
+ }
861
+ /**
862
+ * Update method - called every frame
863
+ */
864
+ update(deltaTime) {
865
+ this.updateMovement(deltaTime);
866
+ this.updateCameraPosition();
867
+ }
868
+ /**
869
+ * Update player movement based on input
870
+ */
871
+ updateMovement(deltaTime) {
872
+ this.direction.set(0, 0, 0);
873
+ const isRunning = this.keys.has(this.controls.run);
874
+ const currentSpeed = isRunning ? this.runSpeed : this.moveSpeed;
875
+ const forward = new THREE4.Vector3();
876
+ const right = new THREE4.Vector3();
877
+ this.camera.getWorldDirection(forward);
878
+ forward.y = 0;
879
+ forward.normalize();
880
+ right.crossVectors(forward, this.camera.up).normalize();
881
+ if (this.keys.has(this.controls.forward)) {
882
+ this.direction.add(forward);
883
+ }
884
+ if (this.keys.has(this.controls.backward)) {
885
+ this.direction.sub(forward);
886
+ }
887
+ if (this.keys.has(this.controls.right)) {
888
+ this.direction.add(right);
889
+ }
890
+ if (this.keys.has(this.controls.left)) {
891
+ this.direction.sub(right);
892
+ }
893
+ if (this.direction.length() > 0) {
894
+ this.direction.normalize();
895
+ this.velocity.copy(this.direction).multiplyScalar(currentSpeed * deltaTime);
896
+ this.gameObject.position.add(this.velocity);
897
+ }
898
+ this.gameObject.position.y = 0;
899
+ }
900
+ /**
901
+ * Update camera position to follow player
902
+ */
903
+ updateCameraPosition() {
904
+ this.camera.position.copy(this.gameObject.position);
905
+ this.camera.position.y += this.cameraHeight;
906
+ }
907
+ /**
908
+ * Get current movement state for debugging
909
+ */
910
+ getMovementState() {
911
+ const lookDirection = new THREE4.Vector3();
912
+ this.camera.getWorldDirection(lookDirection);
913
+ return {
914
+ position: this.gameObject.position.clone(),
915
+ isMoving: this.direction.length() > 0,
916
+ isRunning: this.keys.has(this.controls.run),
917
+ lookDirection
918
+ };
919
+ }
920
+ /**
921
+ * Set player position
922
+ */
923
+ setPosition(position) {
924
+ this.gameObject.position.copy(position);
925
+ this.updateCameraPosition();
926
+ }
927
+ /**
928
+ * Clean up event listeners
929
+ */
930
+ onCleanup() {
931
+ document.removeEventListener("keydown", this.onKeyDown.bind(this));
932
+ document.removeEventListener("keyup", this.onKeyUp.bind(this));
933
+ document.removeEventListener("mousemove", this.onMouseMove.bind(this));
934
+ document.removeEventListener("click", this.onClick.bind(this));
935
+ document.removeEventListener("pointerlockchange", this.onPointerLockChange.bind(this));
936
+ if (this.isPointerLocked) {
937
+ document.exitPointerLock();
938
+ }
939
+ console.log("\u{1F3AE} Player controller cleaned up");
940
+ }
941
+ };
942
+
943
+ // src/engine/render/SkeletalRenderer.ts
944
+ import * as THREE5 from "three";
945
+ var SkeletalRenderer = class extends Component {
946
+ _group = null;
947
+ _assetPath;
948
+ _material;
949
+ _skeletalModel = null;
950
+ constructor(assetPath, material) {
951
+ super();
952
+ this._assetPath = assetPath;
953
+ this._material = material || null;
954
+ }
955
+ onCreate() {
956
+ this.createSkeletalMesh();
957
+ }
958
+ createSkeletalMesh() {
959
+ if (!this._assetPath) {
960
+ throw new Error("SkeletalRenderer: No asset path specified");
961
+ }
962
+ this._group = new THREE5.Group();
963
+ this._group.name = `skeletal_${this._assetPath.split("/").pop()}`;
964
+ this.gameObject.add(this._group);
965
+ this._skeletalModel = AssetManager.getSkeletalClone(this._assetPath);
966
+ if (!this._skeletalModel) {
967
+ throw new Error(
968
+ `No skeletal model found for '${this._assetPath}'. Make sure to preload with AssetManager.preloadSkeletalModel() first.`
969
+ );
970
+ }
971
+ if (this._material) {
972
+ this._skeletalModel.traverse((child) => {
973
+ if (child instanceof THREE5.Mesh) {
974
+ child.material = this._material;
975
+ child.frustumCulled = false;
976
+ }
977
+ });
978
+ } else {
979
+ this._skeletalModel.traverse((child) => {
980
+ if (child instanceof THREE5.Mesh) {
981
+ child.frustumCulled = false;
982
+ }
983
+ });
984
+ }
985
+ this._group.add(this._skeletalModel);
986
+ this.applyShadowsToGroup(this._group);
987
+ }
988
+ applyShadowsToGroup(group) {
989
+ group.traverse((child) => {
990
+ if (child instanceof THREE5.Mesh) {
991
+ child.castShadow = true;
992
+ child.receiveShadow = true;
993
+ }
994
+ });
995
+ }
996
+ // ========== Public API ==========
997
+ /**
998
+ * Get the wrapper group (attached to GameObject)
999
+ */
1000
+ getGroup() {
1001
+ return this._group;
1002
+ }
1003
+ /**
1004
+ * Get the skeletal model (for animation setup)
1005
+ */
1006
+ getSkeletalModel() {
1007
+ return this._skeletalModel;
1008
+ }
1009
+ /**
1010
+ * Get the asset path being rendered
1011
+ */
1012
+ getAssetPath() {
1013
+ return this._assetPath;
1014
+ }
1015
+ /**
1016
+ * Enable or disable visibility
1017
+ */
1018
+ setVisible(visible) {
1019
+ if (this._group) {
1020
+ this._group.visible = visible;
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Get visibility state
1025
+ */
1026
+ isVisible() {
1027
+ return this._group?.visible ?? false;
1028
+ }
1029
+ // ========== Component Lifecycle ==========
1030
+ onCleanup() {
1031
+ if (this._group) {
1032
+ this.gameObject.remove(this._group);
1033
+ }
1034
+ }
1035
+ onEnabled() {
1036
+ this.setVisible(true);
1037
+ }
1038
+ onDisabled() {
1039
+ this.setVisible(false);
1040
+ }
1041
+ };
1042
+
1043
+ // src/engine/render/InstancedRenderer.ts
1044
+ var InstancedRenderer = class extends Component {
1045
+ batchKey;
1046
+ options;
1047
+ instanceId = null;
1048
+ /**
1049
+ * Create an InstancedRenderer
1050
+ * @param batchKey The batch key to register with. If no batch exists, one is auto-created.
1051
+ * @param options Configuration options (or just pass `true`/`false` for isDynamic)
1052
+ */
1053
+ constructor(batchKey, options = {}) {
1054
+ super();
1055
+ this.batchKey = batchKey;
1056
+ this.options = typeof options === "boolean" ? { isDynamic: options } : options;
1057
+ }
1058
+ /**
1059
+ * Register with the batch when the component is created.
1060
+ * If no batch exists, one will be created automatically from this GameObject's mesh.
1061
+ */
1062
+ onCreate() {
1063
+ const manager = InstancedMeshManager.getInstance();
1064
+ if (!manager.isReady()) {
1065
+ console.error(
1066
+ `InstancedRenderer: Manager not initialized. Call InstancedMeshManager.getInstance().initialize(scene) first.`
1067
+ );
1068
+ return;
1069
+ }
1070
+ this.instanceId = manager.addInstance(this.batchKey, this.gameObject, {
1071
+ isDynamic: this.options.isDynamic ?? true,
1072
+ castShadow: this.options.castShadow ?? false,
1073
+ receiveShadow: this.options.receiveShadow ?? false,
1074
+ initialCapacity: this.options.initialCapacity
1075
+ });
1076
+ if (!this.instanceId) {
1077
+ console.error(`InstancedRenderer: Failed to add instance to batch '${this.batchKey}'`);
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Set the visibility of this instance
1082
+ */
1083
+ setVisible(visible) {
1084
+ if (this.instanceId) {
1085
+ InstancedMeshManager.getInstance().setInstanceVisible(this.batchKey, this.instanceId, visible);
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Get the visibility of this instance
1090
+ */
1091
+ getVisible() {
1092
+ if (this.instanceId) {
1093
+ return InstancedMeshManager.getInstance().getInstanceVisible(this.batchKey, this.instanceId);
1094
+ }
1095
+ return false;
1096
+ }
1097
+ /**
1098
+ * Show the instance (convenience method)
1099
+ */
1100
+ show() {
1101
+ this.setVisible(true);
1102
+ }
1103
+ /**
1104
+ * Hide the instance (convenience method)
1105
+ */
1106
+ hide() {
1107
+ this.setVisible(false);
1108
+ }
1109
+ /**
1110
+ * Get the batch key this renderer is registered with
1111
+ */
1112
+ getBatchKey() {
1113
+ return this.batchKey;
1114
+ }
1115
+ /**
1116
+ * Check if this renderer is successfully registered with a batch
1117
+ */
1118
+ isRegistered() {
1119
+ return this.instanceId !== null;
1120
+ }
1121
+ /**
1122
+ * Get the instance ID (for debugging)
1123
+ */
1124
+ getInstanceId() {
1125
+ return this.instanceId;
1126
+ }
1127
+ /**
1128
+ * Mark this instance as needing a matrix update.
1129
+ * Only relevant for static instances - dynamic instances update every frame anyway.
1130
+ * Call this when the transform of a static instance changes.
1131
+ */
1132
+ markDirty() {
1133
+ if (this.instanceId) {
1134
+ InstancedMeshManager.getInstance().markInstanceDirty(this.batchKey, this.instanceId);
1135
+ }
1136
+ }
1137
+ /**
1138
+ * Set whether this instance is dynamic (updates every frame) or static (only when marked dirty).
1139
+ * Use this to optimize performance when items transition between moving and stationary states.
1140
+ * @param isDynamic If true, updates every frame. If false, only updates when markDirty() is called.
1141
+ */
1142
+ setDynamic(isDynamic) {
1143
+ if (this.instanceId) {
1144
+ InstancedMeshManager.getInstance().setInstanceDynamic(
1145
+ this.batchKey,
1146
+ this.instanceId,
1147
+ isDynamic
1148
+ );
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Called when the GameObject becomes enabled
1153
+ */
1154
+ onEnabled() {
1155
+ this.setVisible(true);
1156
+ }
1157
+ /**
1158
+ * Called when the GameObject becomes disabled
1159
+ */
1160
+ onDisabled() {
1161
+ this.setVisible(false);
1162
+ }
1163
+ /**
1164
+ * Unregister from the batch when the component is cleaned up
1165
+ */
1166
+ onCleanup() {
1167
+ if (this.instanceId) {
1168
+ InstancedMeshManager.getInstance().removeInstance(this.batchKey, this.instanceId);
1169
+ this.instanceId = null;
1170
+ }
1171
+ }
1172
+ };
1173
+
1174
+ // src/engine/render/MeshRenderer.ts
1175
+ import * as THREE6 from "three";
1176
+ var MeshRenderer = class extends Component {
1177
+ static fromPrefabJSON(json, _node) {
1178
+ if (!json.mesh?.assetId) {
1179
+ console.error(`[MeshRenderer] stow_mesh component missing mesh.assetId:`, json);
1180
+ return new MeshRenderer("unknown");
1181
+ }
1182
+ const options = PrefabInstance.currentOptions;
1183
+ const castShadow = json.castShadow ?? options?.castShadow ?? true;
1184
+ const receiveShadow = json.receiveShadow ?? options?.receiveShadow ?? true;
1185
+ return new MeshRenderer(json.mesh.assetId, castShadow, receiveShadow);
1186
+ }
1187
+ mesh = null;
1188
+ meshName;
1189
+ castShadow;
1190
+ receiveShadow;
1191
+ _isStatic;
1192
+ isMeshLoaded = false;
1193
+ materialOverride = null;
1194
+ /**
1195
+ * @param meshName The name of the mesh in the StowKit pack
1196
+ * @param castShadow Whether meshes should cast shadows (default: true)
1197
+ * @param receiveShadow Whether meshes should receive shadows (default: true)
1198
+ * @param isStatic Whether this mesh is static (default: false). Static meshes have matrixAutoUpdate disabled for better performance.
1199
+ * @param materialOverride Optional material to use instead of the default StowKit material
1200
+ */
1201
+ constructor(meshName, castShadow = true, receiveShadow = true, isStatic = false, materialOverride = null) {
1202
+ super();
1203
+ this.meshName = meshName;
1204
+ this.castShadow = castShadow;
1205
+ this.receiveShadow = receiveShadow;
1206
+ this._isStatic = isStatic;
1207
+ this.materialOverride = materialOverride;
1208
+ }
1209
+ onCreate() {
1210
+ const stowkit = StowKitSystem.getInstance();
1211
+ const cachedMesh = stowkit.getMeshSync(this.meshName);
1212
+ if (cachedMesh) {
1213
+ this.addMesh(cachedMesh);
1214
+ } else {
1215
+ stowkit.getMesh(this.meshName);
1216
+ }
1217
+ }
1218
+ update(_deltaTime) {
1219
+ if (this.isMeshLoaded) return;
1220
+ const stowkit = StowKitSystem.getInstance();
1221
+ const cachedMesh = stowkit.getMeshSync(this.meshName);
1222
+ if (cachedMesh) {
1223
+ this.addMesh(cachedMesh);
1224
+ }
1225
+ }
1226
+ addMesh(original) {
1227
+ this.isMeshLoaded = true;
1228
+ this.mesh = StowKitSystem.getInstance().cloneMeshSync(
1229
+ original,
1230
+ this.castShadow,
1231
+ this.receiveShadow
1232
+ );
1233
+ if (this.materialOverride) {
1234
+ this.applyMaterialOverride();
1235
+ }
1236
+ this.gameObject.add(this.mesh);
1237
+ if (this._isStatic) {
1238
+ this.setStatic(true);
1239
+ }
1240
+ }
1241
+ /**
1242
+ * Apply the material override to all meshes
1243
+ */
1244
+ applyMaterialOverride() {
1245
+ if (!this.mesh || !this.materialOverride) return;
1246
+ this.mesh.traverse((child) => {
1247
+ if (child instanceof THREE6.Mesh) {
1248
+ child.material = this.materialOverride;
1249
+ }
1250
+ });
1251
+ }
1252
+ /**
1253
+ * Set a material override for all meshes in this renderer.
1254
+ * Call this after the mesh is loaded, or pass it in the constructor.
1255
+ */
1256
+ setMaterial(material) {
1257
+ this.materialOverride = material;
1258
+ if (this.mesh) {
1259
+ this.applyMaterialOverride();
1260
+ }
1261
+ }
1262
+ /**
1263
+ * Check if this mesh is currently static (no automatic matrix updates)
1264
+ */
1265
+ get isStatic() {
1266
+ return this._isStatic;
1267
+ }
1268
+ /**
1269
+ * Set whether this mesh is static (no automatic matrix updates).
1270
+ * Static meshes save CPU by not recalculating transforms every frame.
1271
+ * Call forceMatrixUpdate() after moving a static mesh.
1272
+ */
1273
+ setStatic(isStatic) {
1274
+ this._isStatic = isStatic;
1275
+ if (!this.mesh) return;
1276
+ if (isStatic) {
1277
+ this.forceMatrixUpdate();
1278
+ this.mesh.matrixAutoUpdate = false;
1279
+ this.mesh.traverse((child) => {
1280
+ child.matrixAutoUpdate = false;
1281
+ });
1282
+ this.gameObject.matrixAutoUpdate = false;
1283
+ } else {
1284
+ this.mesh.matrixAutoUpdate = true;
1285
+ this.mesh.traverse((child) => {
1286
+ child.matrixAutoUpdate = true;
1287
+ });
1288
+ this.gameObject.matrixAutoUpdate = true;
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Force a one-time matrix update. Call this after moving a static mesh.
1293
+ * Does not change the static/dynamic state.
1294
+ */
1295
+ forceMatrixUpdate() {
1296
+ if (this.mesh) {
1297
+ this.mesh.updateMatrix();
1298
+ this.mesh.updateMatrixWorld(true);
1299
+ }
1300
+ this.gameObject.updateMatrix();
1301
+ this.gameObject.updateMatrixWorld(true);
1302
+ }
1303
+ /**
1304
+ * Get the mesh group (null if not yet loaded)
1305
+ */
1306
+ getMesh() {
1307
+ return this.mesh;
1308
+ }
1309
+ /**
1310
+ * Get the name of the mesh this component is managing
1311
+ */
1312
+ getMeshName() {
1313
+ return this.meshName;
1314
+ }
1315
+ /**
1316
+ * Check if the mesh was successfully loaded
1317
+ */
1318
+ isLoaded() {
1319
+ return this.mesh !== null;
1320
+ }
1321
+ /**
1322
+ * Set the visibility of the mesh
1323
+ */
1324
+ setVisible(visible) {
1325
+ if (this.mesh) {
1326
+ this.mesh.visible = visible;
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Get bounds of the mesh (useful for physics)
1331
+ */
1332
+ getBounds() {
1333
+ if (!this.mesh) {
1334
+ return null;
1335
+ }
1336
+ return StowKitSystem.getInstance().getBounds(this.mesh);
1337
+ }
1338
+ /**
1339
+ * Cleanup - remove mesh from scene and dispose of resources
1340
+ */
1341
+ onCleanup() {
1342
+ if (this.mesh) {
1343
+ this.gameObject.remove(this.mesh);
1344
+ this.mesh.traverse((child) => {
1345
+ if (child instanceof THREE6.Mesh) {
1346
+ if (child.geometry) {
1347
+ child.geometry.dispose();
1348
+ }
1349
+ if (child.material) {
1350
+ if (Array.isArray(child.material)) {
1351
+ child.material.forEach((m) => {
1352
+ if (m.map) m.map.dispose();
1353
+ m.dispose();
1354
+ });
1355
+ } else {
1356
+ if (child.material.map) child.material.map.dispose();
1357
+ child.material.dispose();
1358
+ }
1359
+ }
1360
+ }
1361
+ });
1362
+ this.mesh = null;
1363
+ }
1364
+ }
1365
+ };
1366
+ MeshRenderer = __decorateClass([
1367
+ PrefabComponent("stow_mesh")
1368
+ ], MeshRenderer);
1369
+
1370
+ // src/index.ts
1371
+ export * from "@series-inc/rundot-game-sdk";
1372
+ import { default as default2 } from "@series-inc/rundot-game-sdk/api";
1373
+ export {
1374
+ AssetManager,
1375
+ Component,
1376
+ ComponentUpdater,
1377
+ GameObject,
1378
+ InstancedMeshManager,
1379
+ InstancedRenderer,
1380
+ InteractionZone,
1381
+ MeshRenderer,
1382
+ MovementController,
1383
+ PlayerControllerThree,
1384
+ default2 as RundotGameAPI,
1385
+ SkeletalRenderer,
1386
+ SkeletonCache,
1387
+ VenusGame,
1388
+ VirtualJoystickThree
1389
+ };
1390
+ //# sourceMappingURL=index.js.map