1
0

initial commit

This commit is contained in:
Rokas Puzonas 2023-05-11 21:08:04 +03:00
commit bbf98a08b2
6 changed files with 302 additions and 0 deletions

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Fractals in Love2D
```sh
love .
```
Available fractals:
* Sierpiński triangle
* Circles

146
Vector2.lua Normal file
View File

@ -0,0 +1,146 @@
local ffi = assert(require("ffi"), "Vector2 needs ffi to be enabled")
local Vector2 = {}
setmetatable(Vector2, Vector2)
ffi.cdef[[
typedef struct {
double x, y;
} vector2;
]]
function Vector2.copy(t)
return ffi.new("vector2", t.x, t.y)
end
function Vector2.__call(t, x, y)
return ffi.new("vector2", x or 0, y or 0)
end
function Vector2.__concat(v1, v2)
return v1 .. tostring(v2)
end
function Vector2.__tostring(t)
return ffi.istype("vector2", t) and string.format("Vector2(%.5f,%.5f)", t.x, t.y) or tostring(t)
end
function Vector2.__eq(v1, v2)
if (not ffi.istype("vector2", v2)) or (not ffi.istype("vector2", v1)) then return false end
return v1.x == v2.x and v1.y == v2.y
end
function Vector2.__unm(v)
return Vector2(-v.x, -v.y)
end
function Vector2.__div(v1, op)
if type(op) == "number" then
return Vector2(v1.x / op, v1.y / op)
elseif ffi.istype("vector2", op) then
return Vector2(v1.x / op.x, v1.y / op.y)
else
error("Vector2 must be divided by scalar or another Vector2")
end
end
function Vector2.__mul(v1, op)
if type(op) == "number" then
return Vector2(v1.x * op, v1.y * op)
elseif type(v1) == "number" then
return Vector2(op.x * v1, op.y * v1)
end
return Vector2(v1.x * op.x, v1.y * op.y)
end
function Vector2.__sub(v1, v2)
if type(v1) == "number" then
return Vector2(v1 - v2.x, v1 - v2.y)
elseif type(v2) == "number" then
return Vector2(v1.x - v2, v1.y - v2)
end
return Vector2(v1.x - v2.x, v1.y - v2.y)
end
function Vector2.__add(v1, v2)
if type(v1) == "number" then
return Vector2(v1 + v2.x, v1 + v2.y)
elseif type(v2) == "number" then
return Vector2(v1.x + v2, v1.y + v2)
end
return Vector2(v1.x + v2.x, v1.y + v2.y)
end
function Vector2.split(v)
return v.x, v.y
end
function Vector2.setAngle(v, angle)
local magnitude = v.magnitude
return Vector2(math.cos(angle) * magnitude, math.sin(angle) * magnitude)
end
function Vector2.setMagnitude(t, mag)
return t.normalized * mag
end
function Vector2.__newindex(t, k, v)
if k == "magnitude" then
local result = t:setMagnitude(v)
t:set(result)
elseif k == "angle" then
local result = t:setAngle(v)
t:set(result)
elseif type(t) == "cdata" then
log.error("Cannot assign new property '" .. k .. "' to a Vector2")
else
rawset(t, k, v)
end
end
function Vector2.getAngle(v)
return math.atan2(v.y, v.x)
end
function Vector2.getNormalized(v)
local magnitude = v.magnitude
if magnitude == 0 then return Vector2() end
return Vector2(v.x / magnitude, v.y / magnitude)
end
function Vector2.getMagnitude(v)
return (v.x^2 + v.y^2)^0.5
end
function Vector2.__index(t, k)
if k == "magnitude" then
return Vector2.getMagnitude(t)
elseif k == "normalized" then
return Vector2.getNormalized(t)
elseif k == "angle" then
return Vector2.getAngle(t)
end
return rawget(Vector2, k)
end
function Vector2.rotate(t, rad)
return Vector2.setAngle(t, t:getAngle() + rad)
end
function Vector2.set(t, v)
if ffi.istype("vector2", v) then
t.x = v.x
t.y = v.y
end
end
function Vector2.distance(v1, v2)
return ((v1.x-v2.x)^2 + (v1.y-v2.y)^2)^0.5
end
function Vector2.type(v)
return "Vector2"
end
ffi.metatype("vector2", Vector2)
return Vector2

6
conf.lua Normal file
View File

@ -0,0 +1,6 @@
function love.conf(t)
t.title = "Fractals!"
t.window.resizable = true
t.console = true
end

27
gens/circle.lua Executable file
View File

@ -0,0 +1,27 @@
local circleStack
local starting_radius = 250
function insertCircle(pos, radius)
if radius > 2 then
table.insert(circleStack, {pos, radius})
end
end
function drawCircle(pos, radius)
love.graphics.circle("line", pos.x, pos.y, radius)
local offset = Vector2(radius)
insertCircle(pos + offset, radius/2)
insertCircle(pos - offset, radius/2)
end
return function(offset, scale)
circleStack = {}
insertCircle(-offset/scale, starting_radius/scale)
while #circleStack > 0 do
local circle = table.remove(circleStack)
drawCircle(unpack(circle))
end
end

50
gens/triangle.lua Executable file
View File

@ -0,0 +1,50 @@
local originalSize = 1000
local halfSqrt3 = 0.5*3^0.5
local trianglesToDraw = {}
local function insertTriangle(pos, size, triangleType)
pos = pos or Vector2()
size = size or originalSize
local visibleSize = Vector2(love.graphics.getDimensions())+size
local modifiedPosition = pos + size/2
if (modifiedPosition.x > 0 and modifiedPosition.x < visibleSize.x and modifiedPosition.y > 0 and modifiedPosition.y < visibleSize.y) then
table.insert(trianglesToDraw, {
pos = pos, size = size,
triangleType = triangleType or "full"
})
end
end
local function drawTriangle(pos, size, triangleType)
if size <= 6.5 then return end
local h = size * halfSqrt3
local halfSize = size/2
local halfH = h/2
if triangleType == "full" then
love.graphics.line(pos.x, pos.y-halfH, pos.x+halfSize, pos.y+halfH, pos.x-halfSize, pos.y+halfH, pos.x, pos.y-halfH)
elseif triangleType == "top" then
love.graphics.line(pos.x+halfSize, pos.y+halfH, pos.x-halfSize, pos.y+halfH)
elseif triangleType == "left" then
love.graphics.line(pos.x, pos.y-halfH, pos.x+halfSize, pos.y+halfH)
elseif triangleType == "right" then
love.graphics.line(pos.x-halfSize, pos.y+halfH, pos.x, pos.y-halfH)
end
insertTriangle(pos + Vector2( 0 , -h/4), halfSize, "top")
insertTriangle(pos + Vector2( size/4, h/4), halfSize, "right")
insertTriangle(pos + Vector2(-size/4, h/4), halfSize, "left")
end
return function(offset, scale)
trianglesToDraw = {}
insertTriangle(-offset/scale, originalSize/scale)
while #trianglesToDraw > 0 do
local triangle = table.remove(trianglesToDraw)
drawTriangle(triangle.pos, triangle.size, triangle.triangleType)
end
end

64
main.lua Normal file
View File

@ -0,0 +1,64 @@
Vector2 = require("Vector2")
local showDebug = false
local generator = require("gens.circle")
local scaleSpeed = 0.1
love.graphics.setNewFont(25)
local function resetCamera()
scale = 1
offset = -Vector2(love.graphics.getDimensions())/2
end
resetCamera()
function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
function math.clamp(x, min, max)
return (x < min and min) or (x > max and max) or x
end
function love.keypressed(keycode, scancode)
if scancode == "space" and love.keyboard.isDown("lctrl") then
love.window.setFullscreen(not love.window.getFullscreen())
elseif scancode == "f1" then
showDebug = not showDebug
elseif scancode == "f2" then
resetCamera()
end
end
function love.mousemoved(x, y, dx, dy)
if love.mouse.isDown(1) then
offset = offset - Vector2(dx, dy)*scale
end
end
function love.wheelmoved(x, y)
if y == 0 then return end
local oldScale = scale
if y < 0 then
scale = scale*(1+scaleSpeed)
else
scale = scale*(1-scaleSpeed)
end
offset = offset + Vector2(love.mouse.getPosition()) * (oldScale-scale)
end
function love.draw()
love.graphics.setColor(1, 1, 1, 1)
generator(offset, scale)
if showDebug then
love.graphics.setColor(0.8, 0.2, 0.2)
love.graphics.print("Offset: ("..round(offset.x, 2)..";"..round(offset.y, 2)..")", 0, 0)
love.graphics.print("Zoom: "..(1/scale), 0, 30)
end
end