summary refs log tree commit diff stats
path: root/compiler/nimsets.nim
blob: 337aedda9d3a57b2aecb78c421c2c16c79a3f5c0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#
#
#           The Nimrod Compiler
#        (c) Copyright 2009 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# this unit handles Nimrod sets; it implements symbolic sets

import 
  ast, astalgo, trees, nversion, msgs, platform, bitsets, types, rnimsyn

proc toBitSet*(s: PNode, b: var TBitSet)
  # this function is used for case statement checking:
proc overlap*(a, b: PNode): bool
proc inSet*(s: PNode, elem: PNode): bool
proc someInSet*(s: PNode, a, b: PNode): bool
proc emptyRange*(a, b: PNode): bool
proc SetHasRange*(s: PNode): bool
  # returns true if set contains a range (needed by the code generator)
  # these are used for constant folding:
proc unionSets*(a, b: PNode): PNode
proc diffSets*(a, b: PNode): PNode
proc intersectSets*(a, b: PNode): PNode
proc symdiffSets*(a, b: PNode): PNode
proc containsSets*(a, b: PNode): bool
proc equalSets*(a, b: PNode): bool
proc cardSet*(s: PNode): BiggestInt
# implementation

proc inSet(s: PNode, elem: PNode): bool = 
  if s.kind != nkCurly: InternalError(s.info, "inSet")
  for i in countup(0, sonsLen(s) - 1): 
    if s.sons[i].kind == nkRange: 
      if leValue(s.sons[i].sons[0], elem) and
          leValue(elem, s.sons[i].sons[1]): 
        return true
    else: 
      if sameValue(s.sons[i], elem): 
        return true
  result = false

proc overlap(a, b: PNode): bool = 
  if a.kind == nkRange: 
    if b.kind == nkRange: 
      result = leValue(a.sons[0], b.sons[1]) and
          leValue(b.sons[1], a.sons[1]) or
          leValue(a.sons[0], b.sons[0]) and leValue(b.sons[0], a.sons[1])
    else: 
      result = leValue(a.sons[0], b) and leValue(b, a.sons[1])
  else: 
    if b.kind == nkRange: 
      result = leValue(b.sons[0], a) and leValue(a, b.sons[1])
    else: 
      result = sameValue(a, b)

proc SomeInSet(s: PNode, a, b: PNode): bool = 
  # checks if some element of a..b is in the set s
  if s.kind != nkCurly: InternalError(s.info, "SomeInSet")
  for i in countup(0, sonsLen(s) - 1): 
    if s.sons[i].kind == nkRange: 
      if leValue(s.sons[i].sons[0], b) and leValue(b, s.sons[i].sons[1]) or
          leValue(s.sons[i].sons[0], a) and leValue(a, s.sons[i].sons[1]): 
        return true
    else: 
      # a <= elem <= b
      if leValue(a, s.sons[i]) and leValue(s.sons[i], b): 
        return true
  result = false

proc toBitSet(s: PNode, b: var TBitSet) = 
  var first, j: BiggestInt
  first = firstOrd(s.typ.sons[0])
  bitSetInit(b, int(getSize(s.typ)))
  for i in countup(0, sonsLen(s) - 1): 
    if s.sons[i].kind == nkRange: 
      j = getOrdValue(s.sons[i].sons[0])
      while j <= getOrdValue(s.sons[i].sons[1]): 
        BitSetIncl(b, j - first)
        inc(j)
    else: 
      BitSetIncl(b, getOrdValue(s.sons[i]) - first)
  
proc ToTreeSet(s: TBitSet, settype: PType, info: TLineInfo): PNode = 
  var 
    a, b, e, first: BiggestInt # a, b are interval borders
    elemType: PType
    n: PNode
  elemType = settype.sons[0]
  first = firstOrd(elemType)
  result = newNodeI(nkCurly, info)
  result.typ = settype
  result.info = info
  e = 0
  while e < high(s) * elemSize: 
    if bitSetIn(s, e): 
      a = e
      b = e
      while true: 
        Inc(b)
        if (b > high(s) * elemSize) or not bitSetIn(s, b): break 
      Dec(b)
      if a == b: 
        addSon(result, newIntTypeNode(nkIntLit, a + first, elemType))
      else: 
        n = newNodeI(nkRange, info)
        n.typ = elemType
        addSon(n, newIntTypeNode(nkIntLit, a + first, elemType))
        addSon(n, newIntTypeNode(nkIntLit, b + first, elemType))
        addSon(result, n)
      e = b
    Inc(e)

type 
  TSetOP = enum 
    soUnion, soDiff, soSymDiff, soIntersect

proc nodeSetOp(a, b: PNode, op: TSetOp): PNode = 
  var x, y: TBitSet
  toBitSet(a, x)
  toBitSet(b, y)
  case op
  of soUnion: BitSetUnion(x, y)
  of soDiff: BitSetDiff(x, y)
  of soSymDiff: BitSetSymDiff(x, y)
  of soIntersect: BitSetIntersect(x, y)
  result = toTreeSet(x, a.typ, a.info)

proc unionSets(a, b: PNode): PNode = 
  result = nodeSetOp(a, b, soUnion)

proc diffSets(a, b: PNode): PNode = 
  result = nodeSetOp(a, b, soDiff)

proc intersectSets(a, b: PNode): PNode = 
  result = nodeSetOp(a, b, soIntersect)

proc symdiffSets(a, b: PNode): PNode = 
  result = nodeSetOp(a, b, soSymDiff)

proc containsSets(a, b: PNode): bool = 
  var x, y: TBitSet
  toBitSet(a, x)
  toBitSet(b, y)
  result = bitSetContains(x, y)

proc equalSets(a, b: PNode): bool = 
  var x, y: TBitSet
  toBitSet(a, x)
  toBitSet(b, y)
  result = bitSetEquals(x, y)

proc cardSet(s: PNode): BiggestInt = 
  # here we can do better than converting it into a compact set
  # we just count the elements directly
  result = 0
  for i in countup(0, sonsLen(s) - 1): 
    if s.sons[i].kind == nkRange: 
      result = result + getOrdValue(s.sons[i].sons[1]) -
          getOrdValue(s.sons[i].sons[0]) + 1
    else: 
      Inc(result)
  
proc SetHasRange(s: PNode): bool = 
  if s.kind != nkCurly: InternalError(s.info, "SetHasRange")
  for i in countup(0, sonsLen(s) - 1): 
    if s.sons[i].kind == nkRange: 
      return true
  result = false

proc emptyRange(a, b: PNode): bool = 
  result = not leValue(a, b)  # a > b iff not (a <= b)
  
span class="n">mx), py(my) local shape = drawing.pending if shape.mode == nil then -- nothing pending elseif shape.mode == 'freehand' then local shape_copy = deepcopy(shape) Drawing.smoothen(shape_copy) Drawing.draw_shape(drawing, shape_copy, top, left,right) elseif shape.mode == 'line' then if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then return end local p1 = drawing.points[shape.p1] love.graphics.line(px(p1.x),py(p1.y), pmx,pmy) elseif shape.mode == 'manhattan' then if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then return end local p1 = drawing.points[shape.p1] if math.abs(mx-p1.x) > math.abs(my-p1.y) then love.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y)) else love.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy) end elseif shape.mode == 'polygon' then -- don't close the loop on a pending polygon local prev = nil for _,point in ipairs(shape.vertices) do local curr = drawing.points[point] if prev then love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y)) end prev = curr end love.graphics.line(px(prev.x),py(prev.y), pmx,pmy) elseif shape.mode == 'rectangle' then local first = drawing.points[shape.vertices[1]] if #shape.vertices == 1 then love.graphics.line(px(first.x),py(first.y), pmx,pmy) return end local second = drawing.points[shape.vertices[2]] local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my) love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y)) love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy)) love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy)) love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y)) elseif shape.mode == 'square' then local first = drawing.points[shape.vertices[1]] if #shape.vertices == 1 then love.graphics.line(px(first.x),py(first.y), pmx,pmy) return end local second = drawing.points[shape.vertices[2]] local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my) love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y)) love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy)) love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy)) love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y)) elseif shape.mode == 'circle' then local center = drawing.points[shape.center] if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then return end local cx,cy = px(center.x), py(center.y) love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y())) elseif shape.mode == 'arc' then local center = drawing.points[shape.center] if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then return end shape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle) local cx,cy = px(center.x), py(center.y) love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360) elseif shape.mode == 'move' then -- nothing pending; changes are immediately committed elseif shape.mode == 'name' then -- nothing pending; changes are immediately committed else print(shape.mode) assert(false) end end function Drawing.in_drawing(drawing, line_cache, x,y, left,right) if line_cache.starty == nil then return false end -- outside current page local width = right-left return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right end function Drawing.mouse_press(State, drawing_index, x,y, mouse_button) local drawing = State.lines[drawing_index] local line_cache = State.line_cache[drawing_index] local cx = Drawing.coord(x-State.left, State.width) local cy = Drawing.coord(y-line_cache.starty, State.width) if State.current_drawing_mode == 'freehand' then drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}} elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width) drawing.pending = {mode=State.current_drawing_mode, p1=j} elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' then local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width) drawing.pending = {mode=State.current_drawing_mode, vertices={j}} elseif State.current_drawing_mode == 'circle' then local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width) drawing.pending = {mode=State.current_drawing_mode, center=j} elseif State.current_drawing_mode == 'move' then -- all the action is in mouse_release elseif State.current_drawing_mode == 'name' then -- nothing else print(State.current_drawing_mode) assert(false) end end -- a couple of operations on drawings need to constantly check the state of the mouse function Drawing.update(State) if State.lines.current_drawing == nil then return end local drawing = State.lines.current_drawing local line_cache = State.line_cache[State.lines.current_drawing_index] assert(drawing.mode == 'drawing') local pmx, pmy = App.mouse_x(), App.mouse_y() local mx = Drawing.coord(pmx-State.left, State.width) local my = Drawing.coord(pmy-line_cache.starty, State.width) if App.mouse_down(1) then if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then if drawing.pending.mode == 'freehand' then table.insert(drawing.pending.points, {x=mx, y=my}) elseif drawing.pending.mode == 'move' then drawing.pending.target_point.x = mx drawing.pending.target_point.y = my Drawing.relax_constraints(drawing, drawing.pending.target_point_index) end end elseif State.current_drawing_mode == 'move' then if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then drawing.pending.target_point.x = mx drawing.pending.target_point.y = my Drawing.relax_constraints(drawing, drawing.pending.target_point_index) end else -- do nothing end end function Drawing.relax_constraints(drawing, p) for _,shape in ipairs(drawing.shapes) do if shape.mode == 'manhattan' then if shape.p1 == p then shape.mode = 'line' elseif shape.p2 == p then shape.mode = 'line' end elseif shape.mode == 'rectangle' or shape.mode == 'square' then for _,v in ipairs(shape.vertices) do if v == p then shape.mode = 'polygon' end end end end end function Drawing.mouse_release(State, x,y, mouse_button) if State.current_drawing_mode == 'move' then State.current_drawing_mode = State.previous_drawing_mode State.previous_drawing_mode = nil if State.lines.current_drawing then State.lines.current_drawing.pending = {} State.lines.current_drawing = nil end elseif State.lines.current_drawing then local drawing = State.lines.current_drawing local line_cache = State.line_cache[State.lines.current_drawing_index] if drawing.pending then if drawing.pending.mode == nil then -- nothing pending elseif drawing.pending.mode == 'freehand' then -- the last point added during update is good enough Drawing.smoothen(drawing.pending) table.insert(drawing.shapes, drawing.pending) elseif drawing.pending.mode == 'line' then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'manhattan' then local p1 = drawing.points[drawing.pending.p1] local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if math.abs(mx-p1.x) > math.abs(my-p1.y) then drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width) else drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width) end local p2 = drawing.points[drawing.pending.p2] App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width)) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'polygon' then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width)) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'rectangle' then assert(#drawing.pending.vertices <= 2) if #drawing.pending.vertices == 2 then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local first = drawing.points[drawing.pending.vertices[1]] local second = drawing.points[drawing.pending.vertices[2]] local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my) table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width)) table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width)) table.insert(drawing.shapes, drawing.pending) end else -- too few points; draw nothing end elseif drawing.pending.mode == 'square' then assert(#drawing.pending.vertices <= 2) if #drawing.pending.vertices == 2 then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local first = drawing.points[drawing.pending.vertices[1]] local second = drawing.points[drawing.pending.vertices[2]] local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my) table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width)) table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width)) table.insert(drawing.shapes, drawing.pending) end end elseif drawing.pending.mode == 'circle' then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local center = drawing.points[drawing.pending.center] drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'arc' then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local center = drawing.points[drawing.pending.center] drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'name' then -- drop it else print(drawing.pending.mode) assert(false) end State.lines.current_drawing.pending = {} State.lines.current_drawing = nil end end end function Drawing.keychord_press(State, chord) if chord == 'C-p' and not App.mouse_down(1) then State.current_drawing_mode = 'freehand' elseif App.mouse_down(1) and chord == 'l' then State.current_drawing_mode = 'line' local _,drawing = Drawing.current_drawing(State) if drawing.pending.mode == 'freehand' then drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width) elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then drawing.pending.p1 = drawing.pending.vertices[1] elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then drawing.pending.p1 = drawing.pending.center end drawing.pending.mode = 'line' elseif chord == 'C-l' and not App.mouse_down(1) then State.current_drawing_mode = 'line' elseif App.mouse_down(1) and chord == 'm' then State.current_drawing_mode = 'manhattan' local drawing = Drawing.select_drawing_at_mouse(State) if drawing.pending.mode == 'freehand' then drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width) elseif drawing.pending.mode == 'line' then -- do nothing elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then drawing.pending.p1 = drawing.pending.vertices[1] elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then drawing.pending.p1 = drawing.pending.center end drawing.pending.mode = 'manhattan' elseif chord == 'C-m' and not App.mouse_down(1) then State.current_drawing_mode = 'manhattan' elseif chord == 'C-g' and not App.mouse_down(1) then State.current_drawing_mode = 'polygon' elseif App.mouse_down(1) and chord == 'g' then State.current_drawing_mode = 'polygon' local _,drawing = Drawing.current_drawing(State) if drawing.pending.mode == 'freehand' then drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)} elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then if drawing.pending.vertices == nil then drawing.pending.vertices = {drawing.pending.p1} end elseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then -- reuse existing vertices elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then drawing.pending.vertices = {drawing.pending.center} end drawing.pending.mode = 'polygon' elseif chord == 'C-r' and not App.mouse_down(1) then State.current_drawing_mode = 'rectangle' elseif App.mouse_down(1) and chord == 'r' then State.current_drawing_mode = 'rectangle' local _,drawing = Drawing.current_drawing(State) if drawing.pending.mode == 'freehand' then drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)} elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then if drawing.pending.vertices == nil then drawing.pending.vertices = {drawing.pending.p1} end elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then -- reuse existing (1-2) vertices elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then drawing.pending.vertices = {drawing.pending.center} end drawing.pending.mode = 'rectangle' elseif chord == 'C-s' and not App.mouse_down(1) then State.current_drawing_mode = 'square' elseif App.mouse_down(1) and chord == 's' then State.current_drawing_mode = 'square' local _,drawing = Drawing.current_drawing(State) if drawing.pending.mode == 'freehand' then drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)} elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then if drawing.pending.vertices == nil then drawing.pending.vertices = {drawing.pending.p1} end elseif drawing.pending.mode == 'polygon' then while #drawing.pending.vertices > 2 do table.remove(drawing.pending.vertices) end elseif drawing.pending.mode == 'rectangle' then -- reuse existing (1-2) vertices elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then drawing.pending.vertices = {drawing.pending.center} end drawing.pending.mode = 'square' elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then local _,drawing,line_cache = Drawing.current_drawing(State) local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) table.insert(drawing.pending.vertices, j) elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then local _,drawing,line_cache = Drawing.current_drawing(State) local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) while #drawing.pending.vertices >= 2 do table.remove(drawing.pending.vertices) end table.insert(drawing.pending.vertices, j) elseif chord == 'C-o' and not App.mouse_down(1) then State.current_drawing_mode = 'circle' elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then local _,drawing,line_cache = Drawing.current_drawing(State) drawing.pending.mode = 'arc' local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local center = drawing.points[drawing.pending.center] drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my) elseif App.mouse_down(1) and chord == 'o' then State.current_drawing_mode = 'circle' local _,drawing = Drawing.current_drawing(State) if drawing.pending.mode == 'freehand' then drawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width) elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then drawing.pending.center = drawing.pending.p1 elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then drawing.pending.center = drawing.pending.vertices[1] end drawing.pending.mode = 'circle' elseif chord == 'C-u' and not App.mouse_down(1) then local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State) if drawing then if State.previous_drawing_mode == nil then State.previous_drawing_mode = State.current_drawing_mode end State.current_drawing_mode = 'move' drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i} State.lines.current_drawing_index = drawing_index State.lines.current_drawing = drawing end elseif chord == 'C-n' and not App.mouse_down(1) then local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State) if drawing then if State.previous_drawing_mode == nil then -- don't clobber State.previous_drawing_mode = State.current_drawing_mode end State.current_drawing_mode = 'name' p.name = '' drawing.pending = {mode=State.current_drawing_mode, target_point=point_index} State.lines.current_drawing_index = drawing_index State.lines.current_drawing = drawing end elseif chord == 'C-d' and not App.mouse_down(1) then local _,drawing,_,i,p = Drawing.select_point_at_mouse(State) if drawing then for _,shape in ipairs(drawing.shapes) do if Drawing.contains_point(shape, i) then if shape.mode == 'polygon' then local idx = table.find(shape.vertices, i) assert(idx) table.remove(shape.vertices, idx) if #shape.vertices < 3 then shape.mode = 'deleted' end else shape.mode = 'deleted' end end end drawing.points[i].deleted = true end local drawing,_,_,shape = Drawing.select_shape_at_mouse(State) if drawing then shape.mode = 'deleted' end elseif chord == 'C-h' and not App.mouse_down(1) then local drawing = Drawing.select_drawing_at_mouse(State) if drawing then drawing.show_help = true end elseif chord == 'escape' and App.mouse_down(1) then local _,drawing = Drawing.current_drawing(State) drawing.pending = {} end end function Drawing.complete_rectangle(firstx,firsty, secondx,secondy, x,y) if firstx == secondx then return x,secondy, x,firsty end if firsty == secondy then return secondx,y, firstx,y end local first_slope = (secondy-firsty)/(secondx-firstx) -- slope of second edge: -- -1/first_slope -- equation of line containing the second edge: -- y-secondy = -1/first_slope*(x-secondx) -- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0 -- now we want to find the point on this line that's closest to the mouse pointer. -- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation local a = 1/first_slope local c = -secondy - secondx/first_slope local thirdx = round(((x-a*y) - a*c) / (a*a + 1)) local thirdy = round((a*(-x + a*y) - c) / (a*a + 1)) -- slope of third edge = first_slope -- equation of line containing third edge: -- y - thirdy = first_slope*(x-thirdx) -- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0 -- now we want to find the point on this line that's closest to the first point local a = -first_slope local c = -thirdy + thirdx*first_slope local fourthx = round(((firstx-a*firsty) - a*c) / (a*a + 1)) local fourthy = round((a*(-firstx + a*firsty) - c) / (a*a + 1)) return thirdx,thirdy, fourthx,fourthy end function Drawing.complete_square(firstx,firsty, secondx,secondy, x,y) -- use x,y only to decide which side of the first edge to complete the square on local deltax = secondx-firstx local deltay = secondy-firsty local thirdx = secondx+deltay local thirdy = secondy-deltax if not geom.same_side(firstx,firsty, secondx,secondy, thirdx,thirdy, x,y) then deltax = -deltax deltay = -deltay thirdx = secondx+deltay thirdy = secondy-deltax end local fourthx = firstx+deltay local fourthy = firsty-deltax return thirdx,thirdy, fourthx,fourthy end function Drawing.current_drawing(State) local x, y = App.mouse_x(), App.mouse_y() for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then return drawing_index,drawing,line_cache end end end return nil end function Drawing.select_shape_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) for i,shape in ipairs(drawing.shapes) do assert(shape) if geom.on_shape(mx,my, drawing, shape) then return drawing,line_cache,i,shape end end end end end end function Drawing.select_point_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) for i,point in ipairs(drawing.points) do assert(point) if Drawing.near(point, mx,my, State.width) then return drawing_index,drawing,line_cache,i,point end end end end end end function Drawing.select_drawing_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then return drawing end end end end function Drawing.contains_point(shape, p) if shape.mode == 'freehand' then -- not supported elseif shape.mode == 'line' or shape.mode == 'manhattan' then return shape.p1 == p or shape.p2 == p elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then return table.find(shape.vertices, p) elseif shape.mode == 'circle' then return shape.center == p elseif shape.mode == 'arc' then return shape.center == p -- ugh, how to support angles elseif shape.mode == 'deleted' then -- already done else print(shape.mode) assert(false) end end function Drawing.smoothen(shape) assert(shape.mode == 'freehand') for _=1,7 do for i=2,#shape.points-1 do local a = shape.points[i-1] local b = shape.points[i] local c = shape.points[i+1] b.x = round((a.x + b.x + c.x)/3) b.y = round((a.y + b.y + c.y)/3) end end end function round(num) return math.floor(num+.5) end function Drawing.find_or_insert_point(points, x,y, width) -- check if UI would snap the two points together for i,point in ipairs(points) do if Drawing.near(point, x,y, width) then return i end end table.insert(points, {x=x, y=y}) return #points end function Drawing.near(point, x,y, width) local px,py = Drawing.pixels(x, width),Drawing.pixels(y, width) local cx,cy = Drawing.pixels(point.x, width), Drawing.pixels(point.y, width) return (cx-px)*(cx-px) + (cy-py)*(cy-py) < Same_point_distance*Same_point_distance end function Drawing.pixels(n, width) -- parts to pixels return math.floor(n*width/256) end function Drawing.coord(n, width) -- pixels to parts return math.floor(n*256/width) end function table.find(h, x) for k,v in pairs(h) do if v == x then return k end end end