mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-25 22:23:42 +08:00
Merge branch '3.6' into 3.7-beta
This commit is contained in:
commit
581d67f490
@ -85,6 +85,7 @@
|
||||
* Added support for two color tinting. All base materials, e.g. SpineUnlitNormalMaterial, now do proper two color tinting. No material parameters have changed.
|
||||
* Updated to Unreal Engine 4.16.1. Note that 4.16 has a regression which will make it impossible to compile plain .c files!
|
||||
* spine-c is now exposed from the plugin shared library on Windows via __declspec.
|
||||
* Updated to Unreal Engine 4.18
|
||||
|
||||
## C#
|
||||
* **Breaking changes**
|
||||
|
||||
Binary file not shown.
@ -33,6 +33,7 @@ package spine {
|
||||
|
||||
public class PathConstraint implements Constraint {
|
||||
private static const NONE : int = -1, BEFORE : int = -2, AFTER : int = -3;
|
||||
private static const epsilon : Number = 0.00001;
|
||||
internal var _data : PathConstraintData;
|
||||
internal var _bones : Vector.<Bone>;
|
||||
public var target : Slot;
|
||||
@ -88,11 +89,15 @@ package spine {
|
||||
for (var i : int = 0, n : int = spacesCount - 1; i < n;) {
|
||||
var bone : Bone = bones[i];
|
||||
var setupLength : Number = bone.data.length;
|
||||
if (setupLength == 0) setupLength = 0.000000001;
|
||||
var x : Number = setupLength * bone.a, y : Number = setupLength * bone.c;
|
||||
var length : Number = Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
if (setupLength < epsilon) {
|
||||
if (scale) lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
} else {
|
||||
var x : Number = setupLength * bone.a, y : Number = setupLength * bone.c;
|
||||
var length : Number = Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 1; i < spacesCount; i++)
|
||||
|
||||
@ -207,9 +207,10 @@ package spine.animation {
|
||||
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, currentPose);
|
||||
|
||||
var mix : Number = 0;
|
||||
if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
|
||||
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
else {
|
||||
currentPose = MixPose.setup;
|
||||
} else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
|
||||
@ -253,10 +253,10 @@ void _spRotateTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton,
|
||||
bone->rotation = bone->data->rotation;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
r = bone->data->rotation - bone->rotation;
|
||||
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
|
||||
bone->rotation += r * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -333,9 +333,9 @@ void _spTranslateTimeline_apply (const spTimeline* timeline, spSkeleton* skeleto
|
||||
bone->y = bone->data->y;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
bone->x += (bone->data->x - bone->x) * alpha;
|
||||
bone->y += (bone->data->y - bone->y) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -405,9 +405,9 @@ void _spScaleTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, f
|
||||
bone->scaleY = bone->data->scaleY;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
bone->scaleX += (bone->data->scaleX - bone->scaleX) * alpha;
|
||||
bone->scaleY += (bone->data->scaleY - bone->scaleY) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -492,9 +492,9 @@ void _spShearTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, f
|
||||
bone->shearY = bone->data->shearY;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
bone->shearX += (bone->data->shearX - bone->shearX) * alpha;
|
||||
bone->shearY += (bone->data->shearY - bone->shearY) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -561,11 +561,11 @@ void _spColorTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, f
|
||||
spColor_setFromColor(&slot->color, &slot->data->color);
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
color = &slot->color;
|
||||
setup = &slot->data->color;
|
||||
spColor_addFloats(color, (setup->r - color->r) * alpha, (setup->g - color->g) * alpha, (setup->b - color->b) * alpha,
|
||||
(setup->a - color->a) * alpha);
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -651,7 +651,6 @@ void _spTwoColorTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton
|
||||
spColor_setFromColor(slot->darkColor, slot->data->darkColor);
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
light = &slot->color;
|
||||
dark = slot->darkColor;
|
||||
setupLight = &slot->data->color;
|
||||
@ -659,6 +658,7 @@ void _spTwoColorTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton
|
||||
spColor_addFloats(light, (setupLight->r - light->r) * alpha, (setupLight->g - light->g) * alpha, (setupLight->b - light->b) * alpha,
|
||||
(setupLight->a - light->a) * alpha);
|
||||
spColor_addFloats(dark, (setupDark->r - dark->r) * alpha, (setupDark->g - dark->g) * alpha, (setupDark->b - dark->b) * alpha, 0);
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -863,7 +863,6 @@ void _spDeformTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton,
|
||||
slot->attachmentVerticesCount = 0;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
if (alpha == 1) {
|
||||
slot->attachmentVerticesCount = 0;
|
||||
return;
|
||||
@ -880,6 +879,7 @@ void _spDeformTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton,
|
||||
vertices[i] *= alpha;
|
||||
}
|
||||
}
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1164,9 +1164,9 @@ void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skel
|
||||
constraint->bendDirection = constraint->data->bendDirection;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
constraint->mix += (constraint->data->mix - constraint->mix) * alpha;
|
||||
constraint->bendDirection = constraint->data->bendDirection;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1250,11 +1250,11 @@ void _spTransformConstraintTimeline_apply (const spTimeline* timeline, spSkeleto
|
||||
constraint->shearMix = data->shearMix;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
constraint->rotateMix += (data->rotateMix - constraint->rotateMix) * alpha;
|
||||
constraint->translateMix += (data->translateMix - constraint->translateMix) * alpha;
|
||||
constraint->scaleMix += (data->scaleMix - constraint->scaleMix) * alpha;
|
||||
constraint->shearMix += (data->shearMix - constraint->shearMix) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
return;
|
||||
@ -1341,8 +1341,8 @@ void _spPathConstraintPositionTimeline_apply(const spTimeline* timeline, spSkele
|
||||
constraint->position = constraint->data->position;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
constraint->position += (constraint->data->position - constraint->position) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1406,8 +1406,8 @@ void _spPathConstraintSpacingTimeline_apply(const spTimeline* timeline, spSkelet
|
||||
constraint->spacing = constraint->data->spacing;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
constraint->spacing += (constraint->data->spacing - constraint->spacing) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1476,9 +1476,9 @@ void _spPathConstraintMixTimeline_apply(const spTimeline* timeline, spSkeleton*
|
||||
constraint->translateMix = constraint->data->translateMix;
|
||||
return;
|
||||
case SP_MIX_POSE_CURRENT:
|
||||
case SP_MIX_POSE_CURRENT_LAYERED: /* to appease compiler */
|
||||
constraint->rotateMix += (constraint->data->rotateMix - constraint->rotateMix) * alpha;
|
||||
constraint->translateMix += (constraint->data->translateMix - constraint->translateMix) * alpha;
|
||||
case SP_MIX_POSE_CURRENT_LAYERED:; /* to appease compiler */
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -419,9 +419,10 @@ float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* t
|
||||
spTrackEntry* from = to->mixingFrom;
|
||||
if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton, currentPose);
|
||||
|
||||
if (to->mixDuration == 0) /* Single frame mix to undo mixingFrom changes. */
|
||||
if (to->mixDuration == 0) { /* Single frame mix to undo mixingFrom changes. */
|
||||
mix = 1;
|
||||
else {
|
||||
currentPose = SP_MIX_POSE_SETUP;
|
||||
} else {
|
||||
mix = to->mixTime / to->mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
#define PATHCONSTRAINT_NONE -1
|
||||
#define PATHCONSTRAINT_BEFORE -2
|
||||
#define PATHCONSTRAINT_AFTER -3
|
||||
#define EPSILON 0.00001f
|
||||
|
||||
spPathConstraint* spPathConstraint_create (spPathConstraintData* data, const spSkeleton* skeleton) {
|
||||
int i;
|
||||
@ -113,13 +114,17 @@ void spPathConstraint_apply (spPathConstraint* self) {
|
||||
lengths = self->lengths;
|
||||
}
|
||||
for (i = 0, n = spacesCount - 1; i < n;) {
|
||||
spBone* bone = bones[i];
|
||||
spBone *bone = bones[i];
|
||||
setupLength = bone->data->length;
|
||||
if (setupLength == 0) setupLength = 0.000000001f;
|
||||
x = setupLength * bone->a, y = setupLength * bone->c;
|
||||
length = SQRT(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
if (setupLength < EPSILON) {
|
||||
if (scale) lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
} else {
|
||||
x = setupLength * bone->a, y = setupLength * bone->c;
|
||||
length = SQRT(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 1; i < spacesCount; i++) {
|
||||
|
||||
@ -220,9 +220,10 @@ namespace Spine {
|
||||
if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose);
|
||||
|
||||
float mix;
|
||||
if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
|
||||
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
else {
|
||||
currentPose = MixPose.Setup;
|
||||
} else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
@ -258,13 +259,17 @@ namespace Spine {
|
||||
break;
|
||||
case Dip:
|
||||
pose = MixPose.Setup;
|
||||
alpha = alphaDip;
|
||||
alpha = mix == 1 ? 0 : alphaDip;
|
||||
break;
|
||||
default:
|
||||
pose = MixPose.Setup;
|
||||
alpha = alphaDip;
|
||||
var dipMix = timelineDipMix[i];
|
||||
alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
|
||||
if (mix == 1) {
|
||||
alpha = 0;
|
||||
} else {
|
||||
alpha = alphaDip;
|
||||
var dipMix = timelineDipMix[i];
|
||||
alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
from.totalAlpha += alpha;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// System.Collections.Generic.List
|
||||
//
|
||||
// Authors:
|
||||
@ -90,7 +90,18 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public ExposedList<T> Resize (int newSize) {
|
||||
if (newSize > Items.Length) Array.Resize(ref Items, newSize);
|
||||
int itemsLength = Items.Length;
|
||||
var oldItems = Items;
|
||||
if (newSize > itemsLength) {
|
||||
Array.Resize(ref Items, newSize);
|
||||
// var newItems = new T[newSize];
|
||||
// Array.Copy(oldItems, newItems, Count);
|
||||
// Items = newItems;
|
||||
} else if (newSize < itemsLength) {
|
||||
// Allow nulling of T reference type to allow GC.
|
||||
for (int i = newSize; i < itemsLength; i++)
|
||||
oldItems[i] = default(T);
|
||||
}
|
||||
Count = newSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ using System;
|
||||
namespace Spine {
|
||||
public class PathConstraint : IConstraint {
|
||||
const int NONE = -1, BEFORE = -2, AFTER = -3;
|
||||
const float Epsilon = 0.00001f;
|
||||
|
||||
internal PathConstraintData data;
|
||||
internal ExposedList<Bone> bones;
|
||||
@ -93,11 +94,15 @@ namespace Spine {
|
||||
for (int i = 0, n = spacesCount - 1; i < n;) {
|
||||
Bone bone = bonesItems[i];
|
||||
float setupLength = bone.data.length;
|
||||
if (setupLength == 0) setupLength = 0.000000001f;
|
||||
float x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
float length = (float)Math.Sqrt(x * x + y * y);
|
||||
if (scale) lengths.Items[i] = length;
|
||||
spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
if (setupLength < PathConstraint.Epsilon) {
|
||||
if (scale) lengths.Items[i] = 0;
|
||||
spaces.Items[++i] = 0;
|
||||
} else {
|
||||
float x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
float length = (float)Math.Sqrt(x * x + y * y);
|
||||
if (scale) lengths.Items[i] = length;
|
||||
spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < spacesCount; i++)
|
||||
@ -122,7 +127,7 @@ namespace Spine {
|
||||
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
|
||||
if (scale) {
|
||||
float length = lengths.Items[i];
|
||||
if (length != 0) {
|
||||
if (length >= PathConstraint.Epsilon) {
|
||||
float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
@ -134,7 +139,7 @@ namespace Spine {
|
||||
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
|
||||
if (tangents)
|
||||
r = positions[p - 1];
|
||||
else if (spaces.Items[i + 1] == 0)
|
||||
else if (spaces.Items[i + 1] < PathConstraint.Epsilon)
|
||||
r = positions[p + 2];
|
||||
else
|
||||
r = MathUtils.Atan2(dy, dx);
|
||||
@ -230,7 +235,7 @@ namespace Spine {
|
||||
path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0);
|
||||
}
|
||||
AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
|
||||
tangents || (i > 0 && space == 0));
|
||||
tangents || (i > 0 && space < PathConstraint.Epsilon));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@ -378,7 +383,7 @@ namespace Spine {
|
||||
}
|
||||
break;
|
||||
}
|
||||
AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space == 0));
|
||||
AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@ -399,13 +404,14 @@ namespace Spine {
|
||||
|
||||
static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
|
||||
float[] output, int o, bool tangents) {
|
||||
if (p == 0 || float.IsNaN(p)) p = 0.0001f;
|
||||
if (p < PathConstraint.Epsilon || float.IsNaN(p)) p = PathConstraint.Epsilon;
|
||||
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
|
||||
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
|
||||
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
|
||||
output[o] = x;
|
||||
output[o + 1] = y;
|
||||
if (tangents) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
|
||||
if (tangents)
|
||||
output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ namespace Spine {
|
||||
public ExposedList<int> ClippedTriangles { get { return clippedTriangles; } }
|
||||
public ExposedList<float> ClippedUVs { get { return clippedUVs; } }
|
||||
|
||||
public bool IsClipping () { return clipAttachment != null; }
|
||||
public bool IsClipping { get { return clipAttachment != null; } }
|
||||
|
||||
public int ClipStart (Slot slot, ClippingAttachment clip) {
|
||||
if (clipAttachment != null) return 0;
|
||||
@ -89,7 +89,7 @@ namespace Spine {
|
||||
clippedVertices.Clear();
|
||||
clippedUVs.Clear();
|
||||
clippedTriangles.Clear();
|
||||
//outer: // libgdx
|
||||
//outer:
|
||||
for (int i = 0; i < trianglesLength; i += 3) {
|
||||
int vertexOffset = triangles[i] << 1;
|
||||
float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
|
||||
@ -258,7 +258,7 @@ namespace Spine {
|
||||
return clipped;
|
||||
}
|
||||
|
||||
public static void MakeClockwise (ExposedList<float> polygon) {
|
||||
static void MakeClockwise (ExposedList<float> polygon) {
|
||||
float[] vertices = polygon.Items;
|
||||
int verticeslength = polygon.Count;
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
public class Triangulator {
|
||||
internal class Triangulator {
|
||||
private readonly ExposedList<ExposedList<float>> convexPolygons = new ExposedList<ExposedList<float>>();
|
||||
private readonly ExposedList<ExposedList<int>> convexPolygonsIndices = new ExposedList<ExposedList<int>>();
|
||||
|
||||
|
||||
@ -51,7 +51,31 @@ import com.esotericsoftware.spine.Animation.Timeline;
|
||||
* See <a href='http://esotericsoftware.com/spine-applying-animations/'>Applying Animations</a> in the Spine Runtimes Guide. */
|
||||
public class AnimationState {
|
||||
static private final Animation emptyAnimation = new Animation("<empty>", new Array(0), 0);
|
||||
static private final int SUBSEQUENT = 0, FIRST = 1, DIP = 2, DIP_MIX = 3;
|
||||
|
||||
/** 1) A previously applied timeline has set this property.<br>
|
||||
* Result: Mix from the current pose to the timeline pose. */
|
||||
static private final int SUBSEQUENT = 0;
|
||||
/** 1) This is the first timeline to set this property.<br>
|
||||
* 2) The next track entry applied after this one does not have a timeline to set this property.<br>
|
||||
* Result: Mix from the setup pose to the timeline pose. */
|
||||
static private final int FIRST = 1;
|
||||
/** 1) This is the first timeline to set this property.<br>
|
||||
* 2) The next track entry to be applied does have a timeline to set this property.<br>
|
||||
* 3) The next track entry after that one does not have a timeline to set this property.<br>
|
||||
* Result: Mix from the setup pose to the timeline pose, but avoid the "dipping" problem by not using the mix percentage. This
|
||||
* means the timeline pose won't mix out toward the setup pose. A subsequent timeline will set this property using a mix. */
|
||||
static private final int DIP = 2;
|
||||
/** 1) This is the first timeline to set this property.<br>
|
||||
* 2) The next track entry to be applied does have a timeline to set this property.<br>
|
||||
* 3) The next track entry after that one does have a timeline to set this property.<br>
|
||||
* 4) timelineDipMix stores the first subsequent track entry that does not have a timeline to set this property.<br>
|
||||
* Result: This is the same as DIP except the mix percentage from the timelineDipMix track entry is used. This handles when
|
||||
* more than 2 track entries in a row have a timeline which sets the same property.<br>
|
||||
* Eg, A -> B -> C -> D where A, B, and C have a timeline to set the same property, but D does not. When A is applied, A's mix
|
||||
* percentage is not used to avoid dipping, however a later track entry (D, the first entry without a timeline which sets the
|
||||
* property) is actually mixing out A (which affects B and C). Without using D's mix percentage, A would be applied fully until
|
||||
* mixed out, causing snapping. */
|
||||
static private final int DIP_MIX = 3;
|
||||
|
||||
private AnimationStateData data;
|
||||
final Array<TrackEntry> tracks = new Array();
|
||||
@ -220,9 +244,10 @@ public class AnimationState {
|
||||
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, currentPose);
|
||||
|
||||
float mix;
|
||||
if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
|
||||
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
else {
|
||||
currentPose = MixPose.setup;
|
||||
} else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
@ -261,10 +286,8 @@ public class AnimationState {
|
||||
break;
|
||||
default:
|
||||
pose = MixPose.setup;
|
||||
alpha = alphaDip;
|
||||
TrackEntry dipMix = (TrackEntry)timelineDipMix[i];
|
||||
alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
|
||||
break;
|
||||
alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
|
||||
}
|
||||
from.totalAlpha += alpha;
|
||||
if (timeline instanceof RotateTimeline)
|
||||
@ -517,12 +540,19 @@ public class AnimationState {
|
||||
}
|
||||
|
||||
/** Sets an empty animation for a track, discarding any queued animations, and sets the track entry's
|
||||
* {@link TrackEntry#getMixDuration()}.
|
||||
* {@link TrackEntry#getMixDuration()}. An empty animation has no timelines and serves as a placeholder for mixing in or out.
|
||||
* <p>
|
||||
* Mixing out is done by setting an empty animation. A mix duration of 0 still mixes out over one frame.
|
||||
* Mixing out is done by setting an empty animation with a mix duration using either {@link #setEmptyAnimation(int, float)},
|
||||
* {@link #setEmptyAnimations(float)}, or {@link #addEmptyAnimation(int, float, float)}. Mixing to an empty animation causes
|
||||
* the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation
|
||||
* transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of
|
||||
* 0 still mixes out over one frame.
|
||||
* <p>
|
||||
* To mix in, first set an empty animation and add an animation using {@link #addAnimation(int, Animation, boolean, float)},
|
||||
* then set the {@link TrackEntry#setMixDuration(float)} on the returned track entry. */
|
||||
* Mixing in is done by first setting an empty animation, then adding an animation using
|
||||
* {@link #addAnimation(int, Animation, boolean, float)} and on the returned track entry, set the
|
||||
* {@link TrackEntry#setMixDuration(float)}. Mixing from an empty animation causes the new animation to be applied more and
|
||||
* more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
|
||||
* setup pose value if no lower tracks key the property to the value keyed in the new animation. */
|
||||
public TrackEntry setEmptyAnimation (int trackIndex, float mixDuration) {
|
||||
TrackEntry entry = setAnimation(trackIndex, emptyAnimation, false);
|
||||
entry.mixDuration = mixDuration;
|
||||
@ -533,6 +563,8 @@ public class AnimationState {
|
||||
/** Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
|
||||
* {@link TrackEntry#getMixDuration()}. If the track is empty, it is equivalent to calling
|
||||
* {@link #setEmptyAnimation(int, float)}.
|
||||
* <p>
|
||||
* See {@link #setEmptyAnimation(int, float)}.
|
||||
* @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
|
||||
* duration of the previous track minus any mix duration plus <code>delay</code>.
|
||||
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
|
||||
@ -810,8 +842,8 @@ public class AnimationState {
|
||||
* is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the
|
||||
* properties keyed by the animation are set to the setup pose and the track is cleared.
|
||||
* <p>
|
||||
* It may be desired to use {@link AnimationState#addEmptyAnimation(int, float, float)} to mix the properties back to the
|
||||
* setup pose over time, rather than have it happen instantly. */
|
||||
* It may be desired to use {@link AnimationState#addEmptyAnimation(int, float, float)} rather than have the animation
|
||||
* abruptly cease being applied. */
|
||||
public float getTrackEnd () {
|
||||
return trackEnd;
|
||||
}
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
|
||||
package com.esotericsoftware.spine;
|
||||
|
||||
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.FloatArray;
|
||||
import com.esotericsoftware.spine.PathConstraintData.PositionMode;
|
||||
@ -46,6 +45,7 @@ import com.esotericsoftware.spine.utils.SpineUtils;
|
||||
* See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide. */
|
||||
public class PathConstraint implements Constraint {
|
||||
static private final int NONE = -1, BEFORE = -2, AFTER = -3;
|
||||
static private final float epsilon = 0.00001f;
|
||||
|
||||
final PathConstraintData data;
|
||||
final Array<Bone> bones;
|
||||
@ -113,11 +113,15 @@ public class PathConstraint implements Constraint {
|
||||
for (int i = 0, n = spacesCount - 1; i < n;) {
|
||||
Bone bone = (Bone)bones[i];
|
||||
float setupLength = bone.data.length;
|
||||
if (setupLength == 0) setupLength = 0.000000001f;
|
||||
float x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
float length = (float)Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
if (setupLength < epsilon) {
|
||||
if (scale) lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
} else {
|
||||
float x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
float length = (float)Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < spacesCount; i++)
|
||||
@ -142,7 +146,7 @@ public class PathConstraint implements Constraint {
|
||||
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
|
||||
if (scale) {
|
||||
float length = lengths[i];
|
||||
if (length != 0) {
|
||||
if (length >= epsilon) {
|
||||
float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
@ -154,7 +158,7 @@ public class PathConstraint implements Constraint {
|
||||
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
|
||||
if (tangents)
|
||||
r = positions[p - 1];
|
||||
else if (spaces[i + 1] == 0)
|
||||
else if (spaces[i + 1] < epsilon)
|
||||
r = positions[p + 2];
|
||||
else
|
||||
r = (float)Math.atan2(dy, dx);
|
||||
@ -247,7 +251,7 @@ public class PathConstraint implements Constraint {
|
||||
path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
|
||||
}
|
||||
addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
|
||||
tangents || (i > 0 && space == 0));
|
||||
tangents || (i > 0 && space < epsilon));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@ -395,7 +399,7 @@ public class PathConstraint implements Constraint {
|
||||
}
|
||||
break;
|
||||
}
|
||||
addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
|
||||
addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space < epsilon));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@ -416,13 +420,14 @@ public class PathConstraint implements Constraint {
|
||||
|
||||
private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
|
||||
float[] out, int o, boolean tangents) {
|
||||
if (p == 0 || Float.isNaN(p)) p = 0.0001f;
|
||||
if (p < epsilon || Float.isNaN(p)) p = epsilon;
|
||||
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
|
||||
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
|
||||
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
|
||||
out[o] = x;
|
||||
out[o + 1] = y;
|
||||
if (tangents) out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
|
||||
if (tangents)
|
||||
out[o + 2] = (float)Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
|
||||
}
|
||||
|
||||
public int getOrder () {
|
||||
|
||||
@ -57,6 +57,7 @@ import com.badlogic.gdx.graphics.Texture.TextureFilter;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
@ -96,6 +97,7 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
|
||||
OrthographicCamera camera;
|
||||
TwoColorPolygonBatch batch;
|
||||
TextureAtlas atlas;
|
||||
SkeletonRenderer renderer;
|
||||
SkeletonRendererDebug debugRenderer;
|
||||
SkeletonData skeletonData;
|
||||
@ -155,7 +157,20 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
}
|
||||
}
|
||||
TextureAtlasData data = !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false);
|
||||
TextureAtlas atlas = new TextureAtlas(data) {
|
||||
|
||||
if (data != null) {
|
||||
boolean linear = true;
|
||||
for (int i = 0, n = data.getPages().size; i < n; i++) {
|
||||
Page page = data.getPages().get(i);
|
||||
if (page.minFilter != TextureFilter.Linear || page.magFilter != TextureFilter.Linear) {
|
||||
linear = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui.linearCheckbox.setChecked(linear);
|
||||
}
|
||||
|
||||
atlas = new TextureAtlas(data) {
|
||||
public AtlasRegion findRegion (String name) {
|
||||
AtlasRegion region = super.findRegion(name);
|
||||
if (region == null) {
|
||||
@ -416,6 +431,8 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
|
||||
CheckBox premultipliedCheckbox = new CheckBox("Premultiplied", skin);
|
||||
|
||||
CheckBox linearCheckbox = new CheckBox("Linear", skin);
|
||||
|
||||
TextButton bonesSetupPoseButton = new TextButton("Bones", skin);
|
||||
TextButton slotsSetupPoseButton = new TextButton("Slots", skin);
|
||||
TextButton setupPoseButton = new TextButton("Both", skin);
|
||||
@ -458,6 +475,8 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
|
||||
premultipliedCheckbox.setChecked(true);
|
||||
|
||||
linearCheckbox.setChecked(true);
|
||||
|
||||
loopCheckbox.setChecked(true);
|
||||
|
||||
scaleSlider.setValue(1);
|
||||
@ -519,7 +538,13 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
root.add();
|
||||
root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row();
|
||||
root.add("Atlas alpha:");
|
||||
root.add(premultipliedCheckbox).row();
|
||||
{
|
||||
Table table = table();
|
||||
table.add(premultipliedCheckbox);
|
||||
table.add("Filtering:").growX().getActor().setAlignment(Align.right);
|
||||
table.add(linearCheckbox);
|
||||
root.add(table).fill().row();
|
||||
}
|
||||
|
||||
root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(-3, 0, 1, 0)
|
||||
.row();
|
||||
@ -739,6 +764,15 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
}
|
||||
});
|
||||
|
||||
linearCheckbox.addListener(new ChangeListener() {
|
||||
public void changed (ChangeEvent event, Actor actor) {
|
||||
if (atlas == null) return;
|
||||
TextureFilter filter = linearCheckbox.isChecked() ? TextureFilter.Linear : TextureFilter.Nearest;
|
||||
for (Texture texture : atlas.getTextures())
|
||||
texture.setFilter(filter, filter);
|
||||
}
|
||||
});
|
||||
|
||||
skinList.addListener(new ChangeListener() {
|
||||
public void changed (ChangeEvent event, Actor actor) {
|
||||
if (skeleton != null) {
|
||||
@ -777,19 +811,19 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
|
||||
public boolean touchDown (int screenX, int screenY, int pointer, int button) {
|
||||
offsetX = screenX;
|
||||
offsetY = Gdx.graphics.getHeight() - screenY;
|
||||
offsetY = Gdx.graphics.getHeight() - 1 - screenY;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean touchDragged (int screenX, int screenY, int pointer) {
|
||||
float deltaX = screenX - offsetX;
|
||||
float deltaY = Gdx.graphics.getHeight() - screenY - offsetY;
|
||||
float deltaY = Gdx.graphics.getHeight() - 1 - screenY - offsetY;
|
||||
|
||||
camera.position.x -= deltaX * camera.zoom;
|
||||
camera.position.y -= deltaY * camera.zoom;
|
||||
|
||||
offsetX = screenX;
|
||||
offsetY = Gdx.graphics.getHeight() - screenY;
|
||||
offsetY = Gdx.graphics.getHeight() - 1 - screenY;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -834,7 +868,7 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
}
|
||||
|
||||
Table table (Actor... actors) {
|
||||
Table table = new Table();
|
||||
Table table = new Table(skin);
|
||||
table.defaults().space(6);
|
||||
table.add(actors);
|
||||
return table;
|
||||
|
||||
@ -428,6 +428,7 @@ function AnimationState:applyMixingFrom (to, skeleton, currentPose)
|
||||
local mix = 0
|
||||
if to.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes.
|
||||
mix = 1
|
||||
currentPose = MixPose.setup
|
||||
else
|
||||
mix = to.mixTime / to.mixDuration
|
||||
if mix > 1 then mix = 1 end
|
||||
|
||||
@ -55,6 +55,7 @@ PathConstraint.__index = PathConstraint
|
||||
PathConstraint.NONE = -1
|
||||
PathConstraint.BEFORE = -2
|
||||
PathConstraint.AFTER = -3
|
||||
PathConstraint.epsilon = 0.00001
|
||||
|
||||
function PathConstraint.new (data, skeleton)
|
||||
if not data then error("data cannot be nil", 2) end
|
||||
@ -118,13 +119,22 @@ function PathConstraint:update ()
|
||||
while i < n do
|
||||
local bone = bones[i + 1];
|
||||
local setupLength = bone.data.length
|
||||
if setupLength == 0 then setupLength = 0.0000001 end
|
||||
local x = setupLength * bone.a
|
||||
local y = setupLength * bone.c
|
||||
local length = math_sqrt(x * x + y * y)
|
||||
if scale then lengths[i + 1] = length end
|
||||
i = i + 1
|
||||
if lengthSpacing then spaces[i + 1] = (setupLength + spacing) * length / setupLength else spaces[i + 1] = spacing * length / setupLength end
|
||||
if setupLength < PathConstraint.epsilon then
|
||||
if scale then lengths[i + 1] = 0 end
|
||||
i = i + 1
|
||||
spaces[i + 1] = 0
|
||||
else
|
||||
local x = setupLength * bone.a
|
||||
local y = setupLength * bone.c
|
||||
local length = math_sqrt(x * x + y * y)
|
||||
if scale then lengths[i + 1] = length end
|
||||
i = i + 1
|
||||
if lengthSpacing then
|
||||
spaces[i + 1] = (setupLength + spacing) * length / setupLength
|
||||
else
|
||||
spaces[i + 1] = spacing * length / setupLength
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local i = 1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
1
spine-ts/build/spine-all.d.ts
vendored
1
spine-ts/build/spine-all.d.ts
vendored
@ -752,6 +752,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1715,8 +1715,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -3290,13 +3292,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3602,6 +3609,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
spine-ts/build/spine-canvas.d.ts
vendored
1
spine-ts/build/spine-canvas.d.ts
vendored
@ -752,6 +752,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1715,8 +1715,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -3290,13 +3292,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3602,6 +3609,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
spine-ts/build/spine-core.d.ts
vendored
1
spine-ts/build/spine-core.d.ts
vendored
@ -681,6 +681,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1266,8 +1266,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -2969,13 +2971,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3281,6 +3288,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
spine-ts/build/spine-threejs.d.ts
vendored
1
spine-ts/build/spine-threejs.d.ts
vendored
@ -681,6 +681,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1266,8 +1266,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -2969,13 +2971,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3281,6 +3288,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
spine-ts/build/spine-webgl.d.ts
vendored
1
spine-ts/build/spine-webgl.d.ts
vendored
@ -681,6 +681,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1266,8 +1266,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -2969,13 +2971,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3281,6 +3288,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
spine-ts/build/spine-widget.d.ts
vendored
1
spine-ts/build/spine-widget.d.ts
vendored
@ -681,6 +681,7 @@ declare module spine {
|
||||
static NONE: number;
|
||||
static BEFORE: number;
|
||||
static AFTER: number;
|
||||
static epsilon: number;
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
target: Slot;
|
||||
|
||||
@ -1266,8 +1266,10 @@ var spine;
|
||||
if (from.mixingFrom != null)
|
||||
this.applyMixingFrom(from, skeleton, currentPose);
|
||||
var mix = 0;
|
||||
if (to.mixDuration == 0)
|
||||
if (to.mixDuration == 0) {
|
||||
mix = 1;
|
||||
currentPose = spine.MixPose.setup;
|
||||
}
|
||||
else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1)
|
||||
@ -2969,13 +2971,18 @@ var spine;
|
||||
for (var i = 0, n = spacesCount - 1; i < n;) {
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.data.length;
|
||||
if (setupLength == 0)
|
||||
setupLength = 0.0000001;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale)
|
||||
lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
}
|
||||
else {
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length_1 = Math.sqrt(x * x + y * y);
|
||||
if (scale)
|
||||
lengths[i] = length_1;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length_1 / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -3281,6 +3288,7 @@ var spine;
|
||||
PathConstraint.NONE = -1;
|
||||
PathConstraint.BEFORE = -2;
|
||||
PathConstraint.AFTER = -3;
|
||||
PathConstraint.epsilon = 0.00001;
|
||||
return PathConstraint;
|
||||
}());
|
||||
spine.PathConstraint = PathConstraint;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -191,9 +191,10 @@ module spine {
|
||||
if (from.mixingFrom != null) this.applyMixingFrom(from, skeleton, currentPose);
|
||||
|
||||
let mix = 0;
|
||||
if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
|
||||
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
else {
|
||||
currentPose = MixPose.setup;
|
||||
} else {
|
||||
mix = to.mixTime / to.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
module spine {
|
||||
export class PathConstraint implements Constraint {
|
||||
static NONE = -1; static BEFORE = -2; static AFTER = -3;
|
||||
static epsilon = 0.00001;
|
||||
|
||||
data: PathConstraintData;
|
||||
bones: Array<Bone>;
|
||||
@ -81,11 +82,15 @@ module spine {
|
||||
for (let i = 0, n = spacesCount - 1; i < n;) {
|
||||
let bone = bones[i];
|
||||
let setupLength = bone.data.length;
|
||||
if (setupLength == 0) setupLength = 0.0000001;
|
||||
let x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
let length = Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (scale) lengths[i] = 0;
|
||||
spaces[++i] = 0;
|
||||
} else {
|
||||
let x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
let length = Math.sqrt(x * x + y * y);
|
||||
if (scale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 1; i < spacesCount; i++)
|
||||
|
||||
14
spine-ts/webgl/example/js/ace.js
Normal file
14
spine-ts/webgl/example/js/ace.js
Normal file
File diff suppressed because one or more lines are too long
1
spine-ts/webgl/example/js/mode-html.js
Normal file
1
spine-ts/webgl/example/js/mode-html.js
Normal file
File diff suppressed because one or more lines are too long
1
spine-ts/webgl/example/js/mode-javascript.js
Normal file
1
spine-ts/webgl/example/js/mode-javascript.js
Normal file
File diff suppressed because one or more lines are too long
1
spine-ts/webgl/example/js/theme-monokai.js
Normal file
1
spine-ts/webgl/example/js/theme-monokai.js
Normal file
@ -0,0 +1 @@
|
||||
define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-monokai",t.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
||||
1
spine-ts/webgl/example/js/worker-html.js
Normal file
1
spine-ts/webgl/example/js/worker-html.js
Normal file
File diff suppressed because one or more lines are too long
1
spine-ts/webgl/example/js/worker-javascript.js
Normal file
1
spine-ts/webgl/example/js/worker-javascript.js
Normal file
File diff suppressed because one or more lines are too long
96
spine-ts/webgl/example/tutorial.html
Normal file
96
spine-ts/webgl/example/tutorial.html
Normal file
@ -0,0 +1,96 @@
|
||||
<html>
|
||||
<script src="../../build/spine-webgl.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
|
||||
<style>
|
||||
/* Dead Simple Grid (c) 2015 Vladimir Agafonkin */
|
||||
|
||||
.row .row { margin: 0 -1.5em; }
|
||||
.col { padding: 0 1.5em; }
|
||||
|
||||
.row:after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
@media only screen { .col {
|
||||
float: left;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
|
||||
@media only screen and (min-width: 30em) {
|
||||
.content { width: 50%; height: 100%; padding: 0 }
|
||||
.sidebar { width: 50%; height: 100%; padding: 0 }
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border:none;
|
||||
}
|
||||
.panel {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
.buttons {
|
||||
position: absolute;
|
||||
top: 5; left: 5;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="buttons">
|
||||
<button id="playButton">Run</button>
|
||||
<button id="stopButton">Stop</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col content">
|
||||
<iframe id="iframe"></iframe>
|
||||
</div>
|
||||
<div class="col sidebar">
|
||||
<div id="codeJs" class="panel"></div>
|
||||
<div id="codeHtml" class="panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script id="initialJs" type="text/plain">
|
||||
var canvas = document.getElementById("canvas");
|
||||
var config = { alpha: false };
|
||||
var context = new spine.webgl.ManagedWebGLRenderingContext(canvas, config);
|
||||
var gl = context.gl;
|
||||
|
||||
function render() {
|
||||
gl.clearColor(0.2, 0.2, 0.2, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
requestAnimationFrame(render);
|
||||
</script>
|
||||
|
||||
<script src="js/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var editorJs = ace.edit("codeJs");
|
||||
editorJs.setTheme("ace/theme/monokai");
|
||||
editorJs.getSession().setMode("ace/mode/javascript");
|
||||
editorJs.setValue(document.getElementById("initialJs").innerHTML);
|
||||
|
||||
var editorHtml = ace.edit("codeHtml");
|
||||
editorHtml.setTheme("ace/theme/monokai");
|
||||
editorHtml.getSession().setMode("ace/mode/html");
|
||||
editorHtml.setValue('<script src="../../build/spine-webgl.js"><\/script>\n<canvas id="canvas" style="width: 100%; height: 98vh;"></canvas>');
|
||||
|
||||
$("#playButton").click(function() {
|
||||
var iframe = document.getElementById("iframe");
|
||||
var source = "<html><body>" + editorHtml.getValue() + "<script>" + editorJs.getValue() + "<\/script></body></html>";
|
||||
iframe.srcdoc = source;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@ -25,7 +25,11 @@ void FRuntimeMeshComponentDetails::CustomizeDetails( IDetailLayoutBuilder& Detai
|
||||
const FText ConvertToStaticMeshText = LOCTEXT("ConvertToStaticMesh", "Create StaticMesh");
|
||||
|
||||
// Cache set of selected things
|
||||
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION < 18
|
||||
SelectedObjectsList = DetailBuilder.GetDetailsView().GetSelectedObjects();
|
||||
#else
|
||||
SelectedObjectsList = DetailBuilder.GetDetailsView()->GetSelectedObjects();
|
||||
#endif
|
||||
|
||||
RuntimeMeshCategory.AddCustomRow(ConvertToStaticMeshText, false)
|
||||
.NameContent()
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
@ -4,7 +4,7 @@ MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
icon: {fileID: 2800000, guid: 3fc714a0dc1cf6b4b959e073fff2844e, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -726,6 +726,9 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
|
||||
void DoRenderPreview (bool drawHandles) {
|
||||
if (this.PreviewUtilityCamera.activeTexture == null || this.PreviewUtilityCamera.targetTexture == null )
|
||||
return;
|
||||
|
||||
GameObject go = this.m_previewInstance;
|
||||
|
||||
if (m_requireRefresh && go != null) {
|
||||
|
||||
@ -4,7 +4,7 @@ MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
icon: {fileID: 2800000, guid: 68defdbc95b30a74a9ad396bfc9a2277, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
BIN
spine-unity/Assets/spine-unity/Editor/GUI/AtlasAsset Icon.png
Normal file
BIN
spine-unity/Assets/spine-unity/Editor/GUI/AtlasAsset Icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 591 B |
@ -0,0 +1,92 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fc714a0dc1cf6b4b959e073fff2844e
|
||||
timeCreated: 1508165143
|
||||
licenseType: Free
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 0
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Standalone
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: Android
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
- buildTarget: WebGL
|
||||
maxTextureSize: 1024
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 563 B |
@ -375,12 +375,10 @@ namespace Spine.Unity.Editor {
|
||||
ISkeletonComponent skeletonComponent = GetTargetSkeletonComponent(property);
|
||||
var validSkins = new List<Skin>();
|
||||
|
||||
|
||||
|
||||
if (skeletonComponent != null && targetAttribute.currentSkinOnly) {
|
||||
Skin currentSkin = null;
|
||||
|
||||
var skinProperty = property.FindPropertyRelative(targetAttribute.skinField);
|
||||
var skinProperty = property.FindBaseOrSiblingProperty(targetAttribute.skinField);
|
||||
if (skinProperty != null) currentSkin = skeletonComponent.Skeleton.Data.FindSkin(skinProperty.stringValue);
|
||||
|
||||
currentSkin = currentSkin ?? skeletonComponent.Skeleton.Skin;
|
||||
|
||||
@ -230,9 +230,16 @@ namespace Spine.Unity.Editor {
|
||||
EditorApplication.hierarchyWindowItemOnGUI += HierarchyDragAndDrop;
|
||||
|
||||
// Hierarchy Icons
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
EditorApplication.playModeStateChanged -= HierarchyIconsOnPlaymodeStateChanged;
|
||||
EditorApplication.playModeStateChanged += HierarchyIconsOnPlaymodeStateChanged;
|
||||
HierarchyIconsOnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
|
||||
#else
|
||||
EditorApplication.playmodeStateChanged -= HierarchyIconsOnPlaymodeStateChanged;
|
||||
EditorApplication.playmodeStateChanged += HierarchyIconsOnPlaymodeStateChanged;
|
||||
HierarchyIconsOnPlaymodeStateChanged();
|
||||
#endif
|
||||
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
@ -255,7 +262,11 @@ namespace Spine.Unity.Editor {
|
||||
showHierarchyIcons = EditorGUILayout.Toggle(new GUIContent("Show Hierarchy Icons", "Show relevant icons on GameObjects with Spine Components on them. Disable this if you have large, complex scenes."), showHierarchyIcons);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
EditorPrefs.SetBool(SHOW_HIERARCHY_ICONS_KEY, showHierarchyIcons);
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
HierarchyIconsOnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
|
||||
#else
|
||||
HierarchyIconsOnPlaymodeStateChanged();
|
||||
#endif
|
||||
}
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
@ -496,7 +507,11 @@ namespace Spine.Unity.Editor {
|
||||
#endregion
|
||||
|
||||
#region Hierarchy
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
static void HierarchyIconsOnPlaymodeStateChanged (PlayModeStateChange stateChange) {
|
||||
#else
|
||||
static void HierarchyIconsOnPlaymodeStateChanged () {
|
||||
#endif
|
||||
skeletonRendererTable.Clear();
|
||||
skeletonUtilityBoneTable.Clear();
|
||||
boundingBoxFollowerTable.Clear();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -113,37 +113,37 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
#region Runtime RegionAttachments
|
||||
/// <summary>
|
||||
/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses a new AtlasPage with the Material provided./// </summary>
|
||||
public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material) {
|
||||
return sprite.ToRegionAttachment(material.ToSpineAtlasPage());
|
||||
public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material, float rotation = 0f) {
|
||||
return sprite.ToRegionAttachment(material.ToSpineAtlasPage(), rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses the AtlasPage provided./// </summary>
|
||||
public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page) {
|
||||
public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page, float rotation = 0f) {
|
||||
if (sprite == null) throw new System.ArgumentNullException("sprite");
|
||||
if (page == null) throw new System.ArgumentNullException("page");
|
||||
var region = sprite.ToAtlasRegion(page);
|
||||
var unitsPerPixel = 1f / sprite.pixelsPerUnit;
|
||||
return region.ToRegionAttachment(sprite.name, unitsPerPixel);
|
||||
return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate texture of the Sprite's texture data. Returns a RegionAttachment that uses it. Use this if you plan to use a premultiply alpha shader such as "Spine/Skeleton"</summary>
|
||||
public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader, TextureFormat textureFormat = SpriteAtlasRegionExtensions.SpineTextureFormat, bool mipmaps = SpriteAtlasRegionExtensions.UseMipMaps, Material materialPropertySource = null) {
|
||||
public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, Material materialPropertySource = null, float rotation = 0f) {
|
||||
if (sprite == null) throw new System.ArgumentNullException("sprite");
|
||||
if (shader == null) throw new System.ArgumentNullException("shader");
|
||||
var region = sprite.ToAtlasRegionPMAClone(shader, textureFormat, mipmaps, materialPropertySource);
|
||||
var unitsPerPixel = 1f / sprite.pixelsPerUnit;
|
||||
return region.ToRegionAttachment(sprite.name, unitsPerPixel);
|
||||
return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
|
||||
}
|
||||
|
||||
public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Material materialPropertySource, TextureFormat textureFormat = SpriteAtlasRegionExtensions.SpineTextureFormat, bool mipmaps = SpriteAtlasRegionExtensions.UseMipMaps) {
|
||||
return sprite.ToRegionAttachmentPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource);
|
||||
public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Material materialPropertySource, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, float rotation = 0f) {
|
||||
return sprite.ToRegionAttachmentPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RegionAttachment from a given AtlasRegion.</summary>
|
||||
public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f) {
|
||||
public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f, float rotation = 0f) {
|
||||
if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName can't be null or empty.", "attachmentName");
|
||||
if (region == null) throw new System.ArgumentNullException("region");
|
||||
|
||||
@ -162,14 +162,13 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
attachment.Path = region.name;
|
||||
attachment.scaleX = 1;
|
||||
attachment.scaleY = 1;
|
||||
attachment.rotation = 0;
|
||||
attachment.rotation = rotation;
|
||||
|
||||
attachment.r = 1;
|
||||
attachment.g = 1;
|
||||
attachment.b = 1;
|
||||
attachment.a = 1;
|
||||
|
||||
|
||||
// pass OriginalWidth and OriginalHeight because UpdateOffset uses it in its calculation.
|
||||
attachment.width = attachment.regionOriginalWidth * scale;
|
||||
attachment.height = attachment.regionOriginalHeight * scale;
|
||||
@ -210,7 +209,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class SpriteAtlasRegionExtensions {
|
||||
public static class AtlasUtilities {
|
||||
internal const TextureFormat SpineTextureFormat = TextureFormat.RGBA32;
|
||||
internal const float DefaultMipmapBias = -0.5f;
|
||||
internal const bool UseMipMaps = false;
|
||||
@ -395,16 +394,98 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
|
||||
#region Runtime Repacking
|
||||
/// <summary>
|
||||
/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.</summary>
|
||||
/// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
|
||||
public static Skin GetRepackedSkin (this Skin o, string newName, Material materialPropertySource, out Material m, out Texture2D t, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
|
||||
return GetRepackedSkin(o, newName, materialPropertySource.shader, out m, out t, maxAtlasSize, padding, textureFormat, mipmaps, materialPropertySource);
|
||||
/// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, but mapped to a new single texture using the same material.</summary>
|
||||
/// <param name="sourceAttachments">The list of attachments to be repacked.</param>
|
||||
/// <param name = "outputAttachments">The List(Attachment) to populate with the newly created Attachment objects.</param>
|
||||
///
|
||||
/// <param name="materialPropertySource">May be null. If no Material property source is provided, no special </param>
|
||||
public static void GetRepackedAttachments (List<Attachment> sourceAttachments, List<Attachment> outputAttachments, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, string newAssetName = "Repacked Attachments") {
|
||||
if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments");
|
||||
if (outputAttachments == null) throw new System.ArgumentNullException("outputAttachments");
|
||||
|
||||
// Use these to detect and use shared regions.
|
||||
var existingRegions = new Dictionary<AtlasRegion, int>();
|
||||
var regionIndexes = new List<int>();
|
||||
var texturesToPack = new List<Texture2D>();
|
||||
var originalRegions = new List<AtlasRegion>();
|
||||
|
||||
outputAttachments.Clear();
|
||||
outputAttachments.AddRange(sourceAttachments);
|
||||
|
||||
int newRegionIndex = 0;
|
||||
for (int i = 0, n = sourceAttachments.Count; i < n; i++) {
|
||||
var originalAttachment = sourceAttachments[i];
|
||||
var newAttachment = originalAttachment.GetClone(true);
|
||||
if (IsRenderable(newAttachment)) {
|
||||
|
||||
var region = newAttachment.GetAtlasRegion();
|
||||
int existingIndex;
|
||||
if (existingRegions.TryGetValue(region, out existingIndex)) {
|
||||
regionIndexes.Add(existingIndex); // Store the region index for the eventual new attachment.
|
||||
} else {
|
||||
originalRegions.Add(region);
|
||||
texturesToPack.Add(region.ToTexture()); // Add the texture to the PackTextures argument
|
||||
existingRegions.Add(region, newRegionIndex); // Add the region to the dictionary of known regions
|
||||
regionIndexes.Add(newRegionIndex); // Store the region index for the eventual new attachment.
|
||||
newRegionIndex++;
|
||||
}
|
||||
|
||||
outputAttachments[i] = newAttachment;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill a new texture with the collected attachment textures.
|
||||
var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, textureFormat, mipmaps);
|
||||
newTexture.mipMapBias = AtlasUtilities.DefaultMipmapBias;
|
||||
newTexture.anisoLevel = texturesToPack[0].anisoLevel;
|
||||
newTexture.name = newAssetName;
|
||||
var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
|
||||
|
||||
// Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments
|
||||
Shader shader = materialPropertySource == null ? Shader.Find("Spine/Skeleton") : materialPropertySource.shader;
|
||||
var newMaterial = new Material(shader);
|
||||
if (materialPropertySource != null) {
|
||||
newMaterial.CopyPropertiesFromMaterial(materialPropertySource);
|
||||
newMaterial.shaderKeywords = materialPropertySource.shaderKeywords;
|
||||
}
|
||||
|
||||
newMaterial.name = newAssetName;
|
||||
newMaterial.mainTexture = newTexture;
|
||||
var page = newMaterial.ToSpineAtlasPage();
|
||||
page.name = newAssetName;
|
||||
|
||||
var repackedRegions = new List<AtlasRegion>();
|
||||
for (int i = 0, n = originalRegions.Count; i < n; i++) {
|
||||
var oldRegion = originalRegions[i];
|
||||
var newRegion = UVRectToAtlasRegion(rects[i], oldRegion.name, page, oldRegion.offsetX, oldRegion.offsetY, oldRegion.rotate);
|
||||
repackedRegions.Add(newRegion);
|
||||
}
|
||||
|
||||
// Map the cloned attachments to the repacked atlas.
|
||||
for (int i = 0, n = outputAttachments.Count; i < n; i++) {
|
||||
var a = outputAttachments[i];
|
||||
a.SetRegion(repackedRegions[regionIndexes[i]]);
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
foreach (var ttp in texturesToPack)
|
||||
UnityEngine.Object.Destroy(ttp);
|
||||
|
||||
outputTexture = newTexture;
|
||||
outputMaterial = newMaterial;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.</summary>
|
||||
/// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
|
||||
public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material m, out Texture2D t, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null) {
|
||||
public static Skin GetRepackedSkin (this Skin o, string newName, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
|
||||
return GetRepackedSkin(o, newName, materialPropertySource.shader, out outputMaterial, out outputTexture, maxAtlasSize, padding, textureFormat, mipmaps, materialPropertySource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.</summary>
|
||||
/// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
|
||||
public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null) {
|
||||
var skinAttachments = o.Attachments;
|
||||
var newSkin = new Skin(newName);
|
||||
|
||||
@ -441,7 +522,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
|
||||
// Fill a new texture with the collected attachment textures.
|
||||
var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, textureFormat, mipmaps);
|
||||
newTexture.mipMapBias = SpriteAtlasRegionExtensions.DefaultMipmapBias;
|
||||
newTexture.mipMapBias = AtlasUtilities.DefaultMipmapBias;
|
||||
newTexture.anisoLevel = texturesToPack[0].anisoLevel;
|
||||
newTexture.name = newName;
|
||||
var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
|
||||
@ -471,12 +552,12 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
a.SetRegion(repackedRegions[regionIndexes[i]]);
|
||||
}
|
||||
|
||||
// // Clean up
|
||||
// foreach (var ttp in texturesToPack)
|
||||
// UnityEngine.Object.Destroy(ttp);
|
||||
// Clean up.
|
||||
foreach (var ttp in texturesToPack)
|
||||
UnityEngine.Object.Destroy(ttp);
|
||||
|
||||
t = newTexture;
|
||||
m = newMaterial;
|
||||
outputTexture = newTexture;
|
||||
outputMaterial = newMaterial;
|
||||
return newSkin;
|
||||
}
|
||||
|
||||
@ -679,7 +760,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
|
||||
}
|
||||
}
|
||||
|
||||
public static class SkinExtensions {
|
||||
public static class SkinUtilities {
|
||||
|
||||
#region Skeleton Skin Extensions
|
||||
/// <summary>
|
||||
|
||||
@ -344,8 +344,8 @@ VertexOutput vert(VertexInput input)
|
||||
output.pos = calculateLocalPos(input.vertex);
|
||||
output.color = calculateVertexColor(input.color);
|
||||
output.texcoord = float3(calculateTextureCoord(input.texcoord), 0);
|
||||
|
||||
float3 viewPos = mul(UNITY_MATRIX_MV, input.vertex);
|
||||
|
||||
float3 viewPos = UnityObjectViewPos(input.vertex); //float3 viewPos = mul(UNITY_MATRIX_MV, input.vertex);
|
||||
#if defined(FIXED_NORMALS_BACKFACE_RENDERING) || defined(_RIM_LIGHTING)
|
||||
float4 powWorld = calculateWorldPos(input.vertex);
|
||||
#endif
|
||||
|
||||
@ -110,9 +110,9 @@ Shader "Spine/SkeletonGraphic Tint Black (Premultiply Alpha)"
|
||||
clip (texColor.a - 0.001);
|
||||
#endif
|
||||
|
||||
return (texColor * IN.color) + float4(((1-texColor.rgb) * (_Black.rgb + float3(IN.uv1.r, IN.uv1.g, IN.uv2.r)) * texColor.a*_Color.a*i.vertexColor.a), 0);
|
||||
return (texColor * IN.color) + float4(((1-texColor.rgb) * (_Black.rgb + float3(IN.uv1.r, IN.uv1.g, IN.uv2.r)) * texColor.a * _Color.a * IN.color.a), 0);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ Shader "Spine/SkeletonGraphic (Premultiply Alpha)"
|
||||
OUT.vertex.xy += (_ScreenParams.zw-1.0) * float2(-1,1);
|
||||
#endif
|
||||
|
||||
OUT.color = IN.color * _Color;
|
||||
OUT.color = IN.color * float4(_Color.rgb * _Color.a, _Color.a); // Combine a PMA version of _Color with vertexColor.
|
||||
return OUT;
|
||||
}
|
||||
|
||||
@ -112,4 +112,4 @@ Shader "Spine/SkeletonGraphic (Premultiply Alpha)"
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ namespace Spine {
|
||||
darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0;
|
||||
|
||||
// clip
|
||||
if (clipper.IsClipping()) {
|
||||
if (clipper.IsClipping) {
|
||||
clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs);
|
||||
vertices = clipper.ClippedVertices.Items;
|
||||
verticesCount = clipper.ClippedVertices.Count >> 1;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user