Subdivide multi segmented Cubic Bezier spline - bezier

First of let me apologies for a bad English and probably not very straight forward question, as I am not really sure how to call it.
I have a multi segmented Cubic Bezier curve In After Effects, it is is defined by 5 vertices with IN & OUT tangents. My task is to subdivide it into N small linear chunks in Java Script.
EDIT submited more info.
Given a multi segmented Cubic Bezier spline defined by 5 points with In & Out tangents, I need to get a linear representation of it. Where N is number of linear segments, defined by user.
Cubic Bezier Spline:
Segment1: P0, P0out, P1in, P1;
Segment2: P1, P1out, P2in, P2;
Segment3: P2, P2out, P3in, P3;
Segment4: P3, P3out, P4in, P4;
Expected output:
N = 1: linear spline with 2 anchors representing entire shape;
N = 2: linear spline with 3 anchors representing entire shape;
N = 3: linear spline with 4 anchors representing entire shape;
N = 4: linear spline with 5 anchors representing entire shape;
...
N = 8: linear spline with 9 anchors representing entire shape;
distance(L0,L1) = distance(L1,L2) = distance(L2,L3) = ... = distance(L-n, Ln)
In example image I use 4-segmented spline, where segment length is equal to one another - this is just easier to draw to explain my task. But in real project those segments will not be equal, and there will be more then 4 segments total.
I have looked at de Casteljau method, but as I can understand, it works with one segment spline. My math skills are dusty, so I am not really sure if I can use de Casteljau in my example.

This is conceptually straight forward, although it might involve quite a bit of code for reasons explained a little later on. What you're trying to do is flatten a (cubic) poly-Bezier, so let's start with that:
Individual cubic Bezier curves are generated by four points: a start point, a control point that determines the tangent at the start point, an end point, and a control point that determines the tangent at the end point. The curve, then, is a plot of the cubic Bezier function:
Bx(t) = p1.x × (1-t)³ + 2 × p2.x × (1-t)² × t + 2 × p3.x × (1-t) × t² + p4.x × t³
By(t) = p1.y × (1-t)³ + 2 × p2.y × (1-t)² × t + 2 × p3.y × (1-t) × t² + p4.y × t³
A single Bezier curve is plotted over the interval t=[0,1], so a poly-Bezier of N segments is plotted over a total interval N × [0,1].
First, the simple case: simple flattening. Bezier curves are non-linear curves and so let's first not bother to enforce "same length for each of the line segments". Given an N-segment poly-Bezier:
S = number of segments we want
points = empty list
for (s = 0):(s = S):(step = S/N):
v = s * step
segmentid = floor(v)
segment = polycurve.segments[segmentid]
t = v % 1
points.push(
segment.pointAt(t)
)
We now have all the points we need, and we just connect them with lines. Done.
However, Bezier curves are non-linear curves, so flattening in this way does not yield equidistant segments in the slightest. If we want to do that, we need to work with distance along the curve rather than t values.
S = number of segments we want
points = empty list
for (s = 0):(s = S):(step = S/N):
v = s * step
segmentid = floor(v)
segment = polycurve.segments[segmentid]
distanceRatio = v % 1
t = segment.getTforDistanceRatio(distanceRatio)
points.push(
segment.pointAt(t)
)
This will work exactly as you want, but getTforDistanceRatio is the hard part, because what we're doing here is reparameterizing the curve for distance, rather than time, and that is a very hard mathematical problem (for which no general symbolic solution exists). The cheapest way to do this is using a Lookup Table (LUT), which is explained in the link above, for "distance along the curve".

The de Casteljau method is used to compute a point on the Bezier curve and also obtain the control points for the two subdivided curves in the process. So, yes you should be able to use de Cateljau method to evaluate as many points as you want on a Bezier curve if you know the control points.
From the picture you show and the fact that your "cubic Bezier spline" takes in/out tangents as input, I think your spline is actually "cubic Hermite spline", in which each segment is indeed a cubic Bezier curve. You can convert each segment of your spline to a cubic Bezier curve, then use de Cateljau method to evaluate as many points as you want, then connect these points by straight lines.

Related

Using least square to fit a parametric curve

I have a curve represented as a parametric function, z(t)=(x(t), y(t)), x=f(t), y=g(t). If I wanna find a approximate curve using least square(using polynomial funtions), am I supposed to get one for x and one for y or just one for z?
So the problem is like this: given a parametric curve r(t) = (x(t), y(t)), find a approximate cubic polynomial curve. I collect multiple points in x(t) and y(t) and then use least square to find a corresponding curve for x(t), y(t). The final approximate curve is like r'(t) = (x'(t), y'(t))

Simplify high-order Bezier curve

I have an array of control points that represent a Bezier curve. It could be a fifth-order or 100-order Bezier curve, or anything in-between. I am looking for a way to simplify that Bezier curve into multiple cubic Bezier curves. An illustration below shows how tenth-degree curve can be simplified to three-degree curve, but I want to go further and simplify it to several cubic Bezier curves to achieve better approximation.
Code example would be very helpful.
As mohsenmadi already pointed out: in general this is not a thing you can do without coming up with your own error metric. Another idea is to go "well let's just approximate this curve as a sequence of lower order curves", so that we get something that looks better, and doesn't really require error metrics. This is a bit like "flattening" the curve to lines, except instead of lines we're going to use cubic Bezier segments instead, which gives nice looking curves, while keeping everything "tractable" as far as modern graphics libraries are concerned.
Then what we can do is: split up that "100th order curve" into a sequence of cubic Beziers by sampling the curve at regular intervals and then running those points through a Catmull-Rom algorithm. The procedure's pretty simple:
Pick some regularly spaced values for t, like 0, 0.2, 0.4, 0.6, 0.8 and 1, then
create the set of points tvalues.map(t => getCoordinate(curve, t)). Then,
build a virtual start and end point: forming a point 0 by starting at point 1 and moving back along its tangent, and forminga point n+1 by starting at n and following its tangent. We do this, because:
build the poly-Catmull-Rom, starting at virtual point 0 and ending at virtual point n+1.
Let's do this in pictures. Let's start with an 11th order Bezier curve:
And then let's just sample that at regular intervals:
We invent a 0th and n+1st point:
And then we run the Catmull-Rom procedure:
i = 0
e = points.length-4
curves = []
do {
crset = points.subset(i, 4)
curves.push(formCRCurve(crset))
} while(i++<e)
What does formCRCurve do? Good question:
formCRCurve(points: p1, p2, p3, p4):
d_start = vector(p2.x - p1.x, p2.y - p1.y)
d_end = vector(p4.x - p3.x, p4.y - p3.y)
return Curve(p2, d_start, d_end, p3)
So we see why we need those virtual points: given four points, we can form a Catmull-Rom curve from points 2 to point 3, using the tangent information we get with a little help from points 1 and 4.
Of course, we actualy want Bezier curves, not Catmull-Rom curves, but because they're the same "kind" of curve, we can freely convert between the two, so:
i = 0
e = points.length-4
bcurves = []
do {
pointset = points.subset(i, 4)
bcurves.push(formBezierCurve(pointset))
} while(i++<e)
formBezierCurve(points: p1, p2, p3, p4):
return bezier(
p2,
p2 + (p3 - p1)/6
p3 - (p4 - p2)/6
p3
)
So a Catmull-Rom curve based on points {p1,p2,p3,p4}, which passes through points p2 and p3, can be written as an equivalent Bezier curve that uses the start/control1/control2/end coodinates p2, p2 + (p3 - p1)/6, p3 - (p4 - p2)/6, and p3.
First, you have to know that there are no approximating lower degree curves that would do you justice! You are bound to introduce errors no escape. The questions then is: how to approximate such that the original and resultant curves are visually similar?
Assume your original curve is of degree n. First, subdivide it. You can subdivide a curve as many times as you want without introducing any errors. Here, the degree of each subdivisions is still n, but the geometric complexity and rate of curvature are reduced considerably. Second, you reduce the degree of each subdivision which is by now a simple shape with no high curvature that would introduce approximation errors.

Initial velocity vector for circular orbit

I'm trying to create a solar system simulation, and I'm having problems trying to figure out initial velocity vectors for random objects I've placed into the simulation.
Assume:
- I'm using Gaussian grav constant, so all my units are AU/Solar Masses/Day
- Using x,y,z for coordinates
- One star, which is fixed at 0,0,0. Quasi-random mass is determined for it
- I place a planet, at a random x,y,z coordinate, and its own quasi-random mass determined.
Before I start the nbody loop (using RK4), I would like the initial velocity of the planet to be such that it has a circular orbit around the star. Other placed planets will, of course, pull on it once the simulation starts, but I want to give it the chance to have a stable orbit...
So, in the end, I need to have an initial velocity vector (x,y,z) for the planet that means it would have a circular orbit around the star after 1 timestep.
Help? I've been beating my head against this for weeks and I don't believe I have any reasonable solution yet...
It is quite simple if you assume that the mass of the star M is much bigger than the total mass of all planets sum(m[i]). This simplifies the problem as it allows you to pin the star to the centre of the coordinate system. Also it is much easier to assume that the motion of all planets is coplanar, which further reduces the dimensionality of the problem to 2D.
First determine the magnitude of the circular orbit velocity given the magnitude of the radius vector r[i] (the radius of the orbit). It only depends on the mass of the star, because of the above mentioned assumption: v[i] = sqrt(mu / r[i]), where mu is the standard gravitational parameter of the star, mu = G * M.
Pick a random orbital phase parameter phi[i] by sampling uniformly from [0, 2*pi). Then the initial position of the planet in Cartesian coordinates is:x[i] = r[i] * cos(phi[i]) y[i] = r[i] * sin(phi[i])
With circular orbits the velocity vector is always perpendicular to the radial vector, i.e. its direction is phi[i] +/- pi/2 (+pi/2 for counter-clockwise (CCW) rotation and -pi/2 for clockwise rotation). Let's take CCW rotation as an example. The Cartesian coordinates of the planet's velocity are:vx[i] = v[i] * cos(phi[i] + pi/2) = -v[i] * sin(phi[i])vy[i] = v[i] * sin(phi[i] + pi/2) = v[i] * cos(phi[i])
This easily extends to coplanar 3D motion by adding z[i] = 0 and vz[i] = 0, but it makes no sense, since there are no forces in the Z direction and hence z[i] and vz[i] would forever stay equal to 0 (i.e. you will be solving for a 2D subspace problem of the full 3D space).
With full 3D simulation where each planet moves in a randomly inclined initial orbit, one can work that way:
This step is equal to step 1 from the 2D case.
You need to pick an initial position on the surface of the unit sphere. See here for examples on how to do that in a uniformly random fashion. Then scale the unit sphere coordinates by the magnitude of r[i].
In the 3D case, instead of two possible perpendicular vectors, there is a whole tangential plane where the planet velocity lies. The tangential plane has its normal vector collinear to the radius vector and dot(r[i], v[i]) = 0 = x[i]*vx[i] + y[i]*vy[i] + z[i]*vz[i]. One could pick any vector that is perpendicular to r[i], for example e1[i] = (-y[i], x[i], 0). This results in a null vector at the poles, so there one could pick e1[i] = (0, -z[i], y[i]) instead. Then another perpendicular vector can be found by taking the cross product of r[i] and e1[i]:e2[i] = r[i] x e1[i] = (r[2]*e1[3]-r[3]*e1[2], r[3]*e1[1]-r[1]*e1[3], r[1]*e1[2]-r[2]*e1[1]). Now e1[i] and e2[i] can be normalised by dividing them by their norms:n1[i] = e1[i] / ||e1[i]||n2[i] = e2[i] / ||e2[i]||where ||a|| = sqrt(dot(a, a)) = sqrt(a.x^2 + a.y^2 + a.z^2). Now that you have an orthogonal vector basis in the tangential plane, you can pick one random angle omega in [0, 2*pi) and compute the velocity vector as v[i] = cos(omega) * n1[i] + sin(omega) * n2[i], or as Cartesian components:vx[i] = cos(omega) * n1[i].x + sin(omega) * n2[i].xvy[i] = cos(omega) * n1[i].y + sin(omega) * n2[i].yvz[i] = cos(omega) * n1[i].z + sin(omega) * n2[i].z.
Note that by construction the basis in step 3 depends on the radius vector, but this does not matter since a random direction (omega) is added.
As to the choice of units, in simulation science we always tend to keep things in natural units, i.e. units where all computed quantities are dimensionless and kept in [0, 1] or at least within 1-2 orders of magnitude and so the full resolution of the limited floating-point representation could be used. If you take the star mass to be in units of Solar mass, distances to be in AUs and time to be in years, then for an Earth-like planet at 1 AU around a Sun-like star, the magnitude of the orbital velocity would be 2*pi (AU/yr) and the magnitude of the radius vector would be 1 (AU).
Just let centripetal acceleration equal gravitational acceleration.
m1v2 / r = G m1m2 / r2
v = sqrt( G m2 / r )
Of course the star mass m2 must be much greater than the planet mass m1 or you don't really have a one-body problem.
Units are a pain in the butt when setting up physics problems. I've spent days resolving errors in seconds vs timestep units. Your choice of AU/Solar Masses/Day is utterly insane. Fix that before anything else.
And, keep in mind that computers have inherently limited precision. An nbody simulation accumulates integration error, so after a million or a billion steps you will certainly not have a circle, regardless of the step duration. I don't know much about that math, but I think stable n-body systems keep themselves stable by resonances which absorb minor variations, whether introduced by nearby stars or by the FPU. So the setup might work fine for a stable, 5-body problem but still fail for a 1-body problem.
As Ed suggested, I would use the mks units, rather than some other set of units.
For the initial velocity, I would agree with part of what Ed said, but I would use the vector form of the centripetal acceleration:
m1v2/r r(hat) = G m1 m2 / r2 r(hat)
Set z to 0, and convert from polar coordinates to cartesian coordinates (x,y). Then, you can assign either y or x an initial velocity, and compute what the other variable is to satisfy the circular orbit criteria. This should give you an initial (Vx,Vy) that you can start your nbody problem from. There should also be quite a bit of literature out there on numerical recipes for nbody central force problems.

Most efficient way to check if a point is in or on a convex quad polygon

I'm trying to figure out the most efficient/fast way to add a large number of convex quads (four given x,y points) into an array/list and then to check against those quads if a point is within or on the border of those quads.
I originally tried using ray casting but thought that it was a little overkill since I know that all my polygons will be quads and that they are also all convex.
currently, I am splitting each quad into two triangles that share an edge and then checking if the point is on or in each of those two triangles using their areas.
for example
Triangle ABC and test point P.
if (areaPAB + areaPAC + areaPBC == areaABC) { return true; }
This seems like it may run a little slow since I need to calculate the area of 4 different triangles to run the check and if the first triangle of the quad returns false, I have to get 4 more areas. (I include a bit of an epsilon in the check to make up for floating point errors)
I'm hoping that there is an even faster way that might involve a single check of a point against a quad rather than splitting it into two triangles.
I've attempted to reduce the number of checks by putting the polygon's into an array[,]. When adding a polygon, it checks the minimum and maximum x and y values and then using those, places the same poly into the proper array positions. When checking a point against the available polygons, it retrieves the proper list from the array of lists.
I've been searching through similar questions and I think what I'm using now may be the fastest way to figure out if a point is in a triangle, but I'm hoping that there's a better method to test against a quad that is always convex. Every polygon test I've looked up seems to be testing against a polygon that has many sides or is an irregular shape.
Thanks for taking the time to read my long winded question to what's prolly a simple problem.
I believe that fastest methods are:
1: Find mutual orientation of all vector pairs (DirectedEdge-CheckedPoint) through cross product signs. If all four signs are the same, then point is inside
Addition: for every edge
EV[i] = V[i+1] - V[i], where V[] - vertices in order
PV[i] = P - V[i]
Cross[i] = CrossProduct(EV[i], PV[i]) = EV[i].X * PV[i].Y - EV[i].Y * PV[i].X
Cross[i] value is positive, if point P lies in left semi-plane relatively to i-th edge (V[i] - V[i+1]), and negative otherwise. If all the Cross[] values are positive, then point p is inside the quad, vertices are in counter-clockwise order. f all the Cross[] values are negative, then point p is inside the quad, vertices are in clockwise order. If values have different signs, then point is outside the quad.
If quad set is the same for many point queries, then dmuir suggests to precalculate uniform line equation for every edge. Uniform line equation is a * x + b * y + c = 0. (a, b) is normal vector to edge. This equation has important property: sign of expression
(a * P.x + b * Y + c) determines semi-plane, where point P lies (as for crossproducts)
2: Split quad to 2 triangles and use vector method for each: express CheckedPoint vector in terms of basis vectors.
P = a*V1+b*V2
point is inside when a,b>=0 and their sum <=1
Both methods require about 10-15 additions, 6-10 multiplications and 2-7 comparisons (I don't consider floating point error compensation)
If you could afford to store, with each quad, the equation of each of its edges then you could save a little time over MBo's answer.
For example if you have an inward pointing normal vector N for each edge of the quad, and a constant d (which is N.p for one of the vertcies p on the edge) then a point x is in the quad if and only if N.x >= d for each edge. So thats 2 multiplications, one addition and one comparison per edge, and you'll need to perform up to 4 tests per point.This technique works for any convex polygon.

Find bezier control-points for curve passing through N points

Considering the following nice solution for finding cubic Bézier control points for a curve passing through 4 points:
How to find control points for a BezierSegment given Start, End, and 2 Intersection Pts in C# - AKA Cubic Bezier 4-point Interpolation
I wonder, if there is a straightforward extension to this for making the Bézier curve pass through N points, for N > 2 and maybe N ≤ 20?
This is a really old question, but I'm leaving this here for people who have the same question in the future.
#divanov has mentioned that there's no Bezier curve passing through N arbitrary points for N >4.
I think the OP was asking how to compute the control points to join multiple bezier curves to produce a single curve that looks smooth.
This pdf will show you how to compute the control points: http://www.math.ucla.edu/~baker/149.1.02w/handouts/dd_splines.pdf
which I found on this writeup https://developer.squareup.com/blog/smoother-signatures/ from Square about how they render a smooth curve that passes through all the sampled points of a mouse drawn signature.
In general, there is no Bezier curve passing through N arbitrary points, where N > 4. One should consider curve fitting to minimize least square error between computed Bezier curve and given N data points. Which is discussed, for example, here.