加入5套spine官方运行时

This commit is contained in:
tianmo committed 2026-09-07 21:40:29 +08:00
1 parent 98e51a259e
commit 6837ecada9
935 files changed
+204665 -8

No files matched your search

@@ -0,0 +1,124 @@
/******************************************************************************
* 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/Animation.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Timeline.h>
#include <spine/BoneTimeline.h>
#include <spine/RotateTimeline.h>
#include <spine/TranslateTimeline.h>
#include <spine/ArrayUtils.h>
#include <stdint.h>
using namespace spine;
Animation::Animation(const String &name) : _timelines(), _timelineIds(), _bones(), _duration(0), _name(name), _color(1, 1, 1, 1) {
}
bool Animation::hasTimeline(Array<PropertyId> &ids) {
for (size_t i = 0; i < ids.size(); i++) {
if (_timelineIds.containsKey(ids[i])) return true;
}
return false;
}
Animation::~Animation() {
ArrayUtils::deleteElements(_timelines);
}
void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
if (loop && _duration != 0) {
time = MathUtil::fmod(time, _duration);
if (lastTime > 0) {
lastTime = MathUtil::fmod(lastTime, _duration);
}
}
for (size_t i = 0, n = _timelines.size(); i < n; ++i) {
_timelines[i]->apply(skeleton, lastTime, time, events, alpha, from, add, out, appliedPose);
}
}
const String &Animation::getName() {
return _name;
}
const Array<int> &Animation::getBones() {
return _bones;
}
Color &Animation::getColor() {
return _color;
}
Array<Timeline *> &Animation::getTimelines() {
return _timelines;
}
float Animation::getDuration() {
return _duration;
}
void Animation::setDuration(float inValue) {
_duration = inValue;
}
int Animation::search(Array<float> &frames, float target) {
size_t n = (int) frames.size();
for (size_t i = 1; i < n; i++) {
if (frames[i] > target) return (int) (i - 1);
}
return (int) (n - 1);
}
int Animation::search(Array<float> &frames, float target, int step) {
size_t n = frames.size();
for (size_t i = step; i < n; i += step)
if (frames[i] > target) return (int) (i - step);
return (int) (n - step);
}
void Animation::setTimelines(Array<Timeline *> &timelines, Array<int> &bones) {
_timelines = timelines;
_bones = bones;
size_t n = timelines.size();
_timelineIds.clear();
for (size_t i = 0; i < n; i++) {
Timeline *timeline = timelines[i];
Array<PropertyId> &propertyIds = timeline->getPropertyIds();
for (size_t ii = 0; ii < propertyIds.size(); ii++) {
_timelineIds.put(propertyIds[ii], true);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,87 @@
/******************************************************************************
* 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/AnimationStateData.h>
#include <spine/Animation.h>
#include <spine/SkeletonData.h>
using namespace spine;
AnimationStateData::AnimationStateData(SkeletonData &skeletonData) : _skeletonData(&skeletonData), _defaultMix(0) {
}
void AnimationStateData::setMix(const String &fromName, const String &toName, float duration) {
Animation *from = _skeletonData->findAnimation(fromName);
Animation *to = _skeletonData->findAnimation(toName);
assert(from != NULL);
assert(to != NULL);
setMix(*from, *to, duration);
}
void AnimationStateData::setMix(Animation &from, Animation &to, float duration) {
AnimationPair key(&from, &to);
_animationToMixTime.put(key, duration);
}
float AnimationStateData::getMix(Animation &from, Animation &to) {
AnimationPair key(&from, &to);
if (_animationToMixTime.containsKey(key)) return _animationToMixTime[key];
return _defaultMix;
}
SkeletonData &AnimationStateData::getSkeletonData() {
return *_skeletonData;
}
float AnimationStateData::getDefaultMix() {
return _defaultMix;
}
void AnimationStateData::setDefaultMix(float inValue) {
_defaultMix = inValue;
}
void AnimationStateData::clear() {
_defaultMix = 0;
_animationToMixTime.clear();
}
AnimationStateData::AnimationPair::AnimationPair(Animation *a1, Animation *a2) : _a1(a1), _a2(a2) {
}
bool AnimationStateData::AnimationPair::operator==(const AnimationPair &other) const {
return _a1->_name == other._a1->_name && _a2->_name == other._a2->_name;
}
size_t AnimationStateData::AnimationPairHash::operator()(const AnimationPair &pair) const {
MapHash<String> hash;
return hash(pair._a1->_name) * 31 + hash(pair._a2->_name);
}
@@ -0,0 +1,361 @@
/******************************************************************************
* 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/Atlas.h>
#include <spine/ArrayUtils.h>
#include <spine/TextureLoader.h>
#include <ctype.h>
using namespace spine;
RTTI_IMPL(AtlasRegion, TextureRegion)
Atlas::Atlas(const String &path, TextureLoader *textureLoader, bool createTexture) : _textureLoader(textureLoader) {
int dirLength;
char *dir;
int length;
const char *data;
/* Get directory from atlas path. */
const char *lastForwardSlash = strrchr(path.buffer(), '/');
const char *lastBackwardSlash = strrchr(path.buffer(), '\\');
const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
dirLength = (int) (lastSlash ? lastSlash - path.buffer() : 0);
dir = SpineExtension::calloc<char>(dirLength + 1, __FILE__, __LINE__);
memcpy(dir, path.buffer(), dirLength);
dir[dirLength] = '\0';
data = SpineExtension::readFile(path, &length);
if (data) {
load(data, length, dir, createTexture);
}
SpineExtension::free(data, __FILE__, __LINE__);
SpineExtension::free(dir, __FILE__, __LINE__);
}
Atlas::Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture) : _textureLoader(textureLoader) {
load(data, length, dir, createTexture);
}
Atlas::~Atlas() {
if (_textureLoader) {
for (size_t i = 0, n = _pages.size(); i < n; ++i) {
_textureLoader->unload(_pages[i]->texture);
}
}
ArrayUtils::deleteElements(_pages);
ArrayUtils::deleteElements(_regions);
}
void Atlas::flipV() {
for (size_t i = 0, n = _regions.size(); i < n; ++i) {
AtlasRegion *regionP = _regions[i];
AtlasRegion &region = *regionP;
region._v = 1 - region._v;
region._v2 = 1 - region._v2;
}
}
AtlasRegion *Atlas::findRegion(const String &name) {
for (size_t i = 0, n = _regions.size(); i < n; ++i)
if (_regions[i]->_name == name) return _regions[i];
return nullptr;
}
Array<AtlasPage *> &Atlas::getPages() {
return _pages;
}
Array<AtlasRegion *> &Atlas::getRegions() {
return _regions;
}
struct SimpleString {
char *start;
char *end;
int length;
SimpleString trim() {
while (isspace((unsigned char) *start) && start < end) start++;
if (start == end) {
length = (int) (end - start);
return *this;
}
end--;
while (((unsigned char) *end == '\r') && end >= start) end--;
end++;
length = (int) (end - start);
return *this;
}
int indexOf(char needle) {
char *c = start;
while (c < end) {
if (*c == needle) return (int) (c - start);
c++;
}
return -1;
}
int indexOf(char needle, int at) {
char *c = start + at;
while (c < end) {
if (*c == needle) return (int) (c - start);
c++;
}
return -1;
}
SimpleString substr(int s, int e) {
e = s + e;
SimpleString result;
result.start = start + s;
result.end = start + e;
result.length = e - s;
return result;
}
SimpleString substr(int s) {
SimpleString result;
result.start = start + s;
result.end = end;
result.length = (int) (result.end - result.start);
return result;
}
bool equals(const char *str) {
int otherLen = (int) strlen(str);
if (length != otherLen) return false;
for (int i = 0; i < length; i++) {
if (start[i] != str[i]) return false;
}
return true;
}
char *copy() {
char *string = SpineExtension::calloc<char>(length + 1, __FILE__, __LINE__);
memcpy(string, start, length);
string[length] = '\0';
return string;
}
int toInt() {
return (int) strtol(start, &end, 10);
}
};
struct AtlasInput {
const char *start;
const char *end;
char *index;
int length;
SimpleString line;
AtlasInput(const char *data, int length) : start(data), end(data + length), index((char *) data), length(length) {
}
SimpleString *readLine() {
if (index >= end) return 0;
line.start = index;
while (index < end && *index != '\n') index++;
line.end = index;
if (index != end) index++;
line = line.trim();
line.length = (int) (end - start);
return &line;
}
static int readEntry(SimpleString entry[5], SimpleString *line) {
if (line == nullptr) return 0;
line->trim();
if (line->length == 0) return 0;
int colon = line->indexOf(':');
if (colon == -1) return 0;
entry[0] = line->substr(0, colon).trim();
for (int i = 1, lastMatch = colon + 1;; i++) {
int comma = line->indexOf(',', lastMatch);
if (comma == -1) {
entry[i] = line->substr(lastMatch).trim();
return i;
}
entry[i] = line->substr(lastMatch, comma - lastMatch).trim();
lastMatch = comma + 1;
if (i == 4) return 4;
}
}
};
int indexOf(const char **array, int count, SimpleString *str) {
for (int i = 0; i < count; i++)
if (str->equals(array[i])) return i;
return 0;
}
void Atlas::load(const char *begin, int length, const char *dir, bool createTexture) {
static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", "RGBA8888"};
static const char *textureFilterNames[] =
{"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest", "MipMapLinearNearest", "MipMapNearestLinear", "MipMapLinearLinear"};
int dirLength = (int) strlen(dir);
int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
AtlasInput reader(begin, length);
SimpleString entry[5];
AtlasPage *page = nullptr;
SimpleString *line = reader.readLine();
while (line != nullptr && line->length == 0) line = reader.readLine();
while (true) {
if (line == nullptr || line->length == 0) break;
if (reader.readEntry(entry, line) == 0) break;
line = reader.readLine();
}
while (true) {
if (line == nullptr) break;
if (line->trim().length == 0) {
page = nullptr;
line = reader.readLine();
} else if (page == nullptr) {
char *name = line->copy();
char *path = SpineExtension::calloc<char>(dirLength + needsSlash + strlen(name) + 1, __FILE__, __LINE__);
memcpy(path, dir, dirLength);
if (needsSlash) path[dirLength] = '/';
strcpy(path + dirLength + needsSlash, name);
page = new (__FILE__, __LINE__) AtlasPage(String(name, true));
while (true) {
line = reader.readLine();
if (reader.readEntry(entry, line) == 0) break;
if (entry[0].equals("size")) {
page->width = entry[1].toInt();
page->height = entry[2].toInt();
} else if (entry[0].equals("format")) {
page->format = (Format) indexOf(formatNames, 8, &entry[1]);
} else if (entry[0].equals("filter")) {
#ifdef SPINE_UE4
page->minFilter = (SpineTextureFilter) indexOf(textureFilterNames, 8, &entry[1]);
page->magFilter = (SpineTextureFilter) indexOf(textureFilterNames, 8, &entry[2]);
#else
page->minFilter = (TextureFilter) indexOf(textureFilterNames, 8, &entry[1]);
page->magFilter = (TextureFilter) indexOf(textureFilterNames, 8, &entry[2]);
#endif
} else if (entry[0].equals("repeat")) {
page->uWrap = TextureWrap_ClampToEdge;
page->vWrap = TextureWrap_ClampToEdge;
if (entry[1].indexOf('x') != -1) page->uWrap = TextureWrap_Repeat;
if (entry[1].indexOf('y') != -1) page->vWrap = TextureWrap_Repeat;
} else if (entry[0].equals("pma")) {
page->pma = entry[1].equals("true");
}
}
page->index = (int) _pages.size();
if (createTexture && _textureLoader) _textureLoader->load(*page, String(path));
page->texturePath = String(path, true);
_pages.add(page);
} else {
AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion();
region->_page = page;
region->_rendererObject = page->texture;
region->_name = String(line->copy(), true);
while (true) {
line = reader.readLine();
int count = reader.readEntry(entry, line);
if (count == 0) break;
if (entry[0].equals("xy")) {
region->_x = entry[1].toInt();
region->_y = entry[2].toInt();
} else if (entry[0].equals("size")) {
region->_packedWidth = entry[1].toInt();
region->_packedHeight = entry[2].toInt();
} else if (entry[0].equals("bounds")) {
region->_x = entry[1].toInt();
region->_y = entry[2].toInt();
region->_packedWidth = entry[3].toInt();
region->_packedHeight = entry[4].toInt();
} else if (entry[0].equals("offset")) {
region->_offsetX = entry[1].toInt();
region->_offsetY = entry[2].toInt();
} else if (entry[0].equals("orig")) {
region->_originalWidth = entry[1].toInt();
region->_originalHeight = entry[2].toInt();
} else if (entry[0].equals("offsets")) {
region->_offsetX = entry[1].toInt();
region->_offsetY = entry[2].toInt();
region->_originalWidth = entry[3].toInt();
region->_originalHeight = entry[4].toInt();
} else if (entry[0].equals("rotate")) {
if (entry[1].equals("true")) {
region->_degrees = 90;
} else if (!entry[1].equals("false")) {
region->_degrees = entry[1].toInt();
}
region->_rotate = region->_degrees == 90;
} else if (entry[0].equals("index")) {
region->_index = entry[1].toInt();
} else {
region->_names.add(String(entry[0].copy()));
for (int i = 0; i < count; i++) {
region->_values.add(entry[i + 1].toInt());
}
}
}
if (region->_originalWidth == 0 && region->_originalHeight == 0) {
region->_originalWidth = region->_packedWidth;
region->_originalHeight = region->_packedHeight;
}
region->_u = (float) region->_x / page->width;
region->_v = (float) region->_y / page->height;
if (region->_degrees == 90) {
region->_u2 = (float) (region->_x + region->_packedHeight) / page->width;
region->_v2 = (float) (region->_y + region->_packedWidth) / page->height;
} else {
region->_u2 = (float) (region->_x + region->_packedWidth) / page->width;
region->_v2 = (float) (region->_y + region->_packedHeight) / page->height;
}
// Calculate regionWidth/Height from UV coordinates
region->_regionWidth = abs((int) ((region->_u2 - region->_u) * page->width));
region->_regionHeight = abs((int) ((region->_v2 - region->_v) * page->height));
if (region->_degrees == 90) {
int temp = region->_packedWidth;
region->_packedWidth = region->_packedHeight;
region->_packedHeight = temp;
}
_regions.add(region);
}
}
}
@@ -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/AtlasAttachmentLoader.h>
#include <spine/BoundingBoxAttachment.h>
#include <spine/ClippingAttachment.h>
#include <spine/MeshAttachment.h>
#include <spine/PathAttachment.h>
#include <spine/PointAttachment.h>
#include <spine/RegionAttachment.h>
#include <spine/Skin.h>
#include <spine/Atlas.h>
using namespace spine;
AtlasAttachmentLoader::AtlasAttachmentLoader(Atlas &atlas) : AttachmentLoader(), _atlas(&atlas) {
}
static void findRegions(Atlas *atlas, AtlasAttachmentLoader *loader, const String &name, const String &basePath, Sequence *sequence) {
Array<TextureRegion *> &regions = sequence->getRegions();
for (int i = 0, n = (int) regions.size(); i < n; i++) {
String path = sequence->getPath(basePath, i);
regions[i] = loader->findRegion(path);
}
}
RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const String &placeholder, const String &name, const String &path,
Sequence *sequence) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
findRegions(_atlas, this, name, path, sequence);
return new (__FILE__, __LINE__) RegionAttachment(name, sequence);
}
MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const String &placeholder, const String &name, const String &path,
Sequence *sequence) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
findRegions(_atlas, this, name, path, sequence);
return new (__FILE__, __LINE__) MeshAttachment(name, sequence);
}
BoundingBoxAttachment *AtlasAttachmentLoader::newBoundingBoxAttachment(Skin &skin, const String &placeholder, const String &name) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
return new (__FILE__, __LINE__) BoundingBoxAttachment(name);
}
PathAttachment *AtlasAttachmentLoader::newPathAttachment(Skin &skin, const String &placeholder, const String &name) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
return new (__FILE__, __LINE__) PathAttachment(name);
}
PointAttachment *AtlasAttachmentLoader::newPointAttachment(Skin &skin, const String &placeholder, const String &name) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
return new (__FILE__, __LINE__) PointAttachment(name);
}
ClippingAttachment *AtlasAttachmentLoader::newClippingAttachment(Skin &skin, const String &placeholder, const String &name) {
SP_UNUSED(skin);
SP_UNUSED(placeholder);
return new (__FILE__, __LINE__) ClippingAttachment(name);
}
AtlasRegion *AtlasAttachmentLoader::findRegion(const String &name) {
return _atlas->findRegion(name);
}
@@ -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.
*****************************************************************************/
#include <spine/Attachment.h>
#include <spine/Bone.h>
#include <spine/Slot.h>
#include <assert.h>
using namespace spine;
RTTI_IMPL_NOPARENT(Attachment)
Attachment::Attachment(const String &name) : _name(name), _timelineAttachment(this), _refCount(0) {
assert(_name.length() > 0);
}
Attachment::~Attachment() {
}
const String &Attachment::getName() const {
return _name;
}
Attachment *Attachment::getTimelineAttachment() {
return _timelineAttachment;
}
void Attachment::setTimelineAttachment(Attachment *attachment) {
_timelineAttachment = attachment;
}
Array<int> &Attachment::getTimelineSlots() {
return _timelineSlots;
}
void Attachment::setTimelineSlots(Array<int> &timelineSlots) {
_timelineSlots.clearAndAddAll(timelineSlots);
}
bool Attachment::isTimelineActive(Array<Slot *> &slots, int slotIndex, bool appliedPose) {
Slot *slot = slots[slotIndex];
if (slot->getBone().isActive()) {
Attachment *attachment = appliedPose ? slot->getAppliedPose().getAttachment() : slot->getPose().getAttachment();
if (attachment != NULL && attachment->getTimelineAttachment() == this) return true;
}
for (size_t i = 0, n = _timelineSlots.size(); i < n; ++i) {
slot = slots[_timelineSlots[i]];
if (!slot->getBone().isActive()) continue;
Attachment *attachment = appliedPose ? slot->getAppliedPose().getAttachment() : slot->getPose().getAttachment();
if (attachment != NULL && attachment->getTimelineAttachment() == this) return true;
}
return false;
}
int Attachment::getRefCount() {
return _refCount;
}
void Attachment::reference() {
_refCount++;
}
void Attachment::dereference() {
_refCount--;
}
@@ -0,0 +1,46 @@
/******************************************************************************
* 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/AttachmentLoader.h>
#include <spine/BoundingBoxAttachment.h>
#include <spine/ClippingAttachment.h>
#include <spine/MeshAttachment.h>
#include <spine/PathAttachment.h>
#include <spine/PointAttachment.h>
#include <spine/RegionAttachment.h>
#include <spine/Skin.h>
using namespace spine;
AttachmentLoader::AttachmentLoader() {
}
AttachmentLoader::~AttachmentLoader() {
}
@@ -0,0 +1,89 @@
/******************************************************************************
* 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/AttachmentTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/SlotPose.h>
using namespace spine;
RTTI_IMPL_MULTI(AttachmentTimeline, Timeline, SlotTimeline)
AttachmentTimeline::AttachmentTimeline(size_t frameCount, int slotIndex) : Timeline(frameCount, 1), SlotTimeline(), _slotIndex(slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Attachment << 32) | slotIndex};
setPropertyIds(ids, 1);
_instant = true;
_attachmentNames.ensureCapacity(frameCount);
for (size_t i = 0; i < frameCount; ++i) {
_attachmentNames.add(String());
}
}
AttachmentTimeline::~AttachmentTimeline() {
}
void AttachmentTimeline::setAttachment(Skeleton &skeleton, SlotPose &pose, String *attachmentName) {
pose.setAttachment(attachmentName == NULL || attachmentName->isEmpty() ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName));
}
void AttachmentTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(alpha);
SP_UNUSED(add);
Slot *slot = skeleton._slots[_slotIndex];
if (!slot->_bone.isActive()) return;
SlotPose &pose = appliedPose ? *slot->_appliedPose : slot->_pose;
if (out || time < _frames[0]) {
if (from != MixFrom_Current) setAttachment(skeleton, pose, &slot->_data._attachmentName);
} else {
setAttachment(skeleton, pose, &_attachmentNames[Animation::search(_frames, time)]);
}
}
void AttachmentTimeline::setFrame(int frame, float time, const String &attachmentName) {
_frames[frame] = time;
_attachmentNames[frame] = attachmentName;
}
Array<String> &AttachmentTimeline::getAttachmentNames() {
return _attachmentNames;
}
@@ -0,0 +1,59 @@
/******************************************************************************
* 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/Bone.h>
#include <spine/BoneData.h>
#include <spine/BonePose.h>
using namespace spine;
RTTI_IMPL(Bone, Update)
bool Bone::yDown = true;
Bone::Bone(BoneData &data, Bone *parent)
: PosedGeneric<BoneData, BonePose, BonePose>(data), PosedActive(), _parent(parent), _children(), _sorted(false) {
_constrainedPose._bone = this;
_appliedPose->_bone = this;
}
Bone::Bone(Bone &bone, Bone *parent)
: PosedGeneric<BoneData, BonePose, BonePose>(bone._data), PosedActive(), _parent(parent), _children(), _sorted(false) {
_constrainedPose._bone = this;
_appliedPose->_bone = this;
_pose.set(bone._pose);
}
Bone *Bone::getParent() {
return _parent;
}
Array<Bone *> &Bone::getChildren() {
return _children;
}
@@ -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.
*****************************************************************************/
#include <spine/BoneData.h>
#include <spine/BonePose.h>
#include <assert.h>
using namespace spine;
BoneData::BoneData(int index, const String &name, BoneData *parent)
: PosedDataGeneric<BonePose>(name), _index(index), _parent(parent), _length(0), _color(0.61f, 0.61f, 0.61f, 1.0f), _icon(), _iconSize(1),
_iconRotation(0), _visible(true) {
assert(index >= 0);
}
int BoneData::getIndex() {
return _index;
}
BoneData *BoneData::getParent() {
return _parent;
}
float BoneData::getLength() {
return _length;
}
void BoneData::setLength(float inValue) {
_length = inValue;
}
Color &BoneData::getColor() {
return _color;
}
const String &BoneData::getIcon() {
return _icon;
}
void BoneData::setIcon(const String &icon) {
this->_icon = icon;
}
float BoneData::getIconSize() {
return _iconSize;
}
void BoneData::setIconSize(float iconSize) {
_iconSize = iconSize;
}
float BoneData::getIconRotation() {
return _iconRotation;
}
void BoneData::setIconRotation(float iconRotation) {
_iconRotation = iconRotation;
}
bool BoneData::getVisible() {
return _visible;
}
void BoneData::setVisible(bool inValue) {
this->_visible = inValue;
}
@@ -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(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() {
return _x;
}
void BoneLocal::setX(float x) {
_x = x;
}
float BoneLocal::getY() {
return _y;
}
void BoneLocal::setY(float y) {
_y = y;
}
void BoneLocal::setPosition(float x, float y) {
_x = x;
_y = y;
}
float BoneLocal::getRotation() {
return _rotation;
}
void BoneLocal::setRotation(float rotation) {
_rotation = rotation;
}
float BoneLocal::getScaleX() {
return _scaleX;
}
void BoneLocal::setScaleX(float scaleX) {
_scaleX = scaleX;
}
float BoneLocal::getScaleY() {
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() {
return _shearX;
}
void BoneLocal::setShearX(float shearX) {
_shearX = shearX;
}
float BoneLocal::getShearY() {
return _shearY;
}
void BoneLocal::setShearY(float shearY) {
_shearY = shearY;
}
Inherit BoneLocal::getInherit() {
return _inherit;
}
void BoneLocal::setInherit(Inherit inherit) {
_inherit = inherit;
}
@@ -0,0 +1,419 @@
/******************************************************************************
* 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/BonePose.h>
#include "spine/RTTI.h"
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/Skeleton.h>
#include <spine/Physics.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL(BonePose, Update);
BonePose::BonePose() : BoneLocal(), _bone(nullptr), _a(0), _b(0), _worldX(0), _c(0), _d(0), _worldY(0), _world(0), _local(0) {
}
BonePose::~BonePose() {
}
void BonePose::update(Skeleton &skeleton, Physics physics) {
if (_world != skeleton._update) updateWorldTransform(skeleton);
}
void BonePose::updateWorldTransform(Skeleton &skeleton) {
if (_local == skeleton._update)
updateLocalTransform(skeleton);
else
_world = skeleton._update;
if (_bone->getParent() == nullptr) {// Root bone.
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()->getAppliedPose();
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;
return;
}
case Inherit_OnlyTranslation: {
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;
break;
}
case Inherit_NoRotationOrReflection: {
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY(), sxi = 1 / sx, syi = 1 / sy;
pa *= sxi;
pc *= syi;
float s = pa * pa + pc * pc, r;
if (s > MathUtil::EpsilonSq) {
s = MathUtil::abs(pa * pd * syi - pb * sxi * pc) / s;
pb = pc * s;
pd = pa * s;
r = _rotation - MathUtil::atan2Deg(pc, pa);
} else {
pa = 0;
pc = 0;
r = _rotation - 90 + MathUtil::atan2Deg(pd, pb);
}
float rx = (r + _shearX) * MathUtil::Deg_Rad;
float ry = (r + _shearY + 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) * sx;
_b = (pa * lb - pb * ld) * sx;
_c = (pc * la + pd * lc) * sy;
_d = (pc * lb + pd * ld) * sy;
break;
}
case Inherit_NoScale:
case Inherit_NoScaleOrReflection: {
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY(), sxi = 1 / sx, syi = 1 / sy;
float r = _rotation * MathUtil::Deg_Rad, cosR = MathUtil::cos(r), sinR = MathUtil::sin(r);
float za = (pa * cosR + pb * sinR) * sxi;
float zc = (pc * cosR + pd * sinR) * syi;
float s = 1 / MathUtil::sqrt(za * za + zc * zc);
za *= s;
zc *= s;
float zb = -zc, zd = za;
if (_inherit == Inherit_NoScale && (pa * pd - pb * pc < 0) != ((sx < 0) != (sy < 0))) {
zb = -zb;
zd = -zd;
}
float rx = _shearX * MathUtil::Deg_Rad;
float ry = (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 = (za * la + zb * lc) * sx;
_b = (za * lb + zb * ld) * sx;
_c = (zc * la + zd * lc) * sy;
_d = (zc * lb + zd * ld) * sy;
break;
}
}
}
void BonePose::updateLocalTransform(Skeleton &skeleton) {
_local = 0;
_world = skeleton._update;
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY();
if (_bone->getParent() == nullptr) {
float sxi = 1 / sx, syi = 1 / sy;
_x = (_worldX - skeleton.getX()) * sxi;
_y = (_worldY - skeleton.getY()) * syi;
setLocal(_a * sxi, _b * sxi, _c * syi, _d * syi, 0);
return;
}
BonePose &parent = _bone->getParent()->getAppliedPose();
float pa = parent._a, pb = parent._b, pc = parent._c, pd = parent._d;
float pad = pa * pd - pb * pc, pid = 1 / pad;
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;
switch (_inherit) {
case Inherit_Normal:
setLocal(ia * _a - ib * _c, ia * _b - ib * _d, id * _c - ic * _a, id * _d - ic * _b, 0);
break;
case Inherit_OnlyTranslation: {
float sxi = 1 / sx, syi = 1 / sy;
setLocal(_a * sxi, _b * sxi, _c * syi, _d * syi, 0);
break;
}
case Inherit_NoRotationOrReflection: {
float sxi = 1 / sx, syi = 1 / sy;
pa *= sxi;
pc *= syi;
float wa = _a * sxi, wb = _b * sxi, wc = _c * syi, wd = _d * syi;
float s = 1 / (pa * pa + pc * pc), det = 1 / MathUtil::abs(pad * sxi * syi);
setLocal((pa * wa + pc * wc) * s, (pa * wb + pc * wd) * s, (pa * wc - pc * wa) * det, (pa * wd - pc * wb) * det,
MathUtil::atan2Deg(pc, pa));
break;
}
case Inherit_NoScale:
case Inherit_NoScaleOrReflection: {
float sxi = 1 / sx, syi = 1 / sy;
float wa = _a * sxi, wb = _b * sxi, wc = _c * syi, wd = _d * syi;
float tx = pd * _a - pb * _c, ty = pa * _c - pc * _a;
if (pad < 0) {
tx = -tx;
ty = -ty;
}
float r = MathUtil::atan2Deg(ty, tx);
_rotation = r;
r *= MathUtil::Deg_Rad;
float cosR = MathUtil::cos(r), sinR = MathUtil::sin(r);
float za = (pa * cosR + pb * sinR) * sxi;
float zc = (pc * cosR + pd * sinR) * syi;
float s = 1 / MathUtil::sqrt(za * za + zc * zc);
za *= s;
zc *= s;
float si = _inherit == Inherit_NoScale && (pad < 0) != ((sx < 0) != (sy < 0)) ? -1.0f : 1.0f;
setLocal(za * wa + zc * wc, za * wb + zc * wd, (za * wc - zc * wa) * si, (za * wd - zc * wb) * si);
break;
}
}
}
void BonePose::setLocal(float ra, float rb, float rc, float rd) {
float x = ra * ra + rc * rc, y = rb * rb + rd * rd;
if (x > MathUtil::EpsilonSq) {
_shearX = MathUtil::atan2Deg(rc, ra);
_scaleX = MathUtil::sqrt(x);
} else {
_shearX = 0;
_scaleX = 0;
}
_scaleY = MathUtil::sqrt(y);
if (y > MathUtil::EpsilonSq) {
_shearY = MathUtil::atan2Deg(rd, rb);
if (ra * rd - rb * rc < 0) {
_scaleY = -_scaleY;
_shearY += 90;
} else
_shearY -= 90;
if (_shearY > 180)
_shearY -= 360;
else if (_shearY <= -180)//
_shearY += 360;
} else
_shearY = 0;
}
void BonePose::setLocal(float ra, float rb, float rc, float rd, float ro) {
_shearX = 0;
float x = ra * ra + rc * rc, y = rb * rb + rd * rd;
if (x > MathUtil::EpsilonSq) {
float r = MathUtil::atan2Deg(rc, ra);
_rotation = r + ro;
_scaleX = MathUtil::sqrt(x);
_scaleY = MathUtil::sqrt(y);
if (y > MathUtil::EpsilonSq) {
_shearY = MathUtil::atan2Deg(rd, rb);
if (ra * rd - rb * rc < 0) {
_scaleY = -_scaleY;
_shearY += 90 - r;
} else
_shearY -= 90 + r;
if (_shearY > 180)
_shearY -= 360;
else if (_shearY <= -180)//
_shearY += 360;
} else
_shearY = 0;
} else {
_scaleX = 0;
_scaleY = MathUtil::sqrt(y);
_shearY = 0;
_rotation = y > MathUtil::EpsilonSq ? MathUtil::atan2Deg(rd, rb) - 90 + ro : ro;
}
}
void BonePose::validateLocalTransform(Skeleton &skeleton) {
if (_local == skeleton._update) updateLocalTransform(skeleton);
}
void BonePose::modifyLocal(Skeleton &skeleton) {
if (_local == skeleton._update) updateLocalTransform(skeleton);
_world = 0;
resetWorld(skeleton, skeleton._update);
}
void BonePose::modifyWorld(Skeleton &skeleton) {
int update = skeleton._update;
_local = update;
_world = update;
resetWorld(skeleton, update);
}
void BonePose::resetWorld(Skeleton &skeleton, int update) {
Array<Bone *> &children = _bone->getChildren();
for (size_t i = 0, n = children.size(); i < n; i++) {
BonePose &child = children[i]->getAppliedPose();
if (child._world == update) {
if (child._local == update) child.updateLocalTransform(skeleton);
child._world = 0;
child.resetWorld(skeleton, update);
}
}
}
float BonePose::getA() {
return _a;
}
void BonePose::setA(float a) {
this->_a = a;
}
float BonePose::getB() {
return _b;
}
void BonePose::setB(float b) {
this->_b = b;
}
float BonePose::getC() {
return _c;
}
void BonePose::setC(float c) {
this->_c = c;
}
float BonePose::getD() {
return _d;
}
void BonePose::setD(float d) {
this->_d = d;
}
float BonePose::getWorldX() {
return _worldX;
}
void BonePose::setWorldX(float worldX) {
this->_worldX = worldX;
}
float BonePose::getWorldY() {
return _worldY;
}
void BonePose::setWorldY(float worldY) {
this->_worldY = worldY;
}
float BonePose::getWorldRotationX() {
return MathUtil::atan2(_c, _a) * MathUtil::Rad_Deg;
}
float BonePose::getWorldRotationY() {
return MathUtil::atan2(_d, _b) * MathUtil::Rad_Deg;
}
float BonePose::getWorldScaleX() {
return MathUtil::sqrt(_a * _a + _c * _c);
}
float BonePose::getWorldScaleY() {
return MathUtil::sqrt(_b * _b + _d * _d);
}
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->getParent() == nullptr) {
outParentX = worldX;
outParentY = worldY;
} else {
_bone->getParent()->getAppliedPose().worldToLocal(worldX, worldY, outParentX, outParentY);
}
}
void BonePose::parentToWorld(float parentX, float parentY, float &outWorldX, float &outWorldY) {
if (_bone->getParent() == nullptr) {
outWorldX = parentX;
outWorldY = parentY;
} else {
_bone->getParent()->getAppliedPose().localToWorld(parentX, parentY, outWorldX, outWorldY);
}
}
float BonePose::worldToLocalRotation(float worldRotation) {
worldRotation *= MathUtil::Deg_Rad;
float sinRot = MathUtil::sin(worldRotation), cosRot = MathUtil::cos(worldRotation);
return MathUtil::atan2(_a * sinRot - _c * cosRot, _d * cosRot - _b * sinRot) * MathUtil::Rad_Deg + _rotation - _shearX;
}
float BonePose::localToWorldRotation(float localRotation) {
localRotation = (localRotation - _rotation - _shearX) * MathUtil::Deg_Rad;
float sinRot = MathUtil::sin(localRotation), cosRot = MathUtil::cos(localRotation);
return MathUtil::atan2(cosRot * _c + sinRot * _d, cosRot * _a + sinRot * _b) * MathUtil::Rad_Deg;
}
void BonePose::rotateWorld(float degrees) {
degrees *= MathUtil::Deg_Rad;
float sinRot = MathUtil::sin(degrees), cosRot = MathUtil::cos(degrees);
float ra = _a, rb = _b;
_a = cosRot * ra - sinRot * _c;
_b = cosRot * rb - sinRot * _d;
_c = sinRot * ra + cosRot * _c;
_d = sinRot * rb + cosRot * _d;
}
@@ -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.
*****************************************************************************/
#include <spine/BoneTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/BonePose.h>
#include <spine/Property.h>
using namespace spine;
// Define static constants for BoneTimeline2
const int BoneTimeline2::ENTRIES = 3;
const int BoneTimeline2::VALUE1 = 1;
const int BoneTimeline2::VALUE2 = 2;
RTTI_IMPL_NOPARENT(BoneTimeline)
RTTI_IMPL_MULTI(BoneTimeline1, CurveTimeline1, BoneTimeline)
BoneTimeline1::BoneTimeline1(size_t frameCount, size_t bezierCount, int boneIndex, Property property)
: CurveTimeline1(frameCount, bezierCount), BoneTimeline(boneIndex), _boneIndex(boneIndex) {
PropertyId ids[] = {((PropertyId) property << 32) | boneIndex};
setPropertyIds(ids, 1);
_additive = true;
}
void BoneTimeline1::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
Bone *bone = skeleton._bones[_boneIndex];
if (bone->isActive()) {
_apply(appliedPose ? *bone->_appliedPose : bone->_pose, bone->_data._setupPose, time, alpha, from, add, out);
}
}
RTTI_IMPL_MULTI(BoneTimeline2, CurveTimeline, BoneTimeline)
BoneTimeline2::BoneTimeline2(size_t frameCount, size_t bezierCount, int boneIndex, Property property1, Property property2)
: CurveTimeline(frameCount, BoneTimeline2::ENTRIES, bezierCount), BoneTimeline(boneIndex), _boneIndex(boneIndex) {
PropertyId ids[] = {((PropertyId) property1 << 32) | boneIndex, ((PropertyId) property2 << 32) | boneIndex};
setPropertyIds(ids, 2);
_additive = true;
}
void BoneTimeline2::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
Bone *bone = skeleton._bones[_boneIndex];
if (bone->isActive()) {
_apply(appliedPose ? *bone->_appliedPose : bone->_pose, bone->_data._setupPose, time, alpha, from, add, out);
}
}
void BoneTimeline2::setFrame(size_t frame, float time, float value1, float value2) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + VALUE1] = value1;
_frames[frame + VALUE2] = value2;
}
@@ -0,0 +1,47 @@
/******************************************************************************
* 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/BoundingBoxAttachment.h>
using namespace spine;
RTTI_IMPL(BoundingBoxAttachment, VertexAttachment)
BoundingBoxAttachment::BoundingBoxAttachment(const String &name) : VertexAttachment(name), _color(0.38f, 0.94f, 0.0f, 1.0f) {
}
Color &BoundingBoxAttachment::getColor() {
return _color;
}
Attachment &BoundingBoxAttachment::copy() {
BoundingBoxAttachment *copy = new (__FILE__, __LINE__) BoundingBoxAttachment(getName());
copyTo(*copy);
return *copy;
}
@@ -0,0 +1,76 @@
/******************************************************************************
* 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/ClippingAttachment.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(ClippingAttachment, VertexAttachment)
ClippingAttachment::ClippingAttachment(const String &name) : VertexAttachment(name), _endSlot(NULL), _convex(false), _inverse(false), _color() {
}
SlotData *ClippingAttachment::getEndSlot() {
return _endSlot;
}
void ClippingAttachment::setEndSlot(SlotData *inValue) {
_endSlot = inValue;
}
bool ClippingAttachment::getConvex() {
return _convex;
}
void ClippingAttachment::setConvex(bool convex) {
_convex = convex;
}
bool ClippingAttachment::getInverse() {
return _inverse;
}
void ClippingAttachment::setInverse(bool inverse) {
_inverse = inverse;
}
Color &ClippingAttachment::getColor() {
return _color;
}
Attachment &ClippingAttachment::copy() {
ClippingAttachment *copy = new (__FILE__, __LINE__) ClippingAttachment(getName());
copyTo(*copy);
copy->_endSlot = _endSlot;
copy->_convex = _convex;
copy->_inverse = _inverse;
return *copy;
}
@@ -0,0 +1,507 @@
/******************************************************************************
* 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/ColorTimeline.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/Event.h>
#include <spine/Property.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/SlotPose.h>
using namespace spine;
RTTI_IMPL(RGBATimeline, SlotCurveTimeline)
RGBATimeline::RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex) : SlotCurveTimeline(frameCount, ENTRIES, bezierCount, slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, ((PropertyId) Property_Alpha << 32) | slotIndex};
setPropertyIds(ids, 2);
}
RGBATimeline::~RGBATimeline() {
}
void RGBATimeline::setFrame(int frame, float time, float r, float g, float b, float a) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + R] = r;
_frames[frame + G] = g;
_frames[frame + B] = b;
_frames[frame + A] = a;
}
void RGBATimeline::_apply(Slot &slot, SlotPose &pose, float time, float alpha, MixFrom from, bool add) {
SP_UNUSED(add);
Color &color = pose._color;
if (time < _frames[0]) {
Color &setup = slot._data._setupPose._color;
switch (from) {
case MixFrom_Setup:
color.set(setup);
break;
case MixFrom_First:
color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, (setup.a - color.a) * alpha);
break;
case MixFrom_Current:
break;
}
return;
}
float r, g, b, a;
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i / ENTRIES];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
a = _frames[i + A];
float t = (time - before) / (_frames[i + ENTRIES] - before);
r += (_frames[i + ENTRIES + R] - r) * t;
g += (_frames[i + ENTRIES + G] - g) * t;
b += (_frames[i + ENTRIES + B] - b) * t;
a += (_frames[i + ENTRIES + A] - a) * t;
break;
}
case STEPPED: {
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
a = _frames[i + A];
break;
}
default: {
r = getBezierValue(time, i, R, curveType - BEZIER);
g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER);
b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER);
a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER);
break;
}
}
if (alpha == 1)
color.set(r, g, b, a);
else {
if (from == MixFrom_Setup) {
Color &setup = slot._data._setupPose._color;
color.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha,
setup.a + (a - setup.a) * alpha);
} else
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
}
}
RTTI_IMPL(RGBTimeline, SlotCurveTimeline)
RGBTimeline::RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : SlotCurveTimeline(frameCount, ENTRIES, bezierCount, slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex};
setPropertyIds(ids, 1);
}
RGBTimeline::~RGBTimeline() {
}
void RGBTimeline::setFrame(int frame, float time, float r, float g, float b) {
frame <<= 2;
_frames[frame] = time;
_frames[frame + R] = r;
_frames[frame + G] = g;
_frames[frame + B] = b;
}
void RGBTimeline::_apply(Slot &slot, SlotPose &pose, float time, float alpha, MixFrom from, bool add) {
SP_UNUSED(add);
Color &color = pose._color;
float r, g, b;
if (time < _frames[0]) {
Color &setup = slot._data._setupPose._color;
switch (from) {
case MixFrom_Setup:
color.r = setup.r;
color.g = setup.g;
color.b = setup.b;
break;
case MixFrom_First:
color.r += (setup.r - color.r) * alpha;
color.g += (setup.g - color.g) * alpha;
color.b += (setup.b - color.b) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i >> 2];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
float t = (time - before) / (_frames[i + ENTRIES] - before);
r += (_frames[i + ENTRIES + R] - r) * t;
g += (_frames[i + ENTRIES + G] - g) * t;
b += (_frames[i + ENTRIES + B] - b) * t;
break;
}
case STEPPED: {
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
break;
}
default: {
r = getBezierValue(time, i, R, curveType - BEZIER);
g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER);
b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER);
break;
}
}
if (alpha != 1) {
if (from == MixFrom_Setup) {
Color &setup = slot._data._setupPose._color;
r = setup.r + (r - setup.r) * alpha;
g = setup.g + (g - setup.g) * alpha;
b = setup.b + (b - setup.b) * alpha;
} else {
r = color.r + (r - color.r) * alpha;
g = color.g + (g - color.g) * alpha;
b = color.b + (b - color.b) * alpha;
}
}
color.r = r < 0 ? 0 : (r > 1 ? 1 : r);
color.g = g < 0 ? 0 : (g > 1 ? 1 : g);
color.b = b < 0 ? 0 : (b > 1 ? 1 : b);
}
RTTI_IMPL_MULTI(AlphaTimeline, CurveTimeline1, SlotTimeline)
AlphaTimeline::AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex)
: CurveTimeline1(frameCount, bezierCount), SlotTimeline(), _slotIndex(slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Alpha << 32) | slotIndex};
setPropertyIds(ids, 1);
}
AlphaTimeline::~AlphaTimeline() {
}
int AlphaTimeline::getSlotIndex() {
return _slotIndex;
}
void AlphaTimeline::setSlotIndex(int inValue) {
_slotIndex = inValue;
}
void AlphaTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(add);
SP_UNUSED(out);
Slot *slot = skeleton._slots[_slotIndex];
if (!slot->_bone._active) return;
Color &color = (appliedPose ? *slot->_appliedPose : slot->_pose)._color;
if (time < _frames[0]) {
float setup = slot->_data._setupPose._color.a;
switch (from) {
case MixFrom_Setup:
color.a = setup;
break;
case MixFrom_First:
color.a += (setup - color.a) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float a = getCurveValue(time);
if (alpha != 1) {
if (from == MixFrom_Setup) {
Color &setup = slot->_data._setupPose._color;
a = setup.a + (a - setup.a) * alpha;
} else
a = color.a + (a - color.a) * alpha;
}
color.a = a < 0 ? 0 : (a > 1 ? 1 : a);
}
RTTI_IMPL(RGBA2Timeline, SlotCurveTimeline)
RGBA2Timeline::RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : SlotCurveTimeline(frameCount, ENTRIES, bezierCount, slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, ((PropertyId) Property_Alpha << 32) | slotIndex,
((PropertyId) Property_Rgb2 << 32) | slotIndex};
setPropertyIds(ids, 3);
}
RGBA2Timeline::~RGBA2Timeline() {
}
void RGBA2Timeline::setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) {
frame <<= 3;
_frames[frame] = time;
_frames[frame + R] = r;
_frames[frame + G] = g;
_frames[frame + B] = b;
_frames[frame + A] = a;
_frames[frame + R2] = r2;
_frames[frame + G2] = g2;
_frames[frame + B2] = b2;
}
void RGBA2Timeline::_apply(Slot &slot, SlotPose &pose, float time, float alpha, MixFrom from, bool add) {
SP_UNUSED(add);
Color &light = pose._color;
Color &dark = pose._darkColor;
float r2, g2, b2;
if (time < _frames[0]) {
SlotPose &setup = slot._data._setupPose;
Color &setupLight = setup._color;
Color &setupDark = setup._darkColor;
switch (from) {
case MixFrom_Setup:
light.set(setupLight);
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
break;
case MixFrom_First:
light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
(setupLight.a - light.a) * alpha);
dark.r += (setupDark.r - dark.r) * alpha;
dark.g += (setupDark.g - dark.g) * alpha;
dark.b += (setupDark.b - dark.b) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float r, g, b, a;
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i >> 3];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
a = _frames[i + A];
r2 = _frames[i + R2];
g2 = _frames[i + G2];
b2 = _frames[i + B2];
float t = (time - before) / (_frames[i + ENTRIES] - before);
r += (_frames[i + ENTRIES + R] - r) * t;
g += (_frames[i + ENTRIES + G] - g) * t;
b += (_frames[i + ENTRIES + B] - b) * t;
a += (_frames[i + ENTRIES + A] - a) * t;
r2 += (_frames[i + ENTRIES + R2] - r2) * t;
g2 += (_frames[i + ENTRIES + G2] - g2) * t;
b2 += (_frames[i + ENTRIES + B2] - b2) * t;
break;
}
case STEPPED: {
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
a = _frames[i + A];
r2 = _frames[i + R2];
g2 = _frames[i + G2];
b2 = _frames[i + B2];
break;
}
default: {
r = getBezierValue(time, i, R, curveType - BEZIER);
g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER);
b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER);
a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER);
r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER);
g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER);
b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER);
break;
}
}
if (alpha == 1)
light.set(r, g, b, a);
else if (from == MixFrom_Setup) {
SlotPose &setup = slot._data._setupPose;
Color &setupLight = setup._color;
light.set(setupLight.r + (r - setupLight.r) * alpha, setupLight.g + (g - setupLight.g) * alpha, setupLight.b + (b - setupLight.b) * alpha,
setupLight.a + (a - setupLight.a) * alpha);
Color &setupDark = setup._darkColor;
r2 = setupDark.r + (r2 - setupDark.r) * alpha;
g2 = setupDark.g + (g2 - setupDark.g) * alpha;
b2 = setupDark.b + (b2 - setupDark.b) * alpha;
} else {
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
r2 = dark.r + (r2 - dark.r) * alpha;
g2 = dark.g + (g2 - dark.g) * alpha;
b2 = dark.b + (b2 - dark.b) * alpha;
}
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
}
RTTI_IMPL(RGB2Timeline, SlotCurveTimeline)
RGB2Timeline::RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : SlotCurveTimeline(frameCount, ENTRIES, bezierCount, slotIndex) {
PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, ((PropertyId) Property_Rgb2 << 32) | slotIndex};
setPropertyIds(ids, 2);
}
RGB2Timeline::~RGB2Timeline() {
}
void RGB2Timeline::setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + R] = r;
_frames[frame + G] = g;
_frames[frame + B] = b;
_frames[frame + R2] = r2;
_frames[frame + G2] = g2;
_frames[frame + B2] = b2;
}
void RGB2Timeline::_apply(Slot &slot, SlotPose &pose, float time, float alpha, MixFrom from, bool add) {
SP_UNUSED(add);
Color &light = pose._color;
Color &dark = pose._darkColor;
float r, g, b, r2, g2, b2;
if (time < _frames[0]) {
SlotPose &setup = slot._data._setupPose;
Color &setupLight = setup._color;
Color &setupDark = setup._darkColor;
switch (from) {
case MixFrom_Setup:
light.r = setupLight.r;
light.g = setupLight.g;
light.b = setupLight.b;
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
break;
case MixFrom_First:
light.r += (setupLight.r - light.r) * alpha;
light.g += (setupLight.g - light.g) * alpha;
light.b += (setupLight.b - light.b) * alpha;
dark.r += (setupDark.r - dark.r) * alpha;
dark.g += (setupDark.g - dark.g) * alpha;
dark.b += (setupDark.b - dark.b) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i / ENTRIES];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
r2 = _frames[i + R2];
g2 = _frames[i + G2];
b2 = _frames[i + B2];
float t = (time - before) / (_frames[i + ENTRIES] - before);
r += (_frames[i + ENTRIES + R] - r) * t;
g += (_frames[i + ENTRIES + G] - g) * t;
b += (_frames[i + ENTRIES + B] - b) * t;
r2 += (_frames[i + ENTRIES + R2] - r2) * t;
g2 += (_frames[i + ENTRIES + G2] - g2) * t;
b2 += (_frames[i + ENTRIES + B2] - b2) * t;
break;
}
case STEPPED: {
r = _frames[i + R];
g = _frames[i + G];
b = _frames[i + B];
r2 = _frames[i + R2];
g2 = _frames[i + G2];
b2 = _frames[i + B2];
break;
}
default: {
r = getBezierValue(time, i, R, curveType - BEZIER);
g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER);
b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER);
r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER);
g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER);
b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER);
break;
}
}
if (alpha != 1) {
if (from == MixFrom_Setup) {
SlotPose &setup = slot._data._setupPose;
Color &setupLight = setup._color;
r = setupLight.r + (r - setupLight.r) * alpha;
g = setupLight.g + (g - setupLight.g) * alpha;
b = setupLight.b + (b - setupLight.b) * alpha;
Color &setupDark = setup._darkColor;
r2 = setupDark.r + (r2 - setupDark.r) * alpha;
g2 = setupDark.g + (g2 - setupDark.g) * alpha;
b2 = setupDark.b + (b2 - setupDark.b) * alpha;
} else {
r = light.r + (r - light.r) * alpha;
g = light.g + (g - light.g) * alpha;
b = light.b + (b - light.b) * alpha;
r2 = dark.r + (r2 - dark.r) * alpha;
g2 = dark.g + (g2 - dark.g) * alpha;
b2 = dark.b + (b2 - dark.b) * alpha;
}
}
light.r = r < 0 ? 0 : (r > 1 ? 1 : r);
light.g = g < 0 ? 0 : (g > 1 ? 1 : g);
light.b = b < 0 ? 0 : (b > 1 ? 1 : b);
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
}
@@ -0,0 +1,40 @@
/******************************************************************************
* 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/Constraint.h>
using namespace spine;
RTTI_IMPL(Constraint, Update)
Constraint::Constraint() {
}
Constraint::~Constraint() {
}
@@ -0,0 +1,34 @@
/******************************************************************************
* 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/ConstraintData.h>
using namespace spine;
RTTI_IMPL_NOPARENT(ConstraintData)
@@ -0,0 +1,40 @@
/******************************************************************************
* 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/ConstraintTimeline.h>
using namespace spine;
RTTI_IMPL_NOPARENT(ConstraintTimeline)
ConstraintTimeline::ConstraintTimeline() {
}
ConstraintTimeline::~ConstraintTimeline() {
}
@@ -0,0 +1,43 @@
/******************************************************************************
* 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/ConstraintTimeline1.h>
using namespace spine;
RTTI_IMPL_MULTI(ConstraintTimeline1, CurveTimeline1, ConstraintTimeline)
ConstraintTimeline1::ConstraintTimeline1(size_t frameCount, size_t bezierCount, int constraintIndex, Property property)
: CurveTimeline1(frameCount, bezierCount), ConstraintTimeline(), _constraintIndex(constraintIndex) {
PropertyId ids[] = {((PropertyId) property << 32) | constraintIndex};
setPropertyIds(ids, 1);
}
ConstraintTimeline1::~ConstraintTimeline1() {
}
@@ -0,0 +1,171 @@
/******************************************************************************
* 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/CurveTimeline.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL(CurveTimeline, Timeline)
CurveTimeline::CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount) : Timeline(frameCount, frameEntries) {
_curves.setSize(frameCount + bezierCount * BEZIER_SIZE, 0);
_curves[frameCount - 1] = STEPPED;
}
CurveTimeline::~CurveTimeline() {
}
void CurveTimeline::setLinear(size_t frame) {
_curves[frame] = LINEAR;
}
void CurveTimeline::setStepped(size_t frame) {
_curves[frame] = STEPPED;
}
void CurveTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, float cy2,
float time2, float value2) {
size_t i = getFrameCount() + bezier * BEZIER_SIZE;
if (value == 0) _curves[frame] = BEZIER + i;
float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
float x = time1 + dx, y = value1 + dy;
for (size_t n = i + BEZIER_SIZE; i < n; i += 2) {
_curves[i] = x;
_curves[i + 1] = y;
dx += ddx;
dy += ddy;
ddx += dddx;
ddy += dddy;
x += dx;
y += dy;
}
}
float CurveTimeline::getBezierValue(float time, size_t frameIndex, size_t valueOffset, size_t i) {
if (_curves[i] > time) {
float x = _frames[frameIndex], y = _frames[frameIndex + valueOffset];
return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y);
}
size_t n = i + BEZIER_SIZE;
for (i += 2; i < n; i += 2) {
if (_curves[i] >= time) {
float x = _curves[i - 2], y = _curves[i - 1];
return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y);
}
}
frameIndex += getFrameEntries();
float x = _curves[n - 2], y = _curves[n - 1];
return y + (time - x) / (_frames[frameIndex] - x) * (_frames[frameIndex + valueOffset] - y);
}
Array<float> &CurveTimeline::getCurves() {
return _curves;
}
RTTI_IMPL(CurveTimeline1, CurveTimeline)
CurveTimeline1::CurveTimeline1(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, CurveTimeline1::ENTRIES, bezierCount) {
}
CurveTimeline1::~CurveTimeline1() {
}
void CurveTimeline1::setFrame(size_t frame, float time, float value) {
frame <<= 1;
_frames[frame] = time;
_frames[frame + CurveTimeline1::VALUE] = value;
}
float CurveTimeline1::getCurveValue(float time) {
int i = (int) _frames.size() - 2;
for (int ii = 2; ii <= i; ii += 2) {
if (_frames[ii] > time) {
i = ii - 2;
break;
}
}
int curveType = (int) _curves[i >> 1];
switch (curveType) {
case CurveTimeline::LINEAR: {
float before = _frames[i], value = _frames[i + CurveTimeline1::VALUE];
return value +
(time - before) / (_frames[i + CurveTimeline1::ENTRIES] - before) *
(_frames[i + CurveTimeline1::ENTRIES + CurveTimeline1::VALUE] - value);
}
case CurveTimeline::STEPPED:
return _frames[i + CurveTimeline1::VALUE];
}
return getBezierValue(time, i, CurveTimeline1::VALUE, curveType - CurveTimeline1::BEZIER);
}
float CurveTimeline1::getRelativeValue(float time, float alpha, MixFrom from, bool add, float current, float setup) {
if (time < _frames[0]) return beforeFirstKey(from, alpha, current, setup);
float value = getCurveValue(time);
return from == MixFrom_Setup ? setup + value * alpha : current + (add ? value : value + setup - current) * alpha;
}
float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixFrom from, bool add, float current, float setup) {
if (time < _frames[0]) return beforeFirstKey(from, alpha, current, setup);
float value = getCurveValue(time);
return from == MixFrom_Setup ? setup + (add ? value : value - setup) * alpha : current + (add ? value : value - current) * alpha;
}
float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixFrom from, bool add, float current, float setup, float value) {
if (time < _frames[0]) return beforeFirstKey(from, alpha, current, setup);
return from == MixFrom_Setup ? setup + (add ? value : value - setup) * alpha : current + (add ? value : value - current) * alpha;
}
float CurveTimeline1::getScaleValue(float time, float alpha, MixFrom from, bool add, bool out, float current, float setup) {
if (time < _frames[0]) return beforeFirstKey(from, alpha, current, setup);
float value = getCurveValue(time) * setup;
if (alpha == 1 && !add) return value;
float base = from == MixFrom_Setup ? setup : current;
if (add) return base + (value - setup) * alpha;
if (out) return base + (MathUtil::abs(value) * MathUtil::sign(base) - base) * alpha;
base = MathUtil::abs(base) * MathUtil::sign(value);
return base + (value - base) * alpha;
}
float CurveTimeline1::beforeFirstKey(MixFrom from, float alpha, float current, float setup) {
switch (from) {
case MixFrom_Setup:
return setup;
case MixFrom_First:
return current + (setup - current) * alpha;
case MixFrom_Current:
return current;
}
return current;
}
@@ -0,0 +1,357 @@
/******************************************************************************
* 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/DeformTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/VertexAttachment.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/SlotPose.h>
using namespace spine;
RTTI_IMPL(DeformTimeline, SlotCurveTimeline)
DeformTimeline::DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment &attachment)
: SlotCurveTimeline(frameCount, 1, bezierCount, slotIndex), _attachment(&attachment) {
PropertyId ids[] = {((PropertyId) Property_Deform << 32) | ((slotIndex << 16 | attachment._id) & 0xffffffff)};
setPropertyIds(ids, 1);
_additive = true;
_vertices.ensureCapacity(frameCount);
for (size_t i = 0; i < frameCount; ++i) {
Array<float> vec;
_vertices.add(vec);
}
}
void DeformTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
Array<Slot *> &slots = skeleton.getSlots();
if (!_attachment->isTimelineActive(slots, getSlotIndex(), appliedPose)) return;
Array<int> &timelineSlots = _attachment->getTimelineSlots();
Array<float> &frames = _frames;
if (time < frames[0]) {
applyBeforeFirst(*slots[getSlotIndex()], appliedPose, alpha, from);
for (size_t i = 0; i < timelineSlots.size(); ++i) applyBeforeFirst(*slots[timelineSlots[i]], appliedPose, alpha, from);
return;
}
Array<float> *v1 = NULL;
Array<float> *v2 = NULL;
float percent = 0;
if (time >= frames[frames.size() - 1]) {
v1 = &_vertices[frames.size() - 1];
} else {
int frame = Animation::search(frames, time);
percent = getCurvePercent(time, frame);
v1 = &_vertices[frame];
v2 = &_vertices[frame + 1];
}
size_t vertexCount = _vertices[0].size();
applyToSlot(*slots[getSlotIndex()], appliedPose, *v1, v2, percent, vertexCount, alpha, from, add);
for (size_t i = 0; i < timelineSlots.size(); ++i)
applyToSlot(*slots[timelineSlots[i]], appliedPose, *v1, v2, percent, vertexCount, alpha, from, add);
}
void DeformTimeline::applyBeforeFirst(Slot &slot, bool appliedPose, float alpha, MixFrom from) {
if (!slot.getBone().isActive()) return;
SlotPose &pose = appliedPose ? slot.getAppliedPose() : slot.getPose();
Attachment *attachment = pose.getAttachment();
if (attachment == NULL || attachment->getTimelineAttachment() != _attachment) return;
Array<float> &deformArray = pose.getDeform();
if (deformArray.size() == 0) from = MixFrom_Setup;
switch (from) {
case MixFrom_Setup:
deformArray.clear();
break;
case MixFrom_First: {
if (alpha == 1) {
deformArray.clear();
return;
}
size_t vertexCount = _vertices[0].size();
deformArray.setSize(vertexCount, 0);
VertexAttachment *vertexAttachment = static_cast<VertexAttachment *>(attachment);
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) deformArray[i] += (setupVertices[i] - deformArray[i]) * alpha;
} else {
float a = 1 - alpha;
for (size_t i = 0; i < vertexCount; i++) deformArray[i] *= a;
}
break;
}
case MixFrom_Current:
break;
}
}
void DeformTimeline::applyToPose(SlotPose &pose, Array<float> &v1, Array<float> *v2, float percent, size_t vertexCount, float alpha, MixFrom from,
bool add) {
Attachment *slotAttachment = pose.getAttachment();
if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti)) return;
VertexAttachment *vertexAttachment = static_cast<VertexAttachment *>(slotAttachment);
if (vertexAttachment->getTimelineAttachment() != _attachment) return;
Array<float> &deformArray = pose.getDeform();
if (deformArray.size() == 0) from = MixFrom_Setup;
bool fromSetup = from == MixFrom_Setup;
deformArray.setSize(vertexCount, 0);
Array<float> &deform = deformArray;
if (v2 == NULL) {
if (alpha == 1) {
if (add && !fromSetup) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) deform[i] += v1[i] - setupVertices[i];
} else {
for (size_t i = 0; i < vertexCount; i++) deform[i] += v1[i];
}
} else {
memcpy(deform.buffer(), v1.buffer(), vertexCount * sizeof(float));
}
} else if (fromSetup) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) {
float setup = setupVertices[i];
deform[i] = setup + (v1[i] - setup) * alpha;
}
} else {
for (size_t i = 0; i < vertexCount; i++) deform[i] = v1[i] * alpha;
}
} else if (add) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) deform[i] += (v1[i] - setupVertices[i]) * alpha;
} else {
for (size_t i = 0; i < vertexCount; i++) deform[i] += v1[i] * alpha;
}
} else {
for (size_t i = 0; i < vertexCount; i++) deform[i] += (v1[i] - deform[i]) * alpha;
}
return;
}
if (alpha == 1) {
if (add && !fromSetup) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] += prev + ((*v2)[i] - prev) * percent - setupVertices[i];
}
} else {
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] += prev + ((*v2)[i] - prev) * percent;
}
}
} else if (percent == 0) {
memcpy(deform.buffer(), v1.buffer(), vertexCount * sizeof(float));
} else {
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] = prev + ((*v2)[i] - prev) * percent;
}
}
} else if (fromSetup) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i], setup = setupVertices[i];
deform[i] = setup + (prev + ((*v2)[i] - prev) * percent - setup) * alpha;
}
} else {
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] = (prev + ((*v2)[i] - prev) * percent) * alpha;
}
}
} else if (add) {
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] += (prev + ((*v2)[i] - prev) * percent - setupVertices[i]) * alpha;
}
} else {
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] += (prev + ((*v2)[i] - prev) * percent) * alpha;
}
}
} else {
for (size_t i = 0; i < vertexCount; i++) {
float prev = v1[i];
deform[i] += (prev + ((*v2)[i] - prev) * percent - deform[i]) * alpha;
}
}
}
void DeformTimeline::applyToSlot(Slot &slot, bool appliedPose, Array<float> &v1, Array<float> *v2, float percent, size_t vertexCount, float alpha,
MixFrom from, bool add) {
if (!slot.getBone().isActive()) return;
SlotPose &pose = appliedPose ? slot.getAppliedPose() : slot.getPose();
Attachment *attachment = pose.getAttachment();
if (attachment == NULL || attachment->getTimelineAttachment() != _attachment) return;
applyToPose(pose, v1, v2, percent, vertexCount, alpha, from, add);
}
void DeformTimeline::_apply(Slot &slot, SlotPose &pose, float time, float alpha, MixFrom from, bool add) {
Array<float> &frames = _frames;
if (time < frames[0]) {
Array<float> &deformArray = pose.getDeform();
if (deformArray.size() == 0) from = MixFrom_Setup;
switch (from) {
case MixFrom_Setup:
deformArray.clear();
break;
case MixFrom_First: {
if (alpha == 1) {
deformArray.clear();
return;
}
size_t vertexCount = _vertices[0].size();
deformArray.setSize(vertexCount, 0);
VertexAttachment *vertexAttachment = static_cast<VertexAttachment *>(pose.getAttachment());
if (vertexAttachment->getBones().size() == 0) {
Array<float> &setupVertices = vertexAttachment->getVertices();
for (size_t i = 0; i < vertexCount; i++) deformArray[i] += (setupVertices[i] - deformArray[i]) * alpha;
} else {
float a = 1 - alpha;
for (size_t i = 0; i < vertexCount; i++) deformArray[i] *= a;
}
break;
}
case MixFrom_Current:
break;
}
return;
}
Array<float> *v1 = NULL;
Array<float> *v2 = NULL;
float percent = 0;
if (time >= frames[frames.size() - 1]) {
v1 = &_vertices[frames.size() - 1];
} else {
int frame = Animation::search(frames, time);
percent = getCurvePercent(time, frame);
v1 = &_vertices[frame];
v2 = &_vertices[frame + 1];
}
applyToPose(pose, *v1, v2, percent, _vertices[0].size(), alpha, from, add);
}
void DeformTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, float cy2,
float time2, float value2) {
SP_UNUSED(value1);
SP_UNUSED(value2);
Array<float> &curves = _curves;
size_t i = getFrameCount() + bezier * BEZIER_SIZE;
if (value == 0) curves[frame] = BEZIER + (float) i;
float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f;
float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f;
float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f;
float x = time1 + dx, y = dy;
for (size_t n = i + BEZIER_SIZE; i < n; i += 2) {
curves[i] = x;
curves[i + 1] = y;
dx += ddx;
dy += ddy;
ddx += dddx;
ddy += dddy;
x += dx;
y += dy;
}
}
float DeformTimeline::getCurvePercent(float time, int frame) {
Array<float> &curves = _curves;
int i = (int) curves[frame];
switch (i) {
case LINEAR: {
float x = _frames[frame];
return (time - x) / (_frames[frame + getFrameEntries()] - x);
}
case STEPPED: {
return 0;
}
}
i -= BEZIER;
if (curves[i] > time) {
float x = _frames[frame];
return curves[i + 1] * (time - x) / (curves[i] - x);
}
int n = i + BEZIER_SIZE;
for (i += 2; i < n; i += 2) {
if (curves[i] >= time) {
float x = curves[i - 2], y = curves[i - 1];
return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
}
float x = curves[n - 2], y = curves[n - 1];
return y + (1 - y) * (time - x) / (_frames[frame + getFrameEntries()] - x);
}
void DeformTimeline::setFrame(int frame, float time, Array<float> &vertices) {
_frames[frame] = time;
_vertices[frame].clear();
_vertices[frame].addAll(vertices);
}
Array<Array<float>> &DeformTimeline::getVertices() {
return _vertices;
}
VertexAttachment &DeformTimeline::getAttachment() {
return *_attachment;
}
void DeformTimeline::setAttachment(VertexAttachment &inValue) {
_attachment = &inValue;
}
@@ -0,0 +1,69 @@
/******************************************************************************
* 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/DrawOrder.h>
#include <spine/Slot.h>
using namespace spine;
DrawOrder::DrawOrder(Array<Slot *> &setupPose) : _setupPose(setupPose), _pose(), _constrainedPose(), _appliedPose(&_pose) {
}
void DrawOrder::setupPose() {
_pose.clear();
_pose.setSize(_setupPose.size(), NULL);
for (size_t i = 0, n = _setupPose.size(); i < n; ++i) {
_pose[i] = _setupPose[i];
}
}
Array<Slot *> &DrawOrder::getPose() {
return _pose;
}
Array<Slot *> &DrawOrder::getAppliedPose() {
return *_appliedPose;
}
void DrawOrder::unconstrained() {
_appliedPose = &_pose;
}
void DrawOrder::constrained() {
_appliedPose = &_constrainedPose;
}
void DrawOrder::reset() {
_constrainedPose.clear();
_constrainedPose.setSize(_pose.size(), NULL);
for (size_t i = 0, n = _pose.size(); i < n; ++i) {
_constrainedPose[i] = _pose[i];
}
}
@@ -0,0 +1,113 @@
/******************************************************************************
* 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/DrawOrderFolderTimeline.h>
#include <spine/Animation.h>
#include <spine/Event.h>
#include <spine/Property.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(DrawOrderFolderTimeline, Timeline)
DrawOrderFolderTimeline::DrawOrderFolderTimeline(size_t frameCount, Array<int> &slots, size_t slotCount) : Timeline(frameCount, 1) {
Array<PropertyId> ids(slots.size());
for (size_t i = 0; i < slots.size(); ++i) ids.add(((PropertyId) Property_DrawOrderFolder << 32) | (PropertyId) slots[i]);
setPropertyIds(ids.buffer(), ids.size());
_slots.addAll(slots);
_drawOrders.ensureCapacity(frameCount);
_inFolder.setSize(slotCount, false);
for (size_t i = 0; i < _slots.size(); ++i) _inFolder[_slots[i]] = true;
_instant = true;
for (size_t i = 0; i < frameCount; ++i) {
Array<int> vec;
_drawOrders.add(vec);
}
}
void DrawOrderFolderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(alpha);
SP_UNUSED(add);
Array<Slot *> &pose = appliedPose ? skeleton._drawOrder.getAppliedPose() : skeleton._drawOrder.getPose();
Array<Slot *> &setupPose = skeleton._slots;
if (out || time < _frames[0]) {
if (from != MixFrom_Current) setup(pose, setupPose);
} else {
Array<int> &drawOrder = _drawOrders[Animation::search(_frames, time)];
if (drawOrder.size() == 0)
setup(pose, setupPose);
else
apply(pose, setupPose, drawOrder);
}
}
size_t DrawOrderFolderTimeline::getFrameCount() {
return _frames.size();
}
Array<int> &DrawOrderFolderTimeline::getSlots() {
return _slots;
}
Array<Array<int>> &DrawOrderFolderTimeline::getDrawOrders() {
return _drawOrders;
}
void DrawOrderFolderTimeline::setFrame(size_t frame, float time, Array<int> *drawOrder) {
_frames[frame] = time;
_drawOrders[frame].clear();
if (drawOrder != NULL) _drawOrders[frame].addAll(*drawOrder);
}
void DrawOrderFolderTimeline::setup(Array<Slot *> &pose, Array<Slot *> &setupPose) {
for (size_t i = 0, found = 0, done = _slots.size();; ++i) {
if (_inFolder[pose[i]->getData().getIndex()]) {
pose[i] = setupPose[_slots[found]];
if (++found == done) break;
}
}
}
void DrawOrderFolderTimeline::apply(Array<Slot *> &pose, Array<Slot *> &setupPose, Array<int> &drawOrderIndices) {
for (size_t i = 0, found = 0, done = _slots.size();; ++i) {
if (_inFolder[pose[i]->getData().getIndex()]) {
pose[i] = setupPose[_slots[drawOrderIndices[found]]];
if (++found == done) break;
}
}
}
@@ -0,0 +1,102 @@
/******************************************************************************
* 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/DrawOrderTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(DrawOrderTimeline, Timeline)
PropertyId DrawOrderTimeline::getPropertyId() {
return ((PropertyId) Property_DrawOrder << 32);
}
DrawOrderTimeline::DrawOrderTimeline(size_t frameCount) : Timeline(frameCount, 1) {
PropertyId ids[] = {getPropertyId()};
setPropertyIds(ids, 1);
_instant = true;
_drawOrders.ensureCapacity(frameCount);
for (size_t i = 0; i < frameCount; ++i) {
Array<int> vec;
_drawOrders.add(vec);
}
}
void DrawOrderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(appliedPose);
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(alpha);
SP_UNUSED(add);
Array<Slot *> &pose = appliedPose ? skeleton._drawOrder.getAppliedPose() : skeleton._drawOrder.getPose();
Array<Slot *> &setup = skeleton._slots;
if (out || time < _frames[0]) {
if (from != MixFrom_Current) {
pose.setSize(setup.size(), NULL);
for (size_t i = 0, n = setup.size(); i < n; ++i) pose[i] = setup[i];
}
return;
}
Array<int> &drawOrderToSetupIndex = _drawOrders[Animation::search(_frames, time)];
if (drawOrderToSetupIndex.size() == 0) {
pose.setSize(setup.size(), NULL);
for (size_t i = 0, n = setup.size(); i < n; ++i) pose[i] = setup[i];
} else {
pose.setSize(drawOrderToSetupIndex.size(), NULL);
for (size_t i = 0, n = drawOrderToSetupIndex.size(); i < n; ++i) pose[i] = setup[drawOrderToSetupIndex[i]];
}
}
void DrawOrderTimeline::setFrame(size_t frame, float time, Array<int> *drawOrder) {
_frames[frame] = time;
_drawOrders[frame].clear();
if (drawOrder != NULL) {
_drawOrders[frame].addAll(*drawOrder);
}
}
size_t DrawOrderTimeline::getFrameCount() {
return _frames.size();
}
Array<Array<int>> &DrawOrderTimeline::getDrawOrders() {
return _drawOrders;
}
@@ -0,0 +1,86 @@
/******************************************************************************
* 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/Event.h>
#include <spine/EventData.h>
using namespace spine;
Event::Event(float time, const EventData &data) : _data(data), _time(time), _intValue(0), _floatValue(0), _stringValue(), _volume(0), _balance(0) {
}
const EventData &Event::getData() {
return _data;
}
float Event::getTime() {
return _time;
}
int Event::getInt() {
return _intValue;
}
void Event::setInt(int inValue) {
_intValue = inValue;
}
float Event::getFloat() {
return _floatValue;
}
void Event::setFloat(float inValue) {
_floatValue = inValue;
}
const String &Event::getString() {
return _stringValue;
}
void Event::setString(const String &inValue) {
_stringValue = inValue;
}
float Event::getVolume() {
return _volume;
}
void Event::setVolume(float inValue) {
_volume = inValue;
}
float Event::getBalance() {
return _balance;
}
void Event::setBalance(float inValue) {
_balance = inValue;
}
@@ -0,0 +1,59 @@
/******************************************************************************
* 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/EventData.h>
#include <assert.h>
using namespace spine;
EventData::EventData(const String &name) : _name(name), _audioPath(), _setupPose(-1, *this) {
assert(_name.length() > 0);
}
/// The name of the event, which is unique within the skeleton.
const String &EventData::getName() const {
return _name;
}
Event &EventData::getSetupPose() {
return _setupPose;
}
const Event &EventData::getSetupPose() const {
return _setupPose;
}
const String &EventData::getAudioPath() const {
return _audioPath;
}
void EventData::setAudioPath(const String &inValue) {
_audioPath = inValue;
}
@@ -0,0 +1,106 @@
/******************************************************************************
* 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/EventTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/ArrayUtils.h>
#include <spine/EventData.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <float.h>
using namespace spine;
RTTI_IMPL(EventTimeline, Timeline)
EventTimeline::EventTimeline(size_t frameCount) : Timeline(frameCount, 1) {
PropertyId ids[] = {((PropertyId) Property_Event << 32)};
setPropertyIds(ids, 1);
_events.setSize(frameCount, NULL);
_instant = true;
}
EventTimeline::~EventTimeline() {
ArrayUtils::deleteElements(_events);
}
void EventTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *pEvents, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(skeleton);
if (pEvents == NULL) return;
Array<Event *> &events = *pEvents;
size_t frameCount = _frames.size();
if (lastTime > time) {
// Apply after lastTime for looped animations.
apply(skeleton, lastTime, FLT_MAX, pEvents, 0, MixFrom_Current, false, false, false);
lastTime = -1.0f;
} else if (lastTime >= _frames[frameCount - 1]) {
// Last time is after last frame.
return;
}
if (time < _frames[0]) return;// Time is before first frame.
int i;
if (lastTime < _frames[0]) {
i = 0;
} else {
i = Animation::search(_frames, lastTime) + 1;
float frameTime = _frames[i];
while (i > 0) {
// Fire multiple events with the same frame.
if (_frames[i - 1] != frameTime) break;
i--;
}
}
for (; (size_t) i < frameCount && time >= _frames[i]; i++) events.add(_events[i]);
}
void EventTimeline::setFrame(size_t frame, Event &event) {
_frames[frame] = event.getTime();
_events[frame] = &event;
}
size_t EventTimeline::getFrameCount() {
return _frames.size();
}
Array<Event *> &EventTimeline::getEvents() {
return _events;
}
@@ -0,0 +1,124 @@
/******************************************************************************
* 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/Extension.h>
#include <spine/SpineString.h>
#include <assert.h>
using namespace spine;
SpineExtension *SpineExtension::_instance = NULL;
void SpineExtension::setInstance(SpineExtension *inValue) {
assert(inValue);
_instance = inValue;
}
SpineExtension *SpineExtension::getInstance() {
if (!_instance) _instance = getDefaultExtension();
assert(_instance);
return _instance;
}
SpineExtension::~SpineExtension() {
}
SpineExtension::SpineExtension() {
}
DefaultSpineExtension::~DefaultSpineExtension() {
}
void *DefaultSpineExtension::_alloc(size_t size, const char *file, int line) {
SP_UNUSED(file);
SP_UNUSED(line);
if (size == 0) return 0;
void *ptr = ::malloc(size);
return ptr;
}
void *DefaultSpineExtension::_calloc(size_t size, const char *file, int line) {
SP_UNUSED(file);
SP_UNUSED(line);
if (size == 0) return 0;
void *ptr = ::malloc(size);
if (ptr) {
memset(ptr, 0, size);
}
return ptr;
}
void *DefaultSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) {
SP_UNUSED(file);
SP_UNUSED(line);
void *mem = NULL;
if (size == 0) return 0;
if (ptr == NULL)
mem = ::malloc(size);
else
mem = ::realloc(ptr, size);
return mem;
}
void DefaultSpineExtension::_free(void *mem, const char *file, int line) {
SP_UNUSED(file);
SP_UNUSED(line);
::free(mem);
}
char *DefaultSpineExtension::_readFile(const String &path, int *length) {
#if !defined(__EMSCRIPTEN__) && !defined(SPINE_NO_FILE_IO)
char *data;
FILE *file = fopen(path.buffer(), "rb");
if (!file) return 0;
fseek(file, 0, SEEK_END);
*length = (int) ftell(file);
fseek(file, 0, SEEK_SET);
data = SpineExtension::alloc<char>(*length, __FILE__, __LINE__);
fread(data, 1, *length, file);
fclose(file);
return data;
#else
return nullptr;
#endif
}
DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() {
}
@@ -0,0 +1,327 @@
/******************************************************************************
* 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/IkConstraint.h>
#include <spine/Bone.h>
#include <spine/BonePose.h>
#include <spine/ConstraintData.h>
#include <spine/IkConstraintData.h>
#include <spine/Skeleton.h>
#include <spine/BoneData.h>
using namespace spine;
RTTI_IMPL(IkConstraint, Constraint)
IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : IkConstraintBase(data), _target(skeleton._bones[data._target->getIndex()]) {
_bones.ensureCapacity(data._bones.size());
for (size_t i = 0; i < data._bones.size(); i++) {
BoneData *boneData = data._bones[i];
_bones.add(&skeleton._bones[boneData->getIndex()]->_constrainedPose);
}
}
IkConstraint &IkConstraint::copy(Skeleton &skeleton) {
IkConstraint *copy = new (__FILE__, __LINE__) IkConstraint(_data, skeleton);
copy->_pose.set(_pose);
return *copy;
}
void IkConstraint::update(Skeleton &skeleton, Physics physics) {
IkConstraintPose &p = *_appliedPose;
if (p._mix == 0) return;
BonePose &target = *_target->_appliedPose;
switch (_bones.size()) {
case 1: {
apply(skeleton, *_bones[0], target._worldX, target._worldY, p._compress, p._stretch, _data._scaleYMode, p._mix);
} break;
case 2: {
apply(skeleton, *_bones[0], *_bones[1], target._worldX, target._worldY, p._bendDirection, p._stretch, _data._scaleYMode, p._softness,
p._mix);
} break;
}
}
void IkConstraint::sort(Skeleton &skeleton) {
skeleton.sortBone(_target);
Bone *parent = _bones[0]->_bone;
skeleton.sortBone(parent);
skeleton._updateCache.add(this);
parent->_sorted = false;
skeleton.sortReset(parent->_children);
skeleton.constrained(*parent);
if (_bones.size() > 1) skeleton.constrained(*_bones[1]->_bone);
}
Array<BonePose *> &IkConstraint::getBones() {
return _bones;
}
Bone &IkConstraint::getTarget() {
return *_target;
}
void IkConstraint::setTarget(Bone &target) {
_target = &target;
}
bool IkConstraint::isSourceActive() {
return _target->_active;
}
void IkConstraint::apply(Skeleton &skeleton, BonePose &bone, float targetX, float targetY, bool compress, bool stretch, ScaleYMode scaleYMode,
float mix) {
bone.modifyLocal(skeleton);
BonePose &p = *bone._bone->_parent->_appliedPose;
float pa = p._a, pb = p._b, pc = p._c, pd = p._d;
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY();
float rotationIK = -bone._shearX - bone._rotation, tx, ty;
switch (bone._inherit) {
case Inherit_OnlyTranslation:
tx = (targetX - bone._worldX) * MathUtil::sign(sx);
ty = (targetY - bone._worldY) * MathUtil::sign(sy);
break;
case Inherit_NoRotationOrReflection: {
float s = MathUtil::abs(pa * pd - pb * pc) / MathUtil::max(MathUtil::Epsilon, pa * pa + pc * pc);
float sa = pa / sx;
float sc = pc / sy;
pb = -sc * s * sx;
pd = sa * s * sy;
rotationIK += MathUtil::atan2Deg(sc, sa);
// Fall through.
}
default:
float x = targetX - p._worldX, y = targetY - p._worldY;
float d = pa * pd - pb * pc;
if (MathUtil::abs(d) <= MathUtil::Epsilon) {
tx = 0;
ty = 0;
} else {
tx = (x * pd - y * pb) / d - bone._x;
ty = (y * pa - x * pc) / d - bone._y;
}
}
rotationIK += MathUtil::atan2Deg(ty, tx);
if (bone._scaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK <= -180)//
rotationIK += 360;
bone._rotation += rotationIK * mix;
if (compress || stretch) {
switch (bone._inherit) {
case Inherit_NoScale:
case Inherit_NoScaleOrReflection:
tx = targetX - bone._worldX;
ty = targetY - bone._worldY;
break;
default:
break;
}
float b = bone._bone->_data.getLength() * bone._scaleX;
if (b > MathUtil::Epsilon) {
float dd = tx * tx + ty * ty;
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
float s = (MathUtil::sqrt(dd) / b - 1) * mix + 1;
bone._scaleX *= s;
switch (scaleYMode) {
case ScaleYMode_Uniform:
bone._scaleY *= s;
break;
case ScaleYMode_Volume:
bone._scaleY /= s < 0.7f ? 0.25f + 0.642857f * s : s;
break;
default:
break;
}
}
}
}
}
void IkConstraint::apply(Skeleton &skeleton, BonePose &parent, BonePose &child, float targetX, float targetY, int bendDir, bool stretch,
ScaleYMode scaleYMode, float softness, float mix) {
if (parent._inherit != Inherit_Normal || child._inherit != Inherit_Normal) return;
parent.modifyLocal(skeleton);
child.modifyLocal(skeleton);
float px = parent._x, py = parent._y, psx = parent._scaleX, psy = parent._scaleY, csx = child._scaleX;
int os1, os2, s2;
if (psx < 0) {
psx = -psx;
os1 = 180;
s2 = -1;
} else {
os1 = 0;
s2 = 1;
}
if (psy < 0) {
psy = -psy;
s2 = -s2;
}
if (csx < 0) {
csx = -csx;
os2 = 180;
} else
os2 = 0;
float cwx, cwy, a = parent._a, b = parent._b, c = parent._c, d = parent._d;
bool u = MathUtil::abs(psx - psy) <= MathUtil::Epsilon;
if (!u || stretch) {
child._y = 0;
cwx = a * child._x + parent._worldX;
cwy = c * child._x + parent._worldY;
} else {
cwx = a * child._x + b * child._y + parent._worldX;
cwy = c * child._x + d * child._y + parent._worldY;
}
BonePose &pp = *parent._bone->_parent->_appliedPose;
a = pp._a;
b = pp._b;
c = pp._c;
d = pp._d;
float id = a * d - b * c, x = cwx - pp._worldX, y = cwy - pp._worldY;
id = MathUtil::abs(id) <= MathUtil::Epsilon ? 0 : 1 / id;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = MathUtil::sqrt(dx * dx + dy * dy), l2 = child._bone->_data.getLength() * csx, a1, a2;
if (l1 < MathUtil::Epsilon) {
apply(skeleton, parent, targetX, targetY, false, stretch, ScaleYMode_None, mix);
child._rotation = 0;
return;
}
x = targetX - pp._worldX;
y = targetY - pp._worldY;
float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
float dd = tx * tx + ty * ty;
if (softness != 0) {
softness *= psx * (csx + 1) * 0.5f;
float td = MathUtil::sqrt(dd), sd = td - l1 - l2 * psx + softness;
if (sd > 0) {
float p = MathUtil::min(1.0f, sd / (softness * 2)) - 1;
p = (sd - softness * (1 - p * p)) / td;
tx -= p * tx;
ty -= p * ty;
dd = tx * tx + ty * ty;
}
}
if (u) {
l2 *= psx;
float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1) {
cos = -1;
a2 = MathUtil::Pi * bendDir;
} else if (cos > 1) {
cos = 1;
a2 = 0;
if (stretch) {
a = (MathUtil::sqrt(dd) / (l1 + l2) - 1) * mix + 1;
parent._scaleX *= a;
switch (scaleYMode) {
case ScaleYMode_Uniform:
parent._scaleY *= a;
break;
case ScaleYMode_Volume:
parent._scaleY /= a < 0.7f ? 0.25f + 0.642857f * a : a;
break;
default:
break;
}
}
} else
a2 = MathUtil::acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * MathUtil::sin(a2);
a1 = MathUtil::atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
float aa = a * a, bb = b * b, ta = MathUtil::atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) {
float q = MathUtil::sqrt(d);
if (c1 < 0) q = -q;
q = -(c1 + q) * 0.5f;
float r0 = q / c2, r1 = c / q;
float r = MathUtil::abs(r0) < MathUtil::abs(r1) ? r0 : r1;
r0 = dd - r * r;
if (r0 >= 0) {
y = MathUtil::sqrt(r0) * bendDir;
a1 = ta - MathUtil::atan2(y, r);
a2 = MathUtil::atan2(y / psy, (r - l1) / psx);
goto outer_break;
}
}
float minAngle = MathUtil::Pi, minX = l1 - a, minDist = minX * minX, minY = 0;
float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
c = -a * l1 / (aa - bb);
if (c >= -1 && c <= 1) {
c = MathUtil::acos(c);
x = a * MathUtil::cos(c) + l1;
y = b * MathUtil::sin(c);
d = x * x + y * y;
if (d < minDist) {
minAngle = c;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = c;
maxDist = d;
maxX = x;
maxY = y;
}
}
if (dd <= (minDist + maxDist) * 0.5f) {
a1 = ta - MathUtil::atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - MathUtil::atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
outer_break:
float os = MathUtil::atan2(child._y, child._x) * s2;
a1 = (a1 - os) * MathUtil::Rad_Deg + os1 - parent._rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 <= -180)//
a1 += 360;
parent._rotation += a1 * mix;
a2 = ((a2 + os) * MathUtil::Rad_Deg - child._shearX) * s2 + os2 - child._rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 <= -180)//
a2 += 360;
child._rotation += a2 * mix;
}
@@ -0,0 +1,65 @@
/******************************************************************************
* 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/IkConstraintData.h>
#include <spine/IkConstraint.h>
#include <spine/BoneData.h>
#include <spine/Skeleton.h>
using namespace spine;
RTTI_IMPL(IkConstraintData, ConstraintData)
IkConstraintData::IkConstraintData(const String &name)
: ConstraintDataGeneric<IkConstraint, IkConstraintPose>(name), _target(NULL), _scaleYMode(ScaleYMode_None) {
}
Array<BoneData *> &IkConstraintData::getBones() {
return _bones;
}
BoneData &IkConstraintData::getTarget() {
return *_target;
}
void IkConstraintData::setTarget(BoneData &inValue) {
_target = &inValue;
}
ScaleYMode IkConstraintData::getScaleYMode() {
return _scaleYMode;
}
void IkConstraintData::setScaleYMode(ScaleYMode scaleYMode) {
_scaleYMode = scaleYMode;
}
Constraint &IkConstraintData::create(Skeleton &skeleton) {
return *(new (__FILE__, __LINE__) IkConstraint(*this, skeleton));
}
@@ -0,0 +1,86 @@
/******************************************************************************
* 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/IkConstraintPose.h>
using namespace spine;
IkConstraintPose::IkConstraintPose() : _bendDirection(0), _compress(false), _stretch(false), _mix(0), _softness(0) {
}
IkConstraintPose::~IkConstraintPose() {
}
void IkConstraintPose::set(IkConstraintPose &pose) {
_mix = pose._mix;
_softness = pose._softness;
_bendDirection = pose._bendDirection;
_compress = pose._compress;
_stretch = pose._stretch;
}
float IkConstraintPose::getMix() {
return _mix;
}
void IkConstraintPose::setMix(float mix) {
_mix = mix;
}
float IkConstraintPose::getSoftness() {
return _softness;
}
void IkConstraintPose::setSoftness(float softness) {
_softness = softness;
}
int IkConstraintPose::getBendDirection() {
return _bendDirection;
}
void IkConstraintPose::setBendDirection(int bendDirection) {
_bendDirection = bendDirection;
}
bool IkConstraintPose::getCompress() {
return _compress;
}
void IkConstraintPose::setCompress(bool compress) {
_compress = compress;
}
bool IkConstraintPose::getStretch() {
return _stretch;
}
void IkConstraintPose::setStretch(bool stretch) {
_stretch = stretch;
}
@@ -0,0 +1,137 @@
/******************************************************************************
* 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/IkConstraintTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/IkConstraint.h>
#include <spine/IkConstraintData.h>
#include <spine/IkConstraintPose.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL_MULTI(IkConstraintTimeline, CurveTimeline, ConstraintTimeline)
IkConstraintTimeline::IkConstraintTimeline(size_t frameCount, size_t bezierCount, int constraintIndex)
: CurveTimeline(frameCount, IkConstraintTimeline::ENTRIES, bezierCount), ConstraintTimeline(), _constraintIndex(constraintIndex) {
PropertyId ids[] = {((PropertyId) Property_IkConstraint << 32) | constraintIndex};
setPropertyIds(ids, 1);
}
IkConstraintTimeline::~IkConstraintTimeline() {
}
void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(add);
IkConstraint *constraint = (IkConstraint *) skeleton._constraints[_constraintIndex];
if (!constraint->isActive()) return;
IkConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
if (time < _frames[0]) {
IkConstraintPose &setup = constraint->_data._setupPose;
switch (from) {
case MixFrom_Setup:
pose._mix = setup._mix;
pose._softness = setup._softness;
pose._bendDirection = setup._bendDirection;
pose._compress = setup._compress;
pose._stretch = setup._stretch;
break;
case MixFrom_First:
pose._mix += (setup._mix - pose._mix) * alpha;
pose._softness += (setup._softness - pose._softness) * alpha;
pose._bendDirection = setup._bendDirection;
pose._compress = setup._compress;
pose._stretch = setup._stretch;
break;
case MixFrom_Current:
break;
}
return;
}
float mix = 0, softness = 0;
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i / ENTRIES];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
mix = _frames[i + MIX];
softness = _frames[i + SOFTNESS];
float t = (time - before) / (_frames[i + ENTRIES] - before);
mix += (_frames[i + ENTRIES + MIX] - mix) * t;
softness += (_frames[i + ENTRIES + SOFTNESS] - softness) * t;
break;
}
case STEPPED: {
mix = _frames[i + MIX];
softness = _frames[i + SOFTNESS];
break;
}
default: {
mix = getBezierValue(time, i, MIX, curveType - BEZIER);
softness = getBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER);
}
}
IkConstraintPose &base = from == MixFrom_Setup ? constraint->_data._setupPose : pose;
pose._mix = base._mix + (mix - base._mix) * alpha;
pose._softness = base._softness + (softness - base._softness) * alpha;
if (out) {
if (from == MixFrom_Setup) {
pose._bendDirection = base._bendDirection;
pose._compress = base._compress;
pose._stretch = base._stretch;
}
} else {
pose._bendDirection = (int) _frames[i + BEND_DIRECTION];
pose._compress = _frames[i + COMPRESS] != 0;
pose._stretch = _frames[i + STRETCH] != 0;
}
}
void IkConstraintTimeline::setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, bool stretch) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + MIX] = mix;
_frames[frame + SOFTNESS] = softness;
_frames[frame + BEND_DIRECTION] = (float) bendDirection;
_frames[frame + COMPRESS] = compress ? 1 : 0;
_frames[frame + STRETCH] = stretch ? 1 : 0;
}
@@ -0,0 +1,81 @@
/******************************************************************************
* 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/InheritTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/BonePose.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL_MULTI(InheritTimeline, Timeline, BoneTimeline)
InheritTimeline::InheritTimeline(size_t frameCount, int boneIndex) : Timeline(frameCount, ENTRIES), BoneTimeline(boneIndex), _boneIndex(boneIndex) {
PropertyId ids[] = {((PropertyId) Property_Inherit << 32) | boneIndex};
setPropertyIds(ids, 1);
_instant = true;
}
InheritTimeline::~InheritTimeline() {
}
void InheritTimeline::setFrame(int frame, float time, Inherit inherit) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + INHERIT] = (float) inherit;
}
void InheritTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(alpha);
SP_UNUSED(add);
Bone *bone = skeleton._bones[_boneIndex];
if (!bone->isActive()) return;
BonePose &pose = appliedPose ? *bone->_appliedPose : bone->_pose;
if (out) {
if (from != MixFrom_Current) pose._inherit = bone->_data._setupPose._inherit;
} else {
if (time < _frames[0]) {
if (from != MixFrom_Current) pose._inherit = bone->_data._setupPose._inherit;
} else {
int idx = Animation::search(_frames, time, ENTRIES) + INHERIT;
pose._inherit = static_cast<Inherit>((int) _frames[idx]);
}
}
}
@@ -0,0 +1,112 @@
/******************************************************************************
* 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/Interpolation.h>
#include <spine/MathUtil.h>
using namespace spine;
namespace {
class LinearInterpolation : public Interpolation {
public:
float apply(float a) override {
return a;
}
};
class SmoothInterpolation : public Interpolation {
public:
float apply(float a) override {
return a * a * (3 - 2 * a);
}
};
class SlowFastInterpolation : public Interpolation {
public:
float apply(float a) override {
return a * a;
}
};
class FastSlowInterpolation : public Interpolation {
public:
float apply(float a) override {
return (a - 1) * (a - 1) * -1 + 1;
}
};
class CircleInterpolation : public Interpolation {
public:
float apply(float a) override {
if (a <= 0.5f) {
a *= 2;
return (1 - MathUtil::sqrt(1 - a * a)) / 2;
}
a--;
a *= 2;
return (MathUtil::sqrt(1 - a * a) + 1) / 2;
}
};
}
Interpolation::~Interpolation() {
}
float Interpolation::apply(float a) {
return a;
}
float Interpolation::apply(float start, float end, float a) {
return start + (end - start) * apply(a);
}
Interpolation &Interpolation::linear() {
static LinearInterpolation interpolation;
return interpolation;
}
Interpolation &Interpolation::smooth() {
static SmoothInterpolation interpolation;
return interpolation;
}
Interpolation &Interpolation::slowFast() {
static SlowFastInterpolation interpolation;
return interpolation;
}
Interpolation &Interpolation::fastSlow() {
static FastSlowInterpolation interpolation;
return interpolation;
}
Interpolation &Interpolation::circle() {
static CircleInterpolation interpolation;
return interpolation;
}
@@ -0,0 +1,555 @@
/*
Copyright (c) 2009, Dave Gamble
Copyright (c) 2013, Esoteric Software
Permission is hereby granted, dispose of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/* Json */
/* JSON parser in CPP, from json.c in the spine-c runtime */
#ifndef _DEFAULT_SOURCE
/* Bring strings.h definitions into string.h, where appropriate */
#define _DEFAULT_SOURCE
#endif
#ifndef _BSD_SOURCE
/* Bring strings.h definitions into string.h, where appropriate */
#define _BSD_SOURCE
#endif
#include <spine/Json.h>
#include <spine/Extension.h>
#include <spine/SpineString.h>
#include <spine/Array.h>
#include <assert.h>
#include <math.h>
using namespace spine;
const int Json::JSON_FALSE = 0;
const int Json::JSON_TRUE = 1;
const int Json::JSON_NULL = 2;
const int Json::JSON_NUMBER = 3;
const int Json::JSON_STRING = 4;
const int Json::JSON_ARRAY = 5;
const int Json::JSON_OBJECT = 6;
const char *Json::_error = NULL;
Json *Json::getItem(Json *object, const char *string) {
Json *c = object->_child;
while (c && json_strcasecmp(c->_name, string)) {
c = c->_next;
}
return c;
}
Json *Json::getItem(Json *object, int childIndex) {
Json *current = object->_child;
while (current != NULL && childIndex > 0) {
childIndex--;
current = current->_next;
}
return current;
}
const char *Json::getString(Json *object, const char *name, const char *defaultValue) {
object = getItem(object, name);
if (object) {
return object->_valueString;
}
return defaultValue;
}
float Json::getFloat(Json *value, const char *name, float defaultValue) {
value = getItem(value, name);
return value ? value->_valueFloat : defaultValue;
}
int Json::getInt(Json *value, const char *name, int defaultValue) {
value = getItem(value, name);
return value ? value->_valueInt : defaultValue;
}
bool Json::getBoolean(Json *value, const char *name, bool defaultValue) {
value = getItem(value, name);
if (value) {
if (value->_valueString) return strcmp(value->_valueString, "true") == 0;
if (value->_type == JSON_NULL) return false;
if (value->_type == JSON_NUMBER) return value->_valueFloat != 0;
if (value->_type == JSON_FALSE) return false;
if (value->_type == JSON_TRUE) return true;
return defaultValue;
} else {
return defaultValue;
}
}
const char *Json::getError() {
return _error;
}
Json::Json(const char *value)
: _next(NULL),
#if SPINE_JSON_HAVE_PREV
_prev(NULL),
#endif
_child(NULL), _type(0), _size(0), _valueString(NULL), _valueInt(0), _valueFloat(0), _name(NULL) {
if (value) {
value = parseValue(this, skip(value));
assert(value);
}
}
Json::~Json() {
Json *curr = NULL;
Json *next = _child;
do {
curr = next;
if (curr) {
next = curr->_next;
}
delete curr;
} while (next);
if (_valueString) {
SpineExtension::free(_valueString, __FILE__, __LINE__);
}
if (_name) {
SpineExtension::free(_name, __FILE__, __LINE__);
}
}
const char *Json::skip(const char *inValue) {
if (!inValue) {
/* must propagate NULL since it's often called in skip(f(...)) form */
return NULL;
}
while (*inValue && (unsigned char) *inValue <= 32) {
inValue++;
}
return inValue;
}
const char *Json::parseValue(Json *item, const char *value) {
/* Referenced by constructor, parseArray(), and parseObject(). */
/* Always called with the result of skip(). */
#ifdef SPINE_JSON_DEBUG /* Checked at entry to graph, constructor, and after every parse call. */
if (!value) {
/* Fail on null. */
return NULL;
}
#endif
switch (*value) {
case 'n': {
if (!strncmp(value + 1, "ull", 3)) {
item->_type = JSON_NULL;
return value + 4;
}
break;
}
case 'f': {
if (!strncmp(value + 1, "alse", 4)) {
item->_type = JSON_FALSE;
/* calloc prevents us needing item->_type = JSON_FALSE or valueInt = 0 here */
return value + 5;
}
break;
}
case 't': {
if (!strncmp(value + 1, "rue", 3)) {
item->_type = JSON_TRUE;
item->_valueInt = 1;
return value + 4;
}
break;
}
case '\"':
return parseString(item, value);
case '[':
return parseArray(item, value);
case '{':
return parseObject(item, value);
case '-': /* fallthrough */
case '0': /* fallthrough */
case '1': /* fallthrough */
case '2': /* fallthrough */
case '3': /* fallthrough */
case '4': /* fallthrough */
case '5': /* fallthrough */
case '6': /* fallthrough */
case '7': /* fallthrough */
case '8': /* fallthrough */
case '9':
return parseNumber(item, value);
default:
break;
}
_error = value;
return NULL; /* failure. */
}
static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
const char *Json::parseString(Json *item, const char *str) {
const char *ptr = str + 1;
char *ptr2;
char *out;
int len = 0;
unsigned uc, uc2;
if (*str != '\"') {
/* TODO: don't need this check when called from parseValue, but do need from parseObject */
_error = str;
return 0;
} /* not a string! */
while (*ptr != '\"' && *ptr && ++len) {
if (*ptr++ == '\\') {
ptr++; /* Skip escaped quotes. */
}
}
out = SpineExtension::alloc<char>(len + 1, __FILE__, __LINE__); /* The length needed for the string, roughly. */
if (!out) {
return 0;
}
ptr = str + 1;
ptr2 = out;
while (*ptr != '\"' && *ptr) {
if (*ptr != '\\') {
*ptr2++ = *ptr++;
} else {
ptr++;
switch (*ptr) {
case 'b':
*ptr2++ = '\b';
break;
case 'f':
*ptr2++ = '\f';
break;
case 'n':
*ptr2++ = '\n';
break;
case 'r':
*ptr2++ = '\r';
break;
case 't':
*ptr2++ = '\t';
break;
case 'u': {
/* transcode utf16 to utf8. */
sscanf(ptr + 1, "%4x", &uc);
ptr += 4; /* get the unicode char. */
if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) {
break; /* check for invalid. */
}
/* TODO provide an option to ignore surrogates, use unicode replacement character? */
if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ {
if (ptr[1] != '\\' || ptr[2] != 'u') {
break; /* missing second-half of surrogate. */
}
sscanf(ptr + 3, "%4x", &uc2);
ptr += 6;
if (uc2 < 0xDC00 || uc2 > 0xDFFF) {
break; /* invalid second-half of surrogate. */
}
uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF));
}
len = 4;
if (uc < 0x80) {
len = 1;
} else if (uc < 0x800) {
len = 2;
} else if (uc < 0x10000) {
len = 3;
}
ptr2 += len;
switch (len) {
case 4:
*--ptr2 = ((uc | 0x80) & 0xBF);
uc >>= 6;
/* fallthrough */
case 3:
*--ptr2 = ((uc | 0x80) & 0xBF);
uc >>= 6;
/* fallthrough */
case 2:
*--ptr2 = ((uc | 0x80) & 0xBF);
uc >>= 6;
/* fallthrough */
case 1:
*--ptr2 = (uc | firstByteMark[len]);
}
ptr2 += len;
break;
}
default:
*ptr2++ = *ptr;
break;
}
ptr++;
}
}
*ptr2 = 0;
if (*ptr == '\"') {
ptr++; /* TODO error handling if not \" or \0 ? */
}
item->_valueString = out;
item->_type = JSON_STRING;
return ptr;
}
const char *Json::parseNumber(Json *item, const char *num) {
double result = 0.0;
int negative = 0;
char *ptr = (char *) num;
if (*ptr == '-') {
negative = -1;
++ptr;
}
while (*ptr >= '0' && *ptr <= '9') {
result = result * 10.0 + (*ptr - '0');
++ptr;
}
if (*ptr == '.') {
double fraction = 0.0;
int n = 0;
++ptr;
while (*ptr >= '0' && *ptr <= '9') {
fraction = (fraction * 10.0) + (*ptr - '0');
++ptr;
++n;
}
result += fraction / pow(10.0, n);
}
if (negative) {
result = -result;
}
if (*ptr == 'e' || *ptr == 'E') {
double exponent = 0;
int expNegative = 0;
++ptr;
if (*ptr == '-') {
expNegative = -1;
++ptr;
} else if (*ptr == '+') {
++ptr;
}
while (*ptr >= '0' && *ptr <= '9') {
exponent = (exponent * 10.0) + (*ptr - '0');
++ptr;
}
if (expNegative) {
result = result / pow(10, exponent);
} else {
result = result * pow(10, exponent);
}
}
if (ptr != num) {
/* Parse success, number found. */
item->_valueFloat = (float) result;
item->_valueInt = (int) result;
item->_type = JSON_NUMBER;
return ptr;
} else {
/* Parse failure, _error is set. */
_error = num;
return NULL;
}
}
const char *Json::parseArray(Json *item, const char *value) {
Json *child;
#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */
if (*value != '[') {
ep = value;
return 0;
} /* not an array! */
#endif
item->_type = JSON_ARRAY;
value = skip(value + 1);
if (*value == ']') {
return value + 1; /* empty array. */
}
item->_child = child = new (__FILE__, __LINE__) Json(NULL);
if (!item->_child) {
return NULL; /* memory fail */
}
value = skip(parseValue(child, skip(value))); /* skip any spacing, get the value. */
if (!value) {
return NULL;
}
item->_size = 1;
while (*value == ',') {
Json *new_item = new (__FILE__, __LINE__) Json(NULL);
if (!new_item) {
return NULL; /* memory fail */
}
child->_next = new_item;
#if SPINE_JSON_HAVE_PREV
new_item->prev = child;
#endif
child = new_item;
value = skip(parseValue(child, skip(value + 1)));
if (!value) {
return NULL; /* parse fail */
}
item->_size++;
}
if (*value == ']') {
return value + 1; /* end of array */
}
_error = value;
return NULL; /* malformed. */
}
/* Build an object from the text. */
const char *Json::parseObject(Json *item, const char *value) {
Json *child;
#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */
if (*value != '{') {
ep = value;
return 0;
} /* not an object! */
#endif
item->_type = JSON_OBJECT;
value = skip(value + 1);
if (*value == '}') {
return value + 1; /* empty array. */
}
item->_child = child = new (__FILE__, __LINE__) Json(NULL);
if (!item->_child) {
return NULL;
}
value = skip(parseString(child, skip(value)));
if (!value) {
return NULL;
}
child->_name = child->_valueString;
child->_valueString = 0;
if (*value != ':') {
_error = value;
return NULL;
} /* fail! */
value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */
if (!value) {
return NULL;
}
item->_size = 1;
while (*value == ',') {
Json *new_item = new (__FILE__, __LINE__) Json(NULL);
if (!new_item) {
return NULL; /* memory fail */
}
child->_next = new_item;
#if SPINE_JSON_HAVE_PREV
new_item->prev = child;
#endif
child = new_item;
value = skip(parseString(child, skip(value + 1)));
if (!value) {
return NULL;
}
child->_name = child->_valueString;
child->_valueString = 0;
if (*value != ':') {
_error = value;
return NULL;
} /* fail! */
value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */
if (!value) {
return NULL;
}
item->_size++;
}
if (*value == '}') {
return value + 1; /* end of array */
}
_error = value;
return NULL; /* malformed. */
}
int Json::json_strcasecmp(const char *s1, const char *s2) {
/* TODO we may be able to elide these NULL checks if we can prove
* the graph and input (only callsite is Json_getItem) should not have NULLs
*/
if (s1 && s2) {
#if defined(_WIN32)
return _stricmp(s1, s2);
#else
return strcasecmp(s1, s2);
#endif
} else {
if (s1 < s2) {
return -1; /* s1 is null, s2 is not */
} else if (s1 == s2) {
return 0; /* both are null */
} else {
return 1; /* s2 is nul s1 is not */
}
}
}
@@ -0,0 +1,44 @@
/******************************************************************************
* 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/LinkedMesh.h>
#include <spine/MeshAttachment.h>
using namespace spine;
LinkedMesh::LinkedMesh(MeshAttachment &mesh, const int skinIndex, size_t slotIndex, size_t sourceIndex, const String &source, bool inheritTimelines)
: _mesh(&mesh), _skinIndex(skinIndex), _skin(""), _slotIndex(slotIndex), _sourceIndex(sourceIndex), _source(source),
_inheritTimelines(inheritTimelines) {
}
LinkedMesh::LinkedMesh(MeshAttachment &mesh, const String &skin, size_t slotIndex, size_t sourceIndex, const String &source, bool inheritTimelines)
: _mesh(&mesh), _skinIndex(-1), _skin(skin), _slotIndex(slotIndex), _sourceIndex(sourceIndex), _source(source),
_inheritTimelines(inheritTimelines) {
}
@@ -0,0 +1,133 @@
/******************************************************************************
* 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/MathUtil.h>
#include <math.h>
#include <stdlib.h>
#include <cmath>
// Required for division by 0 in _isNaN on MSVC
#ifdef _MSC_VER
#pragma warning(disable : 4723)
#endif
using namespace spine;
const float MathUtil::Epsilon = 0.00001f;
const float MathUtil::EpsilonSq = MathUtil::Epsilon * MathUtil::Epsilon;
const float MathUtil::Pi = 3.1415926535897932385f;
const float MathUtil::Pi_2 = 3.1415926535897932385f * 2;
const float MathUtil::InvPi_2 = 1 / MathUtil::Pi_2;
const float MathUtil::Deg_Rad = (3.1415926535897932385f / 180.0f);
const float MathUtil::Rad_Deg = (180.0f / 3.1415926535897932385f);
float MathUtil::abs(float v) {
return ((v) < 0 ? -(v) : (v));
}
float MathUtil::sign(float v) {
return ((v) < 0 ? -1.0f : (v) > 0 ? 1.0f : 0.0f);
}
float MathUtil::clamp(float x, float min, float max) {
return ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)));
}
float MathUtil::fmod(float a, float b) {
return (float) ::fmod(a, b);
}
/// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
/// degrees), largest error of 0.00488 radians (0.2796 degrees).
float MathUtil::atan2(float y, float x) {
return (float) ::atan2(y, x);
}
float MathUtil::atan2Deg(float y, float x) {
return MathUtil::atan2(y, x) * MathUtil::Rad_Deg;
}
/// Returns the cosine in radians from a lookup table.
float MathUtil::cos(float radians) {
return (float) ::cos(radians);
}
/// Returns the sine in radians from a lookup table.
float MathUtil::sin(float radians) {
return (float) ::sin(radians);
}
float MathUtil::sqrt(float v) {
return (float) ::sqrt(v);
}
float MathUtil::acos(float v) {
return (float) ::acos(v);
}
/// Returns the sine in radians from a lookup table.
float MathUtil::sinDeg(float degrees) {
return (float) ::sin(degrees * MathUtil::Deg_Rad);
}
/// Returns the cosine in radians from a lookup table.
float MathUtil::cosDeg(float degrees) {
return (float) ::cos(degrees * MathUtil::Deg_Rad);
}
bool MathUtil::isNan(float v) {
return std::isnan(v);
}
float MathUtil::quietNan() {
return std::nan("");
}
float MathUtil::random() {
return ::rand() / (float) RAND_MAX;
}
float MathUtil::randomTriangular(float min, float max) {
return randomTriangular(min, max, (min + max) * 0.5f);
}
float MathUtil::randomTriangular(float min, float max, float mode) {
float u = random();
float d = max - min;
if (u <= (mode - min) / d) return min + sqrt(u * d * (mode - min));
return max - sqrt((1 - u) * d * (max - mode));
}
float MathUtil::pow(float a, float b) {
return (float) ::pow(a, b);
}
float MathUtil::ceil(float v) {
return ::ceil(v);
}
@@ -0,0 +1,230 @@
/******************************************************************************
* 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/MeshAttachment.h>
#include <spine/Atlas.h>
#include <assert.h>
using namespace spine;
RTTI_IMPL(MeshAttachment, VertexAttachment)
MeshAttachment::MeshAttachment(const String &name, Sequence *sequence)
: VertexAttachment(name), _sequence(sequence), _regionUVs(), _triangles(), _hullLength(0), _path(), _color(1, 1, 1, 1), _sourceMesh(NULL),
_edges(), _width(0), _height(0) {
assert(sequence);
}
MeshAttachment::~MeshAttachment() {
delete _sequence;
}
void MeshAttachment::computeWorldVertices(Skeleton &skeleton, Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset,
size_t stride) {
VertexAttachment::computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride);
}
Array<float> &MeshAttachment::getRegionUVs() {
return _regionUVs;
}
void MeshAttachment::setRegionUVs(Array<float> &inValue) {
_regionUVs.clearAndAddAll(inValue);
}
Array<unsigned short> &MeshAttachment::getTriangles() {
return _triangles;
}
void MeshAttachment::setTriangles(Array<unsigned short> &inValue) {
_triangles.clearAndAddAll(inValue);
}
int MeshAttachment::getHullLength() {
return _hullLength;
}
void MeshAttachment::setHullLength(int inValue) {
_hullLength = inValue;
}
Sequence &MeshAttachment::getSequence() {
return *_sequence;
}
void MeshAttachment::updateSequence() {
_sequence->update(*this);
}
const String &MeshAttachment::getPath() {
return _path;
}
void MeshAttachment::setPath(const String &inValue) {
_path = inValue;
}
Color &MeshAttachment::getColor() {
return _color;
}
MeshAttachment *MeshAttachment::getSourceMesh() {
return _sourceMesh;
}
void MeshAttachment::setSourceMesh(MeshAttachment *inValue) {
_sourceMesh = inValue;
if (inValue != NULL) {
_bones.clearAndAddAll(inValue->_bones);
_vertices.clearAndAddAll(inValue->_vertices);
_regionUVs.clearAndAddAll(inValue->_regionUVs);
_triangles.clearAndAddAll(inValue->_triangles);
_hullLength = inValue->_hullLength;
_worldVerticesLength = inValue->_worldVerticesLength;
_edges.clearAndAddAll(inValue->_edges);
_width = inValue->_width;
_height = inValue->_height;
}
}
Array<unsigned short> &MeshAttachment::getEdges() {
return _edges;
}
void MeshAttachment::setEdges(Array<unsigned short> &inValue) {
_edges.clearAndAddAll(inValue);
}
float MeshAttachment::getWidth() {
return _width;
}
void MeshAttachment::setWidth(float inValue) {
_width = inValue;
}
float MeshAttachment::getHeight() {
return _height;
}
void MeshAttachment::setHeight(float inValue) {
_height = inValue;
}
Attachment &MeshAttachment::copy() {
if (_sourceMesh) return newLinkedMesh();
MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName(), new (__FILE__, __LINE__) Sequence(*_sequence));
copy->_path = _path;
copy->_color.set(_color);
copyTo(*copy);
copy->_regionUVs.clearAndAddAll(_regionUVs);
copy->_triangles.clearAndAddAll(_triangles);
copy->_hullLength = _hullLength;
copy->_edges.clearAndAddAll(_edges);
copy->_width = _width;
copy->_height = _height;
return *copy;
}
MeshAttachment &MeshAttachment::newLinkedMesh() {
MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName(), new (__FILE__, __LINE__) Sequence(*_sequence));
copy->setTimelineAttachment(getTimelineAttachment());
copy->_path = _path;
copy->_color.set(_color);
copy->setSourceMesh(_sourceMesh != NULL ? _sourceMesh : this);
copy->updateSequence();
return *copy;
}
void MeshAttachment::computeUVs(TextureRegion *region, Array<float> &regionUVs, Array<float> &uvs) {
int n = (int) uvs.size();
float u, v, width, height;
if (region != NULL && region->getRTTI().instanceOf(AtlasRegion::rtti)) {
AtlasRegion *r = static_cast<AtlasRegion *>(region);
u = r->_u;
v = r->_v;
float textureWidth = r->getPage()->width;
float textureHeight = r->getPage()->height;
switch (r->_degrees) {
case 90: {
u -= (r->_originalHeight - r->_offsetY - r->_packedWidth) / textureWidth;
v -= (r->_originalWidth - r->_offsetX - r->_packedHeight) / textureHeight;
width = r->_originalHeight / textureWidth;
height = r->_originalWidth / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + (1 - regionUVs[i]) * height;
}
return;
}
case 180: {
u -= (r->_originalWidth - r->_offsetX - r->_packedWidth) / textureWidth;
v -= r->_offsetY / textureHeight;
width = r->_originalWidth / textureWidth;
height = r->_originalHeight / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i]) * width;
uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
}
return;
}
case 270: {
u -= r->_offsetY / textureWidth;
v -= r->_offsetX / textureHeight;
width = r->_originalHeight / textureWidth;
height = r->_originalWidth / textureHeight;
for (int i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i + 1]) * width;
uvs[i + 1] = v + regionUVs[i] * height;
}
return;
}
default: {
u -= r->_offsetX / textureWidth;
v -= (r->_originalHeight - r->_offsetY - r->_packedHeight) / textureHeight;
width = r->_originalWidth / textureWidth;
height = r->_originalHeight / textureHeight;
}
}
} else if (region == NULL) {
u = v = 0;
width = height = 1;
} else {
u = region->_u;
v = region->_v;
width = region->_u2 - u;
height = region->_v2 - v;
}
for (int i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i] * width;
uvs[i + 1] = v + regionUVs[i + 1] * height;
}
}
@@ -0,0 +1,74 @@
/******************************************************************************
* 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/PathAttachment.h>
using namespace spine;
RTTI_IMPL(PathAttachment, VertexAttachment)
PathAttachment::PathAttachment(const String &name) : VertexAttachment(name), _closed(false), _constantSpeed(false), _color() {
}
Array<float> &PathAttachment::getLengths() {
return _lengths;
}
void PathAttachment::setLengths(Array<float> &inValue) {
_lengths.clearAndAddAll(inValue);
}
bool PathAttachment::getClosed() {
return _closed;
}
void PathAttachment::setClosed(bool inValue) {
_closed = inValue;
}
bool PathAttachment::getConstantSpeed() {
return _constantSpeed;
}
void PathAttachment::setConstantSpeed(bool inValue) {
_constantSpeed = inValue;
}
Color &PathAttachment::getColor() {
return _color;
}
Attachment &PathAttachment::copy() {
PathAttachment *copy = new (__FILE__, __LINE__) PathAttachment(getName());
copyTo(*copy);
copy->_lengths.clearAndAddAll(_lengths);
copy->_closed = _closed;
copy->_constantSpeed = _constantSpeed;
return *copy;
}
@@ -0,0 +1,552 @@
/******************************************************************************
* 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/PathConstraint.h>
#include <spine/Bone.h>
#include <spine/BonePose.h>
#include <spine/PathAttachment.h>
#include <spine/PathConstraintData.h>
#include <spine/PathConstraintPose.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <spine/MathUtil.h>
#include <spine/Skin.h>
#include <spine/BoneData.h>
#include <spine/SlotData.h>
#include <spine/SkeletonData.h>
using namespace spine;
RTTI_IMPL(PathConstraint, Constraint)
const float PathConstraint::epsilon = 0.00001f;
const int PathConstraint::NONE = -1;
const int PathConstraint::BEFORE = -2;
const int PathConstraint::AFTER = -3;
PathConstraint::PathConstraint(PathConstraintData &data, Skeleton &skeleton) : PathConstraintBase(data) {
_bones.ensureCapacity(data.getBones().size());
for (size_t i = 0; i < data.getBones().size(); i++) {
BoneData *boneData = data.getBones()[i];
_bones.add(&skeleton._bones[boneData->getIndex()]->_constrainedPose);
}
_slot = skeleton._slots[data._slot->_index];
_segments.setSize(10, 0);
}
PathConstraint &PathConstraint::copy(Skeleton &skeleton) {
PathConstraint *copy = new (__FILE__, __LINE__) PathConstraint(_data, skeleton);
copy->_pose.set(_pose);
return *copy;
}
void PathConstraint::update(Skeleton &skeleton, Physics physics) {
Attachment *baseAttachment = _slot->_appliedPose->_attachment;
if (baseAttachment == NULL || !baseAttachment->getRTTI().instanceOf(PathAttachment::rtti)) {
return;
}
PathAttachment *pathAttachment = static_cast<PathAttachment *>(baseAttachment);
PathConstraintPose &p = *_appliedPose;
float mixRotate = p._mixRotate, mixX = p._mixX, mixY = p._mixY;
if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
PathConstraintData &data = _data;
bool tangents = data._rotateMode == RotateMode_Tangent, scale = data._rotateMode == RotateMode_ChainScale;
size_t boneCount = _bones.size();
size_t spacesCount = tangents ? boneCount : boneCount + 1;
BonePose **bones = _bones.buffer();
_spaces.setSize(spacesCount, 0);
float *spaces = _spaces.buffer();
float *lengths = NULL;
if (scale) {
_lengths.setSize(boneCount, 0);
lengths = _lengths.buffer();
}
float spacing = p._spacing;
switch (data._spacingMode) {
case SpacingMode_Percent: {
if (scale) {
for (size_t i = 0, n = spacesCount - 1; i < n; i++) {
BonePose *bone = bones[i];
float setupLength = bone->_bone->getData().getLength();
float x = setupLength * bone->_a;
float y = setupLength * bone->_c;
lengths[i] = MathUtil::sqrt(x * x + y * y);
}
}
for (size_t i = 1; i < spacesCount; i++) {
spaces[i] = spacing;
}
break;
}
case SpacingMode_Proportional: {
float sum = 0;
for (size_t i = 0, n = spacesCount - 1; i < n;) {
BonePose *bone = bones[i];
float setupLength = bone->_bone->getData().getLength();
if (setupLength < epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone->_a, y = setupLength * bone->_c;
float length = MathUtil::sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = length;
sum += length;
}
}
if (sum > 0) {
sum = spacesCount / sum * spacing;
for (size_t i = 1; i < spacesCount; i++) {
spaces[i] *= sum;
}
}
break;
}
default: {
bool lengthSpacing = data._spacingMode == SpacingMode_Length;
for (size_t i = 0, n = spacesCount - 1; i < n;) {
BonePose *bone = bones[i];
float setupLength = bone->_bone->getData().getLength();
if (setupLength < epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
float x = setupLength * bone->_a, y = setupLength * bone->_c;
float length = MathUtil::sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = (lengthSpacing ? MathUtil::max(0.0f, setupLength + spacing) : spacing) * length / setupLength;
}
}
}
}
Array<float> &positions = computeWorldPositions(skeleton, *pathAttachment, (int) spacesCount, tangents);
float *positionsBuffer = positions.buffer();
float boneX = positionsBuffer[0], boneY = positionsBuffer[1], offsetRotation = data._offsetRotation;
bool tip;
if (offsetRotation == 0)
tip = data._rotateMode == RotateMode_Chain;
else {
tip = false;
BonePose &bone = _slot->getBone().getAppliedPose();
offsetRotation *= bone._a * bone._d - bone._b * bone._c > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad;
}
for (size_t i = 0, ip = 3; i < boneCount; i++, ip += 3) {
BonePose *bone = bones[i];
bone->modifyWorld(skeleton);
bone->_worldX += (boneX - bone->_worldX) * mixX;
bone->_worldY += (boneY - bone->_worldY) * mixY;
float x = positionsBuffer[ip], y = positionsBuffer[ip + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
float length = lengths[i];
if (length >= epsilon) {
float s = (MathUtil::sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
bone->_a *= s;
bone->_c *= s;
}
}
boneX = x;
boneY = y;
if (mixRotate > 0) {
float a = bone->_a, b = bone->_b, c = bone->_c, d = bone->_d, r, cos, sin;
if (tangents)
r = positionsBuffer[ip - 1];
else if (spaces[i + 1] < epsilon)
r = positionsBuffer[ip + 2];
else
r = MathUtil::atan2(dy, dx);
r -= MathUtil::atan2(c, a);
if (tip) {
cos = MathUtil::cos(r);
sin = MathUtil::sin(r);
float length = bone->_bone->getData().getLength();
boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else
r += offsetRotation;
if (r > MathUtil::Pi)
r -= MathUtil::Pi_2;
else if (r < -MathUtil::Pi)
r += MathUtil::Pi_2;
r *= mixRotate;
cos = MathUtil::cos(r);
sin = MathUtil::sin(r);
bone->_a = cos * a - sin * c;
bone->_b = cos * b - sin * d;
bone->_c = sin * a + cos * c;
bone->_d = sin * b + cos * d;
}
}
}
void PathConstraint::sort(Skeleton &skeleton) {
int slotIndex = _slot->getData().getIndex();
Bone &slotBone = _slot->getBone();
if (skeleton.getSkin() != NULL) sortPathSlot(skeleton, *skeleton.getSkin(), slotIndex, slotBone);
if (skeleton.getData().getDefaultSkin() != NULL && skeleton.getData().getDefaultSkin() != skeleton.getSkin())
sortPathSlot(skeleton, *skeleton.getData().getDefaultSkin(), slotIndex, slotBone);
sortPath(skeleton, _slot->_pose._attachment, slotBone);
BonePose **bones = _bones.buffer();
size_t boneCount = _bones.size();
for (size_t i = 0; i < boneCount; i++) {
Bone *bone = bones[i]->_bone;
skeleton.sortBone(bone);
skeleton.constrained(*bone);
}
skeleton._updateCache.add(this);
for (size_t i = 0; i < boneCount; i++) skeleton.sortReset(bones[i]->_bone->getChildren());
for (size_t i = 0; i < boneCount; i++) bones[i]->_bone->_sorted = true;
}
bool PathConstraint::isSourceActive() {
return _slot->getBone().isActive();
}
Array<BonePose *> &PathConstraint::getBones() {
return _bones;
}
Slot &PathConstraint::getSlot() {
return *_slot;
}
void PathConstraint::setSlot(Slot &slot) {
_slot = &slot;
}
Array<float> &PathConstraint::computeWorldPositions(Skeleton &skeleton, PathAttachment &path, int spacesCount, bool tangents) {
float position = _appliedPose->_position;
float *spaces = _spaces.buffer();
_positions.setSize(spacesCount * 3 + 2, 0);
Array<float> &out = _positions;
Array<float> &world = _world;
bool closed = path.getClosed();
int verticesLength = (int) path.getWorldVerticesLength();
int curveCount = verticesLength / 6;
int prevCurve = NONE;
float pathLength;
if (!path.getConstantSpeed()) {
Array<float> &lengths = path.getLengths();
float *lengthsBuffer = lengths.buffer();
curveCount -= closed ? 1 : 2;
pathLength = lengthsBuffer[curveCount];
if (_data._positionMode == PositionMode_Percent) position *= pathLength;
float multiplier = 0;
switch (_data._spacingMode) {
case SpacingMode_Percent:
multiplier = pathLength;
break;
case SpacingMode_Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
}
world.setSize(8, 0);
float *worldBuffer = world.buffer();
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p = MathUtil::fmod(p, pathLength);
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != BEFORE) {
prevCurve = BEFORE;
path.computeWorldVertices(skeleton, *_slot, 2, 4, world, 0, 2);
}
addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
if (prevCurve != AFTER) {
prevCurve = AFTER;
path.computeWorldVertices(skeleton, *_slot, verticesLength - 6, 4, world, 0, 2);
}
addAfterPosition(p - pathLength, world, 0, out, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
float length = lengthsBuffer[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = lengthsBuffer[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.computeWorldVertices(skeleton, *_slot, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(skeleton, *_slot, 0, 4, world, 4, 2);
} else
path.computeWorldVertices(skeleton, *_slot, curve * 6 + 2, 8, world, 0, 2);
}
addCurvePosition(p, worldBuffer[0], worldBuffer[1], worldBuffer[2], worldBuffer[3], worldBuffer[4], worldBuffer[5], worldBuffer[6],
worldBuffer[7], out, o, tangents || (i > 0 && space < epsilon));
}
return out;
}
// World vertices.
if (closed) {
verticesLength += 2;
world.setSize(verticesLength, 0);
float *worldBuffer = world.buffer();
path.computeWorldVertices(skeleton, *_slot, 2, verticesLength - 4, world, 0, 2);
path.computeWorldVertices(skeleton, *_slot, 0, 2, world, verticesLength - 4, 2);
worldBuffer[verticesLength - 2] = worldBuffer[0];
worldBuffer[verticesLength - 1] = worldBuffer[1];
} else {
curveCount--;
verticesLength -= 4;
world.setSize(verticesLength, 0);
path.computeWorldVertices(skeleton, *_slot, 2, verticesLength, world, 0, 2);
}
float *worldBuffer = world.buffer();
// Curve lengths.
_curves.setSize(curveCount, 0);
float *curvesBuffer = _curves.buffer();
pathLength = 0;
float x1 = worldBuffer[0], y1 = worldBuffer[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = worldBuffer[w];
cy1 = worldBuffer[w + 1];
cx2 = worldBuffer[w + 2];
cy2 = worldBuffer[w + 3];
x2 = worldBuffer[w + 4];
y2 = worldBuffer[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
curvesBuffer[i] = pathLength;
x1 = x2;
y1 = y2;
}
if (_data._positionMode == PositionMode_Percent) position *= pathLength;
float multiplier = 0;
switch (_data._spacingMode) {
case SpacingMode_Percent:
multiplier = pathLength;
break;
case SpacingMode_Proportional:
multiplier = pathLength / spacesCount;
break;
default:
multiplier = 1;
}
float curveLength = 0;
for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i] * multiplier;
position += space;
float p = position;
if (closed) {
p = MathUtil::fmod(p, pathLength);
if (p < 0) p += pathLength;
curve = 0;
segment = 0;
} else if (p < 0) {
addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
float length = curvesBuffer[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = curvesBuffer[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
prevCurve = curve;
int ii = curve * 6;
x1 = worldBuffer[ii];
y1 = worldBuffer[ii + 1];
cx1 = worldBuffer[ii + 2];
cy1 = worldBuffer[ii + 3];
cx2 = worldBuffer[ii + 4];
cy2 = worldBuffer[ii + 5];
x2 = worldBuffer[ii + 6];
y2 = worldBuffer[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
curveLength = MathUtil::sqrt(dfx * dfx + dfy * dfy);
_segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
_segments[ii] = curveLength;
}
dfx += ddfx;
dfy += ddfy;
curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
_segments[8] = curveLength;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy);
_segments[9] = curveLength;
segment = 0;
}
// Weight by segment length.
p *= curveLength;
for (;; segment++) {
float length = _segments[segment];
if (p > length) continue;
if (segment == 0)
p /= length;
else {
float prev = _segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
break;
}
addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space < epsilon));
}
return out;
}
void PathConstraint::addBeforePosition(float p, Array<float> &temp, int i, Array<float> &output, int o) {
float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtil::atan2(dy, dx);
output[o] = x1 + p * MathUtil::cos(r);
output[o + 1] = y1 + p * MathUtil::sin(r);
output[o + 2] = r;
}
void PathConstraint::addAfterPosition(float p, Array<float> &temp, int i, Array<float> &output, int o) {
float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtil::atan2(dy, dx);
output[o] = x1 + p * MathUtil::cos(r);
output[o + 1] = y1 + p * MathUtil::sin(r);
output[o + 2] = r;
}
void PathConstraint::addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
Array<float> &output, int o, bool tangents) {
if (p < epsilon || MathUtil::isNan(p)) {
output[o] = x1;
output[o + 1] = y1;
output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1);
return;
}
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
output[o] = x;
output[o + 1] = y;
if (tangents) {
if (p < 0.001f)
output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1);
else
output[o + 2] = MathUtil::atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
void PathConstraint::sortPathSlot(Skeleton &skeleton, Skin &skin, int slotIndex, Bone &slotBone) {
Skin::AttachmentMap::Entries entries = skin.getAttachments();
while (entries.hasNext()) {
Skin::AttachmentMap::Entry &entry = entries.next();
if (entry._slotIndex == (size_t) slotIndex) sortPath(skeleton, entry._attachment, slotBone);
}
}
void PathConstraint::sortPath(Skeleton &skeleton, Attachment *attachment, Bone &slotBone) {
if (attachment == NULL || !attachment->getRTTI().instanceOf(PathAttachment::rtti)) return;
PathAttachment *pathAttachment = static_cast<PathAttachment *>(attachment);
Array<int> &pathBones = pathAttachment->getBones();
if (pathBones.size() == 0)
skeleton.sortBone(&slotBone);
else {
Array<Bone *> &bones = skeleton._bones;
for (size_t i = 0, n = pathBones.size(); i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < (size_t) nn) skeleton.sortBone(bones[pathBones[i++]]);
}
}
}
@@ -0,0 +1,92 @@
/******************************************************************************
* 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/PathConstraintData.h>
#include <spine/PathConstraint.h>
#include <spine/BoneData.h>
#include <spine/SlotData.h>
#include <spine/Skeleton.h>
using namespace spine;
RTTI_IMPL(PathConstraintData, ConstraintData)
PathConstraintData::PathConstraintData(const String &name)
: ConstraintDataGeneric<PathConstraint, PathConstraintPose>(name), _slot(NULL), _positionMode(PositionMode_Fixed),
_spacingMode(SpacingMode_Length), _rotateMode(RotateMode_Tangent), _offsetRotation(0) {
}
Array<BoneData *> &PathConstraintData::getBones() {
return _bones;
}
SlotData &PathConstraintData::getSlot() {
return *_slot;
}
void PathConstraintData::setSlot(SlotData &slot) {
_slot = &slot;
}
PositionMode PathConstraintData::getPositionMode() {
return _positionMode;
}
void PathConstraintData::setPositionMode(PositionMode positionMode) {
_positionMode = positionMode;
}
SpacingMode PathConstraintData::getSpacingMode() {
return _spacingMode;
}
void PathConstraintData::setSpacingMode(SpacingMode spacingMode) {
_spacingMode = spacingMode;
}
RotateMode PathConstraintData::getRotateMode() {
return _rotateMode;
}
void PathConstraintData::setRotateMode(RotateMode rotateMode) {
_rotateMode = rotateMode;
}
float PathConstraintData::getOffsetRotation() {
return _offsetRotation;
}
void PathConstraintData::setOffsetRotation(float offsetRotation) {
_offsetRotation = offsetRotation;
}
Constraint &PathConstraintData::create(Skeleton &skeleton) {
return *(new (__FILE__, __LINE__) PathConstraint(*this, skeleton));
}
@@ -0,0 +1,131 @@
/******************************************************************************
* 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/PathConstraintMixTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/PathConstraint.h>
#include <spine/PathConstraintData.h>
#include <spine/PathConstraintPose.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL_MULTI(PathConstraintMixTimeline, CurveTimeline, ConstraintTimeline)
PathConstraintMixTimeline::PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int constraintIndex)
: CurveTimeline(frameCount, PathConstraintMixTimeline::ENTRIES, bezierCount), ConstraintTimeline(), _constraintIndex(constraintIndex) {
PropertyId ids[] = {((PropertyId) Property_PathConstraintMix << 32) | constraintIndex};
setPropertyIds(ids, 1);
}
PathConstraintMixTimeline::~PathConstraintMixTimeline() {
}
void PathConstraintMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
PathConstraint *constraint = (PathConstraint *) skeleton._constraints[_constraintIndex];
if (!constraint->isActive()) return;
PathConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
if (time < _frames[0]) {
PathConstraintPose &setup = constraint->_data._setupPose;
switch (from) {
case MixFrom_Setup:
pose._mixRotate = setup._mixRotate;
pose._mixX = setup._mixX;
pose._mixY = setup._mixY;
break;
case MixFrom_First:
pose._mixRotate += (setup._mixRotate - pose._mixRotate) * alpha;
pose._mixX += (setup._mixX - pose._mixX) * alpha;
pose._mixY += (setup._mixY - pose._mixY) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float rotate, x, y;
int i = Animation::search(_frames, time, PathConstraintMixTimeline::ENTRIES);
int curveType = (int) _curves[i >> 2];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
rotate = _frames[i + ROTATE];
x = _frames[i + X];
y = _frames[i + Y];
float t = (time - before) / (_frames[i + ENTRIES] - before);
rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t;
x += (_frames[i + ENTRIES + X] - x) * t;
y += (_frames[i + ENTRIES + Y] - y) * t;
break;
}
case STEPPED: {
rotate = _frames[i + ROTATE];
x = _frames[i + X];
y = _frames[i + Y];
break;
}
default: {
rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER);
x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER);
y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER);
}
}
PathConstraintPose &base = from == MixFrom_Setup ? constraint->_data._setupPose : pose;
if (add) {
pose._mixRotate = base._mixRotate + rotate * alpha;
pose._mixX = base._mixX + x * alpha;
pose._mixY = base._mixY + y * alpha;
} else {
pose._mixRotate = base._mixRotate + (rotate - base._mixRotate) * alpha;
pose._mixX = base._mixX + (x - base._mixX) * alpha;
pose._mixY = base._mixY + (y - base._mixY) * alpha;
}
}
void PathConstraintMixTimeline::setFrame(int frame, float time, float mixRotate, float mixX, float mixY) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + ROTATE] = mixRotate;
_frames[frame + X] = mixX;
_frames[frame + Y] = mixY;
}
@@ -0,0 +1,86 @@
/******************************************************************************
* 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/PathConstraintPose.h>
using namespace spine;
PathConstraintPose::PathConstraintPose() : Pose<PathConstraintPose>(), _position(0), _spacing(0), _mixRotate(0), _mixX(0), _mixY(0) {
}
PathConstraintPose::~PathConstraintPose() {
}
void PathConstraintPose::set(PathConstraintPose &pose) {
_position = pose._position;
_spacing = pose._spacing;
_mixRotate = pose._mixRotate;
_mixX = pose._mixX;
_mixY = pose._mixY;
}
float PathConstraintPose::getPosition() {
return _position;
}
void PathConstraintPose::setPosition(float position) {
_position = position;
}
float PathConstraintPose::getSpacing() {
return _spacing;
}
void PathConstraintPose::setSpacing(float spacing) {
_spacing = spacing;
}
float PathConstraintPose::getMixRotate() {
return _mixRotate;
}
void PathConstraintPose::setMixRotate(float mixRotate) {
_mixRotate = mixRotate;
}
float PathConstraintPose::getMixX() {
return _mixX;
}
void PathConstraintPose::setMixX(float mixX) {
_mixX = mixX;
}
float PathConstraintPose::getMixY() {
return _mixY;
}
void PathConstraintPose::setMixY(float mixY) {
_mixY = mixY;
}
@@ -0,0 +1,66 @@
/******************************************************************************
* 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/PathConstraintPositionTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/PathConstraint.h>
#include <spine/PathConstraintData.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(PathConstraintPositionTimeline, ConstraintTimeline1)
PathConstraintPositionTimeline::PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, int constraintIndex)
: ConstraintTimeline1(frameCount, bezierCount, constraintIndex, Property_PathConstraintPosition) {
_additive = true;
}
PathConstraintPositionTimeline::~PathConstraintPositionTimeline() {
}
void PathConstraintPositionTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from,
bool add, bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
PathConstraint *constraint = (PathConstraint *) skeleton._constraints[_constraintIndex];
if (constraint->isActive()) {
PathConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
PathConstraintData &data = constraint->_data;
pose._position = getAbsoluteValue(time, alpha, from, add, pose._position, data._setupPose._position);
}
}
@@ -0,0 +1,65 @@
/******************************************************************************
* 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/PathConstraintSpacingTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/PathConstraint.h>
#include <spine/PathConstraintData.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(PathConstraintSpacingTimeline, ConstraintTimeline1)
PathConstraintSpacingTimeline::PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, int constraintIndex)
: ConstraintTimeline1(frameCount, bezierCount, constraintIndex, Property_PathConstraintSpacing) {
}
PathConstraintSpacingTimeline::~PathConstraintSpacingTimeline() {
}
void PathConstraintSpacingTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
PathConstraint *constraint = (PathConstraint *) skeleton._constraints[_constraintIndex];
if (constraint->isActive()) {
PathConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
PathConstraintData &data = constraint->_data;
pose._spacing = getAbsoluteValue(time, alpha, from, false, pose._spacing, data._setupPose._spacing);
}
}
@@ -0,0 +1,305 @@
/******************************************************************************
* 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/PhysicsConstraint.h>
#include <spine/PhysicsConstraintData.h>
#include <spine/PhysicsConstraintPose.h>
#include <spine/BonePose.h>
#include <spine/Skeleton.h>
#include <spine/SkeletonData.h>
#include <spine/BoneData.h>
#include <spine/Bone.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL(PhysicsConstraint, Constraint)
PhysicsConstraint::PhysicsConstraint(PhysicsConstraintData &data, Skeleton &skeleton)
: PhysicsConstraintBase(data), _reset(true), _ux(0), _uy(0), _cx(0), _cy(0), _tx(0), _ty(0), _xOffset(0), _xLag(0), _xVelocity(0), _yOffset(0),
_yLag(0), _yVelocity(0), _rotateOffset(0), _rotateLag(0), _rotateVelocity(0), _scaleOffset(0), _scaleLag(0), _scaleVelocity(0), _remaining(0),
_lastTime(0) {
_bone = &skeleton._bones[(size_t) data._bone->getIndex()]->_constrainedPose;
}
PhysicsConstraint &PhysicsConstraint::copy(Skeleton &skeleton) {
PhysicsConstraint *copy = new (__FILE__, __LINE__) PhysicsConstraint(_data, skeleton);
copy->_pose.set(_pose);
return *copy;
}
void PhysicsConstraint::reset(Skeleton &skeleton) {
_remaining = 0;
_lastTime = skeleton.getTime();
_reset = true;
_xOffset = 0;
_xLag = 0;
_xVelocity = 0;
_yOffset = 0;
_yLag = 0;
_yVelocity = 0;
_rotateOffset = 0;
_rotateLag = 0;
_rotateVelocity = 0;
_scaleOffset = 0;
_scaleLag = 0;
_scaleVelocity = 0;
}
void PhysicsConstraint::translate(float x, float y) {
_ux -= x;
_uy -= y;
_cx -= x;
_cy -= y;
}
void PhysicsConstraint::rotate(float x, float y, float degrees) {
float r = degrees * MathUtil::Deg_Rad, cosVal = MathUtil::cos(r), sinVal = MathUtil::sin(r);
float dx = _cx - x, dy = _cy - y;
translate(dx * cosVal - dy * sinVal - dx, dx * sinVal + dy * cosVal - dy);
}
void PhysicsConstraint::update(Skeleton &skeleton, Physics physics) {
PhysicsConstraintPose &p = *_appliedPose;
float mix = p._mix;
if (mix == 0) return;
bool x = _data._x > 0, y = _data._y > 0, rotateOrShearX = _data._rotate > 0 || _data._shearX > 0, scaleX = _data._scaleX > 0;
BonePose *bone = _bone;
float l = bone->_bone->_data.getLength(), t = _data._step, z = 0;
if (physics == Physics_None) return;
bone->modifyWorld(skeleton);
switch (physics) {
case Physics_Reset:
reset(skeleton);
// Fall through.
case Physics_Update: {
float delta = MathUtil::max(skeleton._time - _lastTime, 0.0f), aa = _remaining;
_remaining += delta;
_lastTime = skeleton._time;
float bx = bone->_worldX, by = bone->_worldY;
if (_reset) {
_reset = false;
_ux = bx;
_uy = by;
} else {
float a = _remaining, i = p._inertia, f = skeleton._data.getReferenceScale(), d = -1, m = 0, e = 0, qx = _data._limit * delta,
qy = qx * MathUtil::abs(skeleton.getScaleY());
qx *= MathUtil::abs(skeleton._scaleX);
if (x || y) {
if (x) {
float u = (_ux - bx) * i;
_xOffset += u > qx ? qx : u < -qx ? -qx : u;
_ux = bx;
}
if (y) {
float u = (_uy - by) * i;
_yOffset += u > qy ? qy : u < -qy ? -qy : u;
_uy = by;
}
if (a >= t) {
float xs = _xOffset, ys = _yOffset;
d = MathUtil::pow(p._damping, 60 * t);
m = t * p._massInverse;
e = p._strength;
float w = f * p._wind, g = f * p._gravity;
float ax = (w * skeleton._windX + g * skeleton._gravityX) * skeleton._scaleX;
float ay = (w * skeleton._windY + g * skeleton._gravityY) * skeleton.getScaleY();
do {
if (x) {
_xVelocity += (ax - _xOffset * e) * m;
_xOffset += _xVelocity * t;
_xVelocity *= d;
}
if (y) {
_yVelocity -= (ay + _yOffset * e) * m;
_yOffset += _yVelocity * t;
_yVelocity *= d;
}
a -= t;
} while (a >= t);
_xLag = _xOffset - xs;
_yLag = _yOffset - ys;
}
z = MathUtil::max(0.0f, 1 - a / t);
if (x) bone->_worldX += (_xOffset - _xLag * z) * mix * _data._x;
if (y) bone->_worldY += (_yOffset - _yLag * z) * mix * _data._y;
}
if (rotateOrShearX || scaleX) {
float ca = MathUtil::atan2(bone->_c, bone->_a), c, s, mr = 0, dx = _cx - bone->_worldX, dy = _cy - bone->_worldY;
if (dx > qx)
dx = qx;
else if (dx < -qx)
dx = -qx;
if (dy > qy)
dy = qy;
else if (dy < -qy)
dy = -qy;
if (rotateOrShearX) {
mr = (_data._rotate + _data._shearX) * mix;
z = _rotateLag * MathUtil::max(0.0f, 1 - aa / t);
float r = MathUtil::atan2(dy + _ty, dx + _tx) - ca - (_rotateOffset - z) * mr;
_rotateOffset += (r - MathUtil::ceil(r * MathUtil::InvPi_2 - 0.5f) * MathUtil::Pi_2) * i;
r = (_rotateOffset - z) * mr + ca;
c = MathUtil::cos(r);
s = MathUtil::sin(r);
if (scaleX) {
r = l * bone->getWorldScaleX();
if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r;
}
} else {
c = MathUtil::cos(ca);
s = MathUtil::sin(ca);
float r = l * bone->getWorldScaleX() - _scaleLag * MathUtil::max(0.0f, 1 - aa / t);
if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r;
}
a = _remaining;
if (a >= t) {
if (d == -1) {
d = MathUtil::pow(p._damping, 60 * t);
m = t * p._massInverse;
e = p._strength;
}
float ax = p._wind * skeleton._windX + p._gravity * skeleton._gravityX;
float ay = (p._wind * skeleton._windY + p._gravity * skeleton._gravityY) * (Bone::isYDown() ? -1 : 1);
float rs = _rotateOffset, ss = _scaleOffset, h = l / f;
while (true) {
a -= t;
if (scaleX) {
_scaleVelocity += (ax * c - ay * s - _scaleOffset * e) * m;
_scaleOffset += _scaleVelocity * t;
_scaleVelocity *= d;
}
if (rotateOrShearX) {
_rotateVelocity -= ((ax * s + ay * c) * h + _rotateOffset * e) * m;
_rotateOffset += _rotateVelocity * t;
_rotateVelocity *= d;
if (a < t) break;
float r = _rotateOffset * mr + ca;
c = MathUtil::cos(r);
s = MathUtil::sin(r);
} else if (a < t)
break;
}
_rotateLag = _rotateOffset - rs;
_scaleLag = _scaleOffset - ss;
}
z = MathUtil::max(0.0f, 1 - a / t);
}
_remaining = a;
}
_cx = bone->_worldX;
_cy = bone->_worldY;
break;
}
case Physics_Pose: {
z = MathUtil::max(0.0f, 1 - _remaining / t);
if (x) bone->_worldX += (_xOffset - _xLag * z) * mix * _data._x;
if (y) bone->_worldY += (_yOffset - _yLag * z) * mix * _data._y;
break;
}
}
if (rotateOrShearX) {
float o = (_rotateOffset - _rotateLag * z) * mix, s, c, a;
if (_data._shearX > 0) {
float r = 0;
if (_data._rotate > 0) {
r = o * _data._rotate;
s = MathUtil::sin(r);
c = MathUtil::cos(r);
a = bone->_b;
bone->_b = c * a - s * bone->_d;
bone->_d = s * a + c * bone->_d;
}
r += o * _data._shearX;
s = MathUtil::sin(r);
c = MathUtil::cos(r);
a = bone->_a;
bone->_a = c * a - s * bone->_c;
bone->_c = s * a + c * bone->_c;
} else {
o *= _data._rotate;
s = MathUtil::sin(o);
c = MathUtil::cos(o);
a = bone->_a;
bone->_a = c * a - s * bone->_c;
bone->_c = s * a + c * bone->_c;
a = bone->_b;
bone->_b = c * a - s * bone->_d;
bone->_d = s * a + c * bone->_d;
}
}
if (scaleX) {
float s = 1 + (_scaleOffset - _scaleLag * z) * mix * _data._scaleX;
bone->_a *= s;
bone->_c *= s;
switch (_data._scaleYMode) {
case ScaleYMode_Uniform:
bone->_b *= s;
bone->_d *= s;
break;
case ScaleYMode_Volume:
s = MathUtil::abs(s);
s = s >= 0.7f ? 1 / s : 4 - 3.67347f * s;
bone->_b *= s;
bone->_d *= s;
break;
default:
break;
}
}
if (physics != Physics_Pose) {
_tx = l * bone->_a;
_ty = l * bone->_c;
}
}
void PhysicsConstraint::sort(Skeleton &skeleton) {
Bone *bone = _bone->_bone;
skeleton.sortBone(bone);
skeleton._updateCache.add(this);
skeleton.sortReset(bone->_children);
skeleton.constrained(*bone);
}
bool PhysicsConstraint::isSourceActive() {
return _bone->_bone->isActive();
}
BonePose &PhysicsConstraint::getBone() {
return *_bone;
}
void PhysicsConstraint::setBone(BonePose &bone) {
_bone = &bone;
}
@@ -0,0 +1,175 @@
/******************************************************************************
* 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/PhysicsConstraintData.h>
#include <spine/PhysicsConstraint.h>
#include <spine/BoneData.h>
#include <spine/Skeleton.h>
using namespace spine;
RTTI_IMPL(PhysicsConstraintData, ConstraintData)
PhysicsConstraintData::PhysicsConstraintData(const String &name)
: ConstraintDataGeneric<PhysicsConstraint, PhysicsConstraintPose>(name), _bone(NULL), _x(0), _y(0), _rotate(0), _scaleX(0), _shearX(0), _limit(0),
_step(0), _scaleYMode(ScaleYMode_None), _inertiaGlobal(false), _strengthGlobal(false), _dampingGlobal(false), _massGlobal(false),
_windGlobal(false), _gravityGlobal(false), _mixGlobal(false) {
}
BoneData &PhysicsConstraintData::getBone() {
return *_bone;
}
void PhysicsConstraintData::setBone(BoneData &bone) {
_bone = &bone;
}
float PhysicsConstraintData::getStep() {
return _step;
}
void PhysicsConstraintData::setStep(float step) {
_step = step;
}
float PhysicsConstraintData::getX() {
return _x;
}
void PhysicsConstraintData::setX(float x) {
_x = x;
}
float PhysicsConstraintData::getY() {
return _y;
}
void PhysicsConstraintData::setY(float y) {
_y = y;
}
float PhysicsConstraintData::getRotate() {
return _rotate;
}
void PhysicsConstraintData::setRotate(float rotate) {
_rotate = rotate;
}
float PhysicsConstraintData::getScaleX() {
return _scaleX;
}
void PhysicsConstraintData::setScaleX(float scaleX) {
_scaleX = scaleX;
}
float PhysicsConstraintData::getShearX() {
return _shearX;
}
void PhysicsConstraintData::setShearX(float shearX) {
_shearX = shearX;
}
float PhysicsConstraintData::getLimit() {
return _limit;
}
void PhysicsConstraintData::setLimit(float limit) {
_limit = limit;
}
ScaleYMode PhysicsConstraintData::getScaleYMode() {
return _scaleYMode;
}
void PhysicsConstraintData::setScaleYMode(ScaleYMode scaleYMode) {
_scaleYMode = scaleYMode;
}
bool PhysicsConstraintData::getInertiaGlobal() {
return _inertiaGlobal;
}
void PhysicsConstraintData::setInertiaGlobal(bool inertiaGlobal) {
_inertiaGlobal = inertiaGlobal;
}
bool PhysicsConstraintData::getStrengthGlobal() {
return _strengthGlobal;
}
void PhysicsConstraintData::setStrengthGlobal(bool strengthGlobal) {
_strengthGlobal = strengthGlobal;
}
bool PhysicsConstraintData::getDampingGlobal() {
return _dampingGlobal;
}
void PhysicsConstraintData::setDampingGlobal(bool dampingGlobal) {
_dampingGlobal = dampingGlobal;
}
bool PhysicsConstraintData::getMassGlobal() {
return _massGlobal;
}
void PhysicsConstraintData::setMassGlobal(bool massGlobal) {
_massGlobal = massGlobal;
}
bool PhysicsConstraintData::getWindGlobal() {
return _windGlobal;
}
void PhysicsConstraintData::setWindGlobal(bool windGlobal) {
_windGlobal = windGlobal;
}
bool PhysicsConstraintData::getGravityGlobal() {
return _gravityGlobal;
}
void PhysicsConstraintData::setGravityGlobal(bool gravityGlobal) {
_gravityGlobal = gravityGlobal;
}
bool PhysicsConstraintData::getMixGlobal() {
return _mixGlobal;
}
void PhysicsConstraintData::setMixGlobal(bool mixGlobal) {
_mixGlobal = mixGlobal;
}
Constraint &PhysicsConstraintData::create(Skeleton &skeleton) {
return *(new (__FILE__, __LINE__) PhysicsConstraint(*this, skeleton));
}
@@ -0,0 +1,105 @@
/******************************************************************************
* 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/PhysicsConstraintPose.h>
using namespace spine;
PhysicsConstraintPose::PhysicsConstraintPose()
: Pose<PhysicsConstraintPose>(), _inertia(0), _strength(0), _damping(0), _massInverse(0), _wind(0), _gravity(0), _mix(0) {
}
PhysicsConstraintPose::~PhysicsConstraintPose() {
}
void PhysicsConstraintPose::set(PhysicsConstraintPose &pose) {
_inertia = pose._inertia;
_strength = pose._strength;
_damping = pose._damping;
_massInverse = pose._massInverse;
_wind = pose._wind;
_gravity = pose._gravity;
_mix = pose._mix;
}
float PhysicsConstraintPose::getInertia() {
return _inertia;
}
void PhysicsConstraintPose::setInertia(float inertia) {
_inertia = inertia;
}
float PhysicsConstraintPose::getStrength() {
return _strength;
}
void PhysicsConstraintPose::setStrength(float strength) {
_strength = strength;
}
float PhysicsConstraintPose::getDamping() {
return _damping;
}
void PhysicsConstraintPose::setDamping(float damping) {
_damping = damping;
}
float PhysicsConstraintPose::getMassInverse() {
return _massInverse;
}
void PhysicsConstraintPose::setMassInverse(float massInverse) {
_massInverse = massInverse;
}
float PhysicsConstraintPose::getWind() {
return _wind;
}
void PhysicsConstraintPose::setWind(float wind) {
_wind = wind;
}
float PhysicsConstraintPose::getGravity() {
return _gravity;
}
void PhysicsConstraintPose::setGravity(float gravity) {
_gravity = gravity;
}
float PhysicsConstraintPose::getMix() {
return _mix;
}
void PhysicsConstraintPose::setMix(float mix) {
_mix = mix;
}
@@ -0,0 +1,110 @@
/******************************************************************************
* 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/PhysicsConstraintTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/PhysicsConstraint.h>
#include <cfloat>
using namespace spine;
RTTI_IMPL_MULTI(PhysicsConstraintTimeline, CurveTimeline, ConstraintTimeline)
RTTI_IMPL(PhysicsConstraintInertiaTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintStrengthTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintDampingTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintMassTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintWindTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintGravityTimeline, PhysicsConstraintTimeline)
RTTI_IMPL(PhysicsConstraintMixTimeline, PhysicsConstraintTimeline)
RTTI_IMPL_MULTI(PhysicsConstraintResetTimeline, Timeline, ConstraintTimeline)
PhysicsConstraintTimeline::PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, int constraintIndex, Property property)
: CurveTimeline1(frameCount, bezierCount), ConstraintTimeline(), _constraintIndex(constraintIndex) {
PropertyId ids[] = {((PropertyId) property << 32) | constraintIndex};
setPropertyIds(ids, 1);
}
void PhysicsConstraintTimeline::apply(Skeleton &skeleton, float, float time, Array<Event *> *, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(out);
if (add && !_additive) add = false;
if (_constraintIndex == -1) {
float value = time >= _frames[0] ? getCurveValue(time) : 0;
Array<PhysicsConstraint *> &physicsConstraints = skeleton.getPhysicsConstraints();
for (size_t i = 0; i < physicsConstraints.size(); i++) {
PhysicsConstraint *constraint = physicsConstraints[i];
if (constraint->isActive() && global(constraint->_data)) {
PhysicsConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
set(pose, getAbsoluteValue(time, alpha, from, add, get(pose), get(constraint->_data._setupPose), value));
}
}
} else {
PhysicsConstraint *constraint = static_cast<PhysicsConstraint *>(skeleton.getConstraints()[_constraintIndex]);
if (constraint->isActive()) {
PhysicsConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
set(pose, getAbsoluteValue(time, alpha, from, add, get(pose), get(constraint->_data._setupPose)));
}
}
}
void PhysicsConstraintResetTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
PhysicsConstraint *constraint = nullptr;
if (_constraintIndex != -1) {
constraint = static_cast<PhysicsConstraint *>(skeleton.getConstraints()[_constraintIndex]);
if (!constraint->isActive()) return;
}
if (lastTime > time) {// Apply after lastTime for looped animations.
apply(skeleton, lastTime, FLT_MAX, nullptr, alpha, MixFrom_Current, false, false, false);
lastTime = -1;
} else if (lastTime >= _frames[_frames.size() - 1])// Last time is after last frame.
return;
if (time < _frames[0]) return;
if (lastTime < _frames[0] || time >= _frames[Animation::search(_frames, lastTime) + 1]) {
if (constraint != nullptr)
constraint->reset(skeleton);
else {
Array<PhysicsConstraint *> &physicsConstraints = skeleton.getPhysicsConstraints();
for (size_t i = 0; i < physicsConstraints.size(); i++) {
constraint = physicsConstraints[i];
if (constraint->isActive()) constraint->reset(skeleton);
}
}
}
}
@@ -0,0 +1,92 @@
/******************************************************************************
* 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/PointAttachment.h>
#include <spine/Bone.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL(PointAttachment, Attachment)
PointAttachment::PointAttachment(const String &name) : Attachment(name), _x(0), _y(0), _rotation(0), _color(0.9451f, 0.9451f, 0, 1) {
}
float PointAttachment::getX() {
return _x;
}
void PointAttachment::setX(float inValue) {
_x = inValue;
}
float PointAttachment::getY() {
return _y;
}
void PointAttachment::setY(float inValue) {
_y = inValue;
}
float PointAttachment::getRotation() {
return _rotation;
}
void PointAttachment::setRotation(float inValue) {
_rotation = inValue;
}
Color &PointAttachment::getColor() {
return _color;
}
void PointAttachment::computeWorldPosition(BonePose &bone, float &ox, float &oy) {
ox = _x * bone.getA() + _y * bone.getB() + bone.getWorldX();
oy = _x * bone.getC() + _y * bone.getD() + bone.getWorldY();
}
float PointAttachment::computeWorldRotation(BonePose &bone) {
float r = _rotation * MathUtil::Deg_Rad, cosine = MathUtil::cos(r), sine = MathUtil::sin(r);
float x = cosine * bone.getA() + sine * bone.getB();
float y = cosine * bone.getC() + sine * bone.getD();
return MathUtil::atan2Deg(y, x);
}
Attachment &PointAttachment::copy() {
PointAttachment *copy = new (__FILE__, __LINE__) PointAttachment(getName());
copy->setTimelineAttachment(getTimelineAttachment());
copy->setTimelineSlots(getTimelineSlots());
copy->_x = _x;
copy->_y = _y;
copy->_rotation = _rotation;
copy->_color.set(_color);
return *copy;
}
@@ -0,0 +1,83 @@
/******************************************************************************
* 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/RTTI.h>
#include <spine/SpineString.h>
using namespace spine;
RTTI::RTTI(const char *className) : _className(className), _pBaseRTTI(NULL), _interfaceCount(0) {
_interfaces[0] = NULL;
_interfaces[1] = NULL;
_interfaces[2] = NULL;
}
RTTI::RTTI(const char *className, const RTTI &baseRTTI) : _className(className), _pBaseRTTI(&baseRTTI), _interfaceCount(0) {
_interfaces[0] = NULL;
_interfaces[1] = NULL;
_interfaces[2] = NULL;
}
RTTI::RTTI(const char *className, const RTTI &baseRTTI, const RTTI *interface1, const RTTI *interface2, const RTTI *interface3)
: _className(className), _pBaseRTTI(&baseRTTI), _interfaceCount(0) {
_interfaces[0] = interface1;
_interfaces[1] = interface2;
_interfaces[2] = interface3;
if (interface1) _interfaceCount++;
if (interface2) _interfaceCount++;
if (interface3) _interfaceCount++;
}
const char *RTTI::getClassName() const {
return _className;
}
bool RTTI::isExactly(const RTTI &rtti) const {
return !strcmp(this->_className, rtti._className);
}
bool RTTI::instanceOf(const RTTI &rtti) const {
// Check the main inheritance chain
const RTTI *pCompare = this;
while (pCompare) {
if (!strcmp(pCompare->_className, rtti._className)) return true;
// Check interfaces at this level of the hierarchy
for (int i = 0; i < pCompare->_interfaceCount; i++) {
if (pCompare->_interfaces[i] && !strcmp(pCompare->_interfaces[i]->_className, rtti._className)) {
return true;
}
}
pCompare = pCompare->_pBaseRTTI;
}
return false;
}
@@ -0,0 +1,258 @@
/******************************************************************************
* 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/RegionAttachment.h>
#include <spine/Atlas.h>
#include <spine/Bone.h>
#include <spine/MathUtil.h>
#include <spine/Slot.h>
#include <spine/SlotPose.h>
#include <assert.h>
using namespace spine;
RTTI_IMPL(RegionAttachment, Attachment)
const int RegionAttachment::BLX = 0;
const int RegionAttachment::BLY = 1;
const int RegionAttachment::ULX = 2;
const int RegionAttachment::ULY = 3;
const int RegionAttachment::URX = 4;
const int RegionAttachment::URY = 5;
const int RegionAttachment::BRX = 6;
const int RegionAttachment::BRY = 7;
RegionAttachment::RegionAttachment(const String &name, Sequence *sequence)
: Attachment(name), _sequence(sequence), _x(0), _y(0), _scaleX(1), _scaleY(1), _rotation(0), _width(0), _height(0), _path(), _color(1, 1, 1, 1) {
assert(sequence);
}
RegionAttachment::~RegionAttachment() {
delete _sequence;
}
void RegionAttachment::computeWorldVertices(Slot &slot, Array<float> &vertexOffsets, Array<float> &worldVertices, size_t offset, size_t stride) {
assert(worldVertices.size() >= (offset + 8));
computeWorldVertices(slot, vertexOffsets.buffer(), worldVertices.buffer(), offset, stride);
}
void RegionAttachment::computeWorldVertices(Slot &slot, float *vertexOffsets, float *worldVertices, size_t offset, size_t stride) {
BonePose &bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX(), y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
float offsetX = vertexOffsets[BRX];
float offsetY = vertexOffsets[BRY];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[BLX];
offsetY = vertexOffsets[BLY];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[ULX];
offsetY = vertexOffsets[ULY];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[URX];
offsetY = vertexOffsets[URY];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
}
Array<float> &RegionAttachment::getOffsets(SlotPose &pose) {
return _sequence->getOffsets(_sequence->resolveIndex(pose));
}
float RegionAttachment::getX() {
return _x;
}
void RegionAttachment::setX(float inValue) {
_x = inValue;
}
float RegionAttachment::getY() {
return _y;
}
void RegionAttachment::setY(float inValue) {
_y = inValue;
}
float RegionAttachment::getScaleX() {
return _scaleX;
}
void RegionAttachment::setScaleX(float inValue) {
_scaleX = inValue;
}
float RegionAttachment::getScaleY() {
return _scaleY;
}
void RegionAttachment::setScaleY(float inValue) {
_scaleY = inValue;
}
float RegionAttachment::getRotation() {
return _rotation;
}
void RegionAttachment::setRotation(float inValue) {
_rotation = inValue;
}
float RegionAttachment::getWidth() {
return _width;
}
void RegionAttachment::setWidth(float inValue) {
_width = inValue;
}
float RegionAttachment::getHeight() {
return _height;
}
void RegionAttachment::setHeight(float inValue) {
_height = inValue;
}
Sequence &RegionAttachment::getSequence() {
return *_sequence;
}
void RegionAttachment::updateSequence() {
_sequence->update(*this);
}
const String &RegionAttachment::getPath() {
return _path;
}
void RegionAttachment::setPath(const String &inValue) {
_path = inValue;
}
Color &RegionAttachment::getColor() {
return _color;
}
Attachment &RegionAttachment::copy() {
RegionAttachment *copy = new (__FILE__, __LINE__) RegionAttachment(getName(), new (__FILE__, __LINE__) Sequence(*_sequence));
copy->setTimelineAttachment(getTimelineAttachment());
copy->setTimelineSlots(getTimelineSlots());
copy->_path = _path;
copy->_x = _x;
copy->_y = _y;
copy->_scaleX = _scaleX;
copy->_scaleY = _scaleY;
copy->_rotation = _rotation;
copy->_width = _width;
copy->_height = _height;
copy->_color.set(_color);
return *copy;
}
void RegionAttachment::computeUVs(TextureRegion *region, float x, float y, float scaleX, float scaleY, float rotation, float width, float height,
Array<float> &offset, Array<float> &uvs) {
float localX2 = width / 2, localY2 = height / 2;
float localX = -localX2, localY = -localY2;
bool rotated = false;
if (region != NULL && region->getRTTI().instanceOf(AtlasRegion::rtti)) {
AtlasRegion *r = static_cast<AtlasRegion *>(region);
localX += r->_offsetX / r->_originalWidth * width;
localY += r->_offsetY / r->_originalHeight * height;
if (r->_degrees == 90) {
rotated = true;
localX2 -= (r->_originalWidth - r->_offsetX - r->_packedHeight) / r->_originalWidth * width;
localY2 -= (r->_originalHeight - r->_offsetY - r->_packedWidth) / r->_originalHeight * height;
} else {
localX2 -= (r->_originalWidth - r->_offsetX - r->_packedWidth) / r->_originalWidth * width;
localY2 -= (r->_originalHeight - r->_offsetY - r->_packedHeight) / r->_originalHeight * height;
}
}
localX *= scaleX;
localY *= scaleY;
localX2 *= scaleX;
localY2 *= scaleY;
float cos = MathUtil::cosDeg(rotation);
float sin = MathUtil::sinDeg(rotation);
float localXCos = localX * cos + x;
float localXSin = localX * sin;
float localYCos = localY * cos + y;
float localYSin = localY * sin;
float localX2Cos = localX2 * cos + x;
float localX2Sin = localX2 * sin;
float localY2Cos = localY2 * cos + y;
float localY2Sin = localY2 * sin;
offset[BLX] = localXCos - localYSin;
offset[BLY] = localYCos + localXSin;
offset[ULX] = localXCos - localY2Sin;
offset[ULY] = localY2Cos + localXSin;
offset[URX] = localX2Cos - localY2Sin;
offset[URY] = localY2Cos + localX2Sin;
offset[BRX] = localX2Cos - localYSin;
offset[BRY] = localYCos + localX2Sin;
if (region == NULL) {
uvs[BLX] = 0;
uvs[BLY] = 0;
uvs[ULX] = 0;
uvs[ULY] = 1;
uvs[URX] = 1;
uvs[URY] = 1;
uvs[BRX] = 1;
uvs[BRY] = 0;
} else {
uvs[BLX] = region->_u2;
uvs[ULY] = region->_v2;
uvs[URX] = region->_u;
uvs[BRY] = region->_v;
if (rotated) {
uvs[BLY] = region->_v;
uvs[ULX] = region->_u2;
uvs[URY] = region->_v2;
uvs[BRX] = region->_u;
} else {
uvs[BLY] = region->_v2;
uvs[ULX] = region->_u;
uvs[URY] = region->_v;
uvs[BRX] = region->_u2;
}
}
}
@@ -0,0 +1,45 @@
/******************************************************************************
* 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/RotateTimeline.h>
#include <spine/BonePose.h>
using namespace spine;
RTTI_IMPL(RotateTimeline, BoneTimeline1)
RotateTimeline::RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_Rotate) {
}
void RotateTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
pose._rotation = getRelativeValue(time, alpha, from, add, pose._rotation, setup._rotation);
}
@@ -0,0 +1,137 @@
/******************************************************************************
* 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/ScaleTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(ScaleTimeline, BoneTimeline2)
ScaleTimeline::ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline2(frameCount, bezierCount, boneIndex, Property_ScaleX, Property_ScaleY) {
}
void ScaleTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
if (time < _frames[0]) {
switch (from) {
case MixFrom_Setup:
pose._scaleX = setup._scaleX;
pose._scaleY = setup._scaleY;
break;
case MixFrom_First:
pose._scaleX += (setup._scaleX - pose._scaleX) * alpha;
pose._scaleY += (setup._scaleY - pose._scaleY) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float x, y;
int i = Animation::search(_frames, time, BoneTimeline2::ENTRIES);
int curveType = (int) _curves[i / BoneTimeline2::ENTRIES];
switch (curveType) {
case CurveTimeline::LINEAR: {
float before = _frames[i];
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
float t = (time - before) / (_frames[i + BoneTimeline2::ENTRIES] - before);
x += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE1] - x) * t;
y += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE2] - y) * t;
break;
}
case CurveTimeline::STEPPED: {
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
break;
}
default: {
x = getBezierValue(time, i, BoneTimeline2::VALUE1, curveType - BoneTimeline2::BEZIER);
y = getBezierValue(time, i, BoneTimeline2::VALUE2, curveType + BoneTimeline2::BEZIER_SIZE - BoneTimeline2::BEZIER);
}
}
x *= setup._scaleX;
y *= setup._scaleY;
if (alpha == 1 && !add) {
pose._scaleX = x;
pose._scaleY = y;
} else {
float bx, by;
if (from == MixFrom_Setup) {
bx = setup._scaleX;
by = setup._scaleY;
} else {
bx = pose._scaleX;
by = pose._scaleY;
}
if (add) {
pose._scaleX = bx + (x - setup._scaleX) * alpha;
pose._scaleY = by + (y - setup._scaleY) * alpha;
} else if (out) {
pose._scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha;
pose._scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha;
} else {
bx = MathUtil::abs(bx) * MathUtil::sign(x);
by = MathUtil::abs(by) * MathUtil::sign(y);
pose._scaleX = bx + (x - bx) * alpha;
pose._scaleY = by + (y - by) * alpha;
}
}
}
RTTI_IMPL(ScaleXTimeline, BoneTimeline1)
ScaleXTimeline::ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_ScaleX) {
}
void ScaleXTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
pose._scaleX = getScaleValue(time, alpha, from, add, out, pose._scaleX, setup._scaleX);
}
RTTI_IMPL(ScaleYTimeline, BoneTimeline1)
ScaleYTimeline::ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_ScaleY) {
}
void ScaleYTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
pose._scaleY = getScaleValue(time, alpha, from, add, out, pose._scaleY, setup._scaleY);
}
@@ -0,0 +1,110 @@
/******************************************************************************
* 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/Sequence.h>
#include <spine/MeshAttachment.h>
#include <spine/RegionAttachment.h>
#include <spine/SlotPose.h>
using namespace spine;
int Sequence::_nextID = 0;
Sequence::Sequence(int count, bool pathSuffix)
: _id(nextID()), _regions(count), _pathSuffix(pathSuffix), _uvs(), _offsets(), _start(0), _digits(0), _setupIndex(0) {
_regions.setSize(count, NULL);
}
Sequence::Sequence(const Sequence &other)
: _id(nextID()), _regions(other._regions), _pathSuffix(other._pathSuffix), _uvs(other._uvs), _offsets(other._offsets), _start(other._start),
_digits(other._digits), _setupIndex(other._setupIndex) {
}
Sequence::~Sequence() {
}
void Sequence::update(RegionAttachment &attachment) {
int regionCount = (int) _regions.size();
Array<float> empty;
_uvs.setSize(regionCount, empty);
_offsets.setSize(regionCount, empty);
for (int i = 0; i < regionCount; i++) {
_uvs[i].setSize(8, 0);
_offsets[i].setSize(8, 0);
RegionAttachment::computeUVs(_regions[i], attachment.getX(), attachment.getY(), attachment.getScaleX(), attachment.getScaleY(),
attachment.getRotation(), attachment.getWidth(), attachment.getHeight(), _offsets[i], _uvs[i]);
}
}
void Sequence::update(MeshAttachment &attachment) {
int regionCount = (int) _regions.size();
Array<float> empty;
_uvs.setSize(regionCount, empty);
_offsets.clear();
for (int i = 0; i < regionCount; i++) {
_uvs[i].setSize(attachment.getRegionUVs().size(), 0);
MeshAttachment::computeUVs(_regions[i], attachment.getRegionUVs(), _uvs[i]);
}
}
int Sequence::resolveIndex(SlotPose &pose) {
int index = pose.getSequenceIndex();
if (index == -1) index = _setupIndex;
if (index >= (int) _regions.size()) index = (int) _regions.size() - 1;
return index;
}
TextureRegion *Sequence::getRegion(int index) {
return _regions[index];
}
Array<float> &Sequence::getUVs(int index) {
return _uvs[index];
}
Array<float> &Sequence::getOffsets(int index) {
return _offsets[index];
}
String &Sequence::getPath(const String &basePath, int index) {
if (!_pathSuffix) {
_tmpPath = basePath;
return _tmpPath;
}
_tmpPath = basePath;
String frame;
frame.append(_start + index);
for (int i = _digits - (int) frame.length(); i > 0; i--) _tmpPath.append("0");
_tmpPath.append(frame);
return _tmpPath;
}
int Sequence::nextID() {
return _nextID++;
}
@@ -0,0 +1,156 @@
/******************************************************************************
* 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/SequenceTimeline.h>
#include <spine/Bone.h>
#include <spine/RegionAttachment.h>
#include <spine/MeshAttachment.h>
#include <spine/VertexAttachment.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Attachment.h>
#include <spine/Slot.h>
#include <spine/Animation.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL_MULTI(SequenceTimeline, Timeline, SlotTimeline)
SequenceTimeline::SequenceTimeline(size_t frameCount, int slotIndex, Attachment &attachment)
: Timeline(frameCount, ENTRIES), SlotTimeline(), _slotIndex(slotIndex), _attachment(&attachment) {
_instant = true;
int sequenceId = 0;
if (attachment.getRTTI().instanceOf(RegionAttachment::rtti)) sequenceId = ((RegionAttachment *) &attachment)->getSequence().getId();
if (attachment.getRTTI().instanceOf(MeshAttachment::rtti)) sequenceId = ((MeshAttachment *) &attachment)->getSequence().getId();
PropertyId ids[] = {((PropertyId) Property_Sequence << 32) | ((slotIndex << 16 | sequenceId) & 0xffffffff)};
setPropertyIds(ids, 1);
}
SequenceTimeline::~SequenceTimeline() {
}
void SequenceTimeline::setFrame(int frame, float time, SequenceMode mode, int index, float delay) {
Array<float> &frames = this->_frames;
frame *= ENTRIES;
frames[frame] = time;
frames[frame + MODE] = mode | (index << 4);
frames[frame + DELAY] = delay;
}
int SequenceTimeline::getSlotIndex() {
return _slotIndex;
}
void SequenceTimeline::setSlotIndex(int inValue) {
_slotIndex = inValue;
}
void SequenceTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(alpha);
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(add);
Array<Slot *> &slots = skeleton.getSlots();
if (!_attachment->isTimelineActive(slots, getSlotIndex(), appliedPose)) return;
Array<int> &timelineSlots = _attachment->getTimelineSlots();
Sequence *sequence = NULL;
if (_attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequence = &static_cast<RegionAttachment *>(_attachment)->getSequence();
if (_attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequence = &static_cast<MeshAttachment *>(_attachment)->getSequence();
if (!sequence) return;
Array<float> &frames = this->_frames;
if (out || time < frames[0]) {
if (from != MixFrom_Current) {
setupPose(*slots[getSlotIndex()], appliedPose);
for (size_t i = 0; i < timelineSlots.size(); ++i) setupPose(*slots[timelineSlots[i]], appliedPose);
}
return;
}
int i = Animation::search(frames, time, ENTRIES);
float before = frames[i];
int modeAndIndex = (int) frames[i + MODE];
float delay = frames[i + DELAY];
applyToSlot(*slots[getSlotIndex()], appliedPose, *sequence, time, before, modeAndIndex, delay);
for (size_t j = 0; j < timelineSlots.size(); ++j)
applyToSlot(*slots[timelineSlots[j]], appliedPose, *sequence, time, before, modeAndIndex, delay);
}
void SequenceTimeline::setupPose(Slot &slot, bool appliedPose) {
if (!slot.getBone().isActive()) return;
SlotPose &pose = appliedPose ? slot.getAppliedPose() : slot.getPose();
Attachment *attachment = pose.getAttachment();
if (attachment == NULL || attachment->getTimelineAttachment() != _attachment) return;
pose.setSequenceIndex(-1);
}
void SequenceTimeline::applyToSlot(Slot &slot, bool appliedPose, Sequence &sequence, float time, float before, int modeAndIndex, float delay) {
if (!slot.getBone().isActive()) return;
SlotPose &pose = appliedPose ? slot.getAppliedPose() : slot.getPose();
Attachment *attachment = pose.getAttachment();
if (attachment == NULL || attachment->getTimelineAttachment() != _attachment) return;
int index = modeAndIndex >> 4, count = (int) sequence.getRegions().size();
int mode = modeAndIndex & 0xf;
if (mode != SequenceMode_hold) {
index += (int) (((time - before) / delay + 0.0001));
switch (mode) {
case SequenceMode_once:
index = MathUtil::min(count - 1, index);
break;
case SequenceMode_loop:
index %= count;
break;
case SequenceMode_pingpong: {
int n = (count << 1) - 2;
index = n == 0 ? 0 : index % n;
if (index >= count) index = n - index;
break;
}
case SequenceMode_onceReverse:
index = MathUtil::max(count - 1 - index, 0);
break;
case SequenceMode_loopReverse:
index = count - 1 - (index % count);
break;
case SequenceMode_pingpongReverse: {
int n = (count << 1) - 2;
index = n == 0 ? 0 : (index + count - 1) % n;
if (index >= count) index = n - index;
break;
}
}
}
pose.setSequenceIndex(index);
}
@@ -0,0 +1,123 @@
/******************************************************************************
* 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/ShearTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(ShearTimeline, BoneTimeline2)
ShearTimeline::ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline2(frameCount, bezierCount, boneIndex, Property_ShearX, Property_ShearY) {
}
void ShearTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
if (time < _frames[0]) {
switch (from) {
case MixFrom_Setup:
pose._shearX = setup._shearX;
pose._shearY = setup._shearY;
break;
case MixFrom_First:
pose._shearX += (setup._shearX - pose._shearX) * alpha;
pose._shearY += (setup._shearY - pose._shearY) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float x, y;
int i = Animation::search(_frames, time, BoneTimeline2::ENTRIES);
int curveType = (int) _curves[i / BoneTimeline2::ENTRIES];
switch (curveType) {
case CurveTimeline::LINEAR: {
float before = _frames[i];
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
float t = (time - before) / (_frames[i + BoneTimeline2::ENTRIES] - before);
x += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE1] - x) * t;
y += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE2] - y) * t;
break;
}
case CurveTimeline::STEPPED: {
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
break;
}
default: {
x = getBezierValue(time, i, BoneTimeline2::VALUE1, curveType - BoneTimeline2::BEZIER);
y = getBezierValue(time, i, BoneTimeline2::VALUE2, curveType + BoneTimeline2::BEZIER_SIZE - BoneTimeline2::BEZIER);
}
}
if (from == MixFrom_Setup) {
pose._shearX = setup._shearX + x * alpha;
pose._shearY = setup._shearY + y * alpha;
} else if (add) {
pose._shearX += x * alpha;
pose._shearY += y * alpha;
} else {
pose._shearX += (setup._shearX + x - pose._shearX) * alpha;
pose._shearY += (setup._shearY + y - pose._shearY) * alpha;
}
}
RTTI_IMPL(ShearXTimeline, BoneTimeline1)
ShearXTimeline::ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_ShearX) {
}
void ShearXTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
pose._shearX = getRelativeValue(time, alpha, from, add, pose._shearX, setup._shearX);
}
RTTI_IMPL(ShearYTimeline, BoneTimeline1)
ShearYTimeline::ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_ShearY) {
}
void ShearYTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
pose._shearY = getRelativeValue(time, alpha, from, add, pose._shearY, setup._shearY);
}
@@ -0,0 +1,550 @@
/******************************************************************************
* 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), _drawOrder(_slots), _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());
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.setupPose();
_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();
_drawOrder.unconstrained();
Slot **slots = _slots.buffer();
for (size_t i = 0, n = _slots.size(); i < n; i++) {
slots[i]->unconstrained();
}
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->unconstrained();
}
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]->unconstrained();
}
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->_appliedPose;
}
}
}
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++;
if (_drawOrder._appliedPose == &_drawOrder._constrainedPose) _drawOrder.reset();
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.setupPose();
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;
}
DrawOrder &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 &placeholder) {
SlotData *slot = _data.findSlot(slotName);
if (slot == NULL) return NULL;
return getAttachment(slot->getIndex(), placeholder);
}
Attachment *Skeleton::getAttachment(int slotIndex, const String &placeholder) {
if (placeholder.isEmpty()) return NULL;
if (_skin != NULL) {
Attachment *attachment = _skin->getAttachment(slotIndex, placeholder);
if (attachment != NULL) return attachment;
}
if (_data.getDefaultSkin() != NULL) return _data.getDefaultSkin()->getAttachment(slotIndex, placeholder);
return NULL;
}
void Skeleton::setAttachment(const String &slotName, const String &placeholder) {
if (slotName.isEmpty()) return;
Slot *slot = findSlot(slotName);
if (slot == NULL) return;
Attachment *attachment = NULL;
if (!placeholder.isEmpty()) {
attachment = getAttachment(slot->_data.getIndex(), placeholder);
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;
Array<Slot *> &drawOrder = _drawOrder.getAppliedPose();
Slot **drawOrderSlots = drawOrder.buffer();
for (size_t i = 0, n = drawOrder.size(); i < n; ++i) {
Slot *slot = drawOrderSlots[i];
if (!slot->_bone._active) continue;
size_t verticesLength = 0;
float *vertices = NULL;
unsigned short *triangles = NULL;
size_t trianglesLength = 0;
Attachment *attachment = slot->getAppliedPose().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, regionAttachment->getOffsets(slot->getAppliedPose()), outVertexBuffer, 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;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,245 @@
/******************************************************************************
* 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/SkeletonBounds.h>
#include <spine/Bone.h>
#include <spine/BoundingBoxAttachment.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <float.h>
using namespace spine;
SkeletonBounds::SkeletonBounds() : _minX(0), _minY(0), _maxX(0), _maxY(0) {
}
SkeletonBounds::~SkeletonBounds() {
for (size_t i = 0, n = _polygons.size(); i < n; i++) _polygonPool.free(_polygons[i]);
_polygons.clear();
}
void SkeletonBounds::update(Skeleton &skeleton, bool updateAabb) {
Array<Slot *> &slots = skeleton.getSlots();
size_t slotCount = slots.size();
_boundingBoxes.clear();
for (size_t i = 0, n = _polygons.size(); i < n; ++i) {
_polygonPool.free(_polygons[i]);
}
_polygons.clear();
for (size_t i = 0; i < slotCount; i++) {
Slot *slot = slots[i];
if (!slot->getBone().isActive()) continue;
Attachment *attachment = slot->_appliedPose->getAttachment();
if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue;
BoundingBoxAttachment *boundingBox = static_cast<BoundingBoxAttachment *>(attachment);
_boundingBoxes.add(boundingBox);
Polygon *polygonP = _polygonPool.obtain();
_polygons.add(polygonP);
Polygon &polygon = *polygonP;
size_t count = boundingBox->getWorldVerticesLength();
polygon._count = (int) count;
if (polygon._vertices.size() < count) {
polygon._vertices.setSize(count, 0);
}
boundingBox->computeWorldVertices(skeleton, *slot, 0, count, polygon._vertices, 0, 2);
}
if (updateAabb)
aabbCompute();
else {
_minX = FLT_MIN;
_minY = FLT_MIN;
_maxX = FLT_MAX;
_maxY = FLT_MAX;
}
}
bool SkeletonBounds::aabbContainsPoint(float x, float y) {
return x >= _minX && x <= _maxX && y >= _minY && y <= _maxY;
}
bool SkeletonBounds::aabbIntersectsSegment(float x1, float y1, float x2, float y2) {
float minX = _minX;
float minY = _minY;
float maxX = _maxX;
float maxY = _maxY;
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY)) {
return false;
}
float m = (y2 - y1) / (x2 - x1);
float y = m * (minX - x1) + y1;
if (y > minY && y < maxY) return true;
y = m * (maxX - x1) + y1;
if (y > minY && y < maxY) return true;
float x = (minY - y1) / m + x1;
if (x > minX && x < maxX) return true;
x = (maxY - y1) / m + x1;
if (x > minX && x < maxX) return true;
return false;
}
bool SkeletonBounds::aabbIntersectsSkeleton(SkeletonBounds &bounds) {
return _minX < bounds._maxX && _maxX > bounds._minX && _minY < bounds._maxY && _maxY > bounds._minY;
}
bool SkeletonBounds::containsPoint(Polygon &polygon, float x, float y) {
Array<float> &vertices = polygon._vertices;
int nn = polygon._count;
int prevIndex = nn - 2;
bool inside = false;
for (int ii = 0; ii < nn; ii += 2) {
float vertexY = vertices[ii + 1];
float prevY = vertices[prevIndex + 1];
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
float vertexX = vertices[ii];
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) {
inside = !inside;
}
}
prevIndex = ii;
}
return inside;
}
BoundingBoxAttachment *SkeletonBounds::containsPoint(float x, float y) {
for (size_t i = 0, n = _polygons.size(); i < n; ++i)
if (containsPoint(*_polygons[i], x, y)) return _boundingBoxes[i];
return NULL;
}
BoundingBoxAttachment *SkeletonBounds::intersectsSegment(float x1, float y1, float x2, float y2) {
for (size_t i = 0, n = _polygons.size(); i < n; ++i)
if (intersectsSegment(*_polygons[i], x1, y1, x2, y2)) return _boundingBoxes[i];
return NULL;
}
bool SkeletonBounds::intersectsSegment(Polygon &polygon, float x1, float y1, float x2, float y2) {
Array<float> &vertices = polygon._vertices;
size_t nn = polygon._count;
float width12 = x1 - x2, height12 = y1 - y2;
float det1 = x1 * y2 - y1 * x2;
float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (size_t ii = 0; ii < nn; ii += 2) {
float x4 = vertices[ii], y4 = vertices[ii + 1];
float det2 = x3 * y4 - y3 * x4;
float width34 = x3 - x4, height34 = y3 - y4;
float det3 = width12 * height34 - height12 * width34;
float x = (det1 * width34 - width12 * det2) / det3;
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
float y = (det1 * height34 - height12 * det2) / det3;
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) {
return true;
}
}
x3 = x4;
y3 = y4;
}
return false;
}
spine::Polygon *SkeletonBounds::getPolygon(BoundingBoxAttachment *attachment) {
int index = _boundingBoxes.indexOf(attachment);
return index == -1 ? NULL : _polygons[index];
}
BoundingBoxAttachment *SkeletonBounds::getBoundingBox(spine::Polygon *polygon) {
int index = _polygons.indexOf(polygon);
return index == -1 ? NULL : _boundingBoxes[index];
}
Array<spine::Polygon *> &SkeletonBounds::getPolygons() {
return _polygons;
}
Array<BoundingBoxAttachment *> &SkeletonBounds::getBoundingBoxes() {
return _boundingBoxes;
}
float SkeletonBounds::getMinX() {
return _minX;
}
float SkeletonBounds::getMinY() {
return _minY;
}
float SkeletonBounds::getMaxX() {
return _maxX;
}
float SkeletonBounds::getMaxY() {
return _maxY;
}
float SkeletonBounds::getWidth() {
return _maxX - _minX;
}
float SkeletonBounds::getHeight() {
return _maxY - _minY;
}
void SkeletonBounds::aabbCompute() {
float minX = FLT_MAX;
float minY = FLT_MAX;
float maxX = FLT_MIN;
float maxY = FLT_MIN;
for (size_t i = 0, n = _polygons.size(); i < n; ++i) {
Polygon *polygon = _polygons[i];
Array<float> &vertices = polygon->_vertices;
for (int ii = 0, nn = polygon->_count; ii < nn; ii += 2) {
float x = vertices[ii];
float y = vertices[ii + 1];
minX = MathUtil::min(minX, x);
minY = MathUtil::min(minY, y);
maxX = MathUtil::max(maxX, x);
maxY = MathUtil::max(maxY, y);
}
}
_minX = minX;
_minY = minY;
_maxX = maxX;
_maxY = maxY;
}
@@ -0,0 +1,600 @@
/******************************************************************************
* 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/SkeletonClipping.h>
#include <spine/ClippingAttachment.h>
#include <spine/Slot.h>
#include <cstring>
using namespace spine;
SkeletonClipping::SkeletonClipping() : _clipAttachment(NULL), _inverse(false) {
_clippingPolygons.ensureCapacity(1);
}
size_t SkeletonClipping::clipStart(Skeleton &skeleton, Slot &slot, ClippingAttachment *clip) {
if (_clipAttachment != NULL) return 0;
int n = (int) clip->getWorldVerticesLength();
if (n < 6) return 0;
_clipAttachment = clip;
_inverse = clip->getInverse();
_clippingPolygons.clear();
clip->computeWorldVertices(skeleton, slot, 0, n, _clippingPolygon.setSize(n, 0).buffer(), 0, 2);
bool convex = makeClockwise(_clippingPolygon);
if (convex || _inverse || clip->getConvex()) {
if (!convex) makeConvex(_clippingPolygon);
_clippingPolygon.add(_clippingPolygon[0]);
_clippingPolygon.add(_clippingPolygon[1]);
_clippingPolygons.add(&_clippingPolygon);
} else {
Array<Array<float> *> &polygons = _triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon));
for (size_t i = 0, n2 = polygons.size(); i < n2; ++i) _clippingPolygons.add(polygons[i]);
}
return _clippingPolygons.size();
}
void SkeletonClipping::clipEnd(Slot &slot) {
if (_clipAttachment != NULL && _clipAttachment->_endSlot == &slot._data) {
clipEnd();
}
}
void SkeletonClipping::clipEnd() {
if (_clipAttachment == NULL) return;
_clipAttachment = NULL;
_clippingPolygons.clear();
}
bool SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength) {
Array<float> &clippedVertices = _clippedVertices;
clippedVertices.clear();
Array<unsigned short> &clippedTriangles = _clippedTriangles;
clippedTriangles.clear();
unsigned short index = 0;
if (_inverse) {
Array<float> *polygon = _clippingPolygons[0];
for (size_t i = 0; i < trianglesLength; i += 3) {
int t = triangles[i] << 1;
float x1 = vertices[t], y1 = vertices[t + 1];
t = triangles[i + 1] << 1;
float x2 = vertices[t], y2 = vertices[t + 1];
t = triangles[i + 2] << 1;
float x3 = vertices[t], y3 = vertices[t + 1];
clipInverse(x1, y1, x2, y2, x3, y3, polygon);
float *iv = _inverseVertices.buffer();
for (size_t offset = 0, nn = _inverseVertices.size(); offset < nn;) {
int polygonSize = (int) iv[offset++];
int vertexCount = polygonSize >> 1;
size_t s = clippedVertices.size();
float *cv = clippedVertices.setSize(s + polygonSize, 0).buffer();
memcpy(cv + s, iv + offset, sizeof(float) * polygonSize);
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3 * (vertexCount - 2), 0).buffer();
for (int ii = 1; ii < vertexCount - 1; ii++, s += 3) {
ct[s] = index;
ct[s + 1] = (unsigned short) (index + ii);
ct[s + 2] = (unsigned short) (index + ii + 1);
}
index += (unsigned short) vertexCount;
offset += polygonSize;
}
}
return true;
}
Array<float> &clipOutput = _clipOutput;
Array<float> **polygons = _clippingPolygons.buffer();
int polygonsCount = (int) _clippingPolygons.size();
bool clipped = false;
for (size_t i = 0; i < trianglesLength; i += 3) {
int t = triangles[i] << 1;
float x1 = vertices[t], y1 = vertices[t + 1];
t = triangles[i + 1] << 1;
float x2 = vertices[t], y2 = vertices[t + 1];
t = triangles[i + 2] << 1;
float x3 = vertices[t], y3 = vertices[t + 1];
for (int p = 0; p < polygonsCount; p++) {
size_t s = clippedVertices.size();
if (clip(x1, y1, x2, y2, x3, y3, polygons[p])) {
int clipOutputLength = (int) clipOutput.size();
if (clipOutputLength == 0) continue;
clipped = true;
int clipOutputCount = clipOutputLength >> 1;
float *cv = clippedVertices.setSize(s + clipOutputLength, 0).buffer();
memcpy(cv + s, clipOutput.buffer(), sizeof(float) * clipOutputLength);
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0).buffer();
for (int ii = 1, nn = clipOutputCount - 1; ii < nn; ii++, s += 3) {
ct[s] = index;
ct[s + 1] = (unsigned short) (index + ii);
ct[s + 2] = (unsigned short) (index + ii + 1);
}
index += (unsigned short) clipOutputCount;
} else {
float *cv = clippedVertices.setSize(s + 6, 0).buffer();
cv[s] = x1;
cv[s + 1] = y1;
cv[s + 2] = x2;
cv[s + 3] = y2;
cv[s + 4] = x3;
cv[s + 5] = y3;
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3, 0).buffer();
ct[s] = index;
ct[s + 1] = (unsigned short) (index + 1);
ct[s + 2] = (unsigned short) (index + 2);
index += 3;
break;
}
}
}
return clipped;
}
bool SkeletonClipping::clipTriangles(Array<float> &vertices, Array<unsigned short> &triangles, Array<float> &uvs, size_t stride) {
return clipTriangles(vertices.buffer(), triangles.buffer(), triangles.size(), uvs.buffer(), stride);
}
bool SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength, float *uvs, size_t stride) {
Array<float> &clippedVertices = _clippedVertices;
clippedVertices.clear();
Array<unsigned short> &clippedTriangles = _clippedTriangles;
clippedTriangles.clear();
_clippedUVs.clear();
unsigned short index = 0;
if (_inverse) {
Array<float> *polygon = _clippingPolygons[0];
for (size_t i = 0; i < trianglesLength; i += 3) {
int t0 = triangles[i], t1 = triangles[i + 1], t2 = triangles[i + 2];
float x1 = vertices[t0 * stride], y1 = vertices[t0 * stride + 1];
float x2 = vertices[t1 * stride], y2 = vertices[t1 * stride + 1];
float x3 = vertices[t2 * stride], y3 = vertices[t2 * stride + 1];
clipInverse(x1, y1, x2, y2, x3, y3, polygon);
size_t nn = _inverseVertices.size();
if (nn == 0) continue;
float u1 = uvs[t0 << 1], v1 = uvs[(t0 << 1) + 1];
float u2 = uvs[t1 << 1], v2 = uvs[(t1 << 1) + 1];
float u3 = uvs[t2 << 1], v3 = uvs[(t2 << 1) + 1];
float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1, d = 1 / (d0 * d2 + d1 * (y1 - y3));
float *iv = _inverseVertices.buffer();
for (size_t offset = 0; offset < nn;) {
int polygonSize = (int) iv[offset++];
int vertexCount = polygonSize >> 1;
size_t s = clippedVertices.size();
float *cv = clippedVertices.setSize(s + polygonSize, 0).buffer();
float *cu = _clippedUVs.setSize(s + polygonSize, 0).buffer();
for (int ii = 0; ii < polygonSize; ii += 2, s += 2) {
float x = iv[offset + ii], y = iv[offset + ii + 1];
cv[s] = x;
cv[s + 1] = y;
float c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b;
cu[s] = u1 * a + u2 * b + u3 * c;
cu[s + 1] = v1 * a + v2 * b + v3 * c;
}
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3 * (vertexCount - 2), 0).buffer();
for (int ii = 1; ii < vertexCount - 1; ii++, s += 3) {
ct[s] = index;
ct[s + 1] = (unsigned short) (index + ii);
ct[s + 2] = (unsigned short) (index + ii + 1);
}
index += (unsigned short) vertexCount;
offset += polygonSize;
}
}
return true;
}
Array<float> &clipOutput = _clipOutput;
Array<float> **polygons = _clippingPolygons.buffer();
int polygonsCount = (int) _clippingPolygons.size();
bool clipped = false;
for (size_t i = 0; i < trianglesLength; i += 3) {
int t = triangles[i];
float x1 = vertices[t * stride], y1 = vertices[t * stride + 1];
float u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1];
t = triangles[i + 1];
float x2 = vertices[t * stride], y2 = vertices[t * stride + 1];
float u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1];
t = triangles[i + 2];
float x3 = vertices[t * stride], y3 = vertices[t * stride + 1];
float u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1];
float d0 = 0, d1 = 0, d2 = 0, d4 = 0, d = 0;
for (int p = 0; p < polygonsCount; p++) {
size_t s = clippedVertices.size();
if (clip(x1, y1, x2, y2, x3, y3, polygons[p])) {
int clipOutputLength = (int) clipOutput.size();
if (clipOutputLength == 0) continue;
clipped = true;
int clipOutputCount = clipOutputLength >> 1;
if (d == 0) {
d0 = y2 - y3;
d1 = x3 - x2;
d2 = x1 - x3;
d4 = y3 - y1;
d = 1 / (d0 * d2 - d1 * d4);
}
float *cv = clippedVertices.setSize(s + clipOutputCount * 2, 0).buffer();
float *cu = _clippedUVs.setSize(s + clipOutputCount * 2, 0).buffer();
for (int ii = 0; ii < clipOutputLength; ii += 2, s += 2) {
float x = clipOutput[ii], y = clipOutput[ii + 1];
cv[s] = x;
cv[s + 1] = y;
float c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b;
cu[s] = u1 * a + u2 * b + u3 * c;
cu[s + 1] = v1 * a + v2 * b + v3 * c;
}
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0).buffer();
clipOutputCount--;
for (int ii = 1; ii < clipOutputCount; ii++, s += 3) {
ct[s] = index;
ct[s + 1] = (unsigned short) (index + ii);
ct[s + 2] = (unsigned short) (index + ii + 1);
}
index += (unsigned short) (clipOutputCount + 1);
} else {
float *cv = clippedVertices.setSize(s + 6, 0).buffer();
cv[s] = x1;
cv[s + 1] = y1;
cv[s + 2] = x2;
cv[s + 3] = y2;
cv[s + 4] = x3;
cv[s + 5] = y3;
float *cu = _clippedUVs.setSize(s + 6, 0).buffer();
cu[s] = u1;
cu[s + 1] = v1;
cu[s + 2] = u2;
cu[s + 3] = v2;
cu[s + 4] = u3;
cu[s + 5] = v3;
s = clippedTriangles.size();
unsigned short *ct = clippedTriangles.setSize(s + 3, 0).buffer();
ct[s] = index;
ct[s + 1] = (unsigned short) (index + 1);
ct[s + 2] = (unsigned short) (index + 2);
index += 3;
break;
}
}
}
return clipped;
}
bool SkeletonClipping::isClipping() {
return _clipAttachment != NULL;
}
Array<float> &SkeletonClipping::getClippedVertices() {
return _clippedVertices;
}
Array<unsigned short> &SkeletonClipping::getClippedTriangles() {
return _clippedTriangles;
}
Array<float> &SkeletonClipping::getClippedUVs() {
return _clippedUVs;
}
bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Array<float> *polygon) {
Array<float> &originalOutput = _clipOutput;
bool clipped = false;
Array<float> *input;
Array<float> *output;
if (polygon->size() % 4 >= 2) {
input = &_clipOutput;
output = &_scratch;
} else {
input = &_scratch;
output = &_clipOutput;
}
float *v = polygon->buffer();
float *iv = input->setSize(8, 0).buffer();
iv[0] = x1;
iv[1] = y1;
iv[2] = x2;
iv[3] = y2;
iv[4] = x3;
iv[5] = y3;
iv[6] = x1;
iv[7] = y1;
output->clear();
int last = (int) polygon->size() - 4;
for (int i = 0;; i += 2) {
float edgeX = v[i], edgeY = v[i + 1], ex = edgeX - v[i + 2], ey = edgeY - v[i + 3];
size_t outputStart = output->size();
iv = input->buffer();
x1 = iv[0];
y1 = iv[1];
float s1 = ey * (edgeX - x1) - ex * (edgeY - y1);
for (size_t ii = 2, nn = input->size() - 2; ii <= nn; ii += 2) {
x2 = iv[ii];
y2 = iv[ii + 1];
float s2 = ey * (edgeX - x2) - ex * (edgeY - y2);
if (s1 > 0) {
if (s2 > 0) {
output->add(x2);
output->add(y2);
} else {
float ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) {
output->add(x1 + ix * t);
output->add(y1 + iy * t);
clipped = true;
} else {
output->add(x2);
output->add(y2);
}
}
} else if (s2 > 0) {
float ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) {
output->add(x1 + ix * t);
output->add(y1 + iy * t);
output->add(x2);
output->add(y2);
clipped = true;
} else {
output->add(x2);
output->add(y2);
}
} else {
clipped = true;
}
x1 = x2;
y1 = y2;
s1 = s2;
}
if (outputStart == output->size()) {
originalOutput.clear();
return true;
}
output->add((*output)[0]);
output->add((*output)[1]);
if (i == last) break;
Array<float> *temp = output;
output = input;
output->clear();
input = temp;
}
if (&originalOutput != output) {
originalOutput.clear();
for (size_t i = 0, n = output->size() - 2; i < n; ++i) originalOutput.add((*output)[i]);
} else {
originalOutput.setSize(originalOutput.size() - 2, 0);
}
return clipped;
}
void SkeletonClipping::clipInverse(float x1, float y1, float x2, float y2, float x3, float y3, Array<float> *polygon) {
_inverseVertices.clear();
_inverseVertices.ensureCapacity(polygon->size() * 3);
int last = (int) polygon->size() - 4;
Array<float> *input;
Array<float> *output;
if (polygon->size() % 4 >= 2) {
input = &_clipOutput;
output = &_scratch;
} else {
input = &_scratch;
output = &_clipOutput;
}
float *v = polygon->buffer();
float *iv = input->setSize(8, 0).buffer();
iv[0] = x1;
iv[1] = y1;
iv[2] = x2;
iv[3] = y2;
iv[4] = x3;
iv[5] = y3;
iv[6] = x1;
iv[7] = y1;
output->clear();
for (int i = 0;; i += 2) {
float edgeX = v[i], edgeY = v[i + 1], ex = edgeX - v[i + 2], ey = edgeY - v[i + 3];
size_t outputStart = output->size(), fragmentStart = _inverseVertices.size();
_inverseVertices.add(0);
iv = input->buffer();
x1 = iv[0];
y1 = iv[1];
float s1 = ey * (edgeX - x1) - ex * (edgeY - y1);
for (size_t ii = 2, nn = input->size() - 2; ii <= nn; ii += 2) {
x2 = iv[ii];
y2 = iv[ii + 1];
float s2 = ey * (edgeX - x2) - ex * (edgeY - y2);
if (s1 > 0) {
if (s2 > 0) {
output->add(x2);
output->add(y2);
} else {
float ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) {
float cx = x1 + ix * t, cy = y1 + iy * t;
output->add(cx);
output->add(cy);
_inverseVertices.add(cx);
_inverseVertices.add(cy);
_inverseVertices.add(x2);
_inverseVertices.add(y2);
} else {
output->add(x2);
output->add(y2);
}
}
} else if (s2 > 0) {
float ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) {
float cx = x1 + ix * t, cy = y1 + iy * t;
_inverseVertices.add(cx);
_inverseVertices.add(cy);
output->add(cx);
output->add(cy);
output->add(x2);
output->add(y2);
} else {
output->add(x2);
output->add(y2);
}
} else {
_inverseVertices.add(x2);
_inverseVertices.add(y2);
}
x1 = x2;
y1 = y2;
s1 = s2;
}
int fragmentSize = (int) _inverseVertices.size() - (int) fragmentStart - 1;
if (fragmentSize >= 6)
_inverseVertices[fragmentStart] = (float) fragmentSize;
else
_inverseVertices.setSize(fragmentStart, 0);
if (outputStart == output->size()) break;
output->add((*output)[0]);
output->add((*output)[1]);
if (i == last) break;
Array<float> *temp = output;
output = input;
output->clear();
input = temp;
}
}
bool SkeletonClipping::makeClockwise(Array<float> &polygon) {
float *v = polygon.buffer();
int n = (int) polygon.size();
bool noCW = true, noCCW = true;
float area = 0, prevX = v[n - 2], prevY = v[n - 1], currX = v[0], currY = v[1];
for (int i = 2; i < n; i += 2) {
float nextX = v[i], nextY = v[i + 1];
area += currX * nextY - nextX * currY;
float cross = (currX - prevX) * (nextY - currY) - (currY - prevY) * (nextX - currX);
noCCW &= cross <= 0;
noCW &= cross >= 0;
prevX = currX;
prevY = currY;
currX = nextX;
currY = nextY;
}
area += currX * v[1] - v[0] * currY;
float cross = (currX - prevX) * (v[1] - currY) - (currY - prevY) * (v[0] - currX);
noCCW &= cross <= 0;
noCW &= cross >= 0;
if (area >= 0) {
for (int i = 0, lastX = n - 2, half = n >> 1; i < half; i += 2) {
float x = v[i], y = v[i + 1];
int other = lastX - i;
v[i] = v[other];
v[i + 1] = v[other + 1];
v[other] = x;
v[other + 1] = y;
}
return noCW;
}
return noCCW;
}
void SkeletonClipping::makeConvex(Array<float> &polygon) {
int n = (int) polygon.size();
float *v = polygon.buffer();
float *sorted = _clipOutput.setSize(n, 0).buffer();
for (int i = 0; i < n; i++) sorted[i] = v[i];
for (int i = 2; i < n; i += 2) {
float x = sorted[i], y = sorted[i + 1];
int p = i - 2;
for (; p >= 0 && (sorted[p] > x || (sorted[p] == x && sorted[p + 1] > y)); p -= 2) {
sorted[p + 2] = sorted[p];
sorted[p + 3] = sorted[p + 1];
}
sorted[p + 2] = x;
sorted[p + 3] = y;
}
v[0] = sorted[0];
v[1] = sorted[1];
v[2] = sorted[2];
v[3] = sorted[3];
int s = 4;
for (int i = 4; i < n; i += 2, s += 2) {
float x = sorted[i], y = sorted[i + 1];
while ((v[s - 2] - v[s - 4]) * (y - v[s - 3]) - (v[s - 1] - v[s - 3]) * (x - v[s - 4]) >= 0) {
s -= 2;
if (s == 2) break;
}
v[s] = x;
v[s + 1] = y;
}
v[s] = sorted[n - 4];
v[s + 1] = sorted[n - 3];
int t = s;
s += 2;
for (int i = n - 6; i >= 0; i -= 2, s += 2) {
float x = sorted[i], y = sorted[i + 1];
while ((v[s - 2] - v[s - 4]) * (y - v[s - 3]) - (v[s - 1] - v[s - 3]) * (x - v[s - 4]) >= 0) {
s -= 2;
if (s == t) break;
}
v[s] = x;
v[s + 1] = y;
}
polygon.setSize(s - 2, 0);
}
@@ -0,0 +1,218 @@
/******************************************************************************
* 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/SkeletonData.h>
#include <spine/Animation.h>
#include <spine/BoneData.h>
#include <spine/ConstraintData.h>
#include <spine/EventData.h>
#include <spine/IkConstraintData.h>
#include <spine/PathConstraintData.h>
#include <spine/PhysicsConstraintData.h>
#include <spine/Skin.h>
#include <spine/SlotData.h>
#include <spine/TransformConstraintData.h>
#include <spine/SliderData.h>
#include <spine/ArrayUtils.h>
using namespace spine;
SkeletonData::SkeletonData()
: _name(), _defaultSkin(NULL), _x(0), _y(0), _width(0), _height(0), _referenceScale(100), _version(), _hash(), _fps(30), _imagesPath(),
_audioPath() {
}
SkeletonData::~SkeletonData() {
ArrayUtils::deleteElements(_bones);
ArrayUtils::deleteElements(_slots);
ArrayUtils::deleteElements(_skins);
_defaultSkin = NULL;
ArrayUtils::deleteElements(_events);
ArrayUtils::deleteElements(_animations);
ArrayUtils::deleteElements(_constraints);
for (size_t i = 0; i < _strings.size(); i++) {
SpineExtension::free(_strings[i], __FILE__, __LINE__);
}
}
BoneData *SkeletonData::findBone(const String &boneName) {
return ArrayUtils::findWithName(_bones, boneName);
}
SlotData *SkeletonData::findSlot(const String &slotName) {
return ArrayUtils::findWithName(_slots, slotName);
}
Skin *SkeletonData::findSkin(const String &skinName) {
return ArrayUtils::findWithName(_skins, skinName);
}
EventData *SkeletonData::findEvent(const String &eventDataName) {
return ArrayUtils::findWithName(_events, eventDataName);
}
Animation *SkeletonData::findAnimation(const String &animationName) {
return ArrayUtils::findWithName(_animations, animationName);
}
Array<Animation *> &SkeletonData::findSliderAnimations(Array<Animation *> &animations) {
for (size_t i = 0, n = _constraints.size(); i < n; i++) {
ConstraintData *constraint = _constraints[i];
if (constraint->getRTTI().instanceOf(SliderData::rtti)) {
SliderData *data = static_cast<SliderData *>(constraint);
if (data->_animation != NULL) animations.add(data->_animation);
}
}
return animations;
}
const String &SkeletonData::getName() {
return _name;
}
void SkeletonData::setName(const String &inValue) {
_name = inValue;
}
Array<BoneData *> &SkeletonData::getBones() {
return _bones;
}
Array<SlotData *> &SkeletonData::getSlots() {
return _slots;
}
Array<Skin *> &SkeletonData::getSkins() {
return _skins;
}
Skin *SkeletonData::getDefaultSkin() {
return _defaultSkin;
}
void SkeletonData::setDefaultSkin(Skin *inValue) {
_defaultSkin = inValue;
}
Array<EventData *> &SkeletonData::getEvents() {
return _events;
}
Array<Animation *> &SkeletonData::getAnimations() {
return _animations;
}
float SkeletonData::getX() {
return _x;
}
void SkeletonData::setX(float inValue) {
_x = inValue;
}
float SkeletonData::getY() {
return _y;
}
void SkeletonData::setY(float inValue) {
_y = inValue;
}
float SkeletonData::getWidth() {
return _width;
}
void SkeletonData::setWidth(float inValue) {
_width = inValue;
}
float SkeletonData::getHeight() {
return _height;
}
void SkeletonData::setHeight(float inValue) {
_height = inValue;
}
float SkeletonData::getReferenceScale() {
return _referenceScale;
}
void SkeletonData::setReferenceScale(float inValue) {
_referenceScale = inValue;
}
const String &SkeletonData::getVersion() {
return _version;
}
void SkeletonData::setVersion(const String &inValue) {
_version = inValue;
}
const String &SkeletonData::getHash() {
return _hash;
}
void SkeletonData::setHash(const String &inValue) {
_hash = inValue;
}
const String &SkeletonData::getImagesPath() {
return _imagesPath;
}
void SkeletonData::setImagesPath(const String &inValue) {
_imagesPath = inValue;
}
const String &SkeletonData::getAudioPath() {
return _audioPath;
}
void SkeletonData::setAudioPath(const String &inValue) {
_audioPath = inValue;
}
float SkeletonData::getFps() {
return _fps;
}
void SkeletonData::setFps(float inValue) {
_fps = inValue;
}
Array<ConstraintData *> &SkeletonData::getConstraints() {
return _constraints;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,249 @@
/******************************************************************************
* 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/SkeletonRenderer.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/RegionAttachment.h>
#include <spine/MeshAttachment.h>
#include <spine/ClippingAttachment.h>
#include <spine/Bone.h>
using namespace spine;
SkeletonRenderer::SkeletonRenderer() : _allocator(4096), _worldVertices(), _quadIndices(), _clipping(), _renderCommands() {
_quadIndices.add(0);
_quadIndices.add(1);
_quadIndices.add(2);
_quadIndices.add(2);
_quadIndices.add(3);
_quadIndices.add(0);
}
SkeletonRenderer::~SkeletonRenderer() {
}
static RenderCommand *createRenderCommand(BlockAllocator &allocator, int numVertices, int32_t numIndices, BlendMode blendMode, void *texture) {
RenderCommand *cmd = allocator.allocate<RenderCommand>(1);
cmd->positions = allocator.allocate<float>(numVertices << 1);
cmd->uvs = allocator.allocate<float>(numVertices << 1);
cmd->colors = allocator.allocate<uint32_t>(numVertices);
cmd->darkColors = allocator.allocate<uint32_t>(numVertices);
cmd->numVertices = numVertices;
cmd->indices = allocator.allocate<uint16_t>(numIndices);
cmd->numIndices = numIndices;
cmd->blendMode = blendMode;
cmd->texture = texture;
cmd->next = nullptr;
return cmd;
}
static RenderCommand *batchSubCommands(BlockAllocator &allocator, Array<RenderCommand *> &commands, int first, int last, int numVertices,
int numIndices) {
RenderCommand *batched = createRenderCommand(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->texture);
float *positions = batched->positions;
float *uvs = batched->uvs;
uint32_t *colors = batched->colors;
uint32_t *darkColors = batched->darkColors;
uint16_t *indices = batched->indices;
int indicesOffset = 0;
for (int i = first; i <= last; i++) {
RenderCommand *cmd = commands[i];
memcpy(positions, cmd->positions, sizeof(float) * 2 * cmd->numVertices);
memcpy(uvs, cmd->uvs, sizeof(float) * 2 * cmd->numVertices);
memcpy(colors, cmd->colors, sizeof(int32_t) * cmd->numVertices);
memcpy(darkColors, cmd->darkColors, sizeof(int32_t) * cmd->numVertices);
for (int ii = 0; ii < cmd->numIndices; ii++) indices[ii] = cmd->indices[ii] + indicesOffset;
indicesOffset += cmd->numVertices;
positions += 2 * cmd->numVertices;
uvs += 2 * cmd->numVertices;
colors += cmd->numVertices;
darkColors += cmd->numVertices;
indices += cmd->numIndices;
}
return batched;
}
static RenderCommand *batchCommands(BlockAllocator &allocator, Array<RenderCommand *> &commands) {
if (commands.size() == 0) return nullptr;
RenderCommand *root = nullptr;
RenderCommand *last = nullptr;
RenderCommand *first = commands[0];
int startIndex = 0;
int i = 1;
int numVertices = first->numVertices;
int numIndices = first->numIndices;
while (i <= (int) commands.size()) {
RenderCommand *cmd = i < (int) commands.size() ? commands[i] : nullptr;
if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) {
i++;
continue;
}
if (cmd != nullptr && cmd->texture == first->texture && cmd->blendMode == first->blendMode && cmd->colors[0] == first->colors[0] &&
cmd->darkColors[0] == first->darkColors[0] && numIndices + cmd->numIndices < 0xffff) {
numVertices += cmd->numVertices;
numIndices += cmd->numIndices;
} else {
RenderCommand *batched = batchSubCommands(allocator, commands, startIndex, i - 1, numVertices, numIndices);
if (!last) {
root = last = batched;
} else {
last->next = batched;
last = batched;
}
if (i == (int) commands.size()) break;
first = commands[i];
startIndex = i;
numVertices = first->numVertices;
numIndices = first->numIndices;
}
i++;
}
return root;
}
RenderCommand *SkeletonRenderer::render(Skeleton &skeleton) {
_allocator.compress();
_renderCommands.clear();
SkeletonClipping &clipper = _clipping;
Array<Slot *> &drawOrder = skeleton.getDrawOrder().getAppliedPose();
for (unsigned i = 0; i < drawOrder.size(); ++i) {
Slot &slot = *drawOrder[i];
Attachment *attachment = slot.getAppliedPose().getAttachment();
if (!attachment) {
clipper.clipEnd(slot);
continue;
}
// Early out if the slot color is 0 or the bone is not active
if ((slot.getAppliedPose().getColor().a == 0 || !slot.getBone().isActive()) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) {
clipper.clipEnd(slot);
continue;
}
Array<float> *worldVertices = &_worldVertices;
Array<unsigned short> *quadIndices = &_quadIndices;
Array<float> *vertices = worldVertices;
int32_t verticesCount;
Array<float> *uvs;
Array<unsigned short> *indices;
int32_t indicesCount;
Color *attachmentColor;
void *texture;
if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
RegionAttachment *regionAttachment = (RegionAttachment *) attachment;
attachmentColor = &regionAttachment->getColor();
if (attachmentColor->a == 0) {
clipper.clipEnd(slot);
continue;
}
Sequence &sequence = regionAttachment->getSequence();
int sequenceIndex = sequence.resolveIndex(slot.getAppliedPose());
TextureRegion *region = sequence.getRegion(sequenceIndex);
worldVertices->setSize(8, 0);
regionAttachment->computeWorldVertices(slot, regionAttachment->getOffsets(slot.getAppliedPose()), *worldVertices, 0, 2);
verticesCount = 4;
uvs = &sequence.getUVs(sequenceIndex);
indices = quadIndices;
indicesCount = 6;
texture = region->_rendererObject;
} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
MeshAttachment *mesh = (MeshAttachment *) attachment;
attachmentColor = &mesh->getColor();
if (attachmentColor->a == 0) {
clipper.clipEnd(slot);
continue;
}
Sequence &sequence = mesh->getSequence();
int sequenceIndex = sequence.resolveIndex(slot.getAppliedPose());
TextureRegion *region = sequence.getRegion(sequenceIndex);
worldVertices->setSize(mesh->getWorldVerticesLength(), 0);
mesh->computeWorldVertices(skeleton, slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2);
verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1);
uvs = &sequence.getUVs(sequenceIndex);
indices = &mesh->getTriangles();
indicesCount = (int32_t) indices->size();
texture = region->_rendererObject;
} else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) {
ClippingAttachment *clip = (ClippingAttachment *) slot.getAppliedPose().getAttachment();
clipper.clipStart(skeleton, slot, clip);
continue;
} else
continue;
uint8_t r = static_cast<uint8_t>(skeleton.getColor().r * slot.getAppliedPose().getColor().r * attachmentColor->r * 255);
uint8_t g = static_cast<uint8_t>(skeleton.getColor().g * slot.getAppliedPose().getColor().g * attachmentColor->g * 255);
uint8_t b = static_cast<uint8_t>(skeleton.getColor().b * slot.getAppliedPose().getColor().b * attachmentColor->b * 255);
uint8_t a = static_cast<uint8_t>(skeleton.getColor().a * slot.getAppliedPose().getColor().a * attachmentColor->a * 255);
uint32_t color = (a << 24) | (r << 16) | (g << 8) | b;
uint32_t darkColor = 0xff000000;
if (slot.getAppliedPose().hasDarkColor()) {
Color &slotDarkColor = slot.getAppliedPose().getDarkColor();
darkColor = 0xff000000 | (static_cast<uint8_t>(slotDarkColor.r * 255) << 16) | (static_cast<uint8_t>(slotDarkColor.g * 255) << 8) |
static_cast<uint8_t>(slotDarkColor.b * 255);
}
if (clipper.isClipping()) {
clipper.clipTriangles(*worldVertices, *indices, *uvs, 2);
vertices = &clipper.getClippedVertices();
verticesCount = (int32_t) (clipper.getClippedVertices().size() >> 1);
uvs = &clipper.getClippedUVs();
indices = &clipper.getClippedTriangles();
indicesCount = (int32_t) (clipper.getClippedTriangles().size());
}
RenderCommand *cmd = createRenderCommand(_allocator, verticesCount, indicesCount, slot.getData().getBlendMode(), texture);
_renderCommands.add(cmd);
memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float));
memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float));
for (int ii = 0; ii < verticesCount; ii++) {
cmd->colors[ii] = color;
cmd->darkColors[ii] = darkColor;
}
memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t));
clipper.clipEnd(slot);
}
clipper.clipEnd();
return batchCommands(_allocator, _renderCommands);
}
@@ -0,0 +1,192 @@
/******************************************************************************
* 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/Skin.h>
#include <spine/Attachment.h>
#include <spine/MeshAttachment.h>
#include <spine/Skeleton.h>
#include <spine/ConstraintData.h>
#include <spine/Slot.h>
#include <assert.h>
using namespace spine;
Skin::AttachmentMap::AttachmentMap() {
}
static void disposeAttachment(Attachment *attachment) {
if (!attachment) return;
attachment->dereference();
if (attachment->getRefCount() == 0) delete attachment;
}
void Skin::AttachmentMap::put(size_t slotIndex, const String &placeholder, Attachment *attachment) {
if (slotIndex >= _buckets.size()) _buckets.setSize(slotIndex + 1, Array<Entry>());
Array<Entry> &bucket = _buckets[slotIndex];
int existing = findInBucket(bucket, placeholder);
attachment->reference();
if (existing >= 0) {
disposeAttachment(bucket[existing]._attachment);
bucket[existing]._attachment = attachment;
} else {
bucket.add(Entry(slotIndex, placeholder, attachment));
}
}
Attachment *Skin::AttachmentMap::get(size_t slotIndex, const String &placeholder) {
if (slotIndex >= _buckets.size()) return NULL;
int existing = findInBucket(_buckets[slotIndex], placeholder);
return existing >= 0 ? _buckets[slotIndex][existing]._attachment : NULL;
}
void Skin::AttachmentMap::remove(size_t slotIndex, const String &placeholder) {
if (slotIndex >= _buckets.size()) return;
int existing = findInBucket(_buckets[slotIndex], placeholder);
if (existing >= 0) {
disposeAttachment(_buckets[slotIndex][existing]._attachment);
_buckets[slotIndex].removeAt(existing);
}
}
int Skin::AttachmentMap::findInBucket(Array<Entry> &bucket, const String &placeholder) {
for (size_t i = 0; i < bucket.size(); i++)
if (bucket[i]._placeholder == placeholder) return (int) i;
return -1;
}
Skin::AttachmentMap::Entries Skin::AttachmentMap::getEntries() {
return Skin::AttachmentMap::Entries(_buckets);
}
Skin::Skin(const String &name) : _name(name), _attachments(), _color(0.99607843f, 0.61960787f, 0.30980393f, 1) {
assert(_name.length() > 0);
}
Skin::~Skin() {
Skin::AttachmentMap::Entries entries = _attachments.getEntries();
while (entries.hasNext()) {
Skin::AttachmentMap::Entry entry = entries.next();
disposeAttachment(entry._attachment);
}
}
void Skin::setAttachment(size_t slotIndex, const String &placeholder, Attachment *attachment) {
_attachments.put(slotIndex, placeholder, attachment);
}
Attachment *Skin::getAttachment(size_t slotIndex, const String &placeholder) {
return _attachments.get(slotIndex, placeholder);
}
void Skin::removeAttachment(size_t slotIndex, const String &placeholder) {
_attachments.remove(slotIndex, placeholder);
}
void Skin::findNamesForSlot(size_t slotIndex, Array<String> &names) {
Skin::AttachmentMap::Entries entries = _attachments.getEntries();
while (entries.hasNext()) {
Skin::AttachmentMap::Entry &entry = entries.next();
if (entry._slotIndex == slotIndex) {
names.add(entry._placeholder);
}
}
}
void Skin::findAttachmentsForSlot(size_t slotIndex, Array<Attachment *> &attachments) {
Skin::AttachmentMap::Entries entries = _attachments.getEntries();
while (entries.hasNext()) {
Skin::AttachmentMap::Entry &entry = entries.next();
if (entry._slotIndex == slotIndex) attachments.add(entry._attachment);
}
}
const String &Skin::getName() {
return _name;
}
Skin::AttachmentMap::Entries Skin::getAttachments() {
return _attachments.getEntries();
}
void Skin::attachAll(Skeleton &skeleton, Skin &oldSkin) {
Array<Slot *> &slots = skeleton.getSlots();
Skin::AttachmentMap::Entries entries = oldSkin.getAttachments();
while (entries.hasNext()) {
Skin::AttachmentMap::Entry &entry = entries.next();
int slotIndex = (int) entry._slotIndex;
Slot *slot = slots[slotIndex];
if (slot->getPose().getAttachment() == entry._attachment) {
Attachment *attachment = getAttachment(slotIndex, entry._placeholder);
if (attachment) slot->getPose().setAttachment(attachment);
}
}
}
void Skin::addSkin(Skin &other) {
for (size_t i = 0; i < other.getBones().size(); i++)
if (!_bones.contains(other.getBones()[i])) _bones.add(other.getBones()[i]);
for (size_t i = 0; i < other.getConstraints().size(); i++)
if (!_constraints.contains(other.getConstraints()[i])) _constraints.add(other.getConstraints()[i]);
AttachmentMap::Entries entries = other.getAttachments();
while (entries.hasNext()) {
AttachmentMap::Entry &entry = entries.next();
setAttachment(entry._slotIndex, entry._placeholder, entry._attachment);
}
}
void Skin::copySkin(Skin &other) {
for (size_t i = 0; i < other.getBones().size(); i++)
if (!_bones.contains(other.getBones()[i])) _bones.add(other.getBones()[i]);
for (size_t i = 0; i < other.getConstraints().size(); i++)
if (!_constraints.contains(other.getConstraints()[i])) _constraints.add(other.getConstraints()[i]);
AttachmentMap::Entries entries = other.getAttachments();
while (entries.hasNext()) {
AttachmentMap::Entry &entry = entries.next();
if (entry._attachment->getRTTI().isExactly(MeshAttachment::rtti))
setAttachment(entry._slotIndex, entry._placeholder, &static_cast<MeshAttachment *>(entry._attachment)->newLinkedMesh());
else
setAttachment(entry._slotIndex, entry._placeholder, &entry._attachment->copy());
}
}
Array<ConstraintData *> &Skin::getConstraints() {
return _constraints;
}
Array<BoneData *> &Skin::getBones() {
return _bones;
}
@@ -0,0 +1,182 @@
/******************************************************************************
* 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/Slider.h>
#include <spine/Skeleton.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/BonePose.h>
#include <spine/Animation.h>
#include <spine/Timeline.h>
#include <spine/SlotTimeline.h>
#include <spine/SlotCurveTimeline.h>
#include <spine/AttachmentTimeline.h>
#include <spine/SequenceTimeline.h>
#include <spine/ColorTimeline.h>
#include <spine/ConstraintTimeline.h>
#include <spine/ConstraintTimeline1.h>
#include <spine/PhysicsConstraintTimeline.h>
#include <spine/IkConstraintTimeline.h>
#include <spine/PathConstraintMixTimeline.h>
#include <spine/TransformConstraintTimeline.h>
#include <spine/DrawOrderTimeline.h>
#include <spine/DrawOrderFolderTimeline.h>
#include <spine/SliderData.h>
#include <spine/SliderPose.h>
#include <spine/Slot.h>
#include <spine/IkConstraint.h>
#include <spine/PathConstraint.h>
#include <spine/PhysicsConstraint.h>
#include <spine/TransformConstraint.h>
#include <spine/MathUtil.h>
using namespace spine;
RTTI_IMPL(Slider, Constraint)
namespace {
SlotTimeline *asSlotTimeline(Timeline *timeline) {
if (timeline->getRTTI().isExactly(AlphaTimeline::rtti)) return static_cast<AlphaTimeline *>(timeline);
if (timeline->getRTTI().instanceOf(SlotCurveTimeline::rtti)) return static_cast<SlotCurveTimeline *>(timeline);
if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) return static_cast<AttachmentTimeline *>(timeline);
if (timeline->getRTTI().isExactly(SequenceTimeline::rtti)) return static_cast<SequenceTimeline *>(timeline);
return NULL;
}
ConstraintTimeline *asConstraintTimeline(Timeline *timeline) {
if (timeline->getRTTI().instanceOf(ConstraintTimeline1::rtti)) return static_cast<ConstraintTimeline1 *>(timeline);
if (timeline->getRTTI().instanceOf(PhysicsConstraintTimeline::rtti)) return static_cast<PhysicsConstraintTimeline *>(timeline);
if (timeline->getRTTI().isExactly(PhysicsConstraintResetTimeline::rtti)) return static_cast<PhysicsConstraintResetTimeline *>(timeline);
if (timeline->getRTTI().isExactly(IkConstraintTimeline::rtti)) return static_cast<IkConstraintTimeline *>(timeline);
if (timeline->getRTTI().isExactly(PathConstraintMixTimeline::rtti)) return static_cast<PathConstraintMixTimeline *>(timeline);
if (timeline->getRTTI().isExactly(TransformConstraintTimeline::rtti)) return static_cast<TransformConstraintTimeline *>(timeline);
return NULL;
}
void constrain(Skeleton &skeleton, Constraint *constraint) {
if (constraint->getRTTI().instanceOf(IkConstraint::rtti))
skeleton.constrained(*static_cast<IkConstraint *>(constraint));
else if (constraint->getRTTI().instanceOf(PathConstraint::rtti))
skeleton.constrained(*static_cast<PathConstraint *>(constraint));
else if (constraint->getRTTI().instanceOf(PhysicsConstraint::rtti))
skeleton.constrained(*static_cast<PhysicsConstraint *>(constraint));
else if (constraint->getRTTI().instanceOf(Slider::rtti))
skeleton.constrained(*static_cast<Slider *>(constraint));
else if (constraint->getRTTI().instanceOf(TransformConstraint::rtti))
skeleton.constrained(*static_cast<TransformConstraint *>(constraint));
}
}
float Slider::_offsets[6];
Slider::Slider(SliderData &data, Skeleton &skeleton) : SliderBase(data), _bone(NULL) {
if (data._bone != NULL) {
_bone = skeleton._bones[data._bone->getIndex()];
}
}
Slider &Slider::copy(Skeleton &skeleton) {
Slider *copy = new (__FILE__, __LINE__) Slider(_data, skeleton);
copy->_pose.set(_pose);
return *copy;
}
void Slider::update(Skeleton &skeleton, Physics physics) {
SliderPose &p = *_appliedPose;
if (p._mix == 0) return;
Animation *animation = _data._animation;
if (_bone != NULL) {
if (!_bone->isActive()) return;
if (_data._local) _bone->_appliedPose->validateLocalTransform(skeleton);
p._time = _data._offset +
(_data._property->value(skeleton, *_bone->_appliedPose, _data._local, _offsets) - _data._property->_offset) * _data._scale;
if (_data._loop)
p._time = animation->getDuration() + MathUtil::fmod(p._time, animation->getDuration());
else
p._time = MathUtil::max(0.0f, p._time);
}
Array<Bone *> &bones = skeleton._bones;
const Array<int> &indices = animation->getBones();
for (size_t i = 0, n = indices.size(); i < n; i++) bones[indices[i]]->_appliedPose->modifyLocal(skeleton);
animation->apply(skeleton, p._time, p._time, _data._loop, NULL, p._mix, MixFrom_Current, _data._additive, false, true);
}
void Slider::sort(Skeleton &skeleton) {
if (_bone != NULL && !_data._local) skeleton.sortBone(_bone);
skeleton._updateCache.add(this);
Array<Bone *> &bones = skeleton._bones;
const Array<int> &indices = _data._animation->getBones();
for (size_t i = 0, n = indices.size(); i < n; i++) {
Bone *bone = bones[indices[i]];
bone->_sorted = false;
skeleton.sortReset(bone->getChildren());
skeleton.constrained(*bone);
}
Array<Timeline *> &timelines = _data._animation->getTimelines();
Array<Slot *> &slots = skeleton._slots;
Array<Constraint *> &constraints = skeleton._constraints;
Array<PhysicsConstraint *> &physics = skeleton._physics;
size_t physicsCount = physics.size();
for (size_t i = 0, n = timelines.size(); i < n; i++) {
Timeline *t = timelines[i];
SlotTimeline *slotTimeline = asSlotTimeline(t);
if (slotTimeline) {
skeleton.constrained(*slots[slotTimeline->getSlotIndex()]);
} else if (t->getRTTI().instanceOf(DrawOrderTimeline::rtti) || t->getRTTI().instanceOf(DrawOrderFolderTimeline::rtti)) {
skeleton.getDrawOrder().constrained();
} else {
ConstraintTimeline *constraintTimeline = asConstraintTimeline(t);
if (!constraintTimeline) continue;
if (t->getRTTI().instanceOf(PhysicsConstraintTimeline::rtti) && constraintTimeline->getConstraintIndex() == -1) {
for (size_t ii = 0; ii < physicsCount; ii++) skeleton.constrained(*physics[ii]);
} else {
int index = constraintTimeline->getConstraintIndex();
if (index != -1) constrain(skeleton, constraints[index]);
}
}
}
}
bool Slider::isSourceActive() {
return _bone == NULL || _bone->isActive();
}
Bone &Slider::getBone() {
return *_bone;
}
void Slider::setBone(Bone &bone) {
_bone = &bone;
}
@@ -0,0 +1,127 @@
/******************************************************************************
* 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/SliderData.h>
#include <spine/Slider.h>
#include <spine/Skeleton.h>
#include <spine/ArrayUtils.h>
#include <spine/TransformConstraintData.h>
using namespace spine;
RTTI_IMPL(SliderData, ConstraintData)
SliderData::SliderData(const String &name)
: ConstraintDataGeneric<Slider, SliderPose>(name), _animation(NULL), _additive(false), _loop(false), _bone(NULL), _property(NULL), _offset(0.0f),
_scale(0.0f), _max(0.0f), _local(false) {
}
SliderData::~SliderData() {
if (_property) {
ArrayUtils::deleteElements(_property->_to);
delete _property;
_property = NULL;
}
}
Constraint &SliderData::create(Skeleton &skeleton) {
return *(new (__FILE__, __LINE__) Slider(*this, skeleton));
}
Animation &SliderData::getAnimation() {
return *_animation;
}
void SliderData::setAnimation(Animation &animation) {
_animation = &animation;
}
bool SliderData::getAdditive() {
return _additive;
}
void SliderData::setAdditive(bool additive) {
_additive = additive;
}
bool SliderData::getLoop() {
return _loop;
}
void SliderData::setLoop(bool loop) {
_loop = loop;
}
BoneData *SliderData::getBone() {
return _bone;
}
void SliderData::setBone(BoneData *bone) {
_bone = bone;
}
FromProperty *SliderData::getProperty() {
return _property;
}
void SliderData::setProperty(FromProperty *property) {
_property = property;
}
float SliderData::getScale() {
return _scale;
}
void SliderData::setScale(float scale) {
_scale = scale;
}
float SliderData::getOffset() {
return _offset;
}
void SliderData::setOffset(float offset) {
_offset = offset;
}
float SliderData::getMax() {
return _max;
}
void SliderData::setMax(float max) {
_max = max;
}
bool SliderData::getLocal() {
return _local;
}
void SliderData::setLocal(bool local) {
_local = local;
}
@@ -0,0 +1,67 @@
/******************************************************************************
* 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/SliderMixTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Slider.h>
#include <spine/SliderData.h>
#include <spine/SliderPose.h>
#include <spine/Property.h>
using namespace spine;
RTTI_IMPL(SliderMixTimeline, ConstraintTimeline1)
SliderMixTimeline::SliderMixTimeline(size_t frameCount, size_t bezierCount, int sliderIndex)
: ConstraintTimeline1(frameCount, bezierCount, sliderIndex, Property_SliderMix) {
PropertyId ids[] = {((PropertyId) Property_SliderMix << 32) | sliderIndex};
setPropertyIds(ids, 1);
_additive = true;
}
SliderMixTimeline::~SliderMixTimeline() {
}
void SliderMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
Slider *constraint = (Slider *) skeleton._constraints[_constraintIndex];
if (constraint->isActive()) {
SliderPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
SliderData &data = constraint->_data;
pose._mix = getAbsoluteValue(time, alpha, from, add, pose._mix, data._setupPose._mix);
}
}
@@ -0,0 +1,59 @@
/******************************************************************************
* 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/SliderPose.h>
using namespace spine;
SliderPose::SliderPose() : _time(0), _mix(0) {
}
SliderPose::~SliderPose() {
}
void SliderPose::set(SliderPose &pose) {
_time = pose._time;
_mix = pose._mix;
}
float SliderPose::getTime() {
return _time;
}
void SliderPose::setTime(float time) {
this->_time = time;
}
float SliderPose::getMix() {
return _mix;
}
void SliderPose::setMix(float mix) {
this->_mix = mix;
}
@@ -0,0 +1,66 @@
/******************************************************************************
* 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/SliderTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Slider.h>
#include <spine/SliderData.h>
#include <spine/SliderPose.h>
#include <spine/Property.h>
using namespace spine;
RTTI_IMPL(SliderTimeline, ConstraintTimeline1)
SliderTimeline::SliderTimeline(size_t frameCount, size_t bezierCount, int sliderIndex)
: ConstraintTimeline1(frameCount, bezierCount, sliderIndex, Property_SliderTime) {
PropertyId ids[] = {((PropertyId) Property_SliderTime << 32) | sliderIndex};
setPropertyIds(ids, 1);
}
SliderTimeline::~SliderTimeline() {
}
void SliderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
Slider *constraint = (Slider *) skeleton._constraints[_constraintIndex];
if (constraint->isActive()) {
SliderPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
SliderData &data = constraint->_data;
pose._time = getAbsoluteValue(time, alpha, from, add, pose._time, data._setupPose._time);
}
}
@@ -0,0 +1,64 @@
/******************************************************************************
* 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/Slot.h>
#include <spine/Bone.h>
#include <spine/Skeleton.h>
#include <spine/SlotData.h>
#include <spine/SlotPose.h>
#include <spine/Color.h>
using namespace spine;
Slot::Slot(SlotData &data, Skeleton &skeleton)
: PosedGeneric<SlotData, SlotPose, SlotPose>(data), _skeleton(skeleton), _bone(*skeleton.getBones()[data._boneData._index]), _attachmentState(0) {
if (data.getSetupPose().hasDarkColor()) {
_pose._hasDarkColor = true;
_constrainedPose._hasDarkColor = true;
}
setupPose();
}
Bone &Slot::getBone() {
return _bone;
}
void Slot::setupPose() {
_pose._color.set(_data._setupPose._color);
if (_pose._hasDarkColor) _pose._darkColor.set(_data._setupPose._darkColor);
_pose._sequenceIndex = _data._setupPose._sequenceIndex;
if (_data._attachmentName.isEmpty())
_pose.setAttachment(NULL);
else {
_pose._attachment = NULL;
_pose.setAttachment(_skeleton.getAttachment(_data._index, _data._attachmentName));
}
}
@@ -0,0 +1,64 @@
/******************************************************************************
* 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/SlotCurveTimeline.h>
#include <spine/Bone.h>
#include <spine/Skeleton.h>
#include <spine/Slot.h>
#include <spine/SlotPose.h>
using namespace spine;
RTTI_IMPL_MULTI(SlotCurveTimeline, CurveTimeline, SlotTimeline)
SlotCurveTimeline::SlotCurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount, int slotIndex)
: CurveTimeline(frameCount, frameEntries, bezierCount), SlotTimeline(), _slotIndex(slotIndex) {
}
SlotCurveTimeline::~SlotCurveTimeline() {
}
int SlotCurveTimeline::getSlotIndex() {
return _slotIndex;
}
void SlotCurveTimeline::setSlotIndex(int inValue) {
_slotIndex = inValue;
}
void SlotCurveTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add, bool out,
bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
Slot *slot = skeleton._slots[_slotIndex];
if (slot->_bone.isActive()) _apply(*slot, appliedPose ? *slot->_appliedPose : slot->_pose, time, alpha, from, add);
}
@@ -0,0 +1,73 @@
/******************************************************************************
* 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/SlotData.h>
#include <spine/SlotPose.h>
#include <spine/BoneData.h>
#include <assert.h>
using namespace spine;
SlotData::SlotData(int index, const String &name, BoneData &boneData)
: PosedDataGeneric<SlotPose>(name), _index(index), _boneData(boneData), _attachmentName(), _blendMode(BlendMode_Normal), _visible(true) {
assert(index >= 0);
}
int SlotData::getIndex() {
return _index;
}
BoneData &SlotData::getBoneData() {
return _boneData;
}
void SlotData::setAttachmentName(const String &attachmentName) {
_attachmentName = attachmentName;
}
const String &SlotData::getAttachmentName() {
return _attachmentName;
}
BlendMode SlotData::getBlendMode() {
return _blendMode;
}
void SlotData::setBlendMode(BlendMode blendMode) {
_blendMode = blendMode;
}
bool SlotData::getVisible() {
return _visible;
}
void SlotData::setVisible(bool visible) {
_visible = visible;
}
@@ -0,0 +1,91 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, 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, 0), _hasDarkColor(false), _attachment(nullptr), _sequenceIndex(0) {
}
SlotPose::~SlotPose() {
}
void SlotPose::set(SlotPose &pose) {
_color.set(pose._color);
if (pose._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;
// Check if we need to clear deform based on timeline attachment.
if (!attachment || !_attachment || attachment->getTimelineAttachment() != _attachment->getTimelineAttachment()) _deform.clear();
_attachment = attachment;
_sequenceIndex = -1;
}
int SlotPose::getSequenceIndex() {
return _sequenceIndex;
}
void SlotPose::setSequenceIndex(int sequenceIndex) {
_sequenceIndex = sequenceIndex;
}
Array<float> &SlotPose::getDeform() {
return _deform;
}
@@ -0,0 +1,40 @@
/******************************************************************************
* 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/SlotTimeline.h>
using namespace spine;
RTTI_IMPL_NOPARENT(SlotTimeline)
SlotTimeline::SlotTimeline() {
}
SlotTimeline::~SlotTimeline() {
}
@@ -0,0 +1,63 @@
/******************************************************************************
* 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/SpineObject.h>
#include <spine/Extension.h>
using namespace spine;
void *SpineObject::operator new(size_t sz) {
return SpineExtension::getInstance()->_calloc(sz, __FILE__, __LINE__);
}
void *SpineObject::operator new(size_t sz, const char *file, int line) {
return SpineExtension::getInstance()->_calloc(sz, file, line);
}
void *SpineObject::operator new(size_t sz, void *ptr) {
SP_UNUSED(sz);
return ptr;
}
void SpineObject::operator delete(void *p, const char *file, int line) {
SpineExtension::free(p, file, line);
}
void SpineObject::operator delete(void *p, void *mem) {
SP_UNUSED(mem);
SpineExtension::free(p, __FILE__, __LINE__);
}
void SpineObject::operator delete(void *p) {
SpineExtension::free(p, __FILE__, __LINE__);
}
SpineObject::~SpineObject() {
SpineExtension::beforeFree(this);
}
@@ -0,0 +1,38 @@
/******************************************************************************
* 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/TextureLoader.h>
namespace spine {
TextureLoader::TextureLoader() {
}
TextureLoader::~TextureLoader() {
}
}// namespace spine
@@ -0,0 +1,34 @@
/******************************************************************************
* 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/TextureRegion.h>
using namespace spine;
RTTI_IMPL_NOPARENT(TextureRegion)
@@ -0,0 +1,73 @@
/******************************************************************************
* 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/Timeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
using namespace spine;
RTTI_IMPL_NOPARENT(Timeline)
Timeline::Timeline(size_t frameCount, size_t frameEntries)
: _propertyIds(), _frames(), _frameEntries(frameEntries), _additive(false), _instant(false) {
_frames.setSize(frameCount * frameEntries, 0);
}
Timeline::~Timeline() {
}
Array<PropertyId> &Timeline::getPropertyIds() {
return _propertyIds;
}
void Timeline::setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount) {
_propertyIds.clear();
_propertyIds.ensureCapacity(propertyIdsCount);
for (size_t i = 0; i < propertyIdsCount; i++) {
_propertyIds.add(propertyIds[i]);
}
}
size_t Timeline::getFrameCount() {
return _frames.size() / _frameEntries;
}
Array<float> &Timeline::getFrames() {
return _frames;
}
size_t Timeline::getFrameEntries() {
return _frameEntries;
}
float Timeline::getDuration() {
return _frames[_frames.size() - getFrameEntries()];
}
@@ -0,0 +1,138 @@
/******************************************************************************
* 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/TransformConstraint.h>
#include <spine/Bone.h>
#include <spine/BonePose.h>
#include <spine/Skeleton.h>
#include <spine/TransformConstraintData.h>
#include <spine/MathUtil.h>
#include <spine/BoneData.h>
using namespace spine;
RTTI_IMPL(TransformConstraint, Constraint)
TransformConstraint::TransformConstraint(TransformConstraintData &data, Skeleton &skeleton) : TransformConstraintBase(data) {
_bones.ensureCapacity(data.getBones().size());
for (size_t i = 0; i < data.getBones().size(); i++) {
BoneData *boneData = data.getBones()[i];
_bones.add(&skeleton._bones[boneData->getIndex()]->_constrainedPose);
}
_source = skeleton._bones[data._source->getIndex()];
}
TransformConstraint &TransformConstraint::copy(Skeleton &skeleton) {
TransformConstraint *copy = new (__FILE__, __LINE__) TransformConstraint(_data, skeleton);
copy->_pose.set(_pose);
return *copy;
}
/// Applies the constraint to the constrained bones.
void TransformConstraint::update(Skeleton &skeleton, Physics physics) {
TransformConstraintPose &p = *_appliedPose;
if (p._mixRotate == 0 && p._mixX == 0 && p._mixY == 0 && p._mixScaleX == 0 && p._mixScaleY == 0 && p._mixShearY == 0) return;
TransformConstraintData &data = _data;
bool localSource = data._localSource, localTarget = data._localTarget, additive = data._additive, clamp = data._clamp;
float *offsets = data._offsets;
BonePose &source = *_source->_appliedPose;
if (localSource) {
source.validateLocalTransform(skeleton);
}
FromProperty **fromItems = data._properties.buffer();
size_t fn = data._properties.size();
BonePose **bones = _bones.buffer();
for (size_t i = 0, n = _bones.size(); i < n; i++) {
BonePose *bone = bones[i];
if (localTarget) {
bone->modifyLocal(skeleton);
} else {
bone->modifyWorld(skeleton);
}
for (size_t f = 0; f < fn; f++) {
FromProperty *from = fromItems[f];
float value = from->value(skeleton, source, localSource, offsets) - from->_offset;
Array<ToProperty *> &toProps = from->_to;
ToProperty **toItems = toProps.buffer();
for (size_t t = 0, tn = toProps.size(); t < tn; t++) {
ToProperty *to = toItems[t];
if (to->mix(p) != 0) {
float clamped = to->_offset + value * to->_scale;
if (clamp) {
if (to->_offset < to->_max)
clamped = MathUtil::clamp(clamped, to->_offset, to->_max);
else
clamped = MathUtil::clamp(clamped, to->_max, to->_offset);
}
to->apply(skeleton, p, *bone, clamped, localTarget, additive);
}
}
}
}
}
void TransformConstraint::sort(Skeleton &skeleton) {
if (!_data._localSource) skeleton.sortBone(_source);
BonePose **bones = _bones.buffer();
size_t boneCount = _bones.size();
bool worldTarget = !_data._localTarget;
if (worldTarget) {
for (size_t i = 0; i < boneCount; i++) skeleton.sortBone(bones[i]->_bone);
}
skeleton._updateCache.add(this);
for (size_t i = 0; i < boneCount; i++) {
Bone *bone = bones[i]->_bone;
skeleton.sortReset(bone->_children);
skeleton.constrained(*bone);
}
for (size_t i = 0; i < boneCount; i++) bones[i]->_bone->_sorted = worldTarget;
}
bool TransformConstraint::isSourceActive() {
return _source->_active;
}
/// The bones that will be modified by this transform constraint.
Array<BonePose *> &TransformConstraint::getBones() {
return _bones;
}
/// The bone whose world transform will be copied to the constrained bones.
Bone &TransformConstraint::getSource() {
return *_source;
}
void TransformConstraint::setSource(Bone &source) {
_source = &source;
}
@@ -0,0 +1,363 @@
/******************************************************************************
* 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/TransformConstraintData.h>
#include <spine/TransformConstraint.h>
#include <spine/BoneData.h>
#include <spine/BonePose.h>
#include <spine/TransformConstraintPose.h>
#include <spine/MathUtil.h>
#include <spine/ArrayUtils.h>
#include <spine/Skeleton.h>
using namespace spine;
// Define static constants for TransformConstraintData
const int TransformConstraintData::ROTATION = 0;
const int TransformConstraintData::X = 1;
const int TransformConstraintData::Y = 2;
const int TransformConstraintData::SCALEX = 3;
const int TransformConstraintData::SCALEY = 4;
const int TransformConstraintData::SHEARY = 5;
RTTI_IMPL(TransformConstraintData, ConstraintData)
RTTI_IMPL_NOPARENT(FromProperty)
RTTI_IMPL_NOPARENT(ToProperty)
RTTI_IMPL(FromRotate, FromProperty)
RTTI_IMPL(ToRotate, ToProperty)
RTTI_IMPL(FromX, FromProperty)
RTTI_IMPL(ToX, ToProperty)
RTTI_IMPL(FromY, FromProperty)
RTTI_IMPL(ToY, ToProperty)
RTTI_IMPL(FromScaleX, FromProperty)
RTTI_IMPL(ToScaleX, ToProperty)
RTTI_IMPL(FromScaleY, FromProperty)
RTTI_IMPL(ToScaleY, ToProperty)
RTTI_IMPL(FromShearY, FromProperty)
RTTI_IMPL(ToShearY, ToProperty)
TransformConstraintData::TransformConstraintData(const String &name)
: ConstraintDataGeneric<TransformConstraint, TransformConstraintPose>(name), _source(NULL), _localSource(false), _localTarget(false),
_additive(false), _clamp(false) {
for (int i = 0; i < 6; i++) {
_offsets[i] = 0;
}
}
Array<BoneData *> &TransformConstraintData::getBones() {
return _bones;
}
BoneData &TransformConstraintData::getSource() {
return *_source;
}
void TransformConstraintData::setSource(BoneData &source) {
_source = &source;
}
float TransformConstraintData::getOffsetRotation() {
return _offsets[ROTATION];
}
void TransformConstraintData::setOffsetRotation(float offsetRotation) {
_offsets[ROTATION] = offsetRotation;
}
float TransformConstraintData::getOffsetX() {
return _offsets[X];
}
void TransformConstraintData::setOffsetX(float offsetX) {
_offsets[X] = offsetX;
}
float TransformConstraintData::getOffsetY() {
return _offsets[Y];
}
void TransformConstraintData::setOffsetY(float offsetY) {
_offsets[Y] = offsetY;
}
float TransformConstraintData::getOffsetScaleX() {
return _offsets[SCALEX];
}
void TransformConstraintData::setOffsetScaleX(float offsetScaleX) {
_offsets[SCALEX] = offsetScaleX;
}
float TransformConstraintData::getOffsetScaleY() {
return _offsets[SCALEY];
}
void TransformConstraintData::setOffsetScaleY(float offsetScaleY) {
_offsets[SCALEY] = offsetScaleY;
}
float TransformConstraintData::getOffsetShearY() {
return _offsets[SHEARY];
}
void TransformConstraintData::setOffsetShearY(float offsetShearY) {
_offsets[SHEARY] = offsetShearY;
}
bool TransformConstraintData::getLocalSource() {
return _localSource;
}
void TransformConstraintData::setLocalSource(bool localSource) {
_localSource = localSource;
}
bool TransformConstraintData::getLocalTarget() {
return _localTarget;
}
void TransformConstraintData::setLocalTarget(bool localTarget) {
_localTarget = localTarget;
}
bool TransformConstraintData::getAdditive() {
return _additive;
}
void TransformConstraintData::setAdditive(bool additive) {
_additive = additive;
}
bool TransformConstraintData::getClamp() {
return _clamp;
}
void TransformConstraintData::setClamp(bool clamp) {
_clamp = clamp;
}
Array<FromProperty *> &TransformConstraintData::getProperties() {
return _properties;
}
FromProperty::FromProperty() : SpineObject(), _offset(0) {
}
FromProperty::~FromProperty() {
}
ToProperty::ToProperty() : SpineObject(), _offset(0), _max(0), _scale(1) {
}
ToProperty::~ToProperty() {
}
float FromRotate::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
if (local) return source._rotation + offsets[TransformConstraintData::ROTATION];
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY();
float value = MathUtil::atan2(source._c / sy, source._a / sx) * MathUtil::Rad_Deg +
((source._a * source._d - source._b * source._c) * sx * sy > 0 ? offsets[TransformConstraintData::ROTATION]
: -offsets[TransformConstraintData::ROTATION]);
if (value < 0) value += 360;
return value;
}
float ToRotate::mix(TransformConstraintPose &pose) {
return pose._mixRotate;
}
void ToRotate::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
bone._rotation += (additive ? value : value - bone._rotation) * pose._mixRotate;
} else {
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY(), ix = 1 / sx, iy = 1 / sy;
float a = bone._a * ix, b = bone._b * ix, c = bone._c * iy, d = bone._d * iy;
value *= MathUtil::Deg_Rad;
if (!additive) value -= MathUtil::atan2(c, a);
if (value > MathUtil::Pi)
value -= MathUtil::Pi_2;
else if (value < -MathUtil::Pi)//
value += MathUtil::Pi_2;
value *= pose._mixRotate;
float cosVal = MathUtil::cos(value), sinVal = MathUtil::sin(value);
bone._a = (cosVal * a - sinVal * c) * sx;
bone._b = (cosVal * b - sinVal * d) * sx;
bone._c = (sinVal * a + cosVal * c) * sy;
bone._d = (sinVal * b + cosVal * d) * sy;
}
}
float FromX::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
return local
? source._x + offsets[TransformConstraintData::X]
: (offsets[TransformConstraintData::X] * source._a + offsets[TransformConstraintData::Y] * source._b + source._worldX) / skeleton.getScaleX();
}
float ToX::mix(TransformConstraintPose &pose) {
return pose._mixX;
}
void ToX::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
bone._x += (additive ? value : value - bone._x) * pose._mixX;
} else {
if (!additive) value -= bone._worldX / skeleton.getScaleX();
bone._worldX += value * pose._mixX * skeleton.getScaleX();
}
}
float FromY::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
return local
? source._y + offsets[TransformConstraintData::Y]
: (offsets[TransformConstraintData::X] * source._c + offsets[TransformConstraintData::Y] * source._d + source._worldY) / skeleton.getScaleY();
}
float ToY::mix(TransformConstraintPose &pose) {
return pose._mixY;
}
void ToY::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
bone._y += (additive ? value : value - bone._y) * pose._mixY;
} else {
if (!additive) value -= bone._worldY / skeleton.getScaleY();
bone._worldY += value * pose._mixY * skeleton.getScaleY();
}
}
float FromScaleX::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
if (local) return source._scaleX + offsets[TransformConstraintData::SCALEX];
float a = source._a / skeleton.getScaleX(), c = source._c / skeleton.getScaleY();
return MathUtil::sqrt(a * a + c * c) + offsets[TransformConstraintData::SCALEX];
}
float ToScaleX::mix(TransformConstraintPose &pose) {
return pose._mixScaleX;
}
void ToScaleX::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
if (additive)
bone._scaleX *= 1 + (value - 1) * pose._mixScaleX;
else if (bone._scaleX != 0)//
bone._scaleX += (value - bone._scaleX) * pose._mixScaleX;
} else if (additive) {
float s = 1 + (value - 1) * pose._mixScaleX;
bone._a *= s;
bone._c *= s;
} else {
float a = bone._a / skeleton.getScaleX(), c = bone._c / skeleton.getScaleY(), s = MathUtil::sqrt(a * a + c * c);
if (s != 0) {
s = 1 + (value - s) * pose._mixScaleX / s;
bone._a *= s;
bone._c *= s;
}
}
}
float FromScaleY::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
if (local) return source._scaleY + offsets[TransformConstraintData::SCALEY];
float b = source._b / skeleton.getScaleX(), d = source._d / skeleton.getScaleY();
return MathUtil::sqrt(b * b + d * d) + offsets[TransformConstraintData::SCALEY];
}
float ToScaleY::mix(TransformConstraintPose &pose) {
return pose._mixScaleY;
}
void ToScaleY::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
if (additive)
bone._scaleY *= 1 + (value - 1) * pose._mixScaleY;
else if (bone._scaleY != 0)//
bone._scaleY += (value - bone._scaleY) * pose._mixScaleY;
} else if (additive) {
float s = 1 + (value - 1) * pose._mixScaleY;
bone._b *= s;
bone._d *= s;
} else {
float b = bone._b / skeleton.getScaleX(), d = bone._d / skeleton.getScaleY(), s = MathUtil::sqrt(b * b + d * d);
if (s != 0) {
s = 1 + (value - s) * pose._mixScaleY / s;
bone._b *= s;
bone._d *= s;
}
}
}
float FromShearY::value(Skeleton &skeleton, BonePose &source, bool local, float *offsets) {
if (local) return source._shearY + offsets[TransformConstraintData::SHEARY];
float ix = 1 / skeleton.getScaleX(), iy = 1 / skeleton.getScaleY();
return (MathUtil::atan2(source._d * iy, source._b * ix) - MathUtil::atan2(source._c * iy, source._a * ix)) * MathUtil::Rad_Deg - 90 +
offsets[TransformConstraintData::SHEARY];
}
float ToShearY::mix(TransformConstraintPose &pose) {
return pose._mixShearY;
}
void ToShearY::apply(Skeleton &skeleton, TransformConstraintPose &pose, BonePose &bone, float value, bool local, bool additive) {
if (local) {
if (!additive) value -= bone._shearY;
bone._shearY += value * pose._mixShearY;
} else {
float sx = skeleton.getScaleX(), sy = skeleton.getScaleY(), b = bone._b / sx, d = bone._d / sy, by = MathUtil::atan2(d, b);
value = (value + 90) * MathUtil::Deg_Rad;
if (additive)
value -= MathUtil::Pi / 2;
else {
value -= by - MathUtil::atan2(bone._c / sy, bone._a / sx);
if (value > MathUtil::Pi)
value -= MathUtil::Pi_2;
else if (value < -MathUtil::Pi)//
value += MathUtil::Pi_2;
}
value = by + value * pose._mixShearY;
float s = MathUtil::sqrt(b * b + d * d);
bone._b = MathUtil::cos(value) * s * sx;
bone._d = MathUtil::sin(value) * s * sy;
}
}
TransformConstraintData::~TransformConstraintData() {
// Clean up all FromProperty objects, which will in turn clean up their ToProperty objects
for (size_t i = 0; i < _properties.size(); i++) {
FromProperty *fromProp = _properties[i];
if (fromProp) {
// Clean up the ToProperty objects in the FromProperty
ArrayUtils::deleteElements(fromProp->_to);
delete fromProp;
}
}
_properties.clear();
}
Constraint &TransformConstraintData::create(Skeleton &skeleton) {
return *(new (__FILE__, __LINE__) TransformConstraint(*this, skeleton));
}
@@ -0,0 +1,95 @@
/******************************************************************************
* 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/TransformConstraintPose.h>
using namespace spine;
TransformConstraintPose::TransformConstraintPose() : _mixRotate(0), _mixX(0), _mixY(0), _mixScaleX(0), _mixScaleY(0), _mixShearY(0) {
}
TransformConstraintPose::~TransformConstraintPose() {
}
void TransformConstraintPose::set(TransformConstraintPose &pose) {
_mixRotate = pose._mixRotate;
_mixX = pose._mixX;
_mixY = pose._mixY;
_mixScaleX = pose._mixScaleX;
_mixScaleY = pose._mixScaleY;
_mixShearY = pose._mixShearY;
}
float TransformConstraintPose::getMixRotate() {
return _mixRotate;
}
void TransformConstraintPose::setMixRotate(float mixRotate) {
this->_mixRotate = mixRotate;
}
float TransformConstraintPose::getMixX() {
return _mixX;
}
void TransformConstraintPose::setMixX(float mixX) {
this->_mixX = mixX;
}
float TransformConstraintPose::getMixY() {
return _mixY;
}
void TransformConstraintPose::setMixY(float mixY) {
this->_mixY = mixY;
}
float TransformConstraintPose::getMixScaleX() {
return _mixScaleX;
}
void TransformConstraintPose::setMixScaleX(float mixScaleX) {
this->_mixScaleX = mixScaleX;
}
float TransformConstraintPose::getMixScaleY() {
return _mixScaleY;
}
void TransformConstraintPose::setMixScaleY(float mixScaleY) {
this->_mixScaleY = mixScaleY;
}
float TransformConstraintPose::getMixShearY() {
return _mixShearY;
}
void TransformConstraintPose::setMixShearY(float mixShearY) {
this->_mixShearY = mixShearY;
}
@@ -0,0 +1,160 @@
/******************************************************************************
* 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/TransformConstraintTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Property.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
#include <spine/TransformConstraint.h>
#include <spine/TransformConstraintData.h>
#include <spine/TransformConstraintPose.h>
using namespace spine;
RTTI_IMPL_MULTI(TransformConstraintTimeline, CurveTimeline, ConstraintTimeline)
TransformConstraintTimeline::TransformConstraintTimeline(size_t frameCount, size_t bezierCount, int transformConstraintIndex)
: CurveTimeline(frameCount, TransformConstraintTimeline::ENTRIES, bezierCount), ConstraintTimeline(), _constraintIndex(transformConstraintIndex) {
PropertyId ids[] = {((PropertyId) Property_TransformConstraint << 32) | transformConstraintIndex};
setPropertyIds(ids, 1);
_additive = true;
}
TransformConstraintTimeline::~TransformConstraintTimeline() {
}
void TransformConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Array<Event *> *events, float alpha, MixFrom from, bool add,
bool out, bool appliedPose) {
SP_UNUSED(lastTime);
SP_UNUSED(events);
SP_UNUSED(out);
TransformConstraint *constraint = (TransformConstraint *) skeleton._constraints[_constraintIndex];
if (!constraint->isActive()) return;
TransformConstraintPose &pose = appliedPose ? *constraint->_appliedPose : constraint->_pose;
if (time < _frames[0]) {
TransformConstraintPose &setup = constraint->_data._setupPose;
switch (from) {
case MixFrom_Setup:
pose._mixRotate = setup._mixRotate;
pose._mixX = setup._mixX;
pose._mixY = setup._mixY;
pose._mixScaleX = setup._mixScaleX;
pose._mixScaleY = setup._mixScaleY;
pose._mixShearY = setup._mixShearY;
break;
case MixFrom_First:
pose._mixRotate += (setup._mixRotate - pose._mixRotate) * alpha;
pose._mixX += (setup._mixX - pose._mixX) * alpha;
pose._mixY += (setup._mixY - pose._mixY) * alpha;
pose._mixScaleX += (setup._mixScaleX - pose._mixScaleX) * alpha;
pose._mixScaleY += (setup._mixScaleY - pose._mixScaleY) * alpha;
pose._mixShearY += (setup._mixShearY - pose._mixShearY) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float rotate, x, y, scaleX, scaleY, shearY;
int i = Animation::search(_frames, time, ENTRIES);
int curveType = (int) _curves[i / ENTRIES];
switch (curveType) {
case LINEAR: {
float before = _frames[i];
rotate = _frames[i + ROTATE];
x = _frames[i + X];
y = _frames[i + Y];
scaleX = _frames[i + SCALEX];
scaleY = _frames[i + SCALEY];
shearY = _frames[i + SHEARY];
float t = (time - before) / (_frames[i + ENTRIES] - before);
rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t;
x += (_frames[i + ENTRIES + X] - x) * t;
y += (_frames[i + ENTRIES + Y] - y) * t;
scaleX += (_frames[i + ENTRIES + SCALEX] - scaleX) * t;
scaleY += (_frames[i + ENTRIES + SCALEY] - scaleY) * t;
shearY += (_frames[i + ENTRIES + SHEARY] - shearY) * t;
break;
}
case STEPPED: {
rotate = _frames[i + ROTATE];
x = _frames[i + X];
y = _frames[i + Y];
scaleX = _frames[i + SCALEX];
scaleY = _frames[i + SCALEY];
shearY = _frames[i + SHEARY];
break;
}
default: {
rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER);
x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER);
y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER);
scaleX = getBezierValue(time, i, SCALEX, curveType + BEZIER_SIZE * 3 - BEZIER);
scaleY = getBezierValue(time, i, SCALEY, curveType + BEZIER_SIZE * 4 - BEZIER);
shearY = getBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER);
}
}
TransformConstraintPose &base = from == MixFrom_Setup ? constraint->_data._setupPose : pose;
if (add) {
pose._mixRotate = base._mixRotate + rotate * alpha;
pose._mixX = base._mixX + x * alpha;
pose._mixY = base._mixY + y * alpha;
pose._mixScaleX = base._mixScaleX + scaleX * alpha;
pose._mixScaleY = base._mixScaleY + scaleY * alpha;
pose._mixShearY = base._mixShearY + shearY * alpha;
} else {
pose._mixRotate = base._mixRotate + (rotate - base._mixRotate) * alpha;
pose._mixX = base._mixX + (x - base._mixX) * alpha;
pose._mixY = base._mixY + (y - base._mixY) * alpha;
pose._mixScaleX = base._mixScaleX + (scaleX - base._mixScaleX) * alpha;
pose._mixScaleY = base._mixScaleY + (scaleY - base._mixScaleY) * alpha;
pose._mixShearY = base._mixShearY + (shearY - base._mixShearY) * alpha;
}
}
void TransformConstraintTimeline::setFrame(int frame, float time, float mixRotate, float mixX, float mixY, float mixScaleX, float mixScaleY,
float mixShearY) {
frame *= ENTRIES;
_frames[frame] = time;
_frames[frame + ROTATE] = mixRotate;
_frames[frame + X] = mixX;
_frames[frame + Y] = mixY;
_frames[frame + SCALEX] = mixScaleX;
_frames[frame + SCALEY] = mixScaleY;
_frames[frame + SHEARY] = mixShearY;
}
@@ -0,0 +1,123 @@
/******************************************************************************
* 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/TranslateTimeline.h>
#include <spine/Event.h>
#include <spine/Skeleton.h>
#include <spine/Animation.h>
#include <spine/Bone.h>
#include <spine/BoneData.h>
#include <spine/Slot.h>
#include <spine/SlotData.h>
using namespace spine;
RTTI_IMPL(TranslateTimeline, BoneTimeline2)
TranslateTimeline::TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline2(frameCount, bezierCount, boneIndex, Property_X, Property_Y) {
}
void TranslateTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
if (time < _frames[0]) {
switch (from) {
case MixFrom_Setup:
pose._x = setup._x;
pose._y = setup._y;
break;
case MixFrom_First:
pose._x += (setup._x - pose._x) * alpha;
pose._y += (setup._y - pose._y) * alpha;
break;
case MixFrom_Current:
break;
}
return;
}
float x, y;
int i = Animation::search(_frames, time, BoneTimeline2::ENTRIES);
int curveType = (int) _curves[i / BoneTimeline2::ENTRIES];
switch (curveType) {
case CurveTimeline::LINEAR: {
float before = _frames[i];
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
float t = (time - before) / (_frames[i + BoneTimeline2::ENTRIES] - before);
x += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE1] - x) * t;
y += (_frames[i + BoneTimeline2::ENTRIES + BoneTimeline2::VALUE2] - y) * t;
break;
}
case CurveTimeline::STEPPED: {
x = _frames[i + BoneTimeline2::VALUE1];
y = _frames[i + BoneTimeline2::VALUE2];
break;
}
default: {
x = getBezierValue(time, i, BoneTimeline2::VALUE1, curveType - BoneTimeline2::BEZIER);
y = getBezierValue(time, i, BoneTimeline2::VALUE2, curveType + BoneTimeline2::BEZIER_SIZE - BoneTimeline2::BEZIER);
}
}
if (from == MixFrom_Setup) {
pose._x = setup._x + x * alpha;
pose._y = setup._y + y * alpha;
} else if (add) {
pose._x += x * alpha;
pose._y += y * alpha;
} else {
pose._x += (setup._x + x - pose._x) * alpha;
pose._y += (setup._y + y - pose._y) * alpha;
}
}
RTTI_IMPL(TranslateXTimeline, BoneTimeline1)
TranslateXTimeline::TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_X) {
}
void TranslateXTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
pose._x = getRelativeValue(time, alpha, from, add, pose._x, setup._x);
}
RTTI_IMPL(TranslateYTimeline, BoneTimeline1)
TranslateYTimeline::TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex)
: BoneTimeline1(frameCount, bezierCount, boneIndex, Property_Y) {
}
void TranslateYTimeline::_apply(BonePose &pose, BonePose &setup, float time, float alpha, MixFrom from, bool add, bool out) {
SP_UNUSED(out);
pose._y = getRelativeValue(time, alpha, from, add, pose._y, setup._y);
}
@@ -0,0 +1,258 @@
/******************************************************************************
* 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/Triangulator.h>
#include <spine/MathUtil.h>
using namespace spine;
Triangulator::~Triangulator() {
ArrayUtils::deleteElements(_convexPolygons);
ArrayUtils::deleteElements(_convexPolygonsIndices);
}
Array<int> &Triangulator::triangulate(Array<float> &verticesArray) {
float *vertices = verticesArray.buffer();
int vertexCount = (int) (verticesArray.size() >> 1);
Array<int> &indicesArray = _indices;
indicesArray.clear();
int *indices = indicesArray.setSize(vertexCount, 0).buffer();
for (int i = 0; i < vertexCount; i++) indices[i] = i;
Array<bool> &isConcaveArray = _isConcaveArray;
bool *isConcaveItems = isConcaveArray.setSize(vertexCount, false).buffer();
for (int i = 0; i < vertexCount; i++) isConcaveItems[i] = isConcave(i, vertexCount, vertices, indices);
Array<int> &triangles = _triangles;
triangles.clear();
triangles.ensureCapacity(MathUtil::max(0, vertexCount - 2) * 3);
while (vertexCount > 3) {
int previous = vertexCount - 1, i = 0, next = 1;
while (true) {
if (!isConcaveItems[i]) {
int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1;
float p1x = vertices[p1], p1y = vertices[p1 + 1];
float p2x = vertices[p2], p2y = vertices[p2 + 1];
float p3x = vertices[p3], p3y = vertices[p3 + 1];
bool ear = true;
for (int ii = next + 1 < vertexCount ? next + 1 : 0; ii != previous;) {
if (isConcaveItems[ii]) {
int v = indices[ii] << 1;
float vx = vertices[v], vy = vertices[v + 1];
if (positiveArea(p3x, p3y, p1x, p1y, vx, vy) && positiveArea(p1x, p1y, p2x, p2y, vx, vy) &&
positiveArea(p2x, p2y, p3x, p3y, vx, vy)) {
ear = false;
break;
}
}
if (++ii == vertexCount) ii = 0;
}
if (ear) break;
}
if (next == 0) {
do {
if (!isConcaveItems[i]) break;
i--;
} while (i > 0);
previous = i > 0 ? i - 1 : vertexCount - 1;
next = i + 1 < vertexCount ? i + 1 : 0;
break;
}
previous = i;
i = next;
if (++next == vertexCount) next = 0;
}
triangles.add(indices[previous]);
triangles.add(indices[i]);
triangles.add(indices[next]);
indicesArray.removeAt(i);
isConcaveArray.removeAt(i);
vertexCount--;
indices = indicesArray.buffer();
isConcaveItems = isConcaveArray.buffer();
int previousIndex = i > 0 ? i - 1 : vertexCount - 1;
int nextIndex = i < vertexCount ? i : 0;
isConcaveItems[previousIndex] = isConcave(previousIndex, vertexCount, vertices, indices);
isConcaveItems[nextIndex] = isConcave(nextIndex, vertexCount, vertices, indices);
}
if (vertexCount == 3) {
triangles.add(indices[2]);
triangles.add(indices[0]);
triangles.add(indices[1]);
}
return triangles;
}
Array<Array<float> *> &Triangulator::decompose(Array<float> &verticesArray, Array<int> &triangles) {
float *vertices = verticesArray.buffer();
Array<Array<float> *> &convexPolygons = _convexPolygons;
for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) _polygonPool.free(convexPolygons[i]);
convexPolygons.clear();
Array<Array<int> *> &convexPolygonsIndices = _convexPolygonsIndices;
for (size_t i = 0, n = convexPolygonsIndices.size(); i < n; ++i) _polygonIndicesPool.free(convexPolygonsIndices[i]);
convexPolygonsIndices.clear();
Array<int> *polygonIndices = _polygonIndicesPool.obtain();
polygonIndices->clear();
Array<float> *polygon = _polygonPool.obtain();
polygon->clear();
int fanBaseIndex = -1, lastWinding = 0;
int *trianglesItems = triangles.buffer();
for (int i = 0, n = (int) triangles.size(); i < n; i += 3) {
int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[i + 2] << 1;
float x1 = vertices[t1], y1 = vertices[t1 + 1];
float x2 = vertices[t2], y2 = vertices[t2 + 1];
float x3 = vertices[t3], y3 = vertices[t3 + 1];
if (fanBaseIndex == t1) {
int o = (int) polygon->size() - 4;
float *p = polygon->buffer();
if (winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3) == lastWinding && winding(x3, y3, p[0], p[1], p[2], p[3]) == lastWinding) {
polygon->add(x3);
polygon->add(y3);
polygonIndices->add(t3);
continue;
}
}
if (polygon->size() > 0) {
convexPolygons.add(polygon);
convexPolygonsIndices.add(polygonIndices);
polygon = _polygonPool.obtain();
polygonIndices = _polygonIndicesPool.obtain();
}
polygon->clear();
polygon->add(x1);
polygon->add(y1);
polygon->add(x2);
polygon->add(y2);
polygon->add(x3);
polygon->add(y3);
polygonIndices->clear();
polygonIndices->add(t1);
polygonIndices->add(t2);
polygonIndices->add(t3);
lastWinding = winding(x1, y1, x2, y2, x3, y3);
fanBaseIndex = t1;
}
if (polygon->size() > 0) {
convexPolygons.add(polygon);
convexPolygonsIndices.add(polygonIndices);
}
Array<int> **convexPolygonsIndicesItems = convexPolygonsIndices.buffer();
Array<float> **convexPolygonsItems = convexPolygons.buffer();
for (int i = 0, n = (int) convexPolygons.size(); i < n; i++) {
polygonIndices = convexPolygonsIndicesItems[i];
if (polygonIndices->size() == 0) continue;
int firstIndex = (*polygonIndices)[0];
int lastIndex = (*polygonIndices)[polygonIndices->size() - 1];
polygon = convexPolygonsItems[i];
int o = (int) polygon->size() - 4;
float *p = polygon->buffer();
float prevPrevX = p[o], prevPrevY = p[o + 1];
float prevX = p[o + 2], prevY = p[o + 3];
float firstX = p[0], firstY = p[1];
float secondX = p[2], secondY = p[3];
int polygonWinding = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
for (int ii = 0; ii < n; ii++) {
if (ii == i) continue;
Array<int> *otherIndices = convexPolygonsIndicesItems[ii];
if (otherIndices->size() != 3) continue;
int otherFirstIndex = (*otherIndices)[0];
int otherSecondIndex = (*otherIndices)[1];
int otherLastIndex = (*otherIndices)[2];
Array<float> *otherPoly = convexPolygonsItems[ii];
float x3 = (*otherPoly)[otherPoly->size() - 2], y3 = (*otherPoly)[otherPoly->size() - 1];
if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue;
if (winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3) == polygonWinding &&
winding(x3, y3, firstX, firstY, secondX, secondY) == polygonWinding) {
otherPoly->clear();
otherIndices->clear();
polygon->add(x3);
polygon->add(y3);
polygonIndices->add(otherLastIndex);
lastIndex = otherLastIndex;
prevPrevX = prevX;
prevPrevY = prevY;
prevX = x3;
prevY = y3;
ii = -1;
}
}
}
for (int i = (int) convexPolygons.size() - 1; i >= 0; --i) {
polygon = convexPolygonsItems[i];
if (polygon->size() == 0) {
convexPolygons.removeAt(i);
_polygonPool.free(polygon);
polygonIndices = convexPolygonsIndices[i];
convexPolygonsIndices.removeAt(i);
_polygonIndicesPool.free(polygonIndices);
} else {
polygon->add((*polygon)[0]);
polygon->add((*polygon)[1]);
}
}
return convexPolygons;
}
bool Triangulator::isConcave(int index, int vertexCount, const float *vertices, const int *indices) {
int previous = indices[index > 0 ? index - 1 : vertexCount - 1] << 1;
int current = indices[index] << 1;
int next = indices[index + 1 < vertexCount ? index + 1 : 0] << 1;
return !positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next], vertices[next + 1]);
}
bool Triangulator::positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0;
}
int Triangulator::winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0 ? 1 : -1;
}
@@ -0,0 +1,40 @@
/******************************************************************************
* 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/Update.h>
using namespace spine;
RTTI_IMPL_NOPARENT(Update)
Update::Update() {
}
Update::~Update() {
}
@@ -0,0 +1,167 @@
/******************************************************************************
* 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/VertexAttachment.h>
#include <spine/Slot.h>
#include <spine/Bone.h>
#include <spine/Skeleton.h>
using namespace spine;
RTTI_IMPL(VertexAttachment, Attachment)
VertexAttachment::VertexAttachment(const String &name) : Attachment(name), _worldVerticesLength(0), _id(getNextID()) {
}
VertexAttachment::~VertexAttachment() {
}
void VertexAttachment::computeWorldVertices(Skeleton &skeleton, Slot &slot, size_t start, size_t count, Array<float> &worldVertices, size_t offset,
size_t stride) {
computeWorldVertices(skeleton, slot, start, count, worldVertices.buffer(), offset, stride);
}
void VertexAttachment::computeWorldVertices(Skeleton &skeleton, Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset,
size_t stride) {
count = offset + (count >> 1) * stride;
Array<float> *deformArray = &slot.getAppliedPose().getDeform();
Array<float> *vertices = &_vertices;
Array<int> &bones = _bones;
if (bones.size() == 0) {
if (deformArray->size() > 0) vertices = deformArray;
BonePose &bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX();
float y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
for (size_t vv = start, w = offset; w < count; vv += 2, w += stride) {
float vx = (*vertices)[vv];
float vy = (*vertices)[vv + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
int v = 0, skip = 0;
for (size_t i = 0; i < start; i += 2) {
int n = (int) bones[v];
v += n + 1;
skip += n;
}
Array<Bone *> &skeletonBones = skeleton.getBones();
if (deformArray->size() == 0) {
for (size_t w = offset, b = skip * 3; w < count; w += stride) {
float wx = 0, wy = 0;
int n = (int) bones[v++];
n += v;
for (; v < n; v++, b += 3) {
Bone *boneP = skeletonBones[bones[v]];
BonePose &bonePose = boneP->getAppliedPose();
float vx = (*vertices)[b];
float vy = (*vertices)[b + 1];
float weight = (*vertices)[b + 2];
wx += (vx * bonePose.getA() + vy * bonePose.getB() + bonePose.getWorldX()) * weight;
wy += (vx * bonePose.getC() + vy * bonePose.getD() + bonePose.getWorldY()) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
for (size_t w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
float wx = 0, wy = 0;
int n = (int) bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
Bone *boneP = skeletonBones[bones[v]];
BonePose &bonePose = boneP->getAppliedPose();
float vx = (*vertices)[b] + (*deformArray)[f];
float vy = (*vertices)[b + 1] + (*deformArray)[f + 1];
float weight = (*vertices)[b + 2];
wx += (vx * bonePose.getA() + vy * bonePose.getB() + bonePose.getWorldX()) * weight;
wy += (vx * bonePose.getC() + vy * bonePose.getD() + bonePose.getWorldY()) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
int VertexAttachment::getId() {
return _id;
}
Array<int> &VertexAttachment::getBones() {
return _bones;
}
void VertexAttachment::setBones(Array<int> &bones) {
_bones.clearAndAddAll(bones);
}
Array<float> &VertexAttachment::getVertices() {
return _vertices;
}
void VertexAttachment::setVertices(Array<float> &vertices) {
_vertices.clearAndAddAll(vertices);
}
size_t VertexAttachment::getWorldVerticesLength() {
return _worldVerticesLength;
}
void VertexAttachment::setWorldVerticesLength(size_t inValue) {
_worldVerticesLength = inValue;
}
Attachment *VertexAttachment::getTimelineAttachment() {
return Attachment::getTimelineAttachment();
}
void VertexAttachment::setTimelineAttachment(Attachment *attachment) {
Attachment::setTimelineAttachment(attachment);
}
int VertexAttachment::getNextID() {
static int nextID = 0;
return nextID++;
}
void VertexAttachment::copyTo(VertexAttachment &other) {
other._bones.clearAndAddAll(this->_bones);
other._vertices.clearAndAddAll(this->_vertices);
other._worldVerticesLength = this->_worldVerticesLength;
other.setTimelineAttachment(this->getTimelineAttachment());
other.setTimelineSlots(this->getTimelineSlots());
}