summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcheck.sh7
-rw-r--r--hsluv.lua8
-rw-r--r--main.lua273
3 files changed, 225 insertions, 63 deletions
diff --git a/check.sh b/check.sh
new file mode 100755
index 0000000..0e28904
--- /dev/null
+++ b/check.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+luacheck . \
+ --std=love+luajit --no-unused --ignore 611 \
+ --max-line-length 70 \
+ -q --codes \
+ --exclude-files '**/dkjson.lua' --exclude-files '*/common/*'
diff --git a/hsluv.lua b/hsluv.lua
index 1ff199a..e076856 100644
--- a/hsluv.lua
+++ b/hsluv.lua
@@ -20,16 +20,16 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
-hsluv = {}
+local hsluv = {}
-hexChars = "0123456789abcdef"
+local hexChars = "0123456789abcdef"
-distance_line_from_origin = function(line)
+local distance_line_from_origin = function(line)
return math.abs(line.intercept) / math.sqrt((line.slope ^ 2) + 1)
end
-length_of_ray_until_intersect = function(theta, line)
+local length_of_ray_until_intersect = function(theta, line)
return line.intercept / (math.sin(theta) - line.slope * math.cos(theta))
end
diff --git a/main.lua b/main.lua
index e1d6748..6888be6 100644
--- a/main.lua
+++ b/main.lua
@@ -6,18 +6,45 @@ local tau = 2*math.pi
local ZOOM = 200
local G = love.graphics
+local function mouse_pos()
+ -- in world space
+ return G.inverseTransformPoint(love.mouse.getPosition())
+end
+
+local function hovered(set)
+ -- returns which of the things in set is hovered over
+ -- or nil if no such thing
+ local mx,my = mouse_pos()
+ for s in pairs(set) do
+ if s:contains(mx,my) then
+ return s
+ end
+ end
+ return nil
+end
+
local function write_at(text,x,y)
+ local tx,ty = G.transformPoint(x,y)
G.push()
- G.translate(x,y)
- G.scale(1/ZOOM)
- G.print(text,0,0)
+ G.origin()
+ G.print(text,tx,ty)
G.pop()
end
+local function phi_color(n)
+ local phi = (1+math.sqrt(5))/2
+ local h = (360*phi*n)%360
+ return hsluv.hsluv_to_rgb({h, 80, 60})
+end
+
local Port = class()
Port.R = 0.1
function Port.make(cls, x,y, num)
- return setmetatable({x=x,y=y,n=num,wires={}},cls)
+ return setmetatable({
+ x=x, y=y, n=num,
+ -- map of
+ conns={},
+ },cls)
end
function Port.draw(self, istate)
local c = {0,0,0}
@@ -34,59 +61,205 @@ function Port.contains(self, px,py)
local d = math.sqrt((self.x - px)^2 + (self.y - py)^2)
return d <= self.R
end
+function Port.__tostring(self)
+ return 'p#'..self.n
+end
-local ports = {}
-local n = 10
-for i = 1,n do
- local theta = tau * i/n
- table.insert(ports, Port:make(math.cos(theta), math.sin(theta), i))
+local Wire = class()
+function Wire.make(cls, p1,p2, color)
+ return setmetatable({
+ color = color,
+ p1 = p1, p2 = p2,
+ curve = catenary.catenary(p1.x,p1.y,p2.x,p2.y),
+ }, cls)
+end
+function Wire.draw(self, istate)
+ G.setColor(self.color)
+ G.line(self.curve)
end
-ports[3].wires = {ports[6]}
-ports[5].wires = {ports[7],ports[2]}
-local function wire_color(n)
- local phi = (1+math.sqrt(5))/2
- local h = (360*phi*n)%360
- return hsluv.hsluv_to_rgb({h, 80, 60})
+local EditHandle = class()
+EditHandle.R = 0.04
+function EditHandle.draw(self)
+ G.setColor(self.color)
+ G.circle('fill',self.x,self.y,self.R)
+end
+function EditHandle.contains(self, px,py)
+ local d = math.sqrt((self.x - px)^2 + (self.y - py)^2)
+ return d <= self.R
+end
+function EditHandle.remove_wire(self)
+ self.pg:remove_conn(self.here,self.there)
+end
+
+
+local PortGraph = class()
+function PortGraph.make(cls)
+ return setmetatable({
+ -- set of ports, set of wires
+ ports={}, wires={},
+ state='normal', selected=nil,
+ wire_n = 1,
+ },cls)
+end
+function PortGraph.add(self,...)
+ local p = Port:make(...)
+ self.ports[p] = true
+end
+function PortGraph.istate_of_port(self,port)
+ if self.state == 'joining' and self.selected == port then
+ return 'selected'
+ elseif port:contains(mouse_pos()) then
+ return 'hover'
+ else
+ return 'normal'
+ end
+end
+function PortGraph.istate_of_wire(self, wire)
+ return 'normal'
end
+function PortGraph.draw(self)
+ for p in pairs(self.ports) do
+ local istate = self:istate_of_port(p)
+ p:draw(istate)
+ end
+ for w in pairs(self.wires) do
+ local istate = self:istate_of_wire(w)
+ w:draw(istate)
+ end
-local state = 'normal'
-local selected = nil
+ if self.state == 'joining' then
+ local p = self.selected
+ G.setColor(phi_color(self.wire_n))
+ local mx,my = mouse_pos()
+ G.line(catenary.catenary(p.x,p.y,mx,my))
+ end
-local function hovering_port(mx,my)
- for i,p in ipairs(ports) do
- if p:contains(mx,my) then
- return p
+ if self.state == 'editing' then
+ for h in pairs(self.handles) do
+ h:draw()
end
end
- return nil
end
-function love.mousepressed(x,y,b)
- if b == 2 then
- state = 'normal'
- selected = nil
- return
+function PortGraph.make_edit_handles(self)
+ local p = self.selected
+ local n = 0
+ for _ in pairs(p.conns) do n = n + 1 end
+ local hs = {}
+ local D = 0.2
+
+ local i = 0
+ for q,w in pairs(p.conns) do
+ local theta = tau * i/n
+ i = i + 1
+ local h = setmetatable({
+ x = D * math.cos(theta) + p.x,
+ y = D * math.sin(theta) + p.y,
+ color = w.color,
+ here = p,
+ there = q,
+ w = w,
+ pg = self,
+ },EditHandle)
+ hs[h] = true
end
- local mx,my = G.inverseTransformPoint(x,y)
- local p = hovering_port(mx,my)
- if p then
- if state == 'normal' then
- state = 'joining'
- selected = p.n
- elseif state == 'joining' then
- table.insert(ports[selected].wires, p)
- state = 'normal'
- selected = nil
+
+ return hs
+end
+
+function PortGraph.click(self,button)
+ if self.state == 'normal' then
+ local p = hovered(self.ports)
+ if p then
+ if button == 1 then
+ self.state = 'joining'
+ self.selected = p
+ elseif button == 2 then
+ self.state = 'editing'
+ self.selected = p
+ self.handles = self:make_edit_handles()
+ end
+ end
+ elseif self.state == 'joining' then
+ if button == 2 then
+ self.state = 'normal'
+ self.selected = nil
+ elseif button == 1 then
+ local p = hovered(self.ports)
+ if p then
+ self:add_conn(self.selected,p)
+ self.state = 'normal'
+ self.selected = nil
+ end
+ end
+ elseif self.state == 'editing' then
+ -- rmb on other port -> edit on that port instead
+ -- rmb on wire blob -> delete that wire, stay in edit mode
+ -- lmb on wire blob -> delete that wire, go to joining mode on connected port
+ -- anything anywhere else -> back to normal mode
+ local p = hovered(self.ports)
+ local h = hovered(self.handles)
+ if button == 2 and p then
+ self.selected = p
+ self.handles = self:make_edit_handles()
+ elseif button == 2 and h then
+ h:remove_wire()
+ self.handles = self:make_edit_handles()
+ elseif button == 1 and h then
+ self.selected = h.there
+ h:remove_wire()
+ self.handles = nil
+ self.state = 'joining'
+ else
+ self.state = 'normal'
+ self.selected = nil
+ self.handles = nil
end
+
+ end
+end
+function PortGraph.connection(self, p1,p2)
+ -- returns the wire connecting p1 and p2, if it exists (truthy)
+ -- or false otherwise
+ local w1 = p1.conns[p2]
+ local w2 = p2.conns[p1]
+ -- better safe than sorry
+ assert(w1==w2, "wire bidirectionality inconsistency")
+ if w1 then
+ assert((w1.p1 == p1 and w1.p2 == p2)
+ or (w1.p1 == p2 and w1.p2 == p1),
+ "wire backlink inconsistency")
end
+ return w1
end
+function PortGraph.add_conn(self, p1,p2)
+ assert(not self:connection(p1,p2),
+ string.format("%s and %s already connected!", p1,p2))
+ local wire = Wire:make(p1, p2, phi_color(self.wire_n))
+ self.wires[wire] = true
+ p1.conns[p2] = wire
+ p2.conns[p1] = wire
+ self.wire_n = self.wire_n + 1
+end
+function PortGraph.remove_conn(self,p1,p2)
+ local wire = self:connection(p1,p2)
+ assert(wire,string.format("%s and %s not connected!", p1,p2))
+ self.wires[wire] = nil
+ p1.conns[p2] = nil
+ p2.conns[p1] = nil
+end
+local pg = PortGraph:make()
+local n = 10
+for i = 1,n do
+ local theta = tau * i/n
+ pg:add(math.cos(theta), math.sin(theta), i)
+end
-local L = 2
-function love.wheelmoved(x,y)
- L = L + (y/15)
+function love.mousepressed(x,y,b)
+ pg:click(b)
end
function love.draw()
@@ -94,29 +267,11 @@ function love.draw()
G.clear(1,1,1)
G.setColor(0,0,0)
G.origin()
- G.print(L,10,10)
G.setLineWidth(0.01)
G.translate(W/2,H/2)
G.scale(ZOOM)
- local mx,my = G.inverseTransformPoint(love.mouse.getPosition())
- local wn = 0
- for i,p in ipairs(ports) do
- local istate = 'normal'
- if p:contains(mx,my) then istate = 'hover' end
- if state ~= 'normal' and selected == i then istate = 'selected' end
- p:draw(istate)
- for _,q in ipairs(p.wires) do
- G.setColor(wire_color(wn))
- wn = wn + 1
- G.line(catenary.catenary(p.x,p.y,q.x,q.y))
- end
- end
+ pg:draw()
- if state == 'joining' then
- G.setColor(wire_color(wn))
- local p = ports[selected]
- G.line(catenary.catenary(p.x,p.y,mx,my))
- end
end