All checks were successful
Build / build (push) Successful in 1m20s
## Major Reorganization
Reorganized all 57 TypeScript files from flat src/ directory into logical subdirectories for improved maintainability and discoverability.
## New Directory Structure
```
src/
├── core/ (4 files)
│ └── Foundation modules: defaultScene, gameConfig, debug, router
│
├── ship/ (10 files)
│ ├── Ship coordination and subsystems
│ └── input/ - VR controller and keyboard input
│
├── levels/ (10 files)
│ ├── config/ - Level schema, serialization, deserialization
│ ├── generation/ - Level generator and editor
│ └── ui/ - Level selector
│
├── environment/ (11 files)
│ ├── asteroids/ - Rock factory and explosions
│ ├── celestial/ - Suns, planets, textures
│ ├── stations/ - Star base loading
│ └── background/ - Stars, mirror, radar
│
├── ui/ (9 files)
│ ├── hud/ - Scoreboard and status screen
│ ├── screens/ - Login, settings, preloader
│ └── widgets/ - Discord integration
│
├── replay/ (7 files)
│ ├── Replay system components
│ └── recording/ - Physics recording and storage
│
├── game/ (3 files)
│ └── Game systems: stats, progression, demo
│
├── services/ (2 files)
│ └── External integrations: auth, social
│
└── utils/ (5 files)
└── Shared utilities and helpers
```
## Changes Made
### File Moves (57 files)
- Core modules: 4 files → core/
- Ship system: 10 files → ship/ + ship/input/
- Level system: 10 files → levels/ (+ 3 subdirs)
- Environment: 11 files → environment/ (+ 4 subdirs)
- UI components: 9 files → ui/ (+ 3 subdirs)
- Replay system: 7 files → replay/ + replay/recording/
- Game systems: 3 files → game/
- Services: 2 files → services/
- Utilities: 5 files → utils/
### Import Path Updates
- Updated ~200 import statements across all files
- Fixed relative paths based on new directory structure
- Fixed case-sensitive import issues (physicsRecorder, physicsStorage)
- Ensured consistent lowercase filenames for imports
## Benefits
1. **Easy Navigation** - Related code grouped together
2. **Clear Boundaries** - Logical separation of concerns
3. **Scalability** - Easy pattern for adding new features
4. **Discoverability** - Find ship code in /ship, levels in /levels, etc.
5. **Maintainability** - Isolated modules easier to update
6. **No Circular Dependencies** - Clean dependency graph maintained
## Testing
- All TypeScript compilation errors resolved
- Build succeeds with new structure
- Import paths verified and corrected
- Case-sensitivity issues fixed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
188 lines
5.0 KiB
TypeScript
188 lines
5.0 KiB
TypeScript
import {
|
|
AbstractMesh,
|
|
ArcRotateCamera,
|
|
Scene,
|
|
Vector3
|
|
} from "@babylonjs/core";
|
|
import debugLog from "../core/debug";
|
|
|
|
/**
|
|
* Camera modes for replay viewing
|
|
*/
|
|
export enum CameraMode {
|
|
FREE = "free",
|
|
FOLLOW_SHIP = "follow_ship"
|
|
}
|
|
|
|
/**
|
|
* Manages camera for replay viewing with free and follow modes
|
|
*/
|
|
export class ReplayCamera {
|
|
private _camera: ArcRotateCamera;
|
|
private _scene: Scene;
|
|
private _mode: CameraMode = CameraMode.FREE;
|
|
private _followTarget: AbstractMesh | null = null;
|
|
|
|
constructor(scene: Scene) {
|
|
this._scene = scene;
|
|
|
|
// Create orbiting camera
|
|
this._camera = new ArcRotateCamera(
|
|
"replayCamera",
|
|
Math.PI / 2, // alpha (horizontal rotation)
|
|
Math.PI / 3, // beta (vertical rotation)
|
|
50, // radius (distance from target)
|
|
Vector3.Zero(),
|
|
scene
|
|
);
|
|
|
|
// Attach controls for user interaction
|
|
const canvas = scene.getEngine().getRenderingCanvas();
|
|
if (canvas) {
|
|
this._camera.attachControl(canvas, true);
|
|
}
|
|
|
|
// Set camera limits
|
|
this._camera.lowerRadiusLimit = 10;
|
|
this._camera.upperRadiusLimit = 500;
|
|
this._camera.lowerBetaLimit = 0.1;
|
|
this._camera.upperBetaLimit = Math.PI / 2;
|
|
|
|
// Set clipping planes for visibility
|
|
this._camera.minZ = 0.1; // Very close near plane
|
|
this._camera.maxZ = 5000; // Far plane for distant objects
|
|
|
|
// Mouse wheel zoom speed
|
|
this._camera.wheelPrecision = 20;
|
|
|
|
// Panning speed
|
|
this._camera.panningSensibility = 50;
|
|
|
|
scene.activeCamera = this._camera;
|
|
|
|
debugLog("ReplayCamera: Created with clipping planes minZ=0.1, maxZ=5000");
|
|
}
|
|
|
|
/**
|
|
* Get the camera instance
|
|
*/
|
|
public getCamera(): ArcRotateCamera {
|
|
return this._camera;
|
|
}
|
|
|
|
/**
|
|
* Set camera mode
|
|
*/
|
|
public setMode(mode: CameraMode): void {
|
|
this._mode = mode;
|
|
debugLog(`ReplayCamera: Mode set to ${mode}`);
|
|
}
|
|
|
|
/**
|
|
* Get current mode
|
|
*/
|
|
public getMode(): CameraMode {
|
|
return this._mode;
|
|
}
|
|
|
|
/**
|
|
* Toggle between free and follow modes
|
|
*/
|
|
public toggleMode(): void {
|
|
if (this._mode === CameraMode.FREE) {
|
|
this.setMode(CameraMode.FOLLOW_SHIP);
|
|
} else {
|
|
this.setMode(CameraMode.FREE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set target to follow (usually the ship)
|
|
*/
|
|
public setFollowTarget(mesh: AbstractMesh | null): void {
|
|
this._followTarget = mesh;
|
|
if (mesh) {
|
|
this._camera.setTarget(mesh.position);
|
|
debugLog("ReplayCamera: Follow target set");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate optimal viewpoint to frame all objects
|
|
*/
|
|
public frameAllObjects(objects: AbstractMesh[]): void {
|
|
if (objects.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Calculate bounding box of all objects
|
|
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
|
|
objects.forEach(obj => {
|
|
const pos = obj.position;
|
|
debugLog(`ReplayCamera: Framing object ${obj.name} at position ${pos.toString()}`);
|
|
minX = Math.min(minX, pos.x);
|
|
minY = Math.min(minY, pos.y);
|
|
minZ = Math.min(minZ, pos.z);
|
|
maxX = Math.max(maxX, pos.x);
|
|
maxY = Math.max(maxY, pos.y);
|
|
maxZ = Math.max(maxZ, pos.z);
|
|
});
|
|
|
|
// Calculate center
|
|
const center = new Vector3(
|
|
(minX + maxX) / 2,
|
|
(minY + maxY) / 2,
|
|
(minZ + maxZ) / 2
|
|
);
|
|
|
|
// Calculate size
|
|
const size = Math.max(
|
|
maxX - minX,
|
|
maxY - minY,
|
|
maxZ - minZ
|
|
);
|
|
|
|
// Position camera to frame everything
|
|
this._camera.setTarget(center);
|
|
this._camera.radius = Math.max(50, size * 1.5); // At least 50 units away
|
|
|
|
debugLog(`ReplayCamera: Framed ${objects.length} objects (radius: ${this._camera.radius.toFixed(1)})`);
|
|
}
|
|
|
|
/**
|
|
* Update camera (call every frame)
|
|
*/
|
|
public update(): void {
|
|
if (this._mode === CameraMode.FOLLOW_SHIP && this._followTarget) {
|
|
// Smooth camera following with lerp
|
|
Vector3.LerpToRef(
|
|
this._camera.target,
|
|
this._followTarget.position,
|
|
0.1, // Smoothing factor (0 = no follow, 1 = instant)
|
|
this._camera.target
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset camera to default position
|
|
*/
|
|
public reset(): void {
|
|
this._camera.alpha = Math.PI / 2;
|
|
this._camera.beta = Math.PI / 3;
|
|
this._camera.radius = 50;
|
|
this._camera.setTarget(Vector3.Zero());
|
|
debugLog("ReplayCamera: Reset to default");
|
|
}
|
|
|
|
/**
|
|
* Dispose of camera
|
|
*/
|
|
public dispose(): void {
|
|
this._camera.dispose();
|
|
debugLog("ReplayCamera: Disposed");
|
|
}
|
|
}
|