This commit is contained in:
JA
2026-06-27 03:37:16 +08:00
parent 7a8d4a5d83
commit 3059e71422
465 changed files with 2675 additions and 58818 deletions

View File

@@ -1,635 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class Skeleton {
internal SkeletonData data;
internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots;
internal ExposedList<Slot> drawOrder;
internal ExposedList<IkConstraint> ikConstraints;
internal ExposedList<TransformConstraint> transformConstraints;
internal ExposedList<PathConstraint> pathConstraints;
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
internal float time;
private float scaleX = 1, scaleY = 1;
internal float x, y;
public SkeletonData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } }
public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
public ExposedList<Slot> Slots { get { return slots; } }
public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
public Skin Skin { get { return skin; } set { SetSkin(value); } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public float Time { get { return time; } set { time = value; } }
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
public Bone RootBone {
get { return bones.Count == 0 ? null : bones.Items[0]; }
}
public Skeleton (SkeletonData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data;
bones = new ExposedList<Bone>(data.bones.Count);
foreach (BoneData boneData in data.bones) {
Bone bone;
if (boneData.parent == null) {
bone = new Bone(boneData, this, null);
} else {
Bone parent = bones.Items[boneData.parent.index];
bone = new Bone(boneData, this, parent);
parent.children.Add(bone);
}
bones.Add(bone);
}
slots = new ExposedList<Slot>(data.slots.Count);
drawOrder = new ExposedList<Slot>(data.slots.Count);
foreach (SlotData slotData in data.slots) {
Bone bone = bones.Items[slotData.boneData.index];
Slot slot = new Slot(slotData, bone);
slots.Add(slot);
drawOrder.Add(slot);
}
ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
foreach (IkConstraintData ikConstraintData in data.ikConstraints)
ikConstraints.Add(new IkConstraint(ikConstraintData, this));
transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
pathConstraints = new ExposedList<PathConstraint> (data.pathConstraints.Count);
foreach (PathConstraintData pathConstraintData in data.pathConstraints)
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
UpdateCache();
UpdateWorldTransform();
}
/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
/// constraints, or weighted path attachments are added or removed.</summary>
public void UpdateCache () {
var updateCache = this.updateCache;
updateCache.Clear();
this.updateCacheReset.Clear();
int boneCount = this.bones.Items.Length;
var bones = this.bones;
for (int i = 0; i < boneCount; i++) {
Bone bone = bones.Items[i];
bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted;
}
if (skin != null) {
Object[] skinBones = skin.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++) {
Bone bone = (Bone)bones.Items[((BoneData)skinBones[i]).index];
do {
bone.sorted = false;
bone.active = true;
bone = bone.parent;
} while (bone != null);
}
}
int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count;
var ikConstraints = this.ikConstraints;
var transformConstraints = this.transformConstraints;
var pathConstraints = this.pathConstraints;
int constraintCount = ikCount + transformCount + pathCount;
//outer:
for (int i = 0; i < constraintCount; i++) {
for (int ii = 0; ii < ikCount; ii++) {
IkConstraint constraint = ikConstraints.Items[ii];
if (constraint.data.order == i) {
SortIkConstraint(constraint);
goto continue_outer; //continue outer;
}
}
for (int ii = 0; ii < transformCount; ii++) {
TransformConstraint constraint = transformConstraints.Items[ii];
if (constraint.data.order == i) {
SortTransformConstraint(constraint);
goto continue_outer; //continue outer;
}
}
for (int ii = 0; ii < pathCount; ii++) {
PathConstraint constraint = pathConstraints.Items[ii];
if (constraint.data.order == i) {
SortPathConstraint(constraint);
goto continue_outer; //continue outer;
}
}
continue_outer: {}
}
for (int i = 0; i < boneCount; i++)
SortBone(bones.Items[i]);
}
private void SortIkConstraint (IkConstraint constraint) {
constraint.active = constraint.target.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
if (!constraint.active) return;
Bone target = constraint.target;
SortBone(target);
var constrained = constraint.bones;
Bone parent = constrained.Items[0];
SortBone(parent);
if (constrained.Count > 1) {
Bone child = constrained.Items[constrained.Count - 1];
if (!updateCache.Contains(child))
updateCacheReset.Add(child);
}
updateCache.Add(constraint);
SortReset(parent.children);
constrained.Items[constrained.Count - 1].sorted = true;
}
private void SortPathConstraint (PathConstraint constraint) {
constraint.active = constraint.target.bone.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
if (!constraint.active) return;
Slot slot = constraint.target;
int slotIndex = slot.data.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 is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
var constrained = constraint.bones;
int boneCount = constrained.Count;
for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]);
updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children);
for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true;
}
private void SortTransformConstraint (TransformConstraint constraint) {
constraint.active = constraint.target.active
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
if (!constraint.active) return;
SortBone(constraint.target);
var constrained = constraint.bones;
int boneCount = constrained.Count;
if (constraint.data.local) {
for (int i = 0; i < boneCount; i++) {
Bone child = constrained.Items[i];
SortBone(child.parent);
if (!updateCache.Contains(child)) updateCacheReset.Add(child);
}
} else {
for (int i = 0; i < boneCount; i++)
SortBone(constrained.Items[i]);
}
updateCache.Add(constraint);
for (int i = 0; i < boneCount; i++)
SortReset(constrained.Items[i].children);
for (int i = 0; i < boneCount; i++)
constrained.Items[i].sorted = true;
}
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
foreach (var entryObj in skin.Attachments.Keys) {
var entry = (Skin.SkinEntry)entryObj;
if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone);
}
}
private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
if (!(attachment is PathAttachment)) return;
int[] pathBones = ((PathAttachment)attachment).bones;
if (pathBones == null)
SortBone(slotBone);
else {
var bones = this.bones;
for (int i = 0, n = pathBones.Length; i < n;) {
int nn = pathBones[i++];
nn += i;
while (i < nn)
SortBone(bones.Items[pathBones[i++]]);
}
}
}
private void SortBone (Bone bone) {
if (bone.sorted) return;
Bone parent = bone.parent;
if (parent != null) SortBone(parent);
bone.sorted = true;
updateCache.Add(bone);
}
private static void SortReset (ExposedList<Bone> bones) {
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (!bone.active) continue;
if (bone.sorted) SortReset(bone.children);
bone.sorted = false;
}
}
/// <summary>Updates the world transform for each bone and applies constraints.</summary>
public void UpdateWorldTransform () {
var updateCacheReset = this.updateCacheReset;
var updateCacheResetItems = updateCacheReset.Items;
for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
Bone bone = updateCacheResetItems[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;
bone.appliedValid = true;
}
var updateItems = this.updateCache.Items;
for (int i = 0, n = updateCache.Count; i < n; i++)
updateItems[i].Update();
}
/// <summary>
/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
/// all constraints.
/// </summary>
public void UpdateWorldTransform (Bone parent) {
// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
// before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
// updateWorldTransform.
var updateCacheReset = this.updateCacheReset;
var updateCacheResetItems = updateCacheReset.Items;
for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
Bone bone = updateCacheResetItems[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;
bone.appliedValid = true;
}
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
Bone rootBone = this.RootBone;
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 rotationY = rootBone.rotation + 90 + rootBone.shearY;
float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
float ld = MathUtils.SinDeg(rotationY) * 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.
var updateCache = this.updateCache;
var updateCacheItems = updateCache.Items;
for (int i = 0, n = updateCache.Count; i < n; i++) {
var updatable = updateCacheItems[i];
if (updatable != rootBone)
updatable.Update();
}
}
/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
public void SetToSetupPose () {
SetBonesToSetupPose();
SetSlotsToSetupPose();
}
/// <summary>Sets the bones and constraints to their setup pose values.</summary>
public void SetBonesToSetupPose () {
var bonesItems = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
bonesItems[i].SetToSetupPose();
var ikConstraintsItems = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraintsItems[i];
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;
}
var transformConstraintsItems = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraintsItems[i];
TransformConstraintData constraintData = constraint.data;
constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix;
constraint.scaleMix = constraintData.scaleMix;
constraint.shearMix = constraintData.shearMix;
}
var pathConstraintItems = this.pathConstraints.Items;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraintItems[i];
PathConstraintData constraintData = constraint.data;
constraint.position = constraintData.position;
constraint.spacing = constraintData.spacing;
constraint.rotateMix = constraintData.rotateMix;
constraint.translateMix = constraintData.translateMix;
}
}
public void SetSlotsToSetupPose () {
var slots = this.slots;
var slotsItems = slots.Items;
drawOrder.Clear();
for (int i = 0, n = slots.Count; i < n; i++)
drawOrder.Add(slotsItems[i]);
for (int i = 0, n = slots.Count; i < n; i++)
slotsItems[i].SetToSetupPose();
}
/// <returns>May be null.</returns>
public Bone FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (bone.data.name == boneName) return bone;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
if (bonesItems[i].data.name == boneName) return i;
return -1;
}
/// <returns>May be null.</returns>
public Slot FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slotsItems[i];
if (slot.data.name == slotName) return slot;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindSlotIndex (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++)
if (slotsItems[i].data.name.Equals(slotName)) return i;
return -1;
}
/// <summary>Sets a skin by name (see SetSkin).</summary>
public void SetSkin (string skinName) {
Skin foundSkin = data.FindSkin(skinName);
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(foundSkin);
}
/// <summary>
/// <para>Sets the skin used to look up attachments before looking in the <see cref="SkeletonData.DefaultSkin"/>. If the
/// skin is changed, <see cref="UpdateCache()"/> is called.
/// </para>
/// <para>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.
/// </para>
/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
/// <see cref="Skeleton.SetSlotsToSetupPose()"/>.
/// Also, often <see cref="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.</para>
/// </summary>
/// <param name="newSkin">May be null.</param>
public void SetSkin (Skin newSkin) {
if (newSkin == skin) return;
if (newSkin != null) {
if (skin != null)
newSkin.AttachAll(this, skin);
else {
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i];
string name = slot.data.attachmentName;
if (name != null) {
Attachment attachment = newSkin.GetAttachment(i, name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
}
skin = newSkin;
UpdateCache();
}
/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (string slotName, string attachmentName) {
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
}
/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, string attachmentName) {
if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
if (skin != null) {
Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
}
/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
/// <param name="attachmentName">May be null to clear the slot's attachment.</param>
public void SetAttachment (string slotName, string attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i];
if (slot.data.name == slotName) {
Attachment attachment = null;
if (attachmentName != null) {
attachment = GetAttachment(i, attachmentName);
if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
}
slot.Attachment = attachment;
return;
}
}
throw new Exception("Slot not found: " + slotName);
}
/// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint ikConstraint = ikConstraints.Items[i];
if (ikConstraint.data.name == constraintName) return ikConstraint;
}
return null;
}
/// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints.Items[i];
if (transformConstraint.data.Name == constraintName) return transformConstraint;
}
return null;
}
/// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i];
if (constraint.data.Name.Equals(constraintName)) return constraint;
}
return null;
}
public void Update (float delta) {
time += delta;
}
/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
/// <param name="width">The width of the AABB</param>
/// <param name="height">The height of the AABB.</param>
/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
float[] temp = vertexBuffer;
temp = temp ?? new float[8];
var drawOrderItems = this.drawOrder.Items;
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (int i = 0, n = drawOrderItems.Length; i < n; i++) {
Slot slot = drawOrderItems[i];
if (!slot.bone.active) continue;
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.attachment;
var regionAttachment = attachment as RegionAttachment;
if (regionAttachment != null) {
verticesLength = 8;
vertices = temp;
if (vertices.Length < 8) vertices = temp = new float[8];
regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
} else {
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
MeshAttachment mesh = meshAttachment;
verticesLength = mesh.WorldVerticesLength;
vertices = temp;
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
}
}
if (vertices != null) {
for (int ii = 0; ii < verticesLength; ii += 2) {
float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy);
maxX = Math.Max(maxX, vx);
maxY = Math.Max(maxY, vy);
}
}
}
x = minX;
y = minY;
width = maxX - minX;
height = maxY - minY;
vertexBuffer = temp;
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 12ac3c1c7546be24fb9625d3c850619d
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,997 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
using System.IO;
using System.Collections.Generic;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace Spine {
public class SkeletonBinary {
public const int BONE_ROTATE = 0;
public const int BONE_TRANSLATE = 1;
public const int BONE_SCALE = 2;
public const int BONE_SHEAR = 3;
public const int SLOT_ATTACHMENT = 0;
public const int SLOT_COLOR = 1;
public const int SLOT_TWO_COLOR = 2;
public const int PATH_POSITION = 0;
public const int PATH_SPACING = 1;
public const int PATH_MIX = 2;
public const int CURVE_LINEAR = 0;
public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2;
public float Scale { get; set; }
private AttachmentLoader attachmentLoader;
private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
public SkeletonBinary (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonBinary (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !ISUNITY && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (String path) {
return this.ReadFile(path).Result;
}
#else
public SkeletonData ReadSkeletonData (String path) {
#if WINDOWS_PHONE
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else
using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
#endif
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
#endif // WINDOWS_STOREAPP
public static readonly TransformMode[] TransformModeValues = {
TransformMode.Normal,
TransformMode.OnlyTranslation,
TransformMode.NoRotationOrReflection,
TransformMode.NoScale,
TransformMode.NoScaleOrReflection
};
/// <summary>Returns the version string of binary skeleton data.</summary>
public static string GetVersionString (Stream file) {
if (file == null) throw new ArgumentNullException("file");
SkeletonInput input = new SkeletonInput(file);
return input.GetVersionString();
}
public SkeletonData ReadSkeletonData (Stream file) {
if (file == null) throw new ArgumentNullException("file");
float scale = Scale;
var skeletonData = new SkeletonData();
SkeletonInput input = new SkeletonInput(file);
skeletonData.hash = input.ReadString();
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
skeletonData.version = input.ReadString();
if (skeletonData.version.Length == 0) skeletonData.version = null;
if ("3.8.75" == skeletonData.version)
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
skeletonData.x = input.ReadFloat();
skeletonData.y = input.ReadFloat();
skeletonData.width = input.ReadFloat();
skeletonData.height = input.ReadFloat();
bool nonessential = input.ReadBoolean();
if (nonessential) {
skeletonData.fps = input.ReadFloat();
skeletonData.imagesPath = input.ReadString();
if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
skeletonData.audioPath = input.ReadString();
if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
}
int n;
Object[] o;
// Strings.
input.strings = new ExposedList<string>(n = input.ReadInt(true));
o = input.strings.Resize(n).Items;
for (int i = 0; i < n; i++)
o[i] = input.ReadString();
// Bones.
o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
String name = input.ReadString();
BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)];
BoneData data = new BoneData(i, name, parent);
data.rotation = input.ReadFloat();
data.x = input.ReadFloat() * scale;
data.y = input.ReadFloat() * scale;
data.scaleX = input.ReadFloat();
data.scaleY = input.ReadFloat();
data.shearX = input.ReadFloat();
data.shearY = input.ReadFloat();
data.length = input.ReadFloat() * scale;
data.transformMode = TransformModeValues[input.ReadInt(true)];
data.skinRequired = input.ReadBoolean();
if (nonessential) input.ReadInt(); // Skip bone color.
o[i] = data;
}
// Slots.
o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
String slotName = input.ReadString();
BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)];
SlotData slotData = new SlotData(i, slotName, boneData);
int color = input.ReadInt();
slotData.r = ((color & 0xff000000) >> 24) / 255f;
slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
slotData.a = ((color & 0x000000ff)) / 255f;
int darkColor = input.ReadInt(); // 0x00rrggbb
if (darkColor != -1) {
slotData.hasSecondColor = true;
slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
slotData.b2 = ((darkColor & 0x000000ff)) / 255f;
}
slotData.attachmentName = input.ReadStringRef();
slotData.blendMode = (BlendMode)input.ReadInt(true);
o[i] = slotData;
}
// IK constraints.
o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items;
for (int i = 0, nn; i < n; i++) {
IkConstraintData data = new IkConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
data.target = skeletonData.bones.Items[input.ReadInt(true)];
data.mix = input.ReadFloat();
data.softness = input.ReadFloat() * scale;
data.bendDirection = input.ReadSByte();
data.compress = input.ReadBoolean();
data.stretch = input.ReadBoolean();
data.uniform = input.ReadBoolean();
o[i] = data;
}
// Transform constraints.
o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items;
for (int i = 0, nn; i < n; i++) {
TransformConstraintData data = new TransformConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
data.target = skeletonData.bones.Items[input.ReadInt(true)];
data.local = input.ReadBoolean();
data.relative = input.ReadBoolean();
data.offsetRotation = input.ReadFloat();
data.offsetX = input.ReadFloat() * scale;
data.offsetY = input.ReadFloat() * scale;
data.offsetScaleX = input.ReadFloat();
data.offsetScaleY = input.ReadFloat();
data.offsetShearY = input.ReadFloat();
data.rotateMix = input.ReadFloat();
data.translateMix = input.ReadFloat();
data.scaleMix = input.ReadFloat();
data.shearMix = input.ReadFloat();
o[i] = data;
}
// Path constraints
o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items;
for (int i = 0, nn; i < n; i++) {
PathConstraintData data = new PathConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
data.target = skeletonData.slots.Items[input.ReadInt(true)];
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true));
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true));
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true));
data.offsetRotation = input.ReadFloat();
data.position = input.ReadFloat();
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = input.ReadFloat();
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = input.ReadFloat();
data.translateMix = input.ReadFloat();
o[i] = data;
}
// Default skin.
Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.skins.Add(defaultSkin);
}
// Skins.
{
int i = skeletonData.skins.Count;
o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items;
for (; i < n; i++)
o[i] = ReadSkin(input, skeletonData, false, nonessential);
}
// Linked meshes.
n = linkedMeshes.Count;
for (int i = 0; i < n; i++) {
SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh;
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
linkedMesh.mesh.UpdateUVs();
}
linkedMeshes.Clear();
// Events.
o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
EventData data = new EventData(input.ReadStringRef());
data.Int = input.ReadInt(false);
data.Float = input.ReadFloat();
data.String = input.ReadString();
data.AudioPath = input.ReadString();
if (data.AudioPath != null) {
data.Volume = input.ReadFloat();
data.Balance = input.ReadFloat();
}
o[i] = data;
}
// Animations.
o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++)
o[i] = ReadAnimation(input.ReadString(), input, skeletonData);
return skeletonData;
}
/// <returns>May be null.</returns>
private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) {
Skin skin;
int slotCount;
if (defaultSkin) {
slotCount = input.ReadInt(true);
if (slotCount == 0) return null;
skin = new Skin("default");
} else {
skin = new Skin(input.ReadStringRef());
Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
for (int i = 0, n = skin.bones.Count; i < n; i++)
bones[i] = skeletonData.bones.Items[input.ReadInt(true)];
for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]);
for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]);
for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]);
skin.constraints.TrimExcess();
slotCount = input.ReadInt(true);
}
for (int i = 0; i < slotCount; i++) {
int slotIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
String name = input.ReadStringRef();
Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
if (attachment != null) skin.SetAttachment(slotIndex, name, attachment);
}
}
return skin;
}
private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex,
String attachmentName, bool nonessential) {
float scale = Scale;
String name = input.ReadStringRef();
if (name == null) name = attachmentName;
AttachmentType type = (AttachmentType)input.ReadByte();
switch (type) {
case AttachmentType.Region: {
String path = input.ReadStringRef();
float rotation = input.ReadFloat();
float x = input.ReadFloat();
float y = input.ReadFloat();
float scaleX = input.ReadFloat();
float scaleY = input.ReadFloat();
float width = input.ReadFloat();
float height = input.ReadFloat();
int color = input.ReadInt();
if (path == null) path = name;
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null;
region.Path = path;
region.x = x * scale;
region.y = y * scale;
region.scaleX = scaleX;
region.scaleY = scaleY;
region.rotation = rotation;
region.width = width * scale;
region.height = height * scale;
region.r = ((color & 0xff000000) >> 24) / 255f;
region.g = ((color & 0x00ff0000) >> 16) / 255f;
region.b = ((color & 0x0000ff00) >> 8) / 255f;
region.a = ((color & 0x000000ff)) / 255f;
region.UpdateOffset();
return region;
}
case AttachmentType.Boundingbox: {
int vertexCount = input.ReadInt(true);
Vertices vertices = ReadVertices(input, vertexCount);
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning.
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
box.worldVerticesLength = vertexCount << 1;
box.vertices = vertices.vertices;
box.bones = vertices.bones;
// skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color);
return box;
}
case AttachmentType.Mesh: {
String path = input.ReadStringRef();
int color = input.ReadInt();
int vertexCount = input.ReadInt(true);
float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
int[] triangles = ReadShortArray(input);
Vertices vertices = ReadVertices(input, vertexCount);
int hullLength = input.ReadInt(true);
int[] edges = null;
float width = 0, height = 0;
if (nonessential) {
edges = ReadShortArray(input);
width = input.ReadFloat();
height = input.ReadFloat();
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.bones = vertices.bones;
mesh.vertices = vertices.vertices;
mesh.WorldVerticesLength = vertexCount << 1;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.UpdateUVs();
mesh.HullLength = hullLength << 1;
if (nonessential) {
mesh.Edges = edges;
mesh.Width = width * scale;
mesh.Height = height * scale;
}
return mesh;
}
case AttachmentType.Linkedmesh: {
String path = input.ReadStringRef();
int color = input.ReadInt();
String skinName = input.ReadStringRef();
String parent = input.ReadStringRef();
bool inheritDeform = input.ReadBoolean();
float width = 0, height = 0;
if (nonessential) {
width = input.ReadFloat();
height = input.ReadFloat();
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
if (nonessential) {
mesh.Width = width * scale;
mesh.Height = height * scale;
}
linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
return mesh;
}
case AttachmentType.Path: {
bool closed = input.ReadBoolean();
bool constantSpeed = input.ReadBoolean();
int vertexCount = input.ReadInt(true);
Vertices vertices = ReadVertices(input, vertexCount);
float[] lengths = new float[vertexCount / 3];
for (int i = 0, n = lengths.Length; i < n; i++)
lengths[i] = input.ReadFloat() * scale;
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
if (path == null) return null;
path.closed = closed;
path.constantSpeed = constantSpeed;
path.worldVerticesLength = vertexCount << 1;
path.vertices = vertices.vertices;
path.bones = vertices.bones;
path.lengths = lengths;
// skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color);
return path;
}
case AttachmentType.Point: {
float rotation = input.ReadFloat();
float x = input.ReadFloat();
float y = input.ReadFloat();
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
if (point == null) return null;
point.x = x * scale;
point.y = y * scale;
point.rotation = rotation;
// skipped porting: if (nonessential) point.color = color;
return point;
}
case AttachmentType.Clipping: {
int endSlotIndex = input.ReadInt(true);
int vertexCount = input.ReadInt(true);
Vertices vertices = ReadVertices(input, vertexCount);
if (nonessential) input.ReadInt();
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
if (clip == null) return null;
clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
clip.worldVerticesLength = vertexCount << 1;
clip.vertices = vertices.vertices;
clip.bones = vertices.bones;
// skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
return clip;
}
}
return null;
}
private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
float scale = Scale;
int verticesLength = vertexCount << 1;
Vertices vertices = new Vertices();
if(!input.ReadBoolean()) {
vertices.vertices = ReadFloatArray(input, verticesLength, scale);
return vertices;
}
var weights = new ExposedList<float>(verticesLength * 3 * 3);
var bonesArray = new ExposedList<int>(verticesLength * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = input.ReadInt(true);
bonesArray.Add(boneCount);
for (int ii = 0; ii < boneCount; ii++) {
bonesArray.Add(input.ReadInt(true));
weights.Add(input.ReadFloat() * scale);
weights.Add(input.ReadFloat() * scale);
weights.Add(input.ReadFloat());
}
}
vertices.vertices = weights.ToArray();
vertices.bones = bonesArray.ToArray();
return vertices;
}
private float[] ReadFloatArray (SkeletonInput input, int n, float scale) {
float[] array = new float[n];
if (scale == 1) {
for (int i = 0; i < n; i++)
array[i] = input.ReadFloat();
} else {
for (int i = 0; i < n; i++)
array[i] = input.ReadFloat() * scale;
}
return array;
}
private int[] ReadShortArray (SkeletonInput input) {
int n = input.ReadInt(true);
int[] array = new int[n];
for (int i = 0; i < n; i++)
array[i] = (input.ReadByte() << 8) | input.ReadByte();
return array;
}
private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) {
var timelines = new ExposedList<Timeline>(32);
float scale = Scale;
float duration = 0;
// Slot timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int slotIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = input.ReadInt(true);
switch (timelineType) {
case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef());
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
break;
}
case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.ReadFloat();
int color = input.ReadInt();
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
timeline.SetFrame(frameIndex, time, r, g, b, a);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]);
break;
}
case SLOT_TWO_COLOR: {
TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.ReadFloat();
int color = input.ReadInt();
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
int color2 = input.ReadInt(); // 0x00rrggbb
float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
float b2 = ((color2 & 0x000000ff)) / 255f;
timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]);
break;
}
}
}
}
// Bone timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int boneIndex = input.ReadInt(true);
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = input.ReadInt(true);
switch (timelineType) {
case BONE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
break;
}
case BONE_TRANSLATE:
case BONE_SCALE:
case BONE_SHEAR: {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType == BONE_SCALE)
timeline = new ScaleTimeline(frameCount);
else if (timelineType == BONE_SHEAR)
timeline = new ShearTimeline(frameCount);
else {
timeline = new TranslateTimeline(frameCount);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale,
input.ReadFloat() * timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
break;
}
}
}
}
// IK constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int index = input.ReadInt(true);
int frameCount = input.ReadInt(true);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
ikConstraintIndex = index
};
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(),
input.ReadBoolean());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
}
// Transform constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int index = input.ReadInt(true);
int frameCount = input.ReadInt(true);
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
timeline.transformConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(),
input.ReadFloat());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
}
// Path constraint timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
int index = input.ReadInt(true);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int timelineType = input.ReadSByte();
int frameCount = input.ReadInt(true);
switch(timelineType) {
case PATH_POSITION:
case PATH_SPACING: {
PathConstraintPositionTimeline timeline;
float timelineScale = 1;
if (timelineType == PATH_SPACING) {
timeline = new PathConstraintSpacingTimeline(frameCount);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
} else {
timeline = new PathConstraintPositionTimeline(frameCount);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
break;
}
case PATH_MIX: {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
break;
}
}
}
}
// Deform timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
Skin skin = skeletonData.skins.Items[input.ReadInt(true)];
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
int slotIndex = input.ReadInt(true);
for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef());
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
int frameCount = input.ReadInt(true);
DeformTimeline timeline = new DeformTimeline(frameCount);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.ReadFloat();
float[] deform;
int end = input.ReadInt(true);
if (end == 0)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = input.ReadInt(true);
end += start;
if (scale == 1) {
for (int v = start; v < end; v++)
deform[v] = input.ReadFloat();
} else {
for (int v = start; v < end; v++)
deform[v] = input.ReadFloat() * scale;
}
if (!weighted) {
for (int v = 0, vn = deform.Length; v < vn; v++)
deform[v] += vertices[v];
}
}
timeline.SetFrame(frameIndex, time, deform);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
}
}
}
// Draw order timeline.
int drawOrderCount = input.ReadInt(true);
if (drawOrderCount > 0) {
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
int slotCount = skeletonData.slots.Count;
for (int i = 0; i < drawOrderCount; i++) {
float time = input.ReadFloat();
int offsetCount = input.ReadInt(true);
int[] drawOrder = new int[slotCount];
for (int ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
int[] unchanged = new int[slotCount - offsetCount];
int originalIndex = 0, unchangedIndex = 0;
for (int ii = 0; ii < offsetCount; ii++) {
int slotIndex = input.ReadInt(true);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (int ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.SetFrame(i, time, drawOrder);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
}
// Event timeline.
int eventCount = input.ReadInt(true);
if (eventCount > 0) {
EventTimeline timeline = new EventTimeline(eventCount);
for (int i = 0; i < eventCount; i++) {
float time = input.ReadFloat();
EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
Event e = new Event(time, eventData) {
Int = input.ReadInt(false),
Float = input.ReadFloat(),
String = input.ReadBoolean() ? input.ReadString() : eventData.String
};
if (e.data.AudioPath != null) {
e.volume = input.ReadFloat();
e.balance = input.ReadFloat();
}
timeline.SetFrame(i, e);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[eventCount - 1]);
}
timelines.TrimExcess();
return new Animation(name, timelines, duration);
}
private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) {
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frameIndex);
break;
case CURVE_BEZIER:
timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
break;
}
}
internal class Vertices
{
public int[] bones;
public float[] vertices;
}
internal class SkeletonInput {
private byte[] chars = new byte[32];
private byte[] bytesBigEndian = new byte[4];
internal ExposedList<String> strings;
Stream input;
public SkeletonInput (Stream input) {
this.input = input;
}
public byte ReadByte () {
return (byte)input.ReadByte();
}
public sbyte ReadSByte () {
int value = input.ReadByte();
if (value == -1) throw new EndOfStreamException();
return (sbyte)value;
}
public bool ReadBoolean () {
return input.ReadByte() != 0;
}
public float ReadFloat () {
input.Read(bytesBigEndian, 0, 4);
chars[3] = bytesBigEndian[0];
chars[2] = bytesBigEndian[1];
chars[1] = bytesBigEndian[2];
chars[0] = bytesBigEndian[3];
return BitConverter.ToSingle(chars, 0);
}
public int ReadInt () {
input.Read(bytesBigEndian, 0, 4);
return (bytesBigEndian[0] << 24)
+ (bytesBigEndian[1] << 16)
+ (bytesBigEndian[2] << 8)
+ bytesBigEndian[3];
}
public int ReadInt (bool optimizePositive) {
int b = input.ReadByte();
int result = b & 0x7F;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 7;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 14;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 21;
if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28;
}
}
}
return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
}
public string ReadString () {
int byteCount = ReadInt(true);
switch (byteCount) {
case 0:
return null;
case 1:
return "";
}
byteCount--;
byte[] buffer = this.chars;
if (buffer.Length < byteCount) buffer = new byte[byteCount];
ReadFully(buffer, 0, byteCount);
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
///<return>May be null.</return>
public String ReadStringRef () {
int index = ReadInt(true);
return index == 0 ? null : strings.Items[index - 1];
}
public void ReadFully (byte[] buffer, int offset, int length) {
while (length > 0) {
int count = input.Read(buffer, offset, length);
if (count <= 0) throw new EndOfStreamException();
offset += count;
length -= count;
}
}
/// <summary>Returns the version string of binary skeleton data.</summary>
public string GetVersionString () {
try {
// Hash.
int byteCount = ReadInt(true);
if (byteCount > 1) input.Position += byteCount - 1;
// Version.
byteCount = ReadInt(true);
if (byteCount > 1) {
byteCount--;
var buffer = new byte[byteCount];
ReadFully(buffer, 0, byteCount);
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
} catch (Exception e) {
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
}
}
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 40d8a8f15082f3844a5c9c8c3ef2047f
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,234 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
namespace Spine {
/// <summary>
/// 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.
/// </summary>
public class SkeletonBounds {
private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
private float minX, minY, maxX, maxY;
public ExposedList<BoundingBoxAttachment> BoundingBoxes { get; private set; }
public ExposedList<Polygon> Polygons { get; private set; }
public float MinX { get { return minX; } set { minX = value; } }
public float MinY { get { return minY; } set { minY = value; } }
public float MaxX { get { return maxX; } set { maxX = value; } }
public float MaxY { get { return maxY; } set { maxY = value; } }
public float Width { get { return maxX - minX; } }
public float Height { get { return maxY - minY; } }
public SkeletonBounds () {
BoundingBoxes = new ExposedList<BoundingBoxAttachment>();
Polygons = new ExposedList<Polygon>();
}
/// <summary>
/// Clears any previous polygons, finds all visible bounding box attachments,
/// and computes the world vertices for each bounding box's polygon.</summary>
/// <param name="skeleton">The skeleton.</param>
/// <param name="updateAabb">
/// If true, the axis aligned bounding box containing all the polygons is computed.
/// If false, the SkeletonBounds AABB methods will always return true.
/// </param>
public void Update (Skeleton skeleton, bool updateAabb) {
ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
ExposedList<Polygon> polygons = Polygons;
ExposedList<Slot> slots = skeleton.slots;
int slotCount = slots.Count;
boundingBoxes.Clear();
for (int i = 0, n = polygons.Count; i < n; i++)
polygonPool.Add(polygons.Items[i]);
polygons.Clear();
for (int i = 0; i < slotCount; i++) {
Slot slot = slots.Items[i];
if (!slot.bone.active) continue;
BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
if (boundingBox == null) continue;
boundingBoxes.Add(boundingBox);
Polygon polygon = null;
int poolCount = polygonPool.Count;
if (poolCount > 0) {
polygon = polygonPool.Items[poolCount - 1];
polygonPool.RemoveAt(poolCount - 1);
} else
polygon = new Polygon();
polygons.Add(polygon);
int count = boundingBox.worldVerticesLength;
polygon.Count = count;
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
}
if (updateAabb) {
AabbCompute();
} else {
minX = int.MinValue;
minY = int.MinValue;
maxX = int.MaxValue;
maxY = int.MaxValue;
}
}
private void AabbCompute () {
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++) {
Polygon polygon = polygons.Items[i];
float[] vertices = polygon.Vertices;
for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
float x = vertices[ii];
float y = vertices[ii + 1];
minX = Math.Min(minX, x);
minY = Math.Min(minY, y);
maxX = Math.Max(maxX, x);
maxY = Math.Max(maxY, y);
}
}
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
/// <summary>Returns true if the axis aligned bounding box contains the point.</summary>
public bool AabbContainsPoint (float x, float y) {
return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
/// <summary>Returns true if the axis aligned bounding box intersects the line segment.</summary>
public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
float minX = this.minX;
float minY = this.minY;
float maxX = this.maxX;
float maxY = this.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;
}
/// <summary>Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds.</summary>
public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
}
/// <summary>Returns true if the polygon contains the point.</summary>
public bool ContainsPoint (Polygon polygon, float x, float y) {
float[] vertices = polygon.Vertices;
int nn = polygon.Count;
int prevIndex = nn - 2;
bool inside = false;
for (int ii = 0; ii < nn; ii += 2) {
float vertexY = vertices[ii + 1];
float prevY = vertices[prevIndex + 1];
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
float vertexX = vertices[ii];
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
}
prevIndex = ii;
}
return inside;
}
/// <summary>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.</summary>
public BoundingBoxAttachment ContainsPoint (float x, float y) {
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i];
return null;
}
/// <summary>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.</summary>
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
ExposedList<Polygon> polygons = Polygons;
for (int i = 0, n = polygons.Count; i < n; i++)
if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
return null;
}
/// <summary>Returns true if the polygon contains the line segment.</summary>
public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
float[] vertices = polygon.Vertices;
int 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 (int 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;
}
public Polygon GetPolygon (BoundingBoxAttachment attachment) {
int index = BoundingBoxes.IndexOf(attachment);
return index == -1 ? null : Polygons.Items[index];
}
}
public class Polygon {
public float[] Vertices { get; set; }
public int Count { get; set; }
public Polygon () {
Vertices = new float[16];
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 087b328a58c93b149bb977eee3a17258
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,296 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
namespace Spine {
public class SkeletonClipping {
internal readonly Triangulator triangulator = new Triangulator();
internal readonly ExposedList<float> clippingPolygon = new ExposedList<float>();
internal readonly ExposedList<float> clipOutput = new ExposedList<float>(128);
internal readonly ExposedList<float> clippedVertices = new ExposedList<float>(128);
internal readonly ExposedList<int> clippedTriangles = new ExposedList<int>(128);
internal readonly ExposedList<float> clippedUVs = new ExposedList<float>(128);
internal readonly ExposedList<float> scratch = new ExposedList<float>();
internal ClippingAttachment clipAttachment;
internal ExposedList<ExposedList<float>> clippingPolygons;
public ExposedList<float> ClippedVertices { get { return clippedVertices; } }
public ExposedList<int> ClippedTriangles { get { return clippedTriangles; } }
public ExposedList<float> ClippedUVs { get { return clippedUVs; } }
public bool IsClipping { get { return clipAttachment != null; } }
public int ClipStart (Slot slot, ClippingAttachment clip) {
if (clipAttachment != null) return 0;
clipAttachment = clip;
int n = clip.worldVerticesLength;
float[] vertices = clippingPolygon.Resize(n).Items;
clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2);
MakeClockwise(clippingPolygon);
clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
foreach (var polygon in clippingPolygons) {
MakeClockwise(polygon);
polygon.Add(polygon.Items[0]);
polygon.Add(polygon.Items[1]);
}
return clippingPolygons.Count;
}
public void ClipEnd (Slot slot) {
if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd();
}
public void ClipEnd () {
if (clipAttachment == null) return;
clipAttachment = null;
clippingPolygons = null;
clippedVertices.Clear();
clippedTriangles.Clear();
clippingPolygon.Clear();
}
public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) {
ExposedList<float> clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
var clippedTriangles = this.clippedTriangles;
var polygons = clippingPolygons.Items;
int polygonsCount = clippingPolygons.Count;
int index = 0;
clippedVertices.Clear();
clippedUVs.Clear();
clippedTriangles.Clear();
//outer:
for (int i = 0; i < trianglesLength; i += 3) {
int vertexOffset = triangles[i] << 1;
float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1];
vertexOffset = triangles[i + 1] << 1;
float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1];
float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1];
vertexOffset = triangles[i + 2] << 1;
float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1];
float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1];
for (int p = 0; p < polygonsCount; p++) {
int s = clippedVertices.Count;
if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
int clipOutputLength = clipOutput.Count;
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));
int clipOutputCount = clipOutputLength >> 1;
float[] clipOutputItems = clipOutput.Items;
float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items;
for (int ii = 0; ii < clipOutputLength; ii += 2) {
float x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x;
clippedVerticesItems[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;
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
s += 2;
}
s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items;
clipOutputCount--;
for (int ii = 1; ii < clipOutputCount; ii++) {
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + ii;
clippedTrianglesItems[s + 2] = index + ii + 1;
s += 3;
}
index += clipOutputCount + 1;
}
else {
float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items;
float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items;
clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1;
clippedVerticesItems[s + 2] = x2;
clippedVerticesItems[s + 3] = y2;
clippedVerticesItems[s + 4] = x3;
clippedVerticesItems[s + 5] = y3;
clippedUVsItems[s] = u1;
clippedUVsItems[s + 1] = v1;
clippedUVsItems[s + 2] = u2;
clippedUVsItems[s + 3] = v2;
clippedUVsItems[s + 4] = u3;
clippedUVsItems[s + 5] = v3;
s = clippedTriangles.Count;
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items;
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = index + 1;
clippedTrianglesItems[s + 2] = index + 2;
index += 3;
break; //continue outer;
}
}
}
}
/** 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. */
internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList<float> clippingArea, ExposedList<float> output) {
var originalOutput = output;
var clipped = false;
// Avoid copy at the end.
ExposedList<float> input = null;
if (clippingArea.Count % 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();
float[] clippingVertices = clippingArea.Items;
int clippingVerticesLast = clippingArea.Count - 4;
for (int i = 0; ; i += 2) {
float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;
float[] inputVertices = input.Items;
int inputVerticesLength = input.Count - 2, outputStart = output.Count;
for (int ii = 0; ii < inputVerticesLength; ii += 2) {
float inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3];
bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0;
if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) {
if (side2) { // v1 inside, v2 inside
output.Add(inputX2);
output.Add(inputY2);
continue;
}
// v1 inside, v2 outside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
if (Math.Abs(s) > 0.000001f) {
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
} else {
output.Add(edgeX);
output.Add(edgeY);
}
}
else if (side2) { // v1 outside, v2 inside
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
if (Math.Abs(s) > 0.000001f) {
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
output.Add(edgeX + (edgeX2 - edgeX) * ua);
output.Add(edgeY + (edgeY2 - edgeY) * ua);
} else {
output.Add(edgeX);
output.Add(edgeY);
}
output.Add(inputX2);
output.Add(inputY2);
}
clipped = true;
}
if (outputStart == output.Count) { // All edges outside.
originalOutput.Clear();
return true;
}
output.Add(output.Items[0]);
output.Add(output.Items[1]);
if (i == clippingVerticesLast) break;
var temp = output;
output = input;
output.Clear();
input = temp;
}
if (originalOutput != output) {
originalOutput.Clear();
for (int i = 0, n = output.Count - 2; i < n; i++) {
originalOutput.Add(output.Items[i]);
}
} else {
originalOutput.Resize(originalOutput.Count - 2);
}
return clipped;
}
public static void MakeClockwise (ExposedList<float> polygon) {
float[] vertices = polygon.Items;
int verticeslength = polygon.Count;
float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
for (int i = 0, n = verticeslength - 3; i < n; i += 2) {
p1x = vertices[i];
p1y = vertices[i + 1];
p2x = vertices[i + 2];
p2y = vertices[i + 3];
area += p1x * p2y - p2x * p1y;
}
if (area < 0) return;
for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
float x = vertices[i], y = vertices[i + 1];
int other = lastX - i;
vertices[i] = vertices[other];
vertices[i + 1] = vertices[other + 1];
vertices[other] = x;
vertices[other + 1] = y;
}
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 7db809d277afd0e4a8e8c6b703002ee0
timeCreated: 1492744746
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,230 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
namespace Spine {
/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
public class SkeletonData {
internal string name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
internal ExposedList<Skin> skins = new ExposedList<Skin>();
internal Skin defaultSkin;
internal ExposedList<EventData> events = new ExposedList<EventData>();
internal ExposedList<Animation> animations = new ExposedList<Animation>();
internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
internal float x , y, width, height;
internal string version, hash;
// Nonessential.
internal float fps;
internal string imagesPath, audioPath;
public string Name { get { return name; } set { name = value; } }
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
public ExposedList<BoneData> Bones { get { return bones; } }
public ExposedList<SlotData> Slots { get { return slots; } }
/// <summary>All skins, including the default skin.</summary>
public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
/// <summary>
/// The skeleton's default skin.
/// By default this skin contains all attachments that were not in a skin in Spine.
/// </summary>
/// <return>May be null.</return>
public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
public ExposedList<EventData> Events { get { return events; } set { events = value; } }
public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } }
public float Width { get { return width; } set { width = value; } }
public float Height { get { return height; } set { height = value; } }
/// <summary>The Spine version used to export this data, or null.</summary>
public string Version { get { return version; } set { version = value; } }
public string Hash { get { return hash; } set { hash = value; } }
/// <summary>The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null</summary>
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
/// <summary>The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.</summary>
public string AudioPath { get { return audioPath; } set { audioPath = value; } }
/// <summary>
/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
public float Fps { get { return fps; } set { fps = value; } }
// --- Bones.
/// <summary>
/// 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.</summary>
/// <returns>May be null.</returns>
public BoneData FindBone (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
BoneData bone = bonesItems[i];
if (bone.name == boneName) return bone;
}
return null;
}
/// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (string boneName) {
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++)
if (bonesItems[i].name == boneName) return i;
return -1;
}
// --- Slots.
/// <returns>May be null.</returns>
public SlotData FindSlot (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) {
SlotData slot = slots.Items[i];
if (slot.name == slotName) return slot;
}
return null;
}
/// <returns>-1 if the slot was not found.</returns>
public int FindSlotIndex (string slotName) {
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++)
if (slots.Items[i].name == slotName) return i;
return -1;
}
// --- Skins.
/// <returns>May be null.</returns>
public Skin FindSkin (string skinName) {
if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
foreach (Skin skin in skins)
if (skin.name == skinName) return skin;
return null;
}
// --- Events.
/// <returns>May be null.</returns>
public EventData FindEvent (string eventDataName) {
if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
foreach (EventData eventData in events)
if (eventData.name == eventDataName) return eventData;
return null;
}
// --- Animations.
/// <returns>May be null.</returns>
public Animation FindAnimation (string animationName) {
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
ExposedList<Animation> animations = this.animations;
for (int i = 0, n = animations.Count; i < n; i++) {
Animation animation = animations.Items[i];
if (animation.name == animationName) return animation;
}
return null;
}
// --- IK constraints.
/// <returns>May be null.</returns>
public IkConstraintData FindIkConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraintData ikConstraint = ikConstraints.Items[i];
if (ikConstraint.name == constraintName) return ikConstraint;
}
return null;
}
// --- Transform constraints.
/// <returns>May be null.</returns>
public TransformConstraintData FindTransformConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraintData transformConstraint = transformConstraints.Items[i];
if (transformConstraint.name == constraintName) return transformConstraint;
}
return null;
}
// --- Path constraints.
/// <returns>May be null.</returns>
public PathConstraintData FindPathConstraint (string constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraintData constraint = pathConstraints.Items[i];
if (constraint.name.Equals(constraintName)) return constraint;
}
return null;
}
/// <returns>-1 if the path constraint was not found.</returns>
public int FindPathConstraintIndex (string pathConstraintName) {
if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++)
if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
return -1;
}
// ---
override public string ToString () {
return name ?? base.ToString();
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 2b813f63abbb6d94a80a5c050590a0be
timeCreated: 1456265153
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,918 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
#define IS_UNITY
#endif
using System;
using System.IO;
using System.Collections.Generic;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace Spine {
public class SkeletonJson {
public float Scale { get; set; }
private AttachmentLoader attachmentLoader;
private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
public SkeletonJson (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonJson (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if !IS_UNITY && WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (string path) {
return this.ReadFile(path).Result;
}
#else
public SkeletonData ReadSkeletonData (string path) {
#if WINDOWS_PHONE
using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
#else
using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) {
#endif
SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
#endif
public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
float scale = this.Scale;
var skeletonData = new SkeletonData();
var root = Json.Deserialize(reader) as Dictionary<string, Object>;
if (root == null) throw new Exception("Invalid JSON.");
// Skeleton.
if (root.ContainsKey("skeleton")) {
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
skeletonData.hash = (string)skeletonMap["hash"];
skeletonData.version = (string)skeletonMap["spine"];
if ("3.8.75" == skeletonData.version)
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
skeletonData.x = GetFloat(skeletonMap, "x", 0);
skeletonData.y = GetFloat(skeletonMap, "y", 0);
skeletonData.width = GetFloat(skeletonMap, "width", 0);
skeletonData.height = GetFloat(skeletonMap, "height", 0);
skeletonData.fps = GetFloat(skeletonMap, "fps", 30);
skeletonData.imagesPath = GetString(skeletonMap, "images", null);
skeletonData.audioPath = GetString(skeletonMap, "audio", null);
}
// Bones.
if (root.ContainsKey("bones")) {
foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
BoneData parent = null;
if (boneMap.ContainsKey("parent")) {
parent = skeletonData.FindBone((string)boneMap["parent"]);
if (parent == null)
throw new Exception("Parent bone not found: " + boneMap["parent"]);
}
var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
data.length = GetFloat(boneMap, "length", 0) * scale;
data.x = GetFloat(boneMap, "x", 0) * scale;
data.y = GetFloat(boneMap, "y", 0) * scale;
data.rotation = GetFloat(boneMap, "rotation", 0);
data.scaleX = GetFloat(boneMap, "scaleX", 1);
data.scaleY = GetFloat(boneMap, "scaleY", 1);
data.shearX = GetFloat(boneMap, "shearX", 0);
data.shearY = GetFloat(boneMap, "shearY", 0);
string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
data.skinRequired = GetBoolean(boneMap, "skin", false);
skeletonData.bones.Add(data);
}
}
// Slots.
if (root.ContainsKey("slots")) {
foreach (Dictionary<string, Object> slotMap in (List<Object>)root["slots"]) {
var slotName = (string)slotMap["name"];
var boneName = (string)slotMap["bone"];
BoneData boneData = skeletonData.FindBone(boneName);
if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
if (slotMap.ContainsKey("color")) {
string color = (string)slotMap["color"];
data.r = ToColor(color, 0);
data.g = ToColor(color, 1);
data.b = ToColor(color, 2);
data.a = ToColor(color, 3);
}
if (slotMap.ContainsKey("dark")) {
var color2 = (string)slotMap["dark"];
data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB"
data.g2 = ToColor(color2, 1, 6);
data.b2 = ToColor(color2, 2, 6);
data.hasSecondColor = true;
}
data.attachmentName = GetString(slotMap, "attachment", null);
if (slotMap.ContainsKey("blend"))
data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true);
else
data.blendMode = BlendMode.Normal;
skeletonData.slots.Add(data);
}
}
// IK constraints.
if (root.ContainsKey("ik")) {
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
data.order = GetInt(constraintMap, "order", 0);
data.skinRequired = GetBoolean(constraintMap,"skin", false);
if (constraintMap.ContainsKey("bones")) {
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("IK bone not found: " + boneName);
data.bones.Add(bone);
}
}
string targetName = (string)constraintMap["target"];
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("IK target bone not found: " + targetName);
data.mix = GetFloat(constraintMap, "mix", 1);
data.softness = GetFloat(constraintMap, "softness", 0) * scale;
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
data.compress = GetBoolean(constraintMap, "compress", false);
data.stretch = GetBoolean(constraintMap, "stretch", false);
data.uniform = GetBoolean(constraintMap, "uniform", false);
skeletonData.ikConstraints.Add(data);
}
}
// Transform constraints.
if (root.ContainsKey("transform")) {
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
data.order = GetInt(constraintMap, "order", 0);
data.skinRequired = GetBoolean(constraintMap,"skin", false);
if (constraintMap.ContainsKey("bones")) {
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
data.bones.Add(bone);
}
}
string targetName = (string)constraintMap["target"];
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName);
data.local = GetBoolean(constraintMap, "local", false);
data.relative = GetBoolean(constraintMap, "relative", false);
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
data.shearMix = GetFloat(constraintMap, "shearMix", 1);
skeletonData.transformConstraints.Add(data);
}
}
// Path constraints.
if(root.ContainsKey("path")) {
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
data.order = GetInt(constraintMap, "order", 0);
data.skinRequired = GetBoolean(constraintMap,"skin", false);
if (constraintMap.ContainsKey("bones")) {
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Path bone not found: " + boneName);
data.bones.Add(bone);
}
}
string targetName = (string)constraintMap["target"];
data.target = skeletonData.FindSlot(targetName);
if (data.target == null) throw new Exception("Path target slot not found: " + targetName);
data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.position = GetFloat(constraintMap, "position", 0);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = GetFloat(constraintMap, "spacing", 0);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
skeletonData.pathConstraints.Add(data);
}
}
// Skins.
if (root.ContainsKey("skins")) {
foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) {
Skin skin = new Skin((string)skinMap["name"]);
if (skinMap.ContainsKey("bones")) {
foreach (string entryName in (List<Object>)skinMap["bones"]) {
BoneData bone = skeletonData.FindBone(entryName);
if (bone == null) throw new Exception("Skin bone not found: " + entryName);
skin.bones.Add(bone);
}
}
if (skinMap.ContainsKey("ik")) {
foreach (string entryName in (List<Object>)skinMap["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(entryName);
if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName);
skin.constraints.Add(constraint);
}
}
if (skinMap.ContainsKey("transform")) {
foreach (string entryName in (List<Object>)skinMap["transform"]) {
TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName);
if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName);
skin.constraints.Add(constraint);
}
}
if (skinMap.ContainsKey("path")) {
foreach (string entryName in (List<Object>)skinMap["path"]) {
PathConstraintData constraint = skeletonData.FindPathConstraint(entryName);
if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName);
skin.constraints.Add(constraint);
}
}
if (skinMap.ContainsKey("attachments")) {
foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
try {
Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment);
} catch (Exception e) {
throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
}
}
}
}
skeletonData.skins.Add(skin);
if (skin.name == "default") skeletonData.defaultSkin = skin;
}
}
// Linked meshes.
for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
LinkedMesh linkedMesh = linkedMeshes[i];
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh;
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
linkedMesh.mesh.UpdateUVs();
}
linkedMeshes.Clear();
// Events.
if (root.ContainsKey("events")) {
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["events"]) {
var entryMap = (Dictionary<string, Object>)entry.Value;
var data = new EventData(entry.Key);
data.Int = GetInt(entryMap, "int", 0);
data.Float = GetFloat(entryMap, "float", 0);
data.String = GetString(entryMap, "string", string.Empty);
data.AudioPath = GetString(entryMap, "audio", null);
if (data.AudioPath != null) {
data.Volume = GetFloat(entryMap, "volume", 1);
data.Balance = GetFloat(entryMap, "balance", 0);
}
skeletonData.events.Add(data);
}
}
// Animations.
if (root.ContainsKey("animations")) {
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["animations"]) {
try {
ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
} catch (Exception e) {
throw new Exception("Error reading animation: " + entry.Key, e);
}
}
}
skeletonData.bones.TrimExcess();
skeletonData.slots.TrimExcess();
skeletonData.skins.TrimExcess();
skeletonData.events.TrimExcess();
skeletonData.animations.TrimExcess();
skeletonData.ikConstraints.TrimExcess();
return skeletonData;
}
private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) {
float scale = this.Scale;
name = GetString(map, "name", name);
var typeName = GetString(map, "type", "region");
var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
string path = GetString(map, "path", name);
switch (type) {
case AttachmentType.Region:
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null;
region.Path = path;
region.x = GetFloat(map, "x", 0) * scale;
region.y = GetFloat(map, "y", 0) * scale;
region.scaleX = GetFloat(map, "scaleX", 1);
region.scaleY = GetFloat(map, "scaleY", 1);
region.rotation = GetFloat(map, "rotation", 0);
region.width = GetFloat(map, "width", 32) * scale;
region.height = GetFloat(map, "height", 32) * scale;
if (map.ContainsKey("color")) {
var color = (string)map["color"];
region.r = ToColor(color, 0);
region.g = ToColor(color, 1);
region.b = ToColor(color, 2);
region.a = ToColor(color, 3);
}
region.UpdateOffset();
return region;
case AttachmentType.Boundingbox:
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
return box;
case AttachmentType.Mesh:
case AttachmentType.Linkedmesh: {
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
if (map.ContainsKey("color")) {
var color = (string)map["color"];
mesh.r = ToColor(color, 0);
mesh.g = ToColor(color, 1);
mesh.b = ToColor(color, 2);
mesh.a = ToColor(color, 3);
}
mesh.Width = GetFloat(map, "width", 0) * scale;
mesh.Height = GetFloat(map, "height", 0) * scale;
string parent = GetString(map, "parent", null);
if (parent != null) {
linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true)));
return mesh;
}
float[] uvs = GetFloatArray(map, "uvs", 1);
ReadVertices(map, mesh, uvs.Length);
mesh.triangles = GetIntArray(map, "triangles");
mesh.regionUVs = uvs;
mesh.UpdateUVs();
if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
return mesh;
}
case AttachmentType.Path: {
PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
if (pathAttachment == null) return null;
pathAttachment.closed = GetBoolean(map, "closed", false);
pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
int vertexCount = GetInt(map, "vertexCount", 0);
ReadVertices(map, pathAttachment, vertexCount << 1);
// potential BOZO see Java impl
pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
return pathAttachment;
}
case AttachmentType.Point: {
PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
if (point == null) return null;
point.x = GetFloat(map, "x", 0) * scale;
point.y = GetFloat(map, "y", 0) * scale;
point.rotation = GetFloat(map, "rotation", 0);
//string color = GetString(map, "color", null);
//if (color != null) point.color = color;
return point;
}
case AttachmentType.Clipping: {
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
if (clip == null) return null;
string end = GetString(map, "end", null);
if (end != null) {
SlotData slot = skeletonData.FindSlot(end);
if (slot == null) throw new Exception("Clipping end slot not found: " + end);
clip.EndSlot = slot;
}
ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);
//string color = GetString(map, "color", null);
// if (color != null) clip.color = color;
return clip;
}
}
return null;
}
private void ReadVertices (Dictionary<string, Object> map, VertexAttachment attachment, int verticesLength) {
attachment.WorldVerticesLength = verticesLength;
float[] vertices = GetFloatArray(map, "vertices", 1);
float scale = Scale;
if (verticesLength == vertices.Length) {
if (scale != 1) {
for (int i = 0; i < vertices.Length; i++) {
vertices[i] *= scale;
}
}
attachment.vertices = vertices;
return;
}
ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
for (int i = 0, n = vertices.Length; i < n;) {
int boneCount = (int)vertices[i++];
bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) {
bones.Add((int)vertices[i]);
weights.Add(vertices[i + 1] * this.Scale);
weights.Add(vertices[i + 2] * this.Scale);
weights.Add(vertices[i + 3]);
}
}
attachment.bones = bones.ToArray();
attachment.vertices = weights.ToArray();
}
private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
var scale = this.Scale;
var timelines = new ExposedList<Timeline>();
float duration = 0;
// Slot timelines.
if (map.ContainsKey("slots")) {
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["slots"]) {
string slotName = entry.Key;
int slotIndex = skeletonData.FindSlotIndex(slotName);
var timelineMap = (Dictionary<string, Object>)entry.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (string)timelineEntry.Key;
if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} else if (timelineName == "color") {
var timeline = new ColorTimeline(values.Count);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
string c = (string)valueMap["color"];
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
} else if (timelineName == "twoColor") {
var timeline = new TwoColorTimeline(values.Count);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
string light = (string)valueMap["light"];
string dark = (string)valueMap["dark"];
timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
} else
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
}
}
}
// Bone timelines.
if (map.ContainsKey("bones")) {
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["bones"]) {
string boneName = entry.Key;
int boneIndex = skeletonData.FindBoneIndex(boneName);
if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
var timelineMap = (Dictionary<string, Object>)entry.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (string)timelineEntry.Key;
if (timelineName == "rotate") {
var timeline = new RotateTimeline(values.Count);
timeline.boneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
TranslateTimeline timeline;
float timelineScale = 1, defaultValue = 0;
if (timelineName == "scale") {
timeline = new ScaleTimeline(values.Count);
defaultValue = 1;
}
else if (timelineName == "shear")
timeline = new ShearTimeline(values.Count);
else {
timeline = new TranslateTimeline(values.Count);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float time = GetFloat(valueMap, "time", 0);
float x = GetFloat(valueMap, "x", defaultValue);
float y = GetFloat(valueMap, "y", defaultValue);
timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
} else
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
}
}
}
// IK constraint timelines.
if (map.ContainsKey("ik")) {
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
var values = (List<Object>)constraintMap.Value;
var timeline = new IkConstraintTimeline(values.Count);
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1),
GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
}
}
// Transform constraint timelines.
if (map.ContainsKey("transform")) {
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) {
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
var values = (List<Object>)constraintMap.Value;
var timeline = new TransformConstraintTimeline(values.Count);
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1),
GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
}
}
// Path constraint timelines.
if (map.ContainsKey("path")) {
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["path"]) {
int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
var timelineName = (string)timelineEntry.Key;
if (timelineName == "position" || timelineName == "spacing") {
PathConstraintPositionTimeline timeline;
float timelineScale = 1;
if (timelineName == "spacing") {
timeline = new PathConstraintSpacingTimeline(values.Count);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
}
else {
timeline = new PathConstraintPositionTimeline(values.Count);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
}
else if (timelineName == "mix") {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1),
GetFloat(valueMap, "translateMix", 1));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
}
}
}
}
// Deform timelines.
if (map.ContainsKey("deform")) {
foreach (KeyValuePair<string, Object> deformMap in (Dictionary<string, Object>)map["deform"]) {
Skin skin = skeletonData.FindSkin(deformMap.Key);
foreach (KeyValuePair<string, Object> slotMap in (Dictionary<string, Object>)deformMap.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) {
var values = (List<Object>)timelineMap.Value;
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
var timeline = new DeformTimeline(values.Count);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) {
float[] deform;
if (!valueMap.ContainsKey("vertices")) {
deform = weighted ? new float[deformLength] : vertices;
} else {
deform = new float[deformLength];
int start = GetInt(valueMap, "offset", 0);
float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
if (scale != 1) {
for (int i = start, n = i + verticesValue.Length; i < n; i++)
deform[i] *= scale;
}
if (!weighted) {
for (int i = 0; i < deformLength; i++)
deform[i] += vertices[i];
}
}
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
}
}
}
// Draw order timeline.
if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
var timeline = new DrawOrderTimeline(values.Count);
int slotCount = skeletonData.slots.Count;
int frameIndex = 0;
foreach (Dictionary<string, Object> drawOrderMap in values) {
int[] drawOrder = null;
if (drawOrderMap.ContainsKey("offsets")) {
drawOrder = new int[slotCount];
for (int i = slotCount - 1; i >= 0; i--)
drawOrder[i] = -1;
var offsets = (List<Object>)drawOrderMap["offsets"];
int[] unchanged = new int[slotCount - offsets.Count];
int originalIndex = 0, unchangedIndex = 0;
foreach (Dictionary<string, Object> offsetMap in offsets) {
int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]);
if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
int index = originalIndex + (int)(float)offsetMap["offset"];
drawOrder[index] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (int i = slotCount - 1; i >= 0; i--)
if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
}
timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
// Event timeline.
if (map.ContainsKey("events")) {
var eventsMap = (List<Object>)map["events"];
var timeline = new EventTimeline(eventsMap.Count);
int frameIndex = 0;
foreach (Dictionary<string, Object> eventMap in eventsMap) {
EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
var e = new Event(GetFloat(eventMap, "time", 0), eventData) {
intValue = GetInt(eventMap, "int", eventData.Int),
floatValue = GetFloat(eventMap, "float", eventData.Float),
stringValue = GetString(eventMap, "string", eventData.String)
};
if (e.data.AudioPath != null) {
e.volume = GetFloat(eventMap, "volume", eventData.Volume);
e.balance = GetFloat(eventMap, "balance", eventData.Balance);
}
timeline.SetFrame(frameIndex++, e);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
}
timelines.TrimExcess();
skeletonData.animations.Add(new Animation(name, timelines, duration));
}
static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) {
if (!valueMap.ContainsKey("curve"))
return;
Object curveObject = valueMap["curve"];
if (curveObject is string)
timeline.SetStepped(frameIndex);
else
timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1));
}
internal class LinkedMesh {
internal string parent, skin;
internal int slotIndex;
internal MeshAttachment mesh;
internal bool inheritDeform;
public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
}
}
static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) {
var list = (List<Object>)map[name];
var values = new float[list.Count];
if (scale == 1) {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i];
} else {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i] * scale;
}
return values;
}
static int[] GetIntArray(Dictionary<string, Object> map, string name) {
var list = (List<Object>)map[name];
var values = new int[list.Count];
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (int)(float)list[i];
return values;
}
static float GetFloat(Dictionary<string, Object> map, string name, float defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (float)map[name];
}
static int GetInt(Dictionary<string, Object> map, string name, int defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (int)(float)map[name];
}
static bool GetBoolean(Dictionary<string, Object> map, string name, bool defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (bool)map[name];
}
static string GetString(Dictionary<string, Object> map, string name, string defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (string)map[name];
}
static float ToColor(string hexString, int colorIndex, int expectedLength = 8) {
if (hexString.Length != expectedLength)
throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString");
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 6c4ab7992894bdb44a480981b1953f76
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,193 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using Spine.Collections;
namespace Spine {
/// <summary>Stores attachments by slot index and attachment name.
/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and
/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
/// </summary>
public class Skin {
internal string name;
private OrderedDictionary<SkinEntry, Attachment> attachments = new OrderedDictionary<SkinEntry, Attachment>(SkinEntryComparer.Instance);
internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
public string Name { get { return name; } }
public OrderedDictionary<SkinEntry, Attachment> Attachments { get { return attachments; } }
public ExposedList<BoneData> Bones { get { return bones; } }
public ExposedList<ConstraintData> Constraints { get { return constraints; } }
public Skin (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
/// <summary>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.</summary>
public void SetAttachment (int slotIndex, string name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0.");
attachments[new SkinEntry(slotIndex, name, attachment)] = attachment;
}
///<summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
public void AddSkin (Skin skin) {
foreach (BoneData data in skin.bones)
if (!bones.Contains(data)) bones.Add(data);
foreach (ConstraintData data in skin.constraints)
if (!constraints.Contains(data)) constraints.Add(data);
foreach (SkinEntry entry in skin.attachments.Keys)
SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment);
}
///<summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
public void CopySkin (Skin skin) {
foreach (BoneData data in skin.bones)
if (!bones.Contains(data)) bones.Add(data);
foreach (ConstraintData data in skin.constraints)
if (!constraints.Contains(data)) constraints.Add(data);
foreach (SkinEntry entry in skin.attachments.Keys) {
if (entry.Attachment is MeshAttachment)
SetAttachment(entry.SlotIndex, entry.Name,
entry.Attachment != null ? ((MeshAttachment)entry.Attachment).NewLinkedMesh() : null);
else
SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? entry.Attachment.Copy() : null);
}
}
/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
/// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, string name) {
var lookup = new SkinEntry(slotIndex, name, null);
Attachment attachment = null;
bool containsKey = attachments.TryGetValue(lookup, out attachment);
return containsKey ? attachment : null;
}
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
public void RemoveAttachment (int slotIndex, string name) {
if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
var lookup = new SkinEntry(slotIndex, name, null);
attachments.Remove(lookup);
}
///<summary>Returns all attachments contained in this skin.</summary>
public ICollection<SkinEntry> GetAttachments () {
return this.attachments.Keys;
}
/// <summary>Returns all attachments in this skin for the specified slot index.</summary>
/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
public void GetAttachments (int slotIndex, List<SkinEntry> attachments) {
foreach (SkinEntry entry in this.attachments.Keys)
if (entry.SlotIndex == slotIndex) attachments.Add(entry);
}
///<summary>Clears all attachments, bones, and constraints.</summary>
public void Clear () {
attachments.Clear();
bones.Clear();
constraints.Clear();
}
override public string ToString () {
return name;
}
/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
foreach (SkinEntry entry in oldSkin.attachments.Keys) {
int slotIndex = entry.SlotIndex;
Slot slot = skeleton.slots.Items[slotIndex];
if (slot.Attachment == entry.Attachment) {
Attachment attachment = GetAttachment(slotIndex, entry.Name);
if (attachment != null) slot.Attachment = attachment;
}
}
}
/// <summary>Stores an entry in the skin consisting of the slot index, name, and attachment.</summary>
public struct SkinEntry {
private readonly int slotIndex;
private readonly string name;
private readonly Attachment attachment;
internal readonly int hashCode;
public SkinEntry (int slotIndex, string name, Attachment attachment) {
this.slotIndex = slotIndex;
this.name = name;
this.attachment = attachment;
this.hashCode = this.name.GetHashCode() + this.slotIndex * 37;
}
public int SlotIndex {
get {
return slotIndex;
}
}
/// <summary>The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor.</summary>
public String Name {
get {
return name;
}
}
public Attachment Attachment {
get {
return attachment;
}
}
}
// Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison.
class SkinEntryComparer : IEqualityComparer<SkinEntry> {
internal static readonly SkinEntryComparer Instance = new SkinEntryComparer();
bool IEqualityComparer<SkinEntry>.Equals (SkinEntry e1, SkinEntry e2) {
if (e1.SlotIndex != e2.SlotIndex) return false;
if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false;
return true;
}
int IEqualityComparer<SkinEntry>.GetHashCode (SkinEntry e) {
return e.Name.GetHashCode() + e.SlotIndex * 37;
}
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 7df8caa3a771f464e803316a6b18c909
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,196 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
namespace Spine {
/// <summary>
/// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
/// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared
/// across multiple skeletons.
/// </summary>
public class Slot {
internal SlotData data;
internal Bone bone;
internal float r, g, b, a;
internal float r2, g2, b2;
internal bool hasSecondColor;
internal Attachment attachment;
internal float attachmentTime;
internal ExposedList<float> deform = new ExposedList<float>();
internal int attachmentState;
public Slot (SlotData data, Bone bone) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
this.data = data;
this.bone = bone;
// darkColor = data.darkColor == null ? null : new Color();
if (data.hasSecondColor) {
r2 = g2 = b2 = 0;
}
SetToSetupPose();
}
/// <summary>Copy constructor.</summary>
public Slot(Slot slot, Bone bone) {
if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null.");
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
data = slot.data;
this.bone = bone;
r = slot.r;
g = slot.g;
b = slot.b;
a = slot.a;
// darkColor = slot.darkColor == null ? null : new Color(slot.darkColor);
if (slot.hasSecondColor) {
r2 = slot.r2;
g2 = slot.g2;
b2 = slot.b2;
} else {
r2 = g2 = b2 = 0;
}
hasSecondColor = slot.hasSecondColor;
attachment = slot.attachment;
attachmentTime = slot.attachmentTime;
deform.AddRange(slot.deform);
}
/// <summary>The slot's setup pose data.</summary>
public SlotData Data { get { return data; } }
/// <summary>The bone this slot belongs to.</summary>
public Bone Bone { get { return bone; } }
/// <summary>The skeleton this slot belongs to.</summary>
public Skeleton Skeleton { get { return bone.skeleton; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float R { get { return r; } set { r = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float G { get { return g; } set { g = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float B { get { return b; } set { b = value; } }
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
/// color tinting.</summary>
public float A { get { return a; } set { a = value; } }
public void ClampColor() {
r = MathUtils.Clamp(r, 0, 1);
g = MathUtils.Clamp(g, 0, 1);
b = MathUtils.Clamp(b, 0, 1);
a = MathUtils.Clamp(a, 0, 1);
}
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float R2 { get { return r2; } set { r2 = value; } }
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float G2 { get { return g2; } set { g2 = value; } }
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
/// <seealso cref="HasSecondColor"/>
public float B2 { get { return b2; } set { b2 = value; } }
/// <summary>Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used.</summary>
public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
public void ClampSecondColor () {
r2 = MathUtils.Clamp(r2, 0, 1);
g2 = MathUtils.Clamp(g2, 0, 1);
b2 = MathUtils.Clamp(b2, 0, 1);
}
public Attachment Attachment {
/// <summary>The current attachment for the slot, or null if the slot has no attachment.</summary>
get { return attachment; }
/// <summary>
/// Sets the slot's attachment and, if the attachment changed, resets <see cref="AttachmentTime"/> and clears
/// <see cref="Deform">.</summary>
/// <param name="value">May be null.</param>
set {
if (attachment == value) return;
attachment = value;
attachmentTime = bone.skeleton.time;
deform.Clear(false);
}
}
/// <summary> The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
/// <see cref="Skeleton.Time"/></summary>
public float AttachmentTime {
get { return bone.skeleton.time - attachmentTime; }
set { attachmentTime = bone.skeleton.time - value; }
}
/// <summary> Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
/// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
/// <para />
/// See <see cref="VertexAttachment.ComputeWorldVertices(Slot, int, int, float[], int, int)"/> and <see cref="DeformTimeline"/>.</summary>
public ExposedList<float> Deform {
get {
return deform;
}
set {
if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null.");
deform = value;
}
}
/// <summary>Sets this slot to the setup pose.</summary>
public void SetToSetupPose () {
r = data.r;
g = data.g;
b = data.b;
a = data.a;
// if (darkColor != null) darkColor.set(data.darkColor);
if (HasSecondColor) {
r2 = data.r2;
g2 = data.g2;
b2 = data.b2;
}
if (data.attachmentName == null)
Attachment = null;
else {
attachment = null;
Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
}
}
override public string ToString () {
return data.name;
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 6974c4b5c87687140a2417201ea43066
timeCreated: 1456265154
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,77 +0,0 @@
/******************************************************************************
* 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.
*****************************************************************************/
using System;
namespace Spine {
public class SlotData {
internal int index;
internal string name;
internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1;
internal float r2 = 0, g2 = 0, b2 = 0;
internal bool hasSecondColor = false;
internal string attachmentName;
internal BlendMode blendMode;
/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
public int Index { get { return index; } }
/// <summary>The name of the slot, which is unique across all slots in the skeleton.</summary>
public string Name { get { return name; } }
/// <summary>The bone this slot belongs to.</summary>
public BoneData BoneData { get { return boneData; } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public float R2 { get { return r2; } set { r2 = value; } }
public float G2 { get { return g2; } set { g2 = value; } }
public float B2 { get { return b2; } set { b2 = value; } }
public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
/// <summary>The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible.</summary>
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
/// <summary>The blend mode for drawing the slot's attachment.</summary>
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
public SlotData (int index, String name, BoneData boneData) {
if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
this.index = index;
this.name = name;
this.boneData = boneData;
}
override public string ToString () {
return name;
}
}
}

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: f28cb47bc1e8b434c85e6f69b2c9e15e
timeCreated: 1456265156
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: