initial commit
This commit is contained in:
commit
bbf98a08b2
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Fractals in Love2D
|
||||
|
||||
```sh
|
||||
love .
|
||||
```
|
||||
|
||||
Available fractals:
|
||||
* Sierpiński triangle
|
||||
* Circles
|
146
Vector2.lua
Normal file
146
Vector2.lua
Normal 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
6
conf.lua
Normal 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
27
gens/circle.lua
Executable 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
50
gens/triangle.lua
Executable 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
64
main.lua
Normal 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
|
Loading…
Reference in New Issue
Block a user