Fix level double initialization and refactor ship physics
All checks were successful
Build / build (push) Successful in 1m20s

Major Changes:
- Fix Level1 double initialization by deferring initialize() call
  - Removed initialize() from Level1 constructor
  - Main.ts now explicitly calls initialize() after registering ready observable
  - Added error logging for double initialization detection

- Refactor Ship to use loadAsset utility and new GLB structure
  - Changed from SceneLoader.ImportMeshAsync to loadAsset pattern
  - Ship constructor no longer calls initialize() - must be called explicitly
  - Updated physics to use transformNode from GLB container
  - Adjusted control mappings for yaw/pitch/roll (inverted signs)
  - Reduced force multipliers for better control feel

- Remove MaterialFactory pattern
  - Deleted src/materialFactory.ts
  - LevelDeserializer now creates PBRMaterial directly for planets/sun
  - Removed texture quality settings from gameConfig
  - Cleaned up settings UI to remove texture quality controls

- Fix UI element hiding when entering VR
  - Editor and Settings links now properly hidden on level start

- Update GLB models for new asset structure
  - Updated ship.glb and base.glb models
  - Modified loadAsset to work with container transformNodes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-07 11:16:50 -06:00
parent 146ffccd3d
commit 20dfc238f8
18 changed files with 146 additions and 509 deletions

View File

@ -301,47 +301,6 @@
<p class="subtitle">Configure graphics quality and physics settings</p> <p class="subtitle">Configure graphics quality and physics settings</p>
<div class="settings-grid"> <div class="settings-grid">
<!-- Graphics Settings -->
<div class="section">
<h2>🎨 Graphics Quality</h2>
<p style="color: #aaa; font-size: 0.9em; margin-bottom: 20px;">
Higher quality settings may impact performance on lower-end devices.
</p>
<div class="form-group">
<label for="planetTextureLevel">Planet Quality</label>
<select id="planetTextureLevel">
<option value="WIREFRAME">Wireframe (Lowest)</option>
<option value="SIMPLE_MATERIAL">Simple Material</option>
<option value="FULL_TEXTURE">Full Texture (Recommended)</option>
<option value="PBR_TEXTURE">PBR Texture (Highest)</option>
</select>
<div class="help-text">Controls planet rendering quality and detail</div>
</div>
<div class="form-group">
<label for="asteroidTextureLevel">Asteroid Quality</label>
<select id="asteroidTextureLevel">
<option value="WIREFRAME">Wireframe (Lowest)</option>
<option value="SIMPLE_MATERIAL">Simple Material</option>
<option value="FULL_TEXTURE">Full Texture (Recommended)</option>
<option value="PBR_TEXTURE">PBR Texture (Highest)</option>
</select>
<div class="help-text">Controls asteroid rendering quality and detail</div>
</div>
<div class="form-group">
<label for="sunTextureLevel">Sun Quality</label>
<select id="sunTextureLevel">
<option value="WIREFRAME">Wireframe (Lowest)</option>
<option value="SIMPLE_MATERIAL">Simple Material</option>
<option value="FULL_TEXTURE">Full Texture (Recommended)</option>
<option value="PBR_TEXTURE">PBR Texture (Highest)</option>
</select>
<div class="help-text">Controls sun rendering quality</div>
</div>
</div>
<!-- Physics Settings --> <!-- Physics Settings -->
<div class="section"> <div class="section">
<h2>⚛️ Physics</h2> <h2>⚛️ Physics</h2>

Binary file not shown.

View File

@ -1,13 +1,3 @@
/**
* Texture detail levels for game objects
*/
export enum TextureLevel {
WIREFRAME = 'WIREFRAME',
SIMPLE_MATERIAL = 'SIMPLE_MATERIAL',
FULL_TEXTURE = 'FULL_TEXTURE',
PBR_TEXTURE = 'PBR_TEXTURE'
}
/** /**
* Global game configuration settings * Global game configuration settings
* Singleton class for managing game-wide settings * Singleton class for managing game-wide settings
@ -15,10 +5,6 @@ export enum TextureLevel {
export class GameConfig { export class GameConfig {
private static _instance: GameConfig; private static _instance: GameConfig;
// Texture detail settings
public planetTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
public asteroidTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
public sunTextureLevel: TextureLevel = TextureLevel.FULL_TEXTURE;
public debug: boolean = false; public debug: boolean = false;
// Physics settings // Physics settings
public physicsEnabled: boolean = true; public physicsEnabled: boolean = true;
@ -46,9 +32,6 @@ export class GameConfig {
*/ */
public save(): void { public save(): void {
const config = { const config = {
planetTextureLevel: this.planetTextureLevel,
asteroidTextureLevel: this.asteroidTextureLevel,
sunTextureLevel: this.sunTextureLevel,
physicsEnabled: this.physicsEnabled, physicsEnabled: this.physicsEnabled,
debug: this.debug debug: this.debug
}; };
@ -63,9 +46,6 @@ export class GameConfig {
const stored = localStorage.getItem('game-config'); const stored = localStorage.getItem('game-config');
if (stored) { if (stored) {
const config = JSON.parse(stored); const config = JSON.parse(stored);
this.planetTextureLevel = config.planetTextureLevel ?? TextureLevel.FULL_TEXTURE;
this.asteroidTextureLevel = config.asteroidTextureLevel ?? TextureLevel.FULL_TEXTURE;
this.sunTextureLevel = config.sunTextureLevel ?? TextureLevel.FULL_TEXTURE;
this.physicsEnabled = config.physicsEnabled ?? true; this.physicsEnabled = config.physicsEnabled ?? true;
this.debug = config.debug ?? false; this.debug = config.debug ?? false;
} else { } else {
@ -80,9 +60,6 @@ export class GameConfig {
* Reset to default settings * Reset to default settings
*/ */
public reset(): void { public reset(): void {
this.planetTextureLevel = TextureLevel.FULL_TEXTURE;
this.asteroidTextureLevel = TextureLevel.FULL_TEXTURE;
this.sunTextureLevel = TextureLevel.FULL_TEXTURE;
this.physicsEnabled = true; this.physicsEnabled = true;
this.debug = false; this.debug = false;
this.save(); this.save();

View File

@ -30,6 +30,7 @@ export class Level1 implements Level {
this._deserializer = new LevelDeserializer(levelConfig); this._deserializer = new LevelDeserializer(levelConfig);
this._ship = new Ship(audioEngine); this._ship = new Ship(audioEngine);
const xr = DefaultScene.XR; const xr = DefaultScene.XR;
debugLog('Level1 constructor - Setting up XR observables'); debugLog('Level1 constructor - Setting up XR observables');
@ -45,7 +46,7 @@ export class Level1 implements Level {
this._ship.addController(controller); this._ship.addController(controller);
}); });
}); });
this.initialize(); // Don't call initialize here - let Main call it after registering the observable
} }
getReadyObservable(): Observable<Level> { getReadyObservable(): Observable<Level> {
@ -92,9 +93,10 @@ export class Level1 implements Level {
public async initialize() { public async initialize() {
debugLog('Initializing level from config:', this._levelConfig.difficulty); debugLog('Initializing level from config:', this._levelConfig.difficulty);
if (this._initialized) { if (this._initialized) {
console.error('Initialize called twice');
return; return;
} }
await this._ship.initialize();
setLoadingMessage("Loading level from configuration..."); setLoadingMessage("Loading level from configuration...");
// Use deserializer to create all entities from config // Use deserializer to create all entities from config

View File

@ -2,6 +2,8 @@ import {
AbstractMesh, AbstractMesh,
MeshBuilder, MeshBuilder,
Observable, Observable,
PBRMaterial,
Texture,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import { DefaultScene } from "./defaultScene"; import { DefaultScene } from "./defaultScene";
@ -14,7 +16,8 @@ import {
validateLevelConfig validateLevelConfig
} from "./levelConfig"; } from "./levelConfig";
import { GameConfig } from "./gameConfig"; import { GameConfig } from "./gameConfig";
import { MaterialFactory } from "./materialFactory"; import { FireProceduralTexture } from "@babylonjs/procedural-textures";
import { createSphereLightmap } from "./sphereLightmap";
import debugLog from './debug'; import debugLog from './debug';
import StarBase from "./starBase"; import StarBase from "./starBase";
@ -71,8 +74,7 @@ export class LevelDeserializer {
* Create the start base from config * Create the start base from config
*/ */
private async createStartBase(): Promise<AbstractMesh> { private async createStartBase(): Promise<AbstractMesh> {
const position = this?.config?.startBase?.position?this.arrayToVector3(this?.config?.startBase?.position):null; return await StarBase.buildStarBase();
return await StarBase.buildStarBase(position);
} }
/** /**
@ -85,18 +87,13 @@ export class LevelDeserializer {
segments: 32 segments: 32
}, this.scene); }, this.scene);
sun.position = this.arrayToVector3(config.position); sun.position = this.arrayToVector3(config.position);
// Create material using GameConfig texture level
const gameConfig = GameConfig.getInstance();
const material = MaterialFactory.createSunMaterial(
"sunMaterial",
gameConfig.sunTextureLevel,
this.scene
);
sun.material = material;
// Create glow layer // Create PBR sun material with fire texture
//const gl = new GlowLayer("glow", this.scene); const material = new PBRMaterial("sunMaterial", this.scene);
//gl.intensity = 1; material.emissiveTexture = new FireProceduralTexture("fire", 1024, this.scene);
material.emissiveColor.set(0.5, 0.5, 0.1);
material.unlit = true;
sun.material = material;
return sun; return sun;
} }
@ -122,16 +119,28 @@ export class LevelDeserializer {
// Calculate direction from planet to sun // Calculate direction from planet to sun
const toSun = sunPosition.subtract(planetPosition).normalize(); const toSun = sunPosition.subtract(planetPosition).normalize();
// Create material using GameConfig texture level // Create PBR planet material
const config = GameConfig.getInstance(); const material = new PBRMaterial(planetConfig.name + "-material", this.scene);
const material = MaterialFactory.createPlanetMaterial( const texture = new Texture(planetConfig.texturePath, this.scene);
planetConfig.name + "-material",
planetConfig.texturePath, // Create lightmap with bright light pointing toward sun
config.planetTextureLevel, const lightmap = createSphereLightmap(
planetConfig.name + "-lightmap",
256,
this.scene, this.scene,
toSun toSun,
1,
toSun.negate(),
0.3,
0.3
); );
material.albedoTexture = texture;
material.lightmapTexture = lightmap;
material.useLightmapAsShadowmap = true;
material.roughness = 0.8;
material.metallic = 0;
planet.material = material; planet.material = material;
planets.push(planet); planets.push(planet);

View File

@ -46,20 +46,31 @@ export class Main {
setLoadingMessage("This browser does not support WebXR"); setLoadingMessage("This browser does not support WebXR");
return; return;
} }
this.initialize();
// Listen for level selection event // Listen for level selection event
window.addEventListener('levelSelected', async (e: CustomEvent) => { window.addEventListener('levelSelected', async (e: CustomEvent) => {
this._started = true;
await this.initialize();
const {levelName, config} = e.detail as {levelName: string, config: LevelConfig}; const {levelName, config} = e.detail as {levelName: string, config: LevelConfig};
debugLog(`Starting level: ${levelName}`); debugLog(`Starting level: ${levelName}`);
// Show loading UI again // Hide all UI elements
const mainDiv = document.querySelector('#mainDiv'); const mainDiv = document.querySelector('#mainDiv');
const levelSelect = document.querySelector('#levelSelect') as HTMLElement; const levelSelect = document.querySelector('#levelSelect') as HTMLElement;
const editorLink = document.querySelector('.editor-link') as HTMLElement;
const settingsLink = document.querySelector('.settings-link') as HTMLElement;
if (levelSelect) { if (levelSelect) {
levelSelect.style.display = 'none'; levelSelect.style.display = 'none';
} }
if (editorLink) {
editorLink.style.display = 'none';
}
if (settingsLink) {
settingsLink.style.display = 'none';
}
setLoadingMessage("Initializing Level..."); setLoadingMessage("Initializing Level...");
// Unlock audio engine on user interaction // Unlock audio engine on user interaction
@ -80,10 +91,15 @@ export class Main {
this.play(); this.play();
}, 500); }, 500);
}); });
// Now initialize the level (after observable is registered)
await this._currentLevel.initialize();
}); });
// Listen for test level button click // Listen for test level button click
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
const levelSelect = document.querySelector('#levelSelect');
levelSelect.classList.add('ready');
debugLog('[Main] DOMContentLoaded fired, looking for test button...'); debugLog('[Main] DOMContentLoaded fired, looking for test button...');
const testLevelBtn = document.querySelector('#testLevelBtn'); const testLevelBtn = document.querySelector('#testLevelBtn');
debugLog('[Main] Test button found:', !!testLevelBtn); debugLog('[Main] Test button found:', !!testLevelBtn);
@ -92,9 +108,12 @@ export class Main {
testLevelBtn.addEventListener('click', async () => { testLevelBtn.addEventListener('click', async () => {
debugLog('[Main] ========== TEST LEVEL BUTTON CLICKED =========='); debugLog('[Main] ========== TEST LEVEL BUTTON CLICKED ==========');
// Show loading UI // Hide all UI elements
const mainDiv = document.querySelector('#mainDiv'); const mainDiv = document.querySelector('#mainDiv');
const levelSelect = document.querySelector('#levelSelect') as HTMLElement; const levelSelect = document.querySelector('#levelSelect') as HTMLElement;
const editorLink = document.querySelector('.editor-link') as HTMLElement;
const settingsLink = document.querySelector('.settings-link') as HTMLElement;
debugLog('[Main] mainDiv exists:', !!mainDiv); debugLog('[Main] mainDiv exists:', !!mainDiv);
debugLog('[Main] levelSelect exists:', !!levelSelect); debugLog('[Main] levelSelect exists:', !!levelSelect);
@ -102,6 +121,12 @@ export class Main {
levelSelect.style.display = 'none'; levelSelect.style.display = 'none';
debugLog('[Main] levelSelect hidden'); debugLog('[Main] levelSelect hidden');
} }
if (editorLink) {
editorLink.style.display = 'none';
}
if (settingsLink) {
settingsLink.style.display = 'none';
}
setLoadingMessage("Initializing Test Scene..."); setLoadingMessage("Initializing Test Scene...");
// Unlock audio engine on user interaction // Unlock audio engine on user interaction
@ -193,7 +218,6 @@ export class Main {
// photoDome1.position = DefaultScene.MainScene.activeCamera.globalPosition; // photoDome1.position = DefaultScene.MainScene.activeCamera.globalPosition;
// photoDome2.position = DefaultScene.MainScene.activeCamera.globalPosition; // photoDome2.position = DefaultScene.MainScene.activeCamera.globalPosition;
}); });
setLoadingMessage("Select a difficulty to begin!");
} }
private async setupScene() { private async setupScene() {
@ -236,12 +260,8 @@ export class Main {
window.setTimeout(()=>{ window.setTimeout(()=>{
if (!this._started) { if (!this._started) {
this._started = true; this._started = true;
const levelSelect = document.querySelector('#levelSelect');
if (levelSelect) {
levelSelect.classList.add('ready');
setLoadingMessage("Ready!"); setLoadingMessage("Ready!");
} }
}
}) })
this._engine.runRenderLoop(() => { this._engine.runRenderLoop(() => {
@ -250,6 +270,7 @@ export class Main {
} }
private async setupPhysics() { private async setupPhysics() {
//DefaultScene.MainScene.useRightHandedSystem = true;
const havok = await HavokPhysics(); const havok = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havok); const havokPlugin = new HavokPlugin(true, havok);
//DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1); //DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);

View File

@ -1,305 +0,0 @@
import {
Color3,
DynamicTexture,
NoiseProceduralTexture,
PBRMaterial,
Scene,
StandardMaterial,
Texture,
Vector3
} from "@babylonjs/core";
import { TextureLevel } from "./gameConfig";
import { FireProceduralTexture } from "@babylonjs/procedural-textures";
import { createSphereLightmap } from "./sphereLightmap";
/**
* Factory for creating materials at different quality levels
*/
export class MaterialFactory {
/**
* Create a planet material based on texture level
*/
public static createPlanetMaterial(
name: string,
texturePath: string,
textureLevel: TextureLevel,
scene: Scene,
sunDirection: Vector3
): StandardMaterial | PBRMaterial {
switch (textureLevel) {
case TextureLevel.WIREFRAME:
return this.createWireframeMaterial(name, scene, new Color3(0.5, 0.5, 0.8));
case TextureLevel.SIMPLE_MATERIAL:
return this.createSimplePlanetMaterial(name, scene);
case TextureLevel.FULL_TEXTURE:
return this.createFullTexturePlanetMaterial(name, texturePath, scene, sunDirection);
case TextureLevel.PBR_TEXTURE:
return this.createPBRPlanetMaterial(name, texturePath, scene, sunDirection);
default:
return this.createFullTexturePlanetMaterial(name, texturePath, scene, sunDirection);
}
}
/**
* Create an asteroid material based on texture level
*/
public static createAsteroidMaterial(
name: string,
textureLevel: TextureLevel,
scene: Scene,
originalMaterial?: PBRMaterial
): StandardMaterial | PBRMaterial {
switch (textureLevel) {
case TextureLevel.WIREFRAME:
return this.createWireframeMaterial(name, scene, new Color3(0.5, 0.5, 0.5));
case TextureLevel.SIMPLE_MATERIAL:
return this.createSimpleAsteroidMaterial(name, scene);
case TextureLevel.FULL_TEXTURE:
return this.createFullTextureAsteroidMaterial(name, scene, originalMaterial);
case TextureLevel.PBR_TEXTURE:
return this.createPBRAsteroidMaterial(name, scene, originalMaterial);
default:
return this.createFullTextureAsteroidMaterial(name, scene, originalMaterial);
}
}
/**
* Create a sun material based on texture level
*/
public static createSunMaterial(
name: string,
textureLevel: TextureLevel,
scene: Scene
): StandardMaterial | PBRMaterial {
switch (textureLevel) {
case TextureLevel.WIREFRAME:
return this.createWireframeMaterial(name, scene, new Color3(1, 1, 0));
case TextureLevel.SIMPLE_MATERIAL:
return this.createSimpleSunMaterial(name, scene);
case TextureLevel.FULL_TEXTURE:
return this.createFullTextureSunMaterial(name, scene);
case TextureLevel.PBR_TEXTURE:
return this.createPBRSunMaterial(name, scene);
default:
return this.createFullTextureSunMaterial(name, scene);
}
}
// ========== Private helper methods ==========
/**
* Create wireframe material
*/
private static createWireframeMaterial(
name: string,
scene: Scene,
color: Color3
): StandardMaterial {
const material = new StandardMaterial(name, scene);
material.wireframe = true;
material.emissiveColor = color;
material.disableLighting = true;
return material;
}
/**
* Create simple planet material with solid color
*/
private static createSimplePlanetMaterial(name: string, scene: Scene): StandardMaterial {
const material = new StandardMaterial(name, scene);
material.diffuseColor = new Color3(0.4, 0.6, 0.8);
material.specularColor = Color3.Black();
return material;
}
/**
* Create full texture planet material (current implementation)
*/
private static createFullTexturePlanetMaterial(
name: string,
texturePath: string,
scene: Scene,
sunDirection: Vector3
): StandardMaterial {
const material = new StandardMaterial(name, scene);
const texture = new Texture(texturePath, scene);
// Create lightmap with bright light pointing toward sun
const lightmap = createSphereLightmap(
name + "-lightmap",
256,
scene,
sunDirection,
1,
sunDirection.negate(),
0.3,
0.3
);
material.emissiveTexture = texture;
material.lightmapTexture = lightmap;
material.useLightmapAsShadowmap = true;
material.disableLighting = true;
material.roughness = 1;
material.specularColor = Color3.Black();
material.freeze();
return material;
}
/**
* Create PBR planet material
*/
private static createPBRPlanetMaterial(
name: string,
texturePath: string,
scene: Scene,
sunDirection: Vector3
): PBRMaterial {
const material = new PBRMaterial(name, scene);
const texture = new Texture(texturePath, scene);
// Create lightmap with bright light pointing toward sun
const lightmap = createSphereLightmap(
name + "-lightmap",
256,
scene,
sunDirection,
1,
sunDirection.negate(),
0.3,
0.3
);
material.albedoTexture = texture;
material.lightmapTexture = lightmap;
material.useLightmapAsShadowmap = true;
material.roughness = 0.8;
material.metallic = 0;
return material;
}
/**
* Create simple asteroid material with solid color
*/
private static createSimpleAsteroidMaterial(name: string, scene: Scene): StandardMaterial {
const material = new StandardMaterial(name, scene);
material.diffuseColor = new Color3(0.4, 0.4, 0.4);
material.specularColor = Color3.Black();
return material;
}
/**
* Create full texture asteroid material (current implementation)
*/
private static createFullTextureAsteroidMaterial(name: string, scene: Scene, originalMaterial?: PBRMaterial): StandardMaterial {
// If we have the original material from GLB, use it as a base
if (originalMaterial) {
// Clone the original material to preserve bump texture and other properties
const material = originalMaterial.clone(name) as PBRMaterial;
// Create noise texture for color variation
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
noiseTexture.brightness = 0.6;
noiseTexture.octaves = 4;
// Replace only the albedo texture, keeping bump and other textures
material.albedoTexture = noiseTexture;
material.roughness = 1;
return material as any as StandardMaterial;
}
// Fallback if no original material
const material = new StandardMaterial(name, scene);
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
noiseTexture.brightness = 0.6;
noiseTexture.octaves = 4;
material.ambientTexture = noiseTexture;
material.diffuseTexture = noiseTexture;
material.roughness = 1;
return material;
}
/**
* Create PBR asteroid material
*/
private static createPBRAsteroidMaterial(name: string, scene: Scene, originalMaterial?: PBRMaterial): PBRMaterial {
// If we have the original material from GLB, use it as a base
if (originalMaterial) {
// Clone the original material to preserve bump texture and other properties
const material = originalMaterial.clone(name) as PBRMaterial;
// Create noise texture for color variation
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
noiseTexture.brightness = 0.6;
noiseTexture.octaves = 4;
// Replace only the albedo texture, keeping bump and other textures
material.albedoTexture = noiseTexture;
material.roughness = 1;
material.metallic = 0;
return material;
}
// Fallback if no original material
const material = new PBRMaterial(name, scene);
const noiseTexture = new NoiseProceduralTexture(name + "-noise", 256, scene);
noiseTexture.brightness = 0.6;
noiseTexture.octaves = 4;
material.albedoTexture = noiseTexture;
material.roughness = 1;
material.metallic = 0;
return material;
}
/**
* Create simple sun material with solid color
*/
private static createSimpleSunMaterial(name: string, scene: Scene): StandardMaterial {
const material = new StandardMaterial(name, scene);
material.emissiveColor = new Color3(1, 0.9, 0.2);
material.disableLighting = true;
return material;
}
/**
* Create full texture sun material (current implementation)
*/
private static createFullTextureSunMaterial(name: string, scene: Scene): StandardMaterial {
const material = new StandardMaterial(name, scene);
material.emissiveTexture = new FireProceduralTexture("fire", 1024, scene);
material.emissiveColor = new Color3(0.5, 0.5, 0.1);
material.disableLighting = true;
return material;
}
/**
* Create PBR sun material
*/
private static createPBRSunMaterial(name: string, scene: Scene): PBRMaterial {
const material = new PBRMaterial(name, scene);
material.emissiveTexture = new FireProceduralTexture("fire", 1024, scene);
material.emissiveColor = new Color3(0.5, 0.5, 0.1);
material.unlit = true;
return material;
}
}

View File

@ -55,7 +55,7 @@ export class RockFactory {
private static async loadMesh() { private static async loadMesh() {
debugLog('loading mesh'); debugLog('loading mesh');
this._asteroidMesh = (await loadAsset("asteroid.glb")).meshes.get('Asteroid'); this._asteroidMesh = (await loadAsset("asteroid.glb")).meshes.get('Asteroid');
this._asteroidMesh.setParent(null); //this._asteroidMesh.setParent(null);
this._asteroidMesh.setEnabled(false); this._asteroidMesh.setEnabled(false);
debugLog(this._asteroidMesh); debugLog(this._asteroidMesh);
} }

View File

@ -1,4 +1,4 @@
import { GameConfig, TextureLevel } from "./gameConfig"; import { GameConfig } from "./gameConfig";
/** /**
* Initialize the settings screen * Initialize the settings screen
@ -7,9 +7,6 @@ export function initializeSettingsScreen(): void {
const config = GameConfig.getInstance(); const config = GameConfig.getInstance();
// Get form elements // Get form elements
const planetTextureSelect = document.getElementById('planetTextureLevel') as HTMLSelectElement;
const asteroidTextureSelect = document.getElementById('asteroidTextureLevel') as HTMLSelectElement;
const sunTextureSelect = document.getElementById('sunTextureLevel') as HTMLSelectElement;
const physicsEnabledCheckbox = document.getElementById('physicsEnabled') as HTMLInputElement; const physicsEnabledCheckbox = document.getElementById('physicsEnabled') as HTMLInputElement;
const debugEnabledCheckbox = document.getElementById('debugEnabled') as HTMLInputElement; const debugEnabledCheckbox = document.getElementById('debugEnabled') as HTMLInputElement;
@ -39,9 +36,6 @@ export function initializeSettingsScreen(): void {
* Load current settings into form * Load current settings into form
*/ */
function loadSettings(): void { function loadSettings(): void {
if (planetTextureSelect) planetTextureSelect.value = config.planetTextureLevel;
if (asteroidTextureSelect) asteroidTextureSelect.value = config.asteroidTextureLevel;
if (sunTextureSelect) sunTextureSelect.value = config.sunTextureLevel;
if (physicsEnabledCheckbox) physicsEnabledCheckbox.checked = config.physicsEnabled; if (physicsEnabledCheckbox) physicsEnabledCheckbox.checked = config.physicsEnabled;
if (debugEnabledCheckbox) debugEnabledCheckbox.checked = config.debug; if (debugEnabledCheckbox) debugEnabledCheckbox.checked = config.debug;
} }
@ -50,9 +44,6 @@ export function initializeSettingsScreen(): void {
* Save form settings to GameConfig * Save form settings to GameConfig
*/ */
function saveSettings(): void { function saveSettings(): void {
config.planetTextureLevel = planetTextureSelect.value as TextureLevel;
config.asteroidTextureLevel = asteroidTextureSelect.value as TextureLevel;
config.sunTextureLevel = sunTextureSelect.value as TextureLevel;
config.physicsEnabled = physicsEnabledCheckbox.checked; config.physicsEnabled = physicsEnabledCheckbox.checked;
config.debug = debugEnabledCheckbox.checked; config.debug = debugEnabledCheckbox.checked;
config.save(); config.save();

View File

@ -8,7 +8,6 @@ import {
PhysicsAggregate, PhysicsAggregate,
PhysicsMotionType, PhysicsMotionType,
PhysicsShapeType, PhysicsShapeType,
SceneLoader,
StandardMaterial, StandardMaterial,
TransformNode, TransformNode,
Vector2, Vector2,
@ -23,10 +22,12 @@ import { GameConfig } from "./gameConfig";
import { Sight } from "./sight"; import { Sight } from "./sight";
import debugLog from './debug'; import debugLog from './debug';
import {Scoreboard} from "./scoreboard"; import {Scoreboard} from "./scoreboard";
import loadAsset from "./utils/loadAsset";
import {Debug} from "@babylonjs/core/Legacy/legacy";
const MAX_LINEAR_VELOCITY = 200; const MAX_LINEAR_VELOCITY = 200;
const MAX_ANGULAR_VELOCITY = 1.8; const MAX_ANGULAR_VELOCITY = 1.4;
const LINEAR_FORCE_MULTIPLIER = 1200; const LINEAR_FORCE_MULTIPLIER = 800;
const ANGULAR_FORCE_MULTIPLIER = 20; const ANGULAR_FORCE_MULTIPLIER = 15;
const controllerComponents = [ const controllerComponents = [
'a-button', 'a-button',
@ -69,8 +70,6 @@ export class Ship {
constructor( audioEngine?: AudioEngineV2) { constructor( audioEngine?: AudioEngineV2) {
this._audioEngine = audioEngine; this._audioEngine = audioEngine;
this.setup();
this.initialize();
} }
private async initializeSounds() { private async initializeSounds() {
@ -102,8 +101,8 @@ export class Ship {
this._shot?.play(); this._shot?.play();
const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh); const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh);
ammo.parent = this._ship; ammo.parent = this._ship;
ammo.position.y = .5; ammo.position.y = .1;
ammo.position.z = 7.1; ammo.position.z = 8.4;
//ammo.rotation.x = Math.PI / 2; //ammo.rotation.x = Math.PI / 2;
ammo.setParent(null); ammo.setParent(null);
const ammoAggregate = new PhysicsAggregate(ammo, PhysicsShapeType.SPHERE, { const ammoAggregate = new PhysicsAggregate(ammo, PhysicsShapeType.SPHERE, {
@ -141,13 +140,48 @@ export class Ship {
}) })
} }
private setup() {
this._ship = new TransformNode("ship", DefaultScene.MainScene);
public async initialize() {
this._scoreboard = new Scoreboard();
this._ship = new TransformNode("shipBawe", DefaultScene.MainScene);
//this._ship.rotation.y = Math.PI;
const data = await loadAsset('ship.glb');
const axes = new Debug.AxesViewer(DefaultScene.MainScene, 1);
//axes.xAxis.parent = data.container.rootNodes[0];
//axes.yAxis.parent = data.container.rootNodes[0];
axes.zAxis.parent = data.container.transformNodes[0];
//data.container.transformNodes[0].parent = this._ship;
this._ship = data.container.transformNodes[0];
this._ship.position.y = 5;
const config = GameConfig.getInstance();
if (config.physicsEnabled) {
console.log('Physics Enabled for Ship');
if (this._ship) {
const agg = new PhysicsAggregate(
this._ship,
PhysicsShapeType.MESH,
{
mass: 10,
mesh: data.container.rootNodes[0].getChildMeshes()[0] as Mesh
},
DefaultScene.MainScene
);
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
agg.body.setLinearDamping(.2);
agg.body.setAngularDamping(.4);
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
agg.body.setCollisionCallbackEnabled(true);
} else {
console.warn("No geometry mesh found, cannot create physics");
}
}
//shipMesh.position.z = -1;
// Create sounds asynchronously if audio engine is available
if (this._audioEngine) { if (this._audioEngine) {
this.initializeSounds(); await this.initializeSounds();
} }
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene); this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0); this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
@ -171,11 +205,11 @@ export class Ship {
DefaultScene.MainScene); DefaultScene.MainScene);
this._camera.parent = this._ship; this._camera.parent = this._ship;
DefaultScene.MainScene.setActiveCameraByName("Flat Camera"); //DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
// Create sight reticle // Create sight reticle
this._sight = new Sight(DefaultScene.MainScene, this._ship, { this._sight = new Sight(DefaultScene.MainScene, this._ship, {
position: new Vector3(0, .5, 125), position: new Vector3(0, .1, 125),
circleRadius: 2, circleRadius: 2,
crosshairLength: 1.5, crosshairLength: 1.5,
lineThickness: 0.1, lineThickness: 0.1,
@ -183,78 +217,20 @@ export class Ship {
renderingGroupId: 3, renderingGroupId: 3,
centerGap: 0.5 centerGap: 0.5
}); });
console.log(data.meshes.get('Screen'));
const screen = DefaultScene.MainScene.getMaterialById('Screen').getBindedMeshes()[0] as Mesh
console.log(screen);
const old = screen.parent;
screen.setParent(null);
screen.setPivotPoint(screen.getBoundingInfo().boundingSphere.center);
screen.setParent(old);
screen.rotation.y = Math.PI;
console.log(screen.rotation);
console.log(screen.scaling);
} this._scoreboard.initialize(screen);
private async initialize() {
this._scoreboard = new Scoreboard();
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "ship2.glb", DefaultScene.MainScene);
const shipMesh = importMesh.meshes[0];
shipMesh.id = "shipMesh";
shipMesh.name = "shipMesh";
debugLog(shipMesh.position);
shipMesh.parent = this._ship;
debugLog(shipMesh.position);
// Create physics aggregate based on the loaded mesh (if physics enabled)
const config = GameConfig.getInstance();
if (config.physicsEnabled) {
// Find the actual geometry mesh (usually meshes[1] or a child)
//const geometryMesh = importMesh.meshes.find(m => m instanceof Mesh && m.getTotalVertices() > 0) as Mesh;
const geo = shipMesh.getChildMeshes()[0]
if (geo) {
// Create physics aggregate on the ship TransformNode using the mesh shape
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.CONVEX_HULL, {
mass: 100,
mesh: (geo as Mesh) // Use the actual ship geometry
}, DefaultScene.MainScene);
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
agg.body.setLinearDamping(.2);
agg.body.setAngularDamping(.3);
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
agg.body.setCollisionCallbackEnabled(true);
} else {
console.warn("No geometry mesh found in ship1.glb, falling back to box shape");
// Fallback to box shape if mesh not found
const agg = new PhysicsAggregate(this._ship, PhysicsShapeType.BOX, {
mass: 100,
extents: new Vector3(4, 4, 7.4),
center: new Vector3(0, 1, 1.8)
}, DefaultScene.MainScene);
agg.body.setMotionType(PhysicsMotionType.DYNAMIC);
agg.body.setLinearDamping(.1);
agg.body.setAngularDamping(.2);
agg.body.setAngularVelocity(new Vector3(0, 0, 0));
agg.body.setCollisionCallbackEnabled(true);
}
}
//shipMesh.rotation.y = Angle.FromDegrees(90).radians();
//shipMesh.rotation.y = Math.PI;
//shipMesh.position.y = 1;
shipMesh.position.z = -1;
// shipMesh.renderingGroupId = 3;
//const light = new PointLight("ship.light", new Vector3(0, .5, .1), DefaultScene.MainScene);
//light.intensity = 4;
/*light.includedOnlyMeshes = [shipMesh];
for (const mesh of shipMesh.getChildMeshes()) {
// mesh.renderingGroupId = 3;
if (mesh.material.id.indexOf('glass') === -1) {
light.includedOnlyMeshes.push(mesh);
}
}
light.parent = this._ship;*/
//DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
const screenMesh = DefaultScene.MainScene.getMaterialById('Screen')?.getBindedMeshes()[0];
this._scoreboard.initialize(screenMesh as Mesh);
//this._scoreboard.initialize(null);
} }
@ -287,7 +263,8 @@ export class Ship {
// Transform to world space - TransformNode vectors are in local space! // Transform to world space - TransformNode vectors are in local space!
const worldDirection = Vector3.TransformNormal(localDirection, this._ship.getWorldMatrix()); const worldDirection = Vector3.TransformNormal(localDirection, this._ship.getWorldMatrix());
const force = worldDirection.scale(LINEAR_FORCE_MULTIPLIER); const force = worldDirection.scale(LINEAR_FORCE_MULTIPLIER);
body.applyForce(force, this._ship.physicsBody.transformNode.absolutePosition); const thrustPoint = Vector3.TransformCoordinates(this._ship.physicsBody.getMassProperties().centerOfMass.add(new Vector3(0,1,0)), this._ship.getWorldMatrix());
body.applyForce(force, thrustPoint);
} }
@ -318,9 +295,9 @@ export class Ship {
// Only apply torque if we haven't reached max angular velocity // Only apply torque if we haven't reached max angular velocity
if (currentAngularSpeed < MAX_ANGULAR_VELOCITY) { if (currentAngularSpeed < MAX_ANGULAR_VELOCITY) {
const yaw = this._leftStickVector.x; const yaw = -this._leftStickVector.x;
const pitch = -this._rightStickVector.y; const pitch = this._rightStickVector.y;
const roll = -this._rightStickVector.x; const roll = this._rightStickVector.x;
// Create torque in local space, then transform to world space // Create torque in local space, then transform to world space
const localTorque = new Vector3(pitch, yaw, roll).scale(ANGULAR_FORCE_MULTIPLIER); const localTorque = new Vector3(pitch, yaw, roll).scale(ANGULAR_FORCE_MULTIPLIER);

View File

@ -1,6 +1,6 @@
import { import {
AbstractMesh, AbstractMesh,
HavokPlugin, HavokPlugin, Mesh,
PhysicsAggregate, PhysicsAggregate,
PhysicsMotionType, PhysicsMotionType,
PhysicsShapeType, PhysicsShapeType,
@ -17,13 +17,14 @@ import loadAsset from "./utils/loadAsset";
* @returns Promise resolving to the loaded star base mesh * @returns Promise resolving to the loaded star base mesh
*/ */
export default class StarBase { export default class StarBase {
public static async buildStarBase(position: Vector3): Promise<AbstractMesh> { public static async buildStarBase(): Promise<AbstractMesh> {
const config = GameConfig.getInstance(); const config = GameConfig.getInstance();
const scene = DefaultScene.MainScene; const scene = DefaultScene.MainScene;
const importMeshes = await loadAsset('base.glb'); const importMeshes = await loadAsset('base.glb');
const baseMesh = importMeshes.meshes.get('Base'); const baseMesh = importMeshes.meshes.get('Base');
const landingMesh = importMeshes.meshes.get('BaseLandingZone'); const landingMesh = importMeshes.meshes.get('BaseLandingZone');
clearParent(importMeshes.meshes, position);
if (config.physicsEnabled) { if (config.physicsEnabled) {
@ -38,14 +39,14 @@ export default class StarBase {
const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH); const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH);
landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED); landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED);
landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => { /*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
console.log(collidedCollidedBody);
}); });*/
landingAgg.shape.isTrigger = true; landingAgg.shape.isTrigger = true;
(DefaultScene.MainScene.getPhysicsEngine().getPhysicsPlugin() as HavokPlugin).onTriggerCollisionObservable.add((eventdata, eventState) => { /*(DefaultScene.MainScene.getPhysicsEngine().getPhysicsPlugin() as HavokPlugin).onTriggerCollisionObservable.add((eventdata, eventState) => {
console.log(eventState); console.log(eventState);
console.log(eventdata); console.log(eventdata);
}) })*/
landingAgg.body.setCollisionCallbackEnabled(true); landingAgg.body.setCollisionCallbackEnabled(true);
} }
//importMesh.rootNodes[0].dispose(); //importMesh.rootNodes[0].dispose();

View File

@ -8,7 +8,12 @@ export type LoadedAsset = {
export default async function loadAsset(file: string, theme: string = "default"): Promise<LoadedAsset> { export default async function loadAsset(file: string, theme: string = "default"): Promise<LoadedAsset> {
const container = await LoadAssetContainerAsync(`assets/themes/${theme}/models/${file}`, DefaultScene.MainScene); const container = await LoadAssetContainerAsync(`assets/themes/${theme}/models/${file}`, DefaultScene.MainScene);
const map: Map<string, AbstractMesh> = new Map(); const map: Map<string, AbstractMesh> = new Map();
container.addAllToScene();
for (const mesh of container.rootNodes[0].getChildMeshes(false)) { for (const mesh of container.rootNodes[0].getChildMeshes(false)) {
console.log(mesh.id, mesh);
//mesh.setParent(null);
//mesh.rotation.y = Math.PI /2;
//mesh.rotation.z = Math.PI;
map.set(mesh.id, mesh); map.set(mesh.id, mesh);
} }
return {container: container, meshes: map}; return {container: container, meshes: map};

Binary file not shown.

BIN
themes/default/base.blend1 Normal file

Binary file not shown.

Binary file not shown.

BIN
themes/default/ship.blend1 Normal file

Binary file not shown.