OXa^octane_versionf11000011?®------------------------------------- -- Wireframe. -- Replaces polygon edges with hairs and vertices with spheres. -- @author Otoy -- @version 1.1 -- @script-id 66156b5a-bc80-4ae3-99f8-6da6bb99691c -- @node-registry-category |Geometry|Wireframe ----------------------------------------- local inputLinkersInfo = {} local outputLinkersInfo = {} local inputLinkers = {} local outputLinkers = {} local nodeGeometry = nil ---------------------------------------------------- -- Returns a key to uniquely identify polygon edges based on the endpoints indices. local function getEdgeKey(p1, p2) if p1 < p2 then return string.format("%d;%d", p1, p2) else return string.format("%d;%d", p2, p1) end end ---------------------------------------------------- -- Process one mesh node: replace edges by hairs and vertices by spheres. local function processMeshNode(script, node, buildGeometry) -- Update the sphere radius and hair thickness local sphereRadiuses = { script:getInputValue(inputLinkers.vertexRadius) } local hairThickness = { script:getInputValue(inputLinkers.edgeRadius) * 2 } node:setAttribute(octane.attributeId.A_SPHERE_RADIUSES, sphereRadiuses, false) node:setAttribute(octane.attributeId.A_HAIR_THICKNESS, hairThickness, false) if not buildGeometry then -- We're done, we've been through the geometry in a previous pass. node:evaluate() return end local verticesPerPoly = node:getAttribute(octane.attributeId.A_VERTICES_PER_POLY) local polyVertexIndices = node:getAttribute(octane.attributeId.A_POLY_VERTEX_INDICES) local vertices = node:getAttribute(octane.attributeId.A_VERTICES) if polyVertexIndices == nil or verticesPerPoly == nil or #verticesPerPoly == 0 or vertices == nil or #vertices == 0 then -- Skip the mesh if it doesn't have any polygons. return end -- Get rid of the filename attribute, -- In the case of an .OBJ based mesh node, this prevents the node evaluation from importing back the OBJ file, -- which would overwrite our attributes. node:setAttribute(octane.attributeId.A_FILENAME, "") node:updateProperties({name=node.name.."_copy"}) -- Deduplicating maps. local mapVertices = {} local mapEdges = {} -- Data arrays we are filling. local sphereCenters = {} local verticesPerHair = {} local hairVertices = {} -- Loop over all polygons. local polyFirstVertexIndex = 1 for i=1, #verticesPerPoly do -- Loop over vertices of the polygon. local count = verticesPerPoly[i] for j=0, count-1 do local ix1 = polyFirstVertexIndex + j local ix2 = polyFirstVertexIndex + ((j + 1) % count) local vIx1 = polyVertexIndices[ix1] + 1 local vIx2 = polyVertexIndices[ix2] + 1 if not mapVertices[vIx1] then mapVertices[vIx1] = true; table.insert(sphereCenters, vertices[vIx1]) end if not mapVertices[vIx2] then mapVertices[vIx2] = true table.insert(sphereCenters, vertices[vIx2]) end local key = getEdgeKey(vIx1, vIx2) if not mapEdges[key] then mapEdges[key] = true table.insert(verticesPerHair, 2) table.insert(hairVertices, vertices[vIx1]) table.insert(hairVertices, vertices[vIx2]) end end polyFirstVertexIndex = polyFirstVertexIndex + count end -- Add hair and sphere data. node:setAttribute(octane.attributeId.A_SPHERE_CENTERS, sphereCenters, false) node:setAttribute(octane.attributeId.A_VERTICES_PER_HAIR, verticesPerHair, false) node:setAttribute(octane.attributeId.A_HAIR_VERTICES, hairVertices, false) -- Remove polygon data. node:setAttribute(octane.attributeId.A_VERTICES_PER_POLY, {}, false) node:setAttribute(octane.attributeId.A_POLY_VERTEX_INDICES, {}, false) node:setAttribute(octane.attributeId.A_VERTICES, {}, false) node:evaluate() end ------------------------------------------------------------------------ -- Recursively process an item. local function processItem(script, node, buildGeometry) if node == nil then return end if node.type == octane.nodeType.NT_GEO_MESH then processMeshNode(script, node, buildGeometry) elseif node.isOutputLinker then -- In this case the downstream node was a nodegraph, a geo archive, a scripted graph or a wrapped scripted graph. -- We are now inside the graph, walk up and continue. processItem(script, node:getConnectedNode(octane.pinId.P_INPUT, true), buildGeometry) else -- In this case the downstream node was some other geometry node. -- For example a Geometry group, a scatter node, a volume, a placement, etc. -- We'll loop over all the inputs and process them. -- There is a potential pitfall here. If we are inside a scripted graph, modifying the input -- will trigger `eval()` on the script we are in and the node we are looping over will be -- destroyed and recreated. This is the case with the Scatter tools for example. for i=1, node:getPinCount() do local pinInfo = node:getPinInfoIx(i) if pinInfo.type == octane.pinType.PT_GEOMETRY then processItem(script, node:getInputNodeIx(i, true), buildGeometry) end end end end -------------------------------------------------- -- Destroy an item and all its inputs recursively. local function destroyItemTree(item) if item == nil then return end if item.isOutputLinker and item.graphOwned then item = item.graphOwner end if item.isGraph then local inputNodes = item:getInputNodes() for _, inputNode in ipairs(inputNodes) do local connectedNode = inputNode:getConnectedNode(octane.pinId.P_INPUT, false) destroyItemTree(connectedNode) end else for pinIx = 1, item:getPinCount() do local inputNode = item:getInputNodeIx(pinIx, false) destroyItemTree(inputNode) end end item:destroy() end -------------------------------------------------------------- -- Destroy the internal copy of the input node and disconnect the output. local function clean() outputLinkers.output:disconnect(octane.pinId.P_INPUT) destroyItemTree(nodeGeometry) nodeGeometry = nil end ----------------------------------------- -- Copy the input node into our own graph. local function copyInput(graph, linker) local inputItem = linker:getConnectedNode(octane.pinId.P_INPUT, false) if inputItem == nil then return nil end if inputItem.isOutputLinker then inputItem = inputItem.graphOwner end local copy = graph:copyItemTree(inputItem) return copy end ------------------------------------ -- The input geometry was changed, re-process it. local function updateInternals(script, graph, init) local buildGeometry = script:inputWasChanged(inputLinkers.inputGeometry) or init if buildGeometry then clean() nodeGeometry = copyInput(graph, inputLinkers.inputGeometry) end if nodeGeometry == nil then return end -- Connect to our output. -- We do this first so we can use our output linker as the start of the recursion. -- Otherwise there is no way to know if the input node is a normal node or a wrapper node. if nodeGeometry.isGraph then local outputNode = nodeGeometry:findFirstOutputNode(octane.pinType.PT_GEOMETRY) if outputNode ~= nil then outputLinkers.output:connectTo(octane.pinId.P_INPUT, outputNode) end else outputLinkers.output:connectTo(octane.pinId.P_INPUT, nodeGeometry) end local item = outputLinkers.output:getConnectedNode(octane.pinId.P_INPUT, true) processItem(script, item, buildGeometry) end ----------------------------------------------------- local function getInputLinkersInfo() local linkerInfoInputGeometry = { key = "inputGeometry", label = "Geometry", description = "Geometry that will be duplicated and replaced with its wireframe version.", type = octane.pinType.PT_GEOMETRY, defaultNodeType = octane.nodeType.NT_GEO_MESH, } local linkerInfoSphereRadius = { key = "vertexRadius", label = "Vertex radius", description = "Radius of spheres replacing the vertices of the original mesh.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0.01, bounds = {0.0001, 100}, logarithmic = true } local linkerInfoHairThickness = { key = "edgeRadius", label = "Edge radius", description = "Radius of hairs replacing the edges of the original mesh.", type = octane.pinType.PT_FLOAT, defaultNodeType = octane.nodeType.NT_FLOAT, defaultValue = 0.01, bounds = {0.0001, 100}, logarithmic = true } return {linkerInfoInputGeometry, linkerInfoSphereRadius, linkerInfoHairThickness} end local function getOutputLinkersInfo() local linkerInfoOutput = { key = "output", label = "Geometry out", type = octane.pinType.PT_GEOMETRY } return { linkerInfoOutput } 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 -------------------------------------------- local node = {} node._name = "Wireframe" node._icon = octane.image.fromBase64("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACF0lEQVQ4jZWSX0hTcRTHP3f3ZqlbLBnoXJhoEc1hy/XHdA+jP64Mi8hQkAoKgtbTiCh6C6KHQIhCe7MSeomIIDAMQqtpD2YJMrOgqczpNJ3byt1NN9eDOSy5G31ffvwO53zOOV+OSHplAQbAAYwAESCZoeYv5YuidL3+3AW/SlrnBLT/UywAu082nhkYC0aSZeY9nYDp3yRVGkAusL2+saHcPTmH5cCxGmAboAbEPw0QFYpFoDQnV33qUfvDvW/6h8kVRT5+cGmWor+0kkBZAsLAvJSm+/725y8vzslLhH0+pnvec/N09dGDdXZ7PL6YbDh79b4nFGlTWqHAtKuirrpynzQ2MY00PoJZF8D54JlQvrNUrDD8lF48vuPcsUnTpDiByWyxxpICfv8Mqlk/thN28HXBvJfZ3kH4PMWhLYWXlADzX4fcPZ8G3ceTkRhZKgFZjpIYcfHj7QC+vnHiagMxObIoKAA2AiXAEZ2+qNagK9gqBDz6d23NuO+2oNJtJhwKcf5V1zUlACxfYTagAcQaU/FopTGbueEE8ag883p04t73hcRTCaCpytjC8rmuqPVJ79BlYAEIAUKeegOHbXpuffHQ+c1bC0wCUyseOGpteVgtWlz9QTq6A46mKuNqIMWF6ynKz0GnyQHoW4mnTLRatKm3ozvA7Ssla3byeOU1sRTA1R9MTQBwo9mj5E3r6o+QwYOM+g29HLcZ+SC1fAAAAABJRU5ErkJggg==", 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, { 1.0, 0.5156, 0.8932 }, true) inputLinkersInfo = getInputLinkersInfo() setInputLinkers(graph) outputLinkersInfo = getOutputLinkersInfo() setOutputLinkers(graph) updateInternals(self, graph, true) end -- Callback called when the connected value of one of the input linker nodes changes. function node.onEvaluate(self, graph) updateInternals(self, graph, false) end return node?Àý 2 0 1 0 0 1 0 0 0 0 0 0 0 24 0 36 0 0 50.000004 0 0 2.8 0 0 39.597752 0 0 0.80498445 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1e+10 0 0 1 1.118034 0 0 0.89285719 0 0 1 0 0 1 0 0 6 0 0 0 0 0 1 0 0 0 0.5 1 0 0 0 0 1 0 0 1 0.064999998 0 0 0 1 0 0.81199998 0 1 0.18799999 1 1 1 0.64999998 0 0 1 1 0 0 1023 1024 512 0 10000 0 0 0 0 65536 65536 0 3 0.2 0 0 0 0 0 1 0 0 500 0 0 3 5 0 0 2 0 0 2 0 0 4 0 0 9.9999997e-05 0 0 1.2 0 0 3 0 0 1 1 0 10 0 0 0 0 0 1 1 0 0 0.30000001 0 0 0 0 0 0 16 0 0 32 0 0 1 0 0.02 0 0 512 0 0 2 0 0 0 1 0 0 0 8 0 0 0.050000001 0 0 0.5 0.5 0.5 0 1 0 0 0 0 0 1 0 6 0 0 10 0 0 128 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 0 0 10 0 0 1 0 0 0 0 0 0 0 0 401 0 1 0 0 0 0 1 10 0 0 20 0 0 0 0 0 1 1 1 10 0 0 10 0 0 0 0 0 0 1 0 0 1 0 0 3 0 0 15 0 0 0.001 0 0 0 0 0 2 0 0 1 0.51560003 0.89319998 assets\wireframe.lua 1 Geometry that will be duplicated and replaced with its wireframe version. Radius of spheres replacing the vertices of the original mesh. 0.025 0 0 Radius of hairs replacing the edges of the original mesh. 0.0050000004 0 0 <?xml version='1.0' encoding='utf-8'?> <storage> <table name="params"><table name="Figure eight"><number name="r1">0.5</number> <number name="a">1</number> <number name="subdivLevel">5</number> </table> <table name="Torus"><number name="r1">0.5</number> <number name="subdivLevel">5</number> <number name="r2">0.25</number> </table> <table name="Box"><number name="depth">1</number> <number name="width">1</number> <number name="height">1</number> <number name="subdivLevel">0</number> </table> <table name="Hyperboloid"><number name="r1">1</number> <number name="a">1</number> <number name="b">0.2</number> <number name="subdivLevel">5</number> </table> <table name="Ding dong"><number name="r1">0.5</number> <number name="a">1</number> <number name="subdivLevel">5</number> </table> <table name="Elliptic torus"><number name="r1">0.5</number> <number name="a">0.25</number> <number name="b">0.5</number> <number name="subdivLevel">5</number> </table> </table> <string name="primitive">Box</string> </storage> 4 0.59675241 0.0862284 0.0862284 0 1 0 0 1 0 0 0 1 0 0 8 0 0 0 0 0 0 0 4096 0 0 1 0 0 1 0 0 1 1 1 1023 0 0 0 0 0 0 4096 0 1 0 0 2 -0 0 0 1 1 1 0 0 0 2 -0 0 0 1 1 1 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 36 0 0 50.000004 0 0 1000 0 0 39.597752 0 0 2.1412377 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1e+10 0 0 1 1.118034 0 0 0 0 0 1 0 0 1 0 0 6 0 0 0 0 0 1 0 0 -1.3925252 0.79147911 2.4635525 -0.0095896199 -0.10771196 -0.010972099 0 1 0 0 1 0.064999998 0 0 0 1 0 0.81199998 0 1 0.18799999 0.7252422 0.65818739 -0.20202243 -29.062498 0 0 166.01038 0 0 3 0 0 1 0 0 10 0 0 12 0 0 2.4000001 0 0 1 0 0 1 0 0 0 0 0 1 0.050000001 0.30000001 1 0.60000002 0.12 0.02 1 0 0 0.031928182 0.1304338 0.55729163 90 0 0 5 0 0 1 1 0 0 1023 0 0 0 800 600 0 403 0 0 0 0 65536 65536 0 3 0.2 0 0 0 0 0 1 0 0 200 0 0 3 5 0 0 2 0 0 2 0 0 4 0 0 9.9999997e-05 0 0 1.2 0 0 3 0 0 1 0 0 10 0 0 0 0 0 1 1 0 0 0.30000001 0 0 0 0 0 0 8 0 0 16 0 0 1 0 0.02 0 0 512 0 0 2 1 0 0 0 1 0 0 8 0 0 0.050000001 0 0 0.5 0.5 0.5 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 10 0 0 0 0 0 128 0 0 0 1 1 0 0 1 0 0 0 0 5 0 0 1 0 1 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 0 3 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0.30000001 0 0 1 1 1 1 0 0 0 0 0 1 0 0 20 0 0 1 0 0 0 0 0 0 0 0 105 0 1 0 0 0 0 1 10 0 0 20 0 0 0 0 0 1 1 1 10 0 0 10 0 0 0 0 0 0 1 0 0 1 0 0 3 0 0 15 0 0 0.001 0 0 0 0 0 2 0 0 yyfile;CÁ+qq3SÀ.3À. SÀý ii#3À.>assets/wireframe.lua#3Àý > project.ocsUZŸyyfile;CÁ+qq3SÀ.3À. SÀý ii#3À.>assets/wireframe.lua#3Àý > project.ocsUZŸ