使用仿射坐标空间创建具有自定义引擎的Spine2d库



对于我当前的项目,我们使用自定义脚本语言(天知道我们为什么要这样做)进行游戏开发。 撇开细节不谈,引擎基本上解释和导出为Flash或iOS。

因此,在这个项目中,我的任务是创建一个Spine库,以协助动画。 在大多数情况下,这并不太困难,因为我们的引擎与 AS3 非常相似,我可以将其翻译过来。

我现在遇到的主要问题是渲染。 这种脚本语言的创建者决定专门使用仿射坐标空间系统来渲染位置等。 我试图绕开它,但对它的工作原理知之甚少,我正在努力弄清楚。我需要做的就是通过 x 和 y 手动设置位置,并按角度手动设置旋转。任何帮助将不胜感激。

无论如何,这是我渲染实际 spine 库内容的代码(这是基于 AS3 库中的 SkeletonSprite.as 类):

package engine.spine.render;
//These have the same functionality as the Spine2d library for AS3
import engine.spine.IGBone;
import engine.spine.IGSkeleton;
import engine.spine.IGSkeletonData;
import engine.spine.IGSlot;
import engine.spine.atlas.IGAtlasRegion;
import engine.spine.attachments.IGRegionAttachment;
//This stuff is our engine libraries
import engine.graphics.*;
import engine.gui.*;
import engine.math.*;
import engine.tween.*;
import engine.util.IGFSM;
import engine.IGApplication;
public class IGSpineWidget extends IGUIWidget
{
private var m_skeleton : IGSkeleton;
private var m_lastTime : int;
private var m_image : IGImage;
private var m_wrappers : Map<IGRegionAttachment, IGSpinePosition> = new Map<IGRegionAttachment, IGSpinePosition>(); // Works the same way as a Dictionary in AS3
public function IGSpineWidget (skeletonData : IGSkeletonData, image : IGImage)
{
IGBone.yDown = true;
m_skeleton = new IGSkeleton(skeletonData);
m_skeleton.updateWorldTransform();
m_image = image;
}
protected override function render(g : IGGraphics) : void
{
m_skeleton.update(IGApplication.delta_time) ;
var drawOrder : Vector<IGSlot> = skeleton.drawOrder;
for (var i : int = 0; i < drawOrder.length; i++)
{
g.push();
{
var slot : IGSlot = drawOrder[i];
if (slot.attachment != null && slot.attachment.type != IGRegionAttachment.Region()) { continue; }
var regionAttachment : IGRegionAttachment = IGRegionAttachment(slot.attachment);
var wrapper : IGSpinePosition = m_wrappers[regionAttachment];
if(regionAttachment != null) {
if (wrapper == null)
{
wrapper = new IGSpinePosition();
var region : IGAtlasRegion = IGAtlasRegion(regionAttachment.rendererObject);
var regionHeight : double = region.rotate ? region.width : region.height;
var regionWidth : double = region.rotate ? region.height : region.width;
wrapper.x = region.x;
wrapper.y = region.y;
wrapper.height = regionHeight;
wrapper.width = regionWidth;
// Rotate and scale using default registration point (top left corner, y-down, cw) instead of image center
wrapper.affine.rotate(regionAttachment.rotation * Math.PI / 180); //This rotates the position of the drawn object, but I need to be able to set the actual rotation instead of translating a rotation
wrapper.affine.scale(regionAttachment.scaleX * (regionAttachment.width / region.width), regionAttachment.scaleY * (regionAttachment.height / region.height));
// Position using attachment translation, shifted as if scale and rotation were at image center
var radians : double = -regionAttachment.rotation * Math.PI / 180;
var cos : double = Math.cos(radians);
var sin : double = Math.sin(radians);
var shiftX : double = -regionAttachment.width / 2 * regionAttachment.scaleX;
var shiftY : double = -regionAttachment.height / 2 * regionAttachment.scaleY;
if (region.rotate)
{
wrapper.affine.rotate(90); // Again, I need to += the rotation by 90 degrees, but I dont have that functionality
shiftX += regionHeight * (regionAttachment.width / region.width);
}
wrapper.affine.translate(0, 0);//(regionAttachment.x + shiftX * cos - shiftY * sin, -regionAttachment.y + shiftX * sin + shiftY * cos);
m_wrappers[regionAttachment] = wrapper;
}
var bone : IGBone = slot.bone;
var flipX : int = skeleton.flipX ? -1 : 1;
var flipY : int = skeleton.flipY ? -1 : 1;
//This is the key part.  I need to be able to set the wrapper's affine2d's x and y position, the the rotation by angle (ie. rotation = someAngle)
//wrapper.affine.translate(bone.worldX, bone.worldY);
//wrapper.affine.rotate(bone.worldRotationX * flipX * flipY);
wrapper.scaleX = bone.worldScaleX * flipX;
wrapper.scaleY = bone.worldScaleY * flipY;
g.scale(wrapper.scaleX/4, wrapper.scaleY/4);
g.translate(1000, 1600); // Set the position of the widget
g.rotate(bone.worldRotationX * flipX * flipY);
g.multTransform(wrapper.affine);
g.drawSubImage(m_image, wrapper.x, wrapper.y, wrapper.width, wrapper.height, 0, 0, wrapper.width, wrapper.height);
}
}
g.pop();
}
}
public function get skeleton () : IGSkeleton {
return m_skeleton;
}
}

这是 Affine2d 类:

package engine.math;
public class IGAffine2D
{
/////////////////////////////////////////////////////////////////////
// State
/////////////////////////////////////////////////////////////////////
public var m0:double;
public var m1:double;
public var m2:double;
public var m3:double;
public var m4:double;
public var m5:double;
/////////////////////////////////////////////////////////////////////
// Construction and Initialsation
/////////////////////////////////////////////////////////////////////
public final function IGAffine2D()
{
this.init();
}

public final function isIdentity() : bool
{
return m0 == 1 && m2 == 0 && m4 == 0 &&
m1 == 0 && m3 == 1 && m5 == 0;
}
public final function init() : IGAffine2D
{
m0 = 1.0;
m1 = 0.0;
m2 = 0.0;
m3 = 1.0;
m4 = 0.0;
m5 = 0.0;
return this;
}
public final function initColumnMajor(
a0 : double, a1 : double, a2 : double,
a3 : double, a4 : double, a5 : double) : IGAffine2D {
// represents the following 3x3 (2d affine) matrix  
// m0 m2 m4
// m1 m3 m5
//  0  0  1
m0 = a0;
m1 = a1;
m2 = a2;
m3 = a3;
m4 = a4;
m5 = a5;
return this;
}
public final function initFromTransform(transform:IGAffine2D) : IGAffine2D
{
m0 = transform.m0;
m1 = transform.m1;
m2 = transform.m2;
m3 = transform.m3;
m4 = transform.m4;
m5 = transform.m5;
return this;
}
public final function initWithInverseFromTransform(other : IGAffine2D) : IGAffine2D
{
// this is 65 ops
var a : double= other.m0;
var b : double= other.m2;
var c : double= other.m1;
var d : double= other.m3;
var det_inv : double = 1.0 / (a*d - b*c);
m0 =  d * det_inv;
m1 = -c * det_inv;
m2 = -b * det_inv;
m3 =  a * det_inv;
var x : double= other.m4;
var y : double= other.m5;
m4 = -(x * m0 + y * m2);
m5 = -(x * m1 + y * m3);
return this;
}
//////////////////////////////////////////////////////////////////////
// Getting Properties of the Transform
//////////////////////////////////////////////////////////////////////
public final function get translate_x() : double
{
return m4;
}
public final function get translate_y() : double
{
return m5;
}
public final function get scale_x() : double
{
return m0;
}
public final function get scale_y() : double
{
return m3;
}
/**
* Determines whether or not the translation applied by this matrix 
* will result in the coordinate being integer bound for a point
* around the origin
**/
public final function isIntegerTranslate() : bool
{
var tx : int = m4;
var ty : int = m5;
if (m0 == 1 && m1 == 0 && m2 == 0 && m3 == 1 && m4 == tx && m5 == ty) {
return true;
}
return false;
}       
//////////////////////////////////////////////////////////////////////
// Performing Transformations
//////////////////////////////////////////////////////////////////////
public final function translate(dx:double, dy:double): void
{
m4 += (m0 * dx) + (m2 * dy);
m5 += (m1 * dx) + (m3 * dy);
}
public final function rotate (theta: double) : void
{
if(theta == 0) {
return;
}
var st  :double = Math.sin(theta);
var ct  :double = Math.cos(theta);
var r00 :double = (m0 *  ct) + (m2 * st);
var r01 :double = (m0 * -st) + (m2 * ct);
var r02 :double =  m4;
var r10 :double = (m1 *  ct) + (m3 * st);
var r11 :double = (m1 * -st) + (m3 * ct);
var r12 :double =  m5;
m0 = r00;
m2 = r01;
m4 = r02;
m1 = r10;
m3 = r11;
m5 = r12;
}
public final function scale(sx : double, sy : double) : void
{   
m0 *= sx;
m1 *= sx;
m2 *= sy;
m3 *= sy;
}
// [m00 m01 m02]    [1   shx  0]
// [m10 m11 m12]    [shy 1    0]
// [  0   0   1]    [0   0    1]
public final function shear(shx:double, shy:double) : void
{
var r00 : double = m0       + m2 * shy;
var r01 : double = m0 * shx + m2;
var r02 : double = m4;
var r10 : double = m1       + m3 * shy;
var r11 : double = m1 * shx + m3;
var r12 : double = m5;
m0 = r00;
m2 = r01;
m4 = r02;
m1 = r10;
m3 = r11;
m5 = r12;
}


/**
* Performs a Translate/Scale/Rotate transformation around a pivot point.
* OPTIMIZED VERSION OF:
* - translate(dx + px, dy + py);
* - rotate(theta);
* - scale(sx, sy);
* - translate(-px, -py);
**/
public final function TRS(dx : double, dy : double, 
px : double, py : double, // positive pivot point
sx : double, sy : double, theta : double): IGAffine2D
{
// early abort for standard case
if (sx == 1.0 && sy == 1.0 && theta == 0) {
m4 += dx;
m5 += dy;
return this;
}   
var l0 : double = m0;
var l1 : double = m1;
var l2 : double = m2;
var l3 : double = m3;
var l4 : double = m4 + (dx + px)*l0 + (dy + py)*l2;
var l5 : double = m5 + (dx + px)*l1 + (dy + py)*l3;
if (theta != 0)
{   
var s : double = -Math.sin(theta);
var c : double = Math.cos(theta);
var r0 : double =  c*sx;
var r1 : double = -s*sy;
var r2 : double =  s*sx;
var r3 : double =  c*sy;
m0 = l0 * r0 + l2 * r1;
m1 = l1 * r0 + l3 * r1;
m2 = l0 * r2 + l2 * r3;
m3 = l1 * r2 + l3 * r3;
m4 = l4 - m0 * px - m2 * py;
m5 = l5 - m1 * px - m3 * py;
}
else
{   
m0 = l0 * sx ;
m1 = l1 * sx ;
m2 = l2 * sy;
m3 = l3 * sy;
m4 = l4 - (m0 * px) - (m2 * py);
m5 = l5 - (m1 * px) - (m3 * py);
}
return this;
}

public final function initFromTransformWithOffset(other : IGAffine2D, dx : double, dy : double) : IGAffine2D
{
m0 = other.m0;
m1 = other.m1;
m2 = other.m2;
m3 = other.m3;
m4 = other.m4 + dx * m0 + dy * m2;
m5 = other.m5 + dx * m1 + dy * m3;
return this;
}
public final function initFromTransformWithTRS(
other : IGAffine2D,
dx : double, dy : double, 
px : double, py : double, // positive pivot point
sx : double, sy : double, theta : double): IGAffine2D
{
var s  :double = 0;
var c  :double = 1;
if (theta != 0)
{
s = -Math.sin(theta);
c = Math.cos(theta);
}
var l0 : double = other.m0;
var l1 : double = other.m1;
var l2 : double = other.m2;
var l3 : double = other.m3;
var l4 : double = other.m4 + (dx + px)*l0 + (dy + py)*l2;
var l5 : double = other.m5 + (dx + px)*l1 + (dy + py)*l3;
var r0 : double = c*sx;
var r1 : double = -s*sy;
var r2 : double = s*sx;
var r3 : double = c*sy;
m0 = l0 * r0 + l2 * r1;
m1 = l1 * r0 + l3 * r1;
m2 = l0 * r2 + l2 * r3;
m3 = l1 * r2 + l3 * r3;
m4 = l4 - m0 * px - m2 * py;
m5 = l5 - m1 * px - m3 * py;
return this;
}

//////////////////////////////////////////////////////////////////////
// Performing Other Operations
//////////////////////////////////////////////////////////////////////
public final function invert(): IGAffine2D
{
var a : double= m0;
var b : double= m2;
var c : double= m1;
var d : double= m3;
var det_inv : double = 1.0 / (a*d - b*c);
m0 =  d * det_inv;
m1 = -c * det_inv;
m2 = -b * det_inv;
m3 =  a * det_inv;
var x : double= m4;
var y : double= m5;
m4 = -x * m0 + -y * m2;
m5 = -x * m1 + -y * m3;
return this;
}       


public final function concat(t : IGAffine2D): IGAffine2D
{
var r00 : double = (m0 * t.m0) + (m2 * t.m1);
var r01 : double = (m0 * t.m2) + (m2 * t.m3);
var r02 : double = (m0 * t.m4) + (m2 * t.m5) + m4;
var r10 : double = (m1 * t.m0) + (m3 * t.m1);
var r11 : double = (m1 * t.m2) + (m3 * t.m3);
var r12 : double = (m1 * t.m4) + (m3 * t.m5) + m5;
m0 = r00;
m2 = r01;
m4 = r02;
m1 = r10;
m3 = r11;
m5 = r12;
return this;
}

//////////////////////////////////////////////////////////////////////
// 
//////////////////////////////////////////////////////////////////////
public final function transformVector2(pin : IGVector2, pout : IGVector2): void
{
var x:double = pin.x * m0 + pin.y * m2 + m4;
var y:double = pin.x * m1 + pin.y * m3 + m5;
pout.x = x;
pout.y = y;
}

public final function debug() : String
{
return  m0 + " t" + m2 + "t" + m4 + "n" + m1 + "t" + m3 + "t" + m5;
}
}

我已经想通了。 在每次需要根据位置重新绘制的调用中,我都会重置仿射坐标,然后将平移并旋转一次到正确的位置。

最新更新