From bbf98a08b24975c722b07aacf49a203989ee6dd0 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Thu, 11 May 2023 21:08:04 +0300 Subject: [PATCH] initial commit --- README.md | 9 +++ Vector2.lua | 146 ++++++++++++++++++++++++++++++++++++++++++++++ conf.lua | 6 ++ gens/circle.lua | 27 +++++++++ gens/triangle.lua | 50 ++++++++++++++++ main.lua | 64 ++++++++++++++++++++ 6 files changed, 302 insertions(+) create mode 100644 README.md create mode 100644 Vector2.lua create mode 100644 conf.lua create mode 100755 gens/circle.lua create mode 100755 gens/triangle.lua create mode 100644 main.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccfa9ec --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Fractals in Love2D + +```sh +love . +``` + +Available fractals: +* SierpiƄski triangle +* Circles diff --git a/Vector2.lua b/Vector2.lua new file mode 100644 index 0000000..eb2db00 --- /dev/null +++ b/Vector2.lua @@ -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 \ No newline at end of file diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..7616fe5 --- /dev/null +++ b/conf.lua @@ -0,0 +1,6 @@ +function love.conf(t) + t.title = "Fractals!" + t.window.resizable = true + + t.console = true +end \ No newline at end of file diff --git a/gens/circle.lua b/gens/circle.lua new file mode 100755 index 0000000..f88c8c3 --- /dev/null +++ b/gens/circle.lua @@ -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 \ No newline at end of file diff --git a/gens/triangle.lua b/gens/triangle.lua new file mode 100755 index 0000000..5a050ef --- /dev/null +++ b/gens/triangle.lua @@ -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 \ No newline at end of file diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..bfc4c40 --- /dev/null +++ b/main.lua @@ -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