[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
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