--demo for hit testing, length, split and bbox for lines, arcs, beizer2, bezier3. local player = require'cplayer' local glue = require'glue' local distance2 = require'path2d_point'.distance2 local line = require'path2d_line' local arc = require'path2d_arc' local bezier2 = require'path2d_bezier2' local bezier3 = require'path2d_bezier3' local cairo = require'cairo' local i = 0 function player:on_render(cr) i=i+1 cr:identity_matrix() cr:rgb(0,0,0) cr:paint() cr:line_width(1) cr:fill_rule'even_odd' cr:font_face('Fixedsys') cr:font_size(12) local function hex_color(s) local r,g,b,a = tonumber(s:sub(2,3), 16), tonumber(s:sub(4,5), 16), tonumber(s:sub(6,7), 16), tonumber(s:sub(8,9), 16) or 255 return r/255, g/255, b/255, a/255 end local function stroke(color, width) cr:rgba(hex_color(color or '#ffffff')) cr:line_width(width or 1) cr:stroke() end local function fill(color) cr:rgba(hex_color(color or '#ffffff')) cr:fill() end local x0 = self.mousex or self.window.client_w/2 local y0 = self.mousey or self.window.client_h/2 local dists = {} local lens = {} local function draw_line(x1,y1,x2,y2,color,width) cr:move_to(x1,y1) cr:line_to(x2,y2) stroke(color,width) end local function line_hit(x1,y1,x2,y2) x2,y2=x1+x2,y1+y2 cr:rectangle(line.bounding_box(x1,y1,x2,y2)); stroke('#666666') draw_line(x1,y1,x2,y2) local d,x,y,t = line.hit(x0,y0,x1,y1,x2,y2) glue.append(dists,d,x,y,t,line.point(t,x1,y1,x2,y2)) local ax1,ay1,ax2,ay2, bx1,by1,bx2,by2 = line.split(t, x1,y1,x2,y2) draw_line(ax1,ay1,ax2,ay2,'#ff000060',10) draw_line(bx1,by1,bx2,by2,'#0000ff60',10) lens[#dists] = line.length(t,x1,y1,x2,y2) end local function observed_sweep(sweep_angle) return math.max(math.min(sweep_angle, 360), -360) end local function draw_arc(cx,cy,r,a1,a2,color,width) a1,a2 = math.rad(a1), math.rad(observed_sweep(a2)) if a2 < 0 then cr:arc_negative(cx,cy,r,a1,a1+a2) else cr:arc(cx,cy,r,a1,a1+a2) end stroke(color,width) end local function arc_hit(cx,cy,r,a1,a2) local x1,y1,x2,y2 = arc.endpoints(cx,cy,r,r,a1,a2) cr:rectangle(arc.bounding_box(cx,cy,r,r,a1,a2)); stroke('#666666') draw_arc(cx,cy,r,a1,a2) local d,x,y,t = arc.hit(x0,y0,cx,cy,r,r,a1,a2) glue.append(dists,d,x,y,t,arc.point(t,cx,cy,r,r,a1,a2)) local acx,acy,ar,ar,aa1,aa2,_, bcx,bcy,br,br,ba1,ba2 = arc.split(t,cx,cy,r,r,a1,a2) draw_arc(acx,acy,ar,aa1,aa2,'#ff000060',10) draw_arc(bcx,bcy,br,ba1,ba2,'#0000ff60',10) lens[#dists] = arc.length(t, cx,cy,r,r,a1,a2) end local function draw_bezier2(x1,y1,x2,y2,x3,y3,color,width) cr:move_to(x1,y1) cr:curve_to(select(3, bezier2.to_bezier3(x1,y1,x2,y2,x3,y3))) stroke(color,width) end local function bezier2_hit(x1,y1,x2,y2,x3,y3) x2,y2,x3,y3=x1+x2,y1+y2,x1+x3,y1+y3 cr:rectangle(bezier2.bounding_box(x1,y1,x2,y2,x3,y3)); stroke('#666666') draw_bezier2(x1,y1,x2,y2,x3,y3) local d,x,y,t = bezier2.hit(x0,y0,x1,y1,x2,y2,x3,y3) glue.append(dists,d,x,y,t,bezier2.point(t,x1,y1,x2,y2,x3,y3)) local ax1,ay1,ax2,ay2,ax3,ay3, bx1,by1,bx2,by2,bx3,by3 = bezier2.split(t, x1,y1,x2,y2,x3,y3) draw_bezier2(ax1,ay1,ax2,ay2,ax3,ay3,'#ff000060',10) draw_bezier2(bx1,by1,bx2,by2,bx3,by3,'#0000ff60',10) lens[#dists] = bezier2.length(t,x1,y1,x2,y2,x3,y3) end local function draw_bezier3(x1,y1,x2,y2,x3,y3,x4,y4,color,width) cr:move_to(x1,y1) cr:curve_to(x2,y2,x3,y3,x4,y4) stroke(color,width) end local function bezier3_hit(x1,y1,x2,y2,x3,y3,x4,y4) x2,y2,x3,y3,x4,y4=x1+x2,y1+y2,x1+x3,y1+y3,x1+x4,y1+y4 cr:rectangle(bezier3.bounding_box(x1,y1,x2,y2,x3,y3,x4,y4)); stroke('#666666') draw_bezier3(x1,y1,x2,y2,x3,y3,x4,y4) local d,x,y,t = bezier3.hit(x0,y0,x1,y1,x2,y2,x3,y3,x4,y4) glue.append(dists,d,x,y,t,bezier3.point(t,x1,y1,x2,y2,x3,y3,x4,y4)) local ax1,ay1,ax2,ay2,ax3,ay3,ax4,ay4, bx1,by1,bx2,by2,bx3,by3,bx4,by4 = bezier3.split(t, x1,y1,x2,y2,x3,y3,x4,y4) draw_bezier3(ax1,ay1,ax2,ay2,ax3,ay3,ax4,ay4,'#ff000060',10) draw_bezier3(bx1,by1,bx2,by2,bx3,by3,bx4,by4,'#0000ff60',10) lens[#dists] = bezier3.length(t,x1,y1,x2,y2,x3,y3,x4,y4) end line_hit(100, 100, 50, 100) line_hit(200, 200, -50, -100) line_hit(350, 100, -100, 0) line_hit(250, 150, 100, 0) line_hit(400, 200, 0, -100) line_hit(450, 100, 0, 100) line_hit(600, 100, -100, 50) line_hit(500, 200, 100, -50) arc_hit(100, 300, 50, 0, 90) arc_hit(300, 300, 50, -270, 180+45) arc_hit(500, 300, 50, 270, -270) arc_hit(700, 300, 50, 0, 360 + 90) arc_hit(900, 300, 50, 0, -360) bezier2_hit(100, 400, 50, 100, 1000, 0) --wide assymetric bezier2_hit(100, 500, 2000, 0, 0, 5) --very close sides bezier2_hit(100, 550, 0, 0, 1000, 0) --handles on endpoints bezier3_hit(100, 600, -500, 10, 1500, 10, 1000, 0) --very close sides bezier3_hit(100, 700, 500, -100, 500, -100, 1000, 0) --elevated quad curve bezier3_hit(700, 100, 400, 100, -200, 100, 200, 0) --loop bezier3_hit(1000, 100, 200, 100, 0, 100, 200, 0) --cusp bezier3_hit(1100, 200, -300, 100, 300, 100, 0, 0) --bowl bezier3_hit(50, 800, 0, -1000, 2000000, -1000, 2000000, 0) --huge (test precision) bezier3_hit(1300, 100, 10000, 0, 10000, 2000000, 0, 2000000) --huge (test precision) local mind = 1/0 local x1,y1,t1,len for i=1,#dists,6 do local d,x,y,t,x2,y2 = unpack(dists,i,i+5) cr:circle(x2,y2,7); stroke('#3333ff') cr:circle(x,y,9); stroke('#ff0000') if d < mind then mind = d x1,y1,t1,len=x,y,t,lens[i+5] end end if x1 then cr:move_to(x0,y0); cr:line_to(x1,y1); stroke('#ff0000') --cr:circle(x1,y1,5); fill('#00ff00') cr:move_to(x0+20,y0+24); cr:text_path(string.format('t: %.2f', t1)); fill('#ffffff') cr:move_to(x0+20,y0+38); cr:text_path(string.format('length: %.2f', len)); fill('#ffffff') end end player:play()