
tripleplay.flump.Movie Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tripleplay Show documentation
Show all versions of tripleplay Show documentation
Utilities for use in PlayN-based games.
The newest version!
//
// Triple Play - utilities for use in PlayN-based games
// Copyright (c) 2011-2018, Triple Play Authors - All rights reserved.
// http://github.com/threerings/tripleplay/blob/master/LICENSE
package tripleplay.flump;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import react.Signal;
import pythagoras.f.FloatMath;
import playn.core.Clock;
import playn.scene.GroupLayer;
import playn.scene.Layer;
public class Movie implements Instance
{
public static class Symbol implements tripleplay.flump.Symbol {
/** The number of frames in this movie. */
public final int frames;
/** The layers in this movie. */
public final List layers;
/** The duration of this movie, in milliseconds. */
public final float duration;
protected Symbol (float frameRate, String name, List layers) {
_name = name;
this.layers = Collections.unmodifiableList(layers);
int frames = 0;
for (LayerData layer : layers) frames = Math.max(layer.frames(), frames);
this.frames = frames;
_framesPerMs = frameRate/1000;
this.duration = frames/_framesPerMs;
}
@Override public String name () { return _name; }
@Override public Movie createInstance () { return new Movie(this); }
protected String _name;
protected float _framesPerMs;
}
public final Signal labelPassed = Signal.create();
protected Movie (Symbol symbol) {
_symbol = symbol;
_animators = new LayerAnimator[symbol.layers.size()];
for (int ii = 0, ll = _animators.length; ii < ll; ++ii) {
LayerAnimator animator = new LayerAnimator(symbol.layers.get(ii));
_animators[ii] = animator;
_root.add(animator.content);
}
setFrame(1, 0);
}
@Override public GroupLayer layer () { return _root; }
public void paint (Clock clock) {
paint(clock.dt);
}
public void paint (float dt) {
dt *= _speed;
_position += dt;
if (_position > _symbol.duration) {
_position = _position % _symbol.duration;
} else if (_position < 0) {
// Normally we shouldn't be negative, but if we're setPositioning, submovies may
// have completely different durations, so stepping backwards wraps around the
// other way
_position = _symbol.duration + (_position % _symbol.duration);
}
float nextFrame = _position*_symbol._framesPerMs;
setFrame(nextFrame, dt);
}
@Override public void close () {
_root.close();
}
/** The playback position, in milliseconds. */
public float position () { return _position; }
/** Changes the playback position. */
public void setPosition (float position) {
if (position < 0) position = 0;
paint(position - _position);
}
public Symbol symbol () { return _symbol; }
/** The playback speed multiplier, defaults to 1. Larger values will play faster. */
public float speed () { return _speed; }
/** Changes the playback speed multiplier. */
public void setSpeed (float speed) {
_speed = speed;
}
/** Retrieves a named layer. It should generally not be modified. */
public Layer getNamedLayer (String name) {
LayerAnimator animator = getNamedAnimator(name);
return animator != null ? animator.content : null;
}
/** Returns all of the {@code Instance}s on a named layer. */
public List getInstances (String name) {
LayerAnimator animator = getNamedAnimator(name);
if (animator == null) return Collections.emptyList();
if (animator._instances == null) return Collections.singletonList(animator._current);
return Collections.unmodifiableList(Arrays.asList(animator._instances));
}
/**
* Replaces the instance on a named layer. The layer should already be empty or contain a single
* Movie.
*/
public void setNamedLayer (String name, Instance instance) {
LayerAnimator animator = getNamedAnimator(name);
if (animator != null) {
assert animator.content instanceof GroupLayer :
"Layer not a container[name=" + name + "]";
animator.setCurrent(instance);
}
}
/** Retrieves all named layers. They generally should not be modified. */
public Map namedLayers () {
Map namedLayers = new HashMap();
for (LayerAnimator animator : _animators) {
namedLayers.put(animator.data.name, animator.content);
}
return Collections.unmodifiableMap(namedLayers);
}
protected LayerAnimator getNamedAnimator (String name) {
for (LayerAnimator animator : _animators) {
if (animator.data.name.equals(name)) {
return animator;
}
}
return null; // Not found
}
protected void setFrame (float frame, float dt) {
if (frame == _frame) {
return;
}
if (frame < _frame) {
// Wrap back to the beginning
for (int ii = 0, ll = _animators.length; ii < ll; ++ii) {
LayerAnimator animator = _animators[ii];
animator.changedKeyframe = true;
animator.keyframeIdx = 0;
}
}
for (int ii = 0, ll = _animators.length; ii < ll; ++ii) {
LayerAnimator animator = _animators[ii];
animator.setFrame(frame, dt);
}
_frame = frame;
}
// Controls a single Flash layer
protected class LayerAnimator {
public final LayerData data;
public final Layer content;
public int keyframeIdx = 0;
public boolean changedKeyframe = false;
public LayerAnimator (LayerData data) {
this.data = data;
if (data._multipleSymbols) {
_instances = new Instance[data.keyframes.size()];
for (int ii = 0, ll = _instances.length; ii < ll; ++ii) {
tripleplay.flump.Symbol sym = data.keyframes.get(ii).symbol();
if (sym == null) {
throw new IllegalArgumentException("Keyframe missing symbol layer=" +
data.name + " frame=" + ii);
}
_instances[ii] = sym.createInstance();
}
content = new GroupLayer();
setCurrent(_instances[0]);
} else if (data._lastSymbol != null) {
_current = data._lastSymbol.createInstance();
content = _current.layer();
} else {
content = new GroupLayer();
}
}
public void setFrame (float frame, float dt) {
List keyframes = data.keyframes;
int finalFrame = keyframes.size()-1;
int startFrame = keyframeIdx + 1;
while (keyframeIdx < finalFrame && keyframes.get(keyframeIdx+1).index <= frame) {
++keyframeIdx;
changedKeyframe = true;
}
if (changedKeyframe && _instances != null) {
// Switch to the next instance if this is a multi-symbol layer
setCurrent(_instances[keyframeIdx]);
changedKeyframe = false;
}
KeyframeData kf = keyframes.get(keyframeIdx);
tripleplay.flump.Symbol currSymbol = kf.symbol();
boolean visible = currSymbol != null && kf.visible;
content.setVisible(visible);
// NOTE: This has some exciting limitations regarding setPosition. If you jump to a
// position, note that like flash itself, we're not smart enough to start anywhere but
// at the very beginning
if (currSymbol != _prevFrameSymbol && _current instanceof Movie) {
((Movie)_current).setPosition(0);
}
_prevFrameSymbol = currSymbol;
if (!visible) {
emitLabelSignals(startFrame, keyframeIdx);
return; // Don't bother animating invisible layers
}
float locX = kf.loc.x();
float locY = kf.loc.y();
float scaleX = kf.scale.x();
float scaleY = kf.scale.y();
float skewX = kf.skew.x();
float skewY = kf.skew.y();
float alpha = kf.alpha;
if (kf.tweened && keyframeIdx < finalFrame) {
// Interpolate with the next keyframe, if there's something on the next keyframe
KeyframeData nextKf = keyframes.get(keyframeIdx+1);
if (nextKf.symbol() != null) {
float interp = (frame-kf.index) / kf.duration;
float ease = kf.ease;
if (ease != 0) {
float t;
if (ease < 0) {
// Ease in
float inv = 1 - interp;
t = 1 - inv*inv;
ease = -ease;
} else {
// Ease out
t = interp*interp;
}
interp = ease*t + (1-ease)*interp;
}
locX += (nextKf.loc.x()-locX) * interp;
locY += (nextKf.loc.y()-locY) * interp;
scaleX += (nextKf.scale.x()-scaleX) * interp;
scaleY += (nextKf.scale.y()-scaleY) * interp;
skewX += (nextKf.skew.x()-skewX) * interp;
skewY += (nextKf.skew.y()-skewY) * interp;
alpha += (nextKf.alpha-alpha) * interp;
}
}
float sinX = FloatMath.sin(skewX), cosX = FloatMath.cos(skewX);
float sinY = FloatMath.sin(skewY), cosY = FloatMath.cos(skewY);
// Create a transformation matrix that translates to locX/Y, skews, then scales
float m00 = cosY * scaleX;
float m01 = sinY * scaleX;
float m10 = -sinX * scaleY;
float m11 = cosX * scaleY;
content.transform().setTransform(m00, m01, m10, m11, locX, locY);
content.setOrigin(kf.pivot.x(), kf.pivot.y());
content.setAlpha(alpha);
if (_current != null) {
_current.paint(dt);
}
emitLabelSignals(startFrame, keyframeIdx);
}
protected void emitLabelSignals (int startIdx, int endIdx) {
for (int ii = startIdx; ii <= endIdx; ii++) {
String label = data.keyframes.get(ii).label;
if (label != null) {
labelPassed.emit(label);
}
}
}
protected void setCurrent (Instance current) {
if (_current != current) {
_current = current;
GroupLayer group = (GroupLayer)content;
group.removeAll();
group.add(current.layer());
}
}
protected Instance _current; // The instance currently visible
protected Instance[] _instances; // Null if only 0-1 instance on this layer
/** We track this to know where we've added a new symbol and thus should reset position. */
protected tripleplay.flump.Symbol _prevFrameSymbol = null;
}
protected Symbol _symbol;
protected GroupLayer _root = new GroupLayer();
protected LayerAnimator[] _animators;
protected float _frame = 0;
protected float _position = 0;
protected float _speed = 1;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy