PDA

View Full Version : Mini-Challenge #4


denisT
06-09-2011, 03:41 PM
well... the Party Of Math Lovers won. :)

So if you like to practice in math try to accomplish this task:

setCommandPanelTaskMode mode:#create

start = dummy name:"start" pos:[0,0,0] boxsize:[10,10,10]
out_start = in start dummy name:"out_start" pos:[33.33,0,0] boxsize:[2.5,2.5,2.5]
setTransformLockFlags out_start #{2..9} --#all
end = dummy name:"end" pos:[100,0,0] boxsize:[10,10,10]
in_end = in end dummy name:"in_end" pos:[66.66,0,0] boxsize:[2.5,2.5,2.5]
setTransformLockFlags in_end #{2..9} --#all

start.rotation.controller = Euler_XYZ()
end.rotation.controller = Euler_XYZ()

wire = splineShape name:"wire" adaptive:on wirecolor:yellow
addnewSpline wire
addKnot wire 1 #corner #curve start.pos
addKnot wire 1 #corner #curve end.pos
setKnotType wire 1 1 #bezierCorner
in coordsys world
(
setOutVec wire 1 1 out_start.pos
setInVec wire 1 2 in_end.pos
setKnotType wire 1 2 #bezierCorner
)
updateShape wire

animateVertex wire #all
wire.controller = Transform_Script script:"matrix3 1"
setTransformLockFlags wire #all

cc = wire[#Object__Editable_Spline][#Master].controller

ps1 = point3_script ()
ps1.addNode "start" start
ps1.setExpression "start.pos"

ps2 = point3_script ()
ps2.addNode "end" end
ps2.setExpression "end.pos"

cc[#Spline_1___Vertex_1].controller = ps1
cc[#Spline_1___Vertex_2].controller = ps2

ps3 = point3_script ()
ps3.addNode "start" out_start
ps3.setExpression "start.pos"

ps4 = point3_script ()
ps4.addNode "end" in_end
ps4.setExpression "end.pos"

cc[#Spline_1___OutVec_1].controller = ps3
cc[#Spline_1___InVec_2].controller = ps4


for k=1 to 9 do
(
factor = 10*k
ghost = point name:("ghost" + k as string) axistripod:on box:on cross:off size:10 wirecolor:red
setTransformLockFlags ghost #all

ghost.rotation.controller = rc = Euler_XYZ()

rc.x_rotation.controller = Float_List()
rc.x_rotation.controller.Bezier_Float.controller = start.rotation.controller.x_rotation.controller
rc.x_rotation.controller.Available.controller = end.rotation.controller.x_rotation.controller

rc.x_rotation.controller.weight[1] = 100 - factor
rc.x_rotation.controller.weight[2] = factor

ghost.position.controller = pc = path_constraint path:wire percent:factor loop:off constantVel:on follow:on bank:off
deletekeys pc.percent.controller #allkeys
)


I made this rig using MAX features (spline shape, controllers, constraints, etc.) to hide as well as possible any math behind. The goal is to do the same but using MATH formulas only and WHEN constructs.
Play with BIG boxes position and rotation, and SMALL boxes local X position. Get the logic and ... run the Math Adventure.

PS. I hope the rig that I made for this challenge will be interesting itself. It's some sort of a combined answer on questions that I saw on this forum.

[edit: bug in the rig fixed]

denisT
06-09-2011, 04:13 PM
As you see in some situations the point objects flip on their spin (X-axis Rotation). It's a result of using Euler XYZ controller. I couldn't find any universal way how to fix this problem and stay with controllers only. The pure math (quaternions, matrix3) really solves the problem.

After getting the math implementation of this task you will see how easy to make custom scripted Loft Object for example. It's actually the reason to use Math in a situation where MAX seemingly works well.

DaveWortley
06-09-2011, 04:30 PM
Tried running your code buy got this error....

K
$Dummy:start @ [0.000000,0.000000,0.000000]
$Dummy:out_start @ [33.330002,0.000000,0.000000]
OK
$Dummy:end @ [100.000000,0.000000,0.000000]
$Dummy:in_end @ [66.660004,0.000000,0.000000]
OK
Controller:Euler_XYZ
-- Unknown property: "rotation" in undefined
$Editable_Spline:wire @ [0.000000,0.000000,0.000000]
1
1
-- Unknown property: "pos" in 62940421
OK
-- Error occurred in anonymous codeblock
-- Frame:
-- Runtime error: spline knot index out of range: 2
-- Runtime error: updateShape: curve with insufficient knots, knots added: Editable Spline
OK
Controller:Transform_Script
OK
Controller:ReferenceTarget
Controller:Point3_Script
true
true
Controller:Point3_Script
-- Unable to convert: 62940421 to type: <node>
-- Error occurred in anonymous codeblock
-- Frame:
-- f: 0.0
-- this: Controller:Point3_Script
-- NT: 0.0
-- s: 0.0
-- t: 0.0
>> MAXScript Script Controller Exception: -- Unknown property: "pos" in 62940421 <<
-- Runtime error: IScriptCtrl::SetExpression - Expression evaluation error:
Controller:Point3_Script
Controller:Point3_Script
Controller:Point3_Script
true
true
Controller:Point3_Script
true
true
Controller:Point3_Script
Controller:Point3_Script
-- Error occurred in k loop
-- Frame:
-- ghost: $ghost1
-- factor: 10
-- rc: Controller:Euler_XYZ
-- k: 1
-- PC: undefined
-- Unknown property: "rotation" in 62940421
OK


I'm on max 9

DaveWortley
06-09-2011, 04:32 PM
Scrub that, needed to create an object first ;)

denisT
06-09-2011, 05:02 PM
Tried running your code buy got this error....

K
$Dummy:start @ [0.000000,0.000000,0.000000]
$Dummy:out_start @ [33.330002,0.000000,0.000000]
OK
$Dummy:end @ [100.000000,0.000000,0.000000]
$Dummy:in_end @ [66.660004,0.000000,0.000000]
OK
Controller:Euler_XYZ
-- Unknown property: "rotation" in undefined

...




i fixed the bug. thanx!

elT
06-09-2011, 05:18 PM
Is path constraint allowed or is that to be done with math too?

denisT
06-09-2011, 05:24 PM
Is path constraint allowed or is that to be done with math too?

MATH and PRS contollers only :)

elT
06-09-2011, 05:27 PM
INTERESTING! :)

Another question (perhaps dumb, sorry, Mom always said I'm SPECIAL ;) )
PRS controllers as in no Scripted Controllers?

denisT
06-09-2011, 05:29 PM
INTERESTING! :)

Another question (perhaps dumb, sorry, Mom always said I'm SPECIAL ;) )
PRS controllers as in no Scripted Controllers?

Do you really need scripted controllers? I don't think so...

elT
06-09-2011, 05:35 PM
:blush:
OK then, seems like I'm not nearly ready to take this on but will definitely follow the topic.
Thanks for doing this and sorry for cluttering up the thread a bit.

denisT
06-09-2011, 06:02 PM
:blush:
OK then, seems like I'm not nearly ready to take this on but will definitely follow the topic.
Thanks for doing this and sorry for cluttering up the thread a bit.
Hey! I don't want to lose players!
Use whatever you want: scripts controllers, ExposeTm nodes, etc. I just want to say that is not necessary.

elT
06-09-2011, 07:46 PM
OK. I'll do my best then. :)

MatanH
06-09-2011, 07:53 PM
I knew about the when constructor but never had any need to use it.
Can anyone hint me on how to make it work without the handleAt attribute?
Personally I would use an expression controller to do these things purely in math because that way the rig will be max script free which gives much faster results and it will still be pure math as well.
Here goes:

(
max create mode

local numSegs = 10
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: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]
)

when transform ctrls changes handleAt:#redrawViews do (
with redraw off (
with animate off (
local pts = for o in ctrls collect o.pos
for i = 1 to numSegs do (
local t = (1. * i / (numSegs + 1))
local p = getBezierInterpolation pts t

ghosts[i].pos = p

local x, y, z
if i == 1 then
x = normalize (ghosts[i].pos - start.pos)
else
x = normalize (ghosts[i].pos - ghosts[i - 1].pos)
z = normalize [0,0,1]
y = normalize (cross z x)
z = normalize (cross x y)

local m = matrix3 x y z p
local a = start.rotation.controller.x_rotation * (1. - t) + end.rotation.controller.x_rotation * t
preRotateX m a

ghosts[i].transform = m
)
)))

move start [0,0,0]
forceCompleteRedraw()
)

denisT
06-09-2011, 08:24 PM
I knew about the when constructor but never had any need to use it.
Can anyone hint me on how to make it work without the handleAt attribute?
Personally I would use an expression controller to do these things purely in math because that way the rig will be max script free which gives much faster results and it will still be pure math as well.
Here goes:

(
max create mode

local numSegs = 10
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: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]
)

when transform ctrls changes handleAt:#redrawViews do (
with redraw off (
with animate off (
local pts = for o in ctrls collect o.pos
for i = 1 to numSegs do (
local t = (1. * i / (numSegs + 1))
local p = getBezierInterpolation pts t

ghosts[i].pos = p

local x, y, z
if i == 1 then
x = normalize (ghosts[i].pos - start.pos)
else
x = normalize (ghosts[i].pos - ghosts[i - 1].pos)
z = normalize [0,0,1]
y = normalize (cross z x)
z = normalize (cross x y)

local m = matrix3 x y z p
local a = start.rotation.controller.x_rotation * (1. - t) + end.rotation.controller.x_rotation * t
preRotateX m a

ghosts[i].transform = m
)
)))

move start [0,0,0]
forceCompleteRedraw()
)

Great! One thing is missed. In my rig the ghost objects distributed by length (not by path). See the path constraint's constantVel param...

PS. there is a trick:

-- instead of
for k=1 to 10 do d_float = 1.*k/10
-- do
for k=1. to 10 do d_float = k/10

denisT
06-09-2011, 08:27 PM
I knew about the when constructor but never had any need to use it.
Can anyone hint me on how to make it work without the handleAt attribute?


just remove it.. where is a problem?

denisT
06-09-2011, 08:35 PM
(
...
if i == 1 then
x = normalize (ghosts[i].pos - start.pos)
else
x = normalize (ghosts[i].pos - ghosts[i - 1].pos)
z = normalize [0,0,1]
y = normalize (cross z x)
z = normalize (cross x y)
...
)

bezier spline tangent might be calculated more accurate.

denisT
06-09-2011, 08:44 PM
another thing is to try not use the start's and end's rotation controllers. try to use their transforms.
in this case we will not be limited by type of assigned controllers. Our control nodes (start, end, out_start, and in_end) might be driven by script controller, or orientation_constraint for example...

MatanH
06-09-2011, 08:45 PM
just remove it.. where is a problem?

It might be my home computer, it's 6 years old now :shrug:

denisT
06-09-2011, 08:48 PM
It might be my home computer, it's 6 years old now :shrug:

might be... in this case x-axis transform has to be odd. y- and z- have to work right.

MatanH
06-09-2011, 10:29 PM
here is an improved version, still haven't managed to figure out how to do the constant velocity yet. I tried Catmull-Rom and Hermite spline interpolations but it doesn't help my situation..

(
max create mode

local numSegs = 10
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: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]
)

when transform ctrls changes handleAt:#redrawViews do (
with redraw off (
with animate off (
local pts = for o in ctrls collect o.pos

for i = 1. to numSegs do (
local t = i / (numSegs + 1)
local p1 = getBezierInterpolation pts t
local p2 = getBezierInterpolation pts (t + 0.0001)


local x, y, z
x = normalize (p2 - p1)
z = normalize [0,0,1]
y = normalize (cross z 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()
)

denisT
06-09-2011, 10:55 PM
local startXRot = (quatToEuler start.transform.rotationpart).x
local endXRot = (quatToEuler end.transform.rotationpart).x


this is not right. Object transform is absolute (in world space coordinate system). we have to interpolate in relative (local) space. The best representation is Angle-Axis. Where the axis is object's local space and the angle is spin (x-rotation in our case).

the tangent is accurate now.

constant velocity... it's kinda expensive to get. but .. look how to calculate bezier spline length.

denisT
06-09-2011, 11:08 PM
local startXRot = (quatToEuler start.transform.rotationpart).x
local endXRot = (quatToEuler end.transform.rotationpart).x


this is not right. Object transform is absolute (in world space coordinate system). we have to interpolate in relative (local) space. The best representation is Angle-Axis. Where the axis is object's local space and the angle is spin (x-rotation in our case).


i've thought a little... only thing that we need to do is to find the right angles application order.

Ruramuq
06-10-2011, 01:00 AM
no constant param yet, but

something very inconsistent, happens with max reference system:

the callback is getting transform changes from the tangents when these are not being transformed .
this does not happen by transforming the cont5rol trhough mxs: $ruh002.pos+=20

uncommenting line 33 to force a CTRL node update, avoids this redundancy

another problem is the undo that is unable to capture all changes depending on what node was updated last,
some of these issues may be fixed if I link all through the ref system.. but it's not clean.
I've had issues like this with the ref system in max, but can't understand where is the origin..

( --mxs curves test cgt=985724
if ::ru_call==undefined then
(
::ru_call=on
local n=9.--param

local prop=#([-70,50,0],[-30,-80,0],[-30,50,0],[-70,-80,0],yellow,yellow,orange,orange)
local nodes=for n in 1 to 4 collect (point name:(uniqueName "ruh") box:true size:5 pos:prop[n] wirecolor:(prop[n+4]))
local ctrls=for o in nodes collect NodeTransformMonitor node:o forwardTransformChangeMsgs:true
local subs=for o=1 to n collect point wirecolor:white size:5 cross:false box:true name:#ruhs
for c=1 to 4 do custAttributes.add nodes[c] (
attributes linkage(
parameters main
(
num type:#integer
offset type:#point3
)
)
)
for n=1 to 4 do nodes[n].num=n
nodes[3].offset=[ 40,0,0]
nodes[4].offset=[-40,0,0]

--

fn bspline subs ctrls nodes obj=
(
-- print obj.num
if obj.num<3 then
(
format "%: CONTROL is dragged\n" (timestamp())
ctrls[obj.num+2].node.transform=(transMatrix ctrls[obj.num+2].node.offset)*obj.transform
-- ctrls[obj.num].node.transform=ctrls[obj.num].node.transform -- HACK to force UNDO
)
else
(
format "%: TANGENT is dragged\n" (timestamp())
--MAX gizmo/controllers // ref messages?
ctrls[obj.num-2].node.offset=(obj.pos*inverse ctrls[obj.num-2].node.transform)*[1,0,0]
ctrls[obj.num].node.transform=ctrls[obj.num-2].node.transform*(transMatrix ctrls[obj.num-2].node.offset)

-- ctrls[obj.num-2].node.transform=ctrls[obj.num-2].node.transform -- HACK to force UNDO
)
for s=1. to n do subs[s].pos=
(
t=s/(n+1)
ctrls[1].node.pos * (1-T)^3 +
ctrls[3].node.pos * 3*(1-T)^2*T +
ctrls[4].node.pos * 3*(1-T)*T^2 +
ctrls[2].node.pos * T^3
)
)
bspline subs ctrls nodes nodes[1]
when transform nodes change obj do bspline subs ctrls nodes obj id:#ru_call
)
else
(
deleteAllChangeHandlers id:#ru_call
globalVars.remove #ru_call
delete $ruh*
)
)

Ruramuq
06-10-2011, 06:35 AM
here it is w constant length stuff added| not optimized.
I tried to modify one of the controls without triggering the callback again, but it seems there is no way, it's not evident buy annoys me.
conding using only Change Handlers doesn't sound like a good idea IMO..

( --mxs curves test cgt=985724
if ::ru_call==undefined then
(
local n=9.--param
local prop=#([-70,50,0],[-30,-80,0],[-30,50,0],[-70,-80,0],yellow,yellow,orange,orange)
::ru_call=#(
for n in 1 to 4 collect (point name:(uniqueName "ruh") box:true size:5 pos:prop[n] wirecolor:(prop[n+4])),
for o=1 to n collect point wirecolor:white size:5 cross:false box:true name:#ruhs
)
for c=1 to 4 do custAttributes.add ru_call[1][c] (
attributes linkage(
parameters main
(
num type:#integer
offset type:#point3
)
)
)
for n=1 to 4 do ru_call[1][n].num=n
ru_call[1][3].offset=[ 40,0,0]
ru_call[1][4].offset=[-40,0,0]
--
fn bspline subs nodes obj =
(
--format "num:% time: %\n" obj.num (timestamp())
if obj.num<3 then nodes[obj.num+2].transform=(transMatrix nodes[obj.num+2].offset)*obj.transform
else
(
nodes[obj.num].offset=(obj.pos*inverse nodes[obj.num-2].transform)*[1,0,0]
nodes[obj.num].transform=(transMatrix nodes[obj.num].offset)*nodes[obj.num-2].transform
)
leng=0
prev=ru_call[1][1].pos
local a=nodes[1].pos,b=nodes[3].pos,c=nodes[4].pos,d=nodes[2].pos
for t=0. to 1 by 1e-2 do
(
new=
(
a * (1-T)^3 +
b * 3*(1-T)^2*T +
c * 3*(1-T)*T^2 +
d * T^3
)
leng+=distance prev new
prev=new
)
leng/=10.
leng2=0
idx=1
prev=ru_call[1][1].pos
for t=0 to 1 by 1e-3 do
(
new=
(
a * (1-T)^3 +
b * 3*(1-T)^2*T +
c * 3*(1-T)*T^2 +
d * T^3
)
leng2+=distance prev new
prev=new
if leng2 >= leng and idx<10 do
(--format "leng: % leng2:%\n" leng leng2
::ru_call[2][idx].pos=new
idx+=1
prev=new
leng2=0
)
)
)
bspline ::ru_call[2] ::ru_call[1] ru_call[1][1]
when transform ::ru_call[1] change id:#ru_call obj do bspline ::ru_call[2] ::ru_call[1] obj

)
else
(
deleteAllChangeHandlers id:#ru_call
globalVars.remove #ru_call
delete $ruh*
)
)

lo
06-10-2011, 09:35 AM
Wow, you don't check this forum for one day, and this is what happens.
Looks really fun, great work everyone.

TzMtn: I get weird results without handleAt:#redrawViews as well, on a new computer. I am not sure why it happens though.

denisT
06-10-2011, 04:08 PM
conding using only Change Handlers doesn't sound like a good idea IMO..

what is the better idea? use whatever you want, and we will compare.

MatanH
06-10-2011, 04:34 PM
Wow, you don't check this forum for one day, and this is what happens.
Looks really fun, great work everyone.

TzMtn: I get weird results without handleAt:#redrawViews as well, on a new computer. I am not sure why it happens though.

maybe it's the max version, I have 2008 here

lo
06-10-2011, 04:39 PM
maybe it's the max version, I have 2008 here

Seems you are right, previously tested on max 2009 and it didn't work. On max 2012 trial it works fine.

MatanH
06-11-2011, 01:04 PM
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...

denisT
06-11-2011, 05:06 PM
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?).

1) 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)

Ruramuq
06-11-2011, 05:52 PM
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..

denisT
06-11-2011, 05:58 PM
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?

Ruramuq
06-11-2011, 06:25 PM
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

denisT
06-11-2011, 06:36 PM
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

x axis is a tangent. does it flip? give me start and end transforms please?

Ruramuq
06-11-2011, 07:39 PM
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..

denisT
06-11-2011, 07:50 PM
mm, reset/execute the code:
select the 'end' object and rotate it's local X axis:
more than 240º == flip
less than -120 == flip

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

MatanH
06-13-2011, 12:06 PM
it's a known problem. i give a time to anyone to find a solution... ;)

Could you give us a hint, this is really interesting to me but it looks like the discussion is dead

denisT
06-13-2011, 08:39 PM
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.

MatanH
06-14-2011, 07:04 AM
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()
)

CGTalk Moderation
06-14-2011, 07:04 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.