Creating your own geometry with the Octane Lua API.

Forums: Creating your own geometry with the Octane Lua API.
Forum for OctaneRender Lua scripting examples, discussion and support.

Creating your own geometry with the Octane Lua API.

Postby stratified » Mon Dec 09, 2013 2:40 am

stratified 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 slate
octane.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 cube
vertices =
{
    { -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 geometry
meshNode: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 face
faceColours =
{
    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 above
for _, 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 slate
octane.project.reset()

-- vertices for the cube
vertices =
{
    { -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 geometry
meshNode:evaluate()

-- colours for each face
faceColours =
{
    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 above
for _, 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:

lua_cube.png
the cube created from Lua


have fun,
Thomas
User avatar
stratified
OctaneRender Team
OctaneRender Team
 
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

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

Postby prehabitat » Mon Jan 18, 2016 4:17 am

prehabitat 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)

Thanks in advance!
Win10/3770/16gb/K600(display)/GTX780(Octane)/GTX590/372.70
Octane 3.x: GH Lands VARQ Rhino5 -Rhino.io- C4D R16 / Revit17
prehabitat
Licensed Customer
Licensed Customer
 
Posts: 495
Joined: Fri Aug 16, 2013 10:30 am
Location: Victoria, Australia

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

Postby stratified » Mon Jan 18, 2016 7:30 am

stratified 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.
User avatar
stratified
OctaneRender Team
OctaneRender Team
 
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

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

Postby prehabitat » Mon Jan 18, 2016 7:37 am

prehabitat 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! :)
Win10/3770/16gb/K600(display)/GTX780(Octane)/GTX590/372.70
Octane 3.x: GH Lands VARQ Rhino5 -Rhino.io- C4D R16 / Revit17
prehabitat
Licensed Customer
Licensed Customer
 
Posts: 495
Joined: Fri Aug 16, 2013 10:30 am
Location: Victoria, Australia

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

Postby stratified » Mon Jan 18, 2016 7:24 pm

stratified 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
User avatar
stratified
OctaneRender Team
OctaneRender Team
 
Posts: 945
Joined: Wed Aug 15, 2012 6:32 am
Location: Auckland, New Zealand

Return to Lua Scripting


Who is online

Users browsing this forum: No registered users and 1 guest

Wed Jul 24, 2019 2:44 am [ UTC ]