Getting appropriate normals for 3d bezier curve, when the normals at the start and end are also known

202 Views Asked by At

Before I explain the rest of my post, I have tried both Frenet Frames, and Rotation Minimising Frames, and some others, but neither produces the desired result I am looking for.

Consider there are two scenarios, which I would like to both be solved:

Scenario 1:

p0: <0, 10, -20>

p1: <3, 40, 40>

p2: <6, 40, -40>

p3: <9, 10, 20>

Scenario 2:

p0: <0, 10, -20>

p1: <-13, 13, 6>

p2: <14, -5, -18>

p3: <65, 10, 20>

p0's direction vectors can be considered to be the following:

p0LookVector: (p1-p0).unit (aka, tangent, facing towards first control point)

p0RightVector: p0LookVector:Cross(<0, 1, 0>)

p0UpVector: -p0LookVector:Cross(p0RightVector) (aka, normal)

p3's direction vectors can be considered to be the following:

p3LookVector: (p2-p3).unit (facing in the opposite direction of the second control point)

p3RightVector: p3LookVector:Cross(<0, 1, 0>)

p3UpVector: -p3LookVector:Cross(p3RightVector)

I would like to point out that p0 or p3 could be rotated around which would affect both their rightVector and upVector, but not their lookVector.

A position on the line can then be calculated using the following formula:

$$B(t)=(1-t)^3P_0+3(1-t)^2tP_1+3(1-t)t^2P_2+t^3P_3, 0\leqslant t\leqslant 1.$$

With velocity (and direction) which can be calculated from:

$$B'(t)=3(1-t)^2(P_1-P_0)+6(1-t)t(P_2-P_1)+3t^2(P_3-P_2)$$

Acceleration (change in velocity) can be calculated from:

$$B''(t)=6(1-t)(P_2-2P_1+P_0)+6t(P_3-2P_2+P_1)$$

Note: I retrieved these from https://en.wikipedia.org/wiki/B%C3%A9zier_curve, so if I made a mistake let me know.

At $b(0)$ I expect the point calculated to have exactly the same lookVector, rightVector, and upVector as p0LookVector, p0RightVector, and p0UpVector.

At $b(1)$ I expect the point calculated to have exactly the same lookVector, rightVector, and upVector as p3LookVector, p3RightVector, and p3UpVector.

Notes:

  • lastPos means the same as B(t-1)
  • thisPos means the same as B(t)
  • thisDirection means the same as B'(t)
  • thisFrame is a representation of something which holds the current position using o, and the current direction using t (B(t), not unit).
  • lastFrame is a representation of something which holds the last position using o, the last direction using t (B(t-1), not unit), the last rightVector using r, and the last upVector using n.

Below are what I attempted to use to solve the problem:

Solver 1 (first attempt, always point upwards?):

lookVector = (thisPos - lastPos).unit

rightVector = lookVector:Cross(<0, 1, 0>).unit

upVector = -lookVector:Cross(rightVector).unit

Please note for the next 2 following, I was interpreting what https://pomax.github.io/bezierinfo/#pointvectors3d said on their website.

Solver 2 (Frenet Frame):

lookVector = direction.unit

nextTangent = (lookVector + B''(t)).unit

rightVector = nextTangent:Cross(lookVector).unit

upVector = rightVector:Cross(lookVector).unit

Solver 3 (Rotation Minimising Frame, note the variable names in tutorial were not clear):

v1 = thisFrame.o - lastFrame.o

c1 = v1:Dot(v1)

riL = lastFrame.r - (v1 * 2/c1 * (v1:Dot(lastFrame.r)))

tiL = lastFrame.t - (v1 * 2/c1 * (v1:Dot(lastFrame.t)))

v2 = thisFrame.t - tiL

c2 = v2:Dot(v2)

thisFrame.r = riL - (v2 * 2/c2 * (v1:Dot(lastFrame.r)))

thisFrame.n = thisFrame.r:Cross(thisFrame.t)

For scenario one, here's what it looks like for each (surface normals are shown):

Solver 1:

Solver 2:

Solver 3:

For scenario two, here's what it looks like for each (surface normals are shown):

Solver 1:

Solver 2:

Solver 3:

The reason I need it to line up with the end part, is because I'll be doing a continuous bezier curve (or spline(?)), and they need to match up between each bezier curve (with control about how they look).

As you can tell by the first solver, while it meets the end part without rotation, perfectly (which is what I want), it doesn't handle going upside down well at all, unlike the other two, which handle it perfectly.

As for Frenet Frames and Rotation Minimising Frames, they both handle going upside down well (which is what I need), but they do not line up with the end part (upVector/rightVector) which is not what I want. I had to mention Frenet Frames in case someone said, but they are least ideal out of the other two.

I've seen bezier curves generated without these problems (e.g roads with loops in games), so I have to assume there is already some kind of consensus of how to do this that I am not aware of. If you know how to solve this, please let me know!

1

There are 1 best solutions below

2
On BEST ANSWER

After computing the last RMF normal $r$ which is different from the constrained normal $n$, you can compute how much it need to be rotated about the tangent $d$ as:

$$\theta=\text{arctan2}(r\cdot n,(r\times n)\cdot\overline{d})$$

where overline means normalizing the vector to unit length.

Then rotate all RMF normals about their corresponding tangents by $\theta\cdot t$ (or any other monotonous function from $0$ to $\theta$) where $t$ is your spline parameter:

$$\cos(\theta\cdot t)\cdot r+\sin(\theta\cdot t)\cdot(\overline{d}\times r)$$