This is probably not ideal; let's see how it goes..
VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC JRLBUB6LR2JIAKVQNKF3T4BDICUIJ3HEMRRHX56YP5M5SP7ZS3WAC SYS67ZEJ6M3UWLSXOBRG3AETJSHMFN7PE2NUDAGNHKHV2HI2LDBAC PLLSUOCI777X65MERQPO5FHR7YJVDLVOX2NGMSCJEFLWIJ5MDEJQC AVDRMDDBDHKCCCY35QS2YQSMTAW2R7PDAEJGZ5DAYXWX2YWHESPAC XJ5J7LDI4AI5J3S67AN5GV6VUIAUCADL2YPGV753ENTW5QUYH5BAC BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC 7IKRRESBHMYHHKW4XHUEEKHKPOBLAGZ7A7FJMRU32MTRKIV6S7GQC 6LJZN727CRPYR34LV75CQF55YZI3E7MGESYZSFSYAE73SNEZE3FAC XX7G2FFJ4QCGQGD4REAW5QFHVYAKCFUPGZCK7L6DFGS5ISVBYBQQC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC JCSLDGAH2F6AIY4Z6XM6K4LOMW7EFY3E4NF5YXLMHLTYTX3A4Z3QC AVQ5MC5DWNLI6LUUIPGBLGP4LKRPGWBY4THNY25OBT2FAVHC6MCAC JVRL5TWLBTWMTHJDZSDN5XQDMEIIPVAZBKUP75HMO7JHURAYWG5QC ICIIP4DBZEYFB6LITMFZ4BVGC4ZI6GONCSNXPBVEGXYXVCCZ53OAC VVXVV2D2F5Y6D6N5VVPUPK3N6GMDTG2YCYPQDYTYEKVKBYHRRYEAC TRCAEE2ASBJJCFFJYP2DGPLDOE4R2ACSY37PZSJSKOPQLATFO2EAC ZD63LJ2TGWQQ5KWAX22QUWEYI2X5XUI2UTE4E7LGY26AS7HT2WGQC SNDZOK6QJUASKYLYBHIATA7WXPCPZTEYEWA63I4H7HH3JT3B4U6AC MGOQ5XAVFTWZPBG2O5ZTGSEKU6BRJKQZLDV6CM4737VD2FAEB5JQC WDWXNW7VH57W6KXBNXFV6NPIO5RUS7Q6QNGGI4R6HWIRXLYI5NSAC ZUOL7X6VIPRCMEZURYGNHTDEIP3ZCHZW4PKVKBNXVZL5V4VOE5ZQC GCUARQ2GJT3Y5K6LOOA6S7SOBMSXU7VV7LT355TOMFFPBQ3ZVYKAC OFA3PRBSGC7OXHUJGUSEZI73XMVC2HJIO6LG6XYE73DDAJT6KFPAC YKRF5V3ZZQIQ3UGAFYTQT5PUQVHCP2VHFDX77EY2C3X543HUDYKQC EFMLTMZG5TUEGLSYLVKOKDSTGVSVWSKOMS7CJWOUGK5LADSH4YTQC IFGAJAF7UWM236QV4NWP2C67TJPQGXNBC2RTZCOWPJUJYYC2MMXAC FI6IK76MDA4WQP7MMDMPTKW5Y7LO3WTYYZL3T5WTTESLS4ABAK6QC T664AOUGOSFMPZAO6OGKJ26R22ORJUIBVG3QZOJL24MQXYEHBXOQC HWPK4SMPGZMXWXKCVX667676ZWN5Q2XZSTMVAADG7JNRF6XE45RQC NL5J7Z5H577GPKGNS5TDRVWC55VLA2UCZE34F5WR4AJ5N265UECAC FMQ74DP324YKGBSTNMHBJMT6FYP5NI26MM43VUPGKODI5DVDCUXQC ZOOY3ME4BUD6RLWCKZFA62JNN4BMPOXH24HGTFWPWEKDECOXMFUAC FBDRL6LHPTBPMU3R356OLY4XE4ELQQEDXMCZGCBT2RFHLR7VQXQAC G77XIN7MLX465AXLXDUJDGEHXXCMR2Q7K25UAMKQERBJGNJPNW6AC 4NDYV4WDJWOK6PGURVVPNYEZTX7GPJC2YJ2QTW7P3X3BJBTTJLEQC 7Q4B6M2DAB6FZX6RGA2ZX2UTP6PG2LC745WNPK7ZMEUCSSGXEUQQC RXE6NQTNS7MFNF24KFEQRVCZFQ44MDO5HPSQGQD555HPEMQA76DAC H7OEU6WPOKOSKV5RNAM5W62V5SYOY7VEA5VOK6JD5UEBUBIMOKUQC KCIM5UTVV4KIL7SNRZFOW6FUQHGWAPOGQO4QTVNIWWBWMVG6GYNQC T76KKDWZLQSWMXT2ZE2PPNKBKB4W5M4BW5E6ICHKEBDAUBN6FMZAC HRWN5V6J6VMXS7WNSRGI7WMUSZ2OI52JJ4IK352VVSDZI4EF5HHQC JS6JSYOTXVZRN4XN7TYDY4GCNCFQBSERBPXEJ2TR5A2ITA6JJT2QC M36DBSDECWQYCVIPWUYPZXSR547P76RBVYQ63B5KJGVTFWIXCSJQC RJGZD4INLQ3IAWLCBYBZQDMP4ICSMZWJX2TRYODDYVUNMGPL6GYAC 3XD6M3CFKZJR365MHXUWJ4HGSDTPYO6WYZ4RGW7ECBBITKVQX24QC O2UFJ6G3MDBJFSABWAJWTZGP6VAKRMQ6XCMILLQRRSS43C3UF2OQC WAZVXUV2LMNGGR6WQ3NIFGSKYYLHMQAOM6VPRU4O2A3IJVLSSEZAC IZZVOCLB7KB4ZNQ35OL466MHWOK3XZMOS7ZPFLHUFQ47LJLQQQ3QC D2GCFTTT2RNUBFB243YTA4F2H5ZXFTFHBSKHLZ2CX4JSKRZFTUUQC R3WSFYGY4UTJ54K6M2ZQVW4SB4KP7GUQ73OYAY7L5RI6UFFNKQVAC Z4KNS42NJZTQKUQZ7B5NYU2U4VOCUQCBFT2D7423MAXKF7NQ5ZJAC 5T2E3PDVSLMZSSIIQRNKIKQVV77XQTHP473OP7XBTTMSZHIQID5AC EDY3RQULZEP23VIC6CSNJ5MSQNIIAABEHLFPCH7LDONX2I6FSTQQC LBQAAJN4CGSIEO3L4UNKYTPX6MRLUJYHCMRMFTFLLTTAGCRUSE7QC CXCAERTBB6LHLRRR23SO2CWSMLUM6MF5IDUVBMB2QYZBMUVSUNXAC 6J6EEUAYIK5DT7LP43IJVEH3UX53ALKKRYOI6AD64MVA6QNIYWXQC NW7X4AGMEDHAVLRUK5BPOQD55JRVSIM6SKOLPCI6AUK7WIKV6VPAC V5TP27FPD6GPATHPABRW2FKP7BIKY53KL47UVEU5DF4WXEZF7CKAC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC function pixels(n) -- parts to pixelsreturn n*Drawing_width/256endfunction coord(n) -- pixels to partsreturn math.floor(n*256/Drawing_width)end
if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = coord(x-16), coord(y-drawing.y)
if y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = Drawing.coord(x-16), Drawing.coord(y-drawing.y)
if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenif Current_mode == 'freehand' thendrawing.pending = {mode=Current_mode, points={{x=coord(x-16), y=coord(y-drawing.y)}}}elseif Current_mode == 'line' or Current_mode == 'manhattan' thenlocal j = insert_point(drawing.points, coord(x-16), coord(y-drawing.y))drawing.pending = {mode=Current_mode, p1=j}elseif Current_mode == 'polygon' thenlocal j = insert_point(drawing.points, coord(x-16), coord(y-drawing.y))drawing.pending = {mode=Current_mode, vertices={j}}elseif Current_mode == 'circle' thenlocal j = insert_point(drawing.points, coord(x-16), coord(y-drawing.y))drawing.pending = {mode=Current_mode, center=j}
if y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenif Current_drawing_mode == 'freehand' thendrawing.pending = {mode=Current_drawing_mode, points={{x=Drawing.coord(x-16), y=Drawing.coord(y-drawing.y)}}}elseif Current_drawing_mode == 'line' or Current_drawing_mode == 'manhattan' thenlocal j = Drawing.insert_point(drawing.points, Drawing.coord(x-16), Drawing.coord(y-drawing.y))drawing.pending = {mode=Current_drawing_mode, p1=j}elseif Current_drawing_mode == 'polygon' thenlocal j = Drawing.insert_point(drawing.points, Drawing.coord(x-16), Drawing.coord(y-drawing.y))drawing.pending = {mode=Current_drawing_mode, vertices={j}}elseif Current_drawing_mode == 'circle' thenlocal j = Drawing.insert_point(drawing.points, Drawing.coord(x-16), Drawing.coord(y-drawing.y))drawing.pending = {mode=Current_drawing_mode, center=j}
endendfunction insert_point(points, x,y)for i,point in ipairs(points) doif near(point, x,y) thenreturn iendendtable.insert(points, {x=x, y=y})return #pointsendfunction near(point, x,y)local px,py = pixels(x),pixels(y)local cx,cy = pixels(point.x), pixels(point.y)return (cx-px)*(cx-px) + (cy-py)*(cy-py) < 16endfunction draw_shape(left,top, drawing, shape)if shape.mode == 'freehand' thenlocal prev = nilfor _,point in ipairs(shape.points) doif prev thenlove.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, pixels(point.x)+left,pixels(point.y)+top)endprev = pointendelseif shape.mode == 'line' or shape.mode == 'manhattan' thenlocal p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]love.graphics.line(pixels(p1.x)+left,pixels(p1.y)+top, pixels(p2.x)+left,pixels(p2.y)+top)elseif shape.mode == 'polygon' thenlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, pixels(curr.x)+left,pixels(curr.y)+top)endprev = currend-- close the looplocal curr = drawing.points[shape.vertices[1]]love.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, pixels(curr.x)+left,pixels(curr.y)+top)elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]love.graphics.circle('line', pixels(center.x)+left,pixels(center.y)+top, pixels(shape.radius))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]love.graphics.arc('line', 'open', pixels(center.x)+left,pixels(center.y)+top, pixels(shape.radius), shape.start_angle, shape.end_angle, 360)elseif shape.mode == 'deleted' thenelseprint(shape.mode)assert(false)endendfunction draw_pending_shape(left,top, drawing)local shape = drawing.pendingif shape.mode == 'freehand' thendraw_shape(left,top, drawing, shape)elseif shape.mode == 'line' thenlocal p1 = drawing.points[shape.p1]local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlove.graphics.line(pixels(p1.x)+left,pixels(p1.y)+top, pixels(mx)+left,pixels(my)+top)elseif shape.mode == 'manhattan' thenlocal p1 = drawing.points[shape.p1]local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendif math.abs(mx-p1.x) > math.abs(my-p1.y) thenlove.graphics.line(pixels(p1.x)+left,pixels(p1.y)+top, pixels(mx)+left,pixels(p1.y)+top)elselove.graphics.line(pixels(p1.x)+left,pixels(p1.y)+top, pixels(p1.x)+left,pixels(my)+top)endelseif shape.mode == 'polygon' then-- don't close the loop on a pending polygonlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, pixels(curr.x)+left,pixels(curr.y)+top)endprev = currendlove.graphics.line(pixels(prev.x)+left,pixels(prev.y)+top, love.mouse.getX(),love.mouse.getY())elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlocal cx,cy = pixels(center.x)+left, pixels(center.y)+toplove.graphics.circle('line', cx,cy, math.dist(cx,cy, love.mouse.getX(),love.mouse.getY()))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendshape.end_angle = angle_with_hint(center.x,center.y, mx,my, shape.end_angle)local cx,cy = pixels(center.x)+left, pixels(center.y)+toplove.graphics.arc('line', 'open', cx,cy, pixels(shape.radius), shape.start_angle, shape.end_angle, 360)
function on_shape(x,y, drawing, shape)if shape.mode == 'freehand' thenreturn on_freehand(x,y, drawing, shape)elseif shape.mode == 'line' thenreturn on_line(x,y, drawing, shape)elseif shape.mode == 'manhattan' thenreturn x == drawing.points[shape.p1].x or y == drawing.points[shape.p1].yelseif shape.mode == 'polygon' thenreturn on_polygon(x,y, drawing, shape)elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]return math.dist(center.x,center.y, x,y) == shape.radiuselseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]local dist = math.dist(center.x,center.y, x,y)if dist < shape.radius*0.95 or dist > shape.radius*1.05 thenreturn falseendreturn angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)elseif shape.mode == 'deleted' thenelseprint(shape.mode)assert(false)endendfunction on_freehand(x,y, drawing, shape)local prevfor _,p in ipairs(shape.points) doif prev thenif on_line(x,y, drawing, {p1=prev, p2=p}) thenreturn trueendendprev = pendreturn falseendfunction on_line(x,y, drawing, shape)local p1,p2if type(shape.p1) == 'number' thenp1 = drawing.points[shape.p1]p2 = drawing.points[shape.p2]elsep1 = shape.p1p2 = shape.p2endif p1.x == p2.x thenif math.abs(p1.x-x) > 5 thenreturn falseendlocal y1,y2 = p1.y,p2.yif y1 > y2 theny1,y2 = y2,y1endreturn y >= y1 and y <= y2end-- has the right slope and interceptlocal m = (p2.y - p1.y) / (p2.x - p1.x)local yp = p1.y + m*(x-p1.x)if yp < 0.95*y or yp > 1.05*y thenreturn falseend-- between endpointslocal k = (x-p1.x) / (p2.x-p1.x)return k > -0.05 and k < 1.05endfunction on_polygon(x,y, drawing, shape)local prevfor _,p in ipairs(shape.vertices) doif prev thenif on_line(x,y, drawing, {p1=prev, p2=p}) thenreturn trueendendprev = pendreturn on_line(x,y, drawing, {p1=shape.vertices[1], p2=shape.vertices[#shape.vertices]})endfunction angle_between(x1,y1, x2,y2, s,e)local angle = math.angle(x1,y1, x2,y2)if s > e thens,e = e,send-- I'm not sure this is right or ideal..angle = angle-math.pi*2if s <= angle and angle <= e thenreturn trueendangle = angle+math.pi*2if s <= angle and angle <= e thenreturn trueendangle = angle+math.pi*2return s <= angle and angle <= eend
elseif love.mouse.isDown('1') and chord == 'p' and Current_mode == 'polygon' thenlocal drawing = current_drawing()local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)local j = insert_point(drawing.points, mx,my)
elseif love.mouse.isDown('1') and chord == 'p' and Current_drawing_mode == 'polygon' thenlocal drawing = Drawing.current_drawing()local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)local j = Drawing.insert_point(drawing.points, mx,my)
Current_mode = 'circle'elseif love.mouse.isDown('1') and chord == 'a' and Current_mode == 'circle' thenlocal drawing = current_drawing()
Current_drawing_mode = 'circle'elseif love.mouse.isDown('1') and chord == 'a' and Current_drawing_mode == 'circle' thenlocal drawing = Drawing.current_drawing()
local mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)local j = insert_point(drawing.points, mx,my)
local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)local j = Drawing.insert_point(drawing.points, mx,my)
endendendendfunction current_drawing()local x, y = love.mouse.getX(), love.mouse.getY()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenif y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenreturn drawingendendendreturn nilendfunction select_shape_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)for i,shape in ipairs(drawing.shapes) doassert(shape)if on_shape(mx,my, drawing, shape) thenreturn drawing,i,shapeendendendendendendfunction select_point_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = coord(love.mouse.getX()-16), coord(love.mouse.getY()-drawing.y)for i,point in ipairs(drawing.points) doassert(point)if near(point, mx,my) thenreturn drawing,i,pointendendendendendendfunction select_drawing_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenreturn drawing
endendendfunction contains_point(shape, p)if shape.mode == 'freehand' then-- not supportedelseif shape.mode == 'line' or shape.mode == 'manhattan' thenreturn shape.p1 == p or shape.p2 == pelseif shape.mode == 'polygon' thenreturn table.find(shape.vertices, p)elseif shape.mode == 'circle' thenreturn shape.center == pelseif shape.mode == 'arc' thenreturn shape.center == p-- ugh, how to support angleselseif shape.mode == 'deleted' then-- already doneelseprint(shape.mode)assert(false)endendfunction convert_line(drawing, shape)-- Perhaps we should do a more sophisticated "simple linear regression"-- here:-- https://en.wikipedia.org/wiki/Linear_regression#Simple_and_multiple_linear_regression-- But this works well enough for close-to-linear strokes.assert(shape.mode == 'freehand')shape.mode = 'line'shape.p1 = insert_point(drawing.points, shape.points[1].x, shape.points[1].y)local n = #shape.pointsshape.p2 = insert_point(drawing.points, shape.points[n].x, shape.points[n].y)end-- turn a line either horizontal or verticalfunction convert_horvert(drawing, shape)if shape.mode == 'freehand' thenconvert_line(shape)endassert(shape.mode == 'line')local p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]if math.abs(p1.x-p2.x) > math.abs(p1.y-p2.y) thenp2.y = p1.yelsep2.x = p1.xendendfunction smoothen(shape)assert(shape.mode == 'freehand')for _=1,7 dofor i=2,#shape.points-1 dolocal a = shape.points[i-1]local b = shape.points[i]local c = shape.points[i+1]b.x = (a.x + b.x + c.x)/3b.y = (a.y + b.y + c.y)/3
endendendfunction angle_with_hint(x1, y1, x2, y2, hint)local result = math.angle(x1,y1, x2,y2)if hint then-- Smooth the discontinuity where angle goes from positive to negative.-- The hint is a memory of which way we drew it last time.while result > hint+math.pi/10 doresult = result-math.pi*2endwhile result < hint-math.pi/10 doresult = result+math.pi*2endendreturn resultend-- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3-- (LÖVE is Lua 5.1)function math.angle(x1,y1, x2,y2)local result = math.atan((y2-y1)/(x2-x1))if x2 < x1 thenresult = result+math.piendreturn resultendfunction math.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 endfunction load_from_disk(filename)local infile = io.open(filename)local result = load_from_file(infile)if infile then infile:close() endreturn resultendfunction load_from_file(infile)local result = {}if infile thenlocal infile_next_line = infile:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)while true dolocal line = infile_next_line()if line == nil then break endif line == '```lines' then -- inflexible with whitespace since these files are always autogeneratedtable.insert(result, load_drawing(infile_next_line))elsetable.insert(result, {mode='text', data=line})endendendif #result == 0 thentable.insert(result, {mode='text', data=''})endreturn resultendfunction save_to_disk(lines, filename)local outfile = io.open(filename, 'w')for _,line in ipairs(lines) doif line.mode == 'drawing' thenstore_drawing(outfile, line)elseoutfile:write(line.data..'\n')
json = require 'json'function load_drawing(infile_next_line)local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}while true dolocal line = infile_next_line()assert(line)if line == '```' then break endlocal shape = json.decode(line)if shape.mode == 'line' or shape.mode == 'manhattan' thenshape.p1 = insert_point(drawing.points, shape.p1.x, shape.p1.y)shape.p2 = insert_point(drawing.points, shape.p2.x, shape.p2.y)elseif shape.mode == 'polygon' thenfor i,p in ipairs(shape.vertices) doshape.vertices[i] = insert_point(drawing.points, p.x,p.y)endelseif shape.mode == 'circle' or shape.mode == 'arc' thenshape.center = insert_point(drawing.points, shape.center.x,shape.center.y)endtable.insert(drawing.shapes, shape)endreturn drawingendfunction store_drawing(outfile, drawing)outfile:write('```lines\n')for _,shape in ipairs(drawing.shapes) doif shape.mode == 'freehand' thenoutfile:write(json.encode(shape)..'\n')elseif shape.mode == 'line' or shape.mode == 'manhattan' thenlocal line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})outfile:write(line..'\n')elseif shape.mode == 'polygon' thenlocal obj = {mode=shape.mode, vertices={}}for _,p in ipairs(shape.vertices) dotable.insert(obj.vertices, drawing.points[p])endlocal line = json.encode(obj)outfile:write(line..'\n')elseif shape.mode == 'circle' thenoutfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius})..'\n')elseif shape.mode == 'arc' thenoutfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle})..'\n')endendoutfile:write('```\n')endicon = {}function icon.insert_drawing(x, y)love.graphics.setColor(0.7,0.7,0.7)love.graphics.rectangle('line', x,y, 12,12)love.graphics.line(4,y+6, 16,y+6)love.graphics.line(10,y, 10,y+12)love.graphics.setColor(0, 0, 0)endfunction icon.freehand(x, y)love.graphics.line(x+4,y+7,x+5,y+5)love.graphics.line(x+5,y+5,x+7,y+4)love.graphics.line(x+7,y+4,x+9,y+3)love.graphics.line(x+9,y+3,x+10,y+5)love.graphics.line(x+10,y+5,x+12,y+6)love.graphics.line(x+12,y+6,x+13,y+8)love.graphics.line(x+13,y+8,x+13,y+10)love.graphics.line(x+13,y+10,x+14,y+12)love.graphics.line(x+14,y+12,x+15,y+14)love.graphics.line(x+15,y+14,x+15,y+16)endfunction icon.line(x, y)love.graphics.line(x+4,y+2, x+16,y+18)endfunction icon.manhattan(x, y)love.graphics.line(x+4,y+20, x+4,y+2)love.graphics.line(x+4,y+2, x+10,y+2)love.graphics.line(x+10,y+2, x+10,y+10)love.graphics.line(x+10,y+10, x+18,y+10)endfunction icon.polygon(x, y)love.graphics.line(x+8,y+2, x+14,y+2)love.graphics.line(x+14,y+2, x+18,y+10)love.graphics.line(x+18,y+10, x+10,y+18)love.graphics.line(x+10,y+18, x+4,y+12)love.graphics.line(x+4,y+12, x+8,y+2)endfunction icon.circle(x, y)love.graphics.circle('line', x+10,y+10, 8)endfunction draw_help_without_mouse_pressed(drawing)love.graphics.setColor(0,0.5,0)local y = drawing.y+10love.graphics.print("Things you can do:", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press the mouse button to start drawing a "..current_shape(), 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Hover on a point and press 'ctrl+v' to start moving it,", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("then press the mouse button to finish", 16+30+bullet_indent(),y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Hover on a point or shape and press 'ctrl+d' to delete it", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomif Current_mode ~= 'freehand' thenlove.graphics.print("* Press 'ctrl+f' to switch to drawing freehand strokes", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'line' thenlove.graphics.print("* Press 'ctrl+l' to switch to drawing lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'manhattan' thenlove.graphics.print("* Press 'ctrl+m' to switch to drawing horizontal/vertical lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'circle' thenlove.graphics.print("* Press 'ctrl+c' to switch to drawing circles/arcs", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'polygon' thenlove.graphics.print("* Press 'ctrl+g' to switch to drawing polygons", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.print("* Press 'ctrl+=' or 'ctrl+-' to Zoom in or out", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'ctrl+0' to reset Zoom", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomlove.graphics.print("Hit 'esc' now to hide this message", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.setColor(0,0.5,0, 0.1)love.graphics.rectangle('fill', 16,drawing.y, Drawing_width, math.max(pixels(drawing.h),y-drawing.y))endfunction draw_help_with_mouse_pressed(drawing)love.graphics.setColor(0,0.5,0)local y = drawing.y+10love.graphics.print("You're currently drawing a "..current_shape(drawing.pending), 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print('Things you can do now:', 16+30,y, 0, Zoom)y = y+15*Zoomif Current_mode == 'freehand' thenlove.graphics.print('* Release the mouse button to finish drawing the stroke', 16+30,y, 0, Zoom)y = y+15*Zoomelseif Current_mode == 'line' or Current_mode == 'manhattan' thenlove.graphics.print('* Release the mouse button to finish drawing the line', 16+30,y, 0, Zoom)y = y+15*Zoomelseif Current_mode == 'circle' thenif drawing.pending.mode == 'circle' thenlove.graphics.print('* Release the mouse button to finish drawing the circle', 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'a' to draw just an arc of a circle", 16+30,y, 0, Zoom)elselove.graphics.print('* Release the mouse button to finish drawing the arc', 16+30,y, 0, Zoom)endy = y+15*Zoomelseif Current_mode == 'polygon' thenlove.graphics.print('* Release the mouse button to finish drawing the polygon', 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'p' to add a vertex to the polygon", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.print("* Press 'esc' then release the mouse button to cancel the current shape", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomif Current_mode ~= 'line' thenlove.graphics.print("* Press 'l' to switch to drawing lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'manhattan' thenlove.graphics.print("* Press 'm' to switch to drawing horizontal/vertical lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'circle' thenlove.graphics.print("* Press 'c' to switch to drawing circles/arcs", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_mode ~= 'polygon' thenlove.graphics.print("* Press 'g' to switch to drawing polygons", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.setColor(0,0.5,0, 0.1)love.graphics.rectangle('fill', 16,drawing.y, Drawing_width, math.max(pixels(drawing.h),y-drawing.y))endfunction current_shape(shape)if Current_mode == 'freehand' thenreturn 'freehand stroke'elseif Current_mode == 'line' thenreturn 'straight line'elseif Current_mode == 'manhattan' thenreturn 'horizontal/vertical line'elseif Current_mode == 'circle' and shape and shape.start_angle thenreturn 'arc'elsereturn Current_modeendend_bullet_indent = nilfunction bullet_indent()if _bullet_indent == nil thenlocal text = love.graphics.newText(love.graphics.getFont(), '* ')_bullet_indent = text:getWidth()endreturn _bullet_indentend
icon = {}function icon.insert_drawing(x, y)love.graphics.setColor(0.7,0.7,0.7)love.graphics.rectangle('line', x,y, 12,12)love.graphics.line(4,y+6, 16,y+6)love.graphics.line(10,y, 10,y+12)love.graphics.setColor(0, 0, 0)endfunction icon.freehand(x, y)love.graphics.line(x+4,y+7,x+5,y+5)love.graphics.line(x+5,y+5,x+7,y+4)love.graphics.line(x+7,y+4,x+9,y+3)love.graphics.line(x+9,y+3,x+10,y+5)love.graphics.line(x+10,y+5,x+12,y+6)love.graphics.line(x+12,y+6,x+13,y+8)love.graphics.line(x+13,y+8,x+13,y+10)love.graphics.line(x+13,y+10,x+14,y+12)love.graphics.line(x+14,y+12,x+15,y+14)love.graphics.line(x+15,y+14,x+15,y+16)endfunction icon.line(x, y)love.graphics.line(x+4,y+2, x+16,y+18)endfunction icon.manhattan(x, y)love.graphics.line(x+4,y+20, x+4,y+2)love.graphics.line(x+4,y+2, x+10,y+2)love.graphics.line(x+10,y+2, x+10,y+10)love.graphics.line(x+10,y+10, x+18,y+10)endfunction icon.polygon(x, y)love.graphics.line(x+8,y+2, x+14,y+2)love.graphics.line(x+14,y+2, x+18,y+10)love.graphics.line(x+18,y+10, x+10,y+18)love.graphics.line(x+10,y+18, x+4,y+12)love.graphics.line(x+4,y+12, x+8,y+2)endfunction icon.circle(x, y)love.graphics.circle('line', x+10,y+10, 8)end
function draw_help_without_mouse_pressed(drawing)love.graphics.setColor(0,0.5,0)local y = drawing.y+10love.graphics.print("Things you can do:", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press the mouse button to start drawing a "..current_shape(), 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Hover on a point and press 'ctrl+v' to start moving it,", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("then press the mouse button to finish", 16+30+bullet_indent(),y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Hover on a point or shape and press 'ctrl+d' to delete it", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomif Current_drawing_mode ~= 'freehand' thenlove.graphics.print("* Press 'ctrl+f' to switch to drawing freehand strokes", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'line' thenlove.graphics.print("* Press 'ctrl+l' to switch to drawing lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'manhattan' thenlove.graphics.print("* Press 'ctrl+m' to switch to drawing horizontal/vertical lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'circle' thenlove.graphics.print("* Press 'ctrl+c' to switch to drawing circles/arcs", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'polygon' thenlove.graphics.print("* Press 'ctrl+g' to switch to drawing polygons", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.print("* Press 'ctrl+=' or 'ctrl+-' to Zoom in or out", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'ctrl+0' to reset Zoom", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomlove.graphics.print("Hit 'esc' now to hide this message", 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.setColor(0,0.5,0, 0.1)love.graphics.rectangle('fill', 16,drawing.y, Drawing_width, math.max(Drawing.pixels(drawing.h),y-drawing.y))endfunction draw_help_with_mouse_pressed(drawing)love.graphics.setColor(0,0.5,0)local y = drawing.y+10love.graphics.print("You're currently drawing a "..current_shape(drawing.pending), 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print('Things you can do now:', 16+30,y, 0, Zoom)y = y+15*Zoomif Current_drawing_mode == 'freehand' thenlove.graphics.print('* Release the mouse button to finish drawing the stroke', 16+30,y, 0, Zoom)y = y+15*Zoomelseif Current_drawing_mode == 'line' or Current_drawing_mode == 'manhattan' thenlove.graphics.print('* Release the mouse button to finish drawing the line', 16+30,y, 0, Zoom)y = y+15*Zoomelseif Current_drawing_mode == 'circle' thenif drawing.pending.mode == 'circle' thenlove.graphics.print('* Release the mouse button to finish drawing the circle', 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'a' to draw just an arc of a circle", 16+30,y, 0, Zoom)elselove.graphics.print('* Release the mouse button to finish drawing the arc', 16+30,y, 0, Zoom)endy = y+15*Zoomelseif Current_drawing_mode == 'polygon' thenlove.graphics.print('* Release the mouse button to finish drawing the polygon', 16+30,y, 0, Zoom)y = y+15*Zoomlove.graphics.print("* Press 'p' to add a vertex to the polygon", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.print("* Press 'esc' then release the mouse button to cancel the current shape", 16+30,y, 0, Zoom)y = y+15*Zoomy = y+15*Zoomif Current_drawing_mode ~= 'line' thenlove.graphics.print("* Press 'l' to switch to drawing lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'manhattan' thenlove.graphics.print("* Press 'm' to switch to drawing horizontal/vertical lines", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'circle' thenlove.graphics.print("* Press 'c' to switch to drawing circles/arcs", 16+30,y, 0, Zoom)y = y+15*Zoomendif Current_drawing_mode ~= 'polygon' thenlove.graphics.print("* Press 'g' to switch to drawing polygons", 16+30,y, 0, Zoom)y = y+15*Zoomendlove.graphics.setColor(0,0.5,0, 0.1)love.graphics.rectangle('fill', 16,drawing.y, Drawing_width, math.max(Drawing.pixels(drawing.h),y-drawing.y))endfunction current_shape(shape)if Current_drawing_mode == 'freehand' thenreturn 'freehand stroke'elseif Current_drawing_mode == 'line' thenreturn 'straight line'elseif Current_drawing_mode == 'manhattan' thenreturn 'horizontal/vertical line'elseif Current_drawing_mode == 'circle' and shape and shape.start_angle thenreturn 'arc'elsereturn Current_drawing_modeendend_bullet_indent = nilfunction bullet_indent()if _bullet_indent == nil thenlocal text = love.graphics.newText(love.graphics.getFont(), '* ')_bullet_indent = text:getWidth()endreturn _bullet_indentend
local geom = {}function geom.on_shape(x,y, drawing, shape)if shape.mode == 'freehand' thenreturn geom.on_freehand(x,y, drawing, shape)elseif shape.mode == 'line' thenreturn geom.on_line(x,y, drawing, shape)elseif shape.mode == 'manhattan' thenreturn x == drawing.points[shape.p1].x or y == drawing.points[shape.p1].yelseif shape.mode == 'polygon' thenreturn geom.on_polygon(x,y, drawing, shape)elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]return math.dist(center.x,center.y, x,y) == shape.radiuselseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]local dist = math.dist(center.x,center.y, x,y)if dist < shape.radius*0.95 or dist > shape.radius*1.05 thenreturn falseendreturn geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)elseif shape.mode == 'deleted' thenelseprint(shape.mode)assert(false)endendfunction geom.on_freehand(x,y, drawing, shape)local prevfor _,p in ipairs(shape.points) doif prev thenif geom.on_line(x,y, drawing, {p1=prev, p2=p}) thenreturn trueendendprev = pendreturn falseendfunction geom.on_line(x,y, drawing, shape)local p1,p2if type(shape.p1) == 'number' thenp1 = drawing.points[shape.p1]p2 = drawing.points[shape.p2]elsep1 = shape.p1p2 = shape.p2endif p1.x == p2.x thenif math.abs(p1.x-x) > 5 thenreturn falseendlocal y1,y2 = p1.y,p2.yif y1 > y2 theny1,y2 = y2,y1endreturn y >= y1 and y <= y2end-- has the right slope and interceptlocal m = (p2.y - p1.y) / (p2.x - p1.x)local yp = p1.y + m*(x-p1.x)if yp < 0.95*y or yp > 1.05*y thenreturn falseend-- between endpointslocal k = (x-p1.x) / (p2.x-p1.x)return k > -0.05 and k < 1.05endfunction geom.on_polygon(x,y, drawing, shape)local prevfor _,p in ipairs(shape.vertices) doif prev thenif geom.on_line(x,y, drawing, {p1=prev, p2=p}) thenreturn trueendendprev = pendreturn geom.on_line(x,y, drawing, {p1=shape.vertices[1], p2=shape.vertices[#shape.vertices]})endfunction geom.angle_with_hint(x1, y1, x2, y2, hint)local result = geom.angle(x1,y1, x2,y2)if hint then-- Smooth the discontinuity where angle goes from positive to negative.-- The hint is a memory of which way we drew it last time.while result > hint+math.pi/10 doresult = result-math.pi*2endwhile result < hint-math.pi/10 doresult = result+math.pi*2endendreturn resultend-- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3-- (LÖVE is Lua 5.1)function geom.angle(x1,y1, x2,y2)local result = math.atan((y2-y1)/(x2-x1))if x2 < x1 thenresult = result+math.piendreturn resultend-- is the line between x,y and cx,cy at an angle between s and e?function geom.angle_between(ox,oy, x,y, s,e)local angle = math.angle(ox,oy, x,y)if s > e thens,e = e,send-- I'm not sure this is right or ideal..angle = angle-math.pi*2if s <= angle and angle <= e thenreturn trueendangle = angle+math.pi*2if s <= angle and angle <= e thenreturn trueendangle = angle+math.pi*2return s <= angle and angle <= eendfunction geom.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 endreturn geom
-- primitives for saving to file and loading from fileDrawing = require 'drawing'function load_from_disk(filename)local infile = io.open(filename)local result = load_from_file(infile)if infile then infile:close() endreturn resultendfunction load_from_file(infile)local result = {}if infile thenlocal infile_next_line = infile:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)while true dolocal line = infile_next_line()if line == nil then break endif line == '```lines' then -- inflexible with whitespace since these files are always autogeneratedtable.insert(result, load_drawing(infile_next_line))elsetable.insert(result, {mode='text', data=line})endendendif #result == 0 thentable.insert(result, {mode='text', data=''})endreturn resultendfunction save_to_disk(lines, filename)local outfile = io.open(filename, 'w')for _,line in ipairs(lines) doif line.mode == 'drawing' thenstore_drawing(outfile, line)elseoutfile:write(line.data..'\n')endendoutfile:close()endjson = require 'json'function load_drawing(infile_next_line)local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}while true dolocal line = infile_next_line()assert(line)if line == '```' then break endlocal shape = json.decode(line)if shape.mode == 'line' or shape.mode == 'manhattan' thenshape.p1 = Drawing.insert_point(drawing.points, shape.p1.x, shape.p1.y)shape.p2 = Drawing.insert_point(drawing.points, shape.p2.x, shape.p2.y)elseif shape.mode == 'polygon' thenfor i,p in ipairs(shape.vertices) doshape.vertices[i] = Drawing.insert_point(drawing.points, p.x,p.y)endelseif shape.mode == 'circle' or shape.mode == 'arc' thenshape.center = Drawing.insert_point(drawing.points, shape.center.x,shape.center.y)endtable.insert(drawing.shapes, shape)endreturn drawingendfunction store_drawing(outfile, drawing)outfile:write('```lines\n')for _,shape in ipairs(drawing.shapes) doif shape.mode == 'freehand' thenoutfile:write(json.encode(shape)..'\n')elseif shape.mode == 'line' or shape.mode == 'manhattan' thenlocal line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})outfile:write(line..'\n')elseif shape.mode == 'polygon' thenlocal obj = {mode=shape.mode, vertices={}}for _,p in ipairs(shape.vertices) dotable.insert(obj.vertices, drawing.points[p])endlocal line = json.encode(obj)outfile:write(line..'\n')elseif shape.mode == 'circle' thenoutfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius})..'\n')elseif shape.mode == 'arc' thenoutfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle})..'\n')endendoutfile:write('```\n')end
love.graphics.rectangle('line', 16,line.y, Drawing_width,pixels(line.h))if icon[Current_mode] thenicon[Current_mode](16+Drawing_width-20, line.y+4)
love.graphics.rectangle('line', 16,line.y, Drawing_width,Drawing.pixels(line.h))if icon[Current_drawing_mode] thenicon[Current_drawing_mode](16+Drawing_width-20, line.y+4)
draw_pending_shape(16,line.y, line)
Drawing.draw_pending_shape(16,line.y, line)endfunction Drawing.current_drawing()local x, y = love.mouse.getX(), love.mouse.getY()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenif y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenreturn drawingendendendreturn nilendfunction Drawing.select_shape_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)for i,shape in ipairs(drawing.shapes) doassert(shape)if geom.on_shape(mx,my, drawing, shape) thenreturn drawing,i,shapeendendendendendendfunction Drawing.select_point_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenlocal mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)for i,point in ipairs(drawing.points) doassert(point)if Drawing.near(point, mx,my) thenreturn drawing,i,pointendendendendendendfunction Drawing.select_drawing_at_mouse()for _,drawing in ipairs(Lines) doif drawing.mode == 'drawing' thenlocal x, y = love.mouse.getX(), love.mouse.getY()if y >= drawing.y and y < drawing.y + Drawing.pixels(drawing.h) and x >= 16 and x < 16+Drawing_width thenreturn drawingendendendendfunction Drawing.contains_point(shape, p)if shape.mode == 'freehand' then-- not supportedelseif shape.mode == 'line' or shape.mode == 'manhattan' thenreturn shape.p1 == p or shape.p2 == pelseif shape.mode == 'polygon' thenreturn table.find(shape.vertices, p)elseif shape.mode == 'circle' thenreturn shape.center == pelseif shape.mode == 'arc' thenreturn shape.center == p-- ugh, how to support angleselseif shape.mode == 'deleted' then-- already doneelseprint(shape.mode)assert(false)endendfunction Drawing.convert_line(drawing, shape)-- Perhaps we should do a more sophisticated "simple linear regression"-- here:-- https://en.wikipedia.org/wiki/Linear_regression#Simple_and_multiple_linear_regression-- But this works well enough for close-to-linear strokes.assert(shape.mode == 'freehand')shape.mode = 'line'shape.p1 = insert_point(drawing.points, shape.points[1].x, shape.points[1].y)local n = #shape.pointsshape.p2 = insert_point(drawing.points, shape.points[n].x, shape.points[n].y)end-- turn a line either horizontal or verticalfunction Drawing.convert_horvert(drawing, shape)if shape.mode == 'freehand' thenconvert_line(shape)endassert(shape.mode == 'line')local p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]if math.abs(p1.x-p2.x) > math.abs(p1.y-p2.y) thenp2.y = p1.yelsep2.x = p1.xendendfunction Drawing.smoothen(shape)assert(shape.mode == 'freehand')for _=1,7 dofor i=2,#shape.points-1 dolocal a = shape.points[i-1]local b = shape.points[i]local c = shape.points[i+1]b.x = (a.x + b.x + c.x)/3b.y = (a.y + b.y + c.y)/3endendendfunction Drawing.insert_point(points, x,y)for i,point in ipairs(points) doif Drawing.near(point, x,y) thenreturn iendendtable.insert(points, {x=x, y=y})return #pointsendfunction Drawing.near(point, x,y)local px,py = Drawing.pixels(x),Drawing.pixels(y)local cx,cy = Drawing.pixels(point.x), Drawing.pixels(point.y)return (cx-px)*(cx-px) + (cy-py)*(cy-py) < 16endfunction Drawing.draw_shape(left,top, drawing, shape)if shape.mode == 'freehand' thenlocal prev = nilfor _,point in ipairs(shape.points) doif prev thenlove.graphics.line(Drawing.pixels(prev.x)+left,Drawing.pixels(prev.y)+top, Drawing.pixels(point.x)+left,Drawing.pixels(point.y)+top)endprev = pointendelseif shape.mode == 'line' or shape.mode == 'manhattan' thenlocal p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]love.graphics.line(Drawing.pixels(p1.x)+left,Drawing.pixels(p1.y)+top, Drawing.pixels(p2.x)+left,Drawing.pixels(p2.y)+top)elseif shape.mode == 'polygon' thenlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(Drawing.pixels(prev.x)+left,Drawing.pixels(prev.y)+top, Drawing.pixels(curr.x)+left,Drawing.pixels(curr.y)+top)endprev = currend-- close the looplocal curr = drawing.points[shape.vertices[1]]love.graphics.line(Drawing.pixels(prev.x)+left,Drawing.pixels(prev.y)+top, Drawing.pixels(curr.x)+left,Drawing.pixels(curr.y)+top)elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]love.graphics.circle('line', Drawing.pixels(center.x)+left,Drawing.pixels(center.y)+top, Drawing.pixels(shape.radius))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]love.graphics.arc('line', 'open', Drawing.pixels(center.x)+left,Drawing.pixels(center.y)+top, Drawing.pixels(shape.radius), shape.start_angle, shape.end_angle, 360)elseif shape.mode == 'deleted' thenelseprint(shape.mode)assert(false)endendfunction Drawing.draw_pending_shape(left,top, drawing)local shape = drawing.pendingif shape.mode == 'freehand' thendraw_shape(left,top, drawing, shape)elseif shape.mode == 'line' thenlocal p1 = drawing.points[shape.p1]local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlove.graphics.line(Drawing.pixels(p1.x)+left,Drawing.pixels(p1.y)+top, Drawing.pixels(mx)+left,Drawing.pixels(my)+top)elseif shape.mode == 'manhattan' thenlocal p1 = drawing.points[shape.p1]local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendif math.abs(mx-p1.x) > math.abs(my-p1.y) thenlove.graphics.line(Drawing.pixels(p1.x)+left,Drawing.pixels(p1.y)+top, Drawing.pixels(mx)+left,Drawing.pixels(p1.y)+top)elselove.graphics.line(Drawing.pixels(p1.x)+left,Drawing.pixels(p1.y)+top, Drawing.pixels(p1.x)+left,Drawing.pixels(my)+top)endelseif shape.mode == 'polygon' then-- don't close the loop on a pending polygonlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(Drawing.pixels(prev.x)+left,Drawing.pixels(prev.y)+top, Drawing.pixels(curr.x)+left,Drawing.pixels(curr.y)+top)endprev = currendlove.graphics.line(Drawing.pixels(prev.x)+left,Drawing.pixels(prev.y)+top, love.mouse.getX(),love.mouse.getY())elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlocal cx,cy = Drawing.pixels(center.x)+left, Drawing.pixels(center.y)+toplove.graphics.circle('line', cx,cy, math.dist(cx,cy, love.mouse.getX(),love.mouse.getY()))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]local mx,my = Drawing.coord(love.mouse.getX()-16), Drawing.coord(love.mouse.getY()-drawing.y)if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendshape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)local cx,cy = Drawing.pixels(center.x)+left, Drawing.pixels(center.y)+toplove.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius), shape.start_angle, shape.end_angle, 360)endendfunction Drawing.pixels(n) -- parts to pixelsreturn n*Drawing_width/256endfunction Drawing.coord(n) -- pixels to partsreturn math.floor(n*256/Drawing_width)