Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framelabels #208

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 121 additions & 68 deletions src/flambe/swf/MovieSprite.hx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ package flambe.swf;
import flambe.animation.AnimatedFloat;
import flambe.display.Sprite;
import flambe.math.FMath;

import flambe.swf.MovieSymbol;
import flambe.util.Signal0;
import flambe.util.Signal1.Signal1;

using flambe.util.Arrays;
using flambe.util.BitSets;
Expand All @@ -31,29 +33,40 @@ class MovieSprite extends Sprite
* that.
*/
public var speed (default, null) :AnimatedFloat;

/** get/set the current frame (playhead position) **/
public var frame(get, set):Float;

/* The total number of frames in this MovieSprite */
public var totalFrames(get_totalFrames, never):Int;

/** Whether this movie is currently paused. */
public var paused (get, set) :Bool;

/** Emitted when this movie loops back to the beginning. */
public var looped (get, null) :Signal0;


/** Emitted when the playhead passes a label keyframe on the 'labels' layer. */
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like the restriction of requiring a labels layer. Shouldn't it be possible to search for labels across all layers? If you have multiple different frames with the same label, the behavior could be undefined.

The label searching and mapping should happen in the MovieSymbol/MovieLayer/MovieKeyframe data classes at Library load-time. Since you can have many MovieSprites that point at the same symbol data, that would save a lot of memory/CPU.

public var labelPassed(default, null):Signal1<String>;

public function new (symbol :MovieSymbol)
{
super();
this.symbol = symbol;

speed = new AnimatedFloat(1);


speed = new AnimatedFloat(1);
labelPassed = new Signal1<String>();

_animators = Arrays.create(symbol.layers.length);
for (ii in 0..._animators.length) {
var layer = symbol.layers[ii];
_animators[ii] = new LayerAnimator(layer);
_animators[ii] = new LayerAnimator(layer);
}

_frame = 0;
_frame = -1;
_position = 0;
goto(1);
_lastLabelFrame = -1;
goto(0);
}

/**
Expand All @@ -74,16 +87,26 @@ class MovieSprite extends Sprite
}
return null;
}

override public function onAdded ()


/**
* Get the frame index for a frame-label keyframe
* @param name
* @return The frame index, or -1 if no label exists for the given name
*/
public inline function findLabel(name:String):Float {
return symbol.findLabel(name);
}

override public function onAdded ()
{
super.onAdded();

for (animator in _animators) {
owner.addChild(animator.content);
}
}

override public function onRemoved ()
{
super.onRemoved();
Expand All @@ -97,17 +120,16 @@ class MovieSprite extends Sprite

override public function onUpdate (dt :Float)
{
super.onUpdate(dt);
super.onUpdate(dt);

speed.update(dt);

switch (_flags & (Sprite.MOVIESPRITE_PAUSED | Sprite.MOVIESPRITE_SKIP_NEXT)) {
case 0:
// Neither paused nor skipping set, advance time
_position += speed._*dt;
_position += speed._ * dt;
if (_position > symbol.duration) {
_position = _position % symbol.duration;

if (_looped != null) {
_looped.emit();
}
Expand All @@ -117,47 +139,43 @@ class MovieSprite extends Sprite
_flags = _flags.remove(Sprite.MOVIESPRITE_SKIP_NEXT);
}

var newFrame = _position*symbol.frameRate;
var newFrame = _position * symbol.frameRate;
goto(newFrame);
}

private function goto (newFrame :Float)
{
if (_frame == newFrame) {
return; // No change
}

var wrapped = newFrame < _frame;
if (wrapped) {
for (animator in _animators) {
animator.needsKeyframeUpdate = true;
animator.keyframeIdx = 0;
}
}
for (animator in _animators) {
animator.composeFrame(newFrame);
}


for (animator in _animators) {
animator.composeFrame(newFrame, wrapped);
if (animator.labelChanged) labelPassed.emit(animator.currentLabel);
}

_frame = newFrame;
}

inline private function get_position () :Float
{
return _position;
}

{
return _position;
}
private function set_position (position :Float) :Float
{
return _position = FMath.clamp(position, 0, symbol.duration);
}
{
return _position = FMath.clamp(position, 0, symbol.duration);
}

inline private function get_paused () :Bool
{
return _flags.contains(Sprite.MOVIESPRITE_PAUSED);
}
{
return _flags.contains(Sprite.MOVIESPRITE_PAUSED);
}

private function set_paused (paused :Bool)
{
{
_flags = _flags.set(Sprite.MOVIESPRITE_PAUSED, paused);
return paused;
}
Expand All @@ -169,7 +187,17 @@ class MovieSprite extends Sprite
}
return _looped;
}



inline function get_frame():Float return _frame;
inline function set_frame(value:Float):Float {
_position = value / symbol.frameRate;
goto(value);
return _frame;
}

inline function get_totalFrames():Int return Math.ceil(symbol.duration / symbol.frameRate);

/**
* Internal method to set the position to 0 and skip the next update. This is required to modify
* the playback position of child movies during an update step, so that after the update
Expand All @@ -178,34 +206,42 @@ class MovieSprite extends Sprite
@:allow(flambe) function rewind ()
{
_position = 0;
_lastLabelFrame = -1;
_flags = _flags.add(Sprite.MOVIESPRITE_SKIP_NEXT);
}

private var _animators :Array<LayerAnimator>;

private var _position :Float;
private var _position :Float;
private var _frame :Float;

private var _looped :Signal0 = null;

private var _looped :Signal0 = null;

// frame labels...
var _framesToLabels:Map<Int,String>;
var _labelsToFrames:Map<String,Int>;
var _lastLabelFrame:Int = -1;
var _frameLabels:Array<String>;
}

private class LayerAnimator
{
public var content (default, null) :Entity;

public var needsKeyframeUpdate :Bool = false;
public var content(default, null):Entity;

public var currentLabel(default, null):String = null;
public var labelChanged(default, null):Bool = false;

public var needsKeyframeUpdate:Bool = false;
public var keyframeIdx :Int = 0;

public var layer :MovieLayer;

public var layer:MovieLayer;
public function new (layer :MovieLayer)
{
this.layer = layer;

content = new Entity();
if (layer.empty) {
_sprites = null;

} else {
// Populate _sprites with the Sprite at each keyframe, reusing consecutive symbols
_sprites = Arrays.create(layer.keyframes.length);
Expand All @@ -223,17 +259,25 @@ private class LayerAnimator
}
}

public function composeFrame (frame :Float)
public function composeFrame (frame :Float, wrapped:Bool)
{
if (_sprites == null) {

labelChanged = false;

if (wrapped) {
needsKeyframeUpdate = true;
keyframeIdx = 0;
}

if (_sprites == null) {
// TODO(bruno): Test this code path
// Don't animate empty layers
return;
}

var keyframes = layer.keyframes;
var keyframes = layer.keyframes;
var finalFrame = keyframes.length - 1;

if (frame > layer.frames) {
// TODO(bruno): Test this code path
// Not enough frames on this layer, hide it
Expand All @@ -247,11 +291,12 @@ private class LayerAnimator
++keyframeIdx;
needsKeyframeUpdate = true;
}

var sprite;
if (needsKeyframeUpdate) {
needsKeyframeUpdate = false;
// Switch to the next instance if this is a multi-layer symbol
currentLabel = null;
// Switch to the next instance if this is a multi-layer symbol
sprite = _sprites[keyframeIdx];
if (sprite != content.get(Sprite)) {
if (Type.getClass(sprite) == MovieSprite) {
Expand All @@ -263,22 +308,30 @@ private class LayerAnimator
} else {
sprite = content.get(Sprite);
}



var kf = keyframes[keyframeIdx];
var visible = kf.visible && kf.symbol != null;
sprite.visible = visible;
if (!visible) {

var label = kf.label;
if (label != currentLabel) {
currentLabel = label;
labelChanged = true;
}

if (!visible) {
return; // Don't bother animating invisible layers
}

}
var x = kf.x;
var y = kf.y;
var scaleX = kf.scaleX;
var scaleY = kf.scaleY;
var skewX = kf.skewX;
var skewY = kf.skewY;
var alpha = kf.alpha;

if (kf.tweened && keyframeIdx < finalFrame) {
var interp = (frame-kf.index) / kf.duration;
var ease = kf.ease;
Expand All @@ -295,7 +348,7 @@ private class LayerAnimator
}
interp = ease*t + (1 - ease)*interp;
}

var nextKf = keyframes[keyframeIdx + 1];
x += (nextKf.x-x) * interp;
y += (nextKf.y-y) * interp;
Expand All @@ -310,14 +363,14 @@ private class LayerAnimator
var matrix = sprite.getLocalMatrix();
var sinX = Math.sin(skewX), cosX = Math.cos(skewX);
var sinY = Math.sin(skewY), cosY = Math.cos(skewY);
matrix.set(cosY*scaleX, sinY*scaleX, -sinX*scaleY, cosX*scaleY, x, y);
matrix.set(cosY * scaleX, sinY * scaleX, -sinX * scaleY, cosX * scaleY, x, y);

// Append the pivot
matrix.translate(-kf.pivotX, -kf.pivotY);

sprite.alpha._ = alpha;
sprite.alpha._ = alpha;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of weird reformatting in this PR :) Please try to match the project code-style and avoid cluttering the diff with lots of reformatting.

}

// The sprite to show at each keyframe index, or null if this layer has no symbol instances
private var _sprites :Array<Sprite>;
}
}
Loading