--------------------------------------------------------------------------------------------------- -- Note: This code was generated, any changes to this file will be lost when the tool is run again. --------------------------------------------------------------------------------------------------- -- @author OTOY -- @version 1.2.1481 -- @description Creates a volume from a Vectron input. local function __primitives_loader() -------------------------------------------- -- Volume primitive generator functions. -- The geometry functions don't have any dependency on the node system. -------------------------------------------- local function sphere(x, y, z, params) return (x*x + y*y + z*z) <= (params.radius * params.radius) end local function box(x, y, z, params) return (math.abs(x) <= params.width / 2) and (math.abs(y) <= params.height / 2) and (math.abs(z) <= params.depth / 2) end local function cylinder(x, y, z, params) return math.abs(y) <= (params.height / 2) and ((x*x + z*z) <= params.radius * params.radius) end local function cone(x, y, z, params) local alpha = (y / params.height) + 0.5 local radius = (params.radiusTop * alpha) + (params.radiusBottom * (1-alpha)) return math.abs(y) <= (params.height / 2) and ((x*x + z*z) <= radius * radius) end return { box = box, sphere = sphere, cylinder = cylinder, cone = cone } end local primitives = __primitives_loader() ------------------------------------------------------------------------ local function __description_loader() local oslScript = [==[ #include "octane-oslintrin.h" // Converts a vectron input into a volume. shader VectronConverter( _sdf shape = _SDFDEF [[ string label = "Surface", string help = "The outer enveloppe of the volume." ]], color shapeNoise = color(0) [[ string label = "Surface noise", string help = "Noise at the surface." ]], float shapeNoiseScale = 1 [[ string label = "Surface noise scale", string help = "Scale of the surface noise." ]], color densityNoise = color(1) [[ string label = "Density noise", string help = "Noise inside the volume." ]], output color out = 0) { float dist = shape.dist - (shapeNoise[0] * shapeNoiseScale); float density = dist <= 0 ? 1 : 0; density *= (densityNoise[0] * densityNoise[0] * densityNoise[0]); out = density; } ]==] ------------------------------------------------------------------------ local vectronScript = [==[ #include shader Vectron( float radius = 1 [[float min = 0, float slidermax = 1e4, float sliderexponent = 4]], vector translate = 0, output _sdf out = _SDFDEF) { out.dist = distance(P, translate) - radius; } ]==] ------------------------------------------------------------------------ local function getInputPinsInfo() local pinInfoVectron = { key = "vectron", label = "Vectron", group = "Boundary", description = "The boundary of the volume.", type = octane.pinType.PT_GEOMETRY, defaultNodeType = octane.nodeType.NT_GEO_OSL, } local pinInfoSurfaceNoise = { key = "surfaceNoise", label = "Boundary noise", group = "Noise", description = "Noise at the volume boundary.", type = octane.pinType.PT_TEXTURE, defaultNodeType = octane.nodeType.NT_TEX_NOISE, } local pinInfoSurfaceNoiseScale = { key = "surfaceNoiseScale", label = "Boundary noise scale", group = "Noise", description = "Scale of the noise at the volume boundary.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0.3, bounds = {-100, 100}, } local pinInfoDensityNoise = { key = "densityNoise", label = "Internal noise", group = "Noise", description = "Density noise inside the volume.", type = octane.pinType.PT_TEXTURE, defaultNodeType = octane.nodeType.NT_TEX_FLOAT, defaultValue = 1, } -- Size of the box? Or derived from voxel size and resolution. local pinInfoResolution = { key = "resolution", label = "Volume resolution", group = "Voxel grid parameters", description = "Resolution of the voxel grid.", type = octane.pinType.PT_INT, defaultNodeType = octane.nodeType.NT_INT, defaultValue = {100, 100, 100}, bounds = {1, 10000}, } local pinInfoVoxelSize = { key = "voxelSize", label = "Voxel size", group = "Voxel grid parameters", description = "Size of voxels in cubic meters.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0.1, bounds = {0.001, 5}, } local pinInfoColor = { key = "volumeColor", label = "Volume color", group = "Volume medium", description = "Color not absorbed by the medium.", type = octane.pinType.PT_TEXTURE, defaultNodeType = octane.nodeType.NT_TEX_FLOAT, defaultValue = 1, } local pinInfoVolumeDensity = { key = "volumeDensity", label = "Volume medium density", group = "Volume medium", description = "Absorption and scattering scale.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 100, bounds = {0.0001, 1e5}, logarithmic = true, } local pinInfoVolumeStepLength = { key = "volumeStepLength", label = "Volume step length", group = "Volume medium", description = "Step length used for marching through the volume.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0.4, bounds = {0.01, 1e4}, logarithmic = true, } local pinInfoObjectLayer = { key = "objectLayer", label = "Object layer", type = octane.pinType.PT_OBJECTLAYER, defaultNodeType = octane.nodeType.NT_OBJECTLAYER, } local pinInfoTransform = { key = "transform", label = "Transform", description = "Transform", type = octane.pinType.PT_TRANSFORM, defaultNodeType = octane.nodeType.NT_TRANSFORM_VALUE, defaultValue = octane.matrix.getIdentity(), } return { -- Volume shape pinInfoVectron, pinInfoSurfaceNoise, pinInfoSurfaceNoiseScale, pinInfoDensityNoise, -- Voxel grid pinInfoResolution, pinInfoVoxelSize, -- Volume medium pinInfoColor, pinInfoVolumeDensity, pinInfoVolumeStepLength, -- Extra pinInfoObjectLayer, pinInfoTransform } end local function getOutputPinsInfo() local pinInfoOutput = { key = "geometry", label = "Volume", type = octane.pinType.PT_GEOMETRY } return { pinInfoOutput } end return { getInputPinsInfo = getInputPinsInfo, getOutputPinsInfo = getOutputPinsInfo, script = oslScript, vectronScript = vectronScript, } end local description = __description_loader() ------------------------------------------------------------------------ local function __collection_loader() local inputPinsInfo = {} local inputPins = {} local outputPins = {} local description = nil -- Internal nodes local nodePlacementVectron = nil local nodeOSLTextureConverter = nil local nodeVolumeMedium = nil local nodeVolume = nil local nodePlacementVolume = nil local nodeTextureMultiply = nil local nodeTextureInvert = nil -------------------------------------------- -- Graph plumbing. -------------------------------------------- ------------------------- -- Loop over the grid and evaluate the volume function at each voxel. local function generateVolume(resolution, voxelSize, func, params) local width = resolution[1] local height = resolution[2] local depth = resolution[3] local regularGrid = {} local absorptionOffset = 0 local scatterOffset = 0 local emissionOffset = 0 -- Going from indices to object space, assuming voxels of size 1. local shiftX = 0.5 - (width / 2) local shiftY = 0.5 - (height / 2) local shiftZ = 0.5 - (depth / 2) for k=0, depth do for j=0, height do for i=0, width do local x = (i + shiftX) * voxelSize local y = (j + shiftY) * voxelSize local z = (k + shiftZ) * voxelSize local inside = func(x, y, z, params) local index = (k * height * width) + (j * width) + i + 1 regularGrid[index] = inside and 1 or 0 end end end return { regularGrid = regularGrid, absorptionOffset = absorptionOffset, scatterOffset = scatterOffset, emissionOffset = emissionOffset, } end local function updateNodeVolume(volume, resolution, voxelSize) nodeVolume:setAttribute(octane.attributeId.A_VOLUME_ABSORPTION_OFFSET, volume.absorptionOffset, false) nodeVolume:setAttribute(octane.attributeId.A_VOLUME_SCATTER_OFFSET, volume.scatterOffset, false) nodeVolume:setAttribute(octane.attributeId.A_VOLUME_EMISSION_OFFSET, volume.emissionOffset, false) nodeVolume:setAttribute(octane.attributeId.A_VOLUME_REGULAR_GRID, volume.regularGrid, false) nodeVolume:setAttribute(octane.attributeId.A_VOLUME_RESOLUTION, resolution, false) local volumeTransform = octane.matrix.makeTranslation({-resolution[1]/2, -resolution[2]/2, -resolution[3]/2}) volumeTransform = octane.matrix.scale(volumeTransform, {voxelSize, voxelSize, voxelSize}) nodeVolume:setAttribute(octane.attributeId.A_TRANSFORM, volumeTransform, false) nodeVolume:evaluate() end ---------------------- -- Update the state of the internal nodes according to the current input values. local function updateInternalNodes(script) --local params = grabValues(script, primitiveDescriptor.pinsInfo) local resolution = script:getInputValue(inputPins.resolution) local voxelSize = script:getInputValue(inputPins.voxelSize) local dim = octane.vec.scale(resolution, voxelSize) -- If the final grid dimension is too big it crashes the driver. if dim[1] < 100 or dim[2] < 100 or dim[3] < 100 then local params = { width = dim[1], height = dim[2], depth = dim[3] } local volume = generateVolume(resolution, voxelSize, primitives.box, params) updateNodeVolume(volume, resolution, voxelSize) end end -- Set the initial value of input linker nodes. local function initializeInputNodes() -- If the node already exist it means we are re-loading a scene, don't change it. -- The script should not crash if a node is deleted by the user. if inputPins.vectron:getConnectedNode(octane.pinId.P_INPUT) == nil then local nodeVectron = octane.node.create { type = octane.nodeType.NT_GEO_OSL, pinOwnerNode = inputPins.vectron, pinOwnerName = "input", } nodeVectron:setAttribute(octane.attributeId.A_SHADER_CODE, description.vectronScript, true) nodeVectron:expandOutOfPin() end if inputPins.surfaceNoise:getConnectedNode(octane.pinId.P_INPUT) == nil then local nodeSurfaceNoise = octane.node.create { type = octane.nodeType.NT_TEX_NOISE, pinOwnerNode = inputPins.surfaceNoise, pinOwnerName = "input", } nodeSurfaceNoise:setPinValue(octane.pinId.P_NOISE_TYPE, octane.noiseType.TURBULENCE, true) end if inputPins.densityNoise:getConnectedNode(octane.pinId.P_INPUT) == nil then local nodeDensityNoise = octane.node.create { type = octane.nodeType.NT_TEX_FLOAT, pinOwnerNode = inputPins.densityNoise, pinOwnerName = "input", } nodeDensityNoise:setAttribute(octane.attributeId.A_VALUE, 1.0) end if inputPins.objectLayer:getConnectedNode(octane.pinId.P_INPUT) == nil then octane.node.create { type = octane.nodeType.NT_OBJECTLAYER, pinOwnerNode = inputPins.objectLayer, pinOwnerName = "input" } end if inputPins.transform:getConnectedNode(octane.pinId.P_INPUT) == nil then local nodeTransform = octane.node.create { type = inputPins.transform.defaultNodeType, pinOwnerNode = staticPins.transform, pinOwnerName = "input" } nodeTransform:setAttribute(octane.attributeId.A_TRANSFORM, octane.matrix.getIdentity()) end octane.changemanager.update() end ---------------------- local function setInputLinkers(graph) local inputs = graph:setInputLinkers(inputPinsInfo) for i, info in ipairs(inputPinsInfo) do info.pin = inputs[i] inputPins[info.key] = info.pin end end ---------------------- -- Rebuild the output pins (output linker nodes). local function setOutputLinkers(graph, outputPinInfos) local outputs = graph:setOutputLinkers(outputPinInfos) for i, info in ipairs(outputPinInfos) do outputPins[info.key] = outputs[i] end end local function createNode(type, name, owner) return octane.node.create { type = type, name = name, graphOwner = owner } end local function createInternalNodes(graph) nodePlacementVectron = createNode(octane.nodeType.NT_GEO_PLACEMENT, "Vectron placement", graph) nodeOSLTextureConverter = createNode(octane.nodeType.NT_TEX_OSL, "Converter", graph) nodeOSLTextureConverter:setAttribute(octane.attributeId.A_SHADER_CODE, description.script, true) nodeVolumeMedium = createNode(octane.nodeType.NT_MED_VOLUME, "Volume medium", graph) nodeVolumeMedium:setPinValue(octane.pinId.P_INVERT_ABSORPTION, false, true) nodeVolume = createNode(octane.nodeType.NT_GEO_VOLUME, "Volume", graph) nodePlacementVolume = createNode(octane.nodeType.NT_GEO_PLACEMENT, "Volume placement", graph) nodeTextureInvert = createNode(octane.nodeType.NT_TEX_INVERT, "Invert texture", graph) nodeTextureMultiply = createNode(octane.nodeType.NT_TEX_MULTIPLY, "Multiply texture", graph) end ---------------------- local function connectInternalNodes() nodePlacementVectron:connectTo(octane.pinId.P_TRANSFORM, inputPins.transform) nodePlacementVectron:connectTo(octane.pinId.P_GEOMETRY, inputPins.vectron) nodeOSLTextureConverter:connectTo("shape", nodePlacementVectron) nodeOSLTextureConverter:connectTo("shapeNoise", inputPins.surfaceNoise) nodeOSLTextureConverter:connectTo("shapeNoiseScale", inputPins.surfaceNoiseScale) nodeOSLTextureConverter:connectTo("densityNoise", inputPins.densityNoise) nodeVolumeMedium:connectTo(octane.pinId.P_SCALE, inputPins.volumeDensity) nodeVolumeMedium:connectTo(octane.pinId.P_RAYMARCH_STEP_LENGTH, inputPins.volumeStepLength) nodeTextureInvert:connectTo(octane.pinId.P_TEXTURE, inputPins.volumeColor) nodeTextureMultiply:connectTo(octane.pinId.P_TEXTURE1, nodeOSLTextureConverter) nodeTextureMultiply:connectTo(octane.pinId.P_TEXTURE2, nodeTextureInvert) nodeVolumeMedium:connectTo(octane.pinId.P_ABSORPTION, nodeTextureMultiply) nodeVolumeMedium:connectTo(octane.pinId.P_SCATTERING, nodeOSLTextureConverter) nodeVolume:connectTo(octane.pinId.P_MEDIUM, nodeVolumeMedium) nodeVolume:connectTo(octane.pinId.P_OBJECT_LAYER, inputPins.objectLayer) nodePlacementVolume:connectTo(octane.pinId.P_TRANSFORM, inputPins.transform) nodePlacementVolume:connectTo(octane.pinId.P_GEOMETRY, nodeVolume) outputPins.geometry:connectTo(octane.pinId.P_INPUT, nodePlacementVolume) end local function isVolumeRebuildRequired(script) return script:inputWasChanged(inputPins.resolution) or script:inputWasChanged(inputPins.voxelSize) end ---------------------- -- Callback which is called after initializing the scripted graph. -- This runs when creating the node, changing the script source, -- clicking the reload button or reopening the scene in Octane. local function onInit(script, graph, _description) description = _description inputPinsInfo = description.getInputPinsInfo() setInputLinkers(graph) local outputPinsInfo = description.getOutputPinsInfo() setOutputLinkers(graph, outputPinsInfo) createInternalNodes(graph) connectInternalNodes() initializeInputNodes() updateInternalNodes(script) octane.nodegraph.unfold(graph, true) end local function onEvaluate(script, graph) -- Depending on the pin changed, rebuild the whole volume. if isVolumeRebuildRequired(script) then updateInternalNodes(script) end return false end return { onInit = onInit, onEvaluate = onEvaluate } end local collection = __collection_loader() ------------------------------------------------------------------------ local function __decoration_loader() local icon = octane.image.fromBase64("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AoUFxYKqn+AdwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAACWUlEQVQ4y5WTX0hTcRTHv+fe6713bs4rbjmb5txyOtjUNKigXhLLerCIkB6CIHoIsnoK8q2efBCiXoNerKceilLwQcEZRURGpli0MZiWbrV2m7vL6b3bfj2EovkPz9OBc86Hc758D7BDPO7yXHvegU7GmLRZndsJ4Lfl/Q4LfACEXQMm7rQ9bHIyX04oOtzd3V29K8Dd8833PIjWkoCqkZ8lzcFgsIkxRtsCjh051A4AjQFP4PppL1nKda8mzlVWH2RVuq4fCAQC5f8D+JWE9ZJzYMZ+y15Zo4t8JnPxqO2UXJ7wSdmUrCzlDVc+m5tWhSlZsRdUVdVW5tYK43h2YqICgB3tGEHMbED+Bpip4C41yN1qlC3nqbQv5KDtNKhZNHD5x8c9FyDndAj0CyVIQ6El7OfcJ5syVwAkNwBYXF6hzuYUalwm7TYWwhbwmIaJwjDRPBTS4CIbAGUt4N8Jq/OYNQl8XUbkLdGkKL6ZFKoVyjs5nTn8LYXJMg+XBuADEFsHICXLWC/FASRUFcwwGdJY1H+289JVlEppLHwPYWx4sNGQ0zlfbbYvHEbrBg2oh80B6BNFxhiR7VxbBco8Z8AJlTDDihb3cYwGuZaZmPRgSxGphyUWCzSuZQSYXF1A4jO0t+8RfjSKha8MapTDp6lI/7ZOdNYVbvYPy+lQKAVt/ANCT14hy3uwVOyaDy1K2pZGWrXwU6ZxSgNNDr5u8JoUjbc6NLFqn/ZyaIgb/6MPqKnUi3Vbb/YH9fXevQ5zUYeUTN4oTv5GXJQQ56X7vNX6LhKJfFnb+xekttqGgpZSngAAAABJRU5ErkJggg==", 3) local color = { 1.0, 0.5156, 0.8932 } return { icon = icon, color = color } end local decoration = __decoration_loader() ------------------------------------------------------------------------ ------------------------------------------------------------------------ local node = {} node._name = "Vectron Volume" node._icon = decoration.icon node._evaluateAllChanges = true -- Callback called after initializing the scripted graph with the code. function node.onInit(self, graph) graph:setAttribute(octane.attributeId.A_COLOR, decoration.color, true) collection.onInit(self, graph, description) end -- Callback called when the connected value of one of the input linker nodes changes. function node.onEvaluate(self, graph) return collection.onEvaluate(self, graph) end return node