Third batch of Blender ident scenes and the Wild claw marks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -562,12 +562,13 @@ def flat(name, polygons, location, facing, mat, shapes=()):
|
||||
obj.location = location; obj.rotation_euler = (Vector(facing) - Vector(location)).to_track_quat('-Y', 'Z').to_euler()
|
||||
return obj
|
||||
|
||||
def caped_hero(where, facing, mat, size=1.0):
|
||||
def caped_hero(where, facing, mat, size=1.0, cape_on=True):
|
||||
"""A hero on a rooftop, hands on hips, cape in the wind. Only ever a silhouette against the moon."""
|
||||
body = [[(-.95, 0), (-.45, 0), (-.08, 2.7), (-.6, 3.3)], [(.95, 0), (.45, 0), (.08, 2.7), (.6, 3.3)], [(-.6, 3.3), (.6, 3.3), (.68, 3.75), (1.08, 5.15), (.5, 5.42), (-.5, 5.42), (-1.08, 5.15), (-.68, 3.75)],
|
||||
[(-1.08, 5.15), (-1.75, 4.1), (-.64, 3.42), (-.62, 3.72), (-1.28, 4.15), (-.75, 4.95)], [(1.08, 5.15), (1.75, 4.1), (.64, 3.42), (.62, 3.72), (1.28, 4.15), (.75, 4.95)],
|
||||
[(-.17, 5.42), (.17, 5.42), (.17, 5.75), (-.17, 5.75)], [(.42 * math.cos(i / 16 * math.tau), 6.05 + .48 * math.sin(i / 16 * math.tau)) for i in range(16)]]
|
||||
hero = flat('hero', [[(x * size, z * size) for x, z in p] for p in body], where, facing, mat)
|
||||
if not cape_on: return hero
|
||||
rows, columns = 12, 6
|
||||
def cape_at(phase, wind):
|
||||
points = []
|
||||
@@ -784,6 +785,239 @@ def case_board():
|
||||
for i in range(30):
|
||||
box(f'slat{i}', -70, -50, 40, 1, .6, slat, 16 + i * 1.5).visible_camera = False
|
||||
|
||||
def clone(original):
|
||||
"""A copy with no animation of its own yet. A plain copy would move in step with the original."""
|
||||
one = original.copy(); one.animation_data_clear(); bpy.context.collection.objects.link(one)
|
||||
return one
|
||||
|
||||
def ground(name, colour, patch, size=1600, rough=.95, y=400):
|
||||
m, n, l = material(name); bsdf = n['Principled BSDF']; bsdf.inputs['Roughness'].default_value = rough
|
||||
l.new(noise_ramp(n, l, n.new('ShaderNodeTexCoord').outputs['Object'], .06, 7, [(.35, colour), (.65, patch)]).outputs['Color'], bsdf.inputs['Base Color'])
|
||||
return box(name, 0, y, size, size, .1, m, -.1)
|
||||
|
||||
def acacia(x, y, mat, seed, scale=1.0):
|
||||
"""A flat-topped tree, seen against the sun."""
|
||||
rng = random.Random(seed)
|
||||
box(f'trunk{seed}', x, y, .9 * scale, .9 * scale, 8 * scale, mat)
|
||||
for i in range(7):
|
||||
top = sphere(f'canopy{seed}{i}', 1, 16); top.data.materials.append(mat)
|
||||
top.location = (x + rng.uniform(-6, 6) * scale, y + rng.uniform(-2, 2) * scale, (8 + rng.uniform(0, 1.6)) * scale)
|
||||
top.scale = (rng.uniform(3, 5.2) * scale, 2.6 * scale, rng.uniform(.5, 1.1) * scale)
|
||||
|
||||
GIRAFFE = [[(-1.7, 0), (-1.35, 0), (-.9, 3.7), (-1.5, 3.8)], [(1.3, 0), (1.65, 0), (1.45, 3.9), (.85, 3.8)], [(-.9, 0), (-.55, 0), (-.6, 3.7), (-1.1, 3.8)], [(.6, 0), (.95, 0), (1.05, 3.8), (.5, 3.8)],
|
||||
[(-1.7, 3.6), (1.5, 3.5), (1.75, 5.2), (.4, 5.2), (-1.5, 4.5)], [(.75, 5), (1.75, 4.9), (2.9, 9.4), (2.45, 9.5)], [(2.35, 9.2), (3.05, 9.7), (4.05, 9.25), (4, 8.95), (3, 8.85)],
|
||||
[(2.55, 9.6), (2.68, 9.6), (2.65, 10.2), (2.5, 10.2)], [(2.85, 9.6), (2.98, 9.6), (2.96, 10.2), (2.82, 10.2)], [(-1.7, 4.4), (-1.5, 4.5), (-2.05, 2.4), (-2.2, 2.45)]]
|
||||
ELEPHANT = [[(-3.1, 0), (-1.9, 0), (-1.7, 2.6), (-3.2, 2.8)], [(-1, 0), (.15, 0), (.3, 2.6), (-.95, 2.6)], [(1, 0), (2.05, 0), (2.2, 2.5), (.95, 2.6)], [(2.3, 0), (3.3, 0), (3.2, 2.5), (2.2, 2.6)],
|
||||
[(-3.4, 2.4), (-3.3, 4.2), (-2.3, 5.7), (.5, 5.9), (2.1, 5.5), (3.4, 4.6), (3.6, 2.2), (-3.2, 2)], [(2.6, 5.9), (4.2, 6.2), (5.2, 5.2), (5.3, 3.6), (3.4, 3)],
|
||||
[(4.7, 4.5), (5.4, 4.2), (6, 2), (6.5, .3), (6, .25), (5.3, 1.9), (4.8, 3.2)], [(2.1, 5.8), (3.3, 5.9), (3.3, 3.3), (2.5, 3.1), (1.7, 4.1)], [(-3.4, 4.2), (-3.2, 4.2), (-3.75, 1.9), (-3.95, 2)]]
|
||||
|
||||
def fireball(name, where, start, size):
|
||||
"""An explosion, as a real volume of burning gas: it bursts fast, keeps billowing and burns down to smoke."""
|
||||
ball = sphere(name, 1, 48); ball.location = where
|
||||
m, n, l = material(name); out = n['Material Output']; n.remove(n['Principled BSDF'])
|
||||
coord = n.new('ShaderNodeTexCoord').outputs['Object']
|
||||
noise = n.new('ShaderNodeTexNoise'); noise.noise_dimensions = '4D'; noise.inputs['Scale'].default_value = 2.1; noise.inputs['Detail'].default_value = 9; noise.inputs['Roughness'].default_value = .6; noise.inputs['Distortion'].default_value = .5; l.new(coord, noise.inputs['Vector'])
|
||||
churn = noise.inputs['W']
|
||||
churn.default_value = 0; churn.keyframe_insert('default_value', frame=1); churn.default_value = 3.2; churn.keyframe_insert('default_value', frame=350)
|
||||
away = n.new('ShaderNodeVectorMath'); away.operation = 'LENGTH'; l.new(coord, away.inputs[0])
|
||||
# The fire thins out well inside the mesh, so its edge is billows, not the sphere.
|
||||
spread = n.new('ShaderNodeMath'); spread.operation = 'MULTIPLY'; spread.inputs[1].default_value = 1.6; l.new(away.outputs['Value'], spread.inputs[0])
|
||||
inner = n.new('ShaderNodeMath'); inner.operation = 'SUBTRACT'; inner.inputs[0].default_value = 1; l.new(spread.outputs[0], inner.inputs[1])
|
||||
lumps = n.new('ShaderNodeMapRange'); lumps.inputs['From Min'].default_value = .3; lumps.inputs['From Max'].default_value = .7; lumps.inputs['To Min'].default_value = -.7; lumps.inputs['To Max'].default_value = .7; l.new(noise.outputs['Fac'], lumps.inputs['Value'])
|
||||
body = n.new('ShaderNodeMath'); body.operation = 'ADD'; l.new(inner.outputs[0], body.inputs[0]); l.new(lumps.outputs['Result'], body.inputs[1])
|
||||
thick = ramp(n, [(.1, '#000000'), (.26, '#FFFFFF')]); l.new(body.outputs[0], thick.inputs['Fac'])
|
||||
density = n.new('ShaderNodeMath'); density.operation = 'MULTIPLY'; density.inputs[1].default_value = 1.6; l.new(thick.outputs['Color'], density.inputs[0])
|
||||
tone = ramp(n, [(.05, '#2A0600'), (.26, '#C02800'), (.5, '#FF7A10'), (.78, '#FFDC80')]); hot = n.new('ShaderNodeMapRange'); hot.inputs['From Min'].default_value = .1; hot.inputs['From Max'].default_value = 1.1; l.new(body.outputs[0], hot.inputs['Value']); l.new(hot.outputs['Result'], tone.inputs['Fac'])
|
||||
volume = n.new('ShaderNodeVolumePrincipled'); volume.inputs['Color'].default_value = hexrgb('#120C0A')
|
||||
l.new(density.outputs[0], volume.inputs['Density']); l.new(tone.outputs['Color'], volume.inputs['Emission Color'])
|
||||
# Only the gas itself burns; empty space inside the mesh gives off nothing.
|
||||
burn = n.new('ShaderNodeMath'); burn.operation = 'MULTIPLY'; l.new(thick.outputs['Color'], burn.inputs[0]); l.new(burn.outputs[0], volume.inputs['Emission Strength'])
|
||||
power = burn.inputs[1]
|
||||
for frame, value in ((start, 30), (start + 30, 20), (start + 120, 11), (350, 6)):
|
||||
power.default_value = value; power.keyframe_insert('default_value', frame=frame)
|
||||
l.new(volume.outputs[0], out.inputs['Volume'])
|
||||
scene.cycles.volume_bounces = 1; scene.cycles.transparent_max_bounces = 16
|
||||
ball.data.materials.append(m); ball.visible_shadow = False
|
||||
for frame, scale in ((1, 0), (start - 1, 0), (start, .06), (start + 22, .72), (start + 70, .92), (350, 1.0 + (350 - start) * .0008)):
|
||||
ball.scale = (size * scale,) * 3; ball.keyframe_insert('scale', frame=frame)
|
||||
data = bpy.data.lights.new(name + 'light', 'POINT'); data.color = hexrgb('#FF7A30')[:3]; data.shadow_soft_size = size * .5
|
||||
light = bpy.data.objects.new(name + 'light', data); bpy.context.collection.objects.link(light); light.location = (where[0], where[1] - size * 1.1, where[2])
|
||||
for frame, value in ((1, 0), (start - 1, 0), (start + 4, 420000 * size / 16), (start + 60, 120000 * size / 16), (350, 36000 * size / 16)):
|
||||
data.energy = value; data.keyframe_insert('energy', frame=frame)
|
||||
return ball
|
||||
|
||||
def sparks(where, start, count, seed, reach):
|
||||
rng = random.Random(seed)
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=.16); first = bpy.context.active_object; first.data.materials.append(glowing('spark', '#FF9A30', 5))
|
||||
for i in range(count):
|
||||
one = first if i == 0 else clone(first)
|
||||
away = Vector((rng.uniform(-1, 1), rng.uniform(-1, .3), rng.uniform(-.1, 1))).normalized() * rng.uniform(.4, 1) * reach
|
||||
life = rng.randint(70, 170)
|
||||
one.location = where; one.scale = (0, 0, 0); one.keyframe_insert('scale', frame=1); one.keyframe_insert('scale', frame=start - 1); one.keyframe_insert('location', frame=start)
|
||||
one.scale = (1, 1, 1); one.keyframe_insert('scale', frame=start)
|
||||
one.location = Vector(where) + away * .75; one.keyframe_insert('location', frame=start + life // 2)
|
||||
one.location = Vector(where) + away + Vector((0, 0, -reach * .3)); one.keyframe_insert('location', frame=start + life); one.keyframe_insert('scale', frame=start + life - 8)
|
||||
one.scale = (0, 0, 0); one.keyframe_insert('scale', frame=start + life)
|
||||
|
||||
def rope(name, a, b, mat, sag=1.1):
|
||||
curve = bpy.data.curves.new(name, 'CURVE'); curve.dimensions = '3D'; curve.bevel_depth = .09
|
||||
spline = curve.splines.new('POLY'); spline.points.add(10)
|
||||
for i in range(11):
|
||||
u = i / 10; p = Vector(a).lerp(Vector(b), u); spline.points[i].co = (p.x, p.y, p.z - math.sin(u * math.pi) * sag, 1)
|
||||
obj = bpy.data.objects.new(name, curve); bpy.context.collection.objects.link(obj); curve.materials.append(mat)
|
||||
|
||||
def red_carpet():
|
||||
"""A film premiere: a red carpet between gold posts and ropes, up to a lit picture palace, under camera flashes."""
|
||||
carpet, n, l = material('carpet'); bsdf = n['Principled BSDF']; bsdf.inputs['Base Color'].default_value = hexrgb('#C00A1C'); bsdf.inputs['Roughness'].default_value = .9
|
||||
box('carpet', 0, 70, 12, 260, .08, carpet)
|
||||
box('pavement', 0, 70, 400, 300, .1, stone('pavement', '#14141C', .35), -.1)
|
||||
gold = stone('gold', '#E0AC48', .25, 1); velvet = stone('velvet', '#A00C18', .7)
|
||||
for side in (-1, 1):
|
||||
last = None
|
||||
for y in range(-40, 190, 12):
|
||||
post = box(f'post{side}{y}', side * 7.4, y, .26, .26, 3.6, gold); ball = sphere(f'postball{side}{y}', .3, 16); ball.location = (side * 7.4, y, 3.7); ball.data.materials.append(gold)
|
||||
box(f'postfoot{side}{y}', side * 7.4, y, .8, .8, .12, gold)
|
||||
if last is not None: rope(f'rope{side}{y}', (side * 7.4, last, 3.2), (side * 7.4, y, 3.2), velvet)
|
||||
last = y
|
||||
# The picture palace at the end, its columns lit and its canopy running with bulbs.
|
||||
box('palace', 0, 214, 150, 30, 70, stone('palacewall', '#1A1420', .5))
|
||||
box('palacecornice', 0, 197, 150, 4, 2.4, gold, 46)
|
||||
for x in range(-60, 61, 15):
|
||||
if abs(x) > 12: box(f'column{x}', x, 197, 3.4, 3.4, 46, stone('columnstone', '#8A7858', .6))
|
||||
box('canopy', 0, 188, 66, 18, 2.4, stone('canopytop', '#18141C', .4), 14)
|
||||
box('doors', 0, 198.8, 24, .2, 13, glowing('doors', '#FFC873', 2.4))
|
||||
bulbs = glowing('bulbs', '#FFE2A0', 9)
|
||||
for x in range(-32, 33, 2):
|
||||
bulb_ = sphere(f'canopybulb{x}', .36, 10); bulb_.location = (x, 178.8, 15.2); bulb_.data.materials.append(bulbs)
|
||||
for z, width in ((24, 44), (40, 44)):
|
||||
for x in range(-width // 2, width // 2 + 1, 2):
|
||||
bulb_ = sphere(f'signbulb{x}{z}', .34, 10); bulb_.location = (x, 195, z); bulb_.data.materials.append(bulbs)
|
||||
for z in range(25, 40, 2):
|
||||
for x in (-22, 22):
|
||||
bulb_ = sphere(f'signbulb{x}{z}', .34, 10); bulb_.location = (x, 195, z); bulb_.data.materials.append(bulbs)
|
||||
box('palacename', 0, 196, 42, .3, 14, glowing('palacename', '#2A1408', .7), 25)
|
||||
for x in (-30, 30):
|
||||
data = bpy.data.lights.new(f'wash{x}', 'SPOT'); data.energy = 300000; data.spot_size = math.radians(48); data.spot_blend = .9; data.color = hexrgb('#FFD8A8')[:3]
|
||||
wash = bpy.data.objects.new(f'wash{x}', data); bpy.context.collection.objects.link(wash); wash.location = (x, 120, 30); wash.rotation_euler = (Vector((x * .5, 195, 18)) - Vector(wash.location)).to_track_quat('-Z', 'Y').to_euler()
|
||||
data = bpy.data.lights.new('carpetspot', 'SPOT'); data.energy = 500000; data.spot_size = math.radians(80); data.spot_blend = .9; data.color = hexrgb('#FFE8D0')[:3]
|
||||
wash = bpy.data.objects.new('carpetspot', data); bpy.context.collection.objects.link(wash); wash.location = (0, 60, 70); wash.rotation_euler = (0, 0, 0)
|
||||
# Press flashes from behind the ropes, going off at random.
|
||||
rng = random.Random(77)
|
||||
bulb = glowing('bulb', '#FFFFFF', 0)
|
||||
for i in range(34):
|
||||
side = rng.choice((-1, 1)); x, y, z = side * rng.uniform(9, 15), rng.uniform(84, 180), rng.uniform(3, 6.5)
|
||||
data = bpy.data.lights.new(f'flash{i}', 'POINT'); data.color = (1, 1, 1); data.shadow_soft_size = .3
|
||||
light = bpy.data.objects.new(f'flash{i}', data); bpy.context.collection.objects.link(light); light.location = (x, y, z)
|
||||
m = glowing(f'flashball{i}', '#FFFFFF', 0); ball = sphere(f'flashball{i}', .3, 12); ball.location = (x, y, z); ball.data.materials.append(m)
|
||||
strength = m.node_tree.nodes['Emission'].inputs['Strength']
|
||||
data.energy = 0; data.keyframe_insert('energy', frame=1); strength.default_value = 0; strength.keyframe_insert('default_value', frame=1)
|
||||
for pop in range(4):
|
||||
frame = rng.randint(4, 346)
|
||||
for at, level in ((frame - 1, 0), (frame, 1), (frame + 2, 0)):
|
||||
data.energy = level * 2600; data.keyframe_insert('energy', frame=at); strength.default_value = level * 90; strength.keyframe_insert('default_value', frame=at)
|
||||
# A silhouette of the photographer behind each flash.
|
||||
box(f'press{i}', x + side * .9, y, 1.4, .7, z - .4, stone('press', '#07070C'))
|
||||
data = bpy.data.lights.new('carpetlight', 'SUN'); data.energy = .25; data.color = hexrgb('#FFE2C0')[:3]; lamp = bpy.data.objects.new('carpetlight', data); bpy.context.collection.objects.link(lamp); lamp.rotation_euler = (math.radians(60), 0, math.radians(10))
|
||||
|
||||
def lanterns(count, seed, area):
|
||||
"""Paper lanterns rising from the water, each with a candle's glow."""
|
||||
rng = random.Random(seed)
|
||||
glow_colours = ('#FF9A4A', '#FFB45C', '#FF8A5A')
|
||||
mats = [glowing(f'lantern{i}', c, 2.2) for i, c in enumerate(glow_colours)]
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(segments=16, ring_count=10, radius=1); seed_ball = bpy.context.active_object; bpy.ops.object.shade_smooth()
|
||||
for i in range(count):
|
||||
one = seed_ball if i == 0 else clone(seed_ball)
|
||||
if i: one.data = seed_ball.data.copy()
|
||||
one.data.materials.clear(); one.data.materials.append(rng.choice(mats))
|
||||
x, y, z = rng.uniform(*area[0]), rng.uniform(*area[1]), rng.uniform(*area[2])
|
||||
size = rng.uniform(.7, 1.15); one.scale = (size * .8, size * .8, size)
|
||||
one.location = (x, y, z); one.keyframe_insert('location', frame=1)
|
||||
one.location = (x + rng.uniform(-2, 2), y + rng.uniform(-2, 2), z + rng.uniform(12, 26)); one.keyframe_insert('location', frame=350)
|
||||
|
||||
def petals(count, seed, area):
|
||||
"""Rose petals falling past the camera."""
|
||||
rng = random.Random(seed)
|
||||
bpy.ops.mesh.primitive_plane_add(size=1); first = bpy.context.active_object
|
||||
bm = first.data; first.data.materials.append(stone('petal', '#FF5A8C', .45))
|
||||
for i in range(count):
|
||||
one = first if i == 0 else clone(first)
|
||||
x, y, z = rng.uniform(*area[0]), rng.uniform(*area[1]), rng.uniform(*area[2])
|
||||
one.scale = (rng.uniform(.28, .5), rng.uniform(.18, .32), 1)
|
||||
one.location = (x, y, z); one.rotation_euler = (rng.random() * 6, rng.random() * 6, rng.random() * 6); one.keyframe_insert('location', frame=1); one.keyframe_insert('rotation_euler', frame=1)
|
||||
one.location = (x + rng.uniform(1, 6), y + rng.uniform(-2, 2), z - rng.uniform(10, 22)); one.rotation_euler = (rng.random() * 12, rng.random() * 12, rng.random() * 12); one.keyframe_insert('location', frame=350); one.keyframe_insert('rotation_euler', frame=350)
|
||||
|
||||
def archive():
|
||||
"""A long, dark records room: filing cabinets each side, strip lights failing overhead."""
|
||||
steel = stone('cabinet', '#3A4048', .45, .7); front = stone('drawer', '#4A5058', .5, .6); handle = stone('handle', '#B8BCC0', .3, 1); label = stone('label', '#D8D0B8', .8); red = glowing('stamp', '#C81828', 1.4)
|
||||
box('archivefloor', 0, 110, 60, 320, .1, stone('archivefloormat', '#101114', .28, .1), -.1)
|
||||
box('archiveroof', 0, 110, 60, 320, .1, stone('roof', '#0A0A0D', .9), 18)
|
||||
box('archiveend', 0, 270, 60, .5, 18, stone('endwall', '#15161A', .9))
|
||||
rng = random.Random(31)
|
||||
for side in (-1, 1):
|
||||
for row in range(26):
|
||||
y = -30 + row * 11
|
||||
box(f'cabinet{side}{row}', side * 9, y, 3.4, 10.4, 15, steel)
|
||||
for level in range(5):
|
||||
z = .5 + level * 2.9
|
||||
box(f'drawer{side}{row}{level}', side * 7.25, y, .1, 9.6, 2.6, front, z)
|
||||
box(f'handle{side}{row}{level}', side * 7.15, y, .12, 1.6, .22, handle, z + 1.15)
|
||||
box(f'label{side}{row}{level}', side * 7.15, y - 2.6, .05, 2, .8, red if rng.random() < .08 else label, z + 1.3)
|
||||
for i, y in enumerate(range(-20, 260, 34)):
|
||||
lamp = box(f'strip{i}', 0, y, .5, 8, .2, glowing(f'strip{i}', '#BFD6E8', 7), 17.7)
|
||||
data = bpy.data.lights.new(f'stripglow{i}', 'POINT'); data.color = hexrgb('#BFD6E8')[:3]; data.shadow_soft_size = 1.5
|
||||
light = bpy.data.objects.new(f'stripglow{i}', data); bpy.context.collection.objects.link(light); light.location = (0, y, 16.8)
|
||||
power = lamp.data.materials[0].node_tree.nodes['Emission'].inputs['Strength']
|
||||
for frame in sorted({1, 350} | {rng.randint(2, 349) for _ in range(9 if i % 3 == 0 else 2)}):
|
||||
on = rng.random() > (.42 if i % 3 == 0 else .15)
|
||||
data.energy = 2200 if on else 140; data.keyframe_insert('energy', frame=frame); power.default_value = 7 if on else .2; power.keyframe_insert('default_value', frame=frame)
|
||||
for fc in (data.animation_data.action.layers[0].strips[0].channelbag(data.animation_data.action_slot).fcurves if hasattr(data.animation_data.action, 'layers') else data.animation_data.action.fcurves):
|
||||
for p in fc.keyframe_points: p.interpolation = 'CONSTANT'
|
||||
# Dust hanging in the air.
|
||||
rng = random.Random(8)
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=.05); dust = bpy.context.active_object; dust.data.materials.append(glowing('dustmote', '#C8D0D8', 1.6))
|
||||
for i in range(240):
|
||||
one = dust if i == 0 else clone(dust)
|
||||
one.location = (rng.uniform(-6, 6), rng.uniform(-20, 240), rng.uniform(.5, 16)); one.keyframe_insert('location', frame=1); one.location.z += rng.uniform(-.6, .8); one.location.x += rng.uniform(-.8, .8); one.keyframe_insert('location', frame=350)
|
||||
|
||||
def desert_road():
|
||||
"""A straight highway across the desert at dusk, with mesas, telegraph poles and a neon motel sign."""
|
||||
ground('desert', '#7A3C34', '#B05C40')
|
||||
road, n, l = material('blacktop'); bsdf = n['Principled BSDF']; bsdf.inputs['Base Color'].default_value = hexrgb('#16141A'); bsdf.inputs['Roughness'].default_value = .5
|
||||
box('highway', 0, 400, 13, 1500, .06, road)
|
||||
paint = stone('paint', '#D8C89A', .6)
|
||||
for y in range(-60, 1100, 16):
|
||||
box(f'dash{y}', 0, y, .32, 6, .08, paint)
|
||||
for side in (-1, 1):
|
||||
box(f'edge{side}', side * 6, 400, .22, 1500, .08, paint)
|
||||
rock_, n, l = material('mesa'); bsdf = n['Principled BSDF']; bsdf.inputs['Roughness'].default_value = 1
|
||||
l.new(noise_ramp(n, l, n.new('ShaderNodeTexCoord').outputs['Object'], .9, 5, [(.3, '#7A2C30'), (.65, '#B84C3C')]).outputs['Color'], bsdf.inputs['Base Color'])
|
||||
rng = random.Random(61)
|
||||
for i, (x, y, w, h) in enumerate(((-120, 520, 90, 48), (-210, 700, 140, 70), (150, 620, 110, 62), (260, 820, 170, 80), (70, 860, 90, 56), (-90, 900, 120, 66))):
|
||||
bpy.ops.mesh.primitive_cylinder_add(vertices=9, radius=w / 2, depth=h, location=(x, y, h / 2)); m = bpy.context.active_object; m.name = f'mesa{i}'
|
||||
for v in m.data.vertices:
|
||||
if v.co.z > 0: v.co.x *= .68; v.co.y *= .68
|
||||
v.co.x *= rng.uniform(.9, 1.1)
|
||||
m.data.materials.append(rock_)
|
||||
pole = stone('pole', '#120C10', .9)
|
||||
for y in range(-40, 760, 54):
|
||||
box(f'pole{y}', -13, y, .4, .4, 11, pole); box(f'arm{y}', -13, y, 3.4, .25, .25, pole, 9.8)
|
||||
# The motel: its long low block, and the sign with a neon arrow.
|
||||
wall = window_wall('#2A2030', 2, ('#FFC873', '#FF8AB4', '#FFD27A'), .3, (2.2, 2.6))
|
||||
box('motel', 64, 216, 60, 11, 6, wall)
|
||||
box('motelroof', 64, 216, 64, 13, .7, stone('motelroofmat', '#0C0A10'), 6)
|
||||
box('signpost', 30, 186, .9, .9, 19, pole)
|
||||
pink, cyan, amber = glowing('neonpink', '#FF2E7E', 9), glowing('neoncyan', '#28E0FF', 7), glowing('neonamber', '#FFB42E', 8)
|
||||
board = [(-5, 0), (5, 0), (5, 5.5), (-5, 5.5)]
|
||||
flat('signboard', [board], (30, 186, 18), (14, 60, 18), stone('signback', '#1C1022', .5))
|
||||
outline = [[(-5, -.15), (5, -.15), (5, .1), (-5, .1)], [(-5, 5.4), (5, 5.4), (5, 5.65), (-5, 5.65)], [(-5.15, 0), (-4.9, 0), (-4.9, 5.5), (-5.15, 5.5)], [(4.9, 0), (5.15, 0), (5.15, 5.5), (4.9, 5.5)]]
|
||||
flat('signedge', outline, (30, 185.9, 18), (14, 60, 18), pink)
|
||||
bars = [[(-4 + i * 1.9, 3.3), (-2.6 + i * 1.9, 3.3), (-2.6 + i * 1.9, 4.6), (-4 + i * 1.9, 4.6)] for i in range(4)]
|
||||
flat('signbars', bars, (30, 185.85, 18), (14, 60, 18), cyan)
|
||||
flat('signstrip', [[(-4, 1.4), (4, 1.4), (4, 2.5), (-4, 2.5)]], (30, 185.85, 18), (14, 60, 18), amber)
|
||||
# The arrow, pointing the way off the highway.
|
||||
flat('signarrow', [[(-6, -3.2), (2.6, -3.2), (2.6, -2), (-6, -2)], [(2.6, -4.2), (5.4, -2.6), (2.6, -1)]], (30, 185.85, 18), (14, 60, 18), pink)
|
||||
data = bpy.data.lights.new('signglow', 'POINT'); data.energy = 2600; data.color = hexrgb('#FF4A90')[:3]; lamp = bpy.data.objects.new('signglow', data); bpy.context.collection.objects.link(lamp); lamp.location = (29, 178, 14)
|
||||
|
||||
if SCENE == 'orbit-1':
|
||||
world_tint = ('#061A3C', '#0E3A6E')
|
||||
stars_world()
|
||||
@@ -917,7 +1151,7 @@ elif SCENE == 'orbit-gold':
|
||||
rng = random.Random(21)
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=.09); seed = bpy.context.active_object; seed.data.materials.append(glowing('dust', '#FFCB5A', 5))
|
||||
for i in range(420):
|
||||
speck = seed.copy(); bpy.context.collection.objects.link(speck); speck.location = (rng.uniform(-70, 40), rng.uniform(-40, 60), rng.uniform(-30, 30)); speck.scale = (rng.uniform(.5, 2.2),) * 3
|
||||
speck = clone(seed); speck.location = (rng.uniform(-70, 40), rng.uniform(-40, 60), rng.uniform(-30, 30)); speck.scale = (rng.uniform(.5, 2.2),) * 3
|
||||
speck.keyframe_insert('location', frame=1); speck.location.z += rng.uniform(2, 7); speck.location.x += rng.uniform(-3, 3); speck.keyframe_insert('location', frame=350)
|
||||
camera_path([(1, (34, -46, -14), (6, 0, 0)), (260, (-27, -78, 8), (-24, 0, 1)), (350, (-27, -78, 8), (-24, 0, 1))])
|
||||
elif SCENE == 'evidence':
|
||||
@@ -948,6 +1182,73 @@ elif SCENE == 'orbit-britx':
|
||||
box('southbank', -160, 300, 170, 800, 3, slate); box('northbank', 180, 300, 230, 800, 3, slate)
|
||||
# Skims the water, climbs over the walkway of the bridge, and comes down the river to Big Ben.
|
||||
camera_path([(1, (-6, -60, 7), (0, 70, 24)), (130, (-2, 62, 44), (10, 200, 42)), (285, (-32, 112, 30), (-8, 262, 58)), (350, (-32, 116, 30), (-8, 262, 58))], lens=30)
|
||||
elif SCENE == 'orbit-wild':
|
||||
sky_world([(0, '#FFB050'), (.06, '#E86A30'), (.16, '#8A2E44'), (.42, '#22183C')])
|
||||
ground('savannah', '#7A4418', '#B87428')
|
||||
black = stone('shade', '#050302', 1)
|
||||
sun((-.5, .8, -.32), 1.5, '#FFAA60')
|
||||
flare((320, 520, 58), 96, '#FFD27A', 3.2)
|
||||
for i, (x, y, size) in enumerate(((-70, 170, 1.2), (84, 92, 1.5), (120, 300, 1.5), (-120, 330, 1.4), (-20, 300, 1), (-160, 200, 1.6))):
|
||||
acacia(x, y, black, i, size)
|
||||
# A giraffe family and an elephant herd crossing in front of the sun.
|
||||
for i, (x, y, size, side) in enumerate(((22, 60, 1.6, 1), (31, 66, 1.3, 1), (15, 68, .95, 1))):
|
||||
flat(f'giraffe{i}', [[(p[0] * size * side, p[1] * size) for p in part] for part in GIRAFFE], (x, y, 0), (x, -50, 0), black)
|
||||
for i, (x, y, size) in enumerate(((58, 112, 1.6), (44, 120, 1.15), (72, 124, 1.25))):
|
||||
flat(f'elephant{i}', [[(p[0] * size, p[1] * size) for p in part] for part in ELEPHANT], (x, y, 0), (x, -50, 0), black)
|
||||
camera_path([(1, (-40, -40, 4), (-10, 100, 24)), (280, (-14, -6, 4.5), (2, 100, 24)), (350, (-14, -3, 4.5), (2, 100, 24))], lens=30)
|
||||
elif SCENE == 'orbit-action':
|
||||
sky_world([(0, '#2A0E08'), (.08, '#14080C'), (.3, '#05060C')])
|
||||
ground('dirt', '#0E0A08', '#1E1610')
|
||||
black = stone('shade', '#040304', 1)
|
||||
# A refinery on the skyline, about to go up.
|
||||
for i, (x, w, h) in enumerate(((-40, 18, 16), (-14, 12, 24), (6, 22, 13), (30, 16, 26), (54, 22, 18), (80, 18, 12))):
|
||||
box(f'plant{i}', x, 72, w, 14, h, stone('plant', '#16120F', .7))
|
||||
for i, x in enumerate((-30, 18, 62)):
|
||||
bpy.ops.mesh.primitive_cylinder_add(vertices=48, radius=7, depth=14, location=(x, 58, 7)); bpy.context.active_object.data.materials.append(stone('tank', '#2A2420', .5, .5))
|
||||
for i, x in enumerate((-4, 42)):
|
||||
box(f'stack{i}', x, 76, 2, 2, 44, stone('plant', '#16120F', .7))
|
||||
fireball('blast1', (30, 56, 12), 28, 34); fireball('blast2', (56, 64, 13), 96, 26); fireball('blast3', (14, 64, 15), 150, 24)
|
||||
sparks((30, 54, 10), 28, 170, 1, 66); sparks((14, 60, 12), 150, 110, 2, 58)
|
||||
# Someone walking away from it, and a wreck on the road.
|
||||
caped_hero((10, -6, 0), (10, -90, 0), black, 1.15, cape_on=False)
|
||||
box('wreck', -18, 8, 9, 4, 2, black); box('wrecktop', -18, 8, 4.5, 3.4, 1.4, black, 2)
|
||||
camera_path([(1, (-22, -66, 3), (6, 30, 12)), (24, (-22, -66, 3), (6, 30, 12)), (34, (-21.4, -65, 3.5), (6.2, 30, 11.4)), (44, (-22.2, -64, 2.8), (5.8, 30, 12.3)), (280, (-20, -46, 4), (0, 30, 11)), (350, (-20, -44, 4), (0, 30, 11))], lens=30)
|
||||
elif SCENE == 'orbit-premiere':
|
||||
world_tint = ('#080A18', '#141A34')
|
||||
stars_world(.6)
|
||||
red_carpet()
|
||||
for i, (x, keys) in enumerate(((-70, [(1, -24), (160, 18), (350, -8)]), (-34, [(1, 16), (170, -20), (350, 10)]), (34, [(1, -18), (160, 22), (350, -8)]), (70, [(1, 22), (180, -16), (350, 12)]))):
|
||||
sweep(beam(f'search{i}', (x, 238, 66), 190, 10, '#FFE2A8', .55), keys)
|
||||
camera_path([(1, (0, -46, 3), (0, 100, 8)), (280, (-5, 36, 8), (-44, 200, 30)), (350, (-5, 40, 8), (-44, 200, 30))], lens=30)
|
||||
elif SCENE == 'orbit-romance':
|
||||
sky_world([(0, '#FFB088'), (.07, '#F0708C'), (.2, '#8A3C90'), (.48, '#1C1648')])
|
||||
water, n, l = material('lake'); bsdf = n['Principled BSDF']; bsdf.inputs['Base Color'].default_value = hexrgb('#141030'); bsdf.inputs['Roughness'].default_value = .1; bsdf.inputs['Metallic'].default_value = .4
|
||||
ripple = n.new('ShaderNodeTexNoise'); ripple.inputs['Scale'].default_value = .8; ripple.inputs['Detail'].default_value = 5
|
||||
tiny = n.new('ShaderNodeBump'); tiny.inputs['Strength'].default_value = .08; l.new(ripple.outputs['Fac'], tiny.inputs['Height']); l.new(tiny.outputs[0], bsdf.inputs['Normal'])
|
||||
box('lake', 0, 500, 1800, 1800, .1, water, -.1)
|
||||
hills = stone('hills', '#120C2A', 1)
|
||||
rng = random.Random(4)
|
||||
for i in range(16):
|
||||
bump = sphere(f'hill{i}', 1, 24); bump.data.materials.append(hills); bump.location = (-420 + i * 56, 620 + rng.uniform(-30, 30), 0); bump.scale = (rng.uniform(60, 110), 40, rng.uniform(16, 48))
|
||||
flare((300, 560, 74), 34, '#FFC090', 1.6)
|
||||
lanterns(170, 3, ((-110, 130), (20, 300), (1, 42)))
|
||||
petals(220, 12, ((-34, 26), (-34, 18), (-6, 34)))
|
||||
camera_path([(1, (-12, -60, 3), (2, 200, 26)), (280, (-14, -24, 5), (2, 200, 38)), (350, (-14, -21, 5), (2, 200, 38))], lens=30)
|
||||
elif SCENE == 'orbit-dark-files':
|
||||
scene.world = bpy.data.worlds.new('dark'); scene.world.use_nodes = True; scene.world.node_tree.nodes['Background'].inputs['Color'].default_value = (.001, .001, .002, 1)
|
||||
archive()
|
||||
cam = camera_path([(1, (0, -30, 7), (0, 90, 7)), (285, (-.8, 150, 6.5), (2.6, 262, 7)), (350, (-.8, 158, 6.5), (2.6, 262, 7))], lens=28)
|
||||
# A hand torch, searching the drawers.
|
||||
data = bpy.data.lights.new('torch', 'SPOT'); data.energy = 9000; data.spot_size = math.radians(26); data.spot_blend = .35; data.color = hexrgb('#FFF0D8')[:3]; data.shadow_soft_size = .1
|
||||
torch = bpy.data.objects.new('torch', data); bpy.context.collection.objects.link(torch); torch.parent = cam; torch.location = (.6, -.5, 0)
|
||||
for frame, turn_x, turn_y in ((1, 0, -.5), (90, -.14, .55), (190, .2, -.42), (280, -.1, .3), (350, .02, .22)):
|
||||
torch.rotation_euler = (turn_x, turn_y, 0); torch.keyframe_insert('rotation_euler', frame=frame)
|
||||
elif SCENE == 'orbit-detour':
|
||||
sky_world([(0, '#FF8A4A'), (.05, '#F04A78'), (.16, '#7A2A98'), (.42, '#141440')])
|
||||
sun((.3, .85, -.3), 1.3, '#FFB8A0')
|
||||
desert_road()
|
||||
flare((170, 980, 26), 30, '#FFD27A', 3)
|
||||
camera_path([(1, (0, -60, 2.2), (0, 60, 3)), (285, (-3, 116, 3.2), (6, 240, 15)), (350, (-3, 120, 3.2), (6, 240, 15))], lens=30)
|
||||
else:
|
||||
raise SystemExit('unknown scene')
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Three claw marks slashed beside the Wild logo, as frames to lay over the finished ident, just before the logo settles.
|
||||
claw_marks.py <logo_word.png> <logo copy without marks, to write> <out folder>
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
MARKS_FROM = 940
|
||||
word = Image.open(sys.argv[1]).convert('RGBA')
|
||||
word.paste((0, 0, 0, 0), (MARKS_FROM, 0, word.width, word.height))
|
||||
word.save(sys.argv[2])
|
||||
out = Path(sys.argv[3]); out.mkdir(parents=True, exist_ok=True)
|
||||
SS = 2
|
||||
# Each mark runs from its top right to its bottom left, widest in the middle. Screen position as compose_ident.sh places the logo.
|
||||
MARKS = [dict(top=(668, 244), bottom=(626, 416), start=232), dict(top=(708, 236), bottom=(664, 422), start=238), dict(top=(748, 244), bottom=(704, 416), start=244)]
|
||||
STROKE = 6
|
||||
HALF = 11
|
||||
|
||||
def shape(mark, reach):
|
||||
(tx, ty), (bx, by) = mark['top'], mark['bottom']
|
||||
def at(u): return tx + (bx - tx) * u, ty + (by - ty) * u
|
||||
points = [at(0)]
|
||||
for u, side in ((.3, 1), (.6, 1)):
|
||||
x, y = at(u * reach / 1); points.append((x + HALF * side * (1 - abs(u - .45)), y + 1))
|
||||
points.append(at(reach))
|
||||
for u, side in ((.6, -1), (.3, -1)):
|
||||
x, y = at(u * reach / 1); points.append((x + HALF * side * (1 - abs(u - .45)) * .9, y - 1))
|
||||
return [(x * SS, y * SS) for x, y in points]
|
||||
|
||||
def frame_picture(frame):
|
||||
solid = Image.new('L', (1280 * SS, 720 * SS)); hot = Image.new('L', (1280 * SS, 720 * SS))
|
||||
for mark in MARKS:
|
||||
age = frame - mark['start']
|
||||
if age < 0: continue
|
||||
reach = min(1, (age + 1) / STROKE)
|
||||
ImageDraw.Draw(solid).polygon(shape(mark, reach), fill=255)
|
||||
heat = max(0, 1 - age / 16)
|
||||
if heat: ImageDraw.Draw(hot).polygon(shape(mark, reach), fill=int(255 * heat))
|
||||
picture = Image.new('RGBA', (1280 * SS, 720 * SS), (240, 128, 24, 0)); picture.putalpha(solid)
|
||||
white = Image.new('RGBA', picture.size, (255, 240, 200, 255)); picture = Image.composite(white, picture, hot)
|
||||
# A hot glow that dies back to a low ember.
|
||||
glow_colour = Image.new('RGBA', picture.size, (255, 110, 20, 255))
|
||||
glow = solid.filter(ImageFilter.GaussianBlur(16)).point(lambda v: v * .55 + 0)
|
||||
under = glow_colour.copy(); under.putalpha(glow)
|
||||
under.alpha_composite(picture)
|
||||
return under.resize((1280, 720), Image.LANCZOS)
|
||||
|
||||
settled = frame_picture(MARKS[-1]['start'] + 40)
|
||||
blank = Image.new('RGBA', (1280, 720))
|
||||
for frame in range(1, 351):
|
||||
if frame < MARKS[0]['start']: picture = blank
|
||||
elif frame >= MARKS[-1]['start'] + 40: picture = settled
|
||||
else: picture = frame_picture(frame)
|
||||
picture.save(out / f'over_{frame:04d}.png')
|
||||
Reference in New Issue
Block a user