"""Editable low-poly shapes used for direct Phase-3 icon matching. All dimensions are metres, Blender Z-up and -Y forward. Geometry stays solid, faceted and separated by meaningful part names; there are no image billboards. """ from math import sin, cos, pi from mathutils import Vector from common import mesh_object, ellipsoid, cylinder, beam, ring def r_plate(name, loc, outline, depth, mat, bulge=.035): """Solid X/Z silhouette with a faceted front and an editable local origin.""" area = sum(a[0]*b[1]-b[0]*a[1] for a,b in zip(outline, outline[1:]+outline[:1])) if area < 0: outline = list(reversed(outline)) n = len(outline) v = [(x,-depth/2,z) for x,z in outline] + [(x,depth/2,z) for x,z in outline] v.append((sum(p[0] for p in outline)/n, -depth/2-bulge, sum(p[1] for p in outline)/n)) f = [(2*n,i,(i+1)%n) for i in range(n)] f.append(tuple(range(2*n-1,n-1,-1))) f += [(i,i+n,(i+1)%n+n,(i+1)%n) for i in range(n)] obj=mesh_object(name,v,f,mat) obj.location=loc return obj def r_block(name, loc, size, mat, cut=.16): w,d,h=size x,z=w/2,h/2 c=min(w,h)*cut outline=[(-x+c,-z),(x-c,-z),(x,-z+c),(x,z-c), (x-c,z),(-x+c,z),(-x,z-c),(-x,-z+c)] return r_plate(name,loc,outline,d,mat,min(d*.10,.035)) def r_rock(name, loc, size, mat, twist=0): obj=ellipsoid(name,loc,size,mat,subdivisions=1) obj.rotation_euler=(.12+twist*.2,twist*.16,twist) return obj def r_sweep(name, points, radii, mat, sides=6, ribbon=False): """A single bent solid, tapered to a true pole for claws/flames/roots.""" vertices,loops,faces=[],[],[] previous_u=None for i,(pos,radius) in enumerate(zip(points,radii)): pos=Vector(pos) tangent=Vector(points[min(i+1,len(points)-1)])-Vector(points[max(i-1,0)]) tangent.normalize() if previous_u is None: guide=Vector((0,1,0)) if ribbon else min([Vector((1,0,0)),Vector((0,1,0)),Vector((0,0,1))],key=lambda v:abs(tangent.dot(v))) if abs(tangent.dot(guide))>.98: guide=Vector((1,0,0)) u=tangent.cross(guide).normalized() else: u=(previous_u-tangent*previous_u.dot(tangent)).normalized() if u.length<.001: guide=min([Vector((1,0,0)),Vector((0,1,0)),Vector((0,0,1))],key=lambda v:abs(tangent.dot(v))) u=tangent.cross(guide).normalized() previous_u=u v=tangent.cross(u) if radius==0: loops.append([len(vertices)]) vertices.append(pos) else: loop=[] for k in range(sides): angle=2*pi*k/sides loop.append(len(vertices)) vertices.append(pos+radius*(u*cos(angle)+v*sin(angle)*(.22 if ribbon else 1))) loops.append(loop) if len(loops[0])>1: faces.append(tuple(reversed(loops[0]))) for lower,upper in zip(loops,loops[1:]): for k in range(sides): j=(k+1)%sides if len(upper)==1: faces.append((lower[k],lower[j],upper[0])) elif len(lower)==1: faces.append((lower[0],upper[j],upper[k])) else: # Curving rings do not make planar quads; give each triangle its own normal. faces.extend([(lower[k],lower[j],upper[j]),(lower[k],upper[j],upper[k])]) if len(loops[-1])>1: faces.append(tuple(loops[-1])) origin=Vector(points[0]) obj=mesh_object(name,[p-origin for p in vertices],faces,mat) obj.location=origin return obj def r_arch_shell(name, loc, profile, mat, segments=12): """Solid rear 210-degree bell shell, with actual open front and closed cut edges.""" area=sum(a[0]*b[1]-b[0]*a[1] for a,b in zip(profile,profile[1:]+profile[:1])) if area<0: profile=list(reversed(profile)) verts=[] for radius,z in profile: for k in range(segments+1): a=-pi/12+7*pi/6*k/segments verts.append((radius*cos(a),radius*sin(a),z)) n=segments+1 faces=[] for i in range(len(profile)): j=(i+1)%len(profile) for k in range(segments): faces.append((i*n+k,i*n+k+1,j*n+k+1,j*n+k)) faces.append(tuple(i*n for i in range(len(profile)))) faces.append(tuple(i*n+segments for i in reversed(range(len(profile))))) obj=mesh_object(name,verts,faces,mat) obj.location=loc return obj def r_lathe(name, loc, profile, mat, axis=(0,0,1), segments=12): """Closed lathed profile; supports bowls, bells and their real inner walls.""" w=Vector(axis).normalized() guide=Vector((0,0,1)) if abs(w.z)<.9 else Vector((0,1,0)) u=guide.cross(w).normalized() v=w.cross(u) verts,loops,faces=[],[],[] for radius,z in profile: loop=[] for k in range(segments if radius else 1): a=2*pi*k/segments loop.append(len(verts)) verts.append(w*z+radius*(u*cos(a)+v*sin(a))) loops.append(loop) for lower,upper in zip(loops,loops[1:]+loops[:1]): if len(lower)==len(upper)==1: continue for k in range(segments): j=(k+1)%segments if len(lower)==1: faces.append((lower[0],upper[j],upper[k])) elif len(upper)==1: faces.append((lower[k],lower[j],upper[0])) else: faces.append((lower[k],lower[j],upper[j],upper[k])) obj=mesh_object(name,verts,faces,mat) obj.location=loc return obj def r_band(name, start, end, radius, mat, sides=8): return beam(name,start,end,radius,mat,vertices=sides) def r_disc(name, loc, radius, depth, mat): return cylinder(name,loc,radius,depth,mat,vertices=10,rotation=(pi/2,0,0)) def r_inset(name, loc, outline, depth, rim, face): """Raised face inset in a silhouette-shaped metal border.""" r_plate(name+'_Rim',loc,outline,depth,rim) r_plate(name+'_Face',(loc[0],loc[1]-depth*.55,loc[2]), [(x*.82,z*.88) for x,z in outline],depth*.28,face) def r_sun(name, loc, radius, m, rays=8): x,y,z=loc r_disc(name+'_Ring',loc,radius,.04,m['accent']) r_disc(name+'_Face',(x,y-.027,z),radius*.72,.035,m['secondary']) for k in range(rays): a=2*pi*k/rays r_sweep(name+'_Ray'+str(k),[(x+radius*.88*cos(a),y,z+radius*.88*sin(a)), (x+radius*1.42*cos(a),y,z+radius*1.42*sin(a))], [radius*.14,0],m['accent'],4)