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.
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