mirror of
https://github.com/stevenrobertson/cuburn.git
synced 2025-02-05 11:40:04 -05:00
Add blending with lookup. In theory.
This commit is contained in:
parent
a4178c60fb
commit
627bcf8921
@ -9,14 +9,73 @@ from itertools import izip_longest
|
||||
from scipy.ndimage.filters import gaussian_filter1d
|
||||
|
||||
import spectypes
|
||||
import spec
|
||||
import util
|
||||
from util import get
|
||||
import specs
|
||||
from use import Wrapper
|
||||
from util import get, json_encode, resolve_spec
|
||||
import variations
|
||||
|
||||
# TODO: move to better place before checkin
|
||||
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
|
||||
def node_to_anim(node, half):
|
||||
if half:
|
||||
osrc, odst = -0.25, 0.25
|
||||
else:
|
||||
osrc, odst = 0, 1
|
||||
src = apply_temporal_offset(node, osrc)
|
||||
dst = apply_temporal_offset(node, odst)
|
||||
edge = dict(blend=dict(duration=odst-osrc, xform_sort='natural'))
|
||||
return blend(src, dst, edge)
|
||||
|
||||
def edge_to_anim(gdb, edge):
|
||||
edge = resolve(gdb, edge)
|
||||
src, osrc = _split_ref_id(edge.link.src)
|
||||
dst, odst = _split_ref_id(edge.link.dst)
|
||||
src = apply_temporal_offset(resolve(gdb, src), osrc)
|
||||
dst = apply_temporal_offset(resolve(gdb, dst), odst)
|
||||
return blend(src, dst, edit)
|
||||
|
||||
def resolve(gdb, item):
|
||||
"""
|
||||
Given an item, recursively retrieve its base items, then merge according
|
||||
to type. Returns the merged dict.
|
||||
"""
|
||||
is_edge = (item['type'] == 'edge')
|
||||
spec = specs.toplevels[item['type']]
|
||||
def go(i):
|
||||
if i.get('base') is not None:
|
||||
return go(gdb.get(i['base'])) + [i]
|
||||
return [i]
|
||||
items = map(flatten, go(item))
|
||||
out = {}
|
||||
|
||||
for k in set(ik for i in items for ik in i):
|
||||
sp = _resolve_spec(spec, k)
|
||||
vs = [i.get(k) for i in items if k in i]
|
||||
# TODO: dict and list negation; early-stage removal of negated knots?
|
||||
if is_edge and isinstance(sp, (Spline, List)):
|
||||
r = sum(vs, [])
|
||||
else:
|
||||
r = vs[-1]
|
||||
out[k] = r
|
||||
return unflatten(out)
|
||||
|
||||
def _split_ref_id(s):
|
||||
sp = s.split('@')
|
||||
if len(sp) == 1:
|
||||
return sp, 0
|
||||
return sp[0], float(sp[1])
|
||||
|
||||
def apply_temporal_offset(node, offset=0):
|
||||
"""
|
||||
Given a ``node`` dict, return a node with all periodic splines rotated by
|
||||
``offset * velocity``, with the same velocity.
|
||||
"""
|
||||
class TemporalOffsetWrapper(Wrapper):
|
||||
def wrap_spline(self, path, spec, val):
|
||||
if spec.period is not None and isinstance(val, list) and val[1]:
|
||||
position, velocity = val
|
||||
return [position + offset * velocity, velocity]
|
||||
return val
|
||||
wr = TemporalOffsetWrapper(node)
|
||||
return wr.visit(wr)
|
||||
|
||||
def blend(src, dst, edit={}):
|
||||
"""
|
||||
@ -26,19 +85,22 @@ def blend(src, dst, edit={}):
|
||||
animation. These should be plain node dicts (hierarchical, pre-merged,
|
||||
and adjusted for loop temporal offset).
|
||||
|
||||
``edit`` is an optional edit dict, also hierarchical and pre-merged.
|
||||
``edge`` is an edge dict, also hierarchical and pre-merged. (It can be
|
||||
empty, in violation of the spec, to support rendering straight from nodes
|
||||
without having to insert anything into the genome database.)
|
||||
|
||||
Returns the animation spec as a plain dict.
|
||||
"""
|
||||
# By design, the blend element will contain only scalar values (no
|
||||
# splines or hierarchy), so this can be done blindly
|
||||
opts = dict(default_blend_opts)
|
||||
opts = {}
|
||||
for d in src, dst, edit:
|
||||
opts.update(d.get('blend', {}))
|
||||
opts = Wrapper(opts, specs.blend)
|
||||
|
||||
blended = merge_nodes(specs.node, src, dst, edit, opts['nloops'])
|
||||
name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
|
||||
explicit=zip(*opts.get('xform_map', [])))
|
||||
blended = merge_nodes(specs.node, src, dst, edit, opts.nloops)
|
||||
name_map = sort_xforms(src['xforms'], dst['xforms'], opts.xform_sort,
|
||||
explicit=zip(*opts.xform_map))
|
||||
|
||||
blended['xforms'] = {}
|
||||
for (sxf_key, dxf_key) in name_map:
|
||||
@ -49,7 +111,7 @@ def blend(src, dst, edit={}):
|
||||
blended['xforms'][bxf_key] = blend_xform(
|
||||
src['xforms'].get(sxf_key),
|
||||
dst['xforms'].get(dxf_key),
|
||||
xf_edits, opts['nloops'])
|
||||
xf_edits, opts.nloops)
|
||||
|
||||
if 'final_xform' in src or 'final_xform' in dst:
|
||||
blended['final_xform'] = blend_xform(src.get('final_xform'),
|
||||
@ -58,7 +120,7 @@ def blend(src, dst, edit={}):
|
||||
# TODO: write 'info' section
|
||||
# TODO: palflip
|
||||
blended['type'] = 'animation'
|
||||
blended.setdefault('time', {})['duration'] = opts['duration']
|
||||
blended.setdefault('time', {})['duration'] = opts.duration
|
||||
return blended
|
||||
|
||||
def merge_edits(sv, av, bv):
|
||||
@ -75,16 +137,16 @@ def merge_edits(sv, av, bv):
|
||||
else:
|
||||
return bv if bv is not None else av
|
||||
|
||||
def tospline(spl, src, dst, edit, loops):
|
||||
def split_node_val(val):
|
||||
def split_node_val(spl, val):
|
||||
if val is None:
|
||||
return spl.default, 0
|
||||
if isinstance(val, (int, float)):
|
||||
return val, 0
|
||||
return val
|
||||
|
||||
sp, sv = split_node_val(src) # position, velocity
|
||||
dp, dv = split_node_val(dst)
|
||||
def tospline(spl, src, dst, edit, loops):
|
||||
sp, sv = split_node_val(spl, src) # position, velocity
|
||||
dp, dv = split_node_val(spl, dst)
|
||||
|
||||
# For variation parameters, copy missing values instead of using defaults
|
||||
if spl.var:
|
||||
@ -285,4 +347,4 @@ def palflip(gnm):
|
||||
if __name__ == "__main__":
|
||||
import sys, json
|
||||
a, b, c = [json.load(open(f+'.json')) for f in 'abc']
|
||||
print util.json_encode(blend(a, b, c))
|
||||
print json_encode(blend(a, b, c))
|
||||
|
@ -8,6 +8,9 @@ import numpy as np
|
||||
from variations import var_params
|
||||
import util
|
||||
|
||||
# Re-exported
|
||||
from blend import node_to_anim, edge_to_anim
|
||||
|
||||
class XMLGenomeParser(object):
|
||||
"""
|
||||
Parse an XML genome into a list of dictionaries.
|
||||
@ -163,8 +166,10 @@ def apply_structure(struct, src):
|
||||
out[l[0]] = v
|
||||
return out
|
||||
|
||||
def convert_flame(flame):
|
||||
return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
|
||||
def flam3_to_node(flame):
|
||||
n = util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
|
||||
n['type'] = 'node'
|
||||
return n
|
||||
|
||||
def convert_file(path):
|
||||
"""Quick one-shot conversion for an XML genome."""
|
||||
@ -173,7 +178,7 @@ def convert_file(path):
|
||||
warnings.warn("Lot of flames in this file. Sure it's not a "
|
||||
"frame-based animation?")
|
||||
for flame in flames:
|
||||
yield convert_flame(flame)
|
||||
yield flam3_to_node(flame)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
@ -68,6 +68,13 @@ time = (
|
||||
, 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
|
||||
})
|
||||
|
||||
blend = (
|
||||
{ 'nloops': scalar(2)
|
||||
, 'duration': scalar(2)
|
||||
, 'xform_sort': enum('weightflip weight natural color')
|
||||
, 'xform_map': list_(list_(String('xfid'), d='A pair of src, dst IDs'))
|
||||
})
|
||||
|
||||
base = (
|
||||
{ 'name': String("Human-readable name of this work")
|
||||
, 'camera': camera
|
||||
|
@ -62,6 +62,19 @@ class Wrapper(object):
|
||||
return self.spec.type
|
||||
return self.spec[name]
|
||||
|
||||
@classmethod
|
||||
def visit(cls, obj):
|
||||
"""
|
||||
Visit every node. Note that for simplicity, this function will be
|
||||
called on all elements (i.e. pivoting to a new Wrapper type inside the
|
||||
wrapping function and overriding visit() won't do anything).
|
||||
"""
|
||||
if isinstance(obj, (Wrapper, dict)):
|
||||
return dict((k, cls.visit(obj[k])) for k in obj)
|
||||
elif isinstance(obj, list):
|
||||
return [cls.visit(o) for o in obj]
|
||||
return obj
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.wrap(name, self.get_spec(name), self._val.get(name))
|
||||
|
||||
@ -155,6 +168,7 @@ class SplineEval(object):
|
||||
return times, vals, t, scale
|
||||
|
||||
def __call__(self, itime, deriv=0):
|
||||
# TODO: respect 'interp' THIS IS IMPORTANT.
|
||||
times, vals, t, scale = self.find_knots(itime)
|
||||
|
||||
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
|
||||
|
@ -2,6 +2,7 @@ import base64
|
||||
import numpy as np
|
||||
|
||||
from cuburn.code.util import crep
|
||||
import spectypes
|
||||
|
||||
def get(dct, default, *keys):
|
||||
if len(keys) == 1:
|
||||
@ -49,6 +50,13 @@ def unflatten(kvlist):
|
||||
go(out, k.split('.'), v)
|
||||
return out
|
||||
|
||||
def resolve_spec(sp, path):
|
||||
for name in path:
|
||||
if isinstance(sp, spectypes.Map):
|
||||
sp = sp.type
|
||||
else:
|
||||
sp = sp[name]
|
||||
return sp
|
||||
|
||||
def palette_decode(datastrs):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user