EaseCurve bug : controller interprets values as time AND as ticks

EaseCurve bug : controller interprets values as time AND as ticks

Anonymous
Not applicable
1,748 Views
15 Replies
Message 1 of 16

EaseCurve bug : controller interprets values as time AND as ticks

Anonymous
Not applicable

Hello,

 

I encountered a weird issue with ease_curves that I am not able to handle in a sensefull way. It look like the values I feed into the controller of an ease_curve is once interpreted as time and in another moment as ticks which kills my animation on the node.

 

The setup:

I have a helper with an airplane linked to it and I animate position and rotation of the helper to make the airplane flying through space. I want to be able to have it fly faster without changing the motionpath so I wrote a little tool that helps me doing my job. With this tool I add an instanced easecurve to all 6 pos and rot controllers. To make the airplane fly faster I add a key to the instanced ease-curves and add a certain value to value of this key... all via my little tool, all via maxScript. The thing is, that sometimes the values I use to adjust the key.value needs to be a float-value and sometimes I need to multiply it with the ticksPerFrame to get the correct result. I was not able to read the circumstances for this issue but it happens quite often... did anyone else approach this problem and can share some knowledge?

 

 

Thanks,

Martin

0 Likes
Accepted solutions (1)
1,749 Views
15 Replies
Replies (15)
Message 2 of 16

Alfred.DeFlaminis
Alumni
Alumni

Hello @Anonymous and welcome to the community,

 

This question is a bit outside my normal comfort zone, when it comes to scripting I am not very experienced.  However it would be useful to paste the code snippets to see if I can find the problem or in the case that another user or expert elite knows about this issue.  I don't do much scripting but I have looked for a few links that may help.  You may already have found them.  

 

Possibly a macroscript like the following I found that Bobo pasted in another forum might help to set a time mode.  Again I'm not much of a scripter so it may be the wrong situation for the code.  

 

macroScript TimeTicks category:"TrulyScripts"
(
timeDisplayMode = #timeTicks
)

macroScript TimeFrames category:"TrulyScripts"
(
timeDisplayMode = #frames
)

 

Best Regards,

0 Likes
Message 3 of 16

denisT.MaxDoctor
Advisor
Advisor

 

 


... all via my little tool, all via maxScript. The thing is, that sometimes the values I use to adjust the key.value needs to be a float-value and sometimes I need to multiply it with the ticksPerFrame to get the correct result. 

i'm pretty sure there is some misunderstanding of how time values work. it will help if you show us a piece of your code related to working with easy-curve

0 Likes
Message 4 of 16

Anonymous
Not applicable

Thanks for your replies!

 

With a little delay, here is a piece of my code I use to adjust the keys on the ease_curves...

 

 

fn easeFaster INNode INTime = -- node with ease_curves; timeValue to be added
	(
		myKeys = INNode.position.controller[3][1].ease_curve.controller.keys -- checking for keys on x-pos
		local myKey
		for x = 1 to myKeys.count do -- checks if there is a key at the slidertime
		(
			if myKeys[x].time == sliderTime do
			(
				myKey = myKeys[x]
			)
		)
		if myKey == undefined do -- if no key is found at sliderTime...
		(
			addNewKey INNode.position.controller[3][1].ease_curve.controller sliderTime -- ... a key is added
			myKeys = INNode.position.controller[3][1].ease_curve.controller.keys -- redoing the keysArray
			for x = 1 to myKeys.count do -- filtering for the key at slidertime
			(
				if myKeys[x].time == sliderTime do
				(
					myKey = myKeys[x]
					myKey.inTangentType = #auto -- tangentType auto to have a smooth animation
					myKey.outTangentType = #auto
				)
			)
		)
		myKey.value += (INTime as float)*ticksperframe -- the value from the spinner in the Rollout is added
	)

 

 

I call this FN whenever I press the "faster"button in a rollout. A float-value is then taken from a spinner and handed over to this FN.

 

so the eventhandler is something like:

on button pressed do

(

     easeFaster $ spinner_timeAdd.value

)

 

 

I use this on several nodes at once and cycle through the selection - this is, when the "bug" occures.... The thing I found out is that it works more stable when I convert the spinner-value from float to time when calling this FN (spinner_timeAdd.value as time). But for me, this does not explene the earlier descriped behaviour Smiley Indifferent

 

 

If you want to check for your own, you need a helper with frozen transforms and a third pos_XYZ-controller added to the position_list-controller. Apply ease_curves to this third controller and make it the active controller.

 

 

Thanks for your help!

Martin

0 Likes
Message 5 of 16

denisT.MaxDoctor
Advisor
Advisor
Accepted solution

as i thought you should work with time values better:

slidertime (currenttime) is time value

easy_curve value is a float casting of time

key.time is a time value

ui spinner value is (probably) a float value  

 

ok ... here is a corresponding:

 

float casting of time is <time_value>.frame

 

or 

 

<time_value> == <time_value as float> / ticksperframe

Message 6 of 16

Anonymous
Not applicable


Thank you for your explanations, denisT,

I knew about time and ticksPerFrame and never stumbled, but I was not aware that it is such a thing to handle in a script... passing the second argument of the FN as time (float as time) works fine now.

Still I am curious - if anybody knows this - if there is an initiation of the class of the value that is stored in a key.value...and   when   this is initiated... but this is just a niceToKnow 🙂


Cheers,
Martin

0 Likes
Message 7 of 16

ads_royje
Alumni
Alumni

Hi,

 

I looked at your script and must admit to be a little confused as what you are trying to do.

From what I understand, you are setting key values to an Ease curve, right ?

If that is the case, you need not to transform anything, the float of the spinner can be send as is to the Ease curve value.

The Ease curve value represents 'frames' as float, with no Time representation; it does not matter what fps or ticks per frame are for the Ease curve.

 

The Ease curve takes its float value as a "frame" and reads the position on current frame from the value of the curve it's applied to from that 'frame'.

 

You can just do 

myKey.value += INTime 

Knowing that the float value from the spinner represents frames.

If you what to 'interpret' the float value as time, like seconds for instance.

Then you need to convert the float as frames before setting it to the Ease curve value.

 

In Max there is always 4800 ticks per seconds.

To convert the spinner float value from Seconds to Frames you'd need to do :

spinner.value * (4800/TicksPerFrame)

TicksPerFrame is the fps of the animation, 15, 24, 30 fps. For instance:

TicksPerFrame for 30 fps is 4800 / 30 = 160 ticks per frame

TicksPerFrame for 24 fps is 4800 / 24 = 200 ticks per frame

4800/TicksPerFrame will give the frame rate, the fps of the scene.

 

So if the spinner represents 'frames' send it strait to the Ease of curve ;

if INTime == frames then

   myKey.value += INTime

 

if INTime == seconds then

   myKey.value += INTime * (4800/TicksPerFrame)

 

I hope this can help!

 

Message 8 of 16

Anonymous
Not applicable

Hello @ads_royje,

 

yes you are right - I am changing the the value of a key on an ease_curve that represents frames as floatvalues in the animationrange - as you decripted. For that I am taking the value of a spinner (which originally is float) and pass it over to the function shown above. In the meantime I changed two things in my Tool:

1st - I convert the float-value of the spinner to a time-value before calling the FN

2nd - I changed the last line in the FN to:

myKey.value += ((INTime as float)/ticksperframe) -- the value from the spinner in the Rollout is added

 

It worked fine for some tests and I thought this was the solution, but I just used my Tool again as descriped in the first post and I have the same problem again. I now have two helpers with the exact same setup, but when appling the modified FN they behave different. The first takes the changes as 'frames', the second as 'ticks'...

 

I just checked twice and when changing the value from 18 to 26 (expecting to shift the animation from 18f to 26f) the result is different in the controllers. With '$.position.controller[3][1].ease_curve.controller.value' the first shows "26.0" and the second "3609.0" ... both controllers are classof bezier_float() and I would expect them to behave the same way... Any ideas :-?

 

 

Thanks,

Martin

0 Likes
Message 9 of 16

ads_royje
Alumni
Alumni

Hi! 🙂

 

That does sound very strange that you get different answers with the same code.

There may be something different in one object from the other.

 

What gets me intrigued is if you'd expect a frame value of 26.0f but you get 3609.0, it does not sound like ticks 😕

26.0f as ticks should be (4800/30)*26 = 4160.0 if at 30 fps or (4800/24)*26 = 5200.0 if at 24 fps.

if 3609.0 are ticks for 26.0f , that would mean the scene runs at 34.58 fps, which is not doable in max.

So I am very intrigue as to what/why you'd get 3609.0 ...

 

Would you be able to attached that repro scene where you get different response from the ease curve value ?

From what I understand, the 3609.0 does not look like ticks, there may be something else causing this... 

 

0 Likes
Message 10 of 16

Anonymous
Not applicable

Hello again!

I forgot to mention that the scenes I am working with are at 24 fps so 200 ticks per frame and the maxVersion is '2017 Update 1'.

The numbers I mentioned derive from the slidertime when I first pressed "faster" on my tool at frame 18. The easeCurvecontroller of the first helper stored the value 18.0... the easeCurvecontroller of the second helper stored 18f*ticksPerFrame which is 3600... I pressed the button 8 times so the first controller had a value of 26.0 then (a correct represenation and behavior on the easeCurve) and the second controller had 3600 + 8 ( + 1 I donot know where it came from) and this was not the change I expected.

There are two files attached to this post - the .txt-file includes a reduced version of my sctript... Please read the comments in the head of this file for more informations! The thing I can say at the moment is that after loading the file in a fresh Max, everything works fine. After removing and adding easeCurves again the controller behave different though I used the same functions to apply the easeCurves for this scenefile.


Hopefully, this will help 🙂


Cheers,
Martin

0 Likes
Message 11 of 16

ads_royje
Alumni
Alumni

Thanks for the files Martin!

 

I can now reproduce your issue! That's great!

I can see that track view values are in frames but the returned one in ticks indeed.

Although I get both objects to return ticks values now.

I'll investigate what is going on in there and come back as soon as I can with some update on what I find.

 

 

 

Message 12 of 16

Anonymous
Not applicable

Thats sounds great! Thank you 🙂

0 Likes
Message 13 of 16

denisT.MaxDoctor
Advisor
Advisor

@Anonymous wrote:

Hello again!

I forgot to mention that the scenes I am working with are at 24 fps so 200 ticks per frame and the maxVersion is '2017 Update 1'.
...


I have to say it again... you are working with time values just not right. 

 

the ease-curve controller value is - float cast of time frames

 

the ease-curve key value is - float cast of time number ticks

 

slider-time is - time value

 

spinner value - is float

 

 

now there is a corresponding 

 

<easecurve>.value = <timevalue>.frame (or (spinner_value as time).frame)

<easecurve_key>.value = <timevalue>.ticks (or animationrange.start.ticks) 

 

<timevalue>.ticks = <timevalue>.frame * ticksPerFrame (or (<timevalue> as float))

<timevalue>.frame = <timevalue>.ticks / ticksPerFrame (or (<timevalue> as float) / ticksPerFrame)

 

 

THERE IS ANYTHING WRONG in the file. THERE IS NO UNEXPECTED system behavior. You only need to pass correct values

 

0 Likes
Message 14 of 16

denisT.MaxDoctor
Advisor
Advisor

by the way... you don't need to search all keys by time to find the right one. the built-in methods can do it for you much quicker:

 

 

index = getkeyindex <node>.pos.controller[1].ease_curve.controller <time>
key = if index == 0 then <create key> else (getkey <node>.pos.controller[1].ease_curve.controller index)

 

Message 15 of 16

Anonymous
Not applicable

Thanks for the shortcut to the key, denisT! ... I checked your comments twice and tweaked the script again (see attached .txt) but still the same unexpected behaviour - maybe you could have a closer look, if I got you right. I don't think there is something wrong within the scene, though we've got some third-party-plugins installed and some callbacks running for our piline that could theoretically interfer, but its hard to find out more about this.


Cheers,
Martin

0 Likes
Message 16 of 16

denisT.MaxDoctor
Advisor
Advisor

i don't debug anyone other's code. here is how i would do it:

try (destroydialog EaseOpsRol) catch()
rollout EaseOpsRol "Ease Curve Ops by denisT" width:200
(
	group "Curve: "
	(
		button add_ez_bt "Add" width:90 across:2 align:#left offset:[-5,0]
		button del_ez_bt "Delete" width:90 align:#right offset:[4,0]
	)
	group "Keys: "
	(
		spinner ez_val_sp "Ease Value: " fieldwidth:54 type:#float range:[0.001, 10, 1] align:#right offset:[4,0]
		checkbox add_ez_key_ch "Add New Key" checked:on tooltip:"Add New Ease Curve Key if it doesn't exist" 	
		checkbox auto_key_ch "Set Auto Tangents" checked:on	
		
		button slower_ez_bt "Slower" width:90 across:2  align:#left offset:[-5,4]
		button faster_ez_bt "Faster" width:90 align:#right offset:[4,4]
	)
	
	fn collectEaseCurves nodes:  = 
	(
		if nodes == unsupplied do nodes = selection as array
		curves = #()
		for node in nodes do
		(
			data = getclassinstances bezier_float target:node astrackviewpick:on 
			for d in data where d.name == "Ease Curve" do appendifunique curves d.anim
		)
		curves
	)
	
	fn makeDefaultEaseCurve range: = 
	(
		if not iskindof range Interval do range = animationrange
		
		local ez = bezier_float()

		k0 = addnewkey ez range.start
		k0.value = range.start as float

		k1 = addnewkey ez range.end
		k1.value = range.end as float

		ez.keys.inTangentType = #linear
		ez.keys.outTangentType = #linear
		
		ez
	)
	on add_ez_bt pressed do undo "Add EZ" on
	(
		ez = makeDefaultEaseCurve()
		for node in selection do try (addeasecurve node ez) catch()
	)
	on del_ez_bt pressed do undo "Delete EZ" on
	(
		for node in selection do try (deleteeasecurve node 1) catch() 
	)
	
	fn adjustEasyCurves value:1.0 addkey:on auto:on = 
	(
		cc = collectEaseCurves()
		keys = for c in cc collect
		(
			if (k = getkeyindex c currenttime) != 0 then (getkey c k) else
			(
				if addkey then (addnewkey c currenttime) else dontcollect
			) 
		)
		if keys.count > 0 do
		(
			keys.value += (value as time).ticks 
			if auto do
			(
				keys.inTangentType = #auto
				keys.outTangentType = #auto
			)
		)
		
	)
	on slower_ez_bt pressed do undo "EZ--" on adjustEasyCurves value:(-ez_val_sp.value) addkey:add_ez_key_ch.state auto:auto_key_ch.state
	on faster_ez_bt pressed do undo "EZ++" on adjustEasyCurves value:(ez_val_sp.value) addkey:add_ez_key_ch.state auto:auto_key_ch.state	
)

createdialog EaseOpsRol

of course i would add also a double-check for ease_curve existence, uniqueness, lock, etc.

 

0 Likes