KerbalCalculations/src/gui/renderer.ts
2026-04-06 23:48:00 +02:00

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);
});
}
}