saumwacht-godot/assets-source/phase4/blender/reference_high_detail.py
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:00:36 +02:00

652 lines
38 KiB
Python

"""Phase 7: authored high-detail basalt/metal figures with real layered geometry.
Blender Z up, -Y forward. All pieces retain stable semantic names for Godot's
rigid-joint animation. Bevels, armor overlaps, joints, fingers and ornaments
are modeled geometry; no subdivision-only inflation of the former silhouettes.
"""
from math import pi, sin, cos
import hashlib
import random
import bpy
import bmesh
from mathutils import Matrix, Vector
from common import box, ellipsoid, cylinder, beam, ring, mesh_object, srgb_to_linear
from reference_geometry import r_plate, r_block, r_rock, r_sweep, r_lathe, r_arch_shell
def hd_finish(obj, width=.012, segments=3, smooth=False):
"""Bake scale before bevel so round-overs have consistent physical width."""
obj.data.transform(Matrix.Diagonal((*obj.scale, 1)))
obj.scale = (1,1,1)
bpy.context.view_layer.objects.active = obj
if width:
mod = obj.modifiers.new('Crafted_edge_radius','BEVEL')
mod.width = width
mod.segments = segments
mod.limit_method = 'ANGLE'
mod.angle_limit = .32
mod.use_clamp_overlap = True
bpy.ops.object.modifier_apply(modifier=mod.name)
for face in obj.data.polygons:
face.use_smooth = smooth or len(face.vertices) == 4
if width or smooth:
normal = obj.modifiers.new('Weighted_surface_normals','WEIGHTED_NORMAL')
normal.keep_sharp = True
normal.weight = 50
bpy.ops.object.modifier_apply(modifier=normal.name)
return obj
def hd_box(name, loc, size, mat, radius=.012, rotation=None):
return hd_finish(box(name,loc,size,mat,rotation),radius)
def hd_plate(name,loc,outline,depth,mat,radius=.01,bulge=.0):
return hd_finish(r_plate(name,loc,outline,depth,mat,bulge),radius)
def hd_rock(name,loc,size,mat,seed=0):
obj=ellipsoid(name,loc,size,mat,subdivisions=2)
rng=random.Random(name+str(seed))
# Separate broad chisel facets with chipped edges, not a smooth sphere.
for v in obj.data.vertices:
v.co *= rng.uniform(.94,1.045)
obj.rotation_euler=(.07*seed,.12*seed,.17*seed)
return hd_finish(obj,.009,2)
def hd_rod(name,a,b,radius,mat,sides=20):
return hd_finish(beam(name,a,b,radius,mat,sides),min(radius*.18,.007),2,True)
def hd_ring(name,loc,radius,thickness,mat,rotation=None,segments=40):
obj=ring(name,loc,radius,thickness,mat,rotation,segments)
for face in obj.data.polygons: face.use_smooth=True
return obj
def hd_disc(name,loc,radius,depth,mat):
return hd_finish(cylinder(name,loc,radius,depth,mat,32,(pi/2,0,0)),.006,3,True)
def hd_curve(name,points,radii,mat,sides=12,steps=5):
"""Catmull-Rom centerline and tapered cross sections for claws and cables."""
pts=[Vector(p) for p in points]
positions=[]; widths=[]
for i in range(len(pts)-1):
p0,p1,p2,p3=pts[max(0,i-1)],pts[i],pts[i+1],pts[min(len(pts)-1,i+2)]
for k in range(steps):
t=k/steps
p=.5*((2*p1)+(-p0+p2)*t+(2*p0-5*p1+4*p2-p3)*t*t+(-p0+3*p1-3*p2+p3)*t*t*t)
positions.append(tuple(p)); widths.append(radii[i]*(1-t)+radii[i+1]*t)
positions.append(tuple(pts[-1]));widths.append(radii[-1])
obj=r_sweep(name,positions,widths,mat,sides)
for face in obj.data.polygons:face.use_smooth=True
return obj
def hd_materials(m):
"""A small shared PBR palette; values are linear RGB for glTF."""
definitions={
'basalt':('41434B',.05,.77,0), 'edge':('62636A',.10,.71,0),
'dark':('171A21',.15,.66,0), 'gold':('C99645',.78,.42,0),
'gold_light':('F1CA7C',.68,.39,0), 'ember':('FF5210',.05,.40,1.25),
'hot':('FFC65E',.0,.36,2.2), 'ceramic':('D8CDB2',.10,.37,0),
'bone':('BAC8CF',.16,.55,0), 'rift':('CE609B',.10,.35,2.0),
}
for key,(hexcolor,metal,rough,emission) in definitions.items():
name='SW_HD_'+key
mat=bpy.data.materials.new(name)
mat['material_name']=name
c=tuple(srgb_to_linear(int(hexcolor[i:i+2],16)/255) for i in (0,2,4))
mat.diffuse_color=(*c,1);mat.use_nodes=True
node=mat.node_tree.nodes.get('Principled BSDF')
node.inputs['Base Color'].default_value=(*c,1)
node.inputs['Metallic'].default_value=metal
node.inputs['Roughness'].default_value=rough
if emission:
node.inputs['Emission Color'].default_value=(*c,1)
node.inputs['Emission Strength'].default_value=emission
m[key]=mat
return m
def hd_rivet(name,loc,m,r=.018):
hd_disc(name+'_Seat',loc,r*1.5,.009,m['dark'])
hd_disc(name+'_Head',(loc[0],loc[1]-.009,loc[2]),r,.018,m['gold_light'])
def hd_trim(name,points,m,r=.012):
return hd_curve(name,points,[r]*len(points),m['gold_light'],8,2)
def hd_sun(name,loc,r,m):
x,y,z=loc
hd_disc(name+'_Base',(x,y,z),r,.024,m['gold'])
hd_disc(name+'_Face',(x,y-.020,z),r*.68,.018,m['dark'])
hd_disc(name+'_Heart',(x,y-.033,z),r*.43,.022,m['gold_light'])
for i in range(12):
a=i*pi/6
hd_plate(name+'_Ray'+str(i),(x,y-.032,z),[(r*.64*cos(a-.13),r*.64*sin(a-.13)),
(r*.96*cos(a),r*.96*sin(a)),(r*.64*cos(a+.13),r*.64*sin(a+.13))],.016,m['gold_light'],.002)
def hd_legs(m,spread=.30,height=.50,slim=False):
radius=.070 if slim else .13
for s,side in [(-1,'L'),(1,'R')]:
n='Leg'+side
hip=(s*spread*.8,.055,height)
knee=(s*spread,.06,height*.53)
ankle=(s*spread,-.035,.13)
hd_rod(n+'_InnerThigh',hip,knee,radius*.64,m['dark'])
hd_rod(n+'_InnerShin',knee,ankle,radius*.64,m['dark'])
hd_rock(n+'_ThighArmor',tuple((Vector(hip)+Vector(knee))*.5),(radius*1.24,radius*.90,height*.24),m['basalt'],s)
hd_rock(n+'_ShinArmor',tuple((Vector(knee)+Vector(ankle))*.5),(radius*1.10,radius,height*.22),m['basalt'],2*s)
for p,label in [(hip,'Hip'),(knee,'Knee'),(ankle,'Ankle')]:
hd_disc(n+'_'+label+'Pivot',(p[0],p[1]-radius*.8,p[2]),radius*.72,.055,m['gold'])
hd_disc(n+'_'+label+'Pin',(p[0],p[1]-radius*1.12,p[2]),radius*.36,.022,m['dark'])
hd_disc(n+'_'+label+'Rim',(p[0],p[1]-radius*1.27,p[2]),radius*.16,.025,m['gold_light'])
# Three individually modeled tapered foot/toe plates.
for j in range(3):
x=ankle[0]+(j-1)*radius*.70
toe=[(-radius*.40,-.045),(radius*.40,-.045),(radius*.30,.060),(0,.090),(-radius*.32,.060)]
hd_plate(n+'_Toe'+str(j),(x,-.135,.065),toe,.26 if not slim else .20,m['basalt'],.013)
hd_box(n+'_ToeCap'+str(j),(x,-.225,.075),(radius*.62,.045,.048),m['gold'],.008)
hd_box(n+'_AnkleCuff',(ankle[0],-.05,.19),(radius*2.65,.22,.065),m['gold'],.012)
if not slim:
hd_plate(n+'_KneeGuard',(knee[0],-.103,knee[2]),[(-.10,.09),(.10,.09),(.085,-.08),(0,-.15),(-.085,-.08)],.075,m['edge'],.014)
else:
hd_plate(n+'_ShinGold',(knee[0],-.095,knee[2]-.10),[(-.057,.14),(.057,.14),(.026,-.11),(0,-.21)],.046,m['gold'],.005)
hd_plate(n+'_ShinEmber',(knee[0],-.126,knee[2]-.11),[(-.018,.08),(.018,.08),(0,-.14)],.015,m['ember'],.002)
def hd_arm(name,shoulder,elbow,hand,m,radius=.16,fingers=True):
hd_rod(name+'_UpperCore',shoulder,elbow,radius*.56,m['dark'])
hd_rod(name+'_ForearmCore',elbow,hand,radius*.64,m['dark'])
hd_rock(name+'_Elbow',elbow,(radius*.90,)*3,m['basalt'],2)
hd_rock(name+'_UpperArmor',tuple((Vector(shoulder)+Vector(elbow))*.5),(radius*1.08,radius*.88,Vector(shoulder).lerp(Vector(elbow),.5).z*.17),m['basalt'],1)
hd_rock(name+'_ForearmArmor',tuple((Vector(elbow)+Vector(hand))*.5),(radius*1.20,radius,Vector(elbow).lerp(Vector(hand),.5).z*.28),m['basalt'],3)
hd_rock(name+'_Shoulder',shoulder,(radius*1.48,radius*1.24,radius*1.15),m['basalt'],2)
hd_disc(name+'_ShoulderJoint',(shoulder[0],shoulder[1]-radius*.85,shoulder[2]),radius*.66,.052,m['gold'])
for t,label in [(.58,'UpperBand'),(.85,'WristBand')]:
p=Vector(elbow).lerp(Vector(hand),t)
d=(Vector(hand)-Vector(elbow)).normalized()*.040
hd_rod(name+'_'+label,tuple(p-d),tuple(p+d),radius*1.27,m['gold'],24)
hd_rivet(name+'_'+label+'Rivet',(p.x,p.y-radius*1.25,p.z),m)
hd_rock(name+'_Palm',hand,(radius*1.15,radius*.81,radius*1.06),m['basalt'],4)
if fingers:
for j in range(4):
x=hand[0]+(j-1.5)*radius*.46
for k in range(2):
hd_box(name+'_Finger%d_%d'%(j,k),(x,hand[1]-.09-k*.04,hand[2]-.045-k*.07),
(radius*.40,radius*.53,radius*.55),m['edge'] if k==0 else m['basalt'],radius*.08)
s=-1 if hand[0]<0 else 1
hd_rock(name+'_Thumb',(hand[0]-s*radius,hand[1]-.025,hand[2]-.04),(radius*.46,radius*.62,radius*.76),m['basalt'],1)
def hd_tower(name,loc,size,m):
x,y,z=loc;w,d,h=size
hd_box(name+'_StoneKeep',(x,y,z),(w,d,h),m['basalt'],.018)
hd_box(name+'_LowerCornice',(x,y,z-h*.31),(w*1.04,d*1.04,.045),m['gold'],.008)
hd_box(name+'_UpperCornice',(x,y,z+h*.34),(w*1.055,d*1.055,.045),m['edge'],.008)
for j in range(3):
px=x+(j-1)*w*.26
hd_box(name+'_WindowRecess'+str(j),(px,y-d*.5-.014,z),(w*.12,.018,h*.42),m['dark'],.006)
hd_box(name+'_WindowFire'+str(j),(px,y-d*.5-.026,z),(w*.052,.009,h*.30),m['ember'],.003)
hd_box(name+'_WindowLintel'+str(j),(px,y-d*.5-.020,z+h*.25),(w*.19,.07,.028),m['edge'],.003)
for k,(dx,dy) in enumerate([(-1,-1),(1,-1),(-1,1),(1,1)]):
hd_box(name+'_Merlon'+str(k),(x+dx*w*.35,y+dy*d*.34,z+h*.55),(w*.29,d*.31,h*.29),m['basalt'],.010)
hd_box(name+'_MerlonCap'+str(k),(x+dx*w*.35,y+dy*d*.34,z+h*.70),(w*.31,d*.33,.032),m['edge'],.004)
def hd_tank(up,m):
hd_legs(m,.295,.46)
hd_rock('Body_ArmoredCore',(0,.04,.82),(.42,.31,.48),m['dark'],1)
# Broad tapered chest formed from five slabs, with a narrow recessed furnace.
for s,side in [(-1,'L'),(1,'R')]:
outline=[(s*.045,.45),(s*.34,.40),(s*.41,.20),(s*.34,-.27),(s*.05,-.46)]
hd_plate('Body_Chest'+side,(0,-.28,.84),outline,.22,m['basalt'],.020,.018)
chest_pieces=[[(.078,.40),(.28,.365),(.335,.21),(.18,.075),(.083,.17)],
[(.19,.045),(.337,.17),(.298,-.12),(.21,-.18),(.067,-.052),(.087,.125)],
[(.070,-.094),(.215,-.217),(.286,-.162),(.222,-.30),(.068,-.385)]]
for j,outline_piece in enumerate(chest_pieces):
hd_plate('Body_ChestRelief'+side+str(j),(0,-.411-j*.006,.84),[(s*x,z) for x,z in outline_piece],.066,m['basalt'] if j%2 else m['edge'],.007,.021)
hd_trim('Body_Border'+side,[(s*.05,-.437,.40),(s*.36,-.407,.62),(s*.40,-.385,1.17),(s*.27,-.394,1.28)],m,.021)
# Chisel seams divide the armor into larger readable stone layers.
hd_curve('Body_Seam'+side,[(s*.065,-.418,.72),(s*.18,-.410,.87),(s*.12,-.409,1.01),(s*.32,-.404,1.14)],[.009]*4,m['dark'],6,1)
hd_arm('Arm'+side,(s*.49,.0,1.04),(s*.62,-.01,.76),(s*.65,-.14,.46),m,.155)
hd_plate('Arm'+side+'_Pauldron',(s*.48,-.105,1.12),[(-.21,.05),(-.13,.17),(.13,.17),(.21,.04),(.15,-.15),(-.15,-.15)],.34,m['gold'],.016)
hd_plate('Arm'+side+'_PauldronStone',(s*.48,-.139,1.16),[(-.17,.03),(-.10,.14),(.10,.14),(.17,.03),(.11,-.10),(-.11,-.10)],.33,m['basalt'],.014)
if up:
hd_tower('Arm'+side+'_Castle',(s*.48,.01,1.31),(.38,.40,.29),m)
hd_tower('Arm'+side+'_Bastion',(s*.65,-.08,.68),(.29,.32,.24),m)
for z in [.59,1.19]:
hd_rivet('Body_'+side+'Fastener'+str(z),(s*.31,-.414,z),m,.023)
hd_box('Head_FurnaceRecess',(0,-.402,1.09),(.095,.055,.32),m['dark'],.007)
hd_box('Head_Fire',(0,-.432,1.10),(.039,.025,.258),m['ember'],.008)
hd_box('Head_FireHeart',(0,-.446,1.10),(.012,.005,.19),m['hot'],.002)
for s in [-1,1]:
hd_box('Body_CenterGold'+str(s),(s*.061,-.44,1.02),(.023,.034,.51),m['gold'],.006)
hd_plate('Body_BottomGold',(0,-.443,.70),[(-.072,.05),(.072,.05),(0,-.13)],.030,m['gold'],.005)
if up:
hd_tower('Head_Crown',(0,.01,1.46),(.28,.31,.31),m)
hd_box('Body_SpineGold',(0,-.446,.61),(.025,.040,.26),m['gold'],.005)
def hd_coal(up,m):
hd_legs(m,.25,.39)
hd_rock('Body_LavaCore',(0,.025,.76),(.375,.27,.39),m['ember'],2)
# Interlocking shell plates leave true gaps above an orange inner body.
for j,z in enumerate([.53,.81,1.07]):
for k in range(7):
a=2*pi*k/7+.20*(j%2)
if sin(a)<-.42:continue
pos=(.33*cos(a),.24*sin(a),z)
hd_rock('Body_Shell%d_%d'%(j,k),pos,(.19,.135,.205),m['basalt'],j*3+k)
hd_rock('Head_JawCavity',(0,-.245,.77),(.37,.15,.235),m['dark'],0)
hd_plate('Head_MouthEmber',(0,-.345,.78),[(-.30,.07),(-.17,.10),(0,.02),(.17,.10),(.30,.07),(.24,-.08),(0,-.15),(-.24,-.08)],.028,m['ember'],.008)
for s,side in [(-1,'L'),(1,'R')]:
hd_rock('Head_Brow'+side,(s*.19,-.26,1.025),(.22,.17,.145),m['basalt'],s*2)
hd_curve('Head_Eye'+side,[(s*.29,-.419,1.02),(s*.18,-.433,.99),(s*.09,-.413,.97)],[.012,.019,.007],m['hot'],8,3)
hd_rock('Head_Cheek'+side,(s*.32,-.24,.82),(.15,.13,.22),m['basalt'],4*s)
for j in range(4):
x=s*(.035+j*.07)
hd_curve('Head_UpperTooth'+side+str(j),[(x,-.32,.96-j*.016),(x,-.405,.85-j*.016),(x-s*.010,-.413,.80-j*.012)],[.055,.029,0],m['edge'],8,3)
hd_curve('Head_LowerTooth'+side+str(j),[(x,-.28,.54+j*.02),(x,-.38,.67+j*.01),(x,-.40,.72+j*.01)],[.057,.027,0],m['basalt'],8,3)
hd_arm('Arm'+side,(s*.44,.03,.98),(s*.54,-.015,.72),(s*.57,-.16,.52),m,.14,False)
for j in range(3):
x=s*.58+(j-1)*.14
hd_curve('Weapon'+side+'_Claw'+str(j),[(x,-.20,.60),(x+s*.035,-.37,.48),(x+s*.01,-.48,.26),(x-s*.070,-.41,.18)],
[.10 if up else .075,.095 if up else .068,.045,0],m['gold'],16,6)
hd_rivet('Arm'+side+'_Knuckle'+str(j),(x,-.27,.63),m,.021)
hd_plate('Arm'+side+'_ShoulderTrim',(s*.42,-.125,1.085),[(-.10,.04),(.10,.04),(.08,-.08),(0,-.12),(-.08,-.08)],.04,m['gold'],.008)
for k in range(5 if up else 3):
x=(k-(2 if up else 1))*.15
hd_curve('Body_DorsalSpine'+str(k),[(x,.12,1.02),(x*1.12,.13,1.26+(.10 if k%2==0 else 0)),(x*.95,.10,1.43 if up else 1.32)], [.125,.085,0],m['basalt'],9,3)
hd_rock('Head_Chin',(0,-.20,.53),(.24,.19,.12),m['basalt'],5)
def hd_lancer(m):
hd_legs(m,.23,.74,True)
hd_rod('Body_Spine',(0,.045,.61),(0,.03,1.24),.085,m['dark'])
for s,side in [(-1,'L'),(1,'R')]:
hd_plate('Body_Cuirass'+side,(0,-.025,1.07),[(s*.025,.17),(s*.23,.13),(s*.16,-.13),(s*.025,-.22)],.20,m['gold'],.011)
hd_plate('Body_ChestStone'+side,(0,-.071,1.08),[(s*.048,.13),(s*.18,.10),(s*.12,-.10),(s*.048,-.15)],.16,m['basalt'],.008)
hd_arm('Arm'+side,(s*.24,.01,1.21),(s*.33,-.02,1.03),(s*.30,-.23,.96),m,.065)
hd_plate('Arm'+side+'_ShoulderFin',(s*.28,0,1.23),[(s*-.07,-.07),(s*.20,-.035),(s*.03,.16)],.15,m['gold'],.007)
hd_plate('Body_EmberHeart',(0,-.162,1.03),[(-.028,.10),(.028,.10),(0,-.13)],.019,m['ember'],.003)
hd_rod('Head_Neck',(0,.03,1.21),(0,.03,1.37),.06,m['dark'])
hd_plate('Head_Helmet',(0,-.01,1.47),[(-.19,.105),(0,-.005),(.19,.105),(.072,-.09),(0,-.21),(-.072,-.09)],.22,m['gold'],.008)
hd_plate('Head_Visor',(0,-.135,1.40),[(-.052,.018),(.052,.018),(0,-.116)],.017,m['ember'],.003)
hd_curve('Head_Crest',[(0,.07,1.49),(0,.12,1.64),(-.06,.11,1.76)],[.061,.034,0],m['gold_light'],10,4)
a=Vector((-.71,-.265,1.01)); b=Vector((.70,-.29,.94))
hd_rod('Weapon_LanceShaft',a,b,.046,m['dark'],24)
hd_rod('Weapon_LanceEmber',a+Vector((0,-.042,0)),b+Vector((0,-.042,0)),.011,m['ember'],10)
for k in range(11):
t=.05+k*.087;p=a.lerp(b,t)
hd_rod('Weapon_LanceCollar'+str(k),p-Vector((.012,0,0)),p+Vector((.012,0,0)),.065,m['gold'],24)
hd_curve('Weapon_SpearHead',[b,b+Vector((.14,0,-.005)),b+Vector((.46,0,-.02))],[.06,.092,0],m['ember'],8,3)
for s in [-1,1]:
hd_curve('Weapon_SpearEdge'+str(s),[b+Vector((0,0,s*.07)),b+Vector((.17,0,s*.085)),b+Vector((.47,0,-.02))],[.024,.020,0],m['gold_light'],8,3)
def hd_ballista(m):
hd_legs(m,.33,.62)
hd_box('Body_Gearbox',(0,.02,.66),(.49,.43,.26),m['basalt'],.035)
for s,side in [(-1,'L'),(1,'R')]:
hd_disc('Body_Crank'+side,(s*.23,-.21,.73),.16,.055,m['gold'])
hd_disc('Body_CrankInset'+side,(s*.23,-.247,.73),.10,.018,m['dark'])
for k in range(8):
a=k*pi/4
hd_box('Body_CrankTooth'+side+str(k),(s*.23+.137*cos(a),-.212,.73+.137*sin(a)),(.055,.067,.052),m['gold'],.006,rotation=(0,a,0))
hd_box('Weapon_Rail'+side,(s*.13,-.11,.99),(.095,1.15,.14),m['basalt'],.018)
hd_box('Weapon_RailTrim'+side,(s*.13,-.11,1.068),(.052,1.17,.020),m['gold'],.005)
bow=[(s*.11,-.46,1.00),(s*.39,-.47,.97),(s*.67,-.31,1.01),(s*.80,.045,1.12)]
hd_curve('Weapon_GlassLimb'+side,bow,[.095,.12,.095,.051],m['ember'],12,7)
hd_curve('Weapon_LimbEdge'+side,[(x,y,z+.068) for x,y,z in bow],[.022,.025,.022,.017],m['gold'],10,7)
hd_curve('Weapon_DrawCable'+side,[bow[-1],(s*.37,.26,1.09),(0,.45,1.075)],[.011,.011,.011],m['dark'],10,3)
hd_rod('Weapon_LimbClamp'+side,(s*.18,-.52,.95),(s*.18,-.39,1.055),.089,m['gold'],12)
hd_box('Weapon_RearHousing',(0,.46,1.06),(.38,.25,.29),m['basalt'],.023)
hd_box('Weapon_RearTrim',(0,.40,1.08),(.42,.055,.33),m['gold'],.012)
hd_rod('Weapon_CentralBolt',(0,.38,1.085),(0,-.70,1.085),.035,m['ember'],16)
hd_curve('Weapon_BoltHead',[(0,-.62,1.085),(0,-.76,1.085),(0,-1.00,1.085)],[.040,.085,0],m['ember'],8,3)
for k in range(6):
hd_box('Weapon_RailRivet'+str(k),(.17,-.54+k*.18,1.10),(.025,.025,.026),m['gold_light'],.005)
def hd_mortar(up,m):
hd_legs(m,.29,.42)
hd_rock('Body_LowerCore',(0,.035,.60),(.38,.28,.25),m['basalt'],3)
radius=.49 if up else .435
# Closed outer/inner profile: deep bore with individual radial barrel staves.
profile=[(0,0),(.22,.025),(radius*.83,.24),(radius,.64),(radius*.85,.68),(radius*.74,.60),(radius*.48,.16),(0,.14)]
obj=r_lathe('Body_MortarBore',(0,.07,.51),profile,m['dark'],segments=48)
hd_finish(obj,.006,2,True)
for k in range(12):
a=2*pi*k/12
x=radius*.80*cos(a);y=.07+radius*.80*sin(a)
o=hd_rock('Body_MortarStave'+str(k),(x,y,.86),(.15,.13,.29),m['basalt'],k)
o.rotation_euler[2]=a
p=(radius*.98*cos(a),.07+radius*.98*sin(a),1.125)
hd_box('Body_RimClasp'+str(k),p,(.115,.13,.15),m['gold'],.014,rotation=(0,0,a-pi/2))
hd_ring('Body_MortarLip',(0,.07,1.175),radius*.91,.033,m['gold_light'],segments=64)
hd_ring('Body_MortarBelt',(0,.07,.68),radius*.72,.027,m['gold'],segments=48)
pool_z=1.015 if up else .79
hd_finish(cylinder('Weapon_LavaPool',(0,.07,pool_z),radius*.68,.038,m['ember'],48),.005,2,True)
if not up:
hd_rock('Weapon_EmberBoulder',(0,.02,.87),(.18,.18,.17),m['ember'],1)
for k in range(7):
a=k*2*pi/7
hd_rock('Weapon_BoulderCrust'+str(k),(.14*cos(a),.02+.14*sin(a),.89),(.074,.068,.10),m['basalt'],k)
else:
for k in range(3):
hd_curve('Weapon_LavaEddy'+str(k),[(.26*cos(a+k),.07+.26*sin(a+k),pool_z+.026) for a in [0,.6,1.1,1.8,2.4]], [.009]*5,m['hot'],8,3)
for s,side in [(-1,'L'),(1,'R')]:
hd_arm('Arm'+side,(s*.46,.055,.99),(s*.59,-.04,.73),(s*.43,-.29,.52),m,.155)
hd_plate('Body_FrontBadge',(0,-.355,.83),[(-.15,.17),(.15,.17),(.11,-.12),(0,-.25),(-.11,-.12)],.060,m['gold'],.009)
hd_plate('Body_FrontBadgeStone',(0,-.397,.84),[(-.10,.13),(.10,.13),(.070,-.10),(0,-.18),(-.070,-.10)],.029,m['basalt'],.006)
hd_plate('Body_FrontSigil',(0,-.42,.83),[(-.024,.09),(.025,.12),(.037,-.065),(0,-.14),(-.036,-.065)],.018,m['ember'],.003)
def hd_hearth(up,m):
hd_legs(m,.28,.45)
hd_rock('Body_HearthBack',(0,.13,.88),(.43,.32,.44),m['basalt'],1)
# Deep oven vault: an open front arch with a visible brick-lined interior.
hd_plate('Body_OvenDark',(0,-.20,.99),[(-.30,-.25),(-.30,.10),(-.20,.27),(0,.35),(.20,.27),(.30,.10),(.30,-.25)],.085,m['dark'],.016)
if not up:
for j in range(3):
for k in range(3):
hd_box('Body_HearthBrick%d_%d'%(j,k),((k-1)*.15,-.251,.83+j*.10),(.136,.024,.083),m['basalt'],.009)
for k in range(3):
x=(k-1)*.12
hd_curve('Flame_Oven'+str(k),[(x,-.29,.79),(x+.04,-.30,.93),(x-.015,-.30,1.06+(k%2)*.15)], [.068,.058,0],m['ember'],12,6)
for k in range(11):
a=pi*k/10
hd_box('Body_ArchStone'+str(k),(.335*cos(a),-.16,1.03+.335*sin(a)),(.125,.25,.19),m['ceramic'],.018,rotation=(0,pi/2-a,0))
for s in [-1,1]:
for j in range(2):
hd_box('Body_Pillar%d_%d'%(s,j),(s*.335,-.16,.85+j*.14),(.15,.25,.13),m['ceramic'],.015)
hd_box('Head_Chimney',(0,.045,1.42),(.35,.31,.25),m['ceramic'],.029)
hd_box('Head_ChimneyRecess',(0,-.119,1.41),(.21,.015,.10),m['dark'],.012)
hd_box('Head_ChimneyCap',(0,.04,1.56),(.40,.35,.07),m['ceramic'],.017)
else:
hd_disc('Body_SunSocket',(0,-.30,1.22),.25,.06,m['dark'])
hd_ring('Body_SunFrame',(0,-.36,1.22),.248,.042,m['gold'],(pi/2,0,0),64)
hd_rock('Body_SunHeart',(0,-.34,1.22),(.203,.092,.203),m['ember'],0)
for j in range(3):
points=[]
for k in range(10):
a=k*.30+j*2*pi/3
r=.018+k*.017
points.append((r*cos(a),-.443+.025*(r/.18)**2,1.22+r*sin(a)))
hd_curve('Flame_SolarEddy'+str(j),points,[.006]*10,m['hot'],8,3)
for k in range(9):
a=pi*k/8
hd_curve('Body_HaloRay'+str(k),[(.30*cos(a),-.13,1.22+.30*sin(a)),(.44*cos(a),-.13,1.22+.44*sin(a))],[.045,0],m['gold'],10,3)
for s,side in [(-1,'L'),(1,'R')]:
hd_plate('Body_Mantle'+side,(s*.40,.07,.99),[(s*-.06,.40),(s*.15,.32),(s*.19,-.24),(s*.10,-.35),(s*-.13,-.20)],.27,m['ceramic'],.020)
hd_sun('Body_MantleSun'+side,(s*.43,-.085,1.19),.082,m)
hd_plate('Body_Tabard'+side,(s*.40,-.16,.46),[(-.10,.20),(.10,.20),(.105,-.22),(0,-.32),(-.105,-.22)],.045,m['ceramic'],.011)
hd_sun('Body_TabardSun'+side,(s*.40,-.19,.42),.060,m)
r=.36 if up else .30
obj=r_lathe('Weapon_HearthBowl',(0,-.40,.54),[(0,0),(.15,.02),(r,.16),(r*1.04,.22),(r*.88,.23),(r*.81,.18),(.16,.06),(0,.06)],m['gold'],segments=48)
hd_finish(obj,.009,3,True)
hd_ring('Weapon_BowlRim',(0,-.40,.762),r*.97,.022,m['gold_light'],segments=48)
hd_finish(cylinder('Weapon_EmberBed',(0,-.40,.697),r*.79,.023,m['ember'],40),.005,2,True)
for k in range(5):
x=(k-2)*.085;h=.19+(.15 if k==2 else .06*(k%2))
hd_curve('Weapon_Flame'+str(k),[(x,-.40,.70),(x-.035,-.38,.70+h*.35),(x+.035,-.38,.70+h*.72),(x-.02,-.38,.70+h)], [.050,.049,.023,0],m['hot'] if k%2 else m['ember'],12,6)
for s,side in [(-1,'L'),(1,'R')]:
hd_arm('Arm'+side,(s*.42,.02,1.05),(s*.55,-.16,.80),(s*.34,-.41,.65),m,.15)
hd_sun('Arm'+side+'_Seal',(s*.45,-.155,1.06),.095,m)
hd_sun('Weapon_BowlSeal',(0,-.702,.65),.063,m)
if up:
hd_curve('Body_SolarStream',[(0,-.44,1.11),(.05,-.45,.98),(-.03,-.43,.83),(0,-.43,.72)],[.023,.024,.016,.013],m['hot'],12,6)
def build_high_glutpakt(spec,m):
hd_materials(m)
role=spec['role'];up=spec['upgraded']
if role=='melee':hd_coal(up,m)
elif role=='tank':hd_tank(up,m)
elif role=='ranged':hd_ballista(m) if up else hd_lancer(m)
elif role=='artillery':hd_mortar(up,m)
elif role=='support':hd_hearth(up,m)
def build_high_enemy(spec,m):
hd_materials(m)
id=spec['id']
if id in ('e05','e06','e09','e10'):
build_expedition_enemy(id,m)
return
if id in ('e01','e02'):
heavy=id=='e02'
hd_rock('Body_Core',(0,0,.52),(.44,.55,.30),m['dark'],2)
# Segmented back carapace, raised central ridge and six clawed legs.
for j in range(4):
y=-.34+j*.25
width=.46 if heavy else .40
hd_plate('Body_Carapace'+str(j),(0,y,.62),[(-width,0),(-width*.67,.23),(0,.35),(width*.67,.23),(width,0),(0,-.09)],.26,m['bone'] if j%2 else m['basalt'],.024)
hd_curve('Body_SeamGlow'+str(j),[(-width*.69,y-.145,.83),(0,y-.147,.95),(width*.69,y-.145,.83)],[.012,.015,.012],m['rift'],8,2)
if heavy:
hd_curve('Body_Spine'+str(j),[(0,y,.94),(0,y+.06,1.15),(0,y+.13,1.28)],[.10,.055,0],m['bone'],12,5)
hd_rock('Head_Skull',(0,-.65,.54),(.29,.20,.19),m['basalt'],3)
for s,side in [(-1,'L'),(1,'R')]:
for k in range(3):
y=-.38+k*.35;n='Leg'+side+str(k)
hip=(s*.30,y,.53);knee=(s*.61,y-.11,.36);foot=(s*.77,y-.27,.06)
hd_rod(n+'_Upper',hip,knee,.070 if heavy else .054,m['basalt'])
hd_rock(n+'_Joint',knee,(.085,)*3,m['bone'],k)
hd_curve(n+'_Shin',[knee,(s*.75,y-.20,.20),foot],[.076,.045,0],m['bone'],12,4)
hd_rod(n+'_Inset',tuple(Vector(hip)+Vector((0,-.04,.035))),tuple(Vector(knee)+Vector((0,-.04,.035))),.014,m['rift'],8)
hd_rock('Head_Eye'+side,(s*.115,-.816,.60),(.055,.021,.036),m['rift'],0)
hd_curve('Head_Mandible'+side,[(s*.19,-.74,.45),(s*.29,-.91,.40),(s*.17,-1.00,.44)],[.070,.046,0],m['bone'],12,6)
elif id=='e07':
hd_rock('Body_DeerCore',(0,.12,1.12),(.38,.62,.42),m['dark'],1)
for j in range(5):
y=-.38+j*.22
hd_rock('Body_BackArmor'+str(j),(0,y,1.37),(.40,.19,.21),m['basalt'],j)
for s,side in [(-1,'L'),(1,'R')]:
for j,y in enumerate([-.35,.57]):
n='Leg'+side+str(j)
hip=(s*.27,y,1.08); knee=(s*.36,y+.10,.60); foot=(s*.36,y-.02,.08)
hd_rod(n+'_Upper',hip,knee,.082,m['dark'])
hd_rock(n+'_Knee',knee,(.12,.13,.14),m['basalt'],s)
hd_rod(n+'_Shin',knee,foot,.060,m['bone'])
hd_box(n+'_Hoof',(foot[0],foot[1]-.04,.10),(.19,.27,.20),m['dark'],.025)
hd_box(n+'_ClovenLine',(foot[0],foot[1]-.179,.105),(.018,.012,.16),m['rift'],.004)
for j in range(4):
hd_curve('Body_Rib'+side+str(j),[(s*.12,-.16+j*.16,1.43),(s*.35,-.14+j*.16,1.26),(s*.32,-.13+j*.16,.99)],[.065,.074,.037],m['bone'],10,3)
hd_curve('Head_Antler'+side,[(s*.17,-.39,1.89),(s*.36,-.25,2.17),(s*.56,-.10,2.42),(s*.50,.05,2.61)],[.075,.065,.036,0],m['bone'],12,7)
for k in range(3):
z=2.05+k*.14;x=s*(.28+k*.13);y=-.30+k*.09
hd_curve('Head_AntlerProng'+side+str(k),[(x,y,z),(x+s*.19,y-.05,z+.15),(x+s*.20,y-.13,z+.28)],[.041,.028,0],m['bone'],10,5)
hd_rock('Head_Eye'+side,(s*.155,-.77,1.82),(.060,.041,.039),m['rift'],0)
hd_rock('Head_Neck',(0,-.36,1.54),(.23,.26,.37),m['basalt'],2)
hd_rock('Head_Skull',(0,-.58,1.79),(.25,.24,.25),m['bone'],4)
hd_rock('Head_Muzzle',(0,-.81,1.69),(.16,.24,.13),m['bone'],3)
hd_box('Head_Nose',(0,-1.01,1.69),(.21,.055,.12),m['dark'],.022)
hd_curve('Body_Tail',[(0,.70,1.20),(0,.89,1.05),(0,.95,.88)],[.075,.053,0],m['bone'],10,5)
else:
colossus=id=='e08'; ghost=id=='e03'
if colossus:
# A stone portcullis knight, with layered breastplates and battlements.
mm=dict(m);mm['gold']=m['bone'];mm['gold_light']=m['bone'];mm['ember']=m['rift'];mm['hot']=m['rift']
hd_tank(True,mm)
for s in [-1,1]:
hd_curve('Body_RiftHorn'+str(s),[(s*.30,.06,1.48),(s*.40,.03,1.73),(s*.25,.04,1.86)],[.095,.066,0],m['bone'],12,5)
return
hd_rock('Body_Torso',(0,.025,1.08),(.24,.17,.34),m['dark'],2)
for j in range(5):
z=.93+j*.09
for s,side in [(-1,'L'),(1,'R')]:
hd_curve('Body_Rib'+side+str(j),[(s*.04,-.12,z),(s*.20,-.12,z+.03),(s*.23,.025,z+.01)],[.025,.033,.022],m['bone'],10,3)
hd_rock('Head_Skull',(0,-.005,1.63),(.20,.145,.24),m['bone'],1)
hd_plate('Head_Face',(0,-.16,1.59),[(-.14,.08),(.14,.08),(.10,-.12),(0,-.18),(-.10,-.12)],.045,m['dark'],.012)
for s,side in [(-1,'L'),(1,'R')]:
hd_curve('Head_Eye'+side,[(s*.035,-.20,1.65),(s*.12,-.20,1.68)],[.020,.015],m['rift'],8,2)
hd_arm('Arm'+side,(s*.24,.0,1.29),(s*.38,-.09,1.09),(s*.43,-.31,1.15),m,.065)
if ghost:
for k in range(7):
a=2*pi*k/7
hd_curve('Body_Shroud'+str(k),[(.18*cos(a),.14*sin(a),1.0),(.25*cos(a+.3),.21*sin(a+.3),.59),(.14*cos(a+.6),.16*sin(a+.6),.18),(.30*cos(a+.8),.21*sin(a+.8),.04)],[.08,.09,.048,0],m['basalt'] if k%2 else m['dark'],12,6)
hd_ring('Body_RiftHalo',(0,.08,1.71),.31,.019,m['rift'],(pi/2,0,0),48)
else:
hd_legs(m,.18,.85,True)
for s in [-1,1]:
hd_curve('Weapon_Bow'+str(s),[(0,-.44,1.11),(s*.37,-.44,1.11),(s*.64,-.25,1.15)],[.05,.044,.022],m['bone'],12,6)
hd_curve('Weapon_String'+str(s),[(s*.64,-.25,1.15),(0,-.16,1.15)],[.009,.009],m['rift'],8,2)
hd_rod('Weapon_Arrow',(0,-.15,1.15),(0,-.92,1.15),.02,m['rift'],12)
def hd_mask(name,loc,r,m):
x,y,z=loc
outline=[(-r*.78,r*.58),(-r*.44,r),(.44*r,r),(.78*r,.58*r),(.61*r,-.62*r),(0,-r),(-.61*r,-.62*r)]
hd_plate(name+'_Porcelain',loc,outline,r*.32,m['bone'],.015,.025)
for s in [-1,1]:
hd_plate(name+'_EyeSocket'+str(s),(x+s*r*.33,y-r*.195,z+r*.15),[(-r*.21,r*.08),(r*.21,r*.08),(r*.16,-r*.12),(-r*.12,-r*.16)],.025,m['dark'],.003)
hd_curve(name+'_EyeLight'+str(s),[(x+s*r*.33-r*.13,y-r*.23,z+r*.12),(x+s*r*.33+r*.12,y-r*.23,z+r*.12)],[r*.027]*2,m['rift'],8,2)
hd_plate(name+'_Nose',(x,y-r*.22,z-r*.10),[(-r*.12,r*.25),(r*.12,r*.25),(0,-r*.1)],.055,m['edge'],.004)
for k in range(3):
hd_box(name+'_MouthSlit'+str(k),(x+(k-1)*r*.16,y-r*.18,z-r*.54),(r*.055,.017,r*.18),m['dark'],.003)
def build_expedition_enemy(id,m):
"""Four distinct expedition silhouettes; every part uses the same rigid rig contract."""
stone=dict(m); stone['gold']=m['bone'];stone['gold_light']=m['edge']
if id=='e05':
# Broad hunchback; massive capped stone gauntlets distinguish this bruiser.
hd_legs(stone,.32,.60)
hd_rock('Body_BruteCore',(0,.06,1.03),(.43,.31,.48),m['dark'],2)
for j in range(3):
z=.75+j*.22
for s in [-1,1]:
hd_plate('Body_Chest%d_%d'%(j,s),(s*.21,-.245,z),[(-.20,-.09),(.18,-.10),(.22,.11),(-.14,.14)],.15,m['basalt'],.024,.035)
hd_curve('Body_RiftCrack',[(0,-.34,.66),(.025,-.36,.91),(-.03,-.36,1.14),(0,-.34,1.30)],[.018]*4,m['rift'],8,3)
for j in range(4):
hd_rock('Body_BackSlab'+str(j),(0,.23,.76+j*.21),(.45,.23,.17),m['basalt'],j)
hd_mask('Head_Mask',(0,-.25,1.49),.20,m)
for s,side in [(-1,'L'),(1,'R')]:
hd_arm('Arm'+side,(s*.43,.045,1.32),(s*.65,-.02,1.00),(s*.65,-.15,.54),stone,.175)
hd_box('Arm'+side+'_StoneHammer',(s*.65,-.17,.52),(.42,.48,.36),m['basalt'],.06)
for k in range(3):
hd_box('Arm'+side+'_Knuckle'+str(k),(s*.65+(k-1)*.12,-.419,.57),(.10,.07,.18),m['bone'],.022)
hd_box('Arm'+side+'_Cuff',(s*.65,-.11,.72),(.44,.38,.075),m['edge'],.018)
for k in range(3):
hd_curve('Body_ShoulderSpike'+side+str(k),[(s*(.29+k*.13),.05,1.41),(s*(.36+k*.16),.065,1.70-k*.07)],[.10,0],m['bone'],12,5)
elif id in ('e06','e09'):
boss=id=='e09'
# Floating knotted robe and staff. The judge has a wide suspended mask crown.
hd_rock('Body_RobeCore',(0,.015,1.02),(.23,.19,.41),m['dark'],3)
for k in range(9):
a=k*2*pi/9
hd_curve('Body_RobeFold'+str(k),[(.19*cos(a),.14*sin(a),1.31),(.25*cos(a+.15),.22*sin(a+.15),.84),(.30*cos(a+.25),.27*sin(a+.25),.32),(.37*cos(a+.2),.30*sin(a+.2),.10)], [.065,.082,.054,.009],m['basalt'] if k%2 else m['dark'],12,7)
for j in range(4):
hd_ring('Body_KnotRing'+str(j),(0,0,.92+j*.085),.245-j*.012,.019,m['bone'],segments=40)
hd_mask('Head_Face',(0,-.015,1.60),.235 if boss else .195,m)
for s,side in [(-1,'L'),(1,'R')]:
hd_arm('Arm'+side,(s*.26,0,1.29),(s*.38,-.11,1.04),(s*.43,-.26,1.22),stone,.081)
hd_plate('Arm'+side+'_Pauldron',(s*.28,0,1.34),[(-.12,.02),(0,.16),(.16,.06),(.12,-.13),(-.10,-.13)],.19,m['bone'],.018)
hd_curve('Weapon_Staff',[(.47,-.28,.12),(.46,-.28,.73),(.45,-.27,1.35),(.49,-.27,1.83)],[.028,.035,.035,.025],m['bone'],12,8)
for k in range(5):
hd_ring('Weapon_StaffCollar'+str(k),(.475,-.275,.68+k*.12),.045,.012,m['edge'],segments=24)
hd_ring('Weapon_StaffKnot',(.49,-.27,1.82),.17,.03,m['bone'],(pi/2,0,0),48)
hd_rock('Weapon_StaffHeart',(.49,-.27,1.82),(.105,.06,.105),m['rift'],0)
for s in [-1,1]:
hd_curve('Weapon_KnotBranch'+str(s),[(.48+s*.06,-.27,1.68),(.48+s*.20,-.27,1.84),(.48+s*.11,-.26,1.99)],[.028,.022,0],m['bone'],10,7)
if boss:
hd_ring('Body_JudgeHalo',(0,.12,1.53),.65,.034,m['bone'],(pi/2,0,0),72)
hd_ring('Body_JudgeGlow',(0,.135,1.53),.60,.012,m['rift'],(pi/2,0,0),72)
for k in range(5):
a=.12*pi+k*.19*pi
x=.65*cos(a);z=1.53+.65*sin(a)
hd_mask('Body_CourtMask'+str(k),(x,.065,z),.16,m)
hd_curve('Body_CrownSpike'+str(k),[(x,.14,z+.12),(x*1.07,.14,z+.32)],[.065,0],m['bone'],10,5)
for s in [-1,1]:
hd_curve('Body_Chains'+str(s),[(s*.54,.10,1.63),(s*.64,-.03,1.07),(s*.46,-.16,.47)],[.018]*3,m['bone'],10,9)
else:
for j in range(3):
hd_ring('Body_HealingKnot'+str(j),((j-1)*.13,-.205,1.09),.084,.019,m['rift'],(pi/2,0,.4*j),40)
else:
# Final boss: four heavy legs, segmented carapace, actual open maw and a crown.
hd_rock('Body_CrownCore',(0,.13,.98),(.54,.58,.52),m['dark'],1)
for s,side in [(-1,'L'),(1,'R')]:
for j,y in enumerate([-.10,.64]):
n='Leg'+side+str(j);hip=(s*.40,y,.92);knee=(s*.66,y+.06,.51);foot=(s*.70,y-.10,.12)
hd_rod(n+'_Inner',hip,knee,.10,m['dark'])
hd_rock(n+'_Thigh',tuple((Vector(hip)+Vector(knee))*.5),(.18,.19,.30),m['basalt'],j)
hd_rock(n+'_Shin',tuple((Vector(knee)+Vector(foot))*.5),(.16,.18,.24),m['basalt'],j+1)
hd_disc(n+'_Joint',(knee[0],knee[1]-.18,knee[2]),.105,.045,m['bone'])
for k in range(3):
x=foot[0]+(k-1)*.09
hd_curve(n+'_Claw'+str(k),[(x,y-.05,.19),(x,y-.27,.10),(x,y-.39,.055)],[.064,.043,0],m['bone'],12,5)
for j in range(4):
hd_rock('Body_Shell'+side+str(j),(s*.37,.02+j*.20,1.20),(.25,.19,.28),m['basalt'],j)
hd_curve('Body_Ridge'+side+str(j),[(s*.32,.02+j*.20,1.37),(s*.41,.06+j*.20,1.67),(s*.30,.14+j*.20,1.78)],[.105,.069,0],m['bone'],12,6)
hd_disc('Head_MawVoid',(0,-.43,.99),.40,.15,m['dark'])
hd_ring('Head_MawRim',(0,-.53,.99),.38,.065,m['basalt'],(pi/2,0,0),64)
hd_disc('Head_RiftThroat',(0,-.522,.99),.22,.025,m['rift'])
for k in range(14):
a=k*2*pi/14
hd_curve('Head_Tooth'+str(k),[(.35*cos(a),-.58,.99+.35*sin(a)),(.29*cos(a),-.63,.99+.29*sin(a)),(.19*cos(a+.1),-.60,.99+.19*sin(a+.1))],[.058,.044,0],m['bone'],12,5)
for s in [-1,1]:
hd_rock('Head_Brow'+str(s),(s*.24,-.34,1.41),(.27,.17,.16),m['basalt'],s)
hd_curve('Head_Eye'+str(s),[(s*.12,-.498,1.41),(s*.31,-.49,1.45)],[.023,.029],m['rift'],10,4)
hd_ring('Head_CrownBand',(0,.02,1.63),.36,.052,m['bone'],segments=64)
for k in range(7):
a=k*2*pi/7
hd_curve('Head_CrownPoint'+str(k),[(.36*cos(a),.02+.36*sin(a),1.62),(.47*cos(a),.02+.47*sin(a),1.91),(.37*cos(a),.02+.37*sin(a),2.20 if k%2 else 2.06)],[.095,.065,0],m['bone'],12,7)
hd_rock('Head_CrownGem'+str(k),(.38*cos(a),.02+.38*sin(a),1.69),(.055,)*3,m['rift'],k)
hd_curve('Body_Tail',[(0,.61,.86),(0,.97,.66),(.18,1.13,.35),(.30,1.22,.25)],[.14,.11,.06,0],m['basalt'],16,8)
def enhance_existing(spec,m):
"""Round existing neutral/environment geometry and add constructed details."""
for obj in list(bpy.context.scene.objects):
if obj.type!='MESH':continue
dimensions=obj.dimensions
width=min(min(dimensions)*.09,.014)
hd_finish(obj,max(.001,width),3)
if spec['role']=='lane':
hd_materials(m)
# Detailed edge stones, anchored brass plates and inset light wells.
for s in [-1,1]:
for j in range(4):
y=-1.5+j
hd_box('Body_EdgeStone%d_%d'%(s,j),(s*6.13,y,.16),(.30,.91,.29),m['basalt'],.035)
hd_box('Body_EdgeCap%d_%d'%(s,j),(s*6.13,y,.32),(.34,.92,.060),m['gold'],.014)
for j,y in enumerate([-.95,.95]):
hd_box('Body_LightRecess%d_%d'%(s,j),(s*6.13,y,.364),(.15,.22,.035),m['dark'],.009)
hd_box('Body_LightGlass%d_%d'%(s,j),(s*6.13,y,.387),(.065,.14,.013),m['ember'],.005)
elif spec['role']=='core':
hd_materials(m)
for k in range(8):
a=k*pi/4
hd_box('Body_PlinthArmor'+str(k),(1.02*cos(a),1.02*sin(a),.18),(.37,.25,.29),m['basalt'],.029,rotation=(0,0,a+pi/2))
hd_ring('Body_PlinthGold'+str(k),(1.04*cos(a),1.04*sin(a),.35),.068,.012,m['gold'],segments=24)
hd_ring('Body_OuterRim',(0,0,.36),1.19,.026,m['gold'],segments=72)
def finalize_high_geometry():
"""Resolve tiny bevel slivers and retain valid corner normals after triangulation.
Thin inset discs can produce normals across their back edge when weighted
normals are computed on n-gons. Triangulate first, then constrain only those
corner normals that point below the actual triangle surface.
"""
for obj in bpy.context.scene.objects:
if obj.type != 'MESH': continue
mesh=obj.data
bm=bmesh.new(); bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm,verts=list(bm.verts),dist=.000001)
bmesh.ops.dissolve_degenerate(bm,edges=list(bm.edges),dist=.000001)
bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces))
bmesh.ops.triangulate(bm,faces=list(bm.faces),quad_method='BEAUTY',ngon_method='EAR_CLIP')
bm.to_mesh(mesh);bm.free();mesh.update()
normals=[tuple(n.vector) for n in mesh.corner_normals]
for face in mesh.polygons:
for index in face.loop_indices:
if Vector(normals[index]).dot(face.normal)<.01:
normals[index]=tuple(face.normal)
mesh.normals_split_custom_set(normals)