Mario Zechner 120577bfe6 Formatting
2024-04-04 15:27:43 +02:00

747 lines
22 KiB
C++

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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/IkConstraint.h>
#include <spine/PathConstraint.h>
#include <spine/PhysicsConstraint.h>
#include <spine/SkeletonData.h>
#include <spine/Skin.h>
#include <spine/Slot.h>
#include <spine/TransformConstraint.h>
#include <spine/BoneData.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/SlotData.h>
#include <spine/TransformConstraintData.h>
#include <spine/ContainerUtil.h>
#include <float.h>
using namespace spine;
Skeleton::Skeleton(SkeletonData *skeletonData)
: _data(skeletonData), _skin(NULL), _color(1, 1, 1, 1), _scaleX(1),
_scaleY(1), _x(0), _y(0), _time(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, *this, NULL);
} else {
Bone *parent = _bones[data->getParent()->getIndex()];
bone = new (__FILE__, __LINE__) Bone(*data, *this, 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];
Bone *bone = _bones[data->getBoneData().getIndex()];
Slot *slot = new (__FILE__, __LINE__) Slot(*data, *bone);
_slots.add(slot);
_drawOrder.add(slot);
}
_ikConstraints.ensureCapacity(_data->getIkConstraints().size());
for (size_t i = 0; i < _data->getIkConstraints().size(); ++i) {
IkConstraintData *data = _data->getIkConstraints()[i];
IkConstraint *constraint =
new (__FILE__, __LINE__) IkConstraint(*data, *this);
_ikConstraints.add(constraint);
}
_transformConstraints.ensureCapacity(_data->getTransformConstraints().size());
for (size_t i = 0; i < _data->getTransformConstraints().size(); ++i) {
TransformConstraintData *data = _data->getTransformConstraints()[i];
TransformConstraint *constraint =
new (__FILE__, __LINE__) TransformConstraint(*data, *this);
_transformConstraints.add(constraint);
}
_pathConstraints.ensureCapacity(_data->getPathConstraints().size());
for (size_t i = 0; i < _data->getPathConstraints().size(); ++i) {
PathConstraintData *data = _data->getPathConstraints()[i];
PathConstraint *constraint =
new (__FILE__, __LINE__) PathConstraint(*data, *this);
_pathConstraints.add(constraint);
}
_physicsConstraints.ensureCapacity(_data->getPhysicsConstraints().size());
for (size_t i = 0; i < _data->getPhysicsConstraints().size(); ++i) {
PhysicsConstraintData *data = _data->getPhysicsConstraints()[i];
PhysicsConstraint *constraint =
new (__FILE__, __LINE__) PhysicsConstraint(*data, *this);
_physicsConstraints.add(constraint);
}
updateCache();
}
Skeleton::~Skeleton() {
ContainerUtil::cleanUpVectorOfPointers(_bones);
ContainerUtil::cleanUpVectorOfPointers(_slots);
ContainerUtil::cleanUpVectorOfPointers(_ikConstraints);
ContainerUtil::cleanUpVectorOfPointers(_transformConstraints);
ContainerUtil::cleanUpVectorOfPointers(_pathConstraints);
ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints);
}
void Skeleton::updateCache() {
_updateCache.clear();
for (size_t i = 0, n = _bones.size(); i < n; ++i) {
Bone *bone = _bones[i];
bone->_sorted = bone->_data.isSkinRequired();
bone->_active = !bone->_sorted;
}
if (_skin) {
Vector<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);
}
}
size_t ikCount = _ikConstraints.size();
size_t transformCount = _transformConstraints.size();
size_t pathCount = _pathConstraints.size();
size_t physicsCount = _physicsConstraints.size();
size_t constraintCount = ikCount + transformCount + pathCount + physicsCount;
size_t i = 0;
continue_outer:
for (; i < constraintCount; ++i) {
for (size_t ii = 0; ii < ikCount; ++ii) {
IkConstraint *constraint = _ikConstraints[ii];
if (constraint->getData().getOrder() == i) {
sortIkConstraint(constraint);
i++;
goto continue_outer;
}
}
for (size_t ii = 0; ii < transformCount; ++ii) {
TransformConstraint *constraint = _transformConstraints[ii];
if (constraint->getData().getOrder() == i) {
sortTransformConstraint(constraint);
i++;
goto continue_outer;
}
}
for (size_t ii = 0; ii < pathCount; ++ii) {
PathConstraint *constraint = _pathConstraints[ii];
if (constraint->getData().getOrder() == i) {
sortPathConstraint(constraint);
i++;
goto continue_outer;
}
}
for (size_t ii = 0; ii < physicsCount; ++ii) {
PhysicsConstraint *constraint = _physicsConstraints[ii];
if (constraint->getData().getOrder() == i) {
sortPhysicsConstraint(constraint);
i++;
goto continue_outer;
}
}
}
size_t n = _bones.size();
for (i = 0; i < n; ++i) {
sortBone(_bones[i]);
}
}
void Skeleton::printUpdateCache() {
for (size_t i = 0; i < _updateCache.size(); i++) {
Updatable *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());
}
}
}
void Skeleton::updateWorldTransform(Physics physics) {
for (size_t i = 0, n = _bones.size(); i < n; i++) {
Bone *bone = _bones[i];
bone->_ax = bone->_x;
bone->_ay = bone->_y;
bone->_arotation = bone->_rotation;
bone->_ascaleX = bone->_scaleX;
bone->_ascaleY = bone->_scaleY;
bone->_ashearX = bone->_shearX;
bone->_ashearY = bone->_shearY;
}
for (size_t i = 0, n = _updateCache.size(); i < n; ++i) {
Updatable *updatable = _updateCache[i];
updatable->update(physics);
}
}
void Skeleton::updateWorldTransform(Physics physics, Bone *parent) {
// Apply the parent bone transform to the root bone. The root bone always
// inherits scale, rotation and reflection.
Bone *rootBone = getRootBone();
float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d;
rootBone->_worldX = pa * _x + pb * _y + parent->_worldX;
rootBone->_worldY = pc * _x + pd * _y + parent->_worldY;
float rx = (rootBone->_rotation + rootBone->_shearX) * MathUtil::Deg_Rad;
float ry = (rootBone->_rotation + 90 + rootBone->_shearY) * MathUtil::Deg_Rad;
float la = MathUtil::cos(rx) * rootBone->_scaleX;
float lb = MathUtil::cos(ry) * rootBone->_scaleY;
float lc = MathUtil::sin(rx) * rootBone->_scaleX;
float ld = MathUtil::sin(ry) * rootBone->_scaleY;
rootBone->_a = (pa * la + pb * lc) * _scaleX;
rootBone->_b = (pa * lb + pb * ld) * _scaleX;
rootBone->_c = (pc * la + pd * lc) * _scaleY;
rootBone->_d = (pc * lb + pd * ld) * _scaleY;
// Update everything except root bone.
Bone *rb = getRootBone();
for (size_t i = 0, n = _updateCache.size(); i < n; i++) {
Updatable *updatable = _updateCache[i];
if (updatable != rb)
updatable->update(physics);
}
}
void Skeleton::setToSetupPose() {
setBonesToSetupPose();
setSlotsToSetupPose();
}
void Skeleton::setBonesToSetupPose() {
for (size_t i = 0, n = _bones.size(); i < n; ++i) {
_bones[i]->setToSetupPose();
}
for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) {
_ikConstraints[i]->setToSetupPose();
}
for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) {
_transformConstraints[i]->setToSetupPose();
}
for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) {
_pathConstraints[i]->setToSetupPose();
}
for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) {
_physicsConstraints[i]->setToSetupPose();
}
}
void Skeleton::setSlotsToSetupPose() {
_drawOrder.clear();
for (size_t i = 0, n = _slots.size(); i < n; ++i) {
_drawOrder.add(_slots[i]);
}
for (size_t i = 0, n = _slots.size(); i < n; ++i) {
_slots[i]->setToSetupPose();
}
}
Bone *Skeleton::findBone(const String &boneName) {
return ContainerUtil::findWithDataName(_bones, boneName);
}
Slot *Skeleton::findSlot(const String &slotName) {
return ContainerUtil::findWithDataName(_slots, slotName);
}
void Skeleton::setSkin(const String &skinName) {
Skin *foundSkin = skinName.isEmpty() ? NULL : _data->findSkin(skinName);
setSkin(foundSkin);
}
void Skeleton::setSkin(Skin *newSkin) {
if (_skin == newSkin)
return;
if (newSkin != NULL) {
if (_skin != NULL) {
Skeleton &thisRef = *this;
newSkin->attachAll(thisRef, *_skin);
} else {
for (size_t i = 0, n = _slots.size(); i < n; ++i) {
Slot *slotP = _slots[i];
Slot &slot = *slotP;
const String &name = slot._data.getAttachmentName();
if (name.length() > 0) {
Attachment *attachment = newSkin->getAttachment(i, name);
if (attachment != NULL) {
slot.setAttachment(attachment);
}
}
}
}
}
_skin = newSkin;
updateCache();
}
Attachment *Skeleton::getAttachment(const String &slotName,
const String &attachmentName) {
return getAttachment(_data->findSlot(slotName)->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;
}
}
return _data->getDefaultSkin() != NULL
? _data->getDefaultSkin()->getAttachment(slotIndex, attachmentName)
: NULL;
}
void Skeleton::setAttachment(const String &slotName,
const String &attachmentName) {
assert(slotName.length() > 0);
for (size_t i = 0, n = _slots.size(); i < n; ++i) {
Slot *slot = _slots[i];
if (slot->_data.getName() == slotName) {
Attachment *attachment = NULL;
if (attachmentName.length() > 0) {
attachment = getAttachment((int) i, attachmentName);
assert(attachment != NULL);
}
slot->setAttachment(attachment);
return;
}
}
printf("Slot not found: %s", slotName.buffer());
assert(false);
}
IkConstraint *Skeleton::findIkConstraint(const String &constraintName) {
assert(constraintName.length() > 0);
for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) {
IkConstraint *ikConstraint = _ikConstraints[i];
if (ikConstraint->_data.getName() == constraintName) {
return ikConstraint;
}
}
return NULL;
}
TransformConstraint *
Skeleton::findTransformConstraint(const String &constraintName) {
assert(constraintName.length() > 0);
for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) {
TransformConstraint *transformConstraint = _transformConstraints[i];
if (transformConstraint->_data.getName() == constraintName) {
return transformConstraint;
}
}
return NULL;
}
PathConstraint *Skeleton::findPathConstraint(const String &constraintName) {
assert(constraintName.length() > 0);
for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) {
PathConstraint *constraint = _pathConstraints[i];
if (constraint->_data.getName() == constraintName) {
return constraint;
}
}
return NULL;
}
PhysicsConstraint *
Skeleton::findPhysicsConstraint(const String &constraintName) {
assert(constraintName.length() > 0);
for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) {
PhysicsConstraint *constraint = _physicsConstraints[i];
if (constraint->_data.getName() == constraintName) {
return constraint;
}
}
return NULL;
}
void Skeleton::getBounds(float &outX, float &outY, float &outWidth,
float &outHeight, Vector<float> &outVertexBuffer) {
float minX = FLT_MAX;
float minY = FLT_MAX;
float maxX = -FLT_MAX;
float maxY = -FLT_MAX;
for (size_t i = 0; i < _drawOrder.size(); ++i) {
Slot *slot = _drawOrder[i];
if (!slot->_bone._active)
continue;
size_t verticesLength = 0;
Attachment *attachment = slot->getAttachment();
if (attachment != NULL &&
attachment->getRTTI().instanceOf(RegionAttachment::rtti)) {
RegionAttachment *regionAttachment =
static_cast<RegionAttachment *>(attachment);
verticesLength = 8;
if (outVertexBuffer.size() < 8) {
outVertexBuffer.setSize(8, 0);
}
regionAttachment->computeWorldVertices(*slot, outVertexBuffer, 0);
} else if (attachment != NULL &&
attachment->getRTTI().instanceOf(MeshAttachment::rtti)) {
MeshAttachment *mesh = static_cast<MeshAttachment *>(attachment);
verticesLength = mesh->getWorldVerticesLength();
if (outVertexBuffer.size() < verticesLength) {
outVertexBuffer.setSize(verticesLength, 0);
}
mesh->computeWorldVertices(*slot, 0, verticesLength,
outVertexBuffer.buffer(), 0);
}
for (size_t ii = 0; ii < verticesLength; ii += 2) {
float vx = outVertexBuffer[ii];
float vy = outVertexBuffer[ii + 1];
minX = MathUtil::min(minX, vx);
minY = MathUtil::min(minY, vy);
maxX = MathUtil::max(maxX, vx);
maxY = MathUtil::max(maxY, vy);
}
}
outX = minX;
outY = minY;
outWidth = maxX - minX;
outHeight = maxY - minY;
}
Bone *Skeleton::getRootBone() { return _bones.size() == 0 ? NULL : _bones[0]; }
SkeletonData *Skeleton::getData() { return _data; }
Vector<Bone *> &Skeleton::getBones() { return _bones; }
Vector<Updatable *> &Skeleton::getUpdateCacheList() { return _updateCache; }
Vector<Slot *> &Skeleton::getSlots() { return _slots; }
Vector<Slot *> &Skeleton::getDrawOrder() { return _drawOrder; }
Vector<IkConstraint *> &Skeleton::getIkConstraints() { return _ikConstraints; }
Vector<PathConstraint *> &Skeleton::getPathConstraints() {
return _pathConstraints;
}
Vector<TransformConstraint *> &Skeleton::getTransformConstraints() {
return _transformConstraints;
}
Vector<PhysicsConstraint *> &Skeleton::getPhysicsConstraints() {
return _physicsConstraints;
}
Skin *Skeleton::getSkin() { return _skin; }
Color &Skeleton::getColor() { return _color; }
void Skeleton::setPosition(float x, float y) {
_x = x;
_y = y;
}
float Skeleton::getX() { return _x; }
void Skeleton::setX(float inValue) { _x = inValue; }
float Skeleton::getY() { return _y; }
void Skeleton::setY(float inValue) { _y = inValue; }
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::sortIkConstraint(IkConstraint *constraint) {
constraint->_active =
constraint->_target->_active &&
(!constraint->_data.isSkinRequired() ||
(_skin && _skin->_constraints.contains(&constraint->_data)));
if (!constraint->_active)
return;
Bone *target = constraint->getTarget();
sortBone(target);
Vector<Bone *> &constrained = constraint->getBones();
Bone *parent = constrained[0];
sortBone(parent);
if (constrained.size() == 1) {
_updateCache.add(constraint);
sortReset(parent->_children);
} else {
Bone *child = constrained[constrained.size() - 1];
sortBone(child);
_updateCache.add(constraint);
sortReset(parent->_children);
child->_sorted = true;
}
}
void Skeleton::sortPathConstraint(PathConstraint *constraint) {
constraint->_active =
constraint->_target->_bone._active &&
(!constraint->_data.isSkinRequired() ||
(_skin && _skin->_constraints.contains(&constraint->_data)));
if (!constraint->_active)
return;
Slot *slot = constraint->getTarget();
int slotIndex = slot->getData().getIndex();
Bone &slotBone = slot->getBone();
if (_skin != NULL)
sortPathConstraintAttachment(_skin, slotIndex, slotBone);
if (_data->_defaultSkin != NULL && _data->_defaultSkin != _skin)
sortPathConstraintAttachment(_data->_defaultSkin, slotIndex, slotBone);
for (size_t ii = 0, nn = _data->_skins.size(); ii < nn; ii++)
sortPathConstraintAttachment(_data->_skins[ii], slotIndex, slotBone);
Attachment *attachment = slot->getAttachment();
if (attachment != NULL &&
attachment->getRTTI().instanceOf(PathAttachment::rtti))
sortPathConstraintAttachment(attachment, slotBone);
Vector<Bone *> &constrained = constraint->getBones();
size_t boneCount = constrained.size();
for (size_t i = 0; i < boneCount; ++i) {
sortBone(constrained[i]);
}
_updateCache.add(constraint);
for (size_t i = 0; i < boneCount; i++)
sortReset(constrained[i]->getChildren());
for (size_t i = 0; i < boneCount; i++)
constrained[i]->_sorted = true;
}
void Skeleton::sortTransformConstraint(TransformConstraint *constraint) {
constraint->_active =
constraint->_target->_active &&
(!constraint->_data.isSkinRequired() ||
(_skin && _skin->_constraints.contains(&constraint->_data)));
if (!constraint->_active)
return;
sortBone(constraint->getTarget());
Vector<Bone *> &constrained = constraint->getBones();
size_t boneCount = constrained.size();
if (constraint->_data.isLocal()) {
for (size_t i = 0; i < boneCount; i++) {
Bone *child = constrained[i];
sortBone(child->getParent());
sortBone(child);
}
} else {
for (size_t i = 0; i < boneCount; ++i) {
sortBone(constrained[i]);
}
}
_updateCache.add(constraint);
for (size_t i = 0; i < boneCount; ++i)
sortReset(constrained[i]->getChildren());
for (size_t i = 0; i < boneCount; ++i)
constrained[i]->_sorted = true;
}
void Skeleton::sortPhysicsConstraint(PhysicsConstraint *constraint) {
Bone *bone = constraint->getBone();
constraint->_active =
bone->_active &&
(!constraint->_data.isSkinRequired() ||
(_skin && _skin->_constraints.contains(&constraint->_data)));
if (!constraint->_active)
return;
sortBone(bone);
_updateCache.add(constraint);
sortReset(bone->getChildren());
bone->_sorted = true;
}
void Skeleton::sortPathConstraintAttachment(Skin *skin, size_t slotIndex,
Bone &slotBone) {
Skin::AttachmentMap::Entries attachments = skin->getAttachments();
while (attachments.hasNext()) {
Skin::AttachmentMap::Entry entry = attachments.next();
if (entry._slotIndex == slotIndex) {
Attachment *value = entry._attachment;
sortPathConstraintAttachment(value, slotBone);
}
}
}
void Skeleton::sortPathConstraintAttachment(Attachment *attachment,
Bone &slotBone) {
if (attachment == NULL ||
!attachment->getRTTI().instanceOf(PathAttachment::rtti))
return;
Vector<int> &pathBones =
static_cast<PathAttachment *>(attachment)->getBones();
if (pathBones.size() == 0)
sortBone(&slotBone);
else {
for (size_t i = 0, n = pathBones.size(); i < n;) {
size_t nn = pathBones[i++];
nn += i;
while (i < nn) {
sortBone(_bones[pathBones[i++]]);
}
}
}
}
void Skeleton::sortBone(Bone *bone) {
if (bone->_sorted)
return;
Bone *parent = bone->_parent;
if (parent != NULL)
sortBone(parent);
bone->_sorted = true;
_updateCache.add(bone);
}
void Skeleton::sortReset(Vector<Bone *> &bones) {
for (size_t i = 0, n = bones.size(); i < n; ++i) {
Bone *bone = bones[i];
if (!bone->_active)
continue;
if (bone->_sorted)
sortReset(bone->getChildren());
bone->_sorted = false;
}
}
float Skeleton::getTime() { return _time; }
void Skeleton::setTime(float time) { _time = time; }
void Skeleton::update(float delta) { _time += delta; }
void Skeleton::physicsTranslate(float x, float y) {
for (int i = 0; i < (int) _physicsConstraints.size(); i++) {
_physicsConstraints[i]->translate(x, y);
}
}
void Skeleton::physicsRotate(float x, float y, float degrees) {
for (int i = 0; i < (int) _physicsConstraints.size(); i++) {
_physicsConstraints[i]->rotate(x, y, degrees);
}
}