initial commit
This commit is contained in:
commit
b03af23da5
99
BezierCurve.lua
Normal file
99
BezierCurve.lua
Normal file
@ -0,0 +1,99 @@
|
||||
local Vector2 = require("Vector2")
|
||||
local BezierCurve = {}
|
||||
local width = 5
|
||||
local interval = 1/10
|
||||
BezierCurve.maxDepth = -1
|
||||
BezierCurve.randSeed = love.math.random()
|
||||
BezierCurve.__index = BezierCurve
|
||||
|
||||
local function dist(x1,y1,x2,y2)
|
||||
return ((x1-x2)^2+(y1-y2)^2)^0.5
|
||||
end
|
||||
|
||||
local function linearBezier(p1, p2)
|
||||
return (1-t)*p1 + t*p2
|
||||
end
|
||||
|
||||
function BezierCurve.new(class, points)
|
||||
return setmetatable({
|
||||
_curve = love.math.newBezierCurve(points or {100,180,250,250,200,500}),
|
||||
selectedPoint = false
|
||||
}, class)
|
||||
end
|
||||
|
||||
function BezierCurve:draw(drawPoints, drawLines, drawFinal)
|
||||
love.graphics.setLineJoin("bevel")
|
||||
love.graphics.setLineStyle("smooth")
|
||||
|
||||
if drawLines then
|
||||
love.graphics.setLineWidth(width*0.25)
|
||||
local p = {}
|
||||
for i=1, self._curve:getControlPointCount() do
|
||||
table.insert(p, Vector2(self._curve:getControlPoint(i)))
|
||||
end
|
||||
BezierCurve.stringArt(p, 0)
|
||||
end
|
||||
|
||||
if drawFinal then
|
||||
love.graphics.setLineWidth(width)
|
||||
love.graphics.setColor(0.8,0,0)
|
||||
love.graphics.line(self._curve:render())
|
||||
end
|
||||
|
||||
if drawPoints then
|
||||
love.graphics.setColor(0,0,0.8)
|
||||
for i=1,self._curve:getControlPointCount() do
|
||||
local x,y = self._curve:getControlPoint(i)
|
||||
love.graphics.circle("fill",x,y,width*1.25)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BezierCurve:mousemoved(x, y)
|
||||
if self.selectedPoint then
|
||||
self._curve:setControlPoint(self.selectedPoint, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function BezierCurve:mousepressed(x, y, button)
|
||||
if button == 1 then
|
||||
local isCtrl = love.keyboard.isDown("lctrl", "rctrl")
|
||||
for i=1,self._curve:getControlPointCount() do
|
||||
if dist(x,y,self._curve:getControlPoint(i)) < width*1.25 then
|
||||
if isCtrl then
|
||||
self._curve:removeControlPoint(i)
|
||||
else
|
||||
self.selectedPoint = i
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if isCtrl then
|
||||
self._curve:insertControlPoint(x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BezierCurve:mousereleased(_, _, button)
|
||||
if button == 1 then
|
||||
self.selectedPoint = false
|
||||
end
|
||||
end
|
||||
|
||||
function BezierCurve.stringArt(points, depth)
|
||||
if #points > 2 and (BezierCurve.maxDepth < 0 or depth < BezierCurve.maxDepth) then
|
||||
for t=0,1,interval do
|
||||
love.graphics.setColor(0.3,0.5,0.1)
|
||||
local p = {}
|
||||
for pointI=1, #points-1 do
|
||||
table.insert(p, (1-t)*points[pointI] + t*points[pointI+1])
|
||||
end
|
||||
for pointI=1, #p-1 do
|
||||
love.graphics.line(p[pointI].x, p[pointI].y, p[pointI+1].x, p[pointI+1].y)
|
||||
end
|
||||
BezierCurve.stringArt(p, depth+1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return BezierCurve
|
166
GUI-Framework.lua
Normal file
166
GUI-Framework.lua
Normal file
@ -0,0 +1,166 @@
|
||||
local GUI = {}
|
||||
local EMPTY = {}
|
||||
local Vector2 = require("Vector2")
|
||||
local templates = {}
|
||||
GUI.__index = GUI
|
||||
|
||||
|
||||
local function clone(data)
|
||||
if type(data) ~= "table" then
|
||||
return data
|
||||
else
|
||||
|
||||
local new = {}
|
||||
for k, v in pairs(data) do
|
||||
new[k] = clone(v)
|
||||
end
|
||||
return setmetatable(new, getmetatable(data))
|
||||
end
|
||||
end
|
||||
|
||||
function revipairs(t)
|
||||
return function(t, i) i = i - 1 if i > 0 then return i, t[i] end end, t, #t+1
|
||||
end
|
||||
|
||||
function GUI.sortByDepth(a, b)
|
||||
return a.depth < b.depth
|
||||
end
|
||||
|
||||
function getPos(self)
|
||||
if self._parent then
|
||||
local x0, y0 = self._parent:getPos()
|
||||
return self.pos.x + x0, self.pos.y + y0
|
||||
else
|
||||
return self.pos.x, self.pos.y
|
||||
end
|
||||
end
|
||||
function getSize(self)
|
||||
return self.size.x, self.size.y
|
||||
end
|
||||
function getBounds(self)
|
||||
local x, y = getPos(self)
|
||||
return x, y, self.size.x, self.size.y
|
||||
end
|
||||
function inBounds(self, x, y)
|
||||
local Ex, Ey, width, height = getBounds(self)
|
||||
return (x > Ex and x < Ex + width and y > Ey and y < Ey + height)
|
||||
end
|
||||
|
||||
function GUI.createElement(elementType, data)
|
||||
assert(templates[elementType], "Element type \""..elementType.."\" dosen't exist")
|
||||
|
||||
local elem = data or {}
|
||||
for k, v in pairs(templates[elementType]) do
|
||||
if not (type(k) == "string" and k:find("^__")) and type(elem[k]) == "nil" then
|
||||
elem[k] = clone(v)
|
||||
end
|
||||
end
|
||||
setmetatable(elem, template)
|
||||
|
||||
-- Ensure that the mandatory varaibles are declered
|
||||
elem.depth = elem.depth or 0
|
||||
elem.pos = elem.pos or Vector2()
|
||||
elem.size = elem.size or Vector2()
|
||||
if type(elem.visible) ~= "boolean" then
|
||||
elem.visible = true
|
||||
end
|
||||
|
||||
if elem.load then elem:load() end
|
||||
return elem
|
||||
end
|
||||
|
||||
function GUI.new(class, width, height)
|
||||
assert(width and height, "Width and height are undefined")
|
||||
local self = setmetatable({}, class)
|
||||
self.pos = Vector2()
|
||||
self.size = Vector2(width, height)
|
||||
|
||||
self.getPos = getPos
|
||||
self.getSize = getSize
|
||||
self.getBounds = getBounds
|
||||
self.inBounds = inBounds
|
||||
|
||||
self._elements = {}
|
||||
return self
|
||||
end
|
||||
|
||||
function GUI:removeElement(element)
|
||||
for i, elem in ipairs(self._elements) do
|
||||
if elem == element then
|
||||
table.remove(self._elements, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GUI:addElement(elementType, data)
|
||||
local elem = GUI.createElement(elementType, data)
|
||||
elem._parent = self
|
||||
-- Add it to the list
|
||||
table.insert(self._elements, elem)
|
||||
table.sort(self._elements, GUI.sortByDepth)
|
||||
return elem
|
||||
end
|
||||
|
||||
function GUI.newTemplate(name)
|
||||
local template = {}
|
||||
template.__index = template
|
||||
|
||||
template.getPos = getPos
|
||||
template.getSize = getSize
|
||||
template.getBounds = getBounds
|
||||
template.inBounds = inBounds
|
||||
|
||||
templates[name] = template
|
||||
return template
|
||||
end
|
||||
|
||||
function GUI:resize(...)
|
||||
for _, element in ipairs(self._elements) do
|
||||
if element.resize then
|
||||
element:resize(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GUI.callMethod(parent, name, ...)
|
||||
local useFallback = false
|
||||
for _, element in revipairs(parent._elements) do
|
||||
if element.visible then
|
||||
if type(element._elements) == "table" then
|
||||
if GUI.callMethod(element, name, ...) then break end
|
||||
end
|
||||
if useFallback and element[name.."Fallback"] then
|
||||
element[name.."Fallback"](element, ...)
|
||||
elseif element[name] then
|
||||
if element[name](element, ...) then useFallback = true end
|
||||
end
|
||||
end
|
||||
end
|
||||
return useFallback
|
||||
end
|
||||
|
||||
function GUI:draw(...)
|
||||
local useFallback = false
|
||||
for _, element in ipairs(self._elements) do
|
||||
if element.visible then
|
||||
if useFallback and element["drawFallback"] then
|
||||
element["drawFallback"](element, ...)
|
||||
elseif element["draw"] then
|
||||
if element["draw"](element, ...) then useFallback = true end
|
||||
end
|
||||
if type(element._elements) == "table" then
|
||||
GUI.draw(element, "draw", ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
return useFallback
|
||||
end
|
||||
|
||||
for i, name in pairs{"keypressed", "keyreleased", "mousemoved", "mousepressed", "mousereleased", "textinput"} do
|
||||
GUI[name] = function(self, ...)
|
||||
GUI.callMethod(self, name, ...)
|
||||
end
|
||||
end
|
||||
|
||||
return GUI
|
95
GUI/Button.lua
Normal file
95
GUI/Button.lua
Normal file
@ -0,0 +1,95 @@
|
||||
local GUI = require("GUI-Framework")
|
||||
local Vector2 = require("Vector2")
|
||||
local utils = require("utils")
|
||||
local Button = GUI.newTemplate("Button")
|
||||
|
||||
Button.isHover = false
|
||||
Button.isDown = false
|
||||
Button.text = ""
|
||||
Button.font = love.graphics.getFont()
|
||||
|
||||
function Button:load()
|
||||
local minWidth = self.font:getWidth(self.text)
|
||||
local minHeight = self.font:getHeight(self.text) * ((select(2, self.text:gsub('\n', '\n')) or 0) + 1)
|
||||
|
||||
if self.size.x < minWidth then
|
||||
self.size.x = minWidth + 10
|
||||
end
|
||||
if self.size.y < minHeight then
|
||||
self.size.y = minHeight + 10
|
||||
end
|
||||
end
|
||||
|
||||
function Button:draw()
|
||||
if self.isDown then
|
||||
self:drawDown()
|
||||
elseif self.isHover then
|
||||
self:drawHover()
|
||||
else
|
||||
self:drawDefault()
|
||||
end
|
||||
end
|
||||
|
||||
function Button:drawHover()
|
||||
local x, y, width, height = self:getBounds()
|
||||
local cx, cy = x + width/2, y + height/2
|
||||
|
||||
-- Shadow
|
||||
utils.smoothRectangle(x, y, width, height, 2, utils.rgba(0, 0, 0, 0.62))
|
||||
-- The actual button
|
||||
utils.smoothRectangle(x, y-1, width, height, 2, utils.rgb(42, 147, 247))
|
||||
-- Text
|
||||
love.graphics.setColor(1, 1, 1)
|
||||
utils.alignedPrint(self.font, self.text, cx, cy-1)
|
||||
end
|
||||
|
||||
function Button:drawDefault()
|
||||
local x, y, width, height = self:getBounds()
|
||||
local cx, cy = x + width/2, y + height/2
|
||||
|
||||
-- Shadow
|
||||
utils.smoothRectangle(x, y, width, height, 2, utils.rgba(0, 0, 0, 0.62))
|
||||
-- The actual button
|
||||
utils.smoothRectangle(x, y-1, width, height, 2, utils.rgb(56, 158, 255))
|
||||
-- Text
|
||||
love.graphics.setColor(1, 1, 1)
|
||||
utils.alignedPrint(self.font, self.text, cx, cy-1)
|
||||
end
|
||||
|
||||
function Button:mousemoved(x, y)
|
||||
if self:inBounds(x, y) then
|
||||
self.isHover = true
|
||||
return true
|
||||
else
|
||||
self.isHover = false
|
||||
end
|
||||
end
|
||||
|
||||
function Button:mousemovedFallback()
|
||||
self.isHover = false
|
||||
end
|
||||
|
||||
function Button:mousepressed()
|
||||
if self.isHover then
|
||||
self.isDown = true
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Button:mousereleased(x, y, ...)
|
||||
if self.isDown and self:inBounds(x, y) then
|
||||
if self.mouseClicked then self:mouseClicked(x, y, ...) end
|
||||
end
|
||||
self.isDown = false
|
||||
end
|
||||
|
||||
function Button:drawDown()
|
||||
local x, y, width, height = self:getBounds()
|
||||
local cx, cy = x + width/2, y + height/2
|
||||
|
||||
-- the actual button
|
||||
utils.smoothRectangle(x, y, width, height, 2, utils.rgb(0, 116, 225))
|
||||
-- Text
|
||||
love.graphics.setColor(1, 1, 1)
|
||||
utils.alignedPrint(self.font, self.text, cx, cy)
|
||||
end
|
14
GUI/Label.lua
Normal file
14
GUI/Label.lua
Normal file
@ -0,0 +1,14 @@
|
||||
local Vector2 = require("Vector2")
|
||||
local utils = require("utils")
|
||||
local GUI = require("GUI-Framework")
|
||||
local Label = GUI.newTemplate("Label")
|
||||
local textFont = love.graphics.getFont()
|
||||
|
||||
Label.color = utils.rgb(255 , 255, 255)
|
||||
Label.text = "Lorem ipsum..."
|
||||
|
||||
function Label:draw()
|
||||
local x, y = self:getPos()
|
||||
love.graphics.setColor(self.color)
|
||||
utils.alignedPrint(textFont, self.text, x , y, self.alignX, self.alignY)
|
||||
end
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Bezier curve string art
|
||||
|
||||

|
||||
|
||||
Visualize bezier curves as string art [String art](https://en.wikipedia.org/wiki/String_art)
|
||||
|
||||
How to launch:
|
||||
```
|
||||
love .
|
||||
```
|
||||
|
||||
Controls:
|
||||
* RCtrl + LMB - create/delete point
|
||||
* RCtrl + LMB drag - move point
|
137
Vector2.lua
Normal file
137
Vector2.lua
Normal file
@ -0,0 +1,137 @@
|
||||
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 string.format("Vector2(%.4f,%.4f)", t.x, t.y)
|
||||
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 error("Vector2 must be divided by scalar") end
|
||||
return Vector2(v1.x / op, v1.y / op)
|
||||
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
|
||||
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(0, 0, 0) 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
|
||||
|
||||
ffi.metatype("vector2", Vector2)
|
||||
|
||||
return Vector2
|
4
conf.lua
Normal file
4
conf.lua
Normal file
@ -0,0 +1,4 @@
|
||||
function love.conf(t)
|
||||
t.console = true
|
||||
t.modules.joystick = false
|
||||
end
|
64
main.lua
Normal file
64
main.lua
Normal file
@ -0,0 +1,64 @@
|
||||
local BezierCurve = require("BezierCurve")
|
||||
local GUI = require("GUI-Framework")
|
||||
local Vector2 = require("Vector2")
|
||||
|
||||
function addCurve(points)
|
||||
local newCurve = BezierCurve:new(points)
|
||||
table.insert(allCurves, newCurve)
|
||||
end
|
||||
|
||||
function love.load(args, unfilteredArgs)
|
||||
love.window.setTitle("Bezier Curves")
|
||||
gui = GUI:new(love.graphics.getDimensions())
|
||||
allCurves = {}
|
||||
show = {true, true , false}
|
||||
|
||||
for _, file in pairs(love.filesystem.getDirectoryItems("GUI")) do
|
||||
require("GUI."..file:sub(1, -5))
|
||||
end
|
||||
|
||||
addCurve{200,200,300,150}
|
||||
|
||||
gui:addElement("Button", {pos=Vector2(10,10), size = Vector2(100,25), text = "Hide points", mouseClicked = function(self)
|
||||
show[1] = not show[1]
|
||||
if show[1] then
|
||||
self.text = "Hide points"
|
||||
else
|
||||
self.text = "Show points"
|
||||
end
|
||||
end})
|
||||
gui:addElement("Button", {pos=Vector2(10,50), size = Vector2(100,25), text = "Hide lines", mouseClicked = function(self)
|
||||
show[2] = not show[2]
|
||||
if show[2] then
|
||||
self.text = "Hide lines"
|
||||
else
|
||||
self.text = "Show lines"
|
||||
end
|
||||
end})
|
||||
gui:addElement("Button", {pos=Vector2(10,90), size = Vector2(100,25), text = "Show curves", mouseClicked = function(self)
|
||||
show[3] = not show[3]
|
||||
if show[3] then
|
||||
self.text = "Hide curves"
|
||||
else
|
||||
self.text = "Show curves"
|
||||
end
|
||||
end})
|
||||
|
||||
-- Make functions
|
||||
for _, name in ipairs{"mousepressed", "mousereleased", "mousemoved", "load", "resize", "keypressed", "keyreleased", "textinput"} do
|
||||
love[name] = function(...)
|
||||
for _, curve in ipairs(allCurves) do
|
||||
if curve[name] then curve[name](curve, ...) end
|
||||
if gui[name] then gui[name](gui, ...) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.clear(0.9,0.9,0.9)
|
||||
for _, curve in ipairs(allCurves) do
|
||||
curve:draw(show[1], show[2], show[3])
|
||||
end
|
||||
gui:draw()
|
||||
end
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
225
utils.lua
Normal file
225
utils.lua
Normal file
@ -0,0 +1,225 @@
|
||||
local utils = {}
|
||||
local alignment = {
|
||||
["top"] = 0,
|
||||
["left"] = 0,
|
||||
["middle"] = -0.5,
|
||||
["center"] = -0.5,
|
||||
["bottom"] = -1,
|
||||
["right"] = -1
|
||||
}
|
||||
local Vector2 = require("Vector2")
|
||||
local _tx, _ty = 0, 0
|
||||
local _stack = {
|
||||
sx = {},
|
||||
sy = {},
|
||||
sw = {},
|
||||
sh = {},
|
||||
tx = {},
|
||||
ty = {}
|
||||
}
|
||||
|
||||
--[[
|
||||
function atan2 (y, x)
|
||||
return (x > 0 ) and math.atan(y/x)
|
||||
or (x < 0 and y >= 0) and math.atan(y/x) + math.pi
|
||||
or (x < 0 and y < 0) and math.atan(y/x) - math.pi
|
||||
or (x == 0 and y > 0) and math.pi/2
|
||||
or (x == 0 and y < 0) and -math.pi/2
|
||||
or 0 -- this situration is technically undefined
|
||||
end
|
||||
]]
|
||||
|
||||
function utils.lastIndex(t)
|
||||
local res = -math.huge
|
||||
for k in pairs(t) do
|
||||
if k > res then res = k end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function utils.translate(dx, dy)
|
||||
love.graphics.translate(dx, dy)
|
||||
_tx, _ty = _tx + dx, _ty + dy
|
||||
end
|
||||
|
||||
function utils.pushRegion(x, y, w, h)
|
||||
local sx, sy, sw, sh = love.graphics.getScissor()
|
||||
table.insert(_stack.sx, sx)
|
||||
table.insert(_stack.sy, sy)
|
||||
table.insert(_stack.sw, sw)
|
||||
table.insert(_stack.sh, sh)
|
||||
table.insert(_stack.tx, _ty)
|
||||
table.insert(_stack.ty, _tx)
|
||||
|
||||
love.graphics.push()
|
||||
if x and y then
|
||||
if w and h then
|
||||
love.graphics.intersectScissor(_tx + x, _ty + y, math.max(0, w), math.max(0, h))
|
||||
end
|
||||
utils.translate(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function utils.popRegion()
|
||||
local sx = table.remove(_stack.sx)
|
||||
local sy = table.remove(_stack.sy)
|
||||
local sw = table.remove(_stack.sw)
|
||||
local sh = table.remove(_stack.sh)
|
||||
|
||||
_tx = table.remove(_stack.tx) or 0
|
||||
_ty = table.remove(_stack.ty) or 0
|
||||
|
||||
love.graphics.pop()
|
||||
if sx and sy and sw and sh then
|
||||
love.graphics.setScissor(sx, sy, sw, sh)
|
||||
else
|
||||
love.graphics.setScissor()
|
||||
end
|
||||
end
|
||||
|
||||
function utils.smoothLine (x1, y1, x2, y2, width, color)
|
||||
love.graphics.setLineStyle("smooth")
|
||||
love.graphics.setLineWidth(width)
|
||||
if color then love.graphics.setColor(color) end
|
||||
love.graphics.line(x1, y1, x2, y2)
|
||||
end
|
||||
|
||||
function utils.clamp(x, A, B)
|
||||
if x < A then
|
||||
return A
|
||||
elseif x > B then
|
||||
return B
|
||||
else
|
||||
return x
|
||||
end
|
||||
end
|
||||
|
||||
local function previous(t, i)
|
||||
i = i - 1
|
||||
if i > 0 then return i, t[i] end
|
||||
end
|
||||
|
||||
function utils.revipairs(t)
|
||||
return previous, t, #t+1
|
||||
end
|
||||
|
||||
function utils.smoothRectangle(x, y, w, h, r, color)
|
||||
love.graphics.setLineStyle("smooth")
|
||||
love.graphics.setLineWidth(1)
|
||||
if color then love.graphics.setColor(color) end
|
||||
love.graphics.rectangle("fill", x, y, w, h, r)
|
||||
love.graphics.rectangle("line", x, y, w, h, r)
|
||||
end
|
||||
|
||||
function utils.borderRectangle(x,y,w,h,r,lineWidth,color)
|
||||
love.graphics.setLineStyle("smooth")
|
||||
love.graphics.setLineWidth(lineWidth)
|
||||
if color then love.graphics.setColor(color) end
|
||||
love.graphics.rectangle("line", x, y, w, h, r)
|
||||
end
|
||||
|
||||
function utils.alignedPrint(font, text, x, y, alignX, alignY)
|
||||
local line_count = (select(2, text:gsub('\n', '\n')) or 0) + 1
|
||||
local width = font:getWidth(text)
|
||||
local height = font:getHeight()*line_count
|
||||
|
||||
love.graphics.setFont(font)
|
||||
love.graphics.print(text, utils.round(x + (alignment[alignX] or -0.5) * width), utils.round(y + (alignment[alignY] or -0.5) * height))
|
||||
end
|
||||
|
||||
function utils.rgb(r, g, b)
|
||||
return {r/255, g/255, b/255, 1}
|
||||
end
|
||||
|
||||
function utils.rgba(r, g, b, a)
|
||||
return {r/255, g/255, b/255, a}
|
||||
end
|
||||
|
||||
function utils.round(num, numDecimalPlaces)
|
||||
local mult = 10^(numDecimalPlaces or 0)
|
||||
return math.floor(num * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
function utils.map(x, A, B, C, D)
|
||||
return (x - A) / (B - A) * (D - C) + C
|
||||
end
|
||||
|
||||
function utils.sign(x)
|
||||
if x > 0 then
|
||||
return 1
|
||||
elseif x < 0 then
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function utils.clone(data)
|
||||
if type(data) ~= "table" then
|
||||
return data
|
||||
else
|
||||
|
||||
local new = {}
|
||||
for k, v in pairs(data) do
|
||||
new[k] = utils.clone(v)
|
||||
end
|
||||
return setmetatable(new, getmetatable(data))
|
||||
end
|
||||
end
|
||||
|
||||
function utils.serialize(data)
|
||||
if type(data) == "string" then
|
||||
return "\""..data.."\""
|
||||
elseif type(data) == "table" then
|
||||
local t = {}
|
||||
for key,value in pairs(data) do
|
||||
table.insert(t,"["..utils.serialize(key).."]="..utils.serialize(value))
|
||||
end
|
||||
return "{"..table.concat(t,",").."}"
|
||||
else
|
||||
return tostring(data)
|
||||
end
|
||||
end
|
||||
|
||||
function utils.unserialize(data)
|
||||
return load("return "..data, nil, "t", {Vector2 = Vector2})()
|
||||
end
|
||||
|
||||
function utils.printTable(t,depth)
|
||||
depth = depth or 0
|
||||
for k,v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
print(string.rep(" ", depth)..k, "table:")
|
||||
utils.printTable(v,depth+1)
|
||||
else
|
||||
print(string.rep(" ", depth)..k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function utils.loadFile(fileName)
|
||||
if not love.filesystem.getInfo(fileName) then return nil end
|
||||
local file = love.filesystem.read(fileName)
|
||||
local data = love.data.decompress("string","zlib", file)
|
||||
return utils.unserialize(data)
|
||||
end
|
||||
|
||||
function utils.saveFile(data, fileName)
|
||||
local rawstring = utils.serialize(data)
|
||||
local file = io.open(fileName, "wb")
|
||||
file:write(love.data.compress("string","zlib",rawstring))
|
||||
end
|
||||
|
||||
function utils.pairsByKeys(t)
|
||||
local tableKeys = {}
|
||||
for key in pairs(t) do table.insert(tableKeys, key) end
|
||||
table.sort(tableKeys)
|
||||
local i = 0
|
||||
local n = table.getn(tableKeys)
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= n then return tableKeys[i], t[tableKeys[i]] end
|
||||
end
|
||||
end
|
||||
|
||||
return utils
|
Loading…
Reference in New Issue
Block a user