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
|
from scipy.ndimage.filters import gaussian_filter1d
|
||||||
|
|
||||||
import spectypes
|
import spectypes
|
||||||
import spec
|
import specs
|
||||||
import util
|
from use import Wrapper
|
||||||
from util import get
|
from util import get, json_encode, resolve_spec
|
||||||
import variations
|
import variations
|
||||||
|
|
||||||
# TODO: move to better place before checkin
|
def node_to_anim(node, half):
|
||||||
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
|
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={}):
|
def blend(src, dst, edit={}):
|
||||||
"""
|
"""
|
||||||
@ -26,19 +85,22 @@ def blend(src, dst, edit={}):
|
|||||||
animation. These should be plain node dicts (hierarchical, pre-merged,
|
animation. These should be plain node dicts (hierarchical, pre-merged,
|
||||||
and adjusted for loop temporal offset).
|
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.
|
Returns the animation spec as a plain dict.
|
||||||
"""
|
"""
|
||||||
# By design, the blend element will contain only scalar values (no
|
# By design, the blend element will contain only scalar values (no
|
||||||
# splines or hierarchy), so this can be done blindly
|
# splines or hierarchy), so this can be done blindly
|
||||||
opts = dict(default_blend_opts)
|
opts = {}
|
||||||
for d in src, dst, edit:
|
for d in src, dst, edit:
|
||||||
opts.update(d.get('blend', {}))
|
opts.update(d.get('blend', {}))
|
||||||
|
opts = Wrapper(opts, specs.blend)
|
||||||
|
|
||||||
blended = merge_nodes(specs.node, src, dst, edit, opts['nloops'])
|
blended = merge_nodes(specs.node, src, dst, edit, opts.nloops)
|
||||||
name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
|
name_map = sort_xforms(src['xforms'], dst['xforms'], opts.xform_sort,
|
||||||
explicit=zip(*opts.get('xform_map', [])))
|
explicit=zip(*opts.xform_map))
|
||||||
|
|
||||||
blended['xforms'] = {}
|
blended['xforms'] = {}
|
||||||
for (sxf_key, dxf_key) in name_map:
|
for (sxf_key, dxf_key) in name_map:
|
||||||
@ -49,7 +111,7 @@ def blend(src, dst, edit={}):
|
|||||||
blended['xforms'][bxf_key] = blend_xform(
|
blended['xforms'][bxf_key] = blend_xform(
|
||||||
src['xforms'].get(sxf_key),
|
src['xforms'].get(sxf_key),
|
||||||
dst['xforms'].get(dxf_key),
|
dst['xforms'].get(dxf_key),
|
||||||
xf_edits, opts['nloops'])
|
xf_edits, opts.nloops)
|
||||||
|
|
||||||
if 'final_xform' in src or 'final_xform' in dst:
|
if 'final_xform' in src or 'final_xform' in dst:
|
||||||
blended['final_xform'] = blend_xform(src.get('final_xform'),
|
blended['final_xform'] = blend_xform(src.get('final_xform'),
|
||||||
@ -58,7 +120,7 @@ def blend(src, dst, edit={}):
|
|||||||
# TODO: write 'info' section
|
# TODO: write 'info' section
|
||||||
# TODO: palflip
|
# TODO: palflip
|
||||||
blended['type'] = 'animation'
|
blended['type'] = 'animation'
|
||||||
blended.setdefault('time', {})['duration'] = opts['duration']
|
blended.setdefault('time', {})['duration'] = opts.duration
|
||||||
return blended
|
return blended
|
||||||
|
|
||||||
def merge_edits(sv, av, bv):
|
def merge_edits(sv, av, bv):
|
||||||
@ -75,16 +137,16 @@ def merge_edits(sv, av, bv):
|
|||||||
else:
|
else:
|
||||||
return bv if bv is not None else av
|
return bv if bv is not None else av
|
||||||
|
|
||||||
def tospline(spl, src, dst, edit, loops):
|
def split_node_val(spl, val):
|
||||||
def split_node_val(val):
|
if val is None:
|
||||||
if val is None:
|
return spl.default, 0
|
||||||
return spl.default, 0
|
if isinstance(val, (int, float)):
|
||||||
if isinstance(val, (int, float)):
|
return val, 0
|
||||||
return val, 0
|
return val
|
||||||
return val
|
|
||||||
|
|
||||||
sp, sv = split_node_val(src) # position, velocity
|
def tospline(spl, src, dst, edit, loops):
|
||||||
dp, dv = split_node_val(dst)
|
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
|
# For variation parameters, copy missing values instead of using defaults
|
||||||
if spl.var:
|
if spl.var:
|
||||||
@ -285,4 +347,4 @@ def palflip(gnm):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys, json
|
import sys, json
|
||||||
a, b, c = [json.load(open(f+'.json')) for f in 'abc']
|
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
|
from variations import var_params
|
||||||
import util
|
import util
|
||||||
|
|
||||||
|
# Re-exported
|
||||||
|
from blend import node_to_anim, edge_to_anim
|
||||||
|
|
||||||
class XMLGenomeParser(object):
|
class XMLGenomeParser(object):
|
||||||
"""
|
"""
|
||||||
Parse an XML genome into a list of dictionaries.
|
Parse an XML genome into a list of dictionaries.
|
||||||
@ -163,8 +166,10 @@ def apply_structure(struct, src):
|
|||||||
out[l[0]] = v
|
out[l[0]] = v
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def convert_flame(flame):
|
def flam3_to_node(flame):
|
||||||
return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
|
n = util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
|
||||||
|
n['type'] = 'node'
|
||||||
|
return n
|
||||||
|
|
||||||
def convert_file(path):
|
def convert_file(path):
|
||||||
"""Quick one-shot conversion for an XML genome."""
|
"""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 "
|
warnings.warn("Lot of flames in this file. Sure it's not a "
|
||||||
"frame-based animation?")
|
"frame-based animation?")
|
||||||
for flame in flames:
|
for flame in flames:
|
||||||
yield convert_flame(flame)
|
yield flam3_to_node(flame)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
@ -68,6 +68,13 @@ time = (
|
|||||||
, 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
|
, '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 = (
|
base = (
|
||||||
{ 'name': String("Human-readable name of this work")
|
{ 'name': String("Human-readable name of this work")
|
||||||
, 'camera': camera
|
, 'camera': camera
|
||||||
|
@ -62,6 +62,19 @@ class Wrapper(object):
|
|||||||
return self.spec.type
|
return self.spec.type
|
||||||
return self.spec[name]
|
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):
|
def __getattr__(self, name):
|
||||||
return self.wrap(name, self.get_spec(name), self._val.get(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
|
return times, vals, t, scale
|
||||||
|
|
||||||
def __call__(self, itime, deriv=0):
|
def __call__(self, itime, deriv=0):
|
||||||
|
# TODO: respect 'interp' THIS IS IMPORTANT.
|
||||||
times, vals, t, scale = self.find_knots(itime)
|
times, vals, t, scale = self.find_knots(itime)
|
||||||
|
|
||||||
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
|
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
|
||||||
|
@ -2,6 +2,7 @@ import base64
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from cuburn.code.util import crep
|
from cuburn.code.util import crep
|
||||||
|
import spectypes
|
||||||
|
|
||||||
def get(dct, default, *keys):
|
def get(dct, default, *keys):
|
||||||
if len(keys) == 1:
|
if len(keys) == 1:
|
||||||
@ -49,6 +50,13 @@ def unflatten(kvlist):
|
|||||||
go(out, k.split('.'), v)
|
go(out, k.split('.'), v)
|
||||||
return out
|
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):
|
def palette_decode(datastrs):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user