[as3] Ported stretchy IK. See #1142.

This commit is contained in:
Mario Zechner 2018-08-06 16:08:47 +02:00
parent cdc2d5bc88
commit 8621472bb4
14 changed files with 199 additions and 47 deletions

View File

@ -14,7 +14,7 @@ com.powerflasher.fdt.core.PassManifests=true
com.powerflasher.fdt.core.PassRsls=false
com.powerflasher.fdt.core.PassSwcs=true
com.powerflasher.fdt.core.PlatformType=WEB
com.powerflasher.fdt.core.PlayerVersion=23.0
com.powerflasher.fdt.core.PlayerVersion=30.0
com.powerflasher.fdt.core.ProjectTypeHint=Web
com.powerflasher.fdt.core.Runtime=Flash_Player
com.powerflasher.fdt.core.SdkName=Flex 4.6.0

View File

@ -1,23 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.powerflasher.fdt.ui.CompcGroup">
<booleanAttribute key="ADVANCED_TELEMTRY" value="false"/>
<stringAttribute key="ARGUMENTS" value=" -target-player={playerVersion}"/>
<listAttribute key="COMPILER_CONSTANTS"/>
<stringAttribute key="COMPILER_TECHNOLOGY" value="Flex SDK"/>
<booleanAttribute key="FORCE_FRESH_COMPILATION" value="false"/>
<stringAttribute key="LAUNCHER_DEPENDENCIES" value="[Self]"/>
<stringAttribute key="MAIN_CLASS" value=""/>
<stringAttribute key="OUTPUT" value="../spine-as3-example/lib/spine-as3.swc"/>
<booleanAttribute key="PASS_CP" value="true"/>
<booleanAttribute key="PASS_DEFINES" value="true"/>
<booleanAttribute key="PASS_EXTERNS" value="true"/>
<booleanAttribute key="PASS_MANIFESTS" value="true"/>
<booleanAttribute key="PASS_SWCS" value="true"/>
<stringAttribute key="PROJECT_NAME" value="spine-as3"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/spine-as3"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
</launchConfiguration>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.powerflasher.fdt.ui.CompcGroup">
<booleanAttribute key="ADVANCED_TELEMTRY" value="false"/>
<stringAttribute key="ARGUMENTS" value=" -target-player=30.0"/>
<listAttribute key="COMPILER_CONSTANTS"/>
<stringAttribute key="COMPILER_TECHNOLOGY" value="Flex SDK"/>
<booleanAttribute key="FORCE_FRESH_COMPILATION" value="false"/>
<booleanAttribute key="INCLUDE_TEST_RESOURCES" value="false"/>
<stringAttribute key="LAUNCHER_DEPENDENCIES" value="[Self]"/>
<stringAttribute key="MAIN_CLASS" value=""/>
<stringAttribute key="OUTPUT" value="../spine-as3-example/lib/spine-as3.swc"/>
<booleanAttribute key="PASS_CP" value="true"/>
<booleanAttribute key="PASS_DEFINES" value="true"/>
<booleanAttribute key="PASS_EXTERNS" value="true"/>
<booleanAttribute key="PASS_MANIFESTS" value="true"/>
<booleanAttribute key="PASS_SWCS" value="true"/>
<stringAttribute key="PROJECT_NAME" value="spine-as3"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/spine-as3"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
</launchConfiguration>

View File

@ -1,6 +1,9 @@
eclipse.preferences.version=1
encoding//src/spine/IkConstraint.as=UTF-8
encoding//src/spine/IkConstraintData.as=UTF-8
encoding//src/spine/Interpolation.as=UTF-8
encoding//src/spine/MathUtils.as=UTF-8
encoding//src/spine/Skeleton.as=UTF-8
encoding//src/spine/SkeletonClipping.as=UTF-8
encoding//src/spine/SkeletonJson.as=UTF-8
encoding//src/spine/Triangulator.as=UTF-8

View File

@ -33,8 +33,9 @@ package spine {
internal var _data : IkConstraintData;
public var bones : Vector.<Bone>;
public var target : Bone;
public var mix : Number;
public var bendDirection : int;
public var stretch: Boolean;
public var mix : Number;
public function IkConstraint(data : IkConstraintData, skeleton : Skeleton) {
if (data == null) throw new ArgumentError("data cannot be null.");
@ -42,6 +43,7 @@ package spine {
_data = data;
mix = data.mix;
bendDirection = data.bendDirection;
stretch = data.stretch;
bones = new Vector.<Bone>();
for each (var boneData : BoneData in data.bones)
@ -56,10 +58,10 @@ package spine {
public function update() : void {
switch (bones.length) {
case 1:
apply1(bones[0], target.worldX, target.worldY, mix);
apply1(bones[0], target.worldX, target.worldY, stretch, mix);
break;
case 2:
apply2(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix);
apply2(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, mix);
break;
}
}
@ -78,7 +80,7 @@ package spine {
/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
* coordinate system. */
static public function apply1(bone : Bone, targetX : Number, targetY : Number, alpha : Number) : void {
static public function apply1(bone : Bone, targetX : Number, targetY : Number, stretch : Boolean, alpha : Number) : void {
if (!bone.appliedValid) bone.updateAppliedTransform();
var p : Bone = bone.parent;
var id : Number = 1 / (p.a * p.d - p.b * p.c);
@ -89,20 +91,25 @@ package spine {
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, bone.ashearY);
var sx : Number = bone.ascaleX;
if (stretch) {
var b : Number = bone.data.length * sx, dd : Number = Math.sqrt(tx * tx + ty * ty);
if (dd > b && b > 0.0001) sx *= (dd / b - 1) * alpha + 1;
}
bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, bone.ascaleY, bone.ashearX, bone.ashearY);
}
/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
* target is specified in the world coordinate system.
* @param child Any descendant bone of the parent. */
static public function apply2(parent : Bone, child : Bone, targetX : Number, targetY : Number, bendDir : int, alpha : Number) : void {
static public function apply2(parent : Bone, child : Bone, targetX : Number, targetY : Number, bendDir : int, stretch : Boolean, alpha : Number) : void {
if (alpha == 0) {
child.updateWorldTransform();
return;
}
if (!parent.appliedValid) parent.updateAppliedTransform();
if (!child.appliedValid) child.updateAppliedTransform();
var px : Number = parent.ax, py : Number = parent.ay, psx : Number = parent.ascaleX, psy : Number = parent.ascaleY, csx : Number = child.ascaleX;
var px : Number = parent.ax, py : Number = parent.ay, psx : Number = parent.ascaleX, sx : Number = psx, psy : Number = parent.ascaleY, csx : Number = child.ascaleX;
var os1 : int, os2 : int, s2 : int;
if (psx < 0) {
psx = -psx;
@ -138,7 +145,7 @@ package spine {
c = pp.c;
d = pp.d;
var id : Number = 1 / (a * d - b * c), x : Number = targetX - pp.worldX, y : Number = targetY - pp.worldY;
var tx : Number = (x * d - y * b) * id - px, ty : Number = (y * a - x * c) * id - py;
var tx : Number = (x * d - y * b) * id - px, ty : Number = (y * a - x * c) * id - py, dd : Number = tx * tx + ty * ty;
x = cwx - pp.worldX;
y = cwy - pp.worldY;
var dx : Number = (x * d - y * b) * id - px, dy : Number = (y * a - x * c) * id - py;
@ -146,10 +153,13 @@ package spine {
outer:
if (u) {
l2 *= psx;
var cos : Number = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
var cos : Number = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1)
cos = -1;
else if (cos > 1) cos = 1;
else if (cos > 1) {
cos = 1;
if (stretch && l1 + l2 > 0.0001) sx *= (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
}
a2 = Math.acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * Math.sin(a2);
@ -157,7 +167,7 @@ package spine {
} else {
a = psx * l2;
b = psy * l2;
var aa : Number = a * a, bb : Number = b * b, dd : Number = tx * tx + ty * ty, ta : Number = Math.atan2(ty, tx);
var aa : Number = a * a, bb : Number = b * b, ta : Number = Math.atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
var c1 : Number = -2 * bb * l1, c2 : Number = bb - aa;
d = c1 * c1 - 4 * c2 * c;
@ -209,7 +219,7 @@ package spine {
if (a1 > 180)
a1 -= 360;
else if (a1 < -180) a1 += 360;
parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, parent.ascaleX, parent.ascaleY, 0, 0);
parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0);
rotation = child.arotation;
a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation;
if (a2 > 180)

View File

@ -34,8 +34,9 @@ package spine {
public var order : Number;
public var bones : Vector.<BoneData> = new Vector.<BoneData>();
public var target : BoneData;
public var bendDirection : int = 1;
public var mix : Number = 1;
public var bendDirection : int = 1;
public var stretch : Boolean = false;
public function IkConstraintData(name : String) {
if (name == null) throw new ArgumentError("name cannot be null.");

View File

@ -291,6 +291,7 @@ package spine {
for each (var ikConstraint : IkConstraint in ikConstraints) {
ikConstraint.bendDirection = ikConstraint._data.bendDirection;
ikConstraint.stretch = ikConstraint._data.stretch;
ikConstraint.mix = ikConstraint._data.mix;
}

View File

@ -158,6 +158,7 @@ package spine {
if (!ikConstraintData.target) throw new Error("Target bone not found: " + constraintMap["target"]);
ikConstraintData.bendDirection = (!constraintMap.hasOwnProperty("bendPositive") || constraintMap["bendPositive"]) ? 1 : -1;
ikConstraintData.stretch = (!constraintMap.hasOwnProperty("stretch") || constraintMap["stretch"]);
ikConstraintData.mix = constraintMap.hasOwnProperty("mix") ? constraintMap["mix"] : 1;
skeletonData.ikConstraints.push(ikConstraintData);
@ -531,7 +532,8 @@ package spine {
for each (valueMap in values) {
var mix : Number = valueMap.hasOwnProperty("mix") ? valueMap["mix"] : 1;
var bendDirection : int = (!valueMap.hasOwnProperty("bendPositive") || valueMap["bendPositive"]) ? 1 : -1;
ikTimeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection);
var stretch : Boolean = (!valueMap.hasOwnProperty("stretch") || valueMap["stretch"]);
ikTimeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection, stretch);
readCurve(valueMap, ikTimeline, frameIndex);
frameIndex++;
}

View File

@ -34,9 +34,9 @@ package spine.animation {
import spine.Skeleton;
public class IkConstraintTimeline extends CurveTimeline {
static public const ENTRIES : int = 3;
static internal const PREV_TIME : int = -3, PREV_MIX : int = -2, PREV_BEND_DIRECTION : int = -1;
static internal const MIX : int = 1, BEND_DIRECTION : int = 2;
static public const ENTRIES : int = 4;
static internal const PREV_TIME : int = -4, PREV_MIX : int = -3, PREV_BEND_DIRECTION : int = -2, PREV_STRETCH : int = -1;
static internal const MIX : int = 1, BEND_DIRECTION : int = 2, STRETCH : int = 3;
public var ikConstraintIndex : int;
public var frames : Vector.<Number>; // time, mix, bendDirection, ...
@ -50,11 +50,12 @@ package spine.animation {
}
/** Sets the time, mix and bend direction of the specified keyframe. */
public function setFrame(frameIndex : int, time : Number, mix : Number, bendDirection : int) : void {
public function setFrame(frameIndex : int, time : Number, mix : Number, bendDirection : int, stretch: Boolean) : void {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[int(frameIndex + MIX)] = mix;
frames[int(frameIndex + BEND_DIRECTION)] = bendDirection;
frames[int(frameIndex + STRETCH)] = stretch ? 1 : 0;
}
override public function apply(skeleton : Skeleton, lastTime : Number, time : Number, firedEvents : Vector.<Event>, alpha : Number, blend : MixBlend, direction : MixDirection) : void {
@ -64,10 +65,12 @@ package spine.animation {
case MixBlend.setup:
constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
return;
case MixBlend.first:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
}
return;
}
@ -75,10 +78,20 @@ package spine.animation {
if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame.
if (blend == MixBlend.setup) {
constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha;
constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : int(frames[frames.length + PREV_BEND_DIRECTION]);
if (direction == MixDirection.Out) {
constraint.bendDirection = constraint.data.bendDirection;
constraint.stretch = constraint.data.stretch;
} else {
constraint.bendDirection = int(frames[frames.length + PREV_BEND_DIRECTION]);
constraint.stretch = int(frames[frames.length + PREV_STRETCH]) != 0;
}
} else {
constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha;
if (direction == MixDirection.In) constraint.bendDirection = int(frames[frames.length + PREV_BEND_DIRECTION]);
if (direction == MixDirection.In) {
constraint.bendDirection = int(frames[frames.length + PREV_BEND_DIRECTION]);
constraint.stretch = int(frames[frames.length + PREV_STRETCH]) != 0;
}
}
return;
}
@ -91,10 +104,19 @@ package spine.animation {
if (blend == MixBlend.setup) {
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.stretch = constraint.data.stretch;
} else {
constraint.bendDirection = int(frames[frame + PREV_BEND_DIRECTION]);
constraint.stretch = int(frames[frame + PREV_STRETCH]) != 0;
}
} else {
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.stretch = int(frames[frame + PREV_STRETCH]) != 0;
}
}
}
}

View File

@ -1,4 +1,5 @@
eclipse.preferences.version=1
encoding//src/spine/examples/StretchymanStrechyIkExample.as=UTF-8
encoding//src/spine/examples/TankExample.as=UTF-8
encoding//src/spine/examples/TwoColorExample.as=UTF-8
encoding/<project>=UTF-8

View File

@ -104,7 +104,7 @@ package spine.examples {
if (touch && touch.phase == TouchPhase.BEGAN) {
var parent: DisplayObjectContainer = this.parent;
this.removeFromParent(true);
parent.addChild(new CoinExample());
parent.addChild(new StretchymanStrechyIkExample());
}
}
}

View File

@ -0,0 +1,111 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package spine.examples {
import starling.display.DisplayObjectContainer;
import starling.events.TouchPhase;
import starling.events.Touch;
import starling.events.TouchEvent;
import spine.*;
import spine.animation.AnimationStateData;
import spine.animation.TrackEntry;
import spine.atlas.Atlas;
import spine.attachments.AtlasAttachmentLoader;
import spine.attachments.AttachmentLoader;
import spine.starling.SkeletonAnimation;
import spine.starling.StarlingTextureLoader;
import starling.core.Starling;
import starling.display.Sprite;
public class StretchymanStrechyIkExample extends Sprite {
[Embed(source = "/stretchyman-stretchy-ik.json", mimeType = "application/octet-stream")]
static public const StretchymanJson : Class;
[Embed(source = "/stretchyman.atlas", mimeType = "application/octet-stream")]
static public const StretchymanAtlas : Class;
[Embed(source = "/stretchyman.png")]
static public const StretchymanAtlasTexture : Class;
private var skeleton : SkeletonAnimation;
public function StretchymanStrechyIkExample() {
var spineAtlas : Atlas = new Atlas(new StretchymanAtlas(), new StarlingTextureLoader(new StretchymanAtlasTexture()));
var attachmentLoader : AttachmentLoader = new AtlasAttachmentLoader(spineAtlas);
var json : SkeletonJson = new SkeletonJson(attachmentLoader);
json.scale = 0.4;
var skeletonData : SkeletonData = json.readSkeletonData(new StretchymanJson());
var stateData : AnimationStateData = new AnimationStateData(skeletonData);
skeleton = new SkeletonAnimation(skeletonData, stateData);
skeleton.x = 100;
skeleton.y = 560;
skeleton.state.timeScale = 0.1;
skeleton.state.onStart.add(function(entry : TrackEntry) : void {
trace(entry.trackIndex + " start: " + entry.animation.name);
});
skeleton.state.onInterrupt.add(function(entry : TrackEntry) : void {
trace(entry.trackIndex + " interrupt: " + entry.animation.name);
});
skeleton.state.onEnd.add(function(entry : TrackEntry) : void {
trace(entry.trackIndex + " end: " + entry.animation.name);
});
skeleton.state.onComplete.add(function(entry : TrackEntry) : void {
trace(entry.trackIndex + " complete: " + entry.animation.name);
});
skeleton.state.onDispose.add(function(entry : TrackEntry) : void {
trace(entry.trackIndex + " dispose: " + entry.animation.name);
});
skeleton.state.onEvent.add(function(entry : TrackEntry, event : Event) : void {
trace(entry.trackIndex + " event: " + entry.animation.name + ", " + event.data.name + ": " + event.intValue + ", " + event.floatValue + ", " + event.stringValue);
});
skeleton.skeleton.setToSetupPose();
skeleton.state.setAnimationByName(0, "sneak", true);
addChild(skeleton);
Starling.juggler.add(skeleton);
addEventListener(TouchEvent.TOUCH, onClick);
}
private function onClick(event : TouchEvent) : void {
var touch : Touch = event.getTouch(this);
if (touch && touch.phase == TouchPhase.BEGAN) {
var parent: DisplayObjectContainer = this.parent;
this.removeFromParent(true);
parent.addChild(new CoinExample());
}
}
}
}