import bpy, math, random, os, sys, wave, struct
from mathutils import Vector
random.seed(18)
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUT=os.path.join(ROOT,'preview')
bpy.ops.object.select_all(action='SELECT'); bpy.ops.object.delete(use_global=False)
s=bpy.context.scene
s.render.engine='BLENDER_EEVEE'; s.eevee.taa_render_samples=8; s.eevee.use_gtao=True; s.eevee.gtao_distance=3; s.eevee.use_soft_shadows=True
s.render.resolution_x=1280;s.render.resolution_y=720;s.render.resolution_percentage=100
s.render.fps=24;s.frame_start=1;s.frame_end=480
s.world.color=(.003,.003,.003)
s.view_settings.view_transform='AgX'
def mat(name,color,rough=.7,noise=False):
m=bpy.data.materials.new(name);m.diffuse_color=(*color,1);m.use_nodes=True
p=m.node_tree.nodes.get('Principled BSDF');p.inputs['Base Color'].default_value=(*color,1);p.inputs['Roughness'].default_value=rough
if noise:
n=m.node_tree.nodes.new('ShaderNodeTexNoise');n.inputs['Scale'].default_value=24
b=m.node_tree.nodes.new('ShaderNodeBump');b.inputs['Strength'].default_value=.3;b.inputs['Distance'].default_value=.08
m.node_tree.links.new(n.outputs['Fac'],b.inputs['Height']);m.node_tree.links.new(b.outputs['Normal'],p.inputs['Normal'])
return m
soil=mat('Damp garden soil',(.07,.045,.025),noise=True);stone=mat('Weathered limestone',(.28,.29,.27),noise=True)
cloth=mat('Burial cloth',(.15,.12,.09),noise=True);brick=mat('Rendered house wall',(.24,.19,.14),noise=True)
bone=mat('Old ivory',(.55,.48,.32));black=mat('Dark fabric',(.015,.02,.025),noise=True)
leafm=[mat('Autumn leaf '+str(i),c) for i,c in enumerate([(.21,.065,.014),(.30,.15,.03),(.12,.055,.018)])]
def cube(name,loc,scale,m,bevel=0):
bpy.ops.mesh.primitive_cube_add(size=1,location=loc);o=bpy.context.object;o.name=name;o.scale=scale
bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
if m:o.data.materials.append(m)
if bevel:
mod=o.modifiers.new('Soft edges','BEVEL');mod.width=bevel;mod.segments=3;o.data.use_auto_smooth=True;o.modifiers.new('Normals','WEIGHTED_NORMAL')
return o
def uv(name,loc,scale,m):
bpy.ops.mesh.primitive_uv_sphere_add(segments=16,ring_count=10,location=loc);o=bpy.context.object;o.name=name;o.scale=scale;o.data.materials.append(m)
for p in o.data.polygons:p.use_smooth=True
return o
def rod(name,a,b,r,m,parent=None):
d=Vector(b)-Vector(a);bpy.ops.mesh.primitive_cylinder_add(vertices=10,radius=r,depth=d.length,location=(Vector(a)+Vector(b))/2)
o=bpy.context.object;o.name=name;o.rotation_euler=d.to_track_quat('Z','Y').to_euler();o.data.materials.append(m)
if parent:o.parent=parent
return o
cube('Ground',(0,0,-.12),(16,16,.2),soil)
cube('Walkway',(2.6,0,.01),(1.5,12,.09),stone,.03)
for y in range(-6,7):cube('Paving joint',(2.6,y,.065),(1.5,.017,.005),black)
cube('House',(0,6,2),(12,.3,4),brick)
for x in [-3,0,3]:
cube('Window frame',(x,5.80,2.2),(1.6,.15,1.8),black,.04)
glass=mat('Warm window '+str(x),(.5,.22,.045));p=glass.node_tree.nodes.get('Principled BSDF');p.inputs['Emission Color'].default_value=(1,.35,.08,1);p.inputs['Emission Strength'].default_value=.8
cube('Lit window',(x,5.70,2.2),(1.4,.04,1.6),glass)
cube('Window divider',(x,5.65,2.2),(.05,.06,1.7),black)
for x in [-5,5]:
for y in range(-4,6):cube('Fence post',(x,y,.65),(.09,.09,1.3),stone,.015)
cube('Fence rail',(x,.5,.9),(.07,10,.08),stone)
# Grave border and headstone
for x in [-.9,.9]:cube('Grave side',(x,0,.14),(.15,2.9,.28),stone,.04)
for y in [-1.45,1.45]:cube('Grave end',(0,y,.14),(1.95,.15,.28),stone,.04)
cube('Headstone base',(0,1.65,.25),(1.1,.45,.3),stone,.06)
cube('Headstone',(0,1.65,.85),(.85,.22,1.1),stone,.10)
uv('Rounded headstone',(0,1.65,1.35),(.425,.11,.3),stone)
def text(body,loc,size):
bpy.ops.object.text_add(location=loc,rotation=(math.pi/2,0,0));o=bpy.context.object;o.data.body=body;o.data.align_x='CENTER';o.data.size=size;o.data.extrude=.002;o.data.materials.append(black)
text('R I P',(0,1.525,1.05),.19);text('STILL BREATHING',(0,1.525,.72),.065)
# Fabric mesh with animated breathing shape
verts=[];faces=[];nx=28;ny=44
for j in range(ny+1):
y=-1.35+2.7*j/ny
for i in range(nx+1):
x=-.80+1.6*i/nx;z=.19+.035*math.sin(i*1.3+j*.7)*math.sin(math.pi*i/nx)*math.sin(math.pi*j/ny);verts.append((x,y,z))
for j in range(ny):
for i in range(nx):
a=j*(nx+1)+i;faces.append((a,a+1,a+nx+2,a+nx+1))
mesh=bpy.data.meshes.new('Cloth grid');mesh.from_pydata(verts,[],faces);mesh.update()
o=bpy.data.objects.new('Breathing burial shroud',mesh);s.collection.objects.link(o);o.data.materials.append(cloth)
for p in mesh.polygons:p.use_smooth=True
o.shape_key_add(name='Rest');breath=o.shape_key_add(name='Breath')
for v in breath.data:v.co.z+=.23*math.exp(-((v.co.x/.50)**2+(v.co.y/.85)**2))
for f,val in [(1,.1),(30,1),(65,0),(100,.85),(120,.15),(150,0),(175,.45),(210,0),(240,.1),(241,0),(310,0),(316,1.8),(322,.1),(332,1.2),(345,0),(380,.1),(415,.8),(450,0),(480,.35)]:breath.value=val;breath.keyframe_insert('value',frame=f)
for i in range(220):
x=random.uniform(-4,4);y=random.uniform(-4,4)
if abs(x)<1 and abs(y)<1.5:continue
me=bpy.data.meshes.new('Leaf');me.from_pydata([(-.10,0,0),(-.045,-.04,0),(0,-.065,.01),(.07,-.025,0),(.11,0,0),(.04,.045,0),(-.02,.055,.008)],[],[(0,1,2,3,4,5,6)]);me.update()
l=bpy.data.objects.new('Fallen leaf',me);s.collection.objects.link(l);l.location=(x,y,.025);l.data.materials.append(random.choice(leafm));l.rotation_euler.z=random.random()*6.28
# Grass tufts, shrubs, and Halloween pumpkins
grassmat=mat('Dry grass',(.055,.065,.024))
for i in range(180):
x=random.uniform(-4.5,1.6);y=random.uniform(-4,4)
if abs(x)<1.1 and abs(y)<1.8:continue
rod('Grass blade',(x,y,0),(x+.025,y+.015,random.uniform(.07,.17)),.003,grassmat)
for x,y in [(-2,1),(-3,3),(4,4)]:
for j in range(8):uv('Garden shrub',(x+random.uniform(-.3,.3),y+random.uniform(-.3,.3),random.uniform(.2,.5)),(.3,.3,.3),grassmat)
for x,y in [(-1.5,-1.7),(-1.8,-1.4),(.8,2.1)]:
pumpkin=uv('Ribbed pumpkin',(x,y,.18),(.23,.23,.18),leafm[0])
for v in pumpkin.data.vertices:
ang=math.atan2(v.co.y,v.co.x);fac=1+.055*math.cos(ang*10);v.co.x*=fac;v.co.y*=fac
rod('Pumpkin stem',(x,y,.32),(x+.02,y,.42),.024,soil)
# Cloaked costumed visitors, seen from behind/side
purple=mat('Purple wool costume',(.11,.025,.13),noise=True);red=mat('Burgundy costume',(.18,.018,.022),noise=True)
def person(name,h,color,start,end,child=False):
root=bpy.data.objects.new(name,None);s.collection.objects.link(root)
# Folded tapered cloak
vs=[];fs=[];rings=14;seg=32
for j in range(rings):
z=h*(.18+.60*j/(rings-1));r=h*(.24-.12*j/(rings-1))
for k in range(seg):
ang=k*2*math.pi/seg;rr=r*(1+.085*math.sin(ang*9+j*.25));vs.append((rr*math.cos(ang),rr*.65*math.sin(ang),z))
for j in range(rings-1):
for k in range(seg):a=j*seg+k;b=j*seg+(k+1)%seg;fs.append((a,b,b+seg,a+seg))
me=bpy.data.meshes.new(name+' costume');me.from_pydata(vs,[],fs);me.update();ob=bpy.data.objects.new(name+' cloak',me);s.collection.objects.link(ob);ob.parent=root;ob.data.materials.append(color)
for p in me.polygons:p.use_smooth=True
for x in [-h*.08,h*.08]:
ob=uv('Boot',(x,-.04,h*.08),(h*.065,h*.10,h*.09),black);ob.parent=root
drv=ob.driver_add('location',2).driver;drv.expression=f'{h*.08} + (max(0,sin(frame*0.32+{0 if x<0 else math.pi}))*{h*.035} if 12017 else .006*lp
for begin,end in [(7.1,8.3),(13,13.45)]:
if begin