import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/Addons.js'; import type { Body } from '../calculations/constants'; import type { Orbit, OrbitalCoordinates } from '../calculations/orbit-calculations'; import { addVector, multiplyMatrixWithScalar, normalizeVector } from '../calculations/mathematics'; function getPosition(trueAnomaly: number, orbit: Orbit): number[][] { var radius = orbit.semiLatusRectum / (1 + orbit.eccentricity * Math.cos(trueAnomaly)); var localX = radius * Math.cos(trueAnomaly); var localY = radius * Math.sin(trueAnomaly); let position = addVector( multiplyMatrixWithScalar(localX, orbit.coordinateAxes[0]), multiplyMatrixWithScalar(localY, orbit.coordinateAxes[1]) ); return position; } function getScalingFunction(mesh: THREE.Mesh) { return function (_: number, cameraPosition: THREE.Vector3, cameraDirection: THREE.Vector3) { let displacement = new THREE.Vector3(); displacement.subVectors(cameraPosition, mesh.position); let distanceFromCamera = displacement.dot(cameraDirection); mesh.scale.setScalar(distanceFromCamera / 5000000); } } export class Renderer { parentDiv: HTMLDivElement; scene: THREE.Scene camera: THREE.PerspectiveCamera; renderer: THREE.WebGLRenderer; controls: OrbitControls; body: Body; animationFunctions: ((time: number, cameraPosition: THREE.Vector3, cameraDirection: THREE.Vector3) => void)[]; constructor(body: Body) { this.parentDiv = document.createElement("div"); this.body = body; this.animationFunctions = []; // Rendering this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x888888); this.camera = new THREE.PerspectiveCamera(75, 400 / 400, 0.1, 1e99); this.camera.position.set(0, 0, 5000000); this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize(400, 400); this.parentDiv.appendChild(this.renderer.domElement); this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.05; this.controls.screenSpacePanning = false; this.controls.minDistance = this.body.radius * 2; this.controls.maxDistance = 1e99; this.controls.cursorStyle = 'grab'; this.controls.maxPolarAngle = Math.PI; this.renderer.setAnimationLoop(this.animate.bind(this)); let bodySphere = new THREE.SphereGeometry(this.body.radius); let material = new THREE.MeshBasicMaterial({color: 0x000000}); let mesh = new THREE.Mesh(bodySphere, material); this.scene.add(mesh); } animate ( time: number) { this.controls.update(); let cameraPosition = new THREE.Vector3(); this.camera.getWorldPosition(cameraPosition); let cameraDirection = new THREE.Vector3(); this.camera.getWorldDirection(cameraDirection); this.animationFunctions.forEach(f => f(time, cameraPosition, cameraDirection)); this.renderer.render(this.scene, this.camera); } addOrbit(orbit: Orbit) { let stableOrbit = false; if (orbit.eccentricity < 1) { let maxDistance = orbit.semiLatusRectum / (1 - orbit.eccentricity); if (maxDistance < this.body.sphereOfInfluence) { stableOrbit = true; } } let minimumTrueAnomaly = -Math.PI; let maximumTrueAnomaly = Math.PI if (!stableOrbit) { maximumTrueAnomaly = Math.acos((orbit.semiLatusRectum - this.body.sphereOfInfluence) / (this.body.sphereOfInfluence * orbit.eccentricity)); minimumTrueAnomaly = -maximumTrueAnomaly; } let angles = []; for (var i = 0; i <= 360; i++) { let angle = minimumTrueAnomaly + (maximumTrueAnomaly - minimumTrueAnomaly) * i / 360; angles.push(angle); } let points: THREE.Vector3[] = []; angles.forEach(angle => { let position = getPosition(angle, orbit); points.push(new THREE.Vector3( position[0][0], position[2][0], -position[1][0] )); }); const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({color: 0xffffff}); const line = new THREE.Line(geometry, material); this.scene.add(line); if (orbit.eccentricity > 0.001) { // Add the periapsis let periapsisPoint = new THREE.SphereGeometry(this.body.radius / 20); let periapsisMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00}); let periapsisMesh = new THREE.Mesh(periapsisPoint, periapsisMaterial); let periapsisPosition = getPosition(0, orbit); periapsisMesh.position.x = periapsisPosition[0][0]; periapsisMesh.position.y = periapsisPosition[2][0]; periapsisMesh.position.z = -periapsisPosition[1][0]; this.scene.add(periapsisMesh); this.animationFunctions.push(getScalingFunction(periapsisMesh)); if (stableOrbit) { let apoapsisPoint = new THREE.SphereGeometry(this.body.radius / 20); let apoapsisMaterial = new THREE.MeshBasicMaterial({color: 0xff0000}); let apoapsisMesh = new THREE.Mesh(apoapsisPoint, apoapsisMaterial); let apoapsisPosition = getPosition(Math.PI, orbit); apoapsisMesh.position.x = apoapsisPosition[0][0]; apoapsisMesh.position.y = apoapsisPosition[2][0]; apoapsisMesh.position.z = -apoapsisPosition[1][0]; this.scene.add(apoapsisMesh); this.animationFunctions.push(getScalingFunction(apoapsisMesh)); } } } addCoordinates(coordinates: OrbitalCoordinates, color: THREE.ColorRepresentation) { this.addOrbit(coordinates.orbit); let pointSphere = new THREE.SphereGeometry(this.body.radius / 5); let material = new THREE.MeshBasicMaterial({color: color}); let mesh = new THREE.Mesh(pointSphere, material); let position = getPosition(coordinates.trueAnomaly, coordinates.orbit); mesh.position.x = position[0][0]; mesh.position.y = position[2][0]; mesh.position.z = -position[1][0]; this.scene.add(mesh); // Create an arrow that points in the correct direction let nextPosition = getPosition(coordinates.trueAnomaly + 0.0001, coordinates.orbit); let direction = normalizeVector(addVector(nextPosition, multiplyMatrixWithScalar(-1, position))); let renderDirection = new THREE.Vector3(direction[0][0], direction[2][0], -direction[1][0]); let arrowHelper = new THREE.ArrowHelper(renderDirection, mesh.position, this.body.radius / 1.666, color, this.body.radius / 5, this.body.radius/5); this.scene.add(arrowHelper); this.animationFunctions.push((_, cameraPosition) => { let displacement = new THREE.Vector3(); displacement.subVectors(cameraPosition, mesh.position); let distance = displacement.length(); mesh.scale.setScalar(distance / 5000000); arrowHelper.scale.setScalar(distance / 5000000); }); } }