Browse Source

[cpp] Add core pose classes for 4.3-beta architecture

- Add BoneLocal class for local bone pose (x, y, rotation, scale, shear, inherit)
- Add BonePose class extending BoneLocal with world transform calculations
- Add SlotPose class for slot state (color, darkColor, attachment, sequence, deform)
- Implement complete world transform math in BonePose from reference implementation
- Use Spine's custom RTTI system instead of C++ RTTI for type checking
- Update spine.h to include new core pose classes
- Update development guidelines with RTTI usage patterns

These core pose classes provide the foundation for the new constraint system
and enable separation of local, constrained, and applied poses in the 4.3-beta
architecture redesign.
Mario Zechner 3 months ago
parent
commit
c83a0ba849

+ 90 - 0
spine-cpp/spine-cpp/include/spine/BoneLocal.h

@@ -0,0 +1,90 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#ifndef Spine_BoneLocal_h
+#define Spine_BoneLocal_h
+
+#include <spine/Pose.h>
+#include <spine/Inherit.h>
+#include <spine/SpineObject.h>
+
+namespace spine {
+	/// Stores a bone's local pose.
+	class SP_API BoneLocal : public SpineObject, public Pose<BoneLocal> {
+	public:
+		BoneLocal();
+
+		virtual ~BoneLocal();
+
+		virtual void set(const BoneLocal& pose) override;
+
+		/// The local x translation.
+		float getX() const;
+		void setX(float x);
+
+		/// The local y translation.
+		float getY() const;
+		void setY(float y);
+
+		void setPosition(float x, float y);
+
+		/// The local rotation in degrees, counter clockwise.
+		float getRotation() const;
+		void setRotation(float rotation);
+
+		/// The local scaleX.
+		float getScaleX() const;
+		void setScaleX(float scaleX);
+
+		/// The local scaleY.
+		float getScaleY() const;
+		void setScaleY(float scaleY);
+
+		void setScale(float scaleX, float scaleY);
+		void setScale(float scale);
+
+		/// The local shearX.
+		float getShearX() const;
+		void setShearX(float shearX);
+
+		/// The local shearY.
+		float getShearY() const;
+		void setShearY(float shearY);
+
+		/// Determines how parent world transforms affect this bone.
+		Inherit getInherit() const;
+		void setInherit(Inherit inherit);
+
+	protected:
+		float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY;
+		Inherit _inherit;
+	};
+}
+
+#endif /* Spine_BoneLocal_h */

+ 143 - 0
spine-cpp/spine-cpp/include/spine/BonePose.h

@@ -0,0 +1,143 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#ifndef Spine_BonePose_h
+#define Spine_BonePose_h
+
+#include <spine/BoneLocal.h>
+#include <spine/Update.h>
+
+namespace spine {
+	class Bone;
+	class Skeleton;
+
+	/// The applied pose for a bone. This is the Bone pose with constraints applied and the world transform computed by
+	/// Skeleton::updateWorldTransform(Physics).
+	class SP_API BonePose : public BoneLocal, public Update {
+	public:
+		BonePose();
+
+		virtual ~BonePose();
+
+		/// Called by Skeleton::updateCache() to compute the world transform, if needed.
+		virtual void update(Skeleton& skeleton, Physics physics) override;
+
+		/// Computes the world transform using the parent bone's applied pose and this pose. Child bones are not updated.
+		/// 
+		/// See World transforms in the Spine Runtimes Guide.
+		void updateWorldTransform(Skeleton& skeleton);
+
+		/// Computes the local transform values from the world transform.
+		/// 
+		/// If the world transform is modified (by a constraint, rotateWorld(), etc) then this method should be called so
+		/// the local transform matches the world transform. The local transform may be needed by other code (eg to apply another
+		/// constraint).
+		/// 
+		/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after
+		/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
+		void updateLocalTransform(Skeleton& skeleton);
+
+		/// If the world transform has been modified and the local transform no longer matches, updateLocalTransform(Skeleton)
+		/// is called.
+		void validateLocalTransform(Skeleton& skeleton);
+
+		/// Transforms a point from world coordinates to the bone's local coordinates.
+		void worldToLocal(float worldX, float worldY, float& outLocalX, float& outLocalY);
+
+		/// Transforms a point from the bone's local coordinates to world coordinates.
+		void localToWorld(float localX, float localY, float& outWorldX, float& outWorldY);
+
+		/// Transforms a point from world coordinates to the parent bone's local coordinates.
+		void worldToParent(float worldX, float worldY, float& outParentX, float& outParentY);
+
+		/// Transforms a point from the parent bone's coordinates to world coordinates.
+		void parentToWorld(float parentX, float parentY, float& outWorldX, float& outWorldY);
+
+		/// Transforms a world rotation to a local rotation.
+		float worldToLocalRotation(float worldRotation);
+
+		/// Transforms a local rotation to a world rotation.
+		float localToWorldRotation(float localRotation);
+
+		/// Rotates the world transform the specified amount.
+		/// 
+		/// After changes are made to the world transform, updateLocalTransform(Skeleton) should be called on this bone and any
+		/// child bones, recursively.
+		void rotateWorld(float degrees);
+
+		/// Part of the world transform matrix for the X axis. If changed, updateLocalTransform(Skeleton) should be called.
+		float getA() const;
+		void setA(float a);
+
+		/// Part of the world transform matrix for the Y axis. If changed, updateLocalTransform(Skeleton) should be called.
+		float getB() const;
+		void setB(float b);
+
+		/// Part of the world transform matrix for the X axis. If changed, updateLocalTransform(Skeleton) should be called.
+		float getC() const;
+		void setC(float c);
+
+		/// Part of the world transform matrix for the Y axis. If changed, updateLocalTransform(Skeleton) should be called.
+		float getD() const;
+		void setD(float d);
+
+		/// The world X position. If changed, updateLocalTransform(Skeleton) should be called.
+		float getWorldX() const;
+		void setWorldX(float worldX);
+
+		/// The world Y position. If changed, updateLocalTransform(Skeleton) should be called.
+		float getWorldY() const;
+		void setWorldY(float worldY);
+
+		/// The world rotation for the X axis, calculated using a and c.
+		float getWorldRotationX() const;
+
+		/// The world rotation for the Y axis, calculated using b and d.
+		float getWorldRotationY() const;
+
+		/// The magnitude (always positive) of the world scale X, calculated using a and c.
+		float getWorldScaleX() const;
+
+		/// The magnitude (always positive) of the world scale Y, calculated using b and d.
+		float getWorldScaleY() const;
+
+	private:
+		void modifyLocal(Skeleton& skeleton);
+		void modifyWorld(int update);
+		void resetWorld(int update);
+
+	public:
+		Bone* _bone;
+		float _a, _b, _worldX;
+		float _c, _d, _worldY;
+		int _world, _local;
+	};
+}
+
+#endif /* Spine_BonePose_h */

+ 93 - 0
spine-cpp/spine-cpp/include/spine/SlotPose.h

@@ -0,0 +1,93 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#ifndef Spine_SlotPose_h
+#define Spine_SlotPose_h
+
+#include <spine/Pose.h>
+#include <spine/SpineObject.h>
+#include <spine/Color.h>
+#include <spine/Vector.h>
+
+namespace spine {
+	class Attachment;
+	class VertexAttachment;
+
+	/// Stores a slot's pose. Slots organize attachments for Skeleton drawOrder purposes and provide a place to store state
+	/// for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across
+	/// multiple skeletons.
+	class SP_API SlotPose : public SpineObject, public Pose<SlotPose> {
+	public:
+		SlotPose();
+
+		virtual ~SlotPose();
+
+		virtual void set(const SlotPose& pose) override;
+
+		/// The color used to tint the slot's attachment. If hasDarkColor() is true, this is used as the light color for two
+		/// color tinting.
+		Color& getColor();
+
+		/// The dark color used to tint the slot's attachment for two color tinting. Only use if hasDarkColor() is true.
+		/// The dark color's alpha is not used.
+		Color& getDarkColor();
+
+		bool hasDarkColor();
+		void setHasDarkColor(bool hasDarkColor);
+
+		/// The current attachment for the slot, or null if the slot has no attachment.
+		Attachment* getAttachment();
+
+		/// Sets the slot's attachment and, if the attachment changed, resets sequenceIndex and clears the deform.
+		/// The deform is not cleared if the old attachment has the same VertexAttachment::getTimelineAttachment() as the
+		/// specified attachment.
+		void setAttachment(Attachment* attachment);
+
+		/// The index of the texture region to display when the slot's attachment has a Sequence. -1 represents the
+		/// Sequence::getSetupIndex().
+		int getSequenceIndex();
+		void setSequenceIndex(int sequenceIndex);
+
+		/// Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
+		/// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
+		/// 
+		/// See VertexAttachment::computeWorldVertices and DeformTimeline.
+		Vector<float>& getDeform();
+
+	private:
+		Color _color;
+		Color _darkColor;
+		bool _hasDarkColor;
+		Attachment* _attachment;
+		int _sequenceIndex;
+		Vector<float> _deform;
+	};
+}
+
+#endif /* Spine_SlotPose_h */

+ 3 - 0
spine-cpp/spine-cpp/include/spine/spine.h

@@ -112,6 +112,9 @@
 #include <spine/PosedData.h>
 #include <spine/Posed.h>
 #include <spine/PosedActive.h>
+#include <spine/BoneLocal.h>
+#include <spine/BonePose.h>
+#include <spine/SlotPose.h>
 #include <spine/Vector.h>
 #include <spine/VertexAttachment.h>
 #include <spine/Vertices.h>

+ 128 - 0
spine-cpp/spine-cpp/src/spine/BoneLocal.cpp

@@ -0,0 +1,128 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/BoneLocal.h>
+
+using namespace spine;
+
+BoneLocal::BoneLocal() : _x(0), _y(0), _rotation(0), _scaleX(1), _scaleY(1), _shearX(0), _shearY(0), _inherit(Inherit_Normal) {
+}
+
+BoneLocal::~BoneLocal() {
+}
+
+void BoneLocal::set(const BoneLocal& pose) {
+	_x = pose._x;
+	_y = pose._y;
+	_rotation = pose._rotation;
+	_scaleX = pose._scaleX;
+	_scaleY = pose._scaleY;
+	_shearX = pose._shearX;
+	_shearY = pose._shearY;
+	_inherit = pose._inherit;
+}
+
+float BoneLocal::getX() const {
+	return _x;
+}
+
+void BoneLocal::setX(float x) {
+	_x = x;
+}
+
+float BoneLocal::getY() const {
+	return _y;
+}
+
+void BoneLocal::setY(float y) {
+	_y = y;
+}
+
+void BoneLocal::setPosition(float x, float y) {
+	_x = x;
+	_y = y;
+}
+
+float BoneLocal::getRotation() const {
+	return _rotation;
+}
+
+void BoneLocal::setRotation(float rotation) {
+	_rotation = rotation;
+}
+
+float BoneLocal::getScaleX() const {
+	return _scaleX;
+}
+
+void BoneLocal::setScaleX(float scaleX) {
+	_scaleX = scaleX;
+}
+
+float BoneLocal::getScaleY() const {
+	return _scaleY;
+}
+
+void BoneLocal::setScaleY(float scaleY) {
+	_scaleY = scaleY;
+}
+
+void BoneLocal::setScale(float scaleX, float scaleY) {
+	_scaleX = scaleX;
+	_scaleY = scaleY;
+}
+
+void BoneLocal::setScale(float scale) {
+	_scaleX = scale;
+	_scaleY = scale;
+}
+
+float BoneLocal::getShearX() const {
+	return _shearX;
+}
+
+void BoneLocal::setShearX(float shearX) {
+	_shearX = shearX;
+}
+
+float BoneLocal::getShearY() const {
+	return _shearY;
+}
+
+void BoneLocal::setShearY(float shearY) {
+	_shearY = shearY;
+}
+
+Inherit BoneLocal::getInherit() const {
+	return _inherit;
+}
+
+void BoneLocal::setInherit(Inherit inherit) {
+	_inherit = inherit;
+}

+ 382 - 0
spine-cpp/spine-cpp/src/spine/BonePose.cpp

@@ -0,0 +1,382 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/BonePose.h>
+#include <spine/Bone.h>
+#include <spine/Skeleton.h>
+#include <spine/MathUtil.h>
+
+using namespace spine;
+
+BonePose::BonePose() : _bone(nullptr), _a(1), _b(0), _worldX(0), _c(0), _d(1), _worldY(0), _world(0), _local(0) {
+}
+
+BonePose::~BonePose() {
+}
+
+void BonePose::update(Skeleton& skeleton, Physics physics) {
+	if (_world != skeleton.getUpdate()) updateWorldTransform(skeleton);
+}
+
+void BonePose::updateWorldTransform(Skeleton& skeleton) {
+	if (_local == skeleton.getUpdate()) {
+		updateLocalTransform(skeleton);
+	} else {
+		_world = skeleton.getUpdate();
+	}
+
+	if (_bone->getParent() == nullptr) {
+		float sx = skeleton.getScaleX(), sy = skeleton.getScaleY();
+		float rx = (_rotation + _shearX) * MathUtil::Deg_Rad;
+		float ry = (_rotation + 90 + _shearY) * MathUtil::Deg_Rad;
+		_a = MathUtil::cos(rx) * _scaleX * sx;
+		_b = MathUtil::cos(ry) * _scaleY * sx;
+		_c = MathUtil::sin(rx) * _scaleX * sy;
+		_d = MathUtil::sin(ry) * _scaleY * sy;
+		_worldX = _x * sx + skeleton.getX();
+		_worldY = _y * sy + skeleton.getY();
+		return;
+	}
+
+	BonePose* parent = _bone->getParent()->getApplied();
+	float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d;
+	_worldX = pa * _x + pb * _y + parent->_worldX;
+	_worldY = pc * _x + pd * _y + parent->_worldY;
+
+	switch (_inherit) {
+	case Inherit_Normal: {
+		float rx = (_rotation + _shearX) * MathUtil::Deg_Rad;
+		float ry = (_rotation + 90 + _shearY) * MathUtil::Deg_Rad;
+		float la = MathUtil::cos(rx) * _scaleX;
+		float lb = MathUtil::cos(ry) * _scaleY;
+		float lc = MathUtil::sin(rx) * _scaleX;
+		float ld = MathUtil::sin(ry) * _scaleY;
+		_a = pa * la + pb * lc;
+		_b = pa * lb + pb * ld;
+		_c = pc * la + pd * lc;
+		_d = pc * lb + pd * ld;
+		break;
+	}
+	case Inherit_OnlyTranslation: {
+		float rx = (_rotation + _shearX) * MathUtil::Deg_Rad;
+		float ry = (_rotation + 90 + _shearY) * MathUtil::Deg_Rad;
+		_a = MathUtil::cos(rx) * _scaleX;
+		_b = MathUtil::cos(ry) * _scaleY;
+		_c = MathUtil::sin(rx) * _scaleX;
+		_d = MathUtil::sin(ry) * _scaleY;
+		break;
+	}
+	case Inherit_NoRotationOrReflection: {
+		float sx = 1 / skeleton.getScaleX(), sy = 1 / skeleton.getScaleY();
+		pa *= sx;
+		pc *= sy;
+		float s = pa * pa + pc * pc, prx;
+		if (s > 0.0001f) {
+			s = MathUtil::abs(pa * pd * sy - pb * sx * pc) / s;
+			pb = pc * s;
+			pd = pa * s;
+			prx = MathUtil::atan2Deg(pc, pa);
+		} else {
+			pa = 0;
+			pc = 0;
+			prx = 90 - MathUtil::atan2Deg(pd, pb);
+		}
+		float rx = (_rotation + _shearX - prx) * MathUtil::Deg_Rad;
+		float ry = (_rotation + _shearY - prx + 90) * MathUtil::Deg_Rad;
+		float la = MathUtil::cos(rx) * _scaleX;
+		float lb = MathUtil::cos(ry) * _scaleY;
+		float lc = MathUtil::sin(rx) * _scaleX;
+		float ld = MathUtil::sin(ry) * _scaleY;
+		_a = pa * la - pb * lc;
+		_b = pa * lb - pb * ld;
+		_c = pc * la + pd * lc;
+		_d = pc * lb + pd * ld;
+		break;
+	}
+	case Inherit_NoScale:
+	case Inherit_NoScaleOrReflection: {
+		float rotation = _rotation * MathUtil::Deg_Rad;
+		float cosVal = MathUtil::cos(rotation), sinVal = MathUtil::sin(rotation);
+		float za = (pa * cosVal + pb * sinVal) / skeleton.getScaleX();
+		float zc = (pc * cosVal + pd * sinVal) / skeleton.getScaleY();
+		float s = MathUtil::sqrt(za * za + zc * zc);
+		if (s > 0.00001f) s = 1 / s;
+		za *= s;
+		zc *= s;
+		s = MathUtil::sqrt(za * za + zc * zc);
+		if (_inherit == Inherit_NoScale && (pa * pd - pb * pc < 0) != (skeleton.getScaleX() < 0 != skeleton.getScaleY() < 0)) s = -s;
+		rotation = MathUtil::Pi / 2 + MathUtil::atan2(zc, za);
+		float zb = MathUtil::cos(rotation) * s;
+		float zd = MathUtil::sin(rotation) * s;
+		float shearX = _shearX * MathUtil::Deg_Rad;
+		float shearY = (90 + _shearY) * MathUtil::Deg_Rad;
+		float la = MathUtil::cos(shearX) * _scaleX;
+		float lb = MathUtil::cos(shearY) * _scaleY;
+		float lc = MathUtil::sin(shearX) * _scaleX;
+		float ld = MathUtil::sin(shearY) * _scaleY;
+		_a = za * la + zb * lc;
+		_b = za * lb + zb * ld;
+		_c = zc * la + zd * lc;
+		_d = zc * lb + zd * ld;
+		break;
+	}
+	}
+	_a *= skeleton.getScaleX();
+	_b *= skeleton.getScaleX();
+	_c *= skeleton.getScaleY();
+	_d *= skeleton.getScaleY();
+}
+
+void BonePose::updateLocalTransform(Skeleton& skeleton) {
+	_local = 0;
+	_world = skeleton.getUpdate();
+
+	if (_bone->getParent() == nullptr) {
+		_x = _worldX - skeleton.getX();
+		_y = _worldY - skeleton.getY();
+		float a = _a, b = _b, c = _c, d = _d;
+		_rotation = MathUtil::atan2Deg(c, a);
+		_scaleX = MathUtil::sqrt(a * a + c * c);
+		_scaleY = MathUtil::sqrt(b * b + d * d);
+		_shearX = 0;
+		_shearY = MathUtil::atan2Deg(a * b + c * d, a * d - b * c);
+		return;
+	}
+
+	BonePose* parent = _bone->getParent()->getApplied();
+	float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d;
+	float pid = 1 / (pa * pd - pb * pc);
+	float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
+	float dx = _worldX - parent->_worldX, dy = _worldY - parent->_worldY;
+	_x = (dx * ia - dy * ib);
+	_y = (dy * id - dx * ic);
+
+	float ra, rb, rc, rd;
+	if (_inherit == Inherit_OnlyTranslation) {
+		ra = _a;
+		rb = _b;
+		rc = _c;
+		rd = _d;
+	} else {
+		switch (_inherit) {
+		case Inherit_NoRotationOrReflection: {
+			float s = MathUtil::abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
+			pb = -pc * skeleton.getScaleX() * s / skeleton.getScaleY();
+			pd = pa * skeleton.getScaleY() * s / skeleton.getScaleX();
+			pid = 1 / (pa * pd - pb * pc);
+			ia = pd * pid;
+			ib = pb * pid;
+			break;
+		}
+		case Inherit_NoScale:
+		case Inherit_NoScaleOrReflection: {
+			float r = _rotation * MathUtil::Deg_Rad, cosVal = MathUtil::cos(r), sinVal = MathUtil::sin(r);
+			pa = (pa * cosVal + pb * sinVal) / skeleton.getScaleX();
+			pc = (pc * cosVal + pd * sinVal) / skeleton.getScaleY();
+			float s = MathUtil::sqrt(pa * pa + pc * pc);
+			if (s > 0.00001f) s = 1 / s;
+			pa *= s;
+			pc *= s;
+			s = MathUtil::sqrt(pa * pa + pc * pc);
+			if (_inherit == Inherit_NoScale && pid < 0 != (skeleton.getScaleX() < 0 != skeleton.getScaleY() < 0)) s = -s;
+			r = MathUtil::Pi / 2 + MathUtil::atan2(pc, pa);
+			pb = MathUtil::cos(r) * s;
+			pd = MathUtil::sin(r) * s;
+			pid = 1 / (pa * pd - pb * pc);
+			ia = pd * pid;
+			ib = pb * pid;
+			ic = pc * pid;
+			id = pa * pid;
+			break;
+		}
+		}
+		ra = ia * _a - ib * _c;
+		rb = ia * _b - ib * _d;
+		rc = id * _c - ic * _a;
+		rd = id * _d - ic * _b;
+	}
+
+	_shearX = 0;
+	_scaleX = MathUtil::sqrt(ra * ra + rc * rc);
+	if (_scaleX > 0.0001f) {
+		float det = ra * rd - rb * rc;
+		_scaleY = det / _scaleX;
+		_shearY = -MathUtil::atan2Deg(ra * rb + rc * rd, det);
+		_rotation = MathUtil::atan2Deg(rc, ra);
+	} else {
+		_scaleX = 0;
+		_scaleY = MathUtil::sqrt(rb * rb + rd * rd);
+		_shearY = 0;
+		_rotation = 90 - MathUtil::atan2Deg(rd, rb);
+	}
+}
+
+void BonePose::validateLocalTransform(Skeleton& skeleton) {
+	if (_local == skeleton.getUpdate()) updateLocalTransform(skeleton);
+}
+
+void BonePose::modifyLocal(Skeleton& skeleton) {
+	if (_local == skeleton.getUpdate()) updateLocalTransform(skeleton);
+	_world = 0;
+	resetWorld(skeleton.getUpdate());
+}
+
+void BonePose::modifyWorld(int update) {
+	_local = update;
+	_world = update;
+	resetWorld(update);
+}
+
+void BonePose::resetWorld(int update) {
+	Vector<Bone*>& children = _bone->getChildren();
+	for (int i = 0, n = children.size(); i < n; i++) {
+		BonePose* child = children[i]->getApplied();
+		if (child->_world == update) {
+			child->_world = 0;
+			child->_local = 0;
+			child->resetWorld(update);
+		}
+	}
+}
+
+void BonePose::worldToLocal(float worldX, float worldY, float& outLocalX, float& outLocalY) {
+	float det = _a * _d - _b * _c;
+	float x = worldX - _worldX, y = worldY - _worldY;
+	outLocalX = (x * _d - y * _b) / det;
+	outLocalY = (y * _a - x * _c) / det;
+}
+
+void BonePose::localToWorld(float localX, float localY, float& outWorldX, float& outWorldY) {
+	outWorldX = localX * _a + localY * _b + _worldX;
+	outWorldY = localX * _c + localY * _d + _worldY;
+}
+
+void BonePose::worldToParent(float worldX, float worldY, float& outParentX, float& outParentY) {
+	if (_bone == nullptr || _bone->getParent() == nullptr) {
+		outParentX = worldX;
+		outParentY = worldY;
+		return;
+	}
+	_bone->getParent()->getApplied()->worldToLocal(worldX, worldY, outParentX, outParentY);
+}
+
+void BonePose::parentToWorld(float parentX, float parentY, float& outWorldX, float& outWorldY) {
+	if (_bone == nullptr || _bone->getParent() == nullptr) {
+		outWorldX = parentX;
+		outWorldY = parentY;
+		return;
+	}
+	_bone->getParent()->getApplied()->localToWorld(parentX, parentY, outWorldX, outWorldY);
+}
+
+float BonePose::worldToLocalRotation(float worldRotation) {
+	worldRotation *= MathUtil::Deg_Rad;
+	float sinVal = MathUtil::sin(worldRotation), cosVal = MathUtil::cos(worldRotation);
+	return MathUtil::atan2Deg(_a * sinVal - _c * cosVal, _d * cosVal - _b * sinVal) + _rotation - _shearX;
+}
+
+float BonePose::localToWorldRotation(float localRotation) {
+	localRotation = (localRotation - _rotation - _shearX) * MathUtil::Deg_Rad;
+	float sinVal = MathUtil::sin(localRotation), cosVal = MathUtil::cos(localRotation);
+	return MathUtil::atan2Deg(cosVal * _c + sinVal * _d, cosVal * _a + sinVal * _b);
+}
+
+void BonePose::rotateWorld(float degrees) {
+	degrees *= MathUtil::Deg_Rad;
+	float sinVal = MathUtil::sin(degrees), cosVal = MathUtil::cos(degrees);
+	float ra = _a, rb = _b;
+	_a = cosVal * ra - sinVal * _c;
+	_b = cosVal * rb - sinVal * _d;
+	_c = sinVal * ra + cosVal * _c;
+	_d = sinVal * rb + cosVal * _d;
+}
+
+float BonePose::getA() const {
+	return _a;
+}
+
+void BonePose::setA(float a) {
+	_a = a;
+}
+
+float BonePose::getB() const {
+	return _b;
+}
+
+void BonePose::setB(float b) {
+	_b = b;
+}
+
+float BonePose::getC() const {
+	return _c;
+}
+
+void BonePose::setC(float c) {
+	_c = c;
+}
+
+float BonePose::getD() const {
+	return _d;
+}
+
+void BonePose::setD(float d) {
+	_d = d;
+}
+
+float BonePose::getWorldX() const {
+	return _worldX;
+}
+
+void BonePose::setWorldX(float worldX) {
+	_worldX = worldX;
+}
+
+float BonePose::getWorldY() const {
+	return _worldY;
+}
+
+void BonePose::setWorldY(float worldY) {
+	_worldY = worldY;
+}
+
+float BonePose::getWorldRotationX() const {
+	return MathUtil::atan2Deg(_c, _a);
+}
+
+float BonePose::getWorldRotationY() const {
+	return MathUtil::atan2Deg(_d, _b);
+}
+
+float BonePose::getWorldScaleX() const {
+	return MathUtil::sqrt(_a * _a + _c * _c);
+}
+
+float BonePose::getWorldScaleY() const {
+	return MathUtil::sqrt(_b * _b + _d * _d);
+}

+ 96 - 0
spine-cpp/spine-cpp/src/spine/SlotPose.cpp

@@ -0,0 +1,96 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/SlotPose.h>
+#include <spine/Attachment.h>
+#include <spine/VertexAttachment.h>
+
+using namespace spine;
+
+SlotPose::SlotPose() : _color(1, 1, 1, 1), _darkColor(0, 0, 0, 1), _hasDarkColor(false), _attachment(nullptr), _sequenceIndex(-1) {
+}
+
+SlotPose::~SlotPose() {
+}
+
+void SlotPose::set(const SlotPose& pose) {
+	_color.set(pose._color);
+	if (_hasDarkColor) _darkColor.set(pose._darkColor);
+	_hasDarkColor = pose._hasDarkColor;
+	_attachment = pose._attachment;
+	_sequenceIndex = pose._sequenceIndex;
+	_deform.clear();
+	_deform.addAll(pose._deform);
+}
+
+Color& SlotPose::getColor() {
+	return _color;
+}
+
+Color& SlotPose::getDarkColor() {
+	return _darkColor;
+}
+
+bool SlotPose::hasDarkColor() {
+	return _hasDarkColor;
+}
+
+void SlotPose::setHasDarkColor(bool hasDarkColor) {
+	_hasDarkColor = hasDarkColor;
+}
+
+Attachment* SlotPose::getAttachment() {
+	return _attachment;
+}
+
+void SlotPose::setAttachment(Attachment* attachment) {
+	if (_attachment == attachment) return;
+	
+	if (attachment == nullptr || _attachment == nullptr || 
+		!attachment->getRTTI().instanceOf(VertexAttachment::rtti) ||
+		!_attachment->getRTTI().instanceOf(VertexAttachment::rtti) ||
+		((VertexAttachment*)attachment)->getTimelineAttachment() != ((VertexAttachment*)_attachment)->getTimelineAttachment()) {
+		_deform.clear();
+	}
+	
+	_attachment = attachment;
+	_sequenceIndex = -1;
+}
+
+int SlotPose::getSequenceIndex() {
+	return _sequenceIndex;
+}
+
+void SlotPose::setSequenceIndex(int sequenceIndex) {
+	_sequenceIndex = sequenceIndex;
+}
+
+Vector<float>& SlotPose::getDeform() {
+	return _deform;
+}