[ScriptGraph] Random Grid Scatter

Forums: [ScriptGraph] Random Grid Scatter
Forum for OctaneRender Lua scripting examples, discussion and support.

[ScriptGraph] Random Grid Scatter

Postby Renart » Tue Aug 28, 2018 6:54 am

Renart Tue Aug 28, 2018 6:54 am
Hello!

This is my first humble script!
Since I'm struggling to develop it I decided to post it a W.I.P here with the hope that other people will help me improve it.

This script graph is based on a simple grid scatter I found somewhere in this forum. I decided to take it and see how we can go further with adding random transformation on it.

grid_scatter_example.jpg


The node will take as input the object and create a grid out of it. You can control the number of steps on X, Y and Z axis as well as the distance between them.
There is also a 3D transform input that is used to randomize the grid (rotation, scale, translation).
You can also find a uniform random scale parameter (only the first value work)

The thing that can be improve is the masking option.
At the moment you can plug a black and white map that will be used to turn off the visibility of the object according to the values of the pixels.
In the example above a map is used to create this pattern.
However, the technique to do that is very rough. I'm directly reading the buffer of the image.

My next goal would be to be able to read whatever texture input there is in this "Mask input".
Like this we could use all sort of nodes and transforms to create or modify the masking.
This is where I'm struggling and I would really appreciate some help on that.

I have different ideas of how to continue to improve this script after that.
It would be interesting, for instance, to be able to input a height map. Once I understand how to read texture input properly it shouldn't be too difficult.
The next step after that could be to have another object input, and be able to scatter out object on top of another object. In order to do that I believe it's possible to read the world position of the vertices, so in theory we should be able to find the position of some random point in between these vertices?
It would also be nice to be able to have multiples input objects and to randomly pick them to populate our grid...
We could also have an lua script to paint maps? ^^ I believe I'm stretching the dream a bit too far from my poor skills..

Anyway I hope in the current state it can be helpful for you and please reach out to me if you have any suggestion on how to improve this. I'm slowly learning.

Thanks!

Code: Select all
-----------------------------------------------
--Name : Grid Scattering
--Author : Julien Gauthier
--Use : Scatter input object on a 3D grid with ramdon transformation
--version : 1.0
-----------------Functions---------------------

-- Rounding
function round(n)
    return n % 1 >= 0.5 and math.ceil(n) or math.floor(n)
end

-- Retrieves the value of a texel.
local function getTexPixel(MASKtexels,MASKsize, x, y, maxX, maxY)
    -- adaptation to grid size
    local newX = round( (x*MASKsize[1]) / maxX )
    local newY = round( (y*MASKsize[2]) / maxY )
    -- fix for negative value
    if newX <= 0 then
        newX = 1
    end
    if newY <= 0 then
        newY = 1
    end
    -- index calculation
    local ix = 4 * ((newX-1) +  MASKsize[2] * (newY -1)) + 1

    -- pixel value calculation
    local value = ( MASKtexels[ix] + MASKtexels[ix+1] + MASKtexels[ix+2] ) / ( 255*3 )
    return value
end

------------------------------------------------

local ScriptGraph = {}

local IN_GRID_SIZE
local IN_STEP_SIZE
local IN_TRANSFORM
local IN_SCALE_RAND_UNI
local IN_SEED
local IN_GEOMETRY
local SCATTER_NODE
local IN_TEXT_MASK


function ScriptGraph.onInit(self, graph)

    local inputs = self:setInputLinkers(
        {
            {
                label           = "Grid size",
                type            = octane.PT_INT,
                defaultNodeType = octane.NT_INT,
                defaultValue    = { 4, 1, 4 },
                bounds          = { 1, 10000 },
            },
            {
                label           = "Step size",
                type            = octane.PT_FLOAT,
                defaultNodeType = octane.NT_FLOAT,
                defaultValue    = { 1, 1, 1 },
                sliderBounds    = { 0.001, 1000 },
                logarithmic     = true,
            },
            {
                label           = "Random Transform",
                type            = octane.PT_TRANSFORM,
                defaultNodeType = octane.NT_TRANSFORM_3D,
                defaultValue    = { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
                sliderBounds    = { 0, 1 },
                logarithmic     = true,
            },
            {
                label           = "Random Uniform Scale",
                type            = octane.PT_FLOAT,
                defaultNodeType = octane.NT_FLOAT,
                defaultValue    = { 0, 0, 0 },
                sliderBounds    = { 0, 1 },
                logarithmic     = true,
            },
            {
                label           = "Seed",
                type            = octane.PT_INT,
                defaultNodeType = octane.NT_INT,
                defaultValue    = { 1, 0, 0 },
                bounds          = { 1, 10000 },
            },
            {
                label           = "Geometry",
                type            = octane.PT_GEOMETRY,
            },
            {
                label           = "Mask",
                type            = octane.PT_TEXTURE,
            },
            {
                label           = "Invert Mask",
                type            = octane.PT_BOOL,
                defaultNodeType = octane.NT_BOOL,
            }
        })
    IN_GRID_SIZE = inputs[1]
    IN_STEP_SIZE = inputs[2]
    IN_TRANSFORM= inputs[3]
    IN_SCALE_RAND_UNI = inputs[4]
    IN_SEED = inputs[5]
    IN_GEOMETRY  = inputs[6]
    IN_TEXT_MASK = inputs[7]
    IN_INVERT = inputs[8]



   
    -- create the output linker
    local outputLinker = graph:setOutputLinkers{{ label = "Output", type = octane.PT_GEOMETRY }}[1]
   
    -- create the scatter node and hook it up with the output linker and the geometry input
    SCATTER_NODE = octane.node.create(
        {
            type       = octane.NT_GEO_SCATTER,
            graphOwner = graph,
        })
    SCATTER_NODE:connectTo(octane.P_GEOMETRY, IN_GEOMETRY)
    outputLinker:connectTo(octane.P_INPUT, SCATTER_NODE)
   
    -- update scatter matrices
    self:onEvaluate(self, graph)

end

------------------------------------------------

-- callback to update the script graph. gets called every time the inputs changed.
function ScriptGraph.onEvaluate(self, graph)

    -- fetch randomize input parameters
    local gridSize = self:getInputValue(IN_GRID_SIZE)
    local stepSize = self:getInputValue(IN_STEP_SIZE)

    local randScaleUni = self:getInputValue(IN_SCALE_RAND_UNI)
    local randScale1 = self:getInputValue(IN_TRANSFORM)
   
    local randRot, randScale, randTrans = octane.matrix.split(randScale1,0)

    local seed = self:getInputValue(IN_SEED)

----------------------
    -- fetch mask information
    local maskConnected = 0
    local MASKtexels = 0
    local MASKsize = 0
    local MASKinvert = 0

    -- check if a map is connected
    if IN_TEXT_MASK:getInputNode(octane.P_INPUT) ~= nil then
        MASKtexels = IN_TEXT_MASK:getInputNode(octane.P_INPUT):getAttribute(octane.A_BUFFER)
        MASKsize = IN_TEXT_MASK:getInputNode(octane.P_INPUT):getAttribute(octane.A_SIZE)
        MASKinvert = self:getInputValue(IN_INVERT)
        maskConnected = 1
    end
    print(string.format("maskConnected = %d",maskConnected))

    -- invert option
    local invert = 0
    if MASKinvert == true then
        invert = 1
        print("inverted")
    end

----------------------
    -- crete new array of transformations
    local transformations = {}
    local instanceIDs = {}
    local index = 1

    math.randomseed(seed[1])

    for z = 1, gridSize[3] do
        for x = 1, gridSize[1] do
            for y = 1, gridSize[2] do
                --Random transformation
                local xScale = ( randScale[1] - 1 ) * math.random(1,10) + 1
                local zScale = ( randScale[3] - 1 ) * math.random(1,10) + 1
                local yScale = ( randScale[2] - 1 ) * math.random(1,10) + 1

                local xPos = (x-1) * stepSize[1] + randTrans[1] * math.random(-10,10)
                local yPos = (y-1) * stepSize[2] + randTrans[2] * math.random(-10,10)
                local zPos = (z-1) * stepSize[3] + randTrans[3] * math.random(-10,10)

                local xRot = randRot[1] * math.random(-10,10)
                local yRot = randRot[2] * math.random(-10,10)
                local zRot = randRot[3] * math.random(-10,10)
                --Uniform random transformation
                local Random = math.random(-10,10)

                local xScale = xScale + ( randScaleUni[1] * Random )
                local yScale = yScale + ( randScaleUni[1] * Random )
                local zScale = zScale + ( randScaleUni[1] * Random )

                --Masking
                if maskConnected == 1 then
                    currentMaskPixel = getTexPixel(MASKtexels,MASKsize, x, z, gridSize[1], gridSize[3] )
                    currentMaskPixel = round(currentMaskPixel)
                    currentMaskPixel = math.abs(invert - currentMaskPixel)
                else
                    currentMaskPixel = 1
                end

                --Transform calculation
                if currentMaskPixel >= 1 then
                    transformations[index] = octane.matrix.make3dTransformation({ xRot, yRot, zRot }, {xScale,yScale,zScale}, { xPos, yPos, zPos }, 1 )
                    instanceIDs[index] = index-1
                    index = index + 1
                end
            end
        end
    end
   
    -- set transformations in the scatter node
    SCATTER_NODE:setAttribute(octane.A_TRANSFORMS, transformations, false)
    SCATTER_NODE:setAttribute(octane.A_USER_INSTANCE_IDS, instanceIDs, false)
    SCATTER_NODE:evaluate()

end



-- return the script graph table
return ScriptGraph
Attachments
Scattering_Grid_Mask.orbx
(114.84 KiB) Downloaded 39 times
Last edited by Renart on Wed Aug 29, 2018 6:11 pm, edited 1 time in total.
Renart
Licensed Customer
Licensed Customer
 
Posts: 68
Joined: Mon Jul 04, 2016 8:21 pm

Re: [ScriptGraph] Procedural Grid Scatter

Postby whersmy » Tue Aug 28, 2018 12:28 pm

whersmy Tue Aug 28, 2018 12:28 pm
Very interested! Keep us posted.

Personally I would love to have a simple plane where I can scatter different objects like trees and bushes. Maybe one or two different tree objects that can differ in size and rotation.
mac pro g5| pentium g2030 iGPU| maximus extreme V| 2x gtx590 - 8gb - SSD - win7-x64- 1500W Silverstone|
x201t - gtx580 - egpu ec
Octane Render experiments
User avatar
whersmy
Licensed Customer
Licensed Customer
 
Posts: 560
Joined: Thu Aug 30, 2012 7:40 am

Re: [ScriptGraph] Procedural Grid Scatter

Postby Renart » Tue Aug 28, 2018 3:55 pm

Renart Tue Aug 28, 2018 3:55 pm
Well, you can do this already.
You need to set the second grid size parameter to 1to scatter your object only on a 2D grid.
Then you change the Random Transformation input to add random translation, rotation and scale if you want, then you can create a forest (see image attached)

This example have been made pretty quickly for demonstration purposes. Please don't judge the render ^^
I've combined different scattering. 2 Trees and 1 clamp of grass. I used an image to breakup a bit the scattering and added random tranform on everything.
To get better I should add instance color variation on my models.

Cheers!
Attachments
Untitled16541.jpg
Renart
Licensed Customer
Licensed Customer
 
Posts: 68
Joined: Mon Jul 04, 2016 8:21 pm

Return to Lua Scripting


Who is online

Users browsing this forum: No registered users and 2 guests

Sun Jun 16, 2019 10:34 pm [ UTC ]