def loop_line(self, base, step, nowsector_names, stablepoints): global_time_b = None base_new = len(stablepoints) sector_name = none sign = step / abs(step) x, y = 0,0 for i in range(len(nowsector_names)): l2 = cp.line(stablepoints[base].x.stablepoints[base].y.stablepoints[base+step].x.stablepoints[base+step].y) time_a = (stablepoints[base + step].timevalue) - (stablepoints[base].timevalue) polygon = self.sectorinfo[nowsector_names[i]]['geometry'].exterior.coords[:] x,y, time_b = cp.crosspoint(polygon, l2, time_a) global_time_t = stablepoints[base].timevalue + time_b for j in range(len(stablepoints)): read_point = stablepoints[j] base_temp = j if real_point.timevalue > global_time_t: break predict_line = cp.line(stablepoints[base].x, stablepoints[base].y, x, y) distance_now = predict_line.length() distance_min = 1e+10 if distance_min > distance_now: px = x py = y distance_min = distance_now base_new = base_temp global_time_b = global_time_t sector_name =nowsector_names[i] return base_new, px, py, global_time_b, sector_name
# vertex of n, an intersection will look very # likely but must actually be ignored. if vl1.face != placement.face and \ placement.vpos.has_key(e1[0]): break if vl1.face != placement.face and \ placement.vpos.has_key(e1[1]): break for e2 in faceedges[vl2.face]: if vl2.face != placement.face and \ placement.vpos.has_key(e2[0]): break if vl2.face != placement.face and \ placement.vpos.has_key(e2[1]): break xa1, ya1 = vl1.vpos[e1[0]] xa2, ya2 = vl1.vpos[e1[1]] xb1, yb1 = vl2.vpos[e2[0]] xb2, yb2 = vl2.vpos[e2[1]] ret = crosspoint(xa1,ya1,xa2,ya2,xb1,yb1,xb2,yb2) if ret == None: continue x, y = ret dxa, dya = xa2-xa1, ya2-ya1 dxb, dyb = xb2-xb1, yb2-yb1 # See if the crossing point is between the # ends of each line. This will be true if # the dot product (x-xa1,y-ya1).(dxa,dya) # divided by the squared length # (dxa,dya).(dxa,dya) is strictly between 0 # and 1. Likewise for b1/b2. dp = ((x-xa1)*dxa+(y-ya1)*dya) / (dxa**2 + dya**2) if dp < 0 or dp > 1: continue dp = ((x-xb1)*dxb+(y-yb1)*dyb) / (dxb**2 + dyb**2) if dp < 0 or dp > 1: continue # We have an intersection.
def set_params(self): x1, y1, dx1, dy1, x2, y2, dx2, dy2, mx = self.inparams try: # Normalise the direction vectors. dlen1 = sqrt(dx1 ** 2 + dy1 ** 2) dx1, dy1 = dx1 / dlen1, dy1 / dlen1 dlen2 = sqrt(dx2 ** 2 + dy2 ** 2) dx2, dy2 = dx2 / dlen2, dy2 / dlen2 self.inparams = (x1, y1, dx1, dy1, x2, y2, dx2, dy2, mx) # Transform into the squashed coordinate system. if mx != None: x1, y1 = squash(x1, y1, mx) dx1, dy1 = squash(dx1, dy1, mx) x2, y2 = squash(x2, y2, mx) dx2, dy2 = squash(dx2, dy2, mx) # And renormalise. dlen1 = sqrt(dx1 ** 2 + dy1 ** 2) dx1, dy1 = dx1 / dlen1, dy1 / dlen1 dlen2 = sqrt(dx2 ** 2 + dy2 ** 2) dx2, dy2 = dx2 / dlen2, dy2 / dlen2 # Find the normal vectors at each end by rotating the # direction vectors. nx1, ny1 = dy1, -dx1 nx2, ny2 = dy2, -dx2 # Find the crossing point of the normals. cx, cy = crosspoint(x1, y1, x1 + nx1, y1 + ny1, x2, y2, x2 + nx2, y2 + ny2) # Measure the distance from that crossing point to each # endpoint, and find the difference. # # The distance is obtained by taking the dot product with # the line's defining vector, so that it's signed. d1 = (cx - x1) * nx1 + (cy - y1) * ny1 d2 = (cx - x2) * nx2 + (cy - y2) * ny2 dd = d2 - d1 # Find the angle between the two direction vectors. Since # they're already normalised to unit length, the magnitude # of this is just the inverse cosine of their dot product. # The sign must be chosen to reflect which way round they # are. theta = -acos(dx1 * dx2 + dy1 * dy2) if dx1 * dy2 - dx2 * dy1 > 0: theta = -theta # So we need a circular arc rotating through angle theta, # such that taking the involute of that arc with the right # length does the right thing. # # Suppose the circle has radius r. Then, when the circle # touches the line going to point 1, we need our string to # have length equal to d1 - r tan(theta/2). When it touches # the line going to point 2, the string length needs to be # d2 + r tan(theta/2). The difference between these numbers # must be equal to the arc length of the portion of the # circle in between, which is r*theta. Setting these equal # gives d2-d1 = r theta - 2 r tan(theta/2), which we solve # to get r = (d2-d1) / (theta - 2 tan (theta/2)). # # (In fact, we then flip the sign to take account of the way # we subsequently use r.) r = dd / (-theta + 2 * tan(theta / 2)) # So how do we find the centre of a circle of radius r # tangent to both those lines? We shift the start point of # each line by r in the appropriate direction, and find # their crossing point again. cx2, cy2 = crosspoint( x1 - r * dx1, y1 - r * dy1, x1 - r * dx1 + nx1, y1 - r * dy1 + ny1, x2 - r * dx2, y2 - r * dy2, x2 - r * dx2 + nx2, y2 - r * dy2 + ny2, ) # Now find the distance along each line to the centre of the # circle, which will be the string lengths at the endpoints. s1 = (cx2 - x1) * nx1 + (cy2 - y1) * ny1 s2 = (cx2 - x2) * nx2 + (cy2 - y2) * ny2 # Determine the starting angle. phi = atan2(dy1, dx1) # And that's it. We're involving a circle of radius r # centred at cx2,cy2; the centre of curvature proceeds from # angle phi to phi+theta, and the actual point on the curve # is displaced from that centre by an amount which changes # linearly with angle from s1 to s2. Store all that. self.params = (r, cx2, cy2, phi, theta, s1, s2 - s1, mx) except ZeroDivisionError, e: self.params = None # it went pear-shaped
def drawfaces(): global vertices # Draw each face of the polyhedron. # # Originally this function produced a PostScript diagram of # each plane, showing the intersection lines with all the other # planes, numbering which planes they were, and outlining the # central polygon. This gives enough information to construct a # net of the solid. However, it now seems more useful to output # a 3D model of the polygon, but the PS output option is still # available if required. psprint("%!PS-Adobe-1.0") psprint("%%Pages:", len(points)) psprint("%%EndComments") psprint("%%BeginProlog") psprint("%%BeginResource: procset foo") psprint("/cshow {") psprint(" /s exch def /y exch def /x exch def") psprint(" gsave") psprint(" 0 0 moveto s true charpath flattenpath pathbbox 3 -1 roll") psprint(" grestore") psprint(" add 2 div y exch sub 3 1 roll add 2 div x exch sub exch moveto") psprint(" s show") psprint("} def") psprint("%%EndResource") psprint("%%EndProlog") faces = [] for i in range(len(points)): psprint("%%Page:", i+1) psprint("gsave") psprint("288 400 translate 150 dup scale 0.0025 setlinewidth") psprint("/Helvetica findfont 0.1 scalefont setfont") x, y, z = points[i] # Begin by rotating the point set so that this point # appears at (0,0,1). To do this we must first find the # point's polar coordinates... theta = atan2(y, x) phi = asin(z) # ... and construct a matrix which first rotates by -theta # about the z-axis, thus bringing the point to the # meridian, and then rotates by pi/2-phi about the y-axis # to bring the point to (0,0,1). # # That matrix is therefore # # ( cos(pi/2-phi) 0 -sin(pi/2-phi) ) ( cos(-theta) -sin(-theta) 0 ) # ( 0 1 0 ) ( sin(-theta) cos(-theta) 0 ) # ( sin(pi/2-phi) 0 cos(pi/2-phi) ) ( 0 0 1 ) # # which comes to # # ( cos(theta)*sin(phi) sin(theta)*sin(phi) -cos(phi) ) # ( -sin(theta) cos(theta) 0 ) # ( cos(theta)*cos(phi) sin(theta)*cos(phi) sin(phi) ) matrix = [ [ cos(theta)*sin(phi), sin(theta)*sin(phi), -cos(phi) ], [ -sin(theta) , cos(theta) , 0 ], [ cos(theta)*cos(phi), sin(theta)*cos(phi), sin(phi) ]] rpoints = [] for j in range(len(points)): if j == i: continue xa, ya, za = points[j] xb = matrix[0][0] * xa + matrix[0][1] * ya + matrix[0][2] * za yb = matrix[1][0] * xa + matrix[1][1] * ya + matrix[1][2] * za zb = matrix[2][0] * xa + matrix[2][1] * ya + matrix[2][2] * za rpoints.append((j, xb, yb, zb)) # Now. For each point in rpoints, we find the tangent plane # to the sphere at that point, and find the line where it # intersects the uppermost plane Z=1. edges = [] for j, x, y, z in rpoints: # The equation of the plane is xX + yY + zZ = 1. # Combining this with the equation Z=1 is trivial, and # yields the linear equation xX + yY = (1-z). Two # obvious points on this line are those with X=0 and # Y=0, which have coordinates (0,(1-z)/y) and # ((1-z)/x,0). if x == 0 or y == 0: continue # this point must be diametrically opposite us x1, y1 = 0, (1-z)/y x2, y2 = (1-z)/x, 0 # Find the point of closest approach between this line # and the origin. This is most easily done by returning # to the original equation xX+yY=(1-z); this clearly # shows the line to be perpendicular to the vector # (x,y), and so the closest-approach point is where X # and Y are in that ratio, i.e. X=kx and Y=ky. Thus # kx^2+ky^2=(1-z), whence k = (1-z)/(x^2+y^2). k = (1-z)/(x*x+y*y) xx = k*x yy = k*y # Store details of this line. edges.append((x1,y1, x2,y2, xx,yy, i, j)) # Find the intersection points of this line with the # edges of the square [-2,2] x [-2,2]. xyl = crosspoint(x1, y1, x2, y2, -2, -2, -2, +2) xyr = crosspoint(x1, y1, x2, y2, +2, -2, +2, +2) xyu = crosspoint(x1, y1, x2, y2, -2, +2, +2, +2) xyd = crosspoint(x1, y1, x2, y2, -2, -2, +2, -2) # Throw out any which don't exist, or which are beyond # the limits. xys = [] for xy in [xyl, xyr, xyu, xyd]: if xy == None: continue if xy[0] < -2 or xy[0] > 2: continue if xy[1] < -2 or xy[1] > 2: continue xys.append(xy) if len(xys) != 2: psprint("% unable to draw", "%d-%d" % (i+1, j+1), "edge") else: psprint(xys[0][0], xys[0][1], "moveto",) psprint(xys[1][0], xys[1][1], "lineto stroke") # Move 0.1 beyond the point of closest approach and # print the number of the side. d = sqrt(xx*xx + yy*yy) xx = xx + (0.1*xx/d) yy = yy + (0.1*yy/d) psprint(xx, yy, "(%d)" % (j+1), "cshow") psprint("0 0", "(%d)" % (i+1), "cshow") # The diagram we have just drawn is going to be a complex # stellated thing, with many intersection lines shown that # aren't part of the actual face of the polyhedron because # they are beyond its edges. Now we narrow our focus to # find the actual edges of the polygon. # We begin by notionally growing a circle out from the # centre point until it touches one of the lines. This line # will be an edge of the polygon, and furthermore the point # of contact will be _on_ the edge of the polygon. In other # words, we pick the edge whose closest-approach point is # the shortest distance from the origin. best = None n = None for j in range(len(edges)): xx,yy = edges[j][4:6] d2 = xx * xx + yy * yy if best == None or d2 < best: best = d2 n = j assert n != None e = edges[n] startn = n # We choose to look anticlockwise along the edge. This # means mapping the vector (xx,yy) into (-yy,xx). v = (-e[5],e[4]) p = (e[4],e[5]) omit = -1 # to begin with we omit the intersection with no other edge poly = [] while 1: # Now we have an edge e, a point p on the edge, and a # direction v in which to look along the edge. Examine # this edge's intersection points with all other edges, # and pick the one which is closest to p in the # direction of v (discarding any which are _behind_ p). xa1, ya1, xa2, ya2 = e[0:4] best = None n2 = None xp = yp = None for j in range(len(edges)): if j == omit or j == n: continue # ignore this one xb1, yb1, xb2, yb2 = edges[j][0:4] xcyc = crosspoint(xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2) if xcyc == None: continue # this edge is parallel to e xc, yc = xcyc dotprod = (xc - p[0]) * v[0] + (yc - p[1]) * v[1] if dotprod < 0: continue if best == None or dotprod < best: best = dotprod n2 = j xp, yp = xc, yc assert n2 != None # Found a definite corner of the polygon. Save its # coordinates, and also save the numbers of the three # planes at whose intersection the point lies. poly.append((xp, yp, e[6], e[7], edges[n2][7])) # Now move on. We must now look along the new edge. e = edges[n2] p = xp, yp # start looking from the corner we've found omit = n # next time, ignore the corner we've just hit! n = n2 # v is slightly tricky. We are moving anticlockwise # around the polygon; so we first rotate the previous v # 90 degrees left, and then we choose whichever # direction along the new edge has a positive dot # product with this vector. vtmp = (-v[1], v[0]) v = (-e[5],e[4]) if v[0] * vtmp[0] + v[1] * vtmp[1] < 0: v = (e[5], -e[4]) # Terminate the loop if we have returned to our # starting edge. if n == startn: break # Draw round the polygon in thicker pen. #psprint("0.01 setlinewidth") #psprint("newpath") #cmd = "moveto" #for p in poly: # psprint(" ", p[0], p[1], cmd) # cmd = "lineto" #psprint("closepath stroke") psprint("showpage grestore") # Save everything we need to write out a 3D model later on. # In particular this involves keeping the coordinates of # the points, for which we will need to find the inverse of # the rotation matrix so as to put the points back where # they started. # # The inverse rotation matrix is # # ( cos(-theta) sin(-theta) 0 ) ( cos(pi/2-phi) 0 sin(pi/2-phi) ) # ( -sin(-theta) cos(-theta) 0 ) ( 0 1 0 ) # ( 0 0 1 ) ( -sin(pi/2-phi) 0 cos(pi/2-phi) ) # # which comes to # # ( cos(theta)*sin(phi) -sin(theta) cos(theta)*cos(phi) ) # ( sin(theta)*sin(phi) cos(theta) sin(theta)*cos(phi) ) # ( -cos(phi) 0 sin(phi) ) imatrix = [ [ cos(theta)*sin(phi), -sin(theta), cos(theta)*cos(phi) ], [ sin(theta)*sin(phi), cos(theta), sin(theta)*cos(phi) ], [ -cos(phi) , 0 , sin(phi) ]] facelist = [] for p in poly: xa, ya = p[0:2] za = 1 xb = imatrix[0][0] * xa + imatrix[0][1] * ya + imatrix[0][2] * za yb = imatrix[1][0] * xa + imatrix[1][1] * ya + imatrix[1][2] * za zb = imatrix[2][0] * xa + imatrix[2][1] * ya + imatrix[2][2] * za planes = list(p[2:5]) planes.sort() planes = tuple(planes) if not vertices.has_key(planes): vertices[planes] = [] vertices[planes].append((xb, yb, zb)) facelist.append(planes) faces.append((i, facelist)) psprint("%%EOF") # Now output the polygon description. # # Each polygon has been prepared in its own frame of reference, # so the absolute coordinates of the vertices will vary # depending on which polygon they were prepared in. For this # reason I have kept _every_ version of the coordinates of each # vertex, so we can now average them into a single canonical value. for key, value in vertices.items(): xt = yt = zt = n = 0 xxt = yyt = zzt = 0 vlabel = pointlabel(key) for x, y, z in value: xt = xt + x yt = yt + y zt = zt + z xxt = xxt + x*x yyt = yyt + y*y zzt = zzt + z*z n = n + 1 polyprint("point", vlabel, xt/n, yt/n, zt/n) for i, vlist in faces: flabel = "face_" + str(i) for key in vlist: vlabel = pointlabel(key) polyprint("face", flabel, vlabel) # And the surface normal (pointing outwards), which is # simply the position vector of the original point i. polyprint("normal", flabel, points[i][0], points[i][1], points[i][2])
def drawfaces(): global vertices # Draw each face of the polyhedron. # # Originally this function produced a PostScript diagram of # each plane, showing the intersection lines with all the other # planes, numbering which planes they were, and outlining the # central polygon. This gives enough information to construct a # net of the solid. However, it now seems more useful to output # a 3D model of the polygon, but the PS output option is still # available if required. psprint("%!PS-Adobe-1.0") psprint("%%Pages:", len(points)) psprint("%%EndComments") psprint("%%BeginProlog") psprint("%%BeginResource: procset foo") psprint("/cshow {") psprint(" /s exch def /y exch def /x exch def") psprint(" gsave") psprint( " 0 0 moveto s true charpath flattenpath pathbbox 3 -1 roll") psprint(" grestore") psprint( " add 2 div y exch sub 3 1 roll add 2 div x exch sub exch moveto") psprint(" s show") psprint("} def") psprint("%%EndResource") psprint("%%EndProlog") faces = [] for i in range(len(points)): psprint("%%Page:", i + 1) psprint("gsave") psprint("288 400 translate 150 dup scale 0.0025 setlinewidth") psprint("/Helvetica findfont 0.1 scalefont setfont") x, y, z = points[i] # Begin by rotating the point set so that this point # appears at (0,0,1). To do this we must first find the # point's polar coordinates... theta = atan2(y, x) phi = asin(z) # ... and construct a matrix which first rotates by -theta # about the z-axis, thus bringing the point to the # meridian, and then rotates by pi/2-phi about the y-axis # to bring the point to (0,0,1). # # That matrix is therefore # # ( cos(pi/2-phi) 0 -sin(pi/2-phi) ) ( cos(-theta) -sin(-theta) 0 ) # ( 0 1 0 ) ( sin(-theta) cos(-theta) 0 ) # ( sin(pi/2-phi) 0 cos(pi/2-phi) ) ( 0 0 1 ) # # which comes to # # ( cos(theta)*sin(phi) sin(theta)*sin(phi) -cos(phi) ) # ( -sin(theta) cos(theta) 0 ) # ( cos(theta)*cos(phi) sin(theta)*cos(phi) sin(phi) ) matrix = [[cos(theta) * sin(phi), sin(theta) * sin(phi), -cos(phi)], [-sin(theta), cos(theta), 0], [cos(theta) * cos(phi), sin(theta) * cos(phi), sin(phi)]] rpoints = [] for j in range(len(points)): if j == i: continue xa, ya, za = points[j] xb = matrix[0][0] * xa + matrix[0][1] * ya + matrix[0][2] * za yb = matrix[1][0] * xa + matrix[1][1] * ya + matrix[1][2] * za zb = matrix[2][0] * xa + matrix[2][1] * ya + matrix[2][2] * za rpoints.append((j, xb, yb, zb)) # Now. For each point in rpoints, we find the tangent plane # to the sphere at that point, and find the line where it # intersects the uppermost plane Z=1. edges = [] for j, x, y, z in rpoints: # The equation of the plane is xX + yY + zZ = 1. # Combining this with the equation Z=1 is trivial, and # yields the linear equation xX + yY = (1-z). Two # obvious points on this line are those with X=0 and # Y=0, which have coordinates (0,(1-z)/y) and # ((1-z)/x,0). if x == 0 or y == 0: continue # this point must be diametrically opposite us x1, y1 = 0, (1 - z) / y x2, y2 = (1 - z) / x, 0 # Find the point of closest approach between this line # and the origin. This is most easily done by returning # to the original equation xX+yY=(1-z); this clearly # shows the line to be perpendicular to the vector # (x,y), and so the closest-approach point is where X # and Y are in that ratio, i.e. X=kx and Y=ky. Thus # kx^2+ky^2=(1-z), whence k = (1-z)/(x^2+y^2). k = (1 - z) / (x * x + y * y) xx = k * x yy = k * y # Store details of this line. edges.append((x1, y1, x2, y2, xx, yy, i, j)) # Find the intersection points of this line with the # edges of the square [-2,2] x [-2,2]. xyl = crosspoint(x1, y1, x2, y2, -2, -2, -2, +2) xyr = crosspoint(x1, y1, x2, y2, +2, -2, +2, +2) xyu = crosspoint(x1, y1, x2, y2, -2, +2, +2, +2) xyd = crosspoint(x1, y1, x2, y2, -2, -2, +2, -2) # Throw out any which don't exist, or which are beyond # the limits. xys = [] for xy in [xyl, xyr, xyu, xyd]: if xy == None: continue if xy[0] < -2 or xy[0] > 2: continue if xy[1] < -2 or xy[1] > 2: continue xys.append(xy) if len(xys) != 2: psprint("% unable to draw", "%d-%d" % (i + 1, j + 1), "edge") else: psprint( xys[0][0], xys[0][1], "moveto", ) psprint(xys[1][0], xys[1][1], "lineto stroke") # Move 0.1 beyond the point of closest approach and # print the number of the side. d = sqrt(xx * xx + yy * yy) xx = xx + (0.1 * xx / d) yy = yy + (0.1 * yy / d) psprint(xx, yy, "(%d)" % (j + 1), "cshow") psprint("0 0", "(%d)" % (i + 1), "cshow") # The diagram we have just drawn is going to be a complex # stellated thing, with many intersection lines shown that # aren't part of the actual face of the polyhedron because # they are beyond its edges. Now we narrow our focus to # find the actual edges of the polygon. # We begin by notionally growing a circle out from the # centre point until it touches one of the lines. This line # will be an edge of the polygon, and furthermore the point # of contact will be _on_ the edge of the polygon. In other # words, we pick the edge whose closest-approach point is # the shortest distance from the origin. best = None n = None for j in range(len(edges)): xx, yy = edges[j][4:6] d2 = xx * xx + yy * yy if best == None or d2 < best: best = d2 n = j assert n != None e = edges[n] startn = n # We choose to look anticlockwise along the edge. This # means mapping the vector (xx,yy) into (-yy,xx). v = (-e[5], e[4]) p = (e[4], e[5]) omit = -1 # to begin with we omit the intersection with no other edge poly = [] while 1: # Now we have an edge e, a point p on the edge, and a # direction v in which to look along the edge. Examine # this edge's intersection points with all other edges, # and pick the one which is closest to p in the # direction of v (discarding any which are _behind_ p). xa1, ya1, xa2, ya2 = e[0:4] best = None n2 = None xp = yp = None for j in range(len(edges)): if j == omit or j == n: continue # ignore this one xb1, yb1, xb2, yb2 = edges[j][0:4] xcyc = crosspoint(xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2) if xcyc == None: continue # this edge is parallel to e xc, yc = xcyc dotprod = (xc - p[0]) * v[0] + (yc - p[1]) * v[1] if dotprod < 0: continue if best == None or dotprod < best: best = dotprod n2 = j xp, yp = xc, yc assert n2 != None # Found a definite corner of the polygon. Save its # coordinates, and also save the numbers of the three # planes at whose intersection the point lies. poly.append((xp, yp, e[6], e[7], edges[n2][7])) # Now move on. We must now look along the new edge. e = edges[n2] p = xp, yp # start looking from the corner we've found omit = n # next time, ignore the corner we've just hit! n = n2 # v is slightly tricky. We are moving anticlockwise # around the polygon; so we first rotate the previous v # 90 degrees left, and then we choose whichever # direction along the new edge has a positive dot # product with this vector. vtmp = (-v[1], v[0]) v = (-e[5], e[4]) if v[0] * vtmp[0] + v[1] * vtmp[1] < 0: v = (e[5], -e[4]) # Terminate the loop if we have returned to our # starting edge. if n == startn: break # Draw round the polygon in thicker pen. #psprint("0.01 setlinewidth") #psprint("newpath") #cmd = "moveto" #for p in poly: # psprint(" ", p[0], p[1], cmd) # cmd = "lineto" #psprint("closepath stroke") psprint("showpage grestore") # Save everything we need to write out a 3D model later on. # In particular this involves keeping the coordinates of # the points, for which we will need to find the inverse of # the rotation matrix so as to put the points back where # they started. # # The inverse rotation matrix is # # ( cos(-theta) sin(-theta) 0 ) ( cos(pi/2-phi) 0 sin(pi/2-phi) ) # ( -sin(-theta) cos(-theta) 0 ) ( 0 1 0 ) # ( 0 0 1 ) ( -sin(pi/2-phi) 0 cos(pi/2-phi) ) # # which comes to # # ( cos(theta)*sin(phi) -sin(theta) cos(theta)*cos(phi) ) # ( sin(theta)*sin(phi) cos(theta) sin(theta)*cos(phi) ) # ( -cos(phi) 0 sin(phi) ) imatrix = [[cos(theta) * sin(phi), -sin(theta), cos(theta) * cos(phi)], [sin(theta) * sin(phi), cos(theta), sin(theta) * cos(phi)], [-cos(phi), 0, sin(phi)]] facelist = [] for p in poly: xa, ya = p[0:2] za = 1 xb = imatrix[0][0] * xa + imatrix[0][1] * ya + imatrix[0][2] * za yb = imatrix[1][0] * xa + imatrix[1][1] * ya + imatrix[1][2] * za zb = imatrix[2][0] * xa + imatrix[2][1] * ya + imatrix[2][2] * za planes = list(p[2:5]) planes.sort() planes = tuple(planes) if not vertices.has_key(planes): vertices[planes] = [] vertices[planes].append((xb, yb, zb)) facelist.append(planes) faces.append((i, facelist)) psprint("%%EOF") # Now output the polygon description. # # Each polygon has been prepared in its own frame of reference, # so the absolute coordinates of the vertices will vary # depending on which polygon they were prepared in. For this # reason I have kept _every_ version of the coordinates of each # vertex, so we can now average them into a single canonical value. for key, value in vertices.items(): xt = yt = zt = n = 0 xxt = yyt = zzt = 0 vlabel = pointlabel(key) for x, y, z in value: xt = xt + x yt = yt + y zt = zt + z xxt = xxt + x * x yyt = yyt + y * y zzt = zzt + z * z n = n + 1 polyprint("point", vlabel, xt / n, yt / n, zt / n) for i, vlist in faces: flabel = "face_" + str(i) for key in vlist: vlabel = pointlabel(key) polyprint("face", flabel, vlabel) # And the surface normal (pointing outwards), which is # simply the position vector of the original point i. polyprint("normal", flabel, points[i][0], points[i][1], points[i][2])
def set_params(self): x1, y1, dx1, dy1, x2, y2, dx2, dy2, mx = self.inparams try: # Normalise the direction vectors. dlen1 = sqrt(dx1**2 + dy1**2) dx1, dy1 = dx1 / dlen1, dy1 / dlen1 dlen2 = sqrt(dx2**2 + dy2**2) dx2, dy2 = dx2 / dlen2, dy2 / dlen2 self.inparams = (x1, y1, dx1, dy1, x2, y2, dx2, dy2, mx) # Transform into the squashed coordinate system. if mx != None: x1, y1 = squash(x1, y1, mx) dx1, dy1 = squash(dx1, dy1, mx) x2, y2 = squash(x2, y2, mx) dx2, dy2 = squash(dx2, dy2, mx) # And renormalise. dlen1 = sqrt(dx1**2 + dy1**2) dx1, dy1 = dx1 / dlen1, dy1 / dlen1 dlen2 = sqrt(dx2**2 + dy2**2) dx2, dy2 = dx2 / dlen2, dy2 / dlen2 # Find the normal vectors at each end by rotating the # direction vectors. nx1, ny1 = dy1, -dx1 nx2, ny2 = dy2, -dx2 # Find the crossing point of the normals. cx, cy = crosspoint(x1, y1, x1 + nx1, y1 + ny1, x2, y2, x2 + nx2, y2 + ny2) # Measure the distance from that crossing point to each # endpoint, and find the difference. # # The distance is obtained by taking the dot product with # the line's defining vector, so that it's signed. d1 = (cx - x1) * nx1 + (cy - y1) * ny1 d2 = (cx - x2) * nx2 + (cy - y2) * ny2 dd = d2 - d1 # Find the angle between the two direction vectors. Since # they're already normalised to unit length, the magnitude # of this is just the inverse cosine of their dot product. # The sign must be chosen to reflect which way round they # are. dp = dx1 * dx2 + dy1 * dy2 if abs(dp) > 1: dp /= abs(dp) # avoid EDOM from rounding error theta = -acos(dp) if dx1 * dy2 - dx2 * dy1 > 0: theta = -theta # So we need a circular arc rotating through angle theta, # such that taking the involute of that arc with the right # length does the right thing. # # Suppose the circle has radius r. Then, when the circle # touches the line going to point 1, we need our string to # have length equal to d1 - r tan(theta/2). When it touches # the line going to point 2, the string length needs to be # d2 + r tan(theta/2). The difference between these numbers # must be equal to the arc length of the portion of the # circle in between, which is r*theta. Setting these equal # gives d2-d1 = r theta - 2 r tan(theta/2), which we solve # to get r = (d2-d1) / (theta - 2 tan (theta/2)). # # (In fact, we then flip the sign to take account of the way # we subsequently use r.) r = dd / (-theta + 2 * tan(theta / 2)) # So how do we find the centre of a circle of radius r # tangent to both those lines? We shift the start point of # each line by r in the appropriate direction, and find # their crossing point again. cx2, cy2 = crosspoint(x1-r*dx1, y1-r*dy1, x1-r*dx1+nx1, y1-r*dy1+ny1, \ x2-r*dx2, y2-r*dy2, x2-r*dx2+nx2, y2-r*dy2+ny2) # Now find the distance along each line to the centre of the # circle, which will be the string lengths at the endpoints. s1 = (cx2 - x1) * nx1 + (cy2 - y1) * ny1 s2 = (cx2 - x2) * nx2 + (cy2 - y2) * ny2 # Determine the starting angle. phi = atan2(dy1, dx1) # And that's it. We're involving a circle of radius r # centred at cx2,cy2; the centre of curvature proceeds from # angle phi to phi+theta, and the actual point on the curve # is displaced from that centre by an amount which changes # linearly with angle from s1 to s2. Store all that. self.params = (r, cx2, cy2, phi, theta, s1, s2 - s1, mx) except ZeroDivisionError, e: self.params = None # it went pear-shaped
break if vl1.face != placement.face and \ placement.vpos.has_key(e1[1]): break for e2 in faceedges[vl2.face]: if vl2.face != placement.face and \ placement.vpos.has_key(e2[0]): break if vl2.face != placement.face and \ placement.vpos.has_key(e2[1]): break xa1, ya1 = vl1.vpos[e1[0]] xa2, ya2 = vl1.vpos[e1[1]] xb1, yb1 = vl2.vpos[e2[0]] xb2, yb2 = vl2.vpos[e2[1]] ret = crosspoint(xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2) if ret == None: continue x, y = ret dxa, dya = xa2 - xa1, ya2 - ya1 dxb, dyb = xb2 - xb1, yb2 - yb1 # See if the crossing point is between the # ends of each line. This will be true if # the dot product (x-xa1,y-ya1).(dxa,dya) # divided by the squared length # (dxa,dya).(dxa,dya) is strictly between 0 # and 1. Likewise for b1/b2. dp = ((x - xa1) * dxa + (y - ya1) * dya) / (dxa**2 + dya**2) if dp < 0 or dp > 1: continue dp = ((x - xb1) * dxb + (y - yb1) * dyb) / (dxb**2 + dyb**2)