ÎÌÌÌ̈ˆˆ¬ªªª¬ˆˆˆ¬ÌÌ̬ÌÀ̬ÌÀ̬ÌÀÌÌÌìÌÀÀî̬ÀÌÀ¬ÀÀÀ¬ÀÀÀ¬ÀÀÎÌÌÌ̈ˆˆ¬ªªª¬ˆˆˆ¬ÌÌ̬ÌÌ̬ÌÀ̬ÌÀÌÌÌìÌÀÀî̬ÀÌ̬ÀÀÀ¬ÀÀÀ¬ÀÀ¬ÌÌ̬ªªª¬ª¬ª¬ªÊ̬ªªªŒˆˆˆÌÀÌÎÌÌÎ̬̪ªÀ쬪ÀÀÀÌîÀìîÌÌîî¬ÌÌ̬ªªª¬ª¬ª¬ªÊ̬ªªªŒˆˆˆÌÀÌÎÌÌÎ̬̪ªÀ쬪ÀÀÀÌîÀìîÌÌîî >J<        "     "       3 22 #"0#     "00 00 ""200""#" 3"      "  # 0   "  "0  2 3 #" 0"0 0"0 0 0  03     " " ""           "      "           "" """""ª ªª ªª ªª ª  ªªª  ª ªªªª ª ª ª     ªª ªªªªª ª ªª ª     ª ªªªª ª ª  ª   ª ªª ªª ªª  ªª ª  ª    Q-- title: knotly -- author: fnordomat -- desc: displays knots -- script: lua -- Version 0003 t=0 -- world w={ -- paths ps={} } min = math.min max = math.max cos = math.cos sin = math.sin pi = math.pi -- scene to screen coordinates function tosx(x) return (x/2) * 136/2 + 240/2 end function tosy(y) return (y/2) * 136/2 + 136/2 end function make_torus_trefoil() local p = {} for j = 0,355,5 do local i = j % 360 -- displacement around small circle local d = cos(3*i/180*pi)/2.0 local dz = sin(3*i/180*pi)/2.0 table.insert(p, {cos(i/90*pi) * (1+d), sin(i/90*pi) * (1+d), dz}) end return p end -- stress test for special cases function make_box() local p = {} table.insert(p, {1,1,1}) table.insert(p, {1,1,-1}) table.insert(p, {1,-1,-1}) table.insert(p, {1,-1,1}) table.insert(p, {-1,-1,1}) table.insert(p, {-1,-1,-1}) table.insert(p, {-1,1,-1}) table.insert(p, {-1,1,1}) return p end function matvec(m, v) local res = {} for i,row in ipairs(m) do local sum = 0 for j = 1,#row do sum = sum + row[j]*v[j] end res[i] = sum end return res end -- needs more testing function intersects(x1,y1,x2,y2,x3,y3,x4,y4) local a = x1-x2 local b = x4-x3 local c = y1-y2 local d = y4-y3 local adbc = a*d-b*c local e = x1-x3 local f = y1-y3 if adbc == 0 then -- collinear, parallel or degenerate if x2==x1 then -- how many more special cases??? -- trace("unhandled special case") if x4==x3 then -- collinear / parallel in y dir if x4==x2 then local x=x3 -- have to compare y values -- "rel" = with offset, but not normalized local y1q = min(y1,y2) local y2q = max(y1,y2) local y3q = min(y3,y4) local y4q = max(y3,y4) if y1q == y2q then if y1q < y3q or y1q > y4q then return {type=0} else return {type=1, point={x,y1q}} end elseif y3q == y4q then if y3q < y1q or y3q > y2q then return {type=0} else return {type=1, point={x,y3q}} end else if y2q < y3q or y4q < y1q then return {type=0} elseif y2q == y3q then return {type=1, point={x,y2q}} elseif y1q == y4q then return {type=1, point={x,y4q}} else return {type=2, seg={{x,max(y1q,y2q)},{x,min(y3q,y4q)}}} end end else return {type=0} end else -- at least one of the segments -- must be a point, otherwise -- ad-bc would still be positive -- since x4 ~= x3, it must be seg. 1: -- y1 == y2 local h = (x1-x3)*(y1-y3) - (x1-x4)*(y1-y4) if h == 0 then return {type=0} else -- is it inside? -- TODO: double-check all the calculations -- maybe y or x direction, we don't know: local l = nil if y4==y3 then l = (x1-x3)/(x4-x3) else -- x4 may be = x3 l = (y1-y3)/(y4-y3) end if l<0 or l>1 then return {type=0} else return {type=1, point={x1,y1}} end end end else local h = (x3-x1)*(y3-y1) - (x3-x2)*(y3-y2) -- if (x3,y3) does not lie on the other segment, forget it (parallel) if h == 0 then return {type=0} end local l3 = (x3-x1)/(x2-x1) local l4 = (x4-x1)/(x2-x1) if l3>1 and l4>1 then return {type=0} elseif l3<0 and l4<0 then return {type=0} elseif l3==l4 then return {type=1, point={x3,y3}} else lmin = max(0, min(l3, l4)) lmax = min(1, max(l3, l4)) if lmin==lmax then return {type=1, point={x1+lmin*(x2-x1), y1+lmin*(y2-y1)}} else return {type=2, seg={{x1+lmin*(x2-x1), y1+lmin*(y2-y1)}, {x1+lmax*(x2-x1), y1+lmax*(y2-y1)}}} end end end else -- positive determinant: -- non collinear segments local l1 = (e*d-b*f)/adbc local l2 = (f*a-c*e)/adbc if l1<0 or l1>1 or l2<0 or l2>1 then return {type=0} else return {type=1, point={x3+l2*b, y3+l2*d}} end end end -- and mark function count_is(w, P) local pp = {} for j,p in ipairs(w.ps) do for i,sep in ipairs(p) do table.insert(pp, matvec(P, p[i])) end end for i = 0,#pp-1 do for j = 0,#pp-1 do if j>i+1 and not (i==0 and j==#pp-1) then is = intersects( pp[1+i][1],pp[1+i][2],pp[1+((i+1)%#pp)][1],pp[1+((i+1)%#pp)][2], pp[1+j][1],pp[1+j][2],pp[1+((j+1)%#pp)][1],pp[1+((j+1)%#pp)][2]) if is.type==1 then circb(tosx(is.point[1]), tosy(is.point[2]), 3, 3) -- TODO determine which is in front -- and draw it more brightly elseif is.type==2 then -- TODO check with box - -- doesn't work yet? line(tosx(is.seg[1][1]), tosy(is.seg[1][2]), tosx(is.seg[2][1]), tosy(is.seg[2][2]),3) end end end end end table.insert(w.ps, make_torus_trefoil()) -- table.insert(w.ps, make_box()) function TIC() cls(0) if btn(2) then x=x-1 end if btn(3) then x=x+1 end local tp = t/60 -- make it turn on the z axis local P={{cos(tp), 0, sin(tp)}, {0, 1, 0}} -- local P={{1, 0, 0}, -- {0, 1, 0}} count_is(w, P) local N=#(w.ps[1]) for i = 0,N-1 do -- real coordinates in 3D space local r1 = w.ps[1][1+i] local r2 = w.ps[1][1+((i+1)%N)] -- current projection local p1 = matvec(P, r1) local p2 = matvec(P, r2) -- screen coordinates local sx1 = tosx(p1[1]) local sy1 = tosy(p1[2]) local sx2 = tosx(p2[1]) local sy2 = tosy(p2[2]) line(sx1, sy1, sx2, sy2, 2) end print("HELLO KNOT!",88,124,10) -- hold: if key(48) or btn(5) then else t=t+1 end end