Add background starfield and fix scene background color
Some checks failed
Build / build (push) Failing after 19s
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:
parent
db970ecc8a
commit
9df64b7dd9
155
src/backgroundStars.ts
Normal file
155
src/backgroundStars.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user