Merge branch '3.7-beta' into 3.7-beta-cpp

This commit is contained in:
badlogic 2018-08-24 11:37:12 +02:00
commit f5cc2933be
42 changed files with 11066 additions and 10839 deletions

View File

@ -452,7 +452,7 @@ typedef struct spIkConstraintTimeline {
SP_API spIkConstraintTimeline* spIkConstraintTimeline_create (int framesCount); SP_API spIkConstraintTimeline* spIkConstraintTimeline_create (int framesCount);
SP_API void spIkConstraintTimeline_setFrame (spIkConstraintTimeline* self, int frameIndex, float time, float mix, int bendDirection, int /**boolean**/ stretch); SP_API void spIkConstraintTimeline_setFrame (spIkConstraintTimeline* self, int frameIndex, float time, float mix, int bendDirection, int /*boolean*/ compress, int /**boolean**/ stretch);
#ifdef SPINE_SHORT_NAMES #ifdef SPINE_SHORT_NAMES
typedef spIkConstraintTimeline IkConstraintTimeline; typedef spIkConstraintTimeline IkConstraintTimeline;

View File

@ -49,6 +49,7 @@ typedef struct spIkConstraint {
spBone* target; spBone* target;
int bendDirection; int bendDirection;
int /*boolean*/ compress;
int /*boolean*/ stretch; int /*boolean*/ stretch;
float mix; float mix;
@ -70,7 +71,7 @@ SP_API void spIkConstraint_dispose (spIkConstraint* self);
SP_API void spIkConstraint_apply (spIkConstraint* self); SP_API void spIkConstraint_apply (spIkConstraint* self);
SP_API void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*boolean*/ stretch, float alpha); SP_API void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*boolean*/ compress, int /*boolean*/ stretch, int /*boolean*/ uniform, float alpha);
SP_API void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDirection, int /*boolean*/ stretch, float alpha); SP_API void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDirection, int /*boolean*/ stretch, float alpha);
#ifdef SPINE_SHORT_NAMES #ifdef SPINE_SHORT_NAMES

View File

@ -46,7 +46,9 @@ typedef struct spIkConstraintData {
spBoneData* target; spBoneData* target;
int bendDirection; int bendDirection;
int /*boolean*/ compress;
int /*boolean*/ stretch; int /*boolean*/ stretch;
int /*boolean*/ uniform;
float mix; float mix;
#ifdef __cplusplus #ifdef __cplusplus
@ -56,7 +58,9 @@ typedef struct spIkConstraintData {
bones(0), bones(0),
target(0), target(0),
bendDirection(0), bendDirection(0),
compress(0),
stretch(0), stretch(0),
uniform(0),
mix(0) { mix(0) {
} }
#endif #endif

View File

@ -1282,8 +1282,8 @@ void spDrawOrderTimeline_setFrame (spDrawOrderTimeline* self, int frameIndex, fl
/**/ /**/
static const int IKCONSTRAINT_PREV_TIME = -4, IKCONSTRAINT_PREV_MIX = -3, IKCONSTRAINT_PREV_BEND_DIRECTION = -2, IKCONSTRAINT_PREV_STRETCH = -1; static const int IKCONSTRAINT_PREV_TIME = -5, IKCONSTRAINT_PREV_MIX = -4, IKCONSTRAINT_PREV_BEND_DIRECTION = -3, IKCONSTRAINT_PREV_COMPRESS = -2, IKCONSTRAINT_PREV_STRETCH = -1;
static const int IKCONSTRAINT_MIX = 1, IKCONSTRAINT_BEND_DIRECTION = 2, IKCONSTRAINT_STRETCH = 3; static const int IKCONSTRAINT_MIX = 1, IKCONSTRAINT_BEND_DIRECTION = 2, IKCONSTRAINT_COMPRESS = 3, IKCONSTRAINT_STRETCH = 4;
void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, float lastTime, float time, void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, float lastTime, float time,
spEvent** firedEvents, int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction) { spEvent** firedEvents, int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
@ -1301,11 +1301,13 @@ void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skel
case SP_MIX_BLEND_SETUP: case SP_MIX_BLEND_SETUP:
constraint->mix = constraint->data->mix; constraint->mix = constraint->data->mix;
constraint->bendDirection = constraint->data->bendDirection; constraint->bendDirection = constraint->data->bendDirection;
constraint->compress = constraint->data->compress;
constraint->stretch = constraint->data->stretch; constraint->stretch = constraint->data->stretch;
return; return;
case SP_MIX_BLEND_FIRST: case SP_MIX_BLEND_FIRST:
constraint->mix += (constraint->data->mix - constraint->mix) * alpha; constraint->mix += (constraint->data->mix - constraint->mix) * alpha;
constraint->bendDirection = constraint->data->bendDirection; constraint->bendDirection = constraint->data->bendDirection;
constraint->compress = constraint->data->compress;
constraint->stretch = constraint->data->stretch; constraint->stretch = constraint->data->stretch;
case SP_MIX_BLEND_REPLACE: case SP_MIX_BLEND_REPLACE:
case SP_MIX_BLEND_ADD: case SP_MIX_BLEND_ADD:
@ -1321,15 +1323,18 @@ void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skel
constraint->mix = constraint->data->mix + (frames[framesCount + IKCONSTRAINT_PREV_MIX] - constraint->data->mix) * alpha; constraint->mix = constraint->data->mix + (frames[framesCount + IKCONSTRAINT_PREV_MIX] - constraint->data->mix) * alpha;
if (direction == SP_MIX_DIRECTION_OUT) { if (direction == SP_MIX_DIRECTION_OUT) {
constraint->bendDirection = constraint->data->bendDirection; constraint->bendDirection = constraint->data->bendDirection;
constraint->compress = constraint->data->compress;
constraint->stretch = constraint->data->stretch; constraint->stretch = constraint->data->stretch;
} else { } else {
constraint->bendDirection = (int)frames[framesCount + IKCONSTRAINT_PREV_BEND_DIRECTION]; constraint->bendDirection = (int)frames[framesCount + IKCONSTRAINT_PREV_BEND_DIRECTION];
constraint->compress = frames[framesCount + IKCONSTRAINT_PREV_COMPRESS] ? 1 : 0;
constraint->stretch = frames[framesCount + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0; constraint->stretch = frames[framesCount + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0;
} }
} else { } else {
constraint->mix += (frames[framesCount + IKCONSTRAINT_PREV_MIX] - constraint->mix) * alpha; constraint->mix += (frames[framesCount + IKCONSTRAINT_PREV_MIX] - constraint->mix) * alpha;
if (direction == SP_MIX_DIRECTION_IN) { if (direction == SP_MIX_DIRECTION_IN) {
constraint->bendDirection = (int)frames[framesCount + IKCONSTRAINT_PREV_BEND_DIRECTION]; constraint->bendDirection = (int)frames[framesCount + IKCONSTRAINT_PREV_BEND_DIRECTION];
constraint->compress = frames[framesCount + IKCONSTRAINT_PREV_COMPRESS] ? 1 : 0;
constraint->stretch = frames[framesCount + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0; constraint->stretch = frames[framesCount + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0;
} }
} }
@ -1346,15 +1351,18 @@ void _spIkConstraintTimeline_apply (const spTimeline* timeline, spSkeleton* skel
constraint->mix = constraint->data->mix + (mix + (frames[frame + IKCONSTRAINT_MIX] - mix) * percent - constraint->data->mix) * alpha; constraint->mix = constraint->data->mix + (mix + (frames[frame + IKCONSTRAINT_MIX] - mix) * percent - constraint->data->mix) * alpha;
if (direction == SP_MIX_DIRECTION_OUT) { if (direction == SP_MIX_DIRECTION_OUT) {
constraint->bendDirection = constraint->data->bendDirection; constraint->bendDirection = constraint->data->bendDirection;
constraint->compress = constraint->data->compress;
constraint->stretch = constraint->data->stretch; constraint->stretch = constraint->data->stretch;
} else { } else {
constraint->bendDirection = (int)frames[frame + IKCONSTRAINT_PREV_BEND_DIRECTION]; constraint->bendDirection = (int)frames[frame + IKCONSTRAINT_PREV_BEND_DIRECTION];
constraint->compress = frames[frame + IKCONSTRAINT_PREV_COMPRESS] ? 1 : 0;
constraint->stretch = frames[frame + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0; constraint->stretch = frames[frame + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0;
} }
} else { } else {
constraint->mix += (mix + (frames[frame + IKCONSTRAINT_MIX] - mix) * percent - constraint->mix) * alpha; constraint->mix += (mix + (frames[frame + IKCONSTRAINT_MIX] - mix) * percent - constraint->mix) * alpha;
if (direction == SP_MIX_DIRECTION_IN) { if (direction == SP_MIX_DIRECTION_IN) {
constraint->bendDirection = (int)frames[frame + IKCONSTRAINT_PREV_BEND_DIRECTION]; constraint->bendDirection = (int)frames[frame + IKCONSTRAINT_PREV_BEND_DIRECTION];
constraint->compress = frames[frame + IKCONSTRAINT_PREV_COMPRESS] ? 1 : 0;
constraint->stretch = frames[frame + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0; constraint->stretch = frames[frame + IKCONSTRAINT_PREV_STRETCH] ? 1 : 0;
} }
} }
@ -1372,11 +1380,12 @@ spIkConstraintTimeline* spIkConstraintTimeline_create (int framesCount) {
return (spIkConstraintTimeline*)_spBaseTimeline_create(framesCount, SP_TIMELINE_IKCONSTRAINT, IKCONSTRAINT_ENTRIES, _spIkConstraintTimeline_apply, _spIkConstraintTimeline_getPropertyId); return (spIkConstraintTimeline*)_spBaseTimeline_create(framesCount, SP_TIMELINE_IKCONSTRAINT, IKCONSTRAINT_ENTRIES, _spIkConstraintTimeline_apply, _spIkConstraintTimeline_getPropertyId);
} }
void spIkConstraintTimeline_setFrame (spIkConstraintTimeline* self, int frameIndex, float time, float mix, int bendDirection, int /*boolean*/ stretch) { void spIkConstraintTimeline_setFrame (spIkConstraintTimeline* self, int frameIndex, float time, float mix, int bendDirection, int /*boolean*/ compress, int /*boolean*/ stretch) {
frameIndex *= IKCONSTRAINT_ENTRIES; frameIndex *= IKCONSTRAINT_ENTRIES;
self->frames[frameIndex] = time; self->frames[frameIndex] = time;
self->frames[frameIndex + IKCONSTRAINT_MIX] = mix; self->frames[frameIndex + IKCONSTRAINT_MIX] = mix;
self->frames[frameIndex + IKCONSTRAINT_BEND_DIRECTION] = (float)bendDirection; self->frames[frameIndex + IKCONSTRAINT_BEND_DIRECTION] = (float)bendDirection;
self->frames[frameIndex + IKCONSTRAINT_COMPRESS] = compress ? 1 : 0;
self->frames[frameIndex + IKCONSTRAINT_STRETCH] = stretch ? 1 : 0; self->frames[frameIndex + IKCONSTRAINT_STRETCH] = stretch ? 1 : 0;
} }

View File

@ -554,11 +554,11 @@ void _spAnimationState_applyRotateTimeline (spAnimationState* self, spTimeline*
/* Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. */ /* Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. */
r1 = blend == SP_MIX_BLEND_SETUP ? bone->data->rotation : bone->rotation; r1 = blend == SP_MIX_BLEND_SETUP ? bone->data->rotation : bone->rotation;
diff = r2 - r1; diff = r2 - r1;
diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
if (diff == 0) { if (diff == 0) {
total = timelinesRotation[i]; total = timelinesRotation[i];
} else { } else {
float lastTotal, lastDiff; float lastTotal, lastDiff;
diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
if (firstFrame) { if (firstFrame) {
lastTotal = 0; lastTotal = 0;
lastDiff = diff; lastDiff = diff;

View File

@ -39,6 +39,7 @@ spIkConstraint *spIkConstraint_create(spIkConstraintData *data, const spSkeleton
spIkConstraint *self = NEW(spIkConstraint); spIkConstraint *self = NEW(spIkConstraint);
CONST_CAST(spIkConstraintData*, self->data) = data; CONST_CAST(spIkConstraintData*, self->data) = data;
self->bendDirection = data->bendDirection; self->bendDirection = data->bendDirection;
self->compress = data->compress;
self->stretch = data->stretch; self->stretch = data->stretch;
self->mix = data->mix; self->mix = data->mix;
@ -59,7 +60,7 @@ void spIkConstraint_dispose(spIkConstraint *self) {
void spIkConstraint_apply(spIkConstraint *self) { void spIkConstraint_apply(spIkConstraint *self) {
switch (self->bonesCount) { switch (self->bonesCount) {
case 1: case 1:
spIkConstraint_apply1(self->bones[0], self->target->worldX, self->target->worldY, self->stretch, self->mix); spIkConstraint_apply1(self->bones[0], self->target->worldX, self->target->worldY, self->compress, self->stretch, self->data->uniform, self->mix);
break; break;
case 2: case 2:
spIkConstraint_apply2(self->bones[0], self->bones[1], self->target->worldX, self->target->worldY, self->bendDirection, self->stretch, self->mix); spIkConstraint_apply2(self->bones[0], self->bones[1], self->target->worldX, self->target->worldY, self->bendDirection, self->stretch, self->mix);
@ -67,9 +68,9 @@ void spIkConstraint_apply(spIkConstraint *self) {
} }
} }
void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*boolean*/ stretch, float alpha) { void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*boolean*/ compress, int /*boolean*/ stretch, int /*boolean*/ uniform, float alpha) {
spBone* p = bone->parent; spBone* p = bone->parent;
float id, x, y, tx, ty, rotationIK, sx; float id, x, y, tx, ty, rotationIK, sx, sy, s;
if (!bone->appliedValid) spBone_updateAppliedTransform(bone); if (!bone->appliedValid) spBone_updateAppliedTransform(bone);
id = 1 / (p->a * p->d - p->b * p->c); id = 1 / (p->a * p->d - p->b * p->c);
x = targetX - p->worldX, y = targetY - p->worldY; x = targetX - p->worldX, y = targetY - p->worldY;
@ -79,12 +80,17 @@ void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, int /*bo
if (rotationIK > 180) rotationIK -= 360; if (rotationIK > 180) rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360; else if (rotationIK < -180) rotationIK += 360;
sx = bone->ascaleX; sx = bone->ascaleX;
if (stretch) { sy = bone->ascaleY;
if (compress || stretch) {
float b = bone->data->length * sx, dd = SQRT(tx * tx + ty * ty); float b = bone->data->length * sx, dd = SQRT(tx * tx + ty * ty);
if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha - 1; if ((compress && dd < b) || (stretch && dd > b) && (b > 0.0001f)) {
s = (dd / b - 1) * alpha + 1;
sx *= s;
if (uniform) sy *= s;
}
} }
spBone_updateWorldTransformWith(bone, bone->ax, bone->ay, bone->arotation + rotationIK * alpha, sx, spBone_updateWorldTransformWith(bone, bone->ax, bone->ay, bone->arotation + rotationIK * alpha, sx,
bone->ascaleY, bone->ashearX, bone->ashearY); sy, bone->ashearX, bone->ashearY);
} }
void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDir, int /*boolean*/ stretch, float alpha) { void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDir, int /*boolean*/ stretch, float alpha) {

View File

@ -35,7 +35,9 @@ spIkConstraintData* spIkConstraintData_create (const char* name) {
spIkConstraintData* self = NEW(spIkConstraintData); spIkConstraintData* self = NEW(spIkConstraintData);
MALLOC_STR(self->name, name); MALLOC_STR(self->name, name);
self->bendDirection = 1; self->bendDirection = 1;
self->compress = 0;
self->stretch = 0; self->stretch = 0;
self->uniform = 0;
self->mix = 1; self->mix = 1;
return self; return self;
} }

View File

@ -436,6 +436,7 @@ void spSkeleton_setBonesToSetupPose (const spSkeleton* self) {
for (i = 0; i < self->ikConstraintsCount; ++i) { for (i = 0; i < self->ikConstraintsCount; ++i) {
spIkConstraint* ikConstraint = self->ikConstraints[i]; spIkConstraint* ikConstraint = self->ikConstraints[i];
ikConstraint->bendDirection = ikConstraint->data->bendDirection; ikConstraint->bendDirection = ikConstraint->data->bendDirection;
ikConstraint->compress = ikConstraint->data->compress;
ikConstraint->stretch = ikConstraint->data->stretch; ikConstraint->stretch = ikConstraint->data->stretch;
ikConstraint->mix = ikConstraint->data->mix; ikConstraint->mix = ikConstraint->data->mix;
} }

View File

@ -392,8 +392,9 @@ static spAnimation* _spSkeletonBinary_readAnimation (spSkeletonBinary* self, con
float time = readFloat(input); float time = readFloat(input);
float mix = readFloat(input); float mix = readFloat(input);
signed char bendDirection = readSByte(input); signed char bendDirection = readSByte(input);
int compress = readBoolean(input);
int stretch = readBoolean(input); int stretch = readBoolean(input);
spIkConstraintTimeline_setFrame(timeline, frameIndex, time, mix, bendDirection, stretch); spIkConstraintTimeline_setFrame(timeline, frameIndex, time, mix, bendDirection, compress, stretch);
if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex); if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
} }
spTimelineArray_add(timelines, (spTimeline*)timeline); spTimelineArray_add(timelines, (spTimeline*)timeline);
@ -958,7 +959,9 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
data->target = skeletonData->bones[readVarint(input, 1)]; data->target = skeletonData->bones[readVarint(input, 1)];
data->mix = readFloat(input); data->mix = readFloat(input);
data->bendDirection = readSByte(input); data->bendDirection = readSByte(input);
data->compress = readBoolean(input);
data->stretch = readBoolean(input); data->stretch = readBoolean(input);
data->uniform = readBoolean(input);
skeletonData->ikConstraints[i] = data; skeletonData->ikConstraints[i] = data;
} }

View File

@ -299,7 +299,7 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
} }
for (valueMap = constraintMap->child, frameIndex = 0; valueMap; valueMap = valueMap->next, ++frameIndex) { for (valueMap = constraintMap->child, frameIndex = 0; valueMap; valueMap = valueMap->next, ++frameIndex) {
spIkConstraintTimeline_setFrame(timeline, frameIndex, Json_getFloat(valueMap, "time", 0), Json_getFloat(valueMap, "mix", 1), spIkConstraintTimeline_setFrame(timeline, frameIndex, Json_getFloat(valueMap, "time", 0), Json_getFloat(valueMap, "mix", 1),
Json_getInt(valueMap, "bendPositive", 1) ? 1 : -1, Json_getInt(valueMap, "stretch", 0) ? 1 : 0); Json_getInt(valueMap, "bendPositive", 1) ? 1 : -1, Json_getInt(valueMap, "compress", 0) ? 1 : 0, Json_getInt(valueMap, "stretch", 0) ? 1 : 0);
readCurve(valueMap, SUPER(timeline), frameIndex); readCurve(valueMap, SUPER(timeline), frameIndex);
} }
animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline); animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
@ -737,7 +737,9 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
} }
data->bendDirection = Json_getInt(constraintMap, "bendPositive", 1) ? 1 : -1; data->bendDirection = Json_getInt(constraintMap, "bendPositive", 1) ? 1 : -1;
data->compress = Json_getInt(constraintMap, "compress", 0) ? 1 : 0;
data->stretch = Json_getInt(constraintMap, "stretch", 0) ? 1 : 0; data->stretch = Json_getInt(constraintMap, "stretch", 0) ? 1 : 0;
data->uniform = Json_getInt(constraintMap, "uniform", 0) ? 1 : 0;
data->mix = Json_getFloat(constraintMap, "mix", 1); data->mix = Json_getFloat(constraintMap, "mix", 1);
skeletonData->ikConstraints[i] = data; skeletonData->ikConstraints[i] = data;

View File

@ -52,7 +52,7 @@ RTTI_DECL
public: public:
/// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified /// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
/// in the world coordinate system. /// in the world coordinate system.
static void apply(Bone &bone, float targetX, float targetY, bool stretch, float alpha); static void apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha);
/// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
/// possible. The target is specified in the world coordinate system. /// possible. The target is specified in the world coordinate system.
@ -80,6 +80,10 @@ public:
void setBendDirection(int inValue); void setBendDirection(int inValue);
bool getCompress();
void setCompress(bool inValue);
bool getStretch(); bool getStretch();
void setStretch(bool inValue); void setStretch(bool inValue);
@ -92,6 +96,7 @@ private:
IkConstraintData &_data; IkConstraintData &_data;
Vector<Bone *> _bones; Vector<Bone *> _bones;
int _bendDirection; int _bendDirection;
bool _compress;
bool _stretch; bool _stretch;
float _mix; float _mix;
Bone *_target; Bone *_target;

View File

@ -65,8 +65,14 @@ namespace spine {
int getBendDirection(); int getBendDirection();
void setBendDirection(int inValue); void setBendDirection(int inValue);
bool getCompress();
void setCompress(bool inValue);
bool getStretch(); bool getStretch();
void setStretch(bool inValue); void setStretch(bool inValue);
bool getUniform();
void setUniform(bool inValue);
float getMix(); float getMix();
void setMix(float inValue); void setMix(float inValue);
@ -77,7 +83,9 @@ namespace spine {
Vector<BoneData*> _bones; Vector<BoneData*> _bones;
BoneData* _target; BoneData* _target;
int _bendDirection; int _bendDirection;
bool _compress;
bool _stretch; bool _stretch;
bool _uniform;
float _mix; float _mix;
}; };
} }

View File

@ -50,11 +50,11 @@ namespace spine {
virtual int getPropertyId(); virtual int getPropertyId();
/// Sets the time, mix and bend direction of the specified keyframe. /// Sets the time, mix and bend direction of the specified keyframe.
void setFrame (int frameIndex, float time, float mix, int bendDirection, bool stretch); void setFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch);
private: private:
static const int PREV_TIME, PREV_MIX, PREV_BEND_DIRECTION, PREV_STRETCH; static const int PREV_TIME, PREV_MIX, PREV_BEND_DIRECTION, PREV_COMPRESS, PREV_STRETCH;
static const int MIX, BEND_DIRECTION, STRETCH; static const int MIX, BEND_DIRECTION, COMPRESS, STRETCH;
Vector<float> _frames; Vector<float> _frames;
int _ikConstraintIndex; int _ikConstraintIndex;

View File

@ -716,10 +716,10 @@ void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleto
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
float r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation; float r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation;
float total, diff = r2 - r1; float total, diff = r2 - r1;
diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360;
if (diff == 0) { if (diff == 0) {
total = timelinesRotation[i]; total = timelinesRotation[i];
} else { } else {
diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360;
float lastTotal, lastDiff; float lastTotal, lastDiff;
if (firstFrame) { if (firstFrame) {
lastTotal = 0; lastTotal = 0;

View File

@ -44,7 +44,7 @@ using namespace spine;
RTTI_IMPL(IkConstraint, Constraint) RTTI_IMPL(IkConstraint, Constraint)
void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool stretch, float alpha) { void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) {
Bone *p = bone.getParent(); Bone *p = bone.getParent();
float id, x, y, tx, ty, rotationIK; float id, x, y, tx, ty, rotationIK;
if (!bone._appliedValid) bone.updateAppliedTransform(); if (!bone._appliedValid) bone.updateAppliedTransform();
@ -57,12 +57,17 @@ void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool stretch,
if (rotationIK > 180) rotationIK -= 360; if (rotationIK > 180) rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360; else if (rotationIK < -180) rotationIK += 360;
float sx = bone._ascaleX; float sx = bone._ascaleX;
if (stretch) { float sy = bone._ascaleY;
if (compress || stretch) {
float b = bone._data.getLength() * sx, dd = MathUtil::sqrt(tx * tx + ty * ty); float b = bone._data.getLength() * sx, dd = MathUtil::sqrt(tx * tx + ty * ty);
if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha + 1; if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
float s = (dd / b - 1) * alpha + 1;
sx *= s;
if (uniform) sy *= s;
}
} }
bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx, bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx,
bone._ascaleY, bone._ashearX, bone._ashearY); sy, bone._ashearX, bone._ashearY);
} }
void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float alpha) { void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float alpha) {
@ -206,6 +211,7 @@ void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY
IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Constraint(), IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Constraint(),
_data(data), _data(data),
_bendDirection(data.getBendDirection()), _bendDirection(data.getBendDirection()),
_compress(data.getCompress()),
_stretch(data.getStretch()), _stretch(data.getStretch()),
_mix(data.getMix()), _mix(data.getMix()),
_target(skeleton.findBone( _target(skeleton.findBone(
@ -226,7 +232,7 @@ void IkConstraint::update() {
switch (_bones.size()) { switch (_bones.size()) {
case 1: { case 1: {
Bone *bone0 = _bones[0]; Bone *bone0 = _bones[0];
apply(*bone0, _target->getWorldX(), _target->getWorldY(), _stretch, _mix); apply(*bone0, _target->getWorldX(), _target->getWorldY(), _compress, _stretch, _data._uniform, _mix);
} }
break; break;
case 2: { case 2: {
@ -281,3 +287,11 @@ bool IkConstraint::getStretch() {
void IkConstraint::setStretch(bool inValue) { void IkConstraint::setStretch(bool inValue) {
_stretch = inValue; _stretch = inValue;
} }
bool IkConstraint::getCompress() {
return _compress;
}
void IkConstraint::setCompress(bool inValue) {
_compress = inValue;
}

View File

@ -43,7 +43,9 @@ IkConstraintData::IkConstraintData(const String &name) :
_order(0), _order(0),
_target(NULL), _target(NULL),
_bendDirection(1), _bendDirection(1),
_compress(false),
_stretch(false), _stretch(false),
_uniform(false),
_mix(1) { _mix(1) {
} }
@ -94,3 +96,20 @@ bool IkConstraintData::getStretch() {
void IkConstraintData::setStretch(bool inValue) { void IkConstraintData::setStretch(bool inValue) {
_stretch = inValue; _stretch = inValue;
} }
bool IkConstraintData::getCompress() {
return _compress;
}
void IkConstraintData::setCompress(bool inValue) {
_compress = inValue;
}
bool IkConstraintData::getUniform() {
return _uniform;
}
void IkConstraintData::setUniform(bool inValue) {
_uniform = inValue;
}

View File

@ -48,14 +48,16 @@ using namespace spine;
RTTI_IMPL(IkConstraintTimeline, CurveTimeline) RTTI_IMPL(IkConstraintTimeline, CurveTimeline)
const int IkConstraintTimeline::ENTRIES = 4; const int IkConstraintTimeline::ENTRIES = 5;
const int IkConstraintTimeline::PREV_TIME = -4; const int IkConstraintTimeline::PREV_TIME = -5;
const int IkConstraintTimeline::PREV_MIX = -3; const int IkConstraintTimeline::PREV_MIX = -4;
const int IkConstraintTimeline::PREV_BEND_DIRECTION = -2; const int IkConstraintTimeline::PREV_BEND_DIRECTION = -3;
const int IkConstraintTimeline::PREV_COMPRESS = -2;
const int IkConstraintTimeline::PREV_STRETCH = -1; const int IkConstraintTimeline::PREV_STRETCH = -1;
const int IkConstraintTimeline::MIX = 1; const int IkConstraintTimeline::MIX = 1;
const int IkConstraintTimeline::BEND_DIRECTION = 2; const int IkConstraintTimeline::BEND_DIRECTION = 2;
const int IkConstraintTimeline::STRETCH = 3; const int IkConstraintTimeline::COMPRESS = 3;
const int IkConstraintTimeline::STRETCH = 4;
IkConstraintTimeline::IkConstraintTimeline(int frameCount) : CurveTimeline(frameCount), _ikConstraintIndex(0) { IkConstraintTimeline::IkConstraintTimeline(int frameCount) : CurveTimeline(frameCount), _ikConstraintIndex(0) {
_frames.setSize(frameCount * ENTRIES, 0); _frames.setSize(frameCount * ENTRIES, 0);
@ -73,11 +75,13 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
case MixBlend_Setup: case MixBlend_Setup:
constraint._mix = constraint._data._mix; constraint._mix = constraint._data._mix;
constraint._bendDirection = constraint._data._bendDirection; constraint._bendDirection = constraint._data._bendDirection;
constraint._compress = constraint._data._compress;
constraint._stretch = constraint._data._stretch; constraint._stretch = constraint._data._stretch;
return; return;
case MixBlend_First: case MixBlend_First:
constraint._mix += (constraint._data._mix - constraint._mix) * alpha; constraint._mix += (constraint._data._mix - constraint._mix) * alpha;
constraint._bendDirection = constraint._data._bendDirection; constraint._bendDirection = constraint._data._bendDirection;
constraint._compress = constraint._data._compress;
constraint._stretch = constraint._data._stretch; constraint._stretch = constraint._data._stretch;
return; return;
default: default:
@ -92,15 +96,18 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
constraint._data._mix + (_frames[_frames.size() + PREV_MIX] - constraint._data._mix) * alpha; constraint._data._mix + (_frames[_frames.size() + PREV_MIX] - constraint._data._mix) * alpha;
if (direction == MixDirection_Out) { if (direction == MixDirection_Out) {
constraint._bendDirection = constraint._data._bendDirection; constraint._bendDirection = constraint._data._bendDirection;
constraint._compress = constraint._data._compress;
constraint._stretch = constraint._data._stretch; constraint._stretch = constraint._data._stretch;
} else { } else {
constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION]; constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION];
constraint._compress = _frames[_frames.size() + PREV_COMPRESS] != 0;
constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0; constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0;
} }
} else { } else {
constraint._mix += (_frames[_frames.size() + PREV_MIX] - constraint._mix) * alpha; constraint._mix += (_frames[_frames.size() + PREV_MIX] - constraint._mix) * alpha;
if (direction == MixDirection_In) { if (direction == MixDirection_In) {
constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION]; constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION];
constraint._compress = _frames[_frames.size() + PREV_COMPRESS] != 0;
constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0; constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0;
} }
} }
@ -119,15 +126,18 @@ void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time,
constraint._data._mix + (mix + (_frames[frame + MIX] - mix) * percent - constraint._data._mix) * alpha; constraint._data._mix + (mix + (_frames[frame + MIX] - mix) * percent - constraint._data._mix) * alpha;
if (direction == MixDirection_Out) { if (direction == MixDirection_Out) {
constraint._bendDirection = constraint._data._bendDirection; constraint._bendDirection = constraint._data._bendDirection;
constraint._compress = constraint._data._compress;
constraint._stretch = constraint._data._stretch; constraint._stretch = constraint._data._stretch;
} else { } else {
constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION]; constraint._bendDirection = (int) _frames[_frames.size() + PREV_BEND_DIRECTION];
constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0; constraint._compress = _frames[frame + PREV_COMPRESS] != 0;
constraint._stretch = _frames[frame + PREV_STRETCH] != 0;
} }
} else { } else {
constraint._mix += (mix + (_frames[frame + MIX] - mix) * percent - constraint._mix) * alpha; constraint._mix += (mix + (_frames[frame + MIX] - mix) * percent - constraint._mix) * alpha;
if (direction == MixDirection_In) { if (direction == MixDirection_In) {
constraint._bendDirection = (int) _frames[frame + PREV_BEND_DIRECTION]; constraint._bendDirection = (int) _frames[frame + PREV_BEND_DIRECTION];
constraint._compress = _frames[frame + PREV_COMPRESS] != 0;
constraint._stretch = _frames[frame + PREV_STRETCH] != 0; constraint._stretch = _frames[frame + PREV_STRETCH] != 0;
} }
} }
@ -137,10 +147,11 @@ int IkConstraintTimeline::getPropertyId() {
return ((int) TimelineType_IkConstraint << 24) + _ikConstraintIndex; return ((int) TimelineType_IkConstraint << 24) + _ikConstraintIndex;
} }
void IkConstraintTimeline::setFrame(int frameIndex, float time, float mix, int bendDirection, bool stretch) { void IkConstraintTimeline::setFrame(int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) {
frameIndex *= ENTRIES; frameIndex *= ENTRIES;
_frames[frameIndex] = time; _frames[frameIndex] = time;
_frames[frameIndex + MIX] = mix; _frames[frameIndex + MIX] = mix;
_frames[frameIndex + BEND_DIRECTION] = (float)bendDirection; _frames[frameIndex + BEND_DIRECTION] = (float)bendDirection;
_frames[frameIndex + COMPRESS] = compress ? 1 : 0;
_frames[frameIndex + STRETCH] = stretch ? 1 : 0; _frames[frameIndex + STRETCH] = stretch ? 1 : 0;
} }

View File

@ -230,6 +230,7 @@ void Skeleton::setBonesToSetupPose() {
IkConstraint &constraint = *constraintP; IkConstraint &constraint = *constraintP;
constraint._bendDirection = constraint._data._bendDirection; constraint._bendDirection = constraint._data._bendDirection;
constraint._compress = constraint._data._compress;
constraint._stretch = constraint._data._stretch; constraint._stretch = constraint._data._stretch;
constraint._mix = constraint._data._mix; constraint._mix = constraint._data._mix;
} }

View File

@ -203,7 +203,9 @@ SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, cons
data->_target = skeletonData->_bones[readVarint(input, true)]; data->_target = skeletonData->_bones[readVarint(input, true)];
data->_mix = readFloat(input); data->_mix = readFloat(input);
data->_bendDirection = readSByte(input); data->_bendDirection = readSByte(input);
data->_compress = readBoolean(input);
data->_stretch = readBoolean(input); data->_stretch = readBoolean(input);
data->_uniform = readBoolean(input);
skeletonData->_ikConstraints[i] = data; skeletonData->_ikConstraints[i] = data;
} }
@ -781,8 +783,9 @@ Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, S
float time = readFloat(input); float time = readFloat(input);
float mix = readFloat(input); float mix = readFloat(input);
signed char bendDirection = readSByte(input); signed char bendDirection = readSByte(input);
bool compress = readBoolean(input);
bool stretch = readBoolean(input); bool stretch = readBoolean(input);
timeline->setFrame(frameIndex, time, mix, bendDirection, stretch); timeline->setFrame(frameIndex, time, mix, bendDirection, compress, stretch);
if (frameIndex < frameCount - 1) { if (frameIndex < frameCount - 1) {
readCurve(input, frameIndex, timeline); readCurve(input, frameIndex, timeline);
} }

View File

@ -285,9 +285,11 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
return NULL; return NULL;
} }
data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1;
data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true: false;
data->_mix = Json::getFloat(constraintMap, "mix", 1); data->_mix = Json::getFloat(constraintMap, "mix", 1);
data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1;
data->_compress = Json::getInt(constraintMap, "compress", 0) ? true: false;
data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true: false;
data->_uniform = Json::getInt(constraintMap, "uniform", 0) ? true: false;
skeletonData->_ikConstraints[i] = data; skeletonData->_ikConstraints[i] = data;
} }
@ -932,7 +934,7 @@ Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) {
} }
for (valueMap = constraintMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { for (valueMap = constraintMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) {
timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "mix", 1), timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "mix", 1),
Json::getInt(valueMap, "bendPositive", 1) ? 1 : -1, Json::getInt(valueMap, "stretch", 0) ? true : false); Json::getInt(valueMap, "bendPositive", 1) ? 1 : -1, Json::getInt(valueMap, "compress", 0) ? true : false, Json::getInt(valueMap, "stretch", 0) ? true : false);
readCurve(valueMap, timeline, frameIndex); readCurve(valueMap, timeline, frameIndex);
} }
timelines.add(timeline); timelines.add(timeline);

View File

@ -1210,15 +1210,15 @@ namespace Spine {
} }
public class IkConstraintTimeline : CurveTimeline { public class IkConstraintTimeline : CurveTimeline {
public const int ENTRIES = 4; public const int ENTRIES = 5;
private const int PREV_TIME = -4, PREV_MIX = -3, PREV_BEND_DIRECTION = -2, PREV_STRETCH = -1; private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1;
private const int MIX = 1, BEND_DIRECTION = 2, STRETCH = 3; private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4;
internal int ikConstraintIndex; internal int ikConstraintIndex;
internal float[] frames; internal float[] frames;
public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, compress, stretch ...
override public int PropertyId { override public int PropertyId {
get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
@ -1229,12 +1229,13 @@ namespace Spine {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary> /// <summary>Sets the time, mix, bend direction, compress and stretch of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool stretch) { public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) {
frameIndex *= ENTRIES; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + MIX] = mix; frames[frameIndex + MIX] = mix;
frames[frameIndex + BEND_DIRECTION] = bendDirection; frames[frameIndex + BEND_DIRECTION] = bendDirection;
frames[frameIndex + COMPRESS] = compress ? 1 : 0;
frames[frameIndex + STRETCH] = stretch ? 1 : 0; frames[frameIndex + STRETCH] = stretch ? 1 : 0;
} }
@ -1246,11 +1247,13 @@ namespace Spine {
case MixBlend.Setup: case MixBlend.Setup:
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
return; return;
case MixBlend.First: case MixBlend.First:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
return; return;
} }
@ -1262,15 +1265,18 @@ namespace Spine {
constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
if (direction == MixDirection.Out) { if (direction == MixDirection.Out) {
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
} else { } else {
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
} }
} else { } else {
constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
if (direction == MixDirection.In) { if (direction == MixDirection.In) {
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
} }
} }
@ -1285,10 +1291,22 @@ namespace Spine {
if (blend == MixBlend.Setup) { if (blend == MixBlend.Setup) {
constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; if (direction == MixDirection.Out) {
constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch;
} else {
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
constraint.compress = frames[frame + PREV_COMPRESS] != 0;
constraint.stretch = frames[frame + PREV_STRETCH] != 0;
}
} else { } else {
constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; if (direction == MixDirection.In) {
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
constraint.compress = frames[frame + PREV_COMPRESS] != 0;
constraint.stretch = frames[frame + PREV_STRETCH] != 0;
}
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace Spine {
internal ExposedList<Bone> bones = new ExposedList<Bone>(); internal ExposedList<Bone> bones = new ExposedList<Bone>();
internal Bone target; internal Bone target;
internal int bendDirection; internal int bendDirection;
internal bool stretch; internal bool compress, stretch;
internal float mix; internal float mix;
public IkConstraintData Data { get { return data; } } public IkConstraintData Data { get { return data; } }
@ -59,9 +59,17 @@ namespace Spine {
set { bendDirection = value; } set { bendDirection = value; }
} }
/// <summary>
/// When true and only a single bone is being constrained,
/// if the target is too close, the bone is scaled to reach it.</summary>
public bool Compress {
get { return compress; }
set { compress = value; }
}
/// <summary> /// <summary>
/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
/// IF the parent bone has nonuniform scale, stretching is not applied.</summary> /// If the parent bone has nonuniform scale, stretching is not applied.</summary>
public bool Stretch { public bool Stretch {
get { return stretch; } get { return stretch; }
set { stretch = value; } set { stretch = value; }
@ -79,6 +87,7 @@ namespace Spine {
this.data = data; this.data = data;
mix = data.mix; mix = data.mix;
bendDirection = data.bendDirection; bendDirection = data.bendDirection;
compress = data.compress;
stretch = data.stretch; stretch = data.stretch;
bones = new ExposedList<Bone>(data.bones.Count); bones = new ExposedList<Bone>(data.bones.Count);
@ -97,7 +106,7 @@ namespace Spine {
ExposedList<Bone> bones = this.bones; ExposedList<Bone> bones = this.bones;
switch (bones.Count) { switch (bones.Count) {
case 1: case 1:
Apply(bones.Items[0], target.worldX, target.worldY, stretch, mix); Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
break; break;
case 2: case 2:
Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix); Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix);
@ -109,9 +118,8 @@ namespace Spine {
return data.name; return data.name;
} }
/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified /// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
/// in the world coordinate system.</summary> static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) {
static public void Apply (Bone bone, float targetX, float targetY, bool stretch, float alpha) {
if (!bone.appliedValid) bone.UpdateAppliedTransform(); if (!bone.appliedValid) bone.UpdateAppliedTransform();
Bone p = bone.parent; Bone p = bone.parent;
float id = 1 / (p.a * p.d - p.b * p.c); float id = 1 / (p.a * p.d - p.b * p.c);
@ -121,14 +129,18 @@ namespace Spine {
if (bone.ascaleX < 0) rotationIK += 180; if (bone.ascaleX < 0) rotationIK += 180;
if (rotationIK > 180) if (rotationIK > 180)
rotationIK -= 360; rotationIK -= 360;
else if (rotationIK < -180) else if (rotationIK < -180) //
rotationIK += 360; rotationIK += 360;
float sx = bone.ascaleX; float sx = bone.ascaleX, sy = bone.ascaleY;
if (stretch) { if (compress || stretch) {
float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
if (dd > b && b > 0.0001f) sx *= (dd / b - 1) * alpha + 1; if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
float s = (dd / b - 1) * alpha + 1;
sx *= s;
if (uniform) sy *= s;
}
} }
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX, bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
bone.ashearY); bone.ashearY);
} }

View File

@ -39,7 +39,7 @@ namespace Spine {
internal List<BoneData> bones = new List<BoneData>(); internal List<BoneData> bones = new List<BoneData>();
internal BoneData target; internal BoneData target;
internal int bendDirection = 1; internal int bendDirection = 1;
internal bool stretch; internal bool compress, stretch, uniform;
internal float mix = 1; internal float mix = 1;
/// <summary>The IK constraint's name, which is unique within the skeleton.</summary> /// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
@ -63,12 +63,27 @@ namespace Spine {
set { target = value; } set { target = value; }
} }
/// <summary>
/// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations.</summary>
public float Mix {
get { return mix; }
set { mix = value; }
}
/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary> /// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
public int BendDirection { public int BendDirection {
get { return bendDirection; } get { return bendDirection; }
set { bendDirection = value; } set { bendDirection = value; }
} }
/// <summary>
/// When true, and only a single bone is being constrained,
/// if the target is too close, the bone is scaled to reach it. </summary>
public bool Compress {
get { return compress; }
set { compress = value; }
}
/// <summary> /// <summary>
/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
/// If the bone has local nonuniform scale, stretching is not applied.</summary> /// If the bone has local nonuniform scale, stretching is not applied.</summary>
@ -77,7 +92,13 @@ namespace Spine {
set { stretch = value; } set { stretch = value; }
} }
public float Mix { get { return mix; } set { mix = value; } } /// <summary>
/// When true, only a single bone is being constrained and Compress or Stretch is used,
/// the bone is scaled both on the X and Y axes.</summary>
public bool Uniform {
get { return uniform; }
set { uniform = value; }
}
public IkConstraintData (string name) { public IkConstraintData (string name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");

View File

@ -308,9 +308,10 @@ namespace Spine {
var ikConstraintsItems = this.ikConstraints.Items; var ikConstraintsItems = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraintsItems[i]; IkConstraint constraint = ikConstraintsItems[i];
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch;
} }
var transformConstraintsItems = this.transformConstraints.Items; var transformConstraintsItems = this.transformConstraints.Items;

View File

@ -210,7 +210,9 @@ namespace Spine {
data.target = skeletonData.bones.Items[ReadVarint(input, true)]; data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.mix = ReadFloat(input); data.mix = ReadFloat(input);
data.bendDirection = ReadSByte(input); data.bendDirection = ReadSByte(input);
data.compress = ReadBoolean(input);
data.stretch = ReadBoolean(input); data.stretch = ReadBoolean(input);
data.uniform = ReadBoolean(input);
skeletonData.ikConstraints.Add(data); skeletonData.ikConstraints.Add(data);
} }
@ -646,10 +648,11 @@ namespace Spine {
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int index = ReadVarint(input, true); int index = ReadVarint(input, true);
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
timeline.ikConstraintIndex = index; ikConstraintIndex = index
};
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input)); timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);

View File

@ -180,10 +180,11 @@ namespace Spine {
string targetName = (string)constraintMap["target"]; string targetName = (string)constraintMap["target"];
data.target = skeletonData.FindBone(targetName); data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName); if (data.target == null) throw new Exception("Target bone not found: " + targetName);
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
data.stretch = GetBoolean(constraintMap, "stretch", false);
data.mix = GetFloat(constraintMap, "mix", 1); data.mix = GetFloat(constraintMap, "mix", 1);
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); skeletonData.ikConstraints.Add(data);
} }
@ -595,11 +596,14 @@ namespace Spine {
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
int frameIndex = 0; int frameIndex = 0;
foreach (Dictionary<string, Object> valueMap in values) { foreach (Dictionary<string, Object> valueMap in values) {
float time = (float)valueMap["time"]; timeline.SetFrame(
float mix = GetFloat(valueMap, "mix", 1); frameIndex,
bool bendPositive = GetBoolean(valueMap, "bendPositive", true); (float)valueMap["time"],
bool stretch = GetBoolean(valueMap, "stretch", false); GetFloat(valueMap, "mix", 1),
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1, stretch); GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
GetBoolean(valueMap, "compress", true),
GetBoolean(valueMap, "stretch", false)
);
ReadCurve(valueMap, timeline, frameIndex); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }

View File

@ -1169,19 +1169,21 @@ function Animation.DrawOrderTimeline.new (frameCount)
end end
Animation.IkConstraintTimeline = {} Animation.IkConstraintTimeline = {}
Animation.IkConstraintTimeline.ENTRIES = 4 Animation.IkConstraintTimeline.ENTRIES = 5
function Animation.IkConstraintTimeline.new (frameCount) function Animation.IkConstraintTimeline.new (frameCount)
local ENTRIES = Animation.IkConstraintTimeline.ENTRIES local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
local PREV_TIME = -4 local PREV_TIME = -5
local PREV_MIX = -3 local PREV_MIX = -4
local PREV_BEND_DIRECTION = -2 local PREV_BEND_DIRECTION = -3
local PREV_COMPRESS = -2
local PREV_STRETCH = -1 local PREV_STRETCH = -1
local MIX = 1 local MIX = 1
local BEND_DIRECTION = 2 local BEND_DIRECTION = 2
local COMPRESS = 3
local STRETCH = 1 local STRETCH = 1
local self = Animation.CurveTimeline.new(frameCount) local self = Animation.CurveTimeline.new(frameCount)
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, bendDirection, ... self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, bendDirection, compress, stretch, ...
self.ikConstraintIndex = -1 self.ikConstraintIndex = -1
self.type = TimelineType.ikConstraint self.type = TimelineType.ikConstraint
@ -1189,11 +1191,16 @@ function Animation.IkConstraintTimeline.new (frameCount)
return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex
end end
function self:setFrame (frameIndex, time, mix, bendDirection, stretch) function self:setFrame (frameIndex, time, mix, bendDirection, compress, stretch)
frameIndex = frameIndex * ENTRIES frameIndex = frameIndex * ENTRIES
self.frames[frameIndex] = time self.frames[frameIndex] = time
self.frames[frameIndex + MIX] = mix self.frames[frameIndex + MIX] = mix
self.frames[frameIndex + BEND_DIRECTION] = bendDirection self.frames[frameIndex + BEND_DIRECTION] = bendDirection
if (compress) then
self.frames[frameIndex + COMPRESS] = 1
else
self.frames[frameIndex + COMPRESS] = 0
end
if (stretch) then if (stretch) then
self.frames[frameIndex + STRETCH] = 1 self.frames[frameIndex + STRETCH] = 1
else else
@ -1209,10 +1216,12 @@ function Animation.IkConstraintTimeline.new (frameCount)
if blend == MixBlend.setup then if blend == MixBlend.setup then
constraint.mix = constraint.data.mix constraint.mix = constraint.data.mix
constraint.bendDirection = constraint.data.bendDirection constraint.bendDirection = constraint.data.bendDirection
constraint.compress = constraint.data.compress
constraint.stretch = constraint.data.stretch constraint.stretch = constraint.data.stretch
elseif blend == MixBlend.first then elseif blend == MixBlend.first then
constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha
constraint.bendDirection = constraint.data.bendDirection constraint.bendDirection = constraint.data.bendDirection
constraint.compress = constraint.data.compress
constraint.stretch = constraint.data.stretch constraint.stretch = constraint.data.stretch
end end
return return
@ -1223,15 +1232,18 @@ function Animation.IkConstraintTimeline.new (frameCount)
constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha
if direction == MixDirection.out then if direction == MixDirection.out then
constraint.bendDirection = constraint.data.bendDirection constraint.bendDirection = constraint.data.bendDirection
constraint.compress = constraint.data.compress
constraint.stretch = constraint.data.stretch constraint.stretch = constraint.data.stretch
else else
constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]); constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]);
if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
end end
else else
constraint.mix = constraint.mix + (frames[frames.length + PREV_MIX] - constraint.mix) * alpha; constraint.mix = constraint.mix + (frames[frames.length + PREV_MIX] - constraint.mix) * alpha;
if direction == MixDirection._in then if direction == MixDirection._in then
constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]) constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
if (math_floor(frames[zlen(frames) + PREV_COMPRES]) == 1) then constraint.compress = true else constraint.compress = false end
if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
end end
end end
@ -1249,15 +1261,18 @@ function Animation.IkConstraintTimeline.new (frameCount)
constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha
if direction == MixDirection.out then if direction == MixDirection.out then
constraint.bendDirection = constraint.data.bendDirection constraint.bendDirection = constraint.data.bendDirection
constraint.compress = constraint.data.compress
constraint.stretch = constraint.data.stretch constraint.stretch = constraint.data.stretch
else else
constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
end end
else else
constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
if direction == MixDirection._in then if direction == MixDirection._in then
constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
end end
end end

View File

@ -543,10 +543,10 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
if blend == MixBlend.setup then r1 = bone.data.rotation end if blend == MixBlend.setup then r1 = bone.data.rotation end
local total = 0 local total = 0
local diff = r2 - r1 local diff = r2 - r1
diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
if diff == 0 then if diff == 0 then
total = timelinesRotation[i] total = timelinesRotation[i]
else else
diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
local lastTotal = 0 local lastTotal = 0
local lastDiff = 0 local lastDiff = 0
if firstFrame then if firstFrame then

View File

@ -52,6 +52,7 @@ function IkConstraint.new (data, skeleton)
bones = {}, bones = {},
target = nil, target = nil,
mix = data.mix, mix = data.mix,
compress = data.compress,
stretch = data.stretch, stretch = data.stretch,
bendDirection = data.bendDirection, bendDirection = data.bendDirection,
} }
@ -75,13 +76,13 @@ function IkConstraint:update ()
local bones = self.bones local bones = self.bones
local boneCount = #bones local boneCount = #bones
if boneCount == 1 then if boneCount == 1 then
self:apply1(bones[1], target.worldX, target.worldY, self.stretch, self.mix) self:apply1(bones[1], target.worldX, target.worldY, self.compress, self.stretch, self.data.uniform, self.mix)
elseif boneCount == 2 then elseif boneCount == 2 then
self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.stretch, self.mix) self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.stretch, self.mix)
end end
end end
function IkConstraint:apply1 (bone, targetX, targetY, stretch, alpha) function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
if not bone.appliedValid then bone:updateAppliedTransform() end if not bone.appliedValid then bone:updateAppliedTransform() end
local p = bone.parent local p = bone.parent
local id = 1 / (p.a * p.d - p.b * p.c) local id = 1 / (p.a * p.d - p.b * p.c)
@ -97,11 +98,17 @@ function IkConstraint:apply1 (bone, targetX, targetY, stretch, alpha)
rotationIK = rotationIK + 360 rotationIK = rotationIK + 360
end end
local sx = bone.ascaleX local sx = bone.ascaleX
if stretch then local sy = bone.ascaleY
if compress or stretch then
local b = bone.data.length * sx
local dd = math_sqrt(tx * tx + ty * ty) local dd = math_sqrt(tx * tx + ty * ty)
if dd > bone.data.length * sx then sx = sx * ((dd / (bone.data.length * sx) - 1) * alpha + 1) end if (compress and dd < b) or (stretch and dd > b) and b > 0.0001 then
local s = (dd / b - 1) * alpha + 1
sx = sx * s
if uniform then sy = sy * s end
end
end end
bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX, bone.ashearY) bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY)
end end
function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, alpha) function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, alpha)

View File

@ -38,7 +38,9 @@ function IkConstraintData.new (name)
bones = {}, bones = {},
target = nil, target = nil,
bendDirection = 1, bendDirection = 1,
compress = false,
stretch = false, stretch = false,
uniform = false,
mix = 1 mix = 1
} }

View File

@ -338,9 +338,10 @@ function Skeleton:setBonesToSetupPose ()
end end
for _,ikConstraint in ipairs(self.ikConstraints) do for _,ikConstraint in ipairs(self.ikConstraints) do
ikConstraint.bendDirection = ikConstraint.data.bendDirection
ikConstraint.stretch = ikConstraint.data.stretch
ikConstraint.mix = ikConstraint.data.mix ikConstraint.mix = ikConstraint.data.mix
ikConstraint.bendDirection = ikConstraint.data.bendDirection
ikConstraint.compress = ikConstraint.data.compress
ikConstraint.stretch = ikConstraint.data.stretch
end end
local transformConstraints = self.transformConstraints local transformConstraints = self.transformConstraints

View File

@ -164,9 +164,11 @@ function SkeletonJson.new (attachmentLoader)
data.target = skeletonData:findBone(targetName) data.target = skeletonData:findBone(targetName)
if not data.target then error("Target bone not found: " .. targetName) end if not data.target then error("Target bone not found: " .. targetName) end
if constraintMap["bendPositive"] == false then data.bendDirection = -1 else data.bendDirection = 1 end
if constraintMap["stretch"] == false then data.stretch = false else data.stretch = true end
data.mix = getValue(constraintMap, "mix", 1) data.mix = getValue(constraintMap, "mix", 1)
if constraintMap["bendPositive"] == nil or constraintMap["bendPositive"] == false then data.bendDirection = -1 else data.bendDirection = 1 end
if constraintMap["compress"] == nil or constraintMap["compress"] == false then data.compress = false else data.compress = true end
if constraintMap["stretch"] == nil or constraintMap["stretch"] == false then data.stretch = false else data.stretch = true end
if constraintMap["uniform"] == nil or constraintMap["uniform"] == false then data.uniform = false else data.uniform = true end
table_insert(skeletonData.ikConstraints, data) table_insert(skeletonData.ikConstraints, data)
end end
@ -613,9 +615,11 @@ function SkeletonJson.new (attachmentLoader)
if valueMap["mix"] ~= nil then mix = valueMap["mix"] end if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
local bendPositive = 1 local bendPositive = 1
if valueMap["bendPositive"] == false then bendPositive = -1 end if valueMap["bendPositive"] == false then bendPositive = -1 end
local stretch = true local stretch = false
if valueMap["stretch"] == false then stretch = false end if valueMap["stretch"] ~= nil then stretch = valueMap["stretch"] end
timeline:setFrame(frameIndex, valueMap["time"], mix, bendPositive, stretch) local compress = false
if valueMap["compress"] ~= nil then compress = valueMap["compress"] end
timeline:setFrame(frameIndex, valueMap["time"], mix, bendPositive, compress, stretch)
readCurve(valueMap, timeline, frameIndex) readCurve(valueMap, timeline, frameIndex)
frameIndex = frameIndex + 1 frameIndex = frameIndex + 1
end end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1037,12 +1037,12 @@ module spine {
} }
export class IkConstraintTimeline extends CurveTimeline { export class IkConstraintTimeline extends CurveTimeline {
static ENTRIES = 4; static ENTRIES = 5;
static PREV_TIME = -4; static PREV_MIX = -3; static PREV_BEND_DIRECTION = -2; static PREV_STRETCH = -1; static PREV_TIME = -5; static PREV_MIX = -4; static PREV_BEND_DIRECTION = -3; static PREV_COMPRESS = -2; static PREV_STRETCH = -1;
static MIX = 1; static BEND_DIRECTION = 2; static STRETCH = 3; static MIX = 1; static BEND_DIRECTION = 2; static COMPRESS = 3; static STRETCH = 4;
ikConstraintIndex: number; ikConstraintIndex: number;
frames: ArrayLike<number>; // time, mix, bendDirection, ... frames: ArrayLike<number>; // time, mix, bendDirection, compress, stretch, ...
constructor (frameCount: number) { constructor (frameCount: number) {
super(frameCount); super(frameCount);
@ -1054,11 +1054,12 @@ module spine {
} }
/** Sets the time, mix and bend direction of the specified keyframe. */ /** Sets the time, mix and bend direction of the specified keyframe. */
setFrame (frameIndex: number, time: number, mix: number, bendDirection: number, stretch: boolean) { setFrame (frameIndex: number, time: number, mix: number, bendDirection: number, compress: boolean, stretch: boolean) {
frameIndex *= IkConstraintTimeline.ENTRIES; frameIndex *= IkConstraintTimeline.ENTRIES;
this.frames[frameIndex] = time; this.frames[frameIndex] = time;
this.frames[frameIndex + IkConstraintTimeline.MIX] = mix; this.frames[frameIndex + IkConstraintTimeline.MIX] = mix;
this.frames[frameIndex + IkConstraintTimeline.BEND_DIRECTION] = bendDirection; this.frames[frameIndex + IkConstraintTimeline.BEND_DIRECTION] = bendDirection;
this.frames[frameIndex + IkConstraintTimeline.COMPRESS] = compress ? 1 : 0;
this.frames[frameIndex + IkConstraintTimeline.STRETCH] = stretch ? 1 : 0; this.frames[frameIndex + IkConstraintTimeline.STRETCH] = stretch ? 1 : 0;
} }
@ -1070,11 +1071,13 @@ module spine {
case MixBlend.setup: case MixBlend.setup:
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
return; return;
case MixBlend.first: case MixBlend.first:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
} }
return; return;
@ -1085,15 +1088,18 @@ module spine {
constraint.mix = constraint.data.mix + (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.data.mix) * alpha;
if (direction == MixDirection.out) { if (direction == MixDirection.out) {
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
} else { } else {
constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION] constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION]
constraint.compress = frames[frames.length + IkConstraintTimeline.PREV_COMPRESS] != 0;
constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0; constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0;
} }
} else { } else {
constraint.mix += (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.mix) * alpha; constraint.mix += (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.mix) * alpha;
if (direction == MixDirection.in) { if (direction == MixDirection.in) {
constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION]; constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION];
constraint.compress = frames[frames.length + IkConstraintTimeline.PREV_COMPRESS] != 0;
constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0; constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0;
} }
} }
@ -1111,15 +1117,18 @@ module spine {
constraint.mix = constraint.data.mix + (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.data.mix) * alpha; constraint.mix = constraint.data.mix + (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.data.mix) * alpha;
if (direction == MixDirection.out) { if (direction == MixDirection.out) {
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch; constraint.stretch = constraint.data.stretch;
} else { } else {
constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION]; constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION];
constraint.compress = frames[frame + IkConstraintTimeline.PREV_COMPRESS] != 0;
constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0; constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0;
} }
} else { } else {
constraint.mix += (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.mix) * alpha; constraint.mix += (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.mix) * alpha;
if (direction == MixDirection.in) { if (direction == MixDirection.in) {
constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION]; constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION];
constraint.compress = frames[frame + IkConstraintTimeline.PREV_COMPRESS] != 0;
constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0; constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0;
} }
} }

View File

@ -304,10 +304,10 @@ module spine {
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
let r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation; let r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
let total = 0, diff = r2 - r1; let total = 0, diff = r2 - r1;
diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360;
if (diff == 0) { if (diff == 0) {
total = timelinesRotation[i]; total = timelinesRotation[i];
} else { } else {
diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360;
let lastTotal = 0, lastDiff = 0; let lastTotal = 0, lastDiff = 0;
if (firstFrame) { if (firstFrame) {
lastTotal = 0; lastTotal = 0;

View File

@ -34,6 +34,7 @@ module spine {
bones: Array<Bone>; bones: Array<Bone>;
target: Bone; target: Bone;
bendDirection = 0; bendDirection = 0;
compress = false;
stretch = false; stretch = false;
mix = 1; mix = 1;
@ -43,6 +44,7 @@ module spine {
this.data = data; this.data = data;
this.mix = data.mix; this.mix = data.mix;
this.bendDirection = data.bendDirection; this.bendDirection = data.bendDirection;
this.compress = data.compress;
this.stretch = data.stretch; this.stretch = data.stretch;
this.bones = new Array<Bone>(); this.bones = new Array<Bone>();
@ -64,7 +66,7 @@ module spine {
let bones = this.bones; let bones = this.bones;
switch (bones.length) { switch (bones.length) {
case 1: case 1:
this.apply1(bones[0], target.worldX, target.worldY, this.stretch, this.mix); this.apply1(bones[0], target.worldX, target.worldY, this.compress, this.stretch, this.data.uniform, this.mix);
break; break;
case 2: case 2:
this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.stretch, this.mix); this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.stretch, this.mix);
@ -74,7 +76,7 @@ module spine {
/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world /** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
* coordinate system. */ * coordinate system. */
apply1 (bone: Bone, targetX: number, targetY: number, stretch: boolean, alpha: number) { apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) {
if (!bone.appliedValid) bone.updateAppliedTransform(); if (!bone.appliedValid) bone.updateAppliedTransform();
let p = bone.parent; let p = bone.parent;
let id = 1 / (p.a * p.d - p.b * p.c); let id = 1 / (p.a * p.d - p.b * p.c);
@ -85,12 +87,16 @@ module spine {
if (rotationIK > 180) if (rotationIK > 180)
rotationIK -= 360; rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360; else if (rotationIK < -180) rotationIK += 360;
let sx = bone.ascaleX; let sx = bone.ascaleX, sy = bone.ascaleY;
if (stretch) { if (compress || stretch) {
let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty); let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty);
if (dd > b && b > 0.0001) sx *= (dd / b - 1) * alpha + 1; if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) {
let s = (dd / b - 1) * alpha + 1;
sx *= s;
if (uniform) sy *= s;
}
} }
bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX, bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
bone.ashearY); bone.ashearY);
} }

View File

@ -35,7 +35,9 @@ module spine {
bones = new Array<BoneData>(); bones = new Array<BoneData>();
target: BoneData; target: BoneData;
bendDirection = 1; bendDirection = 1;
compress = false;
stretch = false; stretch = false;
uniform = false;
mix = 1; mix = 1;
constructor (name: string) { constructor (name: string) {

View File

@ -286,9 +286,10 @@ module spine {
let ikConstraints = this.ikConstraints; let ikConstraints = this.ikConstraints;
for (let i = 0, n = ikConstraints.length; i < n; i++) { for (let i = 0, n = ikConstraints.length; i < n; i++) {
let constraint = ikConstraints[i]; let constraint = ikConstraints[i];
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection;
constraint.compress = constraint.data.compress;
constraint.stretch = constraint.data.stretch;
} }
let transformConstraints = this.transformConstraints; let transformConstraints = this.transformConstraints;

View File

@ -123,9 +123,11 @@ module spine {
data.target = skeletonData.findBone(targetName); data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new Error("IK target bone not found: " + targetName); if (data.target == null) throw new Error("IK target bone not found: " + targetName);
data.bendDirection = this.getValue(constraintMap, "bendPositive", true) ? 1 : -1;
data.stretch = this.getValue(constraintMap, "stretch", false);
data.mix = this.getValue(constraintMap, "mix", 1); data.mix = this.getValue(constraintMap, "mix", 1);
data.bendDirection = this.getValue(constraintMap, "bendPositive", true) ? 1 : -1;
data.compress = this.getValue(constraintMap, "compress", false);
data.stretch = this.getValue(constraintMap, "stretch", false);
data.uniform = this.getValue(constraintMap, "uniform", false);
skeletonData.ikConstraints.push(data); skeletonData.ikConstraints.push(data);
} }
@ -521,7 +523,7 @@ module spine {
for (let i = 0; i < constraintMap.length; i++) { for (let i = 0; i < constraintMap.length; i++) {
let valueMap = constraintMap[i]; let valueMap = constraintMap[i];
timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, "mix", 1), timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, "mix", 1),
this.getValue(valueMap, "bendPositive", true) ? 1 : -1, this.getValue(valueMap, "stretch", false)); this.getValue(valueMap, "bendPositive", true) ? 1 : -1, this.getValue(valueMap, "compress", false), this.getValue(valueMap, "stretch", false));
this.readCurve(valueMap, timeline, frameIndex); this.readCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }

View File

@ -371,12 +371,16 @@ namespace Spine.Unity.Editor {
foreach (var c in skeleton.IkConstraints) { foreach (var c in skeleton.IkConstraints) {
EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK)); EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK));
FalseDropDown("Goal", c.Data.Target.Name, Icons.bone, true); FalseDropDown("Goal", c.Data.Target.Name, Icons.bone, true);
using (new EditorGUI.DisabledGroupScope(true)) {
EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Data.Uniform", tooltip: "Uniformly scales a bone when Ik stretches or compresses."), c.Data.Uniform);
}
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
c.Mix = EditorGUILayout.Slider("Mix", c.Mix, MixMin, MixMax); c.Mix = EditorGUILayout.Slider("Mix", c.Mix, MixMin, MixMax);
c.BendDirection = EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Bend Clockwise", tooltip: "IkConstraint.BendDirection == 1 if clockwise; -1 if counterclockwise."), c.BendDirection > 0) ? 1 : -1; c.BendDirection = EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Bend Clockwise", tooltip: "IkConstraint.BendDirection == 1 if clockwise; -1 if counterclockwise."), c.BendDirection > 0) ? 1 : -1;
c.Compress = EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Compress", tooltip: "Compress single bone IK when the target too close. Not applied when parent bone has nonuniform scale."), c.Compress);
c.Stretch = EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Stretch", tooltip: "Stretch the parent bone when the target is out of range. Not applied when parent bone has nonuniform scale."), c.Stretch); c.Stretch = EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Stretch", tooltip: "Stretch the parent bone when the target is out of range. Not applied when parent bone has nonuniform scale."), c.Stretch);
if (EditorGUI.EndChangeCheck()) requireRepaint = true; if (EditorGUI.EndChangeCheck()) requireRepaint = true;
EditorGUILayout.Space(); EditorGUILayout.Space();
} }