646 lines
18 KiB
JavaScript
646 lines
18 KiB
JavaScript
import * as THREE from "three";
|
|
import * as Astronomy from "astronomy-engine";
|
|
|
|
import {
|
|
CELESTIAL_CONFIG,
|
|
EARTH_CONFIG,
|
|
SCENE_LIGHT_CONFIG,
|
|
} from "./constants.js";
|
|
import { latLonToVector3 } from "./utils.js";
|
|
|
|
const textureLoader = new THREE.TextureLoader();
|
|
const defaultSunDirection = new THREE.Vector3(1, 0.2, 0.4).normalize();
|
|
const defaultMoonDirection = new THREE.Vector3(-0.6, 0.45, -0.2).normalize();
|
|
|
|
let celestialRoot = null;
|
|
let skySphere = null;
|
|
let brightStarsGroup = null;
|
|
let sunSprite = null;
|
|
let moonSprite = null;
|
|
let sunHaloSprite = null;
|
|
let moonHaloSprite = null;
|
|
let brightStarTexture = null;
|
|
let sunDirection = defaultSunDirection.clone();
|
|
let moonDirection = defaultMoonDirection.clone();
|
|
let lastUpdatedAt = 0;
|
|
let linkedSunLight = null;
|
|
let linkedBackLight = null;
|
|
let linkedAmbientLight = null;
|
|
let linkedPointLight = null;
|
|
let linkedCamera = null;
|
|
let linkedEarth = null;
|
|
let dayNightLightingEnabled = true;
|
|
let brightStarSprites = [];
|
|
let celestialRotationQuaternion = new THREE.Quaternion();
|
|
let celestialViewQuaternion = new THREE.Quaternion();
|
|
let runtimeOrientationEuler = {
|
|
...CELESTIAL_CONFIG.orientationEulerRad,
|
|
};
|
|
let runtimeFollowConfig = {
|
|
...CELESTIAL_CONFIG.followEarthRotation,
|
|
};
|
|
const scratchEuler = new THREE.Euler(0, 0, 0, "YXZ");
|
|
|
|
function normalizeDegrees180(value) {
|
|
let normalized = value;
|
|
while (normalized <= -180) normalized += 360;
|
|
while (normalized > 180) normalized -= 360;
|
|
return normalized;
|
|
}
|
|
|
|
function computeGreenwichMeanSiderealDegrees(date) {
|
|
const jd = date.getTime() / 86400000 + 2440587.5;
|
|
const t = (jd - 2451545.0) / 36525.0;
|
|
const gmst =
|
|
280.46061837 +
|
|
360.98564736629 * (jd - 2451545.0) +
|
|
0.000387933 * t * t -
|
|
(t * t * t) / 38710000;
|
|
return THREE.MathUtils.euclideanModulo(gmst, 360);
|
|
}
|
|
|
|
function computeSubsolarLocalDirection(date) {
|
|
const vector = Astronomy.GeoVector(Astronomy.Body.Sun, date, false);
|
|
const radius = Math.sqrt(
|
|
vector.x * vector.x +
|
|
vector.y * vector.y +
|
|
vector.z * vector.z,
|
|
);
|
|
if (!Number.isFinite(radius) || radius === 0) {
|
|
return defaultSunDirection.clone();
|
|
}
|
|
|
|
const rightAscensionDeg = THREE.MathUtils.radToDeg(
|
|
Math.atan2(vector.y, vector.x),
|
|
);
|
|
const declinationDeg = THREE.MathUtils.radToDeg(
|
|
Math.asin(THREE.MathUtils.clamp(vector.z / radius, -1, 1)),
|
|
);
|
|
const gmstDeg = computeGreenwichMeanSiderealDegrees(date);
|
|
const subsolarLonDeg = normalizeDegrees180(rightAscensionDeg - gmstDeg);
|
|
|
|
return latLonToVector3(declinationDeg, subsolarLonDeg, 1).normalize();
|
|
}
|
|
|
|
function getPhysicalSunDirection(date = new Date()) {
|
|
const localSunDirection = computeSubsolarLocalDirection(date);
|
|
if (!linkedEarth) {
|
|
return localSunDirection;
|
|
}
|
|
|
|
return localSunDirection
|
|
.clone()
|
|
.applyQuaternion(linkedEarth.quaternion)
|
|
.normalize();
|
|
}
|
|
|
|
function getCelestialEuler() {
|
|
const { x, y, z } = runtimeOrientationEuler;
|
|
return new THREE.Euler(x, y, z, "YXZ");
|
|
}
|
|
|
|
function refreshCelestialOrientation() {
|
|
celestialRotationQuaternion.setFromEuler(getCelestialEuler());
|
|
refreshCelestialView();
|
|
}
|
|
|
|
function refreshCelestialView() {
|
|
if (linkedEarth && runtimeFollowConfig.enabled) {
|
|
const followX = runtimeFollowConfig.x
|
|
? (linkedEarth.rotation.x - EARTH_CONFIG.tiltRad) * (runtimeFollowConfig.invertX ? -1 : 1)
|
|
: 0;
|
|
const followY = runtimeFollowConfig.y
|
|
? linkedEarth.rotation.y * (runtimeFollowConfig.invertY ? -1 : 1)
|
|
: 0;
|
|
const followZ = runtimeFollowConfig.z
|
|
? linkedEarth.rotation.z * (runtimeFollowConfig.invertZ ? -1 : 1)
|
|
: 0;
|
|
|
|
scratchEuler.set(
|
|
followX,
|
|
followY,
|
|
followZ,
|
|
"YXZ",
|
|
);
|
|
celestialViewQuaternion.setFromEuler(scratchEuler);
|
|
} else {
|
|
celestialViewQuaternion.identity();
|
|
}
|
|
|
|
if (celestialRoot) {
|
|
celestialRoot.quaternion
|
|
.copy(celestialRotationQuaternion)
|
|
.multiply(celestialViewQuaternion);
|
|
}
|
|
}
|
|
|
|
function applyCelestialOrientation(direction) {
|
|
return direction
|
|
.clone()
|
|
.applyQuaternion(celestialRotationQuaternion)
|
|
.applyQuaternion(celestialViewQuaternion)
|
|
.normalize();
|
|
}
|
|
|
|
function configureTextureEncoding(texture) {
|
|
if (!texture) return;
|
|
if ("colorSpace" in texture && THREE.SRGBColorSpace) {
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
} else if ("encoding" in texture && THREE.sRGBEncoding) {
|
|
texture.encoding = THREE.sRGBEncoding;
|
|
}
|
|
}
|
|
|
|
function createDiscTexture(stops, size = 256) {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
const ctx = canvas.getContext("2d");
|
|
const gradient = ctx.createRadialGradient(
|
|
size / 2,
|
|
size / 2,
|
|
0,
|
|
size / 2,
|
|
size / 2,
|
|
size / 2,
|
|
);
|
|
|
|
stops.forEach(([offset, color]) => gradient.addColorStop(offset, color));
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, size, size);
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
configureTextureEncoding(texture);
|
|
texture.needsUpdate = true;
|
|
return texture;
|
|
}
|
|
|
|
function createSprite({ texture, scale, opacity = 1, name, color = 0xffffff }) {
|
|
const material = new THREE.SpriteMaterial({
|
|
map: texture,
|
|
transparent: true,
|
|
opacity,
|
|
depthWrite: false,
|
|
depthTest: true,
|
|
toneMapped: false,
|
|
color,
|
|
});
|
|
const sprite = new THREE.Sprite(material);
|
|
sprite.name = name;
|
|
sprite.scale.setScalar(scale);
|
|
sprite.renderOrder = 100;
|
|
return sprite;
|
|
}
|
|
|
|
function createSkySphere() {
|
|
const geometry = new THREE.SphereGeometry(
|
|
CELESTIAL_CONFIG.skyRadius,
|
|
64,
|
|
64,
|
|
);
|
|
const material = new THREE.MeshBasicMaterial({
|
|
color: 0xffffff,
|
|
side: THREE.BackSide,
|
|
transparent: CELESTIAL_CONFIG.skyOpacity < 1,
|
|
opacity: CELESTIAL_CONFIG.skyOpacity,
|
|
depthWrite: false,
|
|
depthTest: false,
|
|
fog: false,
|
|
});
|
|
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
mesh.name = "celestial-sky-sphere";
|
|
mesh.renderOrder = -1000;
|
|
mesh.raycast = () => {};
|
|
|
|
textureLoader.load(
|
|
CELESTIAL_CONFIG.starMapUrl,
|
|
(texture) => {
|
|
configureTextureEncoding(texture);
|
|
material.map = texture;
|
|
material.needsUpdate = true;
|
|
},
|
|
undefined,
|
|
() => {
|
|
console.warn("Failed to load celestial star map texture");
|
|
},
|
|
);
|
|
|
|
return mesh;
|
|
}
|
|
|
|
function geoVectorToWorldDirection(vector) {
|
|
return new THREE.Vector3(vector.x, vector.z, vector.y).normalize();
|
|
}
|
|
|
|
function raDecToWorldDirection(raDeg, decDeg) {
|
|
const raRad = THREE.MathUtils.degToRad(raDeg);
|
|
const decRad = THREE.MathUtils.degToRad(decDeg);
|
|
|
|
const x = Math.cos(decRad) * Math.cos(raRad);
|
|
const y = Math.sin(decRad);
|
|
const z = Math.cos(decRad) * Math.sin(raRad);
|
|
|
|
return new THREE.Vector3(x, z, y).normalize();
|
|
}
|
|
|
|
function colorFromBvIndex(colorIndex) {
|
|
if (colorIndex <= -0.2) return 0xa8c9ff;
|
|
if (colorIndex <= 0.0) return 0xc8dcff;
|
|
if (colorIndex <= 0.3) return 0xf4f7ff;
|
|
if (colorIndex <= 0.7) return 0xfff4dc;
|
|
if (colorIndex <= 1.1) return 0xffdfb0;
|
|
if (colorIndex <= 1.5) return 0xffc47f;
|
|
return 0xffa35e;
|
|
}
|
|
|
|
function scaleFromMagnitude(mag) {
|
|
const brightness = THREE.MathUtils.clamp(
|
|
1 - (mag - (-1.5)) / (CELESTIAL_CONFIG.brightStarMinMag - (-1.5)),
|
|
0,
|
|
1,
|
|
);
|
|
return THREE.MathUtils.lerp(
|
|
CELESTIAL_CONFIG.brightStarMinScale,
|
|
CELESTIAL_CONFIG.brightStarMaxScale,
|
|
Math.pow(brightness, 0.72),
|
|
);
|
|
}
|
|
|
|
function createBrightStarTexture() {
|
|
return createDiscTexture([
|
|
[0, "rgba(255,255,255,1)"],
|
|
[0.18, "rgba(255,255,255,0.98)"],
|
|
[0.46, "rgba(215,230,255,0.42)"],
|
|
[1, "rgba(128,168,255,0)"],
|
|
], 128);
|
|
}
|
|
|
|
function createBrightStarsGroup() {
|
|
const group = new THREE.Group();
|
|
group.name = "bright-stars-group";
|
|
group.visible = true;
|
|
return group;
|
|
}
|
|
|
|
async function loadBrightStars() {
|
|
try {
|
|
const response = await fetch(CELESTIAL_CONFIG.brightStarsUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
|
|
const stars = await response.json();
|
|
const filteredStars = stars
|
|
.filter((star) => Number.isFinite(star?.raDeg) && Number.isFinite(star?.decDeg))
|
|
.filter((star) => star.mag <= CELESTIAL_CONFIG.brightStarMinMag)
|
|
.sort((a, b) => a.mag - b.mag)
|
|
.slice(0, CELESTIAL_CONFIG.brightStarMaxCount);
|
|
|
|
brightStarTexture = createBrightStarTexture();
|
|
brightStarSprites = filteredStars.map((star) => {
|
|
const sprite = createSprite({
|
|
texture: brightStarTexture,
|
|
scale: scaleFromMagnitude(star.mag),
|
|
opacity: CELESTIAL_CONFIG.brightStarOpacity,
|
|
name: `bright-star-${star.name}`,
|
|
color: colorFromBvIndex(star.colorIndex ?? 0.4),
|
|
});
|
|
sprite.position
|
|
.copy(raDecToWorldDirection(star.raDeg, star.decDeg))
|
|
.multiplyScalar(CELESTIAL_CONFIG.brightStarDistance);
|
|
sprite.userData.star = star;
|
|
return sprite;
|
|
});
|
|
|
|
brightStarSprites.forEach((sprite) => brightStarsGroup?.add(sprite));
|
|
} catch (error) {
|
|
console.warn("Failed to load bright star layer", error);
|
|
}
|
|
}
|
|
|
|
function computeBodyDirection(body, date) {
|
|
const vector = Astronomy.GeoVector(body, date, false);
|
|
return geoVectorToWorldDirection(vector);
|
|
}
|
|
|
|
function updateSpritePositions() {
|
|
if (sunSprite) {
|
|
sunSprite.position.copy(sunDirection).multiplyScalar(CELESTIAL_CONFIG.sunDistance);
|
|
}
|
|
if (sunHaloSprite) {
|
|
sunHaloSprite.position.copy(sunDirection).multiplyScalar(CELESTIAL_CONFIG.sunDistance);
|
|
}
|
|
|
|
if (moonSprite) {
|
|
moonSprite.position.copy(moonDirection).multiplyScalar(CELESTIAL_CONFIG.moonDistance);
|
|
}
|
|
if (moonHaloSprite) {
|
|
moonHaloSprite.position.copy(moonDirection).multiplyScalar(CELESTIAL_CONFIG.moonDistance);
|
|
}
|
|
}
|
|
|
|
function updateLighting() {
|
|
if (!dayNightLightingEnabled && CELESTIAL_CONFIG.inspectionLighting?.enabled) {
|
|
const inspection = CELESTIAL_CONFIG.inspectionLighting;
|
|
const cameraDirection = linkedCamera
|
|
? linkedCamera.position.clone().normalize()
|
|
: defaultSunDirection.clone();
|
|
const worldUp = new THREE.Vector3(0, 1, 0);
|
|
const right = new THREE.Vector3().crossVectors(worldUp, cameraDirection);
|
|
if (right.lengthSq() < 1e-6) {
|
|
right.set(1, 0, 0);
|
|
} else {
|
|
right.normalize();
|
|
}
|
|
const adjustedUp = new THREE.Vector3()
|
|
.crossVectors(cameraDirection, right)
|
|
.normalize();
|
|
|
|
const resolveInspectionDirection = (offset) =>
|
|
cameraDirection
|
|
.clone()
|
|
.multiplyScalar(offset.z)
|
|
.add(right.clone().multiplyScalar(offset.x))
|
|
.add(adjustedUp.clone().multiplyScalar(offset.y))
|
|
.normalize();
|
|
|
|
if (linkedAmbientLight) {
|
|
linkedAmbientLight.color.setHex(inspection.ambientColor);
|
|
linkedAmbientLight.intensity = inspection.ambientIntensity;
|
|
}
|
|
|
|
if (linkedSunLight) {
|
|
linkedSunLight.color.setHex(inspection.keyLightColor);
|
|
linkedSunLight.intensity = inspection.keyLightIntensity;
|
|
linkedSunLight.position
|
|
.copy(resolveInspectionDirection(inspection.keyLightOffset))
|
|
.multiplyScalar(inspection.keyLightDistance);
|
|
}
|
|
|
|
if (linkedBackLight) {
|
|
linkedBackLight.color.setHex(inspection.backLightColor);
|
|
linkedBackLight.intensity = inspection.backLightIntensity;
|
|
linkedBackLight.position
|
|
.copy(resolveInspectionDirection(inspection.backLightOffset))
|
|
.multiplyScalar(inspection.backLightDistance);
|
|
}
|
|
|
|
if (linkedPointLight) {
|
|
linkedPointLight.color.setHex(inspection.pointLightColor);
|
|
linkedPointLight.intensity = inspection.pointLightIntensity;
|
|
linkedPointLight.position
|
|
.copy(resolveInspectionDirection(inspection.pointLightOffset))
|
|
.multiplyScalar(inspection.pointLightDistance);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const physicalSunDirection = getPhysicalSunDirection(
|
|
new Date(lastUpdatedAt || Date.now()),
|
|
);
|
|
|
|
if (linkedAmbientLight) {
|
|
linkedAmbientLight.color.setHex(SCENE_LIGHT_CONFIG.ambient.color);
|
|
linkedAmbientLight.intensity = SCENE_LIGHT_CONFIG.ambient.intensity;
|
|
}
|
|
|
|
if (linkedSunLight) {
|
|
linkedSunLight.color.setHex(CELESTIAL_CONFIG.sunLightColor);
|
|
linkedSunLight.intensity = CELESTIAL_CONFIG.sunLightIntensity;
|
|
linkedSunLight.position
|
|
.copy(physicalSunDirection)
|
|
.multiplyScalar(CELESTIAL_CONFIG.sunLightDistance);
|
|
}
|
|
|
|
if (linkedBackLight) {
|
|
linkedBackLight.color.setHex(CELESTIAL_CONFIG.backLightColor);
|
|
linkedBackLight.intensity = CELESTIAL_CONFIG.backLightIntensity;
|
|
linkedBackLight.position
|
|
.copy(physicalSunDirection)
|
|
.multiplyScalar(-CELESTIAL_CONFIG.sunLightDistance * 0.7);
|
|
}
|
|
|
|
if (linkedPointLight) {
|
|
linkedPointLight.color.setHex(SCENE_LIGHT_CONFIG.point.color);
|
|
linkedPointLight.intensity = SCENE_LIGHT_CONFIG.point.intensity;
|
|
linkedPointLight.position.set(
|
|
SCENE_LIGHT_CONFIG.point.position.x,
|
|
SCENE_LIGHT_CONFIG.point.position.y,
|
|
SCENE_LIGHT_CONFIG.point.position.z,
|
|
);
|
|
}
|
|
}
|
|
|
|
function computeCelestialState(date = new Date()) {
|
|
sunDirection = computeBodyDirection(Astronomy.Body.Sun, date);
|
|
moonDirection = computeBodyDirection(Astronomy.Body.Moon, date);
|
|
updateSpritePositions();
|
|
updateLighting();
|
|
lastUpdatedAt = date.getTime();
|
|
}
|
|
|
|
export function initCelestialLayer(
|
|
scene,
|
|
{
|
|
camera = null,
|
|
sunLight = null,
|
|
backLight = null,
|
|
ambientLight = null,
|
|
pointLight = null,
|
|
earth = null,
|
|
} = {},
|
|
) {
|
|
if (!scene || !CELESTIAL_CONFIG.enabled) return null;
|
|
|
|
disposeCelestialLayer();
|
|
|
|
linkedCamera = camera;
|
|
linkedSunLight = sunLight;
|
|
linkedBackLight = backLight;
|
|
linkedAmbientLight = ambientLight;
|
|
linkedPointLight = pointLight;
|
|
linkedEarth = earth;
|
|
|
|
celestialRoot = new THREE.Group();
|
|
celestialRoot.name = "celestial-root";
|
|
celestialRoot.position.set(0, 0, 0);
|
|
|
|
refreshCelestialOrientation();
|
|
|
|
skySphere = createSkySphere();
|
|
brightStarsGroup = createBrightStarsGroup();
|
|
|
|
const sunTexture = createDiscTexture([
|
|
[0, "rgba(255,255,255,1)"],
|
|
[0.08, "rgba(255,251,242,1)"],
|
|
[0.22, "rgba(255,242,205,0.98)"],
|
|
[0.52, "rgba(255,211,117,0.9)"],
|
|
[0.82, "rgba(255,162,54,0.18)"],
|
|
[1, "rgba(255,120,32,0)"],
|
|
]);
|
|
const sunHaloTexture = createDiscTexture([
|
|
[0, "rgba(255,245,214,0.9)"],
|
|
[0.2, "rgba(255,220,154,0.54)"],
|
|
[0.52, "rgba(255,160,72,0.16)"],
|
|
[1, "rgba(255,120,32,0)"],
|
|
], 512);
|
|
const moonTexture = createDiscTexture([
|
|
[0, "rgba(255,255,255,0.98)"],
|
|
[0.42, "rgba(229,236,248,0.92)"],
|
|
[0.76, "rgba(164,178,202,0.34)"],
|
|
[1, "rgba(80,92,118,0)"],
|
|
]);
|
|
const moonHaloTexture = createDiscTexture([
|
|
[0, "rgba(226,235,250,0.42)"],
|
|
[0.38, "rgba(188,203,230,0.16)"],
|
|
[1, "rgba(120,136,170,0)"],
|
|
], 384);
|
|
|
|
sunSprite = createSprite({
|
|
texture: sunTexture,
|
|
scale: CELESTIAL_CONFIG.sunScale,
|
|
name: "sun-sprite",
|
|
});
|
|
sunHaloSprite = createSprite({
|
|
texture: sunHaloTexture,
|
|
scale: CELESTIAL_CONFIG.sunHaloScale,
|
|
opacity: 0.78,
|
|
name: "sun-halo-sprite",
|
|
});
|
|
moonSprite = createSprite({
|
|
texture: moonTexture,
|
|
scale: CELESTIAL_CONFIG.moonScale,
|
|
opacity: 0.98,
|
|
name: "moon-sprite",
|
|
});
|
|
moonHaloSprite = createSprite({
|
|
texture: moonHaloTexture,
|
|
scale: CELESTIAL_CONFIG.moonHaloScale,
|
|
opacity: 0.52,
|
|
name: "moon-halo-sprite",
|
|
});
|
|
|
|
sunHaloSprite.renderOrder = 98;
|
|
moonHaloSprite.renderOrder = 98;
|
|
|
|
celestialRoot.add(skySphere);
|
|
celestialRoot.add(brightStarsGroup);
|
|
celestialRoot.add(sunHaloSprite);
|
|
celestialRoot.add(moonHaloSprite);
|
|
celestialRoot.add(sunSprite);
|
|
celestialRoot.add(moonSprite);
|
|
scene.add(celestialRoot);
|
|
|
|
computeCelestialState(new Date());
|
|
loadBrightStars();
|
|
|
|
return {
|
|
root: celestialRoot,
|
|
getSunDirection: () => sunDirection.clone(),
|
|
getMoonDirection: () => moonDirection.clone(),
|
|
};
|
|
}
|
|
|
|
export function updateCelestialLayer(date = new Date(), camera = null) {
|
|
if (!celestialRoot) return;
|
|
|
|
if (camera) {
|
|
linkedCamera = camera;
|
|
}
|
|
|
|
refreshCelestialView();
|
|
|
|
const now = date.getTime();
|
|
if (!lastUpdatedAt || now - lastUpdatedAt >= CELESTIAL_CONFIG.updateIntervalMs) {
|
|
computeCelestialState(date);
|
|
} else {
|
|
updateLighting();
|
|
updateSpritePositions();
|
|
}
|
|
}
|
|
|
|
export function getSunDirection() {
|
|
return getPhysicalSunDirection(new Date(lastUpdatedAt || Date.now()));
|
|
}
|
|
|
|
export function getMoonDirection() {
|
|
return applyCelestialOrientation(moonDirection);
|
|
}
|
|
|
|
export function getCelestialDebugState() {
|
|
return {
|
|
orientationEulerRad: { ...runtimeOrientationEuler },
|
|
followEarthRotation: { ...runtimeFollowConfig },
|
|
};
|
|
}
|
|
|
|
export function setCelestialOrientation(nextEuler = {}) {
|
|
runtimeOrientationEuler = {
|
|
...runtimeOrientationEuler,
|
|
...nextEuler,
|
|
};
|
|
refreshCelestialOrientation();
|
|
updateLighting();
|
|
return getCelestialDebugState();
|
|
}
|
|
|
|
export function setCelestialFollow(nextFollow = {}) {
|
|
runtimeFollowConfig = {
|
|
...runtimeFollowConfig,
|
|
...nextFollow,
|
|
};
|
|
refreshCelestialView();
|
|
updateLighting();
|
|
return getCelestialDebugState();
|
|
}
|
|
|
|
export function setCelestialDayNightEnabled(enabled) {
|
|
dayNightLightingEnabled = enabled;
|
|
updateLighting();
|
|
}
|
|
|
|
export function disposeCelestialLayer() {
|
|
if (celestialRoot?.parent) {
|
|
celestialRoot.parent.remove(celestialRoot);
|
|
}
|
|
|
|
[skySphere, sunSprite, moonSprite, sunHaloSprite, moonHaloSprite, ...brightStarSprites].forEach((object) => {
|
|
if (!object) return;
|
|
if (object.geometry) object.geometry.dispose();
|
|
if (object.material) {
|
|
if (object.material.map) object.material.map.dispose?.();
|
|
object.material.dispose();
|
|
}
|
|
});
|
|
|
|
skySphere = null;
|
|
brightStarsGroup = null;
|
|
sunSprite = null;
|
|
moonSprite = null;
|
|
sunHaloSprite = null;
|
|
moonHaloSprite = null;
|
|
celestialRoot = null;
|
|
brightStarSprites = [];
|
|
brightStarTexture?.dispose?.();
|
|
brightStarTexture = null;
|
|
|
|
lastUpdatedAt = 0;
|
|
linkedSunLight = null;
|
|
linkedBackLight = null;
|
|
linkedAmbientLight = null;
|
|
linkedPointLight = null;
|
|
linkedCamera = null;
|
|
linkedEarth = null;
|
|
dayNightLightingEnabled = true;
|
|
sunDirection.copy(defaultSunDirection);
|
|
moonDirection.copy(defaultMoonDirection);
|
|
runtimeOrientationEuler = {
|
|
...CELESTIAL_CONFIG.orientationEulerRad,
|
|
};
|
|
runtimeFollowConfig = {
|
|
...CELESTIAL_CONFIG.followEarthRotation,
|
|
};
|
|
}
|