JSFL: Detecting when an Element has been flipped

1k views Asked by At

I'm writing an exporter in JSFL, to export Flash animations into a format that can be replayed in a custom player. The exporter basically iterates through the timeline and through all the elements at each keyframe, and writes out the element's name, position, rotation, scale and a local offset. These are read into the custom player which feeds the data to a sprite engine to recreate each frame of the animation.

What I want to be able to do is detect whether a given Element has been flipped (i.e. in Flash you Select the element (a symbol), then Modify->Transform->Flip Horizontal) so that the exporter can include that information too, allowing the sprite engine in the player to flip the UVs of the texture to replicate what's happening in Flash. This would be useful for (say) using one symbol for a character's right hand, and just flipping it to be their left hand, rather than having to create a whole new symbol.

Unfortunately I can't see any way of finding this information out. None of the information I have available for the Elements seems to imply that any kind of flipping has occurred. How can I detect flipping? If it can't be done algorithmically, I'd settle for the animator having to manually indicate that a symbol had been flipped (by creating some kind of plugin that gives them a tick-box which writes a value into the Element with setPersistentData(), for example), but I don't know how to make that sort of plugin either. Help!

3

There are 3 answers

1
average dev On
0
colin moock On

Here's electrodruid's solution packaged in several convenience functions:

function isFlippedHorizontally (element) {
  return Math.round(getFlip(element)[0]) == -1;
}

function isFlippedVertically (element) {
  return Math.round(getFlip(element)[0]) == -1;
}

function getFlip (element) {
  var rotationRadians;
  if(isNaN(element.rotation)) {
    rotationRadians = element.skewX * Math.PI / 180;
  }
  else {
    rotationRadians = element.rotation * Math.PI / 180;
  }

  var sinRot = Math.sin(rotationRadians);
  var cosRot = Math.cos(rotationRadians);
  var SOME_EPSILON = 0.01;
  var flipScaleX, flipScaleY;

  if(Math.abs(cosRot) <  SOME_EPSILON) {
    // Avoid divide by zero. We can use sine and the other two elements of the matrix instead.
    flipScaleX = (element.matrix.b / sinRot);
    flipScaleY = (element.matrix.c / -sinRot);
  }
  else {
    flipScaleX = element.matrix.a / cosRot;
    flipScaleY = element.matrix.d / cosRot;
  }
  return [flipScaleX, flipScaleY];
}

Usage Example:

var element = currentDoc.getTimeline().layers[0].frames[0].elements[0];
trace("Element is flipped:" + isFlippedHorizontally(element));

Thanks for the code electrodruid.

1
electrodruid On

scaleX doesn't work, it always seems to give a positive value. In the end I had to do this to the matrix to get the answer out. This is based on the idea that we know the matrix always contains a 2D rotation, and we know the angle, so we can get rid of the rotation from the matrix elements to just leave us with the scale. It's horrible but it seems to work.

Also, rotation sometimes comes out as NaN, particularly when the element has been flipped. using the value of skewX seems to work for things which you know aren't skewed, but I want my exporter to be able to handle skewed elements, so I think this might be the basis for another question here.

var rotationRadians;
if(isNaN(someElement.rotation)) {
    rotationRadians = someElement.skewX * Math.PI / 180;
}
else {
    rotationRadians = someElement.rotation * Math.PI / 180;
}   

var sinRot = Math.sin(rotationRadians);
var cosRot = Math.cos(rotationRadians);
var SOME_EPSILON = 0.01;
var flipScaleX, flipScaleY;

if(Math.abs(cosRot) <  SOME_EPSILON) {
    // Avoid divide by zero. We can use sine and the other two elements of the matrix instead.
    flipScaleX = (someElement.matrix.b / sinRot);
    flipScaleY = (someElement.matrix.c / -sinRot);
}
else {
    flipScaleX = someElement.matrix.a / cosRot;
    flipScaleY = someElement.matrix.d / cosRot;
}

flipScaleX comes out at ~-1 if it's flipped horizontally, ~1 if not. flipScaleY is ~-1 if it's flipped vertically, ~1 if not.