Okay, so I’ve created a class based on your fine example:
import {
Object3D,
BufferGeometry,
Float32BufferAttribute,
LineBasicMaterial,
Line
} from 'three';
import { degreesToRadians } from '../Physics/utils';
import { VectorType } from '../Physics/types';
export default class extends Object3D {
uniforms: {
aX: { value: number };
aY: { value: number };
xRadius: { value: number };
yRadius: { value: number };
aStartAngle: { value: number };
aEndAngle: { value: number };
aClockwise: { value: boolean };
aRotation: { value: number };
};
verticesNumber: number;
vertices: VectorType[];
verticesIndices: Float32Array;
color: string;
constructor(
aX: number,
aY: number,
xRadius: number,
yRadius: number,
aStartAngle: number,
aEndAngle: number,
aClockwise: boolean,
aRotation: number,
verticesNumber: number,
color: string
) {
super();
this.verticesNumber = verticesNumber;
this.vertices = [];
this.verticesIndices = new Float32Array(verticesNumber);
this.color = color;
this.uniforms = {
aX: { value: aX },
aY: { value: aY },
xRadius: { value: xRadius },
yRadius: { value: yRadius },
aStartAngle: { value: aStartAngle },
aEndAngle: { value: aEndAngle },
aClockwise: { value: aClockwise },
aRotation: { value: aRotation }
};
this.getEllipseCurve();
}
getEllipseCurve(): void {
const verticesNumber = this.verticesNumber;
for (let i = 0; i < verticesNumber; i++) {
this.vertices.push({ x: 0, y: 0, z: 0 });
this.verticesIndices[i] = i;
}
const ellipseGeometry = new BufferGeometry().setFromPoints(this.vertices);
ellipseGeometry.addAttribute(
'vertIndex',
new Float32BufferAttribute(this.verticesIndices, 1)
);
const ellipseMaterial = new LineBasicMaterial({ color: this.color });
ellipseMaterial.onBeforeCompile = (shader: any) => {
shader.uniforms.aX = this.uniforms.aX;
shader.uniforms.aY = this.uniforms.aY;
shader.uniforms.xRadius = this.uniforms.xRadius;
shader.uniforms.yRadius = this.uniforms.yRadius;
shader.uniforms.aStartAngle = this.uniforms.aStartAngle;
shader.uniforms.aEndAngle = this.uniforms.aEndAngle;
shader.uniforms.aClockwise = this.uniforms.aClockwise;
shader.uniforms.aRotation = this.uniforms.aRotation;
shader.uniforms.vertCount = { value: this.verticesNumber };
shader.vertexShader = `
uniform float aX;
uniform float aY;
uniform float xRadius;
uniform float yRadius;
uniform float aStartAngle;
uniform float aEndAngle;
uniform float aClockwise;
uniform float aRotation;
uniform float vertCount;
attribute float vertIndex;
vec3 getPoint(float t){
vec3 point = vec3(0);
float eps = 0.00001;
float twoPi = 3.1415926 * 2.0;
float deltaAngle = aEndAngle - aStartAngle;
bool samePoints = abs( deltaAngle ) < eps;
if (deltaAngle < eps) deltaAngle = samePoints ? 0.0 : twoPi;
if ( floor(aClockwise + 0.5) == 1.0 && ! samePoints ) deltaAngle = deltaAngle == twoPi ? - twoPi : deltaAngle - twoPi;
float angle = aStartAngle + t * deltaAngle;
float x = aX + xRadius * cos( angle );
float y = aY + yRadius * sin( angle );
if ( aRotation != 0. ) {
float c = cos( aRotation );
float s = sin( aRotation );
float tx = x - aX;
float ty = y - aY;
x = tx * c - ty * s + aX;
y = tx * s + ty * c + aY;
}
point.x = x;
point.y = y;
return point;
}
${shader.vertexShader}`;
shader.vertexShader = shader.vertexShader.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
float t = vertIndex / vertCount;
transformed = getPoint(t);
`
);
};
this.add(new Line(ellipseGeometry, ellipseMaterial));
}
rotateAroundFocus(axisRotations: VectorType): void {
this.rotation.x = degreesToRadians(axisRotations.x);
this.rotation.y = degreesToRadians(axisRotations.y);
this.rotation.z = degreesToRadians(axisRotations.z);
}
update(
aX: number,
aY: number,
xRadius: number,
yRadius: number,
aStartAngle: number,
aEndAngle: number,
aClockwise: boolean,
aRotation: number,
axisRotations: VectorType
): void {
this.uniforms.aX.value = aX;
this.uniforms.aY.value = aY;
this.uniforms.xRadius.value = xRadius;
this.uniforms.yRadius.value = yRadius;
this.uniforms.aStartAngle.value = aStartAngle;
this.uniforms.aEndAngle.value = aEndAngle;
this.uniforms.aClockwise.value = aClockwise;
this.uniforms.aRotation.value = aRotation;
this.rotateAroundFocus(axisRotations);
}
}
It works beeeeaaauuutifulllly !!!
In case you’re wondering about the rotateAroundFocus nonsense method, in celestial mechanics, when you want to define an orbit, you need to know its argument of periapsis, inclination and then the longitude of the ascending node, and these parameters are set relative to the focus (i.e sun or Earth) of your ellipse rather than its centre, and since the three.js ellipsecurve only lets you rotate the ellipse around its center, I had to add that method.
To convert the orbital parameters - semimajor axis (xRadius) and eccentricity (yRadius) - into the values I need to draw the ellipse I use these helper methods:
export function getPeriapsis(a, e) {
return a * (1 - e);
}
export function getApoapsis(a, e) {
return a * (1 + e);
}
export function getSemiMinorAxis(a, e) {
return a * Math.sqrt(1 - e * e);
}
export function getEllipse(a, e) {
return {
xRadius: a,
yRadius: getSemiMinorAxis(a, e)
};
}
An update and link will be coming shortly