diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 816502974b6c..54c75becef5c 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -62,7 +62,7 @@ jobs: - name: Minimal template (target=template_release, tests=yes, everything disabled) cache-name: linux-template-minimal target: template_release - sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no module_spx_enabled=yes + sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no module_spx_enabled=yes module_spine_godot_enabled=yes bin: ./bin/godot.linuxbsd.template_release.x86_64 tests: true artifact: true diff --git a/SConstruct b/SConstruct index 13c073bc46b7..5b9ccde0c931 100644 --- a/SConstruct +++ b/SConstruct @@ -268,6 +268,7 @@ opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True)) opts.Add(BoolVariable("builtin_zstd", "Use the built-in Zstd library", True)) opts.Add(BoolVariable("spx", "Enable the spx library", True)) +opts.Add(BoolVariable("spine_godot", "Enable the spine-godot library", True)) # Compilation environment setup # CXX, CC, and LINK directly set the equivalent `env` values (which may still diff --git a/modules/spine_godot/GodotSpineExtension.cpp b/modules/spine_godot/GodotSpineExtension.cpp new file mode 100644 index 000000000000..9b86b8af43c9 --- /dev/null +++ b/modules/spine_godot/GodotSpineExtension.cpp @@ -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 "GodotSpineExtension.h" +#ifdef SPINE_GODOT_EXTENSION +#include "SpineCommon.h" +#include +#else +#include "core/os/memory.h" +#endif +#include + +spine::SpineExtension *spine::getDefaultExtension() { + return new GodotSpineExtension(); +} + +void *GodotSpineExtension::_alloc(size_t size, const char *file, int line) { + return memalloc(size); +} + +void *GodotSpineExtension::_calloc(size_t size, const char *file, int line) { + auto p = memalloc(size); + memset(p, 0, size); + return p; +} + +void *GodotSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) { + return memrealloc(ptr, size); +} + +void GodotSpineExtension::_free(void *mem, const char *file, int line) { + memfree(mem); +} + +char *GodotSpineExtension::_readFile(const spine::String &path, int *length) { + return NULL; +} diff --git a/modules/spine_godot/GodotSpineExtension.h b/modules/spine_godot/GodotSpineExtension.h new file mode 100644 index 000000000000..f287fe59936b --- /dev/null +++ b/modules/spine_godot/GodotSpineExtension.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include + +class GodotSpineExtension : public spine::SpineExtension { +protected: + virtual void *_alloc(size_t size, const char *file, int line); + + virtual void *_calloc(size_t size, const char *file, int line); + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line); + + virtual void _free(void *mem, const char *file, int line); + + virtual char *_readFile(const spine::String &path, int *length); +}; diff --git a/modules/spine_godot/SCsub b/modules/spine_godot/SCsub new file mode 100644 index 000000000000..b37f237a8699 --- /dev/null +++ b/modules/spine_godot/SCsub @@ -0,0 +1,16 @@ +Import('env') +Import("env_modules") + +# Need to add the include path to env so the vsproj generator consumes it. +if env["vsproj"]: + env.Append(CPPPATH=["#modules/spine_godot/spine-cpp/include"]) + +env_spine_runtime = env.Clone() +env_spine_runtime.Append(CPPPATH=["#modules/spine_godot/spine-cpp/include"]) +env_spine_runtime.add_source_files(env.modules_sources, "spine-cpp/src/spine/*.cpp") +env_spine_runtime.add_source_files(env.modules_sources, "*.cpp") + +# Needed on Clang to not have a gazillion -Winconsistent-missing-override warnings for GDCLASS +# I do not understand why other modules using GDCLASS do not have this issue when compiling. +if not env_spine_runtime.msvc: + env_spine_runtime.Append(CXXFLAGS=["-Wno-inconsistent-missing-override"]) diff --git a/modules/spine_godot/SpineAnimation.cpp b/modules/spine_godot/SpineAnimation.cpp new file mode 100644 index 000000000000..c1b31a6e8f0b --- /dev/null +++ b/modules/spine_godot/SpineAnimation.cpp @@ -0,0 +1,109 @@ +/****************************************************************************** + * 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 "SpineAnimation.h" +#include "SpineSkeleton.h" +#include "SpineEvent.h" +#include "SpineTimeline.h" +#if VERSION_MAJOR == 3 +#include "core/method_bind_ext.gen.inc" +#endif + +void SpineAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_name"), &SpineAnimation::get_name); + ClassDB::bind_method(D_METHOD("get_duration"), &SpineAnimation::get_duration); + ClassDB::bind_method(D_METHOD("set_duration", "duration"), &SpineAnimation::set_duration); + + ClassDB::bind_method(D_METHOD("apply", "skeleton", "last_time", "time", "loop", "events", "alpha", "blend", "direction"), &SpineAnimation::apply); + ClassDB::bind_method(D_METHOD("get_timelines"), &SpineAnimation::get_timelines); + ClassDB::bind_method(D_METHOD("has_timeline", "ids"), &SpineAnimation::has_timeline); +} + +String SpineAnimation::get_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +float SpineAnimation::get_duration() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getDuration(); +} + +void SpineAnimation::set_duration(float duration) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setDuration(duration); +} + +void SpineAnimation::apply(Ref skeleton, float last_time, float time, bool loop, + Array events, float alpha, SpineConstant::MixBlend blend, + SpineConstant::MixDirection direction) { + SPINE_CHECK(get_spine_object(), ) + spine::Vector spineEvents; + get_spine_object()->apply(*(skeleton->get_spine_object()), last_time, time, loop, &spineEvents, alpha, (spine::MixBlend) blend, (spine::MixDirection) direction); + for (int i = 0; i < (int) spineEvents.size(); ++i) { + auto event_ref = memnew(SpineEvent); + event_ref->set_spine_object(skeleton->get_spine_owner(), spineEvents[i]); + events.append(event_ref); + } +} + +Array SpineAnimation::get_timelines() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &timelines = get_spine_object()->getTimelines(); + result.resize((int) timelines.size()); + + for (int i = 0; i < (int) result.size(); ++i) { + auto timeline_ref = Ref(memnew(SpineTimeline)); + timeline_ref->set_spine_object(get_spine_owner(), timelines[i]); +#ifdef SPINE_GODOT_EXTENSION + result[i] = timeline_ref; +#else + result.set(i, timeline_ref); +#endif + } + return result; +} + +bool SpineAnimation::has_timeline(Array ids) { + SPINE_CHECK(get_spine_object(), false) + spine::Vector property_ids; + property_ids.setSize(ids.size(), 0); + + for (int i = 0; i < (int) property_ids.size(); ++i) { + property_ids[i] = (int64_t) ids[i]; + } + return get_spine_object()->hasTimeline(property_ids); +} diff --git a/modules/spine_godot/SpineAnimation.h b/modules/spine_godot/SpineAnimation.h new file mode 100644 index 000000000000..2d857a2e86a5 --- /dev/null +++ b/modules/spine_godot/SpineAnimation.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineConstant.h" +#include + +class SpineEvent; +class SpineSkeleton; +class SpineTimeline; +class SpineSkeletonDataResource; + +class SpineAnimation : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineAnimation, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void apply(Ref skeleton, float last_time, float time, bool loop, Array events, float alpha, SpineConstant::MixBlend blend, SpineConstant::MixDirection direction); + + Array get_timelines(); + + bool has_timeline(Array ids); + + String get_name(); + + float get_duration(); + + void set_duration(float duration); +}; diff --git a/modules/spine_godot/SpineAnimationState.cpp b/modules/spine_godot/SpineAnimationState.cpp new file mode 100644 index 000000000000..c7565b3567db --- /dev/null +++ b/modules/spine_godot/SpineAnimationState.cpp @@ -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 "SpineAnimationState.h" +#include "SpineTrackEntry.h" + +void SpineAnimationState::_bind_methods() { + ClassDB::bind_method(D_METHOD("update", "delta"), &SpineAnimationState::update, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("apply", "skeleton"), &SpineAnimationState::apply); + ClassDB::bind_method(D_METHOD("clear_tracks"), &SpineAnimationState::clear_tracks); + ClassDB::bind_method(D_METHOD("clear_track", "track_id"), &SpineAnimationState::clear_track); + ClassDB::bind_method(D_METHOD("get_num_tracks"), &SpineAnimationState::get_num_tracks); + ClassDB::bind_method(D_METHOD("set_animation", "animation_name", "loop", "track_id"), &SpineAnimationState::set_animation, DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_animation", "animation_name", "delay", "loop", "track_id"), &SpineAnimationState::add_animation, DEFVAL(0), DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_empty_animation", "track_id", "mix_duration"), &SpineAnimationState::set_empty_animation); + ClassDB::bind_method(D_METHOD("add_empty_animation", "track_id", "mix_duration", "delay"), &SpineAnimationState::add_empty_animation); + ClassDB::bind_method(D_METHOD("set_empty_animations", "mix_duration"), &SpineAnimationState::set_empty_animations); + ClassDB::bind_method(D_METHOD("get_current", "track_id"), &SpineAnimationState::get_current); + ClassDB::bind_method(D_METHOD("get_time_scale"), &SpineAnimationState::get_time_scale); + ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &SpineAnimationState::set_time_scale); + ClassDB::bind_method(D_METHOD("disable_queue"), &SpineAnimationState::disable_queue); + ClassDB::bind_method(D_METHOD("enable_queue"), &SpineAnimationState::enable_queue); +} + +SpineAnimationState::SpineAnimationState() : animation_state(nullptr), sprite(nullptr) { +} + +SpineAnimationState::~SpineAnimationState() { + delete animation_state; +} + +void SpineAnimationState::set_spine_sprite(SpineSprite *_sprite) { + delete animation_state; + animation_state = nullptr; + sprite = _sprite; + if (!sprite || !sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return; + animation_state = new spine::AnimationState(sprite->get_skeleton_data_res()->get_animation_state_data()); +} + +void SpineAnimationState::update(float delta) { + SPINE_CHECK(animation_state, ) + animation_state->update(delta); +} + +bool SpineAnimationState::apply(Ref skeleton) { + SPINE_CHECK(animation_state, false) + if (!skeleton->get_spine_object()) return false; + return animation_state->apply(*(skeleton->get_spine_object())); +} + +void SpineAnimationState::clear_tracks() { + SPINE_CHECK(animation_state, ) + animation_state->clearTracks(); +} + +void SpineAnimationState::clear_track(int track_id) { + SPINE_CHECK(animation_state, ) + animation_state->clearTrack(track_id); +} + +int SpineAnimationState::get_num_tracks() { + SPINE_CHECK(animation_state, 0) + int highest_index = -1; + for (int i = 0; i < animation_state->getTracks().size(); i++) { + if (animation_state->getTracks()[i]) highest_index = i; + } + return highest_index + 1; +} + + +Ref SpineAnimationState::set_animation(const String &animation_name, bool loop, int track) { + SPINE_CHECK(animation_state, nullptr) + auto skeleton_data = animation_state->getData()->getSkeletonData(); + auto animation = skeleton_data->findAnimation(animation_name.utf8().ptr()); + if (!animation) { + ERR_PRINT(String("Can not find animation: ") + animation_name); + return nullptr; + } + auto track_entry = animation_state->setAnimation(track, animation, loop); + Ref track_entry_ref(memnew(SpineTrackEntry)); + track_entry_ref->set_spine_object(sprite, track_entry); + return track_entry_ref; +} + +Ref SpineAnimationState::add_animation(const String &animation_name, float delay, bool loop, int track) { + SPINE_CHECK(animation_state, nullptr) + auto skeleton_data = animation_state->getData()->getSkeletonData(); + auto animation = skeleton_data->findAnimation(animation_name.utf8().ptr()); + if (!animation) { + ERR_PRINT(String("Can not find animation: ") + animation_name); + return nullptr; + } + auto track_entry = animation_state->addAnimation(track, animation, loop, delay); + Ref track_entry_ref(memnew(SpineTrackEntry)); + track_entry_ref->set_spine_object(sprite, track_entry); + return track_entry_ref; +} + +Ref SpineAnimationState::set_empty_animation(int track_id, float mix_duration) { + SPINE_CHECK(animation_state, nullptr) + auto track_entry = animation_state->setEmptyAnimation(track_id, mix_duration); + Ref track_entry_ref(memnew(SpineTrackEntry)); + track_entry_ref->set_spine_object(sprite, track_entry); + return track_entry_ref; +} +Ref SpineAnimationState::add_empty_animation(int track_id, float mix_duration, float delay) { + SPINE_CHECK(animation_state, nullptr) + auto track_entry = animation_state->addEmptyAnimation(track_id, mix_duration, delay); + Ref track_entry_ref(memnew(SpineTrackEntry)); + track_entry_ref->set_spine_object(sprite, track_entry); + return track_entry_ref; +} +void SpineAnimationState::set_empty_animations(float mix_duration) { + SPINE_CHECK(animation_state, ) + animation_state->setEmptyAnimations(mix_duration); +} + +Ref SpineAnimationState::get_current(int track_index) { + SPINE_CHECK(animation_state, nullptr) + auto track_entry = animation_state->getCurrent(track_index); + if (!track_entry) return nullptr; + Ref track_entry_ref(memnew(SpineTrackEntry)); + track_entry_ref->set_spine_object(sprite, track_entry); + return track_entry_ref; +} + +float SpineAnimationState::get_time_scale() { + SPINE_CHECK(animation_state, 0) + return animation_state->getTimeScale(); +} + +void SpineAnimationState::set_time_scale(float time_scale) { + SPINE_CHECK(animation_state, ) + animation_state->setTimeScale(time_scale); +} + +void SpineAnimationState::disable_queue() { + SPINE_CHECK(animation_state, ) + animation_state->disableQueue(); +} + +void SpineAnimationState::enable_queue() { + SPINE_CHECK(animation_state, ) + animation_state->enableQueue(); +} diff --git a/modules/spine_godot/SpineAnimationState.h b/modules/spine_godot/SpineAnimationState.h new file mode 100644 index 000000000000..29ea13e8b53c --- /dev/null +++ b/modules/spine_godot/SpineAnimationState.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineSkeleton.h" + +class SpineTrackEntry; + +class SpineAnimationState : public REFCOUNTED { + GDCLASS(SpineAnimationState, REFCOUNTED) + +protected: + static void _bind_methods(); + +private: + spine::AnimationState *animation_state; + SpineSprite *sprite; + +public: + SpineAnimationState(); + ~SpineAnimationState(); + + spine::AnimationState *get_spine_object() { return animation_state; } + + void set_spine_sprite(SpineSprite *sprite); + + void update(float delta); + + bool apply(Ref skeleton); + + void clear_tracks(); + + void clear_track(int track_id); + + int get_num_tracks(); + + Ref set_animation(const String &animation_name, bool loop, int track_id); + + Ref add_animation(const String &animation_name, float delay, bool loop, int track_id); + + Ref set_empty_animation(int track_id, float mix_duration); + + Ref add_empty_animation(int track_id, float mix_duration, float delay); + + void set_empty_animations(float mix_duration); + + Ref get_current(int track_index); + + float get_time_scale(); + + void set_time_scale(float time_scale); + + void disable_queue(); + + void enable_queue(); +}; diff --git a/modules/spine_godot/SpineAnimationTrack.cpp b/modules/spine_godot/SpineAnimationTrack.cpp new file mode 100644 index 000000000000..018067a0fb8f --- /dev/null +++ b/modules/spine_godot/SpineAnimationTrack.cpp @@ -0,0 +1,594 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_GODOT_EXTENSION + +#include "SpineAnimationTrack.h" +#if VERSION_MAJOR > 3 +#include "core/config/engine.h" +#else +#include "core/engine.h" +#endif +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) +#include "editor/animation/animation_player_editor_plugin.h" +#include "editor/animation/animation_tree_editor_plugin.h" +#else +#include "editor/plugins/animation_player_editor_plugin.h" +#include "editor/plugins/animation_tree_editor_plugin.h" +#endif +#endif + +void SpineAnimationTrack::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation_name", "animation_name"), &SpineAnimationTrack::set_animation_name); + ClassDB::bind_method(D_METHOD("get_animation_name"), &SpineAnimationTrack::get_animation_name); + ClassDB::bind_method(D_METHOD("set_loop", "loop"), &SpineAnimationTrack::set_loop); + ClassDB::bind_method(D_METHOD("get_loop"), &SpineAnimationTrack::get_loop); + + ClassDB::bind_method(D_METHOD("set_track_index", "track_index"), &SpineAnimationTrack::set_track_index); + ClassDB::bind_method(D_METHOD("get_track_index"), &SpineAnimationTrack::get_track_index); + ClassDB::bind_method(D_METHOD("set_mix_duration", "mix_duration"), &SpineAnimationTrack::set_mix_duration); + ClassDB::bind_method(D_METHOD("get_mix_duration"), &SpineAnimationTrack::get_mix_duration); + ClassDB::bind_method(D_METHOD("set_hold_previous", "hold_previous"), &SpineAnimationTrack::set_hold_previous); + ClassDB::bind_method(D_METHOD("get_hold_previous"), &SpineAnimationTrack::get_hold_previous); + ClassDB::bind_method(D_METHOD("set_reverse", "reverse"), &SpineAnimationTrack::set_reverse); + ClassDB::bind_method(D_METHOD("get_reverse"), &SpineAnimationTrack::get_reverse); + ClassDB::bind_method(D_METHOD("set_shortest_rotation", "shortest_rotation"), &SpineAnimationTrack::set_shortest_rotation); + ClassDB::bind_method(D_METHOD("get_shortest_rotation"), &SpineAnimationTrack::get_shortest_rotation); + ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &SpineAnimationTrack::set_time_scale); + ClassDB::bind_method(D_METHOD("get_time_scale"), &SpineAnimationTrack::get_time_scale); + ClassDB::bind_method(D_METHOD("set_alpha", "alpha"), &SpineAnimationTrack::set_alpha); + ClassDB::bind_method(D_METHOD("get_alpha"), &SpineAnimationTrack::get_alpha); + ClassDB::bind_method(D_METHOD("set_mix_attachment_threshold", "mix_attachment_threshold"), &SpineAnimationTrack::set_mix_attachment_threshold); + ClassDB::bind_method(D_METHOD("get_mix_attachment_threshold"), &SpineAnimationTrack::get_mix_attachment_threshold); + ClassDB::bind_method(D_METHOD("set_mix_draw_order_threshold", "mix_draw_order_threshold"), &SpineAnimationTrack::set_mix_draw_order_threshold); + ClassDB::bind_method(D_METHOD("get_mix_draw_order_threshold"), &SpineAnimationTrack::get_mix_draw_order_threshold); + ClassDB::bind_method(D_METHOD("set_mix_blend", "mix_blend"), &SpineAnimationTrack::set_mix_blend); + ClassDB::bind_method(D_METHOD("get_mix_blend"), &SpineAnimationTrack::get_mix_blend); + ClassDB::bind_method(D_METHOD("set_blend_tree_mode", "blend_tree_mode_enabled"), &SpineAnimationTrack::set_blend_tree_mode); + ClassDB::bind_method(D_METHOD("get_blend_tree_mode"), &SpineAnimationTrack::get_blend_tree_mode); + ClassDB::bind_method(D_METHOD("set_debug", "debug"), &SpineAnimationTrack::set_debug); + ClassDB::bind_method(D_METHOD("get_debug"), &SpineAnimationTrack::get_debug); + + ClassDB::bind_method(D_METHOD("update_animation_state", "spine_sprite"), &SpineAnimationTrack::update_animation_state); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_NOEDITOR), "set_animation_name", "get_animation_name"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_NOEDITOR), "set_loop", "get_loop"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "track_index", PROPERTY_HINT_RANGE, "0,256,1"), "set_track_index", "get_track_index"); + ADD_PROPERTY(PropertyInfo(VARIANT_FLOAT, "mix_duration"), "set_mix_duration", "get_mix_duration"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hold_previous"), "set_hold_previous", "get_hold_previous"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse"), "set_reverse", "get_reverse"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortest_rotation"), "set_shortest_rotation", "get_shortest_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::VARIANT_FLOAT, "time_scale"), "set_time_scale", "get_time_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VARIANT_FLOAT, "alpha"), "set_alpha", "get_alpha"); + ADD_PROPERTY(PropertyInfo(Variant::VARIANT_FLOAT, "attachment_threshold"), "set_mix_attachment_threshold", "get_mix_attachment_threshold"); + ADD_PROPERTY(PropertyInfo(Variant::VARIANT_FLOAT, "draw_order_threshold"), "set_mix_draw_order_threshold", "get_mix_draw_order_threshold"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_blend", PROPERTY_HINT_ENUM, "Setup,First,Replace,Add"), "set_mix_blend", "get_mix_blend"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blend_tree_mode"), "set_blend_tree_mode", "get_blend_tree_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug"), "set_debug", "get_debug"); +} + +SpineAnimationTrack::SpineAnimationTrack() : loop(false), + animation_changed(false), + track_index(-1), + mix_duration(-1), + hold_previous(false), + reverse(false), + shortest_rotation(false), + time_scale(1), + alpha(1), + mix_attachment_threshold(0), + mix_draw_order_threshold(0), + mix_blend(SpineConstant::MixBlend_Replace), + blend_tree_mode(false), + debug(false), + sprite(nullptr) { +} + +void SpineAnimationTrack::_notification(int what) { + switch (what) { + case NOTIFICATION_PARENTED: { + sprite = Object::cast_to(get_parent()); + if (sprite) +#if VERSION_MAJOR > 3 + sprite->connect(SNAME("before_animation_state_update"), callable_mp(this, &SpineAnimationTrack::update_animation_state)); +#else + sprite->connect(SNAME("before_animation_state_update"), this, SNAME("update_animation_state")); +#endif + NOTIFY_PROPERTY_LIST_CHANGED(); + break; + } + case NOTIFICATION_READY: { + setup_animation_player(); + break; + } + case NOTIFICATION_UNPARENTED: { + if (sprite) { +#if VERSION_MAJOR > 3 + sprite->disconnect(SNAME("before_animation_state_update"), callable_mp(this, &SpineAnimationTrack::update_animation_state)); +#else + sprite->disconnect(SNAME("before_animation_state_update"), this, SNAME("update_animation_state")); +#endif + sprite = nullptr; + } + break; + } + default: + break; + } +} + +AnimationPlayer *SpineAnimationTrack::find_animation_player() { + AnimationPlayer *animation_player = nullptr; + for (int i = 0; i < get_child_count(); i++) { + animation_player = cast_to(get_child(i)); + if (animation_player) { + break; + } + } + return animation_player; +} + +void SpineAnimationTrack::setup_animation_player() { + if (!sprite) return; + if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return; + AnimationPlayer *animation_player = find_animation_player(); + + // If we don't have a track index yet, find the highest track number used + // by existing tracks. + if (track_index < 0) { + int highest_track_number = -1; + for (int i = 0; i < sprite->get_child_count(); i++) { + auto other_track = cast_to(sprite->get_child(i)); + if (other_track) { + if (other_track->track_index > highest_track_number) + highest_track_number = other_track->track_index; + } + } + track_index = highest_track_number + 1; + } + + // Find the animation player under the track and reset its animation. Create a new one + // if there isn't one already. + if (!animation_player) { + animation_player = memnew(AnimationPlayer); + animation_player->set_name(String("{0} Track {1}").format(varray(sprite->get_name(), String::num_int64(track_index)))); + add_child(animation_player); + animation_player->set_owner(sprite->get_owner()); + } else { +#if VERSION_MAJOR > 3 + List animation_libraries; + animation_player->get_animation_library_list(&animation_libraries); + for (auto iter = animation_libraries.front(); iter; iter = iter->next()) { + animation_player->remove_animation_library(iter->get()); + } +#else + List animation_names; + animation_player->get_animation_list(&animation_names); + for (int i = 0; i < animation_names.size(); i++) { + animation_player->remove_animation(animation_names[i]); + } +#endif + } + + auto skeleton_data = sprite->get_skeleton_data_res()->get_skeleton_data(); + auto &animations = skeleton_data->getAnimations(); +#if VERSION_MAJOR > 3 + Ref animation_library; + animation_library.instantiate(); + animation_player->add_animation_library("", animation_library); +#endif + for (int i = 0; i < (int) animations.size(); i++) { + auto &animation = animations[i]; + Ref animation_ref = create_animation(animation, false); + Ref animation_looped_ref = create_animation(animation, true); +#if VERSION_MAJOR > 3 + animation_library->add_animation(animation_ref->get_name(), animation_ref); + animation_library->add_animation(animation_looped_ref->get_name(), animation_looped_ref); +#else + animation_player->add_animation(animation_ref->get_name(), animation_ref); + animation_player->add_animation(animation_looped_ref->get_name(), animation_looped_ref); +#endif + } + + Ref reset_animation_ref; + INSTANTIATE(reset_animation_ref); + reset_animation_ref->set_name("RESET"); +#if VERSION_MAJOR > 3 + // reset_animation_ref->set_loop(true); +#else + reset_animation_ref->set_loop(true); +#endif + reset_animation_ref->set_length(0.5f); + reset_animation_ref->add_track(Animation::TYPE_VALUE); + reset_animation_ref->track_set_path(0, NodePath(".:animation_name")); + reset_animation_ref->track_insert_key(0, 0, ""); + reset_animation_ref->add_track(Animation::TYPE_VALUE); + reset_animation_ref->track_set_path(1, NodePath(".:loop")); + reset_animation_ref->track_insert_key(1, 0, false); + reset_animation_ref->value_track_set_update_mode(0, Animation::UPDATE_DISCRETE); + reset_animation_ref->value_track_set_update_mode(1, Animation::UPDATE_DISCRETE); + +#if VERSION_MAJOR > 3 + animation_library->add_animation(reset_animation_ref->get_name(), reset_animation_ref); + animation_library->add_animation("-- Empty --", reset_animation_ref); +#else + animation_player->add_animation(reset_animation_ref->get_name(), reset_animation_ref); + animation_player->add_animation("-- Empty --", reset_animation_ref); +#endif +} + +Ref SpineAnimationTrack::create_animation(spine::Animation *animation, bool loop) { + float duration = animation->getDuration(); + if (duration == 0) duration = 0.5; + + Ref animation_ref; + INSTANTIATE(animation_ref); + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(animation->getName().buffer()); +#else + name.parse_utf8(animation->getName().buffer()); +#endif + animation_ref->set_name(name + (loop ? "" : "_looped")); +#if VERSION_MAJOR > 3 + // animation_ref->set_loop(!loop); +#else + animation_ref->set_loop(!loop); +#endif + animation_ref->set_length(duration); + + animation_ref->add_track(Animation::TYPE_VALUE); + animation_ref->track_set_path(0, NodePath(".:animation_name")); + animation_ref->track_insert_key(0, 0, name); + + animation_ref->add_track(Animation::TYPE_VALUE); + animation_ref->track_set_path(1, NodePath(".:loop")); + animation_ref->track_insert_key(1, 0, !loop); + + animation_ref->value_track_set_update_mode(0, Animation::UPDATE_DISCRETE); + animation_ref->value_track_set_update_mode(1, Animation::UPDATE_DISCRETE); + + return animation_ref; +} + +void SpineAnimationTrack::update_animation_state(const Variant &variant_sprite) { + if (track_index < 0) return; + sprite = Object::cast_to(variant_sprite); + if (!sprite) return; + if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return; + if (!sprite->get_skeleton().is_valid() || !sprite->get_animation_state().is_valid()) return; + spine::AnimationState *animation_state = sprite->get_animation_state()->get_spine_object(); + if (!animation_state) return; + spine::Skeleton *skeleton = sprite->get_skeleton()->get_spine_object(); + if (!skeleton) return; + AnimationPlayer *animation_player = find_animation_player(); + if (!animation_player) return; + + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + if (blend_tree_mode) { + AnimationTreeEditor *tree_editor = AnimationTreeEditor::get_singleton(); + // When the animation tree dock is no longer visible, bail. + if (!tree_editor->is_visible_in_tree()) { + skeleton->setToSetupPose(); + animation_state->clearTracks(); + animation_state->setTimeScale(1); + return; + } + auto current_entry = animation_state->getCurrent(track_index); + bool should_set_mix = mix_duration >= 0; + String other_name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + if (current_entry) other_name = String::utf8(current_entry->getAnimation()->getName().buffer()); +#else + if (current_entry) other_name.parse_utf8(current_entry->getAnimation()->getName().buffer()); +#endif + bool should_set_animation = !current_entry || (animation_name != other_name || current_entry->getLoop() != loop); + + if (should_set_animation) { + if (!EMPTY(animation_name)) { + auto entry = animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop); + if (should_set_mix) entry->setMixDuration(mix_duration); + + entry->setHoldPrevious(hold_previous); + entry->setReverse(reverse); + entry->setShortestRotation(shortest_rotation); + entry->setTimeScale(time_scale); + entry->setAlpha(alpha); + entry->setMixAttachmentThreshold(mix_attachment_threshold); + entry->setMixDrawOrderThreshold(mix_draw_order_threshold); + entry->setMixBlend((spine::MixBlend) mix_blend); + + if (debug) print_line(String("Setting animation {0} with mix_duration {1} on track {2} on {3}").format(varray(animation_name, mix_duration, track_index, sprite->get_name())).utf8().ptr()); + } else { + if (!current_entry || (String("") != other_name)) { + auto entry = animation_state->setEmptyAnimation(track_index, should_set_mix ? mix_duration : 0); + entry->setTrackEnd(FLT_MAX); + if (debug) print_line(String("Setting empty animation with mix_duration {0} on track {1} on {2}").format(varray(mix_duration, track_index, sprite->get_name())).utf8().ptr()); + } + } + } + return; + } + + // When the animation dock is no longer visible or we aren't being + // keyed in the current animation, bail. +#if VERSION_MAJOR > 3 + auto player_editor = AnimationPlayerEditor::get_singleton(); +#else + auto player_editor = AnimationPlayerEditor::singleton; +#endif + if (!player_editor->is_visible_in_tree()) { + skeleton->setToSetupPose(); + animation_state->clearTracks(); + animation_state->setTimeScale(1); + return; + } + + // Check if the player is actually editing an animation for which there is a track + // for us. + Ref edited_animation = player_editor->get_track_editor()->get_current_animation(); + if (!edited_animation.is_valid()) { + skeleton->setToSetupPose(); + animation_state->clearTracks(); + animation_state->setTimeScale(1); + return; + } + + int found_track_index = -1; + auto scene_path = EditorNode::get_singleton()->get_edited_scene()->get_path(); + auto animation_player_path = scene_path.rel_path_to(animation_player->get_path()); + for (int i = 0; i < edited_animation->get_track_count(); i++) { + auto path = edited_animation->track_get_path(i); + if (path == animation_player_path) { + found_track_index = i; + break; + } + } + + // if we are track 0, set the skeleton to the setup pose + // and the animation state time scale to 0, as we are + // setting track times manually. Also, kill anything + // currently in the track. + if (track_index == 0) { + skeleton->setToSetupPose(); + animation_state->setTimeScale(0); + } + animation_state->clearTrack(track_index); + if (found_track_index == -1) return; + + // If no animation is set or it's set to "[stop]", we are done. + if (EMPTY(animation_name) || animation_name == "[stop]") return; + + // If there's no keys on the timeline for this track, we are done. + if (edited_animation->track_get_key_count(found_track_index) == 0) return; + + // Find the key in the track that matches the editor's playback position + auto playback_position = player_editor->get_player()->get_current_animation_position(); + int key_index = -1; + for (int i = 0; i < edited_animation->track_get_key_count(found_track_index); i++) { + float key_time = edited_animation->track_get_key_time(found_track_index, i); + if (key_time <= playback_position) { + key_index = i; + } else { + // epsilon compare key and playback time, as playback time is imprecise + if (fabs(key_time - playback_position) < edited_animation->get_step()) { + key_index = i; + } + break; + } + } + + // No key found? bail. + if (key_index == -1) return; + + // Get the animation from our player for the key + float key_time = edited_animation->track_get_key_time(found_track_index, key_index); + String key_value = edited_animation->track_get_key_value(found_track_index, key_index); + Ref keyed_animation = animation_player->get_animation(key_value); + if (!keyed_animation.is_valid()) return; + + // Calculate the track time and setup the track entry based on the currently keyed + // properties. + float track_time = (playback_position - key_time) * time_scale; + if (track_time < 0) track_time = 0; + auto entry = animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop); + entry->setMixDuration(0); + entry->setTrackTime(track_time); + + entry->setHoldPrevious(hold_previous); + entry->setReverse(reverse); + entry->setShortestRotation(shortest_rotation); + entry->setAlpha(alpha); + entry->setMixAttachmentThreshold(mix_attachment_threshold); + entry->setMixDrawOrderThreshold(mix_draw_order_threshold); + entry->setMixBlend((spine::MixBlend) mix_blend); +#endif + } else { + if (animation_player->is_playing()) { + auto current_entry = animation_state->getCurrent(track_index); + bool should_set_mix = mix_duration >= 0; + String other_name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + if (current_entry) other_name = String::utf8(current_entry->getAnimation()->getName().buffer()); +#else + if (current_entry) other_name.parse_utf8(current_entry->getAnimation()->getName().buffer()); +#endif + bool should_set_animation = !current_entry || (animation_name != other_name || current_entry->getLoop() != loop) || animation_changed; + animation_changed = false; + + if (should_set_animation) { + if (!EMPTY(animation_name)) { + auto entry = animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop); + if (should_set_mix) entry->setMixDuration(mix_duration); + + entry->setHoldPrevious(hold_previous); + entry->setReverse(reverse); + entry->setShortestRotation(shortest_rotation); + entry->setTimeScale(time_scale); + entry->setAlpha(alpha); + entry->setMixAttachmentThreshold(mix_attachment_threshold); + entry->setMixDrawOrderThreshold(mix_draw_order_threshold); + entry->setMixBlend((spine::MixBlend) mix_blend); + + if (debug) print_line(String("Setting animation {0} with mix_duration {1} on track {2} on {3}").format(varray(animation_name, mix_duration, track_index, sprite->get_name())).utf8().ptr()); + } else { + if (!current_entry || (String("") != other_name)) { + auto entry = animation_state->setEmptyAnimation(track_index, should_set_mix ? mix_duration : 0); + entry->setTrackEnd(FLT_MAX); + if (debug) print_line(String("Setting empty animation with mix_duration {0} on track {1} on {2}").format(varray(mix_duration, track_index, sprite->get_name())).utf8().ptr()); + } + } + } + } + } +} + +void SpineAnimationTrack::set_animation_name(const String &_animation_name) { + if (debug) print_line(String("Animation name changed")); + animation_name = _animation_name; + animation_changed = true; +} + +String SpineAnimationTrack::get_animation_name() { + return animation_name; +} + +void SpineAnimationTrack::set_loop(bool _loop) { + animation_changed = true; + loop = _loop; +} + +bool SpineAnimationTrack::get_loop() { + return loop; +} + +void SpineAnimationTrack::set_track_index(int _track_index) { + track_index = _track_index; +} + +int SpineAnimationTrack::get_track_index() { + return track_index; +} + +void SpineAnimationTrack::set_mix_duration(float _mix_duration) { + mix_duration = _mix_duration; +} + +float SpineAnimationTrack::get_mix_duration() { + return mix_duration; +} + +void SpineAnimationTrack::set_hold_previous(bool _hold_previous) { + hold_previous = _hold_previous; +} + +bool SpineAnimationTrack::get_hold_previous() { + return hold_previous; +} + +void SpineAnimationTrack::set_reverse(bool _reverse) { + reverse = _reverse; +} + +bool SpineAnimationTrack::get_reverse() { + return reverse; +} + +void SpineAnimationTrack::set_shortest_rotation(bool _shortest_rotation) { + shortest_rotation = _shortest_rotation; +} + +bool SpineAnimationTrack::get_shortest_rotation() { + return shortest_rotation; +} + +void SpineAnimationTrack::set_time_scale(float _time_scale) { + time_scale = _time_scale; +} + +float SpineAnimationTrack::get_time_scale() { + return time_scale; +} + +void SpineAnimationTrack::set_alpha(float _alpha) { + alpha = _alpha; +} + +float SpineAnimationTrack::get_alpha() { + return alpha; +} + +void SpineAnimationTrack::set_mix_attachment_threshold(float _mix_attachment_threshold) { + mix_attachment_threshold = _mix_attachment_threshold; +} + +float SpineAnimationTrack::get_mix_attachment_threshold() { + return mix_attachment_threshold; +} + +void SpineAnimationTrack::set_mix_draw_order_threshold(float _mix_draw_order_threshold) { + mix_draw_order_threshold = _mix_draw_order_threshold; +} + +float SpineAnimationTrack::get_mix_draw_order_threshold() { + return mix_draw_order_threshold; +} + +void SpineAnimationTrack::set_mix_blend(SpineConstant::MixBlend _blend) { + mix_blend = _blend; +} + +SpineConstant::MixBlend SpineAnimationTrack::get_mix_blend() { + return mix_blend; +} + +void SpineAnimationTrack::set_blend_tree_mode(bool _blend_tree_mode) { + blend_tree_mode = _blend_tree_mode; +} + +bool SpineAnimationTrack::get_blend_tree_mode() { + return blend_tree_mode; +} + +void SpineAnimationTrack::set_debug(bool _debug) { + debug = _debug; +} + +bool SpineAnimationTrack::get_debug() { + return debug; +} + +#endif diff --git a/modules/spine_godot/SpineAnimationTrack.h b/modules/spine_godot/SpineAnimationTrack.h new file mode 100644 index 000000000000..3454cec248b3 --- /dev/null +++ b/modules/spine_godot/SpineAnimationTrack.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#ifndef SPINE_GODOT_EXTENSION +#include "SpineSprite.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" + +class SpineAnimationTrack : public Node { + GDCLASS(SpineAnimationTrack, Node) +protected: + // These are not exposed in the inspector, see SpineAnimationTrackInspectorPlugin. + // Instead, they are are keyed by the animations created in setup_animation_player + // and primarily used for animation player editor support like scrubbing and playing + // back the . + String animation_name; + bool loop; + bool animation_changed; + + // These can be set by the user. + int track_index; + float mix_duration; + bool hold_previous; + bool reverse; + bool shortest_rotation; + float time_scale; + float alpha; + float mix_attachment_threshold; + float mix_draw_order_threshold; + SpineConstant::MixBlend mix_blend; + bool blend_tree_mode; + bool debug; + + SpineSprite *sprite; + + static void _bind_methods(); + + void _notification(int what); + + AnimationPlayer *find_animation_player(); + + void setup_animation_player(); + + static Ref create_animation(spine::Animation *animation, bool loop); + + void update_animation_state(const Variant &variant_sprite); + +public: + SpineAnimationTrack(); + + void set_animation_name(const String &_animation_name); + + String get_animation_name(); + + void set_animation_time(float _animation_time); + + float get_animation_time(); + + void set_loop(bool _loop); + + bool get_loop(); + + void set_track_index(int _track_index); + + int get_track_index(); + + void set_mix_duration(float _mix_duration); + + float get_mix_duration(); + + void set_hold_previous(bool _hold_previous); + + bool get_hold_previous(); + + void set_reverse(bool _reverse); + + bool get_reverse(); + + void set_shortest_rotation(bool _shortest_rotation); + + bool get_shortest_rotation(); + + void set_time_scale(float _time_scale); + + float get_time_scale(); + + void set_alpha(float _alpha); + + float get_alpha(); + + void set_mix_attachment_threshold(float _mix_attachment_threshold); + + float get_mix_attachment_threshold(); + + void set_mix_draw_order_threshold(float _mix_draw_order_threshold); + + float get_mix_draw_order_threshold(); + + void set_mix_blend(SpineConstant::MixBlend _blend); + + SpineConstant::MixBlend get_mix_blend(); + + void set_blend_tree_mode(bool _blend_tree_mode); + + bool get_blend_tree_mode(); + + void set_debug(bool _debug); + + bool get_debug(); +}; + +#endif diff --git a/modules/spine_godot/SpineAtlasResource.cpp b/modules/spine_godot/SpineAtlasResource.cpp new file mode 100644 index 000000000000..4d10d3dacbcd --- /dev/null +++ b/modules/spine_godot/SpineAtlasResource.cpp @@ -0,0 +1,502 @@ +/****************************************************************************** + * 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 "SpineAtlasResource.h" +#include "SpineRendererObject.h" + +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#include +#include +#include +#else +#include "core/io/json.h" +#include "scene/resources/texture.h" +#if VERSION_MAJOR > 3 +#include "core/io/image.h" +#include "scene/resources/image_texture.h" +#else +#include "core/image.h" +#endif +#endif + +#ifdef TOOLS_ENABLED +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) +#include "editor/file_system/editor_file_system.h" +#else +#include "editor/editor_file_system.h" +#endif +#endif +#endif + +#include + +class GodotSpineTextureLoader : public spine::TextureLoader { + + Array *textures; + Array *normal_maps; + Array *specular_maps; + String normal_map_prefix; + String specular_map_prefix; + +public: + GodotSpineTextureLoader(Array *_textures, Array *_normal_maps, Array *_specular_maps, const String &normal_map_prefix, const String &specular_map_prefix, bool is_importing) : textures(_textures), normal_maps(_normal_maps), specular_maps(_specular_maps), normal_map_prefix(normal_map_prefix), specular_map_prefix(specular_map_prefix) { + } + + static bool fix_path(String &path) { + const String prefix = "res:/"; + auto i = path.find(prefix); + if (i == -1) { + return false; + } + + auto sub_str_pos = i + SSIZE(prefix) - 1; + auto res = path.substr(sub_str_pos); + if (!EMPTY(res)) { + if (res[0] != '/') { + path = prefix + String("/") + res; + } else { + path = prefix + res; + } + } + return true; + } + +#if VERSION_MAJOR > 3 + Ref get_texture_from_image(const String &path, bool is_resource) { + Error error = OK; + if (is_resource) { +#ifdef SPINE_GODOT_EXTENSION + return ResourceLoader::get_singleton()->load(path, "", ResourceLoader::CACHE_MODE_REUSE); +#else + return ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error); +#endif + } else { + Ref img; + img.instantiate(); + img = img->load_from_file(path); + return ImageTexture::create_from_image(img); + } + } +#else + Ref get_texture_from_image(const String &path, bool is_resource) { + Error error = OK; + if (is_resource) { + return ResourceLoader::load(path, "", false, &error); + } else { + Vector buf = FileAccess::get_file_as_array(path, &error); + if (error == OK) { + Ref img; + INSTANTIATE(img); + img->load(path); + + Ref texture; + INSTANTIATE(texture); + texture->create_from_image(img); + return texture; + } + return Ref(); + } + } +#endif + + void import_image_resource(const String &path) { +#if VERSION_MAJOR > 4 +#ifdef TOOLS_ENABLED + // Required when importing into editor by e.g. drag & drop. The .png files + // of the atlas might not have been imported yet. + // See https://github.com/EsotericSoftware/spine-runtimes/issues/2385 + if (is_importing) { + HashMap custom_options; + Dictionary generator_parameters; + EditorFileSystem::get_singleton()->reimport_append(path, custom_options, "", generator_parameters); + } +#endif +#endif + } + + void load(spine::AtlasPage &page, const spine::String &path) override { + String fixed_path; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + fixed_path = String::utf8(path.buffer()); +#else + fixed_path.parse_utf8(path.buffer()); +#endif + bool is_resource = fix_path(fixed_path); + + import_image_resource(fixed_path); + +#if VERSION_MAJOR > 3 + Ref texture = get_texture_from_image(fixed_path, is_resource); +#else + Ref texture = get_texture_from_image(fixed_path, is_resource); +#endif + if (!texture.is_valid()) { + ERR_PRINT(vformat("Can't load texture: \"%s\"", fixed_path)); + auto renderer_object = memnew(SpineRendererObject); + renderer_object->texture = Ref(nullptr); + renderer_object->normal_map = Ref(nullptr); + renderer_object->specular_map = Ref(nullptr); + page.texture = (void *) renderer_object; + return; + } + + textures->append(texture); + auto renderer_object = memnew(SpineRendererObject); + renderer_object->texture = texture; + renderer_object->normal_map = Ref(nullptr); + renderer_object->specular_map = Ref(nullptr); + + String normal_map_path = vformat("%s/%s_%s", fixed_path.get_base_dir(), normal_map_prefix, fixed_path.get_file()); + String specular_map_path = vformat("%s/%s_%s", fixed_path.get_base_dir(), specular_map_prefix, fixed_path.get_file()); + is_resource = fix_path(normal_map_path); + is_resource = fix_path(specular_map_path); +#if SPINE_GODOT_EXTENSION + if (ResourceLoader::get_singleton()->exists(normal_map_path)) { + import_image_resource(normal_map_path); + Ref normal_map = get_texture_from_image(normal_map_path, is_resource); + normal_maps->append(normal_map); + renderer_object->normal_map = normal_map; + } + + if (ResourceLoader::get_singleton()->exists(specular_map_path)) { + import_image_resource(specular_map_path); + Ref specular_map = get_texture_from_image(specular_map_path, is_resource); + specular_maps->append(specular_map); + renderer_object->specular_map = specular_map; + } +#else + if (ResourceLoader::exists(normal_map_path)) { + import_image_resource(normal_map_path); + Ref normal_map = get_texture_from_image(normal_map_path, is_resource); + normal_maps->append(normal_map); + renderer_object->normal_map = normal_map; + } + + if (ResourceLoader::exists(specular_map_path)) { + import_image_resource(specular_map_path); + Ref specular_map = get_texture_from_image(specular_map_path, is_resource); + specular_maps->append(specular_map); + renderer_object->specular_map = specular_map; + } +#endif + +#if VERSION_MAJOR > 3 + renderer_object->canvas_texture.instantiate(); + renderer_object->canvas_texture->set_diffuse_texture(renderer_object->texture); + renderer_object->canvas_texture->set_normal_texture(renderer_object->normal_map); + renderer_object->canvas_texture->set_specular_texture(renderer_object->specular_map); +#endif + + page.texture = (void *) renderer_object; + page.width = texture->get_width(); + page.height = texture->get_height(); + } + + void unload(void *data) override { + auto renderer_object = (SpineRendererObject *) data; + if (renderer_object->texture.is_valid()) renderer_object->texture.unref(); + if (renderer_object->normal_map.is_valid()) renderer_object->normal_map.unref(); + if (renderer_object->specular_map.is_valid()) renderer_object->specular_map.unref(); +#if VERSION_MAJOR > 3 + if (renderer_object->canvas_texture.is_valid()) renderer_object->canvas_texture.unref(); +#endif + memdelete(renderer_object); + } +}; + +void SpineAtlasResource::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_from_atlas_file", "path"), &SpineAtlasResource::load_from_atlas_file); + ClassDB::bind_method(D_METHOD("get_source_path"), &SpineAtlasResource::get_source_path); + ClassDB::bind_method(D_METHOD("get_textures"), &SpineAtlasResource::get_textures); + ClassDB::bind_method(D_METHOD("get_normal_maps"), &SpineAtlasResource::get_normal_maps); + ClassDB::bind_method(D_METHOD("get_specular_maps"), &SpineAtlasResource::get_specular_maps); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_path"), "", "get_source_path"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "specular_maps"), "", "get_specular_maps"); +} + +SpineAtlasResource::SpineAtlasResource() : atlas(nullptr), texture_loader(nullptr), normal_map_prefix("n"), specular_map_prefix("s") { +} + +SpineAtlasResource::~SpineAtlasResource() { + delete atlas; + delete texture_loader; +} + +void SpineAtlasResource::clear() { + delete atlas; + atlas = nullptr; + delete texture_loader; + texture_loader = nullptr; + textures.clear(); + normal_maps.clear(); + specular_maps.clear(); +} + +Array SpineAtlasResource::get_textures() { + return textures; +} + +Array SpineAtlasResource::get_normal_maps() { + return normal_maps; +} + +Array SpineAtlasResource::get_specular_maps() { + return specular_maps; +} + +String SpineAtlasResource::get_source_path() { + return source_path; +} + +Error SpineAtlasResource::load_from_atlas_file(const String &path) { + return load_from_atlas_file_internal(path, false); +} + +Error SpineAtlasResource::load_from_atlas_file_internal(const String &path, bool is_importing) { + source_path = path; +#ifdef SPINE_GODOT_EXTENSION + atlas_data = FileAccess::get_file_as_string(path); + if (SSIZE(atlas_data) == 0) return ERR_FILE_UNRECOGNIZED; +#else + Error err; + atlas_data = FileAccess::get_file_as_string(path, &err); + if (err != OK) return err; +#endif + + clear(); + texture_loader = new GodotSpineTextureLoader(&textures, &normal_maps, &specular_maps, normal_map_prefix, specular_map_prefix, is_importing); + auto atlas_utf8 = atlas_data.utf8(); + auto dir_utf8 = source_path.get_base_dir().utf8(); + atlas = new spine::Atlas(atlas_utf8.ptr(), atlas_utf8.length(), dir_utf8.ptr(), texture_loader); + if (atlas) return OK; + + clear(); + return ERR_FILE_UNRECOGNIZED; +} + +Error SpineAtlasResource::load_from_file(const String &path) { + Error error; +#ifdef SPINE_GODOT_EXTENSION + String json_string = FileAccess::get_file_as_string(path); + if (SSIZE(json_string) == 0) return ERR_FILE_UNRECOGNIZED; +#else + String json_string = FileAccess::get_file_as_string(path, &error); + if (error != OK) return error; +#endif + +#if VERSION_MAJOR > 3 + JSON *json = memnew(JSON); + error = json->parse(json_string); + if (error != OK) { + memdelete(json); + return error; + } + Variant result = json->get_data(); + memdelete(json); +#else + String error_string; + int error_line; + Variant result; + error = JSON::parse(json_string, result, error_string, error_line); + if (error != OK) return error; +#endif + + Dictionary content = Dictionary(result); + source_path = content["source_path"]; + atlas_data = content["atlas_data"]; + normal_map_prefix = content["normal_texture_prefix"]; + specular_map_prefix = content["specular_texture_prefix"]; + + clear(); + texture_loader = new GodotSpineTextureLoader(&textures, &normal_maps, &specular_maps, normal_map_prefix, specular_map_prefix, false); + auto utf8 = atlas_data.utf8(); + auto dir_utf8 = source_path.get_base_dir().utf8(); + atlas = new spine::Atlas(utf8.ptr(), utf8.size(), dir_utf8.ptr(), texture_loader); + if (atlas) return OK; + + clear(); + return ERR_FILE_UNRECOGNIZED; +} + +Error SpineAtlasResource::save_to_file(const String &path) { + Error err; +#if VERSION_MAJOR > 3 +#if SPINE_GODOT_EXTENSION + Ref file = FileAccess::open(path, FileAccess::WRITE); + if (file.is_null()) return ERR_FILE_UNRECOGNIZED; +#else + Ref file = FileAccess::open(path, FileAccess::WRITE, &err); + if (err != OK) return err; +#endif +#else + FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err); + if (err != OK) { + if (file) file->close(); + return err; + } +#endif + + Dictionary content; + content["source_path"] = source_path; + content["atlas_data"] = atlas_data; + content["normal_texture_prefix"] = normal_map_prefix; + content["specular_texture_prefix"] = specular_map_prefix; +#if VERSION_MAJOR > 3 + JSON *json = memnew(JSON); + file->store_string(json->stringify(content)); + file->flush(); + memdelete(json); +#else + file->store_string(JSON::print(content)); + file->close(); +#endif + return OK; +} + +#ifndef SPINE_GODOT_EXTENSION +#if VERSION_MAJOR > 3 +Error SpineAtlasResource::copy_from(const Ref &p_resource) { + auto error = Resource::copy_from(p_resource); + if (error != OK) return error; + + const Ref &spineAtlas = static_cast &>(p_resource); + this->clear(); + this->atlas = spineAtlas->atlas; + this->texture_loader = spineAtlas->texture_loader; + spineAtlas->clear_native_data(); + + this->source_path = spineAtlas->source_path; + this->atlas_data = spineAtlas->atlas_data; + this->normal_map_prefix = spineAtlas->normal_map_prefix; + this->specular_map_prefix = spineAtlas->specular_map_prefix; + this->textures = spineAtlas->textures; + this->normal_maps = spineAtlas->normal_maps; + this->specular_maps = spineAtlas->specular_maps; + emit_signal(SNAME("skeleton_file_changed")); + + return OK; +} +#endif +#endif + +#ifdef SPINE_GODOT_EXTENSION +Variant SpineAtlasResourceFormatLoader::_load(const String &path, const String &original_path, bool use_sub_threads, int32_t cache_mode) { +#else +#if VERSION_MAJOR > 3 +RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) { +#else +#if VERSION_MINOR > 5 +RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool p_no_subresource_cache) { +#else +RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error) { +#endif +#endif +#endif + Ref atlas = memnew(SpineAtlasResource); + atlas->load_from_file(path); +#ifndef SPINE_GODOT_EXTENSION + if (error) *error = OK; +#endif + return atlas; +} + + +#ifdef SPINE_GODOT_EXTENSION +PackedStringArray SpineAtlasResourceFormatLoader::_get_recognized_extensions() { + PackedStringArray extensions; + extensions.push_back("spatlas"); + return extensions; +} +#else +void SpineAtlasResourceFormatLoader::get_recognized_extensions(List *extensions) const { + const char atlas_ext[] = "spatlas"; + if (!extensions->find(atlas_ext)) + extensions->push_back(atlas_ext); +} +#endif + +#ifdef SPINE_GODOT_EXTENSION +String SpineAtlasResourceFormatLoader::_get_resource_type(const String &path) { +#else +String SpineAtlasResourceFormatLoader::get_resource_type(const String &path) const { +#endif + return path.ends_with("spatlas") || path.ends_with(".atlas") ? "SpineAtlasResource" : ""; +} + +#ifdef SPINE_GODOT_EXTENSION +bool SpineAtlasResourceFormatLoader::_handles_type(const StringName &type) { +#else +bool SpineAtlasResourceFormatLoader::handles_type(const String &type) const { +#endif + return type == StringName("SpineAtlasResource") || ClassDB::is_parent_class(type, "SpineAtlasResource"); +} + +#ifdef SPINE_GODOT_EXTENSION +Error SpineAtlasResourceFormatSaver::_save(const Ref &resource, const String &path, uint32_t flags) { +#else +#if VERSION_MAJOR > 3 +Error SpineAtlasResourceFormatSaver::save(const RES &resource, const String &path, uint32_t flags) { +#else +Error SpineAtlasResourceFormatSaver::save(const String &path, const RES &resource, uint32_t flags) { +#endif +#endif + Ref res = resource; + return res->save_to_file(path); +} + +#ifdef SPINE_GODOT_EXTENSION +PackedStringArray SpineAtlasResourceFormatSaver::_get_recognized_extensions(const Ref &resource) { + PackedStringArray extensions; + if (Object::cast_to(*resource)) { + extensions.push_back("spatlas"); + } + return extensions; +} +#else +void SpineAtlasResourceFormatSaver::get_recognized_extensions(const RES &resource, List *extensions) const { + if (Object::cast_to(*resource)) + extensions->push_back("spatlas"); +} +#endif + +#ifdef SPINE_GODOT_EXTENSION +bool SpineAtlasResourceFormatSaver::_recognize(const RES &resource) { +#else +bool SpineAtlasResourceFormatSaver::recognize(const RES &resource) const { +#endif + return Object::cast_to(*resource) != nullptr; +} diff --git a/modules/spine_godot/SpineAtlasResource.h b/modules/spine_godot/SpineAtlasResource.h new file mode 100644 index 000000000000..4635654e2079 --- /dev/null +++ b/modules/spine_godot/SpineAtlasResource.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#include +#include +#include +#include +#else +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/io/image_loader.h" +#include +#endif + +class GodotSpineTextureLoader; + +class SpineAtlasResource : public Resource { + GDCLASS(SpineAtlasResource, Resource) + + void clear(); + +protected: + static void _bind_methods(); + + mutable spine::Atlas *atlas; + mutable GodotSpineTextureLoader *texture_loader; + + String source_path; + String atlas_data; + String normal_map_prefix; + String specular_map_prefix; + + Array textures; + Array normal_maps; + Array specular_maps; + +public: + SpineAtlasResource(); + ~SpineAtlasResource() override; + + spine::Atlas *get_spine_atlas() { return atlas; } + + void set_normal_texture_prefix(const String &prefix) { normal_map_prefix = prefix; } + + void set_specular_texture_prefix(const String &prefix) { specular_map_prefix = prefix; } + + Error load_from_atlas_file(const String &path);// .atlas + + Error load_from_atlas_file_internal(const String &path, bool is_importing);// .atlas + + Error load_from_file(const String &path);// .spatlas + + Error save_to_file(const String &path);// .spatlas + +#ifndef SPINE_GODOT_EXTENSION +#if VERSION_MAJOR > 3 + virtual Error copy_from(const Ref &p_resource); +#endif +#endif + + String get_source_path(); + + Array get_textures(); + + Array get_normal_maps(); + + Array get_specular_maps(); + + void clear_native_data() const { + this->atlas = nullptr; + this->texture_loader = nullptr; + } +}; + +class SpineAtlasResourceFormatLoader : public ResourceFormatLoader { + GDCLASS(SpineAtlasResourceFormatLoader, ResourceFormatLoader) + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + PackedStringArray _get_recognized_extensions(); + + bool _handles_type(const StringName &type); + + String _get_resource_type(const String &path); + + Variant _load(const String &path, const String &original_path, bool use_sub_threads, int32_t cache_mode); +#else +#if VERSION_MAJOR > 3 + RES load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) override; +#else +#if VERSION_MINOR > 5 + RES load(const String &path, const String &original_path, Error *error, bool no_subresource_cache = false) override; +#else + RES load(const String &path, const String &original_path, Error *error) override; +#endif +#endif + + void get_recognized_extensions(List *extensions) const override; + + bool handles_type(const String &type) const override; + + String get_resource_type(const String &path) const override; +#endif +}; + +class SpineAtlasResourceFormatSaver : public ResourceFormatSaver { + GDCLASS(SpineAtlasResourceFormatSaver, ResourceFormatSaver) + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + Error _save(const Ref &resource, const String &path, uint32_t flags) override; + + bool _recognize(const Ref &resource); + + PackedStringArray _get_recognized_extensions(const Ref &resource); +#else +#if VERSION_MAJOR > 3 + Error save(const RES &resource, const String &path, uint32_t flags) override; +#else + Error save(const String &path, const RES &resource, uint32_t flags) override; +#endif + + void get_recognized_extensions(const RES &resource, List *extensions) const override; + + bool recognize(const RES &resource) const override; +#endif +}; diff --git a/modules/spine_godot/SpineAttachment.cpp b/modules/spine_godot/SpineAttachment.cpp new file mode 100644 index 000000000000..b6e91929d9ae --- /dev/null +++ b/modules/spine_godot/SpineAttachment.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * 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 "SpineAttachment.h" +#include "SpineCommon.h" + +void SpineAttachment::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_attachment_name"), &SpineAttachment::get_attachment_name); + ClassDB::bind_method(D_METHOD("copy"), &SpineAttachment::copy); +} + +SpineAttachment::~SpineAttachment() { + if (get_spine_object()) get_spine_object()->dereference(); +} + +String SpineAttachment::get_attachment_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +Ref SpineAttachment::copy() { + SPINE_CHECK(get_spine_object(), nullptr) + auto copy = get_spine_object()->copy(); + if (!copy) return nullptr; + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(get_spine_owner(), copy); + return attachment_ref; +} diff --git a/modules/spine_godot/SpineAttachment.h b/modules/spine_godot/SpineAttachment.h new file mode 100644 index 000000000000..3ac8188f3d0c --- /dev/null +++ b/modules/spine_godot/SpineAttachment.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "spine/Attachment.h" +#include + +class SpineSkeletonDataResource; + +class SpineAttachment : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineAttachment, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + ~SpineAttachment() override; + + String get_attachment_name(); + + Ref copy(); + + void set_spine_object(const SpineSkeletonDataResource *_owner, spine::Attachment *_object) override { + if (get_spine_object()) get_spine_object()->dereference(); + _set_spine_object_internal(_owner, _object); + if (_object) _object->reference(); + } +}; diff --git a/modules/spine_godot/SpineBone.cpp b/modules/spine_godot/SpineBone.cpp new file mode 100644 index 000000000000..b8c74646ab2f --- /dev/null +++ b/modules/spine_godot/SpineBone.cpp @@ -0,0 +1,504 @@ +/****************************************************************************** + * 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 "SpineBone.h" +#include "SpineConstant.h" +#include "SpineSprite.h" +#include "SpineSkeleton.h" +#include "SpineCommon.h" + +void SpineBone::_bind_methods() { + ClassDB::bind_method(D_METHOD("update_world_transform"), &SpineBone::update_world_transform); + ClassDB::bind_method(D_METHOD("set_to_setup_pose"), &SpineBone::set_to_setup_pose); + ClassDB::bind_method(D_METHOD("world_to_local", "world_position"), &SpineBone::world_to_local); + ClassDB::bind_method(D_METHOD("world_to_parent", "world_position"), &SpineBone::world_to_parent); + ClassDB::bind_method(D_METHOD("local_to_world", "local_position"), &SpineBone::local_to_world); + ClassDB::bind_method(D_METHOD("parent_to_world", "local_position"), &SpineBone::parent_to_world); + ClassDB::bind_method(D_METHOD("world_to_local_rotation", "world_rotation"), &SpineBone::world_to_local_rotation); + ClassDB::bind_method(D_METHOD("local_to_world_rotation", "local_rotation"), &SpineBone::local_to_world_rotation); + ClassDB::bind_method(D_METHOD("rotate_world", "degrees"), &SpineBone::rotate_world); + ClassDB::bind_method(D_METHOD("get_world_to_local_rotation_x"), &SpineBone::get_world_to_local_rotation_x); + ClassDB::bind_method(D_METHOD("get_world_to_local_rotation_y"), &SpineBone::get_world_to_local_rotation_y); + ClassDB::bind_method(D_METHOD("get_data"), &SpineBone::get_data); + ClassDB::bind_method(D_METHOD("get_parent"), &SpineBone::get_parent); + ClassDB::bind_method(D_METHOD("get_children"), &SpineBone::get_children); + ClassDB::bind_method(D_METHOD("get_x"), &SpineBone::get_x); + ClassDB::bind_method(D_METHOD("set_x", "v"), &SpineBone::set_x); + ClassDB::bind_method(D_METHOD("get_y"), &SpineBone::get_y); + ClassDB::bind_method(D_METHOD("set_y", "v"), &SpineBone::set_y); + ClassDB::bind_method(D_METHOD("get_rotation"), &SpineBone::get_rotation); + ClassDB::bind_method(D_METHOD("set_rotation", "v"), &SpineBone::set_rotation); + ClassDB::bind_method(D_METHOD("get_scale_x"), &SpineBone::get_scale_x); + ClassDB::bind_method(D_METHOD("set_scale_x", "v"), &SpineBone::set_scale_x); + ClassDB::bind_method(D_METHOD("get_scale_y"), &SpineBone::get_scale_y); + ClassDB::bind_method(D_METHOD("set_scale_y", "v"), &SpineBone::set_scale_y); + ClassDB::bind_method(D_METHOD("get_shear_x"), &SpineBone::get_shear_x); + ClassDB::bind_method(D_METHOD("set_shear_x", "v"), &SpineBone::set_shear_x); + ClassDB::bind_method(D_METHOD("get_shear_y"), &SpineBone::get_shear_y); + ClassDB::bind_method(D_METHOD("set_shear_y", "v"), &SpineBone::set_shear_y); + ClassDB::bind_method(D_METHOD("get_applied_rotation"), &SpineBone::get_applied_rotation); + ClassDB::bind_method(D_METHOD("set_applied_rotation", "v"), &SpineBone::set_applied_rotation); + ClassDB::bind_method(D_METHOD("get_a_x"), &SpineBone::get_a_x); + ClassDB::bind_method(D_METHOD("set_a_x", "v"), &SpineBone::set_a_x); + ClassDB::bind_method(D_METHOD("get_a_y"), &SpineBone::get_a_y); + ClassDB::bind_method(D_METHOD("set_a_y", "v"), &SpineBone::set_a_y); + ClassDB::bind_method(D_METHOD("get_a_scale_x"), &SpineBone::get_a_scale_x); + ClassDB::bind_method(D_METHOD("set_a_scale_x", "v"), &SpineBone::set_a_scale_x); + ClassDB::bind_method(D_METHOD("get_a_scale_y"), &SpineBone::get_a_scale_y); + ClassDB::bind_method(D_METHOD("set_a_scale_y", "v"), &SpineBone::set_a_scale_y); + ClassDB::bind_method(D_METHOD("get_a_shear_x"), &SpineBone::get_a_shear_x); + ClassDB::bind_method(D_METHOD("set_a_shear_x", "v"), &SpineBone::set_a_shear_x); + ClassDB::bind_method(D_METHOD("get_a_shear_y"), &SpineBone::get_a_shear_y); + ClassDB::bind_method(D_METHOD("set_a_shear_y", "v"), &SpineBone::set_a_shear_y); + ClassDB::bind_method(D_METHOD("get_a"), &SpineBone::get_a); + ClassDB::bind_method(D_METHOD("set_a", "v"), &SpineBone::set_a); + ClassDB::bind_method(D_METHOD("get_b"), &SpineBone::get_b); + ClassDB::bind_method(D_METHOD("set_b", "v"), &SpineBone::set_b); + ClassDB::bind_method(D_METHOD("get_c"), &SpineBone::get_c); + ClassDB::bind_method(D_METHOD("set_c", "v"), &SpineBone::set_c); + ClassDB::bind_method(D_METHOD("get_d"), &SpineBone::get_d); + ClassDB::bind_method(D_METHOD("set_d", "v"), &SpineBone::set_d); + ClassDB::bind_method(D_METHOD("get_world_x"), &SpineBone::get_world_x); + ClassDB::bind_method(D_METHOD("set_world_x", "v"), &SpineBone::set_world_x); + ClassDB::bind_method(D_METHOD("get_world_y"), &SpineBone::get_world_y); + ClassDB::bind_method(D_METHOD("set_world_y", "v"), &SpineBone::set_world_y); + ClassDB::bind_method(D_METHOD("get_world_rotation_x"), &SpineBone::get_world_rotation_x); + ClassDB::bind_method(D_METHOD("get_world_rotation_y"), &SpineBone::get_world_rotation_y); + ClassDB::bind_method(D_METHOD("get_world_scale_x"), &SpineBone::get_world_scale_x); + ClassDB::bind_method(D_METHOD("get_world_scale_y"), &SpineBone::get_world_scale_y); + ClassDB::bind_method(D_METHOD("is_active"), &SpineBone::is_active); + ClassDB::bind_method(D_METHOD("set_active", "v"), &SpineBone::set_active); + ClassDB::bind_method(D_METHOD("set_inherit", "v"), &SpineBone::set_inherit); + ClassDB::bind_method(D_METHOD("get_inherit"), &SpineBone::get_inherit); + ClassDB::bind_method(D_METHOD("get_transform"), &SpineBone::get_transform); + ClassDB::bind_method(D_METHOD("set_transform", "local_transform"), &SpineBone::set_transform); + ClassDB::bind_method(D_METHOD("get_global_transform"), &SpineBone::get_global_transform); + ClassDB::bind_method(D_METHOD("set_global_transform", "global_transform"), &SpineBone::set_global_transform); +} + +void SpineBone::update_world_transform() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->updateWorldTransform(); +} + +void SpineBone::set_to_setup_pose() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setToSetupPose(); +} + +Vector2 SpineBone::world_to_local(Vector2 world_position) { + SPINE_CHECK(get_spine_object(), Vector2()) + float x, y; + get_spine_object()->worldToLocal(world_position.x, world_position.y, x, y); + return Vector2(x, y); +} + +Vector2 SpineBone::world_to_parent(Vector2 world_position) { + SPINE_CHECK(get_spine_object(), Vector2()) + float x, y; + get_spine_object()->worldToParent(world_position.x, world_position.y, x, y); + return Vector2(x, y); +} + +Vector2 SpineBone::local_to_world(Vector2 local_position) { + SPINE_CHECK(get_spine_object(), Vector2()) + float x, y; + get_spine_object()->localToWorld(local_position.x, local_position.y, x, y); + return Vector2(x, y); +} + +Vector2 SpineBone::parent_to_world(Vector2 local_position) { + SPINE_CHECK(get_spine_object(), Vector2()) + float x, y; + get_spine_object()->parentToWorld(local_position.x, local_position.y, x, y); + return Vector2(x, y); +} + +float SpineBone::world_to_local_rotation(float world_rotation) { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->worldToLocalRotation(world_rotation); +} + +float SpineBone::local_to_world_rotation(float local_rotation) { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->localToWorldRotation(local_rotation); +} + +void SpineBone::rotate_world(float degrees) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->rotateWorld(degrees); +} + +float SpineBone::get_world_to_local_rotation_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldToLocalRotationX(); +} + +float SpineBone::get_world_to_local_rotation_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldToLocalRotationY(); +} + +Ref SpineBone::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &bone_data = get_spine_object()->getData(); + Ref bone_data_ref(memnew(SpineBoneData)); + bone_data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &bone_data); + return bone_data_ref; +} + +Ref SpineBone::get_parent() { + SPINE_CHECK(get_spine_object(), nullptr) + auto parent = get_spine_object()->getParent(); + if (!parent) return nullptr; + Ref parent_ref(memnew(SpineBone)); + parent_ref->set_spine_object(get_spine_owner(), parent); + return parent_ref; +} + + Array SpineBone::get_children() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto children = get_spine_object()->getChildren(); + result.resize((int) children.size()); + for (size_t i = 0; i < children.size(); ++i) { + auto child = children[i]; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(get_spine_owner(), child); + result[i] = bone_ref; + } + return result; +} + +float SpineBone::get_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getX(); +} + +void SpineBone::set_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setX(v); +} + +float SpineBone::get_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getY(); +} + +void SpineBone::set_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setY(v); +} + +float SpineBone::get_rotation() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getRotation(); +} + +void SpineBone::set_rotation(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setRotation(v); +} + +float SpineBone::get_scale_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleX(); +} + +void SpineBone::set_scale_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleX(v); +} + +float SpineBone::get_scale_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleY(); +} + +void SpineBone::set_scale_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleY(v); +} + +float SpineBone::get_shear_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getShearX(); +} + +void SpineBone::set_shear_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setShearX(v); +} + +float SpineBone::get_shear_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getShearY(); +} + +void SpineBone::set_shear_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setShearY(v); +} + +float SpineBone::get_applied_rotation() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAppliedRotation(); +} + +void SpineBone::set_applied_rotation(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAppliedRotation(v); +} + +float SpineBone::get_a_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAX(); +} + +void SpineBone::set_a_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAX(v); +} + +float SpineBone::get_a_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAY(); +} + +void SpineBone::set_a_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAY(v); +} + +float SpineBone::get_a_scale_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAScaleX(); +} + +void SpineBone::set_a_scale_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAScaleX(v); +} + +float SpineBone::get_a_scale_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAScaleY(); +} + +void SpineBone::set_a_scale_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAScaleY(v); +} + +float SpineBone::get_a_shear_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAShearX(); +} + +void SpineBone::set_a_shear_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAShearX(v); +} + +float SpineBone::get_a_shear_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAShearY(); +} + +void SpineBone::set_a_shear_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAShearY(v); +} + +float SpineBone::get_a() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getA(); +} + +void SpineBone::set_a(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setA(v); +} + +float SpineBone::get_b() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getB(); +} + +void SpineBone::set_b(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setB(v); +} + +float SpineBone::get_c() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getC(); +} + +void SpineBone::set_c(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setC(v); +} + +float SpineBone::get_d() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getD(); +} + +void SpineBone::set_d(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setD(v); +} + +float SpineBone::get_world_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldX(); +} + +void SpineBone::set_world_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setWorldX(v); +} + +float SpineBone::get_world_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldY(); +} + +void SpineBone::set_world_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setWorldY(v); +} + +float SpineBone::get_world_rotation_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldRotationX(); +} + +float SpineBone::get_world_rotation_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldRotationY(); +} + + +float SpineBone::get_world_scale_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldScaleX(); +} + +float SpineBone::get_world_scale_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWorldScaleY(); +} + +bool SpineBone::is_active() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isActive(); +} +void SpineBone::set_active(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setActive(v); +} + +SpineConstant::Inherit SpineBone::get_inherit() { + SPINE_CHECK(get_spine_object(), SpineConstant::Inherit_Normal); + return (SpineConstant::Inherit) get_spine_object()->getInherit(); +} + +void SpineBone::set_inherit(SpineConstant::Inherit inherit) { + SPINE_CHECK(get_spine_object(), ); + get_spine_object()->setInherit((spine::Inherit) inherit); +} + +Transform2D SpineBone::get_transform() { + SPINE_CHECK(get_spine_object(), Transform2D()) + Transform2D transform; + transform.rotate(spine::MathUtil::Deg_Rad * get_rotation()); + transform.scale(Size2(get_scale_x(), get_scale_y())); + transform.set_origin(Vector2(get_x(), get_y())); + return transform; +} + +void SpineBone::set_transform(Transform2D transform) { + SPINE_CHECK(get_spine_object(), ) + Vector2 position = transform.get_origin(); + float rotation = spine::MathUtil::Rad_Deg * transform.get_rotation(); + Vector2 scale = transform.get_scale(); + + set_x(position.x); + set_y(position.y); + set_rotation(rotation); + set_scale_x(scale.x); + set_scale_y(scale.y); + + get_spine_owner()->set_modified_bones(); +} + +Transform2D SpineBone::get_global_transform() { + SPINE_CHECK(get_spine_object(), Transform2D()) + if (!get_spine_owner()) return get_transform(); + if (!get_spine_owner()->is_visible_in_tree()) return get_transform(); + Transform2D local; + local.rotate(spine::MathUtil::Deg_Rad * get_world_rotation_x()); + local.scale(Vector2(get_world_scale_x(), get_world_scale_y())); + local.set_origin(Vector2(get_world_x(), get_world_y())); + return get_spine_owner()->get_global_transform() * local; +} + +void SpineBone::set_global_transform(Transform2D transform) { + SPINE_CHECK(get_spine_object(), ) + if (!get_spine_owner()) set_transform(transform); + if (!get_spine_owner()->is_visible_in_tree()) return; + + auto bone = get_spine_object(); + + Transform2D inverse_sprite_transform = get_spine_owner()->get_global_transform().affine_inverse(); + transform = inverse_sprite_transform * transform; + Vector2 position = transform.get_origin(); + float rotation = spine::MathUtil::Rad_Deg * transform.get_rotation(); + Vector2 scale = transform.get_scale(); + Vector2 local_position = position; + float local_rotation = bone->worldToLocalRotation(rotation) - 180; + Vector2 local_scale = scale; + spine::Bone *parent = bone->getParent(); + if (parent) { + float local_x = (float)local_position.x; + float local_y = (float)local_position.y; + parent->worldToLocal((float)local_position.x, (float)local_position.y, local_x, local_y); + local_position.x = local_x; + local_position.y = local_y; + } + bone->setX(local_position.x); + bone->setY(local_position.y); + bone->setRotation(local_rotation); + bone->setScaleX(local_scale.x); + bone->setScaleY(local_scale.y); + + get_spine_owner()->set_modified_bones(); +} diff --git a/modules/spine_godot/SpineBone.h b/modules/spine_godot/SpineBone.h new file mode 100644 index 000000000000..9a286cfd0fe5 --- /dev/null +++ b/modules/spine_godot/SpineBone.h @@ -0,0 +1,186 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineBoneData.h" +#include "SpineConstant.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "scene/2d/node_2d.h" +#endif +#include + +class SpineSkeleton; +class SpineSprite; + +class SpineBone : public SpineSpriteOwnedObject { + GDCLASS(SpineBone, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void update_world_transform(); + + void set_to_setup_pose(); + + Vector2 world_to_local(Vector2 world_position); + + Vector2 world_to_parent(Vector2 world_position); + + Vector2 local_to_world(Vector2 local_position); + + Vector2 parent_to_world(Vector2 local_position); + + float world_to_local_rotation(float world_rotation); + + float local_to_world_rotation(float local_rotation); + + void rotate_world(float degrees); + + float get_world_to_local_rotation_x(); + + float get_world_to_local_rotation_y(); + + Ref get_data(); + + Ref get_parent(); + + Array get_children(); + + float get_x(); + + void set_x(float v); + + float get_y(); + + void set_y(float v); + + float get_rotation(); + + void set_rotation(float v); + + float get_scale_x(); + + void set_scale_x(float v); + + float get_scale_y(); + + void set_scale_y(float v); + + float get_shear_x(); + + void set_shear_x(float v); + + float get_shear_y(); + + void set_shear_y(float v); + + float get_applied_rotation(); + + void set_applied_rotation(float v); + + float get_a_x(); + + void set_a_x(float v); + + float get_a_y(); + + void set_a_y(float v); + + float get_a_scale_x(); + + void set_a_scale_x(float v); + + float get_a_scale_y(); + + void set_a_scale_y(float v); + + float get_a_shear_x(); + + void set_a_shear_x(float v); + + float get_a_shear_y(); + + void set_a_shear_y(float v); + + float get_a(); + + void set_a(float v); + + float get_b(); + + void set_b(float v); + + float get_c(); + + void set_c(float v); + + float get_d(); + + void set_d(float v); + + float get_world_x(); + + void set_world_x(float v); + + float get_world_y(); + + void set_world_y(float v); + + float get_world_rotation_x(); + + float get_world_rotation_y(); + + float get_world_scale_x(); + + float get_world_scale_y(); + + bool is_active(); + + void set_active(bool v); + + SpineConstant::Inherit get_inherit(); + + void set_inherit(SpineConstant::Inherit inherit); + + // External feature functions + void apply_world_transform_2d(const Variant &o); + + Transform2D get_transform(); + + void set_transform(Transform2D transform); + + Transform2D get_global_transform(); + + void set_global_transform(Transform2D trans); +}; diff --git a/modules/spine_godot/SpineBoneData.cpp b/modules/spine_godot/SpineBoneData.cpp new file mode 100644 index 000000000000..228464d6020b --- /dev/null +++ b/modules/spine_godot/SpineBoneData.cpp @@ -0,0 +1,213 @@ +/****************************************************************************** + * 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 "SpineBoneData.h" +#include "SpineCommon.h" + +void SpineBoneData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_index"), &SpineBoneData::get_index); + ClassDB::bind_method(D_METHOD("get_bone_name"), &SpineBoneData::get_bone_name); + ClassDB::bind_method(D_METHOD("get_parent"), &SpineBoneData::get_parent); + ClassDB::bind_method(D_METHOD("get_length"), &SpineBoneData::get_length); + ClassDB::bind_method(D_METHOD("set_length", "v"), &SpineBoneData::set_length); + ClassDB::bind_method(D_METHOD("get_x"), &SpineBoneData::get_x); + ClassDB::bind_method(D_METHOD("set_x", "v"), &SpineBoneData::set_x); + ClassDB::bind_method(D_METHOD("get_y"), &SpineBoneData::get_y); + ClassDB::bind_method(D_METHOD("set_y", "v"), &SpineBoneData::set_y); + ClassDB::bind_method(D_METHOD("get_rotation"), &SpineBoneData::get_rotation); + ClassDB::bind_method(D_METHOD("set_rotation", "v"), &SpineBoneData::set_rotation); + ClassDB::bind_method(D_METHOD("get_scale_x"), &SpineBoneData::get_scale_x); + ClassDB::bind_method(D_METHOD("set_scale_x", "v"), &SpineBoneData::set_scale_x); + ClassDB::bind_method(D_METHOD("get_scale_y"), &SpineBoneData::get_scale_y); + ClassDB::bind_method(D_METHOD("set_scale_y", "v"), &SpineBoneData::set_scale_y); + ClassDB::bind_method(D_METHOD("get_shear_x"), &SpineBoneData::get_shear_x); + ClassDB::bind_method(D_METHOD("set_shear_x", "v"), &SpineBoneData::set_shear_x); + ClassDB::bind_method(D_METHOD("get_shear_y"), &SpineBoneData::get_shear_y); + ClassDB::bind_method(D_METHOD("set_shear_y", "v"), &SpineBoneData::set_shear_y); + ClassDB::bind_method(D_METHOD("get_inherit"), &SpineBoneData::get_inherit); + ClassDB::bind_method(D_METHOD("set_inherit", "v"), &SpineBoneData::set_inherit); + ClassDB::bind_method(D_METHOD("is_skin_required"), &SpineBoneData::is_skin_required); + ClassDB::bind_method(D_METHOD("set_skin_required", "v"), &SpineBoneData::set_skin_required); + ClassDB::bind_method(D_METHOD("get_color"), &SpineBoneData::get_color); + ClassDB::bind_method(D_METHOD("set_color", "v"), &SpineBoneData::set_color); + ClassDB::bind_method(D_METHOD("get_icon"), &SpineBoneData::get_icon); + ClassDB::bind_method(D_METHOD("set_visible", "v"), &SpineBoneData::set_visible); + ClassDB::bind_method(D_METHOD("is_visible"), &SpineBoneData::is_visible); +} + +int SpineBoneData::get_index() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getIndex(); +} + +String SpineBoneData::get_bone_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +Ref SpineBoneData::get_parent() { + SPINE_CHECK(get_spine_object(), nullptr) + auto parent = get_spine_object()->getParent(); + if (!parent) return nullptr; + Ref parent_ref(memnew(SpineBoneData)); + parent_ref->set_spine_object(get_spine_owner(), parent); + return parent_ref; +} + +float SpineBoneData::get_length() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getLength(); +} + +void SpineBoneData::set_length(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setLength(v); +} + +float SpineBoneData::get_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getX(); +} + +void SpineBoneData::set_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setX(v); +} + +float SpineBoneData::get_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getY(); +} + +void SpineBoneData::set_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setY(v); +} + +float SpineBoneData::get_rotation() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getRotation(); +} + +void SpineBoneData::set_rotation(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setRotation(v); +} + +float SpineBoneData::get_scale_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleX(); +} + +void SpineBoneData::set_scale_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleX(v); +} + +float SpineBoneData::get_scale_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleY(); +} + +void SpineBoneData::set_scale_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleY(v); +} + +float SpineBoneData::get_shear_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getShearX(); +} + +void SpineBoneData::set_shear_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setShearX(v); +} + +float SpineBoneData::get_shear_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getShearY(); +} + +void SpineBoneData::set_shear_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setShearY(v); +} + +SpineConstant::Inherit SpineBoneData::get_inherit() { + SPINE_CHECK(get_spine_object(), SpineConstant::Inherit::Inherit_Normal) + return (SpineConstant::Inherit) get_spine_object()->getInherit(); +} + +void SpineBoneData::set_inherit(SpineConstant::Inherit v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setInherit((spine::Inherit) v); +} + +bool SpineBoneData::is_skin_required() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isSkinRequired(); +} + +void SpineBoneData::set_skin_required(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setSkinRequired(v); +} + +Color SpineBoneData::get_color() { + SPINE_CHECK(get_spine_object(), Color()) + auto color = get_spine_object()->getColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineBoneData::set_color(Color color) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->getColor().set(color.r, color.g, color.b, color.a); +} + +String SpineBoneData::get_icon() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getIcon().buffer(); +} + +bool SpineBoneData::is_visible() { + SPINE_CHECK(get_spine_object(), true) + return get_spine_object()->isVisible(); +} + +void SpineBoneData::set_visible(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setVisible(v); +} diff --git a/modules/spine_godot/SpineBoneData.h b/modules/spine_godot/SpineBoneData.h new file mode 100644 index 000000000000..e9f916eceefe --- /dev/null +++ b/modules/spine_godot/SpineBoneData.h @@ -0,0 +1,100 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineConstant.h" +#include + +class SpineSkeletonDataResource; + +class SpineBoneData : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineBoneData, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + int get_index(); + + String get_bone_name(); + + Ref get_parent(); + + float get_length(); + + void set_length(float v); + + float get_x(); + + void set_x(float v); + + float get_y(); + + void set_y(float v); + + float get_rotation(); + + void set_rotation(float v); + + float get_scale_x(); + + void set_scale_x(float v); + + float get_scale_y(); + + void set_scale_y(float v); + + float get_shear_x(); + + void set_shear_x(float v); + + float get_shear_y(); + + void set_shear_y(float v); + + SpineConstant::Inherit get_inherit(); + + void set_inherit(SpineConstant::Inherit v); + + bool is_skin_required(); + + void set_skin_required(bool v); + + Color get_color(); + + void set_color(Color color); + + String get_icon(); + + bool is_visible(); + + void set_visible(bool v); +}; diff --git a/modules/spine_godot/SpineBoneNode.cpp b/modules/spine_godot/SpineBoneNode.cpp new file mode 100644 index 000000000000..0c5c7b718491 --- /dev/null +++ b/modules/spine_godot/SpineBoneNode.cpp @@ -0,0 +1,283 @@ +/****************************************************************************** + * 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 "SpineBoneNode.h" + +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#else +#if VERSION_MAJOR > 3 +#include "core/config/engine.h" +#else +#include "core/engine.h" +#endif +#endif + +void SpineBoneNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone_mode", "bone_mode"), &SpineBoneNode::set_bone_mode); + ClassDB::bind_method(D_METHOD("get_bone_mode"), &SpineBoneNode::get_bone_mode); + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SpineBoneNode::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SpineBoneNode::get_enabled); + ClassDB::bind_method(D_METHOD("set_debug_thickness", "thickness"), &SpineBoneNode::set_debug_thickness); + ClassDB::bind_method(D_METHOD("get_debug_thickness"), &SpineBoneNode::get_debug_thickness); + ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &SpineBoneNode::set_debug_color); + ClassDB::bind_method(D_METHOD("get_debug_color"), &SpineBoneNode::get_debug_color); + ClassDB::bind_method(D_METHOD("_on_world_transforms_changed", "spine_sprite"), &SpineBoneNode::on_world_transforms_changed); + ClassDB::bind_method(D_METHOD("find_bone"), &SpineBoneNode::find_bone); + ClassDB::bind_method(D_METHOD("find_sprite"), &SpineBoneNode::find_parent_sprite); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_mode", PROPERTY_HINT_ENUM, "Follow,Drive"), "set_bone_mode", "get_bone_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_GROUP("Debug", ""); + ADD_PROPERTY(PropertyInfo(VARIANT_FLOAT, "thickness"), "set_debug_thickness", "get_debug_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_debug_color", "get_debug_color"); +} + +void SpineBoneNode::_notification(int what) { + switch (what) { + case NOTIFICATION_PARENTED: { + SpineSprite *sprite = find_parent_sprite(); + if (sprite) { +#if VERSION_MAJOR > 3 + sprite->connect(SNAME("world_transforms_changed"), callable_mp(this, &SpineBoneNode::on_world_transforms_changed)); +#else + sprite->connect(SNAME("world_transforms_changed"), this, SNAME("_on_world_transforms_changed")); +#endif + update_transform(sprite); +#if VERSION_MAJOR == 3 + _change_notify("transform/translation"); + _change_notify("transform/rotation"); + _change_notify("transform/scale"); + _change_notify("translation"); + _change_notify("rotation"); + _change_notify("rotation_deg"); + _change_notify("scale"); +#endif + } else { + WARN_PRINT("SpineBoneNode parent is not a SpineSprite."); + } + NOTIFY_PROPERTY_LIST_CHANGED(); + break; + } + case NOTIFICATION_UNPARENTED: { + SpineSprite *sprite = find_parent_sprite(); + if (sprite) { +#if VERSION_MAJOR > 3 + sprite->disconnect(SNAME("world_transforms_changed"), callable_mp(this, &SpineBoneNode::on_world_transforms_changed)); +#else + sprite->disconnect(SNAME("world_transforms_changed"), this, SNAME("_on_world_transforms_changed")); +#endif + } + break; + } + case NOTIFICATION_DRAW: { + draw(); + break; + } + default: + break; + } +} + +void SpineBoneNode::_get_property_list(List *list) const { +#ifdef SPINE_GODOT_EXTENSION + PackedStringArray bone_names; +#else + Vector bone_names; +#endif + SpineSprite *sprite = find_parent_sprite(); + if (sprite) sprite->get_skeleton_data_res()->get_bone_names(bone_names); + else + bone_names.push_back(bone_name); + auto element = list->front(); + while (element) { + auto property_info = element->get(); + if (property_info.name == StringName("SpineBoneNode")) break; + element = element->next(); + } + PropertyInfo slot_name_property; + slot_name_property.name = "bone_name"; + slot_name_property.type = Variant::STRING; + slot_name_property.hint_string = String(",").join(bone_names); + slot_name_property.hint = PROPERTY_HINT_ENUM; + slot_name_property.usage = PROPERTY_USAGE_DEFAULT; + list->insert_after(element, slot_name_property); +} + +bool SpineBoneNode::_get(const StringName &property, Variant &value) const { + if (property == StringName("bone_name")) { + value = bone_name; + return true; + } + return false; +} + +bool SpineBoneNode::_set(const StringName &property, const Variant &value) { + if (property == StringName("bone_name")) { + bone_name = value; + SpineSprite *sprite = find_parent_sprite(); + init_transform(sprite); + return true; + } + return false; +} + +void SpineBoneNode::on_world_transforms_changed(const Variant &_sprite) { + SpineSprite *sprite = cast_to(_sprite.operator Object *()); + update_transform(sprite); +#if VERSION_MAJOR > 3 + queue_redraw(); +#else + update(); +#endif +} + +void SpineBoneNode::update_transform(SpineSprite *sprite) { + if (!enabled) return; + Ref bone = find_bone(); + if (!bone.is_valid()) return; + + Transform2D bone_transform = bone->get_global_transform(); + Transform2D this_transform = get_global_transform(); + + if (bone_mode == SpineConstant::BoneMode_Drive) { + bone->set_global_transform(this_transform); + } else { + set_global_transform(bone_transform); + } + + if (Engine::get_singleton()->is_editor_hint()) { +#if VERSION_MAJOR == 3 + _change_notify("transform/translation"); + _change_notify("transform/rotation"); + _change_notify("transform/scale"); + _change_notify("translation"); + _change_notify("rotation"); + _change_notify("rotation_deg"); + _change_notify("scale"); +#endif + } +} + +void SpineBoneNode::init_transform(SpineSprite *sprite) { + if (!sprite) return; + if (bone_mode == SpineConstant::BoneMode_Drive) return; + sprite->get_skeleton()->set_to_setup_pose(); + sprite->get_skeleton()->update_world_transform(SpineConstant::Physics_Update); + Transform2D global_transform = sprite->get_global_bone_transform(bone_name); + set_global_transform(global_transform); + update_transform(sprite); +} + +SpineSprite *SpineBoneNode::find_parent_sprite() const { + auto parent = get_parent(); + SpineSprite *sprite = nullptr; + while (parent) { + sprite = cast_to(parent); + if (sprite) break; + parent = parent->get_parent(); + } + return sprite; +} + +Ref SpineBoneNode::find_bone() const { + if (!is_visible_in_tree()) return nullptr; + SpineSprite *sprite = find_parent_sprite(); + if (!sprite) return nullptr; + if (!sprite->get_skeleton().is_valid() || !sprite->get_skeleton()->get_spine_object()) return nullptr; + auto bone = sprite->get_skeleton()->find_bone(bone_name); + return bone; +} + +void SpineBoneNode::draw() { + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) return; + Ref bone = find_bone(); + if (!bone.is_valid()) return; + + spine::Bone *spine_bone = bone->get_spine_object(); + if (!spine_bone) return; + float bone_length = spine_bone->getData().getLength(); + if (bone_length == 0) { + draw_circle(Vector2(0, 0), debug_thickness, debug_color); + } else { +#ifdef SPINE_GODOT_EXTENSION + PackedVector2Array points; +#else + Vector points; +#endif + points.push_back(Vector2(-debug_thickness, 0)); + points.push_back(Vector2(0, debug_thickness)); + points.push_back(Vector2(bone_length, 0)); + points.push_back(Vector2(0, -debug_thickness)); + draw_colored_polygon(points, debug_color); + } +} + +SpineConstant::BoneMode SpineBoneNode::get_bone_mode() { + return bone_mode; +} + +void SpineBoneNode::set_bone_mode(SpineConstant::BoneMode _bone_mode) { + if (bone_mode != _bone_mode) { + bone_mode = _bone_mode; + SpineSprite *sprite = find_parent_sprite(); + init_transform(sprite); + } +} + +void SpineBoneNode::set_debug_thickness(float _thickness) { + debug_thickness = _thickness; +} + +float SpineBoneNode::get_debug_thickness() { + return debug_thickness; +} + +void SpineBoneNode::set_debug_color(Color _color) { + debug_color = _color; +} + +Color SpineBoneNode::get_debug_color() { + return debug_color; +} + +void SpineBoneNode::set_enabled(bool _enabled) { + enabled = _enabled; + if (!enabled && Engine::get_singleton()->is_editor_hint()) { + auto sprite = find_parent_sprite(); + if (!sprite) return; + sprite->get_skeleton()->set_to_setup_pose(); + sprite->get_skeleton()->update_world_transform(SpineConstant::Physics_Update); + } +} + +bool SpineBoneNode::get_enabled() { + return enabled; +} diff --git a/modules/spine_godot/SpineBoneNode.h b/modules/spine_godot/SpineBoneNode.h new file mode 100644 index 000000000000..7aa2f19b02a3 --- /dev/null +++ b/modules/spine_godot/SpineBoneNode.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineSkeleton.h" +#include "SpineSprite.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "scene/2d/node_2d.h" +#endif + +class SpineBoneNode : public Node2D { + GDCLASS(SpineBoneNode, Node2D) + +protected: + String bone_name; + SpineConstant::BoneMode bone_mode; + bool enabled; + Color debug_color; + float debug_thickness; + + static void _bind_methods(); + void _notification(int what); + void _get_property_list(List *list) const; + bool _get(const StringName &property, Variant &value) const; + bool _set(const StringName &property, const Variant &value); + void on_world_transforms_changed(const Variant &_sprite); + void update_transform(SpineSprite *sprite); + void init_transform(SpineSprite *sprite); + void draw(); + +public: + SpineBoneNode() : bone_mode(SpineConstant::BoneMode_Follow), enabled(true), debug_color(Color::hex(0xff000077)), debug_thickness(5) {} + + SpineConstant::BoneMode get_bone_mode(); + + void set_bone_mode(SpineConstant::BoneMode bone_mode); + + void set_enabled(bool _enabled); + + bool get_enabled(); + + void set_debug_thickness(float _thickness); + + float get_debug_thickness(); + + void set_debug_color(Color _color); + + Color get_debug_color(); + + SpineSprite *find_parent_sprite() const; + + Ref find_bone() const; +}; diff --git a/modules/spine_godot/SpineCommon.h b/modules/spine_godot/SpineCommon.h new file mode 100644 index 000000000000..29e8e6ff8752 --- /dev/null +++ b/modules/spine_godot/SpineCommon.h @@ -0,0 +1,202 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_COMMON_H +#define SPINE_COMMON_H + +#ifdef SPINE_GODOT_EXTENSION +#include + +// When running scons with deprecated=no, these are not defined in version.h in Godot 4.5.1 +// but our code for older versions of Godot relies on them. +#ifndef VERSION_MAJOR +#define VERSION_MAJOR GODOT_VERSION_MAJOR +#define VERSION_MINOR GODOT_VERSION_MINOR +#define VERSION_PATCH GODOT_VERSION_PATCH +#endif + +#include +#include +using namespace godot; +#define REFCOUNTED RefCounted +#define EMPTY(x) ((x).is_empty()) +#define EMPTY_PTR(x) ((x)->is_empty()) +#define SSIZE(x) ((x).length()) +#define INSTANTIATE(x) (x).instantiate() +#define NOTIFY_PROPERTY_LIST_CHANGED() notify_property_list_changed() +#define VARIANT_FLOAT Variant::FLOAT +#define PROPERTY_USAGE_NOEDITOR PROPERTY_USAGE_NO_EDITOR +#define RES Ref +#define REF Ref +#define GEOMETRY2D Geometry2D +// FIXME this doesn't do the same as the engine SNAME in terms of caching +#define SNAME(name) StringName(name) +#define RS RenderingServer +#else +#include "core/version.h" + +// When running scons with deprecated=no, these are not defined in version.h in Godot 4.5.1 +// but our code for older versions of Godot relies on them. +#ifndef VERSION_MAJOR +#define VERSION_MAJOR GODOT_VERSION_MAJOR +#define VERSION_MINOR GODOT_VERSION_MINOR +#define VERSION_PATCH GODOT_VERSION_PATCH +#endif + +#if VERSION_MAJOR > 3 +#include "core/core_bind.h" +#include "core/error/error_macros.h" +#define REFCOUNTED RefCounted +#define EMPTY(x) ((x).is_empty()) +#define EMPTY_PTR(x) ((x)->is_empty()) +#define SSIZE(x) ((x).size()) +#define INSTANTIATE(x) (x).instantiate() +#define NOTIFY_PROPERTY_LIST_CHANGED() notify_property_list_changed() +#define VARIANT_FLOAT Variant::FLOAT +#define PROPERTY_USAGE_NOEDITOR PROPERTY_USAGE_NO_EDITOR +#define RES Ref +#define REF Ref +#define GEOMETRY2D Geometry2D +#else +#include "core/object.h" +#include "core/reference.h" +#include "core/error_macros.h" +#define REFCOUNTED Reference +#define EMPTY(x) ((x).empty()) +#define EMPTY_PTR(x) ((x)->empty()) +#define SSIZE(x) ((x).size()) +#define INSTANTIATE(x) (x).instance() +#define NOTIFY_PROPERTY_LIST_CHANGED() property_list_changed_notify() +#define VARIANT_FLOAT Variant::REAL +#define GDREGISTER_CLASS(x) ClassDB::register_class() +#define GEOMETRY2D Geometry +#ifndef SNAME +#define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = _scs_create(m_arg); return sname; })() +#endif +#endif +#endif + +#define SPINE_CHECK(obj, ret) \ + if (!(obj)) { \ + ERR_PRINT("Native Spine object not set."); \ + return ret; \ + } + +#define SPINE_STRING(x) spine::String((x).utf8().ptr()) +#define SPINE_STRING_TMP(x) spine::String((x).utf8().ptr(), true, false) + +// Can't do template classes with Godot's object model :( +class SpineObjectWrapper : public REFCOUNTED { + GDCLASS(SpineObjectWrapper, REFCOUNTED) + + Object *spine_owner; + void *spine_object; + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("_internal_spine_objects_invalidated"), &SpineObjectWrapper::spine_objects_invalidated); + } + + void spine_objects_invalidated() { + spine_object = nullptr; +#if VERSION_MAJOR > 3 + spine_owner->disconnect(SNAME("_internal_spine_objects_invalidated"), callable_mp(this, &SpineObjectWrapper::spine_objects_invalidated)); +#else + spine_owner->disconnect(SNAME("_internal_spine_objects_invalidated"), this, SNAME("_internal_spine_objects_invalidated")); +#endif + } + + SpineObjectWrapper() : spine_owner(nullptr), spine_object(nullptr) { + } + + template + void _set_spine_object_internal(const OWNER *_owner, OBJECT *_object) { + if (spine_owner) { + ERR_PRINT("Owner already set."); + return; + } + if (spine_object) { + ERR_PRINT("Object already set."); + return; + } + if (!_owner) { + ERR_PRINT("Owner must not be null."); + return; + } + + spine_owner = (Object *) _owner; + spine_object = _object; +#if VERSION_MAJOR > 3 + spine_owner->connect(SNAME("_internal_spine_objects_invalidated"), callable_mp(this, &SpineObjectWrapper::spine_objects_invalidated)); +#else + spine_owner->connect(SNAME("_internal_spine_objects_invalidated"), this, SNAME("_internal_spine_objects_invalidated")); +#endif + } + + void *_get_spine_object_internal() { return spine_object; } + void *_get_spine_owner_internal() { return spine_owner; } +}; + +class SpineSprite; + +template +class SpineSpriteOwnedObject : public SpineObjectWrapper { +public: + void set_spine_object(const SpineSprite *_owner, OBJECT *_object) { + _set_spine_object_internal(_owner, _object); + } + + OBJECT *get_spine_object() { + return (OBJECT *) _get_spine_object_internal(); + } + + SpineSprite *get_spine_owner() { + return (SpineSprite *) _get_spine_owner_internal(); + } +}; + +class SpineSkeletonDataResource; + +template +class SpineSkeletonDataResourceOwnedObject : public SpineObjectWrapper { +public: + virtual void set_spine_object(const SpineSkeletonDataResource *_owner, OBJECT *_object) { + _set_spine_object_internal(_owner, _object); + } + + OBJECT *get_spine_object() { + return (OBJECT *) _get_spine_object_internal(); + } + + SpineSkeletonDataResource *get_spine_owner() { + return (SpineSkeletonDataResource *) _get_spine_owner_internal(); + } +}; + +#endif diff --git a/modules/spine_godot/SpineConstant.cpp b/modules/spine_godot/SpineConstant.cpp new file mode 100644 index 000000000000..dfe4aabb5fff --- /dev/null +++ b/modules/spine_godot/SpineConstant.cpp @@ -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 "SpineConstant.h" + +void SpineConstant::_bind_methods() { + BIND_ENUM_CONSTANT(MixBlend_Setup) + BIND_ENUM_CONSTANT(MixBlend_First) + BIND_ENUM_CONSTANT(MixBlend_Replace) + BIND_ENUM_CONSTANT(MixBlend_Add) + + BIND_ENUM_CONSTANT(MixDirection_In) + BIND_ENUM_CONSTANT(MixDirection_Out) + + BIND_ENUM_CONSTANT(Property_Rotate) + BIND_ENUM_CONSTANT(Property_X) + BIND_ENUM_CONSTANT(Property_Y) + BIND_ENUM_CONSTANT(Property_ScaleX) + BIND_ENUM_CONSTANT(Property_ScaleY) + BIND_ENUM_CONSTANT(Property_ShearX) + BIND_ENUM_CONSTANT(Property_ShearY) + BIND_ENUM_CONSTANT(Property_Rgb) + BIND_ENUM_CONSTANT(Property_Alpha) + BIND_ENUM_CONSTANT(Property_Rgb2) + BIND_ENUM_CONSTANT(Property_Attachment) + BIND_ENUM_CONSTANT(Property_Deform) + BIND_ENUM_CONSTANT(Property_Event) + BIND_ENUM_CONSTANT(Property_DrawOrder) + BIND_ENUM_CONSTANT(Property_IkConstraint) + BIND_ENUM_CONSTANT(Property_TransformConstraint) + BIND_ENUM_CONSTANT(Property_PathConstraintPosition) + BIND_ENUM_CONSTANT(Property_PathConstraintSpacing) + BIND_ENUM_CONSTANT(Property_PathConstraintMix) + BIND_ENUM_CONSTANT(Property_Sequence) + + BIND_ENUM_CONSTANT(Inherit_Normal) + BIND_ENUM_CONSTANT(Inherit_OnlyTranslation) + BIND_ENUM_CONSTANT(Inherit_NoRotationOrReflection) + BIND_ENUM_CONSTANT(Inherit_NoScale) + BIND_ENUM_CONSTANT(Inherit_NoScaleOrReflection) + + BIND_ENUM_CONSTANT(PositionMode_Fixed) + BIND_ENUM_CONSTANT(PositionMode_Percent) + + BIND_ENUM_CONSTANT(SpacingMode_Length) + BIND_ENUM_CONSTANT(SpacingMode_Fixed) + BIND_ENUM_CONSTANT(SpacingMode_Percent) + + BIND_ENUM_CONSTANT(RotateMode_Tangent) + BIND_ENUM_CONSTANT(RotateMode_Chain) + BIND_ENUM_CONSTANT(RotateMode_ChainScale) + + BIND_ENUM_CONSTANT(BlendMode_Normal) + BIND_ENUM_CONSTANT(BlendMode_Additive) + BIND_ENUM_CONSTANT(BlendMode_Multiply) + BIND_ENUM_CONSTANT(BlendMode_Screen) + + BIND_ENUM_CONSTANT(UpdateMode_Process) + BIND_ENUM_CONSTANT(UpdateMode_Physics) + BIND_ENUM_CONSTANT(UpdateMode_Manual) + + BIND_ENUM_CONSTANT(BoneMode_Follow) + BIND_ENUM_CONSTANT(BoneMode_Drive) + + BIND_ENUM_CONSTANT(Physics_None); + BIND_ENUM_CONSTANT(Physics_Reset); + BIND_ENUM_CONSTANT(Physics_Update); + BIND_ENUM_CONSTANT(Physics_Pose); +} diff --git a/modules/spine_godot/SpineConstant.h b/modules/spine_godot/SpineConstant.h new file mode 100644 index 000000000000..9bb96f87e187 --- /dev/null +++ b/modules/spine_godot/SpineConstant.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" + +class SpineConstant : public Object { + GDCLASS(SpineConstant, Object); + +protected: + static void _bind_methods(); + +public: + enum MixBlend { + MixBlend_Setup = 0, + MixBlend_First, + MixBlend_Replace, + MixBlend_Add + }; + + enum MixDirection { + MixDirection_In = 0, + MixDirection_Out + }; + + enum PropertyId { + Property_Rotate = 1 << 0, + Property_X = 1 << 1, + Property_Y = 1 << 2, + Property_ScaleX = 1 << 3, + Property_ScaleY = 1 << 4, + Property_ShearX = 1 << 5, + Property_ShearY = 1 << 6, + Property_Rgb = 1 << 7, + Property_Alpha = 1 << 8, + Property_Rgb2 = 1 << 9, + Property_Attachment = 1 << 10, + Property_Deform = 1 << 11, + Property_Event = 1 << 12, + Property_DrawOrder = 1 << 13, + Property_IkConstraint = 1 << 14, + Property_TransformConstraint = 1 << 15, + Property_PathConstraintPosition = 1 << 16, + Property_PathConstraintSpacing = 1 << 17, + Property_PathConstraintMix = 1 << 18, + Property_Sequence = 1 << 19 + }; + + enum Inherit { + Inherit_Normal = 0, + Inherit_OnlyTranslation, + Inherit_NoRotationOrReflection, + Inherit_NoScale, + Inherit_NoScaleOrReflection + }; + + enum PositionMode { + PositionMode_Fixed = 0, + PositionMode_Percent + }; + + enum SpacingMode { + SpacingMode_Length = 0, + SpacingMode_Fixed, + SpacingMode_Percent + }; + + enum RotateMode { + RotateMode_Tangent = 0, + RotateMode_Chain, + RotateMode_ChainScale + }; + + enum BlendMode { + BlendMode_Normal = 0, + BlendMode_Additive, + BlendMode_Multiply, + BlendMode_Screen + }; + + enum UpdateMode { + UpdateMode_Process, + UpdateMode_Physics, + UpdateMode_Manual + }; + + enum BoneMode { + BoneMode_Follow, + BoneMode_Drive + }; + + enum Physics { + Physics_None, + Physics_Reset, + Physics_Update, + Physics_Pose + }; +}; + +VARIANT_ENUM_CAST(SpineConstant::MixBlend) +VARIANT_ENUM_CAST(SpineConstant::MixDirection) +VARIANT_ENUM_CAST(SpineConstant::PropertyId) +VARIANT_ENUM_CAST(SpineConstant::Inherit) +VARIANT_ENUM_CAST(SpineConstant::PositionMode) +VARIANT_ENUM_CAST(SpineConstant::SpacingMode) +VARIANT_ENUM_CAST(SpineConstant::RotateMode) +VARIANT_ENUM_CAST(SpineConstant::BlendMode) +VARIANT_ENUM_CAST(SpineConstant::UpdateMode) +VARIANT_ENUM_CAST(SpineConstant::BoneMode) +VARIANT_ENUM_CAST(SpineConstant::Physics) diff --git a/modules/spine_godot/SpineConstraintData.cpp b/modules/spine_godot/SpineConstraintData.cpp new file mode 100644 index 000000000000..0336d101ff94 --- /dev/null +++ b/modules/spine_godot/SpineConstraintData.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * 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 "SpineConstraintData.h" +#include "SpineCommon.h" +#include + +void SpineConstraintData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_constraint_name"), &SpineConstraintData::get_constraint_name); + ClassDB::bind_method(D_METHOD("get_order"), &SpineConstraintData::get_order); + ClassDB::bind_method(D_METHOD("set_order", "v"), &SpineConstraintData::set_order); + ClassDB::bind_method(D_METHOD("is_skin_required"), &SpineConstraintData::is_skin_required); + ClassDB::bind_method(D_METHOD("set_skin_required", "v"), &SpineConstraintData::set_skin_required); +} + +String SpineConstraintData::get_constraint_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +int SpineConstraintData::get_order() { + SPINE_CHECK(get_spine_object(), 0) + return (int) get_spine_object()->getOrder(); +} + +void SpineConstraintData::set_order(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setOrder(v); +} + +bool SpineConstraintData::is_skin_required() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isSkinRequired(); +} + +void SpineConstraintData::set_skin_required(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setSkinRequired(v); +} diff --git a/modules/spine_godot/SpineConstraintData.h b/modules/spine_godot/SpineConstraintData.h new file mode 100644 index 000000000000..80b3db7f324b --- /dev/null +++ b/modules/spine_godot/SpineConstraintData.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include + +class SpineSkeletonDataResource; + +class SpineConstraintData : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineConstraintData, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + String get_constraint_name(); + + int get_order(); + + void set_order(int v); + + bool is_skin_required(); + + void set_skin_required(bool v); +}; diff --git a/modules/spine_godot/SpineEditorPlugin.cpp b/modules/spine_godot/SpineEditorPlugin.cpp new file mode 100644 index 000000000000..830a4ee47740 --- /dev/null +++ b/modules/spine_godot/SpineEditorPlugin.cpp @@ -0,0 +1,488 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifdef TOOLS_ENABLED +#include "SpineEditorPlugin.h" +#include "SpineAtlasResource.h" +#include "SpineSkeletonFileResource.h" + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "editor/editor_undo_redo_manager.h" +#endif +#ifdef SPINE_GODOT_EXTENSION +Error SpineAtlasResourceImportPlugin::_import(const String &source_file, const String &save_path, const Dictionary &options, const TypedArray &platform_variants, const TypedArray &gen_files) const { +#else +#if VERSION_MINOR > 3 +Error SpineAtlasResourceImportPlugin::import(ResourceUID::ID p_source_id, const String &source_file, const String &save_path, const HashMap &options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { +#else +Error SpineAtlasResourceImportPlugin::import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif +#endif +#else +Error SpineAtlasResourceImportPlugin::import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif + Ref atlas(memnew(SpineAtlasResource)); + atlas->set_normal_texture_prefix(options["normal_map_prefix"]); + atlas->set_specular_texture_prefix(options["specular_map_prefix"]); + atlas->load_from_atlas_file_internal(source_file, true); + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + String file_name = save_path + String(".") + _get_save_extension(); + auto error = ResourceSaver::get_singleton()->save(atlas, file_name); +#else + String file_name = save_path + String(".") + get_save_extension(); + auto error = ResourceSaver::save(atlas, file_name); +#endif +#else + String file_name = save_path + String(".") + get_save_extension(); + auto error = ResourceSaver::save(file_name, atlas); +#endif + return error; +} + +#ifdef SPINE_GODOT_EXTENSION +TypedArray SpineAtlasResourceImportPlugin::_get_import_options(const String &p_path, int32_t p_preset_index) const { + TypedArray options; + Dictionary normal_map_dictionary; + normal_map_dictionary["name"] = "normal_map_prefix"; + normal_map_dictionary["type"] = Variant::STRING; + normal_map_dictionary["hint_string"] = "String"; + normal_map_dictionary["default_value"] = String("n"); + options.push_back(normal_map_dictionary); + + Dictionary specular_map_dictionary; + specular_map_dictionary["name"] = "specular_map_prefix"; + specular_map_dictionary["type"] = Variant::STRING; + specular_map_dictionary["hint_string"] = "String"; + specular_map_dictionary["default_value"] = String("s"); + options.push_back(specular_map_dictionary); + return options; +} +#else +#if VERSION_MAJOR > 3 +void SpineAtlasResourceImportPlugin::get_import_options(const String &path, List *options, int preset) const { +#else +void SpineAtlasResourceImportPlugin::get_import_options(List *options, int preset) const { +#endif + if (preset == 0) { + ImportOption normal_map_op; + normal_map_op.option.name = "normal_map_prefix"; + normal_map_op.option.type = Variant::STRING; + normal_map_op.option.hint_string = "String"; + normal_map_op.default_value = String("n"); + options->push_back(normal_map_op); + + ImportOption specular_map_op; + specular_map_op.option.name = "specular_map_prefix"; + specular_map_op.option.type = Variant::STRING; + specular_map_op.option.hint_string = "String"; + specular_map_op.default_value = String("s"); + options->push_back(specular_map_op); + } +} +#endif + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION +Error SpineJsonResourceImportPlugin::_import(const String &source_file, const String &save_path, const Dictionary &options, const TypedArray &platform_variants, const TypedArray &gen_files) const { +#else +#if VERSION_MINOR > 3 +Error SpineJsonResourceImportPlugin::import(ResourceUID::ID p_source_id, const String &source_file, const String &save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { +#else +Error SpineJsonResourceImportPlugin::import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif +#endif +#else +Error SpineJsonResourceImportPlugin::import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif + Ref skeleton_file_res(memnew(SpineSkeletonFileResource)); + Error error = skeleton_file_res->load_from_file(source_file); + if (error != OK) return error; + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + String file_name = save_path + String(".") + _get_save_extension(); + error = ResourceSaver::get_singleton()->save(skeleton_file_res, file_name); +#else + String file_name = save_path + String(".") + get_save_extension(); + error = ResourceSaver::save(skeleton_file_res, file_name); +#endif +#else + String file_name = save_path + String(".") + get_save_extension(); + error = ResourceSaver::save(file_name, skeleton_file_res); +#endif + return error; +} + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION +Error SpineBinaryResourceImportPlugin::_import(const String &source_file, const String &save_path, const Dictionary &options, const TypedArray &platform_variants, const TypedArray &gen_files) const { +#else +#if VERSION_MINOR > 3 +Error SpineBinaryResourceImportPlugin::import(ResourceUID::ID p_source_id, const String &source_file, const String &save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { +#else +Error SpineBinaryResourceImportPlugin::import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif +#endif +#else +Error SpineBinaryResourceImportPlugin::import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) { +#endif + Ref skeleton_file_res(memnew(SpineSkeletonFileResource)); + Error error = skeleton_file_res->load_from_file(source_file); + if (error != OK) return error; + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + String file_name = save_path + String(".") + _get_save_extension(); + error = ResourceSaver::get_singleton()->save(skeleton_file_res, file_name); +#else + String file_name = save_path + String(".") + get_save_extension(); + error = ResourceSaver::save(skeleton_file_res, file_name); +#endif +#else + String file_name = save_path + String(".") + get_save_extension(); + error = ResourceSaver::save(file_name, skeleton_file_res); +#endif + return error; +} + +#ifdef SPINE_GODOT_EXTENSION +SpineEditorPlugin::SpineEditorPlugin() { + atlas_import_plugin = Ref(memnew(SpineAtlasResourceImportPlugin)); + json_import_plugin = Ref(memnew(SpineJsonResourceImportPlugin)); + binary_import_plugin = Ref(memnew(SpineBinaryResourceImportPlugin)); + skeleton_data_inspector_plugin = Ref(memnew(SpineSkeletonDataResourceInspectorPlugin)); + + add_import_plugin(atlas_import_plugin); + add_import_plugin(json_import_plugin); + add_import_plugin(binary_import_plugin); + add_inspector_plugin(skeleton_data_inspector_plugin); +} + +void SpineEditorPlugin::_notification(int p_what) { + if (p_what == NOTIFICATION_PREDELETE) { + remove_import_plugin(atlas_import_plugin); + remove_import_plugin(json_import_plugin); + remove_import_plugin(binary_import_plugin); + remove_inspector_plugin(skeleton_data_inspector_plugin); + } +} +#else +SpineEditorPlugin::SpineEditorPlugin(EditorNode *node) { + add_import_plugin(memnew(SpineAtlasResourceImportPlugin)); + add_import_plugin(memnew(SpineJsonResourceImportPlugin)); + add_import_plugin(memnew(SpineBinaryResourceImportPlugin)); + add_inspector_plugin(memnew(SpineSkeletonDataResourceInspectorPlugin)); + add_inspector_plugin(memnew(SpineSpriteInspectorPlugin)); +} +#endif + +#ifdef SPINE_GODOT_EXTENSION +bool SpineSkeletonDataResourceInspectorPlugin::_can_handle(Object *object) const { +#else +bool SpineSkeletonDataResourceInspectorPlugin::can_handle(Object *object) { +#endif + return object->is_class("SpineSkeletonDataResource"); +} + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION +bool SpineSkeletonDataResourceInspectorPlugin::_parse_property(Object *object, Variant::Type type, const String &path, PropertyHint hint, const String &hint_text, const BitField p_usage, bool wide) { +#else +bool SpineSkeletonDataResourceInspectorPlugin::parse_property(Object *object, const Variant::Type type, const String &path, const PropertyHint hint, const String &hint_text, const BitField p_usage, const bool wide) { +#endif +#else +bool SpineSkeletonDataResourceInspectorPlugin::parse_property(Object *object, Variant::Type type, const String &path, + PropertyHint hint, const String &hint_text, int usage) { +#endif +// FIXME can't do this in godot-cpp +#ifndef SPINE_GODOT_EXTENSION + if (path == "animation_mixes") { + Ref skeleton_data = Object::cast_to(object); + if (!skeleton_data.is_valid() || !skeleton_data->is_skeleton_data_loaded()) return true; + auto mixes_property = memnew(SpineEditorPropertyAnimationMixes); + mixes_property->setup(skeleton_data); + add_property_editor(path, mixes_property); + return true; + } +#endif + return false; +} + +// FIXME can't do this in godot-cpp +#ifndef SPINE_GODOT_EXTENSION +SpineEditorPropertyAnimationMixes::SpineEditorPropertyAnimationMixes() : skeleton_data(nullptr), container(nullptr), updating(false) { + INSTANTIATE(array_object); +} + +void SpineEditorPropertyAnimationMixes::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_mix"), &SpineEditorPropertyAnimationMixes::add_mix); + ClassDB::bind_method(D_METHOD("delete_mix", "idx"), &SpineEditorPropertyAnimationMixes::delete_mix); + ClassDB::bind_method(D_METHOD("update_mix_property", "index"), &SpineEditorPropertyAnimationMixes::update_mix_property); +} + +void SpineEditorPropertyAnimationMixes::add_mix() { + if (!skeleton_data.is_valid() || !skeleton_data->is_skeleton_data_loaded() || updating) return; + + Vector animation_names; + skeleton_data->get_animation_names(animation_names); + Ref mix = Ref(memnew(SpineAnimationMix)); + mix->set_from(animation_names[0]); + mix->set_to(animation_names[0]); + mix->set_mix(0); + + Array mixes = skeleton_data->get_animation_mixes().duplicate(); + mixes.push_back(mix); + emit_changed(get_edited_property(), mixes); +} + +void SpineEditorPropertyAnimationMixes::delete_mix(int idx) { + if (!skeleton_data.is_valid() || !skeleton_data->is_skeleton_data_loaded() || updating) return; + + auto mixes = skeleton_data->get_animation_mixes().duplicate(); +#if VERSION_MAJOR > 3 + mixes.remove_at((int) idx); +#else + mixes.remove((int) idx); +#endif + emit_changed(get_edited_property(), mixes); +} + +void SpineEditorPropertyAnimationMixes::update_mix_property(int index) { + if (index < 0 || index > mix_properties.size()) return; + mix_properties[index]->update_property(); +} + +void SpineEditorPropertyAnimationMixes::update_property() { + if (updating) return; + updating = true; + + mix_properties.clear(); + + if (container) { + set_bottom_editor(nullptr); + memdelete(container); + container = nullptr; + } + + if (!skeleton_data.is_valid() || !skeleton_data->is_skeleton_data_loaded()) { + updating = false; + return; + } + + Vector animation_names; + skeleton_data->get_animation_names(animation_names); + + container = memnew(VBoxContainer); + add_child(container); + set_bottom_editor(container); + + Array mixes = skeleton_data->get_animation_mixes(); + array_object->set_array(mixes); + for (int i = 0; i < mixes.size(); i++) { + Ref mix = mixes[i]; + String property_name = "indices/" + itos(i); + + auto hbox = memnew(HBoxContainer); + hbox->set_h_size_flags(SIZE_EXPAND_FILL); + container->add_child(hbox); + + auto mix_property = memnew(SpineEditorPropertyAnimationMix); + mix_property->set_h_size_flags(SIZE_EXPAND_FILL); + mix_property->set_name_split_ratio(0); + hbox->add_child(mix_property); + mix_property->setup(this, skeleton_data, i); + mix_property->set_object_and_property(*array_object, property_name); + mix_property->update_property(); + mix_properties.push_back(mix_property); + + auto delete_button = memnew(Button); + hbox->add_child(delete_button); + delete_button->set_text("Remove"); +#if VERSION_MAJOR > 3 + delete_button->connect(SNAME("pressed"), callable_mp(this, &SpineEditorPropertyAnimationMixes::delete_mix).bind(i)); +#else + delete_button->connect(SNAME("pressed"), this, SNAME("delete_mix"), varray(i)); +#endif + } + + auto add_mix_button = memnew(Button); + add_mix_button->set_text("Add mix"); +#if VERSION_MAJOR > 3 + add_mix_button->connect(SNAME("pressed"), callable_mp(this, &SpineEditorPropertyAnimationMixes::add_mix)); +#else + add_mix_button->connect(SNAME("pressed"), this, SNAME("add_mix")); +#endif + container->add_child(add_mix_button); + + updating = false; +} + +SpineEditorPropertyAnimationMix::SpineEditorPropertyAnimationMix() : mixes_property(nullptr), skeleton_data(nullptr), index(0), container(nullptr), updating(false) { +} + +void SpineEditorPropertyAnimationMix::setup(SpineEditorPropertyAnimationMixes *_mixes_property, const Ref &_skeleton_data, int _index) { + this->mixes_property = _mixes_property; + this->skeleton_data = _skeleton_data; + this->index = _index; +} + +void SpineEditorPropertyAnimationMix::_bind_methods() { + ClassDB::bind_method(D_METHOD("data_changed", "property", "value", "name", "changing"), &SpineEditorPropertyAnimationMix::data_changed); +} + +void SpineEditorPropertyAnimationMix::data_changed(const String &property, const Variant &value, const String &name, bool changing) { + auto mix = Object::cast_to(get_edited_object()->get(get_edited_property())); + +#if VERSION_MAJOR > 3 + auto undo_redo = EditorUndoRedoManager::get_singleton(); +#else + auto undo_redo = EditorNode::get_undo_redo(); +#endif + undo_redo->create_action("Set mix property " + property); + undo_redo->add_do_property(mix, property, value); + undo_redo->add_undo_property(mix, property, mix->get(property)); + undo_redo->add_do_method(mixes_property, "update_mix_property", index); + undo_redo->add_undo_method(mixes_property, "update_mix_property", index); + // temporarily disable rebuilding the UI, as commit_action() calls update() which calls update_property(). however, + // data_changed is invoked by the control that changed the property, which would get deleted in update_property(). + updating = true; + undo_redo->commit_action(); + updating = false; + emit_changed(property, value, name, changing); +} + +void SpineEditorPropertyAnimationMix::update_property() { + if (updating) return; + updating = true; + + if (container) { + memdelete(container); +#if VERSION_MAJOR > 3 + SceneTree::get_singleton()->queue_delete(container); +#else + container->queue_delete(); +#endif + container = nullptr; + } + + if (!skeleton_data.is_valid() || !skeleton_data->is_skeleton_data_loaded()) { + updating = false; + return; + } + + auto mix = Object::cast_to(get_edited_object()->get(get_edited_property())); + if (!mix) { + updating = false; + return; + } + + Vector animation_names; + skeleton_data->get_animation_names(animation_names); + + container = memnew(HBoxContainer); + container->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(container); + + auto from_enum = memnew(EditorPropertyTextEnum); + from_enum->set_h_size_flags(SIZE_EXPAND_FILL); + from_enum->set_name_split_ratio(0); + from_enum->set_selectable(false); + from_enum->setup(animation_names); + from_enum->set_object_and_property(mix, "from"); + from_enum->update_property(); +#if VERSION_MAJOR > 3 + from_enum->connect(SNAME("property_changed"), callable_mp(this, &SpineEditorPropertyAnimationMix::data_changed)); +#else + from_enum->connect(SNAME("property_changed"), this, SNAME("data_changed")); +#endif + container->add_child(from_enum); + + auto to_enum = memnew(EditorPropertyTextEnum); + to_enum->set_h_size_flags(SIZE_EXPAND_FILL); + to_enum->set_name_split_ratio(0); + to_enum->set_selectable(false); + to_enum->setup(animation_names); + to_enum->set_object_and_property(mix, "to"); + to_enum->update_property(); +#if VERSION_MAJOR > 3 + to_enum->connect(SNAME("property_changed"), callable_mp(this, &SpineEditorPropertyAnimationMix::data_changed)); +#else + to_enum->connect(SNAME("property_changed"), this, SNAME("data_changed")); +#endif + container->add_child(to_enum); + + auto mix_float = memnew(EditorPropertyFloat); + mix_float->set_h_size_flags(SIZE_EXPAND_FILL); + mix_float->set_name_split_ratio(0); + mix_float->set_selectable(false); + mix_float->setup(0, 9999999, 0.001, true, false, false, false); + mix_float->set_object_and_property(mix, "mix"); + mix_float->update_property(); +#if VERSION_MAJOR > 3 + mix_float->connect(SNAME("property_changed"), callable_mp(this, &SpineEditorPropertyAnimationMix::data_changed)); +#else + mix_float->connect(SNAME("property_changed"), this, SNAME("data_changed")); +#endif + container->add_child(mix_float); + + updating = false; +} +#endif + +void SpineSpriteInspectorPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("button_clicked", "button_name"), &SpineSpriteInspectorPlugin::button_clicked); +} + +void SpineSpriteInspectorPlugin::button_clicked(const String &button_name) { +} + +#ifdef SPINE_GODOT_EXTENSION +bool SpineSpriteInspectorPlugin::_can_handle(Object *object) const { +#else +bool SpineSpriteInspectorPlugin::can_handle(Object *object) { +#endif + return Object::cast_to(object) != nullptr; +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSpriteInspectorPlugin::_parse_begin(Object *object) { +#else +void SpineSpriteInspectorPlugin::parse_begin(Object *object) { +#endif + sprite = Object::cast_to(object); + if (!sprite) return; + if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return; +} + +#endif diff --git a/modules/spine_godot/SpineEditorPlugin.h b/modules/spine_godot/SpineEditorPlugin.h new file mode 100644 index 000000000000..37493824e227 --- /dev/null +++ b/modules/spine_godot/SpineEditorPlugin.h @@ -0,0 +1,394 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#ifdef TOOLS_ENABLED +#include "SpineSprite.h" +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "editor/import/editor_import_plugin.h" +#endif +#endif +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#include +#else +#include "editor/editor_node.h" +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) +#include "editor/inspector/editor_properties.h" +#include "editor/inspector/editor_properties_array_dict.h" +#else +#include "editor/editor_properties.h" +#include "editor/editor_properties_array_dict.h" +#endif +#endif + +class SpineAtlasResourceImportPlugin : public EditorImportPlugin { + GDCLASS(SpineAtlasResourceImportPlugin, EditorImportPlugin) + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + String _get_importer_name() const override { return "spine.atlas"; } + + String _get_visible_name() const override { return "Spine Runtime Atlas"; } + + PackedStringArray _get_recognized_extensions() const override { + PackedStringArray extensions; + extensions.push_back("atlas"); + return extensions; + } + + String _get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int _get_preset_count() const override { return 1; } + + String _get_save_extension() const override { return "spatlas"; } + + String _get_resource_type() const override { return "SpineAtlasResource"; } +#else + String get_importer_name() const override { return "spine.atlas"; } + + String get_visible_name() const override { return "Spine Runtime Atlas"; } + + void get_recognized_extensions(List *extensions) const override { extensions->push_back("atlas"); } + + String get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int get_preset_count() const override { return 1; } + + String get_save_extension() const override { return "spatlas"; } + + String get_resource_type() const override { return "SpineAtlasResource"; } +#endif + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + virtual int _get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + virtual float _get_priority() const override { return 1; } + + TypedArray _get_import_options(const String &p_path, int32_t p_preset_index) const override; + + virtual bool _get_option_visibility(const String &p_path, const StringName &p_option_name, const Dictionary &p_options) const override { return true; }; + + virtual Error _import(const String &p_source_file, const String &p_save_path, const Dictionary &p_options, const TypedArray &p_platform_variants, const TypedArray &p_gen_files) const override; +#else + int get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + float get_priority() const override { return 1; } + + void get_import_options(const String &path, List *options, int preset) const override; + + virtual bool get_option_visibility(const String &path, const String &option, const HashMap &options) const override { return true; } +#if VERSION_MINOR > 3 + Error import(ResourceUID::ID p_source_id, const String &source_file, const String &save_path, const HashMap &options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata = nullptr) override; +#else + Error import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +#endif +#else + void get_import_options(List *options, int preset) const override; + + bool get_option_visibility(const String &option, const Map &options) const override { return true; } + + Error import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +}; + +class SpineJsonResourceImportPlugin : public EditorImportPlugin { + GDCLASS(SpineJsonResourceImportPlugin, EditorImportPlugin) + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + String _get_importer_name() const override { return "spine.json"; } + + String _get_visible_name() const override { return "Spine Skeleton Json"; } + + PackedStringArray _get_recognized_extensions() const override { + PackedStringArray extensions; + extensions.push_back("spine-json"); + return extensions; + } + + String _get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int _get_preset_count() const override { return 1; } + + String _get_save_extension() const override { return "spjson"; } + + String _get_resource_type() const override { return "SpineSkeletonFileResource"; } +#else + String get_importer_name() const override { return "spine.json"; } + + String get_visible_name() const override { return "Spine Skeleton Json"; } + + void get_recognized_extensions(List *extensions) const override { extensions->push_back("spine-json"); } + + String get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int get_preset_count() const override { return 1; } + + String get_save_extension() const override { return "spjson"; } + + String get_resource_type() const override { return "SpineSkeletonFileResource"; } +#endif + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + int _get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + float _get_priority() const override { return 1; } + + TypedArray _get_import_options(const String &p_path, int32_t p_preset_index) const override { + TypedArray options; + return options; + } + + virtual bool _get_option_visibility(const String &p_path, const StringName &p_option_name, const Dictionary &p_options) const override { return true; }; + + virtual Error _import(const String &p_source_file, const String &p_save_path, const Dictionary &p_options, const TypedArray &p_platform_variants, const TypedArray &p_gen_files) const override; +#else + int get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + float get_priority() const override { return 1; } + + void get_import_options(const String &path, List *options, int preset) const override {} + + bool get_option_visibility(const String &path, const String &option, const HashMap &options) const override { return true; } + +#if VERSION_MINOR > 3 + Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata = nullptr) override; +#else + Error import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +#endif +#else + void get_import_options(List *options, int preset) const override {} + + bool get_option_visibility(const String &option, const Map &options) const override { return true; } + + Error import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +}; + +class SpineBinaryResourceImportPlugin : public EditorImportPlugin { + GDCLASS(SpineBinaryResourceImportPlugin, EditorImportPlugin); + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + String _get_importer_name() const override { return "spine.skel"; } + + String _get_visible_name() const override { return "Spine Skeleton Binary"; } + + PackedStringArray _get_recognized_extensions() const override { + PackedStringArray extensions; + extensions.push_back("skel"); + return extensions; + } + + String _get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int _get_preset_count() const override { return 1; } + + String _get_save_extension() const override { return "spskel"; } + + String _get_resource_type() const override { return "SpineSkeletonFileResource"; } +#else + String get_importer_name() const override { return "spine.skel"; } + + String get_visible_name() const override { return "Spine Skeleton Binary"; } + + void get_recognized_extensions(List *extensions) const override { extensions->push_back("skel"); } + + String get_preset_name(int idx) const override { return idx == 0 ? "Default" : "Unknown"; } + + int get_preset_count() const override { return 1; } + + String get_save_extension() const override { return "spskel"; } + + String get_resource_type() const override { return "SpineSkeletonFileResource"; } +#endif + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + int _get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + float _get_priority() const override { return 1; } + + TypedArray _get_import_options(const String &p_path, int32_t p_preset_index) const override { + TypedArray options; + return options; + } + + virtual bool _get_option_visibility(const String &p_path, const StringName &p_option_name, const Dictionary &p_options) const override { return true; }; + + virtual Error _import(const String &p_source_file, const String &p_save_path, const Dictionary &p_options, const TypedArray &p_platform_variants, const TypedArray &p_gen_files) const override; +#else + int get_import_order() const override { return IMPORT_ORDER_DEFAULT; } + + float get_priority() const override { return 1; } + + void get_import_options(const String &path, List *options, int preset) const override {} + + bool get_option_visibility(const String &path, const String &option, const HashMap &options) const override { return true; } +#if VERSION_MINOR > 3 + Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata = nullptr) override; +#else + Error import(const String &source_file, const String &save_path, const HashMap &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +#endif +#else + void get_import_options(List *options, int preset) const override {} + + bool get_option_visibility(const String &option, const Map &options) const override { return true; } + + Error import(const String &source_file, const String &save_path, const Map &options, List *platform_variants, List *gen_files, Variant *metadata) override; +#endif +}; + +class SpineEditorPlugin : public EditorPlugin { + GDCLASS(SpineEditorPlugin, EditorPlugin) + + static void _bind_methods() {} + +#ifdef SPINE_GODOT_EXTENSION + Ref atlas_import_plugin; + Ref json_import_plugin; + Ref binary_import_plugin; + Ref skeleton_data_inspector_plugin; +#endif + +public: +#ifdef SPINE_GODOT_EXTENSION + explicit SpineEditorPlugin(); + + String _get_plugin_name() const override { return "SpineEditorPlugin"; } + + void _notification(int p_what); +#else + explicit SpineEditorPlugin(EditorNode *node); + +#if VERSION_MAJOR > 3 && VERSION_MINOR > 3 + String get_plugin_name() const override { return "SpineEditorPlugin"; } +#else + String get_name() const override { return "SpineEditorPlugin"; } +#endif +#endif +}; + +class SpineSkeletonDataResourceInspectorPlugin : public EditorInspectorPlugin { + GDCLASS(SpineSkeletonDataResourceInspectorPlugin, EditorInspectorPlugin) + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods() {} + + bool _can_handle(Object *p_object) const override; + bool _parse_property(Object *object, Variant::Type type, const String &path, PropertyHint hint, const String &hint_text, const BitField p_usage, bool wide) override; +#else + bool can_handle(Object *object) override; +#if VERSION_MAJOR > 3 + bool parse_property(Object *object, Variant::Type type, const String &path, PropertyHint hint, const String &hint_text, const BitField p_usage, bool wide) override; +#else + bool parse_property(Object *object, Variant::Type type, const String &path, PropertyHint hint, const String &hint_text, int usage) override; +#endif +#endif +}; + +#ifndef SPINE_GODOT_EXTENSION +class SpineEditorPropertyAnimationMix; + +class SpineEditorPropertyAnimationMixes : public EditorProperty { + GDCLASS(SpineEditorPropertyAnimationMixes, EditorProperty) + + Ref array_object; + Ref skeleton_data; + VBoxContainer *container; + Vector mix_properties; + bool updating; + + static void _bind_methods(); + void add_mix(); + void delete_mix(int idx); + void update_mix_property(int index); + +public: + SpineEditorPropertyAnimationMixes(); + void setup(const Ref &_skeleton_data) { this->skeleton_data = _skeleton_data; }; + void update_property() override; +}; + +class SpineEditorPropertyAnimationMix : public EditorProperty { + GDCLASS(SpineEditorPropertyAnimationMix, EditorProperty) + + SpineEditorPropertyAnimationMixes *mixes_property; + Ref skeleton_data; + int index; + Container *container; + bool updating; + + static void _bind_methods(); + void data_changed(const String &property, const Variant &value, const String &name, bool changing); + +public: + SpineEditorPropertyAnimationMix(); + void setup(SpineEditorPropertyAnimationMixes *mixes_property, const Ref &skeleton_data, int index); + void update_property() override; +}; +#endif + +class SpineSpriteInspectorPlugin : public EditorInspectorPlugin { + GDCLASS(SpineSpriteInspectorPlugin, EditorInspectorPlugin) + + SpineSprite *sprite; + + static void _bind_methods(); + void button_clicked(const String &button_name); + +public: +#ifdef SPINE_GODOT_EXTENSION + virtual bool _can_handle(Object *object) const override; + virtual void _parse_begin(Object *object) override; +#else + bool can_handle(Object *object) override; + void parse_begin(Object *object) override; +#endif +}; + +#endif diff --git a/modules/spine_godot/SpineEvent.cpp b/modules/spine_godot/SpineEvent.cpp new file mode 100644 index 000000000000..109abbc77fb5 --- /dev/null +++ b/modules/spine_godot/SpineEvent.cpp @@ -0,0 +1,109 @@ +/****************************************************************************** + * 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 "SpineEvent.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpineEvent::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_data"), &SpineEvent::get_data); + ClassDB::bind_method(D_METHOD("get_time"), &SpineEvent::get_time); + ClassDB::bind_method(D_METHOD("get_int_value"), &SpineEvent::get_int_value); + ClassDB::bind_method(D_METHOD("set_int_value", "v"), &SpineEvent::set_int_value); + ClassDB::bind_method(D_METHOD("get_float_value"), &SpineEvent::get_float_value); + ClassDB::bind_method(D_METHOD("set_float_value", "v"), &SpineEvent::set_float_value); + ClassDB::bind_method(D_METHOD("get_string_value"), &SpineEvent::get_string_value); + ClassDB::bind_method(D_METHOD("set_string_value", "v"), &SpineEvent::set_string_value); + ClassDB::bind_method(D_METHOD("get_volume"), &SpineEvent::get_volume); + ClassDB::bind_method(D_METHOD("set_volume", "v"), &SpineEvent::set_volume); + ClassDB::bind_method(D_METHOD("get_balance"), &SpineEvent::get_balance); + ClassDB::bind_method(D_METHOD("set_balance", "v"), &SpineEvent::set_balance); +} + +Ref SpineEvent::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + Ref event_data(memnew(SpineEventData)); + event_data->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), (spine::EventData *) &get_spine_object()->getData()); + return event_data; +} + +float SpineEvent::get_time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTime(); +} + +int SpineEvent::get_int_value() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getIntValue(); +} + +void SpineEvent::set_int_value(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setIntValue(v); +} + +float SpineEvent::get_float_value() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getFloatValue(); +} + +void SpineEvent::set_float_value(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setFloatValue(v); +} + +String SpineEvent::get_string_value() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getStringValue().buffer(); +} + +void SpineEvent::set_string_value(const String &v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setStringValue(spine::String(v.utf8().ptr())); +} + +float SpineEvent::get_volume() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getVolume(); +} + +void SpineEvent::set_volume(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setVolume(v); +} + +float SpineEvent::get_balance() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getBalance(); +} + +void SpineEvent::set_balance(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setBalance(v); +} diff --git a/modules/spine_godot/SpineEvent.h b/modules/spine_godot/SpineEvent.h new file mode 100644 index 000000000000..c5179fe9fbfc --- /dev/null +++ b/modules/spine_godot/SpineEvent.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineEventData.h" +#include + +class SpineSprite; + +class SpineEvent : public SpineSpriteOwnedObject { + GDCLASS(SpineEvent, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + Ref get_data(); + + float get_time(); + + int get_int_value(); + + void set_int_value(int v); + + float get_float_value(); + + void set_float_value(float v); + + String get_string_value(); + + void set_string_value(const String &v); + + float get_volume(); + + void set_volume(float v); + + float get_balance(); + + void set_balance(float v); +}; diff --git a/modules/spine_godot/SpineEventData.cpp b/modules/spine_godot/SpineEventData.cpp new file mode 100644 index 000000000000..ca1026546b6f --- /dev/null +++ b/modules/spine_godot/SpineEventData.cpp @@ -0,0 +1,118 @@ +/****************************************************************************** + * 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 "SpineEventData.h" +#include "SpineCommon.h" + +void SpineEventData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_event_name"), &SpineEventData::get_event_name); + ClassDB::bind_method(D_METHOD("get_int_value"), &SpineEventData::get_int_value); + ClassDB::bind_method(D_METHOD("set_int_value", "v"), &SpineEventData::set_int_value); + ClassDB::bind_method(D_METHOD("get_float_value"), &SpineEventData::get_float_value); + ClassDB::bind_method(D_METHOD("set_float_value", "v"), &SpineEventData::set_float_value); + ClassDB::bind_method(D_METHOD("get_string_value"), &SpineEventData::get_string_value); + ClassDB::bind_method(D_METHOD("set_string_value", "v"), &SpineEventData::set_string_value); + ClassDB::bind_method(D_METHOD("get_audio_path"), &SpineEventData::get_audio_path); + ClassDB::bind_method(D_METHOD("set_audio_path", "v"), &SpineEventData::set_audio_path); + ClassDB::bind_method(D_METHOD("get_volume"), &SpineEventData::get_volume); + ClassDB::bind_method(D_METHOD("set_volume", "v"), &SpineEventData::set_volume); + ClassDB::bind_method(D_METHOD("get_balance"), &SpineEventData::get_balance); + ClassDB::bind_method(D_METHOD("set_balance", "v"), &SpineEventData::set_balance); +} + +String SpineEventData::get_event_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +int SpineEventData::get_int_value() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getIntValue(); +} + +void SpineEventData::set_int_value(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setIntValue(v); +} + +float SpineEventData::get_float_value() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getFloatValue(); +} + +void SpineEventData::set_float_value(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setFloatValue(v); +} + +String SpineEventData::get_string_value() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getStringValue().buffer(); +} + +void SpineEventData::set_string_value(const String &v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setStringValue(spine::String(v.utf8().ptr())); +} + +String SpineEventData::get_audio_path() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getAudioPath().buffer(); +} + +void SpineEventData::set_audio_path(const String &v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAudioPath(spine::String(v.utf8().ptr())); +} + +float SpineEventData::get_volume() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getVolume(); +} + +void SpineEventData::set_volume(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setVolume(v); +} + +float SpineEventData::get_balance() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getBalance(); +} + +void SpineEventData::set_balance(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setBalance(v); +} diff --git a/modules/spine_godot/SpineEventData.h b/modules/spine_godot/SpineEventData.h new file mode 100644 index 000000000000..4cbd9f3d7bb2 --- /dev/null +++ b/modules/spine_godot/SpineEventData.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include + +class SpineSkeletonDataResource; + +class SpineEventData : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineEventData, SpineObjectWrapper); + +protected: + static void _bind_methods(); + +public: + String get_event_name(); + + int get_int_value(); + + void set_int_value(int v); + + float get_float_value(); + + void set_float_value(float v); + + String get_string_value(); + + void set_string_value(const String &v); + + String get_audio_path(); + + void set_audio_path(const String &v); + + float get_volume(); + + void set_volume(float v); + + float get_balance(); + + void set_balance(float v); +}; diff --git a/modules/spine_godot/SpineIkConstraint.cpp b/modules/spine_godot/SpineIkConstraint.cpp new file mode 100644 index 000000000000..429b2a9dfb9e --- /dev/null +++ b/modules/spine_godot/SpineIkConstraint.cpp @@ -0,0 +1,159 @@ +/****************************************************************************** + * 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 "SpineIkConstraint.h" +#include "SpineBone.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpineIkConstraint::_bind_methods() { + ClassDB::bind_method(D_METHOD("update"), &SpineIkConstraint::update); + ClassDB::bind_method(D_METHOD("get_order"), &SpineIkConstraint::get_order); + ClassDB::bind_method(D_METHOD("get_data"), &SpineIkConstraint::get_data); + ClassDB::bind_method(D_METHOD("get_bones"), &SpineIkConstraint::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpineIkConstraint::get_target); + ClassDB::bind_method(D_METHOD("set_target", "v"), &SpineIkConstraint::set_target); + ClassDB::bind_method(D_METHOD("get_bend_direction"), &SpineIkConstraint::get_bend_direction); + ClassDB::bind_method(D_METHOD("set_bend_direction", "v"), &SpineIkConstraint::set_bend_direction); + ClassDB::bind_method(D_METHOD("get_compress"), &SpineIkConstraint::get_compress); + ClassDB::bind_method(D_METHOD("set_compress", "v"), &SpineIkConstraint::set_compress); + ClassDB::bind_method(D_METHOD("get_stretch"), &SpineIkConstraint::get_stretch); + ClassDB::bind_method(D_METHOD("set_stretch", "v"), &SpineIkConstraint::set_stretch); + ClassDB::bind_method(D_METHOD("get_mix"), &SpineIkConstraint::get_mix); + ClassDB::bind_method(D_METHOD("set_mix", "v"), &SpineIkConstraint::set_mix); + ClassDB::bind_method(D_METHOD("get_softness"), &SpineIkConstraint::get_softness); + ClassDB::bind_method(D_METHOD("set_softness", "v"), &SpineIkConstraint::set_softness); + ClassDB::bind_method(D_METHOD("is_active"), &SpineIkConstraint::is_active); + ClassDB::bind_method(D_METHOD("set_active", "v"), &SpineIkConstraint::set_active); +} + +void SpineIkConstraint::update() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->update(spine::Physics_Update); +} + +int SpineIkConstraint::get_order() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getOrder(); +} + +Ref SpineIkConstraint::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &ik_constraint_data = get_spine_object()->getData(); + Ref ik_constraint_data_ref(memnew(SpineIkConstraintData)); + ik_constraint_data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &ik_constraint_data); + return ik_constraint_data_ref; +} + +Array SpineIkConstraint::get_bones() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &bones = get_spine_object()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + auto bone = bones[i]; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(get_spine_owner(), bone); + result[i] = bone_ref; + } + return result; +} + +Ref SpineIkConstraint::get_target() { + SPINE_CHECK(get_spine_object(), nullptr) + auto target = get_spine_object()->getTarget(); + if (!target) return nullptr; + Ref target_ref(memnew(SpineBone)); + target_ref->set_spine_object(get_spine_owner(), target); + return target_ref; +} + +void SpineIkConstraint::set_target(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTarget(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +int SpineIkConstraint::get_bend_direction() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getBendDirection(); +} + +void SpineIkConstraint::set_bend_direction(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setBendDirection(v); +} + +bool SpineIkConstraint::get_compress() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getCompress(); +} + +void SpineIkConstraint::set_compress(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setCompress(v); +} + +bool SpineIkConstraint::get_stretch() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getStretch(); +} + +void SpineIkConstraint::set_stretch(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setStretch(v); +} + +float SpineIkConstraint::get_mix() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMix(); +} +void SpineIkConstraint::set_mix(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMix(v); +} + +float SpineIkConstraint::get_softness() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getSoftness(); +} + +void SpineIkConstraint::set_softness(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setSoftness(v); +} + +bool SpineIkConstraint::is_active() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isActive(); +} + +void SpineIkConstraint::set_active(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setActive(v); +} diff --git a/modules/spine_godot/SpineIkConstraint.h b/modules/spine_godot/SpineIkConstraint.h new file mode 100644 index 000000000000..ce74e07bf4b4 --- /dev/null +++ b/modules/spine_godot/SpineIkConstraint.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineIkConstraintData.h" +#include + +class SpineBone; +class SpineSprite; + +class SpineIkConstraint : public SpineSpriteOwnedObject { + GDCLASS(SpineIkConstraint, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void update(); + + int get_order(); + + Ref get_data(); + + Array get_bones(); + + Ref get_target(); + + void set_target(Ref v); + + int get_bend_direction(); + + void set_bend_direction(int v); + + bool get_compress(); + + void set_compress(bool v); + + bool get_stretch(); + + void set_stretch(bool v); + + float get_mix(); + + void set_mix(float v); + + float get_softness(); + + void set_softness(float v); + + bool is_active(); + + void set_active(bool v); +}; diff --git a/modules/spine_godot/SpineIkConstraintData.cpp b/modules/spine_godot/SpineIkConstraintData.cpp new file mode 100644 index 000000000000..d359fcee0104 --- /dev/null +++ b/modules/spine_godot/SpineIkConstraintData.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * 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 "SpineIkConstraintData.h" +#include "SpineCommon.h" + +void SpineIkConstraintData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_bones"), &SpineIkConstraintData::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpineIkConstraintData::get_target); + ClassDB::bind_method(D_METHOD("set_target", "v"), &SpineIkConstraintData::set_target); + ClassDB::bind_method(D_METHOD("get_bend_direction"), &SpineIkConstraintData::get_bend_direction); + ClassDB::bind_method(D_METHOD("set_bend_direction", "v"), &SpineIkConstraintData::set_bend_direction); + ClassDB::bind_method(D_METHOD("get_compress"), &SpineIkConstraintData::get_compress); + ClassDB::bind_method(D_METHOD("set_compress", "v"), &SpineIkConstraintData::set_compress); + ClassDB::bind_method(D_METHOD("get_stretch"), &SpineIkConstraintData::get_stretch); + ClassDB::bind_method(D_METHOD("set_stretch", "v"), &SpineIkConstraintData::set_stretch); + ClassDB::bind_method(D_METHOD("get_uniform"), &SpineIkConstraintData::get_uniform); + ClassDB::bind_method(D_METHOD("set_uniform", "v"), &SpineIkConstraintData::set_uniform); + ClassDB::bind_method(D_METHOD("get_mix"), &SpineIkConstraintData::get_mix); + ClassDB::bind_method(D_METHOD("set_mix", "v"), &SpineIkConstraintData::set_mix); + ClassDB::bind_method(D_METHOD("get_softness"), &SpineIkConstraintData::get_softness); + ClassDB::bind_method(D_METHOD("set_softness", "v"), &SpineIkConstraintData::set_softness); +} + +Array SpineIkConstraintData::get_bones() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto bones = get_spine_constraint_data()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(get_spine_owner(), bones[i]); + result[i] = bone_ref; + } + return result; +} + +Ref SpineIkConstraintData::get_target() { + SPINE_CHECK(get_spine_object(), nullptr) + auto target = get_spine_constraint_data()->getTarget(); + if (!target) return nullptr; + Ref target_ref(memnew(SpineBoneData)); + target_ref->set_spine_object(get_spine_owner(), target); + return target_ref; +} + +void SpineIkConstraintData::set_target(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setTarget(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +int SpineIkConstraintData::get_bend_direction() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_constraint_data()->getBendDirection(); +} + +void SpineIkConstraintData::set_bend_direction(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setBendDirection(v); +} + +bool SpineIkConstraintData::get_compress() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_constraint_data()->getCompress(); +} + +void SpineIkConstraintData::set_compress(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setCompress(v); +} + +bool SpineIkConstraintData::get_stretch() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_constraint_data()->getStretch(); +} + +void SpineIkConstraintData::set_stretch(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setStretch(v); +} + +bool SpineIkConstraintData::get_uniform() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_constraint_data()->getUniform(); +} + +void SpineIkConstraintData::set_uniform(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setUniform(v); +} + +float SpineIkConstraintData::get_mix() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_constraint_data()->getMix(); +} + +void SpineIkConstraintData::set_mix(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setMix(v); +} + +float SpineIkConstraintData::get_softness() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_constraint_data()->getSoftness(); +} + +void SpineIkConstraintData::set_softness(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_constraint_data()->setSoftness(v); +} diff --git a/modules/spine_godot/SpineIkConstraintData.h b/modules/spine_godot/SpineIkConstraintData.h new file mode 100644 index 000000000000..0e6ef9ed3d2d --- /dev/null +++ b/modules/spine_godot/SpineIkConstraintData.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineConstraintData.h" +#include "SpineBoneData.h" +#include + +class SpineIkConstraintData : public SpineConstraintData { + GDCLASS(SpineIkConstraintData, SpineConstraintData) + + spine::IkConstraintData *get_spine_constraint_data() { return (spine::IkConstraintData *) get_spine_object(); } + +protected: + static void _bind_methods(); + +public: + Array get_bones(); + + Ref get_target(); + + void set_target(Ref v); + + int get_bend_direction(); + + void set_bend_direction(int v); + + bool get_compress(); + + void set_compress(bool v); + + bool get_stretch(); + + void set_stretch(bool v); + + bool get_uniform(); + + void set_uniform(bool v); + + float get_mix(); + + void set_mix(float v); + + float get_softness(); + + void set_softness(float v); +}; diff --git a/modules/spine_godot/SpinePathConstraint.cpp b/modules/spine_godot/SpinePathConstraint.cpp new file mode 100644 index 000000000000..88f28a7b3927 --- /dev/null +++ b/modules/spine_godot/SpinePathConstraint.cpp @@ -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 "SpinePathConstraint.h" +#include "SpineBone.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpinePathConstraint::_bind_methods() { + ClassDB::bind_method(D_METHOD("update"), &SpinePathConstraint::update); + ClassDB::bind_method(D_METHOD("get_order"), &SpinePathConstraint::get_order); + ClassDB::bind_method(D_METHOD("get_position"), &SpinePathConstraint::get_position); + ClassDB::bind_method(D_METHOD("set_position", "v"), &SpinePathConstraint::set_position); + ClassDB::bind_method(D_METHOD("get_spacing"), &SpinePathConstraint::get_spacing); + ClassDB::bind_method(D_METHOD("set_spacing", "v"), &SpinePathConstraint::set_spacing); + ClassDB::bind_method(D_METHOD("get_mix_rotate"), &SpinePathConstraint::get_mix_rotate); + ClassDB::bind_method(D_METHOD("set_mix_rotate", "v"), &SpinePathConstraint::set_mix_rotate); + ClassDB::bind_method(D_METHOD("get_mix_x"), &SpinePathConstraint::get_mix_x); + ClassDB::bind_method(D_METHOD("set_mix_x", "v"), &SpinePathConstraint::set_mix_x); + ClassDB::bind_method(D_METHOD("get_mix_y"), &SpinePathConstraint::get_mix_y); + ClassDB::bind_method(D_METHOD("set_mix_y", "v"), &SpinePathConstraint::set_mix_y); + ClassDB::bind_method(D_METHOD("get_bones"), &SpinePathConstraint::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpinePathConstraint::get_target); + ClassDB::bind_method(D_METHOD("set_target", "v"), &SpinePathConstraint::set_target); + ClassDB::bind_method(D_METHOD("get_data"), &SpinePathConstraint::get_data); + ClassDB::bind_method(D_METHOD("is_active"), &SpinePathConstraint::is_active); + ClassDB::bind_method(D_METHOD("set_active", "v"), &SpinePathConstraint::set_active); +} + +void SpinePathConstraint::update() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->update(spine::Physics_Update); +} + +int SpinePathConstraint::get_order() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getOrder(); +} + +float SpinePathConstraint::get_position() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getPosition(); +} + +void SpinePathConstraint::set_position(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setPosition(v); +} + +float SpinePathConstraint::get_spacing() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getSpacing(); +} + +void SpinePathConstraint::set_spacing(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setSpacing(v); +} + +float SpinePathConstraint::get_mix_rotate() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixRotate(); +} + +void SpinePathConstraint::set_mix_rotate(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixRotate(v); +} + +float SpinePathConstraint::get_mix_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixX(); +} + +void SpinePathConstraint::set_mix_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixX(v); +} + +float SpinePathConstraint::get_mix_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixY(); +} + +void SpinePathConstraint::set_mix_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixY(v); +} + +Array SpinePathConstraint::get_bones() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &bones = get_spine_object()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + auto bone = bones[i]; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(get_spine_owner(), bone); + result[i] = bone_ref; + } + return result; +} + +Ref SpinePathConstraint::get_target() { + SPINE_CHECK(get_spine_object(), nullptr) + auto target = get_spine_object()->getTarget(); + if (!target) return nullptr; + Ref target_ref(memnew(SpineSlot)); + target_ref->set_spine_object(get_spine_owner(), target); + return target_ref; +} + +void SpinePathConstraint::set_target(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTarget(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +Ref SpinePathConstraint::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &data = get_spine_object()->getData(); + Ref data_ref(memnew(SpinePathConstraintData)); + data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &data); + return data_ref; +} + +bool SpinePathConstraint::is_active() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isActive(); +} + +void SpinePathConstraint::set_active(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setActive(v); +} diff --git a/modules/spine_godot/SpinePathConstraint.h b/modules/spine_godot/SpinePathConstraint.h new file mode 100644 index 000000000000..276cbac4c20a --- /dev/null +++ b/modules/spine_godot/SpinePathConstraint.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpinePathConstraintData.h" +#include "SpineSlot.h" +#include + +class SpinePathConstraint : public SpineSpriteOwnedObject { + GDCLASS(SpinePathConstraint, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void update(); + + int get_order(); + + float get_position(); + + void set_position(float v); + + float get_spacing(); + + void set_spacing(float v); + + float get_mix_rotate(); + + void set_mix_rotate(float v); + + float get_mix_x(); + + void set_mix_x(float v); + + float get_mix_y(); + + void set_mix_y(float v); + + Array get_bones(); + + Ref get_target(); + + void set_target(Ref v); + + Ref get_data(); + + bool is_active(); + + void set_active(bool v); +}; diff --git a/modules/spine_godot/SpinePathConstraintData.cpp b/modules/spine_godot/SpinePathConstraintData.cpp new file mode 100644 index 000000000000..b0a3f90a4287 --- /dev/null +++ b/modules/spine_godot/SpinePathConstraintData.cpp @@ -0,0 +1,173 @@ +/****************************************************************************** + * 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 "SpinePathConstraintData.h" +#include "SpineCommon.h" +#include "SpineSkeletonDataResource.h" + +void SpinePathConstraintData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_bones"), &SpinePathConstraintData::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpinePathConstraintData::get_target); + ClassDB::bind_method(D_METHOD("set_target", "v"), &SpinePathConstraintData::set_target); + ClassDB::bind_method(D_METHOD("get_position_mode"), &SpinePathConstraintData::get_position_mode); + ClassDB::bind_method(D_METHOD("set_position_mode", "v"), &SpinePathConstraintData::set_position_mode); + ClassDB::bind_method(D_METHOD("get_spacing_mode"), &SpinePathConstraintData::get_spacing_mode); + ClassDB::bind_method(D_METHOD("set_spacing_mode", "v"), &SpinePathConstraintData::set_spacing_mode); + ClassDB::bind_method(D_METHOD("get_rotate_mode"), &SpinePathConstraintData::get_rotate_mode); + ClassDB::bind_method(D_METHOD("set_rotate_mode", "v"), &SpinePathConstraintData::set_rotate_mode); + ClassDB::bind_method(D_METHOD("get_offset_rotation"), &SpinePathConstraintData::get_offset_rotation); + ClassDB::bind_method(D_METHOD("set_offset_rotation", "v"), &SpinePathConstraintData::set_offset_rotation); + ClassDB::bind_method(D_METHOD("get_position"), &SpinePathConstraintData::get_position); + ClassDB::bind_method(D_METHOD("set_position", "v"), &SpinePathConstraintData::set_position); + ClassDB::bind_method(D_METHOD("get_spacing"), &SpinePathConstraintData::get_spacing); + ClassDB::bind_method(D_METHOD("set_spacing", "v"), &SpinePathConstraintData::set_spacing); + ClassDB::bind_method(D_METHOD("get_mix_rotate"), &SpinePathConstraintData::get_mix_rotate); + ClassDB::bind_method(D_METHOD("set_mix_rotate", "v"), &SpinePathConstraintData::set_mix_rotate); + ClassDB::bind_method(D_METHOD("get_mix_x"), &SpinePathConstraintData::get_mix_x); + ClassDB::bind_method(D_METHOD("set_mix_x", "v"), &SpinePathConstraintData::set_mix_x); + ClassDB::bind_method(D_METHOD("get_mix_y"), &SpinePathConstraintData::get_mix_y); + ClassDB::bind_method(D_METHOD("set_mix_y", "v"), &SpinePathConstraintData::set_mix_y); +} + +Array SpinePathConstraintData::get_bones() { + Array result; + SPINE_CHECK(get_spine_constraint_data(), result) + auto bones = get_spine_constraint_data()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(get_spine_owner(), bones[i]); + result[i] = bone_ref; + } + return result; +} + +Ref SpinePathConstraintData::get_target() { + SPINE_CHECK(get_spine_constraint_data(), nullptr) + auto slot = get_spine_constraint_data()->getTarget(); + if (!slot) return nullptr; + Ref slot_ref(memnew(SpineSlotData)); + slot_ref->set_spine_object(get_spine_owner(), slot); + return slot_ref; +} + +void SpinePathConstraintData::set_target(Ref v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setTarget(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +SpineConstant::PositionMode SpinePathConstraintData::get_position_mode() { + SPINE_CHECK(get_spine_constraint_data(), SpineConstant::PositionMode_Fixed) + return (SpineConstant::PositionMode) get_spine_constraint_data()->getPositionMode(); +} + +void SpinePathConstraintData::set_position_mode(SpineConstant::PositionMode v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setPositionMode((spine::PositionMode) v); +} + +SpineConstant::SpacingMode SpinePathConstraintData::get_spacing_mode() { + SPINE_CHECK(get_spine_constraint_data(), SpineConstant::SpacingMode_Fixed) + return (SpineConstant::SpacingMode) get_spine_constraint_data()->getSpacingMode(); +} + +void SpinePathConstraintData::set_spacing_mode(SpineConstant::SpacingMode v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setSpacingMode((spine::SpacingMode) v); +} + +SpineConstant::RotateMode SpinePathConstraintData::get_rotate_mode() { + SPINE_CHECK(get_spine_constraint_data(), SpineConstant::RotateMode_Tangent) + return (SpineConstant::RotateMode) get_spine_constraint_data()->getRotateMode(); +} + +void SpinePathConstraintData::set_rotate_mode(SpineConstant::RotateMode v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setRotateMode((spine::RotateMode) v); +} + +float SpinePathConstraintData::get_offset_rotation() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetRotation(); +} + +void SpinePathConstraintData::set_offset_rotation(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setOffsetRotation(v); +} + +float SpinePathConstraintData::get_position() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getPosition(); +} + +void SpinePathConstraintData::set_position(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setPosition(v); +} + +float SpinePathConstraintData::get_spacing() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getSpacing(); +} + +void SpinePathConstraintData::set_spacing(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setSpacing(v); +} + +float SpinePathConstraintData::get_mix_rotate() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixRotate(); +} + +void SpinePathConstraintData::set_mix_rotate(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setMixRotate(v); +} + +float SpinePathConstraintData::get_mix_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixX(); +} + +void SpinePathConstraintData::set_mix_x(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setMixX(v); +} + +float SpinePathConstraintData::get_mix_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixY(); +} + +void SpinePathConstraintData::set_mix_y(float v) { + SPINE_CHECK(get_spine_constraint_data(), ) + get_spine_constraint_data()->setMixY(v); +} diff --git a/modules/spine_godot/SpinePathConstraintData.h b/modules/spine_godot/SpinePathConstraintData.h new file mode 100644 index 000000000000..b2e4fa7f1869 --- /dev/null +++ b/modules/spine_godot/SpinePathConstraintData.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineConstraintData.h" +#include "SpineConstant.h" +#include "SpineBoneData.h" +#include "SpineSlotData.h" +#include + +class SpinePathConstraintData : public SpineConstraintData { + GDCLASS(SpinePathConstraintData, SpineConstraintData) + + spine::PathConstraintData *get_spine_constraint_data() { return (spine::PathConstraintData *) get_spine_object(); } + +protected: + static void _bind_methods(); + +public: + Array get_bones(); + + Ref get_target(); + + void set_target(Ref v); + + SpineConstant::PositionMode get_position_mode(); + + void set_position_mode(SpineConstant::PositionMode v); + + SpineConstant::SpacingMode get_spacing_mode(); + + void set_spacing_mode(SpineConstant::SpacingMode v); + + SpineConstant::RotateMode get_rotate_mode(); + + void set_rotate_mode(SpineConstant::RotateMode v); + + float get_offset_rotation(); + + void set_offset_rotation(float v); + + float get_position(); + + void set_position(float v); + + float get_spacing(); + + void set_spacing(float v); + + float get_mix_rotate(); + + void set_mix_rotate(float v); + + float get_mix_x(); + + void set_mix_x(float v); + + float get_mix_y(); + + void set_mix_y(float v); +}; diff --git a/modules/spine_godot/SpinePhysicsConstraint.cpp b/modules/spine_godot/SpinePhysicsConstraint.cpp new file mode 100644 index 000000000000..94e6a5af08f5 --- /dev/null +++ b/modules/spine_godot/SpinePhysicsConstraint.cpp @@ -0,0 +1,382 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpinePhysicsConstraint.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpinePhysicsConstraint::_bind_methods() { + ClassDB::bind_method(D_METHOD("update", "physics"), &SpinePhysicsConstraint::update); + ClassDB::bind_method(D_METHOD("get_bone"), &SpinePhysicsConstraint::get_bone); + ClassDB::bind_method(D_METHOD("set_inertia", "value"), &SpinePhysicsConstraint::set_inertia); + ClassDB::bind_method(D_METHOD("get_inertia"), &SpinePhysicsConstraint::get_inertia); + ClassDB::bind_method(D_METHOD("set_strength", "value"), &SpinePhysicsConstraint::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &SpinePhysicsConstraint::get_strength); + ClassDB::bind_method(D_METHOD("set_damping", "value"), &SpinePhysicsConstraint::set_damping); + ClassDB::bind_method(D_METHOD("get_damping"), &SpinePhysicsConstraint::get_damping); + ClassDB::bind_method(D_METHOD("set_mass_inverse", "value"), &SpinePhysicsConstraint::set_mass_inverse); + ClassDB::bind_method(D_METHOD("get_mass_inverse"), &SpinePhysicsConstraint::get_mass_inverse); + ClassDB::bind_method(D_METHOD("set_wind", "value"), &SpinePhysicsConstraint::set_wind); + ClassDB::bind_method(D_METHOD("get_wind"), &SpinePhysicsConstraint::get_wind); + ClassDB::bind_method(D_METHOD("set_gravity", "value"), &SpinePhysicsConstraint::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity"), &SpinePhysicsConstraint::get_gravity); + ClassDB::bind_method(D_METHOD("set_mix", "value"), &SpinePhysicsConstraint::set_mix); + ClassDB::bind_method(D_METHOD("get_mix"), &SpinePhysicsConstraint::get_mix); + ClassDB::bind_method(D_METHOD("set_reset", "value"), &SpinePhysicsConstraint::set_reset); + ClassDB::bind_method(D_METHOD("get_reset"), &SpinePhysicsConstraint::get_reset); + ClassDB::bind_method(D_METHOD("set_ux", "value"), &SpinePhysicsConstraint::set_ux); + ClassDB::bind_method(D_METHOD("get_ux"), &SpinePhysicsConstraint::get_ux); + ClassDB::bind_method(D_METHOD("set_uy", "value"), &SpinePhysicsConstraint::set_uy); + ClassDB::bind_method(D_METHOD("get_uy"), &SpinePhysicsConstraint::get_uy); + ClassDB::bind_method(D_METHOD("set_cx", "value"), &SpinePhysicsConstraint::set_cx); + ClassDB::bind_method(D_METHOD("get_cx"), &SpinePhysicsConstraint::get_cx); + ClassDB::bind_method(D_METHOD("set_cy", "value"), &SpinePhysicsConstraint::set_cy); + ClassDB::bind_method(D_METHOD("get_cy"), &SpinePhysicsConstraint::get_cy); + ClassDB::bind_method(D_METHOD("set_tx", "value"), &SpinePhysicsConstraint::set_tx); + ClassDB::bind_method(D_METHOD("get_tx"), &SpinePhysicsConstraint::get_tx); + ClassDB::bind_method(D_METHOD("set_ty", "value"), &SpinePhysicsConstraint::set_ty); + ClassDB::bind_method(D_METHOD("get_ty"), &SpinePhysicsConstraint::get_ty); + ClassDB::bind_method(D_METHOD("set_x_offset", "value"), &SpinePhysicsConstraint::set_x_offset); + ClassDB::bind_method(D_METHOD("get_x_offset"), &SpinePhysicsConstraint::get_x_offset); + ClassDB::bind_method(D_METHOD("set_x_velocity", "value"), &SpinePhysicsConstraint::set_x_velocity); + ClassDB::bind_method(D_METHOD("get_x_velocity"), &SpinePhysicsConstraint::get_x_velocity); + ClassDB::bind_method(D_METHOD("set_y_offset", "value"), &SpinePhysicsConstraint::set_y_offset); + ClassDB::bind_method(D_METHOD("get_y_offset"), &SpinePhysicsConstraint::get_y_offset); + ClassDB::bind_method(D_METHOD("set_y_velocity", "value"), &SpinePhysicsConstraint::set_y_velocity); + ClassDB::bind_method(D_METHOD("get_y_velocity"), &SpinePhysicsConstraint::get_y_velocity); + ClassDB::bind_method(D_METHOD("set_rotate_offset", "value"), &SpinePhysicsConstraint::set_rotate_offset); + ClassDB::bind_method(D_METHOD("get_rotate_offset"), &SpinePhysicsConstraint::get_rotate_offset); + ClassDB::bind_method(D_METHOD("set_rotate_velocity", "value"), &SpinePhysicsConstraint::set_rotate_velocity); + ClassDB::bind_method(D_METHOD("get_rotate_velocity"), &SpinePhysicsConstraint::get_rotate_velocity); + ClassDB::bind_method(D_METHOD("set_scale_offset", "value"), &SpinePhysicsConstraint::set_scale_offset); + ClassDB::bind_method(D_METHOD("get_scale_offset"), &SpinePhysicsConstraint::get_scale_offset); + ClassDB::bind_method(D_METHOD("set_scale_velocity", "value"), &SpinePhysicsConstraint::set_scale_velocity); + ClassDB::bind_method(D_METHOD("get_scale_velocity"), &SpinePhysicsConstraint::get_scale_velocity); + ClassDB::bind_method(D_METHOD("set_active", "value"), &SpinePhysicsConstraint::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &SpinePhysicsConstraint::is_active); + ClassDB::bind_method(D_METHOD("set_remaining", "value"), &SpinePhysicsConstraint::set_remaining); + ClassDB::bind_method(D_METHOD("get_remaining"), &SpinePhysicsConstraint::get_remaining); + ClassDB::bind_method(D_METHOD("set_last_Time", "value"), &SpinePhysicsConstraint::set_last_Time); + ClassDB::bind_method(D_METHOD("get_last_Time"), &SpinePhysicsConstraint::get_last_Time); + ClassDB::bind_method(D_METHOD("reset"), &SpinePhysicsConstraint::reset); + ClassDB::bind_method(D_METHOD("translate", "x", "y"), &SpinePhysicsConstraint::translate); + ClassDB::bind_method(D_METHOD("rotate", "x", "y", "degrees"), &SpinePhysicsConstraint::rotate); +} + +void SpinePhysicsConstraint::update(SpineConstant::Physics physics) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->update((spine::Physics) physics); +} + +Ref SpinePhysicsConstraint::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &data = get_spine_object()->getData(); + Ref data_ref(memnew(SpinePhysicsConstraintData)); + data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &data); + return data_ref; +} + +Ref SpinePhysicsConstraint::get_bone() { + SPINE_CHECK(get_spine_object(), nullptr) + auto target = get_spine_object()->getBone(); + if (!target) return nullptr; + Ref target_ref(memnew(SpineBone)); + target_ref->set_spine_object(get_spine_owner(), target); + return target_ref; +} + +void SpinePhysicsConstraint::set_bone(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setBone(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +void SpinePhysicsConstraint::set_inertia(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setInertia(value); +} + +float SpinePhysicsConstraint::get_inertia() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getInertia(); +} + +void SpinePhysicsConstraint::set_strength(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setStrength(value); +} + +float SpinePhysicsConstraint::get_strength() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getStrength(); +} + +void SpinePhysicsConstraint::set_damping(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setDamping(value); +} + +float SpinePhysicsConstraint::get_damping() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getDamping(); +} + +void SpinePhysicsConstraint::set_mass_inverse(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMassInverse(value); +} + +float SpinePhysicsConstraint::get_mass_inverse() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMassInverse(); +} + +void SpinePhysicsConstraint::set_wind(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setWind(value); +} + +float SpinePhysicsConstraint::get_wind() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getWind(); +} + +void SpinePhysicsConstraint::set_gravity(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setGravity(value); +} + +float SpinePhysicsConstraint::get_gravity() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getGravity(); +} + +void SpinePhysicsConstraint::set_mix(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMix(value); +} + +float SpinePhysicsConstraint::get_mix() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMix(); +} + +void SpinePhysicsConstraint::set_reset(bool value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setReset(value); +} + +bool SpinePhysicsConstraint::get_reset() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getReset(); +} + +void SpinePhysicsConstraint::set_ux(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setUx(value); +} + +float SpinePhysicsConstraint::get_ux() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getUx(); +} + +void SpinePhysicsConstraint::set_uy(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setUy(value); +} + +float SpinePhysicsConstraint::get_uy() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getUy(); +} + +void SpinePhysicsConstraint::set_cx(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setCx(value); +} + +float SpinePhysicsConstraint::get_cx() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getCx(); +} + +void SpinePhysicsConstraint::set_cy(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setCy(value); +} + +float SpinePhysicsConstraint::get_cy() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getCy(); +} + +void SpinePhysicsConstraint::set_tx(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTx(value); +} + +float SpinePhysicsConstraint::get_tx() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTx(); +} + +void SpinePhysicsConstraint::set_ty(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTy(value); +} + +float SpinePhysicsConstraint::get_ty() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTy(); +} + +void SpinePhysicsConstraint::set_x_offset(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setXOffset(value); +} + +float SpinePhysicsConstraint::get_x_offset() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getXOffset(); +} + +void SpinePhysicsConstraint::set_x_velocity(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setXVelocity(value); +} + +float SpinePhysicsConstraint::get_x_velocity() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getXVelocity(); +} + +void SpinePhysicsConstraint::set_y_offset(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setYOffset(value); +} + +float SpinePhysicsConstraint::get_y_offset() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getYOffset(); +} + +void SpinePhysicsConstraint::set_y_velocity(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setYVelocity(value); +} + +float SpinePhysicsConstraint::get_y_velocity() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getYVelocity(); +} + +void SpinePhysicsConstraint::set_rotate_offset(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setRotateOffset(value); +} + +float SpinePhysicsConstraint::get_rotate_offset() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getRotateOffset(); +} + +void SpinePhysicsConstraint::set_rotate_velocity(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setRotateVelocity(value); +} + +float SpinePhysicsConstraint::get_rotate_velocity() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getRotateVelocity(); +} + +void SpinePhysicsConstraint::set_scale_offset(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleOffset(value); +} + +float SpinePhysicsConstraint::get_scale_offset() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleOffset(); +} + +void SpinePhysicsConstraint::set_scale_velocity(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setScaleVelocity(value); +} + +float SpinePhysicsConstraint::get_scale_velocity() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getScaleVelocity(); +} + +void SpinePhysicsConstraint::set_active(bool value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setActive(value); +} + +bool SpinePhysicsConstraint::is_active() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isActive(); +} + +void SpinePhysicsConstraint::set_remaining(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setRemaining(value); +} + +float SpinePhysicsConstraint::get_remaining() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getRemaining(); +} + +void SpinePhysicsConstraint::set_last_Time(float value) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setLastTime(value); +} + +float SpinePhysicsConstraint::get_last_Time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getLastTime(); +} + +void SpinePhysicsConstraint::reset() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->reset(); +} + +void SpinePhysicsConstraint::translate(float x, float y) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->translate(x, y); +} + +void SpinePhysicsConstraint::rotate(float x, float y, float degrees) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->rotate(x, y, degrees); +} diff --git a/modules/spine_godot/SpinePhysicsConstraint.h b/modules/spine_godot/SpinePhysicsConstraint.h new file mode 100644 index 000000000000..e435668c78ee --- /dev/null +++ b/modules/spine_godot/SpinePhysicsConstraint.h @@ -0,0 +1,132 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpinePhysicsConstraintData.h" +#include "SpineBone.h" +#include + +class SpinePhysicsConstraint : public SpineSpriteOwnedObject { + GDCLASS(SpinePhysicsConstraint, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void update(SpineConstant::Physics physics); + + Ref get_data(); + + Ref get_bone(); + + void set_bone(Ref v); + + void set_inertia(float value); + float get_inertia(); + + void set_strength(float value); + float get_strength(); + + void set_damping(float value); + float get_damping(); + + void set_mass_inverse(float value); + float get_mass_inverse(); + + void set_wind(float value); + float get_wind(); + + void set_gravity(float value); + float get_gravity(); + + void set_mix(float value); + float get_mix(); + + void set_reset(bool value); + bool get_reset(); + + void set_ux(float value); + float get_ux(); + + void set_uy(float value); + float get_uy(); + + void set_cx(float value); + float get_cx(); + + void set_cy(float value); + float get_cy(); + + void set_tx(float value); + float get_tx(); + + void set_ty(float value); + float get_ty(); + + void set_x_offset(float value); + float get_x_offset(); + + void set_x_velocity(float value); + float get_x_velocity(); + + void set_y_offset(float value); + float get_y_offset(); + + void set_y_velocity(float value); + float get_y_velocity(); + + void set_rotate_offset(float value); + float get_rotate_offset(); + + void set_rotate_velocity(float value); + float get_rotate_velocity(); + + void set_scale_offset(float value); + float get_scale_offset(); + + void set_scale_velocity(float value); + float get_scale_velocity(); + + void set_active(bool value); + bool is_active(); + + void set_remaining(float value); + float get_remaining(); + + void set_last_Time(float value); + float get_last_Time(); + + void reset(); + + void translate(float x, float y); + + void rotate(float x, float y, float degrees); +}; diff --git a/modules/spine_godot/SpinePhysicsConstraintData.cpp b/modules/spine_godot/SpinePhysicsConstraintData.cpp new file mode 100644 index 000000000000..8c6bb4474020 --- /dev/null +++ b/modules/spine_godot/SpinePhysicsConstraintData.cpp @@ -0,0 +1,153 @@ +/****************************************************************************** + * 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 "SpinePhysicsConstraintData.h" +#include "SpineCommon.h" + +void SpinePhysicsConstraintData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_bone"), &SpinePhysicsConstraintData::get_bone); + ClassDB::bind_method(D_METHOD("get_scale_x"), &SpinePhysicsConstraintData::get_scale_x); + ClassDB::bind_method(D_METHOD("get_shear_x"), &SpinePhysicsConstraintData::get_shear_x); + ClassDB::bind_method(D_METHOD("get_limit"), &SpinePhysicsConstraintData::get_limit); + ClassDB::bind_method(D_METHOD("get_step"), &SpinePhysicsConstraintData::get_step); + ClassDB::bind_method(D_METHOD("get_inertia"), &SpinePhysicsConstraintData::get_inertia); + ClassDB::bind_method(D_METHOD("get_strength"), &SpinePhysicsConstraintData::get_strength); + ClassDB::bind_method(D_METHOD("get_damping"), &SpinePhysicsConstraintData::get_damping); + ClassDB::bind_method(D_METHOD("get_mass_inverse"), &SpinePhysicsConstraintData::get_mass_inverse); + ClassDB::bind_method(D_METHOD("get_wind"), &SpinePhysicsConstraintData::get_wind); + ClassDB::bind_method(D_METHOD("get_gravity"), &SpinePhysicsConstraintData::get_gravity); + ClassDB::bind_method(D_METHOD("get_mix"), &SpinePhysicsConstraintData::get_mix); + ClassDB::bind_method(D_METHOD("is_inertia_global"), &SpinePhysicsConstraintData::is_inertia_global); + ClassDB::bind_method(D_METHOD("is_strength_global"), &SpinePhysicsConstraintData::is_strength_global); + ClassDB::bind_method(D_METHOD("is_damping_global"), &SpinePhysicsConstraintData::is_damping_global); + ClassDB::bind_method(D_METHOD("is_mass_global"), &SpinePhysicsConstraintData::is_mass_global); + ClassDB::bind_method(D_METHOD("is_wind_global"), &SpinePhysicsConstraintData::is_wind_global); + ClassDB::bind_method(D_METHOD("is_gravity_global"), &SpinePhysicsConstraintData::is_gravity_global); + ClassDB::bind_method(D_METHOD("is_mix_global"), &SpinePhysicsConstraintData::is_mix_global); +} + + +Ref SpinePhysicsConstraintData::get_bone() { + SPINE_CHECK(get_spine_constraint_data(), nullptr) + auto bone = get_spine_constraint_data()->getBone(); + if (!bone) return nullptr; + Ref slot_ref(memnew(SpineBoneData)); + slot_ref->set_spine_object(get_spine_owner(), bone); + return slot_ref; +} + +float SpinePhysicsConstraintData::get_scale_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getScaleX(); +} + +float SpinePhysicsConstraintData::get_shear_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getShearX(); +} + +float SpinePhysicsConstraintData::get_limit() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getLimit(); +} + +float SpinePhysicsConstraintData::get_step() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getStep(); +} + +float SpinePhysicsConstraintData::get_inertia() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getInertia(); +} + +float SpinePhysicsConstraintData::get_strength() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getStrength(); +} + +float SpinePhysicsConstraintData::get_damping() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getDamping(); +} + +float SpinePhysicsConstraintData::get_mass_inverse() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMassInverse(); +} + +float SpinePhysicsConstraintData::get_wind() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getWind(); +} + +float SpinePhysicsConstraintData::get_gravity() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getGravity(); +} + +float SpinePhysicsConstraintData::get_mix() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMix(); +} + +bool SpinePhysicsConstraintData::is_inertia_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isInertiaGlobal(); +} + +bool SpinePhysicsConstraintData::is_strength_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isStrengthGlobal(); +} + +bool SpinePhysicsConstraintData::is_damping_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isDampingGlobal(); +} + +bool SpinePhysicsConstraintData::is_mass_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isMassGlobal(); +} + +bool SpinePhysicsConstraintData::is_wind_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isWindGlobal(); +} + +bool SpinePhysicsConstraintData::is_gravity_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isGravityGlobal(); +} + +bool SpinePhysicsConstraintData::is_mix_global() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isMixGlobal(); +} diff --git a/modules/spine_godot/SpinePhysicsConstraintData.h b/modules/spine_godot/SpinePhysicsConstraintData.h new file mode 100644 index 000000000000..ffcd0b4c530b --- /dev/null +++ b/modules/spine_godot/SpinePhysicsConstraintData.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineConstraintData.h" +#include "SpineBoneData.h" +#include + +class SpinePhysicsConstraintData : public SpineConstraintData { + GDCLASS(SpinePhysicsConstraintData, SpineConstraintData) + + spine::PhysicsConstraintData *get_spine_constraint_data() { return (spine::PhysicsConstraintData *) SpineConstraintData::get_spine_object(); } + +protected: + static void _bind_methods(); + +public: + Ref get_bone(); + + float get_x(); + + float get_y(); + + float get_rotate(); + + float get_scale_x(); + + float get_shear_x(); + + float get_limit(); + + float get_step(); + + float get_inertia(); + + float get_strength(); + + float get_damping(); + + float get_mass_inverse(); + + float get_wind(); + + float get_gravity(); + + float get_mix(); + + bool is_inertia_global(); + + bool is_strength_global(); + + bool is_damping_global(); + + bool is_mass_global(); + + bool is_wind_global(); + + bool is_gravity_global(); + + bool is_mix_global(); +}; diff --git a/modules/spine_godot/SpineRendererObject.h b/modules/spine_godot/SpineRendererObject.h new file mode 100644 index 000000000000..e27170d2e56a --- /dev/null +++ b/modules/spine_godot/SpineRendererObject.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef GODOT_SPINERENDEREROBJECT_H +#define GODOT_SPINERENDEREROBJECT_H + +#include "SpineCommon.h" + +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#else +#include + +#if VERSION_MAJOR > 3 +#include +#endif +#endif + +struct SpineRendererObject { + Ref texture; + Ref normal_map; + Ref specular_map; +#if VERSION_MAJOR > 3 + Ref canvas_texture; +#endif +}; + +#endif diff --git a/modules/spine_godot/SpineSkeleton.cpp b/modules/spine_godot/SpineSkeleton.cpp new file mode 100644 index 000000000000..8104561fc859 --- /dev/null +++ b/modules/spine_godot/SpineSkeleton.cpp @@ -0,0 +1,428 @@ +/****************************************************************************** + * 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 "SpineSkeleton.h" +#include "SpineCommon.h" +#include "SpineSprite.h" +#include + +void SpineSkeleton::_bind_methods() { + ClassDB::bind_method(D_METHOD("update_world_transform", "physics"), &SpineSkeleton::update_world_transform); + ClassDB::bind_method(D_METHOD("set_to_setup_pose"), &SpineSkeleton::set_to_setup_pose); + ClassDB::bind_method(D_METHOD("set_bones_to_setup_pose"), &SpineSkeleton::set_bones_to_setup_pose); + ClassDB::bind_method(D_METHOD("set_slots_to_setup_pose"), &SpineSkeleton::set_slots_to_setup_pose); + ClassDB::bind_method(D_METHOD("find_bone", "bone_name"), &SpineSkeleton::find_bone); + ClassDB::bind_method(D_METHOD("find_slot", "slot_name"), &SpineSkeleton::find_slot); + ClassDB::bind_method(D_METHOD("set_skin_by_name", "skin_name"), &SpineSkeleton::set_skin_by_name); + ClassDB::bind_method(D_METHOD("set_skin", "new_skin"), &SpineSkeleton::set_skin); + ClassDB::bind_method(D_METHOD("get_attachment_by_slot_name", "slot_name", "attachment_name"), &SpineSkeleton::get_attachment_by_slot_name); + ClassDB::bind_method(D_METHOD("get_attachment_by_slot_index", "slot_index", "attachment_name"), &SpineSkeleton::get_attachment_by_slot_index); + ClassDB::bind_method(D_METHOD("set_attachment", "slot_name", "attachment_name"), &SpineSkeleton::set_attachment); + ClassDB::bind_method(D_METHOD("find_ik_constraint", "constraint_name"), &SpineSkeleton::find_ik_constraint); + ClassDB::bind_method(D_METHOD("find_transform_constraint", "constraint_name"), &SpineSkeleton::find_transform_constraint); + ClassDB::bind_method(D_METHOD("find_path_constraint", "constraint_name"), &SpineSkeleton::find_path_constraint); + ClassDB::bind_method(D_METHOD("find_physics_constraint", "constraint_name"), &SpineSkeleton::find_physics_constraint); + ClassDB::bind_method(D_METHOD("get_bounds"), &SpineSkeleton::get_bounds); + ClassDB::bind_method(D_METHOD("get_root_bone"), &SpineSkeleton::get_root_bone); + ClassDB::bind_method(D_METHOD("get_data"), &SpineSkeleton::get_skeleton_data_res); + ClassDB::bind_method(D_METHOD("get_bones"), &SpineSkeleton::get_bones); + ClassDB::bind_method(D_METHOD("get_slots"), &SpineSkeleton::get_slots); + ClassDB::bind_method(D_METHOD("get_draw_order"), &SpineSkeleton::get_draw_order); + ClassDB::bind_method(D_METHOD("get_ik_constraints"), &SpineSkeleton::get_ik_constraints); + ClassDB::bind_method(D_METHOD("get_path_constraints"), &SpineSkeleton::get_path_constraints); + ClassDB::bind_method(D_METHOD("get_transform_constraints"), &SpineSkeleton::get_transform_constraints); + ClassDB::bind_method(D_METHOD("get_skin"), &SpineSkeleton::get_skin); + ClassDB::bind_method(D_METHOD("get_color"), &SpineSkeleton::get_color); + ClassDB::bind_method(D_METHOD("set_color", "v"), &SpineSkeleton::set_color); + ClassDB::bind_method(D_METHOD("set_position", "position"), &SpineSkeleton::set_position); + ClassDB::bind_method(D_METHOD("get_x"), &SpineSkeleton::get_x); + ClassDB::bind_method(D_METHOD("set_x", "v"), &SpineSkeleton::set_x); + ClassDB::bind_method(D_METHOD("get_y"), &SpineSkeleton::get_y); + ClassDB::bind_method(D_METHOD("set_y", "v"), &SpineSkeleton::set_y); + ClassDB::bind_method(D_METHOD("get_scale_x"), &SpineSkeleton::get_scale_x); + ClassDB::bind_method(D_METHOD("set_scale_x", "v"), &SpineSkeleton::set_scale_x); + ClassDB::bind_method(D_METHOD("get_scale_y"), &SpineSkeleton::get_scale_y); + ClassDB::bind_method(D_METHOD("set_scale_y", "v"), &SpineSkeleton::set_scale_y); + ClassDB::bind_method(D_METHOD("get_time"), &SpineSkeleton::get_time); + ClassDB::bind_method(D_METHOD("set_time", "time"), &SpineSkeleton::set_time); + ClassDB::bind_method(D_METHOD("update", "delta"), &SpineSkeleton::update); + ClassDB::bind_method(D_METHOD("physics_translate", "x", "y"), &SpineSkeleton::physics_translate); + ClassDB::bind_method(D_METHOD("physics_rotate", "x", "y", "degrees"), &SpineSkeleton::physics_rotate); +} + +SpineSkeleton::SpineSkeleton() : skeleton(nullptr), sprite(nullptr), last_skin(nullptr) { +} + +SpineSkeleton::~SpineSkeleton() { + if (last_skin.is_valid()) last_skin.unref(); + delete skeleton; +} + +void SpineSkeleton::set_spine_sprite(SpineSprite *_sprite) { + delete skeleton; + skeleton = nullptr; + sprite = _sprite; + if (!sprite || !sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return; + skeleton = new spine::Skeleton(sprite->get_skeleton_data_res()->get_skeleton_data()); +} + +Ref SpineSkeleton::get_skeleton_data_res() const { + if (!sprite) return nullptr; + return sprite->get_skeleton_data_res(); +} + +void SpineSkeleton::update_world_transform(SpineConstant::Physics physics) { + SPINE_CHECK(skeleton, ) + skeleton->updateWorldTransform((spine::Physics) physics); +} + +void SpineSkeleton::set_to_setup_pose() { + SPINE_CHECK(skeleton, ) + skeleton->setToSetupPose(); +} + +void SpineSkeleton::set_bones_to_setup_pose() { + SPINE_CHECK(skeleton, ) + skeleton->setBonesToSetupPose(); +} + +void SpineSkeleton::set_slots_to_setup_pose() { + SPINE_CHECK(skeleton, ) + skeleton->setSlotsToSetupPose(); +} + +Ref SpineSkeleton::find_bone(const String &name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(name)) return nullptr; + auto bone = skeleton->findBone(SPINE_STRING_TMP(name)); + if (!bone) return nullptr; + if (_cached_bones.count(bone) > 0) { + return _cached_bones[bone]; + } + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(sprite, bone); + _cached_bones[bone] = bone_ref; + return bone_ref; +} + +Ref SpineSkeleton::find_slot(const String &name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(name)) return nullptr; + auto slot = skeleton->findSlot(SPINE_STRING_TMP(name)); + if (!slot) return nullptr; + if (_cached_slots.count(slot) > 0) { + return _cached_slots[slot]; + } + Ref slot_ref(memnew(SpineSlot)); + slot_ref->set_spine_object(sprite, slot); + _cached_slots[slot] = slot_ref; + return slot_ref; +} + +void SpineSkeleton::set_skin_by_name(const String &skin_name) { + SPINE_CHECK(skeleton, ) + skeleton->setSkin(SPINE_STRING_TMP(skin_name)); +} + +void SpineSkeleton::set_skin(Ref new_skin) { + SPINE_CHECK(skeleton, ) + if (last_skin.is_valid()) last_skin.unref(); + last_skin = new_skin; + skeleton->setSkin(new_skin.is_valid() && new_skin->get_spine_object() ? new_skin->get_spine_object() : nullptr); +} + +Ref SpineSkeleton::get_attachment_by_slot_name(const String &slot_name, const String &attachment_name) { + SPINE_CHECK(skeleton, nullptr) + auto attachment = skeleton->getAttachment(SPINE_STRING_TMP(slot_name), SPINE_STRING_TMP(attachment_name)); + if (!attachment) return nullptr; + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(*sprite->get_skeleton_data_res(), attachment); + return attachment_ref; +} + +Ref SpineSkeleton::get_attachment_by_slot_index(int slot_index, const String &attachment_name) { + SPINE_CHECK(skeleton, nullptr) + auto attachment = skeleton->getAttachment(slot_index, SPINE_STRING_TMP(attachment_name)); + if (!attachment) return nullptr; + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(*sprite->get_skeleton_data_res(), attachment); + return attachment_ref; +} + +void SpineSkeleton::set_attachment(const String &slot_name, const String &attachment_name) { + SPINE_CHECK(skeleton, ) + skeleton->setAttachment(SPINE_STRING(slot_name), SPINE_STRING(attachment_name)); +} + +Ref SpineSkeleton::find_ik_constraint(const String &constraint_name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(constraint_name)) return nullptr; + auto constraint = skeleton->findIkConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) return nullptr; + Ref constraint_ref(memnew(SpineIkConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + return constraint_ref; +} + +Ref SpineSkeleton::find_transform_constraint(const String &constraint_name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(constraint_name)) return nullptr; + auto constraint = skeleton->findTransformConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) return nullptr; + Ref constraint_ref(memnew(SpineTransformConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + return constraint_ref; +} + +Ref SpineSkeleton::find_path_constraint(const String &constraint_name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(constraint_name)) return nullptr; + auto constraint = skeleton->findPathConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) return nullptr; + Ref constraint_ref(memnew(SpinePathConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + return constraint_ref; +} + + +Ref SpineSkeleton::find_physics_constraint(const String &constraint_name) { + SPINE_CHECK(skeleton, nullptr) + if (EMPTY(constraint_name)) return nullptr; + auto constraint = skeleton->findPhysicsConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) return nullptr; + Ref constraint_ref(memnew(SpinePhysicsConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + return constraint_ref; +} + +Rect2 SpineSkeleton::get_bounds() { + SPINE_CHECK(skeleton, Rect2(0, 0, 0, 0)) + float x, y, w, h; + spine::SkeletonClipping clipper; + skeleton->getBounds(x, y, w, h, bounds_vertex_buffer, &clipper); + return Rect2(x, y, w, h); +} + +Ref SpineSkeleton::get_root_bone() { + SPINE_CHECK(skeleton, nullptr) + auto bone = skeleton->getRootBone(); + if (!bone) return nullptr; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(sprite, bone); + return bone_ref; +} + +Array SpineSkeleton::get_bones() { + Array result; + SPINE_CHECK(skeleton, result) + auto &bones = skeleton->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < result.size(); ++i) { + auto bone = bones[i]; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(sprite, bone); + result[i] = bone_ref; + } + return result; +} + +Array SpineSkeleton::get_slots() { + Array result; + SPINE_CHECK(skeleton, result) + auto &slots = skeleton->getSlots(); + result.resize((int) slots.size()); + for (int i = 0; i < result.size(); ++i) { + auto slot = slots[i]; + Ref slot_ref(memnew(SpineSlot)); + slot_ref->set_spine_object(sprite, slot); + result[i] = slot_ref; + } + return result; +} + +Array SpineSkeleton::get_draw_order() { + Array result; + SPINE_CHECK(skeleton, result) + auto &slots = skeleton->getDrawOrder(); + result.resize((int) slots.size()); + for (int i = 0; i < result.size(); ++i) { + auto slot = slots[i]; + Ref slot_ref(memnew(SpineSlot)); + slot_ref->set_spine_object(sprite, slot); + result[i] = slot_ref; + } + return result; +} + +Array SpineSkeleton::get_ik_constraints() { + Array result; + SPINE_CHECK(skeleton, result) + auto &constraints = skeleton->getIkConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < result.size(); ++i) { + auto constraint = constraints[i]; + Ref constraint_ref(memnew(SpineIkConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeleton::get_transform_constraints() { + Array result; + SPINE_CHECK(skeleton, result) + auto &constraints = skeleton->getTransformConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < result.size(); ++i) { + auto constraint = constraints[i]; + Ref constraint_ref(memnew(SpineTransformConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeleton::get_path_constraints() { + Array result; + SPINE_CHECK(skeleton, result) + auto &constraints = skeleton->getPathConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < result.size(); ++i) { + auto constraint = constraints[i]; + Ref constraint_ref(memnew(SpinePathConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeleton::get_physics_constraints() { + Array result; + SPINE_CHECK(skeleton, result) + auto &constraints = skeleton->getPhysicsConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < result.size(); ++i) { + auto constraint = constraints[i]; + Ref constraint_ref(memnew(SpinePhysicsConstraint)); + constraint_ref->set_spine_object(sprite, constraint); + result[i] = constraint_ref; + } + return result; +} + +Ref SpineSkeleton::get_skin() { + SPINE_CHECK(skeleton, nullptr) + auto skin = skeleton->getSkin(); + if (!skin) return nullptr; + Ref skin_ref(memnew(SpineSkin)); + skin_ref->set_spine_object(*sprite->get_skeleton_data_res(), skin); + return skin_ref; +} + +Color SpineSkeleton::get_color() { + SPINE_CHECK(skeleton, Color(0, 0, 0, 0)) + auto &color = skeleton->getColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineSkeleton::set_color(Color v) { + SPINE_CHECK(skeleton, ) + auto &color = skeleton->getColor(); + color.set(v.r, v.g, v.b, v.a); +} + +void SpineSkeleton::set_position(Vector2 position) { + SPINE_CHECK(skeleton, ) + skeleton->setPosition(position.x, position.y); +} + +float SpineSkeleton::get_x() { + SPINE_CHECK(skeleton, 0) + return skeleton->getX(); +} + +void SpineSkeleton::set_x(float v) { + SPINE_CHECK(skeleton, ) + skeleton->setX(v); +} + +float SpineSkeleton::get_y() { + SPINE_CHECK(skeleton, 0) + return skeleton->getY(); +} + +void SpineSkeleton::set_y(float v) { + SPINE_CHECK(skeleton, ) + skeleton->setY(v); +} + +float SpineSkeleton::get_scale_x() { + SPINE_CHECK(skeleton, 1) + return skeleton->getScaleX(); +} + +void SpineSkeleton::set_scale_x(float v) { + SPINE_CHECK(skeleton, ) + skeleton->setScaleX(v); +} + +float SpineSkeleton::get_scale_y() { + SPINE_CHECK(skeleton, 1) + return -skeleton->getScaleY(); +} + +void SpineSkeleton::set_scale_y(float v) { + SPINE_CHECK(skeleton, ) + skeleton->setScaleY(v); +} + +float SpineSkeleton::get_time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTime(); +} + +void SpineSkeleton::set_time(float time) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTime(time); +} + +void SpineSkeleton::update(float delta) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->update(delta); +} + +void SpineSkeleton::physics_translate(float x, float y) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->physicsTranslate(x, y); +} + +void SpineSkeleton::physics_rotate(float x, float y, float degrees) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->physicsRotate(x, y, degrees); +} diff --git a/modules/spine_godot/SpineSkeleton.h b/modules/spine_godot/SpineSkeleton.h new file mode 100644 index 000000000000..ff98aa3645fa --- /dev/null +++ b/modules/spine_godot/SpineSkeleton.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineSkeletonDataResource.h" +#include "SpineBone.h" +#include "SpineSlot.h" +#include "SpineIkConstraint.h" +#include "SpineTransformConstraint.h" +#include "SpinePathConstraint.h" +#include "SpinePhysicsConstraint.h" + +#include + +class SpineSprite; + +class SpineSkeleton : public REFCOUNTED { + GDCLASS(SpineSkeleton, REFCOUNTED); + + friend class SpineBone; + friend class SpineSlot; + friend class SpineTimeline; + friend class SpineSprite; + friend class SpineAnimation; + friend class SpineAnimationState; + friend class SpineAnimationTrack; + friend class SpineBoneNode; + friend class SpineSlotNode; + +protected: + static void _bind_methods(); + + void set_spine_sprite(SpineSprite *_sprite); + spine::Skeleton *get_spine_object() { return skeleton; } + SpineSprite *get_spine_owner() { return sprite; } + Ref get_skeleton_data_res() const; + +private: + spine::Skeleton *skeleton; + SpineSprite *sprite; + spine::Vector bounds_vertex_buffer; + Ref last_skin; + + std::unordered_map> _cached_bones; + std::unordered_map> _cached_slots; + +public: + SpineSkeleton(); + ~SpineSkeleton() override; + + void update_world_transform(SpineConstant::Physics physics); + + void set_to_setup_pose(); + + void set_bones_to_setup_pose(); + + void set_slots_to_setup_pose(); + + Ref find_bone(const String &name); + + Ref find_slot(const String &name); + + void set_skin_by_name(const String &skin_name); + + void set_skin(Ref new_skin); + + Ref get_attachment_by_slot_name(const String &slot_name, const String &attachment_name); + + Ref get_attachment_by_slot_index(int slot_index, const String &attachment_name); + + void set_attachment(const String &slot_name, const String &attachment_name); + + Ref find_ik_constraint(const String &constraint_name); + + Ref find_transform_constraint(const String &constraint_name); + + Ref find_path_constraint(const String &constraint_name); + + Ref find_physics_constraint(const String &constraint_name); + + Rect2 get_bounds(); + + Ref get_root_bone(); + + Array get_bones(); + + Array get_slots(); + + Array get_draw_order(); + + Array get_ik_constraints(); + + Array get_transform_constraints(); + + Array get_path_constraints(); + + Array get_physics_constraints(); + + Ref get_skin(); + + Color get_color(); + + void set_color(Color v); + + void set_position(Vector2 position); + + float get_x(); + + void set_x(float v); + + float get_y(); + + void set_y(float v); + + float get_scale_x(); + + void set_scale_x(float v); + + float get_scale_y(); + + void set_scale_y(float v); + + float get_time(); + + void set_time(float time); + + void update(float delta); + + void physics_translate(float x, float y); + + void physics_rotate(float x, float y, float degrees); +}; diff --git a/modules/spine_godot/SpineSkeletonDataResource.cpp b/modules/spine_godot/SpineSkeletonDataResource.cpp new file mode 100644 index 000000000000..f4e6ac273879 --- /dev/null +++ b/modules/spine_godot/SpineSkeletonDataResource.cpp @@ -0,0 +1,855 @@ +/****************************************************************************** + * 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 "SpineSkeletonDataResource.h" +#include "SpineCommon.h" + +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#ifdef TOOLS_ENABLED +#include +#endif +#else +#if VERSION_MAJOR > 3 +#include "core/config/engine.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_interface.h" +#endif +#else +#include "core/engine.h" +#endif +#include +#endif + +#ifdef TOOLS_ENABLED +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) +#include "editor/file_system/editor_file_system.h" +#else +#include "editor/editor_file_system.h" +#endif +#endif +#endif + +void SpineAnimationMix::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_from", "from"), + &SpineAnimationMix::set_from); + ClassDB::bind_method(D_METHOD("get_from"), &SpineAnimationMix::get_from); + ClassDB::bind_method(D_METHOD("set_to", "to"), &SpineAnimationMix::set_to); + ClassDB::bind_method(D_METHOD("get_to"), &SpineAnimationMix::get_to); + ClassDB::bind_method(D_METHOD("set_mix", "mix"), &SpineAnimationMix::set_mix); + ClassDB::bind_method(D_METHOD("get_mix"), &SpineAnimationMix::get_mix); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "from"), "set_from", "get_from"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "to"), "set_to", "get_to"); +#if VERSION_MAJOR > 3 + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mix"), "set_mix", "get_mix"); +#else + ADD_PROPERTY(PropertyInfo(Variant::REAL, "mix"), "set_mix", "get_mix"); +#endif +} + +SpineAnimationMix::SpineAnimationMix() : from(""), to(""), mix(0) {} + +void SpineAnimationMix::set_from(const String &_from) { this->from = _from; } + +String SpineAnimationMix::get_from() { return from; } + +void SpineAnimationMix::set_to(const String &_to) { this->to = _to; } + +String SpineAnimationMix::get_to() { return to; } + +void SpineAnimationMix::set_mix(float _mix) { this->mix = _mix; } + +float SpineAnimationMix::get_mix() { return mix; } + +void SpineSkeletonDataResource::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_skeleton_data_loaded"), + &SpineSkeletonDataResource::is_skeleton_data_loaded); + ClassDB::bind_method(D_METHOD("set_atlas_res", "atlas_res"), + &SpineSkeletonDataResource::set_atlas_res); + ClassDB::bind_method(D_METHOD("get_atlas_res"), + &SpineSkeletonDataResource::get_atlas_res); + ClassDB::bind_method(D_METHOD("set_skeleton_file_res", "skeleton_file_res"), + &SpineSkeletonDataResource::set_skeleton_file_res); + ClassDB::bind_method(D_METHOD("get_skeleton_file_res"), + &SpineSkeletonDataResource::get_skeleton_file_res); + ClassDB::bind_method(D_METHOD("set_default_mix", "default_mix"), + &SpineSkeletonDataResource::set_default_mix); + ClassDB::bind_method(D_METHOD("get_default_mix"), + &SpineSkeletonDataResource::get_default_mix); + ClassDB::bind_method(D_METHOD("set_animation_mixes", "mixes"), + &SpineSkeletonDataResource::set_animation_mixes); + ClassDB::bind_method(D_METHOD("get_animation_mixes"), + &SpineSkeletonDataResource::get_animation_mixes); + + // Spine API + ClassDB::bind_method(D_METHOD("find_bone", "bone_name"), + &SpineSkeletonDataResource::find_bone); + ClassDB::bind_method(D_METHOD("find_slot", "slot_name"), + &SpineSkeletonDataResource::find_slot); + ClassDB::bind_method(D_METHOD("find_skin", "skin_name"), + &SpineSkeletonDataResource::find_skin); + ClassDB::bind_method(D_METHOD("find_event", "event_data_name"), + &SpineSkeletonDataResource::find_event); + ClassDB::bind_method(D_METHOD("find_animation", "animation_name"), + &SpineSkeletonDataResource::find_animation); + ClassDB::bind_method(D_METHOD("find_ik_constraint_data", "constraint_name"), + &SpineSkeletonDataResource::find_ik_constraint); + ClassDB::bind_method( + D_METHOD("find_transform_constraint_data", "constraint_name"), + &SpineSkeletonDataResource::find_transform_constraint); + ClassDB::bind_method(D_METHOD("find_path_constraint_data", "constraint_name"), + &SpineSkeletonDataResource::find_path_constraint); + ClassDB::bind_method(D_METHOD("find_physics_constraint_data", "constraint_name"), + &SpineSkeletonDataResource::find_physics_constraint); + ClassDB::bind_method(D_METHOD("get_skeleton_name"), + &SpineSkeletonDataResource::get_skeleton_name); + ClassDB::bind_method(D_METHOD("get_bones"), + &SpineSkeletonDataResource::get_bones); + ClassDB::bind_method(D_METHOD("get_slots"), + &SpineSkeletonDataResource::get_slots); + ClassDB::bind_method(D_METHOD("get_skins"), + &SpineSkeletonDataResource::get_skins); + ClassDB::bind_method(D_METHOD("get_default_skin"), + &SpineSkeletonDataResource::get_default_skin); + ClassDB::bind_method(D_METHOD("set_default_skin", "skin"), + &SpineSkeletonDataResource::set_default_skin); + ClassDB::bind_method(D_METHOD("get_events"), + &SpineSkeletonDataResource::get_events); + ClassDB::bind_method(D_METHOD("get_animations"), + &SpineSkeletonDataResource::get_animations); + ClassDB::bind_method(D_METHOD("get_ik_constraints"), + &SpineSkeletonDataResource::get_ik_constraints); + ClassDB::bind_method(D_METHOD("get_transform_constraints"), + &SpineSkeletonDataResource::get_transform_constraints); + ClassDB::bind_method(D_METHOD("get_path_constraints"), + &SpineSkeletonDataResource::get_path_constraints); + ClassDB::bind_method(D_METHOD("get_physics_constraints"), + &SpineSkeletonDataResource::get_physics_constraints); + ClassDB::bind_method(D_METHOD("get_x"), &SpineSkeletonDataResource::get_x); + ClassDB::bind_method(D_METHOD("get_y"), &SpineSkeletonDataResource::get_y); + ClassDB::bind_method(D_METHOD("get_width"), + &SpineSkeletonDataResource::get_width); + ClassDB::bind_method(D_METHOD("get_height"), + &SpineSkeletonDataResource::get_height); + ClassDB::bind_method(D_METHOD("get_version"), + &SpineSkeletonDataResource::get_version); + ClassDB::bind_method(D_METHOD("get_hash"), + &SpineSkeletonDataResource::get_hash); + ClassDB::bind_method(D_METHOD("get_images_path"), + &SpineSkeletonDataResource::get_images_path); + ClassDB::bind_method(D_METHOD("get_audio_path"), + &SpineSkeletonDataResource::get_audio_path); + ClassDB::bind_method(D_METHOD("get_fps"), + &SpineSkeletonDataResource::get_fps); + ClassDB::bind_method(D_METHOD("get_reference_scale"), + &SpineSkeletonDataResource::get_reference_scale); + ClassDB::bind_method(D_METHOD("set_reference_scale", "reference_scale"), + &SpineSkeletonDataResource::set_reference_scale); + ClassDB::bind_method(D_METHOD("update_skeleton_data"), + &SpineSkeletonDataResource::update_skeleton_data); + + ADD_SIGNAL(MethodInfo("skeleton_data_changed")); + ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated")); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas_res", + PropertyHint::PROPERTY_HINT_RESOURCE_TYPE, + "SpineAtlasResource"), + "set_atlas_res", "get_atlas_res"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skeleton_file_res", + PropertyHint::PROPERTY_HINT_RESOURCE_TYPE, + "SpineSkeletonFileResource"), + "set_skeleton_file_res", "get_skeleton_file_res"); +#if VERSION_MAJOR > 3 + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_mix"), "set_default_mix", + "get_default_mix"); +#else + ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_mix"), "set_default_mix", + "get_default_mix"); +#endif + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animation_mixes"), + "set_animation_mixes", "get_animation_mixes"); + +#ifdef TOOLS_ENABLED +#if VERSION_MAJOR > 3 + ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"), + &SpineSkeletonDataResource::_on_resources_reimported); +#else + ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"), + &SpineSkeletonDataResource::_on_resources_reimported); +#endif +#endif +} + +#ifdef TOOLS_ENABLED +EditorFileSystem *get_editor_file_system() { +#ifdef SPINE_GODOT_EXTENSION + EditorInterface *editor_interface = EditorInterface::get_singleton(); + if (editor_interface) { + return editor_interface->get_resource_filesystem(); + } + return nullptr; +#else + return EditorFileSystem::get_singleton(); +#endif +} +#endif + +SpineSkeletonDataResource::SpineSkeletonDataResource() + : default_mix(0), skeleton_data(nullptr), animation_state_data(nullptr) { + +#ifdef TOOLS_ENABLED +#if VERSION_MAJOR > 3 + if (Engine::get_singleton()->is_editor_hint()) { + EditorFileSystem *efs = get_editor_file_system(); + if (efs) { + editor_file_system_id = efs->get_instance_id(); + efs->connect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported)); + } + } +#else + if (Engine::get_singleton()->is_editor_hint()) { + EditorFileSystem *efs = EditorFileSystem::get_singleton(); + if (efs) { + editor_file_system_id = efs->get_instance_id(); + efs->connect("resources_reimported", this, "_on_resources_reimported"); + } + } +#endif +#endif +} + +SpineSkeletonDataResource::~SpineSkeletonDataResource() { +#ifdef TOOLS_ENABLED +#if VERSION_MAJOR > 3 + if (Engine::get_singleton()->is_editor_hint()) { + EditorFileSystem *efs = Object::cast_to(ObjectDB::get_instance(editor_file_system_id)); + if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) { + efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported)); + } + } +#else + if (Engine::get_singleton()->is_editor_hint()) { + EditorFileSystem *efs = Object::cast_to(ObjectDB::get_instance(editor_file_system_id)); + if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) { + efs->disconnect("resources_reimported", this, "_on_resources_reimported"); + } + } +#endif +#endif + + delete skeleton_data; + delete animation_state_data; +} + +#ifdef TOOLS_ENABLED +#if VERSION_MAJOR > 3 +void SpineSkeletonDataResource::_on_resources_reimported(const PackedStringArray &resources) { + for (int i = 0; i < resources.size(); i++) { + if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) { +#ifdef SPINE_GODOT_EXTENSION + atlas_res = ResourceLoader::get_singleton()->load(resources[i], "SpineAtlasResource", ResourceLoader::CACHE_MODE_IGNORE); +#else + atlas_res = ResourceLoader::load(resources[i], "SpineAtlasResource", ResourceFormatLoader::CACHE_MODE_IGNORE); +#endif + update_skeleton_data(); + } else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) { +#ifdef SPINE_GODOT_EXTENSION + skeleton_file_res = ResourceLoader::get_singleton()->load(resources[i], "SpineSkeletonFileResource", ResourceLoader::CACHE_MODE_IGNORE); +#else + skeleton_file_res = ResourceLoader::load(resources[i], "SpineSkeletonFileResource", ResourceFormatLoader::CACHE_MODE_IGNORE); +#endif + update_skeleton_data(); + } + } +} +#else +void SpineSkeletonDataResource::_on_resources_reimported(const PoolStringArray &resources) { + for (int i = 0; i < resources.size(); i++) { + if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) { + atlas_res = ResourceLoader::load(resources[i]); + update_skeleton_data(); + } else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) { + skeleton_file_res = ResourceLoader::load(resources[i]); + update_skeleton_data(); + } + } +} +#endif +#endif + +void SpineSkeletonDataResource::update_skeleton_data() { + if (skeleton_data) { + delete skeleton_data; + skeleton_data = nullptr; + } + if (animation_state_data) { + delete animation_state_data; + animation_state_data = nullptr; + } + + emit_signal(SNAME("_internal_spine_objects_invalidated")); + + if (atlas_res.is_valid() && skeleton_file_res.is_valid()) { + load_resources(atlas_res->get_spine_atlas(), skeleton_file_res->get_json(), + skeleton_file_res->get_binary()); + } + emit_signal(SNAME("skeleton_data_changed")); +#ifdef TOOLS_ENABLED + NOTIFY_PROPERTY_LIST_CHANGED(); +#endif +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSkeletonDataResource::load_resources(spine::Atlas *atlas, + const String &json, + const PackedByteArray &binary) { +#else +void SpineSkeletonDataResource::load_resources(spine::Atlas *atlas, + const String &json, + const Vector &binary) { +#endif + if ((EMPTY(json) && EMPTY(binary)) || atlas == nullptr) + return; + + spine::SkeletonData *data; + if (!EMPTY(json)) { + spine::SkeletonJson skeletonJson(atlas); + data = skeletonJson.readSkeletonData(json.utf8().ptr()); + if (!data) { + ERR_PRINT(String("Error while loading skeleton data: ") + get_path()); + ERR_PRINT(String("Error message: ") + skeletonJson.getError().buffer()); + return; + } + } else { + spine::SkeletonBinary skeletonBinary(atlas); + data = skeletonBinary.readSkeletonData(binary.ptr(), binary.size()); + if (!data) { + ERR_PRINT(String("Error while loading skeleton data: ") + get_path()); + ERR_PRINT(String("Error message: ") + skeletonBinary.getError().buffer()); + return; + } + } + skeleton_data = data; + animation_state_data = new spine::AnimationStateData(data); + update_mixes(); +} + +bool SpineSkeletonDataResource::is_skeleton_data_loaded() const { + return skeleton_data != nullptr; +} + +void SpineSkeletonDataResource::set_atlas_res( + const Ref &atlas) { + atlas_res = atlas; + update_skeleton_data(); +} + +Ref SpineSkeletonDataResource::get_atlas_res() { + return atlas_res; +} + +void SpineSkeletonDataResource::set_skeleton_file_res( + const Ref &skeleton_file) { + skeleton_file_res = skeleton_file; + update_skeleton_data(); +} + +Ref +SpineSkeletonDataResource::get_skeleton_file_res() { + return skeleton_file_res; +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSkeletonDataResource::get_animation_names(PackedStringArray &animation_names) const { +#else +void SpineSkeletonDataResource::get_animation_names(Vector &animation_names) const { +#endif + animation_names.clear(); + if (!is_skeleton_data_loaded()) + return; + auto animations = skeleton_data->getAnimations(); + for (size_t i = 0; i < animations.size(); ++i) { + auto animation = animations[i]; + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(animation->getName().buffer()); +#else + name.parse_utf8(animation->getName().buffer()); +#endif + animation_names.push_back(name); + } +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSkeletonDataResource::get_skin_names(PackedStringArray &skin_names) const { +#else +void SpineSkeletonDataResource::get_skin_names(Vector &skin_names) const { +#endif + skin_names.clear(); + if (!is_skeleton_data_loaded()) + return; + auto skins = skeleton_data->getSkins(); + for (size_t i = 0; i < skins.size(); ++i) { + auto skin = skins[i]; + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(skin->getName().buffer()); +#else + name.parse_utf8(skin->getName().buffer()); +#endif + skin_names.push_back(name); + } +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSkeletonDataResource::get_slot_names(PackedStringArray &slot_names) { +#else +void SpineSkeletonDataResource::get_slot_names(Vector &slot_names) { +#endif + slot_names.clear(); + if (!is_skeleton_data_loaded()) + return; + auto slots = skeleton_data->getSlots(); + for (size_t i = 0; i < slots.size(); ++i) { + auto slot = slots[i]; + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(slot->getName().buffer()); +#else + name.parse_utf8(slot->getName().buffer()); +#endif + slot_names.push_back(name); + } +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineSkeletonDataResource::get_bone_names(PackedStringArray &bone_names) { +#else +void SpineSkeletonDataResource::get_bone_names(Vector &bone_names) { +#endif + bone_names.clear(); + if (!is_skeleton_data_loaded()) + return; + auto bones = skeleton_data->getBones(); + for (size_t i = 0; i < bones.size(); ++i) { + auto bone = bones[i]; + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(bone->getName().buffer()); +#else + name.parse_utf8(bone->getName().buffer()); +#endif + bone_names.push_back(name); + } +} + +void SpineSkeletonDataResource::set_default_mix(float _default_mix) { + this->default_mix = _default_mix; + update_mixes(); +} + +float SpineSkeletonDataResource::get_default_mix() { return default_mix; } + +void SpineSkeletonDataResource::set_animation_mixes(Array _animation_mixes) { + for (int i = 0; i < _animation_mixes.size(); i++) { + auto objectId = Object::cast_to(_animation_mixes[0]); + if (objectId) { + ERR_PRINT("Live-editing of animation mixes is not supported."); + return; + } + } + + this->animation_mixes = _animation_mixes; + update_mixes(); +} + +Array SpineSkeletonDataResource::get_animation_mixes() { + return animation_mixes; +} + +void SpineSkeletonDataResource::update_mixes() { + if (!is_skeleton_data_loaded()) + return; + animation_state_data->clear(); + animation_state_data->setDefaultMix(default_mix); + for (int i = 0; i < animation_mixes.size(); i++) { + Ref mix = animation_mixes[i]; + spine::Animation *from = + skeleton_data->findAnimation(mix->get_from().utf8().ptr()); + spine::Animation *to = + skeleton_data->findAnimation(mix->get_to().utf8().ptr()); + if (!from) { + ERR_PRINT(vformat("Failed to set animation mix %s->%s. Animation %s does " + "not exist in skeleton.", + from, to, from)); + continue; + } + if (!to) { + ERR_PRINT(vformat("Failed to set animation mix %s->%s. Animation %s does " + "not exist in skeleton.", + from, to, to)); + continue; + } + animation_state_data->setMix(from, to, mix->get_mix()); + } +} + +Ref +SpineSkeletonDataResource::find_animation(const String &animation_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(animation_name)) + return nullptr; + auto animation = + skeleton_data->findAnimation(SPINE_STRING_TMP(animation_name)); + if (!animation) + return nullptr; + Ref animation_ref(memnew(SpineAnimation)); + animation_ref->set_spine_object(this, animation); + return animation_ref; +} + +Ref +SpineSkeletonDataResource::find_bone(const String &bone_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(bone_name)) + return nullptr; + auto bone = skeleton_data->findBone(SPINE_STRING_TMP(bone_name)); + if (!bone) + return nullptr; + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(this, bone); + return bone_ref; +} + +Ref +SpineSkeletonDataResource::find_slot(const String &slot_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(slot_name)) + return nullptr; + auto slot = skeleton_data->findSlot(SPINE_STRING_TMP(slot_name)); + if (!slot) + return nullptr; + Ref slot_ref(memnew(SpineSlotData)); + slot_ref->set_spine_object(this, slot); + return slot_ref; +} + +Ref +SpineSkeletonDataResource::find_skin(const String &skin_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(skin_name)) + return nullptr; + auto skin = skeleton_data->findSkin(SPINE_STRING_TMP(skin_name)); + if (!skin) + return nullptr; + Ref skin_ref(memnew(SpineSkin)); + skin_ref->set_spine_object(this, skin); + return skin_ref; +} + +Ref +SpineSkeletonDataResource::find_event(const String &event_data_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(event_data_name)) + return nullptr; + auto event = skeleton_data->findEvent(SPINE_STRING_TMP(event_data_name)); + if (!event) + return nullptr; + Ref event_ref(memnew(SpineEventData)); + event_ref->set_spine_object(this, event); + return event_ref; +} + +Ref SpineSkeletonDataResource::find_ik_constraint( + const String &constraint_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(constraint_name)) + return nullptr; + auto constraint = + skeleton_data->findIkConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) + return nullptr; + Ref constraint_ref(memnew(SpineIkConstraintData)); + constraint_ref->set_spine_object(this, constraint); + return constraint_ref; +} + +Ref +SpineSkeletonDataResource::find_transform_constraint( + const String &constraint_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(constraint_name)) + return nullptr; + auto constraint = + skeleton_data->findTransformConstraint(SPINE_STRING_TMP(constraint_name)); + if (!constraint) + return nullptr; + Ref constraint_ref( + memnew(SpineTransformConstraintData)); + constraint_ref->set_spine_object(this, constraint); + return constraint_ref; +} + +Ref SpineSkeletonDataResource::find_path_constraint( + const String &constraint_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(constraint_name)) + return nullptr; + auto constraint = + skeleton_data->findPathConstraint(SPINE_STRING_TMP(constraint_name)); + if (constraint == nullptr) + return nullptr; + Ref constraint_ref(memnew(SpinePathConstraintData)); + constraint_ref->set_spine_object(this, constraint); + return constraint_ref; +} + +Ref +SpineSkeletonDataResource::find_physics_constraint( + const String &constraint_name) const { + SPINE_CHECK(skeleton_data, nullptr) + if (EMPTY(constraint_name)) + return nullptr; + auto constraint = + skeleton_data->findPhysicsConstraint(SPINE_STRING_TMP(constraint_name)); + if (constraint == nullptr) + return nullptr; + Ref constraint_ref( + memnew(SpinePhysicsConstraintData)); + constraint_ref->set_spine_object(this, constraint); + return constraint_ref; +} + +String SpineSkeletonDataResource::get_skeleton_name() const { + SPINE_CHECK(skeleton_data, "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(skeleton_data->getName().buffer()); +#else + name.parse_utf8(skeleton_data->getName().buffer()); +#endif + return name; +} + +Array SpineSkeletonDataResource::get_bones() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto bones = skeleton_data->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(this, bones[i]); + result[i] = bone_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_slots() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto slots = skeleton_data->getSlots(); + result.resize((int) slots.size()); + for (int i = 0; i < slots.size(); ++i) { + Ref slot_ref(memnew(SpineSlotData)); + slot_ref->set_spine_object(this, slots[i]); + result[i] = slot_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_skins() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto skins = skeleton_data->getSkins(); + result.resize((int) skins.size()); + for (int i = 0; i < skins.size(); ++i) { + Ref skin_ref(memnew(SpineSkin)); + skin_ref->set_spine_object(this, skins[i]); + result[i] = skin_ref; + } + return result; +} + +Ref SpineSkeletonDataResource::get_default_skin() const { + SPINE_CHECK(skeleton_data, nullptr) + auto skin = skeleton_data->getDefaultSkin(); + if (skin) + return nullptr; + Ref skin_ref(memnew(SpineSkin)); + skin_ref->set_spine_object(this, skin); + return skin_ref; +} + +void SpineSkeletonDataResource::set_default_skin(Ref skin) { + SPINE_CHECK(skeleton_data, ) + skeleton_data->setDefaultSkin(skin.is_valid() && skin->get_spine_object() + ? skin->get_spine_object() + : nullptr); +} + +Array SpineSkeletonDataResource::get_events() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto events = skeleton_data->getEvents(); + result.resize((int) events.size()); + for (int i = 0; i < events.size(); ++i) { + Ref event_ref(memnew(SpineEventData)); + event_ref->set_spine_object(this, events[i]); + result[i] = event_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_animations() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto animations = skeleton_data->getAnimations(); + result.resize((int) animations.size()); + for (int i = 0; i < animations.size(); ++i) { + Ref animation_ref(memnew(SpineAnimation)); + animation_ref->set_spine_object(this, animations[i]); + result[i] = animation_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_ik_constraints() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto constraints = skeleton_data->getIkConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < constraints.size(); ++i) { + Ref constraint_ref(memnew(SpineIkConstraintData)); + constraint_ref->set_spine_object(this, constraints[i]); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_transform_constraints() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto constraints = skeleton_data->getTransformConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < constraints.size(); ++i) { + Ref constraint_ref( + memnew(SpineTransformConstraintData)); + constraint_ref->set_spine_object(this, constraints[i]); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_path_constraints() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto constraints = skeleton_data->getPathConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < constraints.size(); ++i) { + Ref constraint_ref( + memnew(SpinePathConstraintData)); + constraint_ref->set_spine_object(this, constraints[i]); + result[i] = constraint_ref; + } + return result; +} + +Array SpineSkeletonDataResource::get_physics_constraints() const { + Array result; + SPINE_CHECK(skeleton_data, result) + auto constraints = skeleton_data->getPhysicsConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < constraints.size(); ++i) { + Ref constraint_ref( + memnew(SpinePhysicsConstraintData)); + constraint_ref->set_spine_object(this, constraints[i]); + result[i] = constraint_ref; + } + return result; +} + +float SpineSkeletonDataResource::get_x() const { + SPINE_CHECK(skeleton_data, 0) + return skeleton_data->getX(); +} + +float SpineSkeletonDataResource::get_y() const { + SPINE_CHECK(skeleton_data, 0) + return skeleton_data->getY(); +} + +float SpineSkeletonDataResource::get_width() const { + SPINE_CHECK(skeleton_data, 0) + return skeleton_data->getWidth(); +} + +float SpineSkeletonDataResource::get_height() const { + SPINE_CHECK(skeleton_data, 0) + return skeleton_data->getHeight(); +} + +String SpineSkeletonDataResource::get_version() const { + SPINE_CHECK(skeleton_data, "") + return skeleton_data->getVersion().buffer(); +} + +String SpineSkeletonDataResource::get_hash() const { + SPINE_CHECK(skeleton_data, "") + return skeleton_data->getHash().buffer(); +} + +String SpineSkeletonDataResource::get_images_path() const { + SPINE_CHECK(skeleton_data, "") + return skeleton_data->getImagesPath().buffer(); +} + +String SpineSkeletonDataResource::get_audio_path() const { + SPINE_CHECK(skeleton_data, "") + return skeleton_data->getAudioPath().buffer(); +} + +float SpineSkeletonDataResource::get_fps() const { + SPINE_CHECK(skeleton_data, 0) + return skeleton_data->getFps(); +} + +float SpineSkeletonDataResource::get_reference_scale() const { + SPINE_CHECK(skeleton_data, 100); + return skeleton_data->getReferenceScale(); +} + +void SpineSkeletonDataResource::set_reference_scale(float reference_scale) { + SPINE_CHECK(skeleton_data, ) + skeleton_data->setReferenceScale(reference_scale); +} diff --git a/modules/spine_godot/SpineSkeletonDataResource.h b/modules/spine_godot/SpineSkeletonDataResource.h new file mode 100644 index 000000000000..f07eff8a1032 --- /dev/null +++ b/modules/spine_godot/SpineSkeletonDataResource.h @@ -0,0 +1,224 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineAnimation.h" +#include "SpineAtlasResource.h" +#include "SpineBoneData.h" +#include "SpineEventData.h" +#include "SpineIkConstraintData.h" +#include "SpinePathConstraintData.h" +#include "SpinePhysicsConstraintData.h" +#include "SpineSkeletonFileResource.h" +#include "SpineSkin.h" +#include "SpineSlotData.h" +#include "SpineTransformConstraintData.h" + +class SpineAnimationMix : public Resource { + GDCLASS(SpineAnimationMix, Resource) + +protected: + static void _bind_methods(); + + String from; + String to; + float mix; + +public: + SpineAnimationMix(); + + void set_from(const String &from); + + String get_from(); + + void set_to(const String &to); + + String get_to(); + + void set_mix(float mix); + + float get_mix(); +}; + +class SpineSkeletonDataResource : public Resource { + GDCLASS(SpineSkeletonDataResource, Resource) + +protected: + static void _bind_methods(); + +private: + Ref atlas_res; + Ref skeleton_file_res; + float default_mix; + Array animation_mixes; + + spine::SkeletonData *skeleton_data; + spine::AnimationStateData *animation_state_data; + +#ifdef TOOLS_ENABLED + ObjectID editor_file_system_id; +#endif + + void update_skeleton_data(); + +#ifdef SPINE_GODOT_EXTENSION + void load_resources(spine::Atlas *atlas, const String &json, + const PackedByteArray &binary); +#else + void load_resources(spine::Atlas *atlas, const String &json, + const Vector &binary); +#endif + +public: + SpineSkeletonDataResource(); + virtual ~SpineSkeletonDataResource(); + + bool is_skeleton_data_loaded() const; + + void set_atlas_res(const Ref &atlas); + Ref get_atlas_res(); + + void + set_skeleton_file_res(const Ref &skeleton_file); + Ref get_skeleton_file_res(); + + spine::SkeletonData *get_skeleton_data() const { return skeleton_data; } + + spine::AnimationStateData *get_animation_state_data() const { + return animation_state_data; + } + +#ifdef SPINE_GODOT_EXTENSION + void get_animation_names(PackedStringArray &animation_names) const; + + void get_skin_names(PackedStringArray &l) const; + + void get_slot_names(PackedStringArray &slot_names); + + void get_bone_names(PackedStringArray &bone_names); +#else + void get_animation_names(Vector &animation_names) const; + + void get_skin_names(Vector &l) const; + + void get_slot_names(Vector &slot_names); + + void get_bone_names(Vector &bone_names); +#endif + + void set_default_mix(float default_mix); + + float get_default_mix(); + + void set_animation_mixes(Array animation_mixes); + + Array get_animation_mixes(); + + // Used by SpineEditorPropertyAnimationMix(es) to update the underlying + // AnimationState + void update_mixes(); + + // Spine API + Ref find_bone(const String &bone_name) const; + + Ref find_slot(const String &slot_name) const; + + Ref find_skin(const String &skin_name) const; + + Ref find_event(const String &event_data_name) const; + + Ref find_animation(const String &animation_name) const; + + Ref + find_ik_constraint(const String &constraint_name) const; + + Ref + find_transform_constraint(const String &constraint_name) const; + + Ref + find_path_constraint(const String &constraint_name) const; + + Ref + find_physics_constraint(const String &constraint_name) const; + + String get_skeleton_name() const; + + Array get_bones() const; + + Array get_slots() const; + + Array get_skins() const; + + Ref get_default_skin() const; + + void set_default_skin(Ref skin); + + Array get_events() const; + + Array get_animations() const; + + Array get_ik_constraints() const; + + Array get_transform_constraints() const; + + Array get_path_constraints() const; + + Array get_physics_constraints() const; + + float get_x() const; + + float get_y() const; + + float get_width() const; + + float get_height() const; + + String get_version() const; + + String get_hash() const; + + String get_images_path() const; + + String get_audio_path() const; + + float get_fps() const; + + float get_reference_scale() const; + + void set_reference_scale(float reference_scale); + +#ifdef TOOLS_ENABLED +#if VERSION_MAJOR > 3 + void _on_resources_reimported(const PackedStringArray &resources); +#else + void _on_resources_reimported(const PoolStringArray &resources); +#endif +#endif +}; diff --git a/modules/spine_godot/SpineSkeletonFileResource.cpp b/modules/spine_godot/SpineSkeletonFileResource.cpp new file mode 100644 index 000000000000..87d9e43f6431 --- /dev/null +++ b/modules/spine_godot/SpineSkeletonFileResource.cpp @@ -0,0 +1,288 @@ +/****************************************************************************** + * 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 "SpineSkeletonFileResource.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#else +#if VERSION_MAJOR > 3 +#include "core/error/error_list.h" +#include "core/error/error_macros.h" +#include "core/io/file_access.h" +#else +#include "core/error_list.h" +#include "core/error_macros.h" +#include "core/os/file_access.h" +#endif +#endif +#include +#include +#include + + +struct BinaryInput { + const unsigned char *cursor; + const unsigned char *end; +}; + +static unsigned char readByte(BinaryInput *input) { + return *input->cursor++; +} + +static int readVarint(BinaryInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) value |= (readByte(input) & 0x7F) << 28; + } + } + } + + if (!optimizePositive) { + value = (((unsigned int) value >> 1) ^ -(value & 1)); + } + + return value; +} + +static char *readString(BinaryInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) { + return NULL; + } + string = spine::SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +void SpineSkeletonFileResource::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_from_file", "path"), &SpineSkeletonFileResource::load_from_file); +} + +static bool checkVersion(const char *version) { + if (!version) return false; + char *result = (char *) (strstr(version, SPINE_VERSION_STRING) - version); + return result == 0; +} + +static bool checkJson(const char *jsonData) { + spine::Json json(jsonData); + spine::Json *skeleton = spine::Json::getItem(&json, "skeleton"); + if (!skeleton) return false; + const char *version = spine::Json::getString(skeleton, "spine", 0); + if (!version) return false; + + return checkVersion(version); +} + +static bool checkBinary(const char *binaryData, int length) { + BinaryInput input; + input.cursor = (const unsigned char *) binaryData; + input.end = (const unsigned char *) binaryData + length; + // Skip hash + input.cursor += 8; + char *version = readString(&input); + bool result = checkVersion(version); + spine::SpineExtension::free(version, __FILE__, __LINE__); + return result; +} + +Error SpineSkeletonFileResource::load_from_file(const String &path) { + Error error = OK; + if (path.ends_with(".spjson") || path.ends_with(".spine-json")) { +#ifdef SPINE_GODOT_EXTENSION + json = FileAccess::get_file_as_string(path); + if (SSIZE(json) == 0) return ERR_INVALID_DATA; +#else + json = FileAccess::get_file_as_string(path, &error); + if (error != OK) return error; +#endif + if (!checkJson(json.utf8().ptr())) return ERR_INVALID_DATA; + } else { +#ifdef SPINE_GODOT_EXTENSION + binary = FileAccess::get_file_as_bytes(path); + if (binary.size() == 0) return ERR_INVALID_DATA; +#else +#if VERSION_MAJOR > 3 + binary = FileAccess::get_file_as_bytes(path, &error); +#else + binary = FileAccess::get_file_as_array(path, &error); +#endif + if (error != OK) return error; +#endif + if (!checkBinary((const char *) binary.ptr(), binary.size())) return ERR_INVALID_DATA; + } + return error; +} + +Error SpineSkeletonFileResource::save_to_file(const String &path) { + Error error; +#ifdef SPINE_GODOT_EXTENSION + Ref file = FileAccess::open(path, FileAccess::WRITE); + if (!file.is_valid()) return ERR_FILE_CANT_OPEN; +#else +#if VERSION_MAJOR > 3 + Ref file = FileAccess::open(path, FileAccess::WRITE, &error); + if (error != OK) return error; +#else + FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &error); + if (error != OK) { + if (file) file->close(); + return error; + } +#endif +#endif + if (!is_binary()) + file->store_string(json); + else + file->store_buffer(binary.ptr(), binary.size()); +#if VERSION_MAJOR > 3 + file->flush(); +#else + file->close(); +#endif + return OK; +} + +#ifndef SPINE_GODOT_EXTENSION +#if VERSION_MAJOR > 3 +Error SpineSkeletonFileResource::copy_from(const Ref &p_resource) { + auto error = Resource::copy_from(p_resource); + if (error != OK) return error; + const Ref &spineFile = static_cast &>(p_resource); + this->json = spineFile->json; + this->binary = spineFile->binary; + emit_signal(SNAME("skeleton_file_changed")); + return OK; +} +#endif +#endif + +#ifdef SPINE_GODOT_EXTENSION +Variant SpineSkeletonFileResourceFormatLoader::_load(const String &path, const String &original_path, bool use_sub_threads, int32_t cache_mode) { +#else +#if VERSION_MAJOR > 3 +RES SpineSkeletonFileResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) { +#else +#if VERSION_MINOR > 5 +RES SpineSkeletonFileResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool no_subresource_cache) { +#else +RES SpineSkeletonFileResourceFormatLoader::load(const String &path, const String &original_path, Error *error) { +#endif +#endif +#endif + Ref skeleton_file = memnew(SpineSkeletonFileResource); + skeleton_file->load_from_file(path); +#ifndef SPINE_GODOT_EXTENSION + if (error) *error = OK; +#endif + return skeleton_file; +} + +#ifdef SPINE_GODOT_EXTENSION +PackedStringArray SpineSkeletonFileResourceFormatLoader::_get_recognized_extensions() { + PackedStringArray extensions; + extensions.push_back("spjson"); + extensions.push_back("spskel"); + return extensions; +} +#else +void SpineSkeletonFileResourceFormatLoader::get_recognized_extensions(List *extensions) const { + extensions->push_back("spjson"); + extensions->push_back("spskel"); +} +#endif + +#ifdef SPINE_GODOT_EXTENSION +String SpineSkeletonFileResourceFormatLoader::_get_resource_type(const String &path) { +#else +String SpineSkeletonFileResourceFormatLoader::get_resource_type(const String &path) const { +#endif + return path.ends_with(".spjson") || path.ends_with(".spskel") || path.ends_with(".spine-json") || path.ends_with(".skel") ? "SpineSkeletonFileResource" : ""; +} + +#ifdef SPINE_GODOT_EXTENSION +bool SpineSkeletonFileResourceFormatLoader::_handles_type(const StringName &type) { +#else +bool SpineSkeletonFileResourceFormatLoader::handles_type(const String &type) const { +#endif + return type == StringName("SpineSkeletonFileResource") || ClassDB::is_parent_class(type, "SpineSkeletonFileResource"); +} + +#ifdef SPINE_GODOT_EXTENSION +Error SpineSkeletonFileResourceFormatSaver::_save(const Ref &resource, const String &path, uint32_t flags) { +#else +#if VERSION_MAJOR > 3 +Error SpineSkeletonFileResourceFormatSaver::save(const RES &resource, const String &path, uint32_t flags) { +#else +Error SpineSkeletonFileResourceFormatSaver::save(const String &path, const RES &resource, uint32_t flags) { +#endif +#endif + Ref res = resource; + Error error = res->save_to_file(path); + return error; +} + +#ifdef SPINE_GODOT_EXTENSION +PackedStringArray SpineSkeletonFileResourceFormatSaver::_get_recognized_extensions(const Ref &resource) { + PackedStringArray extensions; + if (Object::cast_to(*resource)) { + extensions.push_back("spjson"); + extensions.push_back("spskel"); + } + return extensions; +} +#else +void SpineSkeletonFileResourceFormatSaver::get_recognized_extensions(const RES &resource, List *p_extensions) const { + if (Object::cast_to(*resource)) { + p_extensions->push_back("spjson"); + p_extensions->push_back("spskel"); + } +} +#endif + +#ifdef SPINE_GODOT_EXTENSION +bool SpineSkeletonFileResourceFormatSaver::_recognize(const Ref &resource) { +#else +bool SpineSkeletonFileResourceFormatSaver::recognize(const RES &resource) const { +#endif + return Object::cast_to(*resource) != nullptr; +} diff --git a/modules/spine_godot/SpineSkeletonFileResource.h b/modules/spine_godot/SpineSkeletonFileResource.h new file mode 100644 index 000000000000..b0064518c1b0 --- /dev/null +++ b/modules/spine_godot/SpineSkeletonFileResource.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#include +#include +#include +#include +#else +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#endif +#include + +class SpineSkeletonFileResource : public Resource { + GDCLASS(SpineSkeletonFileResource, Resource); + +protected: + static void _bind_methods(); + + String json; +#ifdef SPINE_GODOT_EXTENSION + PackedByteArray binary; +#else + Vector binary; +#endif + +public: + bool is_binary() { return binary.size() > 0; } + +#ifdef SPINE_GODOT_EXTENSION + const PackedByteArray &get_binary() { return binary; } +#else + const Vector &get_binary() { return binary; } +#endif + + const String &get_json() { return json; } + + Error load_from_file(const String &path); + + Error save_to_file(const String &path); + +#ifndef SPINE_GODOT_EXTENSION +#if VERSION_MAJOR > 3 + virtual Error copy_from(const Ref &p_resource); +#endif +#endif +}; + +class SpineSkeletonFileResourceFormatLoader : public ResourceFormatLoader { + GDCLASS(SpineSkeletonFileResourceFormatLoader, ResourceFormatLoader); + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + PackedStringArray _get_recognized_extensions(); + + bool _handles_type(const StringName &type); + + String _get_resource_type(const String &path); + + Variant _load(const String &path, const String &original_path, bool use_sub_threads, int32_t cache_mode); +#else +#if VERSION_MAJOR > 3 + RES load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode); +#else +#if VERSION_MINOR > 5 + RES load(const String &path, const String &original_path, Error *error, bool no_subresource_cache = false); +#else + RES load(const String &path, const String &original_path, Error *error); +#endif +#endif + + void get_recognized_extensions(List *extensions) const override; + + bool handles_type(const String &type) const override; + + String get_resource_type(const String &path) const override; +#endif +}; + +class SpineSkeletonFileResourceFormatSaver : public ResourceFormatSaver { + GDCLASS(SpineSkeletonFileResourceFormatSaver, ResourceFormatSaver); + +public: +#ifdef SPINE_GODOT_EXTENSION + static void _bind_methods(){}; + + Error _save(const Ref &resource, const String &path, uint32_t flags) override; + + bool _recognize(const Ref &resource); + + PackedStringArray _get_recognized_extensions(const Ref &resource); +#else +#if VERSION_MAJOR > 3 + Error save(const RES &resource, const String &path, uint32_t flags) override; +#else + Error save(const String &path, const RES &resource, uint32_t flags); +#endif + + void get_recognized_extensions(const RES &resource, List *p_extensions) const override; + + bool recognize(const RES &p_resource) const override; +#endif +}; diff --git a/modules/spine_godot/SpineSkin.cpp b/modules/spine_godot/SpineSkin.cpp new file mode 100644 index 000000000000..32ef1070bb3f --- /dev/null +++ b/modules/spine_godot/SpineSkin.cpp @@ -0,0 +1,219 @@ +/****************************************************************************** + * 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 "SpineSkin.h" +#include "SpineBoneData.h" +#include "SpineConstraintData.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpineSkin::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_attachment", "slot_index", "name", "attachment"), &SpineSkin::set_attachment); + ClassDB::bind_method(D_METHOD("get_attachment", "slot_index", "name"), &SpineSkin::get_attachment); + ClassDB::bind_method(D_METHOD("remove_attachment", "slot_index", "name"), &SpineSkin::remove_attachment); + ClassDB::bind_method(D_METHOD("find_names_for_slot", "slot_index"), &SpineSkin::find_names_for_slot); + ClassDB::bind_method(D_METHOD("find_attachments_for_slot", "slot_index"), &SpineSkin::find_attachments_for_slot); + ClassDB::bind_method(D_METHOD("get_name"), &SpineSkin::get_name); + ClassDB::bind_method(D_METHOD("add_skin", "other"), &SpineSkin::add_skin); + ClassDB::bind_method(D_METHOD("copy_skin", "other"), &SpineSkin::copy_skin); + ClassDB::bind_method(D_METHOD("get_attachments"), &SpineSkin::get_attachments); + ClassDB::bind_method(D_METHOD("get_bones"), &SpineSkin::get_bones); + ClassDB::bind_method(D_METHOD("get_constraints"), &SpineSkin::get_constraints); +#if VERSION_MAJOR >= 4 + ClassDB::bind_method(D_METHOD("init", "name", "sprite"), &SpineSkin::init); +#endif +} + +SpineSkin::SpineSkin() : owns_skin(false) { +} + +SpineSkin::~SpineSkin() { + if (owns_skin) delete get_spine_object(); +} + +Ref SpineSkin::init(const String &name, SpineSprite *sprite) { + if (get_spine_object()) { + ERR_PRINT("Can not initialize an already initialized skin."); + return this; + } + if (!sprite) { + ERR_PRINT("Must provide a valid SpineSprite."); + return this; + } + if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) { + ERR_PRINT("SpineSkeletonDataResource on SpineSprite must be valid and loaded."); + return this; + } + owns_skin = true; + set_spine_object(*sprite->get_skeleton_data_res(), new spine::Skin(SPINE_STRING(name))); + return this; +} + +void SpineSkin::set_attachment(int slot_index, const String &name, Ref attachment) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAttachment(slot_index, SPINE_STRING(name), attachment.is_valid() && attachment->get_spine_owner() ? attachment->get_spine_object() : nullptr); +} + +Ref SpineSkin::get_attachment(int slot_index, const String &name) { + SPINE_CHECK(get_spine_object(), nullptr) + auto attachment = get_spine_object()->getAttachment(slot_index, SPINE_STRING(name)); + if (attachment) return nullptr; + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(get_spine_owner(), attachment); + return attachment_ref; +} + +void SpineSkin::remove_attachment(int slot_index, const String &name) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->removeAttachment(slot_index, SPINE_STRING(name)); +} + +Array SpineSkin::find_names_for_slot(int slot_index) { + Array result; + SPINE_CHECK(get_spine_object(), result) + spine::Vector names; + get_spine_object()->findNamesForSlot(slot_index, names); + result.resize((int) names.size()); + for (int i = 0; i < names.size(); ++i) { + result[i] = names[i].buffer(); + } + return result; +} + +Array SpineSkin::find_attachments_for_slot(int slot_index) { + Array result; + SPINE_CHECK(get_spine_object(), result) + spine::Vector attachments; + get_spine_object()->findAttachmentsForSlot(slot_index, attachments); + result.resize((int) attachments.size()); + for (int i = 0; i < attachments.size(); ++i) { + if (!attachments[i]) { + result[i] = Ref(nullptr); + } else { + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(get_spine_owner(), attachments[i]); + result[i] = attachment_ref; + } + } + return result; +} + +String SpineSkin::get_name() { + SPINE_CHECK(get_spine_object(), "") + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +void SpineSkin::add_skin(Ref other) { + SPINE_CHECK(get_spine_object(), ) + if (!other.is_valid() || !other->get_spine_object()) { + ERR_PRINT("other is not a valid SpineSkin."); + return; + } + get_spine_object()->addSkin(other->get_spine_object()); +} + +void SpineSkin::copy_skin(Ref other) { + SPINE_CHECK(get_spine_object(), ) + if (!other.is_valid() || !other->get_spine_object()) { + ERR_PRINT("other is not a valid SpineSkin."); + return; + } + get_spine_object()->copySkin(other->get_spine_object()); +} + +Array SpineSkin::get_attachments() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto entries = get_spine_object()->getAttachments(); + while (entries.hasNext()) { + spine::Skin::AttachmentMap::Entry &entry = entries.next(); + Ref entry_ref = memnew(SpineSkinEntry); + Ref attachment_ref = nullptr; + if (entry._attachment) { + attachment_ref = Ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(get_spine_owner(), entry._attachment); + } + entry_ref->init(entry._slotIndex, entry._name.buffer(), attachment_ref); + result.push_back(entry_ref); + } + return result; +} + +Array SpineSkin::get_bones() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto bones = get_spine_object()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(get_spine_owner(), bones[i]); + result[i] = bone_ref; + } + return result; +} + +Array SpineSkin::get_constraints() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto constraints = get_spine_object()->getConstraints(); + result.resize((int) constraints.size()); + for (int i = 0; i < constraints.size(); ++i) { + Ref constraint_ref(memnew(SpineConstraintData)); + constraint_ref->set_spine_object(get_spine_owner(), constraints[i]); + result[i] = constraint_ref; + } + return result; +} + +void SpineSkinEntry::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_slot_index"), &SpineSkinEntry::get_slot_index); + ClassDB::bind_method(D_METHOD("get_name"), &SpineSkinEntry::get_name); + ClassDB::bind_method(D_METHOD("get_attachment"), &SpineSkinEntry::get_attachment); +} + +SpineSkinEntry::SpineSkinEntry() : slot_index(0) { +} + +int SpineSkinEntry::get_slot_index() { + return slot_index; +} + +const String &SpineSkinEntry::get_name() { + return name; +} + +Ref SpineSkinEntry::get_attachment() { + return attachment; +} diff --git a/modules/spine_godot/SpineSkin.h b/modules/spine_godot/SpineSkin.h new file mode 100644 index 000000000000..d155b86de5ca --- /dev/null +++ b/modules/spine_godot/SpineSkin.h @@ -0,0 +1,103 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineAttachment.h" + +class SpineSkeletonDataResource; +class SpineSprite; + +class SpineSkin : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineSkin, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +private: + bool owns_skin; + +public: + SpineSkin(); + ~SpineSkin() override; + + Ref init(const String &name, SpineSprite *sprite); + + void set_attachment(int slot_index, const String &name, Ref attachment); + + Ref get_attachment(int slot_index, const String &name); + + void remove_attachment(int slot_index, const String &name); + + Array find_names_for_slot(int slot_index); + + Array find_attachments_for_slot(int slot_index); + + String get_name(); + + void add_skin(Ref other); + + void copy_skin(Ref other); + + Array get_attachments(); + + Array get_bones(); + + Array get_constraints(); +}; + +class SpineSkinEntry : public REFCOUNTED { + GDCLASS(SpineSkinEntry, REFCOUNTED); + + friend class SpineSkin; + +protected: + static void _bind_methods(); + + void init(int _slot_index, const String &_name, Ref _attachment) { + this->slot_index = _slot_index; + this->name = _name; + this->attachment = _attachment; + } + +private: + int slot_index; + String name; + Ref attachment; + +public: + SpineSkinEntry(); + + int get_slot_index(); + + const String &get_name(); + + Ref get_attachment(); +}; diff --git a/modules/spine_godot/SpineSlot.cpp b/modules/spine_godot/SpineSlot.cpp new file mode 100644 index 000000000000..0adb0981fc8e --- /dev/null +++ b/modules/spine_godot/SpineSlot.cpp @@ -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 "SpineSlot.h" +#include "SpineBone.h" +#include "SpineCommon.h" +#include "SpineSprite.h" +#include "SpineSkeletonDataResource.h" + +void SpineSlot::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_to_setup_pose"), &SpineSlot::set_to_setup_pose); + ClassDB::bind_method(D_METHOD("get_data"), &SpineSlot::get_data); + ClassDB::bind_method(D_METHOD("get_bone"), &SpineSlot::get_bone); + ClassDB::bind_method(D_METHOD("get_color"), &SpineSlot::get_color); + ClassDB::bind_method(D_METHOD("set_color", "color"), &SpineSlot::set_color); + ClassDB::bind_method(D_METHOD("get_dark_color"), &SpineSlot::get_dark_color); + ClassDB::bind_method(D_METHOD("set_dark_color", "v"), &SpineSlot::set_dark_color); + ClassDB::bind_method(D_METHOD("has_dark_color"), &SpineSlot::has_dark_color); + ClassDB::bind_method(D_METHOD("get_attachment"), &SpineSlot::get_attachment); + ClassDB::bind_method(D_METHOD("set_attachment", "v"), &SpineSlot::set_attachment); + ClassDB::bind_method(D_METHOD("get_attachment_state"), &SpineSlot::get_attachment_state); + ClassDB::bind_method(D_METHOD("set_attachment_state", "v"), &SpineSlot::set_attachment_state); + ClassDB::bind_method(D_METHOD("get_deform"), &SpineSlot::get_deform); + ClassDB::bind_method(D_METHOD("set_deform", "v"), &SpineSlot::set_deform); + ClassDB::bind_method(D_METHOD("get_sequence_index"), &SpineSlot::get_sequence_index); + ClassDB::bind_method(D_METHOD("set_sequence_index", "v"), &SpineSlot::set_sequence_index); +} + +void SpineSlot::set_to_setup_pose() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setToSetupPose(); +} + +Ref SpineSlot::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + if (_data.is_valid()) { + return _data; + } else { + auto &slot_data = get_spine_object()->getData(); + Ref slot_data_ref(memnew(SpineSlotData)); + slot_data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &slot_data); + _data = slot_data_ref; + return slot_data_ref; + } +} + +Ref SpineSlot::get_bone() { + SPINE_CHECK(get_spine_object(), nullptr) + if (_bone.is_valid()) { + return _bone; + } else { + auto &bone = get_spine_object()->getBone(); + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(get_spine_owner(), &bone); + _bone = bone_ref; + return bone_ref; + } +} + +Color SpineSlot::get_color() { + SPINE_CHECK(get_spine_object(), Color(0, 0, 0, 0)) + auto &color = get_spine_object()->getColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineSlot::set_color(Color v) { + SPINE_CHECK(get_spine_object(), ) + auto &color = get_spine_object()->getColor(); + color.set(v.r, v.g, v.b, v.a); +} + +Color SpineSlot::get_dark_color() { + SPINE_CHECK(get_spine_object(), Color(0, 0, 0, 0)) + auto &color = get_spine_object()->getDarkColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineSlot::set_dark_color(Color v) { + SPINE_CHECK(get_spine_object(), ) + auto &color = get_spine_object()->getDarkColor(); + color.set(v.r, v.g, v.b, v.a); +} + +bool SpineSlot::has_dark_color() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->hasDarkColor(); +} + +Ref SpineSlot::get_attachment() { + SPINE_CHECK(get_spine_object(), nullptr) + auto attachment = get_spine_object()->getAttachment(); + if (!attachment) return nullptr; + Ref attachment_ref(memnew(SpineAttachment)); + attachment_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), attachment); + return attachment_ref; +} + +void SpineSlot::set_attachment(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAttachment(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +int SpineSlot::get_attachment_state() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAttachmentState(); +} + +void SpineSlot::set_attachment_state(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAttachmentState(v); +} + +Array SpineSlot::get_deform() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &deform = get_spine_object()->getDeform(); + result.resize((int) deform.size()); + for (int i = 0; i < (int) deform.size(); ++i) { + result[i] = deform[i]; + } + return result; +} + +void SpineSlot::set_deform(Array v) { + SPINE_CHECK(get_spine_object(), ) + auto &deform = get_spine_object()->getDeform(); + deform.setSize(v.size(), 0); + for (int i = 0; i < v.size(); ++i) { + deform[i] = v[i]; + } +} + +int SpineSlot::get_sequence_index() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAttachmentState(); +} + +void SpineSlot::set_sequence_index(int v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAttachmentState(v); +} diff --git a/modules/spine_godot/SpineSlot.h b/modules/spine_godot/SpineSlot.h new file mode 100644 index 000000000000..f5422e91fa9b --- /dev/null +++ b/modules/spine_godot/SpineSlot.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineSlotData.h" +#include "SpineAttachment.h" +#include "SpineBone.h" + +class SpineSkeleton; +class SpineSprite; + +class SpineSlot : public SpineSpriteOwnedObject { + GDCLASS(SpineSlot, SpineObjectWrapper) + +private: + Ref _bone; + Ref _data; + +protected: + static void _bind_methods(); + +public: + void set_to_setup_pose(); + + Ref get_data(); + + Ref get_bone(); + + Color get_color(); + + void set_color(Color v); + + Color get_dark_color(); + + void set_dark_color(Color v); + + bool has_dark_color(); + + Ref get_attachment(); + + void set_attachment(Ref v); + + int get_attachment_state(); + + void set_attachment_state(int v); + + Array get_deform(); + + void set_deform(Array v); + + int get_sequence_index(); + + void set_sequence_index(int v); +}; diff --git a/modules/spine_godot/SpineSlotData.cpp b/modules/spine_godot/SpineSlotData.cpp new file mode 100644 index 000000000000..96e55bbaceac --- /dev/null +++ b/modules/spine_godot/SpineSlotData.cpp @@ -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 "SpineSlotData.h" +#include "SpineCommon.h" + +void SpineSlotData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_index"), &SpineSlotData::get_index); + ClassDB::bind_method(D_METHOD("get_name"), &SpineSlotData::get_name); + ClassDB::bind_method(D_METHOD("get_bone_data"), &SpineSlotData::get_bone_data); + ClassDB::bind_method(D_METHOD("get_color"), &SpineSlotData::get_color); + ClassDB::bind_method(D_METHOD("set_color", "v"), &SpineSlotData::set_color); + ClassDB::bind_method(D_METHOD("get_dark_color"), &SpineSlotData::get_dark_color); + ClassDB::bind_method(D_METHOD("set_dark_color", "v"), &SpineSlotData::set_dark_color); + ClassDB::bind_method(D_METHOD("has_dark_color"), &SpineSlotData::has_dark_color); + ClassDB::bind_method(D_METHOD("set_has_dark_color", "v"), &SpineSlotData::set_has_dark_color); + ClassDB::bind_method(D_METHOD("get_attachment_name"), &SpineSlotData::get_attachment_name); + ClassDB::bind_method(D_METHOD("set_attachment_name", "v"), &SpineSlotData::set_attachment_name); + ClassDB::bind_method(D_METHOD("get_blend_mode"), &SpineSlotData::get_blend_mode); + ClassDB::bind_method(D_METHOD("set_blend_mode", "v"), &SpineSlotData::set_blend_mode); +} + +int SpineSlotData::get_index() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getIndex(); +} + +String SpineSlotData::get_name() { + SPINE_CHECK(get_spine_object(), String("")) + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(get_spine_object()->getName().buffer()); +#else + name.parse_utf8(get_spine_object()->getName().buffer()); +#endif + return name; +} + +Ref SpineSlotData::get_bone_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &bone_data = get_spine_object()->getBoneData(); + Ref bone_data_ref(memnew(SpineBoneData)); + bone_data_ref->set_spine_object(get_spine_owner(), &bone_data); + return bone_data_ref; +} + +Color SpineSlotData::get_color() { + SPINE_CHECK(get_spine_object(), Color(0, 0, 0, 0)) + auto &color = get_spine_object()->getColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineSlotData::set_color(Color v) { + SPINE_CHECK(get_spine_object(), ) + auto &color = get_spine_object()->getColor(); + color.set(v.r, v.g, v.b, v.a); +} + +Color SpineSlotData::get_dark_color() { + SPINE_CHECK(get_spine_object(), Color(0, 0, 0, 0)) + auto &color = get_spine_object()->getDarkColor(); + return Color(color.r, color.g, color.b, color.a); +} + +void SpineSlotData::set_dark_color(Color v) { + SPINE_CHECK(get_spine_object(), ) + auto &color = get_spine_object()->getDarkColor(); + color.set(v.r, v.g, v.b, v.a); +} + +bool SpineSlotData::has_dark_color() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->hasDarkColor(); +} + +void SpineSlotData::set_has_dark_color(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setHasDarkColor(v); +} + +String SpineSlotData::get_attachment_name() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getAttachmentName().buffer(); +} +void SpineSlotData::set_attachment_name(const String &v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAttachmentName(SPINE_STRING_TMP(v)); +} + +SpineConstant::BlendMode SpineSlotData::get_blend_mode() { + SPINE_CHECK(get_spine_object(), SpineConstant::BlendMode_Normal) + return (SpineConstant::BlendMode) get_spine_object()->getBlendMode(); +} +void SpineSlotData::set_blend_mode(SpineConstant::BlendMode v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setBlendMode((spine::BlendMode) v); +} diff --git a/modules/spine_godot/SpineSlotData.h b/modules/spine_godot/SpineSlotData.h new file mode 100644 index 000000000000..3cb1c8e8558c --- /dev/null +++ b/modules/spine_godot/SpineSlotData.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineBoneData.h" +#include + +class SpineSkeletonDataResource; + +class SpineSlotData : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineSlotData, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + int get_index(); + + String get_name(); + + Ref get_bone_data(); + + Color get_color(); + + void set_color(Color c); + + Color get_dark_color(); + + void set_dark_color(Color c); + + bool has_dark_color(); + + void set_has_dark_color(bool v); + + String get_attachment_name(); + + void set_attachment_name(const String &v); + + SpineConstant::BlendMode get_blend_mode(); + + void set_blend_mode(SpineConstant::BlendMode v); +}; diff --git a/modules/spine_godot/SpineSlotNode.cpp b/modules/spine_godot/SpineSlotNode.cpp new file mode 100644 index 000000000000..3aa54ee0ed6b --- /dev/null +++ b/modules/spine_godot/SpineSlotNode.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** + * 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 "SpineSlotNode.h" + +#ifdef TOOLS_ENABLED +#ifdef SPINE_GODOT_EXTENSION +// FIXME +#else +#include "editor/editor_node.h" +#endif +#endif +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "scene/main/viewport.h" +#endif + +void SpineSlotNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("_on_world_transforms_changed", "spine_sprite"), &SpineSlotNode::on_world_transforms_changed); + + ClassDB::bind_method(D_METHOD("set_normal_material", "material"), &SpineSlotNode::set_normal_material); + ClassDB::bind_method(D_METHOD("get_normal_material"), &SpineSlotNode::get_normal_material); + ClassDB::bind_method(D_METHOD("set_additive_material", "material"), &SpineSlotNode::set_additive_material); + ClassDB::bind_method(D_METHOD("get_additive_material"), &SpineSlotNode::get_additive_material); + ClassDB::bind_method(D_METHOD("set_multiply_material", "material"), &SpineSlotNode::set_multiply_material); + ClassDB::bind_method(D_METHOD("get_multiply_material"), &SpineSlotNode::get_multiply_material); + ClassDB::bind_method(D_METHOD("set_screen_material", "material"), &SpineSlotNode::set_screen_material); + ClassDB::bind_method(D_METHOD("get_screen_material"), &SpineSlotNode::get_screen_material); + + ADD_GROUP("Materials", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_normal_material", "get_normal_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "additive_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_additive_material", "get_additive_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiply_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_multiply_material", "get_multiply_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "screen_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_screen_material", "get_screen_material"); +} + +SpineSlotNode::SpineSlotNode() : slot_index(-1) { +} + +void SpineSlotNode::_notification(int what) { + switch (what) { + case NOTIFICATION_PARENTED: { + SpineSprite *sprite = cast_to(get_parent()); + if (sprite) { +#if VERSION_MAJOR > 3 + sprite->connect(SNAME("world_transforms_changed"), callable_mp(this, &SpineSlotNode::on_world_transforms_changed)); +#else + sprite->connect(SNAME("world_transforms_changed"), this, SNAME("_on_world_transforms_changed")); +#endif + update_transform(sprite); +#if VERSION_MAJOR == 3 + _change_notify("transform/translation"); + _change_notify("transform/rotation"); + _change_notify("transform/scale"); + _change_notify("translation"); + _change_notify("rotation"); + _change_notify("rotation_deg"); + _change_notify("scale"); +#endif + } else { + WARN_PRINT("SpineSlotNode parent is not a SpineSprite."); + } + NOTIFY_PROPERTY_LIST_CHANGED(); + break; + } + case NOTIFICATION_UNPARENTED: { + SpineSprite *sprite = cast_to(get_parent()); + if (sprite) { +#if VERSION_MAJOR > 3 + sprite->disconnect(SNAME("world_transforms_changed"), callable_mp(this, &SpineSlotNode::on_world_transforms_changed)); +#else + sprite->disconnect(SNAME("world_transforms_changed"), this, SNAME("_on_world_transforms_changed")); +#endif + } + break; + } + default: + break; + } +} + +void SpineSlotNode::_get_property_list(List *list) const { +#ifdef SPINE_GODOT_EXTENSION + PackedStringArray slot_names; +#else + Vector slot_names; +#endif + SpineSprite *sprite = cast_to(get_parent()); + if (sprite && sprite->get_skeleton_data_res().is_valid()) sprite->get_skeleton_data_res()->get_slot_names(slot_names); + else + slot_names.push_back(slot_name); + auto element = list->front(); + while (element) { + auto property_info = element->get(); + if (property_info.name == StringName("SpineSlotNode")) break; + element = element->next(); + } + PropertyInfo slot_name_property; + slot_name_property.name = "slot_name"; + slot_name_property.type = Variant::STRING; + slot_name_property.hint_string = String(",").join(slot_names); + slot_name_property.hint = PROPERTY_HINT_ENUM; + slot_name_property.usage = PROPERTY_USAGE_DEFAULT; + list->insert_after(element, slot_name_property); +} + +bool SpineSlotNode::_get(const StringName &property, Variant &value) const { + if (property == StringName("slot_name")) { + value = slot_name; + return true; + } + return false; +} + +bool SpineSlotNode::_set(const StringName &property, const Variant &value) { + if (property == StringName("slot_name")) { + slot_name = value; + SpineSprite *sprite = cast_to(get_parent()); + update_transform(sprite); +#if VERSION_MAJOR == 3 + _change_notify("transform/translation"); + _change_notify("transform/rotation"); + _change_notify("transform/scale"); + _change_notify("translation"); + _change_notify("rotation"); + _change_notify("rotation_deg"); + _change_notify("scale"); +#endif + return true; + } + return false; +} + +void SpineSlotNode::on_world_transforms_changed(const Variant &_sprite) { + SpineSprite *sprite = cast_to(_sprite.operator Object *()); + update_transform(sprite); +} + +void SpineSlotNode::update_transform(SpineSprite *sprite) { + if (!is_visible_in_tree()) return; + if (!sprite) return; + if (!sprite->get_skeleton().is_valid() || !sprite->get_skeleton()->get_spine_object()) return; + auto slot = sprite->get_skeleton()->find_slot(slot_name); + if (!slot.is_valid()) { + slot_index = -1; + return; + } else { + slot_index = slot->get_data()->get_index(); + } + auto bone = slot->get_bone(); + if (!bone.is_valid()) return; + this->set_global_transform(bone->get_global_transform()); +} + +void SpineSlotNode::set_slot_name(const String &_slot_name) { + slot_name = _slot_name; +} + +String SpineSlotNode::get_slot_name() { + return slot_name; +} + +Ref SpineSlotNode::get_normal_material() { + return normal_material; +} + +void SpineSlotNode::set_normal_material(Ref material) { + normal_material = material; +} + +Ref SpineSlotNode::get_additive_material() { + return additive_material; +} + +void SpineSlotNode::set_additive_material(Ref material) { + additive_material = material; +} + +Ref SpineSlotNode::get_multiply_material() { + return multiply_material; +} + +void SpineSlotNode::set_multiply_material(Ref material) { + multiply_material = material; +} + +Ref SpineSlotNode::get_screen_material() { + return screen_material; +} + +void SpineSlotNode::set_screen_material(Ref material) { + screen_material = material; +} diff --git a/modules/spine_godot/SpineSlotNode.h b/modules/spine_godot/SpineSlotNode.h new file mode 100644 index 000000000000..504b2f21b007 --- /dev/null +++ b/modules/spine_godot/SpineSlotNode.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineSprite.h" +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "scene/2d/node_2d.h" +#endif + +class SpineSlotNode : public Node2D { + GDCLASS(SpineSlotNode, Node2D) + +protected: + String slot_name; + int slot_index; + Ref normal_material; + Ref additive_material; + Ref multiply_material; + Ref screen_material; + + static void _bind_methods(); + void _notification(int what); + void _get_property_list(List *list) const; + bool _get(const StringName &property, Variant &value) const; + bool _set(const StringName &property, const Variant &value); + void on_world_transforms_changed(const Variant &_sprite); + void update_transform(SpineSprite *sprite); + +public: + SpineSlotNode(); + + void set_slot_name(const String &_slot_name); + + String get_slot_name(); + + int get_slot_index() { return slot_index; } + + Ref get_normal_material(); + + void set_normal_material(Ref material); + + Ref get_additive_material(); + + void set_additive_material(Ref material); + + Ref get_multiply_material(); + + void set_multiply_material(Ref material); + + Ref get_screen_material(); + + void set_screen_material(Ref material); +}; diff --git a/modules/spine_godot/SpineSprite.cpp b/modules/spine_godot/SpineSprite.cpp new file mode 100644 index 000000000000..eb96bb111760 --- /dev/null +++ b/modules/spine_godot/SpineSprite.cpp @@ -0,0 +1,1440 @@ +/****************************************************************************** + * 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 "SpineSprite.h" +#include "SpineEvent.h" +#include "SpineTrackEntry.h" +#include "SpineSkeleton.h" +#include "SpineRendererObject.h" +#include "SpineSlotNode.h" + +#ifdef SPINE_GODOT_EXTENSION +#include +#include +#include +#include +#include +#include +#ifdef TOOLS_ENABLED +#include +#endif +#include +#include +#include +#if TOOLS_ENABLED +#include +#include +#endif +#else +#include "core/os/memory.h" + +#if VERSION_MAJOR > 3 +#include "core/config/engine.h" +#include "core/math/geometry_2d.h" +#include "core/math/transform_2d.h" +#include "core/variant/array.h" +#include "scene/resources/mesh.h" +#include "servers/rendering_server.h" +#include "scene/resources/canvas_item_material.h" +#if VERSION_MINOR > 0 && defined(TOOLS_ENABLED) +#include "editor/editor_interface.h" +#endif +#else +#include "core/engine.h" +#endif + +#include "scene/gui/control.h" +#include "scene/main/viewport.h" + +#if TOOLS_ENABLED + +#if VERSION_MAJOR > 3 +#if VERSION_MINOR > 2 +#include "editor/plugins/editor_plugin.h" +#else +#include "editor/editor_plugin.h" +#endif +#else +#include "editor/editor_plugin.h" +#endif + +#endif +#endif + +// Needed due to shared lib initializers in GDExtension. +// See: https://x.com/badlogicgames/status/1843661872404591068 +struct SpineSpriteStatics { +private: + static SpineSpriteStatics *_instance; + +public: + Ref default_materials[4] = {}; + int sprite_count; + spine::Vector quad_indices; + spine::Vector scratch_vertices; +#ifdef SPINE_GODOT_EXTENSION + PackedVector2Array scratch_points; +#else + Vector scratch_points; +#endif + + SpineSpriteStatics() : sprite_count(0) { + quad_indices.setSize(6, 0); + quad_indices[0] = 0; + quad_indices[1] = 1; + quad_indices[2] = 2; + quad_indices[3] = 2; + quad_indices[4] = 3; + quad_indices[5] = 0; + scratch_vertices.ensureCapacity(1200); + + Ref material_normal(memnew(CanvasItemMaterial)); + material_normal->set_blend_mode(CanvasItemMaterial::BLEND_MODE_MIX); + default_materials[spine::BlendMode_Normal] = material_normal; + + Ref material_additive(memnew(CanvasItemMaterial)); + material_additive->set_blend_mode(CanvasItemMaterial::BLEND_MODE_ADD); + default_materials[spine::BlendMode_Additive] = material_additive; + + Ref material_multiply(memnew(CanvasItemMaterial)); + material_multiply->set_blend_mode(CanvasItemMaterial::BLEND_MODE_MUL); + default_materials[spine::BlendMode_Multiply] = material_multiply; + + Ref material_screen(memnew(CanvasItemMaterial)); + material_screen->set_blend_mode(CanvasItemMaterial::BLEND_MODE_SUB); + default_materials[spine::BlendMode_Screen] = material_screen; + } + + static SpineSpriteStatics &instance() { + if (!_instance) { + _instance = new SpineSpriteStatics(); + } + return *_instance; + } + + static void clear() { + if (_instance) { + delete _instance; + } + _instance = nullptr; + } +}; + +SpineSpriteStatics *SpineSpriteStatics::_instance = nullptr; + +static void +clear_triangles(SpineMesh2D *mesh_instance) { +#if VERSION_MAJOR > 3 + RenderingServer::get_singleton()->canvas_item_clear(mesh_instance->get_canvas_item()); +#else + VisualServer::get_singleton()->canvas_item_clear(mesh_instance->get_canvas_item()); +#endif +} + +#ifdef SPINE_GODOT_EXTENSION +static void add_triangles(SpineMesh2D *mesh_instance, + const PackedVector2Array &vertices, + const PackedVector2Array &uvs, + const PackedColorArray &colors, + const PackedInt32Array &indices, + SpineRendererObject *renderer_object) { +#else +static void add_triangles(SpineMesh2D *mesh_instance, + const Vector &vertices, + const Vector &uvs, + const Vector &colors, + const Vector &indices, + SpineRendererObject *renderer_object) { +#endif +#if VERSION_MAJOR > 3 + mesh_instance->update_mesh(vertices, uvs, colors, indices, renderer_object); +#else +#define USE_MESH 0 +#if USE_MESH + mesh_instance->update_mesh(vertices, uvs, colors, indices, renderer_object); +#else + auto texture = renderer_object->texture; + auto normal_map = renderer_object->normal_map; + VisualServer::get_singleton()->canvas_item_add_triangle_array(mesh_instance->get_canvas_item(), + indices, + vertices, + colors, + uvs, + Vector(), + Vector(), + texture.is_null() ? RID() : texture->get_rid(), + -1, + normal_map.is_null() ? RID() : normal_map->get_rid()); +#endif +#endif +} + +void SpineMesh2D::_notification(int what) { + switch (what) { + case NOTIFICATION_READY: { + set_process_internal(true); + break; + } + case NOTIFICATION_INTERNAL_PROCESS: +#if VERSION_MAJOR > 3 + queue_redraw(); +#else + update(); +#endif + break; + case NOTIFICATION_DRAW: + clear_triangles(this); + if (renderer_object) + add_triangles(this, vertices, uvs, colors, indices, renderer_object); + break; + default: + break; + } +} + +void SpineMesh2D::_bind_methods() { +} + +#ifdef SPINE_GODOT_EXTENSION +void SpineMesh2D::update_mesh(const PackedVector2Array &vertices, + const PackedVector2Array &uvs, + const PackedColorArray &colors, + const PackedInt32Array &indices, + SpineRendererObject *renderer_object) { + if (!mesh.is_valid() || vertices.size() != num_vertices || indices.size() != num_indices || indices_changed) { + if (mesh.is_valid()) { + RS::get_singleton()->free_rid(mesh); + } + mesh = RS::get_singleton()->mesh_create(); + Array arrays; + arrays.resize(Mesh::ARRAY_MAX); + arrays[Mesh::ARRAY_VERTEX] = vertices; + arrays[Mesh::ARRAY_TEX_UV] = uvs; + arrays[Mesh::ARRAY_COLOR] = colors; + arrays[Mesh::ARRAY_INDEX] = indices; + RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PrimitiveType::PRIMITIVE_TRIANGLES, arrays, Array(), Dictionary(), RS::ArrayFormat::ARRAY_FLAG_USE_DYNAMIC_UPDATE); + Dictionary surface = RS::get_singleton()->mesh_get_surface(mesh, 0); + RS::ArrayFormat surface_format = (RS::ArrayFormat) static_cast(surface["format"]); + surface_offsets[RS::ARRAY_VERTEX] = RS::get_singleton()->mesh_surface_get_format_offset(surface_format, vertices.size(), RS::ARRAY_VERTEX); + surface_offsets[RS::ARRAY_COLOR] = RS::get_singleton()->mesh_surface_get_format_offset(surface_format, vertices.size(), RS::ARRAY_COLOR); + surface_offsets[RS::ARRAY_TEX_UV] = RS::get_singleton()->mesh_surface_get_format_offset(surface_format, vertices.size(), RS::ARRAY_TEX_UV); + vertex_stride = RS::get_singleton()->mesh_surface_get_format_vertex_stride(surface_format, vertices.size()); + attribute_stride = RS::get_singleton()->mesh_surface_get_format_attribute_stride(surface_format, vertices.size()); + vertex_buffer = surface["vertex_data"]; + attribute_buffer = surface["attribute_data"]; + num_vertices = vertices.size(); + num_indices = indices.size(); + indices_changed = false; + } else { + AABB aabb_new; + uint8_t color[4] = { + uint8_t(CLAMP(colors[0].r * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].g * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].b * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].a * 255.0, 0.0, 255.0))}; + + uint8_t *vertex_write_buffer = vertex_buffer.ptrw(); + uint8_t *attribute_write_buffer = attribute_buffer.ptrw(); + for (int i = 0; i < vertices.size(); i++) { + Vector2 vertex(vertices[i]); + if (i == 0) { + aabb_new.position = Vector3(vertex.x, vertex.y, 0); + aabb_new.size = Vector3(); + } else { + aabb_new.expand_to(Vector3(vertex.x, vertex.y, 0)); + } + + float uv[2] = {(float) uvs[i].x, (float) uvs[i].y}; + memcpy(&vertex_write_buffer[i * vertex_stride + surface_offsets[RS::ARRAY_VERTEX]], &vertex, sizeof(float) * 2); + memcpy(&attribute_write_buffer[i * attribute_stride + surface_offsets[RS::ARRAY_COLOR]], color, 4); + memcpy(&attribute_write_buffer[i * attribute_stride + surface_offsets[RS::ARRAY_TEX_UV]], uv, 8); + } + RS::get_singleton()->mesh_surface_update_vertex_region(mesh, 0, 0, vertex_buffer); + RS::get_singleton()->mesh_surface_update_attribute_region(mesh, 0, 0, attribute_buffer); + RS::get_singleton()->mesh_set_custom_aabb(mesh, aabb_new); + } + + RenderingServer::get_singleton()->canvas_item_add_mesh(this->get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1, 1), renderer_object->canvas_texture->get_rid()); +} +#else +void SpineMesh2D::update_mesh(const Vector &vertices, + const Vector &uvs, + const Vector &colors, + const Vector &indices, + SpineRendererObject *renderer_object) { +#if VERSION_MAJOR > 3 + if (!mesh.is_valid() || vertices.size() != num_vertices || indices.size() != num_indices || indices_changed) { + if (mesh.is_valid()) { +#ifdef SPINE_GODOT_EXTENSION + RS::get_singleton()->free_rid(mesh); +#else + RS::get_singleton()->free(mesh); +#endif + } + mesh = RS::get_singleton()->mesh_create(); + Array arrays; + arrays.resize(Mesh::ARRAY_MAX); + arrays[Mesh::ARRAY_VERTEX] = vertices; + arrays[Mesh::ARRAY_TEX_UV] = uvs; + arrays[Mesh::ARRAY_COLOR] = colors; + arrays[Mesh::ARRAY_INDEX] = indices; + RS::SurfaceData surface; + uint32_t skin_stride; + RS::get_singleton()->mesh_create_surface_data_from_arrays(&surface, (RS::PrimitiveType) Mesh::PRIMITIVE_TRIANGLES, arrays, TypedArray(), Dictionary(), Mesh::ArrayFormat::ARRAY_FLAG_USE_DYNAMIC_UPDATE); + RS::get_singleton()->mesh_add_surface(mesh, surface); +#if VERSION_MINOR > 1 + RS::get_singleton()->mesh_surface_make_offsets_from_format(surface.format, surface.vertex_count, surface.index_count, surface_offsets, vertex_stride, normal_tangent_stride, attribute_stride, skin_stride); +#else + RS::get_singleton()->mesh_surface_make_offsets_from_format(surface.format, surface.vertex_count, surface.index_count, surface_offsets, vertex_stride, attribute_stride, skin_stride); +#endif + num_vertices = vertices.size(); + num_indices = indices.size(); + vertex_buffer = surface.vertex_data; + attribute_buffer = surface.attribute_data; + indices_changed = false; + } else { + AABB aabb_new; + uint8_t *vertex_write_buffer = vertex_buffer.ptrw(); + uint8_t *attribute_write_buffer = attribute_buffer.ptrw(); + uint8_t color[4] = { + uint8_t(CLAMP(colors[0].r * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].g * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].b * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].a * 255.0, 0.0, 255.0))}; + + for (int i = 0; i < vertices.size(); i++) { + Vector2 vertex(vertices[i]); + if (i == 0) { + aabb_new.position = Vector3(vertex.x, vertex.y, 0); + aabb_new.size = Vector3(); + } else { + aabb_new.expand_to(Vector3(vertex.x, vertex.y, 0)); + } + + float uv[2] = {(float) uvs[i].x, (float) uvs[i].y}; + memcpy(&vertex_write_buffer[i * vertex_stride + surface_offsets[RS::ARRAY_VERTEX]], &vertex, sizeof(float) * 2); + memcpy(&attribute_write_buffer[i * attribute_stride + surface_offsets[RS::ARRAY_COLOR]], color, 4); + memcpy(&attribute_write_buffer[i * attribute_stride + surface_offsets[RS::ARRAY_TEX_UV]], uv, 8); + } + RS::get_singleton()->mesh_surface_update_vertex_region(mesh, 0, 0, vertex_buffer); + RS::get_singleton()->mesh_surface_update_attribute_region(mesh, 0, 0, attribute_buffer); + RS::get_singleton()->mesh_set_custom_aabb(mesh, aabb_new); + } + + RenderingServer::get_singleton()->canvas_item_add_mesh(this->get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1, 1), renderer_object->canvas_texture->get_rid()); +#else + if (!mesh.is_valid() || vertices.size() != num_vertices || indices.size() != num_indices || indices_changed) { + if (mesh.is_valid()) { + VS::get_singleton()->free(mesh); + } + mesh = VS::get_singleton()->mesh_create(); + Array arrays; + arrays.resize(Mesh::ARRAY_MAX); + arrays[Mesh::ARRAY_VERTEX] = vertices; + arrays[Mesh::ARRAY_TEX_UV] = uvs; + arrays[Mesh::ARRAY_COLOR] = colors; + arrays[Mesh::ARRAY_INDEX] = indices; + uint32_t compress_format = (VS::ARRAY_COMPRESS_DEFAULT & ~VS::ARRAY_COMPRESS_TEX_UV); + VS::get_singleton()->mesh_add_surface_from_arrays(mesh, (VS::PrimitiveType) Mesh::PRIMITIVE_TRIANGLES, arrays, Array(), compress_format); + int surface_vertex_len = VS::get_singleton()->mesh_surface_get_array_len(mesh, 0); + int surface_index_len = VS::get_singleton()->mesh_surface_get_array_index_len(mesh, 0); + mesh_surface_format = VS::get_singleton()->mesh_surface_get_format(mesh, 0); + mesh_buffer = VS::get_singleton()->mesh_surface_get_array(mesh, 0); + VS::get_singleton()->mesh_surface_make_offsets_from_format(mesh_surface_format, surface_vertex_len, surface_index_len, mesh_surface_offsets, mesh_stride); + num_vertices = vertices.size(); + num_indices = indices.size(); + indices_changed = false; + } else { + AABB aabb_new; + PoolVector::Write write_buffer = mesh_buffer.write(); + + uint8_t color[4] = { + uint8_t(CLAMP(colors[0].r * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].g * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].b * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(colors[0].a * 255.0, 0.0, 255.0))}; + + for (int i = 0; i < vertices.size(); i++) { + Vector2 vertex(vertices[i]); + if (i == 0) { + aabb_new.position = Vector3(vertex.x, vertex.y, 0); + aabb_new.size = Vector3(); + } else { + aabb_new.expand_to(Vector3(vertex.x, vertex.y, 0)); + } + + float uv[2] = {(float) uvs[i].x, (float) uvs[i].y}; + memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_VERTEX] + mesh_surface_offsets[VS::ARRAY_VERTEX]], &vertex, sizeof(float) * 2); + memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_TEX_UV] + mesh_surface_offsets[VS::ARRAY_TEX_UV]], uv, 8); + memcpy(&write_buffer[i * mesh_stride[VS::ARRAY_COLOR] + mesh_surface_offsets[VS::ARRAY_COLOR]], color, 4); + } + write_buffer.release(); + VS::get_singleton()->mesh_surface_update_region(mesh, 0, 0, mesh_buffer); + VS::get_singleton()->mesh_set_custom_aabb(mesh, aabb_new); + } + + VS::get_singleton()->canvas_item_add_mesh( + this->get_canvas_item(), + mesh, + Transform2D(), + Color(1, 1, 1, 1), + renderer_object->texture.is_null() ? RID() : renderer_object->texture->get_rid(), + renderer_object->normal_map.is_null() ? RID() : renderer_object->normal_map->get_rid()); +#endif +} +#endif + +void SpineSprite::clear_statics() { + SpineSpriteStatics::clear(); +} + +void SpineSprite::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_skeleton_data_res", "skeleton_data_res"), &SpineSprite::set_skeleton_data_res); + ClassDB::bind_method(D_METHOD("get_skeleton_data_res"), &SpineSprite::get_skeleton_data_res); + ClassDB::bind_method(D_METHOD("get_skeleton"), &SpineSprite::get_skeleton); + ClassDB::bind_method(D_METHOD("get_animation_state"), &SpineSprite::get_animation_state); + ClassDB::bind_method(D_METHOD("on_skeleton_data_changed"), &SpineSprite::on_skeleton_data_changed); + + ClassDB::bind_method(D_METHOD("get_global_bone_transform", "bone_name"), &SpineSprite::get_global_bone_transform); + ClassDB::bind_method(D_METHOD("set_global_bone_transform", "bone_name", "global_transform"), &SpineSprite::set_global_bone_transform); + + ClassDB::bind_method(D_METHOD("set_update_mode", "v"), &SpineSprite::set_update_mode); + ClassDB::bind_method(D_METHOD("get_update_mode"), &SpineSprite::get_update_mode); + + ClassDB::bind_method(D_METHOD("set_normal_material", "material"), &SpineSprite::set_normal_material); + ClassDB::bind_method(D_METHOD("get_normal_material"), &SpineSprite::get_normal_material); + ClassDB::bind_method(D_METHOD("set_additive_material", "material"), &SpineSprite::set_additive_material); + ClassDB::bind_method(D_METHOD("get_additive_material"), &SpineSprite::get_additive_material); + ClassDB::bind_method(D_METHOD("set_multiply_material", "material"), &SpineSprite::set_multiply_material); + ClassDB::bind_method(D_METHOD("get_multiply_material"), &SpineSprite::get_multiply_material); + ClassDB::bind_method(D_METHOD("set_screen_material", "material"), &SpineSprite::set_screen_material); + ClassDB::bind_method(D_METHOD("get_screen_material"), &SpineSprite::get_screen_material); + + ClassDB::bind_method(D_METHOD("get_time_scale"), &SpineSprite::get_time_scale); + ClassDB::bind_method(D_METHOD("set_time_scale", "v"), &SpineSprite::set_time_scale); + + ClassDB::bind_method(D_METHOD("set_debug_root", "v"), &SpineSprite::set_debug_root); + ClassDB::bind_method(D_METHOD("get_debug_root"), &SpineSprite::get_debug_root); + ClassDB::bind_method(D_METHOD("set_debug_root_color", "v"), &SpineSprite::set_debug_root_color); + ClassDB::bind_method(D_METHOD("get_debug_root_color"), &SpineSprite::get_debug_root_color); + ClassDB::bind_method(D_METHOD("set_debug_bones", "v"), &SpineSprite::set_debug_bones); + ClassDB::bind_method(D_METHOD("get_debug_bones"), &SpineSprite::get_debug_bones); + ClassDB::bind_method(D_METHOD("set_debug_bones_color", "v"), &SpineSprite::set_debug_bones_color); + ClassDB::bind_method(D_METHOD("get_debug_bones_color"), &SpineSprite::get_debug_bones_color); + ClassDB::bind_method(D_METHOD("set_debug_bones_thickness", "v"), &SpineSprite::set_debug_bones_thickness); + ClassDB::bind_method(D_METHOD("get_debug_bones_thickness"), &SpineSprite::get_debug_bones_thickness); + ClassDB::bind_method(D_METHOD("set_debug_regions", "v"), &SpineSprite::set_debug_regions); + ClassDB::bind_method(D_METHOD("get_debug_regions"), &SpineSprite::get_debug_regions); + ClassDB::bind_method(D_METHOD("set_debug_regions_color", "v"), &SpineSprite::set_debug_regions_color); + ClassDB::bind_method(D_METHOD("get_debug_regions_color"), &SpineSprite::get_debug_regions_color); + ClassDB::bind_method(D_METHOD("set_debug_meshes", "v"), &SpineSprite::set_debug_meshes); + ClassDB::bind_method(D_METHOD("get_debug_meshes"), &SpineSprite::get_debug_meshes); + ClassDB::bind_method(D_METHOD("set_debug_meshes_color", "v"), &SpineSprite::set_debug_meshes_color); + ClassDB::bind_method(D_METHOD("get_debug_meshes_color"), &SpineSprite::get_debug_meshes_color); + ClassDB::bind_method(D_METHOD("set_debug_bounding_boxes", "v"), &SpineSprite::set_debug_bounding_boxes); + ClassDB::bind_method(D_METHOD("get_debug_bounding_boxes"), &SpineSprite::get_debug_bounding_boxes); + ClassDB::bind_method(D_METHOD("set_debug_bounding_boxes_color", "v"), &SpineSprite::set_debug_bounding_boxes_color); + ClassDB::bind_method(D_METHOD("get_debug_bounding_boxes_color"), &SpineSprite::get_debug_bounding_boxes_color); + ClassDB::bind_method(D_METHOD("set_debug_paths", "v"), &SpineSprite::set_debug_paths); + ClassDB::bind_method(D_METHOD("get_debug_paths"), &SpineSprite::get_debug_paths); + ClassDB::bind_method(D_METHOD("set_debug_paths_color", "v"), &SpineSprite::set_debug_paths_color); + ClassDB::bind_method(D_METHOD("get_debug_paths_color"), &SpineSprite::get_debug_paths_color); + ClassDB::bind_method(D_METHOD("set_debug_clipping", "v"), &SpineSprite::set_debug_clipping); + ClassDB::bind_method(D_METHOD("get_debug_clipping"), &SpineSprite::get_debug_clipping); + ClassDB::bind_method(D_METHOD("set_debug_clipping_color", "v"), &SpineSprite::set_debug_clipping_color); + ClassDB::bind_method(D_METHOD("get_debug_clipping_color"), &SpineSprite::get_debug_clipping_color); + + ClassDB::bind_method(D_METHOD("update_skeleton", "delta"), &SpineSprite::update_skeleton); + ClassDB::bind_method(D_METHOD("new_skin", "name"), &SpineSprite::new_skin); + + ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"))); + ADD_SIGNAL(MethodInfo("animation_interrupted", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"))); + ADD_SIGNAL(MethodInfo("animation_ended", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"))); + ADD_SIGNAL(MethodInfo("animation_completed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"))); + ADD_SIGNAL(MethodInfo("animation_disposed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"))); + ADD_SIGNAL(MethodInfo("animation_event", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_TYPE_STRING, "SpineEvent"))); + ADD_SIGNAL(MethodInfo("before_animation_state_update", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"))); + ADD_SIGNAL(MethodInfo("before_animation_state_apply", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"))); + ADD_SIGNAL(MethodInfo("before_world_transforms_change", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"))); + ADD_SIGNAL(MethodInfo("world_transforms_changed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"))); + ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated")); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skeleton_data_res", PropertyHint::PROPERTY_HINT_RESOURCE_TYPE, "SpineSkeletonDataResource"), "set_skeleton_data_res", "get_skeleton_data_res"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Process,Physics,Manual"), "set_update_mode", "get_update_mode"); + ADD_GROUP("Materials", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_normal_material", "get_normal_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "additive_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_additive_material", "get_additive_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiply_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_multiply_material", "get_multiply_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "screen_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_screen_material", "get_screen_material"); + + ADD_GROUP("Debug", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "root"), "set_debug_root", "get_debug_root"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "root_color"), "set_debug_root_color", "get_debug_root_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones"), "set_debug_bones", "get_debug_bones"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bones_color"), "set_debug_bones_color", "get_debug_bones_color"); + ADD_PROPERTY(PropertyInfo(VARIANT_FLOAT, "bones_thickness"), "set_debug_bones_thickness", "get_debug_bones_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "regions"), "set_debug_regions", "get_debug_regions"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "regions_color"), "set_debug_regions_color", "get_debug_regions_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meshes"), "set_debug_meshes", "get_debug_meshes"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "meshes_color"), "set_debug_meshes_color", "get_debug_meshes_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bounding_boxes"), "set_debug_bounding_boxes", "get_debug_bounding_boxes"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bounding_boxes_color"), "set_debug_bounding_boxes_color", "get_debug_bounding_boxes_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paths"), "set_debug_paths", "get_debug_paths"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "paths_color"), "set_debug_paths_color", "get_debug_paths_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clipping"), "set_debug_clipping", "get_debug_clipping"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "paths_clipping"), "set_debug_clipping_color", "get_debug_clipping_color"); + + ADD_GROUP("Preview", ""); + // Filled in in _get_property_list() +} + +SpineSprite::SpineSprite() : update_mode(SpineConstant::UpdateMode_Process), time_scale(1.0), preview_skin("Default"), preview_animation("-- Empty --"), preview_frame(false), preview_time(0), skeleton_clipper(nullptr), modified_bones(false) { + skeleton_clipper = new spine::SkeletonClipping(); + auto statics = SpineSpriteStatics::instance(); + + // Default debug settings + debug_root = false; + debug_root_color = Color(1, 1, 1, 0.5); + debug_bones = false; + debug_bones_color = Color(1, 1, 0, 0.5); + debug_bones_thickness = 5; + debug_regions = false; + debug_regions_color = Color(0, 0, 1, 0.5); + debug_meshes = false; + debug_meshes_color = Color(0, 0, 1, 0.5); + debug_bounding_boxes = false; + debug_bounding_boxes_color = Color(0, 1, 0, 0.5); + debug_paths = false; + debug_paths_color = Color::hex(0xff7f0077); + debug_clipping = false; + debug_clipping_color = Color(0.8, 0, 0, 0.8); + + statics.sprite_count++; +} + +SpineSprite::~SpineSprite() { + delete skeleton_clipper; + auto statics = SpineSpriteStatics::instance(); + statics.sprite_count--; + if (!statics.sprite_count) { + for (int i = 0; i < 4; i++) + statics.default_materials[i].unref(); + } +} + +void SpineSprite::set_skeleton_data_res(const Ref &_skeleton_data) { + skeleton_data_res = _skeleton_data; + on_skeleton_data_changed(); +} +Ref SpineSprite::get_skeleton_data_res() { + return skeleton_data_res; +} + +void SpineSprite::on_skeleton_data_changed() { + remove_meshes(); + skeleton.unref(); + animation_state.unref(); + emit_signal(SNAME("_internal_spine_objects_invalidated")); + + if (skeleton_data_res.is_valid()) { +#if VERSION_MAJOR > 3 + if (!skeleton_data_res->is_connected(SNAME("skeleton_data_changed"), callable_mp(this, &SpineSprite::on_skeleton_data_changed))) + skeleton_data_res->connect(SNAME("skeleton_data_changed"), callable_mp(this, &SpineSprite::on_skeleton_data_changed)); +#else + if (!skeleton_data_res->is_connected(SNAME("skeleton_data_changed"), this, SNAME("on_skeleton_data_changed"))) + skeleton_data_res->connect(SNAME("skeleton_data_changed"), this, SNAME("on_skeleton_data_changed")); +#endif + } + + if (skeleton_data_res.is_valid() && skeleton_data_res->is_skeleton_data_loaded()) { + skeleton = Ref(memnew(SpineSkeleton)); + skeleton->set_spine_sprite(this); + + animation_state = Ref(memnew(SpineAnimationState)); + animation_state->set_spine_sprite(this); + animation_state->get_spine_object()->setListener(this); + + animation_state->update(0); + animation_state->apply(skeleton); + skeleton->update_world_transform(SpineConstant::Physics_Update); + generate_meshes_for_slots(skeleton); + + if (update_mode == SpineConstant::UpdateMode_Process) { + _notification(NOTIFICATION_INTERNAL_PROCESS); + } else if (update_mode == SpineConstant::UpdateMode_Physics) { + _notification(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + } + } + + NOTIFY_PROPERTY_LIST_CHANGED(); +} + +void SpineSprite::generate_meshes_for_slots(Ref skeleton_ref) { + auto skeleton = skeleton_ref->get_spine_object(); + auto statics = SpineSpriteStatics::instance(); + for (int i = 0, n = (int) skeleton->getSlots().size(); i < n; i++) { + auto mesh_instance = memnew(SpineMesh2D); + mesh_instance->set_position(Vector2(0, 0)); + mesh_instance->set_material(statics.default_materials[spine::BlendMode_Normal]); + // Needed so that debug drawables are rendered in front of attachments + mesh_instance->set_draw_behind_parent(true); + add_child(mesh_instance); + mesh_instances.push_back(mesh_instance); + slot_nodes.add(spine::Vector()); + } +} + +void SpineSprite::remove_meshes() { + for (int i = 0; i < mesh_instances.size(); ++i) { + remove_child(mesh_instances[i]); + memdelete(mesh_instances[i]); + } + mesh_instances.clear(); + slot_nodes.clear(); +} + +void SpineSprite::sort_slot_nodes() { + for (int i = 0; i < (int) slot_nodes.size(); i++) { + slot_nodes[i].setSize(0, nullptr); + } + + auto draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < get_child_count(); i++) { + auto child = cast_to(get_child(i)); + if (!child) continue; + // Needed so that debug drawables are rendered in front of attachments and other nodes under the sprite. + child->set_draw_behind_parent(true); + auto slot_node = Object::cast_to(get_child(i)); + if (!slot_node) continue; + if (slot_node->get_slot_index() == -1 || slot_node->get_slot_index() >= (int) draw_order.size()) { + continue; + } + slot_nodes[slot_node->get_slot_index()].add(slot_node); + } + + for (int i = 0; i < (int) draw_order.size(); i++) { + int slot_index = draw_order[i]->getData().getIndex(); + int mesh_index = mesh_instances[i]->get_index(); + spine::Vector &nodes = slot_nodes[slot_index]; + for (int j = 0; j < (int) nodes.size(); j++) { + auto node = nodes[j]; + move_child(node, mesh_index + 1); + } + } +} + +Ref SpineSprite::get_skeleton() { + return skeleton; +} + +Ref SpineSprite::get_animation_state() { + return animation_state; +} + +void SpineSprite::_notification(int what) { + switch (what) { + case NOTIFICATION_READY: { + set_process_internal(update_mode == SpineConstant::UpdateMode_Process); + set_physics_process_internal(update_mode == SpineConstant::UpdateMode_Physics); + break; + } + case NOTIFICATION_INTERNAL_PROCESS: { + if (update_mode == SpineConstant::UpdateMode_Process) + update_skeleton(get_process_delta_time()); + break; + } + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (update_mode == SpineConstant::UpdateMode_Physics) + update_skeleton(get_physics_process_delta_time()); + break; + } + case NOTIFICATION_DRAW: { + draw(); + break; + } + default: + break; + } +} + +void SpineSprite::_get_property_list(List *list) const { + if (!skeleton_data_res.is_valid() || !skeleton_data_res->is_skeleton_data_loaded()) return; +#ifdef SPINE_GODOT_EXTENSION + PackedStringArray animation_names; + PackedStringArray skin_names; +#else + Vector animation_names; + Vector skin_names; +#endif + skeleton_data_res->get_animation_names(animation_names); + skeleton_data_res->get_skin_names(skin_names); + animation_names.insert(0, "-- Empty --"); + + PropertyInfo preview_skin_property; + preview_skin_property.name = "preview_skin"; + preview_skin_property.type = Variant::STRING; + preview_skin_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE; + preview_skin_property.hint_string = String(",").join(skin_names); + preview_skin_property.hint = PROPERTY_HINT_ENUM; + list->push_back(preview_skin_property); + + PropertyInfo preview_anim_property; + preview_anim_property.name = "preview_animation"; + preview_anim_property.type = Variant::STRING; + preview_anim_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE; + preview_anim_property.hint_string = String(",").join(animation_names); + preview_anim_property.hint = PROPERTY_HINT_ENUM; + list->push_back(preview_anim_property); + + PropertyInfo preview_frame_property; + preview_frame_property.name = "preview_frame"; + preview_frame_property.type = Variant::BOOL; + preview_frame_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE; + list->push_back(preview_frame_property); + + PropertyInfo preview_time_property; + preview_time_property.name = "preview_time"; + preview_time_property.type = VARIANT_FLOAT; + preview_time_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE; + float animation_duration = 0; + if (!EMPTY(preview_animation) && preview_animation != "-- Empty --") { + auto animation = skeleton_data_res->find_animation(preview_animation); + if (animation.is_valid()) animation_duration = animation->get_duration(); + } +#ifdef SPINE_GODOT_EXTENSION + preview_time_property.hint_string = String("0.0,") + String::num(animation_duration) + String(",0.01"); +#else + preview_time_property.hint_string = String("0.0,{0},0.01").format(varray(animation_duration)); +#endif + preview_time_property.hint = PROPERTY_HINT_RANGE; + list->push_back(preview_time_property); +} + +bool SpineSprite::_get(const StringName &property, Variant &value) const { + if (property == StringName("preview_skin")) { + value = preview_skin; + return true; + } + + if (property == StringName("preview_animation")) { + value = preview_animation; + return true; + } + + if (property == StringName("preview_frame")) { + value = preview_frame; + return true; + } + + if (property == StringName("preview_time")) { + value = preview_time; + return true; + } + return false; +} + +static void update_preview_animation(SpineSprite *sprite, const String &skin, const String &animation, bool frame, float time) { + if (!Engine::get_singleton()->is_editor_hint()) return; + if (!sprite->get_skeleton().is_valid()) return; + + if (EMPTY(skin) || skin == "Default") { + sprite->get_skeleton()->set_skin(nullptr); + } else { + sprite->get_skeleton()->set_skin_by_name(skin); + } + sprite->get_skeleton()->set_to_setup_pose(); + if (EMPTY(animation) || animation == "-- Empty --") { + sprite->get_animation_state()->set_empty_animation(0, 0); + return; + } + + auto track_entry = sprite->get_animation_state()->set_animation(animation, true, 0); + track_entry->set_mix_duration(0); + if (frame) { + track_entry->set_time_scale(0); + track_entry->set_track_time(time); + } +} + +bool SpineSprite::_set(const StringName &property, const Variant &value) { + if (property == StringName("preview_skin")) { + preview_skin = value; + update_preview_animation(this, preview_skin, preview_animation, preview_frame, preview_time); + NOTIFY_PROPERTY_LIST_CHANGED(); + return true; + } + + if (property == StringName("preview_animation")) { + preview_animation = value; + update_preview_animation(this, preview_skin, preview_animation, preview_frame, preview_time); + NOTIFY_PROPERTY_LIST_CHANGED(); + return true; + } + + if (property == StringName("preview_frame")) { + preview_frame = value; + update_preview_animation(this, preview_skin, preview_animation, preview_frame, preview_time); + return true; + } + + if (property == StringName("preview_time")) { + preview_time = value; + update_preview_animation(this, preview_skin, preview_animation, preview_frame, preview_time); + return true; + } + + return false; +} + +void SpineSprite::update_skeleton(float delta) { + if (!skeleton_data_res.is_valid() || + !skeleton_data_res->is_skeleton_data_loaded() || + !skeleton.is_valid() || + !skeleton->get_spine_object() || + !animation_state.is_valid() || + !animation_state->get_spine_object()) + return; + + emit_signal(SNAME("before_animation_state_update"), this); + animation_state->update(delta * time_scale); + if (!is_visible_in_tree()) return; + emit_signal(SNAME("before_animation_state_apply"), this); + animation_state->apply(skeleton); + emit_signal(SNAME("before_world_transforms_change"), this); + skeleton->update(delta * time_scale); + skeleton->update_world_transform(SpineConstant::Physics_Update); + modified_bones = false; + emit_signal(SNAME("world_transforms_changed"), this); + if (modified_bones) skeleton->update_world_transform(SpineConstant::Physics_Update); + sort_slot_nodes(); + update_meshes(skeleton); +#if VERSION_MAJOR > 3 + queue_redraw(); +#else + update(); +#endif +} + +void SpineSprite::update_meshes(Ref skeleton_ref) { + auto statics = SpineSpriteStatics::instance(); + spine::Skeleton *skeleton = skeleton_ref->get_spine_object(); + for (int i = 0, n = (int) skeleton->getSlots().size(); i < n; ++i) { + spine::Slot *slot = skeleton->getDrawOrder()[i]; + spine::Attachment *attachment = slot->getAttachment(); + SpineMesh2D *mesh_instance = mesh_instances[i]; + mesh_instance->renderer_object = nullptr; + + if (!attachment) { + skeleton_clipper->clipEnd(*slot); + continue; + } + if (!slot->getBone().isActive()) { + skeleton_clipper->clipEnd(*slot); + continue; + } + + spine::Color skeleton_color = skeleton->getColor(); + spine::Color slot_color = slot->getColor(); + spine::Color tint(skeleton_color.r * slot_color.r, skeleton_color.g * slot_color.g, skeleton_color.b * slot_color.b, skeleton_color.a * slot_color.a); + SpineRendererObject *renderer_object; + spine::Vector *vertices = &statics.scratch_vertices; + spine::Vector *uvs; + spine::Vector *indices; + + if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) { + auto *region = (spine::RegionAttachment *) attachment; + + vertices->setSize(8, 0); + region->computeWorldVertices(*slot, *vertices, 0); + renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) region->getRegion())->page->texture; + uvs = ®ion->getUVs(); + indices = &statics.quad_indices; + + auto attachment_color = region->getColor(); + tint.r *= attachment_color.r; + tint.g *= attachment_color.g; + tint.b *= attachment_color.b; + tint.a *= attachment_color.a; + } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) { + auto *mesh = (spine::MeshAttachment *) attachment; + + vertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(*slot, *vertices); + renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) mesh->getRegion())->page->texture; + uvs = &mesh->getUVs(); + indices = &mesh->getTriangles(); + + auto attachment_color = mesh->getColor(); + tint.r *= attachment_color.r; + tint.g *= attachment_color.g; + tint.b *= attachment_color.b; + tint.a *= attachment_color.a; + } else if (attachment->getRTTI().isExactly(spine::ClippingAttachment::rtti)) { + auto clip = (spine::ClippingAttachment *) attachment; + skeleton_clipper->clipStart(*slot, clip); + continue; + } else { + skeleton_clipper->clipEnd(*slot); + continue; + } + + if (skeleton_clipper->isClipping()) { + skeleton_clipper->clipTriangles(*vertices, *indices, *uvs, 2); + if (skeleton_clipper->getClippedTriangles().size() == 0) { + skeleton_clipper->clipEnd(*slot); + continue; + } + + vertices = &skeleton_clipper->getClippedVertices(); + uvs = &skeleton_clipper->getClippedUVs(); + indices = &skeleton_clipper->getClippedTriangles(); + } + + if (indices->size() > 0) { + mesh_instance->set_light_mask(get_light_mask()); + size_t num_vertices = vertices->size() / 2; + mesh_instance->vertices.resize((int) num_vertices); + memcpy(mesh_instance->vertices.ptrw(), vertices->buffer(), num_vertices * 2 * sizeof(float)); + mesh_instance->uvs.resize((int) num_vertices); + memcpy(mesh_instance->uvs.ptrw(), uvs->buffer(), num_vertices * 2 * sizeof(float)); + mesh_instance->colors.resize((int) num_vertices); + for (int j = 0; j < (int) num_vertices; j++) { + mesh_instance->colors.set(j, Color(tint.r, tint.g, tint.b, tint.a)); + } + + auto indices_changed = false; + if (mesh_instance->indices.size() == indices->size()) { + auto old_indices = mesh_instance->indices.ptr(); + auto new_indices = indices->buffer(); + for (int j = 0; j < (int) indices->size(); j++) { + if (old_indices[j] != new_indices[j]) { + indices_changed = true; + break; + } + } + } else { + indices_changed = true; + } + + if (indices_changed) { + mesh_instance->indices.resize((int) indices->size()); + for (int j = 0; j < (int) indices->size(); ++j) { + mesh_instance->indices.set(j, indices->buffer()[j]); + } + mesh_instance->indices_changed = true; + } + + mesh_instance->renderer_object = renderer_object; + + spine::BlendMode blend_mode = slot->getData().getBlendMode(); + Ref custom_material; + + // See if we have a slot node for this slot with a custom material + auto &nodes = slot_nodes[slot->getData().getIndex()]; + if (nodes.size() > 0) { + auto slot_node = nodes[0]; + if (slot_node) { + switch (blend_mode) { + case spine::BlendMode_Normal: + custom_material = slot_node->get_normal_material(); + break; + case spine::BlendMode_Additive: + custom_material = slot_node->get_additive_material(); + break; + case spine::BlendMode_Multiply: + custom_material = slot_node->get_multiply_material(); + break; + case spine::BlendMode_Screen: + custom_material = slot_node->get_screen_material(); + break; + } + } + } + + // Else, check if we have a material on the sprite itself + if (!custom_material.is_valid()) { + switch (blend_mode) { + case spine::BlendMode_Normal: + custom_material = normal_material; + break; + case spine::BlendMode_Additive: + custom_material = additive_material; + break; + case spine::BlendMode_Multiply: + custom_material = multiply_material; + break; + case spine::BlendMode_Screen: + custom_material = screen_material; + break; + } + } + + // Set the custom material, or the default material + if (custom_material.is_valid()) mesh_instance->set_material(custom_material); + else + mesh_instance->set_material(statics.default_materials[slot->getData().getBlendMode()]); + } + skeleton_clipper->clipEnd(*slot); + } + skeleton_clipper->clipEnd(); +} + +#ifdef SPINE_GODOT_EXTENSION +void createLinesFromMesh(PackedVector2Array &scratch_points, spine::Vector &triangles, spine::Vector *vertices) { +#else +void createLinesFromMesh(Vector &scratch_points, spine::Vector &triangles, spine::Vector *vertices) { +#endif + scratch_points.resize(0); + for (int i = 0; i < triangles.size(); i += 3) { + int i1 = triangles[i]; + int i2 = triangles[i + 1]; + int i3 = triangles[i + 2]; + Vector2 v1(vertices->buffer()[i1 * 2], vertices->buffer()[i1 * 2 + 1]); + Vector2 v2(vertices->buffer()[i2 * 2], vertices->buffer()[i2 * 2 + 1]); + Vector2 v3(vertices->buffer()[i3 * 2], vertices->buffer()[i3 * 2 + 1]); + scratch_points.push_back(v1); + scratch_points.push_back(v2); + scratch_points.push_back(v2); + scratch_points.push_back(v3); + scratch_points.push_back(v3); + scratch_points.push_back(v1); + } +} + +void SpineSprite::draw() { + if (!animation_state.is_valid() && !skeleton.is_valid()) return; + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) return; + + auto statics = SpineSpriteStatics::instance(); + +#if VERSION_MAJOR > 3 + RS::get_singleton()->canvas_item_clear(this->get_canvas_item()); +#else + VisualServer::get_singleton()->canvas_item_clear(this->get_canvas_item()); +#endif + + auto mouse_position = get_local_mouse_position(); + spine::Slot *hovered_slot = nullptr; + + if (debug_regions) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int) draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) continue; + auto *region = (spine::RegionAttachment *) attachment; + auto *vertices = &statics.scratch_vertices; + vertices->setSize(8, 0); + region->computeWorldVertices(*slot, *vertices, 0); + + // Render triangles. + createLinesFromMesh(statics.scratch_points, statics.quad_indices, vertices); + draw_polyline(statics.scratch_points, debug_regions_color); + + // Render hull. + statics.scratch_points.resize(0); + for (int i = 0, j = 0; i < 4; i++, j += 2) { + float x = vertices->buffer()[j]; + float y = vertices->buffer()[j + 1]; + statics.scratch_points.push_back(Vector2(x, y)); + } + statics.scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + + Color color = debug_regions_color; +#ifdef SPINE_GODOT_EXTENSION + if (GEOMETRY2D::get_singleton()->is_point_in_polygon(mouse_position, statics.scratch_points)) { +#else + if (GEOMETRY2D::is_point_in_polygon(mouse_position, statics.scratch_points)) { +#endif + hovered_slot = slot; + color = Color(1, 1, 1, 1); + } + statics.scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(statics.scratch_points, color, 2); + } + } + + if (debug_meshes) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int) draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) continue; + auto *mesh = (spine::MeshAttachment *) attachment; + auto *vertices = &statics.scratch_vertices; + vertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(*slot, *vertices); + + // Render triangles. + createLinesFromMesh(statics.scratch_points, mesh->getTriangles(), vertices); + draw_polyline(statics.scratch_points, debug_meshes_color); + + // Render hull + statics.scratch_points.resize(0); + for (int i = 0, j = 0; i < mesh->getHullLength(); i++, j += 2) { + float x = vertices->buffer()[j]; + float y = vertices->buffer()[j + 1]; + statics.scratch_points.push_back(Vector2(x, y)); + } + + Color color = debug_meshes_color; +#ifdef SPINE_GODOT_EXTENSION + if (GEOMETRY2D::get_singleton()->is_point_in_polygon(mouse_position, statics.scratch_points)) { +#else + if (GEOMETRY2D::is_point_in_polygon(mouse_position, statics.scratch_points)) { +#endif + hovered_slot = slot; + color = Color(1, 1, 1, 1); + } + statics.scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(statics.scratch_points, color, 2); + } + } + + if (debug_bounding_boxes) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int) draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::BoundingBoxAttachment::rtti)) continue; + auto *bounding_box = (spine::BoundingBoxAttachment *) attachment; + auto *vertices = &statics.scratch_vertices; + vertices->setSize(bounding_box->getWorldVerticesLength(), 0); + bounding_box->computeWorldVertices(*slot, *vertices); + size_t num_vertices = vertices->size() / 2; + statics.scratch_points.resize((int) num_vertices); + memcpy(statics.scratch_points.ptrw(), vertices->buffer(), num_vertices * 2 * sizeof(float)); + statics.scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(statics.scratch_points, debug_bounding_boxes_color, 2); + } + } + + if (debug_clipping) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int) draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::ClippingAttachment::rtti)) continue; + auto *clipping = (spine::ClippingAttachment *) attachment; + auto *vertices = &statics.scratch_vertices; + vertices->setSize(clipping->getWorldVerticesLength(), 0); + clipping->computeWorldVertices(*slot, *vertices); + size_t num_vertices = vertices->size() / 2; + statics.scratch_points.resize((int) num_vertices); + memcpy(statics.scratch_points.ptrw(), vertices->buffer(), num_vertices * 2 * sizeof(float)); + statics.scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(statics.scratch_points, debug_clipping_color, 2); + } + } + + + spine::Bone *hovered_bone = nullptr; + if (debug_root) { + auto bone = skeleton->get_spine_object()->getRootBone(); + draw_bone(bone, debug_root_color); + + float bone_length = bone->getData().getLength(); + if (bone_length == 0) bone_length = debug_bones_thickness * 2; + + statics.scratch_points.resize(5); + statics.scratch_points.set(0, Vector2(-debug_bones_thickness, 0)); + statics.scratch_points.set(1, Vector2(0, debug_bones_thickness)); + statics.scratch_points.set(2, Vector2(bone_length, 0)); + statics.scratch_points.set(3, Vector2(0, -debug_bones_thickness)); + statics.scratch_points.set(4, Vector2(-debug_bones_thickness, 0)); + Transform2D bone_transform(spine::MathUtil::Deg_Rad * bone->getWorldRotationX(), Vector2(bone->getWorldX(), bone->getWorldY())); + bone_transform.scale_basis(Vector2(bone->getWorldScaleX(), bone->getWorldScaleY())); + auto mouse_local_position = bone_transform.affine_inverse().xform(mouse_position); +#ifdef SPINE_GODOT_EXTENSION + if (GEOMETRY2D::get_singleton()->is_point_in_polygon(mouse_local_position, statics.scratch_points)) { +#else + if (GEOMETRY2D::is_point_in_polygon(mouse_local_position, statics.scratch_points)) { +#endif + hovered_bone = bone; + } + } + + if (debug_bones) { + auto &bones = skeleton->get_spine_object()->getBones(); + for (int i = 0; i < (int) bones.size(); i++) { + auto *bone = bones[i]; + if (!bone->isActive()) continue; + draw_bone(bone, debug_bones_color); + + float bone_length = bone->getData().getLength(); + if (bone_length == 0) bone_length = debug_bones_thickness * 2; + + statics.scratch_points.resize(5); + statics.scratch_points.set(0, Vector2(-debug_bones_thickness, 0)); + statics.scratch_points.set(1, Vector2(0, debug_bones_thickness)); + statics.scratch_points.set(2, Vector2(bone_length, 0)); + statics.scratch_points.set(3, Vector2(0, -debug_bones_thickness)); + statics.scratch_points.set(4, Vector2(-debug_bones_thickness, 0)); + Transform2D bone_transform(spine::MathUtil::Deg_Rad * bone->getWorldRotationX(), Vector2(bone->getWorldX(), bone->getWorldY())); + bone_transform.scale_basis(Vector2(bone->getWorldScaleX(), bone->getWorldScaleY())); + auto mouse_local_position = bone_transform.affine_inverse().xform(mouse_position); +#ifdef SPINE_GODOT_EXTENSION + if (GEOMETRY2D::get_singleton()->is_point_in_polygon(mouse_local_position, statics.scratch_points)) { +#else + if (GEOMETRY2D::is_point_in_polygon(mouse_local_position, statics.scratch_points)) { +#endif + hovered_bone = bone; + } + } + } + +#if TOOLS_ENABLED + float editor_scale = 1.0; + if (Engine::get_singleton()->is_editor_hint()) editor_scale = EditorInterface::get_singleton()->get_editor_scale(); + + float inverse_zoom = 1 / get_viewport()->get_global_canvas_transform().get_scale().x * editor_scale; + Vector hover_text_lines; + if (hovered_slot) { + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(hovered_slot->getData().getName().buffer()); +#else + name.parse_utf8(hovered_slot->getData().getName().buffer()); +#endif + hover_text_lines.push_back(String("Slot: ") + name); + } + + if (hovered_bone) { + float thickness = debug_bones_thickness; + debug_bones_thickness *= 1.1; + draw_bone(hovered_bone, Color(debug_bones_color.r, debug_bones_color.g, debug_bones_color.b, 1)); + debug_bones_thickness = thickness; + String name; +#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5) + name = String::utf8(hovered_bone->getData().getName().buffer()); +#else + name.parse_utf8(hovered_bone->getData().getName().buffer()); +#endif + hover_text_lines.push_back(String("Bone: ") + name); + } + + auto global_scale = get_global_scale(); + draw_set_transform(mouse_position + Vector2(20, 0), -get_global_rotation(), Vector2(inverse_zoom * (1 / global_scale.x), inverse_zoom * (1 / global_scale.y))); + + Ref default_font; + auto control = memnew(Control); +#if VERSION_MAJOR > 3 + default_font = control->get_theme_default_font(); +#else + default_font = control->get_font(SNAME("font"), SNAME("Label")); +#endif + memdelete(control); + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + // FIXME possibly wrong + float line_height = default_font->get_height() + default_font->get_descent(); +#else + float line_height = default_font->get_height(Font::DEFAULT_FONT_SIZE) + default_font->get_descent(Font::DEFAULT_FONT_SIZE); +#endif +#else + float line_height = default_font->get_height() + default_font->get_descent(); +#endif + float rect_width = 0; + for (int i = 0; i < hover_text_lines.size(); i++) { + rect_width = MAX(rect_width, default_font->get_string_size(hover_text_lines[i]).x); + } + +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + Rect2 background_rect(0, -default_font->get_height() - 5, rect_width + 20, line_height * hover_text_lines.size() + 10); +#else + Rect2 background_rect(0, -default_font->get_height(Font::DEFAULT_FONT_SIZE) - 5, rect_width + 20, line_height * hover_text_lines.size() + 10); +#endif +#else + Rect2 background_rect(0, -default_font->get_height() - 5, rect_width + 20, line_height * hover_text_lines.size() + 10); +#endif + if (hover_text_lines.size() > 0) draw_rect(background_rect, Color(0, 0, 0, 0.8)); + for (int i = 0; i < hover_text_lines.size(); i++) { +#if VERSION_MAJOR > 3 +#ifdef SPINE_GODOT_EXTENSION + draw_string(default_font, Vector2(10, 0 + i * default_font->get_height()), hover_text_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, 16, Color(1, 1, 1, 1)); +#else + draw_string(default_font, Vector2(10, 0 + i * default_font->get_height(Font::DEFAULT_FONT_SIZE)), hover_text_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, Font::DEFAULT_FONT_SIZE, Color(1, 1, 1, 1)); +#endif +#else + draw_string(default_font, Vector2(10, 0 + i * default_font->get_height()), hover_text_lines[i], Color(1, 1, 1, 1)); +#endif + } +#endif +} + +void SpineSprite::draw_bone(spine::Bone *bone, const Color &color) { + draw_set_transform(Vector2(bone->getWorldX(), bone->getWorldY()), spine::MathUtil::Deg_Rad * bone->getWorldRotationX(), Vector2(bone->getWorldScaleX(), bone->getWorldScaleY())); + float bone_length = bone->getData().getLength(); + if (bone_length == 0) bone_length = debug_bones_thickness * 2; +#ifdef SPINE_GODOT_EXTENSION + PackedVector2Array points; +#else + Vector points; +#endif + points.push_back(Vector2(-debug_bones_thickness, 0)); + points.push_back(Vector2(0, debug_bones_thickness)); + points.push_back(Vector2(bone_length, 0)); + points.push_back(Vector2(0, -debug_bones_thickness)); + draw_colored_polygon(points, color); +} + +void SpineSprite::callback(spine::AnimationState *state, spine::EventType type, spine::TrackEntry *entry, spine::Event *event) { + Ref entry_ref = Ref(memnew(SpineTrackEntry)); + entry_ref->set_spine_object(this, entry); + + Ref event_ref(nullptr); + if (event) { + event_ref = Ref(memnew(SpineEvent)); + event_ref->set_spine_object(this, event); + } + + switch (type) { + case spine::EventType_Start: + emit_signal(SNAME("animation_started"), this, animation_state, entry_ref); + break; + case spine::EventType_Interrupt: + emit_signal(SNAME("animation_interrupted"), this, animation_state, entry_ref); + break; + case spine::EventType_End: + emit_signal(SNAME("animation_ended"), this, animation_state, entry_ref); + break; + case spine::EventType_Complete: + emit_signal(SNAME("animation_completed"), this, animation_state, entry_ref); + break; + case spine::EventType_Dispose: + emit_signal(SNAME("animation_disposed"), this, animation_state, entry_ref); + break; + case spine::EventType_Event: + emit_signal(SNAME("animation_event"), this, animation_state, entry_ref, event_ref); + break; + } +} + +Transform2D SpineSprite::get_global_bone_transform(const String &bone_name) { + if (!animation_state.is_valid() && !skeleton.is_valid()) return get_global_transform(); + auto bone = skeleton->find_bone(bone_name); + if (!bone.is_valid()) { + return get_global_transform(); + } + return bone->get_global_transform(); +} + +void SpineSprite::set_global_bone_transform(const String &bone_name, Transform2D transform) { + if (!animation_state.is_valid() && !skeleton.is_valid()) return; + auto bone = skeleton->find_bone(bone_name); + if (!bone.is_valid()) return; + bone->set_global_transform(transform); +} + +SpineConstant::UpdateMode SpineSprite::get_update_mode() { + return update_mode; +} + +void SpineSprite::set_update_mode(SpineConstant::UpdateMode v) { + update_mode = v; + set_process_internal(update_mode == SpineConstant::UpdateMode_Process); + set_physics_process_internal(update_mode == SpineConstant::UpdateMode_Physics); +} + +Ref SpineSprite::new_skin(const String &name) { + Ref skin = memnew(SpineSkin); + skin->init(name, this); + return skin; +} + +Ref SpineSprite::get_normal_material() { + return normal_material; +} + +void SpineSprite::set_normal_material(Ref material) { + normal_material = material; +} + +Ref SpineSprite::get_additive_material() { + return additive_material; +} + +void SpineSprite::set_additive_material(Ref material) { + additive_material = material; +} + +Ref SpineSprite::get_multiply_material() { + return multiply_material; +} + +void SpineSprite::set_multiply_material(Ref material) { + multiply_material = material; +} + +Ref SpineSprite::get_screen_material() { + return screen_material; +} + +void SpineSprite::set_screen_material(Ref material) { + screen_material = material; +} + +void SpineSprite::set_time_scale(float time_scale) { + this->time_scale = time_scale; +} + +float SpineSprite::get_time_scale() { + return time_scale; +} + +#ifndef SPINE_GODOT_EXTENSION +// FIXME +#ifdef TOOLS_ENABLED +Rect2 SpineSprite::_edit_get_rect() const { + if (skeleton_data_res.is_valid() && skeleton_data_res->is_skeleton_data_loaded()) { + auto data = skeleton_data_res->get_skeleton_data(); + return Rect2(data->getX(), -data->getY() - data->getHeight(), data->getWidth(), data->getHeight()); + } + return Node2D::_edit_get_rect(); +} + +bool SpineSprite::_edit_use_rect() const { + return skeleton_data_res.is_valid() && skeleton_data_res->is_skeleton_data_loaded(); +} +#endif +#endif diff --git a/modules/spine_godot/SpineSprite.h b/modules/spine_godot/SpineSprite.h new file mode 100644 index 000000000000..151ec475ca4c --- /dev/null +++ b/modules/spine_godot/SpineSprite.h @@ -0,0 +1,307 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineSkeleton.h" +#include "SpineAnimationState.h" +#ifdef SPINE_GODOT_EXTENSION +#include "SpineCommon.h" +#include +#include +#include +#include +#else +#include "scene/2d/node_2d.h" +#endif + +class SpineSlotNode; + +struct SpineRendererObject; + +class SpineSprite; + +class Attachment; + +class SpineMesh2D : public Node2D { + GDCLASS(SpineMesh2D, Node2D); + + friend class SpineSprite; + +protected: + void _notification(int what); + static void _bind_methods(); + +#ifdef SPINE_GODOT_EXTENSION + PackedVector2Array vertices; + PackedVector2Array uvs; + PackedColorArray colors; + PackedInt32Array indices; +#else + Vector vertices; + Vector uvs; + Vector colors; + Vector indices; +#endif + SpineRendererObject *renderer_object; + + bool indices_changed; + +#if VERSION_MAJOR > 3 + RID mesh; + uint32_t surface_offsets[RS::ARRAY_MAX]; + int num_vertices; + int num_indices; + PackedByteArray vertex_buffer; + PackedByteArray attribute_buffer; + uint32_t vertex_stride; + uint32_t normal_tangent_stride; + uint32_t attribute_stride; +#else + RID mesh; + uint32_t surface_offsets[VS::ARRAY_MAX]; + int num_vertices; + int num_indices; + uint32_t mesh_surface_offsets[VS::ARRAY_MAX]; + PoolByteArray mesh_buffer; + uint32_t mesh_stride[VS::ARRAY_MAX]; + uint32_t mesh_surface_format; +#endif + +public: +#if VERSION_MAJOR > 3 + SpineMesh2D() : renderer_object(nullptr), indices_changed(true), num_vertices(0), num_indices(0), vertex_stride(0), normal_tangent_stride(0), attribute_stride(0){}; + ~SpineMesh2D() { + if (mesh.is_valid()) { +#ifdef SPINE_GODOT_EXTENSION + RS::get_singleton()->free_rid(mesh); +#else + RS::get_singleton()->free(mesh); +#endif + } + } +#else + SpineMesh2D() : renderer_object(nullptr), indices_changed(true), num_vertices(0), num_indices(0){}; + ~SpineMesh2D() { + if (mesh.is_valid()) { + VS::get_singleton()->free(mesh); + } + } +#endif + +#ifdef SPINE_GODOT_EXTENSION + void update_mesh(const PackedVector2Array &vertices, + const PackedVector2Array &uvs, + const PackedColorArray &colors, + const PackedInt32Array &indices, + SpineRendererObject *renderer_object); +#else + void update_mesh(const Vector &vertices, + const Vector &uvs, + const Vector &colors, + const Vector &indices, + SpineRendererObject *renderer_object); +#endif +}; + +class SpineSprite : public Node2D, + public spine::AnimationStateListenerObject { + GDCLASS(SpineSprite, Node2D) + + friend class SpineBone; + +protected: + Ref skeleton_data_res; + Ref skeleton; + Ref animation_state; + SpineConstant::UpdateMode update_mode; + float time_scale; + + String preview_skin; + String preview_animation; + bool preview_frame; + float preview_time; + + bool debug_root; + Color debug_root_color; + bool debug_bones; + Color debug_bones_color; + float debug_bones_thickness; + bool debug_regions; + Color debug_regions_color; + bool debug_meshes; + Color debug_meshes_color; + bool debug_bounding_boxes; + Color debug_bounding_boxes_color; + bool debug_paths; + Color debug_paths_color; + bool debug_clipping; + Color debug_clipping_color; + + spine::Vector> slot_nodes; + Vector mesh_instances; + Ref normal_material; + Ref additive_material; + Ref multiply_material; + Ref screen_material; + spine::SkeletonClipping *skeleton_clipper; + bool modified_bones; + + static void _bind_methods(); + void _notification(int what); + void _get_property_list(List *list) const; + bool _get(const StringName &property, Variant &value) const; + bool _set(const StringName &property, const Variant &value); + + void generate_meshes_for_slots(Ref skeleton_ref); + void remove_meshes(); + void sort_slot_nodes(); + void update_meshes(Ref skeleton_ref); + void set_modified_bones() { modified_bones = true; } + void draw(); + void draw_bone(spine::Bone *bone, const Color &color); + + void callback(spine::AnimationState *state, spine::EventType type, spine::TrackEntry *entry, spine::Event *event) override; + +public: + SpineSprite(); + ~SpineSprite(); + + void set_skeleton_data_res(const Ref &_spine_skeleton_data_resource); + + Ref get_skeleton_data_res(); + + Ref get_skeleton(); + + Ref get_animation_state(); + + void on_skeleton_data_changed(); + + void update_skeleton(float delta); + + Transform2D get_global_bone_transform(const String &bone_name); + + void set_global_bone_transform(const String &bone_name, Transform2D transform); + + SpineConstant::UpdateMode get_update_mode(); + + void set_update_mode(SpineConstant::UpdateMode v); + + Ref new_skin(const String &name); + + Ref get_normal_material(); + + void set_normal_material(Ref material); + + Ref get_additive_material(); + + void set_additive_material(Ref material); + + Ref get_multiply_material(); + + void set_multiply_material(Ref material); + + Ref get_screen_material(); + + void set_screen_material(Ref material); + + void set_time_scale(float time_scale); + + float get_time_scale(); + + bool get_debug_root() { return debug_root; } + + void set_debug_root(bool root) { debug_root = root; } + + Color get_debug_root_color() { return debug_root_color; } + + void set_debug_root_color(const Color &color) { debug_root_color = color; } + + bool get_debug_bones() { return debug_bones; } + + void set_debug_bones(bool bones) { debug_bones = bones; } + + Color get_debug_bones_color() { return debug_bones_color; } + + void set_debug_bones_color(const Color &color) { debug_bones_color = color; } + + float get_debug_bones_thickness() { return debug_bones_thickness; } + + void set_debug_bones_thickness(float thickness) { debug_bones_thickness = thickness; } + + bool get_debug_regions() { return debug_regions; } + + void set_debug_regions(bool regions) { debug_regions = regions; } + + Color get_debug_regions_color() { return debug_regions_color; } + + void set_debug_regions_color(const Color &color) { debug_regions_color = color; } + + bool get_debug_meshes() { return debug_meshes; } + + void set_debug_meshes(bool meshes) { debug_meshes = meshes; } + + Color get_debug_meshes_color() { return debug_meshes_color; } + + void set_debug_meshes_color(const Color &color) { debug_meshes_color = color; } + + bool get_debug_paths() { return debug_paths; } + + void set_debug_paths(bool paths) { debug_paths = paths; } + + Color get_debug_paths_color() { return debug_paths_color; } + + void set_debug_paths_color(const Color &color) { debug_paths_color = color; } + + bool get_debug_bounding_boxes() { return debug_bounding_boxes; } + + void set_debug_bounding_boxes(bool paths) { debug_bounding_boxes = paths; } + + Color get_debug_bounding_boxes_color() { return debug_bounding_boxes_color; } + + void set_debug_bounding_boxes_color(const Color &color) { debug_bounding_boxes_color = color; } + + bool get_debug_clipping() { return debug_clipping; } + + void set_debug_clipping(bool clipping) { debug_clipping = clipping; } + + Color get_debug_clipping_color() { return debug_clipping_color; } + + void set_debug_clipping_color(const Color &color) { debug_clipping_color = color; } + +#ifndef SPINE_GODOT_EXTENSION +// FIXME +#ifdef TOOLS_ENABLED + virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; +#endif +#endif + + static void clear_statics(); +}; diff --git a/modules/spine_godot/SpineTimeline.cpp b/modules/spine_godot/SpineTimeline.cpp new file mode 100644 index 000000000000..36bd2c8e2a3d --- /dev/null +++ b/modules/spine_godot/SpineTimeline.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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 "SpineTimeline.h" +#include "SpineSkeleton.h" +#include "SpineEvent.h" +#if VERSION_MAJOR == 3 +#include "core/method_bind_ext.gen.inc" +#endif + +void SpineTimeline::_bind_methods() { + ClassDB::bind_method(D_METHOD("apply", "skeleton", "last_time", "time", "events", "alpha", "blend", "direction"), &SpineTimeline::apply); + ClassDB::bind_method(D_METHOD("get_frame_entries"), &SpineTimeline::get_frame_entries); + ClassDB::bind_method(D_METHOD("get_frame_count"), &SpineTimeline::get_frame_count); + ClassDB::bind_method(D_METHOD("get_frames"), &SpineTimeline::get_frames); + ClassDB::bind_method(D_METHOD("get_duration"), &SpineTimeline::get_duration); + ClassDB::bind_method(D_METHOD("get_property_ids"), &SpineTimeline::get_property_ids); + ClassDB::bind_method(D_METHOD("get_type"), &SpineTimeline::get_type); +} + +void SpineTimeline::apply(Ref skeleton, float last_time, float time, Array events, float alpha, + SpineConstant::MixBlend blend, SpineConstant::MixDirection direction) { + SPINE_CHECK(get_spine_object(), ) + if (!skeleton->get_spine_object()) return; + spine::Vector spine_events; + spine_events.setSize((int) events.size(), nullptr); + for (int i = 0; i < events.size(); ++i) { + events[i] = ((Ref) spine_events[i])->get_spine_object(); + } + get_spine_object()->apply(*(skeleton->get_spine_object()), last_time, time, &spine_events, alpha, (spine::MixBlend) blend, (spine::MixDirection) direction); +} + +int SpineTimeline::get_frame_entries() { + SPINE_CHECK(get_spine_object(), 0) + return (int) get_spine_object()->getFrameEntries(); +} + +int SpineTimeline::get_frame_count() { + SPINE_CHECK(get_spine_object(), 0) + return (int) get_spine_object()->getFrameCount(); +} + +Array SpineTimeline::get_frames() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &frames = get_spine_object()->getFrames(); + result.resize((int) frames.size()); + for (int i = 0; i < result.size(); ++i) { + result[i] = frames[i]; + } + return result; +} + +float SpineTimeline::get_duration() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getDuration(); +} + +Array SpineTimeline::get_property_ids() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &ids = get_spine_object()->getPropertyIds(); + result.resize((int) ids.size()); + for (int i = 0; i < result.size(); ++i) { + result[i] = (int64_t) ids[i]; + } + return result; +} + +String SpineTimeline::get_type() { + SPINE_CHECK(get_spine_object(), "") + return get_spine_object()->getRTTI().getClassName(); +} diff --git a/modules/spine_godot/SpineTimeline.h b/modules/spine_godot/SpineTimeline.h new file mode 100644 index 000000000000..3affd93b33d3 --- /dev/null +++ b/modules/spine_godot/SpineTimeline.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineConstant.h" +#include + +#include "SpineSkeletonDataResource.h" + +class SpineSkeleton; +class SpineEvent; + +class SpineTimeline : public SpineSkeletonDataResourceOwnedObject { + GDCLASS(SpineTimeline, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void apply(Ref skeleton, float last_time, float time, Array events, float alpha, SpineConstant::MixBlend blend, SpineConstant::MixDirection direction); + + int get_frame_entries(); + + int get_frame_count(); + + Array get_frames(); + + float get_duration(); + + Array get_property_ids(); + + String get_type(); +}; diff --git a/modules/spine_godot/SpineTrackEntry.cpp b/modules/spine_godot/SpineTrackEntry.cpp new file mode 100644 index 000000000000..ad2d65ba9c75 --- /dev/null +++ b/modules/spine_godot/SpineTrackEntry.cpp @@ -0,0 +1,354 @@ +/****************************************************************************** + * 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 "SpineTrackEntry.h" +#include "SpineCommon.h" + +void SpineTrackEntry::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_track_index"), &SpineTrackEntry::get_track_index); + ClassDB::bind_method(D_METHOD("get_animation"), &SpineTrackEntry::get_animation); + ClassDB::bind_method(D_METHOD("get_previous"), &SpineTrackEntry::get_previous); + ClassDB::bind_method(D_METHOD("get_loop"), &SpineTrackEntry::get_loop); + ClassDB::bind_method(D_METHOD("set_loop", "v"), &SpineTrackEntry::set_loop); + ClassDB::bind_method(D_METHOD("get_hold_previous"), &SpineTrackEntry::get_hold_previous); + ClassDB::bind_method(D_METHOD("set_hold_previous", "v"), &SpineTrackEntry::set_hold_previous); + ClassDB::bind_method(D_METHOD("get_reverse"), &SpineTrackEntry::get_reverse); + ClassDB::bind_method(D_METHOD("set_reverse", "v"), &SpineTrackEntry::set_reverse); + ClassDB::bind_method(D_METHOD("get_shortest_rotation"), &SpineTrackEntry::get_shortest_rotation); + ClassDB::bind_method(D_METHOD("set_shortest_rotation", "v"), &SpineTrackEntry::set_shortest_rotation); + ClassDB::bind_method(D_METHOD("get_delay"), &SpineTrackEntry::get_delay); + ClassDB::bind_method(D_METHOD("set_delay", "v"), &SpineTrackEntry::set_delay); + ClassDB::bind_method(D_METHOD("get_track_time"), &SpineTrackEntry::get_track_time); + ClassDB::bind_method(D_METHOD("set_track_time", "v"), &SpineTrackEntry::set_track_time); + ClassDB::bind_method(D_METHOD("get_track_end"), &SpineTrackEntry::get_track_end); + ClassDB::bind_method(D_METHOD("set_track_end", "v"), &SpineTrackEntry::set_track_end); + ClassDB::bind_method(D_METHOD("get_animation_start"), &SpineTrackEntry::get_animation_start); + ClassDB::bind_method(D_METHOD("set_animation_start", "v"), &SpineTrackEntry::set_animation_start); + ClassDB::bind_method(D_METHOD("get_animation_end"), &SpineTrackEntry::get_animation_end); + ClassDB::bind_method(D_METHOD("set_animation_end", "v"), &SpineTrackEntry::set_animation_end); + ClassDB::bind_method(D_METHOD("get_animation_last"), &SpineTrackEntry::get_animation_last); + ClassDB::bind_method(D_METHOD("set_animation_last", "v"), &SpineTrackEntry::set_animation_last); + ClassDB::bind_method(D_METHOD("get_animation_time"), &SpineTrackEntry::get_animation_time); + ClassDB::bind_method(D_METHOD("get_time_scale"), &SpineTrackEntry::get_time_scale); + ClassDB::bind_method(D_METHOD("set_time_scale", "v"), &SpineTrackEntry::set_time_scale); + ClassDB::bind_method(D_METHOD("get_alpha"), &SpineTrackEntry::get_alpha); + ClassDB::bind_method(D_METHOD("set_alpha", "v"), &SpineTrackEntry::set_alpha); + ClassDB::bind_method(D_METHOD("get_event_threshold"), &SpineTrackEntry::get_event_threshold); + ClassDB::bind_method(D_METHOD("set_event_threshold", "v"), &SpineTrackEntry::set_event_threshold); + ClassDB::bind_method(D_METHOD("get_mix_attachment_threshold"), &SpineTrackEntry::get_mix_attachment_threshold); + ClassDB::bind_method(D_METHOD("set_mix_attachment_threshold", "v"), &SpineTrackEntry::set_mix_attachment_threshold); + ClassDB::bind_method(D_METHOD("get_mix_draw_order_threshold"), &SpineTrackEntry::get_mix_draw_order_threshold); + ClassDB::bind_method(D_METHOD("set_mix_draw_order_threshold", "v"), &SpineTrackEntry::set_mix_draw_order_threshold); + ClassDB::bind_method(D_METHOD("get_alpha_attachment_threshold"), &SpineTrackEntry::get_alpha_attachment_threshold); + ClassDB::bind_method(D_METHOD("set_alpha_attachment_threshold", "v"), &SpineTrackEntry::set_alpha_attachment_threshold); + ClassDB::bind_method(D_METHOD("get_next"), &SpineTrackEntry::get_next); + ClassDB::bind_method(D_METHOD("is_complete"), &SpineTrackEntry::is_complete); + ClassDB::bind_method(D_METHOD("get_mix_time"), &SpineTrackEntry::get_mix_time); + ClassDB::bind_method(D_METHOD("set_mix_time", "v"), &SpineTrackEntry::set_mix_time); + ClassDB::bind_method(D_METHOD("get_mix_duration"), &SpineTrackEntry::get_mix_duration); + ClassDB::bind_method(D_METHOD("set_mix_duration", "v"), &SpineTrackEntry::set_mix_duration); + ClassDB::bind_method(D_METHOD("set_mix_duration_and_delay", "v", "delay"), &SpineTrackEntry::set_mix_duration_and_delay); + ClassDB::bind_method(D_METHOD("get_mix_blend"), &SpineTrackEntry::get_mix_blend); + ClassDB::bind_method(D_METHOD("set_mix_blend", "v"), &SpineTrackEntry::set_mix_blend); + ClassDB::bind_method(D_METHOD("get_mixing_from"), &SpineTrackEntry::get_mixing_from); + ClassDB::bind_method(D_METHOD("get_mixing_to"), &SpineTrackEntry::get_mixing_to); + ClassDB::bind_method(D_METHOD("reset_rotation_directions"), &SpineTrackEntry::reset_rotation_directions); + ClassDB::bind_method(D_METHOD("get_track_complete"), &SpineTrackEntry::get_track_complete); + ClassDB::bind_method(D_METHOD("was_applied"), &SpineTrackEntry::was_applied); +} + +int SpineTrackEntry::get_track_index() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTrackIndex(); +} + +Ref SpineTrackEntry::get_animation() { + SPINE_CHECK(get_spine_object(), nullptr) + auto animation = get_spine_object()->getAnimation(); + if (!animation) return nullptr; + Ref animation_ref(memnew(SpineAnimation)); + animation_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), animation); + return animation_ref; +} + +Ref SpineTrackEntry::get_previous() { + SPINE_CHECK(get_spine_object(), nullptr) + auto previous = get_spine_object()->getPrevious(); + if (!previous) return nullptr; + Ref previous_ref(memnew(SpineTrackEntry)); + previous_ref->set_spine_object(get_spine_owner(), previous); + return previous_ref; +} + +bool SpineTrackEntry::get_loop() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getLoop(); +} + +void SpineTrackEntry::set_loop(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setLoop(v); +} + +bool SpineTrackEntry::get_hold_previous() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getHoldPrevious(); +} + +void SpineTrackEntry::set_hold_previous(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setHoldPrevious(v); +} + +bool SpineTrackEntry::get_reverse() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getReverse(); +} + +void SpineTrackEntry::set_reverse(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setReverse(v); +} + +bool SpineTrackEntry::get_shortest_rotation() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->getShortestRotation(); +} + +void SpineTrackEntry::set_shortest_rotation(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setShortestRotation(v); +} + +float SpineTrackEntry::get_delay() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getDelay(); +} + +void SpineTrackEntry::set_delay(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setDelay(v); +} + +float SpineTrackEntry::get_track_time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTrackTime(); +} + +void SpineTrackEntry::set_track_time(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTrackTime(v); +} + +float SpineTrackEntry::get_track_end() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTrackEnd(); +} + +void SpineTrackEntry::set_track_end(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTrackEnd(v); +} + +float SpineTrackEntry::get_animation_start() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAnimationStart(); +} + +void SpineTrackEntry::set_animation_start(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAnimationStart(v); +} + +float SpineTrackEntry::get_animation_end() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAnimationEnd(); +} + +void SpineTrackEntry::set_animation_end(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAnimationEnd(v); +} + +float SpineTrackEntry::get_animation_last() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAnimationLast(); +} + +void SpineTrackEntry::set_animation_last(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAnimationLast(v); +} + +float SpineTrackEntry::get_animation_time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAnimationTime(); +} + +float SpineTrackEntry::get_time_scale() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTimeScale(); +} + +void SpineTrackEntry::set_time_scale(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTimeScale(v); +} + +float SpineTrackEntry::get_alpha() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAlpha(); +} + +void SpineTrackEntry::set_alpha(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAlpha(v); +} + +float SpineTrackEntry::get_event_threshold() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getEventThreshold(); +} + +void SpineTrackEntry::set_event_threshold(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setEventThreshold(v); +} + +float SpineTrackEntry::get_mix_attachment_threshold() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixAttachmentThreshold(); +} + +void SpineTrackEntry::set_mix_attachment_threshold(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixAttachmentThreshold(v); +} + +float SpineTrackEntry::get_mix_draw_order_threshold() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixDrawOrderThreshold(); +} + +void SpineTrackEntry::set_mix_draw_order_threshold(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixDrawOrderThreshold(v); +} + +float SpineTrackEntry::get_alpha_attachment_threshold() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getAlphaAttachmentThreshold(); +} + +void SpineTrackEntry::set_alpha_attachment_threshold(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setAlphaAttachmentThreshold(v); +} + +Ref SpineTrackEntry::get_next() { + SPINE_CHECK(get_spine_object(), nullptr) + auto next = get_spine_object()->getNext(); + if (!next) return nullptr; + Ref next_ref(memnew(SpineTrackEntry)); + next_ref->set_spine_object(get_spine_owner(), next); + return next_ref; +} + +bool SpineTrackEntry::is_complete() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isComplete(); +} + +float SpineTrackEntry::get_mix_time() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixTime(); +} + +void SpineTrackEntry::set_mix_time(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixTime(v); +} + +float SpineTrackEntry::get_mix_duration() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixDuration(); +} + +void SpineTrackEntry::set_mix_duration(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixDuration(v); +} + +void SpineTrackEntry::set_mix_duration_and_delay(float v, float delay) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixDuration(v, delay); +} + +SpineConstant::MixBlend SpineTrackEntry::get_mix_blend() { + SPINE_CHECK(get_spine_object(), SpineConstant::MixBlend_Setup) + return (SpineConstant::MixBlend) get_spine_object()->getMixBlend(); +} + +void SpineTrackEntry::set_mix_blend(SpineConstant::MixBlend v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixBlend((spine::MixBlend) v); +} + +Ref SpineTrackEntry::get_mixing_from() { + SPINE_CHECK(get_spine_object(), nullptr) + auto mixing_from = get_spine_object()->getMixingFrom(); + if (!mixing_from) return nullptr; + Ref mixing_from_ref(memnew(SpineTrackEntry)); + mixing_from_ref->set_spine_object(get_spine_owner(), mixing_from); + return mixing_from_ref; +} + +Ref SpineTrackEntry::get_mixing_to() { + SPINE_CHECK(get_spine_object(), nullptr) + auto mixing_to = get_spine_object()->getMixingTo(); + if (!mixing_to) return nullptr; + Ref mixing_to_ref(memnew(SpineTrackEntry)); + mixing_to_ref->set_spine_object(get_spine_owner(), mixing_to); + return mixing_to_ref; +} + +void SpineTrackEntry::reset_rotation_directions() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->resetRotationDirections(); +} + +float SpineTrackEntry::get_track_complete() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getTrackComplete(); +} + +bool SpineTrackEntry::was_applied() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->wasApplied(); +} diff --git a/modules/spine_godot/SpineTrackEntry.h b/modules/spine_godot/SpineTrackEntry.h new file mode 100644 index 000000000000..42ff04692442 --- /dev/null +++ b/modules/spine_godot/SpineTrackEntry.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineAnimation.h" +#include "SpineConstant.h" +#include + +#include "SpineSprite.h" + +class SpineTrackEntry : public SpineSpriteOwnedObject { + GDCLASS(SpineTrackEntry, SpineObjectWrapper); + +protected: + static void _bind_methods(); + +public: + int get_track_index(); + + Ref get_animation(); + + Ref get_previous(); + + bool get_loop(); + + void set_loop(bool v); + + bool get_hold_previous(); + + void set_hold_previous(bool v); + + bool get_reverse(); + + void set_reverse(bool v); + + bool get_shortest_rotation(); + + void set_shortest_rotation(bool v); + + float get_delay(); + + void set_delay(float v); + + float get_track_time(); + + void set_track_time(float v); + + float get_track_end(); + + void set_track_end(float v); + + float get_animation_start(); + + void set_animation_start(float v); + + float get_animation_end(); + + void set_animation_end(float v); + + float get_animation_last(); + + void set_animation_last(float v); + + float get_animation_time(); + + float get_time_scale(); + + void set_time_scale(float v); + + float get_alpha(); + + void set_alpha(float v); + + float get_event_threshold(); + + void set_event_threshold(float v); + + float get_mix_attachment_threshold(); + + void set_mix_attachment_threshold(float v); + + float get_mix_draw_order_threshold(); + + void set_mix_draw_order_threshold(float v); + + float get_alpha_attachment_threshold(); + + void set_alpha_attachment_threshold(float v); + + Ref get_next(); + + bool is_complete(); + + float get_mix_time(); + + void set_mix_time(float v); + + float get_mix_duration(); + + void set_mix_duration(float v); + + void set_mix_duration_and_delay(float v, float delay); + + SpineConstant::MixBlend get_mix_blend(); + + void set_mix_blend(SpineConstant::MixBlend v); + + Ref get_mixing_from(); + + Ref get_mixing_to(); + + void reset_rotation_directions(); + + float get_track_complete(); + + bool was_applied(); +}; diff --git a/modules/spine_godot/SpineTransformConstraint.cpp b/modules/spine_godot/SpineTransformConstraint.cpp new file mode 100644 index 000000000000..4d7a81dc0009 --- /dev/null +++ b/modules/spine_godot/SpineTransformConstraint.cpp @@ -0,0 +1,170 @@ +/****************************************************************************** + * 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 "SpineTransformConstraint.h" +#include "SpineCommon.h" +#include "SpineSprite.h" + +void SpineTransformConstraint::_bind_methods() { + ClassDB::bind_method(D_METHOD("update"), &SpineTransformConstraint::update); + ClassDB::bind_method(D_METHOD("get_data"), &SpineTransformConstraint::get_data); + ClassDB::bind_method(D_METHOD("get_bones"), &SpineTransformConstraint::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpineTransformConstraint::get_target); + ClassDB::bind_method(D_METHOD("set_target", "v"), &SpineTransformConstraint::set_target); + ClassDB::bind_method(D_METHOD("get_mix_rotate"), &SpineTransformConstraint::get_mix_rotate); + ClassDB::bind_method(D_METHOD("set_mix_rotate", "v"), &SpineTransformConstraint::set_mix_rotate); + ClassDB::bind_method(D_METHOD("get_mix_x"), &SpineTransformConstraint::get_mix_x); + ClassDB::bind_method(D_METHOD("set_mix_x", "v"), &SpineTransformConstraint::set_mix_x); + ClassDB::bind_method(D_METHOD("get_mix_y"), &SpineTransformConstraint::get_mix_y); + ClassDB::bind_method(D_METHOD("set_mix_y", "v"), &SpineTransformConstraint::set_mix_y); + ClassDB::bind_method(D_METHOD("get_mix_scale_x"), &SpineTransformConstraint::get_mix_scale_x); + ClassDB::bind_method(D_METHOD("set_mix_scale_x", "v"), &SpineTransformConstraint::set_mix_scale_x); + ClassDB::bind_method(D_METHOD("get_mix_scale_y"), &SpineTransformConstraint::get_mix_scale_y); + ClassDB::bind_method(D_METHOD("set_mix_scale_y", "v"), &SpineTransformConstraint::set_mix_scale_y); + ClassDB::bind_method(D_METHOD("get_mix_shear_y"), &SpineTransformConstraint::get_mix_shear_y); + ClassDB::bind_method(D_METHOD("set_mix_shear_y", "v"), &SpineTransformConstraint::set_mix_shear_y); + ClassDB::bind_method(D_METHOD("is_active"), &SpineTransformConstraint::is_active); + ClassDB::bind_method(D_METHOD("set_active", "v"), &SpineTransformConstraint::set_active); +} + +void SpineTransformConstraint::update() { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->update(spine::Physics_Update); +} + +int SpineTransformConstraint::get_order() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getOrder(); +} + +Ref SpineTransformConstraint::get_data() { + SPINE_CHECK(get_spine_object(), nullptr) + auto &data = get_spine_object()->getData(); + Ref data_ref(memnew(SpineTransformConstraintData)); + data_ref->set_spine_object(*get_spine_owner()->get_skeleton_data_res(), &data); + return data_ref; +} + +Array SpineTransformConstraint::get_bones() { + Array result; + SPINE_CHECK(get_spine_object(), result) + auto &bones = get_spine_object()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < bones.size(); ++i) { + auto bone = bones[i]; + Ref bone_ref(memnew(SpineBone)); + bone_ref->set_spine_object(get_spine_owner(), bone); + result[i] = bone_ref; + } + return result; +} + +Ref SpineTransformConstraint::get_target() { + SPINE_CHECK(get_spine_object(), nullptr) + auto target = get_spine_object()->getTarget(); + if (!target) return nullptr; + Ref target_ref(memnew(SpineBone)); + target_ref->set_spine_object(get_spine_owner(), target); + return target_ref; +} + +void SpineTransformConstraint::set_target(Ref v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setTarget(v.is_valid() && v->get_spine_object() ? v->get_spine_object() : nullptr); +} + +float SpineTransformConstraint::get_mix_rotate() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixRotate(); +} + +void SpineTransformConstraint::set_mix_rotate(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixRotate(v); +} + +float SpineTransformConstraint::get_mix_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixX(); +} + +void SpineTransformConstraint::set_mix_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixX(v); +} + +float SpineTransformConstraint::get_mix_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixY(); +} + +void SpineTransformConstraint::set_mix_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixY(v); +} + +float SpineTransformConstraint::get_mix_scale_x() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixScaleX(); +} + +void SpineTransformConstraint::set_mix_scale_x(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixScaleX(v); +} + +float SpineTransformConstraint::get_mix_scale_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixScaleY(); +} + +void SpineTransformConstraint::set_mix_scale_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixScaleY(v); +} + +float SpineTransformConstraint::get_mix_shear_y() { + SPINE_CHECK(get_spine_object(), 0) + return get_spine_object()->getMixShearY(); +} + +void SpineTransformConstraint::set_mix_shear_y(float v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setMixShearY(v); +} + +bool SpineTransformConstraint::is_active() { + SPINE_CHECK(get_spine_object(), false) + return get_spine_object()->isActive(); +} + +void SpineTransformConstraint::set_active(bool v) { + SPINE_CHECK(get_spine_object(), ) + get_spine_object()->setActive(v); +} diff --git a/modules/spine_godot/SpineTransformConstraint.h b/modules/spine_godot/SpineTransformConstraint.h new file mode 100644 index 000000000000..6fc6ea23a115 --- /dev/null +++ b/modules/spine_godot/SpineTransformConstraint.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" +#include "SpineTransformConstraintData.h" +#include "SpineBone.h" +#include + +class SpineTransformConstraint : public SpineSpriteOwnedObject { + GDCLASS(SpineTransformConstraint, SpineObjectWrapper) + +protected: + static void _bind_methods(); + +public: + void update(); + + int get_order(); + + Ref get_data(); + + Array get_bones(); + + Ref get_target(); + + void set_target(Ref v); + + float get_mix_rotate(); + + void set_mix_rotate(float v); + + float get_mix_x(); + + void set_mix_x(float v); + + float get_mix_y(); + + void set_mix_y(float v); + + float get_mix_scale_x(); + + void set_mix_scale_x(float v); + + float get_mix_scale_y(); + + void set_mix_scale_y(float v); + + float get_mix_shear_y(); + + void set_mix_shear_y(float v); + + bool is_active(); + + void set_active(bool v); +}; diff --git a/modules/spine_godot/SpineTransformConstraintData.cpp b/modules/spine_godot/SpineTransformConstraintData.cpp new file mode 100644 index 000000000000..3a01fc073191 --- /dev/null +++ b/modules/spine_godot/SpineTransformConstraintData.cpp @@ -0,0 +1,142 @@ +/****************************************************************************** + * 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 "SpineTransformConstraintData.h" +#include "SpineCommon.h" + +void SpineTransformConstraintData::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_bones"), &SpineTransformConstraintData::get_bones); + ClassDB::bind_method(D_METHOD("get_target"), &SpineTransformConstraintData::get_target); + ClassDB::bind_method(D_METHOD("get_mix_rotate"), &SpineTransformConstraintData::get_mix_rotate); + ClassDB::bind_method(D_METHOD("get_mix_x"), &SpineTransformConstraintData::get_mix_x); + ClassDB::bind_method(D_METHOD("get_mix_y"), &SpineTransformConstraintData::get_mix_y); + ClassDB::bind_method(D_METHOD("get_mix_scale_x"), &SpineTransformConstraintData::get_mix_scale_x); + ClassDB::bind_method(D_METHOD("get_mix_scale_y"), &SpineTransformConstraintData::get_mix_scale_y); + ClassDB::bind_method(D_METHOD("get_mix_shear_y"), &SpineTransformConstraintData::get_mix_shear_y); + ClassDB::bind_method(D_METHOD("get_offset_rotation"), &SpineTransformConstraintData::get_offset_rotation); + ClassDB::bind_method(D_METHOD("get_offset_x"), &SpineTransformConstraintData::get_offset_x); + ClassDB::bind_method(D_METHOD("get_offset_y"), &SpineTransformConstraintData::get_offset_y); + ClassDB::bind_method(D_METHOD("get_offset_scale_x"), &SpineTransformConstraintData::get_offset_scale_x); + ClassDB::bind_method(D_METHOD("get_offset_scale_y"), &SpineTransformConstraintData::get_offset_scale_y); + ClassDB::bind_method(D_METHOD("get_offset_shear_y"), &SpineTransformConstraintData::get_offset_shear_y); + ClassDB::bind_method(D_METHOD("is_relative"), &SpineTransformConstraintData::is_relative); + ClassDB::bind_method(D_METHOD("is_local"), &SpineTransformConstraintData::is_local); +} + +Array SpineTransformConstraintData::get_bones() { + Array result; + SPINE_CHECK(get_spine_constraint_data(), result) + auto bones = get_spine_constraint_data()->getBones(); + result.resize((int) bones.size()); + for (int i = 0; i < (int) bones.size(); ++i) { + Ref bone_ref(memnew(SpineBoneData)); + bone_ref->set_spine_object(get_spine_owner(), bones[i]); + result[i] = bone_ref; + } + return result; +} + +Ref SpineTransformConstraintData::get_target() { + SPINE_CHECK(get_spine_constraint_data(), nullptr) + auto bone = get_spine_constraint_data()->getTarget(); + if (!bone) return nullptr; + Ref slot_ref(memnew(SpineBoneData)); + slot_ref->set_spine_object(get_spine_owner(), bone); + return slot_ref; +} + +float SpineTransformConstraintData::get_mix_rotate() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixRotate(); +} + +float SpineTransformConstraintData::get_mix_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixX(); +} + +float SpineTransformConstraintData::get_mix_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixY(); +} + +float SpineTransformConstraintData::get_mix_scale_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixScaleX(); +} + +float SpineTransformConstraintData::get_mix_scale_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixScaleY(); +} + +float SpineTransformConstraintData::get_mix_shear_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getMixShearY(); +} + +float SpineTransformConstraintData::get_offset_rotation() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetRotation(); +} + +float SpineTransformConstraintData::get_offset_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetX(); +} + +float SpineTransformConstraintData::get_offset_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetY(); +} + +float SpineTransformConstraintData::get_offset_scale_x() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetScaleX(); +} + +float SpineTransformConstraintData::get_offset_scale_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetScaleY(); +} + +float SpineTransformConstraintData::get_offset_shear_y() { + SPINE_CHECK(get_spine_constraint_data(), 0) + return get_spine_constraint_data()->getOffsetShearY(); +} + +bool SpineTransformConstraintData::is_relative() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isRelative(); +} + +bool SpineTransformConstraintData::is_local() { + SPINE_CHECK(get_spine_constraint_data(), false) + return get_spine_constraint_data()->isLocal(); +} diff --git a/modules/spine_godot/SpineTransformConstraintData.h b/modules/spine_godot/SpineTransformConstraintData.h new file mode 100644 index 000000000000..7b0ef7607925 --- /dev/null +++ b/modules/spine_godot/SpineTransformConstraintData.h @@ -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. + *****************************************************************************/ + +#pragma once + +#include "SpineConstraintData.h" +#include "SpineBoneData.h" +#include + +class SpineTransformConstraintData : public SpineConstraintData { + GDCLASS(SpineTransformConstraintData, SpineConstraintData) + + spine::TransformConstraintData *get_spine_constraint_data() { return (spine::TransformConstraintData *) SpineConstraintData::get_spine_object(); } + +protected: + static void _bind_methods(); + +public: + Array get_bones(); + + Ref get_target(); + + float get_mix_rotate(); + + float get_mix_x(); + + float get_mix_y(); + + float get_mix_scale_x(); + + float get_mix_scale_y(); + + float get_mix_shear_y(); + + float get_offset_rotation(); + + float get_offset_x(); + + float get_offset_y(); + + float get_offset_scale_x(); + + float get_offset_scale_y(); + + float get_offset_shear_y(); + + bool is_relative(); + + bool is_local(); +}; diff --git a/modules/spine_godot/config.py b/modules/spine_godot/config.py new file mode 100644 index 000000000000..4cb86dcdc645 --- /dev/null +++ b/modules/spine_godot/config.py @@ -0,0 +1,38 @@ +def can_build(env, platform): + return env.get("module_spine_godot_enabled", True) + +def configure(env): + pass + +def get_doc_path(): + return "docs" + +def get_doc_classes(): + return [ + "SpineAnimation", + "SpineAnimationState", + "SpineAnimationTrack", + "SpineAtlasResource", + "SpineAttachment", + "SpineBone", + "SpineBoneData", + "SpineBoneNode", + "SpineConstraintData", + "SpineEvent", + "SpineIkConstraint", + "SpineIkConstraintData", + "SpinePathConstraint", + "SpinePathConstraintData", + "SpineSkeleton", + "SpineSkeletonDataResource", + "SpineSkeletonFileResource", + "SpineSkin", + "SpineSlot", + "SpineSlotData", + "SpineSlotNode", + "SpineSprite", + "SpineTimeline", + "SpineTrackEntry", + "SpineTransformConstraint", + "SpineTransformConstraintData" + ] diff --git a/modules/spine_godot/icons/SpineAnimationTrack.svg b/modules/spine_godot/icons/SpineAnimationTrack.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineAnimationTrack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineAtlasResource.svg b/modules/spine_godot/icons/SpineAtlasResource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineAtlasResource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineBoneNode.svg b/modules/spine_godot/icons/SpineBoneNode.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineBoneNode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineSkeletonDataResource.svg b/modules/spine_godot/icons/SpineSkeletonDataResource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineSkeletonDataResource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineSkeletonFileResource.svg b/modules/spine_godot/icons/SpineSkeletonFileResource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineSkeletonFileResource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineSlotNode.svg b/modules/spine_godot/icons/SpineSlotNode.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineSlotNode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/SpineSprite.svg b/modules/spine_godot/icons/SpineSprite.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/SpineSprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_animation_track.svg b/modules/spine_godot/icons/icon_spine_animation_track.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_animation_track.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_atlas_resource.svg b/modules/spine_godot/icons/icon_spine_atlas_resource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_atlas_resource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_bone_node.svg b/modules/spine_godot/icons/icon_spine_bone_node.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_bone_node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_skeleton_data_resource.svg b/modules/spine_godot/icons/icon_spine_skeleton_data_resource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_skeleton_data_resource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_skeleton_file_resource.svg b/modules/spine_godot/icons/icon_spine_skeleton_file_resource.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_skeleton_file_resource.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_slot_node.svg b/modules/spine_godot/icons/icon_spine_slot_node.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_slot_node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/icons/icon_spine_sprite.svg b/modules/spine_godot/icons/icon_spine_sprite.svg new file mode 100644 index 000000000000..705bfb161c6c --- /dev/null +++ b/modules/spine_godot/icons/icon_spine_sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/spine_godot/register_types.cpp b/modules/spine_godot/register_types.cpp new file mode 100644 index 000000000000..76176ab2ce76 --- /dev/null +++ b/modules/spine_godot/register_types.cpp @@ -0,0 +1,229 @@ +/****************************************************************************** + * 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 "SpineCommon.h" +#ifndef SPINE_GODOT_EXTENSION +#include "modules/register_module_types.h" +#endif +#include "register_types.h" +#include "SpineEditorPlugin.h" +#include "SpineAtlasResource.h" +#include "SpineSkeletonFileResource.h" +#include "SpineSkeletonDataResource.h" +#include "SpineSprite.h" +#include "SpineSkeleton.h" +#include "SpineAnimationState.h" +#include "SpineAnimationTrack.h" +#include "SpineEventData.h" +#include "SpineEvent.h" +#include "SpineTrackEntry.h" +#include "SpineBoneData.h" +#include "SpineSlotData.h" +#include "SpineAttachment.h" +#include "SpineConstraintData.h" +#include "SpineSkin.h" +#include "SpineIkConstraintData.h" +#include "SpineTransformConstraintData.h" +#include "SpinePathConstraintData.h" +#include "SpinePhysicsConstraintData.h" +#include "SpineTimeline.h" +#include "SpineConstant.h" +#include "SpineSlotNode.h" +#include "SpineBoneNode.h" +#include "spine/Bone.h" + +static SpineAtlasResourceFormatLoader *atlas_loader; +static SpineAtlasResourceFormatSaver *atlas_saver; +static SpineSkeletonFileResourceFormatLoader *skeleton_file_loader; +static SpineSkeletonFileResourceFormatSaver *skeleton_file_saver; + +#ifdef TOOLS_ENABLED +#ifdef SPINE_GODOT_EXTENSION +#include +#else +#include "editor/editor_node.h" +#include "SpineEditorPlugin.h" + +static void editor_init_callback() { + EditorNode::get_singleton()->add_editor_plugin(memnew(SpineEditorPlugin(EditorNode::get_singleton()))); +} +#endif +#endif + +#ifdef SPINE_GODOT_EXTENSION +void initialize_spine_godot_module(ModuleInitializationLevel level) { + if (level == MODULE_INITIALIZATION_LEVEL_EDITOR) { +#ifdef TOOLS_ENABLED + GDREGISTER_CLASS(SpineAtlasResourceImportPlugin); + GDREGISTER_CLASS(SpineJsonResourceImportPlugin); + GDREGISTER_CLASS(SpineBinaryResourceImportPlugin); + GDREGISTER_CLASS(SpineSkeletonDataResourceInspectorPlugin); + GDREGISTER_CLASS(SpineEditorPlugin); + EditorPlugins::add_plugin_class(StringName("SpineEditorPlugin")); +#endif + } + if (level != MODULE_INITIALIZATION_LEVEL_SCENE) return; +#else +#if VERSION_MAJOR > 3 +void initialize_spine_godot_module(ModuleInitializationLevel level) { + if (level == MODULE_INITIALIZATION_LEVEL_EDITOR) { +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(editor_init_callback); + GDREGISTER_CLASS(SpineEditorPropertyAnimationMixes); + return; +#endif + } + if (level != MODULE_INITIALIZATION_LEVEL_CORE) return; +#else +void register_spine_godot_types() { +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(editor_init_callback); + GDREGISTER_CLASS(SpineEditorPropertyAnimationMixes); +#endif +#endif +#endif + spine::Bone::setYDown(true); + + GDREGISTER_CLASS(SpineAtlasResourceFormatLoader); + GDREGISTER_CLASS(SpineAtlasResourceFormatSaver); + GDREGISTER_CLASS(SpineSkeletonFileResourceFormatLoader); + GDREGISTER_CLASS(SpineSkeletonFileResourceFormatSaver); + + GDREGISTER_CLASS(SpineObjectWrapper); + GDREGISTER_CLASS(SpineAtlasResource); + GDREGISTER_CLASS(SpineSkeletonFileResource); + GDREGISTER_CLASS(SpineSkeletonDataResource); + GDREGISTER_CLASS(SpineAnimationMix); + GDREGISTER_CLASS(SpineSprite); + GDREGISTER_CLASS(SpineMesh2D); + GDREGISTER_CLASS(SpineSkeleton); + GDREGISTER_CLASS(SpineAnimationState); + GDREGISTER_CLASS(SpineAnimation); + GDREGISTER_CLASS(SpineEventData); + GDREGISTER_CLASS(SpineTrackEntry); + GDREGISTER_CLASS(SpineEvent); + GDREGISTER_CLASS(SpineBoneData); + GDREGISTER_CLASS(SpineSlotData); + GDREGISTER_CLASS(SpineAttachment); + GDREGISTER_CLASS(SpineSkinEntry); + GDREGISTER_CLASS(SpineConstraintData); + GDREGISTER_CLASS(SpineSkin); + GDREGISTER_CLASS(SpineIkConstraintData); + GDREGISTER_CLASS(SpineTransformConstraintData); + GDREGISTER_CLASS(SpinePathConstraintData); + GDREGISTER_CLASS(SpinePhysicsConstraintData); + GDREGISTER_CLASS(SpineBone); + GDREGISTER_CLASS(SpineSlot); + GDREGISTER_CLASS(SpineIkConstraint); + GDREGISTER_CLASS(SpinePathConstraint); + GDREGISTER_CLASS(SpineTransformConstraint); + GDREGISTER_CLASS(SpinePhysicsConstraint); + GDREGISTER_CLASS(SpineTimeline); + GDREGISTER_CLASS(SpineConstant); + + GDREGISTER_CLASS(SpineSlotNode); + GDREGISTER_CLASS(SpineBoneNode); +#ifndef SPINE_GODOT_EXTENSION + GDREGISTER_CLASS(SpineAnimationTrack); +#endif + +#ifdef SPINE_GODOT_EXTENSION + atlas_loader = memnew(SpineAtlasResourceFormatLoader); + ResourceLoader::get_singleton()->add_resource_format_loader(atlas_loader); + + atlas_saver = memnew(SpineAtlasResourceFormatSaver); + ResourceSaver::get_singleton()->add_resource_format_saver(atlas_saver); + + skeleton_file_loader = memnew(SpineSkeletonFileResourceFormatLoader); + ResourceLoader::get_singleton()->add_resource_format_loader(skeleton_file_loader); + + skeleton_file_saver = memnew(SpineSkeletonFileResourceFormatSaver); + ResourceSaver::get_singleton()->add_resource_format_saver(skeleton_file_saver); +#else +#if VERSION_MAJOR > 3 + atlas_loader = memnew(SpineAtlasResourceFormatLoader); + ResourceLoader::add_resource_format_loader(atlas_loader); + + atlas_saver = memnew(SpineAtlasResourceFormatSaver); + ResourceSaver::add_resource_format_saver(atlas_saver); + + skeleton_file_loader = memnew(SpineSkeletonFileResourceFormatLoader); + ResourceLoader::add_resource_format_loader(skeleton_file_loader); + + skeleton_file_saver = memnew(SpineSkeletonFileResourceFormatSaver); + ResourceSaver::add_resource_format_saver(skeleton_file_saver); +#else + atlas_loader = memnew(SpineAtlasResourceFormatLoader); + ResourceLoader::add_resource_format_loader(atlas_loader); + + atlas_saver = memnew(SpineAtlasResourceFormatSaver); + ResourceSaver::add_resource_format_saver(atlas_saver); + + skeleton_file_loader = memnew(SpineSkeletonFileResourceFormatLoader); + ResourceLoader::add_resource_format_loader(skeleton_file_loader); + + skeleton_file_saver = memnew(SpineSkeletonFileResourceFormatSaver); + ResourceSaver::add_resource_format_saver(skeleton_file_saver); +#endif +#endif +} + +#if VERSION_MAJOR > 3 +void uninitialize_spine_godot_module(ModuleInitializationLevel level) { + if (level == MODULE_INITIALIZATION_LEVEL_SCENE) { + SpineSprite::clear_statics(); + return; + } + if (level != MODULE_INITIALIZATION_LEVEL_CORE) return; +#else +void unregister_spine_godot_types() { +#endif +#ifdef SPINE_GODOT_EXTENSION + ResourceLoader::get_singleton()->remove_resource_format_loader(atlas_loader); + ResourceSaver::get_singleton()->remove_resource_format_saver(atlas_saver); + ResourceLoader::get_singleton()->remove_resource_format_loader(skeleton_file_loader); + ResourceSaver::get_singleton()->remove_resource_format_saver(skeleton_file_saver); +#else + ResourceLoader::remove_resource_format_loader(atlas_loader); + ResourceSaver::remove_resource_format_saver(atlas_saver); + ResourceLoader::remove_resource_format_loader(skeleton_file_loader); + ResourceSaver::remove_resource_format_saver(skeleton_file_saver); +#endif +} + + +#ifdef SPINE_GODOT_EXTENSION +extern "C" GDExtensionBool GDE_EXPORT spine_godot_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + init_obj.register_initializer(initialize_spine_godot_module); + init_obj.register_terminator(uninitialize_spine_godot_module); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_CORE); + return init_obj.init(); +} +#endif diff --git a/modules/spine_godot/register_types.h b/modules/spine_godot/register_types.h new file mode 100644 index 000000000000..2d4b6cb85e20 --- /dev/null +++ b/modules/spine_godot/register_types.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#include "SpineCommon.h" + +#if VERSION_MAJOR > 3 +#ifndef SPINE_GODOT_EXTENSION +#include "modules/register_module_types.h" +#endif +void initialize_spine_godot_module(ModuleInitializationLevel level); +void uninitialize_spine_godot_module(ModuleInitializationLevel level); +#else +void register_spine_godot_types(); +void unregister_spine_godot_types(); +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/Animation.h b/modules/spine_godot/spine-cpp/include/spine/Animation.h new file mode 100644 index 000000000000..7c9355c35fca --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Animation.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Animation_h +#define Spine_Animation_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class Timeline; + + class Skeleton; + + class Event; + + class AnimationState; + + class SP_API Animation : public SpineObject { + friend class AnimationState; + + friend class TrackEntry; + + friend class AnimationStateData; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + Animation(const String &name, Vector &timelines, float duration); + + ~Animation(); + + /// Applies all the animation's timelines to the specified skeleton. + /// See also Timeline::apply(Skeleton&, float, float, Vector, float, MixPose, MixDirection) + void apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction); + + const String &getName(); + + Vector &getTimelines(); + + bool hasTimeline(Vector &ids); + + float getDuration(); + + void setDuration(float inValue); + + /// @param target After the first and before the last entry. + static int search(Vector &values, float target); + + static int search(Vector &values, float target, int step); + private: + Vector _timelines; + HashMap _timelineIds; + float _duration; + String _name; + }; +} + +#endif /* Spine_Animation_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AnimationState.h b/modules/spine_godot/spine-cpp/include/spine/AnimationState.h new file mode 100644 index 000000000000..501e7226aa4a --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AnimationState.h @@ -0,0 +1,521 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationState_h +#define Spine_AnimationState_h + +#include +#include +#include +#include +#include +#include +#include +#include "Slot.h" + +#ifdef SPINE_USE_STD_FUNCTION +#include +#endif + +namespace spine { + enum EventType { + EventType_Start = 0, + EventType_Interrupt, + EventType_End, + EventType_Complete, + EventType_Dispose, + EventType_Event + }; + + class AnimationState; + + class TrackEntry; + + class Animation; + + class Event; + + class AnimationStateData; + + class Skeleton; + + class RotateTimeline; + + class AttachmentTimeline; + +#ifdef SPINE_USE_STD_FUNCTION + typedef std::function AnimationStateListener; +#else + + typedef void (*AnimationStateListener)(AnimationState *state, EventType type, TrackEntry *entry, Event *event); + +#endif + + /// Abstract class to inherit from to create a callback object + class SP_API AnimationStateListenerObject { + public: + AnimationStateListenerObject() {}; + + virtual ~AnimationStateListenerObject() {}; + public: + /// The callback function to be called + virtual void callback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) = 0; + }; + + /// State for the playback of an animation + class SP_API TrackEntry : public SpineObject, public HasRendererObject { + friend class EventQueue; + + friend class AnimationState; + + public: + TrackEntry(); + + virtual ~TrackEntry(); + + /// The index of the track where this entry is either current or queued. + int getTrackIndex(); + + /// The animation to apply for this track entry. + Animation *getAnimation(); + + TrackEntry *getPrevious(); + + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + bool getLoop(); + + void setLoop(bool inValue); + + /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + /// of being mixed out. + /// + /// When mixing between animations that key the same property, if a lower track also keys that property then the value will + /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation + /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + /// keys the property, only when a higher track also keys the property. + /// + /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the + /// previous animation. + bool getHoldPrevious(); + + void setHoldPrevious(bool inValue); + + bool getReverse(); + + void setReverse(bool inValue); + + bool getShortestRotation(); + + void setShortestRotation(bool inValue); + + /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing + /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the + /// track entry will become the current track entry. + float getDelay(); + + void setDelay(float inValue); + + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// TrackEntry.AnimationTime. The track time can be set to start the animation at a time other than 0, without affecting looping. + float getTrackTime(); + + void setTrackTime(float inValue); + + /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for + /// non-looping animations and to int.MaxValue for looping animations. If the track end time is reached and no + /// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation, + /// are set to the setup pose and the track is cleared. + /// + /// It may be desired to use AnimationState.addEmptyAnimation(int, float, float) to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + float getTrackEnd(); + + void setTrackEnd(float inValue); + + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the animation start time, it often makes sense to set TrackEntry.AnimationLast to the same value to + /// prevent timeline keys before the start time from triggering. + float getAnimationStart(); + + void setAnimationStart(float inValue); + + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to TrackEntry.AnimationStart at this time. Defaults to the animation duration. + float getAnimationEnd(); + + void setAnimationEnd(float inValue); + + /// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this + /// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time + /// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. + float getAnimationLast(); + + void setAnimationLast(float inValue); + + /// Uses TrackEntry.TrackTime to compute the animation time between TrackEntry.AnimationStart. and + /// TrackEntry.AnimationEnd. When the track time is 0, the animation time is equal to the animation start time. + float getAnimationTime(); + + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + float getTimeScale(); + + void setTimeScale(float inValue); + + /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with + /// this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense + /// to use alpha on track 0 if the skeleton pose is from the last frame render. + float getAlpha(); + + void setAlpha(float inValue); + + /// + /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation + /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out. + float getEventThreshold(); + + void setEventThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the + /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being + /// mixed out. + float getMixAttachmentThreshold(); + + void setMixAttachmentThreshold(float inValue); + + /// When getAlpha() is greater than alphaAttachmentThreshold, attachment timelines are applied. + /// Defaults to 0, so attachment timelines are always applied. */ + float getAlphaAttachmentThreshold(); + + void setAlphaAttachmentThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the + /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being + /// mixed out. + float getMixDrawOrderThreshold(); + + void setMixDrawOrderThreshold(float inValue); + + /// The animation queued to start after this animation, or NULL. + TrackEntry *getNext(); + + /// Returns true if at least one loop has been completed. + bool isComplete(); + + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// TrackEntry.MixDuration when the mix is complete. + float getMixTime(); + + void setMixTime(float inValue); + + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// AnimationStateData based on the animation before this animation (if any). + /// + /// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix. + /// In that case, the mixDuration must be set before AnimationState.update(float) is next called. + /// + /// When using AnimationState::addAnimation(int, Animation, bool, float) with a delay + /// less than or equal to 0, note the Delay is set using the mix duration from the AnimationStateData + float getMixDuration(); + + void setMixDuration(float inValue); + + void setMixDuration(float mixDuration, float delay); + + MixBlend getMixBlend(); + + void setMixBlend(MixBlend blend); + + /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no + /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. + TrackEntry *getMixingFrom(); + + /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. + /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + TrackEntry *getMixingTo(); + + /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the + /// long way around when using alpha and starting animations on other tracks. + /// + /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. + /// The two rotations likely change over time, so which direction is the short or long way also changes. + /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. + /// TrackEntry chooses the short way the first time it is applied and remembers that direction. + void resetRotationDirections(); + + float getTrackComplete(); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + /// Returns true if this track entry has been applied at least once. + /// + /// See AnimationState::apply(Skeleton). + bool wasApplied(); + + /// Returns true if there is a getNext() track entry that is ready to become the current track entry during the + /// next AnimationState::update(float)} + bool isNextReady () { + return _next != NULL && _nextTrackLast - _next->_delay >= 0; + } + + private: + Animation *_animation; + TrackEntry *_previous; + TrackEntry *_next; + TrackEntry *_mixingFrom; + TrackEntry *_mixingTo; + int _trackIndex; + + bool _loop, _holdPrevious, _reverse, _shortestRotation; + float _eventThreshold, _mixAttachmentThreshold, _alphaAttachmentThreshold, _mixDrawOrderThreshold; + float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; + float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; + float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; + MixBlend _mixBlend; + Vector _timelineMode; + Vector _timelineHoldMix; + Vector _timelinesRotation; + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + void reset(); + }; + + class SP_API EventQueueEntry : public SpineObject { + friend class EventQueue; + + public: + EventType _type; + TrackEntry *_entry; + Event *_event; + + EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event = NULL); + }; + + class SP_API EventQueue : public SpineObject { + friend class AnimationState; + + private: + Vector _eventQueueEntries; + AnimationState &_state; + bool _drainDisabled; + + static EventQueue *newEventQueue(AnimationState &state); + + static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event = NULL); + + EventQueue(AnimationState &state); + + ~EventQueue(); + + void start(TrackEntry *entry); + + void interrupt(TrackEntry *entry); + + void end(TrackEntry *entry); + + void dispose(TrackEntry *entry); + + void complete(TrackEntry *entry); + + void event(TrackEntry *entry, Event *event); + + /// Raises all events in the queue and drains the queue. + void drain(); + }; + + class SP_API AnimationState : public SpineObject, public HasRendererObject { + friend class TrackEntry; + + friend class EventQueue; + + public: + explicit AnimationState(AnimationStateData *data); + + ~AnimationState(); + + /// Increments the track entry times, setting queued animations as current if needed + /// @param delta delta time + void update(float delta); + + /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + /// animation state can be applied to multiple skeletons to pose them identically. + bool apply(Skeleton &skeleton); + + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTracks(); + + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTrack(size_t trackIndex); + + /// Sets an animation by name. setAnimation(int, Animation, bool) + TrackEntry *setAnimation(size_t trackIndex, const String &animationName, bool loop); + + /// Sets the current animation for a track, discarding any queued animations. + /// @param loop If true, the animation will repeat. + /// If false, it will not, instead its last frame is applied if played beyond its duration. + /// In either case TrackEntry.TrackEnd determines when the track is cleared. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose. + TrackEntry *setAnimation(size_t trackIndex, Animation *animation, bool loop); + + /// Queues an animation by name. + /// addAnimation(int, Animation, bool, float) + TrackEntry *addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay); + + /// Adds an animation to be played delay seconds after the current or last queued animation + /// for a track. If the track is empty, it is equivalent to calling setAnimation. + /// @param delay + /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + /// + /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose + TrackEntry *addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay); + + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + TrackEntry *setEmptyAnimation(size_t trackIndex, float mixDuration); + + /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the + /// specified mix duration. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// + /// @param trackIndex Track number. + /// @param mixDuration Mix duration. + /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + TrackEntry *addEmptyAnimation(size_t trackIndex, float mixDuration, float delay); + + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + void setEmptyAnimations(float mixDuration); + + /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing. + TrackEntry *getCurrent(size_t trackIndex); + + AnimationStateData *getData(); + + /// A list of tracks that have animations, which may contain NULLs. + Vector &getTracks(); + + float getTimeScale(); + + void setTimeScale(float inValue); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + void disableQueue(); + + void enableQueue(); + + void setManualTrackEntryDisposal(bool inValue); + + bool getManualTrackEntryDisposal(); + + void disposeTrackEntry(TrackEntry *entry); + + private: + static const int Subsequent = 0; + static const int First = 1; + static const int HoldSubsequent = 2; + static const int HoldFirst = 3; + static const int HoldMix = 4; + + static const int Setup = 1; + static const int Current = 2; + + AnimationStateData *_data; + + Pool _trackEntryPool; + Vector _tracks; + Vector _events; + EventQueue *_queue; + + HashMap _propertyIDs; + bool _animationsChanged; + + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + int _unkeyedState; + + float _timeScale; + + bool _manualTrackEntryDisposal; + + static Animation *getEmptyAnimation(); + + static void + applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, MixBlend pose, + Vector &timelinesRotation, size_t i, bool firstFrame); + + void applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float animationTime, + MixBlend pose, bool firstFrame); + + /// Returns true when all mixing from entries are complete. + bool updateMixingFrom(TrackEntry *to, float delta); + + float applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend currentPose); + + void queueEvents(TrackEntry *entry, float animationTime); + + /// Sets the active TrackEntry for a given track number. + void setCurrent(size_t index, TrackEntry *current, bool interrupt); + + /// Removes the next entry and all entries after it for the specified entry. */ + void clearNext(TrackEntry *entry); + + TrackEntry *expandToIndex(size_t index); + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// @param last May be NULL. + TrackEntry *newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last); + + void animationsChanged(); + + void computeHold(TrackEntry *entry); + + void setAttachment(Skeleton &skeleton, spine::Slot &slot, const String &attachmentName, bool attachments); + }; +} + +#endif /* Spine_AnimationState_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AnimationStateData.h b/modules/spine_godot/spine-cpp/include/spine/AnimationStateData.h new file mode 100644 index 000000000000..a315e7695061 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AnimationStateData.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationStateData_h +#define Spine_AnimationStateData_h + +#include +#include +#include + +#include + +namespace spine { + class SkeletonData; + + class Animation; + + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + class SP_API AnimationStateData : public SpineObject { + friend class AnimationState; + + public: + explicit AnimationStateData(SkeletonData *skeletonData); + + /// The SkeletonData to look up animations when they are specified by name. + SkeletonData *getSkeletonData(); + + /// The mix duration to use when no mix duration has been specifically defined between two animations. + float getDefaultMix(); + + void setDefaultMix(float inValue); + + /// Sets a mix duration by animation names. + void setMix(const String &fromName, const String &toName, float duration); + + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + void setMix(Animation *from, Animation *to, float duration); + + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + float getMix(Animation *from, Animation *to); + + /// Removes all mixes and sets the default mix to 0. + void clear(); + + private: + class AnimationPair : public SpineObject { + public: + Animation *_a1; + Animation *_a2; + + explicit AnimationPair(Animation *a1 = NULL, Animation *a2 = NULL); + + bool operator==(const AnimationPair &other) const; + }; + + SkeletonData *_skeletonData; + float _defaultMix; + HashMap _animationToMixTime; + }; +} + +#endif /* Spine_AnimationStateData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Atlas.h b/modules/spine_godot/spine-cpp/include/spine/Atlas.h new file mode 100644 index 000000000000..f6c7931da170 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Atlas.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Atlas_h +#define Spine_Atlas_h + +#include +#include +#include +#include +#include +#include "TextureRegion.h" + +namespace spine { + enum Format { + Format_Alpha, + Format_Intensity, + Format_LuminanceAlpha, + Format_RGB565, + Format_RGBA4444, + Format_RGB888, + Format_RGBA8888 + }; + + // Our TextureFilter collides with UE4's TextureFilter in unity builds. We rename + // TextureFilter to SpineTextureFilter in UE4. +#ifdef SPINE_UE4 + #define TEXTURE_FILTER_ENUM SpineTextureFilter +#else + #define TEXTURE_FILTER_ENUM TextureFilter +#endif + + enum TEXTURE_FILTER_ENUM { + TextureFilter_Unknown, + TextureFilter_Nearest, + TextureFilter_Linear, + TextureFilter_MipMap, + TextureFilter_MipMapNearestNearest, + TextureFilter_MipMapLinearNearest, + TextureFilter_MipMapNearestLinear, + TextureFilter_MipMapLinearLinear + }; + + enum TextureWrap { + TextureWrap_MirroredRepeat, + TextureWrap_ClampToEdge, + TextureWrap_Repeat + }; + + class SP_API AtlasPage : public SpineObject { + public: + String name; + String texturePath; + Format format; + TEXTURE_FILTER_ENUM minFilter; + TEXTURE_FILTER_ENUM magFilter; + TextureWrap uWrap; + TextureWrap vWrap; + int width, height; + bool pma; + int index; + void *texture; + + explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888), + minFilter(TextureFilter_Nearest), + magFilter(TextureFilter_Nearest), uWrap(TextureWrap_ClampToEdge), + vWrap(TextureWrap_ClampToEdge), width(0), height(0), pma(false), index(0), texture(NULL) { + } + }; + + class SP_API AtlasRegion : public TextureRegion { + public: + AtlasPage *page; + String name; + int index; + int x, y; + Vector splits; + Vector pads; + Vector names; + Vector values; + }; + + class TextureLoader; + + class SP_API Atlas : public SpineObject { + public: + Atlas(const String &path, TextureLoader *textureLoader, bool createTexture = true); + + Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture = true); + + ~Atlas(); + + void flipV(); + + /// Returns the first region found with the specified name. This method uses String comparison to find the region, so the result + /// should be cached rather than calling this method multiple times. + /// @return The region, or NULL. + AtlasRegion *findRegion(const String &name); + + Vector &getPages(); + + Vector &getRegions(); + + private: + Vector _pages; + Vector _regions; + TextureLoader *_textureLoader; + + void load(const char *begin, int length, const char *dir, bool createTexture); + }; +} + +#endif /* Spine_Atlas_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AtlasAttachmentLoader.h b/modules/spine_godot/spine-cpp/include/spine/AtlasAttachmentLoader.h new file mode 100644 index 000000000000..85111674855a --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AtlasAttachmentLoader.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AtlasAttachmentLoader_h +#define Spine_AtlasAttachmentLoader_h + +#include +#include +#include + + +namespace spine { + class Atlas; + + class AtlasRegion; + + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data about Loading Skeleton Data in the Spine Runtimes Guide. + class SP_API AtlasAttachmentLoader : public AttachmentLoader { + public: + RTTI_DECL + + explicit AtlasAttachmentLoader(Atlas *atlas); + + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name); + + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name); + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name); + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name); + + virtual void configureAttachment(Attachment *attachment); + + AtlasRegion *findRegion(const String &name); + + private: + Atlas *_atlas; + }; +} + +#endif /* Spine_AtlasAttachmentLoader_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Attachment.h b/modules/spine_godot/spine-cpp/include/spine/Attachment.h new file mode 100644 index 000000000000..ae72b18af42e --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Attachment.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Attachment_h +#define Spine_Attachment_h + +#include +#include +#include + +namespace spine { + class SP_API Attachment : public SpineObject { + RTTI_DECL + + public: + explicit Attachment(const String &name); + + virtual ~Attachment(); + + const String &getName() const; + + virtual Attachment *copy() = 0; + + int getRefCount(); + + void reference(); + + void dereference(); + + private: + const String _name; + int _refCount; + }; +} + +#endif /* Spine_Attachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AttachmentLoader.h b/modules/spine_godot/spine-cpp/include/spine/AttachmentLoader.h new file mode 100644 index 000000000000..9f02302fdb09 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AttachmentLoader.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentLoader_h +#define Spine_AttachmentLoader_h + +#include +#include +#include + +namespace spine { + class Skin; + + class Attachment; + + class RegionAttachment; + + class MeshAttachment; + + class BoundingBoxAttachment; + + class PathAttachment; + + class PointAttachment; + + class ClippingAttachment; + + class Sequence; + + class SP_API AttachmentLoader : public SpineObject { + public: + RTTI_DECL + + AttachmentLoader(); + + virtual ~AttachmentLoader(); + + /// @return May be NULL to not load any attachment. + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name) = 0; + + /// @return May be NULL to not load any attachment + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name) = 0; + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name) = 0; + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name) = 0; + + virtual void configureAttachment(Attachment *attachment) = 0; + }; +} + +#endif /* Spine_AttachmentLoader_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AttachmentTimeline.h b/modules/spine_godot/spine-cpp/include/spine/AttachmentTimeline.h new file mode 100644 index 000000000000..a42280fa5eb8 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AttachmentTimeline.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentTimeline_h +#define Spine_AttachmentTimeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + + class Skeleton; + + class Slot; + + class Event; + + class SP_API AttachmentTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AttachmentTimeline(size_t frameCount, int slotIndex); + + virtual ~AttachmentTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, const String &attachmentName); + + Vector &getAttachmentNames(); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector _attachmentNames; + + void setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName); + }; +} + +#endif /* Spine_AttachmentTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/AttachmentType.h b/modules/spine_godot/spine-cpp/include/spine/AttachmentType.h new file mode 100644 index 000000000000..8b1a7ed25ed4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/AttachmentType.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_AttachmentType_h +#define Spine_AttachmentType_h + +namespace spine { + enum AttachmentType { + AttachmentType_Region, + AttachmentType_Boundingbox, + AttachmentType_Mesh, + AttachmentType_Linkedmesh, + AttachmentType_Path, + AttachmentType_Point, + AttachmentType_Clipping + }; +} + +#endif /* Spine_AttachmentType_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/BlendMode.h b/modules/spine_godot/spine-cpp/include/spine/BlendMode.h new file mode 100644 index 000000000000..f34e95b1429a --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/BlendMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BlendMode_h +#define Spine_BlendMode_h + +namespace spine { + enum BlendMode { + BlendMode_Normal = 0, + BlendMode_Additive, + BlendMode_Multiply, + BlendMode_Screen + }; +} + +#endif /* Spine_BlendMode_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/BlockAllocator.h b/modules/spine_godot/spine-cpp/include/spine/BlockAllocator.h new file mode 100644 index 000000000000..749a69cbd1f2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/BlockAllocator.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BlockAllocator_h +#define Spine_BlockAllocator_h + +#include +#include +#include +#include +#include + +namespace spine { + struct Block { + int size; + int allocated; + uint8_t *memory; + + int free() { + return size - allocated; + } + + bool canFit(int numBytes) { + return free() >= numBytes; + } + + uint8_t *allocate(int numBytes) { + uint8_t *ptr = memory + allocated; + allocated += numBytes; + return ptr; + } + }; + + class BlockAllocator : public SpineObject { + int initialBlockSize; + Vector blocks; + + public: + BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) { + blocks.add(newBlock(initialBlockSize)); + } + + ~BlockAllocator() { + for (int i = 0, n = (int) blocks.size(); i < n; i++) { + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + } + + template + T *allocate(size_t num) { + return (T *) _allocate((int) (sizeof(T) * num)); + } + + void compress() { + if (blocks.size() == 1) { + blocks[0].allocated = 0; + return; + } + int totalSize = 0; + for (int i = 0, n = (int)blocks.size(); i < n; i++) { + totalSize += blocks[i].size; + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + blocks.clear(); + blocks.add(newBlock(totalSize)); + } + + private: + void *_allocate(int numBytes) { + // 16-byte align allocations + int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0); + Block *block = &blocks[blocks.size() - 1]; + if (!block->canFit(alignedNumBytes)) { + blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes))); + block = &blocks[blocks.size() - 1]; + } + return block->allocate(alignedNumBytes); + } + + Block newBlock(int numBytes) { + Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr}; + block.memory = SpineExtension::alloc(block.size, __FILE__, __LINE__); + return block; + } + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/Bone.h b/modules/spine_godot/spine-cpp/include/spine/Bone.h new file mode 100644 index 000000000000..845fc58df463 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Bone.h @@ -0,0 +1,286 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Bone_h +#define Spine_Bone_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class Skeleton; + +/// Stores a bone's current pose. +/// +/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a +/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a +/// constraint or application code modifies the world transform after it was computed from the local transform. + class SP_API Bone : public Updatable { + friend class AnimationState; + + friend class RotateTimeline; + + friend class IkConstraint; + + friend class TransformConstraint; + + friend class VertexAttachment; + + friend class PathConstraint; + + friend class PhysicsConstraint; + + friend class Skeleton; + + friend class RegionAttachment; + + friend class PointAttachment; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class InheritTimeline; + + RTTI_DECL + + public: + static void setYDown(bool inValue); + + static bool isYDown(); + + /// @param parent May be NULL. + Bone(BoneData &data, Skeleton &skeleton, Bone *parent = NULL); + + /// Same as updateWorldTransform. This method exists for Bone to implement Spine::Updatable. + virtual void update(Physics physics); + + /// Computes the world transform using the parent bone and this bone's local transform. + void updateWorldTransform(); + + /// Computes the world transform using the parent bone and the specified local transform. + void + updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY); + + /// Computes the individual applied transform values from the world transform. This can be useful to perform processing using + /// the applied transform after the world transform has been modified directly (eg, by a constraint).. + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. + void updateAppliedTransform(); + + void setToSetupPose(); + + void worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY); + + void worldToParent(float worldX, float worldY, float &outParentX, float &outParentY); + + void localToWorld(float localX, float localY, float &outWorldX, float &outWorldY); + + void parentToWorld(float worldX, float worldY, float &outX, float &outY); + + float worldToLocalRotation(float worldRotation); + + float localToWorldRotation(float localRotation); + + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// @param degrees Degrees. + void rotateWorld(float degrees); + + float getWorldToLocalRotationX(); + + float getWorldToLocalRotationY(); + + BoneData &getData(); + + Skeleton &getSkeleton(); + + Bone *getParent(); + + Vector &getChildren(); + + /// The local X translation. + float getX(); + + void setX(float inValue); + + /// The local Y translation. + float getY(); + + void setY(float inValue); + + /// The local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// The local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// The local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// The local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// The local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The rotation, as calculated by any constraints. + float getAppliedRotation(); + + void setAppliedRotation(float inValue); + + /// The applied local x translation. + float getAX(); + + void setAX(float inValue); + + /// The applied local y translation. + float getAY(); + + void setAY(float inValue); + + /// The applied local scaleX. + float getAScaleX(); + + void setAScaleX(float inValue); + + /// The applied local scaleY. + float getAScaleY(); + + void setAScaleY(float inValue); + + /// The applied local shearX. + float getAShearX(); + + void setAShearX(float inValue); + + /// The applied local shearY. + float getAShearY(); + + void setAShearY(float inValue); + + float getA(); + + void setA(float inValue); + + float getB(); + + void setB(float inValue); + + float getC(); + + void setC(float inValue); + + float getD(); + + void setD(float inValue); + + float getWorldX(); + + void setWorldX(float inValue); + + float getWorldY(); + + void setWorldY(float inValue); + + float getWorldRotationX(); + + float getWorldRotationY(); + + /// Returns the magnitide (always positive) of the world scale X. + float getWorldScaleX(); + + /// Returns the magnitide (always positive) of the world scale Y. + float getWorldScaleY(); + + bool isActive(); + + void setActive(bool inValue); + + Inherit getInherit() { return _inherit; } + + void setInherit(Inherit inValue) { _inherit = inValue; } + + private: + static bool yDown; + + BoneData &_data; + Skeleton &_skeleton; + Bone *_parent; + Vector _children; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + float _ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY; + float _a, _b, _worldX; + float _c, _d, _worldY; + bool _sorted; + bool _active; + Inherit _inherit; + }; +} + +#endif /* Spine_Bone_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/BoneData.h b/modules/spine_godot/spine-cpp/include/spine/BoneData.h new file mode 100644 index 000000000000..eb53487dd7a7 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/BoneData.h @@ -0,0 +1,150 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoneData_h +#define Spine_BoneData_h + +#include +#include +#include +#include + +namespace spine { + class SP_API BoneData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + public: + BoneData(int index, const String &name, BoneData *parent = NULL); + + /// The index of the bone in Skeleton.Bones + int getIndex(); + + /// The name of the bone, which is unique within the skeleton. + const String &getName(); + + /// May be NULL. + BoneData *getParent(); + + float getLength(); + + void setLength(float inValue); + + /// Local X translation. + float getX(); + + void setX(float inValue); + + /// Local Y translation. + float getY(); + + void setY(float inValue); + + /// Local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// Local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// Local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// Local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// Local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The transform mode for how parent world transforms affect this bone. + Inherit getInherit(); + + void setInherit(Inherit inValue); + + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + Color &getColor(); + + const String &getIcon(); + + void setIcon(const String &icon); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + const String _name; + BoneData *_parent; + float _length; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + Inherit _inherit; + bool _skinRequired; + Color _color; + String _icon; + bool _visible; + }; +} + +#endif /* Spine_BoneData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/BoundingBoxAttachment.h b/modules/spine_godot/spine-cpp/include/spine/BoundingBoxAttachment.h new file mode 100644 index 000000000000..b57d796c21f1 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/BoundingBoxAttachment.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoundingBoxAttachment_h +#define Spine_BoundingBoxAttachment_h + +#include +#include +#include + +namespace spine { + /// Attachment that has a polygon for bounds checking. + class SP_API BoundingBoxAttachment : public VertexAttachment { + RTTI_DECL + + public: + explicit BoundingBoxAttachment(const String &name); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Color _color; + }; +} + +#endif /* Spine_BoundingBoxAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/ClippingAttachment.h b/modules/spine_godot/spine-cpp/include/spine/ClippingAttachment.h new file mode 100644 index 000000000000..7969ea859403 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ClippingAttachment.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_ClippingAttachment_h +#define Spine_ClippingAttachment_h + +#include +#include + +namespace spine { + class SlotData; + + class SP_API ClippingAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class SkeletonClipping; + + RTTI_DECL + + public: + explicit ClippingAttachment(const String &name); + + SlotData *getEndSlot(); + + void setEndSlot(SlotData *inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + SlotData *_endSlot; + Color _color; + }; +} + +#endif /* Spine_ClippingAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Color.h b/modules/spine_godot/spine-cpp/include/spine/Color.h new file mode 100644 index 000000000000..9a8cdeb09c64 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Color.h @@ -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. + *****************************************************************************/ + +#ifndef SPINE_COLOR_H +#define SPINE_COLOR_H + +#include + +namespace spine { + class SP_API Color : public SpineObject { + public: + Color() : r(0), g(0), b(0), a(0) { + } + + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) { + clamp(); + } + + inline Color &set(float _r, float _g, float _b, float _a) { + this->r = _r; + this->g = _g; + this->b = _b; + this->a = _a; + clamp(); + return *this; + } + + inline Color &set(float _r, float _g, float _b) { + this->r = _r; + this->g = _g; + this->b = _b; + clamp(); + return *this; + } + + inline Color &set(const Color &other) { + r = other.r; + g = other.g; + b = other.b; + a = other.a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b, float _a) { + this->r += _r; + this->g += _g; + this->b += _b; + this->a += _a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b) { + this->r += _r; + this->g += _g; + this->b += _b; + clamp(); + return *this; + } + + inline Color &add(const Color &other) { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + clamp(); + return *this; + } + + inline Color &clamp() { + r = MathUtil::clamp(this->r, 0, 1); + g = MathUtil::clamp(this->g, 0, 1); + b = MathUtil::clamp(this->b, 0, 1); + a = MathUtil::clamp(this->a, 0, 1); + return *this; + } + + float r, g, b, a; + }; +} + + +#endif //SPINE_COLOR_H diff --git a/modules/spine_godot/spine-cpp/include/spine/ColorTimeline.h b/modules/spine_godot/spine-cpp/include/spine/ColorTimeline.h new file mode 100644 index 000000000000..3760b1bf00ad --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ColorTimeline.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ColorTimeline_h +#define Spine_ColorTimeline_h + +#include + +namespace spine { + class SP_API RGBATimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBATimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 5; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + }; + + class SP_API RGBTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 4; + static const int R = 1; + static const int G = 2; + static const int B = 3; + }; + + class SP_API AlphaTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~AlphaTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + }; + + class SP_API RGBA2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBA2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 8; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + static const int R2 = 5; + static const int G2 = 6; + static const int B2 = 7; + }; + + class SP_API RGB2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGB2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 7; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int R2 = 4; + static const int G2 = 5; + static const int B2 = 6; + }; +} + +#endif /* Spine_ColorTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/ConstraintData.h b/modules/spine_godot/spine-cpp/include/spine/ConstraintData.h new file mode 100644 index 000000000000..ab51f3298c70 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ConstraintData.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Constraint_h +#define Spine_Constraint_h + +#include +#include + +namespace spine { + /// The interface for all constraints. + class SP_API ConstraintData : public SpineObject { + + friend class SkeletonBinary; + + RTTI_DECL + + public: + ConstraintData(const String &name); + + virtual ~ConstraintData(); + + /// The IK constraint's name, which is unique within the skeleton. + const String &getName(); + + /// The ordinal for the order a skeleton's constraints will be applied. + size_t getOrder(); + + void setOrder(size_t inValue); + + /// Whether the constraint is only active for a specific skin. + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + private: + const String _name; + size_t _order; + bool _skinRequired; + }; +} + +#endif /* Spine_Constraint_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/ContainerUtil.h b/modules/spine_godot/spine-cpp/include/spine/ContainerUtil.h new file mode 100644 index 000000000000..410e2eb01f56 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ContainerUtil.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ContainerUtil_h +#define Spine_ContainerUtil_h + +#include +#include +#include +#include +#include + +#include + +namespace spine { + class SP_API ContainerUtil : public SpineObject { + public: + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + template + static void cleanUpVectorOfPointers(Vector &items) { + for (int i = (int) items.size() - 1; i >= 0; i--) { + T *item = items[i]; + + delete item; + + items.removeAt(i); + } + } + + private: + // ctor, copy ctor, and assignment should be private in a Singleton + ContainerUtil(); + + ContainerUtil(const ContainerUtil &); + + ContainerUtil &operator=(const ContainerUtil &); + }; +} + +#endif /* Spine_ContainerUtil_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/CurveTimeline.h b/modules/spine_godot/spine-cpp/include/spine/CurveTimeline.h new file mode 100644 index 000000000000..7eaac0effc8e --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/CurveTimeline.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_CurveTimeline_h +#define Spine_CurveTimeline_h + +#include +#include + +namespace spine { + /// Base class for frames that use an interpolation bezier curve. + class SP_API CurveTimeline : public Timeline { + RTTI_DECL + + public: + explicit CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount); + + virtual ~CurveTimeline(); + + void setLinear(size_t frame); + + void setStepped(size_t frame); + + virtual void + 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); + + float getBezierValue(float time, size_t frame, size_t valueOffset, size_t i); + + Vector &getCurves(); + + protected: + static const int LINEAR = 0; + static const int STEPPED = 1; + static const int BEZIER = 2; + static const int BEZIER_SIZE = 18; + + Vector _curves; // type, x, y, ... + }; + + class SP_API CurveTimeline1 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline1(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline1(); + + void setFrame(size_t frame, float time, float value); + + float getCurveValue(float time); + + float getRelativeValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value); + + float getScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup); + + protected: + static const int ENTRIES = 2; + static const int VALUE = 1; + }; + + class SP_API CurveTimeline2 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline2(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline2(); + + void setFrame(size_t frame, float time, float value1, float value2); + + float getCurveValue(float time); + + protected: + static const int ENTRIES = 3; + static const int VALUE1 = 1; + static const int VALUE2 = 2; + }; +} + +#endif /* Spine_CurveTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Debug.h b/modules/spine_godot/spine-cpp/include/spine/Debug.h new file mode 100644 index 000000000000..914afa55e090 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Debug.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_LOG_H +#define SPINE_LOG_H + +#include +#include + +#include + +namespace spine { + + class SP_API DebugExtension : public SpineExtension { + struct Allocation { + void *address; + size_t size; + const char *fileName; + int line; + + Allocation() : address(NULL), size(0), fileName(NULL), line(0) { + } + + Allocation(void *a, size_t s, const char *f, int l) : address(a), size(s), fileName(f), line(l) { + } + }; + + public: + DebugExtension(SpineExtension *extension) : _extension(extension), _allocations(0), _reallocations(0), + _frees(0) { + } + + void reportLeaks() { + for (std::map::iterator it = _allocated.begin(); it != _allocated.end(); it++) { + printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size, + it->second.address); + } + printf("allocations: %zu, reallocations: %zu, frees: %zu\n", _allocations, _reallocations, _frees); + if (_allocated.empty()) printf("No leaks detected\n"); + } + + void clearAllocations() { + _allocated.clear(); + _usedMemory = 0; + } + + virtual void *_alloc(size_t size, const char *file, int line) { + void *result = _extension->_alloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_calloc(size_t size, const char *file, int line) { + void *result = _extension->_calloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) { + if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size; + _allocated.erase(ptr); + void *result = _extension->_realloc(ptr, size, file, line); + _reallocations++; + _allocated[result] = Allocation(result, size, file, line); + _usedMemory += size; + return result; + } + + virtual void _free(void *mem, const char *file, int line) { + if (_allocated.count(mem)) { + _extension->_free(mem, file, line); + _frees++; + _usedMemory -= _allocated[mem].size; + _allocated.erase(mem); + return; + } + + printf("%s:%i (address %p): Double free or not allocated through SpineExtension\n", file, line, mem); + _extension->_free(mem, file, line); + } + + virtual char *_readFile(const String &path, int *length) { + auto data = _extension->_readFile(path, length); + + if (_allocated.count(data) == 0) { + _allocated[data] = Allocation(data, sizeof(char) * (*length), nullptr, 0); + _allocations++; + _usedMemory += sizeof(char) * (*length); + } + + return data; + } + + size_t getUsedMemory() { + return _usedMemory; + } + + private: + SpineExtension *_extension; + std::map _allocated; + size_t _allocations; + size_t _reallocations; + size_t _frees; + size_t _usedMemory; + }; +} + + +#endif //SPINE_LOG_H diff --git a/modules/spine_godot/spine-cpp/include/spine/DeformTimeline.h b/modules/spine_godot/spine-cpp/include/spine/DeformTimeline.h new file mode 100644 index 000000000000..4209d2077099 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/DeformTimeline.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DeformTimeline_h +#define Spine_DeformTimeline_h + +#include + +namespace spine { + class VertexAttachment; + + class SP_API DeformTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, Vector &vertices); + + Vector > &getVertices(); + + VertexAttachment *getAttachment(); + + void setAttachment(VertexAttachment *inValue); + + virtual void + 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); + + float getCurvePercent(float time, int frame); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector > _vertices; + + VertexAttachment *_attachment; + }; +} + +#endif /* Spine_DeformTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/DrawOrderTimeline.h b/modules/spine_godot/spine-cpp/include/spine/DrawOrderTimeline.h new file mode 100644 index 000000000000..7d2ab5544e3d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/DrawOrderTimeline.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DrawOrderTimeline_h +#define Spine_DrawOrderTimeline_h + +#include + +namespace spine { + class SP_API DrawOrderTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DrawOrderTimeline(size_t frameCount); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + /// @param drawOrder May be NULL to use bind pose draw order + void setFrame(size_t frame, float time, Vector &drawOrder); + + Vector > &getDrawOrders(); + + private: + Vector > _drawOrders; + }; +} + +#endif /* Spine_DrawOrderTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Event.h b/modules/spine_godot/spine-cpp/include/spine/Event.h new file mode 100644 index 000000000000..b838a48bdfd4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Event.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Event_h +#define Spine_Event_h + +#include +#include + +namespace spine { + class EventData; + +/// Stores the current pose values for an Event. + class SP_API Event : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + public: + Event(float time, const EventData &data); + + const EventData &getData(); + + /// The animation time this event was keyed. + float getTime(); + + int getIntValue(); + + void setIntValue(int inValue); + + float getFloatValue(); + + void setFloatValue(float inValue); + + const String &getStringValue(); + + void setStringValue(const String &inValue); + + float getVolume(); + + void setVolume(float inValue); + + float getBalance(); + + void setBalance(float inValue); + + private: + const EventData &_data; + const float _time; + int _intValue; + float _floatValue; + String _stringValue; + float _volume; + float _balance; + }; +} + +#endif /* Spine_Event_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/EventData.h b/modules/spine_godot/spine-cpp/include/spine/EventData.h new file mode 100644 index 000000000000..fa351608f68d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/EventData.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_EventData_h +#define Spine_EventData_h + +#include +#include + +namespace spine { +/// Stores the setup pose values for an Event. + class SP_API EventData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Event; + + public: + explicit EventData(const String &name); + + /// The name of the event, which is unique within the skeleton. + const String &getName() const; + + int getIntValue() const; + + void setIntValue(int inValue); + + float getFloatValue() const; + + void setFloatValue(float inValue); + + const String &getStringValue() const; + + void setStringValue(const String &inValue); + + const String &getAudioPath() const; + + void setAudioPath(const String &inValue); + + float getVolume() const; + + void setVolume(float inValue); + + float getBalance() const; + + void setBalance(float inValue); + + private: + const String _name; + int _intValue; + float _floatValue; + String _stringValue; + String _audioPath; + float _volume; + float _balance; + }; +} + +#endif /* Spine_EventData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/EventTimeline.h b/modules/spine_godot/spine-cpp/include/spine/EventTimeline.h new file mode 100644 index 000000000000..bd05a49ddfb6 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/EventTimeline.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_EventTimeline_h +#define Spine_EventTimeline_h + +#include + +namespace spine { + class SP_API EventTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit EventTimeline(size_t frameCount); + + ~EventTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(size_t frame, Event *event); + + Vector &getEvents(); + + private: + Vector _events; + }; +} + +#endif /* Spine_EventTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Extension.h b/modules/spine_godot/spine-cpp/include/spine/Extension.h new file mode 100644 index 000000000000..babb397386f8 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Extension.h @@ -0,0 +1,125 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Extension_h +#define Spine_Extension_h + + +#include +#include + +#define SP_UNUSED(x) (void)(x) + +namespace spine { + class String; + + class SP_API SpineExtension { + public: + template + static T *alloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_alloc(sizeof(T) * num, file, line); + } + + template + static T *calloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_calloc(sizeof(T) * num, file, line); + } + + template + static T *realloc(T *ptr, size_t num, const char *file, int line) { + return (T *) getInstance()->_realloc(ptr, sizeof(T) * num, file, line); + } + + template + static void free(T *ptr, const char *file, int line) { + getInstance()->_free((void *) ptr, file, line); + } + + template + static void beforeFree(T *ptr) { + getInstance()->_beforeFree((void *) ptr); + } + + static char *readFile(const String &path, int *length) { + return getInstance()->_readFile(path, length); + } + + static void setInstance(SpineExtension *inSpineExtension); + + static SpineExtension *getInstance(); + + virtual ~SpineExtension(); + + /// Implement this function to use your own memory allocator + virtual void *_alloc(size_t size, const char *file, int line) = 0; + + virtual void *_calloc(size_t size, const char *file, int line) = 0; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) = 0; + + /// If you provide a spineAllocFunc, you should also provide a spineFreeFunc + virtual void _free(void *mem, const char *file, int line) = 0; + + virtual char *_readFile(const String &path, int *length) = 0; + + virtual void _beforeFree(void *ptr) { SP_UNUSED(ptr); } + + protected: + SpineExtension(); + + private: + static SpineExtension *_instance; + }; + + class SP_API DefaultSpineExtension : public SpineExtension { + public: + DefaultSpineExtension(); + + virtual ~DefaultSpineExtension(); + + protected: + virtual void *_alloc(size_t size, const char *file, int line) override; + + virtual void *_calloc(size_t size, const char *file, int line) override; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) override; + + virtual void _free(void *mem, const char *file, int line) override; + + virtual char *_readFile(const String &path, int *length) override; + }; + +// This function is to be implemented by engine specific runtimes to provide +// the default extension for that engine. It is called the first time +// SpineExtension::getInstance() is called, when no instance has been set +// yet. + extern SpineExtension *getDefaultExtension(); +} + +#endif /* Spine_Extension_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/HasRendererObject.h b/modules/spine_godot/spine-cpp/include/spine/HasRendererObject.h new file mode 100644 index 000000000000..db49a48aea2c --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/HasRendererObject.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_HasRendererObject_h +#define Spine_HasRendererObject_h + +#include + +namespace spine { + + typedef void (*DisposeRendererObject)(void *rendererObject); + + class SP_API HasRendererObject { + public: + explicit HasRendererObject() : _rendererObject(0), _dispose(0) {}; + + virtual ~HasRendererObject() { + if (_dispose && _rendererObject) + _dispose(_rendererObject); + } + + void *getRendererObject() { return _rendererObject; } + + void setRendererObject(void *rendererObject, DisposeRendererObject dispose = 0) { + if (_dispose && _rendererObject && _rendererObject != rendererObject) + _dispose(_rendererObject); + + _rendererObject = rendererObject; + _dispose = dispose; + } + + private: + void *_rendererObject; + DisposeRendererObject _dispose; + }; + +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/HashMap.h b/modules/spine_godot/spine-cpp/include/spine/HashMap.h new file mode 100644 index 000000000000..45c54b877378 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/HashMap.h @@ -0,0 +1,200 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_HashMap_h +#define Spine_HashMap_h + +#include +#include + +// Required for new with line number and file name in MSVC +#ifdef _MSC_VER +#pragma warning(disable:4291) + +#pragma warning(disable:4251) + +#endif + +namespace spine { + template + class SP_API HashMap : public SpineObject { + private: + class Entry; + + public: + class SP_API Pair { + public: + explicit Pair(K &k, V &v) : key(k), value(v) {} + + K &key; + V &value; + }; + + class SP_API Entries { + public: + friend class HashMap; + + explicit Entries(Entry *entry) : _entry(NULL), _hasChecked(false) { + _start.next = entry; + _entry = &_start; + } + + Pair next() { + assert(_entry); + assert(_hasChecked); + _entry = _entry->next; + Pair pair(_entry->_key, _entry->_value); + _hasChecked = false; + return pair; + } + + bool hasNext() { + _hasChecked = true; + return _entry->next; + } + + private: + bool _hasChecked; + Entry _start; + Entry *_entry; + }; + + HashMap() : + _head(NULL), + _size(0) { + } + + ~HashMap() { + clear(); + } + + void clear() { + for (Entry *entry = _head; entry != NULL;) { + Entry *next = entry->next; + delete entry; + entry = next; + } + _head = NULL; + _size = 0; + } + + size_t size() { + return _size; + } + + void put(const K &key, const V &value) { + Entry *entry = find(key); + if (entry) { + entry->_key = key; + entry->_value = value; + } else { + entry = new(__FILE__, __LINE__) Entry(); + entry->_key = key; + entry->_value = value; + + Entry *oldHead = _head; + + if (oldHead) { + _head = entry; + oldHead->prev = entry; + entry->next = oldHead; + } else { + _head = entry; + } + _size++; + } + } + + bool addAll(Vector &keys, const V &value) { + size_t oldSize = _size; + for (size_t i = 0; i < keys.size(); i++) { + put(keys[i], value); + } + return _size != oldSize; + } + + bool containsKey(const K &key) { + return find(key) != NULL; + } + + bool remove(const K &key) { + Entry *entry = find(key); + if (!entry) return false; + + Entry *prev = entry->prev; + Entry *next = entry->next; + + if (prev) prev->next = next; + else _head = next; + if (next) next->prev = entry->prev; + + delete entry; + _size--; + + return true; + } + + V operator[](const K &key) { + Entry *entry = find(key); + if (entry) return entry->_value; + else { + assert(false); + return 0; + } + } + + Entries getEntries() const { + return Entries(_head); + } + + private: + Entry *find(const K &key) { + for (Entry *entry = _head; entry != NULL; entry = entry->next) { + if (entry->_key == key) + return entry; + } + return NULL; + } + + class SP_API Entry : public SpineObject { + public: + K _key; + V _value; + Entry *next; + Entry *prev; + + Entry() : next(NULL), prev(NULL) {} + }; + + Entry *_head; + size_t _size; + }; +} + +#endif /* Spine_HashMap_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/IkConstraint.h b/modules/spine_godot/spine-cpp/include/spine/IkConstraint.h new file mode 100644 index 000000000000..077f4f0b8737 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/IkConstraint.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraint_h +#define Spine_IkConstraint_h + +#include + +#include + +namespace spine { + class IkConstraintData; + + class Skeleton; + + class Bone; + + class SP_API IkConstraint : public Updatable { + friend class Skeleton; + + friend class IkConstraintTimeline; + + RTTI_DECL + + public: + /// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified + /// in the world coordinate system. + static void + apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha); + + /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as + /// possible. The target is specified in the world coordinate system. + /// @param child A direct descendant of the parent bone. + static void + apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha); + + IkConstraint(IkConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + IkConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + IkConstraintData &_data; + Vector _bones; + int _bendDirection; + bool _compress; + bool _stretch; + float _mix; + float _softness; + Bone *_target; + bool _active; + }; +} + +#endif /* Spine_IkConstraint_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/IkConstraintData.h b/modules/spine_godot/spine-cpp/include/spine/IkConstraintData.h new file mode 100644 index 000000000000..2d953d4c4c7d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/IkConstraintData.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_IkConstraintData_h +#define Spine_IkConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API IkConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class IkConstraint; + + friend class Skeleton; + + friend class IkConstraintTimeline; + + public: + RTTI_DECL + + explicit IkConstraintData(const String &name); + + /// The bones that are constrained by this IK Constraint. + Vector &getBones(); + + /// The bone that is the IK target. + BoneData *getTarget(); + + void setTarget(BoneData *inValue); + + /// Controls the bend direction of the IK bones, either 1 or -1. + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + bool getUniform(); + + void setUniform(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + private: + Vector _bones; + BoneData *_target; + int _bendDirection; + bool _compress; + bool _stretch; + bool _uniform; + float _mix; + float _softness; + }; +} + +#endif /* Spine_IkConstraintData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/IkConstraintTimeline.h b/modules/spine_godot/spine-cpp/include/spine/IkConstraintTimeline.h new file mode 100644 index 000000000000..11dcf2e9a2ad --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/IkConstraintTimeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraintTimeline_h +#define Spine_IkConstraintTimeline_h + +#include + +namespace spine { + + class SP_API IkConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time, mix and bend direction of the specified keyframe. + void setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, bool stretch); + + int getIkConstraintIndex() { return _constraintIndex; } + + void setIkConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 6; + static const int MIX = 1; + static const int SOFTNESS = 2; + static const int BEND_DIRECTION = 3; + static const int COMPRESS = 4; + static const int STRETCH = 5; + }; +} + +#endif /* Spine_IkConstraintTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Inherit.h b/modules/spine_godot/spine-cpp/include/spine/Inherit.h new file mode 100644 index 000000000000..fcc3a74d8da3 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Inherit.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformMode_h +#define Spine_TransformMode_h + +namespace spine { + enum Inherit { + Inherit_Normal = 0, + Inherit_OnlyTranslation, + Inherit_NoRotationOrReflection, + Inherit_NoScale, + Inherit_NoScaleOrReflection + }; +} + +#endif /* Spine_TransformMode_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/InheritTimeline.h b/modules/spine_godot/spine-cpp/include/spine/InheritTimeline.h new file mode 100644 index 000000000000..6c79eedc6f76 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/InheritTimeline.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_InheritTimeline_h +#define Spine_InheritTimeline_h + +#include + +#include +#include +#include + +namespace spine { + + class SP_API InheritTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit InheritTimeline(size_t frameCount, int boneIndex); + + virtual ~InheritTimeline(); + + void setFrame(int frame, float time, Inherit inherit); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + + static const int ENTRIES = 2; + static const int INHERIT = 1; + }; +} + +#endif /* Spine_InheritTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Json.h b/modules/spine_godot/spine-cpp/include/spine/Json.h new file mode 100644 index 000000000000..03809a129823 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Json.h @@ -0,0 +1,116 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Json_h +#define Spine_Json_h + +#include + +#ifndef SPINE_JSON_HAVE_PREV +/* spine doesn't use the "prev" link in the Json sibling lists. */ +#define SPINE_JSON_HAVE_PREV 0 +#endif + +namespace spine { + class SP_API Json : public SpineObject { + friend class SkeletonJson; + + public: + /* Json Types: */ + static const int JSON_FALSE; + static const int JSON_TRUE; + static const int JSON_NULL; + static const int JSON_NUMBER; + static const int JSON_STRING; + static const int JSON_ARRAY; + static const int JSON_OBJECT; + + /* Get item "string" from object. Case insensitive. */ + static Json *getItem(Json *object, const char *string); + + static Json *getItem(Json *object, int childIndex); + + static const char *getString(Json *object, const char *name, const char *defaultValue); + + static float getFloat(Json *object, const char *name, float defaultValue); + + static int getInt(Json *object, const char *name, int defaultValue); + + static bool getBoolean(Json *object, const char *name, bool defaultValue); + + /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when Json_create() returns 0. 0 when Json_create() succeeds. */ + static const char *getError(); + + /* Supply a block of JSON, and this returns a Json object you can interrogate. Call Json_dispose when finished. */ + explicit Json(const char *value); + + ~Json(); + + + private: + static const char *_error; + + Json *_next; +#if SPINE_JSON_HAVE_PREV + Json* _prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */ +#endif + Json *_child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int _type; /* The type of the item, as above. */ + int _size; /* The number of children. */ + + const char *_valueString; /* The item's string, if type==JSON_STRING */ + int _valueInt; /* The item's number, if type==JSON_NUMBER */ + float _valueFloat; /* The item's number, if type==JSON_NUMBER */ + + const char *_name; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + + /* Utility to jump whitespace and cr/lf */ + static const char *skip(const char *inValue); + + /* Parser core - when encountering text, process appropriately. */ + static const char *parseValue(Json *item, const char *value); + + /* Parse the input text into an unescaped cstring, and populate item. */ + static const char *parseString(Json *item, const char *str); + + /* Parse the input text to generate a number, and populate the result into item. */ + static const char *parseNumber(Json *item, const char *num); + + /* Build an array from input text. */ + static const char *parseArray(Json *item, const char *value); + + /* Build an object from the text. */ + static const char *parseObject(Json *item, const char *value); + + static int json_strcasecmp(const char *s1, const char *s2); + }; +} + +#endif /* Spine_Json_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/LinkedMesh.h b/modules/spine_godot/spine-cpp/include/spine/LinkedMesh.h new file mode 100644 index 000000000000..bd71970bbfed --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/LinkedMesh.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_LinkedMesh_h +#define Spine_LinkedMesh_h + +#include +#include + +namespace spine { + class MeshAttachment; + + class SP_API LinkedMesh : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + public: + LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline); + + LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline); + + private: + MeshAttachment *_mesh; + int _skinIndex; + String _skin; + size_t _slotIndex; + String _parent; + bool _inheritTimeline; + }; +} + +#endif /* Spine_LinkedMesh_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Log.h b/modules/spine_godot/spine-cpp/include/spine/Log.h new file mode 100644 index 000000000000..ce66f44fcd84 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Log.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_DEBUG_LOG_H +#define SPINE_DEBUG_LOG_H + +#include + +namespace spine { + SP_API void spDebug_printSkeletonData(SkeletonData *skeletonData); + + SP_API void spDebug_printAnimation(Animation *animation); + + SP_API void spDebug_printTimeline(Timeline *timeline); + + SP_API void spDebug_printBoneDatas(Vector &boneDatas); + + SP_API void spDebug_printBoneData(BoneData *boneData); + + SP_API void spDebug_printSkeleton(Skeleton *skeleton); + + SP_API void spDebug_printBones(Vector &bones); + + SP_API void spDebug_printBone(Bone *bone); + + SP_API void spDebug_printFloats(float *values, int numFloats); + + SP_API void spDebug_printFloats(Vector &values); +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/MathUtil.h b/modules/spine_godot/spine-cpp/include/spine/MathUtil.h new file mode 100644 index 000000000000..fb6d24b59a1c --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/MathUtil.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MathUtil_h +#define Spine_MathUtil_h + +#include + +#include +// Needed for older MSVC versions +#undef min +#undef max + +namespace spine { + + class SP_API MathUtil : public SpineObject { + private: + MathUtil(); + + public: + static const float Pi; + static const float Pi_2; + static const float InvPi_2; + static const float Deg_Rad; + static const float Rad_Deg; + + template + static inline T min(T a, T b) { return a < b ? a : b; } + + template + static inline T max(T a, T b) { return a > b ? a : b; } + + static float sign(float val); + + static float clamp(float x, float lower, float upper); + + static float abs(float v); + + /// Returns the sine in radians from a lookup table. + static float sin(float radians); + + /// Returns the cosine in radians from a lookup table. + static float cos(float radians); + + /// Returns the sine in radians from a lookup table. + static float sinDeg(float degrees); + + /// Returns the cosine in radians from a lookup table. + static float cosDeg(float degrees); + + /// 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). + static float atan2(float y, float x); + + static float atan2Deg(float x, float y); + + static float acos(float v); + + static float sqrt(float v); + + static float fmod(float a, float b); + + static bool isNan(float v); + + static float quietNan(); + + static float random(); + + static float randomTriangular(float min, float max); + + static float randomTriangular(float min, float max, float mode); + + static float pow(float a, float b); + + static float ceil(float v); + }; + + struct SP_API Interpolation { + virtual float apply(float a) = 0; + + virtual float interpolate(float start, float end, float a) { + return start + (end - start) * apply(a); + } + + virtual ~Interpolation() {}; + }; + + struct SP_API PowInterpolation : public Interpolation { + PowInterpolation(int power) : power(power) { + } + + float apply(float a) { + if (a <= 0.5f) return MathUtil::pow(a * 2.0f, (float) power) / 2.0f; + return MathUtil::pow((a - 1.0f) * 2.0f, (float) power) / (power % 2 == 0 ? -2.0f : 2.0f) + 1.0f; + } + + int power; + }; + + struct SP_API PowOutInterpolation : public Interpolation { + PowOutInterpolation(int power) : power(power) { + } + + float apply(float a) { + return MathUtil::pow(a - 1, (float) power) * (power % 2 == 0 ? -1.0f : 1.0f) + 1.0f; + } + + int power; + }; + +} + +#endif /* Spine_MathUtil_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/MeshAttachment.h b/modules/spine_godot/spine-cpp/include/spine/MeshAttachment.h new file mode 100644 index 000000000000..a553766c91d5 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/MeshAttachment.h @@ -0,0 +1,122 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MeshAttachment_h +#define Spine_MeshAttachment_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + /// Attachment that displays a texture region using a mesh. + class SP_API MeshAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit MeshAttachment(const String &name); + + virtual ~MeshAttachment(); + + using VertexAttachment::computeWorldVertices; + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + void updateRegion(); + + int getHullLength(); + + void setHullLength(int inValue); + + Vector &getRegionUVs(); + + /// The UV pair for each vertex, normalized within the entire texture. See also MeshAttachment::updateRegion + Vector &getUVs(); + + Vector &getTriangles(); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + MeshAttachment *getParentMesh(); + + void setParentMesh(MeshAttachment *inValue); + + // Nonessential. + Vector &getEdges(); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + virtual Attachment *copy(); + + MeshAttachment *newLinkedMesh(); + + private: + MeshAttachment *_parentMesh; + Vector _uvs; + Vector _regionUVs; + Vector _triangles; + Vector _edges; + String _path; + Color _color; + int _hullLength; + int _width, _height; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_MeshAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/MixBlend.h b/modules/spine_godot/spine-cpp/include/spine/MixBlend.h new file mode 100644 index 000000000000..3aa0faf898de --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/MixBlend.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_MixPose_h +#define Spine_MixPose_h + +namespace spine { + +/// Controls how a timeline is mixed with the setup or current pose. +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, Blend, MixDirection) + enum MixBlend { + MixBlend_Setup = 0, + MixBlend_First, + MixBlend_Replace, + MixBlend_Add + }; +} + +#endif /* Spine_MixPose_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/MixDirection.h b/modules/spine_godot/spine-cpp/include/spine/MixDirection.h new file mode 100644 index 000000000000..dbb12870c39c --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/MixDirection.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_MixDirection_h +#define Spine_MixDirection_h + +namespace spine { + +/// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose). +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, MixPose, MixDirection) + enum MixDirection { + MixDirection_In = 0, + MixDirection_Out + }; + +} + +#endif /* Spine_MixDirection_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathAttachment.h b/modules/spine_godot/spine-cpp/include/spine/PathAttachment.h new file mode 100644 index 000000000000..e13784950fb3 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathAttachment.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathAttachment_h +#define Spine_PathAttachment_h + +#include +#include + +namespace spine { + class SP_API PathAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathAttachment(const String &name); + + /// The length in the setup pose from the start of the path to the end of each curve. + Vector &getLengths(); + + bool isClosed(); + + void setClosed(bool inValue); + + bool isConstantSpeed(); + + void setConstantSpeed(bool inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Vector _lengths; + bool _closed; + bool _constantSpeed; + Color _color; + }; +} + +#endif /* Spine_PathAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathConstraint.h b/modules/spine_godot/spine-cpp/include/spine/PathConstraint.h new file mode 100644 index 000000000000..924b35483bd2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathConstraint.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PathConstraint_h +#define Spine_PathConstraint_h + +#include + +#include + +namespace spine { + class PathConstraintData; + + class Skeleton; + + class PathAttachment; + + class Bone; + + class Slot; + + class SP_API PathConstraint : public Updatable { + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + RTTI_DECL + + public: + PathConstraint(PathConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + PathConstraintData &getData(); + + Vector &getBones(); + + Slot *getTarget(); + + void setTarget(Slot *inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + static const float EPSILON; + static const int NONE; + static const int BEFORE; + static const int AFTER; + + PathConstraintData &_data; + Vector _bones; + Slot *_target; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + + Vector _spaces; + Vector _positions; + Vector _world; + Vector _curves; + Vector _lengths; + Vector _segments; + + bool _active; + + Vector &computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents); + + static void addBeforePosition(float p, Vector &temp, int i, Vector &output, int o); + + static void addAfterPosition(float p, Vector &temp, int i, Vector &output, int o); + + static void + addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + Vector &output, int o, bool tangents); + }; +} + +#endif /* Spine_PathConstraint_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathConstraintData.h b/modules/spine_godot/spine-cpp/include/spine/PathConstraintData.h new file mode 100644 index 000000000000..b018ad3a6fdb --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathConstraintData.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintData_h +#define Spine_PathConstraintData_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class SP_API PathConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class PathConstraint; + + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + public: + RTTI_DECL + + explicit PathConstraintData(const String &name); + + Vector &getBones(); + + SlotData *getTarget(); + + void setTarget(SlotData *inValue); + + PositionMode getPositionMode(); + + void setPositionMode(PositionMode inValue); + + SpacingMode getSpacingMode(); + + void setSpacingMode(SpacingMode inValue); + + RotateMode getRotateMode(); + + void setRotateMode(RotateMode inValue); + + float getOffsetRotation(); + + void setOffsetRotation(float inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + private: + Vector _bones; + SlotData *_target; + PositionMode _positionMode; + SpacingMode _spacingMode; + RotateMode _rotateMode; + float _offsetRotation; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + }; +} + +#endif /* Spine_PathConstraintData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathConstraintMixTimeline.h b/modules/spine_godot/spine-cpp/include/spine/PathConstraintMixTimeline.h new file mode 100644 index 000000000000..72c189bd5479 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathConstraintMixTimeline.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintMixTimeline_h +#define Spine_PathConstraintMixTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintMixTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and mixes of the specified keyframe. + void setFrame(int frameIndex, float time, float mixRotate, float mixX, float mixY); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 4; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 3; + }; +} + +#endif /* Spine_PathConstraintMixTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathConstraintPositionTimeline.h b/modules/spine_godot/spine-cpp/include/spine/PathConstraintPositionTimeline.h new file mode 100644 index 000000000000..73e4842d7da2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathConstraintPositionTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintPositionTimeline_h +#define Spine_PathConstraintPositionTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintPositionTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + static const int ENTRIES; + + explicit PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual ~PathConstraintPositionTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + int _constraintIndex; + }; +} + +#endif /* Spine_PathConstraintPositionTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PathConstraintSpacingTimeline.h b/modules/spine_godot/spine-cpp/include/spine/PathConstraintSpacingTimeline.h new file mode 100644 index 000000000000..6d7bee3b4c7e --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PathConstraintSpacingTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PathConstraintSpacingTimeline_h +#define Spine_PathConstraintSpacingTimeline_h + +#include + +namespace spine { + class SP_API PathConstraintSpacingTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _pathConstraintIndex; } + + void setPathConstraintIndex(int inValue) { _pathConstraintIndex = inValue; } + + protected: + int _pathConstraintIndex; + }; +} + +#endif /* Spine_PathConstraintSpacingTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Physics.h b/modules/spine_godot/spine-cpp/include/spine/Physics.h new file mode 100644 index 000000000000..e7b9f7a10ece --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Physics.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Physics_h +#define Spine_Physics_h + +/** Determines how physics and other non-deterministic updates are applied. */ +namespace spine { + enum Physics { + /** Physics are not updated or applied. */ + Physics_None, + + /** Physics are reset to the current pose. */ + Physics_Reset, + + /** Physics are updated and the pose from physics is applied. */ + Physics_Update, + + /** Physics are not updated but the pose from physics is applied. */ + Physics_Pose + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraint.h b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraint.h new file mode 100644 index 000000000000..b63060ee7223 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraint.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraint_h +#define Spine_PhysicsConstraint_h + +#include + +#include + +namespace spine { + class PhysicsConstraintData; + + class Skeleton; + + class Bone; + + class SP_API PhysicsConstraint : public Updatable { + + friend class Skeleton; + + friend class PhysicsConstraintTimeline; + + friend class PhysicsConstraintInertiaTimeline; + + friend class PhysicsConstraintStrengthTimeline; + + friend class PhysicsConstraintDampingTimeline; + + friend class PhysicsConstraintMassTimeline; + + friend class PhysicsConstraintWindTimeline; + + friend class PhysicsConstraintGravityTimeline; + + friend class PhysicsConstraintMixTimeline; + + friend class PhysicsConstraintResetTimeline; + + RTTI_DECL + + public: + PhysicsConstraint(PhysicsConstraintData& data, Skeleton& skeleton); + + PhysicsConstraintData &getData(); + + void setBone(Bone* bone); + Bone* getBone(); + + void setInertia(float value); + float getInertia(); + + void setStrength(float value); + float getStrength(); + + void setDamping(float value); + float getDamping(); + + void setMassInverse(float value); + float getMassInverse(); + + void setWind(float value); + float getWind(); + + void setGravity(float value); + float getGravity(); + + void setMix(float value); + float getMix(); + + void setReset(bool value); + bool getReset(); + + void setUx(float value); + float getUx(); + + void setUy(float value); + float getUy(); + + void setCx(float value); + float getCx(); + + void setCy(float value); + float getCy(); + + void setTx(float value); + float getTx(); + + void setTy(float value); + float getTy(); + + void setXOffset(float value); + float getXOffset(); + + void setXVelocity(float value); + float getXVelocity(); + + void setYOffset(float value); + float getYOffset(); + + void setYVelocity(float value); + float getYVelocity(); + + void setRotateOffset(float value); + float getRotateOffset(); + + void setRotateVelocity(float value); + float getRotateVelocity(); + + void setScaleOffset(float value); + float getScaleOffset(); + + void setScaleVelocity(float value); + float getScaleVelocity(); + + void setActive(bool value); + bool isActive(); + + void setRemaining(float value); + float getRemaining(); + + void setLastTime(float value); + float getLastTime(); + + void reset(); + + void setToSetupPose(); + + virtual void update(Physics physics); + + void translate(float x, float y); + + void rotate(float x, float y, float degrees); + + private: + PhysicsConstraintData& _data; + Bone* _bone; + + float _inertia; + float _strength; + float _damping; + float _massInverse; + float _wind; + float _gravity; + float _mix; + + bool _reset; + float _ux; + float _uy; + float _cx; + float _cy; + float _tx; + float _ty; + float _xOffset; + float _xVelocity; + float _yOffset; + float _yVelocity; + float _rotateOffset; + float _rotateVelocity; + float _scaleOffset; + float _scaleVelocity; + + bool _active; + + Skeleton& _skeleton; + float _remaining; + float _lastTime; + }; +} + +#endif /* Spine_PhysicsConstraint_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintData.h b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintData.h new file mode 100644 index 000000000000..32aa3cc8fc8a --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintData.h @@ -0,0 +1,151 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintData_h +#define Spine_PhysicsConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API PhysicsConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + friend class PhysicsConstraint; + + public: + RTTI_DECL + + explicit PhysicsConstraintData(const String &name); + + void setBone(BoneData* bone); + + BoneData* getBone() const; + + void setX(float x); + + float getX() const; + + void setY(float y); + + float getY() const; + + void setRotate(float rotate); + + float getRotate() const; + + void setScaleX(float scaleX); + + float getScaleX() const; + + void setShearX(float shearX); + + float getShearX() const; + + void setLimit(float limit); + + float getLimit() const; + + void setStep(float step); + + float getStep() const; + + void setInertia(float inertia); + + float getInertia() const; + + void setStrength(float strength); + + float getStrength() const; + + void setDamping(float damping); + + float getDamping() const; + + void setMassInverse(float massInverse); + + float getMassInverse() const; + + void setWind(float wind); + + float getWind() const; + + void setGravity(float gravity); + + float getGravity() const; + + void setMix(float mix); + + float getMix() const; + + void setInertiaGlobal(bool inertiaGlobal); + + bool isInertiaGlobal() const; + + void setStrengthGlobal(bool strengthGlobal); + + bool isStrengthGlobal() const; + + void setDampingGlobal(bool dampingGlobal); + + bool isDampingGlobal() const; + + void setMassGlobal(bool massGlobal); + + bool isMassGlobal() const; + + void setWindGlobal(bool windGlobal); + + bool isWindGlobal() const; + + void setGravityGlobal(bool gravityGlobal); + + bool isGravityGlobal() const; + + void setMixGlobal(bool mixGlobal); + + bool isMixGlobal() const; + + private: + BoneData *_bone; + float _x, _y, _rotate, _scaleX, _shearX, _limit; + float _step, _inertia, _strength, _damping, _massInverse, _wind, _gravity, _mix; + bool _inertiaGlobal, _strengthGlobal, _dampingGlobal, _massGlobal, _windGlobal, _gravityGlobal, _mixGlobal; + }; +} + +#endif /* Spine_PhysicsConstraintData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintTimeline.h b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintTimeline.h new file mode 100644 index 000000000000..e119378d7d7b --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PhysicsConstraintTimeline.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintTimeline_h +#define Spine_PhysicsConstraintTimeline_h + +#include +#include +#include + +namespace spine { + + class SP_API PhysicsConstraintTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex, Property property); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPhysicsConstraintIndex() { return _constraintIndex; } + + void setPhysicsConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + virtual float setup(PhysicsConstraint *constraint) = 0; + virtual float get(PhysicsConstraint *constraint) = 0; + virtual void set(PhysicsConstraint *constraint, float value) = 0; + virtual bool global(PhysicsConstraintData &constraintData) = 0; + + private: + int _constraintIndex; + }; + + class SP_API PhysicsConstraintInertiaTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintInertiaTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintInertia) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getInertia(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_inertia; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_inertia = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isInertiaGlobal(); + } + }; + + class SP_API PhysicsConstraintStrengthTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintStrengthTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintStrength) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getStrength(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_strength; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_strength = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isStrengthGlobal(); + } + }; + + class SP_API PhysicsConstraintDampingTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintDampingTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintDamping) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getDamping(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_damping; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_damping = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isDampingGlobal(); + } + }; + + class SP_API PhysicsConstraintMassTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMassTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMass) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return 1 / constraint->_data.getMassInverse(); + } + + float get(PhysicsConstraint *constraint) { + return 1 / constraint->_massInverse; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_massInverse = 1 / value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMassGlobal(); + } + }; + + class SP_API PhysicsConstraintWindTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintWindTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintWind) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getWind(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_wind; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_wind = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isWindGlobal(); + } + }; + + class SP_API PhysicsConstraintGravityTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintGravityTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintGravity) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getGravity(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_gravity; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_gravity = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isGravityGlobal(); + } + }; + + class SP_API PhysicsConstraintMixTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMixTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMix) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getMix(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_mix; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_mix = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMixGlobal(); + } + }; + + class SP_API PhysicsConstraintResetTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintResetTimeline(size_t frameCount, int physicsConstraintIndex): Timeline(frameCount, 1), _constraintIndex(physicsConstraintIndex) { + PropertyId ids[] = {((PropertyId)Property_PhysicsConstraintReset) << 32}; + setPropertyIds(ids, 1); + } + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time) { + _frames[frame] = time; + } + private: + int _constraintIndex; + }; +} + +#endif /* Spine_PhysicsConstraintTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PointAttachment.h b/modules/spine_godot/spine-cpp/include/spine/PointAttachment.h new file mode 100644 index 000000000000..45f47cffe6ee --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PointAttachment.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PointAttachment_h +#define Spine_PointAttachment_h + +#include +#include + +namespace spine { + class Bone; + + /// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be + /// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a + /// skin. + /// + /// See http://esotericsoftware.com/spine-point-attachments for Point Attachments in the Spine User Guide. + /// + class SP_API PointAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PointAttachment(const String &name); + + void computeWorldPosition(Bone &bone, float &ox, float &oy); + + float computeWorldRotation(Bone &bone); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + float _x, _y, _rotation; + Color _color; + }; +} + +#endif /* Spine_PointAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Pool.h b/modules/spine_godot/spine-cpp/include/spine/Pool.h new file mode 100644 index 000000000000..2f2b43a133b2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Pool.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Pool_h +#define Spine_Pool_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Pool : public SpineObject { + public: + Pool() { + } + + ~Pool() { + ContainerUtil::cleanUpVectorOfPointers(_objects); + } + + T *obtain() { + if (_objects.size() > 0) { + T **object = &_objects[_objects.size() - 1]; + T *ret = *object; + _objects.removeAt(_objects.size() - 1); + + return ret; + } else { + T *ret = new(__FILE__, __LINE__) T(); + + return ret; + } + } + + void free(T *object) { + if (!_objects.contains(object)) { + _objects.add(object); + } + } + + private: + Vector _objects; + }; +} + +#endif /* Spine_Pool_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/PositionMode.h b/modules/spine_godot/spine-cpp/include/spine/PositionMode.h new file mode 100644 index 000000000000..ee5b4b06c4f0 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/PositionMode.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_PositionMode_h +#define Spine_PositionMode_h + +namespace spine { + enum PositionMode { + PositionMode_Fixed = 0, + PositionMode_Percent + }; +} + +#endif /* Spine_PositionMode_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Property.h b/modules/spine_godot/spine-cpp/include/spine/Property.h new file mode 100644 index 000000000000..69fa24f6f81c --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Property.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Property_h +#define Spine_Property_h + +namespace spine { + typedef long long PropertyId; + enum Property { + Property_Rotate = 1 << 0, + Property_X = 1 << 1, + Property_Y = 1 << 2, + Property_ScaleX = 1 << 3, + Property_ScaleY = 1 << 4, + Property_ShearX = 1 << 5, + Property_ShearY = 1 << 6, + Property_Inherit = 1 << 7, + Property_Rgb = 1 << 8, + Property_Alpha = 1 << 9, + Property_Rgb2 = 1 << 10, + Property_Attachment = 1 << 11, + Property_Deform = 1 << 12, + Property_Event = 1 << 13, + Property_DrawOrder = 1 << 14, + Property_IkConstraint = 1 << 15, + Property_TransformConstraint = 1 << 16, + Property_PathConstraintPosition = 1 << 17, + Property_PathConstraintSpacing = 1 << 18, + Property_PathConstraintMix = 1 << 19, + Property_PhysicsConstraintInertia = 1 << 20, + Property_PhysicsConstraintStrength = 1 << 21, + Property_PhysicsConstraintDamping = 1 << 22, + Property_PhysicsConstraintMass = 1 << 23, + Property_PhysicsConstraintWind = 1 << 24, + Property_PhysicsConstraintGravity = 1 << 25, + Property_PhysicsConstraintMix = 1 << 26, + Property_PhysicsConstraintReset = 1 << 27, + Property_Sequence = 1 << 28 + }; +} + +#endif /* Spine_Property_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/RTTI.h b/modules/spine_godot/spine-cpp/include/spine/RTTI.h new file mode 100644 index 000000000000..18d6371cac1d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/RTTI.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RTTI_h +#define Spine_RTTI_h + +#include + +namespace spine { + class SP_API RTTI { + public: + explicit RTTI(const char *className); + + RTTI(const char *className, const RTTI &baseRTTI); + + const char *getClassName() const; + + bool isExactly(const RTTI &rtti) const; + + bool instanceOf(const RTTI &rtti) const; + + private: + // Prevent copying + RTTI(const RTTI &obj); + + RTTI &operator=(const RTTI &obj); + + const char *_className; + const RTTI *_pBaseRTTI; + }; +} + +#define RTTI_DECL \ +public: \ +static const spine::RTTI rtti; \ +virtual const spine::RTTI& getRTTI() const; + +#define RTTI_IMPL_NOPARENT(name) \ +const spine::RTTI name::rtti(#name); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#define RTTI_IMPL(name, parent) \ +const spine::RTTI name::rtti(#name, parent::rtti); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#endif /* Spine_RTTI_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/RegionAttachment.h b/modules/spine_godot/spine-cpp/include/spine/RegionAttachment.h new file mode 100644 index 000000000000..3d288851cac0 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/RegionAttachment.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RegionAttachment_h +#define Spine_RegionAttachment_h + +#include +#include +#include +#include +#include + +#include + +#define NUM_UVS 8 + +namespace spine { + class Bone; + + /// Attachment that displays a texture region. + class SP_API RegionAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit RegionAttachment(const String &name); + + virtual ~RegionAttachment(); + + void updateRegion(); + + /// Transforms the attachment's four vertices to world coordinates. + /// @param slot The parent slot. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + 8. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + void computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride = 2); + + void computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride = 2); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + Vector &getOffset(); + + Vector &getUVs(); + + virtual Attachment *copy(); + + private: + static const int BLX; + static const int BLY; + static const int ULX; + static const int ULY; + static const int URX; + static const int URY; + static const int BRX; + static const int BRY; + + float _x, _y, _rotation, _scaleX, _scaleY, _width, _height; + Vector _vertexOffset; + Vector _uvs; + String _path; + Color _color; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_RegionAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/RotateMode.h b/modules/spine_godot/spine-cpp/include/spine/RotateMode.h new file mode 100644 index 000000000000..fd3e373334f7 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/RotateMode.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateMode_h +#define Spine_RotateMode_h + +namespace spine { + enum RotateMode { + RotateMode_Tangent = 0, + RotateMode_Chain, + RotateMode_ChainScale + }; +} + +#endif /* Spine_RotateMode_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/RotateTimeline.h b/modules/spine_godot/spine-cpp/include/spine/RotateTimeline.h new file mode 100644 index 000000000000..89b8621f9074 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/RotateTimeline.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateTimeline_h +#define Spine_RotateTimeline_h + +#include + +namespace spine { + class SP_API RotateTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + RTTI_DECL + + public: + explicit RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_RotateTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/ScaleTimeline.h b/modules/spine_godot/spine-cpp/include/spine/ScaleTimeline.h new file mode 100644 index 000000000000..ef9f3ac7b298 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ScaleTimeline.h @@ -0,0 +1,109 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ScaleTimeline_h +#define Spine_ScaleTimeline_h + +#include + +namespace spine { + class SP_API ScaleTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ScaleTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Sequence.h b/modules/spine_godot/spine-cpp/include/spine/Sequence.h new file mode 100644 index 000000000000..8bf0fd710fa4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Sequence.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Sequence_h +#define Spine_Sequence_h + +#include +#include +#include + +namespace spine { + class Slot; + + class Attachment; + + class SkeletonBinary; + class SkeletonJson; + + class SP_API Sequence : public SpineObject { + friend class SkeletonBinary; + friend class SkeletonJson; + public: + Sequence(int count); + + ~Sequence(); + + Sequence *copy(); + + void apply(Slot *slot, Attachment *attachment); + + String getPath(const String &basePath, int index); + + int getId() { return _id; } + + void setId(int id) { _id = id; } + + int getStart() { return _start; } + + void setStart(int start) { _start = start; } + + int getDigits() { return _digits; } + + void setDigits(int digits) { _digits = digits; } + + int getSetupIndex() { return _setupIndex; } + + void setSetupIndex(int setupIndex) { _setupIndex = setupIndex; } + + Vector &getRegions() { return _regions; } + + private: + int _id; + Vector _regions; + int _start; + int _digits; + int _setupIndex; + + int getNextID(); + }; + + enum SequenceMode { + hold = 0, + once = 1, + loop = 2, + pingpong = 3, + onceReverse = 4, + loopReverse = 5, + pingpongReverse = 6 + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/SequenceTimeline.h b/modules/spine_godot/spine-cpp/include/spine/SequenceTimeline.h new file mode 100644 index 000000000000..7dd2ae377977 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SequenceTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_SequenceTimeline_h +#define Spine_SequenceTimeline_h + +#include +#include + +namespace spine { + class Attachment; + + class SP_API SequenceTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit SequenceTimeline(size_t frameCount, int slotIndex, spine::Attachment *attachment); + + virtual ~SequenceTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time, SequenceMode mode, int index, float delay); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + Attachment *getAttachment() { return _attachment; } + + protected: + int _slotIndex; + Attachment *_attachment; + + static const int ENTRIES = 3; + static const int MODE = 1; + static const int DELAY = 2; + }; +} + +#endif /* Spine_SequenceTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/ShearTimeline.h b/modules/spine_godot/spine-cpp/include/spine/ShearTimeline.h new file mode 100644 index 000000000000..6717743dfa72 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/ShearTimeline.h @@ -0,0 +1,109 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ShearTimeline_h +#define Spine_ShearTimeline_h + +#include + +namespace spine { + class SP_API ShearTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ShearTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Skeleton.h b/modules/spine_godot/spine-cpp/include/spine/Skeleton.h new file mode 100644 index 000000000000..82304d6a9d9d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Skeleton.h @@ -0,0 +1,285 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Skeleton_h +#define Spine_Skeleton_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Bone; + + class Updatable; + + class Slot; + + class IkConstraint; + + class PathConstraint; + + class PhysicsConstraint; + + class TransformConstraint; + + class Skin; + + class Attachment; + + class SkeletonClipping; + + class SP_API Skeleton : public SpineObject { + friend class AnimationState; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TransformConstraintTimeline; + + friend class RotateTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + explicit Skeleton(SkeletonData *skeletonData); + + ~Skeleton(); + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + void updateCache(); + + void printUpdateCache(); + + /// Updates the world transform for each bone and applies all constraints. + /// + /// See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + /// Runtimes Guide. + void updateWorldTransform(Physics physics); + + void updateWorldTransform(Physics physics, Bone *parent); + + /// Sets the bones, constraints, and slots to their setup pose values. + void setToSetupPose(); + + /// Sets the bones and constraints to their setup pose values. + void setBonesToSetupPose(); + + void setSlotsToSetupPose(); + + /// @return May be NULL. + Bone *findBone(const String &boneName); + + /// @return May be NULL. + Slot *findSlot(const String &slotName); + + /// Sets a skin by name (see setSkin). + void setSkin(const String &skinName); + + /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. + /// If there was no old skin, each slot's setup mode attachment is attached from the new skin. + /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling + /// See Skeleton::setSlotsToSetupPose() + /// Also, often AnimationState::apply(Skeleton&) is called before the next time the + /// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + /// @param newSkin May be NULL. + void setSkin(Skin *newSkin); + + /// @return May be NULL. + Attachment *getAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + Attachment *getAttachment(int slotIndex, const String &attachmentName); + + /// @param attachmentName May be empty. + void setAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + IkConstraint *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraint *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraint *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraint *findPhysicsConstraint(const String &constraintName); + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// @param outX The horizontal distance between the skeleton origin and the left side of the AABB. + /// @param outY The vertical distance between the skeleton origin and the bottom side of the AABB. + /// @param outWidth The width of the AABB + /// @param outHeight The height of the AABB. + /// @param outVertexBuffer Reference to hold a Vector of floats. This method will assign it with new floats as needed. + // @param clipping Pointer to a SkeletonClipping instance or NULL. If a clipper is given, clipping attachments will be taken into account. + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer); + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer, SkeletonClipping *clipper); + + Bone *getRootBone(); + + SkeletonData *getData(); + + Vector &getBones(); + + Vector &getUpdateCacheList(); + + Vector &getSlots(); + + Vector &getDrawOrder(); + + Vector &getIkConstraints(); + + Vector &getPathConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPhysicsConstraints(); + + Skin *getSkin(); + + Color &getColor(); + + void setPosition(float x, float y); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getTime(); + + void setTime(float time); + + void update(float delta); + + /// Rotates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone rotated around the + /// specified point in world space. + void physicsTranslate(float x, float y); + + /// Calls {@link PhysicsConstraint#rotate(float, float, float)} for each physics constraint. */ + void physicsRotate(float x, float y, float degrees); + + private: + SkeletonData *_data; + Vector _bones; + Vector _slots; + Vector _drawOrder; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + Vector _updateCache; + Skin *_skin; + Color _color; + float _scaleX, _scaleY; + float _x, _y; + float _time; + + void sortIkConstraint(IkConstraint *constraint); + + void sortPathConstraint(PathConstraint *constraint); + + void sortPhysicsConstraint(PhysicsConstraint *constraint); + + void sortTransformConstraint(TransformConstraint *constraint); + + void sortPathConstraintAttachment(Skin *skin, size_t slotIndex, Bone &slotBone); + + void sortPathConstraintAttachment(Attachment *attachment, Bone &slotBone); + + void sortBone(Bone *bone); + + static void sortReset(Vector &bones); + }; +} + +#endif /* Spine_Skeleton_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonBinary.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonBinary.h new file mode 100644 index 000000000000..cd74070f0316 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonBinary.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBinary_h +#define Spine_SkeletonBinary_h + +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class Skin; + + class Attachment; + + class VertexAttachment; + + class Animation; + + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class Sequence; + + class SP_API SkeletonBinary : public SpineObject { + public: + static const int BONE_ROTATE = 0; + static const int BONE_TRANSLATE = 1; + static const int BONE_TRANSLATEX = 2; + static const int BONE_TRANSLATEY = 3; + static const int BONE_SCALE = 4; + static const int BONE_SCALEX = 5; + static const int BONE_SCALEY = 6; + static const int BONE_SHEAR = 7; + static const int BONE_SHEARX = 8; + static const int BONE_SHEARY = 9; + static const int BONE_INHERIT = 10; + + static const int SLOT_ATTACHMENT = 0; + static const int SLOT_RGBA = 1; + static const int SLOT_RGB = 2; + static const int SLOT_RGBA2 = 3; + static const int SLOT_RGB2 = 4; + static const int SLOT_ALPHA = 5; + + static const int ATTACHMENT_DEFORM = 0; + static const int ATTACHMENT_SEQUENCE = 1; + + static const int PATH_POSITION = 0; + static const int PATH_SPACING = 1; + static const int PATH_MIX = 2; + + static const int PHYSICS_INERTIA = 0; + static const int PHYSICS_STRENGTH = 1; + static const int PHYSICS_DAMPING = 2; + static const int PHYSICS_MASS = 4; + static const int PHYSICS_WIND = 5; + static const int PHYSICS_GRAVITY = 6; + static const int PHYSICS_MIX = 7; + static const int PHYSICS_RESET = 8; + + static const int CURVE_LINEAR = 0; + static const int CURVE_STEPPED = 1; + static const int CURVE_BEZIER = 2; + + explicit SkeletonBinary(Atlas *atlasArray); + + explicit SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonBinary(); + + SkeletonData *readSkeletonData(const unsigned char *binary, int length); + + SkeletonData *readSkeletonDataFile(const String &path); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + struct DataInput : public SpineObject { + const unsigned char *cursor; + const unsigned char *end; + }; + + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + String _error; + float _scale; + const bool _ownsLoader; + + void setError(const char *value1, const char *value2); + + char *readString(DataInput *input); + + char *readStringRef(DataInput *input, SkeletonData *skeletonData); + + float readFloat(DataInput *input); + + unsigned char readByte(DataInput *input); + + signed char readSByte(DataInput *input); + + bool readBoolean(DataInput *input); + + int readInt(DataInput *input); + + void readColor(DataInput *input, Color &color); + + int readVarint(DataInput *input, bool optimizePositive); + + Skin *readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential); + + Sequence *readSequence(DataInput *input); + + Attachment *readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential); + + int readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted); + + void readFloatArray(DataInput *input, int n, float scale, Vector &array); + + void readShortArray(DataInput *input, Vector &array, int n); + + Animation *readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData); + + void + setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + void readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale); + + void readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale); + }; +} + +#endif /* Spine_SkeletonBinary_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonBounds.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonBounds.h new file mode 100644 index 000000000000..00c5942ad4bd --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonBounds.h @@ -0,0 +1,121 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBounds_h +#define Spine_SkeletonBounds_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + class BoundingBoxAttachment; + + class Polygon; + + /// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon. + /// The polygon vertices are provided along with convenience methods for doing hit detection. + class SP_API SkeletonBounds : public SpineObject { + public: + SkeletonBounds(); + + ~SkeletonBounds(); + + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// @param skeleton The skeleton. + /// @param updateAabb + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + void update(Skeleton &skeleton, bool updateAabb); + + /// Returns true if the axis aligned bounding box contains the point. + bool aabbcontainsPoint(float x, float y); + + /// Returns true if the axis aligned bounding box intersects the line segment. + bool aabbintersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + bool aabbIntersectsSkeleton(SkeletonBounds &bounds); + + /// Returns true if the polygon contains the point. + bool containsPoint(Polygon *polygon, float x, float y); + + /// Returns the first bounding box attachment that contains the point, or NULL. When doing many checks, it is usually more + /// efficient to only call this method if {@link #aabbcontainsPoint(float, float)} returns true. + BoundingBoxAttachment *containsPoint(float x, float y); + + /// Returns the first bounding box attachment that contains the line segment, or NULL. When doing many checks, it is usually + /// more efficient to only call this method if {@link #aabbintersectsSegment(float, float, float, float)} returns true. + BoundingBoxAttachment *intersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the polygon contains the line segment. + bool intersectsSegment(Polygon *polygon, float x1, float y1, float x2, float y2); + + /// Returns the polygon for the given bounding box attachment or null if no + /// polygon can be found for the attachment. Requires a call to update() first. + Polygon *getPolygon(BoundingBoxAttachment *attachment); + + /// Returns the bounding box for the given polygon or null. Requires a call to update() first. + BoundingBoxAttachment * getBoundingBox(Polygon *polygon); + + /// Returns all polygons or an empty vector. Requires a call to update() first. + Vector &getPolygons(); + + /// Returns all bounding boxes. Requires a call to update() first. + Vector &getBoundingBoxes(); + + float getWidth(); + + float getHeight(); + + private: + Pool _polygonPool; + Vector _boundingBoxes; + Vector _polygons; + float _minX, _minY, _maxX, _maxY; + + void aabbCompute(); + }; + + class Polygon : public SpineObject { + public: + Vector _vertices; + int _count; + + Polygon() : _count(0) { + _vertices.ensureCapacity(16); + } + }; +} + +#endif /* Spine_SkeletonBounds_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonClipping.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonClipping.h new file mode 100644 index 000000000000..52b868a86986 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonClipping.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonClipping_h +#define Spine_SkeletonClipping_h + +#include +#include + +namespace spine { + class Slot; + + class ClippingAttachment; + + class SP_API SkeletonClipping : public SpineObject { + public: + SkeletonClipping(); + + size_t clipStart(Slot &slot, ClippingAttachment *clip); + + void clipEnd(Slot &slot); + + void clipEnd(); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength, float *uvs, size_t stride); + + void + clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, size_t stride); + + bool isClipping(); + + Vector &getClippedVertices(); + + Vector &getClippedTriangles(); + + Vector &getClippedUVs(); + + private: + Triangulator _triangulator; + Vector _clippingPolygon; + Vector _clipOutput; + Vector _clippedVertices; + Vector _clippedTriangles; + Vector _clippedUVs; + Vector _scratch; + ClippingAttachment *_clipAttachment; + Vector *> *_clippingPolygons; + + /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ + bool clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output); + + static void makeClockwise(Vector &polygon); + }; +} + +#endif /* Spine_SkeletonClipping_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonData.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonData.h new file mode 100644 index 000000000000..71afa1499d36 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonData.h @@ -0,0 +1,195 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonData_h +#define Spine_SkeletonData_h + +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class Skin; + + class EventData; + + class Animation; + + class IkConstraintData; + + class TransformConstraintData; + + class PathConstraintData; + + class PhysicsConstraintData; + +/// Stores the setup pose and all of the stateless data for a skeleton. + class SP_API SkeletonData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + public: + SkeletonData(); + + ~SkeletonData(); + + /// Finds a bone by comparing each bone's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + BoneData *findBone(const String &boneName); + + /// @return May be NULL. + SlotData *findSlot(const String &slotName); + + /// @return May be NULL. + Skin *findSkin(const String &skinName); + + /// @return May be NULL. + spine::EventData *findEvent(const String &eventDataName); + + /// @return May be NULL. + Animation *findAnimation(const String &animationName); + + /// @return May be NULL. + IkConstraintData *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraintData *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraintData *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraintData *findPhysicsConstraint(const String &constraintName); + + const String &getName(); + + void setName(const String &inValue); + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + Vector &getBones(); + + Vector &getSlots(); + + /// All skins, including the default skin. + Vector &getSkins(); + + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// @return May be NULL. + Skin *getDefaultSkin(); + + void setDefaultSkin(Skin *inValue); + + Vector &getEvents(); + + Vector &getAnimations(); + + Vector &getIkConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPathConstraints(); + + Vector &getPhysicsConstraints(); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + float getReferenceScale(); + + void setReferenceScale(float inValue); + + /// The Spine version used to export this data, or NULL. + const String &getVersion(); + + void setVersion(const String &inValue); + + const String &getHash(); + + void setHash(const String &inValue); + + const String &getImagesPath(); + + void setImagesPath(const String &inValue); + + const String &getAudioPath(); + + void setAudioPath(const String &inValue); + + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + float getFps(); + + void setFps(float inValue); + + private: + String _name; + Vector _bones; // Ordered parents first + Vector _slots; // Setup pose draw order. + Vector _skins; + Skin *_defaultSkin; + Vector _events; + Vector _animations; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + float _x, _y, _width, _height; + float _referenceScale; + String _version; + String _hash; + Vector _strings; + + // Nonessential. + float _fps; + String _imagesPath; + String _audioPath; + }; +} + +#endif /* Spine_SkeletonData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonJson.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonJson.h new file mode 100644 index 000000000000..73ac5a0712f6 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonJson.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonJson_h +#define Spine_SkeletonJson_h + +#include +#include +#include + +namespace spine { + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class VertexAttachment; + + class Animation; + + class Json; + + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class String; + + class Sequence; + + class SP_API SkeletonJson : public SpineObject { + public: + explicit SkeletonJson(Atlas *atlas); + + explicit SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonJson(); + + SkeletonData *readSkeletonDataFile(const String &path); + + SkeletonData *readSkeletonData(const char *json); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + float _scale; + const bool _ownsLoader; + String _error; + + static Sequence *readSequence(Json *sequence); + + static void + setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2); + + static int + readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + static Timeline *readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale); + + static Timeline * + readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, float defaultValue, + float scale); + + Animation *readAnimation(Json *root, SkeletonData *skeletonData); + + void readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength); + + void setError(Json *root, const String &value1, const String &value2); + + int findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines); + }; +} + +#endif /* Spine_SkeletonJson_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SkeletonRenderer.h b/modules/spine_godot/spine-cpp/include/spine/SkeletonRenderer.h new file mode 100644 index 000000000000..7da2b62b4a0d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SkeletonRenderer.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_SkeletonRenderer_h +#define Spine_SkeletonRenderer_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + struct SP_API RenderCommand { + float *positions; + float *uvs; + uint32_t *colors; + uint32_t *darkColors; + int32_t numVertices; + uint16_t *indices; + int32_t numIndices; + BlendMode blendMode; + void *texture; + RenderCommand *next; + }; + + class SP_API SkeletonRenderer: public SpineObject { + public: + explicit SkeletonRenderer(); + + ~SkeletonRenderer(); + + RenderCommand *render(Skeleton &skeleton); + private: + BlockAllocator _allocator; + Vector _worldVertices; + Vector _quadIndices; + SkeletonClipping _clipping; + Vector _renderCommands; + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/Skin.h b/modules/spine_godot/spine-cpp/include/spine/Skin.h new file mode 100644 index 000000000000..9b2383c7c769 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Skin.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Skin_h +#define Spine_Skin_h + +#include +#include +#include + +namespace spine { + class Attachment; + + class Skeleton; + + class BoneData; + + class ConstraintData; + +/// Stores attachments by slot index and attachment name. +/// See SkeletonData::getDefaultSkin, Skeleton::getSkin, and +/// http://esotericsoftware.com/spine-runtime-skins in the Spine Runtimes Guide. + class SP_API Skin : public SpineObject { + friend class Skeleton; + + public: + class SP_API AttachmentMap : public SpineObject { + friend class Skin; + + public: + struct SP_API Entry { + size_t _slotIndex; + String _name; + Attachment *_attachment; + + Entry(size_t slotIndex, const String &name, Attachment *attachment) : + _slotIndex(slotIndex), + _name(name), + _attachment(attachment) { + } + }; + + class SP_API Entries { + friend class AttachmentMap; + + public: + bool hasNext() { + while (true) { + if (_slotIndex >= _buckets.size()) return false; + if (_bucketIndex >= _buckets[_slotIndex].size()) { + _bucketIndex = 0; + ++_slotIndex; + continue; + }; + return true; + } + } + + Entry &next() { + Entry &result = _buckets[_slotIndex][_bucketIndex]; + ++_bucketIndex; + return result; + } + + protected: + Entries(Vector > &buckets) : _buckets(buckets), _slotIndex(0), _bucketIndex(0) { + } + + private: + Vector > &_buckets; + size_t _slotIndex; + size_t _bucketIndex; + }; + + void put(size_t slotIndex, const String &attachmentName, Attachment *attachment); + + Attachment *get(size_t slotIndex, const String &attachmentName); + + void remove(size_t slotIndex, const String &attachmentName); + + Entries getEntries(); + + protected: + AttachmentMap(); + + private: + + int findInBucket(Vector &, const String &attachmentName); + + Vector > _buckets; + }; + + explicit Skin(const String &name); + + ~Skin(); + + /// Adds an attachment to the skin for the specified slot index and name. + /// If the name already exists for the slot, the previous value is replaced. + void setAttachment(size_t slotIndex, const String &name, Attachment *attachment); + + /// Returns the attachment for the specified slot index and name, or NULL. + Attachment *getAttachment(size_t slotIndex, const String &name); + + // Removes the attachment from the skin. + void removeAttachment(size_t slotIndex, const String &name); + + /// Finds the skin keys for a given slot. The results are added to the passed array of names. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param names Found skin key names will be added to this array. + void findNamesForSlot(size_t slotIndex, Vector &names); + + /// Finds the attachments for a given slot. The results are added to the passed array of Attachments. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param attachments Found Attachments will be added to this array. + void findAttachmentsForSlot(size_t slotIndex, Vector &attachments); + + const String &getName(); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. + void addSkin(Skin *other); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. Attachments are deep copied. + void copySkin(Skin *other); + + AttachmentMap::Entries getAttachments(); + + Vector &getBones(); + + Vector &getConstraints(); + + Color &getColor() { return _color; } + + private: + const String _name; + AttachmentMap _attachments; + Vector _bones; + Vector _constraints; + Color _color; + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + void attachAll(Skeleton &skeleton, Skin &oldSkin); + }; +} + +#endif /* Spine_Skin_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Slot.h b/modules/spine_godot/spine-cpp/include/spine/Slot.h new file mode 100644 index 000000000000..1c828ed4493f --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Slot.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Slot_h +#define Spine_Slot_h + +#include +#include +#include + +namespace spine { + class SlotData; + + class Bone; + + class Skeleton; + + class Attachment; + + class SP_API Slot : public SpineObject { + friend class VertexAttachment; + + friend class Skeleton; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + Slot(SlotData &data, Bone &bone); + + void setToSetupPose(); + + SlotData &getData(); + + Bone &getBone(); + + Skeleton &getSkeleton(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + /// May be NULL. + Attachment *getAttachment(); + + void setAttachment(Attachment *inValue); + + int getAttachmentState(); + + void setAttachmentState(int state); + + Vector &getDeform(); + + int getSequenceIndex(); + + void setSequenceIndex(int index); + + private: + SlotData &_data; + Bone &_bone; + Skeleton &_skeleton; + Color _color; + Color _darkColor; + bool _hasDarkColor; + Attachment *_attachment; + int _attachmentState; + int _sequenceIndex; + Vector _deform; + }; +} + +#endif /* Spine_Slot_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SlotData.h b/modules/spine_godot/spine-cpp/include/spine/SlotData.h new file mode 100644 index 000000000000..8e16ea9d0c8d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SlotData.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SlotData_h +#define Spine_SlotData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API SlotData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + SlotData(int index, const String &name, BoneData &boneData); + + int getIndex(); + + const String &getName(); + + BoneData &getBoneData(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + void setHasDarkColor(bool inValue); + + /// May be empty. + const String &getAttachmentName(); + + void setAttachmentName(const String &inValue); + + BlendMode getBlendMode(); + + void setBlendMode(BlendMode inValue); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + String _name; + BoneData &_boneData; + Color _color; + Color _darkColor; + + bool _hasDarkColor; + String _attachmentName; + BlendMode _blendMode; + bool _visible; + }; +} + +#endif /* Spine_SlotData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SpacingMode.h b/modules/spine_godot/spine-cpp/include/spine/SpacingMode.h new file mode 100644 index 000000000000..84a311ba887d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SpacingMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SpacingMode_h +#define Spine_SpacingMode_h + +namespace spine { + enum SpacingMode { + SpacingMode_Length = 0, + SpacingMode_Fixed, + SpacingMode_Percent, + SpacingMode_Proportional + }; +} + +#endif /* Spine_SpacingMode_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/SpineObject.h b/modules/spine_godot/spine-cpp/include/spine/SpineObject.h new file mode 100644 index 000000000000..dc893695aeab --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SpineObject.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Object_h +#define Spine_Object_h + +#include +#include + +#include + +namespace spine { + class String; + + class SP_API SpineObject { + public: + void *operator new(size_t sz); + + void *operator new(size_t sz, const char *file, int line); + + void *operator new(size_t sz, void *ptr); + + void operator delete(void *p, const char *file, int line); + + void operator delete(void *p, void *mem); + + void operator delete(void *p); + + virtual ~SpineObject(); + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/SpineString.h b/modules/spine_godot/spine-cpp/include/spine/SpineString.h new file mode 100644 index 000000000000..3f77431d69e6 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/SpineString.h @@ -0,0 +1,246 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_STRING_H +#define SPINE_STRING_H + +#include +#include + +#include +#include + +namespace spine { + class SP_API String : public SpineObject { + public: + String() : _length(0), _buffer(NULL), _tempowner(true) { + } + + String(const char *chars, bool own = false, bool tofree = true) { + _tempowner = tofree; + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + if (!own) { + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } else { + _buffer = (char *) chars; + } + } + } + + String(const String &other) { + _tempowner = true; + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + } + + size_t length() const { + return _length; + } + + bool isEmpty() const { + return _length == 0; + } + + const char *buffer() const { + return _buffer; + } + + void own(const String &other) { + if (this == &other) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + _length = other._length; + _buffer = other._buffer; + other._length = 0; + other._buffer = NULL; + } + + void own(const char *chars) { + if (_buffer == chars) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = (char *) chars; + } + } + + void unown() { + _length = 0; + _buffer = NULL; + } + + String &operator=(const String &other) { + if (this == &other) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + return *this; + } + + String &operator=(const char *chars) { + if (_buffer == chars) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } + return *this; + } + + String &append(const char *chars) { + size_t len = strlen(chars); + size_t thisLen = _length; + _length = _length + len; + bool same = chars == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : chars), len + 1); + return *this; + } + + String &append(const String &other) { + size_t len = other.length(); + size_t thisLen = _length; + _length = _length + len; + bool same = other._buffer == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : other._buffer), len + 1); + return *this; + } + + String &append(int other) { + char str[100]; + snprintf(str, 100, "%i", other); + append(str); + return *this; + } + + String &append(float other) { + char str[100]; + snprintf(str, 100, "%f", other); + append(str); + return *this; + } + + bool startsWith(const String &needle) const { + if (needle.length() > length()) return false; + for (int i = 0; i < (int)needle.length(); i++) { + if (buffer()[i] != needle.buffer()[i]) return false; + } + return true; + } + + int lastIndexOf(const char c) const { + for (int i = (int)length() - 1; i >= 0; i--) { + if (buffer()[i] == c) return i; + } + return -1; + } + + String substring(int startIndex, int length) const { + if (startIndex < 0 || startIndex >= (int)_length || length < 0 || startIndex + length > (int)_length) { + return String(); + } + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + String substring(int startIndex) const { + if (startIndex < 0 || startIndex >= (int)_length) { + return String(); + } + int length = (int)_length - startIndex; + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + friend bool operator==(const String &a, const String &b) { + if (a._buffer == b._buffer) return true; + if (a._length != b._length) return false; + if (a._buffer && b._buffer) { + return strcmp(a._buffer, b._buffer) == 0; + } else { + return false; + } + } + + friend bool operator!=(const String &a, const String &b) { + return !(a == b); + } + + ~String() { + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + } + + private: + mutable size_t _length; + mutable char *_buffer; + mutable bool _tempowner; + }; +} + + +#endif //SPINE_STRING_H diff --git a/modules/spine_godot/spine-cpp/include/spine/TextureLoader.h b/modules/spine_godot/spine-cpp/include/spine/TextureLoader.h new file mode 100644 index 000000000000..60f86afb9828 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TextureLoader.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TextureLoader_h +#define Spine_TextureLoader_h + +#include +#include + +namespace spine { + class AtlasPage; + + class SP_API TextureLoader : public SpineObject { + public: + TextureLoader(); + + virtual ~TextureLoader(); + + virtual void load(AtlasPage &page, const String &path) = 0; + + virtual void unload(void *texture) = 0; + }; +} + +#endif /* Spine_TextureLoader_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/TextureRegion.h b/modules/spine_godot/spine-cpp/include/spine/TextureRegion.h new file mode 100644 index 000000000000..1082c5de2dc0 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TextureRegion.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TextureRegion_h +#define Spine_TextureRegion_h + +#include + +namespace spine { + class SP_API TextureRegion : public SpineObject { + public: + void *rendererObject; + float u, v, u2, v2; + int degrees; + float offsetX, offsetY; + int width, height; + int originalWidth, originalHeight; + + TextureRegion(): rendererObject(NULL), u(0), v(0), u2(0), v2(0), degrees(0), offsetX(0), offsetY(0), width(0), height(0), originalWidth(0), originalHeight(0) {}; + ~TextureRegion() {}; + }; +} + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/Timeline.h b/modules/spine_godot/spine-cpp/include/spine/Timeline.h new file mode 100644 index 000000000000..c9ded4b641e2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Timeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Timeline_h +#define Spine_Timeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class Skeleton; + + class Event; + + class SP_API Timeline : public SpineObject { + RTTI_DECL + + public: + Timeline(size_t frameCount, size_t frameEntries); + + virtual ~Timeline(); + + /// Sets the value(s) for the specified time. + /// @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// @param lastTime lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and time (inclusive). + /// @param time The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// @param pEvents If any events are fired, they are added to this array. Can be NULL to ignore firing events or if the timeline does not fire events. May be NULL. + /// @param alpha alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline + /// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting alpha over + /// time, an animation can be mixed in or out. alpha can also be useful to apply animations on top of each other (layered). + /// @param blend Controls how mixing is applied when alpha is than 1. + /// @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction) = 0; + + size_t getFrameEntries(); + + size_t getFrameCount(); + + Vector &getFrames(); + + float getDuration(); + + virtual Vector &getPropertyIds(); + + protected: + void setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount); + + Vector _propertyIds; + Vector _frames; + size_t _frameEntries; + }; +} + +#endif /* Spine_Timeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/TransformConstraint.h b/modules/spine_godot/spine-cpp/include/spine/TransformConstraint.h new file mode 100644 index 000000000000..cc8590292373 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TransformConstraint.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TransformConstraint_h +#define Spine_TransformConstraint_h + +#include + +#include + +namespace spine { + class TransformConstraintData; + + class Skeleton; + + class Bone; + + class SP_API TransformConstraint : public Updatable { + friend class Skeleton; + + friend class TransformConstraintTimeline; + + RTTI_DECL + + public: + TransformConstraint(TransformConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + TransformConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + float getMixScaleX(); + + void setMixScaleX(float inValue); + + float getMixScaleY(); + + void setMixScaleY(float inValue); + + float getMixShearY(); + + void setMixShearY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + TransformConstraintData &_data; + Vector _bones; + Bone *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + bool _active; + + void applyAbsoluteWorld(); + + void applyRelativeWorld(); + + void applyAbsoluteLocal(); + + void applyRelativeLocal(); + }; +} + +#endif /* Spine_TransformConstraint_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/TransformConstraintData.h b/modules/spine_godot/spine-cpp/include/spine/TransformConstraintData.h new file mode 100644 index 000000000000..9446bf8a3839 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TransformConstraintData.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintData_h +#define Spine_TransformConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API TransformConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class TransformConstraint; + + friend class Skeleton; + + friend class TransformConstraintTimeline; + + public: + RTTI_DECL + + explicit TransformConstraintData(const String &name); + + Vector &getBones(); + + BoneData *getTarget(); + + void setTarget(BoneData *target); + + float getMixRotate(); + + void setMixRotate(float mixRotate); + + float getMixX(); + + void setMixX(float mixX); + + float getMixY(); + + void setMixY(float mixY); + + float getMixScaleX(); + + void setMixScaleX(float mixScaleX); + + float getMixScaleY(); + + void setMixScaleY(float mixScaleY); + + float getMixShearY(); + + void setMixShearY(float mixShearY); + + float getOffsetRotation(); + + void setOffsetRotation(float offsetRotation); + + float getOffsetX(); + + void setOffsetX(float offsetX); + + float getOffsetY(); + + void setOffsetY(float offsetY); + + float getOffsetScaleX(); + + void setOffsetScaleX(float offsetScaleX); + + float getOffsetScaleY(); + + void setOffsetScaleY(float offsetScaleY); + + float getOffsetShearY(); + + void setOffsetShearY(float offsetShearY); + + bool isRelative(); + + void setRelative(bool isRelative); + + bool isLocal(); + + void setLocal(bool isLocal); + + private: + Vector _bones; + BoneData *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + float _offsetRotation, _offsetX, _offsetY, _offsetScaleX, _offsetScaleY, _offsetShearY; + bool _relative, _local; + }; +} + +#endif /* Spine_TransformConstraintData_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/TransformConstraintTimeline.h b/modules/spine_godot/spine-cpp/include/spine/TransformConstraintTimeline.h new file mode 100644 index 000000000000..514c6d3a9e46 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TransformConstraintTimeline.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintTimeline_h +#define Spine_TransformConstraintTimeline_h + +#include + +namespace spine { + + class SP_API TransformConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TransformConstraintTimeline(size_t frameCount, size_t bezierCount, int transformConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(size_t frameIndex, float time, float mixRotate, float mixX, float mixY, float mixScaleX, + float mixScaleY, float mixShearY); + + int getTransformConstraintIndex() { return _constraintIndex; } + + void setTransformConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 7; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 3; + static const int SCALEX = 4; + static const int SCALEY = 5; + static const int SHEARY = 6; + }; +} + +#endif /* Spine_TransformConstraintTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/TranslateTimeline.h b/modules/spine_godot/spine-cpp/include/spine/TranslateTimeline.h new file mode 100644 index 000000000000..77dbf7e7c2ef --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/TranslateTimeline.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_TranslateTimeline_h +#define Spine_TranslateTimeline_h + +#include + +#include +#include + +namespace spine { + + class SP_API TranslateTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_TranslateTimeline_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Triangulator.h b/modules/spine_godot/spine-cpp/include/spine/Triangulator.h new file mode 100644 index 000000000000..801167d86b19 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Triangulator.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Triangulator_h +#define Spine_Triangulator_h + +#include +#include + +namespace spine { + class SP_API Triangulator : public SpineObject { + public: + ~Triangulator(); + + Vector &triangulate(Vector &vertices); + + Vector* > & + decompose(Vector + &vertices, + Vector &triangles + ); + + private: + Vector* > + _convexPolygons; + Vector* > + _convexPolygonsIndices; + + Vector _indices; + Vector _isConcaveArray; + Vector _triangles; + + Pool > _polygonPool; + Pool > _polygonIndicesPool; + + static bool isConcave(int index, int vertexCount, Vector &vertices, Vector &indices); + + static bool positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + + static int winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + }; +} + +#endif /* Spine_Triangulator_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Updatable.h b/modules/spine_godot/spine-cpp/include/spine/Updatable.h new file mode 100644 index 000000000000..dc5b4d375677 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Updatable.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Updatable_h +#define Spine_Updatable_h + +#include +#include +#include + +namespace spine { + class SP_API Updatable : public SpineObject { + RTTI_DECL + + public: + Updatable(); + + virtual ~Updatable(); + + virtual void update(Physics physics) = 0; + + virtual bool isActive() = 0; + + virtual void setActive(bool inValue) = 0; + }; +} + +#endif /* Spine_Updatable_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Vector.h b/modules/spine_godot/spine-cpp/include/spine/Vector.h new file mode 100644 index 000000000000..8ad9196e3ece --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Vector.h @@ -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. + *****************************************************************************/ + +#ifndef Spine_Vector_h +#define Spine_Vector_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Vector : public SpineObject { + public: + using size_type = size_t; + using value_type = T; + + Vector() : _size(0), _capacity(0), _buffer(NULL) { + } + + Vector(const Vector &inVector) : _size(inVector._size), _capacity(inVector._capacity), _buffer(NULL) { + if (_capacity > 0) { + _buffer = allocate(_capacity); + for (size_t i = 0; i < _size; ++i) { + construct(_buffer + i, inVector._buffer[i]); + } + } + } + + ~Vector() { + clear(); + deallocate(_buffer); + } + + inline void clear() { + for (size_t i = 0; i < _size; ++i) { + destroy(_buffer + (_size - 1 - i)); + } + + _size = 0; + } + + inline size_t getCapacity() const { + return _capacity; + } + + inline size_t size() const { + return _size; + } + + inline void setSize(size_t newSize, const T &defaultValue) { + assert(newSize >= 0); + size_t oldSize = _size; + _size = newSize; + if (_capacity < newSize) { + if (_capacity == 0) { + _capacity = _size; + } else { + _capacity = (int) (_size * 1.75f); + } + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + } + if (oldSize < _size) { + for (size_t i = oldSize; i < _size; i++) { + construct(_buffer + i, defaultValue); + } + } else { + for (size_t i = _size; i < oldSize; i++) { + destroy(_buffer + i); + } + } + } + + inline void ensureCapacity(size_t newCapacity = 0) { + if (_capacity >= newCapacity) return; + _capacity = newCapacity; + _buffer = SpineExtension::realloc(_buffer, newCapacity, __FILE__, __LINE__); + } + + inline void add(const T &inValue) { + if (_size == _capacity) { + // inValue might reference an element in this buffer + // When we reallocate, the reference becomes invalid. + // We thus need to create a defensive copy before + // reallocating. + T valueCopy = inValue; + _capacity = (int) (_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + construct(_buffer + _size++, valueCopy); + } else { + construct(_buffer + _size++, inValue); + } + } + + inline void addAll(const Vector &inValue) { + ensureCapacity(this->size() + inValue.size()); + for (size_t i = 0; i < inValue.size(); i++) { + add(inValue[i]); + } + } + + inline void clearAndAddAll(const Vector &inValue) { + this->clear(); + this->addAll(inValue); + } + + inline void removeAt(size_t inIndex) { + assert(inIndex < _size); + + --_size; + + if (inIndex != _size) { + for (size_t i = inIndex; i < _size; ++i) { + T tmp(_buffer[i]); + _buffer[i] = _buffer[i + 1]; + _buffer[i + 1] = tmp; + } + } + + destroy(_buffer + _size); + } + + inline bool contains(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return true; + } + } + + return false; + } + + inline int indexOf(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return (int) i; + } + } + + return -1; + } + + inline T &operator[](size_t inIndex) { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline const T &operator[](size_t inIndex) const { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline friend bool operator==(Vector &lhs, Vector &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0, n = lhs.size(); i < n; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; + } + + inline friend bool operator!=(Vector &lhs, Vector &rhs) { + return !(lhs == rhs); + } + + Vector &operator=(const Vector &inVector) { + if (this != &inVector) { + clearAndAddAll(inVector); + } + return *this; + } + + inline T *buffer() { + return _buffer; + } + + private: + size_t _size; + size_t _capacity; + T *_buffer; + + inline T *allocate(size_t n) { + assert(n > 0); + + T *ptr = SpineExtension::calloc(n, __FILE__, __LINE__); + + assert(ptr); + + return ptr; + } + + inline void deallocate(T *buffer) { + if (_buffer) { + SpineExtension::free(buffer, __FILE__, __LINE__); + } + } + + inline void construct(T *buffer, const T &val) { + new(buffer) T(val); + } + + inline void destroy(T *buffer) { + buffer->~T(); + } + + }; +} + +#endif /* Spine_Vector_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Version.h b/modules/spine_godot/spine-cpp/include/spine/Version.h new file mode 100644 index 000000000000..eaa56558bbbe --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Version.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_VERSION_H_ +#define SPINE_VERSION_H_ + +#define SPINE_MAJOR_VERSION 4 +#define SPINE_MINOR_VERSION 2 +#define SPINE_VERSION_STRING "4.2" + +#endif diff --git a/modules/spine_godot/spine-cpp/include/spine/VertexAttachment.h b/modules/spine_godot/spine-cpp/include/spine/VertexAttachment.h new file mode 100644 index 000000000000..e4957a9f1cee --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/VertexAttachment.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_VertexAttachment_h +#define Spine_VertexAttachment_h + +#include + +#include + +namespace spine { + class Slot; + + /// An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + class SP_API VertexAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class DeformTimeline; + + RTTI_DECL + + public: + explicit VertexAttachment(const String &name); + + virtual ~VertexAttachment(); + + void computeWorldVertices(Slot &slot, float *worldVertices); + + void computeWorldVertices(Slot &slot, Vector &worldVertices); + + /// Transforms local vertices to world coordinates. + /// @param start The index of the first Vertices value to transform. Each vertex has 2 values, x and y. + /// @param count The number of world vertex values to output. Must be less than or equal to WorldVerticesLength - start. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + count. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, size_t offset, + size_t stride = 2); + + /// Gets a unique ID for this attachment. + int getId(); + + Vector &getBones(); + + Vector &getVertices(); + + size_t getWorldVerticesLength(); + + void setWorldVerticesLength(size_t inValue); + + Attachment * getTimelineAttachment(); + + void setTimelineAttachment(Attachment *attachment); + + void copyTo(VertexAttachment *other); + + protected: + Vector _bones; + Vector _vertices; + size_t _worldVerticesLength; + Attachment *_timelineAttachment; + + private: + const int _id; + + static int getNextID(); + }; +} + +#endif /* Spine_VertexAttachment_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/Vertices.h b/modules/spine_godot/spine-cpp/include/spine/Vertices.h new file mode 100644 index 000000000000..070d823c796d --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/Vertices.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Vertices_h +#define Spine_Vertices_h + +#include + +namespace spine { + class SP_API Vertices : public SpineObject { + public: + Vector _bones; + Vector _vertices; + }; +} + +#endif /* Spine_Vertices_h */ diff --git a/modules/spine_godot/spine-cpp/include/spine/dll.h b/modules/spine_godot/spine-cpp/include/spine/dll.h new file mode 100644 index 000000000000..2681a5381599 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/dll.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SHAREDLIB_H +#define SPINE_SHAREDLIB_H + +#ifdef _WIN32 +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) +#else +#ifndef DLLIMPORT +#define DLLIMPORT +#endif +#ifndef DLLEXPORT +#define DLLEXPORT +#endif +#endif + +#ifdef SPINEPLUGIN_API +#define SP_API SPINEPLUGIN_API +#else +#define SP_API +#endif + +#endif /* SPINE_SHAREDLIB_H */ diff --git a/modules/spine_godot/spine-cpp/include/spine/spine.h b/modules/spine_godot/spine-cpp/include/spine/spine.h new file mode 100644 index 000000000000..ab3e5414ee55 --- /dev/null +++ b/modules/spine_godot/spine-cpp/include/spine/spine.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SPINE_H_ +#define SPINE_SPINE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/modules/spine_godot/spine-cpp/src/spine/Animation.cpp b/modules/spine_godot/spine-cpp/src/spine/Animation.cpp new file mode 100644 index 000000000000..21fafac6f6b9 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Animation.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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 +#include +#include +#include + +#include + +#include + +using namespace spine; + +Animation::Animation(const String &name, Vector &timelines, float duration) : _timelines(timelines), + _timelineIds(), + _duration(duration), + _name(name) { + assert(_name.length() > 0); + for (size_t i = 0; i < timelines.size(); i++) { + Vector propertyIds = timelines[i]->getPropertyIds(); + for (size_t ii = 0; ii < propertyIds.size(); ii++) + _timelineIds.put(propertyIds[ii], true); + } +} + +bool Animation::hasTimeline(Vector &ids) { + for (size_t i = 0; i < ids.size(); i++) { + if (_timelineIds.containsKey(ids[i])) return true; + } + return false; +} + +Animation::~Animation() { + ContainerUtil::cleanUpVectorOfPointers(_timelines); +} + +void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + 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, pEvents, alpha, blend, direction); + } +} + +const String &Animation::getName() { + return _name; +} + +Vector &Animation::getTimelines() { + return _timelines; +} + +float Animation::getDuration() { + return _duration; +} + +void Animation::setDuration(float inValue) { + _duration = inValue; +} + +int Animation::search(Vector &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(Vector &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); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/AnimationState.cpp b/modules/spine_godot/spine-cpp/src/spine/AnimationState.cpp new file mode 100644 index 000000000000..7920e948f3a3 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/AnimationState.cpp @@ -0,0 +1,1104 @@ +/****************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +void dummyOnAnimationEventFunc(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event = NULL) { + SP_UNUSED(state); + SP_UNUSED(type); + SP_UNUSED(entry); + SP_UNUSED(event); +} + +TrackEntry::TrackEntry() : _animation(NULL), _previous(NULL), _next(NULL), _mixingFrom(NULL), _mixingTo(0), + _trackIndex(0), _loop(false), _holdPrevious(false), _reverse(false), + _shortestRotation(false), + _eventThreshold(0), _mixAttachmentThreshold(0), _alphaAttachmentThreshold(0), _mixDrawOrderThreshold(0), _animationStart(0), + _animationEnd(0), _animationLast(0), _nextAnimationLast(0), _delay(0), _trackTime(0), + _trackLast(0), _nextTrackLast(0), _trackEnd(0), _timeScale(1.0f), _alpha(0), _mixTime(0), + _mixDuration(0), _interruptAlpha(0), _totalAlpha(0), _mixBlend(MixBlend_Replace), + _listener(dummyOnAnimationEventFunc), _listenerObject(NULL) { +} + +TrackEntry::~TrackEntry() {} + +int TrackEntry::getTrackIndex() { return _trackIndex; } + +Animation *TrackEntry::getAnimation() { return _animation; } + +TrackEntry *TrackEntry::getPrevious() { return _previous; } + +bool TrackEntry::getLoop() { return _loop; } + +void TrackEntry::setLoop(bool inValue) { _loop = inValue; } + +bool TrackEntry::getHoldPrevious() { return _holdPrevious; } + +void TrackEntry::setHoldPrevious(bool inValue) { _holdPrevious = inValue; } + +bool TrackEntry::getReverse() { return _reverse; } + +void TrackEntry::setReverse(bool inValue) { _reverse = inValue; } + +bool TrackEntry::getShortestRotation() { return _shortestRotation; } + +void TrackEntry::setShortestRotation(bool inValue) { _shortestRotation = inValue; } + +float TrackEntry::getDelay() { return _delay; } + +void TrackEntry::setDelay(float inValue) { _delay = inValue; } + +float TrackEntry::getTrackTime() { return _trackTime; } + +void TrackEntry::setTrackTime(float inValue) { _trackTime = inValue; } + +float TrackEntry::getTrackEnd() { return _trackEnd; } + +void TrackEntry::setTrackEnd(float inValue) { _trackEnd = inValue; } + +float TrackEntry::getAnimationStart() { return _animationStart; } + +void TrackEntry::setAnimationStart(float inValue) { _animationStart = inValue; } + +float TrackEntry::getAnimationEnd() { return _animationEnd; } + +void TrackEntry::setAnimationEnd(float inValue) { _animationEnd = inValue; } + +float TrackEntry::getAnimationLast() { return _animationLast; } + +void TrackEntry::setAnimationLast(float inValue) { + _animationLast = inValue; + _nextAnimationLast = inValue; +} + +float TrackEntry::getAnimationTime() { + if (_loop) { + float duration = _animationEnd - _animationStart; + if (duration == 0) return _animationStart; + return MathUtil::fmod(_trackTime, duration) + _animationStart; + } + + return MathUtil::min(_trackTime + _animationStart, _animationEnd); +} + +float TrackEntry::getTimeScale() { return _timeScale; } + +void TrackEntry::setTimeScale(float inValue) { _timeScale = inValue; } + +float TrackEntry::getAlpha() { return _alpha; } + +void TrackEntry::setAlpha(float inValue) { _alpha = inValue; } + +float TrackEntry::getEventThreshold() { return _eventThreshold; } + +void TrackEntry::setEventThreshold(float inValue) { _eventThreshold = inValue; } + +float TrackEntry::getMixAttachmentThreshold() { return _mixAttachmentThreshold; } + +void TrackEntry::setMixAttachmentThreshold(float inValue) { _mixAttachmentThreshold = inValue; } + +float TrackEntry::getAlphaAttachmentThreshold() { return _alphaAttachmentThreshold; } + +void TrackEntry::setAlphaAttachmentThreshold(float inValue) { _alphaAttachmentThreshold = inValue; } + +float TrackEntry::getMixDrawOrderThreshold() { return _mixDrawOrderThreshold; } + +void TrackEntry::setMixDrawOrderThreshold(float inValue) { _mixDrawOrderThreshold = inValue; } + +TrackEntry *TrackEntry::getNext() { return _next; } + +bool TrackEntry::isComplete() { + return _trackTime >= _animationEnd - _animationStart; +} + +float TrackEntry::getMixTime() { return _mixTime; } + +void TrackEntry::setMixTime(float inValue) { _mixTime = inValue; } + +float TrackEntry::getMixDuration() { return _mixDuration; } + +void TrackEntry::setMixDuration(float inValue) { _mixDuration = inValue; } + +void TrackEntry::setMixDuration(float mixDuration, float delay) { + _mixDuration = mixDuration; + if (delay <= 0) { + if (_previous != nullptr) + delay = MathUtil::max(delay + _previous->getTrackComplete() - mixDuration, 0.0f); + else + delay = 0; + } + this->_delay = delay; +} + +TrackEntry *TrackEntry::getMixingFrom() { return _mixingFrom; } + +TrackEntry *TrackEntry::getMixingTo() { return _mixingTo; } + +void TrackEntry::setMixBlend(MixBlend blend) { _mixBlend = blend; } + +MixBlend TrackEntry::getMixBlend() { return _mixBlend; } + +void TrackEntry::resetRotationDirections() { + _timelinesRotation.clear(); +} + +void TrackEntry::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void TrackEntry::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void TrackEntry::reset() { + _animation = NULL; + _previous = NULL; + _next = NULL; + _mixingFrom = NULL; + _mixingTo = NULL; + + setRendererObject(NULL); + + _timelineMode.clear(); + _timelineHoldMix.clear(); + _timelinesRotation.clear(); + + _listener = dummyOnAnimationEventFunc; + _listenerObject = NULL; +} + +float TrackEntry::getTrackComplete() { + float duration = _animationEnd - _animationStart; + if (duration != 0) { + if (_loop) return duration * (1 + (int) (_trackTime / duration));// Completion of next loop. + if (_trackTime < duration) return duration; // Before duration. + } + return _trackTime;// Next update. +} + +bool TrackEntry::wasApplied() { + return _nextTrackLast != -1; +} + +EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) : _type(eventType), + _entry(trackEntry), + _event(event) { +} + +EventQueue *EventQueue::newEventQueue(AnimationState &state) { + return new (__FILE__, __LINE__) EventQueue(state); +} + +EventQueueEntry EventQueue::newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event) { + return EventQueueEntry(eventType, entry, event); +} + +EventQueue::EventQueue(AnimationState &state) : _state(state), + _drainDisabled(false) { +} + +EventQueue::~EventQueue() { +} + +void EventQueue::start(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Start, entry)); + _state._animationsChanged = true; +} + +void EventQueue::interrupt(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Interrupt, entry)); +} + +void EventQueue::end(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_End, entry)); + _state._animationsChanged = true; +} + +void EventQueue::dispose(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Dispose, entry)); +} + +void EventQueue::complete(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Complete, entry)); +} + +void EventQueue::event(TrackEntry *entry, Event *event) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Event, entry, event)); +} + +/// Raises all events in the queue and drains the queue. +void EventQueue::drain() { + if (_drainDisabled) { + return; + } + + _drainDisabled = true; + + AnimationState &state = _state; + + // Don't cache _eventQueueEntries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete). + for (size_t i = 0; i < _eventQueueEntries.size(); ++i) { + EventQueueEntry queueEntry = _eventQueueEntries[i]; + TrackEntry *trackEntry = queueEntry._entry; + + switch (queueEntry._type) { + case EventType_Start: + case EventType_Interrupt: + case EventType_Complete: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + break; + case EventType_End: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + /* Fall through. */ + case EventType_Dispose: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, EventType_Dispose, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, EventType_Dispose, trackEntry, NULL); + else + state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + + if (!_state.getManualTrackEntryDisposal()) _state.disposeTrackEntry(trackEntry); + break; + case EventType_Event: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + break; + } + } + _eventQueueEntries.clear(); + + _drainDisabled = false; +} + +AnimationState::AnimationState(AnimationStateData *data) : _data(data), + _queue(EventQueue::newEventQueue(*this)), + _animationsChanged(false), + _listener(dummyOnAnimationEventFunc), + _listenerObject(NULL), + _unkeyedState(0), + _timeScale(1), + _manualTrackEntryDisposal(false) { +} + +AnimationState::~AnimationState() { + for (size_t i = 0; i < _tracks.size(); i++) { + TrackEntry *entry = _tracks[i]; + if (entry) { + TrackEntry *from = entry->_mixingFrom; + while (from) { + TrackEntry *curr = from; + from = curr->_mixingFrom; + delete curr; + } + TrackEntry *next = entry->_next; + while (next) { + TrackEntry *curr = next; + next = curr->_next; + delete curr; + } + delete entry; + } + } + delete _queue; +} + +void AnimationState::update(float delta) { + delta *= _timeScale; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL) { + continue; + } + + TrackEntry ¤t = *currentP; + + current._animationLast = current._nextAnimationLast; + current._trackLast = current._nextTrackLast; + + float currentDelta = delta * current._timeScale; + + if (current._delay > 0) { + current._delay -= currentDelta; + if (current._delay > 0) { + continue; + } + currentDelta = -current._delay; + current._delay = 0; + } + + TrackEntry *next = current._next; + if (next != NULL) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + float nextTime = current._trackLast - next->_delay; + if (nextTime >= 0) { + next->_delay = 0; + next->_trackTime += + current._timeScale == 0 ? 0 : (nextTime / current._timeScale + delta) * next->_timeScale; + current._trackTime += currentDelta; + setCurrent(i, next, true); + while (next->_mixingFrom != NULL) { + next->_mixTime += delta; + next = next->_mixingFrom; + } + continue; + } + } else if (current._trackLast >= current._trackEnd && current._mixingFrom == NULL) { + // clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. + _tracks[i] = NULL; + + _queue->end(currentP); + clearNext(currentP); + continue; + } + + if (current._mixingFrom != NULL && updateMixingFrom(currentP, delta)) { + // End mixing from entries once all have completed. + TrackEntry *from = current._mixingFrom; + current._mixingFrom = NULL; + if (from != NULL) from->_mixingTo = NULL; + while (from != NULL) { + _queue->end(from); + from = from->_mixingFrom; + } + } + + current._trackTime += currentDelta; + } + + _queue->drain(); +} + +bool AnimationState::apply(Skeleton &skeleton) { + if (_animationsChanged) { + animationsChanged(); + } + + bool applied = false; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL || currentP->_delay > 0) { + continue; + } + + TrackEntry ¤t = *currentP; + + applied = true; + MixBlend blend = i == 0 ? MixBlend_First : current._mixBlend; + + // apply mixing from entries first. + float alpha = current._alpha; + if (current._mixingFrom != NULL) { + alpha *= applyMixingFrom(currentP, skeleton, blend); + } else if (current._trackTime >= current._trackEnd && current._next == NULL) { + alpha = 0;// Set to setup pose the last time the entry will be applied. + } + bool attachments = alpha >= current._alphaAttachmentThreshold; + + + // apply current entry. + float animationLast = current._animationLast, animationTime = current.getAnimationTime(); + float applyTime = animationTime; + Vector *applyEvents = &_events; + if (current._reverse) { + applyTime = current._animation->getDuration() - applyTime; + applyEvents = NULL; + } + size_t timelineCount = current._animation->_timelines.size(); + Vector &timelines = current._animation->_timelines; + if ((i == 0 && alpha == 1) || blend == MixBlend_Add) { + if (i == 0) attachments = true; + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, blend, + attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection_In); + } + } else { + Vector &timelineMode = current._timelineMode; + + bool shortestRotation = current._shortestRotation; + bool firstFrame = !shortestRotation && current._timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) current._timelinesRotation.setSize(timelines.size() << 1, 0); + Vector &timelinesRotation = current._timelinesRotation; + + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + assert(timeline); + + MixBlend timelineBlend = timelineMode[ii] == Subsequent ? blend : MixBlend_Setup; + + if (!shortestRotation && timeline->getRTTI().isExactly(RotateTimeline::rtti)) + applyRotateTimeline(static_cast(timeline), skeleton, applyTime, alpha, + timelineBlend, timelinesRotation, ii << 1, firstFrame); + else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, + blend, attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, + MixDirection_In); + } + } + + queueEvents(currentP, animationTime); + _events.clear(); + current._nextAnimationLast = animationTime; + current._nextTrackLast = current._trackTime; + } + + int setupState = _unkeyedState + Setup; + Vector &slots = skeleton.getSlots(); + for (int i = 0, n = (int) slots.size(); i < n; i++) { + Slot *slot = slots[i]; + if (slot->getAttachmentState() == setupState) { + const String &attachmentName = slot->getData().getAttachmentName(); + slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName)); + } + } + _unkeyedState += 2; + + _queue->drain(); + return applied; +} + +void AnimationState::clearTracks() { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) + clearTrack(i); + _tracks.clear(); + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +void AnimationState::clearTrack(size_t trackIndex) { + if (trackIndex >= _tracks.size()) return; + + TrackEntry *current = _tracks[trackIndex]; + if (current == NULL) return; + + _queue->end(current); + + clearNext(current); + + TrackEntry *entry = current; + while (true) { + TrackEntry *from = entry->_mixingFrom; + if (from == NULL) break; + + _queue->end(from); + entry->_mixingFrom = NULL; + entry->_mixingTo = NULL; + entry = from; + } + + _tracks[current->_trackIndex] = NULL; + + _queue->drain(); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, const String &animationName, bool loop) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return setAnimation(trackIndex, animation, loop); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation, bool loop) { + assert(animation != NULL); + + bool interrupt = true; + TrackEntry *current = expandToIndex(trackIndex); + if (current != NULL) { + if (current->_nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + _tracks[trackIndex] = current->_mixingFrom; + _queue->interrupt(current); + _queue->end(current); + clearNext(current); + current = current->_mixingFrom; + interrupt = false; + } else { + clearNext(current); + } + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, current); + setCurrent(trackIndex, entry, interrupt); + _queue->drain(); + + return entry; +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return addAnimation(trackIndex, animation, loop, delay); +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay) { + assert(animation != NULL); + + TrackEntry *last = expandToIndex(trackIndex); + if (last != NULL) { + while (last->_next != NULL) + last = last->_next; + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, last); + + if (last == NULL) { + setCurrent(trackIndex, entry, true); + _queue->drain(); + if (delay < 0) delay = 0; + } else { + last->_next = entry; + entry->_previous = last; + if (delay <= 0) delay = MathUtil::max(delay + last->getTrackComplete() - entry->_mixDuration, 0.0f); + } + + entry->_delay = delay; + return entry; +} + +TrackEntry *AnimationState::setEmptyAnimation(size_t trackIndex, float mixDuration) { + TrackEntry *entry = setAnimation(trackIndex, AnimationState::getEmptyAnimation(), false); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +TrackEntry *AnimationState::addEmptyAnimation(size_t trackIndex, float mixDuration, float delay) { + TrackEntry *entry = addAnimation(trackIndex, AnimationState::getEmptyAnimation(), false, delay); + if (delay <= 0) entry->_delay = MathUtil::max(entry->_delay + entry->_mixDuration - mixDuration, 0.0f); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +void AnimationState::setEmptyAnimations(float mixDuration) { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *current = _tracks[i]; + if (current != NULL) { + setEmptyAnimation(i, mixDuration); + } + } + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +TrackEntry *AnimationState::getCurrent(size_t trackIndex) { + return trackIndex >= _tracks.size() ? NULL : _tracks[trackIndex]; +} + +AnimationStateData *AnimationState::getData() { + return _data; +} + +Vector &AnimationState::getTracks() { + return _tracks; +} + +float AnimationState::getTimeScale() { + return _timeScale; +} + +void AnimationState::setTimeScale(float inValue) { + _timeScale = inValue; +} + +void AnimationState::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void AnimationState::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void AnimationState::disableQueue() { + _queue->_drainDisabled = true; +} + +void AnimationState::enableQueue() { + _queue->_drainDisabled = false; +} + +void AnimationState::setManualTrackEntryDisposal(bool inValue) { + _manualTrackEntryDisposal = inValue; +} + +bool AnimationState::getManualTrackEntryDisposal() { + return _manualTrackEntryDisposal; +} + +void AnimationState::disposeTrackEntry(TrackEntry *entry) { + entry->reset(); + _trackEntryPool.free(entry); +} + +Animation *AnimationState::getEmptyAnimation() { + static Vector timelines; + static Animation ret(String(""), timelines, 0); + return &ret; +} + +void AnimationState::applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float time, + MixBlend blend, bool attachments) { + Slot *slot = skeleton.getSlots()[attachmentTimeline->getSlotIndex()]; + if (!slot->getBone().isActive()) return; + + Vector &frames = attachmentTimeline->getFrames(); + if (time < frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, slot->getData().getAttachmentName(), attachments); + } else { + setAttachment(skeleton, *slot, attachmentTimeline->getAttachmentNames()[Animation::search(frames, time)], + attachments); + } + + /* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/ + if (slot->getAttachmentState() <= _unkeyedState) slot->setAttachmentState(_unkeyedState + Setup); +} + + +void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, + MixBlend blend, Vector &timelinesRotation, size_t i, bool firstFrame) { + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + rotateTimeline->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In); + return; + } + + Bone *bone = skeleton._bones[rotateTimeline->_boneIndex]; + if (!bone->isActive()) return; + Vector &frames = rotateTimeline->_frames; + float r1, r2; + if (time < frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation; + default: + return; + case MixBlend_First: + r1 = bone->_rotation; + r2 = bone->_data._rotation; + } + } else { + r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation; + r2 = bone->_data._rotation + rotateTimeline->getCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float total, diff = r2 - r1; + diff -= MathUtil::ceil(diff / 360 - 0.5) * 360; + if (diff == 0) { + total = timelinesRotation[i]; + } else { + float lastTotal, lastDiff; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; + lastDiff = timelinesRotation[i + 1]; + } + float loops = lastTotal - MathUtil::fmod(lastTotal, 360.f); + total = diff + loops; + bool current = diff >= 0, dir = lastTotal >= 0; + if (MathUtil::abs(lastDiff) <= 90 && MathUtil::sign(lastDiff) != MathUtil::sign(diff)) { + if (MathUtil::abs(lastTotal - loops) > 180) { + total += 360.f * MathUtil::sign(lastTotal); + dir = current; + } else if (loops != 0) + total -= 360.f * MathUtil::sign(lastTotal); + else + dir = current; + } + if (dir != current) { + total += 360 * MathUtil::sign(lastTotal); + } + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone->_rotation = r1 + total * alpha; +} + +bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) { + TrackEntry *from = to->_mixingFrom; + if (from == NULL) { + return true; + } + + bool finished = updateMixingFrom(from, delta); + + from->_animationLast = from->_nextAnimationLast; + from->_trackLast = from->_nextTrackLast; + + // The from entry was applied at least once and the mix is complete. + if (to->_nextTrackLast != -1 && to->_mixTime >= to->_mixDuration) { + // Mixing is complete for all entries before the from entry or the mix is instantaneous. + if (from->_totalAlpha == 0 || to->_mixDuration == 0) { + to->_mixingFrom = from->_mixingFrom; + if (from->_mixingFrom) from->_mixingFrom->_mixingTo = to; + to->_interruptAlpha = from->_interruptAlpha; + _queue->end(from); + } + return finished; + } + + from->_trackTime += delta * from->_timeScale; + to->_mixTime += delta; + + return false; +} + +float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend blend) { + TrackEntry *from = to->_mixingFrom; + if (from->_mixingFrom != NULL) applyMixingFrom(from, skeleton, blend); + + float mix; + if (to->_mixDuration == 0) { + // Single frame mix to undo mixingFrom changes. + mix = 1; + if (blend == MixBlend_First) blend = MixBlend_Setup; + } else { + mix = to->_mixTime / to->_mixDuration; + if (mix > 1) { + mix = 1; + } + if (blend != MixBlend_First) blend = from->_mixBlend; + } + + bool attachments = mix < from->_mixAttachmentThreshold, drawOrder = mix < from->_mixDrawOrderThreshold; + Vector &timelines = from->_animation->_timelines; + size_t timelineCount = timelines.size(); + float alphaHold = from->_alpha * to->_interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from->_animationLast, animationTime = from->getAnimationTime(); + float applyTime = animationTime; + Vector *events = NULL; + if (from->_reverse) { + applyTime = from->_animation->_duration - applyTime; + } else { + if (mix < from->_eventThreshold) events = &_events; + } + + if (blend == MixBlend_Add) { + for (size_t i = 0; i < timelineCount; i++) + timelines[i]->apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection_Out); + } else { + Vector &timelineMode = from->_timelineMode; + Vector &timelineHoldMix = from->_timelineHoldMix; + + bool shortestRotation = from->_shortestRotation; + bool firstFrame = !shortestRotation && from->_timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) from->_timelinesRotation.setSize(timelines.size() << 1, 0); + + Vector &timelinesRotation = from->_timelinesRotation; + + from->_totalAlpha = 0; + for (size_t i = 0; i < timelineCount; i++) { + Timeline *timeline = timelines[i]; + MixDirection direction = MixDirection_Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) { + case Subsequent: + if (!drawOrder && (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti))) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case First: + timelineBlend = MixBlend_Setup; + alpha = alphaMix; + break; + case HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case HoldFirst: + timelineBlend = MixBlend_Setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend_Setup; + TrackEntry *holdMix = timelineHoldMix[i]; + alpha = alphaHold * MathUtil::max(0.0f, 1.0f - holdMix->_mixTime / holdMix->_mixDuration); + break; + } + from->_totalAlpha += alpha; + if (!shortestRotation && (timeline->getRTTI().isExactly(RotateTimeline::rtti))) { + applyRotateTimeline((RotateTimeline *) timeline, skeleton, applyTime, alpha, timelineBlend, + timelinesRotation, i << 1, firstFrame); + } else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) { + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, timelineBlend, + attachments && alpha >= from->_alphaAttachmentThreshold); + } else { + if (drawOrder && timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) && + timelineBlend == MixBlend_Setup) + direction = MixDirection_In; + timeline->apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to->_mixDuration > 0) { + queueEvents(from, animationTime); + } + + _events.clear(); + from->_nextAnimationLast = animationTime; + from->_nextTrackLast = from->_trackTime; + + return mix; +} + +void AnimationState::setAttachment(Skeleton &skeleton, Slot &slot, const String &attachmentName, bool attachments) { + slot.setAttachment( + attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot.getData().getIndex(), attachmentName)); + if (attachments) slot.setAttachmentState(_unkeyedState + Current); +} + +void AnimationState::queueEvents(TrackEntry *entry, float animationTime) { + float animationStart = entry->_animationStart, animationEnd = entry->_animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = duration != 0 ? MathUtil::fmod(entry->_trackLast, duration) : MathUtil::quietNan(); + + // Queue events before complete. + size_t i = 0, n = _events.size(); + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < trackLastWrapped) break; + if (e->_time > animationEnd) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } + + // Queue complete if completed a loop iteration or the animation. + bool complete = false; + if (entry->_loop) { + if (duration == 0) + complete = true; + else { + int cycles = (int) (entry->_trackTime / duration); + complete = cycles > 0 && cycles > (int) (entry->_trackLast / duration); + } + } else { + complete = animationTime >= animationEnd && entry->_animationLast < animationEnd; + } + if (complete) _queue->complete(entry); + + // Queue events after complete. + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < animationStart) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } +} + +void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrupt) { + TrackEntry *from = expandToIndex(index); + _tracks[index] = current; + current->_previous = NULL; + + if (from != NULL) { + if (interrupt) _queue->interrupt(from); + + current->_mixingFrom = from; + from->_mixingTo = current; + current->_mixTime = 0; + + // Store interrupted mix percentage. + if (from->_mixingFrom != NULL && from->_mixDuration > 0) { + current->_interruptAlpha *= MathUtil::min(1.0f, from->_mixTime / from->_mixDuration); + } + + from->_timelinesRotation.clear();// Reset rotation for mixing out, in case entry was mixed in. + } + + _queue->start(current);// triggers animationsChanged +} + +TrackEntry *AnimationState::expandToIndex(size_t index) { + if (index < _tracks.size()) return _tracks[index]; + while (index >= _tracks.size()) + _tracks.add(NULL); + return NULL; +} + +TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last) { + TrackEntry *entryP = _trackEntryPool.obtain();// Pooling + TrackEntry &entry = *entryP; + + entry._trackIndex = (int) trackIndex; + entry._animation = animation; + entry._loop = loop; + entry._holdPrevious = 0; + + entry._reverse = false; + entry._shortestRotation = false; + + entry._eventThreshold = 0; + entry._alphaAttachmentThreshold = 0; + entry._mixAttachmentThreshold = 0; + entry._mixDrawOrderThreshold = 0; + + entry._animationStart = 0; + entry._animationEnd = animation->getDuration(); + entry._animationLast = -1; + entry._nextAnimationLast = -1; + + entry._delay = 0; + entry._trackTime = 0; + entry._trackLast = -1; + entry._nextTrackLast = -1;// nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet. + entry._trackEnd = FLT_MAX;// loop ? float.MaxValue : animation.Duration; + entry._timeScale = 1; + + entry._alpha = 1; + entry._mixTime = 0; + entry._mixDuration = (last == NULL) ? 0 : _data->getMix(last->_animation, animation); + entry._interruptAlpha = 1; + entry._totalAlpha = 0; + entry._mixBlend = MixBlend_Replace; + + return entryP; +} + +void AnimationState::clearNext(TrackEntry *entry) { + TrackEntry *next = entry->_next; + while (next != NULL) { + _queue->dispose(next); + next = next->_next; + } + entry->_next = NULL; +} + +void AnimationState::animationsChanged() { + _animationsChanged = false; + + _propertyIDs.clear(); + + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *entry = _tracks[i]; + if (!entry) continue; + + while (entry->_mixingFrom != NULL) + entry = entry->_mixingFrom; + + do { + if (entry->_mixingTo == NULL || entry->_mixBlend != MixBlend_Add) computeHold(entry); + entry = entry->_mixingTo; + } while (entry != NULL); + } +} + +void AnimationState::computeHold(TrackEntry *entry) { + TrackEntry *to = entry->_mixingTo; + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + timelineMode.setSize(timelinesCount, 0); + Vector &timelineHoldMix = entry->_timelineHoldMix; + timelineHoldMix.setSize(timelinesCount, 0); + + if (to != NULL && to->_holdPrevious) { + for (size_t i = 0; i < timelinesCount; i++) { + timelineMode[i] = _propertyIDs.addAll(timelines[i]->getPropertyIds(), true) ? HoldFirst : HoldSubsequent; + } + return; + } + + // outer: + size_t i = 0; +continue_outer: + for (; i < timelinesCount; ++i) { + Timeline *timeline = timelines[i]; + Vector &ids = timeline->getPropertyIds(); + if (!_propertyIDs.addAll(ids, true)) { + timelineMode[i] = Subsequent; + } else { + if (to == NULL || timeline->getRTTI().isExactly(AttachmentTimeline::rtti) || + timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) || + timeline->getRTTI().isExactly(EventTimeline::rtti) || !to->_animation->hasTimeline(ids)) { + timelineMode[i] = First; + } else { + for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { + if (next->_animation->hasTimeline(ids)) continue; + if (next->_mixDuration > 0) { + timelineMode[i] = HoldMix; + timelineHoldMix[i] = next; + i++; + goto continue_outer;// continue outer; + } + break; + } + timelineMode[i] = HoldFirst; + } + } + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/AnimationStateData.cpp b/modules/spine_godot/spine-cpp/src/spine/AnimationStateData.cpp new file mode 100644 index 000000000000..9ba25ca694ab --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/AnimationStateData.cpp @@ -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 +#include +#include + +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); + + setMix(from, to, duration); +} + +void AnimationStateData::setMix(Animation *from, Animation *to, float duration) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + _animationToMixTime.put(key, duration); +} + +float AnimationStateData::getMix(Animation *from, Animation *to) { + assert(from != NULL); + assert(to != NULL); + + 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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Atlas.cpp b/modules/spine_godot/spine-cpp/src/spine/Atlas.cpp new file mode 100644 index 000000000000..9258fa0c92a4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Atlas.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * 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 +#include +#include + +#include + +using namespace spine; + +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(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); + } + } + ContainerUtil::cleanUpVectorOfPointers(_pages); + ContainerUtil::cleanUpVectorOfPointers(_regions); +} + +void Atlas::flipV() { + for (size_t i = 0, n = _regions.size(); i < n; ++i) { + AtlasRegion *regionP = _regions[i]; + AtlasRegion ®ion = *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 NULL; +} + +Vector &Atlas::getPages() { + return _pages; +} + +Vector &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(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 == NULL) 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 = NULL; + + SimpleString *line = reader.readLine(); + while (line != NULL && line->length == 0) + line = reader.readLine(); + + while (true) { + if (line == NULL || line->length == 0) break; + if (reader.readEntry(entry, line) == 0) break; + line = reader.readLine(); + } + + while (true) { + if (line == NULL) break; + if (line->trim().length == 0) { + page = NULL; + line = reader.readLine(); + } else if (page == NULL) { + char *name = line->copy(); + char *path = SpineExtension::calloc(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")) { + page->minFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[1]); + page->magFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[2]); + } 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->width = entry[1].toInt(); + region->height = entry[2].toInt(); + } else if (entry[0].equals("bounds")) { + region->x = entry[1].toInt(); + region->y = entry[2].toInt(); + region->width = entry[3].toInt(); + region->height = 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(); + } + } 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->width; + region->originalHeight = region->height; + } + + region->u = (float) region->x / page->width; + region->v = (float) region->y / page->height; + if (region->degrees == 90) { + region->u2 = (float) (region->x + region->height) / page->width; + region->v2 = (float) (region->y + region->width) / page->height; + } else { + region->u2 = (float) (region->x + region->width) / page->width; + region->v2 = (float) (region->y + region->height) / page->height; + } + _regions.add(region); + } + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/AtlasAttachmentLoader.cpp b/modules/spine_godot/spine-cpp/src/spine/AtlasAttachmentLoader.cpp new file mode 100644 index 000000000000..ff3b009c386d --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/AtlasAttachmentLoader.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spine { + RTTI_IMPL(AtlasAttachmentLoader, AttachmentLoader) + + AtlasAttachmentLoader::AtlasAttachmentLoader(Atlas *atlas) : AttachmentLoader(), _atlas(atlas) { + } + + bool loadSequence(Atlas *atlas, const String &basePath, Sequence *sequence) { + Vector ®ions = sequence->getRegions(); + for (int i = 0, n = (int) regions.size(); i < n; i++) { + String path = sequence->getPath(basePath, i); + regions[i] = atlas->findRegion(path); + if (!regions[i]) return false; + } + return true; + } + + RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + RegionAttachment *attachment = new (__FILE__, __LINE__) RegionAttachment(name); + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + MeshAttachment *attachment = new (__FILE__, __LINE__) MeshAttachment(name); + + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + BoundingBoxAttachment *AtlasAttachmentLoader::newBoundingBoxAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) BoundingBoxAttachment(name); + } + + PathAttachment *AtlasAttachmentLoader::newPathAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PathAttachment(name); + } + + PointAttachment *AtlasAttachmentLoader::newPointAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PointAttachment(name); + } + + ClippingAttachment *AtlasAttachmentLoader::newClippingAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) ClippingAttachment(name); + } + + void AtlasAttachmentLoader::configureAttachment(Attachment *attachment) { + SP_UNUSED(attachment); + } + + AtlasRegion *AtlasAttachmentLoader::findRegion(const String &name) { + return _atlas->findRegion(name); + } + +}// namespace spine diff --git a/modules/spine_godot/spine-cpp/src/spine/Attachment.cpp b/modules/spine_godot/spine-cpp/src/spine/Attachment.cpp new file mode 100644 index 000000000000..e46122a0f30f --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Attachment.cpp @@ -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 + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Attachment) + +Attachment::Attachment(const String &name) : _name(name), _refCount(0) { + assert(_name.length() > 0); +} + +Attachment::~Attachment() { +} + +const String &Attachment::getName() const { + return _name; +} + +int Attachment::getRefCount() { + return _refCount; +} + +void Attachment::reference() { + _refCount++; +} + +void Attachment::dereference() { + _refCount--; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/AttachmentLoader.cpp b/modules/spine_godot/spine-cpp/src/spine/AttachmentLoader.cpp new file mode 100644 index 000000000000..87f1007ebdfe --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/AttachmentLoader.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(AttachmentLoader) + +AttachmentLoader::AttachmentLoader() { +} + +AttachmentLoader::~AttachmentLoader() { +} diff --git a/modules/spine_godot/spine-cpp/src/spine/AttachmentTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/AttachmentTimeline.cpp new file mode 100644 index 000000000000..f439cd3ee533 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/AttachmentTimeline.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(AttachmentTimeline, Timeline) + +AttachmentTimeline::AttachmentTimeline(size_t frameCount, int slotIndex) : Timeline(frameCount, 1), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Attachment << 32) | slotIndex}; + setPropertyIds(ids, 1); + + _attachmentNames.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + _attachmentNames.add(String()); + } +} + +AttachmentTimeline::~AttachmentTimeline() {} + +void AttachmentTimeline::setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName) { + slot.setAttachment(attachmentName == NULL || attachmentName->isEmpty() ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); +} + +void AttachmentTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + if (time < _frames[0]) { + // Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) { + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + setAttachment(skeleton, *slot, &_attachmentNames[Animation::search(_frames, time)]); +} + +void AttachmentTimeline::setFrame(int frame, float time, const String &attachmentName) { + _frames[frame] = time; + _attachmentNames[frame] = attachmentName; +} + +Vector &AttachmentTimeline::getAttachmentNames() { + return _attachmentNames; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Bone.cpp b/modules/spine_godot/spine-cpp/src/spine/Bone.cpp new file mode 100644 index 000000000000..9a78078d2faf --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Bone.cpp @@ -0,0 +1,595 @@ +/****************************************************************************** + * 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 + +#include +#include + +using namespace spine; + +RTTI_IMPL(Bone, Updatable) + +bool Bone::yDown = false; + +void Bone::setYDown(bool inValue) { + yDown = inValue; +} + +bool Bone::isYDown() { + return yDown; +} + +Bone::Bone(BoneData &data, Skeleton &skeleton, Bone *parent) : Updatable(), + _data(data), + _skeleton(skeleton), + _parent(parent), + _x(0), + _y(0), + _rotation(0), + _scaleX(0), + _scaleY(0), + _shearX(0), + _shearY(0), + _ax(0), + _ay(0), + _arotation(0), + _ascaleX(0), + _ascaleY(0), + _ashearX(0), + _ashearY(0), + _a(1), + _b(0), + _worldX(0), + _c(0), + _d(1), + _worldY(0), + _sorted(false), + _active(false), + _inherit(Inherit_Normal) { + setToSetupPose(); +} + +void Bone::update(Physics) { + updateWorldTransform(_ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY); +} + +void Bone::updateWorldTransform() { + updateWorldTransform(_x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY); +} + +void Bone::updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { + float pa, pb, pc, pd; + Bone *parent = _parent; + + _ax = x; + _ay = y; + _arotation = rotation; + _ascaleX = scaleX; + _ascaleY = scaleY; + _ashearX = shearX; + _ashearY = shearY; + + if (!parent) { /* Root bone. */ + Skeleton &skeleton = this->_skeleton; + float sx = skeleton.getScaleX(); + float 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; + } + + 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 rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + _a = MathUtil::cos(rx) * scaleX; + _b = MathUtil::cos(ry) * scaleY; + _c = MathUtil::sin(rx) * scaleX; + _d = MathUtil::sin(ry) * scaleY; + break; + } + case Inherit_NoRotationOrReflection: { + float s = pa * pa + pc * pc; + float prx; + if (s > 0.0001f) { + s = MathUtil::abs(pa * pd - pb * pc) / s; + pa /= _skeleton.getScaleX(); + pc /= _skeleton.getScaleY(); + pb = pc * s; + pd = pa * s; + prx = MathUtil::atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtil::atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * MathUtil::Deg_Rad; + float ry = (rotation + shearY - prx + 90) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * scaleX; + float lb = MathUtil::cos(ry) * scaleY; + float lc = MathUtil::sin(rx) * scaleX; + float ld = MathUtil::sin(ry) * scaleY; + _a = pa * la - pb * lc; + _b = pa * lb - pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + rotation *= MathUtil::Deg_Rad; + float cosine = MathUtil::cos(rotation); + float sine = MathUtil::sin(rotation); + float za = (pa * cosine + pb * sine) / _skeleton.getScaleX(); + float zc = (pc * cosine + pd * sine) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = MathUtil::sqrt(za * za + zc * zc); + if (_inherit == Inherit_NoScale && + (pa * pd - pb * pc < 0) != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + rotation = MathUtil::Pi / 2 + MathUtil::atan2(zc, za); + float zb = MathUtil::cos(rotation) * s; + float zd = MathUtil::sin(rotation) * s; + shearX *= MathUtil::Deg_Rad; + shearY = (90 + shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(shearX) * scaleX; + float lb = MathUtil::cos(shearY) * scaleY; + float lc = MathUtil::sin(shearX) * scaleX; + float ld = MathUtil::sin(shearY) * scaleY; + _a = za * la + zb * lc; + _b = za * lb + zb * ld; + _c = zc * la + zd * lc; + _d = zc * lb + zd * ld; + } + } + _a *= _skeleton.getScaleX(); + _b *= _skeleton.getScaleX(); + _c *= _skeleton.getScaleY(); + _d *= _skeleton.getScaleY(); +} + +void Bone::setToSetupPose() { + BoneData &data = _data; + _x = data.getX(); + _y = data.getY(); + _rotation = data.getRotation(); + _scaleX = data.getScaleX(); + _scaleY = data.getScaleY(); + _shearX = data.getShearX(); + _shearY = data.getShearY(); + _inherit = data.getInherit(); +} + +void Bone::worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY) { + float a = _a; + float b = _b; + float c = _c; + float d = _d; + + float invDet = 1 / (a * d - b * c); + float x = worldX - _worldX; + float y = worldY - _worldY; + + outLocalX = (x * d * invDet - y * b * invDet); + outLocalY = (y * a * invDet - x * c * invDet); +} + +void Bone::worldToParent(float worldX, float worldY, float &outParentX, float &outParentY) { + if (!_parent) { + outParentX = worldX; + outParentY = worldY; + } else { + _parent->worldToLocal(worldX, worldY, outParentX, outParentY); + } +} + +void Bone::localToWorld(float localX, float localY, float &outWorldX, float &outWorldY) { + outWorldX = localX * _a + localY * _b + _worldX; + outWorldY = localX * _c + localY * _d + _worldY; +} + +void Bone::parentToWorld(float worldX, float worldY, float &outX, float &outY) { + if (!_parent) { + outX = worldX; + outY = worldY; + } else { + _parent->localToWorld(worldX, worldY, outX, outY); + } +} + +float Bone::worldToLocalRotation(float worldRotation) { + worldRotation *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(worldRotation), cosine = MathUtil::cos(worldRotation); + return MathUtil::atan2Deg(_a * sine - _c * cosine, _d * cosine - _b * sine) + _rotation - _shearX; +} + +float Bone::localToWorldRotation(float localRotation) { + localRotation = (localRotation - _rotation - _shearX) * MathUtil::Deg_Rad; + float sine = MathUtil::sin(localRotation), cosine = MathUtil::cos(localRotation); + return MathUtil::atan2Deg(cosine * _c + sine * _d, cosine * _a + sine * _b); +} + +void Bone::rotateWorld(float degrees) { + degrees *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(degrees), cosine = MathUtil::cos(degrees); + float ra = _a, rb = _b; + _a = cosine * ra - sine * _c; + _b = cosine * rb - sine * _d; + _c = sine * ra + cosine * _c; + _d = sine * rb + cosine * _d; +} + +float Bone::getWorldToLocalRotationX() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float a = _a; + float c = _c; + + return MathUtil::atan2(pa * c - pc * a, pd * a - pb * c) * MathUtil::Rad_Deg; +} + +float Bone::getWorldToLocalRotationY() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float b = _b; + float d = _d; + + return MathUtil::atan2(pa * d - pc * b, pd * b - pb * d) * MathUtil::Rad_Deg; +} + +BoneData &Bone::getData() { + return _data; +} + +Skeleton &Bone::getSkeleton() { + return _skeleton; +} + +Bone *Bone::getParent() { + return _parent; +} + +Vector &Bone::getChildren() { + return _children; +} + +float Bone::getX() { + return _x; +} + +void Bone::setX(float inValue) { + _x = inValue; +} + +float Bone::getY() { + return _y; +} + +void Bone::setY(float inValue) { + _y = inValue; +} + +float Bone::getRotation() { + return _rotation; +} + +void Bone::setRotation(float inValue) { + _rotation = inValue; +} + +float Bone::getScaleX() { + return _scaleX; +} + +void Bone::setScaleX(float inValue) { + _scaleX = inValue; +} + +float Bone::getScaleY() { + return _scaleY; +} + +void Bone::setScaleY(float inValue) { + _scaleY = inValue; +} + +float Bone::getShearX() { + return _shearX; +} + +void Bone::setShearX(float inValue) { + _shearX = inValue; +} + +float Bone::getShearY() { + return _shearY; +} + +void Bone::setShearY(float inValue) { + _shearY = inValue; +} + +float Bone::getAppliedRotation() { + return _arotation; +} + +void Bone::setAppliedRotation(float inValue) { + _arotation = inValue; +} + +float Bone::getAX() { + return _ax; +} + +void Bone::setAX(float inValue) { + _ax = inValue; +} + +float Bone::getAY() { + return _ay; +} + +void Bone::setAY(float inValue) { + _ay = inValue; +} + +float Bone::getAScaleX() { + return _ascaleX; +} + +void Bone::setAScaleX(float inValue) { + _ascaleX = inValue; +} + +float Bone::getAScaleY() { + return _ascaleY; +} + +void Bone::setAScaleY(float inValue) { + _ascaleY = inValue; +} + +float Bone::getAShearX() { + return _ashearX; +} + +void Bone::setAShearX(float inValue) { + _ashearX = inValue; +} + +float Bone::getAShearY() { + return _ashearY; +} + +void Bone::setAShearY(float inValue) { + _ashearY = inValue; +} + +float Bone::getA() { + return _a; +} + +void Bone::setA(float inValue) { + _a = inValue; +} + +float Bone::getB() { + return _b; +} + +void Bone::setB(float inValue) { + _b = inValue; +} + +float Bone::getC() { + return _c; +} + +void Bone::setC(float inValue) { + _c = inValue; +} + +float Bone::getD() { + return _d; +} + +void Bone::setD(float inValue) { + _d = inValue; +} + +float Bone::getWorldX() { + return _worldX; +} + +void Bone::setWorldX(float inValue) { + _worldX = inValue; +} + +float Bone::getWorldY() { + return _worldY; +} + +void Bone::setWorldY(float inValue) { + _worldY = inValue; +} + +float Bone::getWorldRotationX() { + return MathUtil::atan2Deg(_c, _a); +} + +float Bone::getWorldRotationY() { + return MathUtil::atan2Deg(_d, _b); +} + +float Bone::getWorldScaleX() { + return MathUtil::sqrt(_a * _a + _c * _c); +} + +float Bone::getWorldScaleY() { + return MathUtil::sqrt(_b * _b + _d * _d); +} + +void Bone::updateAppliedTransform() { + Bone *parent = _parent; + if (!parent) { + _ax = _worldX - _skeleton.getX(); + _ay = _worldY - _skeleton.getY(); + _arotation = MathUtil::atan2Deg(_c, _a); + _ascaleX = MathUtil::sqrt(_a * _a + _c * _c); + _ascaleY = MathUtil::sqrt(_b * _b + _d * _d); + _ashearX = 0; + _ashearY = MathUtil::atan2Deg(_a * _b + _c * _d, _a * _d - _b * _c); + } + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + float pid = 1 / (pa * pd - pb * pc); + float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + float dx = _worldX - parent->_worldX, dy = _worldY - parent->_worldY; + _ax = (dx * ia - dy * ib); + _ay = (dy * id - dx * ic); + + float ra, rb, rc, rd; + if (_inherit == Inherit_OnlyTranslation) { + ra = _a; + rb = _b; + rc = _c; + rd = _d; + } else { + switch (_inherit) { + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / _skeleton.getScaleX(); + float sc = pc / _skeleton.getScaleY(); + pb = -sc * s * _skeleton.getScaleX(); + pd = sa * s * _skeleton.getScaleY(); + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + float r = _rotation * MathUtil::Deg_Rad; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + pa = (pa * cos + pb * sin) / _skeleton.getScaleX(); + pc = (pc * cos + pd * sin) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(pa * pa + pc * pc); + if (s > 0.00001) s = 1 / s; + pa *= s; + pc *= s; + s = MathUtil::sqrt(pa * pa + pc * pc); + if (_inherit == Inherit_NoScale && + pid < 0 != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + r = MathUtil::Pi / 2 + MathUtil::atan2(pc, pa); + pb = MathUtil::cos(r) * s; + pd = MathUtil::sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + break; + } + case Inherit_Normal: + case Inherit_OnlyTranslation: + break; + } + ra = ia * _a - ib * _c; + rb = ia * _b - ib * _d; + rc = id * _c - ic * _a; + rd = id * _d - ic * _b; + } + + _ashearX = 0; + _ascaleX = MathUtil::sqrt(ra * ra + rc * rc); + if (_ascaleX > 0.0001f) { + float det = ra * rd - rb * rc; + _ascaleY = det / _ascaleX; + _ashearY = -MathUtil::atan2Deg(ra * rb + rc * rd, det); + _arotation = MathUtil::atan2Deg(rc, ra); + } else { + _ascaleX = 0; + _ascaleY = MathUtil::sqrt(rb * rb + rd * rd); + _ashearY = 0; + _arotation = 90 - MathUtil::atan2Deg(rd, rb); + } +} + +bool Bone::isActive() { + return _active; +} + +void Bone::setActive(bool inValue) { + _active = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/BoneData.cpp b/modules/spine_godot/spine-cpp/src/spine/BoneData.cpp new file mode 100644 index 000000000000..31ab264ef2d3 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/BoneData.cpp @@ -0,0 +1,166 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +BoneData::BoneData(int index, const String &name, BoneData *parent) : _index(index), + _name(name), + _parent(parent), + _length(0), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _shearX(0), + _shearY(0), + _inherit(Inherit_Normal), + _skinRequired(false), + _color(), + _icon(), + _visible(true) { + assert(index >= 0); + assert(_name.length() > 0); +} + +int BoneData::getIndex() { + return _index; +} + +const String &BoneData::getName() { + return _name; +} + +BoneData *BoneData::getParent() { + return _parent; +} + +float BoneData::getLength() { + return _length; +} + +void BoneData::setLength(float inValue) { + _length = inValue; +} + +float BoneData::getX() { + return _x; +} + +void BoneData::setX(float inValue) { + _x = inValue; +} + +float BoneData::getY() { + return _y; +} + +void BoneData::setY(float inValue) { + _y = inValue; +} + +float BoneData::getRotation() { + return _rotation; +} + +void BoneData::setRotation(float inValue) { + _rotation = inValue; +} + +float BoneData::getScaleX() { + return _scaleX; +} + +void BoneData::setScaleX(float inValue) { + _scaleX = inValue; +} + +float BoneData::getScaleY() { + return _scaleY; +} + +void BoneData::setScaleY(float inValue) { + _scaleY = inValue; +} + +float BoneData::getShearX() { + return _shearX; +} + +void BoneData::setShearX(float inValue) { + _shearX = inValue; +} + +float BoneData::getShearY() { + return _shearY; +} + +void BoneData::setShearY(float inValue) { + _shearY = inValue; +} + +Inherit BoneData::getInherit() { + return _inherit; +} + +void BoneData::setInherit(Inherit inValue) { + _inherit = inValue; +} + +bool BoneData::isSkinRequired() { + return _skinRequired; +} + +void BoneData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} + +Color &BoneData::getColor() { + return _color; +} + +const String &BoneData::getIcon() { + return _icon; +} + +void BoneData::setIcon(const String &icon) { + this->_icon = icon; +} + +bool BoneData::isVisible() { + return _visible; +} + +void BoneData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/BoundingBoxAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/BoundingBoxAttachment.cpp new file mode 100644 index 000000000000..5ede2c56bc8b --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/BoundingBoxAttachment.cpp @@ -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 + +using namespace spine; + +RTTI_IMPL(BoundingBoxAttachment, VertexAttachment) + +BoundingBoxAttachment::BoundingBoxAttachment(const String &name) : VertexAttachment(name), _color() { +} + +Color &BoundingBoxAttachment::getColor() { + return _color; +} + +Attachment *BoundingBoxAttachment::copy() { + BoundingBoxAttachment *copy = new (__FILE__, __LINE__) BoundingBoxAttachment(getName()); + copyTo(copy); + return copy; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/ClippingAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/ClippingAttachment.cpp new file mode 100644 index 000000000000..90f5c141bc78 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/ClippingAttachment.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +RTTI_IMPL(ClippingAttachment, VertexAttachment) + +ClippingAttachment::ClippingAttachment(const String &name) : VertexAttachment(name), _endSlot(NULL), _color() { +} + +SlotData *ClippingAttachment::getEndSlot() { + return _endSlot; +} + +void ClippingAttachment::setEndSlot(SlotData *inValue) { + _endSlot = inValue; +} + +Color &ClippingAttachment::getColor() { + return _color; +} + +Attachment *ClippingAttachment::copy() { + ClippingAttachment *copy = new (__FILE__, __LINE__) ClippingAttachment(getName()); + copyTo(copy); + copy->_endSlot = _endSlot; + return copy; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/ColorTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/ColorTimeline.cpp new file mode 100644 index 000000000000..6eacd3ecd938 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/ColorTimeline.cpp @@ -0,0 +1,488 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RGBATimeline, CurveTimeline) + +RGBATimeline::RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBATimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGBATimeline::~RGBATimeline() { +} + +void RGBATimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0; + int i = Animation::search(_frames, time, RGBATimeline::ENTRIES); + int curveType = (int) _curves[i / RGBATimeline::ENTRIES]; + switch (curveType) { + case RGBATimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + float t = (time - before) / (_frames[i + RGBATimeline::ENTRIES] - before); + r += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::R] - r) * t; + g += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::G] - g) * t; + b += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::B] - b) * t; + a += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::A] - a) * t; + break; + } + case RGBATimeline::STEPPED: { + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + break; + } + default: { + r = getBezierValue(time, i, RGBATimeline::R, curveType - RGBATimeline::BEZIER); + g = getBezierValue(time, i, RGBATimeline::G, + curveType + RGBATimeline::BEZIER_SIZE - RGBATimeline::BEZIER); + b = getBezierValue(time, i, RGBATimeline::B, + curveType + RGBATimeline::BEZIER_SIZE * 2 - RGBATimeline::BEZIER); + a = getBezierValue(time, i, RGBATimeline::A, + curveType + RGBATimeline::BEZIER_SIZE * 3 - RGBATimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b, a); + else { + if (blend == MixBlend_Setup) color.set(slot->_data._color); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + } +} + +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; +} + +RTTI_IMPL(RGBTimeline, CurveTimeline) + +RGBTimeline::RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBTimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +RGBTimeline::~RGBTimeline() { +} + +void RGBTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0; + int i = Animation::search(_frames, time, RGBTimeline::ENTRIES); + int curveType = (int) _curves[i / RGBTimeline::ENTRIES]; + switch (curveType) { + case RGBTimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + float t = (time - before) / (_frames[i + RGBTimeline::ENTRIES] - before); + r += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::R] - r) * t; + g += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::G] - g) * t; + b += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::B] - b) * t; + break; + } + case RGBTimeline::STEPPED: { + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + break; + } + default: { + r = getBezierValue(time, i, RGBTimeline::R, curveType - RGBTimeline::BEZIER); + g = getBezierValue(time, i, RGBTimeline::G, + curveType + RGBTimeline::BEZIER_SIZE - RGBTimeline::BEZIER); + b = getBezierValue(time, i, RGBTimeline::B, + curveType + RGBTimeline::BEZIER_SIZE * 2 - RGBTimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b); + else { + Color &setup = slot->_data._color; + if (blend == MixBlend_Setup) color.set(setup.r, setup.g, setup.b); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha); + } +} + +void RGBTimeline::setFrame(int frame, float time, float r, float g, float b) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; +} + +RTTI_IMPL(AlphaTimeline, CurveTimeline1) + +AlphaTimeline::AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline1(frameCount, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +AlphaTimeline::~AlphaTimeline() { +} + +void AlphaTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) {// Time is before first frame. + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.a = setup.a; + return; + case MixBlend_First: + color.a += (setup.a - color.a) * alpha; + default: { + } + } + return; + } + + float a = getCurveValue(time); + if (alpha == 1) + slot->_color.a = a; + else { + if (blend == MixBlend_Setup) slot->_color.a = slot->_data._color.a; + slot->_color.a += (a - slot->_color.a) * alpha; + } +} + +RTTI_IMPL(RGBA2Timeline, CurveTimeline) + +RGBA2Timeline::RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBA2Timeline::ENTRIES, + bezierCount), + _slotIndex(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::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_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; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGBA2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGBA2Timeline::ENTRIES]; + switch (curveType) { + case RGBA2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + float t = (time - before) / (_frames[i + RGBA2Timeline::ENTRIES] - before); + r += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R] - r) * t; + g += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G] - g) * t; + b += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B] - b) * t; + a += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::A] - a) * t; + r2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B2] - b2) * t; + break; + } + case RGBA2Timeline::STEPPED: { + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGBA2Timeline::R, curveType - RGBA2Timeline::BEZIER); + g = getBezierValue(time, i, RGBA2Timeline::G, + curveType + RGBA2Timeline::BEZIER_SIZE - RGBA2Timeline::BEZIER); + b = getBezierValue(time, i, RGBA2Timeline::B, + curveType + RGBA2Timeline::BEZIER_SIZE * 2 - RGBA2Timeline::BEZIER); + a = getBezierValue(time, i, RGBA2Timeline::A, + curveType + RGBA2Timeline::BEZIER_SIZE * 3 - RGBA2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGBA2Timeline::R2, + curveType + RGBA2Timeline::BEZIER_SIZE * 4 - RGBA2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGBA2Timeline::G2, + curveType + RGBA2Timeline::BEZIER_SIZE * 5 - RGBA2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGBA2Timeline::B2, + curveType + RGBA2Timeline::BEZIER_SIZE * 6 - RGBA2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b, a); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +void RGBA2Timeline::setFrame(int frame, float time, float r, float g, float b, float a, 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 + A] = a; + _frames[frame + R2] = r2; + _frames[frame + G2] = g2; + _frames[frame + B2] = b2; +} + +RTTI_IMPL(RGB2Timeline, CurveTimeline) + +RGB2Timeline::RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGB2Timeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Rgb2 << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGB2Timeline::~RGB2Timeline() { +} + +void RGB2Timeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight.r, setupLight.g, setupLight.b); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_First: + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, + (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; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGB2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGB2Timeline::ENTRIES]; + switch (curveType) { + case RGB2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + float t = (time - before) / (_frames[i + RGB2Timeline::ENTRIES] - before); + r += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R] - r) * t; + g += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G] - g) * t; + b += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B] - b) * t; + r2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B2] - b2) * t; + break; + } + case RGB2Timeline::STEPPED: { + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGB2Timeline::R, curveType - RGB2Timeline::BEZIER); + g = getBezierValue(time, i, RGB2Timeline::G, curveType + RGB2Timeline::BEZIER_SIZE - RGB2Timeline::BEZIER); + b = getBezierValue(time, i, RGB2Timeline::B, curveType + RGB2Timeline::BEZIER_SIZE * 2 - RGB2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGB2Timeline::R2, curveType + RGB2Timeline::BEZIER_SIZE * 3 - RGB2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGB2Timeline::G2, curveType + RGB2Timeline::BEZIER_SIZE * 4 - RGB2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGB2Timeline::B2, curveType + RGB2Timeline::BEZIER_SIZE * 5 - RGB2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color.r, slot->_data._color.g, slot->_data._color.b); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/ConstraintData.cpp b/modules/spine_godot/spine-cpp/src/spine/ConstraintData.cpp new file mode 100644 index 000000000000..1be0b2ddc763 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/ConstraintData.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * 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 + +using namespace spine; + +RTTI_IMPL_NOPARENT(ConstraintData) + +ConstraintData::ConstraintData(const String &name) : _name(name), _order(0), _skinRequired(false) { +} + +ConstraintData::~ConstraintData() { +} + +const String &ConstraintData::getName() { + return _name; +} + +size_t ConstraintData::getOrder() { + return _order; +} + +void ConstraintData::setOrder(size_t inValue) { + _order = inValue; +} + +bool ConstraintData::isSkinRequired() { + return _skinRequired; +} + +void ConstraintData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/CurveTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/CurveTimeline.cpp new file mode 100644 index 000000000000..530dabd091a2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/CurveTimeline.cpp @@ -0,0 +1,252 @@ +/****************************************************************************** + * 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 + +#include + +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); +} + +Vector &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, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + switch (blend) { + case MixBlend_Setup: + return setup + value * alpha; + case MixBlend_First: + case MixBlend_Replace: + value += setup - current; + break; + case MixBlend_Add: + break; + } + return current + value * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup, float value) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getScaleValue(float time, float alpha, MixBlend blend, MixDirection direction, float current, + float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time) * setup; + if (alpha == 1) { + if (blend == MixBlend_Add) return current + value - setup; + return value; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + return setup + (MathUtil::abs(value) * MathUtil::sign(setup) - setup) * alpha; + case MixBlend_First: + case MixBlend_Replace: + return current + (MathUtil::abs(value) * MathUtil::sign(current) - current) * alpha; + default: + break; + } + } else { + float s; + switch (blend) { + case MixBlend_Setup: + s = MathUtil::abs(setup) * MathUtil::sign(value); + return s + (value - s) * alpha; + case MixBlend_First: + case MixBlend_Replace: + s = MathUtil::abs(current) * MathUtil::sign(value); + return s + (value - s) * alpha; + default: + break; + } + } + return current + (value - setup) * alpha; +} + + +RTTI_IMPL(CurveTimeline2, CurveTimeline) + +CurveTimeline2::CurveTimeline2(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, + CurveTimeline2::ENTRIES, + bezierCount) { +} + +CurveTimeline2::~CurveTimeline2() { +} + +void CurveTimeline2::setFrame(size_t frame, float time, float value1, float value2) { + frame *= CurveTimeline2::ENTRIES; + _frames[frame] = time; + _frames[frame + CurveTimeline2::VALUE1] = value1; + _frames[frame + CurveTimeline2::VALUE2] = value2; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/DeformTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/DeformTimeline.cpp new file mode 100644 index 000000000000..d7d5aa15909d --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/DeformTimeline.cpp @@ -0,0 +1,328 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DeformTimeline, CurveTimeline) + +DeformTimeline::DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment) + : CurveTimeline(frameCount, 1, bezierCount), _slotIndex(slotIndex), _attachment(attachment) { + PropertyId ids[] = {((PropertyId) Property_Deform << 32) | ((slotIndex << 16 | attachment->_id) & 0xffffffff)}; + setPropertyIds(ids, 1); + + _vertices.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _vertices.add(vec); + } +} + +void DeformTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + Attachment *slotAttachment = slot.getAttachment(); + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti)) { + return; + } + + VertexAttachment *attachment = static_cast(slotAttachment); + if (attachment->_timelineAttachment != _attachment) { + return; + } + + Vector &deformArray = slot._deform; + if (deformArray.size() == 0) { + blend = MixBlend_Setup; + } + + Vector> &vertices = _vertices; + size_t vertexCount = vertices[0].size(); + + Vector &frames = _frames; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + deformArray.clear(); + return; + case MixBlend_First: { + if (alpha == 1) { + deformArray.clear(); + return; + } + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + if (attachment->getBones().size() == 0) { + // Unweighted vertex positions. + Vector &setupVertices = attachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (size_t i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + } + case MixBlend_Replace: + case MixBlend_Add: { + } + } + return; + } + + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + + if (time >= frames[frames.size() - 1]) {// Time is after last frame. + Vector &lastVertices = vertices[frames.size() - 1]; + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } else { + // Vertex positions or deform offsets, no alpha. + memcpy(deform.buffer(), lastVertices.buffer(), vertexCount * sizeof(float)); + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { + // Weighted deform offsets, alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::search(frames, time); + float percent = getCurvePercent(time, frame); + Vector &prevVertices = vertices[frame]; + Vector &nextVertices = vertices[frame + 1]; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } else { + // Vertex positions or deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + } +} + +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); + size_t i = getFrameCount() + bezier * DeformTimeline::BEZIER_SIZE; + if (value == 0) _curves[frame] = DeformTimeline::BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667; + float x = time1 + dx, y = dy; + for (size_t n = i + DeformTimeline::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) { + int i = (int) _curves[frame]; + switch (i) { + case DeformTimeline::LINEAR: { + float x = _frames[frame]; + return (time - x) / (_frames[frame + getFrameEntries()] - x); + } + case DeformTimeline::STEPPED: { + return 0; + } + default: { + } + } + i -= DeformTimeline::BEZIER; + if (_curves[i] > time) { + float x = _frames[frame]; + return _curves[i + 1] * (time - x) / (_curves[i] - x); + } + int n = i + DeformTimeline::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, Vector &vertices) { + _frames[frame] = time; + _vertices[frame].clear(); + _vertices[frame].addAll(vertices); +} + +Vector> &DeformTimeline::getVertices() { + return _vertices; +} + +VertexAttachment *DeformTimeline::getAttachment() { + return _attachment; +} + +void DeformTimeline::setAttachment(VertexAttachment *inValue) { + _attachment = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/DrawOrderTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/DrawOrderTimeline.cpp new file mode 100644 index 000000000000..2708d25d8958 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/DrawOrderTimeline.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DrawOrderTimeline, Timeline) + +DrawOrderTimeline::DrawOrderTimeline(size_t frameCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_DrawOrder << 32)}; + setPropertyIds(ids, 1); + + _drawOrders.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _drawOrders.add(vec); + } +} + +void DrawOrderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Vector &drawOrder = skeleton._drawOrder; + Vector &slots = skeleton._slots; + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + Vector &drawOrderToSetupIndex = _drawOrders[Animation::search(_frames, time)]; + if (drawOrderToSetupIndex.size() == 0) { + drawOrder.clear(); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } else { + for (size_t i = 0, n = drawOrderToSetupIndex.size(); i < n; ++i) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } +} + +void DrawOrderTimeline::setFrame(size_t frame, float time, Vector &drawOrder) { + _frames[frame] = time; + _drawOrders[frame].clear(); + _drawOrders[frame].addAll(drawOrder); +} + +Vector> &DrawOrderTimeline::getDrawOrders() { + return _drawOrders; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Event.cpp b/modules/spine_godot/spine-cpp/src/spine/Event.cpp new file mode 100644 index 000000000000..3086184d5f81 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Event.cpp @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +spine::Event::Event(float time, const spine::EventData &data) : _data(data), + _time(time), + _intValue(0), + _floatValue(0), + _stringValue(), + _volume(1), + _balance(0) { +} + +const spine::EventData &spine::Event::getData() { + return _data; +} + +float spine::Event::getTime() { + return _time; +} + +int spine::Event::getIntValue() { + return _intValue; +} + +void spine::Event::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::Event::getFloatValue() { + return _floatValue; +} + +void spine::Event::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::Event::getStringValue() { + return _stringValue; +} + +void spine::Event::setStringValue(const spine::String &inValue) { + _stringValue = inValue; +} + + +float spine::Event::getVolume() { + return _volume; +} + +void spine::Event::setVolume(float inValue) { + _volume = inValue; +} + +float spine::Event::getBalance() { + return _balance; +} + +void spine::Event::setBalance(float inValue) { + _balance = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/EventData.cpp b/modules/spine_godot/spine-cpp/src/spine/EventData.cpp new file mode 100644 index 000000000000..df7204112a07 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/EventData.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +spine::EventData::EventData(const spine::String &name) : _name(name), + _intValue(0), + _floatValue(0), + _stringValue(), + _audioPath(), + _volume(1), + _balance(0) { + assert(_name.length() > 0); +} + +/// The name of the event, which is unique within the skeleton. +const spine::String &spine::EventData::getName() const { + return _name; +} + +int spine::EventData::getIntValue() const { + return _intValue; +} + +void spine::EventData::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::EventData::getFloatValue() const { + return _floatValue; +} + +void spine::EventData::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::EventData::getStringValue() const { + return _stringValue; +} + +void spine::EventData::setStringValue(const spine::String &inValue) { + this->_stringValue = inValue; +} + +const spine::String &spine::EventData::getAudioPath() const { + return _audioPath; +} + +void spine::EventData::setAudioPath(const spine::String &inValue) { + _audioPath = inValue; +} + + +float spine::EventData::getVolume() const { + return _volume; +} + +void spine::EventData::setVolume(float inValue) { + _volume = inValue; +} + +float spine::EventData::getBalance() const { + return _balance; +} + +void spine::EventData::setBalance(float inValue) { + _balance = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/EventTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/EventTimeline.cpp new file mode 100644 index 000000000000..aa5986f3bc1b --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/EventTimeline.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +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); +} + +EventTimeline::~EventTimeline() { + ContainerUtil::cleanUpVectorOfPointers(_events); +} + +void EventTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (pEvents == NULL) return; + + Vector &events = *pEvents; + + size_t frameCount = _frames.size(); + + if (lastTime > time) { + // Fire events after last time for looped animations. + apply(skeleton, lastTime, FLT_MAX, pEvents, alpha, blend, direction); + lastTime = -1.0f; + } else if (lastTime >= _frames[frameCount - 1]) { + // Last time is after last i. + return; + } + + if (time < _frames[0]) return;// Time is before first i. + + 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 i. + 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; +} + +Vector &EventTimeline::getEvents() { return _events; } diff --git a/modules/spine_godot/spine-cpp/src/spine/Extension.cpp b/modules/spine_godot/spine-cpp/src/spine/Extension.cpp new file mode 100644 index 000000000000..4f47e066a280 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Extension.cpp @@ -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 +#include + +#include + +using namespace spine; + +SpineExtension *SpineExtension::_instance = NULL; + +void SpineExtension::setInstance(SpineExtension *inValue) { + assert(inValue); + + _instance = inValue; +} + +SpineExtension *SpineExtension::getInstance() { + if (!_instance) _instance = spine::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) { +#ifndef __EMSCRIPTEN__ + 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(*length, __FILE__, __LINE__); + fread(data, 1, *length, file); + fclose(file); + + return data; +#else + return nullptr; +#endif +} + +DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() { +} diff --git a/modules/spine_godot/spine-cpp/src/spine/IkConstraint.cpp b/modules/spine_godot/spine-cpp/src/spine/IkConstraint.cpp new file mode 100644 index 000000000000..84609013c5ef --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/IkConstraint.cpp @@ -0,0 +1,379 @@ +/****************************************************************************** + * 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 + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraint, Updatable) + +void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) { + Bone *p = bone.getParent(); + float pa = p->_a, pb = p->_b, pc = p->_c, pd = p->_d; + float rotationIK = -bone._ashearX - bone._arotation; + float tx = 0, ty = 0; + + switch (bone._inherit) { + case Inherit_OnlyTranslation: + tx = (targetX - bone._worldX) * MathUtil::sign(bone.getSkeleton().getScaleX()); + ty = (targetY - bone._worldY) * MathUtil::sign(bone.getSkeleton().getScaleY()); + break; + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / MathUtil::max(0.0001f, pa * pa + pc * pc); + float sa = pa / bone._skeleton.getScaleX(); + float sc = pc / bone._skeleton.getScaleY(); + pb = -sc * s * bone._skeleton.getScaleX(); + pd = sa * s * bone._skeleton.getScaleY(); + rotationIK += MathUtil::atan2Deg(sc, sa); + } + default: + float x = targetX - p->_worldX, y = targetY - p->_worldY; + float d = pa * pd - pb * pc; + if (MathUtil::abs(d) <= 0.0001f) { + tx = 0; + ty = 0; + } else { + tx = (x * pd - y * pb) / d - bone._ax; + ty = (y * pa - x * pc) / d - bone._ay; + } + } + rotationIK += MathUtil::atan2Deg(ty, tx); + if (bone._ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) rotationIK -= 360; + else if (rotationIK < -180) + rotationIK += 360; + float sx = bone._ascaleX; + float sy = bone._ascaleY; + if (compress || stretch) { + switch (bone._inherit) { + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: + tx = targetX - bone._worldX; + ty = targetY - bone._worldY; + default:; + } + + float b = bone._data.getLength() * sx; + if (b > 0.0001) { + float dd = tx * tx + ty * ty; + if ((compress && dd < b * b) || (stretch && dd > b * b)) { + float s = (MathUtil::sqrt(dd) / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } + } + } + bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx, sy, bone._ashearX, + bone._ashearY); +} + +void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha) { + float a, b, c, d; + float px, py, psx, psy, sx, sy; + float cx, cy, csx, cwx, cwy; + int o1, o2, s2, u; + Bone *pp = parent.getParent(); + float tx, ty, dx, dy, dd, l1, l2, a1, a2, r, td, sd, p; + float id, x, y; + if (parent._inherit != Inherit_Normal || child._inherit != Inherit_Normal) return; + px = parent._ax; + py = parent._ay; + psx = parent._ascaleX; + psy = parent._ascaleY; + sx = psx; + sy = psy; + csx = child._ascaleX; + if (psx < 0) { + psx = -psx; + o1 = 180; + s2 = -1; + } else { + o1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + o2 = 180; + } else + o2 = 0; + r = psx - psy; + cx = child._ax; + u = (r < 0 ? -r : r) <= 0.0001f; + if (!u || stretch) { + cy = 0; + cwx = parent._a * cx + parent._worldX; + cwy = parent._c * cx + parent._worldY; + } else { + cy = child._ay; + cwx = parent._a * cx + parent._b * cy + parent._worldX; + cwy = parent._c * cx + parent._d * cy + parent._worldY; + } + a = pp->_a; + b = pp->_b; + c = pp->_c; + d = pp->_d; + id = a * d - b * c; + id = MathUtil::abs(id) <= 0.0001f ? 0 : 1 / id; + x = cwx - pp->_worldX; + y = cwy - pp->_worldY; + dx = (x * d - y * b) * id - px; + dy = (y * a - x * c) * id - py; + l1 = MathUtil::sqrt(dx * dx + dy * dy); + l2 = child._data.getLength() * csx; + if (l1 < 0.0001) { + apply(parent, targetX, targetY, false, stretch, false, alpha); + child.updateWorldTransform(cx, cy, 0, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY); + return; + } + x = targetX - pp->_worldX; + y = targetY - pp->_worldY; + tx = (x * d - y * b) * id - px; + ty = (y * a - x * c) * id - py; + dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) * 0.5f; + td = MathUtil::sqrt(dd); + sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + 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) { + float cosine; + l2 *= psx; + cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cosine < -1) { + cosine = -1; + a2 = MathUtil::Pi * bendDir; + } else if (cosine > 1) { + cosine = 1; + a2 = 0; + if (stretch) { + a = (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } else + a2 = MathUtil::acos(cosine) * bendDir; + a = l1 + l2 * cosine; + 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, ll = l1 * l1, ta = MathUtil::atan2(ty, tx); + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) { + float q = MathUtil::sqrt(d), r0, r1; + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + r0 = q / c2; + r1 = c0 / q; + r = MathUtil::abs(r0) < MathUtil::abs(r1) ? r0 : r1; + if (dd - r * r >= 0) { + y = MathUtil::sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtil::atan2(y, r); + a2 = MathUtil::atan2(y / psy, (r - l1) / psx); + goto break_outer; + } + } + { + float minAngle = MathUtil::Pi, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c0 = -a * l1 / (aa - bb); + if (c0 >= -1 && c0 <= 1) { + c0 = MathUtil::acos(c0); + x = a * MathUtil::cos(c0) + l1; + y = b * MathUtil::sin(c0); + d = x * x + y * y; + if (d < minDist) { + minAngle = c0; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c0; + 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; + } + } + } +break_outer : { + float os = MathUtil::atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtil::Rad_Deg + o1 - parent._arotation; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.updateWorldTransform(px, py, parent._arotation + a1 * alpha, sx, sy, 0, 0); + a2 = ((a2 + os) * MathUtil::Rad_Deg - child._ashearX) * s2 + o2 - child._arotation; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.updateWorldTransform(cx, cy, child._arotation + a2 * alpha, child._ascaleX, child._ascaleY, + child._ashearX, child._ashearY); +} +} + +IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _bendDirection(data.getBendDirection()), + _compress(data.getCompress()), + _stretch(data.getStretch()), + _mix(data.getMix()), + _softness(data.getSoftness()), + _target(skeleton.findBone( + data.getTarget()->getName())), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void IkConstraint::update(Physics) { + if (_mix == 0) return; + switch (_bones.size()) { + case 1: { + Bone *bone0 = _bones[0]; + apply(*bone0, _target->getWorldX(), _target->getWorldY(), _compress, _stretch, _data._uniform, _mix); + } break; + case 2: { + Bone *bone0 = _bones[0]; + Bone *bone1 = _bones[1]; + apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _data._uniform, + _softness, + _mix); + } break; + } +} + +int IkConstraint::getOrder() { + return (int) _data.getOrder(); +} + +IkConstraintData &IkConstraint::getData() { + return _data; +} + +Vector &IkConstraint::getBones() { + return _bones; +} + +Bone *IkConstraint::getTarget() { + return _target; +} + +void IkConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +int IkConstraint::getBendDirection() { + return _bendDirection; +} + +void IkConstraint::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraint::getMix() { + return _mix; +} + +void IkConstraint::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraint::getStretch() { + return _stretch; +} + +void IkConstraint::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraint::getCompress() { + return _compress; +} + +void IkConstraint::setCompress(bool inValue) { + _compress = inValue; +} + +bool IkConstraint::isActive() { + return _active; +} + +void IkConstraint::setActive(bool inValue) { + _active = inValue; +} + +float IkConstraint::getSoftness() { + return _softness; +} + +void IkConstraint::setSoftness(float inValue) { + _softness = inValue; +} + +void IkConstraint::setToSetupPose() { + IkConstraintData &data = this->_data; + this->_mix = data._mix; + this->_softness = data._softness; + this->_bendDirection = data._bendDirection; + this->_compress = data._compress; + this->_stretch = data._stretch; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/IkConstraintData.cpp b/modules/spine_godot/spine-cpp/src/spine/IkConstraintData.cpp new file mode 100644 index 000000000000..5f232ac6932c --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/IkConstraintData.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintData, ConstraintData) + +IkConstraintData::IkConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _bendDirection(0), + _compress(false), + _stretch(false), + _uniform(false), + _mix(0), + _softness(0) { +} + +Vector &IkConstraintData::getBones() { + return _bones; +} + +BoneData *IkConstraintData::getTarget() { + return _target; +} + +void IkConstraintData::setTarget(BoneData *inValue) { + _target = inValue; +} + +int IkConstraintData::getBendDirection() { + return _bendDirection; +} + +void IkConstraintData::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraintData::getMix() { + return _mix; +} + +void IkConstraintData::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraintData::getStretch() { + return _stretch; +} + +void IkConstraintData::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraintData::getCompress() { + return _compress; +} + +void IkConstraintData::setCompress(bool inValue) { + _compress = inValue; +} + + +bool IkConstraintData::getUniform() { + return _uniform; +} + +void IkConstraintData::setUniform(bool inValue) { + _uniform = inValue; +} + +float IkConstraintData::getSoftness() { + return _softness; +} + +void IkConstraintData::setSoftness(float inValue) { + _softness = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/IkConstraintTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/IkConstraintTimeline.cpp new file mode 100644 index 000000000000..8e757f7fc028 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/IkConstraintTimeline.cpp @@ -0,0 +1,141 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintTimeline, CurveTimeline) + +IkConstraintTimeline::IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex) + : CurveTimeline(frameCount, IkConstraintTimeline::ENTRIES, bezierCount), _constraintIndex(ikConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_IkConstraint << 32) | ikConstraintIndex}; + setPropertyIds(ids, 1); +} + +void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + IkConstraint *constraintP = skeleton._ikConstraints[_constraintIndex]; + IkConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mix = constraint._data._mix; + constraint._softness = constraint._data._softness; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + case MixBlend_First: + constraint._mix += (constraint._data._mix - constraint._mix) * alpha; + constraint._softness += (constraint._data._softness - constraint._softness) * alpha; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + default: + return; + } + } + + float mix = 0, softness = 0; + int i = Animation::search(_frames, time, IkConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / IkConstraintTimeline::ENTRIES]; + switch (curveType) { + case IkConstraintTimeline::LINEAR: { + float before = _frames[i]; + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + float t = (time - before) / (_frames[i + IkConstraintTimeline::ENTRIES] - before); + mix += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::MIX] - mix) * t; + softness += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::SOFTNESS] - softness) * t; + break; + } + case IkConstraintTimeline::STEPPED: { + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + break; + } + default: { + mix = getBezierValue(time, i, IkConstraintTimeline::MIX, curveType - IkConstraintTimeline::BEZIER); + softness = getBezierValue(time, i, IkConstraintTimeline::SOFTNESS, + curveType + IkConstraintTimeline::BEZIER_SIZE - + IkConstraintTimeline::BEZIER); + } + } + + if (blend == MixBlend_Setup) { + constraint._mix = constraint._data._mix + (mix - constraint._data._mix) * alpha; + constraint._softness = constraint._data._softness + (softness - constraint._data._softness) * alpha; + + if (direction == MixDirection_Out) { + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + } else { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::STRETCH] != 0; + } + } else { + constraint._mix += (mix - constraint._mix) * alpha; + constraint._softness += (softness - constraint._softness) * alpha; + if (direction == MixDirection_In) { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/InheritTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/InheritTimeline.cpp new file mode 100644 index 000000000000..d8da9e827042 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/InheritTimeline.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(InheritTimeline, Timeline) + +InheritTimeline::InheritTimeline(size_t frameCount, int boneIndex) : Timeline(frameCount, ENTRIES), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Inherit << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +InheritTimeline::~InheritTimeline() { +} + +void InheritTimeline::setFrame(int frame, float time, Inherit inherit) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + INHERIT] = inherit; +} + + +void InheritTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + SP_UNUSED(alpha); + + Bone *bone = skeleton.getBones()[_boneIndex]; + if (!bone->isActive()) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) bone->setInherit(bone->_data.getInherit()); + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) bone->_inherit = bone->_data.getInherit(); + return; + } + int idx = Animation::search(_frames, time, ENTRIES) + INHERIT; + bone->_inherit = static_cast(_frames[idx]); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Json.cpp b/modules/spine_godot/spine-cpp/src/spine/Json.cpp new file mode 100644 index 000000000000..89d1cc9d591a --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Json.cpp @@ -0,0 +1,559 @@ +/* +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 +#include +#include + +#include +#include + +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(spine::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() { + spine::Json *curr = NULL; + spine::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(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 */ + } + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/LinkedMesh.cpp b/modules/spine_godot/spine-cpp/src/spine/LinkedMesh.cpp new file mode 100644 index 000000000000..db10ceddedf4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/LinkedMesh.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(skinIndex), + _skin(""), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(-1), + _skin(skin), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Log.cpp b/modules/spine_godot/spine-cpp/src/spine/Log.cpp new file mode 100644 index 000000000000..063141e839d5 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Log.cpp @@ -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 + +#include + +using namespace spine; + +void spine::spDebug_printSkeletonData(SkeletonData *skeletonData) { + int i, n; + spDebug_printBoneDatas(skeletonData->getBones()); + + for (i = 0, n = (int) skeletonData->getAnimations().size(); i < n; i++) { + spDebug_printAnimation(skeletonData->getAnimations()[i]); + } +} + +void _spDebug_printTimelineBase(Timeline *timeline) { + printf(" Timeline %s:\n", timeline->getRTTI().getClassName()); + printf(" frame count: %zu\n", timeline->getFrameCount()); + printf(" frame entries: %zu\n", timeline->getFrameEntries()); + printf(" frames: "); + spDebug_printFloats(timeline->getFrames()); + printf("\n"); +} + +void _spDebug_printCurveTimeline(CurveTimeline *timeline) { + _spDebug_printTimelineBase(timeline); + printf(" curves: "); + spDebug_printFloats(timeline->getCurves()); + printf("\n"); +} + +void spine::spDebug_printTimeline(Timeline *timeline) { + if (timeline->getRTTI().instanceOf(CurveTimeline::rtti)) + _spDebug_printCurveTimeline(static_cast(timeline)); + else + _spDebug_printTimelineBase(timeline); +} + +void spine::spDebug_printAnimation(Animation *animation) { + int i, n; + printf("Animation %s: %zu timelines\n", animation->getName().buffer(), animation->getTimelines().size()); + + for (i = 0, n = (int) animation->getTimelines().size(); i < n; i++) { + Timeline *timeline = animation->getTimelines()[i]; + spDebug_printTimeline(timeline); + } +} + +void spine::spDebug_printBoneDatas(Vector &boneDatas) { + int i, n; + for (i = 0, n = (int) boneDatas.size(); i < n; i++) { + spDebug_printBoneData(boneDatas[i]); + } +} + +void spine::spDebug_printBoneData(BoneData *boneData) { + printf("Bone data %s: %f, %f, %f, %f, %f, %f %f\n", boneData->getName().buffer(), boneData->getRotation(), + boneData->getScaleX(), boneData->getScaleY(), boneData->getX(), boneData->getY(), boneData->getShearX(), + boneData->getShearY()); +} + +void spine::spDebug_printSkeleton(Skeleton *skeleton) { + spDebug_printBones(skeleton->getBones()); +} + +void spine::spDebug_printBones(Vector &bones) { + int i, n; + for (i = 0, n = (int) bones.size(); i < n; i++) { + spDebug_printBone(bones[i]); + } +} + +void spine::spDebug_printBone(Bone *bone) { + printf("Bone %s: %f, %f, %f, %f, %f, %f\n", bone->getData().getName().buffer(), bone->getA(), bone->getB(), + bone->getC(), bone->getD(), bone->getWorldX(), bone->getWorldY()); +} + +void spine::spDebug_printFloats(float *values, int numFloats) { + int i; + printf("(%i) [", numFloats); + for (i = 0; i < numFloats; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} + +void spine::spDebug_printFloats(Vector &values) { + int i, n; + printf("(%zu) [", values.size()); + for (i = 0, n = (int) values.size(); i < n; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/MathUtil.cpp b/modules/spine_godot/spine-cpp/src/spine/MathUtil.cpp new file mode 100644 index 000000000000..44f48fa9ea58 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/MathUtil.cpp @@ -0,0 +1,132 @@ +/****************************************************************************** + * 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 +#include +#include +#include + +// Required for division by 0 in _isNaN on MSVC +#ifdef _MSC_VER +#pragma warning(disable : 4723) +#endif + +using namespace spine; + +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); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/MeshAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/MeshAttachment.cpp new file mode 100644 index 000000000000..e40bc6e28649 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/MeshAttachment.cpp @@ -0,0 +1,241 @@ +/****************************************************************************** + * 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 + +using namespace spine; + +RTTI_IMPL(MeshAttachment, VertexAttachment) + +MeshAttachment::MeshAttachment(const String &name) : VertexAttachment(name), + _parentMesh(NULL), + _path(), + _color(1, 1, 1, 1), + _hullLength(0), + _width(0), + _height(0), + _region(NULL), + _sequence(NULL) {} + +MeshAttachment::~MeshAttachment() { + if (_sequence) delete _sequence; +} + +void MeshAttachment::updateRegion() { + if (_uvs.size() != _regionUVs.size()) { + _uvs.setSize(_regionUVs.size(), 0); + } + + if (_region == nullptr) { + return; + } + + int i = 0, n = (int) _regionUVs.size(); + float u = _region->u, v = _region->v; + float width = 0, height = 0; + switch (_region->degrees) { + case 90: { + float textureWidth = _region->height / (_region->u2 - _region->u); + float textureHeight = _region->width / (_region->v2 - _region->v); + u -= (_region->originalHeight - _region->offsetY - _region->height) / textureWidth; + v -= (_region->originalWidth - _region->offsetX - _region->width) / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i + 1] * width; + _uvs[i + 1] = v + (1 - _regionUVs[i]) * height; + } + return; + } + case 180: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= (_region->originalWidth - _region->offsetX - _region->width) / textureWidth; + v -= _region->offsetY / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (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: { + float textureHeight = _region->height / (_region->v2 - _region->v); + float textureWidth = _region->width / (_region->u2 - _region->u); + u -= _region->offsetY / textureWidth; + v -= _region->offsetX / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i + 1]) * width; + _uvs[i + 1] = v + _regionUVs[i] * height; + } + return; + } + default: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= _region->offsetX / textureWidth; + v -= (_region->originalHeight - _region->offsetY - _region->height) / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i] * width; + _uvs[i + 1] = v + _regionUVs[i + 1] * height; + } + } + } +} + +int MeshAttachment::getHullLength() { + return _hullLength; +} + +void MeshAttachment::setHullLength(int inValue) { + _hullLength = inValue; +} + +Vector &MeshAttachment::getRegionUVs() { + return _regionUVs; +} + +Vector &MeshAttachment::getUVs() { + return _uvs; +} + +Vector &MeshAttachment::getTriangles() { + return _triangles; +} + +const String &MeshAttachment::getPath() { + return _path; +} + +void MeshAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *MeshAttachment::getRegion() { + return _region; +} + +void MeshAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *MeshAttachment::getSequence() { + return _sequence; +} + +void MeshAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +MeshAttachment *MeshAttachment::getParentMesh() { + return _parentMesh; +} + +void MeshAttachment::setParentMesh(MeshAttachment *inValue) { + _parentMesh = inValue; + if (inValue != NULL) { + _bones.clearAndAddAll(inValue->_bones); + _vertices.clearAndAddAll(inValue->_vertices); + _worldVerticesLength = inValue->_worldVerticesLength; + _regionUVs.clearAndAddAll(inValue->_regionUVs); + _triangles.clearAndAddAll(inValue->_triangles); + _hullLength = inValue->_hullLength; + _edges.clearAndAddAll(inValue->_edges); + _width = inValue->_width; + _height = inValue->_height; + } +} + +Vector &MeshAttachment::getEdges() { + return _edges; +} + +float MeshAttachment::getWidth() { + return _width; +} + +void MeshAttachment::setWidth(float inValue) { + _width = inValue; +} + +float MeshAttachment::getHeight() { + return _height; +} + +void MeshAttachment::setHeight(float inValue) { + _height = inValue; +} + +spine::Color &MeshAttachment::getColor() { + return _color; +} + +Attachment *MeshAttachment::copy() { + if (_parentMesh) return newLinkedMesh(); + + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->setSequence(_sequence != NULL ? _sequence->copy() : NULL); + copy->_path = _path; + copy->_color.set(_color); + + copyTo(copy); + copy->_regionUVs.clearAndAddAll(_regionUVs); + copy->_uvs.clearAndAddAll(_uvs); + copy->_triangles.clearAndAddAll(_triangles); + copy->_hullLength = _hullLength; + + // Nonessential. + copy->_edges.clearAndAddAll(copy->_edges); + copy->_width = _width; + copy->_height = _height; + return copy; +} + +MeshAttachment *MeshAttachment::newLinkedMesh() { + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->_path = _path; + copy->_color.set(_color); + copy->_timelineAttachment = this->_timelineAttachment; + copy->setParentMesh(_parentMesh ? _parentMesh : this); + if (copy->_region) copy->updateRegion(); + return copy; +} + +void MeshAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + VertexAttachment::computeWorldVertices(slot, start, count, worldVertices, offset, stride); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/PathAttachment.cpp new file mode 100644 index 000000000000..5a5670f84b8f --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathAttachment.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * 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 + +using namespace spine; + +RTTI_IMPL(PathAttachment, VertexAttachment) + +PathAttachment::PathAttachment(const String &name) : VertexAttachment(name), _closed(false), _constantSpeed(false), + _color() { +} + +Vector &PathAttachment::getLengths() { + return _lengths; +} + +bool PathAttachment::isClosed() { + return _closed; +} + +void PathAttachment::setClosed(bool inValue) { + _closed = inValue; +} + +bool PathAttachment::isConstantSpeed() { + 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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathConstraint.cpp b/modules/spine_godot/spine-cpp/src/spine/PathConstraint.cpp new file mode 100644 index 000000000000..fb8683e7ada1 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathConstraint.cpp @@ -0,0 +1,588 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraint, Updatable) + +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) : Updatable(), + _data(data), + _target(skeleton.findSlot( + data.getTarget()->getName())), + _position(data.getPosition()), + _spacing(data.getSpacing()), + _mixRotate(data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } + + _segments.setSize(10, 0); +} + +void PathConstraint::update(Physics) { + Attachment *baseAttachment = _target->getAttachment(); + if (baseAttachment == NULL || !baseAttachment->getRTTI().instanceOf(PathAttachment::rtti)) { + return; + } + PathAttachment *attachment = static_cast(baseAttachment); + + float mixRotate = _mixRotate, mixX = _mixX, mixY = _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; + _spaces.setSize(spacesCount, 0); + if (scale) _lengths.setSize(boneCount, 0); + float spacing = _spacing; + + switch (data._spacingMode) { + case SpacingMode_Percent: { + if (scale) { + for (size_t i = 0, n = spacesCount - 1; i < n; i++) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.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;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::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;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::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 ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + } + + Vector &positions = computeWorldPositions(*attachment, (int) spacesCount, tangents); + float boneX = positions[0]; + float boneY = positions[1]; + float offsetRotation = data.getOffsetRotation(); + bool tip; + if (offsetRotation == 0) { + tip = data._rotateMode == RotateMode_Chain; + } else { + tip = false; + Bone &p = _target->getBone(); + offsetRotation *= p.getA() * p.getD() - p.getB() * p.getC() > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + } + + for (size_t i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + bone._worldX += (boneX - bone._worldX) * mixX; + bone._worldY += (boneY - bone._worldY) * mixY; + float x = positions[p]; + float y = positions[p + 1]; + float dx = x - boneX; + float dy = y - boneY; + if (scale) { + float length = _lengths[i]; + if (length >= PathConstraint::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 = positions[p - 1]; + else if (_spaces[i + 1] < PathConstraint::EPSILON) + r = positions[p + 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._data.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; + } + + bone.updateAppliedTransform(); + } +} + +int PathConstraint::getOrder() { + return (int) _data.getOrder(); +} + +float PathConstraint::getPosition() { + return _position; +} + +void PathConstraint::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraint::getSpacing() { + return _spacing; +} + +void PathConstraint::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraint::getMixRotate() { + return _mixRotate; +} + +void PathConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraint::getMixX() { + return _mixX; +} + +void PathConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraint::getMixY() { + return _mixY; +} + +void PathConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +Vector &PathConstraint::getBones() { + return _bones; +} + +Slot *PathConstraint::getTarget() { + return _target; +} + +void PathConstraint::setTarget(Slot *inValue) { + _target = inValue; +} + +PathConstraintData &PathConstraint::getData() { + return _data; +} + +Vector & +PathConstraint::computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents) { + Slot &target = *_target; + float position = _position; + _positions.setSize(spacesCount * 3 + 2, 0); + Vector &out = _positions; + Vector &world = _world; + bool closed = path.isClosed(); + int verticesLength = (int) path.getWorldVerticesLength(); + int curveCount = verticesLength / 6; + int prevCurve = NONE; + + float pathLength; + if (!path.isConstantSpeed()) { + Vector &lengths = path.getLengths(); + curveCount -= closed ? 1 : 2; + pathLength = lengths[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); + 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(target, 2, 4, world, 0); + } + + addBeforePosition(p, world, 0, out, o); + + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + + addAfterPosition(p - pathLength, world, 0, out, o); + + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = lengths[curve]; + if (p > length) continue; + + if (curve == 0) + p /= length; + else { + float prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); + path.computeWorldVertices(target, 0, 4, world, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); + } + + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], + out, o, tangents || (i > 0 && space < EPSILON)); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength, world, 0); + } + + // Curve lengths. + _curves.setSize(curveCount, 0); + pathLength = 0; + float x1 = world[0], y1 = world[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 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[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); + _curves[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; + } 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 = _curves[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = _curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + int ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[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, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i]; + float y1 = temp[i + 1]; + float dx = temp[i + 2] - x1; + float dy = temp[i + 3] - y1; + float 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, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i + 2]; + float y1 = temp[i + 3]; + float dx = x1 - temp[i]; + float dy = y1 - temp[i + 1]; + float 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, Vector &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.001) + 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)); + } +} + +bool PathConstraint::isActive() { + return _active; +} + +void PathConstraint::setActive(bool inValue) { + _active = inValue; +} + +void PathConstraint::setToSetupPose() { + PathConstraintData &data = this->_data; + this->_position = data._position; + this->_spacing = data._spacing; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathConstraintData.cpp b/modules/spine_godot/spine-cpp/src/spine/PathConstraintData.cpp new file mode 100644 index 000000000000..6d13223bc978 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathConstraintData.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintData, ConstraintData) + +PathConstraintData::PathConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _positionMode(PositionMode_Fixed), + _spacingMode(SpacingMode_Length), + _rotateMode(RotateMode_Tangent), + _offsetRotation(0), + _position(0), + _spacing(0), + _mixRotate(0), + _mixX(0), + _mixY(0) { +} + +Vector &PathConstraintData::getBones() { + return _bones; +} + +SlotData *PathConstraintData::getTarget() { + return _target; +} + +void PathConstraintData::setTarget(SlotData *inValue) { + _target = inValue; +} + +PositionMode PathConstraintData::getPositionMode() { + return _positionMode; +} + +void PathConstraintData::setPositionMode(PositionMode inValue) { + _positionMode = inValue; +} + +SpacingMode PathConstraintData::getSpacingMode() { + return _spacingMode; +} + +void PathConstraintData::setSpacingMode(SpacingMode inValue) { + _spacingMode = inValue; +} + +RotateMode PathConstraintData::getRotateMode() { + return _rotateMode; +} + +void PathConstraintData::setRotateMode(RotateMode inValue) { + _rotateMode = inValue; +} + +float PathConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +void PathConstraintData::setOffsetRotation(float inValue) { + _offsetRotation = inValue; +} + +float PathConstraintData::getPosition() { + return _position; +} + +void PathConstraintData::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraintData::getSpacing() { + return _spacing; +} + +void PathConstraintData::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraintData::getMixRotate() { + return _mixRotate; +} + +void PathConstraintData::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraintData::getMixX() { + return _mixX; +} + +void PathConstraintData::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraintData::getMixY() { + return _mixY; +} + +void PathConstraintData::setMixY(float inValue) { + _mixY = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathConstraintMixTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/PathConstraintMixTimeline.cpp new file mode 100644 index 000000000000..9d4969c6fce3 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathConstraintMixTimeline.cpp @@ -0,0 +1,126 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintMixTimeline, CurveTimeline) + +PathConstraintMixTimeline::PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex) + : CurveTimeline(frameCount, PathConstraintMixTimeline::ENTRIES, bezierCount), + _constraintIndex(pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintMix << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_constraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = constraint._data._mixRotate; + constraint._mixX = constraint._data._mixX; + constraint._mixY = constraint._data._mixY; + return; + case MixBlend_First: + constraint._mixRotate += (constraint._data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (constraint._data._mixX - constraint._mixX) * alpha; + constraint._mixY += (constraint._data._mixY - constraint._mixY) * alpha; + default: { + } + } + 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); + } + } + + if (blend == MixBlend_Setup) { + PathConstraintData data = constraint._data; + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp new file mode 100644 index 000000000000..25ab8d4168b9 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintPositionTimeline, CurveTimeline1) + +PathConstraintPositionTimeline::PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _constraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintPosition << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +PathConstraintPositionTimeline::~PathConstraintPositionTimeline() { +} + +void PathConstraintPositionTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_constraintIndex]; + if (constraint->_active) constraint->_position = getAbsoluteValue(time, alpha, blend, constraint->_position, constraint->_data._position); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp new file mode 100644 index 000000000000..e1921dd2d29c --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintSpacingTimeline, PathConstraintPositionTimeline) + +PathConstraintSpacingTimeline::PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _pathConstraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintSpacing << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintSpacingTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_pathConstraintIndex]; + if (constraint->_active) + constraint->_spacing = getAbsoluteValue(time, alpha, blend, constraint->_spacing, constraint->_data._spacing); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraint.cpp b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraint.cpp new file mode 100644 index 000000000000..adf9d8f63239 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraint.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * 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 +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraint, Updatable) + +PhysicsConstraint::PhysicsConstraint(PhysicsConstraintData &data, Skeleton &skeleton) + : _data(data), _skeleton(skeleton) { + _bone = skeleton.getBones()[data.getBone()->getIndex()]; + _inertia = data.getInertia(); + _strength = data.getStrength(); + _damping = data.getDamping(); + _massInverse = data.getMassInverse(); + _wind = data.getWind(); + _gravity = data.getGravity(); + _mix = data.getMix(); + + _reset = true; + _ux = 0; + _uy = 0; + _cx = 0; + _cy = 0; + _tx = 0; + _ty = 0; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; + _active = false; + _remaining = 0; + _lastTime = 0; +} + +PhysicsConstraintData &PhysicsConstraint::getData() { + return _data; +} + +void PhysicsConstraint::setBone(Bone *bone) { + _bone = bone; +} + +Bone *PhysicsConstraint::getBone() { + return _bone; +} + +void PhysicsConstraint::setInertia(float value) { + _inertia = value; +} + +float PhysicsConstraint::getInertia() { + return _inertia; +} + +void PhysicsConstraint::setStrength(float value) { + _strength = value; +} + +float PhysicsConstraint::getStrength() { + return _strength; +} + +void PhysicsConstraint::setDamping(float value) { + _damping = value; +} + +float PhysicsConstraint::getDamping() { + return _damping; +} + +void PhysicsConstraint::setMassInverse(float value) { + _massInverse = value; +} + +float PhysicsConstraint::getMassInverse() { + return _massInverse; +} + +void PhysicsConstraint::setWind(float value) { + _wind = value; +} + +float PhysicsConstraint::getWind() { + return _wind; +} + +void PhysicsConstraint::setGravity(float value) { + _gravity = value; +} + +float PhysicsConstraint::getGravity() { + return _gravity; +} + +void PhysicsConstraint::setMix(float value) { + _mix = value; +} + +float PhysicsConstraint::getMix() { + return _mix; +} + +void PhysicsConstraint::setReset(bool value) { + _reset = value; +} + +bool PhysicsConstraint::getReset() { + return _reset; +} + +void PhysicsConstraint::setUx(float value) { + _ux = value; +} + +float PhysicsConstraint::getUx() { + return _ux; +} + +void PhysicsConstraint::setUy(float value) { + _uy = value; +} + +float PhysicsConstraint::getUy() { + return _uy; +} + +void PhysicsConstraint::setCx(float value) { + _cx = value; +} + +float PhysicsConstraint::getCx() { + return _cx; +} + +void PhysicsConstraint::setCy(float value) { + _cy = value; +} + +float PhysicsConstraint::getCy() { + return _cy; +} + +void PhysicsConstraint::setTx(float value) { + _tx = value; +} + +float PhysicsConstraint::getTx() { + return _tx; +} + +void PhysicsConstraint::setTy(float value) { + _ty = value; +} + +float PhysicsConstraint::getTy() { + return _ty; +} + +void PhysicsConstraint::setXOffset(float value) { + _xOffset = value; +} + +float PhysicsConstraint::getXOffset() { + return _xOffset; +} + +void PhysicsConstraint::setXVelocity(float value) { + _xVelocity = value; +} + +float PhysicsConstraint::getXVelocity() { + return _xVelocity; +} + +void PhysicsConstraint::setYOffset(float value) { + _yOffset = value; +} + +float PhysicsConstraint::getYOffset() { + return _yOffset; +} + +void PhysicsConstraint::setYVelocity(float value) { + _yVelocity = value; +} + +float PhysicsConstraint::getYVelocity() { + return _yVelocity; +} + +void PhysicsConstraint::setRotateOffset(float value) { + _rotateOffset = value; +} + +float PhysicsConstraint::getRotateOffset() { + return _rotateOffset; +} + +void PhysicsConstraint::setRotateVelocity(float value) { + _rotateVelocity = value; +} + +float PhysicsConstraint::getRotateVelocity() { + return _rotateVelocity; +} + +void PhysicsConstraint::setScaleOffset(float value) { + _scaleOffset = value; +} + +float PhysicsConstraint::getScaleOffset() { + return _scaleOffset; +} + +void PhysicsConstraint::setScaleVelocity(float value) { + _scaleVelocity = value; +} + +float PhysicsConstraint::getScaleVelocity() { + return _scaleVelocity; +} + +void PhysicsConstraint::setActive(bool value) { + _active = value; +} + +bool PhysicsConstraint::isActive() { + return _active; +} + +void PhysicsConstraint::setRemaining(float value) { + _remaining = value; +} + +float PhysicsConstraint::getRemaining() { + return _remaining; +} + +void PhysicsConstraint::setLastTime(float value) { + _lastTime = value; +} + +float PhysicsConstraint::getLastTime() { + return _lastTime; +} + +void PhysicsConstraint::reset() { + _remaining = 0; + _lastTime = _skeleton.getTime(); + _reset = true; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; +} + +void PhysicsConstraint::setToSetupPose() { + _inertia = _data.getInertia(); + _strength = _data.getStrength(); + _damping = _data.getDamping(); + _massInverse = _data.getMassInverse(); + _wind = _data.getWind(); + _gravity = _data.getGravity(); + _mix = _data.getMix(); +} +void PhysicsConstraint::update(Physics physics) { + float mix = _mix; + if (mix == 0) return; + + bool x = _data._x > 0; + bool y = _data._y > 0; + bool rotateOrShearX = _data._rotate > 0 || _data._shearX > 0; + bool scaleX = _data._scaleX > 0; + + Bone *bone = _bone; + float l = bone->_data.getLength(); + + switch (physics) { + case Physics::Physics_None: + return; + case Physics::Physics_Reset: + reset(); + // Fall through. + case Physics::Physics_Update: { + float delta = MathUtil::max(_skeleton.getTime() - _lastTime, 0.0f); + _remaining += delta; + _lastTime = _skeleton.getTime(); + + float bx = bone->_worldX, by = bone->_worldY; + if (_reset) { + _reset = false; + _ux = bx; + _uy = by; + } else { + float a = _remaining, i = _inertia, t = _data._step, f = _skeleton.getData()->getReferenceScale(); + float qx = _data._limit * delta, qy = qx * MathUtil::abs(_skeleton.getScaleX()); + qx *= MathUtil::abs(_skeleton.getScaleY()); + 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 d = MathUtil::pow(_damping, 60 * t); + float m = _massInverse * t, e = _strength, w = _wind * f * _skeleton.getScaleX(), g = _gravity * f * _skeleton.getScaleY(); + do { + if (x) { + _xVelocity += (w - _xOffset * e) * m; + _xOffset += _xVelocity * t; + _xVelocity *= d; + } + if (y) { + _yVelocity -= (g + _yOffset * e) * m; + _yOffset += _yVelocity * t; + _yVelocity *= d; + } + a -= t; + } while (a >= t); + } + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + } + + if (rotateOrShearX || scaleX) { + float ca = MathUtil::atan2(bone->_c, bone->_a), c, s, mr = 0; + float 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; + float r = MathUtil::atan2(dy + _ty, dx + _tx) - ca - _rotateOffset * mr; + _rotateOffset += (r - MathUtil::ceil(r * MathUtil::InvPi_2 - 0.5f) * MathUtil::Pi_2) * i; + r = _rotateOffset * 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(); + if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r; + } + a = _remaining; + if (a >= t) { + float m = _massInverse * t, e = _strength, w = _wind, g = _gravity * (Bone::yDown ? -1 : 1), h = l / f; + float d = MathUtil::pow(_damping, 60 * t); + while (true) { + a -= t; + if (scaleX) { + _scaleVelocity += (w * c - g * s - _scaleOffset * e) * m; + _scaleOffset += _scaleVelocity * t; + _scaleVelocity *= d; + } + if (rotateOrShearX) { + _rotateVelocity -= ((w * s + g * 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; + } + } + } + _remaining = a; + } + + _cx = bone->_worldX; + _cy = bone->_worldY; + break; + } + case Physics::Physics_Pose: { + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + break; + } + } + + if (rotateOrShearX) { + float o = _rotateOffset * mix, s = 0, c = 0, a = 0; + 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 * mix * _data._scaleX; + bone->_a *= s; + bone->_c *= s; + } + if (physics != Physics::Physics_Pose) { + _tx = l * bone->_a; + _ty = l * bone->_c; + } + bone->updateAppliedTransform(); +} + +void PhysicsConstraint::rotate(float x, float y, float degrees) { + float r = degrees * MathUtil::Deg_Rad, cos = MathUtil::cos(r), sin = MathUtil::sin(r); + float dx = _cx - x, dy = _cy - y; + translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy); +} + +void PhysicsConstraint::translate(float x, float y) { + _ux -= x; + _uy -= y; + _cx -= x; + _cy -= y; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintData.cpp b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintData.cpp new file mode 100644 index 000000000000..2c88a8d6a9c4 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintData.cpp @@ -0,0 +1,223 @@ +/****************************************************************************** + * 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 + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintData, ConstraintData) + +PhysicsConstraintData::PhysicsConstraintData(const String &name) : ConstraintData(name), + _bone(nullptr), + _x(0), _y(0), _rotate(0), _scaleX(0), _shearX(0), _limit(0), + _step(0), _inertia(0), _strength(0), _damping(0), _massInverse(0), _wind(0), _gravity(0), _mix(0), + _inertiaGlobal(false), _strengthGlobal(false), _dampingGlobal(false), _massGlobal(false), + _windGlobal(false), _gravityGlobal(false), _mixGlobal(false) { +} + + +void PhysicsConstraintData::setBone(BoneData *bone) { + _bone = bone; +} + +BoneData *PhysicsConstraintData::getBone() const { + return _bone; +} + +void PhysicsConstraintData::setX(float x) { + _x = x; +} + +float PhysicsConstraintData::getX() const { + return _x; +} + +void PhysicsConstraintData::setY(float y) { + _y = y; +} + +float PhysicsConstraintData::getY() const { + return _y; +} + +void PhysicsConstraintData::setRotate(float rotate) { + _rotate = rotate; +} + +float PhysicsConstraintData::getRotate() const { + return _rotate; +} + +void PhysicsConstraintData::setScaleX(float scaleX) { + _scaleX = scaleX; +} + +float PhysicsConstraintData::getScaleX() const { + return _scaleX; +} + +void PhysicsConstraintData::setShearX(float shearX) { + _shearX = shearX; +} + +float PhysicsConstraintData::getShearX() const { + return _shearX; +} + +void PhysicsConstraintData::setLimit(float limit) { + _limit = limit; +} + +float PhysicsConstraintData::getLimit() const { + return _limit; +} + +void PhysicsConstraintData::setStep(float step) { + _step = step; +} + +float PhysicsConstraintData::getStep() const { + return _step; +} + +void PhysicsConstraintData::setInertia(float inertia) { + _inertia = inertia; +} + +float PhysicsConstraintData::getInertia() const { + return _inertia; +} + +void PhysicsConstraintData::setStrength(float strength) { + _strength = strength; +} + +float PhysicsConstraintData::getStrength() const { + return _strength; +} + +void PhysicsConstraintData::setDamping(float damping) { + _damping = damping; +} + +float PhysicsConstraintData::getDamping() const { + return _damping; +} + +void PhysicsConstraintData::setMassInverse(float massInverse) { + _massInverse = massInverse; +} + +float PhysicsConstraintData::getMassInverse() const { + return _massInverse; +} + +void PhysicsConstraintData::setWind(float wind) { + _wind = wind; +} + +float PhysicsConstraintData::getWind() const { + return _wind; +} + +void PhysicsConstraintData::setGravity(float gravity) { + _gravity = gravity; +} + +float PhysicsConstraintData::getGravity() const { + return _gravity; +} + +void PhysicsConstraintData::setMix(float mix) { + _mix = mix; +} + +float PhysicsConstraintData::getMix() const { + return _mix; +} + +void PhysicsConstraintData::setInertiaGlobal(bool inertiaGlobal) { + _inertiaGlobal = inertiaGlobal; +} + +bool PhysicsConstraintData::isInertiaGlobal() const { + return _inertiaGlobal; +} + +void PhysicsConstraintData::setStrengthGlobal(bool strengthGlobal) { + _strengthGlobal = strengthGlobal; +} + +bool PhysicsConstraintData::isStrengthGlobal() const { + return _strengthGlobal; +} + +void PhysicsConstraintData::setDampingGlobal(bool dampingGlobal) { + _dampingGlobal = dampingGlobal; +} + +bool PhysicsConstraintData::isDampingGlobal() const { + return _dampingGlobal; +} + +void PhysicsConstraintData::setMassGlobal(bool massGlobal) { + _massGlobal = massGlobal; +} + +bool PhysicsConstraintData::isMassGlobal() const { + return _massGlobal; +} + +void PhysicsConstraintData::setWindGlobal(bool windGlobal) { + _windGlobal = windGlobal; +} + +bool PhysicsConstraintData::isWindGlobal() const { + return _windGlobal; +} + +void PhysicsConstraintData::setGravityGlobal(bool gravityGlobal) { + _gravityGlobal = gravityGlobal; +} + +bool PhysicsConstraintData::isGravityGlobal() const { + return _gravityGlobal; +} + +void PhysicsConstraintData::setMixGlobal(bool mixGlobal) { + _mixGlobal = mixGlobal; +} + +bool PhysicsConstraintData::isMixGlobal() const { + return _mixGlobal; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp new file mode 100644 index 000000000000..4671e3ffdef5 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintTimeline, CurveTimeline) +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(PhysicsConstraintResetTimeline, Timeline) + +PhysicsConstraintTimeline::PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, + int constraintIndex, Property property) : CurveTimeline1(frameCount, bezierCount), + _constraintIndex(constraintIndex) { + PropertyId ids[] = {((PropertyId) property << 32) | constraintIndex}; + setPropertyIds(ids, 1); +} + +void PhysicsConstraintTimeline::apply(Skeleton &skeleton, float, float time, Vector *, + float alpha, MixBlend blend, MixDirection) { + if (_constraintIndex == -1) { + float value = time >= _frames[0] ? getCurveValue(time) : 0; + + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + PhysicsConstraint *constraint = physicsConstraints[i]; + if (constraint->_active && global(constraint->_data)) + set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint), value)); + } + } else { + PhysicsConstraint *constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (constraint->_active) set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint))); + } +} + +void PhysicsConstraintResetTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *, float alpha, MixBlend blend, MixDirection direction) { + PhysicsConstraint *constraint = nullptr; + if (_constraintIndex != -1) { + constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (!constraint->_active) return; + } + + if (lastTime > time) {// Apply after lastTime for looped animations. + apply(skeleton, lastTime, FLT_MAX, nullptr, alpha, blend, direction); + 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(); + else { + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + constraint = physicsConstraints[i]; + if (constraint->_active) constraint->reset(); + } + } + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/PointAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/PointAttachment.cpp new file mode 100644 index 000000000000..611f3aff9713 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/PointAttachment.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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 + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PointAttachment, Attachment) + +PointAttachment::PointAttachment(const String &name) : Attachment(name), _x(0), _y(0), _rotation(0), _color() { +} + +void PointAttachment::computeWorldPosition(Bone &bone, float &ox, float &oy) { + bone.localToWorld(_x, _y, ox, oy); +} + +float PointAttachment::computeWorldRotation(Bone &bone) { + float r = _rotation * MathUtil::Deg_Rad, cosine = MathUtil::cos(r), sine = MathUtil::sin(r); + float x = cosine * bone._a + sine * bone._b; + float y = cosine * bone._c + sine * bone._d; + return MathUtil::atan2Deg(y, x); +} + +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; +} + +Attachment *PointAttachment::copy() { + PointAttachment *copy = new (__FILE__, __LINE__) PointAttachment(getName()); + copy->_x = _x; + copy->_y = _y; + copy->_rotation = _rotation; + return copy; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/RTTI.cpp b/modules/spine_godot/spine-cpp/src/spine/RTTI.cpp new file mode 100644 index 000000000000..04c3160cbc48 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/RTTI.cpp @@ -0,0 +1,56 @@ +/****************************************************************************** + * 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 +#include + +using namespace spine; + +RTTI::RTTI(const char *className) : _className(className), _pBaseRTTI(NULL) { +} + +RTTI::RTTI(const char *className, const RTTI &baseRTTI) : _className(className), _pBaseRTTI(&baseRTTI) { +} + +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 { + const RTTI *pCompare = this; + while (pCompare) { + if (!strcmp(pCompare->_className, rtti._className)) return true; + pCompare = pCompare->_pBaseRTTI; + } + return false; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/RegionAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/RegionAttachment.cpp new file mode 100644 index 000000000000..65e1423e5423 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/RegionAttachment.cpp @@ -0,0 +1,275 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include + +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) : Attachment(name), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _width(0), + _height(0), + _path(), + _color(1, 1, 1, 1), + _region(NULL), + _sequence(NULL) { + _vertexOffset.setSize(NUM_UVS, 0); + _uvs.setSize(NUM_UVS, 0); +} + +RegionAttachment::~RegionAttachment() { + if (_sequence) delete _sequence; +} + +void RegionAttachment::updateRegion() { + 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; + return; + } + + float regionScaleX = _width / _region->originalWidth * _scaleX; + float regionScaleY = _height / _region->originalHeight * _scaleY; + float localX = -_width / 2 * _scaleX + _region->offsetX * regionScaleX; + float localY = -_height / 2 * _scaleY + _region->offsetY * regionScaleY; + float localX2 = localX + _region->width * regionScaleX; + float localY2 = localY + _region->height * regionScaleY; + 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; + + _vertexOffset[BLX] = localXCos - localYSin; + _vertexOffset[BLY] = localYCos + localXSin; + _vertexOffset[ULX] = localXCos - localY2Sin; + _vertexOffset[ULY] = localY2Cos + localXSin; + _vertexOffset[URX] = localX2Cos - localY2Sin; + _vertexOffset[URY] = localY2Cos + localX2Sin; + _vertexOffset[BRX] = localX2Cos - localYSin; + _vertexOffset[BRY] = localYCos + localX2Sin; + + if (_region->degrees == 90) { + _uvs[URX] = _region->u; + _uvs[URY] = _region->v2; + _uvs[BRX] = _region->u; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v; + _uvs[ULX] = _region->u2; + _uvs[ULY] = _region->v2; + } else { + _uvs[ULX] = _region->u; + _uvs[ULY] = _region->v2; + _uvs[URX] = _region->u; + _uvs[URY] = _region->v; + _uvs[BRX] = _region->u2; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v2; + } +} + +void RegionAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride) { + assert(worldVertices.size() >= (offset + 8)); + computeWorldVertices(slot, worldVertices.buffer(), offset, stride); +} + +void RegionAttachment::computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + + Bone &bone = slot.getBone(); + float x = bone.getWorldX(), y = bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + float offsetX, offsetY; + + offsetX = _vertexOffset[BRX]; + offsetY = _vertexOffset[BRY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[BLX]; + offsetY = _vertexOffset[BLY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[ULX]; + offsetY = _vertexOffset[ULY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[URX]; + offsetY = _vertexOffset[URY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; +} + +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::getRotation() { + return _rotation; +} + +void RegionAttachment::setRotation(float inValue) { + _rotation = 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::getWidth() { + return _width; +} + +void RegionAttachment::setWidth(float inValue) { + _width = inValue; +} + +float RegionAttachment::getHeight() { + return _height; +} + +void RegionAttachment::setHeight(float inValue) { + _height = inValue; +} + +const String &RegionAttachment::getPath() { + return _path; +} + +void RegionAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *RegionAttachment::getRegion() { + return _region; +} + +void RegionAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *RegionAttachment::getSequence() { + return _sequence; +} + +void RegionAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +Vector &RegionAttachment::getOffset() { + return _vertexOffset; +} + +Vector &RegionAttachment::getUVs() { + return _uvs; +} + +spine::Color &RegionAttachment::getColor() { + return _color; +} + +Attachment *RegionAttachment::copy() { + RegionAttachment *copy = new (__FILE__, __LINE__) RegionAttachment(getName()); + copy->_region = _region; + 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->_uvs.clearAndAddAll(_uvs); + copy->_vertexOffset.clearAndAddAll(_vertexOffset); + copy->_color.set(_color); + copy->_sequence = _sequence != NULL ? _sequence->copy() : NULL; + return copy; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/RotateTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/RotateTimeline.cpp new file mode 100644 index 000000000000..4c53f3010500 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/RotateTimeline.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RotateTimeline, CurveTimeline1) + +RotateTimeline::RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Rotate << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +void RotateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->isActive()) bone->_rotation = getRelativeValue(time, alpha, blend, bone->_rotation, bone->getData()._rotation); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/ScaleTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/ScaleTimeline.cpp new file mode 100644 index 000000000000..f2fe50ca16d9 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/ScaleTimeline.cpp @@ -0,0 +1,194 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ScaleTimeline, CurveTimeline2) + +ScaleTimeline::ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex, + ((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ScaleTimeline::~ScaleTimeline() {} + +void ScaleTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_scaleX = bone->_data._scaleX; + bone->_scaleY = bone->_data._scaleY; + return; + case MixBlend_First: + bone->_scaleX += (bone->_data._scaleX - bone->_scaleX) * alpha; + bone->_scaleY += (bone->_data._scaleY - bone->_scaleY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + x *= bone->_data._scaleX; + y *= bone->_data._scaleY; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + bone->_scaleX += x - bone->_data._scaleX; + bone->_scaleY += y - bone->_data._scaleY; + } else { + bone->_scaleX = x; + bone->_scaleY = y; + } + } else { + float bx, by; + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + bx = bone->_data._scaleX; + by = bone->_data._scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = bone->_scaleX; + by = bone->_scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } else { + switch (blend) { + case MixBlend_Setup: + bx = MathUtil::abs(bone->_data._scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_data._scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = MathUtil::abs(bone->_scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } + } +} + +RTTI_IMPL(ScaleXTimeline, CurveTimeline1) + +ScaleXTimeline::ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleXTimeline::~ScaleXTimeline() {} + +void ScaleXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleX = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleX); +} + +RTTI_IMPL(ScaleYTimeline, CurveTimeline1) + +ScaleYTimeline::ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleYTimeline::~ScaleYTimeline() {} + +void ScaleYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleY = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleY); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Sequence.cpp b/modules/spine_godot/spine-cpp/src/spine/Sequence.cpp new file mode 100644 index 000000000000..f690dcecc33e --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Sequence.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include + +using namespace spine; + +Sequence::Sequence(int count) : _id(Sequence::getNextID()), + _regions(), + _start(0), + _digits(0), + _setupIndex(0) { + _regions.setSize(count, NULL); +} + +Sequence::~Sequence() { +} + +Sequence *Sequence::copy() { + Sequence *copy = new (__FILE__, __LINE__) Sequence((int) _regions.size()); + for (size_t i = 0; i < _regions.size(); i++) { + copy->_regions[i] = _regions[i]; + } + copy->_start = _start; + copy->_digits = _digits; + copy->_setupIndex = _setupIndex; + return copy; +} + +void Sequence::apply(Slot *slot, Attachment *attachment) { + int index = slot->getSequenceIndex(); + if (index == -1) index = _setupIndex; + if (index >= (int) _regions.size()) index = (int) _regions.size() - 1; + TextureRegion *region = _regions[index]; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = static_cast(attachment); + if (regionAttachment->getRegion() != region) { + regionAttachment->setRegion(region); + regionAttachment->updateRegion(); + } + } + + if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *meshAttachment = static_cast(attachment); + if (meshAttachment->getRegion() != region) { + meshAttachment->setRegion(region); + meshAttachment->updateRegion(); + } + } +} + +String Sequence::getPath(const String &basePath, int index) { + String result(basePath); + String frame; + frame.append(_start + index); + for (int i = _digits - (int) frame.length(); i > 0; i--) + result.append("0"); + result.append(frame); + return result; +} + +int Sequence::getNextID() { + static int _nextID = 0; + return _nextID; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SequenceTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/SequenceTimeline.cpp new file mode 100644 index 000000000000..f00bd8201f80 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SequenceTimeline.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(SequenceTimeline, Timeline) + +SequenceTimeline::SequenceTimeline(size_t frameCount, int slotIndex, Attachment *attachment) : Timeline(frameCount, ENTRIES), _slotIndex(slotIndex), _attachment(attachment) { + 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) { + Vector &frames = this->_frames; + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = mode | (index << 4); + frames[frame + DELAY] = delay; +} + +void SequenceTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(alpha); + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton.getSlots()[_slotIndex]; + if (!slot->getBone().isActive()) return; + Attachment *slotAttachment = slot->getAttachment(); + if (slotAttachment != _attachment) { + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti) || ((VertexAttachment *) slotAttachment)->getTimelineAttachment() != _attachment) return; + } + Sequence *sequence = NULL; + if (_attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequence = ((RegionAttachment *) _attachment)->getSequence(); + if (_attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequence = ((MeshAttachment *) _attachment)->getSequence(); + if (!sequence) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) slot->setSequenceIndex(-1); + return; + } + + Vector &frames = this->_frames; + if (time < frames[0]) {// Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) slot->setSequenceIndex(-1); + return; + } + + int i = Animation::search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int) frames[i + MODE]; + float delay = frames[i + DELAY]; + + 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; + } + } + } + slot->setSequenceIndex(index); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/ShearTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/ShearTimeline.cpp new file mode 100644 index 000000000000..027f70c652f1 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/ShearTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ShearTimeline, CurveTimeline2) + +ShearTimeline::ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex, + ((PropertyId) Property_ShearY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ShearTimeline::~ShearTimeline() { +} + +void ShearTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX; + bone->_shearY = bone->_data._shearY; + return; + case MixBlend_First: + bone->_shearX += (bone->_data._shearX - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY - bone->_shearY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline2::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline2::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX + x * alpha; + bone->_shearY = bone->_data._shearY + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_shearX += (bone->_data._shearX + x - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY + y - bone->_shearY) * alpha; + break; + case MixBlend_Add: + bone->_shearX += x * alpha; + bone->_shearY += y * alpha; + } +} + +RTTI_IMPL(ShearXTimeline, CurveTimeline1) + +ShearXTimeline::ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearXTimeline::~ShearXTimeline() { +} + +void ShearXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearX = getRelativeValue(time, alpha, blend, bone->_shearX, bone->_data._shearX); +} + +RTTI_IMPL(ShearYTimeline, CurveTimeline1) + +ShearYTimeline::ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearYTimeline::~ShearYTimeline() { +} + +void ShearYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearY = getRelativeValue(time, alpha, blend, bone->_shearY, bone->_data._shearY); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Skeleton.cpp b/modules/spine_godot/spine-cpp/src/spine/Skeleton.cpp new file mode 100644 index 000000000000..84292a918ab8 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Skeleton.cpp @@ -0,0 +1,774 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Skeleton::Skeleton(SkeletonData *skeletonData) + : _data(skeletonData), _skin(NULL), _color(1, 1, 1, 1), _scaleX(1), + _scaleY(1), _x(0), _y(0), _time(0) { + _bones.ensureCapacity(_data->getBones().size()); + for (size_t i = 0; i < _data->getBones().size(); ++i) { + BoneData *data = _data->getBones()[i]; + + Bone *bone; + if (data->getParent() == NULL) { + bone = new (__FILE__, __LINE__) Bone(*data, *this, NULL); + } else { + Bone *parent = _bones[data->getParent()->getIndex()]; + bone = new (__FILE__, __LINE__) Bone(*data, *this, parent); + parent->getChildren().add(bone); + } + + _bones.add(bone); + } + + _slots.ensureCapacity(_data->getSlots().size()); + _drawOrder.ensureCapacity(_data->getSlots().size()); + for (size_t i = 0; i < _data->getSlots().size(); ++i) { + SlotData *data = _data->getSlots()[i]; + + Bone *bone = _bones[data->getBoneData().getIndex()]; + Slot *slot = new (__FILE__, __LINE__) Slot(*data, *bone); + + _slots.add(slot); + _drawOrder.add(slot); + } + + _ikConstraints.ensureCapacity(_data->getIkConstraints().size()); + for (size_t i = 0; i < _data->getIkConstraints().size(); ++i) { + IkConstraintData *data = _data->getIkConstraints()[i]; + + IkConstraint *constraint = + new (__FILE__, __LINE__) IkConstraint(*data, *this); + + _ikConstraints.add(constraint); + } + + _transformConstraints.ensureCapacity(_data->getTransformConstraints().size()); + for (size_t i = 0; i < _data->getTransformConstraints().size(); ++i) { + TransformConstraintData *data = _data->getTransformConstraints()[i]; + + TransformConstraint *constraint = + new (__FILE__, __LINE__) TransformConstraint(*data, *this); + + _transformConstraints.add(constraint); + } + + _pathConstraints.ensureCapacity(_data->getPathConstraints().size()); + for (size_t i = 0; i < _data->getPathConstraints().size(); ++i) { + PathConstraintData *data = _data->getPathConstraints()[i]; + + PathConstraint *constraint = + new (__FILE__, __LINE__) PathConstraint(*data, *this); + + _pathConstraints.add(constraint); + } + + _physicsConstraints.ensureCapacity(_data->getPhysicsConstraints().size()); + for (size_t i = 0; i < _data->getPhysicsConstraints().size(); ++i) { + PhysicsConstraintData *data = _data->getPhysicsConstraints()[i]; + + PhysicsConstraint *constraint = + new (__FILE__, __LINE__) PhysicsConstraint(*data, *this); + + _physicsConstraints.add(constraint); + } + + updateCache(); +} + +Skeleton::~Skeleton() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); +} + +void Skeleton::updateCache() { + _updateCache.clear(); + + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + Bone *bone = _bones[i]; + bone->_sorted = bone->_data.isSkinRequired(); + bone->_active = !bone->_sorted; + } + + if (_skin) { + Vector &skinBones = _skin->getBones(); + for (size_t i = 0, n = skinBones.size(); i < n; i++) { + Bone *bone = _bones[skinBones[i]->getIndex()]; + do { + bone->_sorted = false; + bone->_active = true; + bone = bone->_parent; + } while (bone); + } + } + + size_t ikCount = _ikConstraints.size(); + size_t transformCount = _transformConstraints.size(); + size_t pathCount = _pathConstraints.size(); + size_t physicsCount = _physicsConstraints.size(); + size_t constraintCount = ikCount + transformCount + pathCount + physicsCount; + + size_t i = 0; +continue_outer: + for (; i < constraintCount; ++i) { + for (size_t ii = 0; ii < ikCount; ++ii) { + IkConstraint *constraint = _ikConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortIkConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < transformCount; ++ii) { + TransformConstraint *constraint = _transformConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortTransformConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < pathCount; ++ii) { + PathConstraint *constraint = _pathConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPathConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < physicsCount; ++ii) { + PhysicsConstraint *constraint = _physicsConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPhysicsConstraint(constraint); + i++; + goto continue_outer; + } + } + } + + size_t n = _bones.size(); + for (i = 0; i < n; ++i) { + sortBone(_bones[i]); + } +} + +void Skeleton::printUpdateCache() { + for (size_t i = 0; i < _updateCache.size(); i++) { + Updatable *updatable = _updateCache[i]; + if (updatable->getRTTI().isExactly(Bone::rtti)) { + printf("bone %s\n", ((Bone *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(TransformConstraint::rtti)) { + printf("transform constraint %s\n", + ((TransformConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(IkConstraint::rtti)) { + printf("ik constraint %s\n", + ((IkConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PathConstraint::rtti)) { + printf("path constraint %s\n", + ((PathConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PhysicsConstraint::rtti)) { + printf("physics constraint %s\n", + ((PhysicsConstraint *) updatable)->getData().getName().buffer()); + } + } +} + +void Skeleton::updateWorldTransform(Physics physics) { + for (size_t i = 0, n = _bones.size(); i < n; i++) { + Bone *bone = _bones[i]; + bone->_ax = bone->_x; + bone->_ay = bone->_y; + bone->_arotation = bone->_rotation; + bone->_ascaleX = bone->_scaleX; + bone->_ascaleY = bone->_scaleY; + bone->_ashearX = bone->_shearX; + bone->_ashearY = bone->_shearY; + } + + for (size_t i = 0, n = _updateCache.size(); i < n; ++i) { + Updatable *updatable = _updateCache[i]; + updatable->update(physics); + } +} + +void Skeleton::updateWorldTransform(Physics physics, Bone *parent) { + // Apply the parent bone transform to the root bone. The root bone always + // inherits scale, rotation and reflection. + Bone *rootBone = getRootBone(); + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + rootBone->_worldX = pa * _x + pb * _y + parent->_worldX; + rootBone->_worldY = pc * _x + pd * _y + parent->_worldY; + + float rx = (rootBone->_rotation + rootBone->_shearX) * MathUtil::Deg_Rad; + float ry = (rootBone->_rotation + 90 + rootBone->_shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * rootBone->_scaleX; + float lb = MathUtil::cos(ry) * rootBone->_scaleY; + float lc = MathUtil::sin(rx) * rootBone->_scaleX; + float ld = MathUtil::sin(ry) * rootBone->_scaleY; + rootBone->_a = (pa * la + pb * lc) * _scaleX; + rootBone->_b = (pa * lb + pb * ld) * _scaleX; + rootBone->_c = (pc * la + pd * lc) * _scaleY; + rootBone->_d = (pc * lb + pd * ld) * _scaleY; + + // Update everything except root bone. + Bone *rb = getRootBone(); + for (size_t i = 0, n = _updateCache.size(); i < n; i++) { + Updatable *updatable = _updateCache[i]; + if (updatable != rb) + updatable->update(physics); + } +} + +void Skeleton::setToSetupPose() { + setBonesToSetupPose(); + setSlotsToSetupPose(); +} + +void Skeleton::setBonesToSetupPose() { + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + _bones[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + _ikConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + _transformConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + _pathConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + _physicsConstraints[i]->setToSetupPose(); + } +} + +void Skeleton::setSlotsToSetupPose() { + _drawOrder.clear(); + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _drawOrder.add(_slots[i]); + } + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _slots[i]->setToSetupPose(); + } +} + +Bone *Skeleton::findBone(const String &boneName) { + return ContainerUtil::findWithDataName(_bones, boneName); +} + +Slot *Skeleton::findSlot(const String &slotName) { + return ContainerUtil::findWithDataName(_slots, slotName); +} + +void Skeleton::setSkin(const String &skinName) { + Skin *foundSkin = skinName.isEmpty() ? NULL : _data->findSkin(skinName); + setSkin(foundSkin); +} + +void Skeleton::setSkin(Skin *newSkin) { + if (_skin == newSkin) + return; + if (newSkin != NULL) { + if (_skin != NULL) { + Skeleton &thisRef = *this; + newSkin->attachAll(thisRef, *_skin); + } else { + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slotP = _slots[i]; + Slot &slot = *slotP; + const String &name = slot._data.getAttachmentName(); + if (name.length() > 0) { + Attachment *attachment = newSkin->getAttachment(i, name); + if (attachment != NULL) { + slot.setAttachment(attachment); + } + } + } + } + } + + _skin = newSkin; + updateCache(); +} + +Attachment *Skeleton::getAttachment(const String &slotName, + const String &attachmentName) { + return getAttachment(_data->findSlot(slotName)->getIndex(), attachmentName); +} + +Attachment *Skeleton::getAttachment(int slotIndex, + const String &attachmentName) { + if (attachmentName.isEmpty()) + return NULL; + + if (_skin != NULL) { + Attachment *attachment = _skin->getAttachment(slotIndex, attachmentName); + if (attachment != NULL) { + return attachment; + } + } + + return _data->getDefaultSkin() != NULL + ? _data->getDefaultSkin()->getAttachment(slotIndex, attachmentName) + : NULL; +} + +void Skeleton::setAttachment(const String &slotName, + const String &attachmentName) { + assert(slotName.length() > 0); + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slot = _slots[i]; + if (slot->_data.getName() == slotName) { + Attachment *attachment = NULL; + if (attachmentName.length() > 0) { + attachment = getAttachment((int) i, attachmentName); + + assert(attachment != NULL); + } + + slot->setAttachment(attachment); + + return; + } + } + + printf("Slot not found: %s", slotName.buffer()); + + assert(false); +} + +IkConstraint *Skeleton::findIkConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + IkConstraint *ikConstraint = _ikConstraints[i]; + if (ikConstraint->_data.getName() == constraintName) { + return ikConstraint; + } + } + return NULL; +} + +TransformConstraint * +Skeleton::findTransformConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + TransformConstraint *transformConstraint = _transformConstraints[i]; + if (transformConstraint->_data.getName() == constraintName) { + return transformConstraint; + } + } + + return NULL; +} + +PathConstraint *Skeleton::findPathConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + PathConstraint *constraint = _pathConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +PhysicsConstraint * +Skeleton::findPhysicsConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + PhysicsConstraint *constraint = _physicsConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &outVertexBuffer) { + getBounds(outX, outY, outWidth, outHeight, outVertexBuffer, NULL); +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &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; + + for (size_t i = 0; i < _drawOrder.size(); ++i) { + Slot *slot = _drawOrder[i]; + if (!slot->_bone._active) + continue; + size_t verticesLength = 0; + Attachment *attachment = slot->getAttachment(); + unsigned short *triangles = NULL; + size_t trianglesLength = 0; + + if (attachment != NULL && + attachment->getRTTI().instanceOf(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = + static_cast(attachment); + + verticesLength = 8; + if (outVertexBuffer.size() < 8) { + outVertexBuffer.setSize(8, 0); + } + regionAttachment->computeWorldVertices(*slot, outVertexBuffer, 0); + triangles = quadIndices; + trianglesLength = 6; + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(MeshAttachment::rtti)) { + MeshAttachment *mesh = static_cast(attachment); + + verticesLength = mesh->getWorldVerticesLength(); + if (outVertexBuffer.size() < verticesLength) { + outVertexBuffer.setSize(verticesLength, 0); + } + + mesh->computeWorldVertices(*slot, 0, verticesLength, + outVertexBuffer.buffer(), 0); + triangles = mesh->getTriangles().buffer(); + trianglesLength = mesh->getTriangles().size(); + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(ClippingAttachment::rtti) && clipper != NULL) { + clipper->clipStart(*slot, static_cast(attachment)); + continue; + } + + if (verticesLength > 0) { + float *vertices = outVertexBuffer.buffer(); + if (clipper != NULL && clipper->isClipping()) { + clipper->clipTriangles(outVertexBuffer.buffer(), triangles, trianglesLength); + vertices = clipper->getClippedVertices().buffer(); + verticesLength = clipper->getClippedVertices().size(); + } + for (size_t ii = 0; ii < verticesLength; ii += 2) { + float vx = vertices[ii]; + float vy = vertices[ii + 1]; + + minX = MathUtil::min(minX, vx); + minY = MathUtil::min(minY, vy); + maxX = MathUtil::max(maxX, vx); + maxY = MathUtil::max(maxY, vy); + } + } + if (clipper != NULL) clipper->clipEnd(*slot); + } + if (clipper != NULL) clipper->clipEnd(); + + outX = minX; + outY = minY; + outWidth = maxX - minX; + outHeight = maxY - minY; +} + +Bone *Skeleton::getRootBone() { return _bones.size() == 0 ? NULL : _bones[0]; } + +SkeletonData *Skeleton::getData() { return _data; } + +Vector &Skeleton::getBones() { return _bones; } + +Vector &Skeleton::getUpdateCacheList() { return _updateCache; } + +Vector &Skeleton::getSlots() { return _slots; } + +Vector &Skeleton::getDrawOrder() { return _drawOrder; } + +Vector &Skeleton::getIkConstraints() { return _ikConstraints; } + +Vector &Skeleton::getPathConstraints() { + return _pathConstraints; +} + +Vector &Skeleton::getTransformConstraints() { + return _transformConstraints; +} + +Vector &Skeleton::getPhysicsConstraints() { + return _physicsConstraints; +} + +Skin *Skeleton::getSkin() { return _skin; } + +Color &Skeleton::getColor() { return _color; } + +void Skeleton::setPosition(float x, float y) { + _x = x; + _y = y; +} + +float Skeleton::getX() { return _x; } + +void Skeleton::setX(float inValue) { _x = inValue; } + +float Skeleton::getY() { return _y; } + +void Skeleton::setY(float inValue) { _y = inValue; } + +float Skeleton::getScaleX() { return _scaleX; } + +void Skeleton::setScaleX(float inValue) { _scaleX = inValue; } + +float Skeleton::getScaleY() { return _scaleY * (Bone::isYDown() ? -1 : 1); } + +void Skeleton::setScaleY(float inValue) { _scaleY = inValue; } + +void Skeleton::sortIkConstraint(IkConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Bone *target = constraint->getTarget(); + sortBone(target); + + Vector &constrained = constraint->getBones(); + Bone *parent = constrained[0]; + sortBone(parent); + + if (constrained.size() == 1) { + _updateCache.add(constraint); + sortReset(parent->_children); + } else { + Bone *child = constrained[constrained.size() - 1]; + sortBone(child); + + _updateCache.add(constraint); + + sortReset(parent->_children); + child->_sorted = true; + } +} + +void Skeleton::sortPathConstraint(PathConstraint *constraint) { + constraint->_active = + constraint->_target->_bone._active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Slot *slot = constraint->getTarget(); + int slotIndex = slot->getData().getIndex(); + Bone &slotBone = slot->getBone(); + if (_skin != NULL) + sortPathConstraintAttachment(_skin, slotIndex, slotBone); + if (_data->_defaultSkin != NULL && _data->_defaultSkin != _skin) + sortPathConstraintAttachment(_data->_defaultSkin, slotIndex, slotBone); + for (size_t ii = 0, nn = _data->_skins.size(); ii < nn; ii++) + sortPathConstraintAttachment(_data->_skins[ii], slotIndex, slotBone); + + Attachment *attachment = slot->getAttachment(); + if (attachment != NULL && + attachment->getRTTI().instanceOf(PathAttachment::rtti)) + sortPathConstraintAttachment(attachment, slotBone); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; i++) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; i++) + constrained[i]->_sorted = true; +} + +void Skeleton::sortTransformConstraint(TransformConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(constraint->getTarget()); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + if (constraint->_data.isLocal()) { + for (size_t i = 0; i < boneCount; i++) { + Bone *child = constrained[i]; + sortBone(child->getParent()); + sortBone(child); + } + } else { + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; ++i) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; ++i) + constrained[i]->_sorted = true; +} + +void Skeleton::sortPhysicsConstraint(PhysicsConstraint *constraint) { + Bone *bone = constraint->getBone(); + constraint->_active = + bone->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(bone); + _updateCache.add(constraint); + sortReset(bone->getChildren()); + bone->_sorted = true; +} + +void Skeleton::sortPathConstraintAttachment(Skin *skin, size_t slotIndex, + Bone &slotBone) { + Skin::AttachmentMap::Entries attachments = skin->getAttachments(); + + while (attachments.hasNext()) { + Skin::AttachmentMap::Entry entry = attachments.next(); + if (entry._slotIndex == slotIndex) { + Attachment *value = entry._attachment; + sortPathConstraintAttachment(value, slotBone); + } + } +} + +void Skeleton::sortPathConstraintAttachment(Attachment *attachment, + Bone &slotBone) { + if (attachment == NULL || + !attachment->getRTTI().instanceOf(PathAttachment::rtti)) + return; + Vector &pathBones = + static_cast(attachment)->getBones(); + if (pathBones.size() == 0) + sortBone(&slotBone); + else { + for (size_t i = 0, n = pathBones.size(); i < n;) { + size_t nn = pathBones[i++]; + nn += i; + while (i < nn) { + sortBone(_bones[pathBones[i++]]); + } + } + } +} + +void Skeleton::sortBone(Bone *bone) { + if (bone->_sorted) + return; + Bone *parent = bone->_parent; + if (parent != NULL) + sortBone(parent); + bone->_sorted = true; + _updateCache.add(bone); +} + +void Skeleton::sortReset(Vector &bones) { + for (size_t i = 0, n = bones.size(); i < n; ++i) { + Bone *bone = bones[i]; + if (!bone->_active) + continue; + if (bone->_sorted) + sortReset(bone->getChildren()); + bone->_sorted = false; + } +} + +float Skeleton::getTime() { return _time; } + +void Skeleton::setTime(float time) { _time = time; } + +void Skeleton::update(float delta) { _time += delta; } + +void Skeleton::physicsTranslate(float x, float y) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->translate(x, y); + } +} + +void Skeleton::physicsRotate(float x, float y, float degrees) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->rotate(x, y, degrees); + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonBinary.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonBinary.cpp new file mode 100644 index 000000000000..a82ada141fc1 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonBinary.cpp @@ -0,0 +1,1490 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader( + new (__FILE__, __LINE__) AtlasAttachmentLoader(atlasArray)), + _error(), _scale(1), _ownsLoader(true) { +} + +SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader( + attachmentLoader), + _error(), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonBinary::~SkeletonBinary() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) { + bool nonessential; + SkeletonData *skeletonData; + + DataInput *input = new (__FILE__, __LINE__) DataInput(); + input->cursor = binary; + input->end = binary + length; + + _linkedMeshes.clear(); + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + char buffer[16] = {0}; + int lowHash = readInt(input); + int hightHash = readInt(input); + String hashString; + snprintf(buffer, 16, "%x", hightHash); + hashString.append(buffer); + snprintf(buffer, 16, "%x", lowHash); + hashString.append(buffer); + skeletonData->_hash = hashString; + + char *skeletonDataVersion = readString(input); + skeletonData->_version.own(skeletonDataVersion); + + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + setError(errorMsg, ""); + delete input; + delete skeletonData; + return NULL; + } + + skeletonData->_x = readFloat(input); + skeletonData->_y = readFloat(input); + skeletonData->_width = readFloat(input); + skeletonData->_height = readFloat(input); + skeletonData->_referenceScale = readFloat(input) * this->_scale; + + nonessential = readBoolean(input); + + if (nonessential) { + skeletonData->_fps = readFloat(input); + skeletonData->_imagesPath.own(readString(input)); + skeletonData->_audioPath.own(readString(input)); + } + + int numStrings = readVarint(input, true); + for (int i = 0; i < numStrings; i++) + skeletonData->_strings.add(readString(input)); + + /* Bones. */ + int numBones = readVarint(input, true); + skeletonData->_bones.setSize(numBones, 0); + for (int i = 0; i < numBones; ++i) { + const char *name = readString(input); + BoneData *parent = i == 0 ? 0 : skeletonData->_bones[readVarint(input, true)]; + BoneData *data = new (__FILE__, __LINE__) BoneData(i, String(name, true), parent); + data->_rotation = readFloat(input); + data->_x = readFloat(input) * _scale; + data->_y = readFloat(input) * _scale; + data->_scaleX = readFloat(input); + data->_scaleY = readFloat(input); + data->_shearX = readFloat(input); + data->_shearY = readFloat(input); + data->_length = readFloat(input) * _scale; + data->_inherit = static_cast(readVarint(input, true)); + data->_skinRequired = readBoolean(input); + if (nonessential) { + readColor(input, data->getColor()); + data->_icon.own(readString(input)); + data->_visible = readBoolean(input); + } + skeletonData->_bones[i] = data; + } + + /* Slots. */ + int slotsCount = readVarint(input, true); + skeletonData->_slots.setSize(slotsCount, 0); + for (int i = 0; i < slotsCount; ++i) { + String slotName = String(readString(input), true); + BoneData *boneData = skeletonData->_bones[readVarint(input, true)]; + SlotData *slotData = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + readColor(input, slotData->getColor()); + unsigned char a = readByte(input); + unsigned char r = readByte(input); + unsigned char g = readByte(input); + unsigned char b = readByte(input); + if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) { + slotData->getDarkColor().set(r / 255.0f, g / 255.0f, b / 255.0f, 1); + slotData->setHasDarkColor(true); + } + slotData->_attachmentName = readStringRef(input, skeletonData); + slotData->_blendMode = static_cast(readVarint(input, true)); + if (nonessential) { + slotData->_visible = readBoolean(input); + } + skeletonData->_slots[i] = slotData; + } + + /* IK constraints. */ + int ikConstraintsCount = readVarint(input, true); + skeletonData->_ikConstraints.setSize(ikConstraintsCount, 0); + for (int i = 0; i < ikConstraintsCount; ++i) { + const char *name = readString(input); + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_bendDirection = (flags & 2) != 0 ? 1 : -1; + data->_compress = (flags & 4) != 0; + data->_stretch = (flags & 8) != 0; + data->_uniform = (flags & 16) != 0; + if ((flags & 32) != 0) data->_mix = (flags & 64) != 0 ? readFloat(input) : 1; + if ((flags & 128) != 0) data->_softness = readFloat(input) * _scale; + + skeletonData->_ikConstraints[i] = data; + } + + /* Transform constraints. */ + int transformConstraintsCount = readVarint(input, true); + skeletonData->_transformConstraints.setSize(transformConstraintsCount, 0); + for (int i = 0; i < transformConstraintsCount; ++i) { + const char *name = readString(input); + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_local = (flags & 2) != 0; + data->_relative = (flags & 4) != 0; + if ((flags & 8) != 0) data->_offsetRotation = readFloat(input); + if ((flags & 16) != 0) data->_offsetX = readFloat(input) * _scale; + if ((flags & 32) != 0) data->_offsetY = readFloat(input) * _scale; + if ((flags & 64) != 0) data->_offsetScaleX = readFloat(input); + if ((flags & 128) != 0) data->_offsetScaleY = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_offsetShearY = readFloat(input); + if ((flags & 2) != 0) data->_mixRotate = readFloat(input); + if ((flags & 4) != 0) data->_mixX = readFloat(input); + if ((flags & 8) != 0) data->_mixY = readFloat(input); + if ((flags & 16) != 0) data->_mixScaleX = readFloat(input); + if ((flags & 32) != 0) data->_mixScaleY = readFloat(input); + if ((flags & 64) != 0) data->_mixShearY = readFloat(input); + + skeletonData->_transformConstraints[i] = data; + } + + /* Path constraints */ + int pathConstraintsCount = readVarint(input, true); + skeletonData->_pathConstraints.setSize(pathConstraintsCount, 0); + for (int i = 0; i < pathConstraintsCount; ++i) { + const char *name = readString(input); + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_slots[readVarint(input, true)]; + int flags = readByte(input); + data->_positionMode = (PositionMode) (flags & 1); + data->_spacingMode = (SpacingMode) ((flags >> 1) & 3); + data->_rotateMode = (RotateMode) ((flags >> 3) & 3); + if ((flags & 128) != 0) data->_offsetRotation = readFloat(input); + data->_position = readFloat(input); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = readFloat(input); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = readFloat(input); + data->_mixX = readFloat(input); + data->_mixY = readFloat(input); + skeletonData->_pathConstraints[i] = data; + } + + // Physics constraints. + int physicsConstraintsCount = readVarint(input, true); + skeletonData->_physicsConstraints.setSize(physicsConstraintsCount, 0); + for (int i = 0; i < physicsConstraintsCount; i++) { + const char *name = readString(input); + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData(String(name, true)); + data->_order = readVarint(input, true); + data->_bone = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + if ((flags & 2) != 0) data->_x = readFloat(input); + if ((flags & 4) != 0) data->_y = readFloat(input); + if ((flags & 8) != 0) data->_rotate = readFloat(input); + if ((flags & 16) != 0) data->_scaleX = readFloat(input); + if ((flags & 32) != 0) data->_shearX = readFloat(input); + data->_limit = ((flags & 64) != 0 ? readFloat(input) : 5000) * _scale; + data->_step = 1.f / readByte(input); + data->_inertia = readFloat(input); + data->_strength = readFloat(input); + data->_damping = readFloat(input); + data->_massInverse = (flags & 128) != 0 ? readFloat(input) : 1; + data->_wind = readFloat(input); + data->_gravity = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_inertiaGlobal = true; + if ((flags & 2) != 0) data->_strengthGlobal = true; + if ((flags & 4) != 0) data->_dampingGlobal = true; + if ((flags & 8) != 0) data->_massGlobal = true; + if ((flags & 16) != 0) data->_windGlobal = true; + if ((flags & 32) != 0) data->_gravityGlobal = true; + if ((flags & 64) != 0) data->_mixGlobal = true; + data->_mix = (flags & 128) != 0 ? readFloat(input) : 1; + skeletonData->_physicsConstraints[i] = data; + } + + /* Default skin. */ + Skin *defaultSkin = readSkin(input, true, skeletonData, nonessential); + if (defaultSkin) { + skeletonData->_defaultSkin = defaultSkin; + skeletonData->_skins.add(defaultSkin); + } + + if (!this->getError().isEmpty()) { + delete input; + delete skeletonData; + return NULL; + } + + /* Skins. */ + for (size_t i = 0, n = (size_t) readVarint(input, true); i < n; ++i) { + Skin *skin = readSkin(input, false, skeletonData, nonessential); + if (skin) + skeletonData->_skins.add(skin); + else { + delete input; + delete skeletonData; + return NULL; + } + } + + /* Linked meshes. */ + for (int i = 0, n = (int) _linkedMeshes.size(); i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = skeletonData->_skins[linkedMesh->_skinIndex]; + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete input; + delete skeletonData; + setError("Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + int eventsCount = readVarint(input, true); + skeletonData->_events.setSize(eventsCount, 0); + for (int i = 0; i < eventsCount; ++i) { + const char *name = readString(input); + EventData *eventData = new (__FILE__, __LINE__) EventData(String(name, true)); + eventData->_intValue = readVarint(input, false); + eventData->_floatValue = readFloat(input); + eventData->_stringValue.own(readString(input)); + eventData->_audioPath.own(readString(input)); + if (!eventData->_audioPath.isEmpty()) { + eventData->_volume = readFloat(input); + eventData->_balance = readFloat(input); + } + skeletonData->_events[i] = eventData; + } + + /* Animations. */ + int animationsCount = readVarint(input, true); + skeletonData->_animations.setSize(animationsCount, 0); + for (int i = 0; i < animationsCount; ++i) { + String name(readString(input), true); + Animation *animation = readAnimation(name, input, skeletonData); + if (!animation) { + delete input; + delete skeletonData; + return NULL; + } + skeletonData->_animations[i] = animation; + } + + delete input; + return skeletonData; +} + +SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *binary = SpineExtension::readFile(path.buffer(), &length); + if (length == 0 || !binary) { + setError("Unable to read skeleton file: ", path.buffer()); + return NULL; + } + skeletonData = readSkeletonData((unsigned char *) binary, length); + SpineExtension::free(binary, __FILE__, __LINE__); + return skeletonData; +} + +void SkeletonBinary::setError(const char *value1, const char *value2) { + char message[256]; + int length; + strcpy(message, value1); + length = (int) strlen(value1); + if (value2) strncat(message + length, value2, 255 - length); + _error = String(message); +} + +char *SkeletonBinary::readString(DataInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) return NULL; + string = SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +char *SkeletonBinary::readStringRef(DataInput *input, SkeletonData *skeletonData) { + int index = readVarint(input, true); + return index == 0 ? NULL : skeletonData->_strings[index - 1]; +} + +float SkeletonBinary::readFloat(DataInput *input) { + union { + int intValue; + float floatValue; + } intToFloat; + intToFloat.intValue = readInt(input); + return intToFloat.floatValue; +} + +unsigned char SkeletonBinary::readByte(DataInput *input) { + return *input->cursor++; +} + +signed char SkeletonBinary::readSByte(DataInput *input) { + return (signed char) readByte(input); +} + +bool SkeletonBinary::readBoolean(DataInput *input) { + return readByte(input) != 0; +} + +int SkeletonBinary::readInt(DataInput *input) { + int result = readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + return result; +} + +void SkeletonBinary::readColor(DataInput *input, Color &color) { + color.r = readByte(input) / 255.0f; + color.g = readByte(input) / 255.0f; + color.b = readByte(input) / 255.0f; + color.a = readByte(input) / 255.0f; +} + +int SkeletonBinary::readVarint(DataInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) value |= (readByte(input) & 0x7F) << 28; + } + } + } + if (!optimizePositive) value = (((unsigned int) value >> 1) ^ -(value & 1)); + return value; +} + +Skin *SkeletonBinary::readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential) { + Skin *skin; + int slotCount = 0; + if (defaultSkin) { + slotCount = readVarint(input, true); + if (slotCount == 0) return NULL; + skin = new (__FILE__, __LINE__) Skin("default"); + } else { + skin = new (__FILE__, __LINE__) Skin(String(readString(input), true)); + + if (nonessential) readColor(input, skin->getColor()); + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int boneIndex = readVarint(input, true); + if (boneIndex >= (int) skeletonData->_bones.size()) return NULL; + skin->getBones().add(skeletonData->_bones[boneIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int ikIndex = readVarint(input, true); + if (ikIndex >= (int) skeletonData->_ikConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_ikConstraints[ikIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int transformIndex = readVarint(input, true); + if (transformIndex >= (int) skeletonData->_transformConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_transformConstraints[transformIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int pathIndex = readVarint(input, true); + if (pathIndex >= (int) skeletonData->_pathConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_pathConstraints[pathIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int physicsIndex = readVarint(input, true); + if (physicsIndex >= (int) skeletonData->_physicsConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_physicsConstraints[physicsIndex]); + } + slotCount = readVarint(input, true); + } + + for (int i = 0; i < slotCount; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + String name(readStringRef(input, skeletonData)); + Attachment *attachment = readAttachment(input, skin, slotIndex, name, skeletonData, nonessential); + if (attachment) + skin->setAttachment(slotIndex, String(name), attachment); + else { + delete skin; + return NULL; + } + } + } + return skin; +} + +Sequence *SkeletonBinary::readSequence(DataInput *input) { + Sequence *sequence = new (__FILE__, __LINE__) Sequence(readVarint(input, true)); + sequence->_start = readVarint(input, true); + sequence->_digits = readVarint(input, true); + sequence->_setupIndex = readVarint(input, true); + return sequence; +} + +Attachment *SkeletonBinary::readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential) { + + int flags = readByte(input); + String name = (flags & 8) != 0 ? readStringRef(input, skeletonData) : attachmentName; + AttachmentType type = static_cast(flags & 0x7); + switch (type) { + case AttachmentType_Region: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + float rotation = (flags & 128) != 0 ? readFloat(input) : 0; + float x = readFloat(input) * _scale; + float y = readFloat(input) * _scale; + float scaleX = readFloat(input); + float scaleY = readFloat(input); + float width = readFloat(input) * _scale; + float height = readFloat(input) * _scale; + RegionAttachment *region = _attachmentLoader->newRegionAttachment(*skin, String(name), String(path), sequence); + if (!region) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + region->_path = path; + region->_rotation = rotation; + region->_x = x; + region->_y = y; + region->_scaleX = scaleX; + region->_scaleY = scaleY; + region->_width = width; + region->_height = height; + region->getColor().set(color); + region->_sequence = sequence; + if (sequence == NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + return region; + } + case AttachmentType_Boundingbox: { + BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(*skin, String(name)); + if (!box) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + int verticesLength = readVertices(input, box->getVertices(), box->getBones(), (flags & 16) != 0); + box->setWorldVerticesLength(verticesLength); + if (nonessential) { + readColor(input, box->getColor()); + } + _attachmentLoader->configureAttachment(box); + return box; + } + case AttachmentType_Mesh: { + Vector uvs; + Vector triangles; + Vector vertices; + Vector bones; + int hullLength; + float width = 0; + float height = 0; + Vector edges; + + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + hullLength = readVarint(input, true); + int verticesLength = readVertices(input, vertices, bones, (flags & 128) != 0); + readFloatArray(input, verticesLength, 1, uvs); + readShortArray(input, triangles, (verticesLength - hullLength - 2) * 3); + + if (nonessential) { + readShortArray(input, edges, readVarint(input, true)); + width = readFloat(input); + height = readFloat(input); + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_bones.addAll(bones); + mesh->_vertices.addAll(vertices); + mesh->setWorldVerticesLength(verticesLength); + mesh->_triangles.addAll(triangles); + mesh->_regionUVs.addAll(uvs); + if (sequence == NULL) mesh->updateRegion(); + mesh->_hullLength = hullLength; + mesh->_sequence = sequence; + if (nonessential) { + mesh->_edges.addAll(edges); + mesh->_width = width; + mesh->_height = height; + } + _attachmentLoader->configureAttachment(mesh); + return mesh; + } + case AttachmentType_Linkedmesh: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + bool inheritTimelines = (flags & 128) != 0; + int skinIndex = readVarint(input, true); + String parent(readStringRef(input, skeletonData)); + float width = 0, height = 0; + if (nonessential) { + width = readFloat(input) * _scale; + height = readFloat(input) * _scale; + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_sequence = sequence; + if (nonessential) { + mesh->_width = width; + mesh->_height = height; + } + + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, skinIndex, slotIndex, + String(parent), inheritTimelines); + _linkedMeshes.add(linkedMesh); + return mesh; + } + case AttachmentType_Path: { + PathAttachment *path = _attachmentLoader->newPathAttachment(*skin, String(name)); + if (!path) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + path->_closed = (flags & 16) != 0; + path->_constantSpeed = (flags & 32) != 0; + int verticesLength = readVertices(input, path->getVertices(), path->getBones(), (flags & 64) != 0); + path->setWorldVerticesLength(verticesLength); + int lengthsLength = verticesLength / 6; + path->_lengths.setSize(lengthsLength, 0); + for (int i = 0; i < lengthsLength; ++i) { + path->_lengths[i] = readFloat(input) * _scale; + } + if (nonessential) { + readColor(input, path->getColor()); + } + _attachmentLoader->configureAttachment(path); + return path; + } + case AttachmentType_Point: { + PointAttachment *point = _attachmentLoader->newPointAttachment(*skin, String(name)); + if (!point) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + point->_rotation = readFloat(input); + point->_x = readFloat(input) * _scale; + point->_y = readFloat(input) * _scale; + + if (nonessential) { + readColor(input, point->getColor()); + } + _attachmentLoader->configureAttachment(point); + return point; + } + case AttachmentType_Clipping: { + int endSlotIndex = readVarint(input, true); + ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(*skin, name); + if (!clip) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + int verticesLength = readVertices(input, clip->getVertices(), clip->getBones(), (flags & 16) != 0); + clip->setWorldVerticesLength(verticesLength); + clip->_endSlot = skeletonData->_slots[endSlotIndex]; + if (nonessential) { + readColor(input, clip->getColor()); + } + _attachmentLoader->configureAttachment(clip); + return clip; + } + } + return NULL; +} + +int SkeletonBinary::readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted) { + float scale = _scale; + int vertexCount = readVarint(input, true); + int verticesLength = vertexCount << 1; + if (!weighted) { + readFloatArray(input, verticesLength, scale, vertices); + return verticesLength; + } + vertices.ensureCapacity(verticesLength * 3 * 3); + bones.ensureCapacity(verticesLength * 3); + for (int i = 0; i < vertexCount; ++i) { + int boneCount = readVarint(input, true); + bones.add(boneCount); + for (int ii = 0; ii < boneCount; ++ii) { + bones.add(readVarint(input, true)); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input)); + } + } + return verticesLength; +} + +void SkeletonBinary::readFloatArray(DataInput *input, int n, float scale, Vector &array) { + array.setSize(n, 0); + + int i; + if (scale == 1) { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input); + } + } else { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input) * scale; + } + } +} + +void SkeletonBinary::readShortArray(DataInput *input, Vector &array, int n) { + array.setSize(n, 0); + for (int i = 0; i < n; ++i) { + array[i] = (short) readVarint(input, true); + } +} + +void SkeletonBinary::setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + float cx1 = readFloat(input); + float cy1 = readFloat(input); + float cx2 = readFloat(input); + float cy2 = readFloat(input); + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2, value2); +} + +void SkeletonBinary::readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale) { + float time = readFloat(input); + float value = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = readFloat(input); + float value2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + } + timelines.add(timeline); +} + +void SkeletonBinary::readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale) { + float time = readFloat(input); + float value1 = readFloat(input) * scale; + float value2 = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nvalue1 = readFloat(input) * scale; + float nvalue2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale); + setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + timelines.add(timeline); +} + +Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData) { + Vector timelines; + float scale = _scale; + int numTimelines = readVarint(input, true); + SP_UNUSED(numTimelines); + // Slot timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + switch (timelineType) { + case SLOT_ATTACHMENT: { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; ++frame) { + float time = readFloat(input); + String attachmentName(readStringRef(input, skeletonData)); + timeline->setFrame(frame, time, attachmentName); + } + timelines.add(timeline); + break; + } + case SLOT_RGBA: { + int bezierCount = readVarint(input, true); + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + float a2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB: { + int bezierCount = readVarint(input, true); + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.add(timeline); + break; + } + case SLOT_RGBA2: { + int bezierCount = readVarint(input, true); + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float na = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB2: { + int bezierCount = readVarint(input, true); + RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_ALPHA: { + int bezierCount = readVarint(input, true); + AlphaTimeline *timeline = new (__FILE__, __LINE__) AlphaTimeline(frameCount, bezierCount, slotIndex); + float time = readFloat(input); + float a = readByte(input) / 255.0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = readFloat(input); + float a2 = readByte(input) / 255.0; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + } + time = time2; + a = a2; + } + timelines.add(timeline); + break; + } + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a slot: ", skeletonData->_slots[slotIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int boneIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + if (timelineType == BONE_INHERIT) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frameCount, boneIndex); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + Inherit inherit = (Inherit) readByte(input); + timeline->setFrame(frame, time, inherit); + } + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (timelineType) { + case BONE_ROTATE: + readTimeline(input, timelines, + new (__FILE__, __LINE__) RotateTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_TRANSLATE: + readTimeline2(input, timelines, new (__FILE__, __LINE__) TranslateTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEX: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEY: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_SCALE: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ScaleTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEAR: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ShearTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a bone: ", skeletonData->_bones[boneIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // IK timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(frameCount, bezierCount, index); + int flags = readByte(input); + float time = readFloat(input), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness = (flags & 4) != 0 ? readFloat(input) * scale : 0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mix, softness, (flags & 8) != 0 ? 1 : -1, (flags & 16) != 0, (flags & 32) != 0); + if (frame == frameLast) break; + flags = readByte(input); + float time2 = readFloat(input), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness2 = (flags & 4) != 0 ? readFloat(input) * scale : 0; + if ((flags & 64) != 0) + timeline->setStepped(frame); + else if ((flags & 128) != 0) { + setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + TransformConstraintTimeline *timeline = new TransformConstraintTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + float mixScaleX = readFloat(input); + float mixScaleY = readFloat(input); + float mixShearY = readFloat(input); + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + float mixScaleX2 = readFloat(input); + float mixScaleY2 = readFloat(input); + float mixShearY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + } + timelines.add(timeline); + } + + // Path constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + PathConstraintData *data = skeletonData->_pathConstraints[index]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + int bezierCount = readVarint(input, true); + switch (type) { + case PATH_POSITION: { + readTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index), + data->_positionMode == PositionMode_Fixed ? scale : 1); + break; + } + case PATH_SPACING: { + readTimeline(input, timelines, + new PathConstraintSpacingTimeline(frameCount, + bezierCount, + index), + data->_spacingMode == SpacingMode_Length || + data->_spacingMode == SpacingMode_Fixed + ? scale + : 1); + break; + } + case PATH_MIX: + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.add(timeline); + } + } + } + + // Physics timelines. + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int index = readVarint(input, true) - 1; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + if (type == PHYSICS_RESET) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frameCount, index); + for (int frame = 0; frame < frameCount; frame++) + timeline->setFrame(frame, readFloat(input)); + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (type) { + case PHYSICS_INERTIA: + readTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_STRENGTH: + readTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_DAMPING: + readTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MASS: + readTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_WIND: + readTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_GRAVITY: + readTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MIX: + readTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1); + } + } + } + + // Attachment timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + Skin *skin = skeletonData->_skins[readVarint(input, true)]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + int slotIndex = readVarint(input, true); + for (int iii = 0, nnn = readVarint(input, true); iii < nnn; iii++) { + const char *attachmentName = readStringRef(input, skeletonData); + Attachment *baseAttachment = skin->getAttachment(slotIndex, String(attachmentName)); + if (!baseAttachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Attachment not found: ", attachmentName); + return NULL; + } + unsigned int timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment *attachment = static_cast(baseAttachment); + bool weighted = attachment->_bones.size() > 0; + Vector &vertices = attachment->_vertices; + int deformLength = weighted ? (int) vertices.size() / 3 * 2 : (int) vertices.size(); + + int bezierCount = readVarint(input, true); + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frameCount, bezierCount, slotIndex, + attachment); + + float time = readFloat(input); + for (int frame = 0, bezier = 0;; ++frame) { + Vector deform; + size_t end = (size_t) readVarint(input, true); + if (end == 0) { + if (weighted) { + deform.setSize(deformLength, 0); + for (int iiii = 0; iiii < deformLength; ++iiii) + deform[iiii] = 0; + } else { + deform.clearAndAddAll(vertices); + } + } else { + deform.setSize(deformLength, 0); + size_t start = (size_t) readVarint(input, true); + end += start; + if (scale == 1) { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input); + } else { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input) * scale; + } + + if (!weighted) { + for (size_t v = 0, vn = deform.size(); v < vn; ++v) + deform[v] += vertices[v]; + } + } + + timeline->setFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + } + + timelines.add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: { + SequenceTimeline *timeline = new (__FILE__, __LINE__) SequenceTimeline(frameCount, slotIndex, baseAttachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + int modeAndIndex = readInt(input); + float delay = readFloat(input); + timeline->setFrame(frame, time, (spine::SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, delay); + } + timelines.add(timeline); + break; + } + } + } + } + } + + // Draw order timeline. + size_t drawOrderCount = (size_t) readVarint(input, true); + if (drawOrderCount > 0) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrderCount); + + size_t slotCount = skeletonData->_slots.size(); + for (size_t i = 0; i < drawOrderCount; ++i) { + float time = readFloat(input); + size_t offsetCount = (size_t) readVarint(input, true); + + Vector drawOrder; + drawOrder.setSize(slotCount, 0); + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + drawOrder[ii] = -1; + + Vector unchanged; + unchanged.setSize(slotCount - offsetCount, 0); + size_t originalIndex = 0, unchangedIndex = 0; + for (size_t ii = 0; ii < offsetCount; ++ii) { + size_t slotIndex = (size_t) readVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + // Set changed items. + size_t index = originalIndex; + drawOrder[index + (size_t) readVarint(input, true)] = (int) originalIndex++; + } + + // Collect remaining unchanged items. + while (originalIndex < slotCount) { + unchanged[unchangedIndex++] = (int) originalIndex++; + } + + // Fill in unchanged items. + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline->setFrame(i, time, drawOrder); + } + timelines.add(timeline); + } + + // Event timeline. + int eventCount = readVarint(input, true); + if (eventCount > 0) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(eventCount); + + for (int i = 0; i < eventCount; ++i) { + float time = readFloat(input); + EventData *eventData = skeletonData->_events[readVarint(input, true)]; + Event *event = new (__FILE__, __LINE__) Event(time, *eventData); + + event->_intValue = readVarint(input, false); + event->_floatValue = readFloat(input); + const char *event_stringValue = readString(input); + if (event_stringValue == nullptr) { + event->_stringValue = eventData->_stringValue; + } else { + event->_stringValue = String(event_stringValue); + SpineExtension::free(event_stringValue, __FILE__, __LINE__); + } + + if (!eventData->_audioPath.isEmpty()) { + event->_volume = readFloat(input); + event->_balance = readFloat(input); + } + timeline->setFrame(i, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (int i = 0, n = (int) timelines.size(); i < n; i++) { + duration = MathUtil::max(duration, (timelines[i])->getDuration()); + } + return new (__FILE__, __LINE__) Animation(String(name), timelines, duration); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonBounds.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonBounds.cpp new file mode 100644 index 000000000000..5681af487aff --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonBounds.cpp @@ -0,0 +1,231 @@ +/****************************************************************************** + * 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 + +#include +#include +#include + +#include + +#include + +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) { + Vector &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->getAttachment(); + if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue; + BoundingBoxAttachment *boundingBox = static_cast(attachment); + _boundingBoxes.add(boundingBox); + + spine::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(*slot, polygon._vertices); + } + + 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(spine::Polygon *polygon, float x, float y) { + Vector &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(spine::Polygon *polygon, float x1, float y1, float x2, float y2) { + Vector &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(Polygon *polygon) { + int index = _polygons.indexOf(polygon); + return index == -1 ? NULL : _boundingBoxes[index]; +} + +Vector &SkeletonBounds::getPolygons() { + return _polygons; +} + +Vector &SkeletonBounds::getBoundingBoxes() { + return _boundingBoxes; +} + +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) { + spine::Polygon *polygon = _polygons[i]; + Vector &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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonClipping.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonClipping.cpp new file mode 100644 index 000000000000..fbe09ed01997 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonClipping.cpp @@ -0,0 +1,397 @@ +/****************************************************************************** + * 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 + +#include +#include + +using namespace spine; + +SkeletonClipping::SkeletonClipping() : _clipAttachment(NULL) { + _clipOutput.ensureCapacity(128); + _clippedVertices.ensureCapacity(128); + _clippedTriangles.ensureCapacity(128); + _clippedUVs.ensureCapacity(128); +} + +size_t SkeletonClipping::clipStart(Slot &slot, ClippingAttachment *clip) { + if (_clipAttachment != NULL) { + return 0; + } + + _clipAttachment = clip; + + int n = (int) clip->getWorldVerticesLength(); + _clippingPolygon.setSize(n, 0); + clip->computeWorldVertices(slot, 0, n, _clippingPolygon, 0, 2); + makeClockwise(_clippingPolygon); + _clippingPolygons = &_triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon)); + + for (size_t i = 0; i < _clippingPolygons->size(); ++i) { + Vector *polygonP = (*_clippingPolygons)[i]; + Vector &polygon = *polygonP; + makeClockwise(polygon); + polygon.add(polygon[0]); + polygon.add(polygon[1]); + } + + 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 = NULL; + _clippedVertices.clear(); + _clippedUVs.clear(); + _clippedTriangles.clear(); + _clippingPolygon.clear(); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + int stride = 2; + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +void SkeletonClipping::clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, + size_t stride) { + clipTriangles(vertices.buffer(), triangles.buffer(), triangles.size(), uvs.buffer(), stride); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength, float *uvs, size_t stride) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * (int) stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * (int) stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * (int) stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; + float d = 1 / (d0 * d2 + d1 * (y1 - y3)); + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + _clippedUVs.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + float c0 = x - x3, c1 = y - y3; + float a = (d0 * c0 + d1 * c1) * d; + float b = (d4 * c0 + d2 * c1) * d; + float c = 1 - a - b; + _clippedUVs[s] = u1 * a + u2 * b + u3 * c; + _clippedUVs[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + _clippedUVs.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + _clippedUVs[s] = u1; + _clippedUVs[s + 1] = v1; + _clippedUVs[s + 2] = u2; + _clippedUVs[s + 3] = v2; + _clippedUVs[s + 4] = u3; + _clippedUVs[s + 5] = v3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +bool SkeletonClipping::isClipping() { + return _clipAttachment != NULL; +} + +Vector &SkeletonClipping::getClippedVertices() { + return _clippedVertices; +} + +Vector &SkeletonClipping::getClippedTriangles() { + return _clippedTriangles; +} + +Vector &SkeletonClipping::getClippedUVs() { + return _clippedUVs; +} + +bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output) { + Vector *originalOutput = output; + bool clipped = false; + + // Avoid copy at the end. + Vector *input; + if (clippingArea->size() % 4 >= 2) { + input = output; + output = &_scratch; + } else + input = &_scratch; + + input->clear(); + input->add(x1); + input->add(y1); + input->add(x2); + input->add(y2); + input->add(x3); + input->add(y3); + input->add(x1); + input->add(y1); + output->clear(); + + size_t clippingVerticesLast = clippingArea->size() - 4; + Vector &clippingVertices = *clippingArea; + for (size_t i = 0;; i += 2) { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3]; + + size_t outputStart = output->size(); + Vector &inputVertices = *input; + for (size_t ii = 0, nn = input->size() - 2; ii < nn;) { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + ii += 2; + float inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1]; + float s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2); + float s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY); + if (s1 > 0) { + if (s2) {// v1 inside, v2 inside + output->add(inputX2); + output->add(inputY2); + continue; + } + // v1 inside, v2 outside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + } else { + output->add(inputX2); + output->add(inputY2); + } + } else if (s2) {// v1 outside, v2 inside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + output->add(inputX2); + output->add(inputY2); + } else { + output->add(inputX2); + output->add(inputY2); + continue; + } + } + clipped = true; + } + + + if (outputStart == output->size()) { + // All edges outside. + originalOutput->clear(); + return true; + } + + output->add((*output)[0]); + output->add((*output)[1]); + + if (i == clippingVerticesLast) { + break; + } + Vector *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); + + if (originalOutput->size() < 6) { + originalOutput->clear(); + return false; + } + return clipped; +} + +void SkeletonClipping::makeClockwise(Vector &polygon) { + size_t verticeslength = polygon.size(); + + float area = polygon[verticeslength - 2] * polygon[1] - polygon[0] * polygon[verticeslength - 1]; + float p1x, p1y, p2x, p2y; + + for (size_t i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = polygon[i]; + p1y = polygon[i + 1]; + p2x = polygon[i + 2]; + p2y = polygon[i + 3]; + area += p1x * p2y - p2x * p1y; + } + + if (area < 0) return; + + for (size_t i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + float x = polygon[i], y = polygon[i + 1]; + int other = (int) (lastX - i); + polygon[i] = polygon[other]; + polygon[i + 1] = polygon[other + 1]; + polygon[other] = x; + polygon[other + 1] = y; + } +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonData.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonData.cpp new file mode 100644 index 000000000000..e3b35f0cee58 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonData.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +SkeletonData::SkeletonData() : _name(), + _defaultSkin(NULL), + _x(0), + _y(0), + _width(0), + _height(0), + _referenceScale(100), + _version(), + _hash(), + _fps(0), + _imagesPath() { +} + +SkeletonData::~SkeletonData() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_skins); + + _defaultSkin = NULL; + + ContainerUtil::cleanUpVectorOfPointers(_events); + ContainerUtil::cleanUpVectorOfPointers(_animations); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); + for (size_t i = 0; i < _strings.size(); i++) { + SpineExtension::free(_strings[i], __FILE__, __LINE__); + } +} + +BoneData *SkeletonData::findBone(const String &boneName) { + return ContainerUtil::findWithName(_bones, boneName); +} + +SlotData *SkeletonData::findSlot(const String &slotName) { + return ContainerUtil::findWithName(_slots, slotName); +} + +Skin *SkeletonData::findSkin(const String &skinName) { + return ContainerUtil::findWithName(_skins, skinName); +} + +spine::EventData *SkeletonData::findEvent(const String &eventDataName) { + return ContainerUtil::findWithName(_events, eventDataName); +} + +Animation *SkeletonData::findAnimation(const String &animationName) { + return ContainerUtil::findWithName(_animations, animationName); +} + +IkConstraintData *SkeletonData::findIkConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_ikConstraints, constraintName); +} + +TransformConstraintData *SkeletonData::findTransformConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_transformConstraints, constraintName); +} + +PathConstraintData *SkeletonData::findPathConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_pathConstraints, constraintName); +} + +PhysicsConstraintData *SkeletonData::findPhysicsConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_physicsConstraints, constraintName); +} + +const String &SkeletonData::getName() { + return _name; +} + +void SkeletonData::setName(const String &inValue) { + _name = inValue; +} + +Vector &SkeletonData::getBones() { + return _bones; +} + +Vector &SkeletonData::getSlots() { + return _slots; +} + +Vector &SkeletonData::getSkins() { + return _skins; +} + +Skin *SkeletonData::getDefaultSkin() { + return _defaultSkin; +} + +void SkeletonData::setDefaultSkin(Skin *inValue) { + _defaultSkin = inValue; +} + +Vector &SkeletonData::getEvents() { + return _events; +} + +Vector &SkeletonData::getAnimations() { + return _animations; +} + +Vector &SkeletonData::getIkConstraints() { + return _ikConstraints; +} + +Vector &SkeletonData::getTransformConstraints() { + return _transformConstraints; +} + +Vector &SkeletonData::getPathConstraints() { + return _pathConstraints; +} + +Vector &SkeletonData::getPhysicsConstraints() { + return _physicsConstraints; +} + +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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonJson.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonJson.cpp new file mode 100644 index 000000000000..7dc5d34e5cdc --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonJson.cpp @@ -0,0 +1,1617 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +static float toColor(const char *value, size_t index) { + char digits[3]; + char *error; + int color; + + if (index >= strlen(value) / 2) return -1; + + value += index * 2; + + digits[0] = *value; + digits[1] = *(value + 1); + digits[2] = '\0'; + color = (int) strtoul(digits, &error, 16); + if (*error != 0) return -1; + + return color / (float) 255; +} + +static void toColor(Color &color, const char *value, bool hasAlpha) { + color.r = toColor(value, 0); + color.g = toColor(value, 1); + color.b = toColor(value, 2); + if (hasAlpha) color.a = toColor(value, 3); +} + +SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new (__FILE__, __LINE__) AtlasAttachmentLoader(atlas)), + _scale(1), _ownsLoader(true) {} + +SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonJson::~SkeletonJson() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonJson::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *json = SpineExtension::readFile(path, &length); + if (length == 0 || !json) { + setError(NULL, "Unable to read skeleton file: ", path); + return NULL; + } + + skeletonData = readSkeletonData(json); + + SpineExtension::free(json, __FILE__, __LINE__); + + return skeletonData; +} + +SkeletonData *SkeletonJson::readSkeletonData(const char *json) { + int i, ii; + SkeletonData *skeletonData; + Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *path, *physics, *slots, *skins, *animations, *events; + + _error = ""; + _linkedMeshes.clear(); + + root = new (__FILE__, __LINE__) Json(json); + + if (!root) { + setError(NULL, "Invalid skeleton JSON: ", Json::getError()); + return NULL; + } + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + skeleton = Json::getItem(root, "skeleton"); + if (skeleton) { + skeletonData->_hash = Json::getString(skeleton, "hash", 0); + skeletonData->_version = Json::getString(skeleton, "spine", 0); + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + delete skeletonData; + setError(NULL, errorMsg, ""); + return NULL; + } + skeletonData->_x = Json::getFloat(skeleton, "x", 0); + skeletonData->_y = Json::getFloat(skeleton, "y", 0); + skeletonData->_width = Json::getFloat(skeleton, "width", 0); + skeletonData->_height = Json::getFloat(skeleton, "height", 0); + skeletonData->_referenceScale = Json::getFloat(skeleton, "referenceScale", 100) * _scale; + skeletonData->_fps = Json::getFloat(skeleton, "fps", 30); + skeletonData->_audioPath = Json::getString(skeleton, "audio", 0); + skeletonData->_imagesPath = Json::getString(skeleton, "images", 0); + } + + /* Bones. */ + bones = Json::getItem(root, "bones"); + skeletonData->_bones.setSize(bones->_size, 0); + int bonesCount = 0; + for (boneMap = bones->_child, i = 0; boneMap; boneMap = boneMap->_next, ++i) { + BoneData *data; + const char *inherit; + + BoneData *parent = 0; + const char *parentName = Json::getString(boneMap, "parent", 0); + if (parentName) { + parent = skeletonData->findBone(parentName); + if (!parent) { + delete skeletonData; + setError(root, "Parent bone not found: ", parentName); + return NULL; + } + } + + data = new (__FILE__, __LINE__) BoneData(bonesCount, Json::getString(boneMap, "name", 0), parent); + + data->_length = Json::getFloat(boneMap, "length", 0) * _scale; + data->_x = Json::getFloat(boneMap, "x", 0) * _scale; + data->_y = Json::getFloat(boneMap, "y", 0) * _scale; + data->_rotation = Json::getFloat(boneMap, "rotation", 0); + data->_scaleX = Json::getFloat(boneMap, "scaleX", 1); + data->_scaleY = Json::getFloat(boneMap, "scaleY", 1); + data->_shearX = Json::getFloat(boneMap, "shearX", 0); + data->_shearY = Json::getFloat(boneMap, "shearY", 0); + inherit = Json::getString(boneMap, "inherit", "normal"); + data->_inherit = Inherit_Normal; + if (strcmp(inherit, "normal") == 0) data->_inherit = Inherit_Normal; + else if (strcmp(inherit, "onlyTranslation") == 0) + data->_inherit = Inherit_OnlyTranslation; + else if (strcmp(inherit, "noRotationOrReflection") == 0) + data->_inherit = Inherit_NoRotationOrReflection; + else if (strcmp(inherit, "noScale") == 0) + data->_inherit = Inherit_NoScale; + else if (strcmp(inherit, "noScaleOrReflection") == 0) + data->_inherit = Inherit_NoScaleOrReflection; + data->_skinRequired = Json::getBoolean(boneMap, "skin", false); + + const char *color = Json::getString(boneMap, "color", NULL); + if (color) toColor(data->getColor(), color, true); + + data->_icon = Json::getString(boneMap, "icon", ""); + data->_visible = Json::getBoolean(boneMap, "visible", true); + + skeletonData->_bones[i] = data; + bonesCount++; + } + + /* Slots. */ + slots = Json::getItem(root, "slots"); + if (slots) { + Json *slotMap; + skeletonData->_slots.ensureCapacity(slots->_size); + skeletonData->_slots.setSize(slots->_size, 0); + for (slotMap = slots->_child, i = 0; slotMap; slotMap = slotMap->_next, ++i) { + SlotData *data; + const char *color; + const char *dark; + Json *item; + + const char *boneName = Json::getString(slotMap, "bone", 0); + BoneData *boneData = skeletonData->findBone(boneName); + if (!boneData) { + delete skeletonData; + setError(root, "Slot bone not found: ", boneName); + return NULL; + } + + String slotName = String(Json::getString(slotMap, "name", 0)); + data = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + color = Json::getString(slotMap, "color", 0); + if (color) { + Color &c = data->getColor(); + c.r = toColor(color, 0); + c.g = toColor(color, 1); + c.b = toColor(color, 2); + c.a = toColor(color, 3); + } + + dark = Json::getString(slotMap, "dark", 0); + if (dark) { + Color &darkColor = data->getDarkColor(); + darkColor.r = toColor(dark, 0); + darkColor.g = toColor(dark, 1); + darkColor.b = toColor(dark, 2); + darkColor.a = 1; + data->setHasDarkColor(true); + } + + item = Json::getItem(slotMap, "attachment"); + if (item) data->setAttachmentName(item->_valueString); + + item = Json::getItem(slotMap, "blend"); + if (item) { + if (strcmp(item->_valueString, "additive") == 0) data->_blendMode = BlendMode_Additive; + else if (strcmp(item->_valueString, "multiply") == 0) + data->_blendMode = BlendMode_Multiply; + else if (strcmp(item->_valueString, "screen") == 0) + data->_blendMode = BlendMode_Screen; + } + data->_visible = Json::getBoolean(slotMap, "visible", true); + skeletonData->_slots[i] = data; + } + } + + /* IK constraints. */ + ik = Json::getItem(root, "ik"); + if (ik) { + Json *constraintMap; + skeletonData->_ikConstraints.ensureCapacity(ik->_size); + skeletonData->_ikConstraints.setSize(ik->_size, 0); + for (constraintMap = ik->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *targetName; + + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "IK bone not found: ", boneMap->_valueString); + return NULL; + } + } + + targetName = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(targetName); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", targetName); + return NULL; + } + + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_softness = Json::getFloat(constraintMap, "softness", 0) * _scale; + data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1; + data->_compress = Json::getInt(constraintMap, "compress", 0) ? true : false; + data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true : false; + data->_uniform = Json::getInt(constraintMap, "uniform", 0) ? true : false; + + skeletonData->_ikConstraints[i] = data; + } + } + + /* Transform constraints. */ + transform = Json::getItem(root, "transform"); + if (transform) { + Json *constraintMap; + skeletonData->_transformConstraints.ensureCapacity(transform->_size); + skeletonData->_transformConstraints.setSize(transform->_size, 0); + for (constraintMap = transform->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Transform bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", name); + return NULL; + } + + data->_local = Json::getInt(constraintMap, "local", 0) ? true : false; + data->_relative = Json::getInt(constraintMap, "relative", 0) ? true : false; + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_offsetX = Json::getFloat(constraintMap, "x", 0) * _scale; + data->_offsetY = Json::getFloat(constraintMap, "y", 0) * _scale; + data->_offsetScaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_offsetScaleY = Json::getFloat(constraintMap, "scaleY", 0); + data->_offsetShearY = Json::getFloat(constraintMap, "shearY", 0); + + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + data->_mixScaleX = Json::getFloat(constraintMap, "mixScaleX", 1); + data->_mixScaleY = Json::getFloat(constraintMap, "mixScaleY", data->_mixScaleX); + data->_mixShearY = Json::getFloat(constraintMap, "mixShearY", 1); + + skeletonData->_transformConstraints[i] = data; + } + } + + /* Path constraints */ + path = Json::getItem(root, "path"); + if (path) { + Json *constraintMap; + skeletonData->_pathConstraints.ensureCapacity(path->_size); + skeletonData->_pathConstraints.setSize(path->_size, 0); + for (constraintMap = path->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + const char *item; + + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Path bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findSlot(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target slot not found: ", name); + return NULL; + } + + item = Json::getString(constraintMap, "positionMode", "percent"); + if (strcmp(item, "fixed") == 0) { + data->_positionMode = PositionMode_Fixed; + } else if (strcmp(item, "percent") == 0) { + data->_positionMode = PositionMode_Percent; + } + + item = Json::getString(constraintMap, "spacingMode", "length"); + if (strcmp(item, "length") == 0) data->_spacingMode = SpacingMode_Length; + else if (strcmp(item, "fixed") == 0) + data->_spacingMode = SpacingMode_Fixed; + else if (strcmp(item, "percent") == 0) + data->_spacingMode = SpacingMode_Percent; + else + data->_spacingMode = SpacingMode_Proportional; + + item = Json::getString(constraintMap, "rotateMode", "tangent"); + if (strcmp(item, "tangent") == 0) data->_rotateMode = RotateMode_Tangent; + else if (strcmp(item, "chain") == 0) + data->_rotateMode = RotateMode_Chain; + else if (strcmp(item, "chainScale") == 0) + data->_rotateMode = RotateMode_ChainScale; + + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_position = Json::getFloat(constraintMap, "position", 0); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = Json::getFloat(constraintMap, "spacing", 0); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + + skeletonData->_pathConstraints[i] = data; + } + } + + /* Physics constraints */ + physics = Json::getItem(root, "physics"); + if (physics) { + Json *constraintMap; + skeletonData->_physicsConstraints.ensureCapacity(physics->_size); + skeletonData->_physicsConstraints.setSize(physics->_size, 0); + for (constraintMap = physics->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + name = Json::getString(constraintMap, "bone", 0); + data->_bone = skeletonData->findBone(name); + if (!data->_bone) { + delete skeletonData; + setError(root, "Physics bone not found: ", name); + return NULL; + } + + data->_x = Json::getFloat(constraintMap, "x", 0); + data->_y = Json::getFloat(constraintMap, "y", 0); + data->_rotate = Json::getFloat(constraintMap, "rotate", 0); + data->_scaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_shearX = Json::getFloat(constraintMap, "shearX", 0); + data->_limit = Json::getFloat(constraintMap, "limit", 5000) * _scale; + data->_step = 1.0f / Json::getInt(constraintMap, "fps", 60); + data->_inertia = Json::getFloat(constraintMap, "inertia", 1); + data->_strength = Json::getFloat(constraintMap, "strength", 100); + data->_damping = Json::getFloat(constraintMap, "damping", 1); + data->_massInverse = 1.0f / Json::getFloat(constraintMap, "mass", 1); + data->_wind = Json::getFloat(constraintMap, "wind", 0); + data->_gravity = Json::getFloat(constraintMap, "gravity", 0); + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_inertiaGlobal = Json::getBoolean(constraintMap, "inertiaGlobal", false); + data->_strengthGlobal = Json::getBoolean(constraintMap, "strengthGlobal", false); + data->_dampingGlobal = Json::getBoolean(constraintMap, "dampingGlobal", false); + data->_massGlobal = Json::getBoolean(constraintMap, "massGlobal", false); + data->_windGlobal = Json::getBoolean(constraintMap, "windGlobal", false); + data->_gravityGlobal = Json::getBoolean(constraintMap, "gravityGlobal", false); + data->_mixGlobal = Json::getBoolean(constraintMap, "mixGlobal", false); + + skeletonData->_physicsConstraints[i] = data; + } + } + + /* Skins. */ + skins = Json::getItem(root, "skins"); + if (skins) { + Json *skinMap; + skeletonData->_skins.ensureCapacity(skins->_size); + skeletonData->_skins.setSize(skins->_size, 0); + int skinsIndex = 0; + for (skinMap = skins->_child, i = 0; skinMap; skinMap = skinMap->_next, ++i) { + Json *attachmentsMap; + Json *curves; + + Skin *skin = new (__FILE__, __LINE__) Skin(Json::getString(skinMap, "name", "")); + + Json *item = Json::getItem(skinMap, "bones"); + if (item) { + for (item = item->_child; item; item = item->_next) { + BoneData *data = skeletonData->findBone(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin bone not found: "), item->_valueString); + return NULL; + } + skin->getBones().add(data); + } + } + + item = Json::getItem(skinMap, "ik"); + if (item) { + for (item = item->_child; item; item = item->_next) { + IkConstraintData *data = skeletonData->findIkConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin IK constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "transform"); + if (item) { + for (item = item->_child; item; item = item->_next) { + TransformConstraintData *data = skeletonData->findTransformConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin transform constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "path"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PathConstraintData *data = skeletonData->findPathConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin path constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "physics"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PhysicsConstraintData *data = skeletonData->findPhysicsConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin physics constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + skeletonData->_skins[skinsIndex++] = skin; + if (strcmp(Json::getString(skinMap, "name", ""), "default") == 0) { + skeletonData->_defaultSkin = skin; + } + + Json *attachments = Json::getItem(skinMap, "attachments"); + if (attachments) + for (attachmentsMap = attachments->_child; + attachmentsMap; attachmentsMap = attachmentsMap->_next) { + SlotData *slot = skeletonData->findSlot(attachmentsMap->_name); + Json *attachmentMap; + + for (attachmentMap = attachmentsMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = NULL; + const char *skinAttachmentName = attachmentMap->_name; + const char *attachmentName = Json::getString(attachmentMap, "name", skinAttachmentName); + const char *attachmentPath = Json::getString(attachmentMap, "path", attachmentName); + const char *color; + Json *entry; + + const char *typeString = Json::getString(attachmentMap, "type", "region"); + AttachmentType type; + if (strcmp(typeString, "region") == 0) type = AttachmentType_Region; + else if (strcmp(typeString, "mesh") == 0) + type = AttachmentType_Mesh; + else if (strcmp(typeString, "linkedmesh") == 0) + type = AttachmentType_Linkedmesh; + else if (strcmp(typeString, "boundingbox") == 0) + type = AttachmentType_Boundingbox; + else if (strcmp(typeString, "path") == 0) + type = AttachmentType_Path; + else if (strcmp(typeString, "clipping") == 0) + type = AttachmentType_Clipping; + else if (strcmp(typeString, "point") == 0) + type = AttachmentType_Point; + else { + delete skeletonData; + setError(root, "Unknown attachment type: ", typeString); + return NULL; + } + + switch (type) { + case AttachmentType_Region: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newRegionAttachment(*skin, attachmentName, attachmentPath, sequence); + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + RegionAttachment *region = static_cast(attachment); + region->_path = attachmentPath; + + region->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + region->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + region->_scaleX = Json::getFloat(attachmentMap, "scaleX", 1); + region->_scaleY = Json::getFloat(attachmentMap, "scaleY", 1); + region->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + region->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + region->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + region->_sequence = sequence; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(region->getColor(), color, true); + + if (region->_region != NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + break; + } + case AttachmentType_Mesh: + case AttachmentType_Linkedmesh: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newMeshAttachment(*skin, attachmentName, attachmentPath, sequence); + + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + MeshAttachment *mesh = static_cast(attachment); + mesh->_path = attachmentPath; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(mesh->getColor(), color, true); + + mesh->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + mesh->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + mesh->_sequence = sequence; + + entry = Json::getItem(attachmentMap, "parent"); + if (!entry) { + int verticesLength; + entry = Json::getItem(attachmentMap, "triangles"); + mesh->_triangles.ensureCapacity(entry->_size); + mesh->_triangles.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_triangles[ii] = (unsigned short) entry->_valueInt; + + entry = Json::getItem(attachmentMap, "uvs"); + verticesLength = entry->_size; + mesh->_regionUVs.ensureCapacity(verticesLength); + mesh->_regionUVs.setSize(verticesLength, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_regionUVs[ii] = entry->_valueFloat; + + readVertices(attachmentMap, mesh, verticesLength); + + if (mesh->_region != NULL) mesh->updateRegion(); + + mesh->_hullLength = Json::getInt(attachmentMap, "hull", 0); + + entry = Json::getItem(attachmentMap, "edges"); + if (entry) { + mesh->_edges.ensureCapacity(entry->_size); + mesh->_edges.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_edges[ii] = entry->_valueInt; + } + _attachmentLoader->configureAttachment(mesh); + } else { + bool inheritTimelines = Json::getInt(attachmentMap, "timelines", 1) ? true : false; + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, + String(Json::getString( + attachmentMap, + "skin", 0)), + slot->getIndex(), + String(entry->_valueString), + inheritTimelines); + _linkedMeshes.add(linkedMesh); + } + break; + } + case AttachmentType_Boundingbox: { + attachment = _attachmentLoader->newBoundingBoxAttachment(*skin, attachmentName); + + BoundingBoxAttachment *box = static_cast(attachment); + + int vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, box, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(box->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Path: { + attachment = _attachmentLoader->newPathAttachment(*skin, attachmentName); + + PathAttachment *pathAttatchment = static_cast(attachment); + + int vertexCount = 0; + pathAttatchment->_closed = Json::getInt(attachmentMap, "closed", 0) ? true : false; + pathAttatchment->_constantSpeed = Json::getInt(attachmentMap, "constantSpeed", 1) ? true + : false; + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0); + readVertices(attachmentMap, pathAttatchment, vertexCount << 1); + + pathAttatchment->_lengths.ensureCapacity(vertexCount / 3); + pathAttatchment->_lengths.setSize(vertexCount / 3, 0); + + curves = Json::getItem(attachmentMap, "lengths"); + for (curves = curves->_child, ii = 0; curves; curves = curves->_next, ++ii) + pathAttatchment->_lengths[ii] = curves->_valueFloat * _scale; + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(pathAttatchment->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Point: { + attachment = _attachmentLoader->newPointAttachment(*skin, attachmentName); + + PointAttachment *point = static_cast(attachment); + + point->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + point->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + point->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(point->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Clipping: { + attachment = _attachmentLoader->newClippingAttachment(*skin, attachmentName); + + ClippingAttachment *clip = static_cast(attachment); + + int vertexCount = 0; + const char *end = Json::getString(attachmentMap, "end", 0); + if (end) clip->_endSlot = skeletonData->findSlot(end); + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, clip, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(clip->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + } + + skin->setAttachment(slot->getIndex(), skinAttachmentName, attachment); + } + } + } + } + + /* Linked meshes. */ + int n = (int) _linkedMeshes.size(); + for (i = 0; i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(linkedMesh->_skin); + if (skin == NULL) { + delete skeletonData; + setError(root, "Skin not found: ", linkedMesh->_skin.buffer()); + return NULL; + } + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete skeletonData; + setError(root, "Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region != NULL) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + events = Json::getItem(root, "events"); + if (events) { + Json *eventMap; + skeletonData->_events.ensureCapacity(events->_size); + skeletonData->_events.setSize(events->_size, 0); + for (eventMap = events->_child, i = 0; eventMap; eventMap = eventMap->_next, ++i) { + EventData *eventData = new (__FILE__, __LINE__) EventData(String(eventMap->_name)); + + eventData->_intValue = Json::getInt(eventMap, "int", 0); + eventData->_floatValue = Json::getFloat(eventMap, "float", 0); + const char *stringValue = Json::getString(eventMap, "string", 0); + eventData->_stringValue = stringValue; + const char *audioPath = Json::getString(eventMap, "audio", 0); + eventData->_audioPath = audioPath; + if (audioPath) { + eventData->_volume = Json::getFloat(eventMap, "volume", 1); + eventData->_balance = Json::getFloat(eventMap, "balance", 0); + } + skeletonData->_events[i] = eventData; + } + } + + /* Animations. */ + animations = Json::getItem(root, "animations"); + if (animations) { + Json *animationMap; + skeletonData->_animations.ensureCapacity(animations->_size); + skeletonData->_animations.setSize(animations->_size, 0); + int animationsIndex = 0; + for (animationMap = animations->_child; animationMap; animationMap = animationMap->_next) { + Animation *animation = readAnimation(animationMap, skeletonData); + if (!animation) { + delete skeletonData; + delete root; + return NULL; + } + skeletonData->_animations[animationsIndex++] = animation; + } + } + + delete root; + + return skeletonData; +} + +Sequence *SkeletonJson::readSequence(Json *item) { + if (item == NULL) return NULL; + Sequence *sequence = new Sequence(Json::getInt(item, "count", 0)); + sequence->_start = Json::getInt(item, "start", 1); + sequence->_digits = Json::getInt(item, "digits", 0); + sequence->_setupIndex = Json::getInt(item, "setupIndex", 0); + return sequence; +} + +void SkeletonJson::setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2) { + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); +} + +int SkeletonJson::readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + if (curve->_type == Json::JSON_STRING && strcmp(curve->_valueString, "stepped") == 0) { + timeline->setStepped(frame); + return bezier; + } + curve = Json::getItem(curve, value << 2); + float cx1 = curve->_valueFloat; + curve = curve->_next; + float cy1 = curve->_valueFloat * scale; + curve = curve->_next; + float cx2 = curve->_valueFloat; + curve = curve->_next; + float cy2 = curve->_valueFloat * scale; + setBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value = Json::getFloat(keyMap, "value", defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float value2 = Json::getFloat(nextMap, "value", defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + time = time2; + value = value2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, + float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value1 = Json::getFloat(keyMap, name1, defaultValue) * scale; + float value2 = Json::getFloat(keyMap, name2, defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value1, value2); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float nvalue1 = Json::getFloat(nextMap, name1, defaultValue) * scale; + float nvalue2 = Json::getFloat(nextMap, name2, defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +int SkeletonJson::findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines) { + int slotIndex = ContainerUtil::findIndexWithName(skeletonData->getSlots(), slotName); + if (slotIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Slot not found: ", slotName); + } + return slotIndex; +} + +Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) { + Vector timelines; + Json *bones = Json::getItem(root, "bones"); + Json *slots = Json::getItem(root, "slots"); + Json *ik = Json::getItem(root, "ik"); + Json *transform = Json::getItem(root, "transform"); + Json *paths = Json::getItem(root, "path"); + Json *physics = Json::getItem(root, "physics"); + Json *attachments = Json::getItem(root, "attachments"); + Json *drawOrder = Json::getItem(root, "drawOrder"); + Json *events = Json::getItem(root, "events"); + Json *boneMap, *slotMap, *keyMap, *nextMap, *curve; + int frame, bezier; + Color color, color2, newColor, newColor2; + + /** Slot timelines. */ + for (slotMap = slots ? slots->_child : 0; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *timelineMap = slotMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (strcmp(timelineMap->_name, "attachment") == 0) { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frames, slotIndex); + for (keyMap = timelineMap->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), + Json::getItem(keyMap, "name") ? Json::getItem(keyMap, "name")->_valueString : NULL); + } + timelines.add(timeline); + + } else if (strcmp(timelineMap->_name, "rgba") == 0) { + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frames, frames << 2, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), true); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), true); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb") == 0) { + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frames, frames * 3, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "alpha") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new (__FILE__, __LINE__) AlphaTimeline(frames, frames, slotIndex), + 0, 1)); + } else if (strcmp(timelineMap->_name, "rgba2") == 0) { + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frames, frames * 7, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), true); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), true); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb2") == 0) { + RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frames, frames * 6, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), false); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), false); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a slot: ", timelineMap->_name); + return NULL; + } + } + } + + /** Bone timelines. */ + for (boneMap = bones ? bones->_child : 0; boneMap; boneMap = boneMap->_next) { + int boneIndex = ContainerUtil::findIndexWithName(skeletonData->_bones, boneMap->_name); + if (boneIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Bone not found: ", boneMap->_name); + return NULL; + } + + for (Json *timelineMap = boneMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (frames == 0) continue; + + if (strcmp(timelineMap->_name, "rotate") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new RotateTimeline(frames, frames, boneIndex), 0, + 1)); + } else if (strcmp(timelineMap->_name, "translate") == 0) { + TranslateTimeline *timeline = new TranslateTimeline(frames, frames << 1, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatex") == 0) { + TranslateXTimeline *timeline = new TranslateXTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatey") == 0) { + TranslateYTimeline *timeline = new TranslateYTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "scale") == 0) { + ScaleTimeline *timeline = new (__FILE__, __LINE__) ScaleTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 1, 1)); + } else if (strcmp(timelineMap->_name, "scalex") == 0) { + ScaleXTimeline *timeline = new (__FILE__, __LINE__) ScaleXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "scaley") == 0) { + ScaleYTimeline *timeline = new (__FILE__, __LINE__) ScaleYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "shear") == 0) { + ShearTimeline *timeline = new (__FILE__, __LINE__) ShearTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, 1)); + } else if (strcmp(timelineMap->_name, "shearx") == 0) { + ShearXTimeline *timeline = new (__FILE__, __LINE__) ShearXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "sheary") == 0) { + ShearYTimeline *timeline = new (__FILE__, __LINE__) ShearYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "inherit") == 0) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frames, boneIndex); + keyMap = timelineMap->_child; + for (frame = 0;; frame++) { + float time = Json::getFloat(keyMap, "time", 0); + const char *value = Json::getString(keyMap, "inherit", "normal"); + Inherit inherit = Inherit_Normal; + if (strcmp(value, "normal") == 0) inherit = Inherit_Normal; + else if (strcmp(value, "onlyTranslation") == 0) + inherit = Inherit_OnlyTranslation; + else if (strcmp(value, "noRotationOrReflection") == 0) + inherit = Inherit_NoRotationOrReflection; + else if (strcmp(value, "noScale") == 0) + inherit = Inherit_NoScale; + else if (strcmp(value, "noScaleOrReflection") == 0) + inherit = Inherit_NoScaleOrReflection; + timeline->setFrame(frame, time, inherit); + nextMap = keyMap->_next; + if (!nextMap) break; + keyMap = nextMap; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a bone: ", timelineMap->_name); + return NULL; + } + } + } + + /** IK constraint timelines. */ + for (Json *constraintMap = ik ? ik->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + IkConstraintData *constraint = skeletonData->findIkConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_ikConstraints.indexOf(constraint); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(constraintMap->_size, + constraintMap->_size << 1, + constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mix = Json::getFloat(keyMap, "mix", 1); + float softness = Json::getFloat(keyMap, "softness", 0) * _scale; + + for (frame = 0, bezier = 0;; frame++) { + int bendDirection = Json::getBoolean(keyMap, "bendPositive", true) ? 1 : -1; + timeline->setFrame(frame, time, mix, softness, bendDirection, Json::getBoolean(keyMap, "compress", false), + Json::getBoolean(keyMap, "stretch", false)); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mix2 = Json::getFloat(nextMap, "mix", 1); + float softness2 = Json::getFloat(nextMap, "softness", 0) * _scale; + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, _scale); + } + + time = time2; + mix = mix2; + softness = softness2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Transform constraint timelines. */ + for (Json *constraintMap = transform ? transform->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + TransformConstraintData *constraint = skeletonData->findTransformConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_transformConstraints.indexOf(constraint); + TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline( + constraintMap->_size, constraintMap->_size * 6, constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixShearY = Json::getFloat(keyMap, "mixShearY", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + float mixScaleX = Json::getFloat(keyMap, "mixScaleX", 1); + float mixScaleY = Json::getFloat(keyMap, "mixScaleY", mixScaleX); + + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixShearY2 = Json::getFloat(nextMap, "mixShearY", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = Json::getFloat(nextMap, "mixScaleX", 1); + float mixScaleY2 = Json::getFloat(nextMap, "mixScaleY", mixScaleX2); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Path constraint timelines. */ + for (Json *constraintMap = paths ? paths->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + PathConstraintData *constraint = skeletonData->findPathConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Path constraint not found: ", constraintMap->_name); + return NULL; + } + int constraintIndex = skeletonData->_pathConstraints.indexOf(constraint); + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "position") == 0) { + PathConstraintPositionTimeline *timeline = new (__FILE__, __LINE__) PathConstraintPositionTimeline( + frames, frames, constraintIndex); + timelines.add( + readTimeline(keyMap, timeline, 0, constraint->_positionMode == PositionMode_Fixed ? _scale : 1)); + } else if (strcmp(timelineName, "spacing") == 0) { + CurveTimeline1 *timeline = new PathConstraintSpacingTimeline(frames, frames, + constraintIndex); + timelines.add(readTimeline(keyMap, timeline, 0, + constraint->_spacingMode == SpacingMode_Length || + constraint->_spacingMode == SpacingMode_Fixed + ? _scale + : 1)); + } else if (strcmp(timelineName, "mix") == 0) { + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frames, + frames * 3, constraintIndex); + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.add(timeline); + } + } + } + + /** Physics constraint timelines. */ + for (Json *constraintMap = physics ? physics->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + int index = -1; + if (constraintMap->_name && strlen(constraintMap->_name) > 0) { + PhysicsConstraintData *constraint = skeletonData->findPhysicsConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Physics constraint not found: ", constraintMap->_name); + return NULL; + } + index = skeletonData->_physicsConstraints.indexOf(constraint); + } + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "reset") == 0) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frames, index); + for (frame = 0; keyMap != nullptr; keyMap = keyMap->_next, frame++) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0)); + } + timelines.add(timeline); + continue; + } + + CurveTimeline1 *timeline = nullptr; + if (strcmp(timelineName, "inertia") == 0) { + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + } else if (strcmp(timelineName, "strength") == 0) { + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + } else if (strcmp(timelineName, "damping") == 0) { + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mass") == 0) { + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + } else if (strcmp(timelineName, "wind") == 0) { + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + } else if (strcmp(timelineName, "gravity") == 0) { + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mix") == 0) { + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + } else { + continue; + } + timelines.add(readTimeline(keyMap, timeline, 0, 1)); + } + } + + /** Attachment timelines. */ + for (Json *attachmenstMap = attachments ? attachments->_child : NULL; attachmenstMap; attachmenstMap = attachmenstMap->_next) { + Skin *skin = skeletonData->findSkin(attachmenstMap->_name); + for (slotMap = attachmenstMap->_child; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *attachmentMap = slotMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = skin->getAttachment(slotIndex, attachmentMap->_name); + if (!attachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Attachment not found: ", attachmentMap->_name); + return NULL; + } + + for (Json *timelineMap = attachmentMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + int frames = timelineMap->_size; + String timelineName = timelineMap->_name; + if (timelineName == "deform") { + VertexAttachment *vertexAttachment = static_cast(attachment); + bool weighted = vertexAttachment->_bones.size() != 0; + Vector &verts = vertexAttachment->_vertices; + int deformLength = weighted ? (int) verts.size() / 3 * 2 : (int) verts.size(); + + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frames, + frames, slotIndex, vertexAttachment); + float time = Json::getFloat(keyMap, "time", 0); + for (frame = 0, bezier = 0;; frame++) { + Json *vertices = Json::getItem(keyMap, "vertices"); + Vector deformed; + if (!vertices) { + if (weighted) { + deformed.setSize(deformLength, 0); + } else { + deformed.clearAndAddAll(vertexAttachment->_vertices); + } + } else { + deformed.setSize(deformLength, 0); + int v, start = Json::getInt(keyMap, "offset", 0); + Json *vertex; + if (_scale == 1) { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat; + } + } else { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat * _scale; + } + } + if (!weighted) { + Vector &verticesAttachment = vertexAttachment->_vertices; + for (v = 0; v < deformLength; ++v) { + deformed[v] += verticesAttachment[v]; + } + } + } + timeline->setFrame(frame, time, deformed); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (timelineName == "sequence") { + SequenceTimeline *timeline = new SequenceTimeline(frames, slotIndex, attachment); + float lastDelay = 0; + for (frame = 0; keyMap != NULL; keyMap = keyMap->_next, frame++) { + float delay = Json::getFloat(keyMap, "delay", lastDelay); + float time = Json::getFloat(keyMap, "time", 0); + String modeString = Json::getString(keyMap, "mode", "hold"); + int index = Json::getInt(keyMap, "index", 0); + SequenceMode mode = SequenceMode::hold; + if (modeString == "once") mode = SequenceMode::once; + if (modeString == "loop") mode = SequenceMode::loop; + if (modeString == "pingpong") mode = SequenceMode::pingpong; + if (modeString == "onceReverse") mode = SequenceMode::onceReverse; + if (modeString == "loopReverse") mode = SequenceMode::loopReverse; + if (modeString == "pingpongReverse") mode = SequenceMode::pingpongReverse; + timeline->setFrame(frame, time, mode, index, delay); + lastDelay = delay; + } + timelines.add(timeline); + } + } + } + } + } + + /** Draw order timeline. */ + if (drawOrder) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrder->_size); + + for (keyMap = drawOrder->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + int ii; + Vector drawOrder2; + Json *offsets = Json::getItem(keyMap, "offsets"); + if (offsets) { + Json *offsetMap; + Vector unchanged; + unchanged.ensureCapacity(skeletonData->_slots.size() - offsets->_size); + unchanged.setSize(skeletonData->_slots.size() - offsets->_size, 0); + size_t originalIndex = 0, unchangedIndex = 0; + + drawOrder2.ensureCapacity(skeletonData->_slots.size()); + drawOrder2.setSize(skeletonData->_slots.size(), 0); + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; --ii) + drawOrder2[ii] = -1; + + for (offsetMap = offsets->_child; offsetMap; offsetMap = offsetMap->_next) { + int slotIndex = findSlotIndex(skeletonData, Json::getString(offsetMap, "slot", 0), timelines); + if (slotIndex == -1) return NULL; + + /* Collect unchanged items. */ + while (originalIndex != (size_t) slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Set changed items. */ + drawOrder2[originalIndex + Json::getInt(offsetMap, "offset", 0)] = (int) originalIndex; + originalIndex++; + } + /* Collect remaining unchanged items. */ + while ((int) originalIndex < (int) skeletonData->_slots.size()) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Fill in unchanged items. */ + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; ii--) + if (drawOrder2[ii] == -1) drawOrder2[ii] = unchanged[--unchangedIndex]; + } + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), drawOrder2); + } + timelines.add(timeline); + } + + /** Event timeline. */ + if (events) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(events->_size); + + for (keyMap = events->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + Event *event; + EventData *eventData = skeletonData->findEvent(Json::getString(keyMap, "name", 0)); + if (!eventData) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Event not found: ", Json::getString(keyMap, "name", 0)); + return NULL; + } + + event = new (__FILE__, __LINE__) Event(Json::getFloat(keyMap, "time", 0), *eventData); + event->_intValue = Json::getInt(keyMap, "int", eventData->_intValue); + event->_floatValue = Json::getFloat(keyMap, "float", eventData->_floatValue); + event->_stringValue = Json::getString(keyMap, "string", eventData->_stringValue.buffer()); + if (!eventData->_audioPath.isEmpty()) { + event->_volume = Json::getFloat(keyMap, "volume", 1); + event->_balance = Json::getFloat(keyMap, "balance", 0); + } + timeline->setFrame(frame, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (size_t i = 0; i < timelines.size(); i++) + duration = MathUtil::max(duration, timelines[i]->getDuration()); + return new (__FILE__, __LINE__) Animation(String(root->_name), timelines, duration); +} + +void SkeletonJson::readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength) { + Json *entry; + size_t i, n, nn, entrySize; + Vector vertices; + + attachment->setWorldVerticesLength(verticesLength); + + entry = Json::getItem(attachmentMap, "vertices"); + entrySize = entry->_size; + vertices.ensureCapacity(entrySize); + vertices.setSize(entrySize, 0); + for (entry = entry->_child, i = 0; entry; entry = entry->_next, ++i) + vertices[i] = entry->_valueFloat; + + if (verticesLength == entrySize) { + if (_scale != 1) { + for (i = 0; i < entrySize; ++i) + vertices[i] *= _scale; + } + + attachment->getVertices().clearAndAddAll(vertices); + return; + } + + Vertices bonesAndWeights; + bonesAndWeights._bones.ensureCapacity(verticesLength * 3); + bonesAndWeights._vertices.ensureCapacity(verticesLength * 3 * 3); + + for (i = 0, n = entrySize; i < n;) { + int boneCount = (int) vertices[i++]; + bonesAndWeights._bones.add(boneCount); + for (nn = i + boneCount * 4; i < nn; i += 4) { + bonesAndWeights._bones.add((int) vertices[i]); + bonesAndWeights._vertices.add(vertices[i + 1] * _scale); + bonesAndWeights._vertices.add(vertices[i + 2] * _scale); + bonesAndWeights._vertices.add(vertices[i + 3]); + } + } + + attachment->getVertices().clearAndAddAll(bonesAndWeights._vertices); + attachment->getBones().clearAndAddAll(bonesAndWeights._bones); +} + +void SkeletonJson::setError(Json *root, const String &value1, const String &value2) { + _error = String(value1).append(value2); + delete root; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SkeletonRenderer.cpp b/modules/spine_godot/spine-cpp/src/spine/SkeletonRenderer.cpp new file mode 100644 index 000000000000..3617753a7313 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SkeletonRenderer.cpp @@ -0,0 +1,246 @@ +/****************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include +#include + +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(1); + cmd->positions = allocator.allocate(numVertices << 1); + cmd->uvs = allocator.allocate(numVertices << 1); + cmd->colors = allocator.allocate(numVertices); + cmd->darkColors = allocator.allocate(numVertices); + cmd->numVertices = numVertices; + cmd->indices = allocator.allocate(numIndices); + cmd->numIndices = numIndices; + cmd->blendMode = blendMode; + cmd->texture = texture; + cmd->next = nullptr; + return cmd; +} + +static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector &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, Vector &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; + + for (unsigned i = 0; i < skeleton.getSlots().size(); ++i) { + Slot &slot = *skeleton.getDrawOrder()[i]; + Attachment *attachment = slot.getAttachment(); + if (!attachment) { + clipper.clipEnd(slot); + continue; + } + + // Early out if the slot color is 0 or the bone is not active + if ((slot.getColor().a == 0 || !slot.getBone().isActive()) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + clipper.clipEnd(slot); + continue; + } + + Vector *worldVertices = &_worldVertices; + Vector *quadIndices = &_quadIndices; + Vector *vertices = worldVertices; + int32_t verticesCount; + Vector *uvs; + Vector *indices; + int32_t indicesCount; + Color *attachmentColor; + void *texture; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor = ®ionAttachment->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(8, 0); + regionAttachment->computeWorldVertices(slot, *worldVertices, 0, 2); + verticesCount = 4; + uvs = ®ionAttachment->getUVs(); + indices = quadIndices; + indicesCount = 6; + texture = regionAttachment->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + attachmentColor = &mesh->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2); + verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1); + uvs = &mesh->getUVs(); + indices = &mesh->getTriangles(); + indicesCount = (int32_t) indices->size(); + texture = mesh->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); + clipper.clipStart(slot, clip); + continue; + } else + continue; + + uint8_t r = static_cast(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255); + uint8_t g = static_cast(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255); + uint8_t b = static_cast(skeleton.getColor().b * slot.getColor().b * attachmentColor->b * 255); + uint8_t a = static_cast(skeleton.getColor().a * slot.getColor().a * attachmentColor->a * 255); + uint32_t color = (a << 24) | (r << 16) | (g << 8) | b; + uint32_t darkColor = 0xff000000; + if (slot.hasDarkColor()) { + Color &slotDarkColor = slot.getDarkColor(); + darkColor = 0xff000000 | (static_cast(slotDarkColor.r * 255) << 16) | (static_cast(slotDarkColor.g * 255) << 8) | static_cast(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); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Skin.cpp b/modules/spine_godot/spine-cpp/src/spine/Skin.cpp new file mode 100644 index 000000000000..582b26abe073 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Skin.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * 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 + +#include +#include +#include + +#include +#include + +#include + +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 &attachmentName, Attachment *attachment) { + if (slotIndex >= _buckets.size()) + _buckets.setSize(slotIndex + 1, Vector()); + Vector &bucket = _buckets[slotIndex]; + int existing = findInBucket(bucket, attachmentName); + attachment->reference(); + if (existing >= 0) { + disposeAttachment(bucket[existing]._attachment); + bucket[existing]._attachment = attachment; + } else { + bucket.add(Entry(slotIndex, attachmentName, attachment)); + } +} + +Attachment *Skin::AttachmentMap::get(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return NULL; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + return existing >= 0 ? _buckets[slotIndex][existing]._attachment : NULL; +} + +void Skin::AttachmentMap::remove(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + if (existing >= 0) { + disposeAttachment(_buckets[slotIndex][existing]._attachment); + _buckets[slotIndex].removeAt(existing); + } +} + +int Skin::AttachmentMap::findInBucket(Vector &bucket, const String &attachmentName) { + for (size_t i = 0; i < bucket.size(); i++) + if (bucket[i]._name == attachmentName) 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 &name, Attachment *attachment) { + assert(attachment); + _attachments.put(slotIndex, name, attachment); +} + +Attachment *Skin::getAttachment(size_t slotIndex, const String &name) { + return _attachments.get(slotIndex, name); +} + +void Skin::removeAttachment(size_t slotIndex, const String &name) { + _attachments.remove(slotIndex, name); +} + +void Skin::findNamesForSlot(size_t slotIndex, Vector &names) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) { + names.add(entry._name); + } + } +} + +void Skin::findAttachmentsForSlot(size_t slotIndex, Vector &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) { + Vector &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->getAttachment() == entry._attachment) { + Attachment *attachment = getAttachment(slotIndex, entry._name); + if (attachment) slot->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._name, 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._name, + static_cast(entry._attachment)->newLinkedMesh()); + else + setAttachment(entry._slotIndex, entry._name, entry._attachment->copy()); + } +} + +Vector &Skin::getConstraints() { + return _constraints; +} + +Vector &Skin::getBones() { + return _bones; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Slot.cpp b/modules/spine_godot/spine-cpp/src/spine/Slot.cpp new file mode 100644 index 000000000000..a0079f01c40e --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Slot.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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 + +#include +#include +#include +#include +#include + +using namespace spine; + +Slot::Slot(SlotData &data, Bone &bone) : _data(data), + _bone(bone), + _skeleton(bone.getSkeleton()), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(data.hasDarkColor()), + _attachment(NULL), + _attachmentState(0), + _sequenceIndex(0) { + setToSetupPose(); +} + +void Slot::setToSetupPose() { + _color.set(_data.getColor()); + if (_hasDarkColor) _darkColor.set(_data.getDarkColor()); + + const String &attachmentName = _data.getAttachmentName(); + if (attachmentName.length() > 0) { + _attachment = NULL; + setAttachment(_skeleton.getAttachment(_data.getIndex(), attachmentName)); + } else { + setAttachment(NULL); + } +} + +SlotData &Slot::getData() { + return _data; +} + +Bone &Slot::getBone() { + return _bone; +} + +Skeleton &Slot::getSkeleton() { + return _skeleton; +} + +Color &Slot::getColor() { + return _color; +} + +Color &Slot::getDarkColor() { + return _darkColor; +} + +bool Slot::hasDarkColor() { + return _hasDarkColor; +} + +Attachment *Slot::getAttachment() { + return _attachment; +} + +void Slot::setAttachment(Attachment *inValue) { + if (_attachment == inValue) { + return; + } + + if (!inValue || + !_attachment || + !inValue->getRTTI().instanceOf(VertexAttachment::rtti) || + !_attachment->getRTTI().instanceOf(VertexAttachment::rtti) || + static_cast(inValue)->getTimelineAttachment() != + static_cast(_attachment)->getTimelineAttachment()) { + _deform.clear(); + } + + _attachment = inValue; + _sequenceIndex = -1; +} + +int Slot::getAttachmentState() { + return _attachmentState; +} + +void Slot::setAttachmentState(int state) { + _attachmentState = state; +} + +Vector &Slot::getDeform() { + return _deform; +} + +int Slot::getSequenceIndex() { + return _sequenceIndex; +} + +void Slot::setSequenceIndex(int index) { + _sequenceIndex = index; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SlotData.cpp b/modules/spine_godot/spine-cpp/src/spine/SlotData.cpp new file mode 100644 index 000000000000..bc9694a28fd0 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SlotData.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +SlotData::SlotData(int index, const String &name, BoneData &boneData) : _index(index), + _name(name), + _boneData(boneData), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(false), + _attachmentName(), + _blendMode(BlendMode_Normal), + _visible(true) { + assert(_index >= 0); + assert(_name.length() > 0); +} + +int SlotData::getIndex() { + return _index; +} + +const String &SlotData::getName() { + return _name; +} + +BoneData &SlotData::getBoneData() { + return _boneData; +} + +Color &SlotData::getColor() { + return _color; +} + +Color &SlotData::getDarkColor() { + return _darkColor; +} + +bool SlotData::hasDarkColor() { + return _hasDarkColor; +} + +void SlotData::setHasDarkColor(bool inValue) { + _hasDarkColor = inValue; +} + +const String &SlotData::getAttachmentName() { + return _attachmentName; +} + +void SlotData::setAttachmentName(const String &inValue) { + _attachmentName = inValue; +} + +BlendMode SlotData::getBlendMode() { + return _blendMode; +} + +void SlotData::setBlendMode(BlendMode inValue) { + _blendMode = inValue; +} + +bool SlotData::isVisible() { + return _visible; +} + +void SlotData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/SpineObject.cpp b/modules/spine_godot/spine-cpp/src/spine/SpineObject.cpp new file mode 100644 index 000000000000..d71bf6e10cd2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/SpineObject.cpp @@ -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 +#include + +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); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/TextureLoader.cpp b/modules/spine_godot/spine-cpp/src/spine/TextureLoader.cpp new file mode 100644 index 000000000000..4dd2a5f34554 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/TextureLoader.cpp @@ -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 + +namespace spine { + TextureLoader::TextureLoader() { + } + + TextureLoader::~TextureLoader() { + } +}// namespace spine diff --git a/modules/spine_godot/spine-cpp/src/spine/Timeline.cpp b/modules/spine_godot/spine-cpp/src/spine/Timeline.cpp new file mode 100644 index 000000000000..b8624c0a3b58 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Timeline.cpp @@ -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 + +#include +#include + +namespace spine { + RTTI_IMPL_NOPARENT(Timeline) + + Timeline::Timeline(size_t frameCount, size_t frameEntries) + : _propertyIds(), _frames(), _frameEntries(frameEntries) { + _frames.setSize(frameCount * frameEntries, 0); + } + + Timeline::~Timeline() { + } + + Vector &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; + } + + Vector &Timeline::getFrames() { + return _frames; + } + + size_t Timeline::getFrameEntries() { + return _frameEntries; + } + + float Timeline::getDuration() { + return _frames[_frames.size() - getFrameEntries()]; + } +}// namespace spine diff --git a/modules/spine_godot/spine-cpp/src/spine/TransformConstraint.cpp b/modules/spine_godot/spine-cpp/src/spine/TransformConstraint.cpp new file mode 100644 index 000000000000..3fea0ac51690 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/TransformConstraint.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * 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 + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraint, Updatable) + +TransformConstraint::TransformConstraint(TransformConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findBone( + data.getTarget()->getName())), + _mixRotate( + data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _mixScaleX( + data.getMixScaleX()), + _mixScaleY( + data.getMixScaleY()), + _mixShearY( + data.getMixShearY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); ++i) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void TransformConstraint::update(Physics) { + if (_mixRotate == 0 && _mixX == 0 && _mixY == 0 && _mixScaleX == 0 && _mixScaleY == 0 && _mixShearY == 0) return; + + if (_data.isLocal()) { + if (_data.isRelative()) + applyRelativeLocal(); + else + applyAbsoluteLocal(); + } else { + if (_data.isRelative()) + applyRelativeWorld(); + else + applyAbsoluteWorld(); + } +} + +int TransformConstraint::getOrder() { + return (int) _data.getOrder(); +} + +TransformConstraintData &TransformConstraint::getData() { + return _data; +} + +Vector &TransformConstraint::getBones() { + return _bones; +} + +Bone *TransformConstraint::getTarget() { + return _target; +} + +void TransformConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +float TransformConstraint::getMixRotate() { + return _mixRotate; +} + +void TransformConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float TransformConstraint::getMixX() { + return _mixX; +} + +void TransformConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float TransformConstraint::getMixY() { + return _mixY; +} + +void TransformConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +void TransformConstraint::setMixScaleX(float inValue) { + _mixScaleX = inValue; +} + +float TransformConstraint::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraint::getMixScaleY() { + return _mixScaleY; +} + +void TransformConstraint::setMixScaleY(float inValue) { + _mixScaleY = inValue; +} + +float TransformConstraint::getMixShearY() { + return _mixShearY; +} + +void TransformConstraint::setMixShearY(float inValue) { + _mixShearY = inValue; +} + +void TransformConstraint::applyAbsoluteWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) - MathUtil::atan2(c, a) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float 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; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += (tx - bone._worldX) * mixX; + bone._worldY += (ty - bone._worldY) * mixY; + } + + if (mixScaleX > 0) { + float s = MathUtil::sqrt(bone._a * bone._a + bone._c * bone._c); + if (s != 0) s = (s + (MathUtil::sqrt(ta * ta + tc * tc) - s + _data._offsetScaleX) * mixScaleX) / s; + bone._a *= s; + bone._c *= s; + } + + if (mixScaleY > 0) { + float s = MathUtil::sqrt(bone._b * bone._b + bone._d * bone._d); + if (s != 0) s = (s + (MathUtil::sqrt(tb * tb + td * td) - s + _data._offsetScaleY) * mixScaleY) / s; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float b = bone._b, d = bone._d; + float by = MathUtil::atan2(d, b); + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta) - (by - MathUtil::atan2(bone._c, bone._a)); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r = by + (r + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyRelativeWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float 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; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += tx * mixX; + bone._worldY += ty * mixY; + } + + if (mixScaleX != 0) { + float s = (MathUtil::sqrt(ta * ta + tc * tc) - 1 + _data._offsetScaleX) * mixScaleX + 1; + bone._a *= s; + bone._c *= s; + } + if (mixScaleY != 0) { + float s = (MathUtil::sqrt(tb * tb + td * td) - 1 + _data._offsetScaleY) * mixScaleY + 1; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + float b = bone._b, d = bone._d; + r = MathUtil::atan2(d, b) + (r - MathUtil::Pi / 2 + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyAbsoluteLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation; + if (mixRotate != 0) { + float r = target._arotation - rotation + _data._offsetRotation; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + rotation += r * mixRotate; + } + + float x = bone._ax, y = bone._ay; + x += (target._ax - x + _data._offsetX) * mixX; + y += (target._ay - y + _data._offsetY) * mixY; + + float scaleX = bone._ascaleX, scaleY = bone._ascaleY; + if (mixScaleX != 0 && scaleX != 0) + scaleX = (scaleX + (target._ascaleX - scaleX + _data._offsetScaleX) * mixScaleX) / scaleX; + if (mixScaleY != 0 && scaleY != 0) + scaleY = (scaleY + (target._ascaleY - scaleY + _data._offsetScaleY) * mixScaleY) / scaleY; + + float shearY = bone._ashearY; + if (mixShearY != 0) { + float r = target._ashearY - shearY + _data._offsetShearY; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + bone._shearY += r * mixShearY; + } + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +void TransformConstraint::applyRelativeLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation + (target._arotation + _data._offsetRotation) * mixRotate; + float x = bone._ax + (target._ax + _data._offsetX) * mixX; + float y = bone._ay + (target._ay + _data._offsetY) * mixY; + float scaleX = bone._ascaleX * (((target._ascaleX - 1 + _data._offsetScaleX) * mixScaleX) + 1); + float scaleY = bone._ascaleY * (((target._ascaleY - 1 + _data._offsetScaleY) * mixScaleY) + 1); + float shearY = bone._ashearY + (target._ashearY + _data._offsetShearY) * mixShearY; + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +bool TransformConstraint::isActive() { + return _active; +} + +void TransformConstraint::setActive(bool inValue) { + _active = inValue; +} + +void TransformConstraint::setToSetupPose() { + TransformConstraintData &data = this->_data; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; + this->_mixScaleX = data._mixScaleX; + this->_mixScaleY = data._mixScaleY; + this->_mixShearY = data._mixShearY; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/TransformConstraintData.cpp b/modules/spine_godot/spine-cpp/src/spine/TransformConstraintData.cpp new file mode 100644 index 000000000000..38a674e4895c --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/TransformConstraintData.cpp @@ -0,0 +1,180 @@ +/****************************************************************************** + * 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 + +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintData, ConstraintData) + +TransformConstraintData::TransformConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _mixRotate(0), + _mixX(0), + _mixY(0), + _mixScaleX(0), + _mixScaleY(0), + _mixShearY(0), + _offsetRotation(0), + _offsetX(0), + _offsetY(0), + _offsetScaleX(0), + _offsetScaleY(0), + _offsetShearY(0), + _relative(false), + _local(false) { +} + +Vector &TransformConstraintData::getBones() { + return _bones; +} + +BoneData *TransformConstraintData::getTarget() { + return _target; +} + +float TransformConstraintData::getMixRotate() { + return _mixRotate; +} + +float TransformConstraintData::getMixX() { + return _mixX; +} + +float TransformConstraintData::getMixY() { + return _mixY; +} + +float TransformConstraintData::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraintData::getMixScaleY() { + return _mixScaleY; +} + +float TransformConstraintData::getMixShearY() { + return _mixShearY; +} + +float TransformConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +float TransformConstraintData::getOffsetX() { + return _offsetX; +} + +float TransformConstraintData::getOffsetY() { + return _offsetY; +} + +float TransformConstraintData::getOffsetScaleX() { + return _offsetScaleX; +} + +float TransformConstraintData::getOffsetScaleY() { + return _offsetScaleY; +} + +float TransformConstraintData::getOffsetShearY() { + return _offsetShearY; +} + +bool TransformConstraintData::isRelative() { + return _relative; +} + +bool TransformConstraintData::isLocal() { + return _local; +} + +void TransformConstraintData::setTarget(BoneData *target) { + _target = target; +} + +void TransformConstraintData::setMixRotate(float mixRotate) { + _mixRotate = mixRotate; +} + +void TransformConstraintData::setMixX(float mixX) { + _mixX = mixX; +} + +void TransformConstraintData::setMixY(float mixY) { + _mixY = mixY; +} + +void TransformConstraintData::setMixScaleX(float mixScaleX) { + _mixScaleX = mixScaleX; +} + +void TransformConstraintData::setMixScaleY(float mixScaleY) { + _mixScaleY = mixScaleY; +} + +void TransformConstraintData::setMixShearY(float mixShearY) { + _mixShearY = mixShearY; +} + +void TransformConstraintData::setOffsetRotation(float offsetRotation) { + _offsetRotation = offsetRotation; +} + +void TransformConstraintData::setOffsetX(float offsetX) { + _offsetX = offsetX; +} + +void TransformConstraintData::setOffsetY(float offsetY) { + _offsetY = offsetY; +} + +void TransformConstraintData::setOffsetScaleX(float offsetScaleX) { + _offsetScaleX = offsetScaleX; +} + +void TransformConstraintData::setOffsetScaleY(float offsetScaleY) { + _offsetScaleY = offsetScaleY; +} + +void TransformConstraintData::setOffsetShearY(float offsetShearY) { + _offsetShearY = offsetShearY; +} + +void TransformConstraintData::setRelative(bool isRelative) { + _relative = isRelative; +} + +void TransformConstraintData::setLocal(bool isLocal) { + _local = isLocal; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/TransformConstraintTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/TransformConstraintTimeline.cpp new file mode 100644 index 000000000000..6e41e2ef34a9 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/TransformConstraintTimeline.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintTimeline, CurveTimeline) + +TransformConstraintTimeline::TransformConstraintTimeline(size_t frameCount, size_t bezierCount, + int transformConstraintIndex) : CurveTimeline(frameCount, + TransformConstraintTimeline::ENTRIES, + bezierCount), + _constraintIndex( + transformConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_TransformConstraint << 32) | transformConstraintIndex}; + setPropertyIds(ids, 1); +} + +void TransformConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + TransformConstraint *constraintP = skeleton._transformConstraints[_constraintIndex]; + TransformConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + TransformConstraintData &data = constraint._data; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = data._mixRotate; + constraint._mixX = data._mixX; + constraint._mixY = data._mixY; + constraint._mixScaleX = data._mixScaleX; + constraint._mixScaleY = data._mixScaleY; + constraint._mixShearY = data._mixShearY; + return; + case MixBlend_First: + constraint._mixRotate += (data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (data._mixX - constraint._mixX) * alpha; + constraint._mixY += (data._mixY - constraint._mixY) * alpha; + constraint._mixScaleX += (data._mixScaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (data._mixScaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (data._mixShearY - constraint._mixShearY) * alpha; + return; + default: + return; + } + } + + float rotate, x, y, scaleX, scaleY, shearY; + int i = Animation::search(_frames, time, TransformConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / TransformConstraintTimeline::ENTRIES]; + switch (curveType) { + case TransformConstraintTimeline::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 TransformConstraintTimeline::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); + } + } + + if (blend == MixBlend_Setup) { + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + constraint._mixScaleX = data._mixScaleX + (scaleX - data._mixScaleX) * alpha; + constraint._mixScaleY = data._mixScaleY + (scaleY - data._mixScaleY) * alpha; + constraint._mixShearY = data._mixShearY + (shearY - data._mixShearY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._mixY) * alpha; + constraint._mixScaleX += (scaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (scaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (shearY - constraint._mixShearY) * alpha; + } +} + +void TransformConstraintTimeline::setFrame(size_t 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; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/TranslateTimeline.cpp b/modules/spine_godot/spine-cpp/src/spine/TranslateTimeline.cpp new file mode 100644 index 000000000000..129979553ef1 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/TranslateTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * 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 + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TranslateTimeline, CurveTimeline2) + +TranslateTimeline::TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex, + ((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +TranslateTimeline::~TranslateTimeline() { +} + +void TranslateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x; + bone->_y = bone->_data._y; + return; + case MixBlend_First: + bone->_x += (bone->_data._x - bone->_x) * alpha; + bone->_y += (bone->_data._y - bone->_y) * alpha; + default: { + } + } + return; + } + + float x = 0, y = 0; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline::BEZIER_SIZE - CurveTimeline::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x + x * alpha; + bone->_y = bone->_data._y + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_x += (bone->_data._x + x - bone->_x) * alpha; + bone->_y += (bone->_data._y + y - bone->_y) * alpha; + break; + case MixBlend_Add: + bone->_x += x * alpha; + bone->_y += y * alpha; + } +} + +RTTI_IMPL(TranslateXTimeline, CurveTimeline1) + +TranslateXTimeline::TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateXTimeline::~TranslateXTimeline() { +} + +void TranslateXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_x = getRelativeValue(time, alpha, blend, bone->_x, bone->_data._x); +} + +RTTI_IMPL(TranslateYTimeline, CurveTimeline1) + +TranslateYTimeline::TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateYTimeline::~TranslateYTimeline() { +} + +void TranslateYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_y = getRelativeValue(time, alpha, blend, bone->_y, bone->_data._y); +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Triangulator.cpp b/modules/spine_godot/spine-cpp/src/spine/Triangulator.cpp new file mode 100644 index 000000000000..336c6f5227c8 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Triangulator.cpp @@ -0,0 +1,285 @@ +/****************************************************************************** + * 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 + +#include + +using namespace spine; + +Triangulator::~Triangulator() { + ContainerUtil::cleanUpVectorOfPointers(_convexPolygons); + ContainerUtil::cleanUpVectorOfPointers(_convexPolygonsIndices); +} + +Vector &Triangulator::triangulate(Vector &vertices) { + size_t vertexCount = vertices.size() >> 1; + + Vector &indices = _indices; + indices.clear(); + indices.ensureCapacity(vertexCount); + indices.setSize(vertexCount, 0); + for (int i = 0; i < (int) vertexCount; ++i) { + indices[i] = i; + } + + Vector &isConcaveArray = _isConcaveArray; + isConcaveArray.ensureCapacity(vertexCount); + isConcaveArray.setSize(vertexCount, 0); + for (int i = 0, n = (int) vertexCount; i < n; ++i) { + isConcaveArray[i] = isConcave(i, (int) vertexCount, vertices, indices); + } + + Vector &triangles = _triangles; + triangles.clear(); + triangles.ensureCapacity(MathUtil::max((int) 0, (int) vertexCount - 2) << 2); + + while (vertexCount > 3) { + // Find ear tip. + size_t previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) { + if (!isConcaveArray[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]; + for (size_t ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcaveArray[ii]) continue; + + int v = indices[ii] << 1; + float &vx = vertices[v], vy = vertices[v + 1]; + if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { + if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { + if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) { + goto break_outer;// break outer; + } + } + } + } + break; + } + break_outer: + + if (next == 0) { + do { + if (!isConcaveArray[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + + // Cut ear tip. + triangles.add(indices[(vertexCount + i - 1) % vertexCount]); + triangles.add(indices[i]); + triangles.add(indices[(i + 1) % vertexCount]); + indices.removeAt(i); + isConcaveArray.removeAt(i); + vertexCount--; + + int previousIndex = (int) ((vertexCount + i - 1) % vertexCount); + int nextIndex = (int) (i == vertexCount ? 0 : i); + isConcaveArray[previousIndex] = isConcave(previousIndex, (int) vertexCount, vertices, indices); + isConcaveArray[nextIndex] = isConcave(nextIndex, (int) vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.add(indices[2]); + triangles.add(indices[0]); + triangles.add(indices[1]); + } + + return triangles; +} + +Vector *> &Triangulator::decompose(Vector &vertices, Vector &triangles) { + Vector *> &convexPolygons = _convexPolygons; + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) + _polygonPool.free(convexPolygons[i]); + convexPolygons.clear(); + + Vector *> &convexPolygonsIndices = _convexPolygonsIndices; + for (size_t i = 0, n = convexPolygonsIndices.size(); i < n; ++i) + _polygonIndicesPool.free(convexPolygonsIndices[i]); + convexPolygonsIndices.clear(); + + Vector *polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + + Vector *polygon = _polygonPool.obtain(); + polygon->clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastwinding = 0; + for (size_t i = 0, n = triangles.size(); i < n; i += 3) { + int t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[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 the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). + bool merged = false; + if (fanBaseIndex == t1) { + size_t o = polygon->size() - 4; + Vector &p = *polygon; + int winding1 = winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3); + int winding2 = winding(x3, y3, p[0], p[1], p[2], p[3]); + if (winding1 == lastwinding && winding2 == lastwinding) { + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(t3); + merged = true; + } + } + + // Otherwise make this triangle the new base. + if (!merged) { + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } else { + _polygonPool.free(polygon); + _polygonIndicesPool.free(polygonIndices); + } + + polygon = _polygonPool.obtain(); + polygon->clear(); + polygon->add(x1); + polygon->add(y1); + polygon->add(x2); + polygon->add(y2); + polygon->add(x3); + polygon->add(y3); + polygonIndices = _polygonIndicesPool.obtain(); + 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); + } + + // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) { + polygonIndices = convexPolygonsIndices[i]; + + if (polygonIndices->size() == 0) continue; + int firstIndex = (*polygonIndices)[0]; + int lastIndex = (*polygonIndices)[polygonIndices->size() - 1]; + + polygon = convexPolygons[i]; + size_t o = polygon->size() - 4; + Vector &p = *polygon; + 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 winding0 = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (size_t ii = 0; ii < n; ++ii) { + if (ii == i) continue; + + Vector *otherIndicesP = convexPolygonsIndices[ii]; + Vector &otherIndices = *otherIndicesP; + + if (otherIndices.size() != 3) continue; + + int otherFirstIndex = otherIndices[0]; + int otherSecondIndex = otherIndices[1]; + int otherLastIndex = otherIndices[2]; + + Vector *otherPolyP = convexPolygons[ii]; + Vector &otherPoly = *otherPolyP; + + float x3 = otherPoly[otherPoly.size() - 2], y3 = otherPoly[otherPoly.size() - 1]; + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + + int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding0 && winding2 == winding0) { + otherPoly.clear(); + otherIndices.clear(); + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = 0; + } + } + } + + // Remove empty polygons that resulted from the merge step above. + for (int i = (int) convexPolygons.size() - 1; i >= 0; --i) { + polygon = convexPolygons[i]; + if (polygon->size() == 0) { + convexPolygons.removeAt(i); + _polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.removeAt(i); + _polygonIndicesPool.free(polygonIndices); + } + } + + return convexPolygons; +} + +bool Triangulator::isConcave(int index, int vertexCount, Vector &vertices, Vector &indices) { + int previous = indices[(vertexCount + index - 1) % vertexCount] << 1; + int current = indices[index] << 1; + int next = indices[(index + 1) % vertexCount] << 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) { + float px = p2x - p1x, py = p2y - p1y; + return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; +} diff --git a/modules/spine_godot/spine-cpp/src/spine/Updatable.cpp b/modules/spine_godot/spine-cpp/src/spine/Updatable.cpp new file mode 100644 index 000000000000..8e874a272029 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/Updatable.cpp @@ -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 + +using namespace spine; + +RTTI_IMPL_NOPARENT(Updatable) + +Updatable::Updatable() { +} + +Updatable::~Updatable() { +} diff --git a/modules/spine_godot/spine-cpp/src/spine/VertexAttachment.cpp b/modules/spine_godot/spine-cpp/src/spine/VertexAttachment.cpp new file mode 100644 index 000000000000..a84f9a7f4fb2 --- /dev/null +++ b/modules/spine_godot/spine-cpp/src/spine/VertexAttachment.cpp @@ -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 + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(VertexAttachment, Attachment) + +VertexAttachment::VertexAttachment(const String &name) : Attachment(name), _worldVerticesLength(0), + _timelineAttachment(this), _id(getNextID()) { +} + +VertexAttachment::~VertexAttachment() { +} + +void VertexAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, float *worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, + size_t offset, size_t stride) { + computeWorldVertices(slot, start, count, worldVertices.buffer(), offset, stride); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + count = offset + (count >> 1) * stride; + Skeleton &skeleton = slot._bone._skeleton; + Vector *deformArray = &slot.getDeform(); + Vector *vertices = &_vertices; + Vector &bones = _bones; + if (bones.size() == 0) { + if (deformArray->size() > 0) vertices = deformArray; + + Bone &bone = slot._bone; + float x = bone._worldX; + float y = bone._worldY; + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + 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; + } + + Vector &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]]; + Bone &bone = *boneP; + float vx = (*vertices)[b]; + float vy = (*vertices)[b + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * 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]]; + Bone &bone = *boneP; + float vx = (*vertices)[b] + (*deformArray)[f]; + float vy = (*vertices)[b + 1] + (*deformArray)[f + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } +} + +int VertexAttachment::getId() { + return _id; +} + +Vector &VertexAttachment::getBones() { + return _bones; +} + +Vector &VertexAttachment::getVertices() { + return _vertices; +} + +size_t VertexAttachment::getWorldVerticesLength() { + return _worldVerticesLength; +} + +void VertexAttachment::setWorldVerticesLength(size_t inValue) { + _worldVerticesLength = inValue; +} + +Attachment *VertexAttachment::getTimelineAttachment() { + return _timelineAttachment; +} + +void VertexAttachment::setTimelineAttachment(Attachment *attachment) { + _timelineAttachment = 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->_timelineAttachment = this->_timelineAttachment; +} diff --git a/modules/spx/SCsub b/modules/spx/SCsub index b7739c1753e2..b28ff51afc46 100644 --- a/modules/spx/SCsub +++ b/modules/spx/SCsub @@ -6,5 +6,8 @@ Import("env_modules") env_spx = env_modules.Clone() +# Add spine-cpp include path for spine_godot integration +env_spx.Append(CPPPATH=["#modules/spine_godot/spine-cpp/include"]) + # Godot source files env_spx.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/spx/gdextension_spx_ext.cpp b/modules/spx/gdextension_spx_ext.cpp index f9cb233ebc9b..95aba475ba72 100644 --- a/modules/spx/gdextension_spx_ext.cpp +++ b/modules/spx/gdextension_spx_ext.cpp @@ -47,6 +47,7 @@ #include "spx_platform_mgr.h" #include "spx_res_mgr.h" #include "spx_scene_mgr.h" +#include "spx_spine_mgr.h" #include "spx_sprite_mgr.h" #include "spx_tilemap_mgr.h" #include "spx_tilemapparser_mgr.h" @@ -405,6 +406,9 @@ static void gdextension_spx_scene_create_render_sprite(GdString texture_path, Gd static void gdextension_spx_scene_create_static_sprite(GdString texture_path, GdVec2 pos, GdFloat degree, GdVec2 scale, GdInt zindex, GdVec2 pivot, GdInt collider_type, GdVec2 collider_pivot, GdArray collider_params, GdObj *ret_val) { *ret_val = sceneMgr->create_static_sprite(texture_path, pos, degree, scale, zindex, pivot, collider_type, collider_pivot, collider_params); } +static void gdextension_spx_spine_clear_all_caches() { + spineMgr->clear_all_caches(); +} static void gdextension_spx_sprite_set_dont_destroy_on_load(GdObj obj) { spriteMgr->set_dont_destroy_on_load(obj); } @@ -609,6 +613,12 @@ static void gdextension_spx_sprite_is_anim_flipped_v(GdObj obj, GdBool *ret_val) static void gdextension_spx_sprite_get_current_anim_name(GdObj obj, GdString *ret_val) { *ret_val = spriteMgr->get_current_anim_name(obj); } +static void gdextension_spx_sprite_set_spine_skeleton(GdObj obj, GdString atlas_path, GdString skeleton_path, GdFloat default_mix) { + spriteMgr->set_spine_skeleton(obj, atlas_path, skeleton_path, default_mix); +} +static void gdextension_spx_sprite_clear_spine_skeleton(GdObj obj) { + spriteMgr->clear_spine_skeleton(obj); +} static void gdextension_spx_sprite_set_velocity(GdObj obj, GdVec2 velocity) { spriteMgr->set_velocity(obj, velocity); } @@ -1082,6 +1092,7 @@ void gdextension_spx_setup_interface() { REGISTER_SPX_INTERFACE_FUNC(spx_scene_destroy_pure_sprite); REGISTER_SPX_INTERFACE_FUNC(spx_scene_create_render_sprite); REGISTER_SPX_INTERFACE_FUNC(spx_scene_create_static_sprite); + REGISTER_SPX_INTERFACE_FUNC(spx_spine_clear_all_caches); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_dont_destroy_on_load); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_process); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_physic_process); @@ -1150,6 +1161,8 @@ void gdextension_spx_setup_interface() { REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_anim_flip_v); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_is_anim_flipped_v); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_get_current_anim_name); + REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_spine_skeleton); + REGISTER_SPX_INTERFACE_FUNC(spx_sprite_clear_spine_skeleton); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_set_velocity); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_get_velocity); REGISTER_SPX_INTERFACE_FUNC(spx_sprite_is_on_floor); diff --git a/modules/spx/gdextension_spx_ext.h b/modules/spx/gdextension_spx_ext.h index 63b7ffa48d18..53a485c03da1 100644 --- a/modules/spx/gdextension_spx_ext.h +++ b/modules/spx/gdextension_spx_ext.h @@ -343,6 +343,8 @@ typedef void (*GDExtensionSpxSceneCreatePureSprite)(GdString texture_path, GdVec typedef void (*GDExtensionSpxSceneDestroyPureSprite)(GdObj id); typedef void (*GDExtensionSpxSceneCreateRenderSprite)(GdString texture_path, GdVec2 pos, GdFloat degree, GdVec2 scale, GdInt zindex, GdVec2 pivot, GdObj *ret_value); typedef void (*GDExtensionSpxSceneCreateStaticSprite)(GdString texture_path, GdVec2 pos, GdFloat degree, GdVec2 scale, GdInt zindex, GdVec2 pivot, GdInt collider_type, GdVec2 collider_pivot, GdArray collider_params, GdObj *ret_value); +// SpxSpine +typedef void (*GDExtensionSpxSpineClearAllCaches)(); // SpxSprite typedef void (*GDExtensionSpxSpriteSetDontDestroyOnLoad)(GdObj obj); typedef void (*GDExtensionSpxSpriteSetProcess)(GdObj obj, GdBool is_on); @@ -412,6 +414,8 @@ typedef void (*GDExtensionSpxSpriteIsAnimFlippedH)(GdObj obj, GdBool *ret_value) typedef void (*GDExtensionSpxSpriteSetAnimFlipV)(GdObj obj, GdBool p_flip); typedef void (*GDExtensionSpxSpriteIsAnimFlippedV)(GdObj obj, GdBool *ret_value); typedef void (*GDExtensionSpxSpriteGetCurrentAnimName)(GdObj obj, GdString *ret_value); +typedef void (*GDExtensionSpxSpriteSetSpineSkeleton)(GdObj obj, GdString atlas_path, GdString skeleton_path, GdFloat default_mix); +typedef void (*GDExtensionSpxSpriteClearSpineSkeleton)(GdObj obj); typedef void (*GDExtensionSpxSpriteSetVelocity)(GdObj obj, GdVec2 velocity); typedef void (*GDExtensionSpxSpriteGetVelocity)(GdObj obj, GdVec2 *ret_value); typedef void (*GDExtensionSpxSpriteIsOnFloor)(GdObj obj, GdBool *ret_value); diff --git a/modules/spx/spx_engine.cpp b/modules/spx/spx_engine.cpp index b625f06b0961..ac5d35d9412a 100644 --- a/modules/spx/spx_engine.cpp +++ b/modules/spx/spx_engine.cpp @@ -55,6 +55,7 @@ #include "spx_tilemap_mgr.h" #include "spx_tilemapparser_mgr.h" #include "spx_callback_proxy.h" +#include "spx_spine_mgr.h" SpxEngine *SpxEngine::singleton = nullptr; @@ -155,6 +156,8 @@ void SpxEngine::register_callbacks(GDExtensionSpxCallbackInfoPtr callback_ptr) { singleton->mgrs.append((SpxBaseMgr *)singleton->tilemap); singleton->tilemapparser = memnew(SpxTilemapparserMgr); singleton->mgrs.append((SpxBaseMgr *)singleton->tilemapparser); + singleton->spine = memnew(SpxSpineMgr); + singleton->mgrs.append((SpxBaseMgr *)singleton->spine); singleton->callbacks = *(SpxCallbackInfo *)callback_ptr; singleton->global_id = 1; @@ -314,6 +317,7 @@ void SpxEngine::on_destroy() { memdelete(pen); memdelete(tilemap); memdelete(tilemapparser); + memdelete(spine); mgrs.clear(); singleton = nullptr; diff --git a/modules/spx/spx_engine.h b/modules/spx/spx_engine.h index d100a52a5618..05ad33aeeb77 100644 --- a/modules/spx/spx_engine.h +++ b/modules/spx/spx_engine.h @@ -57,6 +57,7 @@ class SpxPenMgr; class SpxTilemapMgr; class SpxTilemapparserMgr; class SpxCallbackProxy; +class SpxSpineMgr; typedef void (*GDExtensionSpxGlobalRuntimePanicCallback)(GdString msg); typedef void (*GDExtensionSpxGlobalRuntimeExitCallback)(GdInt code); @@ -91,6 +92,7 @@ class SpxEngine : SpxBaseMgr { SpxPenMgr *pen; SpxTilemapMgr *tilemap; SpxTilemapparserMgr *tilemapparser; + SpxSpineMgr *spine; SpxCallbackProxy *delay_proxy = nullptr; @@ -110,6 +112,7 @@ class SpxEngine : SpxBaseMgr { SpxPenMgr *get_pen() { return pen; } SpxTilemapMgr *get_tilemap() { return tilemap; } SpxTilemapparserMgr *get_tilemapparser() { return tilemapparser; } + SpxSpineMgr *get_spine() { return spine; } private: SceneTree *tree; diff --git a/modules/spx/spx_mgr_access.h b/modules/spx/spx_mgr_access.h index bfa305cd3eab..50dfbbde10cf 100644 --- a/modules/spx/spx_mgr_access.h +++ b/modules/spx/spx_mgr_access.h @@ -66,4 +66,7 @@ class SpxAudioBusPool; #define audioPool SpxAudioBusPool::get_singleton() #define SPX_CALLBACK SpxEngine::get_singleton()->get_callbacks() +// Spine Manager access macro +#define spineMgr SpxEngine::get_singleton()->get_spine() + #endif // SPX_MGR_ACCESS_H diff --git a/modules/spx/spx_res_mgr.cpp b/modules/spx/spx_res_mgr.cpp index 9dbf493e2f41..5b85ba9c7ba5 100644 --- a/modules/spx/spx_res_mgr.cpp +++ b/modules/spx/spx_res_mgr.cpp @@ -48,6 +48,7 @@ #include "spx_platform_mgr.h" #include "svg_mgr.h" #include "spx_engine.h" +#include "spx_spine_mgr.h" #ifdef TOOLS_ENABLED #include "editor/import/resource_importer_wav.h" #include "modules/minimp3/resource_importer_mp3.h" @@ -330,19 +331,21 @@ void SpxResMgr::set_game_datas(String path, Vector files) { game_data_root = path; platformMgr->_set_persistant_data_dir(path); update_caches(files); - svgMgr->update_caches(files); } void SpxResMgr::update_caches(const Vector& files) { - if (cached_texture.is_empty() && cached_audio.is_empty()) { - return; - } for(auto& file : files){ auto path = _to_engine_path(file); cached_texture.erase(path); cached_audio.erase(path); } + + // Sync SVG caches + svgMgr->update_caches(files); + + // Sync Spine caches + spineMgr->update_caches(files); } Ref SpxResMgr::load_audio(String path, GdBool direct) { diff --git a/modules/spx/spx_spine_mgr.cpp b/modules/spx/spx_spine_mgr.cpp new file mode 100644 index 000000000000..b5b09998669b --- /dev/null +++ b/modules/spx/spx_spine_mgr.cpp @@ -0,0 +1,182 @@ +/**************************************************************************/ +/* spx_spine_mgr.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free 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. */ +/**************************************************************************/ + +#include "spx_spine_mgr.h" +#include "spx_engine.h" +#include "spx_res_mgr.h" + +void SpxSpineMgr::on_awake() { + SpxBaseMgr::on_awake(); +} + +void SpxSpineMgr::on_reset(int reset_code) { + clear_all_caches(); +} + +String SpxSpineMgr::_to_abs_path(const String &path) { + return resMgr->_to_engine_path(path); +} + +String SpxSpineMgr::_make_cache_key(const String &atlas_path, const String &skeleton_path) { + return atlas_path + "|" + skeleton_path; +} + +Ref SpxSpineMgr::load_atlas(const String &path) { + String abs_path = _to_abs_path(path); + + // Check cache + if (cached_atlas.has(abs_path)) { + return cached_atlas[abs_path]; + } + + // Load Atlas + Ref atlas_res; + atlas_res.instantiate(); + Error err = atlas_res->load_from_atlas_file(abs_path); + + if (err != OK) { + print_error(vformat("[SpxSpineMgr] Failed to load atlas: %s, error: %d", abs_path, err)); + return Ref(); + } + + // Store in cache + cached_atlas[abs_path] = atlas_res; + print_line(vformat("[SpxSpineMgr] Atlas loaded and cached: %s", abs_path)); + + return atlas_res; +} + +Ref SpxSpineMgr::load_skeleton_file(const String &path) { + String abs_path = _to_abs_path(path); + + // Check cache + if (cached_skeleton_file.has(abs_path)) { + return cached_skeleton_file[abs_path]; + } + + // Load Skeleton file + Ref skeleton_file; + skeleton_file.instantiate(); + Error err = skeleton_file->load_from_file(abs_path); + + if (err != OK) { + print_error(vformat("[SpxSpineMgr] Failed to load skeleton file: %s, error: %d", abs_path, err)); + return Ref(); + } + + // Store in cache + cached_skeleton_file[abs_path] = skeleton_file; + print_line(vformat("[SpxSpineMgr] Skeleton file loaded and cached: %s", abs_path)); + + return skeleton_file; +} + +Ref SpxSpineMgr::load_spine_data( + const String &atlas_path, + const String &skeleton_path, + float default_mix) { + String abs_atlas = _to_abs_path(atlas_path); + String abs_skeleton = _to_abs_path(skeleton_path); + String cache_key = _make_cache_key(abs_atlas, abs_skeleton); + + // Check SkeletonData cache + if (cached_skeleton_data.has(cache_key)) { + return cached_skeleton_data[cache_key]; + } + + // Load Atlas (with cache) + Ref atlas_res = load_atlas(atlas_path); + if (!atlas_res.is_valid()) { + return Ref(); + } + + // Load Skeleton File (with cache) + Ref skeleton_file = load_skeleton_file(skeleton_path); + if (!skeleton_file.is_valid()) { + return Ref(); + } + + // Create SkeletonDataResource + Ref data_res; + data_res.instantiate(); + data_res->set_default_mix(default_mix); + data_res->set_atlas_res(atlas_res); + data_res->set_skeleton_file_res(skeleton_file); + + // Validate data was loaded successfully + if (!data_res->is_skeleton_data_loaded()) { + print_error(vformat("[SpxSpineMgr] Failed to create skeleton data from: %s", cache_key)); + return Ref(); + } + + // Store in cache + cached_skeleton_data[cache_key] = data_res; + print_line(vformat("[SpxSpineMgr] Spine data loaded and cached: %s", cache_key)); + + return data_res; +} + +void SpxSpineMgr::update_caches(const Vector &files) { + for (const String &file : files) { + String abs_path = _to_abs_path(file); + + // Check if affects Atlas cache + if (cached_atlas.has(abs_path)) { + print_line(vformat("[SpxSpineMgr] Atlas cache invalidated: %s", abs_path)); + cached_atlas.erase(abs_path); + } + + // Check if affects SkeletonFile cache + if (cached_skeleton_file.has(abs_path)) { + print_line(vformat("[SpxSpineMgr] SkeletonFile cache invalidated: %s", abs_path)); + cached_skeleton_file.erase(abs_path); + } + + // Check if affects SkeletonData cache (all combinations containing this path) + Vector keys_to_remove; + for (const auto &entry : cached_skeleton_data) { + if (entry.key.contains(abs_path)) { + keys_to_remove.push_back(entry.key); + } + } + for (const String &key : keys_to_remove) { + print_line(vformat("[SpxSpineMgr] SkeletonData cache invalidated: %s", key)); + cached_skeleton_data.erase(key); + } + } +} + +void SpxSpineMgr::clear_all_caches() { + cached_atlas.clear(); + cached_skeleton_file.clear(); + cached_skeleton_data.clear(); + print_line("[SpxSpineMgr] All caches cleared"); +} + diff --git a/modules/spx/spx_spine_mgr.h b/modules/spx/spx_spine_mgr.h new file mode 100644 index 000000000000..892fa9f6fe68 --- /dev/null +++ b/modules/spx/spx_spine_mgr.h @@ -0,0 +1,80 @@ +/**************************************************************************/ +/* spx_spine_mgr.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free 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. */ +/**************************************************************************/ + +#ifndef SPX_SPINE_MGR_H +#define SPX_SPINE_MGR_H + +#include "spx_base_mgr.h" +#include "modules/spine_godot/SpineAtlasResource.h" +#include "modules/spine_godot/SpineSkeletonFileResource.h" +#include "modules/spine_godot/SpineSkeletonDataResource.h" + +class SpxSpineMgr : public SpxBaseMgr { + SPXCLASS(SpxSpineMgr, SpxBaseMgr) + +public: + virtual ~SpxSpineMgr() = default; + +private: + // First layer cache: Atlas resources (Key: absolute path) + HashMap> cached_atlas; + + // Second layer cache: Skeleton file resources (Key: absolute path) + HashMap> cached_skeleton_file; + + // Third layer cache: Combined SkeletonData (Key: atlas_path|skeleton_path) + HashMap> cached_skeleton_data; + + String _make_cache_key(const String &atlas_path, const String &skeleton_path); + String _to_abs_path(const String &path); + +public: + void on_awake() override; + void on_reset(int reset_code) override; + + // Main load method - external entry point + Ref load_spine_data( + const String &atlas_path, + const String &skeleton_path, + float default_mix = 0.1f); + + // Individual load methods - with caching + Ref load_atlas(const String &path); + Ref load_skeleton_file(const String &path); + + // Cache management + void update_caches(const Vector &files); + +public: + void clear_all_caches(); +}; + +#endif // SPX_SPINE_MGR_H + diff --git a/modules/spx/spx_sprite.cpp b/modules/spx/spx_sprite.cpp index 3ba9e2d8c757..d5ed1ed158f6 100644 --- a/modules/spx/spx_sprite.cpp +++ b/modules/spx/spx_sprite.cpp @@ -46,8 +46,18 @@ #include "spx_res_mgr.h" #include "spx_sprite_mgr.h" #include "spx_camera_mgr.h" +#include "spx_spine_mgr.h" #include "svg_mgr.h" +// Spine includes +#include "modules/spine_godot/SpineSprite.h" +#include "modules/spine_godot/SpineAnimationState.h" +#include "modules/spine_godot/SpineSkeleton.h" +#include "modules/spine_godot/SpineTrackEntry.h" +#include "modules/spine_godot/SpineEvent.h" +#include "modules/spine_godot/SpineEventData.h" +#include "modules/spine_godot/SpineAnimation.h" + void SpxStaticSprite::_notification(int p_what) { switch (p_what) { @@ -182,6 +192,10 @@ void SpxSprite::_draw() { if (!Spx::debug_mode) { return; } + + // Draw position marker (green circle) + draw_circle(Vector2(0, 0), 5.0f, Color(0, 1, 0, 0.8)); + if (trigger2d != nullptr) { trigger2d->set_spx_debug_color(Color(1, 0, 0, 0.2)); } @@ -359,6 +373,10 @@ void SpxSprite::on_sprite_screen_entered() { } void SpxSprite::set_color(GdColor color) { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + spine_child->set_modulate(color); + return; + } default_material->set_shader_parameter("color", color); } @@ -536,6 +554,23 @@ GdString SpxSprite::get_current_anim_name() { } void SpxSprite::play_anim(GdString p_name, GdFloat p_speed, GdBool isLoop, GdBool p_from_end) { + // Spine mode branch + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto anim_state = spine_child->get_animation_state(); + if (anim_state.is_valid()) { + String anim_name = SpxStr(p_name); + auto entry = anim_state->set_animation(anim_name, isLoop, 0); + if (entry.is_valid()) { + entry->set_time_scale(p_speed); + if (p_from_end) { + entry->set_reverse(true); + } + } + } + return; + } + + // Frame animation mode (original logic) String anim_name = SpxStr(p_name); // Enhanced: Check if we need to use a scaled version of the animation String final_anim_key; @@ -580,14 +615,33 @@ void SpxSprite::play_backwards_anim(GdString p_name) { } void SpxSprite::pause_anim() { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + spine_child->set_time_scale(0); + return; + } anim2d->pause(); } void SpxSprite::stop_anim() { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto anim_state = spine_child->get_animation_state(); + if (anim_state.is_valid()) { + anim_state->set_empty_animation(0, 0); + } + return; + } anim2d->stop(); } GdBool SpxSprite::is_playing_anim() const { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto anim_state = spine_child->get_animation_state(); + if (anim_state.is_valid()) { + auto entry = anim_state->get_current(0); + return entry.is_valid() && !entry->is_complete(); + } + return false; + } return anim2d->is_playing(); } @@ -602,10 +656,18 @@ GdString SpxSprite::get_anim() const { } void SpxSprite::set_anim_frame(GdInt p_frame) { + if (animation_mode == ANIM_MODE_SPINE) { + print_line("[SPX Warning] set_anim_frame() is not supported in Spine mode. Spine uses time-based animation."); + return; + } anim2d->set_frame(p_frame); } GdInt SpxSprite::get_anim_frame() const { + if (animation_mode == ANIM_MODE_SPINE) { + print_line("[SPX Warning] get_anim_frame() is not supported in Spine mode. Spine uses time-based animation."); + return 0; + } return anim2d->get_frame(); } @@ -639,18 +701,46 @@ GdVec2 SpxSprite::get_anim_offset() const { } void SpxSprite::set_anim_flip_h(GdBool p_flip) { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto skeleton = spine_child->get_skeleton(); + if (skeleton.is_valid()) { + skeleton->set_scale_x(p_flip ? -1.0f : 1.0f); + } + return; + } anim2d->set_flip_h(p_flip); } GdBool SpxSprite::is_anim_flipped_h() const { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto skeleton = spine_child->get_skeleton(); + if (skeleton.is_valid()) { + return skeleton->get_scale_x() < 0; + } + return false; + } return anim2d->is_flipped_h(); } void SpxSprite::set_anim_flip_v(GdBool p_flip) { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto skeleton = spine_child->get_skeleton(); + if (skeleton.is_valid()) { + skeleton->set_scale_y(p_flip ? -1.0f : 1.0f); + } + return; + } anim2d->set_flip_v(p_flip); } GdBool SpxSprite::is_anim_flipped_v() const { + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + auto skeleton = spine_child->get_skeleton(); + if (skeleton.is_valid()) { + return skeleton->get_scale_y() < 0; + } + return false; + } return anim2d->is_flipped_v(); } @@ -811,6 +901,12 @@ GdBool SpxSprite::check_collision_with_point(GdVec2 point, GdBool is_trigger) { void SpxSprite::set_render_scale(GdVec2 new_scale) { _render_scale = new_scale; + if (animation_mode == ANIM_MODE_SPINE && spine_child) { + spine_child->set_scale(new_scale); + // Update Spine collision box: recalculate and apply scale + _update_spine_collision_with_scale(); + return; + } update_anim_scale(); } @@ -1083,3 +1179,254 @@ void SpxSprite::_disable_collision() { } } +// ============================================================================ +// Spine mode implementation +// ============================================================================ + +void SpxSprite::set_spine_skeleton(GdString atlas_path, GdString skeleton_path, GdFloat default_mix) { + String atlas_str = SpxStr(atlas_path); + String skeleton_str = SpxStr(skeleton_path); + + // 1. If there's an existing Spine child, destroy it first + if (spine_child != nullptr) { + _destroy_spine_child(); + } + + // 2. Load Spine data + spine_data = spineMgr->load_spine_data(atlas_str, skeleton_str, default_mix); + if (!spine_data.is_valid() || !spine_data->is_skeleton_data_loaded()) { + print_error(vformat("[SpxSprite] Failed to load Spine data: atlas=%s, skeleton=%s", atlas_str, skeleton_str)); + return; + } + + // 3. Initialize Spine child node + _init_spine_child(); + + // 4. Switch mode + animation_mode = ANIM_MODE_SPINE; + + // 5. Hide frame animation component + if (anim2d) { + anim2d->hide(); + } + + // 6. Calculate initial collision shape + _calculate_spine_collision_shape(); + + print_line(vformat("[SpxSprite] Spine mode enabled: gid=%d", gid)); +} + +void SpxSprite::_init_spine_child() { + spine_child = memnew(SpineSprite); + spine_child->set_skeleton_data_res(spine_data); + spine_child->set_name("SpineChild"); + add_child(spine_child); + + // Setup event bindings + _setup_spine_event_bindings(); + + spine_initialized = true; +} + +void SpxSprite::_destroy_spine_child() { + if (spine_child != nullptr) { + spine_child->queue_free(); + spine_child = nullptr; + } + spine_initialized = false; +} + +void SpxSprite::clear_spine_skeleton() { + _destroy_spine_child(); + spine_data.unref(); + animation_mode = ANIM_MODE_FRAME; + + // Show frame animation component + if (anim2d) { + anim2d->show(); + } +} + +void SpxSprite::_calculate_spine_collision_shape() { + if (!spine_child) { + print_error("[SpxSprite] Cannot calculate spine collision: spine_child is null"); + return; + } + + auto skeleton = spine_child->get_skeleton(); + if (!skeleton.is_valid()) { + print_error("[SpxSprite] Cannot calculate spine collision: skeleton is invalid"); + return; + } + + // Calculate bounds using setup pose + skeleton->set_to_setup_pose(); + skeleton->update_world_transform(SpineConstant::Physics_Update); + + Rect2 bounds = skeleton->get_bounds(); + + print_line(vformat("[SpxSprite] Spine raw bounds: pos=(%f, %f), size=(%f, %f)", + bounds.position.x, bounds.position.y, bounds.size.x, bounds.size.y)); + + if (bounds.size.x > 0 && bounds.size.y > 0) { + // spine-godot has already set Bone::setYDown(true) + // So get_bounds() returns coordinates in Godot coordinate system (Y-axis down), no need to flip + Vector2 center( + bounds.position.x + bounds.size.x / 2.0f, + bounds.position.y + bounds.size.y / 2.0f + ); + Vector2 size(bounds.size.x, bounds.size.y); + + set_collider_rect(center, size); + set_trigger_rect(center, size); + + print_line(vformat("[SpxSprite] Spine collision shape set: center=(%f, %f), size=(%f, %f)", + center.x, center.y, size.x, size.y)); + } else { + _calculate_spine_collision_shape_fallback(); + } +} + +void SpxSprite::_update_spine_collision_with_scale() { + if (!spine_child) { + return; + } + + auto skeleton = spine_child->get_skeleton(); + if (!skeleton.is_valid()) { + return; + } + + // Calculate bounds using setup pose + skeleton->set_to_setup_pose(); + skeleton->update_world_transform(SpineConstant::Physics_Update); + + Rect2 bounds = skeleton->get_bounds(); + + if (bounds.size.x > 0 && bounds.size.y > 0) { + // spine-godot has already set Bone::setYDown(true) + // So get_bounds() returns coordinates in Godot coordinate system (Y-axis down), no need to flip + // Apply render_scale scaling + Vector2 center( + (bounds.position.x + bounds.size.x / 2.0f) * _render_scale.x, + (bounds.position.y + bounds.size.y / 2.0f) * _render_scale.y + ); + Vector2 size( + bounds.size.x * _render_scale.x, + bounds.size.y * _render_scale.y + ); + + set_collider_rect(center, size); + set_trigger_rect(center, size); + + } +} + +// ============================================================================ +// Spine event handling +// ============================================================================ + +void SpxSprite::_setup_spine_event_bindings() { + if (!spine_child) return; + + spine_child->connect("animation_started", callable_mp(this, &SpxSprite::_on_spine_animation_started)); + spine_child->connect("animation_completed", callable_mp(this, &SpxSprite::_on_spine_animation_completed)); + spine_child->connect("animation_event", callable_mp(this, &SpxSprite::_on_spine_animation_event)); +} + +void SpxSprite::_on_spine_animation_started(SpineSprite* sprite, Ref state, Ref entry) { + if (!Spx::initialed) return; + if (!entry.is_valid()) return; + + // Get animation name for debug logging + auto anim = entry->get_animation(); + String anim_name = anim.is_valid() ? anim->get_name() : ""; + print_line(vformat("[SpxSprite] Spine animation_started: gid=%d, animation=%s", gid, anim_name)); + + // Trigger SPX callback: animation changed + SPX_CALLBACK->func_on_sprite_animation_changed(this->gid); +} + +void SpxSprite::_on_spine_animation_completed(SpineSprite* sprite, Ref state, Ref entry) { + if (!Spx::initialed) return; + if (!entry.is_valid()) return; + + // Get animation info for debug logging + auto anim = entry->get_animation(); + String anim_name = anim.is_valid() ? anim->get_name() : ""; + bool is_loop = entry->get_loop(); + + // Dispatch different callbacks based on loop state + if (is_loop) { + // Looping animation completed one cycle + print_line(vformat("[SpxSprite] Spine animation_looped: gid=%d, animation=%s", gid, anim_name)); + SPX_CALLBACK->func_on_sprite_animation_looped(this->gid); + } else { + // Non-looping animation finished + print_line(vformat("[SpxSprite] Spine animation_finished: gid=%d, animation=%s", gid, anim_name)); + SPX_CALLBACK->func_on_sprite_animation_finished(this->gid); + } +} + +void SpxSprite::_on_spine_animation_event(SpineSprite* sprite, Ref state, Ref entry, Ref event) { + if (!Spx::initialed) return; + if (!event.is_valid()) return; + + // Extract event data for logging + auto event_data = event->get_data(); + String event_name = event_data.is_valid() ? event_data->get_event_name() : ""; + int int_value = event->get_int_value(); + float float_value = event->get_float_value(); + String string_value = event->get_string_value(); + + // Debug log for Spine custom event + print_line(vformat("[SpxSprite] Spine animation_event: gid=%d, event=%s, int=%d, float=%f, string=%s", + gid, event_name, int_value, float_value, string_value)); + + // Optional: Call new Spine event callback (requires adding new interface) + // SPX_CALLBACK->func_on_spine_event(this->gid, SpxReturnStr(event_name), int_value, float_value); +} + +// ============================================================================ +// Spine object access +// ============================================================================ + +Ref SpxSprite::get_spine_skeleton() { + if (spine_child) { + return spine_child->get_skeleton(); + } + return Ref(); +} + +Ref SpxSprite::get_spine_animation_state() { + if (spine_child) { + return spine_child->get_animation_state(); + } + return Ref(); +} + +void SpxSprite::_calculate_spine_collision_shape_fallback() { + if (!spine_data.is_valid()) { + print_line("[SpxSprite] Warning: No spine data for fallback, using default collision box"); + set_collider_rect(Vector2(0, 0), Vector2(100, 100)); + set_trigger_rect(Vector2(0, 0), Vector2(100, 100)); + return; + } + + float width = spine_data->get_width(); + float height = spine_data->get_height(); + + if (width > 0 && height > 0) { + Vector2 center(spine_data->get_x(), spine_data->get_y()); + Vector2 size(width, height); + set_collider_rect(center, size); + set_trigger_rect(center, size); + print_line(vformat("[SpxSprite] Spine collision (fallback): center=(%f, %f), size=(%f, %f)", + center.x, center.y, size.x, size.y)); + } else { + print_line("[SpxSprite] Warning: Spine skeleton_data has no valid bounds, using default"); + set_collider_rect(Vector2(0, 0), Vector2(100, 100)); + set_trigger_rect(Vector2(0, 0), Vector2(100, 100)); + } +} + diff --git a/modules/spx/spx_sprite.h b/modules/spx/spx_sprite.h index ea04483be715..a7db6a9ee4c4 100644 --- a/modules/spx/spx_sprite.h +++ b/modules/spx/spx_sprite.h @@ -45,6 +45,14 @@ class Area2D; class CollisionShape2D; class VisibleOnScreenNotifier2D; +// Spine forward declarations +class SpineSprite; +class SpineSkeletonDataResource; +class SpineAnimationState; +class SpineSkeleton; +class SpineTrackEntry; +class SpineEvent; + // Interface for sortable sprites class ISortableSprite { public: @@ -126,6 +134,12 @@ class SpxSprite : public CharacterBody2D, public ISortableSprite { STATIC = 3, // Static immovable, but has collision, affects other objects }; + // Animation mode enumeration + enum AnimationMode { + ANIM_MODE_FRAME = 0, // Frame animation mode (default) + ANIM_MODE_SPINE = 1, // Spine skeletal animation mode + }; + private: GdObj gid; @@ -166,6 +180,25 @@ class SpxSprite : public CharacterBody2D, public ISortableSprite { String current_svg_path; // Name of the current SVG animation (image) String current_svg_anim_key; // Name of the current SVG animation String current_anim_name = ""; // Name of the current animation + + // Spine mode related + AnimationMode animation_mode = ANIM_MODE_FRAME; + SpineSprite *spine_child = nullptr; + Ref spine_data; + bool spine_initialized = false; + + // Spine internal methods + void _init_spine_child(); + void _destroy_spine_child(); + void _setup_spine_event_bindings(); + void _calculate_spine_collision_shape(); + void _calculate_spine_collision_shape_fallback(); + void _update_spine_collision_with_scale(); + + // Spine event callbacks (signature matches SpineSprite signal: sprite, state, entry, [event]) + void _on_spine_animation_started(SpineSprite* sprite, Ref state, Ref entry); + void _on_spine_animation_completed(SpineSprite* sprite, Ref state, Ref entry); + void _on_spine_animation_event(SpineSprite* sprite, Ref state, Ref entry, Ref event); void update_anim_scale(); @@ -339,6 +372,17 @@ class SpxSprite : public CharacterBody2D, public ISortableSprite { void set_pivot(GdVec2 pivot){pivot_offset = pivot;} GdVec2 get_pivot(){return pivot_offset;} + // Spine mode control + void set_spine_skeleton(GdString atlas_path, GdString skeleton_path, GdFloat default_mix = 0.1f); + void clear_spine_skeleton(); + bool is_spine_mode() const { return animation_mode == ANIM_MODE_SPINE; } + AnimationMode get_animation_mode() const { return animation_mode; } + + // Get Spine internal objects (advanced usage) + SpineSprite *get_spine_sprite() { return spine_child; } + Ref get_spine_skeleton(); + Ref get_spine_animation_state(); + // ISortableSprite interface implementation GdObj get_sort_id() const override { return gid; } Point2 get_sort_position() const override { return get_global_position() - pivot_offset; } diff --git a/modules/spx/spx_sprite_mgr.cpp b/modules/spx/spx_sprite_mgr.cpp index ea2a3f07c8d1..a3261fba8f6a 100644 --- a/modules/spx/spx_sprite_mgr.cpp +++ b/modules/spx/spx_sprite_mgr.cpp @@ -588,6 +588,16 @@ GdString SpxSpriteMgr::get_current_anim_name(GdObj obj) { return sprite->get_current_anim_name(); } +void SpxSpriteMgr::set_spine_skeleton(GdObj obj, GdString atlas_path, GdString skeleton_path, GdFloat default_mix) { + check_and_get_sprite_v() + sprite->set_spine_skeleton(atlas_path, skeleton_path, default_mix); +} + +void SpxSpriteMgr::clear_spine_skeleton(GdObj obj) { + check_and_get_sprite_v() + sprite->clear_spine_skeleton(); +} + void SpxSpriteMgr::set_velocity(GdObj obj, GdVec2 velocity) { check_and_get_sprite_v() // flip y axis diff --git a/modules/spx/spx_sprite_mgr.h b/modules/spx/spx_sprite_mgr.h index e6c10c1fc357..334a64e6f71c 100644 --- a/modules/spx/spx_sprite_mgr.h +++ b/modules/spx/spx_sprite_mgr.h @@ -210,6 +210,10 @@ class SpxSpriteMgr : public SpxBaseMgr { void set_anim_flip_v(GdObj obj, GdBool p_flip); GdBool is_anim_flipped_v(GdObj obj); GdString get_current_anim_name(GdObj obj); + + // spine + void set_spine_skeleton(GdObj obj, GdString atlas_path, GdString skeleton_path, GdFloat default_mix); + void clear_spine_skeleton(GdObj obj); // physics void set_velocity(GdObj obj, GdVec2 velocity); diff --git a/platform/web/SCsub b/platform/web/SCsub index 12e23bd1f261..66c421a3ee0f 100644 --- a/platform/web/SCsub +++ b/platform/web/SCsub @@ -38,6 +38,9 @@ web_files = [ if env["target"] == "editor": env.add_source_files(web_files, "editor/*.cpp") +# Add spine-cpp include path for spine_godot integration (needed by godot_js_spx.cpp) +env.Append(CPPPATH=["#modules/spine_godot/spine-cpp/include"]) + sys_env = env.Clone() sys_env.AddJSLibraries( [ diff --git a/platform/web/godot_js_spx.cpp b/platform/web/godot_js_spx.cpp index b137c7d40075..3723da540ad0 100644 --- a/platform/web/godot_js_spx.cpp +++ b/platform/web/godot_js_spx.cpp @@ -46,6 +46,7 @@ #include "modules/spx/spx_platform_mgr.h" #include "modules/spx/spx_res_mgr.h" #include "modules/spx/spx_scene_mgr.h" +#include "modules/spx/spx_spine_mgr.h" #include "modules/spx/spx_sprite_mgr.h" #include "modules/spx/spx_tilemap_mgr.h" #include "modules/spx/spx_tilemapparser_mgr.h" @@ -63,6 +64,7 @@ #define platformMgr SpxEngine::get_singleton()->get_platform() #define resMgr SpxEngine::get_singleton()->get_res() #define sceneMgr SpxEngine::get_singleton()->get_scene() +#define spineMgr SpxEngine::get_singleton()->get_spine() #define spriteMgr SpxEngine::get_singleton()->get_sprite() #define tilemapMgr SpxEngine::get_singleton()->get_tilemap() #define tilemapparserMgr SpxEngine::get_singleton()->get_tilemapparser() @@ -545,6 +547,10 @@ void gdspx_scene_create_static_sprite(GdString* texture_path, GdVec2* pos, GdFlo *ret_val = sceneMgr->create_static_sprite(*texture_path, *pos, *degree, *scale, *zindex, *pivot, *collider_type, *collider_pivot, *collider_params); } EMSCRIPTEN_KEEPALIVE +void gdspx_spine_clear_all_caches() { + spineMgr->clear_all_caches(); +} +EMSCRIPTEN_KEEPALIVE void gdspx_sprite_set_dont_destroy_on_load(GdObj* obj) { spriteMgr->set_dont_destroy_on_load(*obj); } @@ -817,6 +823,14 @@ void gdspx_sprite_get_current_anim_name(GdObj* obj, GdString *ret_val) { *ret_val = spriteMgr->get_current_anim_name(*obj); } EMSCRIPTEN_KEEPALIVE +void gdspx_sprite_set_spine_skeleton(GdObj* obj, GdString* atlas_path, GdString* skeleton_path, GdFloat* default_mix) { + spriteMgr->set_spine_skeleton(*obj, *atlas_path, *skeleton_path, *default_mix); +} +EMSCRIPTEN_KEEPALIVE +void gdspx_sprite_clear_spine_skeleton(GdObj* obj) { + spriteMgr->clear_spine_skeleton(*obj); +} +EMSCRIPTEN_KEEPALIVE void gdspx_sprite_set_velocity(GdObj* obj, GdVec2* velocity) { spriteMgr->set_velocity(*obj, *velocity); } diff --git a/platform/web/js/engine/gdspx.js b/platform/web/js/engine/gdspx.js index 8188da1d172d..ab5b5b1eeefb 100644 --- a/platform/web/js/engine/gdspx.js +++ b/platform/web/js/engine/gdspx.js @@ -1123,6 +1123,12 @@ gdspx_scene_create_static_sprite(texture_path,pos,degree,scale,zindex,pivot,coll FreeGdObj(_retValue); return _finalRetValue } +gdspx_spine_clear_all_caches() { + var _gdFuncPtr = Module._gdspx_spine_clear_all_caches; + + _gdFuncPtr(); + +} gdspx_sprite_set_dont_destroy_on_load(obj) { var _gdFuncPtr = Module._gdspx_sprite_set_dont_destroy_on_load; @@ -1851,6 +1857,28 @@ gdspx_sprite_get_current_anim_name(obj) { FreeGdString(_retValue); return _finalRetValue } +gdspx_sprite_set_spine_skeleton(obj,atlas_path,skeleton_path,default_mix) { + var _gdFuncPtr = Module._gdspx_sprite_set_spine_skeleton; + + var _arg0 = ToGdObj(obj); + var _arg1 = ToGdString(atlas_path); + var _arg2 = ToGdString(skeleton_path); + var _arg3 = ToGdFloat(default_mix); + _gdFuncPtr(_arg0, _arg1, _arg2, _arg3); + FreeGdObj(_arg0); + FreeGdString(_arg1); + FreeGdString(_arg2); + FreeGdFloat(_arg3); + +} +gdspx_sprite_clear_spine_skeleton(obj) { + var _gdFuncPtr = Module._gdspx_sprite_clear_spine_skeleton; + + var _arg0 = ToGdObj(obj); + _gdFuncPtr(_arg0); + FreeGdObj(_arg0); + +} gdspx_sprite_set_velocity(obj,velocity) { var _gdFuncPtr = Module._gdspx_sprite_set_velocity;