-------------------------------------------- -- Animated float. -- -- @author OTOY -- @version 0.2 -- @script-id 4975dfec-23c3-4e97-a5a3-abebc73acaaa -------------------------------------------- local inputLinkersInfo = {} local outputLinkersInfo = {} local inputLinkers = {} local outputLinkers = {} local nodeValue = nil local mappings = {} local enumLoopType = {"Loop", "Ping pong"} -- Same order as octane.animationType. -------------------------------------------------- -- Various mapping functions. local function getMappings() -- References: -- http://www.gizma.com/easing/ -- https://easings.net/ local linear = { label = "Linear", map = function (t) return t end } local easeInQuad = { label = "Quadratic easing in", map = function (t) return t*t end } local easeOutQuad = { label = "Quadratic easing out", map = function (t) return -t*(t-2) end } local easeInOutQuad = { label = "Quadratic easing in/out", map = function (t) t = t*2 if t < 1 then return 0.5*t*t else t = t-1 return -0.5 * (t*(t-2)-1) end end } local easeInSine = { label = "Sinusoidal easing in", map = function (t) return 1 - math.cos(t * math.pi / 2) end } local easeOutSine = { label = "Sinusoidal easing out", map = function (t) return math.sin(t * math.pi / 2) end } local easeInOutSine = { label = "Sinusoidal easing in/out", map = function (t) return -0.5 * (math.cos(math.pi*t) - 1) end } return { linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInSine, easeOutSine, easeInOutSine } end -------------------------------------------------- -- Set the timeline up. local function update(script) local startValue = script:getInputValue(inputLinkers.startValue) local endValue = script:getInputValue(inputLinkers.endValue) local mappingIndex = script:getInputValue(inputLinkers.mapping) local duration = script:getInputValue(inputLinkers.duration) local framerate = script:getInputValue(inputLinkers.framerate) local offset = script:getInputValue(inputLinkers.offset) local frames = duration * framerate frames = math.max(frames, 1) local times = {} local values = {} for i=1, frames do local t = math.min((i-1) / frames, 1.0) local k = mappings[mappingIndex].map(t) local value = octane.vec.lerp(startValue, endValue, k) table.insert(times, t * duration + offset) table.insert(values, value) end -- Note about looping: -- The `period` in setAnimator is independent from the looping mechanism and shouldn't be changed here. -- It is the total amount of time of one loop, including the time span of the last frame. -- The endTime defines how many loops fit. local loops = script:getInputValue(inputLinkers.loops) local animationType = script:getInputValue(inputLinkers.loopType) local frameInterval = duration / frames --local endTime = duration * loops - frameInterval + offset local endTime = ((duration - frameInterval) * loops) + offset --print("period:"..tostring(duration)..", endTime:"..tostring(endTime)) if loops == 1 then animationType = octane.animationType.ONCE end nodeValue:clearAnimator(octane.attributeId.A_VALUE) nodeValue:setAnimator(octane.attributeId.A_VALUE, times, values, duration, true, animationType, endTime) end local function prepareGraph(graph) nodeValue = octane.node.create { type = octane.nodeType.NT_FLOAT, name = "Value", graphOwner = graph } outputLinkers.outputValue:connectTo(octane.pinId.P_INPUT, nodeValue) end -------------------------------------------------- local function getInputLinkersInfo() local liStartValue = { key = "startValue", label = "Start value", group = "Values", description = "Value at the start of one pass of the animation.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = {0, 0, 0}, } local liEndValue = { key = "endValue", label = "End value", group = "Values", description = "Value at the end of one pass of the animation.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = {1, 1, 1}, } local liDuration = { key = "duration", label = "Duration (s)", group = "Animation", description = "Duration of one pass of the animation, in seconds.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 1, bounds = {0, math.huge} } -- Rebuild the enum table to set up the enum node. local enumMapping = {} for _, mapping in ipairs(mappings) do table.insert(enumMapping, mapping.label) end local liMapping = { key = "mapping", label = "Interpolation", group = "Animation", description = "Interpolation method used to sample values within one pass of the animation.", type = octane.pinType.PT_ENUM, defaultNodeType = octane.nodeType.NT_ENUM, defaultValue = 1, enum = enumMapping } local liFramerate = { key = "framerate", label = "Frame rate", group = "Animation", description = "Frame rate used to define the sampling frequency within one pass of animation. Higher values increase the precision of the interpolation.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 24, bounds = {0.1, math.huge} } local liOffset = { key = "offset", label = "Offset", group = "Timeline", description = "Global start time of the first loop of animation.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0, bounds = {0, math.huge} } local liLoops = { key = "loops", label = "Number of loops", group = "Timeline", description = "Number of times the animation is looped.", type = octane.pinType.PT_INT, defaultNodeType = octane.nodeType.NT_INT, defaultValue = 1, bounds = {1, 1000}, logarithmic = true, } local liLoopType = { key = "loopType", label = "Loop type", group = "Timeline", description = "Type of loop when using more than one loop.", type = octane.pinType.PT_ENUM, defaultNodeType = octane.nodeType.NT_ENUM, defaultValue = 1, enum = enumLoopType, } return { liStartValue, liEndValue, liDuration, liMapping, liFramerate, liOffset, liLoops, liLoopType, } end local function getOutputLinkersInfo() local liValue = { key = "outputValue", label = "Output value", type = octane.pinType.PT_FLOAT } return { liValue } end local function setInputLinkers(graph) local inputs = graph:setInputLinkers(inputLinkersInfo) for i, info in ipairs(inputLinkersInfo) do inputLinkers[info.key] = inputs[i] end end local function setOutputLinkers(graph) local outputs = graph:setOutputLinkers(outputLinkersInfo) for i, info in ipairs(outputLinkersInfo) do outputLinkers[info.key] = outputs[i] end end ---------------------- -- Callbacks ---------------------- local node = {} node._name = "Animated float value" node._icon = octane.image.fromBase64("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz".. "AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHvSURB".. "VDiNhZO/a9NRFMU/33dv0jRfoT8gGir9C0RxrqR0EfwHGidxKyp1cKvgULDaya0oonRQUYiDgpvg".. "UBWpioOrq9LWVikV2jTJu+85pORHo+1Z3uNy7nn3ncNNgLRQLE6naVrOiCoHIgLQMPPbf7YrGxtr".. "C1ooFKenpi7NlcYn1Il0EWP72lULZrxbWjr18P5dNJ+m5bEzJZ2bu8XKj+9t/v+QwMjICDMz1/Xp".. "k8dlFXEaE1hbXaE/n9KXy/2zqRO1Wp0AOBHVCJgFnEvIp0cYHBo+bAY2f//CQoAYUGLAzHDOIeJQ".. "lZ4nL1ydp9PfD29eYt4TiWiMETO/JyDoviAuXruDSHctm8nizSCCRsBbwDlB9wlMTs0iojRqVZ7d".. "u9GyY2P9J6dPXGlOQIyYGeIcom2B85dvI3uxPn8w2yUsInhvhAgaAO+N0rkypbOTJC5pkRr1XV4s".. "3uz5lohgFoix5YGRyWTJ5vq7iK8ezfc0tyYwD4ASwJvx/nWFb1/fcvRYsUVsJtILEYeZEUNEI7Ej".. "xt4U9iPp8IAYUDPv6/Uax0dHAVhfWz1QAGBweJhavY6Zea1WdyqfPy6fHBufyLjEtUgd69RxNC9m".. "xpdPy43qbrWSAOnAwNB0X76/7Jw7ZJ2bCMF8bWe3srW1ufAXavLX1sLSUlsAAAAASUVORK5CYII=", 3) node._evaluateAllChanges = true -- Callback called after initializing the scripted graph with the code. function node.onInit(self, graph) graph:setAttribute(octane.attributeId.A_COLOR, { 0.20, 0.35, 0.55 }, true) mappings = getMappings() inputLinkersInfo = getInputLinkersInfo() setInputLinkers(graph) outputLinkersInfo = getOutputLinkersInfo() setOutputLinkers(graph) prepareGraph(graph) update(self) --self:setEvaluateTimeChanges(true) end -- Callback called when the connected value of one of the input linker nodes changes. function node.onEvaluate(self, graph) update(self) end return node