From 772f69be41b4a2705a3573c50060ede7089832d8 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Wed, 22 Sep 2021 00:21:59 -1000 Subject: [PATCH] Initial commit for spring constraints. --- .../com/esotericsoftware/spine/Skeleton.java | 130 ++++++++++---- .../esotericsoftware/spine/SkeletonData.java | 20 +++ .../spine/SpringConstraint.java | 168 ++++++++++++++++++ .../spine/SpringConstraintData.java | 115 ++++++++++++ 4 files changed, 400 insertions(+), 33 deletions(-) create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraintData.java diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index f02e6653f..87dea2e5e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -55,6 +55,7 @@ public class Skeleton { final Array ikConstraints; final Array transformConstraints; final Array pathConstraints; + final Array springConstraints; final Array updateCache = new Array(); @Null Skin skin; final Color color; @@ -101,6 +102,10 @@ public class Skeleton { for (PathConstraintData pathConstraintData : data.pathConstraints) pathConstraints.add(new PathConstraint(pathConstraintData, this)); + springConstraints = new Array(data.springConstraints.size); + for (SpringConstraintData springConstraintData : data.springConstraints) + springConstraints.add(new SpringConstraint(springConstraintData, this)); + color = new Color(1, 1, 1, 1); updateCache(); @@ -146,6 +151,10 @@ public class Skeleton { for (PathConstraint pathConstraint : skeleton.pathConstraints) pathConstraints.add(new PathConstraint(pathConstraint, this)); + springConstraints = new Array(skeleton.springConstraints.size); + for (SpringConstraint springConstraint : skeleton.springConstraints) + springConstraints.add(new SpringConstraint(springConstraint, this)); + skin = skeleton.skin; color = new Color(skeleton.color); time = skeleton.time; @@ -180,11 +189,11 @@ public class Skeleton { } } - int ikCount = ikConstraints.size, transformCount = transformConstraints.size, pathCount = pathConstraints.size; - Object[] ikConstraints = this.ikConstraints.items; - Object[] transformConstraints = this.transformConstraints.items; - Object[] pathConstraints = this.pathConstraints.items; - int constraintCount = ikCount + transformCount + pathCount; + int ikCount = ikConstraints.size, transformCount = transformConstraints.size, pathCount = pathConstraints.size, + springCount = springConstraints.size; + Object[] ikConstraints = this.ikConstraints.items, transformConstraints = this.transformConstraints.items, + pathConstraints = this.pathConstraints.items, springConstraints = this.springConstraints.items; + int constraintCount = ikCount + transformCount + pathCount + springCount; outer: for (int i = 0; i < constraintCount; i++) { for (int ii = 0; ii < ikCount; ii++) { @@ -208,6 +217,13 @@ public class Skeleton { continue outer; } } + for (int ii = 0; ii < springCount; ii++) { + SpringConstraint constraint = (SpringConstraint)springConstraints[ii]; + if (constraint.data.order == i) { + sortSpringConstraint(constraint); + continue outer; + } + } } for (int i = 0; i < boneCount; i++) @@ -239,34 +255,6 @@ public class Skeleton { } } - private void sortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.getData().index; - Bone slotBone = slot.bone; - if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment instanceof PathAttachment) sortPathConstraintAttachment(attachment, slotBone); - - Object[] constrained = constraint.bones.items; - int boneCount = constraint.bones.size; - for (int i = 0; i < boneCount; i++) - sortBone((Bone)constrained[i]); - - updateCache.add(constraint); - - for (int i = 0; i < boneCount; i++) - sortReset(((Bone)constrained[i]).children); - for (int i = 0; i < boneCount; i++) - ((Bone)constrained[i]).sorted = true; - } - private void sortTransformConstraint (TransformConstraint constraint) { constraint.active = constraint.target.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); @@ -295,6 +283,34 @@ public class Skeleton { ((Bone)constrained[i]).sorted = true; } + private void sortPathConstraint (PathConstraint constraint) { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.getData().index; + Bone slotBone = slot.bone; + if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment instanceof PathAttachment) sortPathConstraintAttachment(attachment, slotBone); + + Object[] constrained = constraint.bones.items; + int boneCount = constraint.bones.size; + for (int i = 0; i < boneCount; i++) + sortBone((Bone)constrained[i]); + + updateCache.add(constraint); + + for (int i = 0; i < boneCount; i++) + sortReset(((Bone)constrained[i]).children); + for (int i = 0; i < boneCount; i++) + ((Bone)constrained[i]).sorted = true; + } + private void sortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { Object[] entries = skin.attachments.orderedItems().items; for (int i = 0, n = skin.attachments.size; i < n; i++) { @@ -319,6 +335,23 @@ public class Skeleton { } } + private void sortSpringConstraint (SpringConstraint constraint) { + constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)); + if (!constraint.active) return; + + Object[] constrained = constraint.bones.items; + int boneCount = constraint.bones.size; + for (int i = 0; i < boneCount; i++) + sortBone((Bone)constrained[i]); + + updateCache.add(constraint); + + for (int i = 0; i < boneCount; i++) + sortReset(((Bone)constrained[i]).children); + for (int i = 0; i < boneCount; i++) + ((Bone)constrained[i]).sorted = true; + } + private void sortBone (Bone bone) { if (bone.sorted) return; Bone parent = bone.parent; @@ -435,6 +468,20 @@ public class Skeleton { constraint.mixX = data.mixX; constraint.mixY = data.mixY; } + + Object[] springConstraints = this.springConstraints.items; + for (int i = 0, n = this.springConstraints.size; i < n; i++) { + SpringConstraint constraint = (SpringConstraint)springConstraints[i]; + SpringConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.friction = data.friction; + constraint.gravity = data.gravity; + constraint.wind = data.wind; + constraint.stiffness = data.stiffness; + constraint.damping = data.damping; + constraint.rope = data.rope; + constraint.stretch = data.stretch; + } } /** Sets the slots and draw order to their setup pose values. */ @@ -641,6 +688,23 @@ public class Skeleton { return null; } + /** The skeleton's spring constraints. */ + public Array getSpringConstraints () { + return springConstraints; + } + + /** Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + * method than to call it repeatedly. */ + public @Null SpringConstraint findSpringConstraint (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Object[] springConstraints = this.springConstraints.items; + for (int i = 0, n = this.springConstraints.size; i < n; i++) { + SpringConstraint constraint = (SpringConstraint)springConstraints[i]; + if (constraint.data.name.equals(constraintName)) return constraint; + } + return null; + } + /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB. * @param size An output value, the width and height of the AABB. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java index d60ee4a41..6c3a82857 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -47,6 +47,7 @@ public class SkeletonData { final Array ikConstraints = new Array(); final Array transformConstraints = new Array(); final Array pathConstraints = new Array(); + final Array springConstraints = new Array(); float x, y, width, height; @Null String version, hash; @@ -215,6 +216,25 @@ public class SkeletonData { return null; } + // --- Spring constraints + + /** The skeleton's spring constraints. */ + public Array getSpringConstraints () { + return springConstraints; + } + + /** Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + * method than to call it multiple times. */ + public @Null SpringConstraintData findSpringConstraint (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Object[] springConstraints = this.springConstraints.items; + for (int i = 0, n = this.springConstraints.size; i < n; i++) { + SpringConstraintData constraint = (SpringConstraintData)springConstraints[i]; + if (constraint.name.equals(constraintName)) return constraint; + } + return null; + } + // --- /** The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java new file mode 100644 index 000000000..9ca091503 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java @@ -0,0 +1,168 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.utils.Array; + +/** Stores the current pose for a spring constraint. A spring constraint applies physics to bones. + *

+ * See Spring constraints in the Spine User Guide. */ +public class SpringConstraint implements Updatable { + final SpringConstraintData data; + final Array bones; + float mix, friction, gravity, wind, stiffness, damping; + boolean rope, stretch; + + boolean active; + + public SpringConstraint (SpringConstraintData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + this.data = data; + mix = data.mix; + friction = data.friction; + gravity = data.gravity; + wind = data.wind; + stiffness = data.stiffness; + damping = data.damping; + rope = data.rope; + stretch = data.stretch; + + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) + bones.add(skeleton.bones.get(boneData.index)); + } + + /** Copy constructor. */ + public SpringConstraint (SpringConstraint constraint, Skeleton skeleton) { + if (constraint == null) throw new IllegalArgumentException("constraint cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + data = constraint.data; + bones = new Array(constraint.bones.size); + for (Bone bone : constraint.bones) + bones.add(skeleton.bones.get(bone.data.index)); + mix = constraint.mix; + friction = constraint.friction; + gravity = constraint.gravity; + wind = constraint.wind; + stiffness = constraint.stiffness; + damping = constraint.damping; + rope = constraint.rope; + stretch = constraint.stretch; + } + + /** Applies the constraint to the constrained bones. */ + public void update () { + + } + + /** The bones that will be modified by this spring constraint. */ + public Array getBones () { + return bones; + } + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */ + public float getMix () { + return mix; + } + + public void setMix (float mix) { + this.mix = mix; + } + + public float getFriction () { + return friction; + } + + public void setFriction (float friction) { + this.friction = friction; + } + + public float getGravity () { + return gravity; + } + + public void setGravity (float gravity) { + this.gravity = gravity; + } + + public float getWind () { + return wind; + } + + public void setWind (float wind) { + this.wind = wind; + } + + public float getStiffness () { + return stiffness; + } + + public void setStiffness (float stiffness) { + this.stiffness = stiffness; + } + + public float getDamping () { + return damping; + } + + public void setDamping (float damping) { + this.damping = damping; + } + + public boolean getRope () { + return rope; + } + + public void setRope (boolean rope) { + this.rope = rope; + } + + public boolean getStretch () { + return stretch; + } + + public void setStretch (boolean stretch) { + this.stretch = stretch; + } + + public boolean isActive () { + return active; + } + + /** The spring constraint's setup pose data. */ + public SpringConstraintData getData () { + return data; + } + + public String toString () { + return data.name; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraintData.java new file mode 100644 index 000000000..1ee2bc183 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraintData.java @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.utils.Array; + +/** Stores the setup pose for a {@link SpringConstraint}. + *

+ * See Spring constraints in the Spine User Guide. */ +public class SpringConstraintData extends ConstraintData { + final Array bones = new Array(); + float mix, friction, gravity, wind, stiffness, damping; + boolean rope, stretch; + + public SpringConstraintData (String name) { + super(name); + } + + /** The bones that are constrained by this spring constraint. */ + public Array getBones () { + return bones; + } + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */ + public float getMix () { + return mix; + } + + public void setMix (float mix) { + this.mix = mix; + } + + public float getFriction () { + return friction; + } + + public void setFriction (float friction) { + this.friction = friction; + } + + public float getGravity () { + return gravity; + } + + public void setGravity (float gravity) { + this.gravity = gravity; + } + + public float getWind () { + return wind; + } + + public void setWind (float wind) { + this.wind = wind; + } + + public float getStiffness () { + return stiffness; + } + + public void setStiffness (float stiffness) { + this.stiffness = stiffness; + } + + public float getDamping () { + return damping; + } + + public void setDamping (float damping) { + this.damping = damping; + } + + public boolean getRope () { + return rope; + } + + public void setRope (boolean rope) { + this.rope = rope; + } + + public boolean getStretch () { + return stretch; + } + + public void setStretch (boolean stretch) { + this.stretch = stretch; + } +}