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>
652 lines
38 KiB
Python
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)
|