-- lua script for drawing snow flakes
-- some basic constants
local abort = false
local gCenterX = width / 2
local gCenterY = height / 2
local MaxDrawSize = gCenterX
if gCenterY < gCenterX then MaxDrawSize  = gCenterY end 
local r, g, b

--@TBCONFIG STYLE: Segments, 4,3,4,5,6,7,8,9,10,11,12,13,14,15,16
--@TBCONFIG STYLE: Extents, 2,1,2,3,4,5
--@TBCONFIG SLIDER: Zoom, 60
--@TBCONFIG SLIDER: Pattern Variation , 0
--@TBCONFIG SLIDER: Line Variation , 40
--@TBCONFIG SLIDER: Inner Complexity, 50
--@TBCONFIG SLIDER: Outer Complexity, 50
--@TBCONFIG FLAG: Paint, false
--@TBCONFIG FLAG: Variation a, false
--@TBCONFIG FLAG: Variation b, false


local uSegments =  styleVal1 + 3
local uZoom = sliderVal1  --  the full image will remain about the same size
local uVariation = 10000 * sliderVal2 
local uLineVariation = 0.25 + sliderVal3 * 0.5
local uInnerComplexity = 1 - sliderVal4	
local uOuterComplexity = 1 - sliderVal5
local uPaint = flagVal1
local uExtents = styleVal2 

if flagVal2 then uVariation = uVariation + 17 end
if flagVal3 then uVariation = uVariation + 57 end

function plot(x,y)
     if (x > 0) and (y > 0) and (x < width) and (y < height) then  
        set_rgba(x, y, r, g, b, 1)
    end 
end

function round(x)
    local xf = math.floor(x)
    if (x - xf) <= 0.5 then 
        return xf
    else
        return math.ceil(x)
    end 
end

-- Line Clipping Algorithm -- Liang-Barsky
function Liang_Barsky_clipTest(p, q, u1, u2)
local r
   if (p < 0) then 
        r = q / p
        if (r > u2) then 
            return 0, u1, u2
        elseif (r > u1) then 
            return 1, r, u2
        end
   elseif (p > 0) then 
      r = q / p
      if (r < u1) then 
         return 0, u1, u2
      elseif (r < u2) then 
            return 1, u1, r
      end
   elseif (q < 0) then 
      return 0, u1, u2
   end
   return 1, u1, u2
end

function Liang_Barsky_clipping(x0, y0, x1, y1)
local XS_MIN = 0
local XS_MAX = width
local YS_MIN = 0
local YS_MAX = height
local dx = x1 - x0
local dy = y1 - y0
local u1 = 0
local u2 = 1
local t1, t2, t3, t4 

t1, u1, u2 = Liang_Barsky_clipTest(-dx, x0-XS_MIN, u1, u2)
t2, u1, u2 = Liang_Barsky_clipTest( dx, XS_MAX-x0, u1, u2)
t3, u1, u2 = Liang_Barsky_clipTest(-dy, y0-YS_MIN, u1, u2)
t4, u1, u2 = Liang_Barsky_clipTest( dy, YS_MAX-y0, u1, u2)
    
   if (t1 == 1) and (t2 == 1) and (t3 == 1) and (t4 == 1)  then 
        if (u2 < 1) then 
            x1 = x0 + round(u2 * dx)
            y1 = y0 + round(u2 * dy)
        end
        if (u1 > 0) then 
            x0 = x0 + round(u1 * dx)
            y0 = y0 + round(u1 * dy)
        end
    end
    return x0, y0, x1, y1
end

-- Line Generating Algorithm using Bresenham's technique
function line_bres(x0, y0, x1, y1)
   x0, y0, x1, y1 = Liang_Barsky_clipping(x0, y0, x1, y1)
    local steep, t
    if (math.abs(y1 - y0) > math.abs(x1 - x0)) then 
        steep = 1 
    else 
        steep = 0 
    end 

    if steep == 1 then
        t = x0; x0 = y0; y0 = t
        t = x1; x1 = y1; y1 = t
    end
    if x0 > x1 then
        t = x0; x0 = x1; x1 = t
        t = y0; y0 = y1; y1 = t
    end 

    local deltax = x1 - x0
    local deltay = math.abs(y1 - y0)
    local error = round(deltax / 2)
    local ystep
    local y = y0
    if y0 < y1 then ystep = 1 else ystep = -1 end
    
    for x = x0, x1 do
         if (steep == 1) then plot(y,x) else plot(x,y) end 
         error = error - deltay
         if error < 0 then
             y = y + ystep
             error = error + deltax
         end
    end
end


function RotateLine(x0, y0, length, angle, draw)
	if length < 0.5 then
		return x0,y0
	else
	local x1 = length * math.cos(angle) + x0
	local y1 = length * math.sin(angle) + y0
	-- hue = math.floor(360 * angle / (math.pi * 2)) 
	if draw then 
		if uPaint == true then
			paint_line(x0, y0, x1, y1)
		else
			line_bres(x0, y0, x1, y1)
		end
	end
	return x1, y1
	end
end

function GetSegments(mrd, extent, dRatio)
-- the deeper we are (mrd)   the more we get into details
-- the extend tells us how from from the start point we are
-- the dRatio tells us the distance from the origin as a ratio of the screen size
-- user slider uInnerComplexity controls the complexity when the DistanceFromCentre ratio is low
-- user slider uOuterComplexity controls the complexity when the DistanceFromCentre ratio is high
-- both can be used to get the desired result
local k, j
local newSegments = uSegments
local zeroLimit, lowLimit, highLimit, maxlimit
local Complexity
	if dRatio < 0.5 then 
		Complexity = uInnerComplexity 
	else
		Complexity = uOuterComplexity 
	end
	maxlimit = 12 
	Complexity = Complexity * (mrd + extent) * 3
	zeroLimit = Complexity 
	lowLimit = Complexity + ( maxlimit - Complexity) / 3
	highLimit = Complexity + ( maxlimit - Complexity) * 2 / 3
	for k = 0, uSegments + mrd do j = math.random() * maxlimit end
	-- no processing for prime numbers 5,7,11
	if j <= zeroLimit then newSegments = 0
	elseif (uSegments == 3) then 
		if (j >=highLimit) then newSegments = 6 
		end
	elseif (uSegments == 4) then
		if (j <= lowLimit)  then newSegments = 2 
		elseif (j >= highLimit)  then newSegments = 8 
		end
	elseif (uSegments == 6 or uSegments == 10) then
		if (j <= lowLimit)  then newSegments = uSegments / 2 
		end
	elseif (uSegments == 8) then
		if (j <= lowLimit)  then newSegments = 2 
		elseif (j <= highLimit)  then newSegments = 4 
		end
	elseif (uSegments == 9) then
		if (j <= lowLimit)  then newSegments = 3 
		elseif (j >= highLimit)  then newSegments = 6 
		end
	end
	return newSegments
end


function MakeLines(xStart, yStart, MRD, Length, Segments, StartSeed, StartAngle, Extents)
	local SegmentAngle = math.pi * 2 / Segments 
	local Segment, Extent, xEnd, yEnd, radialLength, newSeed, newLength, j, Angle, newSegments
	local x,y, x0, y0, x1, y1, draw, SelectedExtent, DistanceFromCentre, dRatio, Scalefactor
	local xPoint = {}
	local yPoint = {}
	if (is_escape() > 0) then
		abort = true
	end
	if abort == true then return end
	for Segment = 0, Segments - 1 do		-- initialise the array contents
		xPoint[Segment] = 0
		yPoint[Segment] = 0 
	end
	if Extents <= 0  then return end  -- limit the maximum recursion depth
	Scalefactor = (MaxDrawSize  * (uZoom + 0.05))
	for Segment = 0,  Segments - 1  do   -- used to go around the circle - like segments of an orange
		if (is_escape() > 0) then
			abort = true
		end
		if abort == true then break end
		math.randomseed(StartSeed)		-- reset the generator so that each branch gets the same sequence
		x = xStart; y = yStart
		Angle = Segment * SegmentAngle + StartAngle
		radialLength = Length * uZoom 
		if (MRD > 0) and (math.abs((Angle - StartAngle) - math.pi) < 1) then  -- dont draw a line if it is already drawn
			draw = false	-- if it is 180 degrees then skip it else keep it
		else	
			draw = true
		end
		SelectedExtent = math.floor(math.random() * Extents + 0.1)
		for Extent = 0, Extents do  -- used to extend the line
			if (is_escape() > 0) then
				abort = true; break
			end
			math.randomseed(StartSeed + Extent)
			--  first draw the current line in the required direction
			xEnd,yEnd = RotateLine(x, y, radialLength, Angle, draw )  -- get the end of main line - this is base for all new lines
			-- find the distance from the origin - this is used to determine inner and outer complexity
			DistanceFromCentre = math.sqrt(((xEnd - gCenterX) * (xEnd - gCenterX) ) + ((yEnd - gCenterY) * (yEnd - gCenterY))) 
			dRatio = DistanceFromCentre  /  Scalefactor -- should be in range 0..1 approx
			-- output_debug_string("dRatio: " .. dRatio)
			-- if this extent is the one we will draw a border around keep the coordinates until the end when they are drawn
			if  SelectedExtent == Extent then 
				xPoint[Segment] = xEnd
				yPoint[Segment] = yEnd 
			end 
			-- now set things up for the radial lines that get drawn at the end of this line. First decide how many segments will get drawn
			-- getSegments controls the whole structure and the complexity of each recursive section
			newSegments = GetSegments(MRD, Extent, dRatio)
			newLength = Length *  uLineVariation * uZoom 
			newSeed = math.random() * 32767  -- so that each new set of lines has a new pattern
			if newSegments > 0 and newLength > 1   then 
				MakeLines(xEnd, yEnd, MRD + 1, newLength , newSegments, newSeed, Angle, Extents - 1)
			end
			x = xEnd
			y = yEnd
		end
	end
	math.randomseed(StartSeed )
	if math.random() < 0.6 then 
		for Segment = 0,  Segments - 2  do 
			if uPaint == true then
				paint_line(xPoint[Segment], yPoint[Segment], xPoint[Segment+1], yPoint[Segment+1])
			else
				line_bres(xPoint[Segment], yPoint[Segment], xPoint[Segment+1], yPoint[Segment+1])
			end
		end
		if uPaint == true then
			paint_line(xPoint[Segments - 1], yPoint[Segments - 1], xPoint[0], yPoint[0])
		else
			line_bres(xPoint[Segments - 1], yPoint[Segments - 1], xPoint[0], yPoint[0])
		end	
	end
end

paint_open(1)
r, g, b = get_color1()
MakeLines(gCenterX, gCenterY, 0, MaxDrawSize / uExtents, uSegments, uVariation, 0, uExtents )
paint_close()
			-- output_debug_string("DistanceFromCentre: " .. DistanceFromCentre)
			-- output_debug_string("Scalefactor: " .. Scalefactor)



