Now that we generated the Mandelbrot set, here's a script to generate a Julia set. The principle is the same. The only thing to note here is that there's a function to convert from the HSL colour model to the RGB model. The HSL model (or HSV) comes in very handy when you'd like to generate a full colour spectrum from a single value. Here is the code:
- Code: Select all
--
-- @description Creates a Julia set texture node.
-- @author Stratified
-- @version 0.1
--
-- radius 2 squared
local RADIUS2 = 4
-- max iterations before we stop
local MAX_ITERATIONS = 255
-- real and imaginary part of the constant
local C = { -0.7, 0.27015 }
-- Converts HSL to RGB (input and output range: 0 - 255)
function HSL(h, s, l)
if s == 0 then return l,l,l end
h, s, l = h/256*6, s/255, l/255
local c = (1-math.abs(2*l-1))*s
local x = (1-math.abs(h%2-1))*c
local m,r,g,b = (l-.5*c), 0,0,0
if h < 1 then r,g,b = c,x,0
elseif h < 2 then r,g,b = x,c,0
elseif h < 3 then r,g,b = 0,c,x
elseif h < 4 then r,g,b = 0,x,c
elseif h < 5 then r,g,b = x,0,c
else r,g,b = c,0,x
end
return math.ceil((r+m)*256),math.ceil((g+m)*256),math.ceil((b+m)*256)
end
-- Checks if a pixel in the image plane is in the julia set.
-- Coordinates (x, y) need to be in the range [-1, 1]. Returns if the point is in the set and
-- the number of iterations to figure that out.
local function julia(x, y)
local zr, zi = x, y
local cr, ci = C[1], C[2]
for k=1,MAX_ITERATIONS do
-- calculate z(n+1) from z(n)
local zrzi2 = zr * zi * 2
local zr2 = zr * zr
local zi2 = zi * zi
-- check if we're still in the circle with radius 2, if not the point isn't
-- in the Julia set anymore.
if (zi2 + zr2 > RADIUS2) then
return false, k
end
-- calculate the values for the next iteration
zr = zr2 - zi2 + cr
zi = ci + zrzi2
end
-- after MAX_ITERATIONS, we're pretty confident that the point is in the julia set.
return true, MAX_ITERATIONS
end
-- Generates a Julia set centered around the centre of the image.
function genJuliaTex(w, h)
-- create a data structure for the image
local image = {}
image.size = { w, h }
image.type = octane.image.type.LDR_RGBA
image.buffer = {}
count = 0
-- see which pixels are in the Julia set
for y = 0, h-1 do
for x = 0, w-1 do
-- transform x so it's in the range [-1, 1]
local sx = 2 * (x / w) - 1
-- transform y so it's in the range [-1, 1]
local sy = 2 * (y / h) - 1
local inSet, iterations = julia(sx, sy)
local r, g, b = 0, 0, 0
if inSet then
-- point in the set are red
r, g, b = 255, 0, 0
else
-- points outside are coloured depending on how quickly they
-- converged.
r, g, b = HSL(iterations, 127, 127)
end
-- colour pixel
table.insert(image.buffer, r)
table.insert(image.buffer, g)
table.insert(image.buffer, b)
table.insert(image.buffer, 255)
end
end
return image
end
-- dimensions of the generated texture
local WIDTH, HEIGHT = 500, 500
-- actually create the julia texture
local tex = genJuliaTex(WIDTH, HEIGHT)
assert(#tex.buffer == 4 * WIDTH * HEIGHT, "invalid buffer length")
-- create an image texture node
texNode = octane.node.create{ type=octane.NT_TEX_IMAGE, name="Julia Set Texture" }
-- set up the gradient in the attributes
texNode:setAttribute(octane.A_BUFFER , tex.buffer , false)
texNode:setAttribute(octane.A_SIZE , tex.size , false)
texNode:setAttribute(octane.A_TYPE , tex.type , false)
-- evaluate the texture node
texNode:evaluate()
Here's a picture of the generated texture:
cheers,
Thomas