import { OrbitingBody, CelestialBody } from "./body.js"; import * as Geometry from "../utilities/geometry.js"; import { SpriteManager } from "../utilities/sprites.js"; export class SolarSystem { constructor(sun, bodies, config) { this.config = config; this._orbiting = new Map(); this._objects = new Map(); this._orbits = new Map(); this._sois = new Map(); this.showSOIs = false; this._customUpdates = []; this.sun = new CelestialBody(sun); for (const data of bodies) { const { orbiting } = data; const attractor = orbiting == 0 ? this.sun : this._orbiting.get(orbiting); const body = new OrbitingBody(data, attractor, this.config.orbit); this._orbiting.set(body.id, body); attractor.orbiters.push(body); } } get orbiting() { return [...this._orbiting.values()]; } get bodies() { return [this.sun, ...this.orbiting]; } get data() { const data = []; for (const body of this.bodies) { data.push(body.data); } return data; } bodyFromName(name) { for (const body of [this.sun, ...this.orbiting]) { if (body.name == name) return body; } throw new Error(`No body with name ${name}`); } bodyFromId(id) { if (id == 0) { return this.sun; } else { const body = this._orbiting.get(id); if (!body) throw new Error(`No body with id ${id}`); return body; } } objectsOfBody(id) { const object = this._objects.get(id); if (!object) throw new Error(`No 3D objects from body of id ${id}`); return object; } fillSceneObjects(scene, canvas) { const { scale } = this.config.rendering; const { satSampPoints, planetSampPoints, orbitLineWidth } = this.config.orbit; const { planetFarSize, satFarSize } = this.config.solarSystem; const { soiOpacity } = this.config.solarSystem; const spriteMaterial = SpriteManager.getMaterial("circle"); const sunSize = scale * this.sun.radius * 2; const sunSprite = Geometry.createSprite(spriteMaterial, this.sun.color, true, sunSize); const sunGroup = new THREE.Group(); sunGroup.add(sunSprite); this._objects.set(0, sunGroup); for (const body of this.orbiting) { const { radius, soi, orbit, color, attractor } = body; const parentGroup = this._objects.get(attractor.id); const bodyGroup = new THREE.Group(); const samplePts = attractor.id == 0 ? planetSampPoints : satSampPoints; const spriteSize = attractor.id == 0 ? planetFarSize : satFarSize; const orbitPoints = Geometry.createOrbitPoints(orbit, samplePts, scale); const ellipse = Geometry.createLine(orbitPoints, canvas, { color: color, linewidth: orbitLineWidth, }); parentGroup.add(ellipse); this._orbits.set(body.id, ellipse); const soiMaterial = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: soiOpacity }); const soiLines = Geometry.createWireframeSphere(soi, scale, soiMaterial); bodyGroup.add(soiLines); this._sois.set(body.id, soiLines); const bodyMaterial = new THREE.MeshBasicMaterial({ color: color }); bodyGroup.add(Geometry.createSphere(radius, scale, bodyMaterial)); bodyGroup.add(Geometry.createSprite(spriteMaterial, color, false, spriteSize)); parentGroup.add(bodyGroup); this._objects.set(body.id, bodyGroup); } scene.add(sunGroup); } set date(date) { for (const body of this.orbiting) { const group = this._objects.get(body.id); const pos = body.positionAtDate(date).multiplyScalar(this.config.rendering.scale); group.position.copy(pos); } } update(camController) { this._updateSatellitesDisplay(camController); this._updateSOIsDisplay(camController); for (const f of this._customUpdates) { f(camController); } } _updateSatellitesDisplay(camController) { const { satDispRadii } = this.config.solarSystem; const { scale } = this.config.rendering; const camPos = camController.camera.position; for (const body of this.orbiting) { const { attractor } = body; const bodyGroup = this._objects.get(body.id); if (attractor.id != 0) { const parentPos = new THREE.Vector3(); const parentGroup = this._objects.get(attractor.id); parentGroup.getWorldPosition(parentPos); const dstToCam = parentPos.distanceTo(camPos); const thresh = scale * satDispRadii * body.orbit.semiMajorAxis; const visible = dstToCam < thresh; const ellipse = this._orbits.get(body.id); ellipse.visible = visible; bodyGroup.visible = visible; } else { bodyGroup.visible = true; } } } _updateSOIsDisplay(camController) { if (!this.showSOIs) { for (const sphere of this._sois.values()) { sphere.visible = false; } } else { const camPos = camController.camera.position; const { scale } = this.config.rendering; for (const body of this.orbiting) { const group = this._objects.get(body.id); const bodyPos = new THREE.Vector3(); group.getWorldPosition(bodyPos); const dstToCam = camPos.distanceTo(bodyPos); const sphere = this._sois.get(body.id); sphere.visible = dstToCam > body.soi * scale; } for (const body of this.orbiting) { const soi = this._sois.get(body.id); const attrSoi = this._sois.get(body.attractor.id); if (attrSoi === null || attrSoi === void 0 ? void 0 : attrSoi.visible) { soi.visible = false; } else { soi.visible && (soi.visible = true); } } } } addCustomUpdate(f) { const id = this._customUpdates.length; this._customUpdates.push(f); return id; } removeCustomUpdate(id) { try { this._customUpdates.splice(id); } catch (err) { console.error(`Failed removing update callback with id ${id}`, err); } } clearCustomUpdate() { this._customUpdates = []; } }