saumwacht-godot/assets-source/phase4/blender/reference_nebelchor.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

206 lines
10 KiB
Python

"""Nebelchor: flowing ghost ribbons and mask/bell silhouettes from Phase 3."""
from math import sin, cos, pi
from common import ellipsoid, ring, beam
from reference_geometry import r_plate, r_rock, r_sweep, r_lathe, r_arch_shell
def n_ribbon(name,points,widths,mat):
if name.startswith('Head_ChoirHorn'):
return r_sweep(name,points,widths,mat,6,ribbon=True)
# One interpolated section per span gives flowing curves instead of zigzag rods.
from mathutils import Vector
refined=[]; radii=[]
for i in range(len(points)-1):
p0=Vector(points[max(i-1,0)]); p1=Vector(points[i])
p2=Vector(points[i+1]); p3=Vector(points[min(i+2,len(points)-1)])
for t in (0,.5):
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)
refined.append(tuple(p)); radii.append(widths[i]*(1-t)+widths[i+1]*t)
refined.append(points[-1]);radii.append(widths[-1])
return r_sweep(name,refined,radii,mat,6,ribbon=True)
def n_mask(name,loc,scale,m,singing=False):
x,y,z=loc
outline=[(-.12,.14),(-.05,.24),(.055,.22),(.145,.11),(.12,-.075),(0,-.24),(-.12,-.08)]
r_plate(name,loc,[(a*scale,b*scale) for a,b in outline],.09*scale,m['light'],.055*scale)
for s in [-1,1]:
r_plate(name+'_Eye'+str(s),(x+s*.065*scale,y-.091*scale,z+.025*scale),
[(s*-.032*scale,.005*scale),(s*.030*scale,.027*scale),(s*.027*scale,-.014*scale),(s*-.019*scale,-.040*scale)],
.012,m['accent'],.003)
if singing:
r_rock(name+'_Mouth',(x,y-.092*scale,z-.09*scale),(.035*scale,.018*scale,.061*scale),m['primary'])
def n_wisps(m,wide=False,tall=False):
"""Tapering curved ribbons replace human legs and the rigid cone skirt."""
top=1.16 if tall else .90
narrow=.55 if tall and not wide else 1
for s,side in [(-1,'L'),(1,'R')]:
spread=.28 if wide else .16
pts=[(s*spread,.08,top),(s*.08,.06,top*.75),(s*.20,.025,top*.54),
(s*.075,0,top*.34),(s*.20,-.015,top*.17),(s*.26,-.025,.055),(s*.36,-.025,.075)]
n_ribbon('Body_Wisp'+side,pts,[v*narrow for v in [.15 if wide else .11,.18,.14,.085,.048,.03,0]],m['secondary'])
pts2=[(s*.16,.16,top*.86),(s*.33,.10,top*.64),(s*.18,.07,top*.39),
(s*.35,.03,.20),(s*.30,.015,.08),(s*.22,0,.01)]
n_ribbon('Body_Tail'+side,pts2,[v*narrow for v in [.09,.105,.085,.057,.025,0]],m['primary'])
n_ribbon('Body_CentralFlow',[(0,.06,top),(-.10,-.055,top*.73),(.13,-.11,top*.46),(-.04,-.09,top*.19),(.07,-.08,.12)],
[v*narrow for v in [.18,.24 if wide else .16,.16,.045,0]],m['primary'])
def n_hood(m,up=False,z=1.22):
n_mask('Head_Mask',(0,-.15,z),1.0,m)
n_ribbon('Head_CurledHood',[(.14,.045,z-.10),(.09,.06,z+.15),(-.10,.04,z+.28),
(-.22,.015,z+.31),(-.25,0,z+.42),(-.15,-.01,z+.43)],
[.18,.17,.13,.075,.04,0],m['secondary'])
def n_crescent(name,loc,scale,m,light=True,flip=1):
x,y,z=loc
pts=[(x-flip*.13*scale,y,z+.17*scale),(x-flip*.17*scale,y,z),
(x-flip*.045*scale,y-.01,z-.14*scale),(x+flip*.15*scale,y-.02,z-.13*scale),
(x+flip*.23*scale,y-.02,z-.025*scale)]
n_ribbon(name,pts,[0,.085*scale,.10*scale,.06*scale,0],m['light'] if light else m['secondary'])
def n_tank(up,m):
n_wisps(m,up)
n_hood(m,up,1.20 if up else 1.14)
for k in range(3):
z=.89-k*.16
w=(.40 if up else .27)-k*.045
outline=[(-w,.10),(0,.045),(w,.10),(w*.65,-.11),(0,-.21),(-w*.65,-.11)]
# Each layer is a thick wrapped plate, with a light inner leading edge.
r_plate('Body_WrappedArmor'+str(k),(0,-.12,z),outline,.26,m['light'] if up and k%2==0 else m['secondary'])
for s,side in [(-1,'L'),(1,'R')]:
n_ribbon('Arm'+side,[(s*.19,.04,1.02),(s*.32,0,.94),(s*.38,-.06,.75)],
[.11,.13,.08],m['secondary'])
if up:
n_ribbon('Arm'+side+'_GreatShield',[(s*.29,-.12,.67),(s*.51,-.08,.81),
(s*.48,-.05,1.13),(s*.37,-.01,1.39)],
[0,.19,.18,0],m['light'])
n_ribbon('Arm'+side+'_ShieldInlay',[(s*.35,-.145,.74),(s*.48,-.12,.90),(s*.43,-.09,1.15)],
[0,.11,0],m['secondary'])
else:
n_crescent('Weapon'+side+'_Claw',(s*.33,-.09,.79),.82,m,flip=s)
def n_dancer(up,m):
n_wisps(m,False,True)
n_hood(m,up,1.31)
for s,side in [(-1,'L'),(1,'R')]:
high=(s<0 and not up)
tip=(s*.46,-.15,1.49 if high else .87)
n_ribbon('Arm'+side,[(s*.12,.025,1.12),(s*.31,.015,1.20 if high else 1.02),tip],
[.085,.10,.055],m['secondary'])
if not up:
r_sweep('Weapon'+side+'_Sickle',[tip,(tip[0]+s*.12,-.17,tip[2]-.12),
(tip[0]+s*.11,-.19,tip[2]-.28)],[.045,.045,0],m['light'],5)
if up:
pivot=(.27,-.25,.94)
r_rock('Weapon_ShearPivot',pivot,(.065,.04,.065),m['accent'])
for s,side in [(-1,'Lower'),(1,'Upper')]:
n_ribbon('Weapon_Shear'+side,[pivot,(.48,-.25,.94+s*.16),(.70,-.21,.94+s*.24),(.78,-.17,.94+s*.40)],
[.045,.115,.095,0],m['light'])
pts=[(.22,-.245,.94),(.01,-.24,.94+s*.14),(-.16,-.21,.94+s*.10),
(-.15,-.20,.94-s*.045),(.02,-.225,.94-s*.02),(.20,-.245,.94)]
n_ribbon('Weapon_ShearLoop'+side,pts,[.025,.035,.035,.035,.035,.025],m['light'])
n_ribbon('Head_CrescentCrown',[(.04,.03,1.44),(-.10,.02,1.64),(-.04,0,1.76)],
[.095,.105,0],m['light'])
def n_horn(name,loc,radius,depth,m,axis):
r_lathe(name,loc,[(0,0),(.035,.015),(.048,depth*.42),(radius*.55,depth*.78),
(radius,depth),(radius*.82,depth),(radius*.42,depth*.78),(.022,depth*.30),(0,depth*.29)],
m['light'],axis,10)
from mathutils import Vector
lip=Vector(loc)+Vector(axis).normalized()*depth
r_lathe(name+'_MintLip',lip,[(radius*1.015,-.025),(radius*1.025,.01),
(radius*.80,.01),(radius*.80,-.025)],m['accent'],axis,10)
def n_ranged(up,m):
n_wisps(m,False,True)
n_hood(m,up,1.29)
if up:
for s,side in [(-1,'L'),(1,'R')]:
n_ribbon('Arm'+side,[(s*.14,0,1.06),(s*.28,-.16,.97),(s*.43,-.20,1.04)],
[.09,.10,.025],m['light'])
n_ribbon('Weapon_HornArch'+side,[(s*.14,.09,1.24),(s*.28,.10,1.48),
(s*.45,.05,1.49),(s*.55,-.05,1.32)],
[.14,.13,.115,.08],m['secondary'])
n_horn('Weapon_Horn'+side,(s*.54,-.025,1.36),.23,.43,m,(s*.28,-.8,-.35))
else:
n_horn('Weapon_MouthHorn',(.025,-.25,1.24),.13,.60,m,(1,-.22,-.03))
n_ribbon('ArmL',[(-.18,0,1.06),(-.23,-.17,1.10),(.16,-.25,1.19)],
[.10,.095,.035],m['light'])
n_ribbon('ArmR',[(.15,0,1.07),(.30,-.12,1.02),(.38,-.27,1.19)],
[.09,.10,.025],m['secondary'])
def n_bell(up,m):
if up:
n_wisps(m,True)
r_arch_shell('Body_CathedralShell',(0,.045,0),[(.13,1.48),(.22,1.15),(.43,.58),(.48,.48),
(.43,.45),(.35,.60),(.15,1.17),(.085,1.48)],m['secondary'])
ring('Body_BellSkirt',(0,.045,.49),.46,.055,m['light'],segments=14)
arch=[(-.38,-.13,.54),(-.29,-.115,.98),(-.12,-.05,1.34),(0,-.01,1.45),
(.12,-.05,1.34),(.29,-.115,.98),(.38,-.13,.54)]
n_ribbon('Body_CathedralFrontArch',arch,[.035]*len(arch),m['light'])
beam('Weapon_ClapperChain',(0,-.055,1.27),(0,-.075,.84),.016,m['accent'],vertices=6)
r_rock('Weapon_Clapper',(0,-.075,.80),(.125,.11,.15),m['accent'])
n_mask('Body_ArchMask',(0,-.07,1.22),.48,m)
for s,side in [(-1,'L'),(1,'R')]:
n_ribbon('Head_Spire'+side,[(s*.17,.055,1.27),(s*.28,.055,1.53),
(s*.24,.05,1.69)],[.085,.07,0],m['light'])
n_crescent('Arm'+side+'_Sickle',(s*.43,0,.89),1,m,flip=s)
n_ribbon('Head_CentralSpire',[(0,.05,1.42),(-.025,.06,1.71),(.02,.02,1.83)],
[.10,.065,0],m['light'])
else:
n_wisps(m,True)
n_hood(m,False,1.39)
axis=(0,-.85,-.40)
r_lathe('Weapon_HangingBell',(0,.03,1.15),[(0,0),(.16,.02),(.22,.14),(.28,.42),(.44,.68),
(.45,.73),(.38,.73),(.23,.43),(.16,.15),(0,.12)],m['secondary'],axis,12)
from mathutils import Vector
lip=Vector((0,.03,1.15))+Vector(axis).normalized()*.70
r_lathe('Weapon_BellRim',lip,[(.455,0),(.46,.045),(.375,.045),(.375,0)],m['light'],axis,12)
beam('Weapon_ClapperStem',(0,-.08,1.10),(0,-.55,.80),.018,m['accent'],vertices=6)
r_rock('Weapon_Clapper',(0,-.56,.78),(.10,.085,.115),m['accent'])
for s,side in [(-1,'L'),(1,'R')]:
n_ribbon('Arm'+side,[(s*.18,.07,1.21),(s*.41,.02,1.13),
(s*.36,-.26,1.02),(s*.23,-.30,1.12)],
[.13,.15,.11,.02],m['secondary'])
def n_singer(up,m):
n_wisps(m,up,True)
n_hood(m,up,1.33)
# Singing mask has an open mouth, and floating masks have their own wisps.
r_rock('Head_SingingMouth',(0,-.248,1.23),(.035,.018,.061),m['primary'])
for k in range(2):
r_plate('Body_ChoirCollar'+str(k),(0,-.115,1.08-k*.13),[(-.25,.12),(0,.045),(.25,.12),(.17,-.045),(0,-.14),(-.17,-.045)],
.17,m['light'] if k==0 else m['secondary'])
r_rock('Body_ChoirGem',(0,-.225,.93),(.065,.045,.105),m['accent'])
for s,side in [(-1,'L'),(1,'R')]:
n_ribbon('Arm'+side,[(s*.18,0,1.04),(s*.32,-.035,.91),(s*.40,-.11,1.02)],
[.11,.12,.05],m['secondary'])
n_crescent('Arm'+side+'_Cuff',(s*.41,-.10,1.04),1,m,flip=-s)
floating=[(.42,.06,1.52)] if not up else [(-.40,.09,1.53),(.38,.08,1.50),(.28,.16,1.86)]
for k,loc in enumerate(floating):
x,y,z=loc
n_mask('Head_ChoirMask'+str(k),loc,.60,m,True)
n_ribbon('Head_ChoirWisp'+str(k),[(x,y,z-.08),(x-.055,y+.025,z-.20),(x+.02,y,z-.27)],
[.06,.04,0],m['secondary'])
for s in [-1,1]:
n_ribbon('Head_ChoirHorn'+str(k)+'_'+str(s),[(x+s*.06,y+.02,z+.09),
(x+s*.13,y+.02,z+.20),(x+s*.09,y,z+.25)],[.045,.045,0],m['light'])
if up:
for s in [-1,1]:
n_ribbon('Head_GreatHorn'+str(s),[(s*.10,.06,1.46),(s*.23,.04,1.65),(s*.16,0,1.79)],
[.09,.08,0],m['light'])
def build_nebelchor(spec,m):
role=spec['role']; up=spec['upgraded']
{'tank':n_tank,'melee':n_dancer,'ranged':n_ranged,'artillery':n_bell,'support':n_singer}[role](up,m)