Notifications
Clear all

[Closed] Rate Limiting Controller

Greetings

I’m having a play with new plugin FloatControllers (via maxcript or MCG) and am hitting a bit of a wall…
(kind of the same wall I’ve always hit, but hoped these new controller types and cache options would have provided a more obvious solution)

Basically I’m trying to extend the ides of a limit controller (which takes a bezier float controller as one of it’s inputs along with maxValue and minValue parameters) but also check the 1st derivative of the bezier controller against a maxSpeed parameter and limit the value based on that as well…
(and eventually limit the 2nd derivative as well)

Has anyone ever gone down this sort of rabbit hole before algorithmically?

Here’s where I got to… started trying to store the last couple values the controller calculates, but deleted all the speed calculation stuff – as the controller seems to be executed in no particularly consistent way. I’m having a tough time even finding code examples in other languages for this sort of thing, so am kind of guessing…

Cheers

Mikie


plugin FloatController FloatLimited_test
name:"Limited FloatController Test"
usePBValidity:false -- false because time dependent
classID:#(0xc1dbdd6, 0x70ecb08a)
(	
	parameters pblock rollout:params
	(
		maxPos type:#float animatable:false ui:sp_maxPos
		minPos type:#float animatable:false ui:sp_minPos
		maxSpeed type:#float animatable:false ui:sp_maxSpeed
		maxAccel type:#float animatable:false ui:sp_maxAccel
		resultsCache type:#floatTab tabsize:2
		timeCache type:#floatTab tabsize:2
		demandValue type:#maxobject subAnim:true
	)
	rollout params "FloatController Test Parameters"
	(
		spinner sp_maxPos "Max Position:" range:[-1e9, 1e9, 0]
		spinner sp_minPos "Min Position:" range:[-1e9, 1e9, 0]
		spinner sp_maxSpeed "Max Speed:" range:[-1e9, 1e9, 0]
		spinner sp_maxAccel "Max Accel:" range:[-1e9, 1e9, 0]
	)
	on getValue do 
	(
		local currentOffset = 0.0
		local demandSpeed = 0.0
		local demandAccel = 0.0
		local previousSpeed = 0.0
		local posLimited = false
		local demand = demandValue.value
		-- positions check, the easy bit
		if demand > maxPos then (
			posLimited = true
			currentOffset = maxPos - demand
		)
		else 
			if demand < minPos then (
				posLimited = true
				currentOffset = minPos - demand
			)
		
		-- speed check - everything stops working
		demandSpeed = ((demandValue.value + currentOffset) - resultsCache[1])/((currentTime as float / ticksPerFrame) - timeCache[1])
		maxDist = maxSpeed * ((currentTime as float / ticksPerFrame) - timeCache[1])

		if abs(demandSpeed) > maxSpeed do (

		)			
			
		-- accel check
		-- push results into cache
		resultsCache[2] = resultsCache[1]
		timeCache[2] = timeCache[1]
		resultsCache[1] = currentOffset
		timeCache[1] = currentTime as float / ticksPerframe
		
		--return result
		demandValue.value + currentOffset
	)
	on setValue val relVal commit do 
	(
		
		-- don't need this in this case, as it's not settable
	)	
	on create do
	(
		demandValue = NewDefaultFloatController()
		maxPos = 40
		minPos = 10
		maxSpeed= 10
		maxAccel = 10
		isLeaf=true
	)
)
try(delete $) catch()
t = teapot isselected:true
t.pos.x_position.controller = c = FloatLimited_test()
c.isKeyable = true
c.isleaf = false
4 Replies

Rate Limiter:
limit the first derivative of the position track so that the output changes no faster than the specified limits (R = Rising Slew Rate and F = Falling Slew Rate).

The derivative is calculated using:

rate = [ u(i) – y(i-1) ] / [ t(i) – t(i-1) ]

where: u(i) and t(i) are the current position and time, and y(i-1) and t(i-1) are the position and time at the previous time step. The output is determined by comparing the rate to the rising slew rate and falling slew rate.

if rate is greater than the Rising Slew Rate ® the output is given by:

y(i) = Δt*R + y(i-1)

if rate is less than the Falling Slew Rate (F) the output is given by:

y(i) = Δt*F + y(i-1)

isn’t this just a convoluted way of implementing smoothing filter with a cut off frequency ?

It’s similar to smoothing, but a little more complex. the limits are indeed like cut off frequency, but unless the slope of the line exceeds a certain amount, the source is left untouched. additionally, the rate of change in slope would also be tested and left unchanged, unless it exceeds a certain amount, in which case it is reduced as well…

That does give me a good idea though, as trying a first or second order smoothing algorithm would run into similar issues with history dependence… and might make a much cleaner example… Will try to bash something out later today…

Thanks!

Mikie

Well, this is one step closer… Limiting on value, and limiting on slope. Still stuck on next derivative down, change in slope… (i.e. to make sure there is a slight ramp up from a dead stop, or so that a change in direction can’t happen too quickly)


plugin FloatController FloatLimited_test
name:"Limited FloatController Test"
usePBValidity:false -- false because time dependent
classID:#(0xc1dbdd6, 0x70ecb08a)
(	
	parameters pblock rollout:params
	(
		maxPos type:#float animatable:false ui:sp_maxPos
		minPos type:#float animatable:false ui:sp_minPos
		maxSpeed type:#float animatable:false ui:sp_maxSpeed
		maxAccel type:#float animatable:false ui:sp_maxAccel
		resultsCache type:#floatTab tabsize:2
		timeCache type:#floatTab tabsize:2
		demandValue type:#maxobject subAnim:true
	)
	rollout params "FloatController Test Parameters"
	(
		spinner sp_maxPos "Max Position:" range:[-1e9, 1e9, 0]
		spinner sp_minPos "Min Position:" range:[-1e9, 1e9, 0]
		spinner sp_maxSpeed "Max Speed:" range:[-1e9, 1e9, 0]
		spinner sp_maxAccel "Max Accel:" range:[-1e9, 1e9, 0]
	)
	on getValue do 
	(
		local currentOffset = 0.0
		local demandSpeed = 0.0
		local demandAccel = 0.0
		local previousSpeed = 0.0
		local posLimited = false
		local demand = demandValue.value
		
		-- positions check
		if demand > maxPos do (
			demand = maxPos
		)
		if demand < minPos do (
			demand = minPos
		)
		
		-- speed check 
		demandSpeed = (demand - resultsCache[1]) / ((currentTime as float / ticksPerFrame) - timeCache[1])
		maxDist = maxSpeed * ((currentTime as float / ticksPerFrame) - timeCache[1])

		if abs(demandSpeed) > maxSpeed do (
			if demandSpeed > 0 do demand = resultsCache[1] + maxDist
			if demandSpeed < 0 do demand = resultsCache[1] - maxDist
			demandSpeed = (demand - resultsCache[1]) / ((currentTime as float / ticksPerFrame) - timeCache[1])
		)
			
		-- accel check - todo
		oldSpeed = (resultsCache[1] - resultsCache[2]) / (timeCache[1] - timeCache[2])
		demandAccel = (demandSpeed - oldSpeed) / ( (((currentTime as float / ticksPerFrame) + timeCache[1])/2) - ((timeCache[1] + timeCache[2])/2))
		
		maxDist = 0 -- need to figure this out based on maxAccel over time squared
		
		if abs(demandAccel) > maxAccel do (
			if demandSpeed >= 0 do (
				demand -= maxDist  -- probably best to go fron resultsCache{1], but this shouldn't modify anything for now...
			)
			if demandSpeed <= 0 do (
				demand += maxDist
			)
		)
		
		-- future todo,  make the calculations symetrical around current time, rather than lagging... 
		
		-- push results into cache
		resultsCache[2] = resultsCache[1]
		timeCache[2] = timeCache[1]
		resultsCache[1] = demand
		timeCache[1] = currentTime as float / ticksPerframe
		
		--return result
		demand
	)
	on setValue val relVal commit do 
	(
		
		-- don't need this in this case, as it's not settable
	)	
	on create do
	(
		demandValue = NewDefaultFloatController()
		maxPos = 40
		minPos = 10
		maxSpeed= 10
		maxAccel = 10
		isLeaf=true
	)
)
try(delete $) catch()
t = teapot isselected:true
t.pos.x_position.controller = c = FloatLimited_test()
c.isKeyable = true
c.isleaf = false