Add background starfield and fix scene background color
Some checks failed
Build / build (push) Failing after 19s

Created BackgroundStars class using PointCloudSystem:
- 5000 stars distributed uniformly on sphere surface
- Multiple star colors (white, warm, cool, yellowish, bluish)
- Varied brightness (0.3-1.0) for depth perception
- Follows camera position to maintain infinite distance effect
- Efficient rendering with disabled lighting and depth write

Integrated starfield into Level1:
- Created during level initialization
- Camera follow in render loop
- Proper disposal on level cleanup

Fixed XR background color:
- Set scene clearColor to pure black (was default grey)
- Adjusted ambientColor to black for space environment

Removed GlowLayer from ship and engines:
- Cleaned up unused glow effects
- Prevents unwanted glow on background stars

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-10-30 09:37:30 -05:00
parent db970ecc8a
commit 9df64b7dd9
5 changed files with 185 additions and 9 deletions

155
src/backgroundStars.ts Normal file
View File

@ -0,0 +1,155 @@
import {Color3, Color4, PointsCloudSystem, Scene, Vector3} from "@babylonjs/core";
/**
* Configuration options for background stars
*/
export interface BackgroundStarsConfig {
/** Number of stars to generate */
count?: number;
/** Radius of the sphere containing the stars */
radius?: number;
/** Minimum star brightness (0-1) */
minBrightness?: number;
/** Maximum star brightness (0-1) */
maxBrightness?: number;
/** Star point size */
pointSize?: number;
/** Star colors (will be randomly selected) */
colors?: Color4[];
}
/**
* Generates a spherical field of background stars using PointCloudSystem
*/
export class BackgroundStars {
private pcs: PointsCloudSystem;
private scene: Scene;
private config: Required<BackgroundStarsConfig>;
// Default configuration
private static readonly DEFAULT_CONFIG: Required<BackgroundStarsConfig> = {
count: 5000,
radius: 5000,
minBrightness: 0.3,
maxBrightness: 1.0,
pointSize: .1,
colors: [
new Color4(1, 1, 1, 1), // White
new Color4(1, 0.95, 0.9, 1), // Warm white
new Color4(0.9, 0.95, 1, 1), // Cool white
new Color4(1, 0.9, 0.8, 1), // Yellowish
new Color4(0.8, 0.9, 1, 1) // Bluish
]
};
constructor(scene: Scene, config?: BackgroundStarsConfig) {
this.scene = scene;
this.config = { ...BackgroundStars.DEFAULT_CONFIG, ...config };
this.createStarfield();
}
/**
* Create the starfield using PointCloudSystem
*/
private createStarfield(): void {
// Create point cloud system
this.pcs = new PointsCloudSystem("backgroundStars", this.config.pointSize, this.scene);
// Function to set position and color for each particle
const initParticle = (particle: any) => {
// Generate random position on sphere surface with some depth variation
const theta = Math.random() * Math.PI * 2; // Azimuth angle (0 to 2π)
const phi = Math.acos(2 * Math.random() - 1); // Polar angle (0 to π) - uniform distribution
// Add some randomness to radius for depth
const radiusVariation = this.config.radius * (0.8 + Math.random() * 0.2);
// Convert spherical coordinates to Cartesian
particle.position = new Vector3(
radiusVariation * Math.sin(phi) * Math.cos(theta),
radiusVariation * Math.sin(phi) * Math.sin(theta),
radiusVariation * Math.cos(phi)
);
// Random brightness
const brightness = this.config.minBrightness +
Math.random() * (this.config.maxBrightness - this.config.minBrightness);
// Random color from palette
const baseColor = this.config.colors[Math.floor(Math.random() * this.config.colors.length)];
// Apply brightness to color
particle.color = new Color4(
baseColor.r * brightness,
baseColor.g * brightness,
baseColor.b * brightness,
1
);
};
// Add particles to the system
this.pcs.addPoints(this.config.count, initParticle);
// Build the mesh
this.pcs.buildMeshAsync().then(() => {
const mesh = this.pcs.mesh;
if (mesh) {
// Stars should not receive lighting
mesh.material.disableLighting = true;
mesh.material.emissiveColor = new Color3(1,1,1);
// Disable depth write so stars don't occlude other objects
mesh.material.disableDepthWrite = true;
// Stars should be in the background
mesh.renderingGroupId = 0;
// Make stars always render behind everything else
mesh.isPickable = false;
console.log(`Created ${this.config.count} background stars`);
}
});
}
/**
* Update star positions to follow camera (keeps stars at infinite distance)
*/
public followCamera(cameraPosition: Vector3): void {
if (this.pcs.mesh) {
this.pcs.mesh.position = cameraPosition;
}
}
/**
* Dispose of the starfield
*/
public dispose(): void {
if (this.pcs) {
this.pcs.dispose();
}
}
/**
* Get the point cloud system
*/
public getPointCloudSystem(): PointsCloudSystem {
return this.pcs;
}
/**
* Get the mesh
*/
public getMesh() {
return this.pcs?.mesh;
}
/**
* Set the visibility of the stars
*/
public setVisible(visible: boolean): void {
if (this.pcs?.mesh) {
this.pcs.mesh.isVisible = visible;
}
}
}

View File

@ -20,6 +20,7 @@ import {Scoreboard} from "./scoreboard";
import setLoadingMessage from "./setLoadingMessage";
import {LevelConfig} from "./levelConfig";
import {LevelDeserializer} from "./levelDeserializer";
import {BackgroundStars} from "./backgroundStars";
export class Level1 implements Level {
private _ship: Ship;
@ -31,6 +32,7 @@ export class Level1 implements Level {
private _levelConfig: LevelConfig;
private _audioEngine: AudioEngineV2;
private _deserializer: LevelDeserializer;
private _backgroundStars: BackgroundStars;
constructor(levelConfig: LevelConfig, audioEngine: AudioEngineV2) {
this._levelConfig = levelConfig;
@ -94,6 +96,9 @@ export class Level1 implements Level {
public dispose() {
this._startBase.dispose();
this._endBase.dispose();
if (this._backgroundStars) {
this._backgroundStars.dispose();
}
}
public async initialize() {
console.log('Initializing level from config:', this._levelConfig.difficulty);
@ -132,6 +137,23 @@ export class Level1 implements Level {
}
}
// Create background starfield
setLoadingMessage("Creating starfield...");
this._backgroundStars = new BackgroundStars(DefaultScene.MainScene, {
count: 5000,
radius: 5000,
minBrightness: 0.3,
maxBrightness: 1.0,
pointSize: 2
});
// Set up camera follow for stars (keeps stars at infinite distance)
DefaultScene.MainScene.onBeforeRenderObservable.add(() => {
if (this._backgroundStars && DefaultScene.XR.baseExperience.camera) {
this._backgroundStars.followCamera(DefaultScene.XR.baseExperience.camera.position);
}
});
this._initialized = true;
// Notify that initialization is complete

View File

@ -134,7 +134,9 @@ export class Main {
}
DefaultScene.DemoScene = new Scene(this._engine);
DefaultScene.MainScene = new Scene(this._engine);
DefaultScene.MainScene.ambientColor = new Color3(.5, .5, .5);
DefaultScene.MainScene.ambientColor = new Color3(0,0,0);
DefaultScene.MainScene.clearColor = new Color3(0, 0, 0).toColor4();
setLoadingMessage("Initializing Physics Engine..");
await this.setupPhysics();
@ -170,7 +172,7 @@ export class Main {
private async setupPhysics() {
const havok = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havok);
DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);
//DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1);
const light = new DirectionalLight("dirLight", new Vector3(-1, -2, -1), DefaultScene.MainScene);
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
DefaultScene.MainScene.getPhysicsEngine().setTimeStep(1/30);

View File

@ -58,7 +58,6 @@ export class Ship {
private _ammoMaterial: StandardMaterial;
private _forwardNode: TransformNode;
private _rotationNode: TransformNode;
private _glowLayer: GlowLayer;
private _primaryThrustVectorSound: StaticSound;
private _secondaryThrustVectorSound: StaticSound;
private _shot: StaticSound;
@ -147,8 +146,7 @@ export class Ship {
}
private setup() {
this._ship = new TransformNode("ship", DefaultScene.MainScene);
this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene);
this._glowLayer.intensity = 1;
// Create sounds asynchronously if audio engine is available
if (this._audioEngine) {

View File

@ -17,15 +17,14 @@ export class ShipEngine {
private _ship: TransformNode;
private _leftMainEngine: MainEngine;
private _rightMainEngine: MainEngine;
private _gl: GlowLayer;
constructor(ship: TransformNode) {
this._ship = ship;
this.initialize();
}
private initialize() {
this._gl = new GlowLayer("glow", DefaultScene.MainScene);
this._gl.intensity =1;
this._leftMainEngine = this.createEngine(new Vector3(-.44, .37, -1.1));
this._rightMainEngine = this.createEngine(new Vector3(.44, .37, -1.1));
}
@ -52,7 +51,7 @@ export class ShipEngine {
engine.parent = this._ship;
engine.position = position;
const leftDisc = MeshBuilder.CreateIcoSphere("engineSphere", {radius: .07}, DefaultScene.MainScene);
this._gl.addIncludedOnlyMesh(leftDisc);
const material = new StandardMaterial("material", DefaultScene.MainScene);
material.emissiveColor = new Color3(.5, .5, .1);
leftDisc.material = material;