@series-inc/rundot-3d-engine 0.5.16 → 0.5.19
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/dist/index.d.ts +37 -0
- package/dist/index.js +80 -1
- package/dist/index.js.map +1 -1
- package/docs/patterns/how-to-load-stowkit-threejs-venussdk.md +197 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1624,6 +1624,12 @@ declare class InstancedRenderer extends Component {
|
|
|
1624
1624
|
protected onCleanup(): void;
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
|
+
interface NodeOverrideJSON {
|
|
1628
|
+
position?: [number, number, number];
|
|
1629
|
+
rotation?: [number, number, number];
|
|
1630
|
+
scale?: [number, number, number];
|
|
1631
|
+
components?: ComponentJSON[];
|
|
1632
|
+
}
|
|
1627
1633
|
interface StowMeshJSON extends ComponentJSON {
|
|
1628
1634
|
type: "stow_mesh";
|
|
1629
1635
|
mesh: {
|
|
@@ -1632,6 +1638,7 @@ interface StowMeshJSON extends ComponentJSON {
|
|
|
1632
1638
|
};
|
|
1633
1639
|
castShadow?: boolean;
|
|
1634
1640
|
receiveShadow?: boolean;
|
|
1641
|
+
nodeOverrides?: Record<string, NodeOverrideJSON>;
|
|
1635
1642
|
}
|
|
1636
1643
|
/**
|
|
1637
1644
|
* MeshRenderer - Component for rendering a mesh from StowKitSystem.
|
|
@@ -1639,6 +1646,11 @@ interface StowMeshJSON extends ComponentJSON {
|
|
|
1639
1646
|
* This is a non-instanced renderer - each instance is a separate draw call.
|
|
1640
1647
|
* Use InstancedRenderer for many instances of the same mesh (better performance).
|
|
1641
1648
|
*
|
|
1649
|
+
* When a mesh has named child groups (from preserveHierarchy), MeshRenderer
|
|
1650
|
+
* promotes them to GameObjects so they can have components and be accessed
|
|
1651
|
+
* via getMeshChild(). Transform overrides and components from nodeOverrides
|
|
1652
|
+
* are applied automatically.
|
|
1653
|
+
*
|
|
1642
1654
|
* Usage:
|
|
1643
1655
|
* ```typescript
|
|
1644
1656
|
* const env = new GameObject("Environment")
|
|
@@ -1655,6 +1667,8 @@ declare class MeshRenderer extends Component {
|
|
|
1655
1667
|
private isMeshLoaded;
|
|
1656
1668
|
private materialOverride;
|
|
1657
1669
|
private loadedCallbacks;
|
|
1670
|
+
private nodeOverrides;
|
|
1671
|
+
private meshChildMap;
|
|
1658
1672
|
/**
|
|
1659
1673
|
* @param meshName The name of the mesh in the StowKit pack
|
|
1660
1674
|
* @param castShadow Whether meshes should cast shadows (default: true)
|
|
@@ -1666,6 +1680,29 @@ declare class MeshRenderer extends Component {
|
|
|
1666
1680
|
protected onCreate(): void;
|
|
1667
1681
|
update(_deltaTime: number): void;
|
|
1668
1682
|
private addMesh;
|
|
1683
|
+
/**
|
|
1684
|
+
* Walk the loaded mesh hierarchy and replace named child Groups with
|
|
1685
|
+
* GameObjects so they can receive components. Applies nodeOverrides
|
|
1686
|
+
* (transform + components) from the prefab data.
|
|
1687
|
+
*/
|
|
1688
|
+
private promoteMeshChildren;
|
|
1689
|
+
/**
|
|
1690
|
+
* Get a named child group from the loaded mesh as a GameObject.
|
|
1691
|
+
* Returns null if the mesh hasn't loaded yet or if no child with that name exists.
|
|
1692
|
+
*
|
|
1693
|
+
* ```typescript
|
|
1694
|
+
* renderer.onLoaded(() => {
|
|
1695
|
+
* const key = renderer.getMeshChild("Key")
|
|
1696
|
+
* key?.addComponent(new Spinner("z"))
|
|
1697
|
+
* })
|
|
1698
|
+
* ```
|
|
1699
|
+
*/
|
|
1700
|
+
getMeshChild(name: string): GameObject | null;
|
|
1701
|
+
/**
|
|
1702
|
+
* Get all named child groups from the loaded mesh.
|
|
1703
|
+
* Returns null if the mesh hasn't loaded yet.
|
|
1704
|
+
*/
|
|
1705
|
+
getMeshChildren(): ReadonlyMap<string, GameObject> | null;
|
|
1669
1706
|
/**
|
|
1670
1707
|
* Apply the material override to all meshes
|
|
1671
1708
|
*/
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AssetManager,
|
|
3
3
|
CapacitorPlatform,
|
|
4
4
|
Component,
|
|
5
|
+
ComponentRegistry,
|
|
5
6
|
ComponentUpdater,
|
|
6
7
|
GameObject,
|
|
7
8
|
InstancedMeshManager,
|
|
@@ -1191,7 +1192,11 @@ var MeshRenderer = class extends Component {
|
|
|
1191
1192
|
const options = PrefabInstance.currentOptions;
|
|
1192
1193
|
const castShadow = json.castShadow ?? options?.castShadow ?? true;
|
|
1193
1194
|
const receiveShadow = json.receiveShadow ?? options?.receiveShadow ?? true;
|
|
1194
|
-
|
|
1195
|
+
const renderer = new MeshRenderer(json.mesh.assetId, castShadow, receiveShadow);
|
|
1196
|
+
if (json.nodeOverrides) {
|
|
1197
|
+
renderer.nodeOverrides = json.nodeOverrides;
|
|
1198
|
+
}
|
|
1199
|
+
return renderer;
|
|
1195
1200
|
}
|
|
1196
1201
|
mesh = null;
|
|
1197
1202
|
meshName;
|
|
@@ -1201,6 +1206,8 @@ var MeshRenderer = class extends Component {
|
|
|
1201
1206
|
isMeshLoaded = false;
|
|
1202
1207
|
materialOverride = null;
|
|
1203
1208
|
loadedCallbacks = null;
|
|
1209
|
+
nodeOverrides = null;
|
|
1210
|
+
meshChildMap = null;
|
|
1204
1211
|
/**
|
|
1205
1212
|
* @param meshName The name of the mesh in the StowKit pack
|
|
1206
1213
|
* @param castShadow Whether meshes should cast shadows (default: true)
|
|
@@ -1244,6 +1251,7 @@ var MeshRenderer = class extends Component {
|
|
|
1244
1251
|
this.applyMaterialOverride();
|
|
1245
1252
|
}
|
|
1246
1253
|
this.gameObject.add(this.mesh);
|
|
1254
|
+
this.promoteMeshChildren(this.mesh);
|
|
1247
1255
|
if (this._isStatic) {
|
|
1248
1256
|
this.setStatic(true);
|
|
1249
1257
|
}
|
|
@@ -1252,6 +1260,76 @@ var MeshRenderer = class extends Component {
|
|
|
1252
1260
|
this.loadedCallbacks = null;
|
|
1253
1261
|
}
|
|
1254
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Walk the loaded mesh hierarchy and replace named child Groups with
|
|
1265
|
+
* GameObjects so they can receive components. Applies nodeOverrides
|
|
1266
|
+
* (transform + components) from the prefab data.
|
|
1267
|
+
*/
|
|
1268
|
+
promoteMeshChildren(group) {
|
|
1269
|
+
for (const child of [...group.children]) {
|
|
1270
|
+
if (child.type !== "Group" || !child.name) continue;
|
|
1271
|
+
const childGO = new GameObject(child.name);
|
|
1272
|
+
childGO.position.copy(child.position);
|
|
1273
|
+
childGO.quaternion.copy(child.quaternion);
|
|
1274
|
+
childGO.scale.copy(child.scale);
|
|
1275
|
+
const override = this.nodeOverrides?.[child.name];
|
|
1276
|
+
if (override?.position) childGO.position.fromArray(override.position);
|
|
1277
|
+
if (override?.rotation) {
|
|
1278
|
+
childGO.rotation.set(
|
|
1279
|
+
override.rotation[0],
|
|
1280
|
+
override.rotation[1],
|
|
1281
|
+
override.rotation[2]
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
if (override?.scale) childGO.scale.fromArray(override.scale);
|
|
1285
|
+
for (const grandchild of [...child.children]) {
|
|
1286
|
+
child.remove(grandchild);
|
|
1287
|
+
childGO.add(grandchild);
|
|
1288
|
+
}
|
|
1289
|
+
group.remove(child);
|
|
1290
|
+
group.add(childGO);
|
|
1291
|
+
if (override?.components) {
|
|
1292
|
+
for (const compJSON of override.components) {
|
|
1293
|
+
const factory = ComponentRegistry.get(compJSON.type);
|
|
1294
|
+
if (factory) {
|
|
1295
|
+
try {
|
|
1296
|
+
const comp = factory.fromPrefabJSON(compJSON, null);
|
|
1297
|
+
if (comp) childGO.addComponent(comp);
|
|
1298
|
+
} catch (e) {
|
|
1299
|
+
console.error(
|
|
1300
|
+
`[MeshRenderer] Failed to create component "${compJSON.type}" on mesh child "${child.name}":`,
|
|
1301
|
+
e
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (!this.meshChildMap) this.meshChildMap = /* @__PURE__ */ new Map();
|
|
1308
|
+
this.meshChildMap.set(child.name, childGO);
|
|
1309
|
+
this.promoteMeshChildren(childGO);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Get a named child group from the loaded mesh as a GameObject.
|
|
1314
|
+
* Returns null if the mesh hasn't loaded yet or if no child with that name exists.
|
|
1315
|
+
*
|
|
1316
|
+
* ```typescript
|
|
1317
|
+
* renderer.onLoaded(() => {
|
|
1318
|
+
* const key = renderer.getMeshChild("Key")
|
|
1319
|
+
* key?.addComponent(new Spinner("z"))
|
|
1320
|
+
* })
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
getMeshChild(name) {
|
|
1324
|
+
return this.meshChildMap?.get(name) ?? null;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Get all named child groups from the loaded mesh.
|
|
1328
|
+
* Returns null if the mesh hasn't loaded yet.
|
|
1329
|
+
*/
|
|
1330
|
+
getMeshChildren() {
|
|
1331
|
+
return this.meshChildMap ?? null;
|
|
1332
|
+
}
|
|
1255
1333
|
/**
|
|
1256
1334
|
* Apply the material override to all meshes
|
|
1257
1335
|
*/
|
|
@@ -1400,6 +1478,7 @@ var MeshRenderer = class extends Component {
|
|
|
1400
1478
|
});
|
|
1401
1479
|
this.mesh = null;
|
|
1402
1480
|
}
|
|
1481
|
+
this.meshChildMap = null;
|
|
1403
1482
|
}
|
|
1404
1483
|
};
|
|
1405
1484
|
MeshRenderer = __decorateClass([
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engine/interaction/InteractionZone.ts","../src/engine/mobile/VirtualJoystickThree.ts","../src/engine/movement/MovementController.ts","../src/engine/player/PlayerControllerThree.ts","../src/engine/render/SkeletalRenderer.ts","../src/engine/render/InstancedRenderer.ts","../src/engine/render/MeshRenderer.ts"],"sourcesContent":["import * as THREE from \"three\"\nimport { Component, GameObject } from \"@engine/core\"\nimport {\n RigidBodyComponentThree,\n RigidBodyType,\n ColliderShape,\n} from \"@systems/physics/RigidBodyComponentThree.ts\"\n\n/**\n * Simple interaction zone using Rapier physics triggers\n * Replaces the complex raycasting system with simple physics triggers\n */\nexport class InteractionZone extends Component {\n public readonly id: string\n\n private active: boolean = true\n private onEnterCallback?: (other: GameObject) => void\n private onExitCallback?: (other: GameObject) => void\n private entitiesInZone: Set<GameObject> = new Set()\n\n private rigidBody: RigidBodyComponentThree | null = null\n private visualMesh: THREE.Mesh | null = null\n private options: InteractionZoneOptions\n\n constructor(\n onEnter?: (other: GameObject) => void,\n onExit?: (other: GameObject) => void,\n options: InteractionZoneOptions = {}\n ) {\n super()\n this.id = `interaction_${Math.random().toString(36).substr(2, 9)}`\n this.onEnterCallback = onEnter\n this.onExitCallback = onExit\n this.options = {\n width: 2,\n depth: 2,\n active: true,\n show: true,\n ...options,\n }\n this.active = this.options.active ?? true\n }\n\n /**\n * Set interaction callbacks\n */\n public setCallbacks(callbacks: {\n onEnter?: (other: GameObject) => void\n onExit?: (other: GameObject) => void\n }): void {\n this.onEnterCallback = callbacks.onEnter\n this.onExitCallback = callbacks.onExit\n }\n\n /**\n * Called when component is attached to GameObject\n */\n protected onCreate(): void {\n if (this.options.show) {\n this.createVisualMesh()\n }\n this.createTriggerCollider()\n this.setActive(this.active)\n }\n\n public onEnabled(): void {\n // Trigger registration handled by explicit registerOnTriggerEnter/Exit() calls\n // No additional logic needed here\n }\n\n /**\n * Create the visual mesh for the interaction zone\n */\n private createVisualMesh(): void {\n const width = this.options.width!\n const height = 0.1 // Fixed height for top-down view\n const depth = this.options.depth!\n\n // Create box geometry\n const geometry = new THREE.BoxGeometry(width, height, depth)\n\n // Create semi-transparent material\n const material = new THREE.MeshBasicMaterial({\n color: 0x000000, // Black like the original\n transparent: true,\n opacity: 0.15, // Original opacity\n side: THREE.DoubleSide,\n })\n\n this.visualMesh = new THREE.Mesh(geometry, material)\n\n // Add to the GameObject\n this.gameObject.add(this.visualMesh)\n this.visualMesh.position.y += 0.1\n\n // Visual mesh created\n }\n\n /**\n * Create the physics trigger collider\n */\n private createTriggerCollider(): void {\n this.rigidBody = new RigidBodyComponentThree({\n type: RigidBodyType.STATIC,\n shape: ColliderShape.BOX,\n size: new THREE.Vector3(this.options.width!, 0.1, this.options.depth!), // Fixed height of 0.1\n isSensor: true, // This makes it a trigger collider\n // No collision groups = default behavior (can detect anything that wants to hit it)\n })\n\n // Apply center offset if specified (only X and Z since it's Vector2)\n if (this.options.centerOffset) {\n this.gameObject.position.x += this.options.centerOffset.x\n this.gameObject.position.z += this.options.centerOffset.y // Vector2.y maps to world Z\n }\n\n this.gameObject.addComponent(this.rigidBody)\n\n // Register methods directly - just like C# actions!\n this.rigidBody.registerOnTriggerEnter(this.onTriggerEnter.bind(this))\n this.rigidBody.registerOnTriggerExit(this.onTriggerExit.bind(this))\n }\n\n /**\n * Handle trigger enter event (to be called by physics system)\n */\n public onTriggerEnter(other: GameObject): void {\n // console.log(`🎯 ENTER: ${other.name} → InteractionZone ${this.id}`); // Reduced spam\n\n if (!this.active) {\n console.warn(`🎯 InteractionZone ${this.id}: Ignoring enter event - zone is inactive`)\n return\n }\n\n // Check if parent GameObject is enabled\n if (!this.gameObject.isEnabled()) {\n // console.log(`🎯 InteractionZone ${this.id}: Ignoring enter event - parent GameObject is disabled`);\n return\n }\n\n if (!this.entitiesInZone.has(other)) {\n this.entitiesInZone.add(other)\n // console.log(`🎯 Added ${other.name} to zone (total: ${this.entitiesInZone.size})`); // Reduced spam\n\n if (this.onEnterCallback) {\n this.onEnterCallback(other)\n }\n }\n }\n\n /**\n * Handle trigger exit event (to be called by physics system)\n */\n public onTriggerExit(other: GameObject): void {\n // console.log(`🎯 EXIT: ${other.name} ← InteractionZone ${this.id}`); // Reduced spam\n\n if (!this.active) {\n // Zone inactive, ignoring exit\n return\n }\n\n // Check if parent GameObject is enabled\n if (!this.gameObject.isEnabled()) {\n // console.log(`🎯 InteractionZone ${this.id}: Ignoring exit event - parent GameObject is disabled`);\n return\n }\n\n if (this.entitiesInZone.has(other)) {\n this.entitiesInZone.delete(other)\n // console.log(`🎯 Removed ${other.name} from zone (total: ${this.entitiesInZone.size})`); // Reduced spam\n\n if (this.onExitCallback) {\n this.onExitCallback(other)\n }\n }\n }\n\n /**\n * Get all entities currently in the zone\n */\n public getEntitiesInZone(): GameObject[] {\n return Array.from(this.entitiesInZone)\n }\n\n /**\n * Check if a specific entity is in the zone\n */\n public hasEntity(entity: GameObject): boolean {\n return this.entitiesInZone.has(entity)\n }\n\n /**\n * Set active state\n */\n public setActive(active: boolean): void {\n this.active = active\n\n // Show/hide visual mesh (only if visual mesh was created)\n if (this.visualMesh) {\n this.visualMesh.visible = active\n }\n\n if (this.rigidBody) {\n // TODO: Enable/disable the rigid body when that feature is available\n }\n\n if (!active) {\n // Clear all entities when deactivated\n this.entitiesInZone.clear()\n }\n }\n\n /**\n * Check if the interaction zone is active\n */\n public isActive(): boolean {\n return this.active\n }\n\n /**\n * Get the visual mesh\n */\n public getVisualMesh(): THREE.Mesh | null {\n return this.visualMesh\n }\n\n /**\n * Get the collider component\n */\n public getCollider(): RigidBodyComponentThree | null {\n return this.rigidBody\n }\n\n /**\n * Get the GameObject this zone is attached to\n */\n public getGameObject(): GameObject {\n return this.gameObject\n }\n\n /**\n * Component cleanup\n */\n protected onCleanup(): void {\n this.entitiesInZone.clear()\n\n // Trigger cleanup handled automatically by RigidBodyComponentThree!\n // The registered callbacks will be cleaned up when the RigidBody is destroyed\n\n // Clean up visual mesh\n if (this.visualMesh) {\n this.visualMesh.geometry.dispose()\n if (this.visualMesh.material instanceof THREE.Material) {\n this.visualMesh.material.dispose()\n }\n this.gameObject.remove(this.visualMesh)\n this.visualMesh = null\n }\n\n // RigidBodyComponentThree will be cleaned up automatically by the GameObject\n }\n}\n\n// Interface for options\nexport interface InteractionZoneOptions {\n width?: number\n depth?: number\n active?: boolean\n centerOffset?: THREE.Vector2\n show?: boolean\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\n\nexport interface VirtualJoystickOptions {\n size?: number // Size of the joystick base\n knobSize?: number // Size of the joystick knob\n deadZone?: number // Dead zone radius (0-1)\n maxDistance?: number // Maximum distance for knob movement\n color?: string // Color of the joystick\n visible?: boolean // Whether to show joystick visuals (still functional when hidden)\n opacity?: number // Opacity of joystick visuals (0-1)\n}\n\n/**\n * Virtual joystick component for mobile/touch input - Three.js version\n * Shows on pointer down, hides on pointer up\n * Provides normalized direction vector for movement\n * Uses HTML/CSS for UI elements\n */\nexport class VirtualJoystickThree extends Component {\n // Configuration\n private options: Required<VirtualJoystickOptions>\n\n // Mobile detection\n private static isMobileDevice(): boolean {\n return (\n /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||\n \"ontouchstart\" in window ||\n navigator.maxTouchPoints > 0\n )\n }\n\n // UI Elements\n private joystickContainer: HTMLElement | null = null\n private joystickBase: HTMLElement | null = null\n private joystickKnob: HTMLElement | null = null\n private mobileHint: HTMLElement | null = null\n\n // State\n private isActive: boolean = false\n private startPosition: THREE.Vector2 = new THREE.Vector2()\n private currentPosition: THREE.Vector2 = new THREE.Vector2()\n private direction: THREE.Vector2 = new THREE.Vector2()\n private magnitude: number = 0\n\n // Input tracking\n private joystickPointerId: number | null = null\n private isDragging: boolean = false\n private joystickRadius: number = 0\n\n // Event handlers (need to bind them for proper cleanup)\n private boundPointerDown = this.onPointerDown.bind(this)\n private boundPointerMove = this.onPointerMove.bind(this)\n private boundPointerUp = this.onPointerUp.bind(this)\n private boundTouchStart = this.onTouchStart.bind(this)\n private boundTouchMove = this.onTouchMove.bind(this)\n private boundTouchEnd = this.onTouchEnd.bind(this)\n\n constructor(options: VirtualJoystickOptions = {}) {\n super()\n\n this.options = {\n size: options.size ?? 120,\n knobSize: options.knobSize ?? 40,\n deadZone: options.deadZone ?? 0.15,\n maxDistance: options.maxDistance ?? 50,\n color: options.color ?? \"white\",\n visible: options.visible ?? true,\n opacity: options.opacity ?? 0.2,\n }\n\n this.joystickRadius = this.options.maxDistance\n }\n\n protected onCreate(): void {\n this.createJoystickUI()\n this.createMobileHint()\n this.setupInputHandlers()\n }\n\n protected onCleanup(): void {\n this.cleanupUI()\n this.removeInputHandlers()\n }\n\n /**\n * Create the joystick UI elements using HTML/CSS\n */\n private createJoystickUI(): void {\n // Create container for joystick (initially hidden)\n this.joystickContainer = document.createElement(\"div\")\n this.joystickContainer.id = \"virtual-joystick-container\"\n this.joystickContainer.style.cssText = `\n position: fixed;\n width: ${this.options.size + this.options.knobSize}px;\n height: ${this.options.size + this.options.knobSize}px;\n display: none;\n pointer-events: none;\n z-index: 1000;\n user-select: none;\n touch-action: none;\n `\n\n // Create joystick base (outer circle)\n this.joystickBase = document.createElement(\"div\")\n this.joystickBase.style.cssText = `\n position: absolute;\n width: ${this.options.size}px;\n height: ${this.options.size}px;\n border: 4px solid ${this.options.color};\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n box-sizing: border-box;\n opacity: ${this.options.opacity};\n `\n\n // Create joystick knob (inner circle)\n this.joystickKnob = document.createElement(\"div\")\n this.joystickKnob.style.cssText = `\n position: absolute;\n width: ${this.options.knobSize}px;\n height: ${this.options.knobSize}px;\n border: 2px solid ${this.options.color};\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.8);\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transition: none;\n box-sizing: border-box;\n opacity: ${this.options.opacity};\n `\n\n // Add elements to container\n this.joystickContainer.appendChild(this.joystickBase)\n this.joystickContainer.appendChild(this.joystickKnob)\n\n // Add to document body\n document.body.appendChild(this.joystickContainer)\n }\n\n /**\n * Create mobile hint for touch controls (only shows on mobile devices)\n */\n private createMobileHint(): void {\n // Only show hint on mobile devices\n if (!VirtualJoystickThree.isMobileDevice()) {\n return\n }\n\n this.mobileHint = document.createElement(\"div\")\n this.mobileHint.id = \"mobile-joystick-hint\"\n this.mobileHint.textContent = \"Touch & drag to move\"\n this.mobileHint.style.cssText = `\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 10px 20px;\n border-radius: 20px;\n font-family: Arial, sans-serif;\n font-size: 14px;\n z-index: 999;\n pointer-events: none;\n user-select: none;\n border: 2px solid rgba(255, 255, 255, 0.3);\n animation: fadeInOut 4s ease-in-out;\n `\n\n // Add CSS animation\n if (!document.querySelector(\"#mobile-hint-styles\")) {\n const style = document.createElement(\"style\")\n style.id = \"mobile-hint-styles\"\n style.textContent = `\n @keyframes fadeInOut {\n 0% { opacity: 0; transform: translateX(-50%) translateY(20px); }\n 20% { opacity: 1; transform: translateX(-50%) translateY(0px); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0px); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }\n }\n `\n document.head.appendChild(style)\n }\n\n document.body.appendChild(this.mobileHint)\n\n // Remove hint after animation completes\n setTimeout(() => {\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n this.mobileHint = null\n }\n }, 4000)\n }\n\n /**\n * Setup input handlers for pointer and touch events\n */\n private setupInputHandlers(): void {\n // Add both pointer and touch event listeners for maximum compatibility\n document.addEventListener(\"pointerdown\", this.boundPointerDown, {\n passive: false,\n })\n document.addEventListener(\"pointermove\", this.boundPointerMove, {\n passive: false,\n })\n document.addEventListener(\"pointerup\", this.boundPointerUp, {\n passive: false,\n })\n\n // Touch events as fallback\n document.addEventListener(\"touchstart\", this.boundTouchStart, {\n passive: false,\n })\n document.addEventListener(\"touchmove\", this.boundTouchMove, {\n passive: false,\n })\n document.addEventListener(\"touchend\", this.boundTouchEnd, {\n passive: false,\n })\n }\n\n /**\n * Remove input handlers\n */\n private removeInputHandlers(): void {\n document.removeEventListener(\"pointerdown\", this.boundPointerDown)\n document.removeEventListener(\"pointermove\", this.boundPointerMove)\n document.removeEventListener(\"pointerup\", this.boundPointerUp)\n\n document.removeEventListener(\"touchstart\", this.boundTouchStart)\n document.removeEventListener(\"touchmove\", this.boundTouchMove)\n document.removeEventListener(\"touchend\", this.boundTouchEnd)\n }\n\n /**\n * Handle pointer down event\n */\n private onPointerDown(event: PointerEvent): void {\n // Only respond to primary pointer (first touch/click)\n if (this.isActive || !event.isPrimary) return\n\n this.startJoystick(event.clientX, event.clientY, event.pointerId)\n event.preventDefault()\n }\n\n /**\n * Handle touch start event (fallback)\n */\n private onTouchStart(event: TouchEvent): void {\n if (this.isActive || event.touches.length === 0) return\n\n const touch = event.touches[0]\n this.startJoystick(touch.clientX, touch.clientY, touch.identifier)\n event.preventDefault()\n }\n\n /**\n * Start the joystick at the given position\n */\n private startJoystick(x: number, y: number, pointerId: number): void {\n this.isActive = true\n this.isDragging = true\n this.joystickPointerId = pointerId\n\n // Hide mobile hint when joystick is first used\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n this.mobileHint = null\n }\n\n // Set positions\n this.startPosition.set(x, y)\n this.currentPosition.set(x, y)\n\n // Show and position the joystick (only if visible is true)\n if (this.joystickContainer) {\n this.joystickContainer.style.display = this.options.visible ? \"block\" : \"none\"\n this.joystickContainer.style.left = `${x - (this.options.size + this.options.knobSize) / 2}px`\n this.joystickContainer.style.top = `${y - (this.options.size + this.options.knobSize) / 2}px`\n }\n\n // Reset knob to center\n if (this.joystickKnob) {\n this.joystickKnob.style.transform = \"translate(-50%, -50%)\"\n }\n\n this.updateDirection()\n }\n\n /**\n * Handle pointer move event\n */\n private onPointerMove(event: PointerEvent): void {\n if (!this.isActive || !this.isDragging || event.pointerId !== this.joystickPointerId) {\n return\n }\n\n this.updateJoystick(event.clientX, event.clientY)\n event.preventDefault()\n }\n\n /**\n * Handle touch move event (fallback)\n */\n private onTouchMove(event: TouchEvent): void {\n if (!this.isActive || !this.isDragging) return\n\n // Find the touch with our ID\n for (let i = 0; i < event.touches.length; i++) {\n const touch = event.touches[i]\n if (touch.identifier === this.joystickPointerId) {\n this.updateJoystick(touch.clientX, touch.clientY)\n event.preventDefault()\n break\n }\n }\n }\n\n /**\n * Update joystick position and direction\n */\n private updateJoystick(x: number, y: number): void {\n this.currentPosition.set(x, y)\n\n // Update knob position and direction\n this.updateKnobPosition()\n this.updateDirection()\n }\n\n /**\n * Handle pointer up event\n */\n private onPointerUp(event: PointerEvent): void {\n if (!this.isActive || event.pointerId !== this.joystickPointerId) {\n return\n }\n\n this.endJoystick()\n event.preventDefault()\n }\n\n /**\n * Handle touch end event (fallback)\n */\n private onTouchEnd(event: TouchEvent): void {\n if (!this.isActive) return\n\n // Check if our touch ended\n let touchEnded = true\n for (let i = 0; i < event.touches.length; i++) {\n if (event.touches[i].identifier === this.joystickPointerId) {\n touchEnded = false\n break\n }\n }\n\n if (touchEnded) {\n this.endJoystick()\n event.preventDefault()\n }\n }\n\n /**\n * End the joystick interaction\n */\n private endJoystick(): void {\n this.isActive = false\n this.isDragging = false\n this.joystickPointerId = null\n\n // Hide the joystick\n if (this.joystickContainer) {\n this.joystickContainer.style.display = \"none\"\n }\n\n // Reset direction\n this.direction.set(0, 0)\n this.magnitude = 0\n }\n\n /**\n * Update knob position based on current pointer position\n */\n private updateKnobPosition(): void {\n if (!this.joystickKnob) return\n\n // Calculate offset from joystick center\n const offset = this.currentPosition.clone().sub(this.startPosition)\n\n // Clamp to max distance\n const distance = offset.length()\n if (distance > this.joystickRadius) {\n offset.normalize().multiplyScalar(this.joystickRadius)\n }\n\n // Update knob position\n this.joystickKnob.style.transform = `translate(calc(-50% + ${offset.x}px), calc(-50% + ${offset.y}px))`\n }\n\n /**\n * Update direction vector based on knob position\n */\n private updateDirection(): void {\n // Calculate offset from center\n const offset = this.currentPosition.clone().sub(this.startPosition)\n const distance = offset.length()\n\n // Apply dead zone\n if (distance < this.options.deadZone * this.joystickRadius) {\n this.direction.set(0, 0)\n this.magnitude = 0\n return\n }\n\n // Calculate normalized direction\n const normalizedDistance = Math.min(distance / this.joystickRadius, 1.0)\n this.direction = offset.normalize()\n this.magnitude = normalizedDistance\n }\n\n /**\n * Get the current input direction as a Vector3 (Y=0 for movement)\n */\n public getDirection(): THREE.Vector3 | null {\n if (!this.isActive || this.magnitude === 0) {\n return null\n }\n\n // Convert 2D joystick input to 3D movement direction\n // X maps to X (left/right), Y maps to Z (forward/back)\n // Screen: drag up = negative Y, drag down = positive Y\n // World: forward = positive Z, back = negative Z\n // So: worldZ = -screenY (drag up = move forward)\n return new THREE.Vector3(\n this.direction.x * this.magnitude,\n 0,\n -this.direction.y * this.magnitude\n )\n }\n\n /**\n * Get the current input magnitude (0-1)\n */\n public getMagnitude(): number {\n return this.magnitude\n }\n\n /**\n * Check if the joystick is currently active\n */\n public isActiveJoystick(): boolean {\n return this.isActive\n }\n\n /**\n * Toggle joystick visual visibility (joystick remains functional)\n */\n public setVisible(visible: boolean): void {\n this.options.visible = visible\n if (this.joystickContainer && this.isActive) {\n this.joystickContainer.style.display = visible ? \"block\" : \"none\"\n }\n }\n\n /**\n * Get current visibility state\n */\n public isVisible(): boolean {\n return this.options.visible\n }\n\n /**\n * Set joystick opacity (0-1)\n */\n public setOpacity(opacity: number): void {\n this.options.opacity = Math.max(0, Math.min(1, opacity))\n if (this.joystickBase) {\n this.joystickBase.style.opacity = `${this.options.opacity}`\n }\n if (this.joystickKnob) {\n this.joystickKnob.style.opacity = `${this.options.opacity}`\n }\n }\n\n /**\n * Get current opacity\n */\n public getOpacity(): number {\n return this.options.opacity\n }\n\n /**\n * Clean up UI resources\n */\n private cleanupUI(): void {\n if (this.joystickContainer && this.joystickContainer.parentNode) {\n this.joystickContainer.parentNode.removeChild(this.joystickContainer)\n }\n\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n }\n\n this.joystickContainer = null\n this.joystickBase = null\n this.joystickKnob = null\n this.mobileHint = null\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\nimport { RigidBodyComponentThree } from \"@systems/physics/RigidBodyComponentThree.ts\"\n\n/**\n * Three.js Movement controller component that handles physics-based movement and rotation\n * Uses Rapier physics with rotation locking for smooth, controlled movement\n * Can be used for player input, AI navigation, or any entity that needs to move\n */\nexport class MovementController extends Component {\n // Public configuration properties\n public maxMoveSpeed: number = 8.0\n public acceleration: number = 40\n public turnSpeed: number = 12 // How fast to rotate (radians per second)\n\n private rigidBodyComponent: RigidBodyComponentThree | null = null\n private targetRotationY: number = 0\n private currentRotationY: number = 0\n\n // Pre-allocated vector for velocity queries to avoid GC pressure\n private _currentVelocity = new THREE.Vector3()\n\n /**\n * Called when the component is created and attached to a GameObject\n */\n protected onCreate(): void {\n // Find the rigid body component on this GameObject\n this.findRigidBodyComponentThree()\n\n // Initialize rotation tracking\n this.currentRotationY = this.gameObject.rotation.y\n this.targetRotationY = this.currentRotationY\n }\n\n /**\n * Find the rigid body component on this GameObject\n */\n private findRigidBodyComponentThree(): void {\n this.rigidBodyComponent = this.gameObject.getComponent(RigidBodyComponentThree) || null\n if (!this.rigidBodyComponent) {\n console.warn(\"MovementController: No RigidBodyComponentThree found on GameObject\")\n }\n }\n\n /**\n * Set the rigid body component this controller should manage\n */\n public setRigidBodyComponentThree(rigidBodyComponent: RigidBodyComponentThree): void {\n this.rigidBodyComponent = rigidBodyComponent\n }\n\n /**\n * Move the entity based on input direction\n * @param inputDirection Normalized direction vector (or null for no movement)\n * @param deltaTime Time since last frame in seconds\n */\n public move(inputDirection: THREE.Vector3 | null, deltaTime: number): void {\n if (!this.rigidBodyComponent) return\n\n const targetVelocity = this.calculateTargetVelocity(inputDirection)\n const smoothedVelocity = this.smoothVelocity(targetVelocity, deltaTime)\n\n // Apply velocity to physics body (Rapier handles rotation locking)\n this.rigidBodyComponent.setVelocity(smoothedVelocity)\n\n // Handle Y rotation separately and smoothly\n this.updateRotation(inputDirection, deltaTime)\n }\n\n /**\n * Calculate target velocity based on input direction\n */\n private calculateTargetVelocity(inputDirection: THREE.Vector3 | null): THREE.Vector3 {\n const targetVelocity = new THREE.Vector3(0, 0, 0)\n\n if (inputDirection && inputDirection.length() > 0.01) {\n targetVelocity.x = inputDirection.x * this.maxMoveSpeed\n targetVelocity.z = inputDirection.z * this.maxMoveSpeed\n }\n\n // Y velocity is always 0 (grounded movement)\n targetVelocity.y = 0\n\n return targetVelocity\n }\n\n /**\n * Smooth velocity towards target using acceleration\n */\n private smoothVelocity(targetVelocity: THREE.Vector3, deltaTime: number): THREE.Vector3 {\n if (!this.rigidBodyComponent) return targetVelocity\n\n this.rigidBodyComponent.getVelocity(this._currentVelocity)\n const maxDelta = this.acceleration * deltaTime\n\n // Smooth X and Z velocities\n const smoothedVelocity = new THREE.Vector3()\n smoothedVelocity.x = this.moveTowards(this._currentVelocity.x, targetVelocity.x, maxDelta)\n smoothedVelocity.z = this.moveTowards(this._currentVelocity.z, targetVelocity.z, maxDelta)\n smoothedVelocity.y = 0 // Keep grounded\n\n return smoothedVelocity\n }\n\n /**\n * Update rotation smoothly towards movement direction using quaternion slerp\n */\n private updateRotation(inputDirection: THREE.Vector3 | null, deltaTime: number): void {\n // Always clear angular velocity first to prevent unwanted spinning\n if (this.rigidBodyComponent) {\n const rigidBody = this.rigidBodyComponent.getRigidBody()\n if (rigidBody) {\n // Stop any existing angular rotation to prevent spinning\n this.rigidBodyComponent.setAngularVelocity(new THREE.Vector3(0, 0, 0))\n }\n }\n\n // If no input direction, don't update rotation - just ensure rotation is stopped\n if (!inputDirection || inputDirection.length() < 0.01) {\n // We've already cleared angular velocity above, so just return\n return\n }\n\n // Calculate target rotation based on movement direction\n const targetRotationY = Math.atan2(inputDirection.x, inputDirection.z)\n\n // Create target quaternion\n const targetQuaternion = new THREE.Quaternion()\n targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), targetRotationY)\n\n // Get current quaternion\n const currentQuaternion = new THREE.Quaternion()\n currentQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.currentRotationY)\n\n // Calculate slerp factor based on turn speed\n const slerpFactor = Math.min(1.0, this.turnSpeed * deltaTime)\n\n // Slerp between current and target rotation\n const resultQuaternion = new THREE.Quaternion()\n resultQuaternion.slerpQuaternions(currentQuaternion, targetQuaternion, slerpFactor)\n\n // Update current rotation Y for tracking\n const euler = new THREE.Euler()\n euler.setFromQuaternion(resultQuaternion, \"YXZ\")\n this.currentRotationY = euler.y\n\n // Apply rotation through physics body if available, otherwise directly to GameObject\n if (this.rigidBodyComponent) {\n const rigidBody = this.rigidBodyComponent.getRigidBody()\n if (rigidBody) {\n // Apply to physics body (this will sync back to GameObject automatically)\n const rapierQuat = {\n x: resultQuaternion.x,\n y: resultQuaternion.y,\n z: resultQuaternion.z,\n w: resultQuaternion.w,\n }\n rigidBody.setRotation(rapierQuat, true)\n } else {\n // Fallback: apply rotation directly to GameObject\n this.gameObject.rotation.y = this.currentRotationY\n }\n } else {\n // Fallback: apply rotation directly to GameObject\n this.gameObject.rotation.y = this.currentRotationY\n }\n }\n\n /**\n * Utility function to move a value towards a target at a given rate\n */\n private moveTowards(current: number, target: number, maxDelta: number): number {\n const delta = target - current\n if (Math.abs(delta) <= maxDelta) {\n return target\n }\n return current + Math.sign(delta) * maxDelta\n }\n\n /**\n * Get current movement state for debugging\n */\n public getMovementState(): any {\n return {\n maxMoveSpeed: this.maxMoveSpeed,\n acceleration: this.acceleration,\n turnSpeed: this.turnSpeed,\n currentRotationY: this.currentRotationY,\n targetRotationY: this.targetRotationY,\n hasRigidBody: !!this.rigidBodyComponent,\n }\n }\n\n /**\n * Clean up resources when the component is removed\n */\n protected onCleanup(): void {\n this.rigidBodyComponent = null\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\n\nexport interface PlayerControls {\n forward: string\n backward: string\n left: string\n right: string\n run: string\n interact: string\n}\n\n/**\n * Three.js Player Controller Component\n * Handles WASD movement, mouse look, and basic interactions\n */\nexport class PlayerControllerThree extends Component {\n // Movement parameters\n public moveSpeed: number = 5.0\n public runSpeed: number = 8.0\n public rotationSpeed: number = 2.0\n\n // Controls configuration\n private controls: PlayerControls = {\n forward: \"KeyW\",\n backward: \"KeyS\",\n left: \"KeyA\",\n right: \"KeyD\",\n run: \"ShiftLeft\",\n interact: \"KeyE\",\n }\n\n // Input state\n private keys: Set<string> = new Set()\n private mouseX: number = 0\n private mouseY: number = 0\n private mouseSensitivity: number = 0.002\n private isPointerLocked: boolean = false\n\n // Camera reference\n private camera: THREE.PerspectiveCamera\n private cameraHeight: number = 1.7 // Eye level height\n\n // Movement state\n private velocity: THREE.Vector3 = new THREE.Vector3()\n private direction: THREE.Vector3 = new THREE.Vector3()\n\n constructor(camera: THREE.PerspectiveCamera) {\n super()\n this.camera = camera\n }\n\n protected onCreate(): void {\n this.setupEventListeners()\n this.setupPointerLock()\n\n // Position camera at player eye level\n this.updateCameraPosition()\n\n console.log(\"🎮 Player controller initialized\")\n console.log(\"📋 Controls: WASD to move, Shift to run, E to interact, Click to look around\")\n }\n\n /**\n * Set up keyboard and mouse event listeners\n */\n private setupEventListeners(): void {\n // Keyboard events\n document.addEventListener(\"keydown\", this.onKeyDown.bind(this))\n document.addEventListener(\"keyup\", this.onKeyUp.bind(this))\n\n // Mouse events\n document.addEventListener(\"mousemove\", this.onMouseMove.bind(this))\n document.addEventListener(\"click\", this.onClick.bind(this))\n\n // Pointer lock events\n document.addEventListener(\"pointerlockchange\", this.onPointerLockChange.bind(this))\n }\n\n /**\n * Set up pointer lock for mouse look\n */\n private setupPointerLock(): void {\n const canvas = document.getElementById(\"renderCanvas\") as HTMLCanvasElement\n if (canvas) {\n canvas.addEventListener(\"click\", () => {\n if (!this.isPointerLocked) {\n canvas.requestPointerLock()\n }\n })\n }\n }\n\n /**\n * Handle key down events\n */\n private onKeyDown(event: KeyboardEvent): void {\n this.keys.add(event.code)\n\n // Handle special keys\n if (event.code === this.controls.interact) {\n this.onInteract()\n }\n }\n\n /**\n * Handle key up events\n */\n private onKeyUp(event: KeyboardEvent): void {\n this.keys.delete(event.code)\n }\n\n /**\n * Handle mouse movement for looking around\n */\n private onMouseMove(event: MouseEvent): void {\n if (!this.isPointerLocked) return\n\n this.mouseX += event.movementX * this.mouseSensitivity\n this.mouseY += event.movementY * this.mouseSensitivity\n\n // Clamp vertical rotation\n this.mouseY = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.mouseY))\n\n // Apply rotation to camera\n this.camera.rotation.order = \"YXZ\"\n this.camera.rotation.y = -this.mouseX\n this.camera.rotation.x = -this.mouseY\n }\n\n /**\n * Handle canvas clicks for pointer lock\n */\n private onClick(event: MouseEvent): void {\n const canvas = document.getElementById(\"renderCanvas\") as HTMLCanvasElement\n if (event.target === canvas && !this.isPointerLocked) {\n canvas.requestPointerLock()\n }\n }\n\n /**\n * Handle pointer lock state changes\n */\n private onPointerLockChange(): void {\n this.isPointerLocked = document.pointerLockElement !== null\n\n if (this.isPointerLocked) {\n console.log(\"🖱️ Mouse locked - look around!\")\n } else {\n console.log(\"🖱️ Mouse unlocked - click canvas to lock again\")\n }\n }\n\n /**\n * Handle interaction key press\n */\n private onInteract(): void {\n console.log(\"🤝 Player trying to interact...\")\n // TODO: Implement interaction system\n }\n\n /**\n * Update method - called every frame\n */\n public update(deltaTime: number): void {\n this.updateMovement(deltaTime)\n this.updateCameraPosition()\n }\n\n /**\n * Update player movement based on input\n */\n private updateMovement(deltaTime: number): void {\n // Reset direction\n this.direction.set(0, 0, 0)\n\n // Get current movement speed\n const isRunning = this.keys.has(this.controls.run)\n const currentSpeed = isRunning ? this.runSpeed : this.moveSpeed\n\n // Calculate movement direction based on camera orientation\n const forward = new THREE.Vector3()\n const right = new THREE.Vector3()\n\n // Get camera's forward direction (ignoring Y rotation for ground movement)\n this.camera.getWorldDirection(forward)\n forward.y = 0\n forward.normalize()\n\n // Get camera's right direction\n right.crossVectors(forward, this.camera.up).normalize()\n\n // Apply movement inputs\n if (this.keys.has(this.controls.forward)) {\n this.direction.add(forward)\n }\n if (this.keys.has(this.controls.backward)) {\n this.direction.sub(forward)\n }\n if (this.keys.has(this.controls.right)) {\n this.direction.add(right)\n }\n if (this.keys.has(this.controls.left)) {\n this.direction.sub(right)\n }\n\n // Normalize direction to prevent faster diagonal movement\n if (this.direction.length() > 0) {\n this.direction.normalize()\n\n // Apply movement to player position\n this.velocity.copy(this.direction).multiplyScalar(currentSpeed * deltaTime)\n this.gameObject.position.add(this.velocity)\n }\n\n // Keep player on ground level (no Y drift)\n this.gameObject.position.y = 0\n }\n\n /**\n * Update camera position to follow player\n */\n private updateCameraPosition(): void {\n // Position camera at player position + eye height\n this.camera.position.copy(this.gameObject.position)\n this.camera.position.y += this.cameraHeight\n }\n\n /**\n * Get current movement state for debugging\n */\n public getMovementState(): {\n position: THREE.Vector3\n isMoving: boolean\n isRunning: boolean\n lookDirection: THREE.Vector3\n } {\n const lookDirection = new THREE.Vector3()\n this.camera.getWorldDirection(lookDirection)\n\n return {\n position: this.gameObject.position.clone(),\n isMoving: this.direction.length() > 0,\n isRunning: this.keys.has(this.controls.run),\n lookDirection: lookDirection,\n }\n }\n\n /**\n * Set player position\n */\n public setPosition(position: THREE.Vector3): void {\n this.gameObject.position.copy(position)\n this.updateCameraPosition()\n }\n\n /**\n * Clean up event listeners\n */\n protected onCleanup(): void {\n document.removeEventListener(\"keydown\", this.onKeyDown.bind(this))\n document.removeEventListener(\"keyup\", this.onKeyUp.bind(this))\n document.removeEventListener(\"mousemove\", this.onMouseMove.bind(this))\n document.removeEventListener(\"click\", this.onClick.bind(this))\n document.removeEventListener(\"pointerlockchange\", this.onPointerLockChange.bind(this))\n\n // Exit pointer lock if active\n if (this.isPointerLocked) {\n document.exitPointerLock()\n }\n\n console.log(\"🎮 Player controller cleaned up\")\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\nimport { AssetManager } from \"@engine/assets\"\n\n/**\n * Three.js Skeletal renderer for animated FBX models\n * - Uses AssetManager for preloading coordination\n * - Uses SkeletonCache for proper skeletal cloning\n * - Specifically designed for animated characters\n * - Always cloned, always has shadows, never static\n */\nexport class SkeletalRenderer extends Component {\n private _group: THREE.Group | null = null\n private _assetPath: string\n private _material: THREE.Material | null\n private _skeletalModel: THREE.Object3D | null = null\n\n constructor(assetPath: string, material?: THREE.Material) {\n super()\n this._assetPath = assetPath\n this._material = material || null\n }\n\n protected onCreate(): void {\n this.createSkeletalMesh()\n }\n\n private createSkeletalMesh(): void {\n if (!this._assetPath) {\n throw new Error(\"SkeletalRenderer: No asset path specified\")\n }\n\n // Create wrapper group for this instance\n this._group = new THREE.Group()\n this._group.name = `skeletal_${this._assetPath.split(\"/\").pop()}`\n this.gameObject.add(this._group)\n\n // Get properly cloned skeletal model from AssetManager's skeleton cache\n this._skeletalModel = AssetManager.getSkeletalClone(this._assetPath)\n\n if (!this._skeletalModel) {\n throw new Error(\n `No skeletal model found for '${this._assetPath}'. Make sure to preload with AssetManager.preloadSkeletalModel() first.`\n )\n }\n\n // Apply custom material if provided\n if (this._material) {\n this._skeletalModel.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.material = this._material!\n child.frustumCulled = false\n }\n })\n } else {\n this._skeletalModel.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.frustumCulled = false\n }\n })\n }\n\n this._group.add(this._skeletalModel)\n\n // Always apply shadows for characters\n this.applyShadowsToGroup(this._group)\n }\n\n private applyShadowsToGroup(group: THREE.Group): void {\n group.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.castShadow = true\n child.receiveShadow = true\n }\n })\n }\n\n // ========== Public API ==========\n\n /**\n * Get the wrapper group (attached to GameObject)\n */\n public getGroup(): THREE.Group | null {\n return this._group\n }\n\n /**\n * Get the skeletal model (for animation setup)\n */\n public getSkeletalModel(): THREE.Object3D | null {\n return this._skeletalModel\n }\n\n /**\n * Get the asset path being rendered\n */\n public getAssetPath(): string {\n return this._assetPath\n }\n\n /**\n * Enable or disable visibility\n */\n public setVisible(visible: boolean): void {\n if (this._group) {\n this._group.visible = visible\n }\n }\n\n /**\n * Get visibility state\n */\n public isVisible(): boolean {\n return this._group?.visible ?? false\n }\n\n // ========== Component Lifecycle ==========\n\n protected onCleanup(): void {\n if (this._group) {\n this.gameObject.remove(this._group)\n }\n // Note: Don't track instance destruction since AssetManager doesn't track SkeletonCache instances\n }\n\n public onEnabled(): void {\n this.setVisible(true)\n }\n\n public onDisabled(): void {\n this.setVisible(false)\n }\n}\n","import { Component } from \"@engine/core\"\nimport { InstancedMeshManager } from \"./InstancedMeshManager\"\n\n/**\n * Options for InstancedRenderer\n */\nexport interface InstancedRendererOptions {\n /** If true (default), matrix updates every frame. If false, only updates when markDirty() is called. */\n isDynamic?: boolean\n /** Whether instances cast shadows (default: false). Only used if batch is auto-created. */\n castShadow?: boolean\n /** Whether instances receive shadows (default: false). Only used if batch is auto-created. */\n receiveShadow?: boolean\n /** Initial batch capacity (default: 16, grows automatically). Only used if batch is auto-created. */\n initialCapacity?: number\n}\n\n/**\n * Component for rendering objects using GPU instancing.\n *\n * Transform is controlled by the GameObject - the manager syncs matrices every frame.\n * If no batch exists for the key, one is created automatically from the GameObject's mesh.\n *\n * Performance modes:\n * - Dynamic (default): Matrix updates every frame. Use for moving objects.\n * - Static: Matrix only updates when markDirty() is called. Use for stationary objects.\n *\n * Usage:\n * ```typescript\n * // Simple - batch auto-creates from GameObject's mesh\n * gameObject.addComponent(new InstancedRenderer(\"burger\"))\n *\n * // With options\n * gameObject.addComponent(new InstancedRenderer(\"burger\", {\n * isDynamic: false,\n * castShadow: true\n * }))\n *\n * // For stationary objects, switch to static mode\n * renderer.setDynamic(false)\n *\n * // When a static object moves, mark it dirty\n * renderer.markDirty()\n * ```\n *\n * Key differences from mesh-based renderers (ObjRenderer, etc.):\n * - No getMesh() - instances don't have their own THREE.Object3D\n * - No getBounds() - can query the batch geometry if needed\n * - All instances share the same geometry and material\n */\nexport class InstancedRenderer extends Component {\n private readonly batchKey: string\n private readonly options: InstancedRendererOptions\n private instanceId: string | null = null\n\n /**\n * Create an InstancedRenderer\n * @param batchKey The batch key to register with. If no batch exists, one is auto-created.\n * @param options Configuration options (or just pass `true`/`false` for isDynamic)\n */\n constructor(batchKey: string, options: InstancedRendererOptions | boolean = {}) {\n super()\n this.batchKey = batchKey\n // Support legacy boolean parameter for isDynamic\n this.options = typeof options === \"boolean\" ? { isDynamic: options } : options\n }\n\n /**\n * Register with the batch when the component is created.\n * If no batch exists, one will be created automatically from this GameObject's mesh.\n */\n protected onCreate(): void {\n const manager = InstancedMeshManager.getInstance()\n\n if (!manager.isReady()) {\n console.error(\n `InstancedRenderer: Manager not initialized. Call InstancedMeshManager.getInstance().initialize(scene) first.`\n )\n return\n }\n\n // addInstance now auto-creates batch from GameObject if needed\n this.instanceId = manager.addInstance(this.batchKey, this.gameObject, {\n isDynamic: this.options.isDynamic ?? true,\n castShadow: this.options.castShadow ?? false,\n receiveShadow: this.options.receiveShadow ?? false,\n initialCapacity: this.options.initialCapacity,\n })\n\n if (!this.instanceId) {\n console.error(`InstancedRenderer: Failed to add instance to batch '${this.batchKey}'`)\n }\n }\n\n /**\n * Set the visibility of this instance\n */\n public setVisible(visible: boolean): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().setInstanceVisible(this.batchKey, this.instanceId, visible)\n }\n }\n\n /**\n * Get the visibility of this instance\n */\n public getVisible(): boolean {\n if (this.instanceId) {\n return InstancedMeshManager.getInstance().getInstanceVisible(this.batchKey, this.instanceId)\n }\n return false\n }\n\n /**\n * Show the instance (convenience method)\n */\n public show(): void {\n this.setVisible(true)\n }\n\n /**\n * Hide the instance (convenience method)\n */\n public hide(): void {\n this.setVisible(false)\n }\n\n /**\n * Get the batch key this renderer is registered with\n */\n public getBatchKey(): string {\n return this.batchKey\n }\n\n /**\n * Check if this renderer is successfully registered with a batch\n */\n public isRegistered(): boolean {\n return this.instanceId !== null\n }\n\n /**\n * Get the instance ID (for debugging)\n */\n public getInstanceId(): string | null {\n return this.instanceId\n }\n\n /**\n * Mark this instance as needing a matrix update.\n * Only relevant for static instances - dynamic instances update every frame anyway.\n * Call this when the transform of a static instance changes.\n */\n public markDirty(): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().markInstanceDirty(this.batchKey, this.instanceId)\n }\n }\n\n /**\n * Set whether this instance is dynamic (updates every frame) or static (only when marked dirty).\n * Use this to optimize performance when items transition between moving and stationary states.\n * @param isDynamic If true, updates every frame. If false, only updates when markDirty() is called.\n */\n public setDynamic(isDynamic: boolean): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().setInstanceDynamic(\n this.batchKey,\n this.instanceId,\n isDynamic\n )\n }\n }\n\n /**\n * Called when the GameObject becomes enabled\n */\n public onEnabled(): void {\n this.setVisible(true)\n }\n\n /**\n * Called when the GameObject becomes disabled\n */\n public onDisabled(): void {\n this.setVisible(false)\n }\n\n /**\n * Unregister from the batch when the component is cleaned up\n */\n protected onCleanup(): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().removeInstance(this.batchKey, this.instanceId)\n this.instanceId = null\n }\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\nimport {\n PrefabComponent,\n PrefabInstance,\n type ComponentJSON,\n type PrefabNode,\n} from \"@systems/prefabs\"\nimport { StowKitSystem } from \"@systems/stowkit\"\n\ninterface StowMeshJSON extends ComponentJSON {\n type: \"stow_mesh\"\n mesh: {\n pack: string\n assetId: string\n }\n castShadow?: boolean\n receiveShadow?: boolean\n}\n\n/**\n * MeshRenderer - Component for rendering a mesh from StowKitSystem.\n *\n * This is a non-instanced renderer - each instance is a separate draw call.\n * Use InstancedRenderer for many instances of the same mesh (better performance).\n *\n * Usage:\n * ```typescript\n * const env = new GameObject(\"Environment\")\n * env.addComponent(new MeshRenderer(\"restaurant_display_common\"))\n * ```\n */\n@PrefabComponent(\"stow_mesh\")\nexport class MeshRenderer extends Component {\n static fromPrefabJSON(json: StowMeshJSON, _node: PrefabNode): MeshRenderer {\n if (!json.mesh?.assetId) {\n console.error(`[MeshRenderer] stow_mesh component missing mesh.assetId:`, json)\n return new MeshRenderer(\"unknown\")\n }\n // Priority: JSON property > context options > default (true)\n const options = PrefabInstance.currentOptions\n const castShadow = json.castShadow ?? options?.castShadow ?? true\n const receiveShadow = json.receiveShadow ?? options?.receiveShadow ?? true\n return new MeshRenderer(json.mesh.assetId, castShadow, receiveShadow)\n }\n\n private mesh: THREE.Group | null = null\n private readonly meshName: string\n private readonly castShadow: boolean\n private readonly receiveShadow: boolean\n private _isStatic: boolean\n private isMeshLoaded: boolean = false\n private materialOverride: THREE.Material | null = null\n private loadedCallbacks: (() => void)[] | null = null\n\n /**\n * @param meshName The name of the mesh in the StowKit pack\n * @param castShadow Whether meshes should cast shadows (default: true)\n * @param receiveShadow Whether meshes should receive shadows (default: true)\n * @param isStatic Whether this mesh is static (default: false). Static meshes have matrixAutoUpdate disabled for better performance.\n * @param materialOverride Optional material to use instead of the default StowKit material\n */\n constructor(\n meshName: string,\n castShadow: boolean = true,\n receiveShadow: boolean = true,\n isStatic: boolean = false,\n materialOverride: THREE.Material | null = null\n ) {\n super()\n this.meshName = meshName\n this.castShadow = castShadow\n this.receiveShadow = receiveShadow\n this._isStatic = isStatic\n this.materialOverride = materialOverride\n }\n\n protected onCreate(): void {\n const stowkit = StowKitSystem.getInstance()\n\n // Check if mesh is already cached\n const cachedMesh = stowkit.getMeshSync(this.meshName)\n if (cachedMesh) {\n this.addMesh(cachedMesh)\n } else {\n // Start async load - will add mesh in update when ready\n stowkit.getMesh(this.meshName)\n }\n }\n\n public update(_deltaTime: number): void {\n if (this.isMeshLoaded) return\n\n const stowkit = StowKitSystem.getInstance()\n const cachedMesh = stowkit.getMeshSync(this.meshName)\n if (cachedMesh) {\n this.addMesh(cachedMesh)\n }\n }\n\n private addMesh(original: THREE.Group): void {\n this.isMeshLoaded = true\n\n // Clone mesh with material conversion\n this.mesh = StowKitSystem.getInstance().cloneMeshSync(\n original,\n this.castShadow,\n this.receiveShadow\n )\n\n // Apply material override if set\n if (this.materialOverride) {\n this.applyMaterialOverride()\n }\n\n this.gameObject.add(this.mesh)\n\n // For static meshes, disable matrix auto-update to save CPU\n if (this._isStatic) {\n this.setStatic(true)\n }\n\n // Fire onLoaded callbacks\n if (this.loadedCallbacks) {\n for (const cb of this.loadedCallbacks) cb()\n this.loadedCallbacks = null\n }\n }\n\n /**\n * Apply the material override to all meshes\n */\n private applyMaterialOverride(): void {\n if (!this.mesh || !this.materialOverride) return\n\n this.mesh.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n child.material = this.materialOverride!\n }\n })\n }\n\n /**\n * Set a material override for all meshes in this renderer.\n * Call this after the mesh is loaded, or pass it in the constructor.\n */\n public setMaterial(material: THREE.Material): void {\n this.materialOverride = material\n if (this.mesh) {\n this.applyMaterialOverride()\n }\n }\n\n /**\n * Check if this mesh is currently static (no automatic matrix updates)\n */\n public get isStatic(): boolean {\n return this._isStatic\n }\n\n /**\n * Set whether this mesh is static (no automatic matrix updates).\n * Static meshes save CPU by not recalculating transforms every frame.\n * Call forceMatrixUpdate() after moving a static mesh.\n */\n public setStatic(isStatic: boolean): void {\n this._isStatic = isStatic\n if (!this.mesh) return\n\n if (isStatic) {\n // Update matrices one final time before freezing\n this.forceMatrixUpdate()\n\n // Disable auto-update on mesh and all descendants\n this.mesh.matrixAutoUpdate = false\n this.mesh.traverse((child) => {\n child.matrixAutoUpdate = false\n })\n this.gameObject.matrixAutoUpdate = false\n } else {\n // Enable auto-update\n this.mesh.matrixAutoUpdate = true\n this.mesh.traverse((child) => {\n child.matrixAutoUpdate = true\n })\n this.gameObject.matrixAutoUpdate = true\n }\n }\n\n /**\n * Force a one-time matrix update. Call this after moving a static mesh.\n * Does not change the static/dynamic state.\n */\n public forceMatrixUpdate(): void {\n if (this.mesh) {\n this.mesh.updateMatrix()\n this.mesh.updateMatrixWorld(true)\n }\n this.gameObject.updateMatrix()\n this.gameObject.updateMatrixWorld(true)\n }\n\n /**\n * Get the mesh group (null if not yet loaded)\n */\n public getMesh(): THREE.Group | null {\n return this.mesh\n }\n\n /**\n * Get the name of the mesh this component is managing\n */\n public getMeshName(): string {\n return this.meshName\n }\n\n /**\n * Check if the mesh was successfully loaded\n */\n public isLoaded(): boolean {\n return this.mesh !== null\n }\n\n /**\n * Register a callback that fires when the mesh finishes loading,\n * or immediately if already loaded.\n */\n public onLoaded(callback: () => void): void {\n if (this.mesh) {\n callback()\n return\n }\n if (!this.loadedCallbacks) this.loadedCallbacks = []\n this.loadedCallbacks.push(callback)\n }\n\n /**\n * Get the first material from the loaded mesh, or null if not yet loaded.\n */\n public getMaterial(): THREE.Material | null {\n if (!this.mesh) return null\n let result: THREE.Material | null = null\n this.mesh.traverse((child) => {\n if (!result && child instanceof THREE.Mesh && child.material) {\n result = Array.isArray(child.material) ? child.material[0] : child.material\n }\n })\n return result\n }\n\n /**\n * Set the visibility of the mesh\n */\n public setVisible(visible: boolean): void {\n if (this.mesh) {\n this.mesh.visible = visible\n }\n }\n\n /**\n * Get bounds of the mesh (useful for physics)\n */\n public getBounds(): THREE.Vector3 | null {\n if (!this.mesh) {\n return null\n }\n return StowKitSystem.getInstance().getBounds(this.mesh)\n }\n\n /**\n * Cleanup - remove mesh from scene and dispose of resources\n */\n protected onCleanup(): void {\n if (this.mesh) {\n // Remove from GameObject/scene\n this.gameObject.remove(this.mesh)\n\n // Traverse and dispose of geometries and materials\n this.mesh.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n if (child.geometry) {\n child.geometry.dispose()\n }\n\n if (child.material) {\n if (Array.isArray(child.material)) {\n child.material.forEach((m) => {\n if (m.map) m.map.dispose()\n m.dispose()\n })\n } else {\n if (child.material.map) child.material.map.dispose()\n child.material.dispose()\n }\n }\n }\n })\n\n this.mesh = null\n }\n }\n}\n"],"mappings":"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","names":["THREE","THREE","THREE","THREE","THREE"]}
|
|
1
|
+
{"version":3,"sources":["../src/engine/interaction/InteractionZone.ts","../src/engine/mobile/VirtualJoystickThree.ts","../src/engine/movement/MovementController.ts","../src/engine/player/PlayerControllerThree.ts","../src/engine/render/SkeletalRenderer.ts","../src/engine/render/InstancedRenderer.ts","../src/engine/render/MeshRenderer.ts"],"sourcesContent":["import * as THREE from \"three\"\nimport { Component, GameObject } from \"@engine/core\"\nimport {\n RigidBodyComponentThree,\n RigidBodyType,\n ColliderShape,\n} from \"@systems/physics/RigidBodyComponentThree.ts\"\n\n/**\n * Simple interaction zone using Rapier physics triggers\n * Replaces the complex raycasting system with simple physics triggers\n */\nexport class InteractionZone extends Component {\n public readonly id: string\n\n private active: boolean = true\n private onEnterCallback?: (other: GameObject) => void\n private onExitCallback?: (other: GameObject) => void\n private entitiesInZone: Set<GameObject> = new Set()\n\n private rigidBody: RigidBodyComponentThree | null = null\n private visualMesh: THREE.Mesh | null = null\n private options: InteractionZoneOptions\n\n constructor(\n onEnter?: (other: GameObject) => void,\n onExit?: (other: GameObject) => void,\n options: InteractionZoneOptions = {}\n ) {\n super()\n this.id = `interaction_${Math.random().toString(36).substr(2, 9)}`\n this.onEnterCallback = onEnter\n this.onExitCallback = onExit\n this.options = {\n width: 2,\n depth: 2,\n active: true,\n show: true,\n ...options,\n }\n this.active = this.options.active ?? true\n }\n\n /**\n * Set interaction callbacks\n */\n public setCallbacks(callbacks: {\n onEnter?: (other: GameObject) => void\n onExit?: (other: GameObject) => void\n }): void {\n this.onEnterCallback = callbacks.onEnter\n this.onExitCallback = callbacks.onExit\n }\n\n /**\n * Called when component is attached to GameObject\n */\n protected onCreate(): void {\n if (this.options.show) {\n this.createVisualMesh()\n }\n this.createTriggerCollider()\n this.setActive(this.active)\n }\n\n public onEnabled(): void {\n // Trigger registration handled by explicit registerOnTriggerEnter/Exit() calls\n // No additional logic needed here\n }\n\n /**\n * Create the visual mesh for the interaction zone\n */\n private createVisualMesh(): void {\n const width = this.options.width!\n const height = 0.1 // Fixed height for top-down view\n const depth = this.options.depth!\n\n // Create box geometry\n const geometry = new THREE.BoxGeometry(width, height, depth)\n\n // Create semi-transparent material\n const material = new THREE.MeshBasicMaterial({\n color: 0x000000, // Black like the original\n transparent: true,\n opacity: 0.15, // Original opacity\n side: THREE.DoubleSide,\n })\n\n this.visualMesh = new THREE.Mesh(geometry, material)\n\n // Add to the GameObject\n this.gameObject.add(this.visualMesh)\n this.visualMesh.position.y += 0.1\n\n // Visual mesh created\n }\n\n /**\n * Create the physics trigger collider\n */\n private createTriggerCollider(): void {\n this.rigidBody = new RigidBodyComponentThree({\n type: RigidBodyType.STATIC,\n shape: ColliderShape.BOX,\n size: new THREE.Vector3(this.options.width!, 0.1, this.options.depth!), // Fixed height of 0.1\n isSensor: true, // This makes it a trigger collider\n // No collision groups = default behavior (can detect anything that wants to hit it)\n })\n\n // Apply center offset if specified (only X and Z since it's Vector2)\n if (this.options.centerOffset) {\n this.gameObject.position.x += this.options.centerOffset.x\n this.gameObject.position.z += this.options.centerOffset.y // Vector2.y maps to world Z\n }\n\n this.gameObject.addComponent(this.rigidBody)\n\n // Register methods directly - just like C# actions!\n this.rigidBody.registerOnTriggerEnter(this.onTriggerEnter.bind(this))\n this.rigidBody.registerOnTriggerExit(this.onTriggerExit.bind(this))\n }\n\n /**\n * Handle trigger enter event (to be called by physics system)\n */\n public onTriggerEnter(other: GameObject): void {\n // console.log(`🎯 ENTER: ${other.name} → InteractionZone ${this.id}`); // Reduced spam\n\n if (!this.active) {\n console.warn(`🎯 InteractionZone ${this.id}: Ignoring enter event - zone is inactive`)\n return\n }\n\n // Check if parent GameObject is enabled\n if (!this.gameObject.isEnabled()) {\n // console.log(`🎯 InteractionZone ${this.id}: Ignoring enter event - parent GameObject is disabled`);\n return\n }\n\n if (!this.entitiesInZone.has(other)) {\n this.entitiesInZone.add(other)\n // console.log(`🎯 Added ${other.name} to zone (total: ${this.entitiesInZone.size})`); // Reduced spam\n\n if (this.onEnterCallback) {\n this.onEnterCallback(other)\n }\n }\n }\n\n /**\n * Handle trigger exit event (to be called by physics system)\n */\n public onTriggerExit(other: GameObject): void {\n // console.log(`🎯 EXIT: ${other.name} ← InteractionZone ${this.id}`); // Reduced spam\n\n if (!this.active) {\n // Zone inactive, ignoring exit\n return\n }\n\n // Check if parent GameObject is enabled\n if (!this.gameObject.isEnabled()) {\n // console.log(`🎯 InteractionZone ${this.id}: Ignoring exit event - parent GameObject is disabled`);\n return\n }\n\n if (this.entitiesInZone.has(other)) {\n this.entitiesInZone.delete(other)\n // console.log(`🎯 Removed ${other.name} from zone (total: ${this.entitiesInZone.size})`); // Reduced spam\n\n if (this.onExitCallback) {\n this.onExitCallback(other)\n }\n }\n }\n\n /**\n * Get all entities currently in the zone\n */\n public getEntitiesInZone(): GameObject[] {\n return Array.from(this.entitiesInZone)\n }\n\n /**\n * Check if a specific entity is in the zone\n */\n public hasEntity(entity: GameObject): boolean {\n return this.entitiesInZone.has(entity)\n }\n\n /**\n * Set active state\n */\n public setActive(active: boolean): void {\n this.active = active\n\n // Show/hide visual mesh (only if visual mesh was created)\n if (this.visualMesh) {\n this.visualMesh.visible = active\n }\n\n if (this.rigidBody) {\n // TODO: Enable/disable the rigid body when that feature is available\n }\n\n if (!active) {\n // Clear all entities when deactivated\n this.entitiesInZone.clear()\n }\n }\n\n /**\n * Check if the interaction zone is active\n */\n public isActive(): boolean {\n return this.active\n }\n\n /**\n * Get the visual mesh\n */\n public getVisualMesh(): THREE.Mesh | null {\n return this.visualMesh\n }\n\n /**\n * Get the collider component\n */\n public getCollider(): RigidBodyComponentThree | null {\n return this.rigidBody\n }\n\n /**\n * Get the GameObject this zone is attached to\n */\n public getGameObject(): GameObject {\n return this.gameObject\n }\n\n /**\n * Component cleanup\n */\n protected onCleanup(): void {\n this.entitiesInZone.clear()\n\n // Trigger cleanup handled automatically by RigidBodyComponentThree!\n // The registered callbacks will be cleaned up when the RigidBody is destroyed\n\n // Clean up visual mesh\n if (this.visualMesh) {\n this.visualMesh.geometry.dispose()\n if (this.visualMesh.material instanceof THREE.Material) {\n this.visualMesh.material.dispose()\n }\n this.gameObject.remove(this.visualMesh)\n this.visualMesh = null\n }\n\n // RigidBodyComponentThree will be cleaned up automatically by the GameObject\n }\n}\n\n// Interface for options\nexport interface InteractionZoneOptions {\n width?: number\n depth?: number\n active?: boolean\n centerOffset?: THREE.Vector2\n show?: boolean\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\n\nexport interface VirtualJoystickOptions {\n size?: number // Size of the joystick base\n knobSize?: number // Size of the joystick knob\n deadZone?: number // Dead zone radius (0-1)\n maxDistance?: number // Maximum distance for knob movement\n color?: string // Color of the joystick\n visible?: boolean // Whether to show joystick visuals (still functional when hidden)\n opacity?: number // Opacity of joystick visuals (0-1)\n}\n\n/**\n * Virtual joystick component for mobile/touch input - Three.js version\n * Shows on pointer down, hides on pointer up\n * Provides normalized direction vector for movement\n * Uses HTML/CSS for UI elements\n */\nexport class VirtualJoystickThree extends Component {\n // Configuration\n private options: Required<VirtualJoystickOptions>\n\n // Mobile detection\n private static isMobileDevice(): boolean {\n return (\n /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||\n \"ontouchstart\" in window ||\n navigator.maxTouchPoints > 0\n )\n }\n\n // UI Elements\n private joystickContainer: HTMLElement | null = null\n private joystickBase: HTMLElement | null = null\n private joystickKnob: HTMLElement | null = null\n private mobileHint: HTMLElement | null = null\n\n // State\n private isActive: boolean = false\n private startPosition: THREE.Vector2 = new THREE.Vector2()\n private currentPosition: THREE.Vector2 = new THREE.Vector2()\n private direction: THREE.Vector2 = new THREE.Vector2()\n private magnitude: number = 0\n\n // Input tracking\n private joystickPointerId: number | null = null\n private isDragging: boolean = false\n private joystickRadius: number = 0\n\n // Event handlers (need to bind them for proper cleanup)\n private boundPointerDown = this.onPointerDown.bind(this)\n private boundPointerMove = this.onPointerMove.bind(this)\n private boundPointerUp = this.onPointerUp.bind(this)\n private boundTouchStart = this.onTouchStart.bind(this)\n private boundTouchMove = this.onTouchMove.bind(this)\n private boundTouchEnd = this.onTouchEnd.bind(this)\n\n constructor(options: VirtualJoystickOptions = {}) {\n super()\n\n this.options = {\n size: options.size ?? 120,\n knobSize: options.knobSize ?? 40,\n deadZone: options.deadZone ?? 0.15,\n maxDistance: options.maxDistance ?? 50,\n color: options.color ?? \"white\",\n visible: options.visible ?? true,\n opacity: options.opacity ?? 0.2,\n }\n\n this.joystickRadius = this.options.maxDistance\n }\n\n protected onCreate(): void {\n this.createJoystickUI()\n this.createMobileHint()\n this.setupInputHandlers()\n }\n\n protected onCleanup(): void {\n this.cleanupUI()\n this.removeInputHandlers()\n }\n\n /**\n * Create the joystick UI elements using HTML/CSS\n */\n private createJoystickUI(): void {\n // Create container for joystick (initially hidden)\n this.joystickContainer = document.createElement(\"div\")\n this.joystickContainer.id = \"virtual-joystick-container\"\n this.joystickContainer.style.cssText = `\n position: fixed;\n width: ${this.options.size + this.options.knobSize}px;\n height: ${this.options.size + this.options.knobSize}px;\n display: none;\n pointer-events: none;\n z-index: 1000;\n user-select: none;\n touch-action: none;\n `\n\n // Create joystick base (outer circle)\n this.joystickBase = document.createElement(\"div\")\n this.joystickBase.style.cssText = `\n position: absolute;\n width: ${this.options.size}px;\n height: ${this.options.size}px;\n border: 4px solid ${this.options.color};\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n box-sizing: border-box;\n opacity: ${this.options.opacity};\n `\n\n // Create joystick knob (inner circle)\n this.joystickKnob = document.createElement(\"div\")\n this.joystickKnob.style.cssText = `\n position: absolute;\n width: ${this.options.knobSize}px;\n height: ${this.options.knobSize}px;\n border: 2px solid ${this.options.color};\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.8);\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transition: none;\n box-sizing: border-box;\n opacity: ${this.options.opacity};\n `\n\n // Add elements to container\n this.joystickContainer.appendChild(this.joystickBase)\n this.joystickContainer.appendChild(this.joystickKnob)\n\n // Add to document body\n document.body.appendChild(this.joystickContainer)\n }\n\n /**\n * Create mobile hint for touch controls (only shows on mobile devices)\n */\n private createMobileHint(): void {\n // Only show hint on mobile devices\n if (!VirtualJoystickThree.isMobileDevice()) {\n return\n }\n\n this.mobileHint = document.createElement(\"div\")\n this.mobileHint.id = \"mobile-joystick-hint\"\n this.mobileHint.textContent = \"Touch & drag to move\"\n this.mobileHint.style.cssText = `\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 10px 20px;\n border-radius: 20px;\n font-family: Arial, sans-serif;\n font-size: 14px;\n z-index: 999;\n pointer-events: none;\n user-select: none;\n border: 2px solid rgba(255, 255, 255, 0.3);\n animation: fadeInOut 4s ease-in-out;\n `\n\n // Add CSS animation\n if (!document.querySelector(\"#mobile-hint-styles\")) {\n const style = document.createElement(\"style\")\n style.id = \"mobile-hint-styles\"\n style.textContent = `\n @keyframes fadeInOut {\n 0% { opacity: 0; transform: translateX(-50%) translateY(20px); }\n 20% { opacity: 1; transform: translateX(-50%) translateY(0px); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0px); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }\n }\n `\n document.head.appendChild(style)\n }\n\n document.body.appendChild(this.mobileHint)\n\n // Remove hint after animation completes\n setTimeout(() => {\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n this.mobileHint = null\n }\n }, 4000)\n }\n\n /**\n * Setup input handlers for pointer and touch events\n */\n private setupInputHandlers(): void {\n // Add both pointer and touch event listeners for maximum compatibility\n document.addEventListener(\"pointerdown\", this.boundPointerDown, {\n passive: false,\n })\n document.addEventListener(\"pointermove\", this.boundPointerMove, {\n passive: false,\n })\n document.addEventListener(\"pointerup\", this.boundPointerUp, {\n passive: false,\n })\n\n // Touch events as fallback\n document.addEventListener(\"touchstart\", this.boundTouchStart, {\n passive: false,\n })\n document.addEventListener(\"touchmove\", this.boundTouchMove, {\n passive: false,\n })\n document.addEventListener(\"touchend\", this.boundTouchEnd, {\n passive: false,\n })\n }\n\n /**\n * Remove input handlers\n */\n private removeInputHandlers(): void {\n document.removeEventListener(\"pointerdown\", this.boundPointerDown)\n document.removeEventListener(\"pointermove\", this.boundPointerMove)\n document.removeEventListener(\"pointerup\", this.boundPointerUp)\n\n document.removeEventListener(\"touchstart\", this.boundTouchStart)\n document.removeEventListener(\"touchmove\", this.boundTouchMove)\n document.removeEventListener(\"touchend\", this.boundTouchEnd)\n }\n\n /**\n * Handle pointer down event\n */\n private onPointerDown(event: PointerEvent): void {\n // Only respond to primary pointer (first touch/click)\n if (this.isActive || !event.isPrimary) return\n\n this.startJoystick(event.clientX, event.clientY, event.pointerId)\n event.preventDefault()\n }\n\n /**\n * Handle touch start event (fallback)\n */\n private onTouchStart(event: TouchEvent): void {\n if (this.isActive || event.touches.length === 0) return\n\n const touch = event.touches[0]\n this.startJoystick(touch.clientX, touch.clientY, touch.identifier)\n event.preventDefault()\n }\n\n /**\n * Start the joystick at the given position\n */\n private startJoystick(x: number, y: number, pointerId: number): void {\n this.isActive = true\n this.isDragging = true\n this.joystickPointerId = pointerId\n\n // Hide mobile hint when joystick is first used\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n this.mobileHint = null\n }\n\n // Set positions\n this.startPosition.set(x, y)\n this.currentPosition.set(x, y)\n\n // Show and position the joystick (only if visible is true)\n if (this.joystickContainer) {\n this.joystickContainer.style.display = this.options.visible ? \"block\" : \"none\"\n this.joystickContainer.style.left = `${x - (this.options.size + this.options.knobSize) / 2}px`\n this.joystickContainer.style.top = `${y - (this.options.size + this.options.knobSize) / 2}px`\n }\n\n // Reset knob to center\n if (this.joystickKnob) {\n this.joystickKnob.style.transform = \"translate(-50%, -50%)\"\n }\n\n this.updateDirection()\n }\n\n /**\n * Handle pointer move event\n */\n private onPointerMove(event: PointerEvent): void {\n if (!this.isActive || !this.isDragging || event.pointerId !== this.joystickPointerId) {\n return\n }\n\n this.updateJoystick(event.clientX, event.clientY)\n event.preventDefault()\n }\n\n /**\n * Handle touch move event (fallback)\n */\n private onTouchMove(event: TouchEvent): void {\n if (!this.isActive || !this.isDragging) return\n\n // Find the touch with our ID\n for (let i = 0; i < event.touches.length; i++) {\n const touch = event.touches[i]\n if (touch.identifier === this.joystickPointerId) {\n this.updateJoystick(touch.clientX, touch.clientY)\n event.preventDefault()\n break\n }\n }\n }\n\n /**\n * Update joystick position and direction\n */\n private updateJoystick(x: number, y: number): void {\n this.currentPosition.set(x, y)\n\n // Update knob position and direction\n this.updateKnobPosition()\n this.updateDirection()\n }\n\n /**\n * Handle pointer up event\n */\n private onPointerUp(event: PointerEvent): void {\n if (!this.isActive || event.pointerId !== this.joystickPointerId) {\n return\n }\n\n this.endJoystick()\n event.preventDefault()\n }\n\n /**\n * Handle touch end event (fallback)\n */\n private onTouchEnd(event: TouchEvent): void {\n if (!this.isActive) return\n\n // Check if our touch ended\n let touchEnded = true\n for (let i = 0; i < event.touches.length; i++) {\n if (event.touches[i].identifier === this.joystickPointerId) {\n touchEnded = false\n break\n }\n }\n\n if (touchEnded) {\n this.endJoystick()\n event.preventDefault()\n }\n }\n\n /**\n * End the joystick interaction\n */\n private endJoystick(): void {\n this.isActive = false\n this.isDragging = false\n this.joystickPointerId = null\n\n // Hide the joystick\n if (this.joystickContainer) {\n this.joystickContainer.style.display = \"none\"\n }\n\n // Reset direction\n this.direction.set(0, 0)\n this.magnitude = 0\n }\n\n /**\n * Update knob position based on current pointer position\n */\n private updateKnobPosition(): void {\n if (!this.joystickKnob) return\n\n // Calculate offset from joystick center\n const offset = this.currentPosition.clone().sub(this.startPosition)\n\n // Clamp to max distance\n const distance = offset.length()\n if (distance > this.joystickRadius) {\n offset.normalize().multiplyScalar(this.joystickRadius)\n }\n\n // Update knob position\n this.joystickKnob.style.transform = `translate(calc(-50% + ${offset.x}px), calc(-50% + ${offset.y}px))`\n }\n\n /**\n * Update direction vector based on knob position\n */\n private updateDirection(): void {\n // Calculate offset from center\n const offset = this.currentPosition.clone().sub(this.startPosition)\n const distance = offset.length()\n\n // Apply dead zone\n if (distance < this.options.deadZone * this.joystickRadius) {\n this.direction.set(0, 0)\n this.magnitude = 0\n return\n }\n\n // Calculate normalized direction\n const normalizedDistance = Math.min(distance / this.joystickRadius, 1.0)\n this.direction = offset.normalize()\n this.magnitude = normalizedDistance\n }\n\n /**\n * Get the current input direction as a Vector3 (Y=0 for movement)\n */\n public getDirection(): THREE.Vector3 | null {\n if (!this.isActive || this.magnitude === 0) {\n return null\n }\n\n // Convert 2D joystick input to 3D movement direction\n // X maps to X (left/right), Y maps to Z (forward/back)\n // Screen: drag up = negative Y, drag down = positive Y\n // World: forward = positive Z, back = negative Z\n // So: worldZ = -screenY (drag up = move forward)\n return new THREE.Vector3(\n this.direction.x * this.magnitude,\n 0,\n -this.direction.y * this.magnitude\n )\n }\n\n /**\n * Get the current input magnitude (0-1)\n */\n public getMagnitude(): number {\n return this.magnitude\n }\n\n /**\n * Check if the joystick is currently active\n */\n public isActiveJoystick(): boolean {\n return this.isActive\n }\n\n /**\n * Toggle joystick visual visibility (joystick remains functional)\n */\n public setVisible(visible: boolean): void {\n this.options.visible = visible\n if (this.joystickContainer && this.isActive) {\n this.joystickContainer.style.display = visible ? \"block\" : \"none\"\n }\n }\n\n /**\n * Get current visibility state\n */\n public isVisible(): boolean {\n return this.options.visible\n }\n\n /**\n * Set joystick opacity (0-1)\n */\n public setOpacity(opacity: number): void {\n this.options.opacity = Math.max(0, Math.min(1, opacity))\n if (this.joystickBase) {\n this.joystickBase.style.opacity = `${this.options.opacity}`\n }\n if (this.joystickKnob) {\n this.joystickKnob.style.opacity = `${this.options.opacity}`\n }\n }\n\n /**\n * Get current opacity\n */\n public getOpacity(): number {\n return this.options.opacity\n }\n\n /**\n * Clean up UI resources\n */\n private cleanupUI(): void {\n if (this.joystickContainer && this.joystickContainer.parentNode) {\n this.joystickContainer.parentNode.removeChild(this.joystickContainer)\n }\n\n if (this.mobileHint && this.mobileHint.parentNode) {\n this.mobileHint.parentNode.removeChild(this.mobileHint)\n }\n\n this.joystickContainer = null\n this.joystickBase = null\n this.joystickKnob = null\n this.mobileHint = null\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\nimport { RigidBodyComponentThree } from \"@systems/physics/RigidBodyComponentThree.ts\"\n\n/**\n * Three.js Movement controller component that handles physics-based movement and rotation\n * Uses Rapier physics with rotation locking for smooth, controlled movement\n * Can be used for player input, AI navigation, or any entity that needs to move\n */\nexport class MovementController extends Component {\n // Public configuration properties\n public maxMoveSpeed: number = 8.0\n public acceleration: number = 40\n public turnSpeed: number = 12 // How fast to rotate (radians per second)\n\n private rigidBodyComponent: RigidBodyComponentThree | null = null\n private targetRotationY: number = 0\n private currentRotationY: number = 0\n\n // Pre-allocated vector for velocity queries to avoid GC pressure\n private _currentVelocity = new THREE.Vector3()\n\n /**\n * Called when the component is created and attached to a GameObject\n */\n protected onCreate(): void {\n // Find the rigid body component on this GameObject\n this.findRigidBodyComponentThree()\n\n // Initialize rotation tracking\n this.currentRotationY = this.gameObject.rotation.y\n this.targetRotationY = this.currentRotationY\n }\n\n /**\n * Find the rigid body component on this GameObject\n */\n private findRigidBodyComponentThree(): void {\n this.rigidBodyComponent = this.gameObject.getComponent(RigidBodyComponentThree) || null\n if (!this.rigidBodyComponent) {\n console.warn(\"MovementController: No RigidBodyComponentThree found on GameObject\")\n }\n }\n\n /**\n * Set the rigid body component this controller should manage\n */\n public setRigidBodyComponentThree(rigidBodyComponent: RigidBodyComponentThree): void {\n this.rigidBodyComponent = rigidBodyComponent\n }\n\n /**\n * Move the entity based on input direction\n * @param inputDirection Normalized direction vector (or null for no movement)\n * @param deltaTime Time since last frame in seconds\n */\n public move(inputDirection: THREE.Vector3 | null, deltaTime: number): void {\n if (!this.rigidBodyComponent) return\n\n const targetVelocity = this.calculateTargetVelocity(inputDirection)\n const smoothedVelocity = this.smoothVelocity(targetVelocity, deltaTime)\n\n // Apply velocity to physics body (Rapier handles rotation locking)\n this.rigidBodyComponent.setVelocity(smoothedVelocity)\n\n // Handle Y rotation separately and smoothly\n this.updateRotation(inputDirection, deltaTime)\n }\n\n /**\n * Calculate target velocity based on input direction\n */\n private calculateTargetVelocity(inputDirection: THREE.Vector3 | null): THREE.Vector3 {\n const targetVelocity = new THREE.Vector3(0, 0, 0)\n\n if (inputDirection && inputDirection.length() > 0.01) {\n targetVelocity.x = inputDirection.x * this.maxMoveSpeed\n targetVelocity.z = inputDirection.z * this.maxMoveSpeed\n }\n\n // Y velocity is always 0 (grounded movement)\n targetVelocity.y = 0\n\n return targetVelocity\n }\n\n /**\n * Smooth velocity towards target using acceleration\n */\n private smoothVelocity(targetVelocity: THREE.Vector3, deltaTime: number): THREE.Vector3 {\n if (!this.rigidBodyComponent) return targetVelocity\n\n this.rigidBodyComponent.getVelocity(this._currentVelocity)\n const maxDelta = this.acceleration * deltaTime\n\n // Smooth X and Z velocities\n const smoothedVelocity = new THREE.Vector3()\n smoothedVelocity.x = this.moveTowards(this._currentVelocity.x, targetVelocity.x, maxDelta)\n smoothedVelocity.z = this.moveTowards(this._currentVelocity.z, targetVelocity.z, maxDelta)\n smoothedVelocity.y = 0 // Keep grounded\n\n return smoothedVelocity\n }\n\n /**\n * Update rotation smoothly towards movement direction using quaternion slerp\n */\n private updateRotation(inputDirection: THREE.Vector3 | null, deltaTime: number): void {\n // Always clear angular velocity first to prevent unwanted spinning\n if (this.rigidBodyComponent) {\n const rigidBody = this.rigidBodyComponent.getRigidBody()\n if (rigidBody) {\n // Stop any existing angular rotation to prevent spinning\n this.rigidBodyComponent.setAngularVelocity(new THREE.Vector3(0, 0, 0))\n }\n }\n\n // If no input direction, don't update rotation - just ensure rotation is stopped\n if (!inputDirection || inputDirection.length() < 0.01) {\n // We've already cleared angular velocity above, so just return\n return\n }\n\n // Calculate target rotation based on movement direction\n const targetRotationY = Math.atan2(inputDirection.x, inputDirection.z)\n\n // Create target quaternion\n const targetQuaternion = new THREE.Quaternion()\n targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), targetRotationY)\n\n // Get current quaternion\n const currentQuaternion = new THREE.Quaternion()\n currentQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.currentRotationY)\n\n // Calculate slerp factor based on turn speed\n const slerpFactor = Math.min(1.0, this.turnSpeed * deltaTime)\n\n // Slerp between current and target rotation\n const resultQuaternion = new THREE.Quaternion()\n resultQuaternion.slerpQuaternions(currentQuaternion, targetQuaternion, slerpFactor)\n\n // Update current rotation Y for tracking\n const euler = new THREE.Euler()\n euler.setFromQuaternion(resultQuaternion, \"YXZ\")\n this.currentRotationY = euler.y\n\n // Apply rotation through physics body if available, otherwise directly to GameObject\n if (this.rigidBodyComponent) {\n const rigidBody = this.rigidBodyComponent.getRigidBody()\n if (rigidBody) {\n // Apply to physics body (this will sync back to GameObject automatically)\n const rapierQuat = {\n x: resultQuaternion.x,\n y: resultQuaternion.y,\n z: resultQuaternion.z,\n w: resultQuaternion.w,\n }\n rigidBody.setRotation(rapierQuat, true)\n } else {\n // Fallback: apply rotation directly to GameObject\n this.gameObject.rotation.y = this.currentRotationY\n }\n } else {\n // Fallback: apply rotation directly to GameObject\n this.gameObject.rotation.y = this.currentRotationY\n }\n }\n\n /**\n * Utility function to move a value towards a target at a given rate\n */\n private moveTowards(current: number, target: number, maxDelta: number): number {\n const delta = target - current\n if (Math.abs(delta) <= maxDelta) {\n return target\n }\n return current + Math.sign(delta) * maxDelta\n }\n\n /**\n * Get current movement state for debugging\n */\n public getMovementState(): any {\n return {\n maxMoveSpeed: this.maxMoveSpeed,\n acceleration: this.acceleration,\n turnSpeed: this.turnSpeed,\n currentRotationY: this.currentRotationY,\n targetRotationY: this.targetRotationY,\n hasRigidBody: !!this.rigidBodyComponent,\n }\n }\n\n /**\n * Clean up resources when the component is removed\n */\n protected onCleanup(): void {\n this.rigidBodyComponent = null\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\n\nexport interface PlayerControls {\n forward: string\n backward: string\n left: string\n right: string\n run: string\n interact: string\n}\n\n/**\n * Three.js Player Controller Component\n * Handles WASD movement, mouse look, and basic interactions\n */\nexport class PlayerControllerThree extends Component {\n // Movement parameters\n public moveSpeed: number = 5.0\n public runSpeed: number = 8.0\n public rotationSpeed: number = 2.0\n\n // Controls configuration\n private controls: PlayerControls = {\n forward: \"KeyW\",\n backward: \"KeyS\",\n left: \"KeyA\",\n right: \"KeyD\",\n run: \"ShiftLeft\",\n interact: \"KeyE\",\n }\n\n // Input state\n private keys: Set<string> = new Set()\n private mouseX: number = 0\n private mouseY: number = 0\n private mouseSensitivity: number = 0.002\n private isPointerLocked: boolean = false\n\n // Camera reference\n private camera: THREE.PerspectiveCamera\n private cameraHeight: number = 1.7 // Eye level height\n\n // Movement state\n private velocity: THREE.Vector3 = new THREE.Vector3()\n private direction: THREE.Vector3 = new THREE.Vector3()\n\n constructor(camera: THREE.PerspectiveCamera) {\n super()\n this.camera = camera\n }\n\n protected onCreate(): void {\n this.setupEventListeners()\n this.setupPointerLock()\n\n // Position camera at player eye level\n this.updateCameraPosition()\n\n console.log(\"🎮 Player controller initialized\")\n console.log(\"📋 Controls: WASD to move, Shift to run, E to interact, Click to look around\")\n }\n\n /**\n * Set up keyboard and mouse event listeners\n */\n private setupEventListeners(): void {\n // Keyboard events\n document.addEventListener(\"keydown\", this.onKeyDown.bind(this))\n document.addEventListener(\"keyup\", this.onKeyUp.bind(this))\n\n // Mouse events\n document.addEventListener(\"mousemove\", this.onMouseMove.bind(this))\n document.addEventListener(\"click\", this.onClick.bind(this))\n\n // Pointer lock events\n document.addEventListener(\"pointerlockchange\", this.onPointerLockChange.bind(this))\n }\n\n /**\n * Set up pointer lock for mouse look\n */\n private setupPointerLock(): void {\n const canvas = document.getElementById(\"renderCanvas\") as HTMLCanvasElement\n if (canvas) {\n canvas.addEventListener(\"click\", () => {\n if (!this.isPointerLocked) {\n canvas.requestPointerLock()\n }\n })\n }\n }\n\n /**\n * Handle key down events\n */\n private onKeyDown(event: KeyboardEvent): void {\n this.keys.add(event.code)\n\n // Handle special keys\n if (event.code === this.controls.interact) {\n this.onInteract()\n }\n }\n\n /**\n * Handle key up events\n */\n private onKeyUp(event: KeyboardEvent): void {\n this.keys.delete(event.code)\n }\n\n /**\n * Handle mouse movement for looking around\n */\n private onMouseMove(event: MouseEvent): void {\n if (!this.isPointerLocked) return\n\n this.mouseX += event.movementX * this.mouseSensitivity\n this.mouseY += event.movementY * this.mouseSensitivity\n\n // Clamp vertical rotation\n this.mouseY = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.mouseY))\n\n // Apply rotation to camera\n this.camera.rotation.order = \"YXZ\"\n this.camera.rotation.y = -this.mouseX\n this.camera.rotation.x = -this.mouseY\n }\n\n /**\n * Handle canvas clicks for pointer lock\n */\n private onClick(event: MouseEvent): void {\n const canvas = document.getElementById(\"renderCanvas\") as HTMLCanvasElement\n if (event.target === canvas && !this.isPointerLocked) {\n canvas.requestPointerLock()\n }\n }\n\n /**\n * Handle pointer lock state changes\n */\n private onPointerLockChange(): void {\n this.isPointerLocked = document.pointerLockElement !== null\n\n if (this.isPointerLocked) {\n console.log(\"🖱️ Mouse locked - look around!\")\n } else {\n console.log(\"🖱️ Mouse unlocked - click canvas to lock again\")\n }\n }\n\n /**\n * Handle interaction key press\n */\n private onInteract(): void {\n console.log(\"🤝 Player trying to interact...\")\n // TODO: Implement interaction system\n }\n\n /**\n * Update method - called every frame\n */\n public update(deltaTime: number): void {\n this.updateMovement(deltaTime)\n this.updateCameraPosition()\n }\n\n /**\n * Update player movement based on input\n */\n private updateMovement(deltaTime: number): void {\n // Reset direction\n this.direction.set(0, 0, 0)\n\n // Get current movement speed\n const isRunning = this.keys.has(this.controls.run)\n const currentSpeed = isRunning ? this.runSpeed : this.moveSpeed\n\n // Calculate movement direction based on camera orientation\n const forward = new THREE.Vector3()\n const right = new THREE.Vector3()\n\n // Get camera's forward direction (ignoring Y rotation for ground movement)\n this.camera.getWorldDirection(forward)\n forward.y = 0\n forward.normalize()\n\n // Get camera's right direction\n right.crossVectors(forward, this.camera.up).normalize()\n\n // Apply movement inputs\n if (this.keys.has(this.controls.forward)) {\n this.direction.add(forward)\n }\n if (this.keys.has(this.controls.backward)) {\n this.direction.sub(forward)\n }\n if (this.keys.has(this.controls.right)) {\n this.direction.add(right)\n }\n if (this.keys.has(this.controls.left)) {\n this.direction.sub(right)\n }\n\n // Normalize direction to prevent faster diagonal movement\n if (this.direction.length() > 0) {\n this.direction.normalize()\n\n // Apply movement to player position\n this.velocity.copy(this.direction).multiplyScalar(currentSpeed * deltaTime)\n this.gameObject.position.add(this.velocity)\n }\n\n // Keep player on ground level (no Y drift)\n this.gameObject.position.y = 0\n }\n\n /**\n * Update camera position to follow player\n */\n private updateCameraPosition(): void {\n // Position camera at player position + eye height\n this.camera.position.copy(this.gameObject.position)\n this.camera.position.y += this.cameraHeight\n }\n\n /**\n * Get current movement state for debugging\n */\n public getMovementState(): {\n position: THREE.Vector3\n isMoving: boolean\n isRunning: boolean\n lookDirection: THREE.Vector3\n } {\n const lookDirection = new THREE.Vector3()\n this.camera.getWorldDirection(lookDirection)\n\n return {\n position: this.gameObject.position.clone(),\n isMoving: this.direction.length() > 0,\n isRunning: this.keys.has(this.controls.run),\n lookDirection: lookDirection,\n }\n }\n\n /**\n * Set player position\n */\n public setPosition(position: THREE.Vector3): void {\n this.gameObject.position.copy(position)\n this.updateCameraPosition()\n }\n\n /**\n * Clean up event listeners\n */\n protected onCleanup(): void {\n document.removeEventListener(\"keydown\", this.onKeyDown.bind(this))\n document.removeEventListener(\"keyup\", this.onKeyUp.bind(this))\n document.removeEventListener(\"mousemove\", this.onMouseMove.bind(this))\n document.removeEventListener(\"click\", this.onClick.bind(this))\n document.removeEventListener(\"pointerlockchange\", this.onPointerLockChange.bind(this))\n\n // Exit pointer lock if active\n if (this.isPointerLocked) {\n document.exitPointerLock()\n }\n\n console.log(\"🎮 Player controller cleaned up\")\n }\n}\n","import * as THREE from \"three\"\nimport { Component } from \"@engine/core\"\nimport { AssetManager } from \"@engine/assets\"\n\n/**\n * Three.js Skeletal renderer for animated FBX models\n * - Uses AssetManager for preloading coordination\n * - Uses SkeletonCache for proper skeletal cloning\n * - Specifically designed for animated characters\n * - Always cloned, always has shadows, never static\n */\nexport class SkeletalRenderer extends Component {\n private _group: THREE.Group | null = null\n private _assetPath: string\n private _material: THREE.Material | null\n private _skeletalModel: THREE.Object3D | null = null\n\n constructor(assetPath: string, material?: THREE.Material) {\n super()\n this._assetPath = assetPath\n this._material = material || null\n }\n\n protected onCreate(): void {\n this.createSkeletalMesh()\n }\n\n private createSkeletalMesh(): void {\n if (!this._assetPath) {\n throw new Error(\"SkeletalRenderer: No asset path specified\")\n }\n\n // Create wrapper group for this instance\n this._group = new THREE.Group()\n this._group.name = `skeletal_${this._assetPath.split(\"/\").pop()}`\n this.gameObject.add(this._group)\n\n // Get properly cloned skeletal model from AssetManager's skeleton cache\n this._skeletalModel = AssetManager.getSkeletalClone(this._assetPath)\n\n if (!this._skeletalModel) {\n throw new Error(\n `No skeletal model found for '${this._assetPath}'. Make sure to preload with AssetManager.preloadSkeletalModel() first.`\n )\n }\n\n // Apply custom material if provided\n if (this._material) {\n this._skeletalModel.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.material = this._material!\n child.frustumCulled = false\n }\n })\n } else {\n this._skeletalModel.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.frustumCulled = false\n }\n })\n }\n\n this._group.add(this._skeletalModel)\n\n // Always apply shadows for characters\n this.applyShadowsToGroup(this._group)\n }\n\n private applyShadowsToGroup(group: THREE.Group): void {\n group.traverse((child: THREE.Object3D) => {\n if (child instanceof THREE.Mesh) {\n child.castShadow = true\n child.receiveShadow = true\n }\n })\n }\n\n // ========== Public API ==========\n\n /**\n * Get the wrapper group (attached to GameObject)\n */\n public getGroup(): THREE.Group | null {\n return this._group\n }\n\n /**\n * Get the skeletal model (for animation setup)\n */\n public getSkeletalModel(): THREE.Object3D | null {\n return this._skeletalModel\n }\n\n /**\n * Get the asset path being rendered\n */\n public getAssetPath(): string {\n return this._assetPath\n }\n\n /**\n * Enable or disable visibility\n */\n public setVisible(visible: boolean): void {\n if (this._group) {\n this._group.visible = visible\n }\n }\n\n /**\n * Get visibility state\n */\n public isVisible(): boolean {\n return this._group?.visible ?? false\n }\n\n // ========== Component Lifecycle ==========\n\n protected onCleanup(): void {\n if (this._group) {\n this.gameObject.remove(this._group)\n }\n // Note: Don't track instance destruction since AssetManager doesn't track SkeletonCache instances\n }\n\n public onEnabled(): void {\n this.setVisible(true)\n }\n\n public onDisabled(): void {\n this.setVisible(false)\n }\n}\n","import { Component } from \"@engine/core\"\nimport { InstancedMeshManager } from \"./InstancedMeshManager\"\n\n/**\n * Options for InstancedRenderer\n */\nexport interface InstancedRendererOptions {\n /** If true (default), matrix updates every frame. If false, only updates when markDirty() is called. */\n isDynamic?: boolean\n /** Whether instances cast shadows (default: false). Only used if batch is auto-created. */\n castShadow?: boolean\n /** Whether instances receive shadows (default: false). Only used if batch is auto-created. */\n receiveShadow?: boolean\n /** Initial batch capacity (default: 16, grows automatically). Only used if batch is auto-created. */\n initialCapacity?: number\n}\n\n/**\n * Component for rendering objects using GPU instancing.\n *\n * Transform is controlled by the GameObject - the manager syncs matrices every frame.\n * If no batch exists for the key, one is created automatically from the GameObject's mesh.\n *\n * Performance modes:\n * - Dynamic (default): Matrix updates every frame. Use for moving objects.\n * - Static: Matrix only updates when markDirty() is called. Use for stationary objects.\n *\n * Usage:\n * ```typescript\n * // Simple - batch auto-creates from GameObject's mesh\n * gameObject.addComponent(new InstancedRenderer(\"burger\"))\n *\n * // With options\n * gameObject.addComponent(new InstancedRenderer(\"burger\", {\n * isDynamic: false,\n * castShadow: true\n * }))\n *\n * // For stationary objects, switch to static mode\n * renderer.setDynamic(false)\n *\n * // When a static object moves, mark it dirty\n * renderer.markDirty()\n * ```\n *\n * Key differences from mesh-based renderers (ObjRenderer, etc.):\n * - No getMesh() - instances don't have their own THREE.Object3D\n * - No getBounds() - can query the batch geometry if needed\n * - All instances share the same geometry and material\n */\nexport class InstancedRenderer extends Component {\n private readonly batchKey: string\n private readonly options: InstancedRendererOptions\n private instanceId: string | null = null\n\n /**\n * Create an InstancedRenderer\n * @param batchKey The batch key to register with. If no batch exists, one is auto-created.\n * @param options Configuration options (or just pass `true`/`false` for isDynamic)\n */\n constructor(batchKey: string, options: InstancedRendererOptions | boolean = {}) {\n super()\n this.batchKey = batchKey\n // Support legacy boolean parameter for isDynamic\n this.options = typeof options === \"boolean\" ? { isDynamic: options } : options\n }\n\n /**\n * Register with the batch when the component is created.\n * If no batch exists, one will be created automatically from this GameObject's mesh.\n */\n protected onCreate(): void {\n const manager = InstancedMeshManager.getInstance()\n\n if (!manager.isReady()) {\n console.error(\n `InstancedRenderer: Manager not initialized. Call InstancedMeshManager.getInstance().initialize(scene) first.`\n )\n return\n }\n\n // addInstance now auto-creates batch from GameObject if needed\n this.instanceId = manager.addInstance(this.batchKey, this.gameObject, {\n isDynamic: this.options.isDynamic ?? true,\n castShadow: this.options.castShadow ?? false,\n receiveShadow: this.options.receiveShadow ?? false,\n initialCapacity: this.options.initialCapacity,\n })\n\n if (!this.instanceId) {\n console.error(`InstancedRenderer: Failed to add instance to batch '${this.batchKey}'`)\n }\n }\n\n /**\n * Set the visibility of this instance\n */\n public setVisible(visible: boolean): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().setInstanceVisible(this.batchKey, this.instanceId, visible)\n }\n }\n\n /**\n * Get the visibility of this instance\n */\n public getVisible(): boolean {\n if (this.instanceId) {\n return InstancedMeshManager.getInstance().getInstanceVisible(this.batchKey, this.instanceId)\n }\n return false\n }\n\n /**\n * Show the instance (convenience method)\n */\n public show(): void {\n this.setVisible(true)\n }\n\n /**\n * Hide the instance (convenience method)\n */\n public hide(): void {\n this.setVisible(false)\n }\n\n /**\n * Get the batch key this renderer is registered with\n */\n public getBatchKey(): string {\n return this.batchKey\n }\n\n /**\n * Check if this renderer is successfully registered with a batch\n */\n public isRegistered(): boolean {\n return this.instanceId !== null\n }\n\n /**\n * Get the instance ID (for debugging)\n */\n public getInstanceId(): string | null {\n return this.instanceId\n }\n\n /**\n * Mark this instance as needing a matrix update.\n * Only relevant for static instances - dynamic instances update every frame anyway.\n * Call this when the transform of a static instance changes.\n */\n public markDirty(): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().markInstanceDirty(this.batchKey, this.instanceId)\n }\n }\n\n /**\n * Set whether this instance is dynamic (updates every frame) or static (only when marked dirty).\n * Use this to optimize performance when items transition between moving and stationary states.\n * @param isDynamic If true, updates every frame. If false, only updates when markDirty() is called.\n */\n public setDynamic(isDynamic: boolean): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().setInstanceDynamic(\n this.batchKey,\n this.instanceId,\n isDynamic\n )\n }\n }\n\n /**\n * Called when the GameObject becomes enabled\n */\n public onEnabled(): void {\n this.setVisible(true)\n }\n\n /**\n * Called when the GameObject becomes disabled\n */\n public onDisabled(): void {\n this.setVisible(false)\n }\n\n /**\n * Unregister from the batch when the component is cleaned up\n */\n protected onCleanup(): void {\n if (this.instanceId) {\n InstancedMeshManager.getInstance().removeInstance(this.batchKey, this.instanceId)\n this.instanceId = null\n }\n }\n}\n","import * as THREE from \"three\"\nimport { GameObject, Component } from \"@engine/core\"\nimport {\n PrefabComponent,\n PrefabInstance,\n ComponentRegistry,\n type ComponentJSON,\n type PrefabNode,\n} from \"@systems/prefabs\"\nimport { StowKitSystem } from \"@systems/stowkit\"\n\ninterface NodeOverrideJSON {\n position?: [number, number, number]\n rotation?: [number, number, number]\n scale?: [number, number, number]\n components?: ComponentJSON[]\n}\n\ninterface StowMeshJSON extends ComponentJSON {\n type: \"stow_mesh\"\n mesh: {\n pack: string\n assetId: string\n }\n castShadow?: boolean\n receiveShadow?: boolean\n nodeOverrides?: Record<string, NodeOverrideJSON>\n}\n\n/**\n * MeshRenderer - Component for rendering a mesh from StowKitSystem.\n *\n * This is a non-instanced renderer - each instance is a separate draw call.\n * Use InstancedRenderer for many instances of the same mesh (better performance).\n *\n * When a mesh has named child groups (from preserveHierarchy), MeshRenderer\n * promotes them to GameObjects so they can have components and be accessed\n * via getMeshChild(). Transform overrides and components from nodeOverrides\n * are applied automatically.\n *\n * Usage:\n * ```typescript\n * const env = new GameObject(\"Environment\")\n * env.addComponent(new MeshRenderer(\"restaurant_display_common\"))\n * ```\n */\n@PrefabComponent(\"stow_mesh\")\nexport class MeshRenderer extends Component {\n static fromPrefabJSON(json: StowMeshJSON, _node: PrefabNode): MeshRenderer {\n if (!json.mesh?.assetId) {\n console.error(`[MeshRenderer] stow_mesh component missing mesh.assetId:`, json)\n return new MeshRenderer(\"unknown\")\n }\n // Priority: JSON property > context options > default (true)\n const options = PrefabInstance.currentOptions\n const castShadow = json.castShadow ?? options?.castShadow ?? true\n const receiveShadow = json.receiveShadow ?? options?.receiveShadow ?? true\n const renderer = new MeshRenderer(json.mesh.assetId, castShadow, receiveShadow)\n if (json.nodeOverrides) {\n renderer.nodeOverrides = json.nodeOverrides\n }\n return renderer\n }\n\n private mesh: THREE.Group | null = null\n private readonly meshName: string\n private readonly castShadow: boolean\n private readonly receiveShadow: boolean\n private _isStatic: boolean\n private isMeshLoaded: boolean = false\n private materialOverride: THREE.Material | null = null\n private loadedCallbacks: (() => void)[] | null = null\n private nodeOverrides: Record<string, NodeOverrideJSON> | null = null\n private meshChildMap: Map<string, GameObject> | null = null\n\n /**\n * @param meshName The name of the mesh in the StowKit pack\n * @param castShadow Whether meshes should cast shadows (default: true)\n * @param receiveShadow Whether meshes should receive shadows (default: true)\n * @param isStatic Whether this mesh is static (default: false). Static meshes have matrixAutoUpdate disabled for better performance.\n * @param materialOverride Optional material to use instead of the default StowKit material\n */\n constructor(\n meshName: string,\n castShadow: boolean = true,\n receiveShadow: boolean = true,\n isStatic: boolean = false,\n materialOverride: THREE.Material | null = null\n ) {\n super()\n this.meshName = meshName\n this.castShadow = castShadow\n this.receiveShadow = receiveShadow\n this._isStatic = isStatic\n this.materialOverride = materialOverride\n }\n\n protected onCreate(): void {\n const stowkit = StowKitSystem.getInstance()\n\n // Check if mesh is already cached\n const cachedMesh = stowkit.getMeshSync(this.meshName)\n if (cachedMesh) {\n this.addMesh(cachedMesh)\n } else {\n // Start async load - will add mesh in update when ready\n stowkit.getMesh(this.meshName)\n }\n }\n\n public update(_deltaTime: number): void {\n if (this.isMeshLoaded) return\n\n const stowkit = StowKitSystem.getInstance()\n const cachedMesh = stowkit.getMeshSync(this.meshName)\n if (cachedMesh) {\n this.addMesh(cachedMesh)\n }\n }\n\n private addMesh(original: THREE.Group): void {\n this.isMeshLoaded = true\n\n // Clone mesh with material conversion\n this.mesh = StowKitSystem.getInstance().cloneMeshSync(\n original,\n this.castShadow,\n this.receiveShadow\n )\n\n // Apply material override if set\n if (this.materialOverride) {\n this.applyMaterialOverride()\n }\n\n this.gameObject.add(this.mesh)\n\n // Promote named child groups to GameObjects for component support\n this.promoteMeshChildren(this.mesh)\n\n // For static meshes, disable matrix auto-update to save CPU\n if (this._isStatic) {\n this.setStatic(true)\n }\n\n // Fire onLoaded callbacks\n if (this.loadedCallbacks) {\n for (const cb of this.loadedCallbacks) cb()\n this.loadedCallbacks = null\n }\n }\n\n /**\n * Walk the loaded mesh hierarchy and replace named child Groups with\n * GameObjects so they can receive components. Applies nodeOverrides\n * (transform + components) from the prefab data.\n */\n private promoteMeshChildren(group: THREE.Object3D): void {\n for (const child of [...group.children]) {\n if (child.type !== \"Group\" || !child.name) continue\n\n // Create a GameObject to replace this group\n const childGO = new GameObject(child.name)\n childGO.position.copy(child.position)\n childGO.quaternion.copy(child.quaternion)\n childGO.scale.copy(child.scale)\n\n // Apply nodeOverride transforms if present\n const override = this.nodeOverrides?.[child.name]\n if (override?.position) childGO.position.fromArray(override.position)\n if (override?.rotation) {\n childGO.rotation.set(\n override.rotation[0],\n override.rotation[1],\n override.rotation[2]\n )\n }\n if (override?.scale) childGO.scale.fromArray(override.scale)\n\n // Move all children from the group into the GameObject\n for (const grandchild of [...child.children]) {\n child.remove(grandchild)\n childGO.add(grandchild)\n }\n\n // Replace group with GameObject in parent\n group.remove(child)\n group.add(childGO)\n\n // Create override components\n if (override?.components) {\n for (const compJSON of override.components) {\n const factory = ComponentRegistry.get(compJSON.type)\n if (factory) {\n try {\n const comp = factory.fromPrefabJSON(compJSON, null as unknown as PrefabNode)\n if (comp) childGO.addComponent(comp)\n } catch (e) {\n console.error(\n `[MeshRenderer] Failed to create component \"${compJSON.type}\" on mesh child \"${child.name}\":`,\n e\n )\n }\n }\n }\n }\n\n // Index for getMeshChild()\n if (!this.meshChildMap) this.meshChildMap = new Map()\n this.meshChildMap.set(child.name, childGO)\n\n // Recurse for nested groups\n this.promoteMeshChildren(childGO)\n }\n }\n\n /**\n * Get a named child group from the loaded mesh as a GameObject.\n * Returns null if the mesh hasn't loaded yet or if no child with that name exists.\n *\n * ```typescript\n * renderer.onLoaded(() => {\n * const key = renderer.getMeshChild(\"Key\")\n * key?.addComponent(new Spinner(\"z\"))\n * })\n * ```\n */\n public getMeshChild(name: string): GameObject | null {\n return this.meshChildMap?.get(name) ?? null\n }\n\n /**\n * Get all named child groups from the loaded mesh.\n * Returns null if the mesh hasn't loaded yet.\n */\n public getMeshChildren(): ReadonlyMap<string, GameObject> | null {\n return this.meshChildMap ?? null\n }\n\n /**\n * Apply the material override to all meshes\n */\n private applyMaterialOverride(): void {\n if (!this.mesh || !this.materialOverride) return\n\n this.mesh.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n child.material = this.materialOverride!\n }\n })\n }\n\n /**\n * Set a material override for all meshes in this renderer.\n * Call this after the mesh is loaded, or pass it in the constructor.\n */\n public setMaterial(material: THREE.Material): void {\n this.materialOverride = material\n if (this.mesh) {\n this.applyMaterialOverride()\n }\n }\n\n /**\n * Check if this mesh is currently static (no automatic matrix updates)\n */\n public get isStatic(): boolean {\n return this._isStatic\n }\n\n /**\n * Set whether this mesh is static (no automatic matrix updates).\n * Static meshes save CPU by not recalculating transforms every frame.\n * Call forceMatrixUpdate() after moving a static mesh.\n */\n public setStatic(isStatic: boolean): void {\n this._isStatic = isStatic\n if (!this.mesh) return\n\n if (isStatic) {\n // Update matrices one final time before freezing\n this.forceMatrixUpdate()\n\n // Disable auto-update on mesh and all descendants\n this.mesh.matrixAutoUpdate = false\n this.mesh.traverse((child) => {\n child.matrixAutoUpdate = false\n })\n this.gameObject.matrixAutoUpdate = false\n } else {\n // Enable auto-update\n this.mesh.matrixAutoUpdate = true\n this.mesh.traverse((child) => {\n child.matrixAutoUpdate = true\n })\n this.gameObject.matrixAutoUpdate = true\n }\n }\n\n /**\n * Force a one-time matrix update. Call this after moving a static mesh.\n * Does not change the static/dynamic state.\n */\n public forceMatrixUpdate(): void {\n if (this.mesh) {\n this.mesh.updateMatrix()\n this.mesh.updateMatrixWorld(true)\n }\n this.gameObject.updateMatrix()\n this.gameObject.updateMatrixWorld(true)\n }\n\n /**\n * Get the mesh group (null if not yet loaded)\n */\n public getMesh(): THREE.Group | null {\n return this.mesh\n }\n\n /**\n * Get the name of the mesh this component is managing\n */\n public getMeshName(): string {\n return this.meshName\n }\n\n /**\n * Check if the mesh was successfully loaded\n */\n public isLoaded(): boolean {\n return this.mesh !== null\n }\n\n /**\n * Register a callback that fires when the mesh finishes loading,\n * or immediately if already loaded.\n */\n public onLoaded(callback: () => void): void {\n if (this.mesh) {\n callback()\n return\n }\n if (!this.loadedCallbacks) this.loadedCallbacks = []\n this.loadedCallbacks.push(callback)\n }\n\n /**\n * Get the first material from the loaded mesh, or null if not yet loaded.\n */\n public getMaterial(): THREE.Material | null {\n if (!this.mesh) return null\n let result: THREE.Material | null = null\n this.mesh.traverse((child) => {\n if (!result && child instanceof THREE.Mesh && child.material) {\n result = Array.isArray(child.material) ? child.material[0] : child.material\n }\n })\n return result\n }\n\n /**\n * Set the visibility of the mesh\n */\n public setVisible(visible: boolean): void {\n if (this.mesh) {\n this.mesh.visible = visible\n }\n }\n\n /**\n * Get bounds of the mesh (useful for physics)\n */\n public getBounds(): THREE.Vector3 | null {\n if (!this.mesh) {\n return null\n }\n return StowKitSystem.getInstance().getBounds(this.mesh)\n }\n\n /**\n * Cleanup - remove mesh from scene and dispose of resources\n */\n protected onCleanup(): void {\n if (this.mesh) {\n // Remove from GameObject/scene\n this.gameObject.remove(this.mesh)\n\n // Traverse and dispose of geometries and materials\n this.mesh.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n if (child.geometry) {\n child.geometry.dispose()\n }\n\n if (child.material) {\n if (Array.isArray(child.material)) {\n child.material.forEach((m) => {\n if (m.map) m.map.dispose()\n m.dispose()\n })\n } else {\n if (child.material.map) child.material.map.dispose()\n child.material.dispose()\n }\n }\n }\n })\n\n this.mesh = null\n }\n\n this.meshChildMap = null\n }\n}\n"],"mappings":"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","names":["THREE","THREE","THREE","THREE","THREE"]}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# How to Load .stow Files with stowkit-three-loader + RUN.game SDK
|
|
2
|
+
|
|
3
|
+
Load `.stow` pack files using `@series-inc/stowkit-three-loader` and the RUN.game SDK CDN — no engine wrapper required.
|
|
4
|
+
|
|
5
|
+
## Packages
|
|
6
|
+
|
|
7
|
+
| Package | Role |
|
|
8
|
+
|---------|------|
|
|
9
|
+
| `@series-inc/rundot-game-sdk` | Fetches `.stow` blobs from the CDN via `RundotGameAPI.cdn.fetchAsset()` |
|
|
10
|
+
| `@series-inc/stowkit-three-loader` | Parses `.stow` binary data into Three.js assets (`StowKitLoader`, `StowKitPack`) |
|
|
11
|
+
| `@series-inc/stowkit-reader` | Low-level WASM reader (peer dependency of the loader) |
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import RundotGameAPI from "@series-inc/rundot-game-sdk/api"
|
|
17
|
+
import { StowKitLoader } from "@series-inc/stowkit-three-loader"
|
|
18
|
+
|
|
19
|
+
// 1. Fetch the .stow file from CDN as a Blob
|
|
20
|
+
const blob = await RundotGameAPI.cdn.fetchAsset("packs/environment.stow")
|
|
21
|
+
const arrayBuffer = await blob.arrayBuffer()
|
|
22
|
+
|
|
23
|
+
// 2. Parse into a StowKitPack
|
|
24
|
+
const pack = await StowKitLoader.loadFromMemory(arrayBuffer, {
|
|
25
|
+
basisPath: "basis/",
|
|
26
|
+
dracoPath: "stowkit/draco/",
|
|
27
|
+
wasmPath: "stowkit_reader.wasm",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// 3. Load assets from the pack
|
|
31
|
+
const mesh = await pack.loadMesh("restaurant_counter")
|
|
32
|
+
scene.add(mesh)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Setup: CDN Assets
|
|
36
|
+
|
|
37
|
+
Place `.stow` files in `public/cdn-assets/`. The RUN.game CLI uploads them on deploy.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
my-game/
|
|
41
|
+
├── public/
|
|
42
|
+
│ └── cdn-assets/
|
|
43
|
+
│ ├── packs/
|
|
44
|
+
│ │ ├── environment.stow
|
|
45
|
+
│ │ └── characters.stow
|
|
46
|
+
│ ├── basis/ # Basis Universal transcoder
|
|
47
|
+
│ ├── stowkit/
|
|
48
|
+
│ │ └── draco/ # Draco decoder
|
|
49
|
+
│ └── stowkit_reader.wasm # StowKit WASM reader
|
|
50
|
+
└── src/
|
|
51
|
+
└── ...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Step-by-Step
|
|
55
|
+
|
|
56
|
+
### 1. Initialize the SDK
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import RundotGameAPI from "@series-inc/rundot-game-sdk/api"
|
|
60
|
+
|
|
61
|
+
await RundotGameAPI.initializeAsync()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Fetch a .stow File
|
|
65
|
+
|
|
66
|
+
`cdn.fetchAsset` takes a path relative to `cdn-assets/` and returns a `Promise<Blob>`. Files are content-hashed and cache-busted automatically.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const blob = await RundotGameAPI.cdn.fetchAsset("packs/environment.stow")
|
|
70
|
+
const arrayBuffer = await blob.arrayBuffer()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For large files you can increase the timeout (default 30s):
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const blob = await RundotGameAPI.cdn.fetchAsset("packs/environment.stow", {
|
|
77
|
+
timeout: 60000,
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Parse with StowKitLoader
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { StowKitLoader } from "@series-inc/stowkit-three-loader"
|
|
85
|
+
|
|
86
|
+
const pack = await StowKitLoader.loadFromMemory(arrayBuffer, {
|
|
87
|
+
basisPath: "basis/", // Path to Basis Universal transcoder files
|
|
88
|
+
dracoPath: "stowkit/draco/", // Path to Draco decoder files
|
|
89
|
+
wasmPath: "stowkit_reader.wasm", // Path to StowKit WASM reader
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`loadFromMemory` returns a `StowKitPack` — the handle to all assets inside the `.stow` file.
|
|
94
|
+
|
|
95
|
+
### 4. Load Assets from the Pack
|
|
96
|
+
|
|
97
|
+
#### Meshes
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Static mesh → THREE.Group
|
|
101
|
+
const mesh = await pack.loadMesh("restaurant_display_Money")
|
|
102
|
+
scene.add(mesh)
|
|
103
|
+
|
|
104
|
+
// Skinned mesh (for skeletal animation) → THREE.Group
|
|
105
|
+
const character = await pack.loadSkinnedMesh("character_mesh")
|
|
106
|
+
scene.add(character)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Textures
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// → THREE.Texture
|
|
113
|
+
const texture = await pack.loadTexture("wood_diffuse")
|
|
114
|
+
|
|
115
|
+
// Apply to a material
|
|
116
|
+
material.map = texture
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Animations
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Needs the mesh the animation targets
|
|
123
|
+
const mesh = await pack.loadMesh("character_mesh")
|
|
124
|
+
const { clip } = await pack.loadAnimation(mesh, "walk_cycle")
|
|
125
|
+
|
|
126
|
+
// Use with Three.js AnimationMixer
|
|
127
|
+
const mixer = new THREE.AnimationMixer(mesh)
|
|
128
|
+
mixer.clipAction(clip).play()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Audio
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Needs a THREE.AudioListener
|
|
135
|
+
const listener = new THREE.AudioListener()
|
|
136
|
+
camera.add(listener)
|
|
137
|
+
|
|
138
|
+
const audio = await pack.loadAudio("sfx_click", listener)
|
|
139
|
+
audio.play()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Loading Multiple Packs
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const packPaths = ["packs/environment.stow", "packs/characters.stow", "packs/props.stow"]
|
|
146
|
+
|
|
147
|
+
const packs = await Promise.all(
|
|
148
|
+
packPaths.map(async (path) => {
|
|
149
|
+
const blob = await RundotGameAPI.cdn.fetchAsset(path)
|
|
150
|
+
return StowKitLoader.loadFromMemory(await blob.arrayBuffer(), {
|
|
151
|
+
basisPath: "basis/",
|
|
152
|
+
dracoPath: "stowkit/draco/",
|
|
153
|
+
wasmPath: "stowkit_reader.wasm",
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// Search across packs for an asset
|
|
159
|
+
async function findMesh(name: string): Promise<THREE.Group> {
|
|
160
|
+
for (const pack of packs) {
|
|
161
|
+
try {
|
|
162
|
+
return await pack.loadMesh(name)
|
|
163
|
+
} catch {
|
|
164
|
+
// Not in this pack, try next
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Mesh "${name}" not found in any pack`)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Common Gotchas
|
|
172
|
+
|
|
173
|
+
### Texture wrapping defaults to ClampToEdge
|
|
174
|
+
|
|
175
|
+
The loader doesn't set `wrapS`/`wrapT`, so Three.js defaults to `ClampToEdgeWrapping`. Fix manually if your assets expect tiling:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const mesh = await pack.loadMesh("tiled_floor")
|
|
179
|
+
mesh.traverse((child) => {
|
|
180
|
+
if ((child as THREE.Mesh).isMesh) {
|
|
181
|
+
const mat = (child as THREE.Mesh).material as THREE.MeshStandardMaterial
|
|
182
|
+
if (mat.map) {
|
|
183
|
+
mat.map.wrapS = THREE.RepeatWrapping
|
|
184
|
+
mat.map.wrapT = THREE.RepeatWrapping
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Decoder files must be accessible
|
|
191
|
+
|
|
192
|
+
The `basisPath`, `dracoPath`, and `wasmPath` must point to files the browser can fetch. Place them in `public/cdn-assets/` or serve them from your own CDN.
|
|
193
|
+
|
|
194
|
+
## Related
|
|
195
|
+
|
|
196
|
+
- [Assets API (RUN.game SDK)](../../.rundot-docs/rundot-developer-platform/api/ASSETS.md) — CDN fetch details
|
|
197
|
+
- [StowKitSystem](../systems/StowKitSystem.md) — Engine wrapper that adds caching, prefabs, and instancing on top of this
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/rundot-3d-engine",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
"@dimforge/rapier3d": "^0.11.2",
|
|
35
35
|
"@dimforge/rapier3d-compat": "^0.11.2",
|
|
36
36
|
"@series-inc/stowkit-reader": "^0.1.42",
|
|
37
|
-
"@series-inc/stowkit-three-loader": "^0.1.
|
|
37
|
+
"@series-inc/stowkit-three-loader": "^0.1.44",
|
|
38
38
|
"three": "^0.180.0",
|
|
39
39
|
"three-stdlib": "^2.36.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@series-inc/rundot-game-sdk": "^5.3.0",
|
|
43
42
|
"@capacitor/app": ">=6.0.0",
|
|
44
43
|
"@capacitor/core": ">=6.0.0",
|
|
45
44
|
"@capacitor/local-notifications": ">=6.0.0",
|
|
46
45
|
"@capacitor/preferences": ">=6.0.0",
|
|
47
46
|
"@capacitor/splash-screen": ">=6.0.0",
|
|
47
|
+
"@series-inc/rundot-game-sdk": "^5.3.0",
|
|
48
48
|
"appsflyer-capacitor-plugin": ">=6.17.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|