186 lines
6.4 KiB
TypeScript
186 lines
6.4 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
} |