Page 1 of 1

### Creating your own geometry with the Octane Lua API.

Posted: Mon Dec 09, 2013 2:40 am
Hi all,

In this second example we'll show how to create your own geometry from the Octane Lua Api. We'll create a simple cube and assign a material to each face of the cube. We do this by creating a mesh node and directly manipulating it's attributes. Be aware that if you set up a mesh node incorrectly you could potentially crash Octane!

All it boils down to is setting up the attributes of the mesh node correctly. Let's see what the API browser has to say about mesh nodes (type NT_GEO_MESH):

Code: Select all
`   A_VERTICES_PER_POLY         • Description    : An array of the number of vertices for each polygon in the polygon sequence. Must not be empty.         • Type           : AT_INT         • Array          : true    A_VERTICES         • Description    : An array of vertex positions. Must not be empty.         • Type           : AT_FLOAT3         • Array          : true    A_POLY_VERTEX_INDICES         • Description    : An array that stores the indices into A_VERTICES for each vertex of each polygon. Its size must be equal to the sum of all vertex counts in A_VERTICES_PER_POLY.         • Type           : AT_INT         • Array          : true    A_NORMALS         • Description    : An array of vertex normals. If not empty, but A_POLY_NORMAL_INDICES is empty this array must directly store the normals for each vertex of each polygon. If left empty, the vertex normals will be calculated on-the-fly. To calculate the vertex normals the geometry import preferences as well as the smooth groups (if specified) will be used.         • Type           : AT_FLOAT3         • Array          : true    A_POLY_NORMAL_INDICES         • Description    : An array that stores the indices into A_NORMALS for each vertex of each polygon. Must be either empty or its size must be equal to the sum of all vertex counts in A_VERTICES_PER_POLY.         • Type           : AT_INT         • Array          : true    A_SMOOTH_GROUPS         • Description    : If A_NORMALS is empty and the vertex normals will be calculated on-the-fly, you can specify a smooth group for each polygon. The edges between different smooth groups will not be smoothed. Also all polygons with a negative smooth group will not be smoothed at all. If not empty, the size of this array must be equal to the size of A_VERTICES_PER_POLY.          • Type           : AT_INT         • Array          : true    A_UVWS         • Description    : An array of UVW coordinates. If empty, every vertex will have a UVW coordinate of (0,0,0). If not empty, but A_POLY_UVW_INDICES is empty this array mst directly store the UVW coordinates for each vertex of each polygon, i.e. its size must be the sum of all elements of A_VERTICES_PER_POLY.         • Type           : AT_FLOAT3         • Array          : true    A_POLY_UVW_INDICES         • Description    : An array that stores the indices into A_UVWS for each vertex of each polygon.Must be either empty or its size must be equal to the sum of all vertex counts in A_VERTICES_PER_POLY.         • Type           : AT_INT         • Array          : true    A_MATERIAL_NAMES         • Description    : An array of material names. Must not be empty.         • Type           : AT_STRING         • Array          : true    A_POLY_MATERIAL_INDICES         • Description    : An array that stores the indices into A_MATERIAL_NAMES for each polygon, i.e. the size must be equal to the size of A_VERTICES_PER_POLY.         • Type           : AT_INT         • Array          : true`

The ones we're going to use here are A_VERTICES_PER_POLY, A_VERTICES, A_POLY_VERTEX_INDICES, A_MATERIAL_NAMES, A_POLY_MATERIAL_INDICES. We're only going to specify the vertices, faces and materials ourselves. We don't set up the normals and texture coordinates. Octane will generate the normals for us automatically and since we're not going to texture the cube we don't bother with assigning texture coordinates.

First we make sure we start with a clean slate:

Code: Select all
`-- start with a clean slateoctane.project.reset()`

Usually a cube has 8 vertices and 6 faces. We need to set-up the node with the vertices, the vertex count for each face and the vertices used for each face. Note that the polyVertexIndices array uses 0-based indices (while Lua usually used 1-based indices):

Code: Select all
`-- vertices for the cubevertices = {    { -1. , -1. ,  1.} ,    { -1. ,  1. ,  1.} ,    { 1.  ,  1. ,  1.} ,    { 1.  , -1. ,  1.} ,    { -1. , -1. , -1.} ,    { -1. ,  1. , -1.} ,    { 1.  ,  1. , -1.} ,    { 1.  , -1. , -1.} ,}-- 4 vertices per poly (each face is a quad)verticesPerPoly ={     4, -- face1    4, -- face2    4, -- face3    4, -- face4    4, -- face5    4, -- face6}-- tells which vertices are used for each face of the cube-- each number is an index in vertices-- NOTE: indices are 0-based!polyVertexIndices ={    3 , 2 , 1 , 0 ,     -- face1    0 , 1 , 5 , 4 ,     -- face2    6 , 5 , 1 , 2 ,     -- face3    3 , 7 , 6 , 2 ,     -- face4    4 , 7 , 3 , 0 ,     -- face5    4 , 5 , 6 , 7       -- face6}`

We assign a different material to each face of the cube (this will create a pin for each material name on the resulting mesh node). We create an array with material names
and an array that assigns each face a material name:

Code: Select all
`-- material names (we use 1 material per face)materialNames = { "f1", "f2", "f3", "f4", "f5", "f6" }-- material indices (we assign 1 material per face)-- NOTE: indices are 0-based!polyMaterialIndices = { 0, 1, 2, 3, 4, 5 }`

Now that we have all our arrays in place we can create the mesh node. This will only give us a mesh node. We still need to set up the node's attributes correctly.

Code: Select all
`-- create the actual mesh node, at this point the mesh node isn't usable yet!meshNode = octane.node.create{ type=octane.NT_GEO_MESH , name="MyCube", position={ 500, 500 } }`

Setting up the attributes is easy. Note that the third parameter of setAttribute is false. This means we want to set the attribute but don't evaluate the node. Evaluation of a node (or graph)means that Octane will check if the attributes have changed and then do something useful with them. It's dangerous to evaluate a node (or graph) while it's not set up correctly (If you're lucky it will give you an error, if you're unlucky it will crash Octane). Only when all attributes are set up correctly, we call evaluate on the node.

Code: Select all
`-- set up the geometry attributes. we shouldn't evaluate them immediately!meshNode:setAttribute(octane.A_VERTICES              , vertices            , false)meshNode:setAttribute(octane.A_VERTICES_PER_POLY     , verticesPerPoly     , false)meshNode:setAttribute(octane.A_POLY_VERTEX_INDICES   , polyVertexIndices   , false)meshNode:setAttribute(octane.A_MATERIAL_NAMES        , materialNames       , false)meshNode:setAttribute(octane.A_POLY_MATERIAL_INDICES , polyMaterialIndices , false)-- all is set up correctly, now we can evaluate the geometrymeshNode:evaluate()`

At this point, the project will have a mesh node with empty material pins. We'll create a diffuse material in each pin with a different colour for each face. To do this we set up a table mapping the material name to a colour. Then we'll create a diffuse material in each pin and set up the diffuse channel with the colour from the table:

Code: Select all
`-- colours for each facefaceColours ={    f1 = { 255, 0, 0   },    f2 = { 0, 255, 0   },    f3 = { 0, 0, 255   },    f4 = { 255, 255, 0 },    f5 = { 255, 0, 255 },    f6 = { 0, 255, 255 },}-- create a diffuse material in each input pin, the input pins will have the same-- names as the material names defined abovefor _, matName in ipairs(materialNames) do    -- create each diffuse material internal to the pin    matNode = octane.node.create    {         type         = octane.NT_MAT_DIFFUSE,        name         = "Diffuse_"..matName,        pinOwnerNode = meshNode,        pinOwnerName = matName    }    -- set the diffuse colour of the material via the pin    matNode:setPinValue(octane.P_DIFFUSE, faceColours[matName])end`

Now our cube is fully set up. Optionally, we can save out the cube into an external obj file:

Code: Select all
`-- let's export our cube to an obj file. The file name will be the node's name in the-- geometry directory (e.g. /tmp/LuaCube/geometry/MyCube.obj)directory = "/tmp/LuaCube"meshNode:exportToFile(directory)`

As an extra I just show you how you would create a mesh from an obj file. All we have to do is create a mesh node, set it's file name attribute (octane.A_FILENAME) and evaluate it. The evaluation of the node will take care of everything:

Code: Select all
`-- let's load the obj file again.loadedMesh = octane.node.create{ type=octane.NT_GEO_MESH , name="Loaded Cube", position={ 550, 550 } }loadedMesh:setAttribute(octane.A_FILENAME, "/tmp/LuaCube/geometry/MyCube.obj", true)`

If you typed over everything carefully, you should end up with this:

Code: Select all
`---- create a textured cube from Lua.---- start with a clean slateoctane.project.reset()-- vertices for the cubevertices = {    { -1. , -1. ,  1.} ,    { -1. ,  1. ,  1.} ,    { 1.  ,  1. ,  1.} ,    { 1.  , -1. ,  1.} ,    { -1. , -1. , -1.} ,    { -1. ,  1. , -1.} ,    { 1.  ,  1. , -1.} ,    { 1.  , -1. , -1.} ,}-- 4 vertices per poly (each face is a quad)verticesPerPoly ={     4, -- face1    4, -- face2    4, -- face3    4, -- face4    4, -- face5    4, -- face6}-- tells which vertices are used for each face of the cube-- each number is an index in vertices-- NOTE: indices are 0-based!polyVertexIndices ={    3 , 2 , 1 , 0 ,     -- face1    0 , 1 , 5 , 4 ,     -- face2    6 , 5 , 1 , 2 ,     -- face3    3 , 7 , 6 , 2 ,     -- face4    4 , 7 , 3 , 0 ,     -- face5    4 , 5 , 6 , 7       -- face6}-- material names (we use 1 material per face)materialNames = { "f1", "f2", "f3", "f4", "f5", "f6" }-- material indices (we assign 1 material per face)-- NOTE: indices are 0-based!polyMaterialIndices = { 0, 1, 2, 3, 4, 5 }-- create the actual mesh node, at this point the mesh node isn't usable yet!meshNode = octane.node.create{ type=octane.NT_GEO_MESH , name="MyCube", position={ 500, 500 } }-- set up the geometry attributes. we shouldn't evaluate them immediately!meshNode:setAttribute(octane.A_VERTICES              , vertices            , false)meshNode:setAttribute(octane.A_VERTICES_PER_POLY     , verticesPerPoly     , false)meshNode:setAttribute(octane.A_POLY_VERTEX_INDICES   , polyVertexIndices   , false)meshNode:setAttribute(octane.A_MATERIAL_NAMES        , materialNames       , false)meshNode:setAttribute(octane.A_POLY_MATERIAL_INDICES , polyMaterialIndices , false)-- all is set up correctly, now we can evaluate the geometrymeshNode:evaluate()-- colours for each facefaceColours ={    f1 = { 255, 0, 0   },    f2 = { 0, 255, 0   },    f3 = { 0, 0, 255   },    f4 = { 255, 255, 0 },    f5 = { 255, 0, 255 },    f6 = { 0, 255, 255 },}-- create a diffuse material in each input pin, the input pins will have the same-- names as the material names defined abovefor _, matName in ipairs(materialNames) do    -- create each diffuse material internal to the pin    matNode = octane.node.create    {         type         = octane.NT_MAT_DIFFUSE,        name         = "Diffuse_"..matName,        pinOwnerNode = meshNode,        pinOwnerName = matName    }    -- set the diffuse colour of the material via the pin    matNode:setPinValue(octane.P_DIFFUSE, faceColours[matName])end-- let's export our cube to an obj file. The file name will be the node's name in the-- geometry directory (e.g. /tmp/LuaCube/geometry/MyCube.obj)directory = "/tmp/LuaCube"meshNode:exportToFile(directory)-- let's load the obj file again.loadedMesh = octane.node.create{ type=octane.NT_GEO_MESH , name="Loaded Cube", position={ 550, 550 } }loadedMesh:setAttribute(octane.A_FILENAME, "/tmp/LuaCube/geometry/MyCube.obj", true)`

If you render your cube it should look something like this:

the cube created from Lua

have fun,
Thomas

### Re: Creating your own geometry with the Octane Lua API.

Posted: Mon Jan 18, 2016 4:17 am
Excuse the newbie question; how would a sphere setup be different to this;

If you scale the vertices and faces method it seems it would become exponentially more difficult before the vertices of the sphere were no longer discernible from the overall sphere form itself... (ie hexagon < Circle)

No idea if there is some inherent understanding of primitives opened to the Lua hooks which would allow a spherical primitive to be created?

Happy to just be pointed to a location in the documentation (or the whole thing if you know its noted in there somewhere)

### Re: Creating your own geometry with the Octane Lua API.

Posted: Mon Jan 18, 2016 7:30 am
I don't understand your question. Octane doesn't have a sphere primitive. It only understands vertices and faces. This means you have to create your sphere from triangles or quads.

### Re: Creating your own geometry with the Octane Lua API.

Posted: Mon Jan 18, 2016 7:37 am
That's what I thought/was trying to explain.

So I'd have to make a sphere sufficient dense (in Tri/quads) that it appeared rounded... Is there a math function that could be used in a For() statement ? Not sure where to start... Prob easier to just stick my wth modelling app!

### Re: Creating your own geometry with the Octane Lua API.

Posted: Mon Jan 18, 2016 7:24 pm
prehabitat wrote:That's what I thought/was trying to explain.

So I'd have to make a sphere sufficient dense (in Tri/quads) that it appeared rounded... Is there a math function that could be used in a For() statement ? Not sure where to start... Prob easier to just stick my wth modelling app!

You would need a sphere described as vertices and faces. You could take it from an obj file. I wouldn't try to create a sphere manually. There's no function (that I know) that spits out the triangles that make up a sphere.

cheers,
Thomas