def get_layer_holes(shape): '''Returns coordinates of all holes in all layers.''' # Clone shape, flood fill all triangles on the outline. The holes are all the coordinates without triangles. s = clone_shape(shape) # Fill layers. for z in range(s.size.z): for x in range(s.size.x//2): # top if not s.triangles[z][0][x*2+1]: flood_fill_layer(vec3(x*2+1,0,z),s) bottom = s.size.y - 1 # bottom for x in range(s.size.x//2): if not s.triangles[z][bottom][x*2]: flood_fill_layer(vec3(x*2,bottom,z),s) for y in range(s.size.y//2): # left if not s.triangles[z][y*2][0]: flood_fill_layer(vec3(0,y*2,z),s) right = s.size.x - 1 # right for y in range(s.size.y//2): if not s.triangles[z][y*2+1][right]: flood_fill_layer(vec3(right,y*2+1,z),s) holes = [] for z,layer in enumerate(s.triangles): for y,row in enumerate(layer): for x,t in enumerate(row): if not t: holes += [vec3(x,y,z)] return holes
def remove_redundant_boxes(): global boxes,box_angles,hole_delimitors drop = defaultdict(int) for i,c in enumerate(boxes): ciq,cc,cs = c c1,c2,c3,c4 = _boxcoords(vec3(*cc),(vec3(*cs)/2).with_z(0)) c5,c6,c7,c8 = _midcoords(c1,c3) for j,b in enumerate(boxes): biq,bc,bs = b if i == j or drop[j] == 0xff or ciq.q[0] != 1 or biq.q[0] != 1: continue b1,_,b2,_ = _boxcoords(vec3(*bc),vec3(*bs)/2) mask = mask_aabb_enclosed([c1,c2,c3,c4,c5,c6,c7,c8], b1,b2) if mask: drop[i] |= mask for i,mask in sorted(drop.items(), reverse=True): if mask == 0xff: print('dropping box', i) for j,a in list(box_angles.items()): if j > i: del box_angles[j] box_angles[j-1] = a for j in list(hole_delimitors.keys()): if j > i: del hole_delimitors[j] hole_delimitors[j-1] = True del boxes[i] del floors[i]
def extrapolate(now, enemy_tanks): shoot = False # Extrapolate velocity. tank.et is time for extrapolation, tank.epos is extrapolated position. for tank in my_tanks+enemy_tanks: if hasattr(tank,'prev_pos') and tank.pos != tank.prev_pos: dt = now-tank.t prev_vel = tank.vel tank.vel = (tank.pos-tank.prev_pos)/dt tank.acc = ((tank.vel-prev_vel)/dt).with_z(0) tank.epos = tank.pos tank.prev_pos = tank.pos tank.t = tank.et = now if shoot_time and shoot_time-now <= 0: shoot = True elif hasattr(tank,'epos'): dt = now-tank.et tank.acc = tank.acc*0.9*dt - tank.epos.with_z(0).normalize()*dt tank.vel += tank.acc * dt tank.epos = tank.epos + tank.vel * dt tank.et = now else: tank.prev_pos = tank.pos tank.epos = tank.pos tank.vel = vec3() tank.acc = vec3() tank.t = tank.et = now return shoot
def _get_longest_edges(f): lengths,straight_lengths = [],[] right,fwd = vec3(1,0,0),vec3(0,1,0) for i in range(0,len(f)): f1,f2 = f[i-1],f[i] p1,p2 = vec3(*f1),vec3(*f2) v = p2-p1 lengths += [(v.length2(), v)] if not v*right and not v*fwd: straight_lengths += [(v.length2(), v)] sl = sorted(straight_lengths, key=lambda e:e[0])[-1][1] if straight_lengths else None return sorted(lengths, key=lambda e:e[0])[-1][1], sl
def find_rotated_boxes(): global floors,box_angles for i,f in enumerate(floors): if i in box_angles: continue longest_edge,longest_straight_edge = _get_longest_edges(f) if longest_edge*vec3(1,0,0) and longest_edge*vec3(0,1,0) and \ (longest_straight_edge == None or longest_edge.length() > 1.4*longest_straight_edge.length()): #if abs(longest_edge.normalize()*vec3(1,0,0)) < 0.9 and abs(longest_edge.normalize()*vec3(0,1,0)) < 0.9: a = longest_edge.angle_z(vec3(1,0,0)) print('Box %i is rotated %g degrees.' % (i,a*180/3.14159)) box_angles[i] = a
def load_shapes_from_file(f, crop): contains_specific = False char_shapes = [] shape = [] # List of layers. layer = [] # List of strings. One string per row. for line in f: line = line.rstrip() if crop else line.rstrip('\r\n') if '~~~' in line: shape += [layer] char_shapes += [shape] shape,layer = [],[] elif '---' in line: shape += [layer] layer = [] else: contains_specific |= bool([1 for ch in '`´ltrb' if ch in line]) layer += [line] shape += [layer] char_shapes += [shape] shapes = [Shape(unify_chars(shape,crop)) for shape in char_shapes] # Handle simplified ascii art, for instance '\' and '/' can mean either upper or lower triangle. if not contains_specific: for shape,char_shape in zip(shapes,char_shapes): for z,layer in enumerate(char_shape): for y,row in enumerate(layer): for _ in range(2): # Run two times to resolve. for x,ch in enumerate(row): if ch not in '/\\><^v': continue left = boundtricnt(shape, vec3(x*2-1,y*2+1,z)) right = boundtricnt(shape, vec3(x*2+2,y*2+0,z)) top = boundtricnt(shape, vec3(x*2+0,y*2-1,z)) bottom = boundtricnt(shape, vec3(x*2+1,y*2+2,z)) sumtot = left+right+top+bottom no_neighbour_tris = (sumtot == 0) nch = '`´lrbt'[r'/\><^v'.index(ch)] if ch == '/' and (left+top > right+bottom or (no_neighbour_tris and row[x-1:x] == '\\')): ch = nch elif ch == '\\' and (right+top > left+bottom or (no_neighbour_tris and row[x+1:x+2] == '/')): ch = nch elif ch == '>' and top+right+bottom-1 > left: ch = nch elif ch == '<' and top+left+bottom-1 > right: ch = nch elif ch == '^' and left+top+right-1 > bottom: ch = nch elif ch == 'v' and left+bottom+right-1 > top: ch = nch shape.settris(ch, x, y, z) return shapes
def set_box_heights(): global boxes gx,gy,gz = lambda p:p.x, lambda p:p.y, lambda p:p.z for i,b in enumerate(boxes): q,c,s = b c,s = vec3(*c),vec3(*s) before = minz = c.z-s.z minz = lowest_neighbor(i, minz) if minz == before: minz -= 32 s.z += before-minz c.z -= (before-minz)/2 boxes[i] = (q,tuple(c),tuple(s))
def boxify(): global boxes gx,gy,gz = lambda p:p.x, lambda p:p.y, lambda p:p.z for i,f in enumerate(floors): q,iq = quat(),quat() if i in box_angles: q = q.rotate_z(+box_angles[i]) iq = iq.rotate_z(-box_angles[i]) # Rotate and calculate size. sps = [q*vec3(*v) for v in f] g = lambda f,l: l(f(sps,key=l)) minx,miny,minz,maxx,maxy,maxz = g(min,gx), g(min,gy), g(min,gz), g(max,gx), g(max,gy), g(max,gz) s = maxx-minx, maxy-miny, maxz-minz c = iq * vec3((maxx+minx)/2, (maxy+miny)/2, (maxz+minz)/2) #print(c,s) boxes += [(iq,c,s)]
def createsphere(radius, latitude=8, longitude=12): v,i = [],[] def st(t,p): return p*longitude + t%longitude def sphere_triangles(t,p): if p >= latitude: return [] return [st(t,p), st(t+1,p), st(t,p+1), st(t+1,p), st(t+1,p+1), st(t,p+1)] for phi_i in range(latitude+1): for theta_i in range(longitude): theta,phi = theta_i*2*pi/longitude,phi_i*pi/latitude waist = sin(phi) r = vec3(waist*cos(theta), -waist*sin(theta), cos(phi)) * radius v.append(r) i += sphere_triangles(theta_i,phi_i) return GfxMesh(quat(),vec3(),v,i),[PhysSphere(quat(),vec3(),radius)]
def update(blips): enemy_tanks = [blip for blip in blips if blip.type == 'tank'] # Extrapolate velocity. tank.et is time for extrapolation, tank.epos is extrapolated position. now = time() for tank in my_tanks+enemy_tanks: if hasattr(tank,'prev_pos') and tank.pos != tank.prev_pos: tank.vel = (tank.pos-tank.prev_pos)/(now-tank.t) tank.epos = tank.pos tank.prev_pos = tank.pos tank.t = tank.et = now elif hasattr(tank,'epos'): tank.epos = tank.epos + tank.vel * (now-tank.et) tank.et = now else: tank.prev_pos = tank.pos tank.epos = tank.pos tank.vel = vec3() tank.t = tank.et = now for tank in my_tanks: enemy = closest_tank(tank.pos, enemy_tanks) if enemy: # Drive towards enemy, but keep somewhat centered. tank.drive((enemy.epos+enemy.vel)/4 - (tank.pos+tank.vel)) # Shoot at where enemy is assumed to be. ds = (enemy.epos+enemy.vel*2) - (tank.pos+tank.vel*2) yaw = -atan2(ds.x,ds.y) pitch = atan((0.15*ds.length()-2)/(ds.length()/2)) tank.shoot(yaw, pitch, 'damage')
def pickobjs(pos, direction, near, far): c = 'pick-objects %s %s %f %f' % (_args2str(pos,'0 0 0'), _args2str(direction,'0 0 0'), near, far) s = cmd(c, lambda s:s) if s: h = s.split(',') return [(int(h[i]),vec3(*[float(j) for j in h[i+1:i+4]])) for i in range(0,len(h),4)] return []
def get_tri_pos(crd): cube = vec3(crd.x//2,crd.y//2,crd.z) px,py = crd.x%2,crd.y%2 if px == 0 and py == 0: return W2E,cube if px == 1 and py == 0: return N2S,cube if px == 0 and py == 1: return S2N,cube if px == 1 and py == 1: return E2W,cube
def getcrds(shape): crds = set() for z in range(shape.size.z): for y in range(shape.size.y): for x in range(shape.size.x): if shape.triangles[z][y][x]: crds.add(vec3(x,y,z)) return crds
def _flip(i, f): ps = [vec3(*v) for v in f] z = 0 for j in range(0,len(ps)): p1,p2,p3 = ps[j-2],ps[j-1],ps[j] z += -1 if (p2-p1).cross(p3-p2).z < 0 else +1 if z < 0: print('flipping floor %i' % i) floors[i] = list(reversed(f))
def get_normal(p,q): v = q-p if v.z > 0: return F2B if v.z < 0: return B2F if v == vec3(+1, 0,0): return SW2NE if v == vec3(-1, 0,0): return NE2SW if v == vec3( 0,+1,0): return NW2SE if v == vec3( 0,-1,0): return SE2NW if v == vec3(+1,+1,0) and p.x&1==0 and p.y&1==1: return N2S if v == vec3(-1,-1,0) and p.x&1==1 and p.y&1==0: return S2N if v == vec3(+1,-1,0) and p.x&1==1 and p.y&1==1: return W2E if v == vec3(-1,+1,0) and p.x&1==0 and p.y&1==0: return E2W raise ValueError('%s and %s are not neighbours?!' % (p,q))
def merge_shapes(from_shape, to_shape): for z in range(from_shape.size.z): for y in range(from_shape.size.y): for x in range(from_shape.size.x): if from_shape.triangles[z][y][x]: if to_shape.triangles[z][y][x]: print(from_shape) print(to_shape) raise ValueError('Trying to merge, but overlaps in %s!' % vec3(x,y,z)) to_shape.triangles[z][y][x] = True to_shape.tricnt += 1
def createcapsule(radius, length, latitude=8, longitude=12): latitude_odd = latitude if latitude&1 else latitude+1 latitude = latitude_odd-1 latitude_half = latitude/2 v,i,l2 = [],[],length/2 def st(t,p): return p*longitude + t%longitude def capsule_triangles(t,p): if p >= latitude_odd: return [] return [st(t,p), st(t+1,p), st(t,p+1), st(t+1,p), st(t+1,p+1), st(t,p+1)] for phi_i in range(latitude_odd+1): phi_j,offz = (phi_i-1,-l2) if phi_i > latitude_half else (phi_i,l2) for theta_i in range(longitude): theta,phi = theta_i*2*pi/longitude,phi_j*pi/latitude waist = sin(phi) r = vec3(waist*cos(theta), -waist*sin(theta), cos(phi)) * radius r.z += offz v.append(r) i += capsule_triangles(theta_i,phi_i) return GfxMesh(quat(),vec3(),v,i),[PhysCapsule(quat(),vec3(),radius,length)]
def pick_enemy(now, pos, enemy_tanks): # Pick one enemy to gather around. move2pos = [vec3()]*len(my_tanks) global enemy_tank,angle,angle_t enemy_tank = enemy_tank if enemy_tank in enemy_tanks else None if not enemy_tank and my_tanks: enemy_tank = closest_tank(pos, enemy_tanks) if enemy_tank: center_vec = -enemy_tank.epos.with_z(0).normalize(60) angle = 0#sin(now*0.1)*0.4 move2pos = [quat().rotate_z(angle+pi*i/(len(my_tanks)+8))*center_vec+enemy_tank.epos for i in range(-1,len(my_tanks)-1)] return move2pos, enemy_tank
def getsetoidcmd(name, oid, *args): l = [] if args != (None,): for arg in args: l += [str(a) for a in arg if a != None] result = cmd('%s %s %s' % (name, oid, ' '.join(l))) if not l and result: result = [float(r) for r in result.split()] if len(result) == 1: return result[0] if len(result) == 3: return vec3(*result) if len(result) == 4: return quat(*result) return result
def convexify(): global floors,sector_linedefs i = 0 while i < len(floors): f = floors[i] for j in range(0,len(f)): f1,f2,f3 = f[j-2],f[j-1],f[j] p1,p2,p3 = vec3(*f1),vec3(*f2),vec3(*f3) if (p2-p1).cross((p3-p2)).z < 0: assert len(f) > 3 a,b = _vsplice(f, j-1) floors[i] = a floors += [b] sector_linedefs += [sector_linedefs[i]] assert len(floors[i]) >= 3 and len(floors[-1]) >= 3 _flip(i, a) _flip(len(floors)-1, b) if i in box_angles: box_angles[len(floors)-1] = box_angles[i] if i in hole_delimitors: hole_delimitors[len(floors)-1] = True i -= 1 break i += 1
def create_floor_holes(): global floors,box_angles,sector_linedefs,sectors sidxes = find_holes() print(sidxes) print('Adding %i holes.' % len(sidxes)) for lo_sidx,hi_sidx in sidxes: lo,hi = sectors[hi_sidx] floor = [(v[0],v[1],lo) for v in floors[lo_sidx]] center = sum((vec3(*f) for f in floors[lo_sidx]), vec3()) / len(floors[lo_sidx]) # Create convex polygons emanating from the hole. up = vec3(0,0,1) for i in range(0,len(floor)): f1,f2 = floor[i-1],floor[i] p1,p2 = vec3(*f1),vec3(*f2) out = (p2-p1).cross(up) if out*((p1+p2)/2-center) < 0: out = -out box_angles[len(floors)] = out.angle_z(vec3(0,1,0)) hole_delimitors[len(floors)] = True fvs = [vec3(*f) for f in floors[hi_sidx]] including = [f for f,p in zip(floors[hi_sidx],fvs) if (p-p1)*out>0] floors += [including+[f2,f1]] # Reverse f order! sector_linedefs += [sector_linedefs[lo_sidx]] sector_linedefs[hi_sidx] += [(0,0,len(sector_linedefs)-1,-1)] sectors += [sectors[lo_sidx]] # Drop used vertices. drop = including if len(drop) > 3: if i == 0: # Exclude extremities on first split. drop.remove(max([(vec3(*f),f) for f in drop], key=lambda vf:(vf[0]-p1).length2())[1]) drop.remove(max([(vec3(*f),f) for f in drop], key=lambda vf:(vf[0]-p2).length2())[1]) for d in drop: floors[hi_sidx].remove(d) if len(floors[hi_sidx]) < 3: assert False floors[hi_sidx] = floors[-1] floors = floors[:-1] box_angles[hi_sidx] = box_angles[len(floors)] del box_angles[len(floors)]
def __init__(self,original): if hasattr(original,'triangles'): self.size = original.size self.tricnt = original.tricnt self.triangles = deepcopy(original.triangles) else: chars = original self.size = vec3(len(chars[0][0])*2,len(chars[0])*2,len(chars)) self.tricnt = 0 self.triangles = [] for z,layer in enumerate(chars): tlayer = [] self.triangles.append(tlayer) for y,row in enumerate(layer): tlayer += [[False]*self.size.x,[False]*self.size.x] # Add two triangle rows per char row. for x,ch in enumerate(row): self.tricnt += _ch2tricnt(ch) self.settris(ch,x,y,z)
def createcube(side): s = side*0.5 v = [(+s.x,-s.y,+s.z), (-s.x,-s.y,+s.z), (+s.x,-s.y,-s.z), (-s.x,-s.y,-s.z), (+s.x,+s.y,+s.z), (-s.x,+s.y,+s.z), (+s.x,+s.y,-s.z), (-s.x,+s.y,-s.z)] i = [0,1,2, 1,3,2, 1,5,3, 5,7,3, 4,0,6, 0,2,6, 5,4,7, 4,6,7, 4,5,0, 5,1,0, 2,3,6, 3,7,6] return GfxMesh(quat(),vec3(),[tovec3(c) for c in v],i),[PhysBox(quat(),vec3(),side)]
def shrink_rotated_boxes(): global boxes shortened_count = 0 drop = defaultdict(int) for i,c in enumerate(boxes): ciq,cc,cs = c if ciq.q[0] == 1: continue #cq = ciq.inverse() cc,cs = vec3(*cc),vec3(*cs) c1,c2,c3,c4 = _boxcoords(cc,(cs/2).with_z(0), ciq) ## cminx,cmaxx = min([c1,c2,c3,c4], key=lambda c:c.x), max([c1,c2,c3,c4], key=lambda c:c.x) cminy,cmaxy = min([c1,c2,c3,c4], key=lambda c:c.y), max([c1,c2,c3,c4], key=lambda c:c.y) for j,b in enumerate(boxes): biq,bc,bs = b bc,bs = vec3(*bc),vec3(*bs)/2 if biq.q[0] != 1 or not almosteq(cc.z+cs.z/2,bc.z+bs.z): continue bmin,bmax = bc-bs,bc+bs ## if cminx.x<bmin.x and cmaxx.x>bmax.x: ## bminy,bmaxy = min(bmin.y,bmax.y),max(bmin.y,bmax.y) ## mincloser = (abs(cminx.x-bc.x) < abs(cmaxx.x-bc.x)) ## print('OUT HERE!', i, cminx, cmaxx, bmin, bmax, mincloser, abs(cmaxx.y-bminy), abs(cmaxx.y-bmaxy), (not mincloser and abs(cmaxx.y-bminy)<cutlimit and abs(cmaxx.y-bmaxy)<cutlimit)) ## if mincloser and abs(cminx.y-bminy)<cutlimit and abs(cminx.y-bmaxy)<cutlimit: ## d = bmin.x-cminx.x ## if d > 0.1: ## print('HERE MIN X!', d) ## if cs.x > cs.y: ## a = vec3(1,0,0).angle_z(cq*vec3(1,0,0)) ## v = cq*vec3(d/cos(a),0,0) ## print('vector is:', v, v.length(), cq*vec3(1,0,0)) ## cs.x -= v.length() ## cc -= v/2 ## print('SHOTENED 1!', i, boxes[i]) ## boxes[i] = ciq,tuple(cc),tuple(cs) ## print('SHOTENED 2!', i, boxes[i]) ## shortened_count += 1 ## break ## elif not mincloser and abs(cmaxx.y-bminy)<cutlimit and abs(cmaxx.y-bmaxy)<cutlimit: ## d = cmaxx.x-bmax.x ## if d > 0.1: ## print('HERE MAX X!', d) ## if cs.x > cs.y: ## a = vec3(1,0,0).angle_z(cq*vec3(1,0,0)) ## v = cq*vec3(d/cos(a),0,0) ## print('vector is:', v, v.length(), cq*vec3(1,0,0)) ## cs.x -= v.length() ## cc -= v/2 ## print('SHOTENED 3!', i, boxes[i]) ## boxes[i] = ciq,tuple(cc),tuple(cs) ## print('SHOTENED 4!', i, boxes[i]) ## print('shortener was', j) ## shortened_count += 1 ## break if cminy.y<bmin.y and cmaxy.y>bmax.y: bminx,bmaxx = min(bmin.x,bmax.x),max(bmin.x,bmax.x) mincloser = (abs(cminy.y-bc.y) < abs(cmaxy.y-bc.y)) print('OUT HERE!', i, j, cminy, cmaxy, bmin, bmax, mincloser, abs(cmaxy.x-bminx), abs(cmaxy.x-bmaxx), (not mincloser and abs(cmaxy.x-bminx)<cutlimit and abs(cmaxy.x-bmaxx)<cutlimit)) if mincloser and cminy.x-bminx>-cutlimit and cminy.x-bmaxx<cutlimit: # Don't cut on the wrong side of a hole-delimitor. fy1,fy2 = vec3(*floors[i][-1]),vec3(*floors[i][-2]) if i in hole_delimitors and ((cminy-fy1).length2()<(bmin-fy1).length2() or (cminy-fy2).length2()<(bmin-fy2).length2()): print('SKIPPING %i due to hole_delimitor.' % i) continue print('NOT HOLE DELIMITOR:', hole_delimitors, fy1,fy2) d = bmin.y-cminy.y if d > 0.1: print('HERE MIN Y!', d) if cs.x > cs.y: a = vec3(0,1,0).angle_z(ciq*vec3(1,0,0)) v = ciq*vec3(d/cos(a),0,0) print('vector is:', v, v.length(), ciq*vec3(1,0,0), d, a, cos(a)) cs.x -= v.length() cc += v/2 print('SHOTENED 1!', i, boxes[i]) boxes[i] = ciq,tuple(cc),tuple(cs) print('SHOTENED 2!', i, boxes[i]) print('shortener was', j) shortened_count += 1 break ## elif not mincloser and cmaxy.x-bminx>-cutlimit and cmaxy.x-bmaxx<cutlimit: ## d = cmaxy.y-bmax.y ## if d > 0.1: ## print('HERE MAX Y!', d) ## if cs.x > cs.y: ## a = vec3(0,1,0).angle_z(ciq*vec3(1,0,0)) ## v = ciq*vec3(d/cos(a),0,0) ## print('vector is:', v, v.length(), ciq*vec3(1,0,0)) ## cs.x -= v.length() ## cc -= v/2 ## print('SHOTENED 3!', i, boxes[i]) ## boxes[i] = ciq,tuple(cc),tuple(cs) ## print('SHOTENED 4!', i, boxes[i]) ## print('shortener was', j) ## shortened_count += 1 ## break return shortened_count
def get_neighbour_crds(shape,crds,isset=True,allowz=True): crds = crds if hasattr(crds,'__contains__') else [crds] ns = set() for crd in crds: if crd.x&1 == 0 and crd.y&1 == 0: # Left triangle (0). neighbours = [vec3(+1, 0,0),vec3( 0,+1,0),vec3(-1,+1,0)] elif crd.x&1 == 1 and crd.y&1 == 0: # Top triangle (1). neighbours = [vec3(-1, 0,0),vec3( 0,+1,0),vec3(-1,-1,0)] elif crd.x&1 == 1 and crd.y&1 == 1: # Right triangle (2). neighbours = [vec3( 0,-1,0),vec3(-1, 0,0),vec3(+1,-1,0)] else: # Bottom triangle (3). neighbours = [vec3( 0,-1,0),vec3(+1, 0,0),vec3(+1,+1,0)] if allowz: neighbours += [vec3(0,0,-1),vec3(0,0,+1)] neighbours = [crd+d for d in neighbours] neighbours = [c for c in neighbours if _validcrd(shape,c) and c not in ns] ns.update([c for c in neighbours if hastri(shape,c)==isset]) return ns
def _midcoords(p1,p2): z = (p1.z+p2.z)/2 s,t = vec3(p1).with_z(z),vec3(p2).with_z(z) return s.with_x((s.x+t.x)/2), s.with_y((s.y+t.y)/2), t.with_x((s.x+t.x)/2), t.with_y((s.y+t.y)/2)
def createmesh(vertices,triangles): return GfxMesh(quat(),vec3(),vertices,triangles),[PhysMesh(quat(),vec3(),vertices[:],triangles[:])]
def get_tri_crds(shape): for z,layer in enumerate(shape.triangles): for y,row in enumerate(layer): for x,t in enumerate(row): if t: yield vec3(x,y,z)
def idx2crd(size,idx): z = idx//(size.x*size.y) idx %= size.x*size.y y = idx//size.x x = idx%size.x return vec3(x,y,z)
def get_topmost_tri(shape): for z,layer in enumerate(shape.triangles): for y,row in enumerate(layer): for x,t in enumerate(row): if t: return vec3(x,y,z)