PDA

View Full Version : curveControl using the wrong values


Pacermike
06-24-2011, 10:19 AM
Hey guys,

A little while ago I was trying to mathematically convert the tangent handle values of a Bezier key in the Track View to point2 values so I could reproduce the curve in Max's curveControl UI element (back in this thread (http://forums.cgsociety.org/showthread.php?f=98&t=986723))

Well I got the math figured out and was able to find the correct values for the curveControl UI. The problem is that even though I'm giving the curveControl the right values... it's refusing to use them.

http://dl.dropbox.com/u/3850172/Control-O-Tron/curveControl%20problem.jpg

(http://dl.dropbox.com/u/3850172/Control-O-Tron/curveControl%20test.max)Here is a link to the Max scene. (http://dl.dropbox.com/u/3850172/Control-O-Tron/curveControl%20test.max) (Made in Max 9, should work on anything 9 and later)

Here is a link to the script. (http://dl.dropbox.com/u/3850172/Control-O-Tron/bezier_to_curveControl.ms)

First I apologize for the script. It's just a test right now so I think my code is probably pretty embarrassing. Most of it is the just the curveControl script from the help file, then I added in some stuff to grab values off the Bezier curve.

Anyway, open curveControl test.max and select the yellow Test Box then evaluate the script. The curveControl pops up with three curves. These curves represent Bezier Float controllers in channels 2, 3 and 4 of the Morpher Modifier in the yellow Test Box's stack. These channels each have List Controllers with a Script Controller and a Bezier Float named "Curve". The controllers named "Curve" are what the curves in the curveControl are supposed to look like. I have all the outTangent values printing out to the Listener right now the first time you evaluate the script. Then if you press the Print Properties button on the curveControl it'll print the values that it's actually using in the Listener.

Can anybody help me figure out why the values keep coming out different than the ones that I give it to use?

Pacermike
06-25-2011, 01:51 AM
http://dl.dropbox.com/u/3850172/Control-O-Tron/sadPuppy.jpg
.......................................................

denisT
06-25-2011, 03:50 AM
there is no one-to-one correspondence between track view and curve control bezier curves. their tangents make different sense. what do you want to get? trackVeiw to curveControl or control to trackView?

Furball89
06-25-2011, 06:00 AM
Hey!
I am not sure about the intangent and outtangent, but you should use insertPoint function instead of specifying the numpoints and then using a loop to add new points (because you are trying to add points to an array which has a size of 2 IMO! so even if you hardcode the values for intangent and outtangent the curve control will show wrong values!) i.e the problem is in this bit of the code :

crv.numPoints = (numKeys ($.modifiers[#morpher][findMorphListCont[i]].curve.controller))
for j=1 to crv.numPoints do(

But it works if you use insertpoint function instead, only you have to figure out how to convert the intangents and outtangents from curve editor to curve control ! (i will be needing this too for a project, i wanted to write script to copy the controllers from loft deformations to certain property of an object (which is in another max file!).
however i cant figure out how max stores the in/out Tangents, For example, for a 2 point curve as in the image below max stores the outtangent for the first point as 0.1875 :S instead of 1 (i always thought it was storing the arc tangent, but something else is going on here :S)
http://img35.imageshack.us/img35/5218/curvecontrol.jpg

local findMorphListCont = (
numTargets = 1
theMorpher = $.modifiers[#morpher]
while WM3_MC_Hastarget theMorpher numTargets do numTargets = numTargets + 1
for i = 2 to numTargets where ( ((classof theMorpher[i].controller) == float_list) and
(hasproperty theMorpher[i].controller #curve) ) collect i
)
local findMorphCurves = (
for i = findMorphListCont collect (
for j in 1 to findMorphListCont.count where $.modifiers[#morpher][i][j].name == "curve" collect j
)
)
fn getKeyValue theKey theFrameCount theNewFrameCount timeRange = (
theXValue = ((theKey.time.frame - timeRange.start) / theFrameCount) * theNewFrameCount
theYValue = theKey.value
return [theXValue,theYValue]
)


on uTestCurveControl open do(
zoom cc_test #all
local colors = #(red, green, blue)
local styles = #(#solid, #dash, #dot, #dashDot, #dashDotDot, #null, #insideFrame)
local num = cc_test.numCurves

for i = 1 to num do (
theCurve = cc_test.curves[i]
theCurveTimeRange = getTimeRange $.modifiers[#morpher][findMorphListCont[i]].curve.controller

theFrameCount = abs(theCurveTimeRange.start.frame - theCurveTimeRange.end.frame)
theNewFrameCount = abs(cc_test.x_range.x - cc_test.x_range.y)

numberOfKeys = numkeys ($.modifiers[#morpher][findMorphListCont[i]].curve.controller)
theFirstKey = getkey $.modifiers[#morpher][findMorphListCont[i]].curve.controller 1
theLastKey = getkey $.modifiers[#morpher][findMorphListCont[i]].curve.controller numberOfKeys

theFirstPoint = ccPoint (getKeyValue theFirstKey theFrameCount theNewFrameCount theCurveTimeRange) (getIT theFirstKey theScaleFactor) (getOT theFirstKey) Bezier:true corner:true
theLastPoint = ccPoint (getKeyValue theLastKey theFrameCount theNewFrameCount theCurveTimeRange) (getIT theLastKey theScaleFactor) (getOT theLastKey) Bezier:true corner:true

setPoint theCurve 1 theFirstPoint
setPoint theCurve 2 theLastPoint

for j = 2 to (numberOfKeys - 1) do (
theKey = getkey $.modifiers[#morpher][findMorphListCont[i]].curve.controller j
theKeyValue = (getKeyValue theKey theFrameCount theNewFrameCount theCurveTimeRange)
curvePoint = ccPoint (getKeyValue theKey theFrameCount theNewFrameCount theCurveTimeRange) (getIT theKey) (getOT theKey) Bezier:true corner:true
insertPoint theCurve j curvePoint
)
)

Pacermike
06-25-2011, 08:35 AM
Haha, my super sad face worked! :)

@Furball89
Oh no, you're right! That outTangent is 45 degrees, its value should be 1... but it's 1.875. That kind of changes everything. Now I have no idea how to convert those values over to a point2.

One question on your code:
theFirstPoint = ccPoint (getKeyValue theFirstKey theFrameCount theNewFrameCount theCurveTimeRange) (getIT theFirstKey theScaleFactor) (getOT theFirstKey) Bezier:true corner:true
theLastPoint = ccPoint (getKeyValue theLastKey theFrameCount theNewFrameCount theCurveTimeRange) (getIT theLastKey theScaleFactor) (getOT theLastKey) Bezier:true corner:true

I think getIT and getOT are referring to functions but I don't see them anywhere in the code. Also, theScaleFactor doesn't seem to be defined either. Am I missing something? Can I just use the formula I was using in the original code for these functions?

By the way, thank you so much for your code. It is a vast improvement over my own :bowdown:

@DenisT
I'm trying to get trackVeiw to curveControl. I thought I had a formula worked out that would convert the TrackView curve's .inTangent and .inTangentLength to an [x,y] point2 value I could use for the curveControl in/out tangents, but like Furball pointed out it looks like those numbers aren't what I thought they were.

Thanks for all the help so far!

Furball89
06-25-2011, 08:52 AM
Hm yea, thats the problem bit, i can't figure out the getIT and getOT functions yet! (the code i posted is able to insertpoints in the curve (for eg, if you hard code [-10,-10] and [10,10] for in / out tangent respectively it will create the tangents in the curve control, but if you do the same with the previous code it won't create the outtangent for some points, as shown in the figure you posted earlier, so i re-scripted it using the maxscript helpfile!) my getIT and getOT functions are flawed (they only work if the time range is not scaled), sadly, the problem is, if you scale the timerange then both x and y coordinates of the in/out tangents will change (in the curve editor) the new x coordinate is easy to find, i think your code finds that bit correctly, but the y coordinate is the problem! unless you can somehow figure out what the in/out tangent values mean! i tried every possible way to look at this but 0.1875 makes no sense! also you cannot have and in/out tangent weight of more than 1 in the curve control (bound by the previous key, ofcourse), where as in the curve editor you can drag the handle ahead of the next key for infinity!

Pacermike
06-25-2011, 09:04 AM
You're right about the tangents in the curveControl being constrained between X 0 and X 100. It's kind of a pain but I could live with that limitation.

Also, I'm messing around in the Track View right now too, trying to figure out if an outTangent of 1.00 is ever equal to 45 degrees and I can't figure it out either... so weird.

I will say this, the curveControl is remarkably hard to work with. I kind of think I hate it. For rigging and animation though curves are SO important. I wish Max had a more convenient way to incorporate curves in to scripted tools and UI :(

Furball89
06-25-2011, 02:16 PM
you could however compute the tangents yourself using the controller's value at the next or previous frame, some thing along the lines :-


fn remapKey theKey timeRange = (
theOldFrameCount = abs(timeRange.start.frame - timeRange.end.frame)
--theNewFrameCount = abs(cc_test.x_range.x - cc_test.x_range.y)
theNewFrameCount = 100
theXValue = ((theKey.time.frame - timeRange.start.frame) / theOldFrameCount) * theNewFrameCount
theYValue = theKey.value
return [theXValue,theYValue]
)
fn getIT theController previousKey thisKey timeRange = (
remappedPreviousKey = remapKey previousKey timeRange
remappedThisKey = remapKey thisKey timeRange

newFrameCount = abs(remappedThisKey.x - remappedPreviousKey.x)
theXValue = thisKey.inTangentLength * newFrameCount

thisValue = at time (thisKey.time) theController.value
previousValue = at time (thisKey.time - 1) theController.value

theYValue = theXValue * (previousValue - thisValue)

return [-theXValue,theYValue]
)
fn getOT theController thisKey nextKey timeRange= (
remappedThisKey = remapKey thisKey timeRange
remappedNextKey = remapKey nextKey timeRange

newFrameCount = abs(remappedThisKey.x - remappedNextKey.x)
theXValue = thisKey.outTangentLength * newFrameCount

thisValue = at time (thisKey.time) theController.value
nextValue = at time (thisKey.time + 1) theController.value

theYValue = theXValue * (nextValue - thisValue)

return [theXValue,theYValue]
)
fn getCurvePoint theController keyIndex timeRange = (
if ((keyIndex - 1) > 0) then
previousKey = getkey theController (keyIndex - 1)
else
previousKey = -1

format "PreviousKey = %\n" PreviousKey

thisKey = getkey theController keyIndex

if ((keyIndex + 1) <= (numkeys theController)) then
nextKey = getkey theController (keyIndex + 1)
else
nextKey = -1

thisKey = getkey theController keyIndex
remappedKey = remapKey thisKey timeRange

IT = [0,0]
OT = [0,0]
if (previousKey != -1) then
IT = (getIT theController previousKey thisKey timeRange)

if (nextKey != -1) then
OT = (getOT theController thisKey nextKey timeRange)

return (ccPoint remappedKey IT OT Bezier:true corner:true)
)
for i = 1 to num do (
theCurve = cc_test.curves[i]
theController = $.modifiers[#morpher][findMorphListCont[i]].curve.controller
theCurveTimeRange = getTimeRange theController

theOldFrameCount = abs(theCurveTimeRange.start.frame - theCurveTimeRange.end.frame)
theNewFrameCount = abs(cc_test.x_range.x - cc_test.x_range.y)

numberOfKeys = numkeys ($.modifiers[#morpher][findMorphListCont[i]].curve.controller)

setPoint theCurve 1 (getCurvePoint theController 1 theCurveTimeRange)
setPoint theCurve 2 (getCurvePoint theController numberOfKeys theCurveTimeRange)

for j = 2 to (numberOfKeys - 1) do
insertPoint theCurve j (getCurvePoint theController j theCurveTimeRange)
)

still some bug with scaling, works with non scaled curve though!

denisT
06-25-2011, 02:18 PM
I know that mxs help doesn't explain well how in/out tangents of track view and curve control bezier curves work.
So I'll try to clear it up.

Track View curve (tvc):
inTangent and inTangentLength define in-tangent handle position
outTangent and outTangentLength define out-tangent

inTangentLength(outTangentLength) is a time distance of handle from current key to previous(next) key. Full distance is 1.0, half 0.5...
So if the distance 16 frames and handle on half way to the adjacent key the inTangentLength is 0.5, which corresponds to (current_key_time - 16*0.5) handle X position.
in-tangent handle Y position is ( inTangent*inTangentLength )

Curve Control curve (ccc):
inTangent and outTangent are poitn2 values. The values in curveControl units, they are relative positions to point value.
So if point's value [0.5, 0.5] and its in-tangent value is [-0.1, 0.1], the in-tangent handle position is [0.5, 0.5] + [-0.1, 0.1] = [0.4, 0.6] ( value + inTangent )

How to translate ccc to tvc?
current ccc point is (value)
inTangent of ccc point is (inTangent)
previous ccc point is (prev_value)

inTangentLength of tvc point equals ( inTangent.x / (value.x - prev_value.x) )
inTangent equals ( inTangent.y / inTangentLength )

EDIT:
i didn't have max open at the moment when i was writing the post. There are some mistakes that I made. But I'm to lazy today to fix them. The main idea is right. The snippet below proves it ;)

Furball89
06-25-2011, 06:38 PM
in-tangent handle Y position is ( inTangent*inTangentLength )

@ denisT:
Can you please explain this with an example :blush:, it would be great if you could explain why the outtangent for 45 degree angle is 0.1875 (see the image in my previous post) ? i.e. :

(
x = 0.5 --outTangentLength
y = x * 0.1875 --outTangent * outTangentLength
print (atan2 y x) --output is 10.6197 !?!?!
)


Thanks :)

denisT
06-25-2011, 06:46 PM
i made a snippet to show how to bind curve control curve and track view curve.

try (destroydialog CurveControlToTrack) catch()
rollout CurveControlToTrack "Curve Control to Track" width:404 height:312
(
CurveControl ccc width:400 height:300 numCurves:1 pos:[2,2] \
x_range:[0,100] y_range:[-100,100] scrollValues:[-100,100] commandMode:#move_xy \
asPopup:false

local track_node, track
on CurveControlToTrack close do try
(
trackviews.close "Curve Control To Track"
deleteTrackViewNode track_node
-- if not (trackviews.isOpen "Curve Control To Track") do trackviews.delete "Curve Control To Track"
)
catch()

local const = 0.25 * (1.0 - 0.25) -- THE MAGIC NUMBER
fn updateTrackCurve curve:1 point: type: event: = if track != undefined and track.keys.count == 3 do
(
cc = ccc.curves[curve]
-- curve control cc points
p1 = cc.points[1]
p2 = cc.points[2]
p3 = cc.points[3]
-- track curve keys
k1 = track.keys[1]
k2 = track.keys[2]
k3 = track.keys[3]
-- format "%: % % %\n" event curve point type

case event of
(
#selChanged:
(
for k=1 to 3 do track.keys[k].selected = cc.points[k].selected
)
)

k1.outTangentLength = p1.outTangent.x/(p2.value.x - p1.value.x)
k1.outTangent = p1.outTangent.y*const/p1.outTangent.x
k1.value = p1.value.y
k1.time = p1.value.x

k2.inTangentLength = p2.inTangent.x/(p1.value.x - p2.value.x)
k2.inTangent = -p2.inTangent.y*const/p2.inTangent.x
k2.outTangentLength = p2.outTangent.x/(p3.value.x - p2.value.x)
k2.outTangent = p2.outTangent.y*const/p2.outTangent.x
k2.value = p2.value.y
k2.time = p2.value.x

k3.inTangentLength = p3.inTangent.x/(p2.value.x - p3.value.x)
k3.inTangent = -p3.inTangent.y*const/p3.inTangent.x
k3.value = p3.value.y
k3.time = p3.value.x
)

on CurveControlToTrack open do
(
trackviews.open "Curve Control To Track" width:800 height:400
trackviews.setCurrent "Curve Control To Track"
track_node = newTrackViewNode "TrackView"
track = bezier_float()
addTrackViewController track_node track "Curve"

cc = ccc.curves[1]
cc.numPoints = 3
cc.points[1] = ccPoint [0,0] [0,0] [0,0] bezier:on corner:on lock_x:off lock_y:off noXConstraint:on
cc.points[2] = ccPoint [50,50] [-33,0] [33,0] bezier:on corner:off lock_x:off lock_y:off noXConstraint:on
cc.points[3] = ccPoint [100,0] [-33,0] [0,0] bezier:on corner:on lock_x:off lock_y:off noXConstraint:on
cc.points[1].outtangent = [33,0]
zoom ccc #all
ccc.scrollValues = [-23,-40]
ccc.zoomValues = [3,1.5]

addnewkey track 0
addnewkey track 50
addnewkey track 100
updateTrackCurve curve:1

trackviews.setFilter "Curve Control To Track" #default
trackviews.current.expandTracks()
trackviews.current.selectTrack track on
trackviews.current.zoomSelected()
-- trackviews.current.ui.showTrackWindow = off
trackviews.setFilter "Curve Control To Track" #animatedTracks
)
on ccc selChanged c p do updateTrackCurve curve:c point:p type:type event:#selChanged
on ccc ptChanged c p do updateTrackCurve curve:c point:p type:type event:#ptChanged
on ccc tangentChanged c p type do updateTrackCurve curve:c point:p type:type event:#tangentChanged
)
createdialog CurveControlToTrack

try to edit the curve control points and check the track curve changes...

everyone is free to extend the snippet to support other features...

denisT
06-25-2011, 06:50 PM
@ denisT:
Can you please explain this with an example :blush:, it would be great if you could explain why the outtangent for 45 degree angle is 0.1875 (see the image in my previous post) ? i.e. :


0.1875 is some magic number. it's 0.25 * (1.0 - 0.25) or (3./16)...
some sort of golden ratio :) i couldn't find any sense for this number.

Furball89
06-25-2011, 06:56 PM
0.1875 is some magic number. it's 0.25 * (1.0 - 0.25) or (3./16)...
some sort of golden ratio :) i could find any sense for this number.


hah.! thanks for the code! disappointed by maxscript help on this issue though, not a single line is mentioned about how the in/out tangents are computed, but there's a detailed para on in/out tangent lengths! am I missing something? :surprised. (also 0.1875 * 240 = 45, Tan 45 = 1 ;) )

denisT
06-25-2011, 07:02 PM
(also 0.1875 * 240 = 45, Tan 45 = 1 ;) )
it's an effect, not a cause...

Pacermike
06-26-2011, 05:00 AM
Wow! Great information! Thank you so much for all this. I'll get back to work on it right away. Thanks again!

EDIT:

DANG! Denis, I just ran your code! This is exactly what I was trying to do but couldn't! Outstanding! I'm going to learn a lot from this. Thank you so, so much!

denisT
06-27-2011, 05:04 AM
there is the list of curve control events:
selChanged,
ptChanged,
tangentChanged,
deleted,
reset,
rightClick

is there any way to catch cc point tangent TYPE CHANGE event?
yes, there is!
who knows how? ;)

Pacermike
06-27-2011, 09:04 AM
I thought maybe you could use a mouse click event handler to test the point props on and after changing the tangent types... but I was wrong. Here's what I tried:


fn tangentCheck val =
(
val = #()

for i=1 to cc_test.numCurves do
(
local crv = cc_test.curves[i]
for j=1 to crv.numPoints do
(
local cp = crv.points[j]
for pp in cpProps where pp == #bezier or pp == #corner collect
(
append val (getProperty cp pp)
)
)
)

return val
)

on uTestCurveControl lbuttondown val do
(

tangentTypes = tangentCheck check1
)

on uTestCurveControl lbuttonup val do
(
tangentTypesChanged = tangentCheck check2

diff = for i=1 to tangentTypes.count where tangentTypes[i] != tangentTypesChanged[i] collect i
if diff.count > 0 then print "Tangent Type Changed" else print "T'ain't no change, sucka!"

)

It collects an array of tangent type properties on and after a left mouse click and then checks to see if anything changed. The problem is that because you have to click on a menu item in the right click menu I don't think the left click even registers.

What's the right answer? :)

Pacermike
07-03-2011, 06:15 AM
Denis, the suspense is killing me on this one! What was the right answer? How do you catch a cc point tangent TYPE CHANGE event? It's way beyond my skills.

denisT
07-03-2011, 06:28 AM
Denis, the suspense is killing me on this one! What was the right answer? How do you catch a cc point tangent TYPE CHANGE event? It's way beyond my skills.

use when construct for curve control target reference. to get the reference use refs.dependents method for ccCurve...
i don't have max open, so it's something like:

when topology (refs.dependents <ccCurve>)[1] changes do <whatever you need>

Pacermike
07-03-2011, 08:46 AM
That is awesome. Thanks.

CGTalk Moderation
07-03-2011, 08:46 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.