使用仿射坐标创建带有自定义引擎的 Spine2d 库 Space

Creating a Spine2d library with a custom engine using Affine Coord Space

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

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

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

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

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 class:

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;
    }

}

我已经弄明白了。在每次我需要根据位置重绘的调用中,我重置仿射坐标,然后平移并旋转一次到正确的位置。