Page 1 of 1

[ScriptGraph] Random Grid Scatter

PostPosted: Tue Aug 28, 2018 6:54 am
by Renart
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

Re: [ScriptGraph] Procedural Grid Scatter

PostPosted: Tue Aug 28, 2018 12:28 pm
by whersmy
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.

Re: [ScriptGraph] Procedural Grid Scatter

PostPosted: Tue Aug 28, 2018 3:55 pm
by Renart
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!

Re: [ScriptGraph] Random Grid Scatter

PostPosted: Mon Aug 12, 2019 2:10 pm
by MAMAKO
Really thanks! Your scatter help me a lot!

And I have a question that is there have any way to create a forest using this script and make it growing on a landscape?(a simple plane using displacement map)
Looking forward to your reply:)

Re: [ScriptGraph] Random Grid Scatter

PostPosted: Thu Jun 18, 2020 10:58 pm
by Renart
Sorry for the late reply!
I tried to do it but my scripting knowledge reached their limit with this and I didn't really have the time to see how to do that.
If anybody more skilled would be interested to try, please let me know!

Re: [ScriptGraph] Random Grid Scatter

PostPosted: Sat Sep 26, 2020 9:39 pm
by Kalua
I really hope Octane 2020.2's new Scatter node works as stable as this solution. This is really killer stable. Is a pitty we can't impove it into a full scattering solution.
I imported a Standalone scene wich contained a grid with 200 x 200 scatter Palm grid into C4d.... it is amazingly stable. I can't expect anything lesser than this for 2020.2.