Add ambient starfield backdrop
This commit is contained in:
@@ -135,6 +135,7 @@ export class GameViewer {
|
||||
private readonly nodeGroup = new THREE.Group();
|
||||
private readonly stationGroup = new THREE.Group();
|
||||
private readonly shipGroup = new THREE.Group();
|
||||
private readonly ambienceGroup = new THREE.Group();
|
||||
private readonly selectableTargets = new Map<THREE.Object3D, Selectable>();
|
||||
private readonly presentationEntries: PresentationEntry[] = [];
|
||||
private readonly nodeMeshes = new Map<string, THREE.Mesh>();
|
||||
@@ -196,7 +197,8 @@ export class GameViewer {
|
||||
const keyLight = new THREE.DirectionalLight(0xdcecff, 1.3);
|
||||
keyLight.position.set(1000, 1200, 800);
|
||||
this.scene.add(keyLight);
|
||||
this.scene.add(this.systemGroup, this.nodeGroup, this.stationGroup, this.shipGroup);
|
||||
this.initializeAmbience();
|
||||
this.scene.add(this.ambienceGroup, this.systemGroup, this.nodeGroup, this.stationGroup, this.shipGroup);
|
||||
|
||||
const hud = document.createElement("div");
|
||||
hud.className = "viewer-shell";
|
||||
@@ -664,6 +666,7 @@ export class GameViewer {
|
||||
const frameStartedAtMs = performance.now();
|
||||
const delta = Math.min(this.clock.getDelta(), 0.033);
|
||||
this.updateCamera(delta);
|
||||
this.updateAmbience(delta);
|
||||
this.updatePlanetPresentation();
|
||||
this.updateShipPresentation();
|
||||
this.updateNetworkPanel();
|
||||
@@ -673,6 +676,12 @@ export class GameViewer {
|
||||
this.updatePerformancePanel();
|
||||
}
|
||||
|
||||
private updateAmbience(delta: number) {
|
||||
this.ambienceGroup.position.copy(this.camera.position);
|
||||
this.ambienceGroup.rotation.y += delta * 0.005;
|
||||
this.ambienceGroup.rotation.x = Math.sin(performance.now() * 0.00003) * 0.015;
|
||||
}
|
||||
|
||||
private updateCamera(delta: number) {
|
||||
this.currentDistance = THREE.MathUtils.damp(this.currentDistance, this.desiredDistance, 7.5, delta);
|
||||
this.zoomLevel = this.classifyZoomLevel(this.currentDistance);
|
||||
@@ -739,10 +748,7 @@ export class GameViewer {
|
||||
this.setObjectOpacity(summaryVisual.sprite, blend.universeWeight);
|
||||
}
|
||||
|
||||
this.scene.fog = new THREE.FogExp2(
|
||||
0x040912,
|
||||
THREE.MathUtils.lerp(0.00011, 0.000012, blend.universeWeight),
|
||||
);
|
||||
this.scene.fog = new THREE.FogExp2(0x040912, 0.000035);
|
||||
}
|
||||
|
||||
private recordDeltaStats(delta: WorldDelta, rawBytes: number) {
|
||||
@@ -1017,6 +1023,122 @@ export class GameViewer {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private initializeAmbience() {
|
||||
this.ambienceGroup.renderOrder = -10;
|
||||
this.ambienceGroup.add(this.createBackdropStars());
|
||||
this.ambienceGroup.add(...this.createNebulaClouds());
|
||||
}
|
||||
|
||||
private createBackdropStars() {
|
||||
const starCount = 1800;
|
||||
const radius = 36000;
|
||||
const positions = new Float32Array(starCount * 3);
|
||||
const colors = new Float32Array(starCount * 3);
|
||||
const color = new THREE.Color();
|
||||
|
||||
for (let index = 0; index < starCount; index += 1) {
|
||||
const direction = new THREE.Vector3(
|
||||
THREE.MathUtils.randFloatSpread(2),
|
||||
THREE.MathUtils.randFloatSpread(2),
|
||||
THREE.MathUtils.randFloatSpread(2),
|
||||
).normalize().multiplyScalar(radius * THREE.MathUtils.randFloat(0.82, 1));
|
||||
positions[index * 3] = direction.x;
|
||||
positions[index * 3 + 1] = direction.y;
|
||||
positions[index * 3 + 2] = direction.z;
|
||||
|
||||
const tint = THREE.MathUtils.randFloat(0, 1);
|
||||
color.setRGB(
|
||||
THREE.MathUtils.lerp(0.68, 1, tint),
|
||||
THREE.MathUtils.lerp(0.76, 0.94, tint),
|
||||
THREE.MathUtils.lerp(0.9, 1, tint),
|
||||
);
|
||||
if (Math.random() < 0.08) {
|
||||
color.lerp(new THREE.Color(0xffd6a0), 0.45);
|
||||
}
|
||||
colors[index * 3] = color.r;
|
||||
colors[index * 3 + 1] = color.g;
|
||||
colors[index * 3 + 2] = color.b;
|
||||
}
|
||||
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
return new THREE.Points(
|
||||
geometry,
|
||||
new THREE.PointsMaterial({
|
||||
size: 2.2,
|
||||
sizeAttenuation: false,
|
||||
vertexColors: true,
|
||||
transparent: true,
|
||||
opacity: 0.9,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private createNebulaClouds() {
|
||||
const texture = this.createNebulaTexture();
|
||||
const directions = [
|
||||
new THREE.Vector3(0.74, 0.34, -0.58),
|
||||
new THREE.Vector3(-0.62, 0.18, -0.77),
|
||||
new THREE.Vector3(0.22, -0.44, -0.87),
|
||||
new THREE.Vector3(-0.38, 0.56, 0.73),
|
||||
];
|
||||
|
||||
return directions.map((direction, index) => {
|
||||
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.14,
|
||||
depthWrite: false,
|
||||
color: ["#6dc7ff", "#ff9ec8", "#8e7dff", "#7ce0c3"][index] ?? "#6dc7ff",
|
||||
blending: THREE.AdditiveBlending,
|
||||
}));
|
||||
sprite.position.copy(direction.normalize().multiplyScalar(25000 + index * 2600));
|
||||
const scale = 15000 + index * 2400;
|
||||
sprite.scale.set(scale, scale * 0.62, 1);
|
||||
return sprite;
|
||||
});
|
||||
}
|
||||
|
||||
private createNebulaTexture() {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
throw new Error("Unable to create nebula texture");
|
||||
}
|
||||
|
||||
const gradient = context.createRadialGradient(128, 128, 18, 128, 128, 118);
|
||||
gradient.addColorStop(0, "rgba(255,255,255,0.95)");
|
||||
gradient.addColorStop(0.2, "rgba(255,255,255,0.48)");
|
||||
gradient.addColorStop(0.55, "rgba(140,180,255,0.14)");
|
||||
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
||||
context.fillStyle = gradient;
|
||||
context.fillRect(0, 0, 256, 256);
|
||||
|
||||
for (let index = 0; index < 10; index += 1) {
|
||||
const x = THREE.MathUtils.randFloat(30, 226);
|
||||
const y = THREE.MathUtils.randFloat(30, 226);
|
||||
const radius = THREE.MathUtils.randFloat(24, 72);
|
||||
const puff = context.createRadialGradient(x, y, 0, x, y, radius);
|
||||
puff.addColorStop(0, "rgba(255,255,255,0.16)");
|
||||
puff.addColorStop(0.45, "rgba(255,255,255,0.08)");
|
||||
puff.addColorStop(1, "rgba(0,0,0,0)");
|
||||
context.fillStyle = puff;
|
||||
context.beginPath();
|
||||
context.arc(x, y, radius, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
}
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
}
|
||||
|
||||
private createTacticalIcon(color: string, size: number) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 64;
|
||||
|
||||
Reference in New Issue
Block a user