-- version: 0.2 -- @node-registry-category |Math|Value interpolation local inputPins = {} local outputPins = {} local mappings = {} local nodeOutputValue = nil 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 local function setInputLinkers(graph) local pinInfoInputValue = { key = "inputValue", label = "Input value", description = "Input value that will be mapped to the output range", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0, } -- 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 pinInfoMapping = { key = "mapping", label = "Mapping function", type = octane.pinType.PT_ENUM, defaultNodeType = octane.nodeType.NT_ENUM, defaultValue = 1, enum = enumMapping } local pinInfoInputMin = { key = "inputMin", label = "Input min", group = "Input range", description = "Minimum value of the input range.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0 } local pinInfoInputMax = { key = "inputMax", label = "Input max", group = "Input range", description = "Maximum value of the input range.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 1 } local pinInfoOutputMin = { key = "outputMin", label = "Output min", group = "Output range", description = "Minimum value of the output range.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0 } local pinInfoOutputMax = { key = "outputMax", label = "Output max", group = "Output range", description = "Maximum value of the output range.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 1 } local pinsInfo = { pinInfoInputValue, pinInfoMapping, pinInfoInputMin, pinInfoInputMax, pinInfoOutputMin, pinInfoOutputMax } local inputs = graph:setInputLinkers(pinsInfo) for i, info in ipairs(pinsInfo) do inputPins[info.key] = inputs[i] end end local function setOutputLinkers(graph) local pinInfoOutput = { key = "outputValue", label = "Output value", type = octane.pinType.PT_FLOAT, } local pinsInfo = { pinInfoOutput } local outputs = graph:setOutputLinkers(pinsInfo) for i, info in ipairs(pinsInfo) do outputPins[info.key] = outputs[i] end end local function initializeInputNodes() if inputPins.inputValue:getConnectedNode(octane.pinId.P_INPUT) == nil then local nodeValue = octane.node.create { type = octane.nodeType.NT_FLOAT, pinOwnerNode = inputPins.inputValue, pinOwnerName = "input" } nodeValue:setAttribute(octane.attributeId.A_VALUE, 0) end end local function updateInternalNodes(script) local inputValue = script:getInputValue(inputPins.inputValue) local inputMin = script:getInputValue(inputPins.inputMin) local inputMax = script:getInputValue(inputPins.inputMax) local outputMin = script:getInputValue(inputPins.outputMin) local outputMax = script:getInputValue(inputPins.outputMax) local mappingIndex = script:getInputValue(inputPins.mapping) local inputRange = inputMax - inputMin local outputRange = outputMax - outputMin local outputValue = inputValue if inputRange ~= 0 and outputRange ~= 0 then local normalizedValue = (inputValue - inputMin) / inputRange normalizedValue = mappings[mappingIndex].map(normalizedValue) outputValue = outputMin + (normalizedValue * outputRange) end nodeOutputValue:setAttribute(octane.attributeId.A_VALUE, outputValue) end local function createNode(type, name, owner) return octane.node.create { type = type, name = name, graphOwner = owner } end local function createInternalNodes(graph) nodeOutputValue = createNode(octane.nodeType.NT_FLOAT, "Output value", graph) end local function connectInternalNodes() outputPins.outputValue:connectTo(octane.pinId.P_INPUT, nodeOutputValue) end -------------------------------------------- local node = {} node._name = "Value interpolation" node._icon = octane.image.fromBase64("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHvSURBVDiNhZO/a9NRFMU/33dv0jRfoT8gGir9C0RxrqR0EfwHGidxKyp1cKvgULDaya0oonRQUYiDgpvgUBWpioOrq9LWVikV2jTJu+85pORHo+1Z3uNy7nn3ncNNgLRQLE6naVrOiCoHIgLQMPPbf7YrGxtrC1ooFKenpi7NlcYn1Il0EWP72lULZrxbWjr18P5dNJ+m5bEzJZ2bu8XKj+9t/v+QwMjICDMz1/Xpk8dlFXEaE1hbXaE/n9KXy/2zqRO1Wp0AOBHVCJgFnEvIp0cYHBo+bAY2f//CQoAYUGLAzHDOIeJQlZ4nL1ydp9PfD29eYt4TiWiMETO/JyDoviAuXruDSHctm8nizSCCRsBbwDlB9wlMTs0iojRqVZ7du9GyY2P9J6dPXGlOQIyYGeIcom2B85dvI3uxPn8w2yUsInhvhAgaAO+N0rkypbOTJC5pkRr1XV4s3uz5lohgFoix5YGRyWTJ5vq7iK8ezfc0tyYwD4ASwJvx/nWFb1/fcvRYsUVsJtILEYeZEUNEI7Ejxt4U9iPp8IAYUDPv6/Uax0dHAVhfWz1QAGBweJhavY6Zea1WdyqfPy6fHBufyLjEtUgd69RxNC9mxpdPy43qbrWSAOnAwNB0X76/7Jw7ZJ2bCMF8bWe3srW1ufAXavLX1sLSUlsAAAAASUVORK5CYII=", 3) -- Callback called after initializing the scripted graph with the code. function node.onInit(self, graph) mappings = getMappings() setInputLinkers(graph) setOutputLinkers(graph) initializeInputNodes() createInternalNodes(graph) updateInternalNodes(self) connectInternalNodes() octane.nodegraph.unfold(graph, true) end -- Callback called when the connected value of one of the input linker nodes changes. function node.onEvaluate(self, graph) updateInternalNodes(self) return false end return node