[Closed] Mini-Challenge #4
here is a my version with constant velocity.
but there must be a better way to distribute points along a parametric bezier spline without an incremental loop.
(
max create mode
local numSegs = 10
local step = 0.001
local up = [0,0,1]
local start = point name:"start" pos:[0,0,0] size:10 box:true cross:false wirecolor:green
local end = point name:"end" pos:[100,0,0] size:10 box:true cross:false wirecolor:green
local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local ctrls = #(start, outStart, inEnd, end)
setTransformLockFlags #(outStart, inEnd) #{2..9}
outStart.parent = start
inEnd.parent = end
local ghosts = for i = 1 to numSegs collect
point name:("ghost" + i as string) axistripod:on box:on cross:off size:7 wirecolor:red
setTransformLockFlags ghosts #all
fn getBezierInterpolation p t =
(
(1. - t) ^ 3 * p[1] + 3 * (1. - t) ^ 2 * t * p[2] + 3 * (1. - t) * t ^ 2 * p[3] + t ^ 3 * p[4]
)
fn bezierSplineLength p start end step =
(
local lastPt = getBezierInterpolation p start
local result = 0
for t = start + step to end by step do (
local newPt = getBezierInterpolation p t
result += distance newPt lastPt
lastPt = newPt
)
result
)
when transform ctrls changes handleAt:#redrawViews do (
if not mouse.buttonStates[1] then (
with redraw off (
with animate off (
local pts = for o in ctrls collect o.pos
local d1 = distance pts[1] pts[2]
local d2 = distance pts[3] pts[4]
local s = (bezierSplineLength pts 0.0 1.0 step) / numSegs
local last = 0.0
for i = 1. to numSegs do (
local t = last + step
while ((bezierSplineLength pts last t step) < s) do
t += step
t = last = amin t 1.0
local p1 = getBezierInterpolation pts t
local p2 = getBezierInterpolation pts (t + step)
local x, y, z
x = normalize (p2 - p1)
y = normalize (cross up x)
z = normalize (cross x y)
local m = matrix3 x y z p1
local startXRot = (quatToEuler start.transform.rotationpart).x
local endXRot = (quatToEuler end.transform.rotationpart).x
local a = startXRot * (1. - t) + endXRot * t
preRotateX m a
ghosts[i].transform = m
)
))))
move start [0,0,0]
forceCompleteRedraw()
)
Now I need to look into what Denis said about the local rotation…
let’s forget about constant velocity for a while…
i made some modification in TzMtN’s code to add a little more Math in it
(
max create mode
delete objects
local numSegs = 10
local start = point name:"start" pos:[0,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
local end = point name:"end" pos:[100,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local ctrls = #(start, outStart, inEnd, end)
setTransformLockFlags #(outStart, inEnd) #{2..9}
outStart.parent = start
inEnd.parent = end
local ghosts = for i = 1 to numSegs collect
point name:("ghost" + i as string) axistripod:on box:on cross:off size:10 wirecolor:red
setTransformLockFlags ghosts #all
fn getBezierInterpolation p t =
(
(1. - t)^3 * p[1] + 3 * (1. - t)^2 * t * p[2] + 3 * (1. - t) * t^2 * p[3] + t^3 * p[4]
)
fn getBezierDerivative p t =
(
-3 * (1. - t)^2 * p[1] + (3 * (1. - t)^2 - 6 * (1. - t)*t) * p[2] + (6 * (1. - t)*t - 3 * t^2) * p[3] + 3 * t^2 * p[4]
)
fn updateGhosts =
(
local pts = for o in ctrls collect o.pos
for i = 1. to numSegs do
(
local t = i / (numSegs + 1)
local p = getBezierInterpolation pts t
local q = slerp start.transform.rotation end.transform.rotation t
local x, y, z
x = normalize (getBezierDerivative pts t) -- normalized tangent
z = (q as matrix3).row3
y = normalize (cross z x)
z = normalize (cross x y)
local m = matrix3 x y z p
with animate off ghosts[i].transform = m
)
)
updateGhosts()
when transform ctrls changes do updateGhosts()
)
I use TzMtN’s code because it’s very similar to my (TzMtN, do you mind?).
- I added mathematically correct method of the tangent calculation. It’s the first derivative of the parametric equation of the Cubic Bézier Curve.
2) I use slerp function to interpolate between start and end rotations.
3) Final matrix is built on tangent and Z of interpolated quaternion (as matrix)
there is flipping in the local X axis
slerp,matrix quats, the usual problem, especially for rigging not limited to 180º
the best is to manipulate the value directly somehow…
it’s a known problem. i give a time to anyone to find a solution…
ps. you are talking about Z – flip, aren’t you?
x axis flip, would be. well, the others too, but are less evident.
I think it will always happen with slerp or any math based on world coordinates
mm, reset/execute the code:
select the ‘end’ object and rotate it’s local X axis:
more than 240º == flip
less than -120 == flip
in the original example with path constraint, you create rotation list to blend the rotation x axis.
in your code, you’re using slerp. that must be… derivatives can’t be the problem…
that’s expected. X-axis stays right. Y and Z change. so we have to find a way to fix it… or find another way how to calculate X-rotation
Could you give us a hint, this is really interesting to me but it looks like the discussion is dead
well, there is a hint.
I’m using slerp function to get quaternion interpolation. It gives me spherical linear interpolation. But do I really need it if later I use only one component of the resulting quaternion? Could we do anything simpler?
Because the discussion is dying I’m leaving the correct method of constant velocity calculation open.
So here is a version using a point3 slerp function, but it still obviously flips after 180 deg.
I think we must work with the angles themselves somehow, but how do we do that independent of the rotation controller?
(
max create mode
delete objects
local numSegs = 10
local start = point name:"start" pos:[0,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
local end = point name:"end" pos:[100,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
local ctrls = #(start, outStart, inEnd, end)
setTransformLockFlags #(outStart, inEnd) #{2..9}
outStart.parent = start
inEnd.parent = end
local ghosts = for i = 1 to numSegs collect
point name:("ghost" + i as string) axistripod:on box:on cross:off size:10 wirecolor:red
setTransformLockFlags ghosts #all
fn getBezierInterpolation p t =
(
(1. - t)^3 * p[1] + 3 * (1. - t)^2 * t * p[2] + 3 * (1. - t) * t^2 * p[3] + t^3 * p[4]
)
fn getBezierDerivative p t =
(
-3 * (1. - t)^2 * p[1] + (3 * (1. - t)^2 - 6 * (1. - t)*t) * p[2] + (6 * (1. - t)*t - 3 * t^2) * p[3] + 3 * t^2 * p[4]
)
fn point3Slerp p t =
(
if distance p[1] p[2] < 0.0001 then
p[1]
else (
local omega = acos (dot p[1] p[2])
(sin ((1 - t) * omega)) / (sin omega) * p[1] + (sin (t * omega)) / (sin omega) * p[2]
)
)
fn updateGhosts =
(
local pts = for o in ctrls collect o.pos
for i = 1. to numSegs do
(
local t = i / (numSegs + 1)
local p = getBezierInterpolation pts t
local x, y, z
x = normalize (getBezierDerivative pts t)
z = point3Slerp #(start.dir, end.dir) t
y = normalize (cross z x)
z = normalize (cross x y)
local m = matrix3 x y z p
with animate off ghosts[i].transform = m
)
)
updateGhosts()
when transform ctrls changes do
updateGhosts()
)