Fix level double initialization and refactor ship physics
All checks were successful
Build / build (push) Successful in 1m20s
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:
parent
146ffccd3d
commit
20dfc238f8
41
index.html
41
index.html
@ -301,47 +301,6 @@
|
||||
<p class="subtitle">Configure graphics quality and physics settings</p>
|
||||
|
||||
<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 -->
|
||||
<div class="section">
|
||||
<h2>⚛️ Physics</h2>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
public/ship1.stl
BIN
public/ship1.stl
Binary file not shown.
@ -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
|
||||
* Singleton class for managing game-wide settings
|
||||
@ -15,10 +5,6 @@ export enum TextureLevel {
|
||||
export class 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;
|
||||
// Physics settings
|
||||
public physicsEnabled: boolean = true;
|
||||
@ -46,9 +32,6 @@ export class GameConfig {
|
||||
*/
|
||||
public save(): void {
|
||||
const config = {
|
||||
planetTextureLevel: this.planetTextureLevel,
|
||||
asteroidTextureLevel: this.asteroidTextureLevel,
|
||||
sunTextureLevel: this.sunTextureLevel,
|
||||
physicsEnabled: this.physicsEnabled,
|
||||
debug: this.debug
|
||||
};
|
||||
@ -63,9 +46,6 @@ export class GameConfig {
|
||||
const stored = localStorage.getItem('game-config');
|
||||
if (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.debug = config.debug ?? false;
|
||||
} else {
|
||||
@ -80,9 +60,6 @@ export class GameConfig {
|
||||
* Reset to default settings
|
||||
*/
|
||||
public reset(): void {
|
||||
this.planetTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.asteroidTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.sunTextureLevel = TextureLevel.FULL_TEXTURE;
|
||||
this.physicsEnabled = true;
|
||||
this.debug = false;
|
||||
this.save();
|
||||
|
||||
@ -30,6 +30,7 @@ export class Level1 implements Level {
|
||||
this._deserializer = new LevelDeserializer(levelConfig);
|
||||
this._ship = new Ship(audioEngine);
|
||||
|
||||
|
||||
const xr = DefaultScene.XR;
|
||||
|
||||
debugLog('Level1 constructor - Setting up XR observables');
|
||||
@ -45,7 +46,7 @@ export class Level1 implements Level {
|
||||
this._ship.addController(controller);
|
||||
});
|
||||
});
|
||||
this.initialize();
|
||||
// Don't call initialize here - let Main call it after registering the observable
|
||||
}
|
||||
|
||||
getReadyObservable(): Observable<Level> {
|
||||
@ -92,9 +93,10 @@ export class Level1 implements Level {
|
||||
public async initialize() {
|
||||
debugLog('Initializing level from config:', this._levelConfig.difficulty);
|
||||
if (this._initialized) {
|
||||
console.error('Initialize called twice');
|
||||
return;
|
||||
}
|
||||
|
||||
await this._ship.initialize();
|
||||
setLoadingMessage("Loading level from configuration...");
|
||||
|
||||
// Use deserializer to create all entities from config
|
||||
|
||||
@ -2,6 +2,8 @@ import {
|
||||
AbstractMesh,
|
||||
MeshBuilder,
|
||||
Observable,
|
||||
PBRMaterial,
|
||||
Texture,
|
||||
Vector3
|
||||
} from "@babylonjs/core";
|
||||
import { DefaultScene } from "./defaultScene";
|
||||
@ -14,7 +16,8 @@ import {
|
||||
validateLevelConfig
|
||||
} from "./levelConfig";
|
||||
import { GameConfig } from "./gameConfig";
|
||||
import { MaterialFactory } from "./materialFactory";
|
||||
import { FireProceduralTexture } from "@babylonjs/procedural-textures";
|
||||
import { createSphereLightmap } from "./sphereLightmap";
|
||||
import debugLog from './debug';
|
||||
import StarBase from "./starBase";
|
||||
|
||||
@ -71,8 +74,7 @@ export class LevelDeserializer {
|
||||
* Create the start base from config
|
||||
*/
|
||||
private async createStartBase(): Promise<AbstractMesh> {
|
||||
const position = this?.config?.startBase?.position?this.arrayToVector3(this?.config?.startBase?.position):null;
|
||||
return await StarBase.buildStarBase(position);
|
||||
return await StarBase.buildStarBase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,18 +87,13 @@ export class LevelDeserializer {
|
||||
segments: 32
|
||||
}, this.scene);
|
||||
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
|
||||
//const gl = new GlowLayer("glow", this.scene);
|
||||
//gl.intensity = 1;
|
||||
// Create PBR sun material with fire texture
|
||||
const material = new PBRMaterial("sunMaterial", this.scene);
|
||||
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;
|
||||
}
|
||||
@ -122,16 +119,28 @@ export class LevelDeserializer {
|
||||
// Calculate direction from planet to sun
|
||||
const toSun = sunPosition.subtract(planetPosition).normalize();
|
||||
|
||||
// Create material using GameConfig texture level
|
||||
const config = GameConfig.getInstance();
|
||||
const material = MaterialFactory.createPlanetMaterial(
|
||||
planetConfig.name + "-material",
|
||||
planetConfig.texturePath,
|
||||
config.planetTextureLevel,
|
||||
// Create PBR planet material
|
||||
const material = new PBRMaterial(planetConfig.name + "-material", this.scene);
|
||||
const texture = new Texture(planetConfig.texturePath, this.scene);
|
||||
|
||||
// Create lightmap with bright light pointing toward sun
|
||||
const lightmap = createSphereLightmap(
|
||||
planetConfig.name + "-lightmap",
|
||||
256,
|
||||
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;
|
||||
|
||||
planets.push(planet);
|
||||
|
||||
37
src/main.ts
37
src/main.ts
@ -46,20 +46,31 @@ export class Main {
|
||||
setLoadingMessage("This browser does not support WebXR");
|
||||
return;
|
||||
}
|
||||
this.initialize();
|
||||
|
||||
|
||||
// Listen for level selection event
|
||||
window.addEventListener('levelSelected', async (e: CustomEvent) => {
|
||||
this._started = true;
|
||||
await this.initialize();
|
||||
const {levelName, config} = e.detail as {levelName: string, config: LevelConfig};
|
||||
|
||||
debugLog(`Starting level: ${levelName}`);
|
||||
|
||||
// Show loading UI again
|
||||
// Hide all UI elements
|
||||
const mainDiv = document.querySelector('#mainDiv');
|
||||
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) {
|
||||
levelSelect.style.display = 'none';
|
||||
}
|
||||
if (editorLink) {
|
||||
editorLink.style.display = 'none';
|
||||
}
|
||||
if (settingsLink) {
|
||||
settingsLink.style.display = 'none';
|
||||
}
|
||||
setLoadingMessage("Initializing Level...");
|
||||
|
||||
// Unlock audio engine on user interaction
|
||||
@ -80,10 +91,15 @@ export class Main {
|
||||
this.play();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Now initialize the level (after observable is registered)
|
||||
await this._currentLevel.initialize();
|
||||
});
|
||||
|
||||
// Listen for test level button click
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const levelSelect = document.querySelector('#levelSelect');
|
||||
levelSelect.classList.add('ready');
|
||||
debugLog('[Main] DOMContentLoaded fired, looking for test button...');
|
||||
const testLevelBtn = document.querySelector('#testLevelBtn');
|
||||
debugLog('[Main] Test button found:', !!testLevelBtn);
|
||||
@ -92,9 +108,12 @@ export class Main {
|
||||
testLevelBtn.addEventListener('click', async () => {
|
||||
debugLog('[Main] ========== TEST LEVEL BUTTON CLICKED ==========');
|
||||
|
||||
// Show loading UI
|
||||
// Hide all UI elements
|
||||
const mainDiv = document.querySelector('#mainDiv');
|
||||
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] levelSelect exists:', !!levelSelect);
|
||||
|
||||
@ -102,6 +121,12 @@ export class Main {
|
||||
levelSelect.style.display = 'none';
|
||||
debugLog('[Main] levelSelect hidden');
|
||||
}
|
||||
if (editorLink) {
|
||||
editorLink.style.display = 'none';
|
||||
}
|
||||
if (settingsLink) {
|
||||
settingsLink.style.display = 'none';
|
||||
}
|
||||
setLoadingMessage("Initializing Test Scene...");
|
||||
|
||||
// Unlock audio engine on user interaction
|
||||
@ -193,7 +218,6 @@ export class Main {
|
||||
// photoDome1.position = DefaultScene.MainScene.activeCamera.globalPosition;
|
||||
// photoDome2.position = DefaultScene.MainScene.activeCamera.globalPosition;
|
||||
});
|
||||
setLoadingMessage("Select a difficulty to begin!");
|
||||
}
|
||||
|
||||
private async setupScene() {
|
||||
@ -236,12 +260,8 @@ export class Main {
|
||||
window.setTimeout(()=>{
|
||||
if (!this._started) {
|
||||
this._started = true;
|
||||
const levelSelect = document.querySelector('#levelSelect');
|
||||
if (levelSelect) {
|
||||
levelSelect.classList.add('ready');
|
||||
setLoadingMessage("Ready!");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this._engine.runRenderLoop(() => {
|
||||
@ -250,6 +270,7 @@ export class Main {
|
||||
}
|
||||
|
||||
private async setupPhysics() {
|
||||
//DefaultScene.MainScene.useRightHandedSystem = true;
|
||||
const havok = await HavokPhysics();
|
||||
const havokPlugin = new HavokPlugin(true, havok);
|
||||
//DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -55,7 +55,7 @@ export class RockFactory {
|
||||
private static async loadMesh() {
|
||||
debugLog('loading mesh');
|
||||
this._asteroidMesh = (await loadAsset("asteroid.glb")).meshes.get('Asteroid');
|
||||
this._asteroidMesh.setParent(null);
|
||||
//this._asteroidMesh.setParent(null);
|
||||
this._asteroidMesh.setEnabled(false);
|
||||
debugLog(this._asteroidMesh);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { GameConfig, TextureLevel } from "./gameConfig";
|
||||
import { GameConfig } from "./gameConfig";
|
||||
|
||||
/**
|
||||
* Initialize the settings screen
|
||||
@ -7,9 +7,6 @@ export function initializeSettingsScreen(): void {
|
||||
const config = GameConfig.getInstance();
|
||||
|
||||
// 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 debugEnabledCheckbox = document.getElementById('debugEnabled') as HTMLInputElement;
|
||||
|
||||
@ -39,9 +36,6 @@ export function initializeSettingsScreen(): void {
|
||||
* Load current settings into form
|
||||
*/
|
||||
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 (debugEnabledCheckbox) debugEnabledCheckbox.checked = config.debug;
|
||||
}
|
||||
@ -50,9 +44,6 @@ export function initializeSettingsScreen(): void {
|
||||
* Save form settings to GameConfig
|
||||
*/
|
||||
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.debug = debugEnabledCheckbox.checked;
|
||||
config.save();
|
||||
|
||||
151
src/ship.ts
151
src/ship.ts
@ -8,7 +8,6 @@ import {
|
||||
PhysicsAggregate,
|
||||
PhysicsMotionType,
|
||||
PhysicsShapeType,
|
||||
SceneLoader,
|
||||
StandardMaterial,
|
||||
TransformNode,
|
||||
Vector2,
|
||||
@ -23,10 +22,12 @@ import { GameConfig } from "./gameConfig";
|
||||
import { Sight } from "./sight";
|
||||
import debugLog from './debug';
|
||||
import {Scoreboard} from "./scoreboard";
|
||||
import loadAsset from "./utils/loadAsset";
|
||||
import {Debug} from "@babylonjs/core/Legacy/legacy";
|
||||
const MAX_LINEAR_VELOCITY = 200;
|
||||
const MAX_ANGULAR_VELOCITY = 1.8;
|
||||
const LINEAR_FORCE_MULTIPLIER = 1200;
|
||||
const ANGULAR_FORCE_MULTIPLIER = 20;
|
||||
const MAX_ANGULAR_VELOCITY = 1.4;
|
||||
const LINEAR_FORCE_MULTIPLIER = 800;
|
||||
const ANGULAR_FORCE_MULTIPLIER = 15;
|
||||
|
||||
const controllerComponents = [
|
||||
'a-button',
|
||||
@ -69,8 +70,6 @@ export class Ship {
|
||||
|
||||
constructor( audioEngine?: AudioEngineV2) {
|
||||
this._audioEngine = audioEngine;
|
||||
this.setup();
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private async initializeSounds() {
|
||||
@ -102,8 +101,8 @@ export class Ship {
|
||||
this._shot?.play();
|
||||
const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh);
|
||||
ammo.parent = this._ship;
|
||||
ammo.position.y = .5;
|
||||
ammo.position.z = 7.1;
|
||||
ammo.position.y = .1;
|
||||
ammo.position.z = 8.4;
|
||||
//ammo.rotation.x = Math.PI / 2;
|
||||
ammo.setParent(null);
|
||||
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) {
|
||||
this.initializeSounds();
|
||||
await this.initializeSounds();
|
||||
}
|
||||
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
||||
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
|
||||
@ -171,11 +205,11 @@ export class Ship {
|
||||
DefaultScene.MainScene);
|
||||
this._camera.parent = this._ship;
|
||||
|
||||
DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
|
||||
//DefaultScene.MainScene.setActiveCameraByName("Flat Camera");
|
||||
|
||||
// Create sight reticle
|
||||
this._sight = new Sight(DefaultScene.MainScene, this._ship, {
|
||||
position: new Vector3(0, .5, 125),
|
||||
position: new Vector3(0, .1, 125),
|
||||
circleRadius: 2,
|
||||
crosshairLength: 1.5,
|
||||
lineThickness: 0.1,
|
||||
@ -183,78 +217,20 @@ export class Ship {
|
||||
renderingGroupId: 3,
|
||||
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!
|
||||
const worldDirection = Vector3.TransformNormal(localDirection, this._ship.getWorldMatrix());
|
||||
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
|
||||
if (currentAngularSpeed < MAX_ANGULAR_VELOCITY) {
|
||||
const yaw = this._leftStickVector.x;
|
||||
const pitch = -this._rightStickVector.y;
|
||||
const roll = -this._rightStickVector.x;
|
||||
const yaw = -this._leftStickVector.x;
|
||||
const pitch = this._rightStickVector.y;
|
||||
const roll = this._rightStickVector.x;
|
||||
|
||||
// Create torque in local space, then transform to world space
|
||||
const localTorque = new Vector3(pitch, yaw, roll).scale(ANGULAR_FORCE_MULTIPLIER);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {
|
||||
AbstractMesh,
|
||||
HavokPlugin,
|
||||
HavokPlugin, Mesh,
|
||||
PhysicsAggregate,
|
||||
PhysicsMotionType,
|
||||
PhysicsShapeType,
|
||||
@ -17,13 +17,14 @@ import loadAsset from "./utils/loadAsset";
|
||||
* @returns Promise resolving to the loaded star base mesh
|
||||
*/
|
||||
export default class StarBase {
|
||||
public static async buildStarBase(position: Vector3): Promise<AbstractMesh> {
|
||||
public static async buildStarBase(): Promise<AbstractMesh> {
|
||||
const config = GameConfig.getInstance();
|
||||
const scene = DefaultScene.MainScene;
|
||||
const importMeshes = await loadAsset('base.glb');
|
||||
|
||||
const baseMesh = importMeshes.meshes.get('Base');
|
||||
const landingMesh = importMeshes.meshes.get('BaseLandingZone');
|
||||
clearParent(importMeshes.meshes, position);
|
||||
|
||||
|
||||
|
||||
if (config.physicsEnabled) {
|
||||
@ -38,14 +39,14 @@ export default class StarBase {
|
||||
|
||||
const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH);
|
||||
landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
|
||||
console.log(collidedCollidedBody);
|
||||
});
|
||||
/*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
|
||||
|
||||
});*/
|
||||
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(eventdata);
|
||||
})
|
||||
})*/
|
||||
landingAgg.body.setCollisionCallbackEnabled(true);
|
||||
}
|
||||
//importMesh.rootNodes[0].dispose();
|
||||
|
||||
@ -8,7 +8,12 @@ export type 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 map: Map<string, AbstractMesh> = new Map();
|
||||
container.addAllToScene();
|
||||
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);
|
||||
}
|
||||
return {container: container, meshes: map};
|
||||
|
||||
Binary file not shown.
BIN
themes/default/base.blend1
Normal file
BIN
themes/default/base.blend1
Normal file
Binary file not shown.
Binary file not shown.
BIN
themes/default/ship.blend1
Normal file
BIN
themes/default/ship.blend1
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user