Updating EllipseCurve

Hey folks,

I’ve been looking at the source code for the EllipseCurve class and I’m wondering how you can update the parameters that define an ellipse without instantiating a new one from scratch? Say that the y radius parameter changes, how do update the EllipseCurve to take this into account and render an ellipse with the new y radius?

Also, it seems the constructor of the ellipse curve does not accept a parameter that lets you render ellipses that are inclined relative to a plane… You can rotate the ellipse along the plane, though, so I’m just wondering if there is a reason for why this functionality has not been included???

Applying a rotation to the ellipse solves the problem but it somehow does not seem very elegant code wise.

Happy Saturday!

Hi!
Maybe to put functionality of .getPoint() into a vertex shader and add a buffer attribute with indices of vertices. Plus, pass all the parameters in uniforms.

1 Like

Hey Prisoner!

In the end I opted for the following snippet (when you run it in the render loop it renders a very confused ellipse on steroids :rofl: ):

 renderEllipse() {
    this.curve.xRadius = getRandomNumberInRange(200, 400);
    this.curve.yRadius = getRandomNumberInRange(200, 600);

    this.ellipse.geometry.setFromPoints(this.curve.getPoints(500));   
 },

Thought it would be more complex than that, but I have the feeling your solution is more performant and what I should go for regardless, no???

This is going to make adding planets and asteroids so much more intuitive… I’m bouncing up and down right now from happiness!!!

Here is an example :slight_smile:

Picture:

Working demo:

2 Likes

Hot jiggedidoo!

I shall implement this code today and then share a link so that you can see how I’m using it :smile:!

And many, many thanks!!

You’re welcome :slight_smile: I hope it will help :beers:
Just keep in mind that it has only implementation of .getPoint(), and doesn’t have .getPointAt() (which needs much more data and calculations).

Oh it will, you’re a champion :medal_sports:!

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 :smile::sun_with_face::rocket::koala:!!!

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 :smile:

1 Like

Still some improvements that could be made, but here you can see a basic prototype of what I have in mind in action.

Use the sliders to add planets as you like, if you have any questions, let me know :smiley:

3 Likes

Looks cool! :+1:

Truly putting the ellipse to good use now that I’ve started incorporating space travel into my space simulator, but as I’ve done so, I’ve bumped into a limitation, namely that an ellipse is but one conic geometry, and spacecraft can move on hyperbolic trajectories, which is to say that the eccentricity of the ellipse > 1 (0 would be a circle). Is there a way the ellipse shader can be made to support conic sections in general rather than just ellipses, which are a special case of conic sections?

After a day of tearing my hair off I’ve finally gotten there, so it’s all good now!

1 Like

Yay! :slight_smile: Glad to hear that :slight_smile:
Haven’t been on laptop so wasn’t able to help.

1 Like

@prisoner849 Do you think a similar implementation can be done for ellipses that use the fat lines material as well? I’ve been looking at the fat line shaders and trying to make it work but with no success