mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
553 lines
15 KiB
C++
553 lines
15 KiB
C++
/******************************************************************************
|
|
* 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/Skeleton.h>
|
|
|
|
#include <spine/Attachment.h>
|
|
#include <spine/Bone.h>
|
|
#include <spine/BonePose.h>
|
|
#include <spine/IkConstraint.h>
|
|
#include <spine/PathConstraint.h>
|
|
#include <spine/PhysicsConstraint.h>
|
|
#include <spine/SkeletonData.h>
|
|
#include <spine/Skin.h>
|
|
#include <spine/Slider.h>
|
|
#include <spine/Slot.h>
|
|
#include <spine/TransformConstraint.h>
|
|
|
|
#include <spine/BoneData.h>
|
|
#include <spine/ClippingAttachment.h>
|
|
#include <spine/IkConstraintData.h>
|
|
#include <spine/MeshAttachment.h>
|
|
#include <spine/PathAttachment.h>
|
|
#include <spine/PathConstraintData.h>
|
|
#include <spine/PhysicsConstraintData.h>
|
|
#include <spine/RegionAttachment.h>
|
|
#include <spine/SkeletonClipping.h>
|
|
#include <spine/SlotData.h>
|
|
#include <spine/TransformConstraintData.h>
|
|
|
|
#include <spine/ArrayUtils.h>
|
|
|
|
#include <float.h>
|
|
|
|
using namespace spine;
|
|
|
|
Skeleton::Skeleton(SkeletonData &skeletonData)
|
|
: _data(skeletonData), _skin(NULL), _color(1, 1, 1, 1), _x(0), _y(0), _scaleX(1), _scaleY(1), _windX(1), _windY(0), _gravityX(0), _gravityY(1),
|
|
_time(0), _update(0) {
|
|
|
|
_bones.ensureCapacity(_data.getBones().size());
|
|
for (size_t i = 0; i < _data.getBones().size(); ++i) {
|
|
BoneData *data = _data.getBones()[i];
|
|
|
|
Bone *bone;
|
|
if (data->getParent() == NULL) {
|
|
bone = new (__FILE__, __LINE__) Bone(*data, NULL);
|
|
} else {
|
|
Bone *parent = _bones[data->getParent()->getIndex()];
|
|
bone = new (__FILE__, __LINE__) Bone(*data, parent);
|
|
parent->getChildren().add(bone);
|
|
}
|
|
|
|
_bones.add(bone);
|
|
}
|
|
|
|
_slots.ensureCapacity(_data.getSlots().size());
|
|
_drawOrder.ensureCapacity(_data.getSlots().size());
|
|
for (size_t i = 0; i < _data.getSlots().size(); ++i) {
|
|
SlotData *data = _data.getSlots()[i];
|
|
Slot *slot = new (__FILE__, __LINE__) Slot(*data, *this);
|
|
_slots.add(slot);
|
|
_drawOrder.add(slot);
|
|
}
|
|
|
|
_physics.ensureCapacity(8);
|
|
_constraints.ensureCapacity(_data.getConstraints().size());
|
|
for (size_t i = 0; i < _data.getConstraints().size(); ++i) {
|
|
ConstraintData *constraintData = _data.getConstraints()[i];
|
|
Constraint *constraint = &constraintData->create(*this);
|
|
if (constraint->getRTTI().instanceOf(PhysicsConstraint::rtti)) {
|
|
_physics.add(static_cast<PhysicsConstraint *>(constraint));
|
|
}
|
|
_constraints.add(constraint);
|
|
}
|
|
|
|
updateCache();
|
|
}
|
|
|
|
Skeleton::~Skeleton() {
|
|
ArrayUtils::deleteElements(_bones);
|
|
ArrayUtils::deleteElements(_slots);
|
|
ArrayUtils::deleteElements(_constraints);
|
|
}
|
|
|
|
void Skeleton::updateCache() {
|
|
_updateCache.clear();
|
|
_resetCache.clear();
|
|
|
|
Slot **slots = _slots.buffer();
|
|
for (size_t i = 0, n = _slots.size(); i < n; i++) {
|
|
slots[i]->pose();
|
|
}
|
|
|
|
size_t boneCount = _bones.size();
|
|
Bone **bones = _bones.buffer();
|
|
for (size_t i = 0; i < boneCount; i++) {
|
|
Bone *bone = bones[i];
|
|
bone->_sorted = bone->_data.getSkinRequired();
|
|
bone->_active = !bone->_sorted;
|
|
bone->pose();
|
|
}
|
|
|
|
if (_skin) {
|
|
Array<BoneData *> &skinBones = _skin->getBones();
|
|
for (size_t i = 0, n = skinBones.size(); i < n; i++) {
|
|
Bone *bone = _bones[skinBones[i]->getIndex()];
|
|
do {
|
|
bone->_sorted = false;
|
|
bone->_active = true;
|
|
bone = bone->_parent;
|
|
} while (bone);
|
|
}
|
|
}
|
|
|
|
Constraint **constraints = _constraints.buffer();
|
|
size_t n = _constraints.size();
|
|
for (size_t i = 0; i < n; i++) {
|
|
constraints[i]->pose();
|
|
}
|
|
for (size_t i = 0; i < n; i++) {
|
|
Constraint *constraint = constraints[i];
|
|
constraint->_active = constraint->isSourceActive() &&
|
|
((!constraint->getData().getSkinRequired()) || (_skin && _skin->_constraints.contains(&constraint->getData())));
|
|
if (constraint->_active) constraint->sort(*this);
|
|
}
|
|
|
|
for (size_t i = 0; i < boneCount; i++) {
|
|
sortBone(bones[i]);
|
|
}
|
|
|
|
Update **updateCache = _updateCache.buffer();
|
|
n = _updateCache.size();
|
|
for (size_t i = 0; i < n; i++) {
|
|
const RTTI &rtti = updateCache[i]->getRTTI();
|
|
if (rtti.instanceOf(Bone::rtti)) {
|
|
Bone *bone = (Bone *) (updateCache[i]);
|
|
updateCache[i] = bone->_applied;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Skeleton::printUpdateCache() {
|
|
for (size_t i = 0; i < _updateCache.size(); i++) {
|
|
Update *updatable = _updateCache[i];
|
|
if (updatable->getRTTI().isExactly(Bone::rtti)) {
|
|
printf("bone %s\n", ((Bone *) updatable)->getData().getName().buffer());
|
|
} else if (updatable->getRTTI().isExactly(TransformConstraint::rtti)) {
|
|
printf("transform constraint %s\n", ((TransformConstraint *) updatable)->getData().getName().buffer());
|
|
} else if (updatable->getRTTI().isExactly(IkConstraint::rtti)) {
|
|
printf("ik constraint %s\n", ((IkConstraint *) updatable)->getData().getName().buffer());
|
|
} else if (updatable->getRTTI().isExactly(PathConstraint::rtti)) {
|
|
printf("path constraint %s\n", ((PathConstraint *) updatable)->getData().getName().buffer());
|
|
} else if (updatable->getRTTI().isExactly(PhysicsConstraint::rtti)) {
|
|
printf("physics constraint %s\n", ((PhysicsConstraint *) updatable)->getData().getName().buffer());
|
|
} else if (updatable->getRTTI().isExactly(Slider::rtti)) {
|
|
printf("slider %s\n", ((Slider *) updatable)->getData().getName().buffer());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Skeleton::constrained(Posed &object) {
|
|
if (object.isPoseEqualToApplied()) {
|
|
object.constrained();
|
|
_resetCache.add(&object);
|
|
}
|
|
}
|
|
|
|
void Skeleton::sortBone(Bone *bone) {
|
|
if (bone->_sorted || !bone->_active) return;
|
|
Bone *parent = bone->_parent;
|
|
if (parent != NULL) sortBone(parent);
|
|
bone->_sorted = true;
|
|
_updateCache.add((Update *) bone);
|
|
}
|
|
|
|
void Skeleton::sortReset(Array<Bone *> &bones) {
|
|
Bone **items = bones.buffer();
|
|
for (size_t i = 0, n = bones.size(); i < n; i++) {
|
|
Bone *bone = items[i];
|
|
if (bone->_active) {
|
|
if (bone->_sorted) sortReset(bone->getChildren());
|
|
bone->_sorted = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Skeleton::updateWorldTransform(Physics physics) {
|
|
_update++;
|
|
|
|
Posed **resetCache = _resetCache.buffer();
|
|
for (size_t i = 0, n = _resetCache.size(); i < n; i++) {
|
|
resetCache[i]->resetConstrained();
|
|
}
|
|
|
|
Update **updateCache = _updateCache.buffer();
|
|
for (size_t i = 0, n = _updateCache.size(); i < n; i++) {
|
|
updateCache[i]->update(*this, physics);
|
|
}
|
|
}
|
|
|
|
void Skeleton::setupPose() {
|
|
setupPoseBones();
|
|
setupPoseSlots();
|
|
}
|
|
|
|
void Skeleton::setupPoseBones() {
|
|
Bone **bones = _bones.buffer();
|
|
for (size_t i = 0, n = _bones.size(); i < n; ++i) {
|
|
bones[i]->setupPose();
|
|
}
|
|
|
|
Constraint **constraints = _constraints.buffer();
|
|
for (size_t i = 0, n = _constraints.size(); i < n; ++i) {
|
|
constraints[i]->setupPose();
|
|
}
|
|
}
|
|
|
|
void Skeleton::setupPoseSlots() {
|
|
Slot **slots = _slots.buffer();
|
|
size_t n = _slots.size();
|
|
_drawOrder.clear();
|
|
_drawOrder.setSize(n, 0);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
_drawOrder[i] = _slots[i];
|
|
}
|
|
|
|
for (size_t i = 0; i < n; ++i) {
|
|
slots[i]->setupPose();
|
|
}
|
|
}
|
|
|
|
SkeletonData &Skeleton::getData() {
|
|
return _data;
|
|
}
|
|
|
|
Array<Bone *> &Skeleton::getBones() {
|
|
return _bones;
|
|
}
|
|
|
|
Array<Update *> &Skeleton::getUpdateCache() {
|
|
return _updateCache;
|
|
}
|
|
|
|
Bone *Skeleton::getRootBone() {
|
|
return _bones.size() == 0 ? NULL : _bones[0];
|
|
}
|
|
|
|
Bone *Skeleton::findBone(const String &boneName) {
|
|
if (boneName.isEmpty()) return NULL;
|
|
Bone **bones = _bones.buffer();
|
|
for (size_t i = 0, n = _bones.size(); i < n; i++) {
|
|
if (bones[i]->_data.getName() == boneName) return bones[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Array<Slot *> &Skeleton::getSlots() {
|
|
return _slots;
|
|
}
|
|
|
|
Slot *Skeleton::findSlot(const String &slotName) {
|
|
if (slotName.isEmpty()) return NULL;
|
|
Slot **slots = _slots.buffer();
|
|
for (size_t i = 0, n = _slots.size(); i < n; i++) {
|
|
if (slots[i]->_data.getName() == slotName) return slots[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Array<Slot *> &Skeleton::getDrawOrder() {
|
|
return _drawOrder;
|
|
}
|
|
|
|
Skin *Skeleton::getSkin() {
|
|
return _skin;
|
|
}
|
|
|
|
void Skeleton::setSkin(const String &skinName) {
|
|
Skin *skin = skinName.isEmpty() ? NULL : _data.findSkin(skinName);
|
|
if (skin == NULL) return;
|
|
setSkin(skin);
|
|
}
|
|
|
|
void Skeleton::setSkin(Skin *newSkin) {
|
|
if (_skin == newSkin) return;
|
|
if (newSkin != NULL) {
|
|
if (_skin != NULL) {
|
|
newSkin->attachAll(*this, *_skin);
|
|
} else {
|
|
Slot **slots = _slots.buffer();
|
|
for (size_t i = 0, n = _slots.size(); i < n; ++i) {
|
|
Slot *slot = slots[i];
|
|
const String &name = slot->_data.getAttachmentName();
|
|
if (name.length() > 0) {
|
|
Attachment *attachment = newSkin->getAttachment(i, name);
|
|
if (attachment != NULL) {
|
|
slot->_pose.setAttachment(attachment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_skin = newSkin;
|
|
updateCache();
|
|
}
|
|
|
|
Attachment *Skeleton::getAttachment(const String &slotName, const String &attachmentName) {
|
|
SlotData *slot = _data.findSlot(slotName);
|
|
if (slot == NULL) return NULL;
|
|
return getAttachment(slot->getIndex(), attachmentName);
|
|
}
|
|
|
|
Attachment *Skeleton::getAttachment(int slotIndex, const String &attachmentName) {
|
|
if (attachmentName.isEmpty()) return NULL;
|
|
if (_skin != NULL) {
|
|
Attachment *attachment = _skin->getAttachment(slotIndex, attachmentName);
|
|
if (attachment != NULL) return attachment;
|
|
}
|
|
if (_data.getDefaultSkin() != NULL) return _data.getDefaultSkin()->getAttachment(slotIndex, attachmentName);
|
|
return NULL;
|
|
}
|
|
|
|
void Skeleton::setAttachment(const String &slotName, const String &attachmentName) {
|
|
if (slotName.isEmpty()) return;
|
|
Slot *slot = findSlot(slotName);
|
|
if (slot == NULL) return;
|
|
Attachment *attachment = NULL;
|
|
if (!attachmentName.isEmpty()) {
|
|
attachment = getAttachment(slot->_data.getIndex(), attachmentName);
|
|
if (attachment == NULL) return;
|
|
}
|
|
slot->_pose.setAttachment(attachment);
|
|
}
|
|
|
|
Array<Constraint *> &Skeleton::getConstraints() {
|
|
return _constraints;
|
|
}
|
|
|
|
Array<PhysicsConstraint *> &Skeleton::getPhysicsConstraints() {
|
|
return _physics;
|
|
}
|
|
|
|
void Skeleton::getBounds(float &outX, float &outY, float &outWidth, float &outHeight) {
|
|
Array<float> outVertexBuffer;
|
|
getBounds(outX, outY, outWidth, outHeight, outVertexBuffer, NULL);
|
|
}
|
|
|
|
void Skeleton::getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Array<float> &outVertexBuffer, SkeletonClipping *clipper) {
|
|
static unsigned short quadIndices[] = {0, 1, 2, 2, 3, 0};
|
|
float minX = FLT_MAX;
|
|
float minY = FLT_MAX;
|
|
float maxX = -FLT_MAX;
|
|
float maxY = -FLT_MAX;
|
|
|
|
Slot **drawOrder = _drawOrder.buffer();
|
|
for (size_t i = 0, n = _drawOrder.size(); i < n; ++i) {
|
|
Slot *slot = drawOrder[i];
|
|
if (!slot->_bone._active) continue;
|
|
size_t verticesLength = 0;
|
|
float *vertices = NULL;
|
|
unsigned short *triangles = NULL;
|
|
size_t trianglesLength = 0;
|
|
Attachment *attachment = slot->_pose.getAttachment();
|
|
|
|
if (attachment != NULL) {
|
|
if (attachment->getRTTI().instanceOf(RegionAttachment::rtti)) {
|
|
RegionAttachment *regionAttachment = static_cast<RegionAttachment *>(attachment);
|
|
verticesLength = 8;
|
|
outVertexBuffer.setSize(8, 0);
|
|
regionAttachment->computeWorldVertices(*slot, outVertexBuffer.buffer(), 0, 2);
|
|
vertices = outVertexBuffer.buffer();
|
|
triangles = quadIndices;
|
|
trianglesLength = 6;
|
|
} else if (attachment->getRTTI().instanceOf(MeshAttachment::rtti)) {
|
|
MeshAttachment *mesh = static_cast<MeshAttachment *>(attachment);
|
|
verticesLength = mesh->getWorldVerticesLength();
|
|
outVertexBuffer.setSize(verticesLength, 0);
|
|
mesh->computeWorldVertices(*this, *slot, 0, verticesLength, outVertexBuffer.buffer(), 0, 2);
|
|
vertices = outVertexBuffer.buffer();
|
|
triangles = mesh->getTriangles().buffer();
|
|
trianglesLength = mesh->getTriangles().size();
|
|
} else if (attachment->getRTTI().instanceOf(ClippingAttachment::rtti) && clipper != NULL) {
|
|
clipper->clipEnd(*slot);
|
|
clipper->clipStart(*this, *slot, static_cast<ClippingAttachment *>(attachment));
|
|
continue;
|
|
}
|
|
|
|
if (vertices != NULL) {
|
|
if (clipper != NULL && clipper->isClipping() && clipper->clipTriangles(vertices, triangles, trianglesLength)) {
|
|
vertices = clipper->getClippedVertices().buffer();
|
|
verticesLength = clipper->getClippedVertices().size();
|
|
}
|
|
for (size_t ii = 0; ii < verticesLength; ii += 2) {
|
|
float x = vertices[ii], y = vertices[ii + 1];
|
|
minX = MathUtil::min(minX, x);
|
|
minY = MathUtil::min(minY, y);
|
|
maxX = MathUtil::max(maxX, x);
|
|
maxY = MathUtil::max(maxY, y);
|
|
}
|
|
}
|
|
}
|
|
if (clipper != NULL) clipper->clipEnd(*slot);
|
|
}
|
|
if (clipper != NULL) clipper->clipEnd();
|
|
|
|
outX = minX;
|
|
outY = minY;
|
|
outWidth = maxX - minX;
|
|
outHeight = maxY - minY;
|
|
}
|
|
|
|
Color &Skeleton::getColor() {
|
|
return _color;
|
|
}
|
|
|
|
void Skeleton::setColor(Color &color) {
|
|
_color.set(color.r, color.g, color.b, color.a);
|
|
}
|
|
|
|
void Skeleton::setColor(float r, float g, float b, float a) {
|
|
_color.set(r, g, b, a);
|
|
}
|
|
|
|
float Skeleton::getScaleX() {
|
|
return _scaleX;
|
|
}
|
|
|
|
void Skeleton::setScaleX(float inValue) {
|
|
_scaleX = inValue;
|
|
}
|
|
|
|
float Skeleton::getScaleY() {
|
|
return _scaleY * (Bone::isYDown() ? -1 : 1);
|
|
}
|
|
|
|
void Skeleton::setScaleY(float inValue) {
|
|
_scaleY = inValue;
|
|
}
|
|
|
|
void Skeleton::setScale(float scaleX, float scaleY) {
|
|
_scaleX = scaleX;
|
|
_scaleY = scaleY;
|
|
}
|
|
|
|
float Skeleton::getX() {
|
|
return _x;
|
|
}
|
|
|
|
void Skeleton::setX(float inValue) {
|
|
_x = inValue;
|
|
}
|
|
|
|
float Skeleton::getY() {
|
|
return _y;
|
|
}
|
|
|
|
void Skeleton::setY(float inValue) {
|
|
_y = inValue;
|
|
}
|
|
|
|
void Skeleton::setPosition(float x, float y) {
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
|
|
void Skeleton::getPosition(float &x, float &y) {
|
|
x = _x;
|
|
y = _y;
|
|
}
|
|
|
|
float Skeleton::getWindX() {
|
|
return _windX;
|
|
}
|
|
|
|
void Skeleton::setWindX(float windX) {
|
|
_windX = windX;
|
|
}
|
|
|
|
float Skeleton::getWindY() {
|
|
return _windY;
|
|
}
|
|
|
|
void Skeleton::setWindY(float windY) {
|
|
_windY = windY;
|
|
}
|
|
|
|
float Skeleton::getGravityX() {
|
|
return _gravityX;
|
|
}
|
|
|
|
void Skeleton::setGravityX(float gravityX) {
|
|
_gravityX = gravityX;
|
|
}
|
|
|
|
float Skeleton::getGravityY() {
|
|
return _gravityY;
|
|
}
|
|
|
|
void Skeleton::setGravityY(float gravityY) {
|
|
_gravityY = gravityY;
|
|
}
|
|
|
|
void Skeleton::physicsTranslate(float x, float y) {
|
|
PhysicsConstraint **constraints = _physics.buffer();
|
|
for (size_t i = 0, n = _physics.size(); i < n; i++) {
|
|
constraints[i]->translate(x, y);
|
|
}
|
|
}
|
|
|
|
void Skeleton::physicsRotate(float x, float y, float degrees) {
|
|
PhysicsConstraint **constraints = _physics.buffer();
|
|
for (size_t i = 0, n = _physics.size(); i < n; i++) {
|
|
constraints[i]->rotate(x, y, degrees);
|
|
}
|
|
}
|
|
|
|
float Skeleton::getTime() {
|
|
return _time;
|
|
}
|
|
|
|
void Skeleton::setTime(float time) {
|
|
_time = time;
|
|
}
|
|
|
|
void Skeleton::update(float delta) {
|
|
_time += delta;
|
|
} |