admin管理员组文章数量:1334422
I am writing a 2D simulator and game using the HTML canvas which involves orbital mechanics. One feature of the program is to take the position and velocity vector of a satellite at one point and return the semi-major axis, eccentricity, argument of periapsis, etc of a 2D orbit around one planet. When the eccentricity is less than one, I can easily graph the orbit as an ellipse using ctx.ellipse(). However, for eccentricities greater than one, the correct shape of the orbit is a hyperbola. At the moment, my program just draws nothing if the eccentricity is greater than one, but I would like it to graph the correct hyperbolic orbit. Since there is no built in "hyperbola" function, I need to convert my orbit into a Bézier curve. I am at a bit of a loss as to how to do this. The inputs would be the location of one focus, semi-major axis, eccentricity, and argument of periapsis (basically how far the orbit is rotated) and it should return the correct control points to graph a Bézier curve approximation of a hyperbola. It does not have to be exactly perfect, as long as it is a close enough fit. How can I approach this problem?
I am writing a 2D simulator and game using the HTML canvas which involves orbital mechanics. One feature of the program is to take the position and velocity vector of a satellite at one point and return the semi-major axis, eccentricity, argument of periapsis, etc of a 2D orbit around one planet. When the eccentricity is less than one, I can easily graph the orbit as an ellipse using ctx.ellipse(). However, for eccentricities greater than one, the correct shape of the orbit is a hyperbola. At the moment, my program just draws nothing if the eccentricity is greater than one, but I would like it to graph the correct hyperbolic orbit. Since there is no built in "hyperbola" function, I need to convert my orbit into a Bézier curve. I am at a bit of a loss as to how to do this. The inputs would be the location of one focus, semi-major axis, eccentricity, and argument of periapsis (basically how far the orbit is rotated) and it should return the correct control points to graph a Bézier curve approximation of a hyperbola. It does not have to be exactly perfect, as long as it is a close enough fit. How can I approach this problem?
Share Improve this question asked May 4, 2021 at 20:33 Adam KaufmanAdam Kaufman 633 bronze badges 2- 1 Sample a few points on your ellipse or hyperbola and create a catmull-rom spline. Each segment of the catmull-rom spine is a cubic Bezier curve. – fang Commented May 4, 2021 at 22:11
- Show your code and we might assist you in fixing it perhap. – Mark Schultheiss Commented May 8, 2021 at 18:30
2 Answers
Reset to default 12In terms of conic sections, hyperbola are unfortunately the one class of curves that the Canvas cannot natively render, so you're stuck with approximating the curve you need. There are some options here:
- Flatten your curve, by sampling the hyperbola at one or two points in the distance and lots of points near the extrema so that you can draw a simple polygon that looks like a curve.
- Model the hyperbola with a single "best approximation" quadratic or cubic curve.
- As @fang mentions: sample the curve at a few points and convert the Catmull-Rom spline through those points to Bezier form.
- Combine approaches 1 and 2. using a single Bezier curve to approximate the part of the hyperbola that actually looks curved, and using straight lines for the parts that don't.
- Combine approaches 1 and 3, using a Catmull-Rom spline for the curvy bit, and straight lines for the straight bits.
1: Curve flattening
Curve flattening is basically trivial. Rotate your curve until it's axis-aligned, and then just pute y
given x
using the standard hyperbolic function, where a
is half the distance between the extrema, and b
is the semi-minor axis:
x²/a² - y²/b² = 1
x²/a² = 1 + y²/b²
x²/a² - 1 = y²/b²
b²(x²/a² - 1) = y²
b²(x²/a² - 1) = y²
± sqrt(b²(x²/a² - 1)) = y
plug in your values, iterate over x
to get a sequence of (x,y
) coordinates (remembering to generate more coordinates near the extrema), then turn those into a moveTo()
for the first coordinate, followed by however many lineTo()
calls you need for the rest. As long as your point density is high enough for the scale you're presenting, this should look fine:
function flattenHyperbola(a, b, inf=1000) {
const points = [],
a2 = a**2,
b2 = b**2;
let x, y, x2;
for (x=inf; x>0.1; x/=2) {
x2 = (a+x)**2;
y = -Math.sqrt(b2*x2/a2 - b2);
points.push({x: a+x, y});
}
points.push({x:a, y:0});
for (x=0.1; x<inf; x*=2) {
x2 = (a+x)*(a+x);
y = Math.sqrt(b2*x2/a2 - b2);
points.push({x: a+x, y});
}
return points;
}
Let's plot the hyperbola in red and the approximation in blue:
Of course the downside with this approach is that you will need to create a separate flattened curve for every scale the user may view your graphics at. Or, you need to generate a flattened curve with lots and lots of points, and then draw it by skipping over coordinates depending on how zoomed in/out things are.
2: Bezier approximation
The parametric representation of a hyperbola is f(t)=(a*sec(t), b*tan(t))
(or rather, that's the representation for the y-axis aligned hyperbola - we can get any other variant by applying the standard rotation transform). We can have a quick look at the Taylor series for these functions to see which order of Bezier curve we can use:
sec(t) = 1 + t²/2 + 5t⁴/15 + ...
tan(t) = x + t³/3 + 2t⁵/15 + ...
So we might be able to get away with just the first two terms for each dimension in which case we can use a cubic Bezier (as the highest order is t³):
Turns out, that won't do: It's just way too inaccurate, so we're going to have to better approximate: we create a Bezier curve with start and end points "well off into the distance", with the control points set such that the Bezier midpoint coincides with the hyperbola's extrema. If we try this, we might be fooled into thinking that'll work:
But if we pick x
far enough away, we see this approximation quickly stops working:
function touchingParabolicHyperbola(a, b, inf=1000) {
const beziers = [],
a2 = a**2,
b2 = b**2;
let x, x2, y, A, CA;
for(x=50; x<inf; x+=50) {
x2 = x**2;
y = sqrt(b2*x2/a2 - b2);
// Hit up https://pomax.github.io/bezierinfo/#abc
// and model the hyperbola in the cubic graphic to
// understand why the next, very simple-looking,
// line actually works:
A = a - (x-a)/3;
// We want the control points for this A to lie on
// the asymptote, but for small x we want it to be 0,
// otherwise the curve won't run parallel to the
// hyperbola at the start and end points.
CA = lerp(0, A*b/a, x/inf);
beziers.push([
{x, y: -y},
{x: A, y:-CA},
{x: A, y: CA},
{x, y},
]);
}
return beziers;
}
This shows us a sequence of curves that starts off looking decent but bees pletely useless pretty fast:
One obvious problem is that the curves end up going past the asymptotes. We can fix that by forcing the control points to (0,0) so that the Bezier hull is a triangle and the curve will always lie inside of that.
function tangentialParabolicHyperbola(a, b, inf=1000) {
const beziers = [],
a2 = a**2,
b2 = b**2;
let x, x2, y;
for(x=50; x<inf; x+=50) {
x2 = x**2;
y = sqrt(b2*x2/a2 - b2);
beziers.push([
{x, y:-y},
{x: 0, y:0},
{x: 0, y:0},
{x, y},
]);
}
return beziers;
}
This leads to a series of curves that go from useless on one side, to useless on the other side:
So single curve approximations are not all that great. What if we use more curves?
3: Poly-Bezier using a Catmull-Rom spline
We can overe the above problem by using multiple Bezier curves along the hyperbola, which we can (almost trivially) pute by picking a few coordinates on the hyperbola, and then constructing a Catmull-Rom spline through those points. Since a Catmull-Rom spline through N points is equivalent to a poly-Bezier made up of N-3 segments, this could be the winning strategy.
function hyperbolaToPolyBezier(a, b, inf=1000) {
const points = [],
a2 = a**2,
b2 = b**2,
step = inf/10;
let x, y, x2,
for (x=a+inf; x>a; x-=step) {
x2 = x**2;
y = -Math.sqrt(b2*x2/a2 - b2);
points.push({x, y});
}
for (x=a; x<a+inf; x+=step) {
x2 = x**2;
y = Math.sqrt(b2*x2/a2 - b2);
points.push({x, y});
}
return crToBezier(points);
}
With the conversion function being:
function crToBezier(points) {
const beziers = [];
for(let i=0; i<points.length-3; i++) {
// NOTE THE i++ HERE! We're performing a sliding window conversion.
let [p1, p2, p3, p4] = points.slice(i);
beziers.push({
start: p2,
end: p3,
c1: { x: p2.x + (p3.x-p1.x)/6, y: p2.y + (p3.y-p1.y)/6 },
c2: { x: p3.x - (p4.x-p2.x)/6, y: p3.y - (p4.y-p2.y)/6 }
})
}
return beziers;
}
Let's plot that:
We have to do a bit more work up front pared to flattening, but the upside is that we now have a curve that actually "looks like a curve" at any scale.
4: Combining (1) and (2)
Now, most of the hyperbola actually "looks straight", so using lots of Bezier curves for those parts does feel a bit silly: why not only model the curvy bit with a curve, and model the straight bits with straight lines?
We already saw that if we fix the control point to (0,0), there might be a curve that's at least decent enough, so let's bine approaches 1 and 2, where we can create a single Bezier curve with start and end points "close enough" to the curve, and tacking two line segments onto the ends that join up the bezier curves to two distant points on the asymptotes (which are at y=±b/a * x
, so any large value for x
will yield a usable-enough y
)
Of course the trick is to find the distance at which the single curve still captures the curvature, while also making our lines to infinity look like they smoothly join up to our single curve. The Bezier projection identity es in handy again: we want A
to be at (0,0)
and we want the Bezier midpoint to be at (a,0)
, which means our start and end points should have an x
coordinate of 4a
:
function hyperbolicallyFitParabolica(a, b, inf=1000) {
const a2 = a**2,
b2 = b**2,
x = 4*a,
x2 = x**2,
y = sqrt(b2*x2/a2 - b2)
bezier = [
{x: x, y:-y},
{x: 0, y: 0},
{x: 0, y: 0},
{x: x, y: y},
],
start = { x1:x, y1:-y, x2:inf, y2: -inf * b/a},
end = { x1:x, y1: y, x2:inf, y2: inf * b/a};
return [start, bezier, end];
}
Which gives us the following result (Bezier in blue, line segments in black):
So that's not great, but it's not terrible either. It's certainly good enough if the audience doesn't scrutinize the render, and it's definitely cheap, but we can do quite a bit better with just a little more work, so: let's also look at the best approximation we can probably e up with here:
5: Combining (1) and (3)
If a single Bezier didn't work, and we already saw that using a Catmull-Rom spline instead of a single curve works way better, then we can of course also just bine approaches 1 and 3. We can form a much better fit around the extrema by constructing two Bezier curves rather than one, by generating five points centered on the extrema and converting the resulting Catmull-Rom spline through those points to Bezier form:
function probablyTheBestHyperbola(a, b, inf=1000) {
let curve = [],
a2 = a**2,
b2 = b**2,
x, y, x2,
cover = 100;
// generate two points approaching the midpoint
for (x=a+cover; x>a; x-=cover/2) {
x2 = x**2;
y = -Math.sqrt(b2*x2/a2 - b2);
curve.add(new Vec2(x, y));
}
// generate three points departing at the midpoint
for (x=a; x<=a+cover; x+=cover/2) {
x2 = x*x;
y = sqrt(b2*x2/a2 - b2);
curve.add(new Vec2(x, y));
}
const beziers = crToBezier(curve),
start = {
x1: points.get(1).x, y1: points.get(1).y,
x2: inf, y2: -inf * b/a
},
end = {
x1: points.get(3).x, y1: points.get(3).y,
x2: inf, y2: inf * b/a
};
return { start, beziers, end };
}
Which gives us the following result (CR in blue, line segments in black):
And that's probably the best we're going to get in a trade-off between "cheap to pute", "easy to scale", and "looks rights".
I'm doing almost the exact same thing except I'm using SVG instead of the canvas. From what I recall they are similar enough that this is applicable. Actually this is regarding Bezier curves in general so application shouldn't matter other than paying attention to the sign of a and y (inverted for graphical applications vs pure math).
Note that this is with the hyperbola centered at the origin not the focus. I would remend coding it this way then using a transform to position and rotate it as needed. I did a lot of playing around in Desmos, the exact graph is here but I won't bet on that working.
I did this two different ways. One using a quadratic curve and one using a cubic curve. Both use b, which is sqrt(c^2-a^2) instead of e, but easy enough to calculate.
Cubic Curve
I set the cubic curve to fit the hyperbola from 2a to a, which should be enough to cover the part that the curvature is noticeable. Since the apex was at the halfway point this made it easy to set the x values of the control points. The Y values were a bit tricky but came out rather elegant.
cubic curve
The control points are
P1 = (2 * a, b * sqrt(3) )
P2 = (2/3 * a, b * (48-26 * sqrt(3) ) / 18
P3 = (2/3 * a, -b * (48-26 * sqrt(3) )/ 18
P4 = (2 *a, -b * sqrt(3) )
To me this is the better option. If you only need half the hyperbola such as going from parking orbit to ejection orbit then you can clip it.
Quadradic Curve
quadradic curve img
To use a quadratic curve for an ejection orbit, notice that the tangent at departure is vertical and at true anomaly of 90 the y value is the parameter and the flight path angle, which is the tangent, reduces to tan(phi) = e. The control point is then the intersection of the tangents, so:
P1 = (a, 0)
P2 = (a,-e(a + c) + p
P3 = (c, p)
To me this curve is a little too short. I was trying to add additional points to extend it, but had trouble getting it right
Other Options
Another option is creating a curve and using transforms to change the size and shape. Apparently any curve can be created from any another by affine transforms. It's also possible to split a curve into two curves and to change the order of the curve.
本文标签: javascriptConvert hyperbola to B233zier curve for graphing orbital pathsStack Overflow
版权声明:本文标题:javascript - Convert hyperbola to Bézier curve for graphing orbital paths - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742270036a2444131.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论