mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-25 22:23:42 +08:00
[haxe] Port to 4.3 (WIP)
This commit is contained in:
parent
3b39aeebbf
commit
4daeefd5cf
@ -29,437 +29,33 @@
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores a bone's current pose.
|
||||
/** The current pose for a bone, before constraints are applied.
|
||||
*
|
||||
* A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
|
||||
* local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
|
||||
* constraint or application code modifies the world transform after it was computed from the local transform. */
|
||||
class Bone implements Updatable {
|
||||
class Bone extends PosedActive<BoneData, BoneLocal, BonePose> {
|
||||
static public var yDown:Bool = false;
|
||||
|
||||
private var _data:BoneData;
|
||||
private var _skeleton:Skeleton;
|
||||
private var _parent:Bone;
|
||||
private var _children:Array<Bone> = new Array<Bone>();
|
||||
|
||||
/** The local x translation. */
|
||||
public var x:Float = 0;
|
||||
/** The local y translation. */
|
||||
public var y:Float = 0;
|
||||
/** The local rotation in degrees, counter clockwise. */
|
||||
public var rotation:Float = 0;
|
||||
/** The local scaleX. */
|
||||
public var scaleX:Float = 0;
|
||||
/** The local scaleY. */
|
||||
public var scaleY:Float = 0;
|
||||
/** The local shearX. */
|
||||
public var shearX:Float = 0;
|
||||
/** The local shearY. */
|
||||
public var shearY:Float = 0;
|
||||
/** The applied local x translation. */
|
||||
public var ax:Float = 0;
|
||||
/** The applied local y translation. */
|
||||
public var ay:Float = 0;
|
||||
/** The applied local rotation in degrees, counter clockwise. */
|
||||
public var arotation:Float = 0;
|
||||
/** The applied local scaleX. */
|
||||
public var ascaleX:Float = 0;
|
||||
/** The applied local scaleY. */
|
||||
public var ascaleY:Float = 0;
|
||||
/** The applied local shearX. */
|
||||
public var ashearX:Float = 0;
|
||||
/** The applied local shearY. */
|
||||
public var ashearY:Float = 0;
|
||||
/** Part of the world transform matrix for the X axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var a:Float = 0;
|
||||
/** Part of the world transform matrix for the Y axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var b:Float = 0;
|
||||
/** Part of the world transform matrix for the X axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var c:Float = 0;
|
||||
/** Part of the world transform matrix for the Y axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var d:Float = 0;
|
||||
/** The world X position. If changed, updateAppliedTransform() should be called. */
|
||||
public var worldX:Float = 0;
|
||||
/** The world Y position. If changed, updateAppliedTransform() should be called. */
|
||||
public var worldY:Float = 0;
|
||||
/** Determines how parent world transforms affect this bone. */
|
||||
public var inherit:Inherit = Inherit.normal;
|
||||
public var sorted:Bool = false;
|
||||
public var active:Bool = false;
|
||||
|
||||
/** The bone's setup pose data. */
|
||||
public var data(get, never):BoneData;
|
||||
|
||||
private function get_data():BoneData {
|
||||
return _data;
|
||||
}
|
||||
|
||||
/** The skeleton this bone belongs to. */
|
||||
public var skeleton(get, never):Skeleton;
|
||||
|
||||
private function get_skeleton():Skeleton {
|
||||
return _skeleton;
|
||||
}
|
||||
|
||||
/** The parent bone, or null if this is the root bone. */
|
||||
public var parent(get, never):Bone;
|
||||
|
||||
private function get_parent():Bone {
|
||||
return _parent;
|
||||
}
|
||||
public final parent:Bone = null;
|
||||
|
||||
/** The immediate children of this bone. */
|
||||
public var children(get, never):Array<Bone>;
|
||||
public final children:Array<Bone> = new Array<Bone>();
|
||||
|
||||
private function get_children():Array<Bone> {
|
||||
return _children;
|
||||
public var sorted = false;
|
||||
|
||||
public function new (data:BoneData, parent:Bone) {
|
||||
super(data, new BonePose(), new BonePose());
|
||||
this.parent = parent;
|
||||
applied.bone = this;
|
||||
constrained.bone = this;
|
||||
}
|
||||
|
||||
/** Copy constructor. Does not copy the children bones. */
|
||||
public function new(data:BoneData, skeleton:Skeleton, parent:Bone) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (skeleton == null)
|
||||
throw new SpineException("skeleton cannot be null.");
|
||||
_data = data;
|
||||
_skeleton = skeleton;
|
||||
_parent = parent;
|
||||
setToSetupPose();
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return active;
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone and this bone's local applied transform. */
|
||||
public function update(physics:Physics):Void {
|
||||
updateWorldTransformWith(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone and this bone's local transform.
|
||||
*
|
||||
* See updateWorldTransformWith(). */
|
||||
public function updateWorldTransform():Void {
|
||||
updateWorldTransformWith(x, y, rotation, scaleX, scaleY, shearX, shearY);
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
|
||||
* specified local transform. Child bones are not updated.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
*/
|
||||
public function updateWorldTransformWith(x:Float, y:Float, rotation:Float, scaleX:Float, scaleY:Float, shearX:Float, shearY:Float):Void {
|
||||
ax = x;
|
||||
ay = y;
|
||||
arotation = rotation;
|
||||
ascaleX = scaleX;
|
||||
ascaleY = scaleY;
|
||||
ashearX = shearX;
|
||||
ashearY = shearY;
|
||||
|
||||
var la:Float = 0;
|
||||
var lb:Float = 0;
|
||||
var lc:Float = 0;
|
||||
var ld:Float = 0;
|
||||
var sin:Float = 0;
|
||||
var cos:Float = 0;
|
||||
var s:Float = 0;
|
||||
var sx:Float = skeleton.scaleX;
|
||||
var sy:Float = skeleton.scaleY;
|
||||
|
||||
var parent:Bone = _parent;
|
||||
if (parent == null) {
|
||||
// Root bone.
|
||||
var rx:Float = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry:Float = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
a = Math.cos(rx) * scaleX * sx;
|
||||
b = Math.cos(ry) * scaleY * sx;
|
||||
c = Math.sin(rx) * scaleX * sy;
|
||||
d = Math.sin(ry) * scaleY * sy;
|
||||
worldX = x * sx + skeleton.x;
|
||||
worldY = y * sy + skeleton.y;
|
||||
return;
|
||||
}
|
||||
|
||||
var pa:Float = parent.a,
|
||||
pb:Float = parent.b,
|
||||
pc:Float = parent.c,
|
||||
pd:Float = parent.d;
|
||||
worldX = pa * x + pb * y + parent.worldX;
|
||||
worldY = pc * x + pd * y + parent.worldY;
|
||||
|
||||
switch (inherit) {
|
||||
case Inherit.normal:
|
||||
var rx:Float = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry:Float = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
la = Math.cos(rx) * scaleX;
|
||||
lb = Math.cos(ry) * scaleY;
|
||||
lc = Math.sin(rx) * scaleX;
|
||||
ld = Math.sin(ry) * scaleY;
|
||||
a = pa * la + pb * lc;
|
||||
b = pa * lb + pb * ld;
|
||||
c = pc * la + pd * lc;
|
||||
d = pc * lb + pd * ld;
|
||||
return;
|
||||
case Inherit.onlyTranslation:
|
||||
var rx:Float = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry:Float = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
a = Math.cos(rx) * scaleX;
|
||||
b = Math.cos(ry) * scaleY;
|
||||
c = Math.sin(rx) * scaleX;
|
||||
d = Math.sin(ry) * scaleY;
|
||||
case Inherit.noRotationOrReflection:
|
||||
var sx:Float = 1 / skeleton.scaleX;
|
||||
var sy:Float = 1 / skeleton.scaleY;
|
||||
pa *= sx;
|
||||
pc *= sy;
|
||||
s = pa * pa + pc * pc;
|
||||
var prx:Float = 0;
|
||||
if (s > 0.0001) {
|
||||
s = Math.abs(pa * pd * sy - pb * sx * pc) / s;
|
||||
pb = pc * s;
|
||||
pd = pa * s;
|
||||
prx = Math.atan2(pc, pa) * MathUtils.radDeg;
|
||||
} else {
|
||||
pa = 0;
|
||||
pc = 0;
|
||||
prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
|
||||
}
|
||||
var rx:Float = (rotation + shearX - prx) * MathUtils.degRad;
|
||||
var ry:Float = (rotation + shearY - prx + 90) * MathUtils.degRad;
|
||||
la = Math.cos(rx) * scaleX;
|
||||
lb = Math.cos(ry) * scaleY;
|
||||
lc = Math.sin(rx) * scaleX;
|
||||
ld = Math.sin(ry) * scaleY;
|
||||
a = pa * la - pb * lc;
|
||||
b = pa * lb - pb * ld;
|
||||
c = pc * la + pd * lc;
|
||||
d = pc * lb + pd * ld;
|
||||
case Inherit.noScale, Inherit.noScaleOrReflection:
|
||||
rotation *= MathUtils.degRad;
|
||||
cos = Math.cos(rotation);
|
||||
sin = Math.sin(rotation);
|
||||
var za:Float = (pa * cos + pb * sin) / sx;
|
||||
var zc:Float = (pc * cos + pd * sin) / sy;
|
||||
s = Math.sqrt(za * za + zc * zc);
|
||||
if (s > 0.00001)
|
||||
s = 1 / s;
|
||||
za *= s;
|
||||
zc *= s;
|
||||
s = Math.sqrt(za * za + zc * zc);
|
||||
if (inherit == Inherit.noScale && ((pa * pd - pb * pc < 0) != ((sx < 0) != (sy < 0)))) {
|
||||
s = -s;
|
||||
}
|
||||
rotation = Math.PI / 2 + Math.atan2(zc, za);
|
||||
var zb:Float = Math.cos(rotation) * s;
|
||||
var zd:Float = Math.sin(rotation) * s;
|
||||
shearX *= MathUtils.degRad;
|
||||
shearY = (90 + shearY) * MathUtils.degRad;
|
||||
la = Math.cos(shearX) * scaleX;
|
||||
lb = Math.cos(shearY) * scaleY;
|
||||
lc = Math.sin(shearX) * scaleX;
|
||||
ld = Math.sin(shearY) * scaleY;
|
||||
a = za * la + zb * lc;
|
||||
b = za * lb + zb * ld;
|
||||
c = zc * la + zd * lc;
|
||||
d = zc * lb + zd * ld;
|
||||
}
|
||||
a *= sx;
|
||||
b *= sx;
|
||||
c *= sy;
|
||||
d *= sy;
|
||||
}
|
||||
|
||||
/** Sets this bone's local transform to the setup pose. */
|
||||
public function setToSetupPose():Void {
|
||||
x = data.x;
|
||||
y = data.y;
|
||||
rotation = data.rotation;
|
||||
scaleX = data.scaleX;
|
||||
scaleY = data.scaleY;
|
||||
shearX = data.shearX;
|
||||
shearY = data.shearY;
|
||||
inherit = data.inherit;
|
||||
}
|
||||
|
||||
/** Computes the applied transform values from the world transform.
|
||||
*
|
||||
* If the world transform is modified (by a constraint, rotateWorld(), etc) then this method should be called so
|
||||
* the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another
|
||||
* constraint).
|
||||
*
|
||||
* Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
|
||||
* calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
|
||||
public function updateAppliedTransform():Void {
|
||||
var parent:Bone = parent;
|
||||
if (parent == null) {
|
||||
ax = worldX - skeleton.x;
|
||||
ay = worldY - skeleton.y;
|
||||
arotation = Math.atan2(c, a) * MathUtils.radDeg;
|
||||
ascaleX = Math.sqrt(a * a + c * c);
|
||||
ascaleY = Math.sqrt(b * b + d * d);
|
||||
ashearX = 0;
|
||||
ashearY = Math.atan2(a * b + c * d, a * d - b * c) * MathUtils.radDeg;
|
||||
return;
|
||||
}
|
||||
var pa:Float = parent.a,
|
||||
pb:Float = parent.b,
|
||||
pc:Float = parent.c,
|
||||
pd:Float = parent.d;
|
||||
var pid:Float = 1 / (pa * pd - pb * pc);
|
||||
var ia:Float = pd * pid,
|
||||
ib:Float = pb * pid,
|
||||
ic:Float = pc * pid,
|
||||
id:Float = pa * pid;
|
||||
var dx:Float = worldX - parent.worldX,
|
||||
dy:Float = worldY - parent.worldY;
|
||||
ax = (dx * ia - dy * ib);
|
||||
ay = (dy * id - dx * ic);
|
||||
var ra:Float, rb:Float, rc:Float, rd:Float;
|
||||
if (inherit == Inherit.onlyTranslation) {
|
||||
ra = a;
|
||||
rb = b;
|
||||
rc = c;
|
||||
rd = d;
|
||||
} else {
|
||||
switch (inherit) {
|
||||
case Inherit.noRotationOrReflection:
|
||||
var s:Float = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
|
||||
pb = -pc * skeleton.scaleX * s / skeleton.scaleY;
|
||||
pd = pa * skeleton.scaleY * s / skeleton.scaleX;
|
||||
pid = 1 / (pa * pd - pb * pc);
|
||||
ia = pd * pid;
|
||||
ib = pb * pid;
|
||||
case Inherit.noScale | Inherit.noScaleOrReflection:
|
||||
var cos:Float = MathUtils.cosDeg(rotation), sin:Float = MathUtils.sinDeg(rotation);
|
||||
pa = (pa * cos + pb * sin) / skeleton.scaleX;
|
||||
pc = (pc * cos + pd * sin) / skeleton.scaleY;
|
||||
var s:Float = Math.sqrt(pa * pa + pc * pc);
|
||||
if (s > 0.00001) s = 1 / s;
|
||||
pa *= s;
|
||||
pc *= s;
|
||||
s = Math.sqrt(pa * pa + pc * pc);
|
||||
if (inherit == Inherit.noScale && pid < 0 != ((skeleton.scaleX < 0) != (skeleton.scaleY < 0))) s = -s;
|
||||
var r:Float = MathUtils.PI / 2 + Math.atan2(pc, pa);
|
||||
pb = Math.cos(r) * s;
|
||||
pd = Math.sin(r) * s;
|
||||
pid = 1 / (pa * pd - pb * pc);
|
||||
ia = pd * pid;
|
||||
ib = pb * pid;
|
||||
ic = pc * pid;
|
||||
id = pa * pid;
|
||||
}
|
||||
ra = ia * a - ib * c;
|
||||
rb = ia * b - ib * d;
|
||||
rc = id * c - ic * a;
|
||||
rd = id * d - ic * b;
|
||||
}
|
||||
|
||||
ashearX = 0;
|
||||
ascaleX = Math.sqrt(ra * ra + rc * rc);
|
||||
if (scaleX > 0.0001) {
|
||||
var det:Float = ra * rd - rb * rc;
|
||||
ascaleY = det / ascaleX;
|
||||
ashearY = -Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
|
||||
arotation = Math.atan2(rc, ra) * MathUtils.radDeg;
|
||||
} else {
|
||||
ascaleX = 0;
|
||||
ascaleY = Math.sqrt(rb * rb + rd * rd);
|
||||
ashearY = 0;
|
||||
arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
|
||||
}
|
||||
}
|
||||
|
||||
/** The world rotation for the X axis, calculated using a and c. */
|
||||
public var worldRotationX(get, never):Float;
|
||||
|
||||
private function get_worldRotationX():Float {
|
||||
return Math.atan2(c, a) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The world rotation for the Y axis, calculated using b and d. */
|
||||
public var worldRotationY(get, never):Float;
|
||||
|
||||
private function get_worldRotationY():Float {
|
||||
return Math.atan2(d, b) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale X, calculated using a and c. */
|
||||
public var worldScaleX(get, never):Float;
|
||||
|
||||
private function get_worldScaleX():Float {
|
||||
return Math.sqrt(a * a + c * c);
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale Y, calculated using b and d. */
|
||||
public var worldScaleY(get, never):Float;
|
||||
|
||||
private function get_worldScaleY():Float {
|
||||
return Math.sqrt(b * b + d * d);
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the parent bone's local coordinates. */
|
||||
public function worldToParent(world: Array<Float>):Array<Float> {
|
||||
if (world == null)
|
||||
throw new SpineException("world cannot be null.");
|
||||
return parent == null ? world : parent.worldToLocal(world);
|
||||
}
|
||||
|
||||
/** Transforms a point from the parent bone's coordinates to world coordinates. */
|
||||
public function parentToWorld(world: Array<Float>):Array<Float> {
|
||||
if (world == null)
|
||||
throw new SpineException("world cannot be null.");
|
||||
return parent == null ? world : parent.localToWorld(world);
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the bone's local coordinates. */
|
||||
public function worldToLocal(world:Array<Float>):Array<Float> {
|
||||
var a:Float = a, b:Float = b, c:Float = c, d:Float = d;
|
||||
var invDet:Float = 1 / (a * d - b * c);
|
||||
var x:Float = world[0] - worldX, y:Float = world[1] - worldY;
|
||||
world[0] = (x * d * invDet - y * b * invDet);
|
||||
world[1] = (y * a * invDet - x * c * invDet);
|
||||
return world;
|
||||
}
|
||||
|
||||
/** Transforms a point from the bone's local coordinates to world coordinates. */
|
||||
public function localToWorld(local:Array<Float>):Array<Float> {
|
||||
var localX:Float = local[0], localY:Float = local[1];
|
||||
local[0] = localX * a + localY * b + worldX;
|
||||
local[1] = localX * c + localY * d + worldY;
|
||||
return local;
|
||||
}
|
||||
|
||||
/** Transforms a world rotation to a local rotation. */
|
||||
public function worldToLocalRotation(worldRotation:Float):Float {
|
||||
var sin:Float = MathUtils.sinDeg(worldRotation),
|
||||
cos:Float = MathUtils.cosDeg(worldRotation);
|
||||
return Math.atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.radDeg + rotation - shearX;
|
||||
}
|
||||
|
||||
/** Transforms a local rotation to a world rotation. */
|
||||
public function localToWorldRotation(localRotation:Float):Float {
|
||||
localRotation -= rotation - shearX;
|
||||
var sin:Float = MathUtils.sinDeg(localRotation),
|
||||
cos:Float = MathUtils.cosDeg(localRotation);
|
||||
return Math.atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** Rotates the world transform the specified amount.
|
||||
*
|
||||
* After changes are made to the world transform, updateAppliedTransform() should be called and
|
||||
* update() will need to be called on any child bones, recursively. */
|
||||
public function rotateWorld(degrees:Float):Void {
|
||||
degrees *= MathUtils.degRad;
|
||||
var sin:Float = Math.sin(degrees), cos:Float = Math.cos(degrees);
|
||||
var ra:Float = a, rb:Float = b;
|
||||
a = cos * ra - sin * c;
|
||||
b = cos * rb - sin * d;
|
||||
c = sin * ra + cos * c;
|
||||
d = sin * rb + cos * d;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return data.name;
|
||||
/** Copy method. Does not copy the children bones. */
|
||||
public function copy(bone:Bone, parent:Bone):Bone {
|
||||
var copy = new Bone(bone.data, parent);
|
||||
pose.set(bone.pose);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,75 +29,41 @@
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a spine.Bone. */
|
||||
class BoneData {
|
||||
private var _index:Int;
|
||||
private var _name:String;
|
||||
private var _parent:BoneData;
|
||||
|
||||
/** The bone's length. */
|
||||
public var length:Float = 0;
|
||||
/** The local x translation. */
|
||||
public var x:Float = 0;
|
||||
/** The local y translation. */
|
||||
public var y:Float = 0;
|
||||
/** The local rotation in degrees, counter clockwise. */
|
||||
public var rotation:Float = 0;
|
||||
/** The local scaleX. */
|
||||
public var scaleX:Float = 1;
|
||||
/** The local scaleY. */
|
||||
public var scaleY:Float = 1;
|
||||
/** The local shearX. */
|
||||
public var shearX:Float = 0;
|
||||
/** The local shearY. */
|
||||
public var shearY:Float = 0;
|
||||
/** Determines how parent world transforms affect this bone. */
|
||||
public var inherit:Inherit = Inherit.normal;
|
||||
/** When true, spine.Skeleton.updateWorldTransform() only updates this bone if the spine.Skeleton.getSkin() contains
|
||||
* this bone.
|
||||
* @see spine.Skin.getBones() */
|
||||
public var skinRequired:Bool = false;
|
||||
/** The color of the bone as it was in Spine, or a default color if nonessential data was not exported. Bones are not usually
|
||||
* rendered at runtime. */
|
||||
public var color:Color = new Color(0, 0, 0, 0);
|
||||
/** The bone icon as it was in Spine, or null if nonessential data was not exported. */
|
||||
public var icon:String;
|
||||
/** False if the bone was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
|
||||
public var visible:Bool = false;
|
||||
|
||||
/** Copy constructor. */
|
||||
public function new(index:Int, name:String, parent:BoneData) {
|
||||
if (index < 0)
|
||||
throw new SpineException("index must be >= 0");
|
||||
if (name == null)
|
||||
throw new SpineException("name cannot be null.");
|
||||
_index = index;
|
||||
_name = name;
|
||||
_parent = parent;
|
||||
}
|
||||
/** The setup pose for a bone. */
|
||||
class BoneData extends PosedData<BoneLocal> {
|
||||
|
||||
/** The index of the bone in spine.Skeleton.getBones(). */
|
||||
public var index(get, never):Int;
|
||||
public final index:Int;
|
||||
|
||||
private function get_index():Int {
|
||||
return _index;
|
||||
public final parent:BoneData = null;
|
||||
|
||||
/** The bone's length. */
|
||||
public var length = 0.;
|
||||
|
||||
// Nonessential.
|
||||
/** The color of the bone as it was in Spine, or a default color if nonessential data was not exported. Bones are not usually
|
||||
* rendered at runtime. */
|
||||
public var color = new Color(0, 0, 0, 0);
|
||||
|
||||
/** The bone icon as it was in Spine, or null if nonessential data was not exported. */
|
||||
public var icon:String = null;
|
||||
|
||||
/** False if the bone was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
|
||||
public var visible = false;
|
||||
|
||||
public function new (index:Int, name:String, parent:BoneData) {
|
||||
super(name, new BoneLocal());
|
||||
if (index < 0) throw new SpineException("index must be >= 0.");
|
||||
if (name == null) throw new SpineException("name cannot be null.");
|
||||
this.index = index;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/** The name of the bone, which is unique across all bones in the skeleton. */
|
||||
public var name(get, never):String;
|
||||
|
||||
private function get_name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/** @return May be null. */
|
||||
public var parent(get, never):BoneData;
|
||||
|
||||
private function get_parent():BoneData {
|
||||
return _parent;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _name;
|
||||
/** Copy method. */
|
||||
public function copy(data:BoneData, parent:BoneData) {
|
||||
var copy = new BoneData(data.index, data.name, parent);
|
||||
length = data.length;
|
||||
setup.set(data.setup);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
79
spine-haxe/spine-haxe/spine/BoneLocal.hx
Normal file
79
spine-haxe/spine-haxe/spine/BoneLocal.hx
Normal file
@ -0,0 +1,79 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores a bone's local pose. */
|
||||
class BoneLocal implements Pose<BoneLocal> {
|
||||
|
||||
/** The local x translation. */
|
||||
public var x:Float = 0;
|
||||
|
||||
/** The local y translation. */
|
||||
public var y:Float = 0;
|
||||
|
||||
/** The local rotation in degrees, counter clockwise. */
|
||||
public var rotation:Float = 0;
|
||||
|
||||
/** The local scaleX. */
|
||||
public var scaleX:Float = 0;
|
||||
|
||||
/** The local scaleY. */
|
||||
public var scaleY:Float = 0;
|
||||
|
||||
/** The local shearX. */
|
||||
public var shearX:Float = 0;
|
||||
|
||||
/** The local shearY. */
|
||||
public var shearY:Float = 0;
|
||||
|
||||
/** Determines how parent world transforms affect this bone. */
|
||||
public var inherit(default, set):Inherit;
|
||||
function set_inherit (value:Inherit):Inherit {
|
||||
if (value == null) throw new SpineException("inherit cannot be null.");
|
||||
inherit = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set (pose:BoneLocal):Void {
|
||||
if (pose == null) throw new SpineException("pose cannot be null.");
|
||||
x = pose.x;
|
||||
y = pose.y;
|
||||
rotation = pose.rotation;
|
||||
scaleX = pose.scaleX;
|
||||
scaleY = pose.scaleY;
|
||||
shearX = pose.shearX;
|
||||
shearY = pose.shearY;
|
||||
inherit = pose.inherit;
|
||||
}
|
||||
|
||||
}
|
||||
381
spine-haxe/spine-haxe/spine/BonePose.hx
Normal file
381
spine-haxe/spine-haxe/spine/BonePose.hx
Normal file
@ -0,0 +1,381 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by
|
||||
* Skeleton.updateWorldTransform(Physics). */
|
||||
class BonePose extends BoneLocal implements Update {
|
||||
|
||||
public var bone:Bone;
|
||||
|
||||
/** Part of the world transform matrix for the X axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var a:Float = 0;
|
||||
|
||||
/** Part of the world transform matrix for the Y axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var b:Float = 0;
|
||||
|
||||
/** Part of the world transform matrix for the X axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var c:Float = 0;
|
||||
|
||||
/** Part of the world transform matrix for the Y axis. If changed, updateAppliedTransform() should be called. */
|
||||
public var d:Float = 0;
|
||||
|
||||
/** The world X position. If changed, updateAppliedTransform() should be called. */
|
||||
public var worldX:Float = 0;
|
||||
|
||||
/** The world Y position. If changed, updateAppliedTransform() should be called. */
|
||||
public var worldY:Float = 0;
|
||||
|
||||
public var world:Int;
|
||||
public var local:Int;
|
||||
|
||||
// public function new () {
|
||||
// super();
|
||||
// }
|
||||
|
||||
/** Called by Skeleton.updateCache() to compute the world transform, if needed. */
|
||||
public function update (skeleton:Skeleton, physics:Physics):Void {
|
||||
if (world != skeleton._update) updateWorldTransform(skeleton);
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone's applied pose and this pose. Child bones are not updated.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
*/
|
||||
public function updateWorldTransform(skeleton:Skeleton):Void {
|
||||
if (local == skeleton._update)
|
||||
updateLocalTransform(skeleton);
|
||||
else
|
||||
world = skeleton._update;
|
||||
|
||||
if (bone.parent == null) { // Root bone.
|
||||
var sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||
var rx = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
a = Math.cos(rx) * scaleX * sx;
|
||||
b = Math.cos(ry) * scaleY * sx;
|
||||
c = Math.sin(rx) * scaleX * sy;
|
||||
d = Math.sin(ry) * scaleY * sy;
|
||||
worldX = x * sx + skeleton.x;
|
||||
worldY = y * sy + skeleton.y;
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = bone.parent.applied;
|
||||
var pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||
worldX = pa * x + pb * y + parent.worldX;
|
||||
worldY = pc * x + pd * y + parent.worldY;
|
||||
|
||||
switch (inherit) {
|
||||
case Inherit.normal:
|
||||
var rx = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
var la = Math.cos(rx) * scaleX;
|
||||
var lb = Math.cos(ry) * scaleY;
|
||||
var lc = Math.sin(rx) * scaleX;
|
||||
var ld = Math.sin(ry) * scaleY;
|
||||
a = pa * la + pb * lc;
|
||||
b = pa * lb + pb * ld;
|
||||
c = pc * la + pd * lc;
|
||||
d = pc * lb + pd * ld;
|
||||
return;
|
||||
case Inherit.onlyTranslation:
|
||||
var rx = (rotation + shearX) * MathUtils.degRad;
|
||||
var ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
a = Math.cos(rx) * scaleX;
|
||||
b = Math.cos(ry) * scaleY;
|
||||
c = Math.sin(rx) * scaleX;
|
||||
d = Math.sin(ry) * scaleY;
|
||||
case Inherit.noRotationOrReflection:
|
||||
var sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY;
|
||||
pa *= sx;
|
||||
pc *= sy;
|
||||
var s = pa * pa + pc * pc, prx:Float;
|
||||
if (s > 0.0001) {
|
||||
s = Math.abs(pa * pd * sy - pb * sx * pc) / s;
|
||||
pb = pc * s;
|
||||
pd = pa * s;
|
||||
prx = MathUtils.atan2Deg(pc, pa);
|
||||
} else {
|
||||
pa = 0;
|
||||
pc = 0;
|
||||
prx = 90 - MathUtils.atan2Deg(pd, pb);
|
||||
}
|
||||
var rx = (rotation + shearX - prx) * MathUtils.degRad;
|
||||
var ry = (rotation + shearY - prx + 90) * MathUtils.degRad;
|
||||
var la = Math.cos(rx) * scaleX;
|
||||
var lb = Math.cos(ry) * scaleY;
|
||||
var lc = Math.sin(rx) * scaleX;
|
||||
var ld = Math.sin(ry) * scaleY;
|
||||
a = pa * la - pb * lc;
|
||||
b = pa * lb - pb * ld;
|
||||
c = pc * la + pd * lc;
|
||||
d = pc * lb + pd * ld;
|
||||
case Inherit.noScale, Inherit.noScaleOrReflection:
|
||||
rotation *= MathUtils.degRad;
|
||||
var cos = Math.cos(rotation);
|
||||
var sin = Math.sin(rotation);
|
||||
var za = (pa * cos + pb * sin) / skeleton.scaleX;
|
||||
var zc = (pc * cos + pd * sin) / skeleton.scaleY;
|
||||
var s = Math.sqrt(za * za + zc * zc);
|
||||
if (s > 0.00001) s = 1 / s;
|
||||
za *= s;
|
||||
zc *= s;
|
||||
s = Math.sqrt(za * za + zc * zc);
|
||||
if (inherit == Inherit.noScale && ((pa * pd - pb * pc < 0) != ((skeleton.scaleX < 0) != (skeleton.scaleY < 0)))) s = -s;
|
||||
rotation = Math.PI / 2 + Math.atan2(zc, za);
|
||||
var zb:Float = Math.cos(rotation) * s;
|
||||
var zd:Float = Math.sin(rotation) * s;
|
||||
shearX *= MathUtils.degRad;
|
||||
shearY = (90 + shearY) * MathUtils.degRad;
|
||||
var la = Math.cos(shearX) * scaleX;
|
||||
var lb = Math.cos(shearY) * scaleY;
|
||||
var lc = Math.sin(shearX) * scaleX;
|
||||
var ld = Math.sin(shearY) * scaleY;
|
||||
a = za * la + zb * lc;
|
||||
b = za * lb + zb * ld;
|
||||
c = zc * la + zd * lc;
|
||||
d = zc * lb + zd * ld;
|
||||
}
|
||||
a *= skeleton.scaleX;
|
||||
b *= skeleton.scaleX;
|
||||
c *= skeleton.scaleY;
|
||||
d *= skeleton.scaleY;
|
||||
}
|
||||
|
||||
/** Computes the applied transform values from the world transform.
|
||||
*
|
||||
* If the world transform is modified (by a constraint, rotateWorld(), etc) then this method should be called so
|
||||
* the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another
|
||||
* constraint).
|
||||
*
|
||||
* Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
|
||||
* calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
|
||||
public function updateLocalTransform(skeleton:Skeleton):Void {
|
||||
local = 0;
|
||||
world = skeleton._update;
|
||||
|
||||
if (bone.parent == null) {
|
||||
x = worldX - skeleton.x;
|
||||
y = worldY - skeleton.y;
|
||||
rotation = MathUtils.atan2Deg(c, a);
|
||||
scaleX = Math.sqrt(a * a + c * c);
|
||||
scaleY = Math.sqrt(b * b + d * d);
|
||||
shearX = 0;
|
||||
shearY = MathUtils.atan2Deg(a * b + c * d, a * d - b * c);
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = bone.parent.applied;
|
||||
var pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||
var pid:Float = 1 / (pa * pd - pb * pc);
|
||||
var ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
|
||||
var dx = worldX - parent.worldX, dy = worldY - parent.worldY;
|
||||
x = (dx * ia - dy * ib);
|
||||
y = (dy * id - dx * ic);
|
||||
|
||||
var ra:Float, rb:Float, rc:Float, rd:Float;
|
||||
if (inherit == Inherit.onlyTranslation) {
|
||||
ra = a;
|
||||
rb = b;
|
||||
rc = c;
|
||||
rd = d;
|
||||
} else {
|
||||
switch (inherit) {
|
||||
case Inherit.noRotationOrReflection:
|
||||
var s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
|
||||
pb = -pc * skeleton.scaleX * s / skeleton.scaleY;
|
||||
pd = pa * skeleton.scaleY * s / skeleton.scaleX;
|
||||
pid = 1 / (pa * pd - pb * pc);
|
||||
ia = pd * pid;
|
||||
ib = pb * pid;
|
||||
case Inherit.noScale, Inherit.noScaleOrReflection:
|
||||
var r = rotation * MathUtils.degRad, cos = Math.cos(rotation), sin = Math.sin(rotation);
|
||||
pa = (pa * cos + pb * sin) / skeleton.scaleX;
|
||||
pc = (pc * cos + pd * sin) / skeleton.scaleY;
|
||||
var s = Math.sqrt(pa * pa + pc * pc);
|
||||
if (s > 0.00001) s = 1 / s;
|
||||
pa *= s;
|
||||
pc *= s;
|
||||
s = Math.sqrt(pa * pa + pc * pc);
|
||||
if (inherit == Inherit.noScale && (pid < 0 != ((skeleton.scaleX < 0) != (skeleton.scaleY < 0)))) s = -s;
|
||||
r = MathUtils.PI / 2 + Math.atan2(pc, pa);
|
||||
pb = Math.cos(r) * s;
|
||||
pd = Math.sin(r) * s;
|
||||
pid = 1 / (pa * pd - pb * pc);
|
||||
ia = pd * pid;
|
||||
ib = pb * pid;
|
||||
ic = pc * pid;
|
||||
id = pa * pid;
|
||||
}
|
||||
ra = ia * a - ib * c;
|
||||
rb = ia * b - ib * d;
|
||||
rc = id * c - ic * a;
|
||||
rd = id * d - ic * b;
|
||||
}
|
||||
|
||||
shearX = 0;
|
||||
scaleX = Math.sqrt(ra * ra + rc * rc);
|
||||
if (scaleX > 0.0001) {
|
||||
var det = ra * rd - rb * rc;
|
||||
scaleY = det / scaleX;
|
||||
shearY = -MathUtils.atan2Deg(ra * rb + rc * rd, det);
|
||||
rotation = MathUtils.atan2Deg(rc, ra);
|
||||
} else {
|
||||
scaleX = 0;
|
||||
scaleY = Math.sqrt(rb * rb + rd * rd);
|
||||
shearY = 0;
|
||||
rotation = 90 - MathUtils.atan2Deg(rd, rb);
|
||||
}
|
||||
}
|
||||
|
||||
/** If the world transform has been modified and the local transform no longer matches, {@link #updateLocalTransform(Skeleton)}
|
||||
* is called. */
|
||||
public function validateLocalTransform (skeleton: Skeleton) {
|
||||
if (local == skeleton._update) updateLocalTransform(skeleton);
|
||||
}
|
||||
|
||||
public function modifyLocal (skeleton: Skeleton) {
|
||||
if (local == skeleton._update) updateLocalTransform(skeleton);
|
||||
world = 0;
|
||||
resetWorld(skeleton._update);
|
||||
}
|
||||
|
||||
public function modifyWorld (update:Int) {
|
||||
local = update;
|
||||
world = update;
|
||||
resetWorld(update);
|
||||
}
|
||||
|
||||
public function resetWorld (update:Int) {
|
||||
var children = bone.children;
|
||||
for (i in 0...bone.children.length) {
|
||||
var child = children[i].applied;
|
||||
if (child.world == update) {
|
||||
child.world = 0;
|
||||
child.local = 0;
|
||||
child.resetWorld(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The world rotation for the X axis, calculated using a and c. */
|
||||
public var worldRotationX(get, never):Float;
|
||||
|
||||
private function get_worldRotationX():Float {
|
||||
return MathUtils.atan2Deg(c, a);
|
||||
}
|
||||
|
||||
/** The world rotation for the Y axis, calculated using b and d. */
|
||||
public var worldRotationY(get, never):Float;
|
||||
|
||||
private function get_worldRotationY():Float {
|
||||
return MathUtils.atan2Deg(d, b);
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale X, calculated using a and c. */
|
||||
public var worldScaleX(get, never):Float;
|
||||
|
||||
private function get_worldScaleX():Float {
|
||||
return Math.sqrt(a * a + c * c);
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale Y, calculated using b and d. */
|
||||
public var worldScaleY(get, never):Float;
|
||||
|
||||
private function get_worldScaleY():Float {
|
||||
return Math.sqrt(b * b + d * d);
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the bone's local coordinates. */
|
||||
public function worldToLocal(world:Array<Float>):Array<Float> {
|
||||
var a:Float = a, b:Float = b, c:Float = c, d:Float = d;
|
||||
var invDet:Float = 1 / (a * d - b * c);
|
||||
var x:Float = world[0] - worldX, y:Float = world[1] - worldY;
|
||||
world[0] = (x * d * invDet - y * b * invDet);
|
||||
world[1] = (y * a * invDet - x * c * invDet);
|
||||
return world;
|
||||
}
|
||||
|
||||
/** Transforms a point from the bone's local coordinates to world coordinates. */
|
||||
public function localToWorld(local:Array<Float>):Array<Float> {
|
||||
var localX:Float = local[0], localY:Float = local[1];
|
||||
local[0] = localX * a + localY * b + worldX;
|
||||
local[1] = localX * c + localY * d + worldY;
|
||||
return local;
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the parent bone's local coordinates. */
|
||||
public function worldToParent(world: Array<Float>):Array<Float> {
|
||||
if (world == null)
|
||||
throw new SpineException("world cannot be null.");
|
||||
return bone.parent == null ? world : bone.parent.applied.worldToLocal(world);
|
||||
}
|
||||
|
||||
/** Transforms a point from the parent bone's coordinates to world coordinates. */
|
||||
public function parentToWorld(world: Array<Float>):Array<Float> {
|
||||
if (world == null)
|
||||
throw new SpineException("world cannot be null.");
|
||||
return bone.parent == null ? world : bone.parent.applied.localToWorld(world);
|
||||
}
|
||||
|
||||
/** Transforms a world rotation to a local rotation. */
|
||||
public function worldToLocalRotation(worldRotation:Float):Float {
|
||||
var sin:Float = MathUtils.sinDeg(worldRotation),
|
||||
cos:Float = MathUtils.cosDeg(worldRotation);
|
||||
return Math.atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.radDeg + rotation - shearX;
|
||||
}
|
||||
|
||||
/** Transforms a local rotation to a world rotation. */
|
||||
public function localToWorldRotation(localRotation:Float):Float {
|
||||
localRotation -= rotation - shearX;
|
||||
var sin:Float = MathUtils.sinDeg(localRotation),
|
||||
cos:Float = MathUtils.cosDeg(localRotation);
|
||||
return Math.atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** Rotates the world transform the specified amount.
|
||||
*
|
||||
* After changes are made to the world transform, updateAppliedTransform() should be called and
|
||||
* update() will need to be called on any child bones, recursively. */
|
||||
public function rotateWorld(degrees:Float):Void {
|
||||
degrees *= MathUtils.degRad;
|
||||
var sin:Float = Math.sin(degrees), cos:Float = Math.cos(degrees);
|
||||
var ra:Float = a, rb:Float = b;
|
||||
a = cos * ra - sin * c;
|
||||
b = cos * rb - sin * d;
|
||||
c = sin * ra + cos * c;
|
||||
d = sin * rb + cos * d;
|
||||
}
|
||||
|
||||
public function toString ():String {
|
||||
return bone.data.name;
|
||||
}
|
||||
}
|
||||
49
spine-haxe/spine-haxe/spine/Constraint.hx
Normal file
49
spine-haxe/spine-haxe/spine/Constraint.hx
Normal file
@ -0,0 +1,49 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
abstract class Constraint< //
|
||||
T:Constraint<T, D, P>, //
|
||||
D:ConstraintData<T, P>, //
|
||||
P:Pose<Any>> //
|
||||
extends PosedActive<D, P, P> implements Update {
|
||||
|
||||
public function new (data:D, pose:P, constrained:P) {
|
||||
super(data, pose, constrained);
|
||||
}
|
||||
|
||||
public abstract function copy (skeleton:Skeleton):T;
|
||||
|
||||
public abstract function sort (skeleton:Skeleton):Void;
|
||||
|
||||
public function isSourceActive ():Bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -30,24 +30,14 @@
|
||||
package spine;
|
||||
|
||||
/** The base class for all constraint datas. */
|
||||
class ConstraintData {
|
||||
/** The constraint's name, which is unique across all constraints in the skeleton of the same type. */
|
||||
public var name:String;
|
||||
/** The ordinal of this constraint for the order a skeleton's constraints will be applied by
|
||||
* spine.Skeleton.updateWorldTransform(). */
|
||||
public var order:Int = 0;
|
||||
/** When true, spine.Skeleton.updateWorldTransform() only updates this constraint if the spine.Skeleton.getSkin()
|
||||
* contains this constraint.
|
||||
* @see spine.Skin.getConstraints() */
|
||||
public var skinRequired:Bool = false;
|
||||
abstract class ConstraintData< //
|
||||
T:Constraint<Dynamic, Dynamic, Dynamic>, //
|
||||
P:Pose<Any>> //
|
||||
extends PosedData<P> {
|
||||
|
||||
function new(name:String, order:Int, skinRequired:Bool) {
|
||||
this.name = name;
|
||||
this.order = order;
|
||||
this.skinRequired = skinRequired;
|
||||
function new(name:String, setup:P) {
|
||||
super(name, setup);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return name;
|
||||
}
|
||||
public abstract function create (skeleton:Skeleton):T;
|
||||
}
|
||||
|
||||
@ -30,38 +30,32 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the current pose values for an Event.
|
||||
*
|
||||
*
|
||||
* @see spine.Timeline
|
||||
* @see spine.Timeline.apply()
|
||||
* @see spine.AnimationStateListener.event()
|
||||
* @see https://esotericsoftware.com/spine-events Events in the Spine User Guide
|
||||
*/
|
||||
class Event {
|
||||
private var _data:EventData;
|
||||
/** The event's setup pose data. */
|
||||
public var data:EventData;
|
||||
|
||||
/** The animation time this event was keyed. */
|
||||
public var time:Float = 0;
|
||||
public var intValue:Int = 0;
|
||||
public var floatValue:Float = 0;
|
||||
public var time = 0.;
|
||||
|
||||
public var intValue = 0;
|
||||
public var floatValue = 0;
|
||||
public var stringValue:String;
|
||||
public var volume:Float = 1;
|
||||
public var balance:Float = 0;
|
||||
public var volume = 1.;
|
||||
public var balance = 0.;
|
||||
|
||||
public function new(time:Float, data:EventData) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (data == null) throw new SpineException("data cannot be null.");
|
||||
this.time = time;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
/** The event's setup pose data. */
|
||||
public var data(get, never):EventData;
|
||||
|
||||
private function get_data():EventData {
|
||||
return _data;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "Event?";
|
||||
return data.name != null ? data.name : "Event?";
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,171 +33,136 @@ package spine;
|
||||
* the last bone is as close to the target bone as possible.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-ik-constraints IK constraints in the Spine User Guide */
|
||||
class IkConstraint implements Updatable {
|
||||
private var _data:IkConstraintData;
|
||||
class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkConstraintPose> {
|
||||
|
||||
/** The 1 or 2 bones that will be modified by this IK constraint. */
|
||||
public final bones:Array<BonePose>;
|
||||
|
||||
/** The bones that will be modified by this IK constraint. */
|
||||
public var bones:Array<Bone>;
|
||||
/** The bone that is the IK target. */
|
||||
public var target:Bone;
|
||||
/** For two bone IK, controls the bend direction of the IK bones, either 1 or -1. */
|
||||
public var bendDirection:Int = 0;
|
||||
/** For one bone IK, when true and the target is too close, the bone is scaled to reach it. */
|
||||
public var compress:Bool = false;
|
||||
/** When true and the target is out of range, the parent bone is scaled to reach it.
|
||||
*
|
||||
* For two bone IK: 1) the child bone's local Y translation is set to 0, 2) stretch is not applied if getSoftness() is
|
||||
* > 0, and 3) if the parent bone has local nonuniform scale, stretch is not applied. */
|
||||
public var stretch:Bool = false;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
|
||||
*
|
||||
* For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. */
|
||||
public var mix:Float = 0;
|
||||
/** For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
|
||||
* will not straighten completely until the target is this far out of range. */
|
||||
public var softness:Float = 0;
|
||||
public var active:Bool = false;
|
||||
public var target(default, set):Bone;
|
||||
|
||||
/** Copy constructor. */
|
||||
public function new(data:IkConstraintData, skeleton:Skeleton) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (skeleton == null)
|
||||
throw new SpineException("skeleton cannot be null.");
|
||||
_data = data;
|
||||
super(data, new IkConstraintPose(), new IkConstraintPose());
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
|
||||
bones = new Array<Bone>();
|
||||
for (boneData in data.bones) {
|
||||
bones.push(skeleton.findBone(boneData.name));
|
||||
}
|
||||
target = skeleton.findBone(data.target.name);
|
||||
|
||||
mix = data.mix;
|
||||
softness = data.softness;
|
||||
bendDirection = data.bendDirection;
|
||||
compress = data.compress;
|
||||
stretch = data.stretch;
|
||||
bones = new Array<BonePose>();
|
||||
for (boneData in data.bones)
|
||||
bones.push(skeleton.bones[boneData.index].constrained);
|
||||
target = skeleton.bones[data.target.index];
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return active;
|
||||
}
|
||||
|
||||
public function setToSetupPose () {
|
||||
var data:IkConstraintData = _data;
|
||||
mix = data.mix;
|
||||
softness = data.softness;
|
||||
bendDirection = data.bendDirection;
|
||||
compress = data.compress;
|
||||
stretch = data.stretch;
|
||||
public function copy (skeleton:Skeleton) {
|
||||
var copy = new IkConstraint(data, skeleton);
|
||||
copy.pose.set(pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
public function update(physics:Physics):Void {
|
||||
if (mix == 0)
|
||||
return;
|
||||
public function update (skeleton:Skeleton, physics:Physics):Void {
|
||||
var p = applied;
|
||||
if (p.mix == 0) return;
|
||||
var target = target.applied;
|
||||
switch (bones.length) {
|
||||
case 1:
|
||||
apply1(bones[0], target.worldX, target.worldY, compress, stretch, _data.uniform, mix);
|
||||
case 2:
|
||||
apply2(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, _data.uniform, softness, mix);
|
||||
case 1: apply1(skeleton, bones[0], target.worldX, target.worldY, p.compress, p.stretch, data.uniform, p.mix);
|
||||
case 2: apply2(skeleton, bones[0], bones[1], target.worldX, target.worldY, p.bendDirection, p.stretch, data.uniform, p.softness, p.mix);
|
||||
}
|
||||
}
|
||||
|
||||
/** The IK constraint's setup pose data. */
|
||||
public var data(get, never):IkConstraintData;
|
||||
|
||||
private function get_data():IkConstraintData {
|
||||
return _data;
|
||||
public function sort (skeleton:Skeleton) {
|
||||
skeleton.sortBone(target);
|
||||
var parent = bones[0].bone;
|
||||
skeleton.sortBone(parent);
|
||||
skeleton._updateCache.push(this);
|
||||
parent.sorted = false;
|
||||
skeleton.sortReset(parent.children);
|
||||
skeleton.constrained(parent);
|
||||
if (bones.length > 1) skeleton.constrained(bones[1].bone);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "IkConstraint?";
|
||||
override public function isSourceActive () {
|
||||
return target.active;
|
||||
}
|
||||
|
||||
public function set_target (target:Bone):Bone {
|
||||
if (target == null) throw new SpineException("target cannot be null.");
|
||||
this.target = target;
|
||||
return target;
|
||||
}
|
||||
|
||||
/** Applies 1 bone IK. The target is specified in the world coordinate system. */
|
||||
static public function apply1(bone:Bone, targetX:Float, targetY:Float, compress:Bool, stretch:Bool, uniform:Bool, alpha:Float):Void {
|
||||
var p:Bone = bone.parent;
|
||||
var pa:Float = p.a, pb:Float = p.b, pc:Float = p.c, pd:Float = p.d;
|
||||
var rotationIK:Float = -bone.ashearX - bone.arotation,
|
||||
tx:Float = 0,
|
||||
ty:Float = 0;
|
||||
static public function apply1(skeleton:Skeleton, bone:BonePose, targetX:Float, targetY:Float, compress:Bool, stretch:Bool,
|
||||
uniform:Bool, mix:Float) {
|
||||
|
||||
if (bone == null) throw new SpineException("bone cannot be null.");
|
||||
bone.modifyLocal(skeleton);
|
||||
var p = bone.bone.parent.applied;
|
||||
var pa = p.a, pb = p.b, pc = p.c, pd = p.d;
|
||||
var rotationIK = -bone.shearX - bone.rotation, tx = 0., ty = 0.;
|
||||
|
||||
function switchDefault() {
|
||||
var x:Float = targetX - p.worldX, y:Float = targetY - p.worldY;
|
||||
var d:Float = pa * pd - pb * pc;
|
||||
var x = targetX - p.worldX, y = targetY - p.worldY;
|
||||
var d = pa * pd - pb * pc;
|
||||
if (Math.abs(d) <= 0.0001) {
|
||||
tx = 0;
|
||||
ty = 0;
|
||||
} else {
|
||||
tx = (x * pd - y * pb) / d - bone.ax;
|
||||
ty = (y * pa - x * pc) / d - bone.ay;
|
||||
tx = (x * pd - y * pb) / d - bone.x;
|
||||
ty = (y * pa - x * pc) / d - bone.y;
|
||||
}
|
||||
}
|
||||
|
||||
switch (bone.inherit) {
|
||||
case Inherit.onlyTranslation:
|
||||
tx = (targetX - bone.worldX) * MathUtils.signum(bone.skeleton.scaleX);
|
||||
ty = (targetY - bone.worldY) * MathUtils.signum(bone.skeleton.scaleY);
|
||||
tx = (targetX - bone.worldX) * MathUtils.signum(skeleton.scaleX);
|
||||
ty = (targetY - bone.worldY) * MathUtils.signum(skeleton.scaleY);
|
||||
case Inherit.noRotationOrReflection:
|
||||
var s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001, pa * pa + pc * pc);
|
||||
var sa:Float = pa / bone.skeleton.scaleX;
|
||||
var sc:Float = pc / bone.skeleton.scaleY;
|
||||
pb = -sc * s * bone.skeleton.scaleX;
|
||||
pd = sa * s * bone.skeleton.scaleY;
|
||||
rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg;
|
||||
var x:Float = targetX - p.worldX, y:Float = targetY - p.worldY;
|
||||
var d:Float = pa * pd - pb * pc;
|
||||
tx = (x * pd - y * pb) / d - bone.ax;
|
||||
ty = (y * pa - x * pc) / d - bone.ay;
|
||||
var sa:Float = pa / skeleton.scaleX;
|
||||
var sc:Float = pc / skeleton.scaleY;
|
||||
pb = -sc * s * skeleton.scaleX;
|
||||
pd = sa * s * skeleton.scaleY;
|
||||
rotationIK += MathUtils.atan2Deg(sc, sa);
|
||||
switchDefault(); // Fall through.
|
||||
default:
|
||||
switchDefault();
|
||||
}
|
||||
|
||||
rotationIK += Math.atan2(ty, tx) * MathUtils.radDeg;
|
||||
if (bone.ascaleX < 0)
|
||||
rotationIK += 180;
|
||||
rotationIK += MathUtils.atan2Deg(ty, tx);
|
||||
if (bone.scaleX < 0) rotationIK += 180;
|
||||
if (rotationIK > 180)
|
||||
rotationIK -= 360;
|
||||
else if (rotationIK < -180)
|
||||
else if (rotationIK < -180) //
|
||||
rotationIK += 360;
|
||||
var sx:Float = bone.ascaleX;
|
||||
var sy:Float = bone.ascaleY;
|
||||
bone.rotation += rotationIK * mix;
|
||||
if (compress || stretch) {
|
||||
switch (bone.inherit) {
|
||||
case Inherit.noScale, Inherit.noScaleOrReflection:
|
||||
tx = targetX - bone.worldX;
|
||||
ty = targetY - bone.worldY;
|
||||
}
|
||||
var b:Float = bone.data.length * sx;
|
||||
var b = bone.bone.data.length * bone.scaleX;
|
||||
if (b > 0.0001) {
|
||||
var dd:Float = tx * tx + ty * ty;
|
||||
var dd = tx * tx + ty * ty;
|
||||
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
|
||||
var s:Float = (Math.sqrt(dd) / b - 1) * alpha + 1;
|
||||
sx *= s;
|
||||
if (uniform) sy *= s;
|
||||
var s = (Math.sqrt(dd) / b - 1) * mix + 1;
|
||||
bone.scaleX *= s;
|
||||
if (uniform) bone.scaleY *= s;
|
||||
}
|
||||
}
|
||||
}
|
||||
bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY);
|
||||
}
|
||||
|
||||
/** Applies 2 bone IK. The target is specified in the world coordinate system.
|
||||
* @param child A direct descendant of the parent bone. */
|
||||
static public function apply2(parent:Bone, child:Bone, targetX:Float, targetY:Float, bendDir:Int, stretch:Bool, uniform:Bool, softness:Float,
|
||||
alpha:Float):Void {
|
||||
static public function apply2(skeleton:Skeleton, parent:BonePose, child:BonePose, targetX:Float, targetY:Float, bendDir:Int,
|
||||
stretch:Bool, uniform:Bool, softness:Float, mix:Float):Void {
|
||||
|
||||
if (parent == null) throw new SpineException("parent cannot be null.");
|
||||
if (child == null) throw new SpineException("child cannot be null.");
|
||||
if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return;
|
||||
var px:Float = parent.ax;
|
||||
var py:Float = parent.ay;
|
||||
var psx:Float = parent.ascaleX;
|
||||
var sx:Float = psx;
|
||||
var psy:Float = parent.ascaleY;
|
||||
var sy:Float = psy;
|
||||
var csx:Float = child.ascaleX;
|
||||
var os1:Int;
|
||||
var os2:Int;
|
||||
var s2:Int;
|
||||
parent.modifyLocal(skeleton);
|
||||
child.modifyLocal(skeleton);
|
||||
var px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
|
||||
var os1 = 0, os2 = 0, s2 = 0;
|
||||
if (psx < 0) {
|
||||
psx = -psx;
|
||||
os1 = 180;
|
||||
@ -213,43 +178,30 @@ class IkConstraint implements Updatable {
|
||||
if (csx < 0) {
|
||||
csx = -csx;
|
||||
os2 = 180;
|
||||
} else {
|
||||
} else
|
||||
os2 = 0;
|
||||
}
|
||||
var cx:Float = child.ax;
|
||||
var cy:Float;
|
||||
var cwx:Float;
|
||||
var cwy:Float;
|
||||
var a:Float = parent.a;
|
||||
var b:Float = parent.b;
|
||||
var c:Float = parent.c;
|
||||
var d:Float = parent.d;
|
||||
var u:Bool = Math.abs(psx - psy) <= 0.0001;
|
||||
var cwx = 0., cwy = 0., a = parent.a, b = parent.b, c = parent.c, d = parent.d;
|
||||
var u = Math.abs(psx - psy) <= 0.0001;
|
||||
if (!u || stretch) {
|
||||
cy = 0;
|
||||
cwx = a * cx + parent.worldX;
|
||||
cwy = c * cx + parent.worldY;
|
||||
child.y = 0;
|
||||
cwx = a * child.x + parent.worldX;
|
||||
cwy = c * child.x + parent.worldY;
|
||||
} else {
|
||||
cy = child.ay;
|
||||
cwx = a * cx + b * cy + parent.worldX;
|
||||
cwy = c * cx + d * cy + parent.worldY;
|
||||
cwx = a * child.x + b * child.y + parent.worldX;
|
||||
cwy = c * child.x + d * child.y + parent.worldY;
|
||||
}
|
||||
var pp:Bone = parent.parent;
|
||||
var pp = parent.bone.parent.applied;
|
||||
a = pp.a;
|
||||
b = pp.b;
|
||||
c = pp.c;
|
||||
d = pp.d;
|
||||
var id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
|
||||
id = Math.abs(id) <= 0.0001 ? 0 : 1 / id;
|
||||
var dx:Float = (x * d - y * b) * id - px,
|
||||
dy:Float = (y * a - x * c) * id - py;
|
||||
var l1:Float = Math.sqrt(dx * dx + dy * dy);
|
||||
var l2:Float = child.data.length * csx;
|
||||
var a1:Float = 0;
|
||||
var a2:Float = 0;
|
||||
var dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
|
||||
var l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1 = 0., a2 = 0.;
|
||||
if (l1 < 0.0001) {
|
||||
apply1(parent, targetX, targetY, false, stretch, false, alpha);
|
||||
child.updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
|
||||
apply1(skeleton, parent, targetX, targetY, false, stretch, false, mix);
|
||||
child.rotation = 0;
|
||||
return;
|
||||
}
|
||||
x = targetX - pp.worldX;
|
||||
@ -279,10 +231,9 @@ class IkConstraint implements Updatable {
|
||||
} else if (cos > 1) {
|
||||
cos = 1;
|
||||
if (stretch) {
|
||||
a = (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
|
||||
sx *= a;
|
||||
if (uniform)
|
||||
sy *= a;
|
||||
a = (Math.sqrt(dd) / (l1 + l2) - 1) * mix + 1;
|
||||
parent.scaleX *= a;
|
||||
if (uniform) parent.scaleY *= a;
|
||||
}
|
||||
}
|
||||
a2 = Math.acos(cos) * bendDir;
|
||||
@ -352,23 +303,18 @@ class IkConstraint implements Updatable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var os:Float = Math.atan2(cy, cx) * s2;
|
||||
var rotation:Float = parent.arotation;
|
||||
a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
|
||||
if (a1 > 180) {
|
||||
var os:Float = Math.atan2(child.y, child.x) * s2;
|
||||
a1 = (a1 - os) * MathUtils.radDeg + os1 - parent.rotation;
|
||||
if (a1 > 180)
|
||||
a1 -= 360;
|
||||
} else if (a1 < -180) {
|
||||
else if (a1 < -180) //
|
||||
a1 += 360;
|
||||
}
|
||||
parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, sy, 0, 0);
|
||||
rotation = child.arotation;
|
||||
a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation;
|
||||
if (a2 > 180) {
|
||||
parent.rotation += a1 * mix;
|
||||
a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - child.rotation;
|
||||
if (a2 > 180)
|
||||
a2 -= 360;
|
||||
} else if (a2 < -180) {
|
||||
else if (a2 < -180) //
|
||||
a2 += 360;
|
||||
}
|
||||
child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
|
||||
child.rotation += a2 * mix;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,33 +30,32 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a spine.IkConstraint.
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-ik-constraints IK constraints in the Spine User Guide */
|
||||
class IkConstraintData extends ConstraintData {
|
||||
class IkConstraintData extends ConstraintData<IkConstraint, IkConstraintPose> {
|
||||
|
||||
/** The bones that are constrained by this IK constraint. */
|
||||
public var bones:Array<BoneData> = new Array<BoneData>();
|
||||
public final bones:Array<BoneData> = new Array<BoneData>();
|
||||
|
||||
/** The bone that is the IK target. */
|
||||
public var target:BoneData;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
|
||||
*
|
||||
* For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. */
|
||||
public var mix:Float = 0;
|
||||
/** For two bone IK, controls the bend direction of the IK bones, either 1 or -1. */
|
||||
public var bendDirection:Int = 0;
|
||||
/** For one bone IK, when true and the target is too close, the bone is scaled to reach it. */
|
||||
public var compress:Bool = false;
|
||||
/** When true and the target is out of range, the parent bone is scaled to reach it.
|
||||
*
|
||||
* For two bone IK: 1) the child bone's local Y translation is set to 0, 2) stretch is not applied if softness is
|
||||
* > 0, and 3) if the parent bone has local nonuniform scale, stretch is not applied. */
|
||||
public var stretch:Bool = false;
|
||||
/** When true and compress or stretch is used, the bone is scaled on both the X and Y axes. */
|
||||
public var uniform:Bool = false;
|
||||
/** For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
|
||||
* will not straighten completely until the target is this far out of range. */
|
||||
public var softness:Float = 0;
|
||||
public var target(default, set):BoneData;
|
||||
|
||||
/** When true and IkConstraintPose.compress or IkConstraintPose.stretch is used, the bone is scaled
|
||||
* on both the X and Y axes. */
|
||||
public var uniform = false;
|
||||
|
||||
|
||||
public function new(name:String) {
|
||||
super(name, 0, false);
|
||||
super(name, new IkConstraintPose());
|
||||
}
|
||||
|
||||
public function create (skeleton:Skeleton):IkConstraint {
|
||||
return new IkConstraint(this, skeleton);
|
||||
}
|
||||
|
||||
public function set_target (target:BoneData) {
|
||||
if (target == null) throw new SpineException("target cannot be null.");
|
||||
this.target = target;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
67
spine-haxe/spine-haxe/spine/IkConstraintPose.hx
Normal file
67
spine-haxe/spine-haxe/spine/IkConstraintPose.hx
Normal file
@ -0,0 +1,67 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores the current pose for an IK constraint. */
|
||||
class IkConstraintPose implements Pose<IkConstraintPose> {
|
||||
|
||||
/** For two bone IK, controls the bend direction of the IK bones, either 1 or -1. */
|
||||
public var bendDirection = 0;
|
||||
|
||||
/** For one bone IK, when true and the target is too close, the bone is scaled to reach it. */
|
||||
public var compress:Bool = false;
|
||||
|
||||
/** When true and the target is out of range, the parent bone is scaled to reach it.
|
||||
*
|
||||
* For two bone IK: 1) the child bone's local Y translation is set to 0, 2) stretch is not applied if softness is
|
||||
* > 0, and 3) if the parent bone has local nonuniform scale, stretch is not applied. */
|
||||
public var stretch:Bool = false;
|
||||
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
|
||||
*
|
||||
* For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. */
|
||||
public var mix = 0.;
|
||||
|
||||
/** For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
|
||||
* will not straighten completely until the target is this far out of range. */
|
||||
public var softness = 0.;
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set (pose:IkConstraintPose) {
|
||||
mix = pose.mix;
|
||||
softness = pose.softness;
|
||||
bendDirection = pose.bendDirection;
|
||||
compress = pose.compress;
|
||||
stretch = pose.stretch;
|
||||
}
|
||||
|
||||
}
|
||||
@ -29,194 +29,140 @@
|
||||
|
||||
package spine;
|
||||
|
||||
import spine.Skeleton;
|
||||
import spine.attachments.Attachment;
|
||||
import spine.attachments.PathAttachment;
|
||||
|
||||
/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
|
||||
* constrained bones so they follow a PathAttachment.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-path-constraints Path constraints in the Spine User Guide */
|
||||
class PathConstraint implements Updatable {
|
||||
private static inline var NONE:Int = -1;
|
||||
private static inline var BEFORE:Int = -2;
|
||||
private static inline var AFTER:Int = -3;
|
||||
private static inline var epsilon:Float = 0.00001;
|
||||
class PathConstraint extends Constraint<PathConstraint, PathConstraintData, PathConstraintPose> {
|
||||
private static inline var NONE = -1;
|
||||
private static inline var BEFORE = -2;
|
||||
private static inline var AFTER = -3;
|
||||
private static inline var epsilon = 0.00001;
|
||||
|
||||
private var _data:PathConstraintData;
|
||||
private var _bones:Array<Bone>;
|
||||
/** The bones that will be modified by this path constraint. */
|
||||
public final bones:Array<BonePose>;
|
||||
|
||||
/** The slot whose path attachment will be used to constrained the bones. */
|
||||
public var target:Slot;
|
||||
/** The position along the path. */
|
||||
public var position:Float = 0;
|
||||
/** The spacing between bones. */
|
||||
public var spacing:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
public var mixX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
public var mixY:Float = 0;
|
||||
public var slot:Slot;
|
||||
|
||||
private var _spaces(default, never):Array<Float> = new Array<Float>();
|
||||
private var _positions(default, never):Array<Float> = new Array<Float>();
|
||||
private var _world(default, never):Array<Float> = new Array<Float>();
|
||||
private var _curves(default, never):Array<Float> = new Array<Float>();
|
||||
private var _lengths(default, never):Array<Float> = new Array<Float>();
|
||||
private var _segments(default, never):Array<Float> = new Array<Float>();
|
||||
private final spaces = new Array<Float>();
|
||||
private final positions = new Array<Float>();
|
||||
private final world = new Array<Float>();
|
||||
private final curves = new Array<Float>();
|
||||
private final lengths = new Array<Float>();
|
||||
private final segments = new Array<Float>();
|
||||
|
||||
public var active:Bool = false;
|
||||
public function new (data:PathConstraintData, skeleton:Skeleton) {
|
||||
super(data, new PathConstraintPose(), new PathConstraintPose());
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
|
||||
public function new(data:PathConstraintData, skeleton:Skeleton) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (skeleton == null)
|
||||
throw new SpineException("skeleton cannot be null.");
|
||||
_data = data;
|
||||
bones = new Array<BonePose>();
|
||||
for (boneData in data.bones)
|
||||
bones.push(skeleton.bones[boneData.index].constrained);
|
||||
|
||||
_bones = new Array<Bone>();
|
||||
for (boneData in data.bones) {
|
||||
_bones.push(skeleton.findBone(boneData.name));
|
||||
}
|
||||
target = skeleton.findSlot(data.target.name);
|
||||
|
||||
position = data.position;
|
||||
spacing = data.spacing;
|
||||
mixRotate = data.mixRotate;
|
||||
mixX = data.mixX;
|
||||
mixY = data.mixY;
|
||||
slot = skeleton.slots[data.slot.index];
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return active;
|
||||
}
|
||||
|
||||
public function setToSetupPose () {
|
||||
var data:PathConstraintData = _data;
|
||||
position = data.position;
|
||||
spacing = data.spacing;
|
||||
mixRotate = data.mixRotate;
|
||||
mixX = data.mixX;
|
||||
mixY = data.mixY;
|
||||
public function copy(skeleton:Skeleton) {
|
||||
var copy = new PathConstraint(data, skeleton);
|
||||
copy.pose.set(pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
public function update(physics:Physics):Void {
|
||||
var attachment:PathAttachment = cast(target.attachment, PathAttachment);
|
||||
if (attachment == null)
|
||||
return;
|
||||
if (mixRotate == 0 && mixX == 0 && mixY == 0)
|
||||
return;
|
||||
public function update(skeleton:Skeleton, physics:Physics):Void {
|
||||
var attachment = slot.applied.attachment;
|
||||
if (!Std.isOfType(attachment, PathAttachment)) return;
|
||||
var pathAttachment = cast(attachment, PathAttachment);
|
||||
|
||||
var data:PathConstraintData = _data;
|
||||
var fTangents:Bool = data.rotateMode == RotateMode.tangent,
|
||||
fScale:Bool = data.rotateMode == RotateMode.chainScale;
|
||||
var boneCount:Int = _bones.length;
|
||||
var spacesCount:Int = fTangents ? boneCount : boneCount + 1;
|
||||
ArrayUtils.resize(_spaces, spacesCount, 0);
|
||||
if (fScale) {
|
||||
ArrayUtils.resize(_lengths, boneCount, 0);
|
||||
}
|
||||
var p = applied;
|
||||
var mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY;
|
||||
if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
|
||||
|
||||
var bones:Array<Bone> = _bones;
|
||||
var data = data;
|
||||
var fTangents = data.rotateMode == RotateMode.tangent, fScale = data.rotateMode == RotateMode.chainScale;
|
||||
var boneCount = bones.length, spacesCount = fTangents ? boneCount : boneCount + 1;
|
||||
ArrayUtils.resize(spaces, spacesCount, 0);
|
||||
if (fScale) ArrayUtils.resize(lengths, boneCount, 0);
|
||||
var spacing = p.spacing;
|
||||
|
||||
var i:Int,
|
||||
n:Int,
|
||||
bone:Bone,
|
||||
setupLength:Float,
|
||||
x:Float,
|
||||
y:Float,
|
||||
length:Float;
|
||||
var bones = bones;
|
||||
switch (data.spacingMode) {
|
||||
case SpacingMode.percent:
|
||||
if (fScale) {
|
||||
n = spacesCount - 1;
|
||||
var n = spacesCount - 1;
|
||||
for (i in 0...n) {
|
||||
bone = bones[i];
|
||||
setupLength = bone.data.length;
|
||||
x = setupLength * bone.a;
|
||||
y = setupLength * bone.c;
|
||||
_lengths[i] = Math.sqrt(x * x + y * y);
|
||||
var bone = bones[i];
|
||||
var setupLength:Float = bone.bone.data.length;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
lengths[i] = Math.sqrt(x * x + y * y);
|
||||
}
|
||||
}
|
||||
for (i in 1...spacesCount) {
|
||||
_spaces[i] = spacing;
|
||||
}
|
||||
for (i in 1...spacesCount) spaces[i] = spacing;
|
||||
case SpacingMode.proportional:
|
||||
var sum:Float = 0;
|
||||
i = 0;
|
||||
n = spacesCount - 1;
|
||||
var sum = 0.;
|
||||
var i = 0, n = spacesCount - 1;
|
||||
while (i < n) {
|
||||
bone = bones[i];
|
||||
setupLength = bone.data.length;
|
||||
var bone = bones[i];
|
||||
var setupLength:Float = bone.bone.data.length;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (fScale)
|
||||
_lengths[i] = 0;
|
||||
_spaces[++i] = spacing;
|
||||
if (fScale) lengths[i] = 0;
|
||||
spaces[++i] = spacing;
|
||||
} else {
|
||||
x = setupLength * bone.a;
|
||||
y = setupLength * bone.c;
|
||||
length = Math.sqrt(x * x + y * y);
|
||||
if (fScale)
|
||||
_lengths[i] = length;
|
||||
_spaces[++i] = length;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length = Math.sqrt(x * x + y * y);
|
||||
if (fScale) lengths[i] = length;
|
||||
spaces[++i] = length;
|
||||
sum += length;
|
||||
}
|
||||
}
|
||||
if (sum > 0) {
|
||||
sum = spacesCount / sum * spacing;
|
||||
for (i in 1...spacesCount) {
|
||||
_spaces[i] *= sum;
|
||||
}
|
||||
for (i in 1...spacesCount)
|
||||
spaces[i] *= sum;
|
||||
}
|
||||
default:
|
||||
var lengthSpacing:Bool = data.spacingMode == SpacingMode.length;
|
||||
i = 0;
|
||||
n = spacesCount - 1;
|
||||
var lengthSpacing = data.spacingMode == SpacingMode.length;
|
||||
var i = 0, n = spacesCount - 1;
|
||||
while (i < n) {
|
||||
bone = bones[i];
|
||||
setupLength = bone.data.length;
|
||||
var bone = bones[i];
|
||||
var setupLength = bone.bone.data.length;
|
||||
if (setupLength < PathConstraint.epsilon) {
|
||||
if (fScale)
|
||||
_lengths[i] = 0;
|
||||
_spaces[++i] = spacing;
|
||||
if (fScale) lengths[i] = 0;
|
||||
spaces[++i] = spacing;
|
||||
} else {
|
||||
x = setupLength * bone.a;
|
||||
y = setupLength * bone.c;
|
||||
length = Math.sqrt(x * x + y * y);
|
||||
if (fScale)
|
||||
_lengths[i] = length;
|
||||
_spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
|
||||
var x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
var length = Math.sqrt(x * x + y * y);
|
||||
if (fScale) lengths[i] = length;
|
||||
spaces[++i] = (lengthSpacing ? Math.max(0, setupLength + spacing) : spacing) * length / setupLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var positions:Array<Float> = computeWorldPositions(attachment, spacesCount, fTangents);
|
||||
var boneX:Float = positions[0];
|
||||
var boneY:Float = positions[1];
|
||||
var offsetRotation:Float = data.offsetRotation;
|
||||
var tip:Bool = false;
|
||||
if (offsetRotation == 0) {
|
||||
var positions = computeWorldPositions(skeleton, pathAttachment, spacesCount, fTangents);
|
||||
var boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
|
||||
var tip = false;
|
||||
if (offsetRotation == 0)
|
||||
tip = data.rotateMode == RotateMode.chain;
|
||||
} else {
|
||||
else {
|
||||
tip = false;
|
||||
var pa:Bone = target.bone;
|
||||
offsetRotation *= pa.a * pa.d - pa.b * pa.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
||||
var bone = slot.bone.applied;
|
||||
offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
var p:Int = 3;
|
||||
var i = 0, ip = 3, u = skeleton._update;
|
||||
while (i < boneCount) {
|
||||
var bone:Bone = bones[i];
|
||||
var bone = bones[i];
|
||||
bone.worldX += (boneX - bone.worldX) * mixX;
|
||||
bone.worldY += (boneY - bone.worldY) * mixY;
|
||||
var x:Float = positions[p];
|
||||
var y:Float = positions[p + 1];
|
||||
var dx:Float = x - boneX;
|
||||
var dy:Float = y - boneY;
|
||||
var x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY;
|
||||
if (fScale) {
|
||||
var length = _lengths[i];
|
||||
if (length != 0) {
|
||||
var s:Float = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
|
||||
var length = lengths[i];
|
||||
if (length >= epsilon) {
|
||||
var s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
@ -224,35 +170,26 @@ class PathConstraint implements Updatable {
|
||||
boneX = x;
|
||||
boneY = y;
|
||||
if (mixRotate > 0) {
|
||||
var a:Float = bone.a,
|
||||
b:Float = bone.b,
|
||||
c:Float = bone.c,
|
||||
d:Float = bone.d,
|
||||
r:Float,
|
||||
cos:Float,
|
||||
sin:Float;
|
||||
if (fTangents) {
|
||||
r = positions[p - 1];
|
||||
} else if (_spaces[i + 1] == 0) {
|
||||
r = positions[p + 2];
|
||||
} else {
|
||||
var a = bone.a, b = bone.b, c = bone.c, d = bone.d, r:Float, cos:Float, sin:Float;
|
||||
if (fTangents)
|
||||
r = positions[ip - 1];
|
||||
else if (spaces[i + 1] < epsilon)
|
||||
r = positions[ip + 2];
|
||||
else
|
||||
r = Math.atan2(dy, dx);
|
||||
}
|
||||
r -= Math.atan2(c, a);
|
||||
if (tip) {
|
||||
cos = Math.cos(r);
|
||||
sin = Math.sin(r);
|
||||
var length:Float = bone.data.length;
|
||||
var length = bone.bone.data.length;
|
||||
boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
|
||||
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
|
||||
} else {
|
||||
} else
|
||||
r += offsetRotation;
|
||||
}
|
||||
if (r > Math.PI) {
|
||||
if (r > Math.PI)
|
||||
r -= (Math.PI * 2);
|
||||
} else if (r < -Math.PI) {
|
||||
else if (r < -Math.PI) //
|
||||
r += (Math.PI * 2);
|
||||
}
|
||||
r *= mixRotate;
|
||||
cos = Math.cos(r);
|
||||
sin = Math.sin(r);
|
||||
@ -261,64 +198,55 @@ class PathConstraint implements Updatable {
|
||||
bone.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
}
|
||||
bone.updateAppliedTransform();
|
||||
|
||||
bone.modifyWorld(u);
|
||||
i++;
|
||||
p += 3;
|
||||
ip += 3;
|
||||
}
|
||||
}
|
||||
|
||||
private function computeWorldPositions(path:PathAttachment, spacesCount:Int, tangents:Bool):Array<Float> {
|
||||
var position:Float = this.position;
|
||||
ArrayUtils.resize(_positions, spacesCount * 3 + 2, 0);
|
||||
var out:Array<Float> = _positions, world:Array<Float>;
|
||||
var closed:Bool = path.closed;
|
||||
var verticesLength:Int = path.worldVerticesLength;
|
||||
var curveCount:Int = Std.int(verticesLength / 6);
|
||||
var prevCurve:Int = NONE;
|
||||
var multiplier:Float, i:Int;
|
||||
private function computeWorldPositions(skeleton:Skeleton, path:PathAttachment, spacesCount:Int, tangents:Bool):Array<Float> {
|
||||
var position = applied.position;
|
||||
ArrayUtils.resize(positions, spacesCount * 3 + 2, 0);
|
||||
var out:Array<Float> = positions, world:Array<Float>;
|
||||
var closed = path.closed;
|
||||
var verticesLength = path.worldVerticesLength, curveCount = Std.int(verticesLength / 6), prevCurve = NONE;
|
||||
|
||||
if (!path.constantSpeed) {
|
||||
var lengths:Array<Float> = path.lengths;
|
||||
var lengths = path.lengths;
|
||||
curveCount -= closed ? 1 : 2;
|
||||
var pathLength:Float = lengths[curveCount];
|
||||
if (data.positionMode == PositionMode.percent)
|
||||
position *= pathLength;
|
||||
var pathLength = lengths[curveCount];
|
||||
|
||||
if (data.positionMode == PositionMode.percent) position *= pathLength;
|
||||
|
||||
var multiplier: Float;
|
||||
switch (data.spacingMode) {
|
||||
case SpacingMode.percent:
|
||||
multiplier = pathLength;
|
||||
case SpacingMode.proportional:
|
||||
multiplier = pathLength / spacesCount;
|
||||
default:
|
||||
multiplier = 1;
|
||||
case SpacingMode.percent: multiplier = pathLength;
|
||||
case SpacingMode.proportional: multiplier = pathLength / spacesCount;
|
||||
default: multiplier = 1;
|
||||
}
|
||||
|
||||
ArrayUtils.resize(_world, 8, 0);
|
||||
world = _world;
|
||||
var i:Int = 0;
|
||||
var o:Int = 0;
|
||||
var curve:Int = 0;
|
||||
ArrayUtils.resize(world, 8, 0);
|
||||
var i = 0, o = 0, curve = 0;
|
||||
while (i < spacesCount) {
|
||||
var space:Float = _spaces[i] * multiplier;
|
||||
var space = spaces[i] * multiplier;
|
||||
position += space;
|
||||
var p:Float = position;
|
||||
var p = position;
|
||||
|
||||
if (closed) {
|
||||
p %= pathLength;
|
||||
if (p < 0)
|
||||
p += pathLength;
|
||||
if (p < 0) p += pathLength;
|
||||
curve = 0;
|
||||
} else if (p < 0) {
|
||||
if (prevCurve != BEFORE) {
|
||||
prevCurve = BEFORE;
|
||||
path.computeWorldVertices(target, 2, 4, world, 0, 2);
|
||||
path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
|
||||
}
|
||||
addBeforePosition(p, world, 0, out, o);
|
||||
continue;
|
||||
} else if (p > pathLength) {
|
||||
if (prevCurve != AFTER) {
|
||||
prevCurve = AFTER;
|
||||
path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
|
||||
path.computeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2);
|
||||
}
|
||||
addAfterPosition(p - pathLength, world, 0, out, o);
|
||||
continue;
|
||||
@ -326,15 +254,15 @@ class PathConstraint implements Updatable {
|
||||
|
||||
// Determine curve containing position.
|
||||
while (true) {
|
||||
var length:Float = lengths[curve];
|
||||
var length = lengths[curve];
|
||||
if (p > length) {
|
||||
curve++;
|
||||
continue;
|
||||
}
|
||||
if (curve == 0) {
|
||||
if (curve == 0)
|
||||
p /= length;
|
||||
} else {
|
||||
var prev:Float = lengths[curve - 1];
|
||||
else {
|
||||
var prev = lengths[curve - 1];
|
||||
p = (p - prev) / (length - prev);
|
||||
}
|
||||
break;
|
||||
@ -342,14 +270,14 @@ class PathConstraint implements Updatable {
|
||||
if (curve != prevCurve) {
|
||||
prevCurve = curve;
|
||||
if (closed && curve == curveCount) {
|
||||
path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
|
||||
path.computeWorldVertices(target, 0, 4, world, 4, 2);
|
||||
path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
|
||||
path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
|
||||
} else {
|
||||
path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
|
||||
path.computeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2);
|
||||
}
|
||||
}
|
||||
addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, tangents || (i > 0 && space == 0));
|
||||
|
||||
addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
|
||||
tangents || (i > 0 && space == 0));
|
||||
i++;
|
||||
o += 3;
|
||||
}
|
||||
@ -359,35 +287,25 @@ class PathConstraint implements Updatable {
|
||||
// World vertices.
|
||||
if (closed) {
|
||||
verticesLength += 2;
|
||||
ArrayUtils.resize(_world, verticesLength, 0);
|
||||
world = _world;
|
||||
path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
|
||||
path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
|
||||
ArrayUtils.resize(world, verticesLength, 0);
|
||||
path.computeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2);
|
||||
path.computeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2);
|
||||
world[verticesLength - 2] = world[0];
|
||||
world[verticesLength - 1] = world[1];
|
||||
} else {
|
||||
curveCount--;
|
||||
verticesLength -= 4;
|
||||
ArrayUtils.resize(_world, verticesLength, 0);
|
||||
world = _world;
|
||||
path.computeWorldVertices(target, 2, verticesLength, world, 0, 2);
|
||||
ArrayUtils.resize(world, verticesLength, 0);
|
||||
path.computeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2);
|
||||
}
|
||||
|
||||
// Curve lengths.
|
||||
ArrayUtils.resize(_curves, curveCount, 0);
|
||||
var curves:Array<Float> = _curves;
|
||||
ArrayUtils.resize(curves, curveCount, 0);
|
||||
var curves:Array<Float> = curves;
|
||||
var pathLength:Float = 0;
|
||||
var x1:Float = world[0],
|
||||
y1:Float = world[1],
|
||||
cx1:Float = 0,
|
||||
cy1:Float = 0,
|
||||
cx2:Float = 0,
|
||||
cy2:Float = 0,
|
||||
x2:Float = 0,
|
||||
y2:Float = 0;
|
||||
var x1 = world[0], y1 = world[1], cx1 = 0., cy1 = 0., cx2 = 0., cy2 = 0., x2 = 0., y2 = 0.;
|
||||
var tmpx:Float, tmpy:Float, dddfx:Float, dddfy:Float, ddfx:Float, ddfy:Float, dfx:Float, dfy:Float;
|
||||
var i:Int = 0;
|
||||
var w:Int = 2;
|
||||
var i = 0, w = 2;
|
||||
while (i < curveCount) {
|
||||
cx1 = world[w];
|
||||
cy1 = world[w + 1];
|
||||
@ -423,33 +341,28 @@ class PathConstraint implements Updatable {
|
||||
w += 6;
|
||||
}
|
||||
|
||||
if (data.positionMode == PositionMode.percent)
|
||||
position *= pathLength;
|
||||
if (data.positionMode == PositionMode.percent) position *= pathLength;
|
||||
|
||||
var multiplier:Float;
|
||||
switch (data.spacingMode) {
|
||||
case SpacingMode.percent:
|
||||
multiplier = pathLength;
|
||||
case SpacingMode.proportional:
|
||||
multiplier = pathLength / spacesCount;
|
||||
default:
|
||||
multiplier = 1;
|
||||
case SpacingMode.percent: multiplier = pathLength;
|
||||
case SpacingMode.proportional: multiplier = pathLength / spacesCount;
|
||||
default: multiplier = 1;
|
||||
}
|
||||
|
||||
var segments:Array<Float> = _segments;
|
||||
var curveLength:Float = 0;
|
||||
var segment:Int;
|
||||
i = 0;
|
||||
var o:Int = 0;
|
||||
var segment:Int = 0;
|
||||
var segments = segments;
|
||||
var curveLength = 0.;
|
||||
var i = 0, o = 0, curve = 0, segment = 0;
|
||||
while (i < spacesCount) {
|
||||
var space = _spaces[i] * multiplier;
|
||||
var space = spaces[i] * multiplier;
|
||||
position += space;
|
||||
var p = position;
|
||||
|
||||
if (closed) {
|
||||
p %= pathLength;
|
||||
if (p < 0)
|
||||
p += pathLength;
|
||||
if (p < 0) p += pathLength;
|
||||
curve = 0;
|
||||
segment = 0;
|
||||
} else if (p < 0) {
|
||||
addBeforePosition(p, world, 0, out, o);
|
||||
i++;
|
||||
@ -528,9 +441,9 @@ class PathConstraint implements Updatable {
|
||||
segment++;
|
||||
continue;
|
||||
}
|
||||
if (segment == 0) {
|
||||
if (segment == 0)
|
||||
p /= length;
|
||||
} else {
|
||||
else {
|
||||
var prev = segments[segment - 1];
|
||||
p = segment + (p - prev) / (length - prev);
|
||||
}
|
||||
@ -596,21 +509,51 @@ class PathConstraint implements Updatable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The bones that will be modified by this path constraint. */
|
||||
public var bones(get, never):Array<Bone>;
|
||||
|
||||
private function get_bones():Array<Bone> {
|
||||
return _bones;
|
||||
public function sort (skeleton:Skeleton) {
|
||||
var slotIndex = slot.data.index;
|
||||
var slotBone = slot.bone;
|
||||
if (skeleton.skin != null) sortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone);
|
||||
if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin)
|
||||
sortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
|
||||
sortPath(skeleton, slot.pose.attachment, slotBone);
|
||||
var boneCount = bones.length;
|
||||
for (i in 0...boneCount) {
|
||||
var bone = bones[i].bone;
|
||||
skeleton.sortBone(bone);
|
||||
skeleton.constrained(bone);
|
||||
}
|
||||
skeleton._updateCache.push(this);
|
||||
for (i in 0...boneCount)
|
||||
skeleton.sortReset(bones[i].bone.children);
|
||||
for (i in 0...boneCount)
|
||||
bones[i].bone.sorted = true;
|
||||
}
|
||||
|
||||
/** The path constraint's setup pose data. */
|
||||
public var data(get, never):PathConstraintData;
|
||||
|
||||
private function get_data():PathConstraintData {
|
||||
return _data;
|
||||
public function sortPathSlot (skeleton:Skeleton, skin:Skin, slotIndex:Int, slotBone:Bone) {
|
||||
var entries = skin.getAttachments();
|
||||
for (entry in entries) {
|
||||
if (entry.slotIndex == slotIndex) sortPath(skeleton, entry.attachment, slotBone);
|
||||
}
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "PathConstraint?";
|
||||
private function sortPath (skeleton:Skeleton, attachment:Attachment, slotBone:Bone) {
|
||||
if (!(Std.isOfType(attachment, PathAttachment))) return;
|
||||
var pathBones = cast(attachment, PathAttachment).bones;
|
||||
if (pathBones == null)
|
||||
skeleton.sortBone(slotBone);
|
||||
else {
|
||||
var bones = skeleton.bones;
|
||||
var i = 0, n = pathBones.length;
|
||||
while (i < n) {
|
||||
var nn = pathBones[i++];
|
||||
nn += i;
|
||||
while (i < nn)
|
||||
skeleton.sortBone(bones[pathBones[i++]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public function isSourceActive (): Bool {
|
||||
return slot.bone.active;
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,41 +30,56 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a spine.PathConstraint.
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-path-constraints Path constraints in the Spine User Guide */
|
||||
class PathConstraintData extends ConstraintData {
|
||||
class PathConstraintData extends ConstraintData<PathConstraint, PathConstraintPose> {
|
||||
/** The bones that will be modified by this path constraint. */
|
||||
private var _bones:Array<BoneData> = new Array<BoneData>();
|
||||
public final bones:Array<BoneData> = new Array<BoneData>();
|
||||
|
||||
/** The slot whose path attachment will be used to constrain the bones. */
|
||||
public var target:SlotData;
|
||||
public var slot(default, set):SlotData;
|
||||
|
||||
/** The mode for positioning the first bone on the path. */
|
||||
public var positionMode:PositionMode = PositionMode.fixed;
|
||||
public var positionMode(default, set):PositionMode = PositionMode.fixed;
|
||||
|
||||
/** The mode for positioning the bones after the first bone on the path. */
|
||||
public var spacingMode:SpacingMode = SpacingMode.fixed;
|
||||
public var spacingMode(default, set):SpacingMode = SpacingMode.fixed;
|
||||
|
||||
/** The mode for adjusting the rotation of the bones. */
|
||||
public var rotateMode:RotateMode = RotateMode.chain;
|
||||
public var rotateMode(default, set):RotateMode = RotateMode.chain;
|
||||
|
||||
/** An offset added to the constrained bone rotation. */
|
||||
public var offsetRotation:Float = 0;
|
||||
/** The position along the path. */
|
||||
public var position:Float = 0;
|
||||
/** The spacing between bones. */
|
||||
public var spacing:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
public var mixX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
public var mixY:Float = 0;
|
||||
|
||||
public function new(name:String) {
|
||||
super(name, 0, false);
|
||||
public function new (name:String) {
|
||||
super(name, new PathConstraintPose());
|
||||
}
|
||||
|
||||
/** The bones that will be modified by this path constraint. */
|
||||
public var bones(get, never):Array<BoneData>;
|
||||
public function create (skeleton:Skeleton) {
|
||||
return new PathConstraint(this, skeleton);
|
||||
}
|
||||
|
||||
private function get_bones():Array<BoneData> {
|
||||
return _bones;
|
||||
public function set_slot (slot:SlotData):SlotData {
|
||||
if (slot == null) throw new SpineException("slot cannot be null.");
|
||||
this.slot = slot;
|
||||
return slot;
|
||||
}
|
||||
|
||||
public function set_positionMode (positionMode:PositionMode):PositionMode {
|
||||
if (positionMode == null) throw new SpineException("positionMode cannot be null.");
|
||||
this.positionMode = positionMode;
|
||||
return positionMode;
|
||||
}
|
||||
|
||||
public function set_spacingMode (spacingMode:SpacingMode):SpacingMode {
|
||||
if (spacingMode == null) throw new SpineException("spacingMode cannot be null.");
|
||||
this.spacingMode = spacingMode;
|
||||
return spacingMode;
|
||||
}
|
||||
|
||||
public function set_rotateMode (rotateMode:RotateMode):RotateMode {
|
||||
if (rotateMode == null) throw new SpineException("rotateMode cannot be null.");
|
||||
this.rotateMode = rotateMode;
|
||||
return rotateMode;
|
||||
}
|
||||
}
|
||||
|
||||
59
spine-haxe/spine-haxe/spine/PathConstraintPose.hx
Normal file
59
spine-haxe/spine-haxe/spine/PathConstraintPose.hx
Normal file
@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
class PathConstraintPose implements Pose<PathConstraintPose> {
|
||||
|
||||
/** The position along the path. */
|
||||
public var position = 0.;
|
||||
|
||||
/** The spacing between bones. */
|
||||
public var spacing = 0.;
|
||||
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate = 0.;
|
||||
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
public var mixX = 0.;
|
||||
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
public var mixY = 0.;
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set(pose:PathConstraintPose) {
|
||||
position = pose.position;
|
||||
spacing = pose.spacing;
|
||||
mixRotate = pose.mixRotate;
|
||||
mixX = pose.mixX;
|
||||
mixY = pose.mixY;
|
||||
}
|
||||
}
|
||||
@ -30,277 +30,72 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-physics-constraints Physics constraints in the Spine User Guide
|
||||
*/
|
||||
class PhysicsConstraint implements Updatable {
|
||||
private var _data:PhysicsConstraintData;
|
||||
private var _bone:Bone = null;
|
||||
class PhysicsConstraint extends Constraint<PhysicsConstraint, PhysicsConstraintData, PhysicsConstraintPose> {
|
||||
|
||||
public var inertia:Float = 0;
|
||||
public var strength:Float = 0;
|
||||
public var damping:Float = 0;
|
||||
public var massInverse:Float = 0;
|
||||
public var wind:Float = 0;
|
||||
public var gravity:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
|
||||
public var mix:Float = 0;
|
||||
/** The bone constrained by this physics constraint. */
|
||||
public var bone:BonePose = null;
|
||||
|
||||
private var _reset:Bool = true;
|
||||
public var _reset = true;
|
||||
|
||||
public var ux:Float = 0;
|
||||
public var uy:Float = 0;
|
||||
public var cx:Float = 0;
|
||||
public var cy:Float = 0;
|
||||
public var tx:Float = 0;
|
||||
public var ty:Float = 0;
|
||||
public var xOffset:Float = 0;
|
||||
public var xVelocity:Float = 0;
|
||||
public var yOffset:Float = 0;
|
||||
public var yVelocity:Float = 0;
|
||||
public var rotateOffset:Float = 0;
|
||||
public var rotateVelocity:Float = 0;
|
||||
public var scaleOffset:Float = 0;
|
||||
public var scaleVelocity:Float = 0;
|
||||
|
||||
public var active:Bool = false;
|
||||
|
||||
private var _skeleton:Skeleton;
|
||||
public var ux = 0.;
|
||||
public var uy = 0.;
|
||||
public var cx = 0.;
|
||||
public var cy = 0.;
|
||||
public var tx = 0.;
|
||||
public var ty = 0.;
|
||||
public var xOffset = 0.;
|
||||
public var xLag = 0.;
|
||||
public var xVelocity = 0.;
|
||||
public var yOffset = 0.;
|
||||
public var yLag = 0.;
|
||||
public var yVelocity = 0.;
|
||||
public var rotateOffset = 0.;
|
||||
public var rotateLag = 0.;
|
||||
public var rotateVelocity = 0.;
|
||||
public var scaleOffset = 0.;
|
||||
public var scaleLag = 0.;
|
||||
public var scaleVelocity = 0.;
|
||||
public var remaining:Float = 0;
|
||||
public var lastTime:Float = 0;
|
||||
|
||||
public function new(data: PhysicsConstraintData, skeleton: Skeleton) {
|
||||
_data = data;
|
||||
_skeleton = skeleton;
|
||||
super(data, new PhysicsConstraintPose(), new PhysicsConstraintPose());
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
|
||||
_bone = skeleton.bones[data.bone.index];
|
||||
|
||||
inertia = data.inertia;
|
||||
strength = data.strength;
|
||||
damping = data.damping;
|
||||
massInverse = data.massInverse;
|
||||
wind = data.wind;
|
||||
gravity = data.gravity;
|
||||
mix = data.mix;
|
||||
bone = skeleton.bones[data.bone.index].constrained;
|
||||
}
|
||||
|
||||
public function reset () {
|
||||
public function copy(skeleton:Skeleton) {
|
||||
var copy = new PhysicsConstraint(data, skeleton);
|
||||
copy.pose.set(pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public function reset (skeleton:Skeleton) {
|
||||
remaining = 0;
|
||||
lastTime = skeleton.time;
|
||||
_reset = true;
|
||||
xOffset = 0;
|
||||
xLag = 0;
|
||||
xVelocity = 0;
|
||||
yOffset = 0;
|
||||
yLag = 0;
|
||||
yVelocity = 0;
|
||||
rotateOffset = 0;
|
||||
rotateLag = 0;
|
||||
rotateVelocity = 0;
|
||||
scaleOffset = 0;
|
||||
scaleLag = 0;
|
||||
scaleVelocity = 0;
|
||||
}
|
||||
|
||||
public function setToSetupPose () {
|
||||
var data:PhysicsConstraintData = _data;
|
||||
inertia = data.inertia;
|
||||
strength = data.strength;
|
||||
damping = data.damping;
|
||||
massInverse = data.massInverse;
|
||||
wind = data.wind;
|
||||
gravity = data.gravity;
|
||||
mix = data.mix;
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return active;
|
||||
}
|
||||
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
public function update(physics:Physics):Void {
|
||||
var mix:Float = this.mix;
|
||||
if (mix == 0) return;
|
||||
|
||||
var x:Bool = _data.x > 0, y:Bool = _data.y > 0,
|
||||
rotateOrShearX:Bool = _data.rotate > 0 || _data.shearX > 0,
|
||||
scaleX:Bool = _data.scaleX > 0;
|
||||
var bone:Bone = _bone;
|
||||
var l:Float = bone.data.length;
|
||||
|
||||
switch (physics) {
|
||||
case Physics.none:
|
||||
return;
|
||||
case Physics.reset, Physics.update:
|
||||
if (physics == Physics.reset) reset();
|
||||
|
||||
var delta:Float = Math.max(skeleton.time - lastTime, 0);
|
||||
remaining += delta;
|
||||
lastTime = _skeleton.time;
|
||||
|
||||
var bx:Float = bone.worldX, by:Float = bone.worldY;
|
||||
if (_reset) {
|
||||
_reset = false;
|
||||
ux = bx;
|
||||
uy = by;
|
||||
} else {
|
||||
var a:Float = remaining,
|
||||
i:Float = inertia,
|
||||
t:Float = _data.step,
|
||||
f:Float = skeleton.data.referenceScale,
|
||||
d:Float = -1;
|
||||
|
||||
var qx:Float = _data.limit * delta,
|
||||
qy:Float = qx * Math.abs(skeleton.scaleY);
|
||||
qx *= Math.abs(skeleton.scaleX);
|
||||
if (x || y) {
|
||||
if (x) {
|
||||
var u:Float = (ux - bx) * i;
|
||||
xOffset += u > qx ? qx : u < -qx ? -qx : u;
|
||||
ux = bx;
|
||||
}
|
||||
if (y) {
|
||||
var u:Float = (uy - by) * i;
|
||||
yOffset += u > qy ? qy : u < -qy ? -qy : u;
|
||||
uy = by;
|
||||
}
|
||||
if (a >= t) {
|
||||
d = Math.pow(damping, 60 * t);
|
||||
var m:Float = massInverse * t,
|
||||
e:Float = strength,
|
||||
w:Float = wind * f * skeleton.scaleX,
|
||||
g:Float = gravity * f * skeleton.scaleY;
|
||||
do {
|
||||
if (x) {
|
||||
xVelocity += (w - xOffset * e) * m;
|
||||
xOffset += xVelocity * t;
|
||||
xVelocity *= d;
|
||||
}
|
||||
if (y) {
|
||||
yVelocity -= (g + yOffset * e) * m;
|
||||
yOffset += yVelocity * t;
|
||||
yVelocity *= d;
|
||||
}
|
||||
a -= t;
|
||||
} while (a >= t);
|
||||
}
|
||||
if (x) bone.worldX += xOffset * mix * data.x;
|
||||
if (y) bone.worldY += yOffset * mix * data.y;
|
||||
}
|
||||
if (rotateOrShearX || scaleX) {
|
||||
var ca:Float = Math.atan2(bone.c, bone.a),
|
||||
c:Float = 0,
|
||||
s:Float = 0,
|
||||
mr:Float = 0;
|
||||
var dx:Float = cx - bone.worldX,
|
||||
dy:Float = cy - bone.worldY;
|
||||
if (dx > qx)
|
||||
dx = qx;
|
||||
else if (dx < -qx) //
|
||||
dx = -qx;
|
||||
if (dy > qy)
|
||||
dy = qy;
|
||||
else if (dy < -qy) //
|
||||
dy = -qy;
|
||||
if (rotateOrShearX) {
|
||||
mr = (_data.rotate + _data.shearX) * mix;
|
||||
var r:Float = Math.atan2(dy + ty, dx + tx) - ca - rotateOffset * mr;
|
||||
rotateOffset += (r - Math.ceil(r * MathUtils.invPI2 - 0.5) * MathUtils.PI2) * i;
|
||||
r = rotateOffset * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
if (scaleX) {
|
||||
r = l * bone.worldScaleX;
|
||||
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
|
||||
}
|
||||
} else {
|
||||
c = Math.cos(ca);
|
||||
s = Math.sin(ca);
|
||||
var r:Float = l * bone.worldScaleX;
|
||||
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
|
||||
}
|
||||
a = remaining;
|
||||
if (a >= t) {
|
||||
if (d == -1) d = Math.pow(damping, 60 * t);
|
||||
var m:Float = massInverse * t,
|
||||
e:Float = strength,
|
||||
w:Float = wind,
|
||||
g:Float = (Bone.yDown ? -gravity : gravity),
|
||||
h:Float = l / f;
|
||||
while (true) {
|
||||
a -= t;
|
||||
if (scaleX) {
|
||||
scaleVelocity += (w * c - g * s - scaleOffset * e) * m;
|
||||
scaleOffset += scaleVelocity * t;
|
||||
scaleVelocity *= d;
|
||||
}
|
||||
if (rotateOrShearX) {
|
||||
rotateVelocity -= ((w * s + g * c) * h + rotateOffset * e) * m;
|
||||
rotateOffset += rotateVelocity * t;
|
||||
rotateVelocity *= d;
|
||||
if (a < t) break;
|
||||
var r:Float = rotateOffset * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
} else if (a < t) //
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
remaining = a;
|
||||
}
|
||||
cx = bone.worldX;
|
||||
cy = bone.worldY;
|
||||
case Physics.pose:
|
||||
if (x) bone.worldX += xOffset * mix * data.x;
|
||||
if (y) bone.worldY += yOffset * mix * data.y;
|
||||
}
|
||||
|
||||
if (rotateOrShearX) {
|
||||
var o:Float = rotateOffset * mix,
|
||||
s:Float = 0,
|
||||
c:Float = 0,
|
||||
a:Float = 0;
|
||||
if (_data.shearX > 0) {
|
||||
var r:Float = 0;
|
||||
if (_data.rotate > 0) {
|
||||
r = o * _data.rotate;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
r += o * _data.shearX;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
} else {
|
||||
o *= _data.rotate;
|
||||
s = Math.sin(o);
|
||||
c = Math.cos(o);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
}
|
||||
if (scaleX) {
|
||||
var s:Float = 1 + scaleOffset * mix * data.scaleX;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
if (physics != Physics.pose) {
|
||||
tx = l * bone.a;
|
||||
ty = l * bone.c;
|
||||
}
|
||||
bone.updateAppliedTransform();
|
||||
}
|
||||
|
||||
/** Translates the physics constraint so next update(Physics) forces are applied as if the bone moved an additional
|
||||
* amount in world space. */
|
||||
public function translate (x:Float, y:Float):Void {
|
||||
public function translate (x:Float, y:Float):Void {
|
||||
ux -= x;
|
||||
uy -= y;
|
||||
cx -= x;
|
||||
@ -315,26 +110,199 @@ class PhysicsConstraint implements Updatable {
|
||||
translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy);
|
||||
}
|
||||
|
||||
/** The bone constrained by this physics constraint. */
|
||||
public var bone(get, never):Bone;
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
public function update(skeleton:Skeleton, physics:Physics):Void {
|
||||
var p = applied;
|
||||
var mix = p.mix;
|
||||
if (mix == 0) return;
|
||||
|
||||
private function get_bone():Bone {
|
||||
if (_bone == null)
|
||||
throw new SpineException("Bone not set.")
|
||||
else return _bone;
|
||||
var x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
|
||||
var l = bone.bone.data.length, t = data.step, z = 0.;
|
||||
|
||||
switch (physics) {
|
||||
case Physics.none:
|
||||
return;
|
||||
case Physics.reset, Physics.update:
|
||||
if (physics == Physics.reset) reset(skeleton);
|
||||
|
||||
var delta = Math.max(skeleton.time - lastTime, 0), aa = remaining;
|
||||
remaining += delta;
|
||||
lastTime = skeleton.time;
|
||||
|
||||
var bx = bone.worldX, by = bone.worldY;
|
||||
if (_reset) {
|
||||
_reset = false;
|
||||
ux = bx;
|
||||
uy = by;
|
||||
} else {
|
||||
var a = remaining, i = p.inertia, f = skeleton.data.referenceScale, d = -1., m = 0., e = 0., qx = data.limit * delta,
|
||||
qy = qx * Math.abs(skeleton.scaleY);
|
||||
qx *= Math.abs(skeleton.scaleX);
|
||||
if (x || y) {
|
||||
if (x) {
|
||||
var u = (ux - bx) * i;
|
||||
xOffset += u > qx ? qx : u < -qx ? -qx : u;
|
||||
ux = bx;
|
||||
}
|
||||
if (y) {
|
||||
var u = (uy - by) * i;
|
||||
yOffset += u > qy ? qy : u < -qy ? -qy : u;
|
||||
uy = by;
|
||||
}
|
||||
if (a >= t) {
|
||||
var xs = xOffset, ys = yOffset;
|
||||
d = Math.pow(p.damping, 60 * t);
|
||||
m = t * p.massInverse;
|
||||
e = p.strength;
|
||||
var w = f * p.wind * skeleton.scaleX, g = f * p.gravity * skeleton.scaleY,
|
||||
ax = w * skeleton.windX + g * skeleton.gravityX, ay = w * skeleton.windY + g * skeleton.gravityY;
|
||||
do {
|
||||
if (x) {
|
||||
xVelocity += (ax - xOffset * e) * m;
|
||||
xOffset += xVelocity * t;
|
||||
xVelocity *= d;
|
||||
}
|
||||
if (y) {
|
||||
yVelocity -= (ay + yOffset * e) * m;
|
||||
yOffset += yVelocity * t;
|
||||
yVelocity *= d;
|
||||
}
|
||||
a -= t;
|
||||
} while (a >= t);
|
||||
xLag = xOffset - xs;
|
||||
yLag = yOffset - ys;
|
||||
}
|
||||
z = Math.max(0, 1 - a / t);
|
||||
if (x) bone.worldX += xOffset * mix * this.data.x;
|
||||
if (y) bone.worldY += yOffset * mix * this.data.y;
|
||||
}
|
||||
if (rotateOrShearX || scaleX) {
|
||||
var ca = Math.atan2(bone.c, bone.a), c = 0., s = 0., mr = 0., dx = cx - bone.worldX, dy = cy - bone.worldY;
|
||||
if (dx > qx)
|
||||
dx = qx;
|
||||
else if (dx < -qx) //
|
||||
dx = -qx;
|
||||
if (dy > qy)
|
||||
dy = qy;
|
||||
else if (dy < -qy) //
|
||||
dy = -qy;
|
||||
a = remaining;
|
||||
if (rotateOrShearX) {
|
||||
mr = (data.rotate + data.shearX) * mix;
|
||||
z = rotateLag * Math.max(0, 1 - aa / t);
|
||||
var r = Math.atan2(dy + ty, dx + tx) - ca - (rotateOffset - z) * mr;
|
||||
rotateOffset += (r - Math.ceil(r * MathUtils.invPI2 - 0.5) * MathUtils.PI2) * i;
|
||||
r = (rotateOffset - z) * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
if (scaleX) {
|
||||
r = l * bone.worldScaleX;
|
||||
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
|
||||
}
|
||||
} else {
|
||||
c = Math.cos(ca);
|
||||
s = Math.sin(ca);
|
||||
var r = l * bone.worldScaleX - scaleLag * Math.max(0, 1 - aa / t);
|
||||
if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
|
||||
}
|
||||
a = remaining;
|
||||
if (a >= t) {
|
||||
if (d == -1) {
|
||||
d = Math.pow(p.damping, 60 * t);
|
||||
m = t * p.massInverse;
|
||||
e = p.strength;
|
||||
}
|
||||
var g = Bone.yDown ? -p.gravity : p.gravity;
|
||||
var rs = rotateOffset, ss = scaleLag, h = l / f,
|
||||
ax = p.wind * skeleton.windX + g * skeleton.gravityX,
|
||||
ay = p.wind * skeleton.windY + g * skeleton.gravityY;
|
||||
while (true) {
|
||||
a -= t;
|
||||
if (scaleX) {
|
||||
scaleVelocity += (ax * c - ay * s - scaleOffset * e) * m;
|
||||
scaleOffset += scaleVelocity * t;
|
||||
scaleVelocity *= d;
|
||||
}
|
||||
if (rotateOrShearX) {
|
||||
rotateVelocity -= ((ax * s + ay * c) * h + rotateOffset * e) * m;
|
||||
rotateOffset += rotateVelocity * t;
|
||||
rotateVelocity *= d;
|
||||
if (a < t) break;
|
||||
var r:Float = rotateOffset * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
} else if (a < t) //
|
||||
break;
|
||||
}
|
||||
rotateLag = rotateOffset - rs;
|
||||
scaleLag = scaleOffset - ss;
|
||||
}
|
||||
z = Math.max(0, 1 - a / t);
|
||||
}
|
||||
remaining = a;
|
||||
}
|
||||
cx = bone.worldX;
|
||||
cy = bone.worldY;
|
||||
case Physics.pose:
|
||||
z = Math.max(0, 1 - remaining / t);
|
||||
if (x) bone.worldX += (xOffset - xLag * z) * mix * data.x;
|
||||
if (y) bone.worldY += (yOffset - yLag * z) * mix * data.y;
|
||||
}
|
||||
|
||||
if (rotateOrShearX) {
|
||||
var o:Float = (rotateOffset - rotateLag * z) * mix,
|
||||
s:Float = 0,
|
||||
c:Float = 0,
|
||||
a:Float = 0;
|
||||
if (data.shearX > 0) {
|
||||
var r:Float = 0;
|
||||
if (data.rotate > 0) {
|
||||
r = o * data.rotate;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
r += o * data.shearX;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
} else {
|
||||
o *= data.rotate;
|
||||
s = Math.sin(o);
|
||||
c = Math.cos(o);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
}
|
||||
if (scaleX) {
|
||||
var s:Float = 1 + (scaleOffset - scaleLag * z) * mix * this.data.scaleX;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
if (physics != Physics.pose) {
|
||||
tx = l * bone.a;
|
||||
ty = l * bone.c;
|
||||
}
|
||||
bone.modifyWorld(skeleton._update);
|
||||
}
|
||||
|
||||
/** The physics constraint's setup pose data. */
|
||||
public var data(get, never):PhysicsConstraintData;
|
||||
|
||||
private function get_data():PhysicsConstraintData {
|
||||
return _data;
|
||||
public function sort (skeleton: Skeleton) {
|
||||
var bone = bone.bone;
|
||||
skeleton.sortBone(bone);
|
||||
skeleton._updateCache.push(this);
|
||||
skeleton.sortReset(bone.children);
|
||||
skeleton.constrained(bone);
|
||||
}
|
||||
|
||||
public var skeleton(get, never):Skeleton;
|
||||
|
||||
private function get_skeleton():Skeleton {
|
||||
return _skeleton;
|
||||
override public function isSourceActive () {
|
||||
return bone.bone.active;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -30,36 +30,42 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a PhysicsConstraint.
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-physics-constraints Physics constraints in the Spine User Guide */
|
||||
class PhysicsConstraintData extends ConstraintData {
|
||||
class PhysicsConstraintData extends ConstraintData<PhysicsConstraint, PhysicsConstraintPose> {
|
||||
|
||||
/** The bone constrained by this physics constraint. */
|
||||
public var bone:BoneData;
|
||||
public var x:Float = 0;
|
||||
public var y:Float = 0;
|
||||
public var rotate:Float = 0;
|
||||
public var scaleX:Float = 0;
|
||||
public var shearX:Float = 0;
|
||||
public var limit:Float = 0;
|
||||
public var step:Float = 0;
|
||||
public var inertia:Float = 0;
|
||||
public var strength:Float = 0;
|
||||
public var damping:Float = 0;
|
||||
public var massInverse:Float = 0;
|
||||
public var wind:Float = 0;
|
||||
public var gravity:Float = 0;
|
||||
|
||||
public var x = 0.;
|
||||
public var y = 0.;
|
||||
public var rotate = 0.;
|
||||
public var scaleX = 0.;
|
||||
public var shearX = 0.;
|
||||
public var limit = 0.;
|
||||
public var step = 0.;
|
||||
public var inertia = 0.;
|
||||
public var strength = 0.;
|
||||
public var damping = 0.;
|
||||
public var massInverse = 0.;
|
||||
public var wind = 0.;
|
||||
public var gravity = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
|
||||
public var mix:Float = 0;
|
||||
public var inertiaGlobal:Bool = false;
|
||||
public var strengthGlobal:Bool = false;
|
||||
public var dampingGlobal:Bool = false;
|
||||
public var massGlobal:Bool = false;
|
||||
public var windGlobal:Bool = false;
|
||||
public var gravityGlobal:Bool = false;
|
||||
public var mixGlobal:Bool = false;
|
||||
|
||||
public var mix = 0.;
|
||||
public var inertiaGlobal = false;
|
||||
public var strengthGlobal = false;
|
||||
public var dampingGlobal = false;
|
||||
public var massGlobal = false;
|
||||
public var windGlobal = false;
|
||||
public var gravityGlobal = false;
|
||||
public var mixGlobal = false;
|
||||
|
||||
public function new(name:String) {
|
||||
super(name, 0, false);
|
||||
super(name, new PhysicsConstraintPose());
|
||||
}
|
||||
|
||||
public function create (skeleton:Skeleton) {
|
||||
return new PhysicsConstraint(this, skeleton);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
56
spine-haxe/spine-haxe/spine/PhysicsConstraintPose.hx
Normal file
56
spine-haxe/spine-haxe/spine/PhysicsConstraintPose.hx
Normal file
@ -0,0 +1,56 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores a pose for a physics constraint. */
|
||||
class PhysicsConstraintPose implements Pose<PhysicsConstraintPose> {
|
||||
|
||||
public var inertia = 0.;
|
||||
public var strength = 0.;
|
||||
public var damping = 0.;
|
||||
public var massInverse = 0.;
|
||||
public var wind = 0.;
|
||||
public var gravity = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
|
||||
public var mix = 0.;
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set (pose:PhysicsConstraintPose) {
|
||||
inertia = pose.inertia;
|
||||
strength = pose.strength;
|
||||
damping = pose.damping;
|
||||
massInverse = pose.massInverse;
|
||||
wind = pose.wind;
|
||||
gravity = pose.gravity;
|
||||
mix = pose.mix;
|
||||
}
|
||||
}
|
||||
34
spine-haxe/spine-haxe/spine/Pose.hx
Normal file
34
spine-haxe/spine-haxe/spine/Pose.hx
Normal file
@ -0,0 +1,34 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
interface Pose<P> {
|
||||
public function set (pose:P):Void;
|
||||
}
|
||||
59
spine-haxe/spine-haxe/spine/Posed.hx
Normal file
59
spine-haxe/spine-haxe/spine/Posed.hx
Normal file
@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
abstract class Posed< //
|
||||
D:PosedData<P>, //
|
||||
P:Pose<Any>, //
|
||||
A:P> {
|
||||
|
||||
/** The constraint's setup pose data. */
|
||||
public final data:D;
|
||||
|
||||
public final pose:P;
|
||||
public final constrained:A;
|
||||
public var applied:A;
|
||||
|
||||
public function new (data:D, pose:P, constrained:A) {
|
||||
if (data == null) throw new SpineException("data cannot be null.");
|
||||
this.data = data;
|
||||
this.pose = pose;
|
||||
this.constrained = constrained;
|
||||
applied = cast pose;
|
||||
}
|
||||
|
||||
public function setupPose ():Void {
|
||||
pose.set(data.setup);
|
||||
}
|
||||
|
||||
public function toString ():String {
|
||||
return data.name;
|
||||
}
|
||||
}
|
||||
55
spine-haxe/spine-haxe/spine/PosedActive.hx
Normal file
55
spine-haxe/spine-haxe/spine/PosedActive.hx
Normal file
@ -0,0 +1,55 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
abstract class PosedActive< //
|
||||
D:PosedData<P>, //
|
||||
P:Pose<Any>, //
|
||||
A:P> //
|
||||
extends Posed<D, P, A> {
|
||||
|
||||
public var active:Bool;
|
||||
|
||||
public function new (data:D, pose:P, constrained:A) {
|
||||
super(data, pose, constrained);
|
||||
setupPose();
|
||||
}
|
||||
|
||||
/** Returns false when this constraint won't be updated by
|
||||
* Skeleton.updateWorldTransform(Physics) because a skin is required and the
|
||||
* Skeleton.getSkin() (active skin) does not contain this item.
|
||||
* @see Skin.getBones()
|
||||
* @see Skin.getConstraints()
|
||||
* @see PosedData.getSkinRequired()
|
||||
* @see Skeleton.updateCache() */
|
||||
public function isActive ():Bool {
|
||||
return active;
|
||||
}
|
||||
}
|
||||
54
spine-haxe/spine-haxe/spine/PosedData.hx
Normal file
54
spine-haxe/spine-haxe/spine/PosedData.hx
Normal file
@ -0,0 +1,54 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** The base class for all constrained datas. */
|
||||
abstract class PosedData<P:Pose<Any>> {
|
||||
/** The constraint's name, which is unique across all constraints in the skeleton of the same type. */
|
||||
public var name:String;
|
||||
|
||||
public final setup:P;
|
||||
|
||||
/** When true, `Skeleton.updateWorldTransform(Physics)` only updates this constraint if the `Skeleton.getSkin()`
|
||||
* contains this constraint.
|
||||
*
|
||||
* See `Skin.getConstraints()`. */
|
||||
public var skinRequired:Bool;
|
||||
|
||||
public function new (name:String, setup:P) {
|
||||
if (name == null) throw new SpineException("name cannot be null.");
|
||||
this.name = name;
|
||||
this.setup = setup;
|
||||
}
|
||||
|
||||
public function toString ():String {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -59,7 +59,7 @@ class Sequence {
|
||||
return copy;
|
||||
}
|
||||
|
||||
public function apply(slot:Slot, attachment:HasTextureRegion) {
|
||||
public function apply(slot:SlotPose, attachment:HasTextureRegion) {
|
||||
var index:Int = slot.sequenceIndex;
|
||||
if (index == -1)
|
||||
index = this.setupIndex;
|
||||
|
||||
@ -43,69 +43,84 @@ import spine.attachments.RegionAttachment;
|
||||
*/
|
||||
class Skeleton {
|
||||
private static var quadTriangles:Array<Int> = [0, 1, 2, 2, 3, 0];
|
||||
private var _data:SkeletonData;
|
||||
|
||||
/** The skeleton's setup pose data. */
|
||||
public final data:SkeletonData;
|
||||
|
||||
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
|
||||
public var bones:Array<Bone>;
|
||||
public final bones:Array<Bone>;
|
||||
|
||||
/** The skeleton's slots. */
|
||||
public var slots:Array<Slot>; // Setup pose draw order.
|
||||
public final slots:Array<Slot>; // Setup pose draw order.
|
||||
|
||||
/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */
|
||||
public var drawOrder:Array<Slot>;
|
||||
/** The skeleton's IK constraints. */
|
||||
public var ikConstraints:Array<IkConstraint>;
|
||||
/** The skeleton's transform constraints. */
|
||||
public var transformConstraints:Array<TransformConstraint>;
|
||||
/** The skeleton's path constraints. */
|
||||
public var pathConstraints:Array<PathConstraint>;
|
||||
/** The skeleton's physics constraints. */
|
||||
public var physicsConstraints:Array<PhysicsConstraint>;
|
||||
|
||||
private var _updateCache:Array<Updatable> = new Array<Updatable>();
|
||||
private var _skin:Skin;
|
||||
/** The skeleton's constraints. */
|
||||
public final constraints:Array<Constraint<Dynamic, Dynamic, Dynamic>>;
|
||||
|
||||
/** The skeleton's physics constraints. */
|
||||
public final physics:Array<PhysicsConstraint>;
|
||||
|
||||
/** The list of bones and constraints, sorted in the order they should be updated, as computed by Skeleton.updateCache(). */
|
||||
public final _updateCache = new Array<Dynamic>();
|
||||
|
||||
private final resetCache = new Array<Posed<Dynamic, Dynamic, Dynamic>> ();
|
||||
|
||||
/** The skeleton's current skin. */
|
||||
public var skin(default, set):Skin = null;
|
||||
|
||||
/** The color to tint all the skeleton's attachments. */
|
||||
public var color:Color = new Color(1, 1, 1, 1);
|
||||
/** Scales the entire skeleton on the X axis.
|
||||
*
|
||||
* Bones that do not inherit scale are still affected by this property. */
|
||||
public var scaleX:Float = 1;
|
||||
|
||||
/** Scales the entire skeleton on the Y axis.
|
||||
*
|
||||
* Bones that do not inherit scale are still affected by this property. */
|
||||
public var scaleY(get, default):Float = 1;
|
||||
function get_scaleY() {
|
||||
return Bone.yDown ? -scaleY : scaleY;
|
||||
}
|
||||
public final color:Color;
|
||||
|
||||
/** Sets the skeleton X position, which is added to the root bone worldX position.
|
||||
*
|
||||
* Bones that do not inherit translation are still affected by this property. */
|
||||
public var x:Float = 0;
|
||||
|
||||
/** Sets the skeleton Y position, which is added to the root bone worldY position.
|
||||
*
|
||||
* Bones that do not inherit translation are still affected by this property. */
|
||||
public var y:Float = 0;
|
||||
|
||||
/** Scales the entire skeleton on the X axis.
|
||||
*
|
||||
* Bones that do not inherit scale are still affected by this property. */
|
||||
public var scaleX:Float = 1;
|
||||
|
||||
/** Scales the entire skeleton on the Y axis.
|
||||
*
|
||||
* Bones that do not inherit scale are still affected by this property. */
|
||||
public var scaleY(get, default):Float = 1;
|
||||
function get_scaleY() {
|
||||
return Bone.yDown ? -scaleY : scaleY;
|
||||
}
|
||||
|
||||
/** Returns the skeleton's time. This is used for time-based manipulations, such as spine.PhysicsConstraint.
|
||||
*
|
||||
* See Skeleton.update(). */
|
||||
public var time:Float = 0;
|
||||
|
||||
public var windX:Float = 1;
|
||||
public var windY:Float = 0;
|
||||
public var gravityX:Float = 0;
|
||||
public var gravityY:Float = 1;
|
||||
|
||||
public var _update = 0;
|
||||
|
||||
/** Creates a new skeleton with the specified skeleton data. */
|
||||
public function new(data:SkeletonData) {
|
||||
if (data == null) {
|
||||
throw new SpineException("data cannot be null.");
|
||||
}
|
||||
_data = data;
|
||||
if (data == null) throw new SpineException("data cannot be null.");
|
||||
this.data = data;
|
||||
|
||||
bones = new Array<Bone>();
|
||||
for (boneData in data.bones) {
|
||||
var bone:Bone;
|
||||
if (boneData.parent == null) {
|
||||
bone = new Bone(boneData, this, null);
|
||||
bone = new Bone(boneData, null);
|
||||
} else {
|
||||
var parent:Bone = bones[boneData.parent.index];
|
||||
bone = new Bone(boneData, this, parent);
|
||||
bone = new Bone(boneData, parent);
|
||||
parent.children.push(bone);
|
||||
}
|
||||
bones.push(bone);
|
||||
@ -114,31 +129,20 @@ class Skeleton {
|
||||
slots = new Array<Slot>();
|
||||
drawOrder = new Array<Slot>();
|
||||
for (slotData in data.slots) {
|
||||
var bone = bones[slotData.boneData.index];
|
||||
var slot:Slot = new Slot(slotData, bone);
|
||||
var slot = new Slot(slotData, this);
|
||||
slots.push(slot);
|
||||
drawOrder.push(slot);
|
||||
}
|
||||
|
||||
ikConstraints = new Array<IkConstraint>();
|
||||
for (ikConstraintData in data.ikConstraints) {
|
||||
ikConstraints.push(new IkConstraint(ikConstraintData, this));
|
||||
physics = new Array<PhysicsConstraint>();
|
||||
constraints = new Array<Constraint<Dynamic, Dynamic, Dynamic>>();
|
||||
for (constraintData in data.constraints) {
|
||||
var constraint = constraintData.create(this);
|
||||
if (Std.isOfType(constraint, PhysicsConstraint)) physics.push(cast(constraint, PhysicsConstraint));
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
transformConstraints = new Array<TransformConstraint>();
|
||||
for (transformConstraintData in data.transformConstraints) {
|
||||
transformConstraints.push(new TransformConstraint(transformConstraintData, this));
|
||||
}
|
||||
|
||||
pathConstraints = new Array<PathConstraint>();
|
||||
for (pathConstraintData in data.pathConstraints) {
|
||||
pathConstraints.push(new PathConstraint(pathConstraintData, this));
|
||||
}
|
||||
|
||||
physicsConstraints = new Array<PhysicsConstraint>();
|
||||
for (physicConstraintData in data.physicsConstraints) {
|
||||
physicsConstraints.push(new PhysicsConstraint(physicConstraintData, this));
|
||||
}
|
||||
color = new Color(1, 1, 1, 1);
|
||||
|
||||
updateCache();
|
||||
}
|
||||
@ -147,14 +151,19 @@ class Skeleton {
|
||||
* constraints, or weighted path attachments are added or removed. */
|
||||
public function updateCache():Void {
|
||||
_updateCache.resize(0);
|
||||
resetCache.resize(0);
|
||||
|
||||
for (slot in slots)
|
||||
slot.applied = slot.pose;
|
||||
|
||||
for (bone in bones) {
|
||||
bone.sorted = bone.data.skinRequired;
|
||||
bone.active = !bone.sorted;
|
||||
bone.applied = cast(bone.pose, BonePose);
|
||||
}
|
||||
|
||||
if (skin != null) {
|
||||
var skinBones:Array<BoneData> = skin.bones;
|
||||
var skinBones = skin.bones;
|
||||
for (i in 0...skin.bones.length) {
|
||||
var bone:Bone = bones[skinBones[i].index];
|
||||
do {
|
||||
@ -165,216 +174,54 @@ class Skeleton {
|
||||
}
|
||||
}
|
||||
|
||||
// IK first, lowest hierarchy depth first.
|
||||
var ikCount:Int = ikConstraints.length;
|
||||
var transformCount:Int = transformConstraints.length;
|
||||
var pathCount:Int = pathConstraints.length;
|
||||
var physicCount:Int = physicsConstraints.length;
|
||||
var constraintCount:Int = ikCount + transformCount + pathCount + physicCount;
|
||||
|
||||
var continueOuter:Bool;
|
||||
for (i in 0...constraintCount) {
|
||||
continueOuter = false;
|
||||
for (ikConstraint in ikConstraints) {
|
||||
if (ikConstraint.data.order == i) {
|
||||
sortIkConstraint(ikConstraint);
|
||||
continueOuter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (continueOuter)
|
||||
continue;
|
||||
for (transformConstraint in transformConstraints) {
|
||||
if (transformConstraint.data.order == i) {
|
||||
sortTransformConstraint(transformConstraint);
|
||||
continueOuter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (continueOuter)
|
||||
continue;
|
||||
for (pathConstraint in pathConstraints) {
|
||||
if (pathConstraint.data.order == i) {
|
||||
sortPathConstraint(pathConstraint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (continueOuter)
|
||||
continue;
|
||||
for (physicConstraint in physicsConstraints) {
|
||||
if (physicConstraint.data.order == i) {
|
||||
sortPhysicsConstraint(physicConstraint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (constraint in constraints)
|
||||
constraint.applied = constraint.pose;
|
||||
for (c in constraints) {
|
||||
var constraint:Constraint<Dynamic, Dynamic, Dynamic> = c;
|
||||
constraint.active = constraint.isSourceActive()
|
||||
&& (!constraint.data.skinRequired || (skin != null && contains(skin.constraints, constraint.data)));
|
||||
if (constraint.active) constraint.sort(this);
|
||||
}
|
||||
|
||||
for (bone in bones) {
|
||||
for (bone in bones)
|
||||
sortBone(bone);
|
||||
|
||||
var updateCache = this._updateCache;
|
||||
var n = updateCache.length;
|
||||
for (i in 0...n) {
|
||||
var updatable = updateCache[i];
|
||||
if (Std.isOfType(updatable, Bone)) {
|
||||
var b:Bone = cast updatable;
|
||||
updateCache[i] = b.applied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function contains(list:Array<ConstraintData>, element:ConstraintData):Bool {
|
||||
private static function contains(list:Array<ConstraintData<Dynamic, Dynamic>>, element:ConstraintData<Dynamic, Dynamic>):Bool {
|
||||
return list.indexOf(element) != -1;
|
||||
}
|
||||
|
||||
private function sortIkConstraint(constraint:IkConstraint):Void {
|
||||
constraint.active = constraint.target.isActive()
|
||||
&& (!constraint.data.skinRequired || (this.skin != null && contains(this.skin.constraints, constraint.data)));
|
||||
if (!constraint.active)
|
||||
return;
|
||||
|
||||
var target:Bone = constraint.target;
|
||||
sortBone(target);
|
||||
|
||||
var constrained:Array<Bone> = constraint.bones;
|
||||
var parent:Bone = constrained[0];
|
||||
sortBone(parent);
|
||||
|
||||
if (constrained.length == 1) {
|
||||
_updateCache.push(constraint);
|
||||
sortReset(parent.children);
|
||||
} else {
|
||||
var child:Bone = constrained[constrained.length - 1];
|
||||
sortBone(child);
|
||||
|
||||
_updateCache.push(constraint);
|
||||
|
||||
sortReset(parent.children);
|
||||
child.sorted = true;
|
||||
}
|
||||
|
||||
_updateCache.push(constraint);
|
||||
|
||||
sortReset(parent.children);
|
||||
constrained[constrained.length - 1].sorted = true;
|
||||
}
|
||||
|
||||
private function sortPathConstraint(constraint:PathConstraint):Void {
|
||||
constraint.active = constraint.target.bone.isActive()
|
||||
&& (!constraint.data.skinRequired || (this.skin != null && contains(this.skin.constraints, constraint.data)));
|
||||
if (!constraint.active)
|
||||
return;
|
||||
|
||||
var slot:Slot = constraint.target;
|
||||
var slotIndex:Int = slot.data.index;
|
||||
var slotBone:Bone = slot.bone;
|
||||
if (skin != null)
|
||||
sortPathConstraintAttachment(skin, slotIndex, slotBone);
|
||||
if (data.defaultSkin != null && data.defaultSkin != skin) {
|
||||
sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
|
||||
}
|
||||
for (i in 0...data.skins.length) {
|
||||
sortPathConstraintAttachment(data.skins[i], slotIndex, slotBone);
|
||||
}
|
||||
|
||||
var attachment:Attachment = slot.attachment;
|
||||
if (Std.isOfType(attachment, PathAttachment))
|
||||
sortPathConstraintAttachment2(attachment, slotBone);
|
||||
|
||||
var constrainedBones:Array<Bone> = constraint.bones;
|
||||
for (bone in constrainedBones) {
|
||||
sortBone(bone);
|
||||
}
|
||||
|
||||
_updateCache.push(constraint);
|
||||
|
||||
for (bone in constrainedBones) {
|
||||
sortReset(bone.children);
|
||||
}
|
||||
for (bone in constrainedBones) {
|
||||
bone.sorted = true;
|
||||
public function constrained (object:Posed<Dynamic, Dynamic, Dynamic>) {
|
||||
if (object.pose == object.applied) {
|
||||
object.applied = object.constrained;
|
||||
resetCache.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
private function sortTransformConstraint(constraint:TransformConstraint):Void {
|
||||
constraint.active = constraint.target.isActive()
|
||||
&& (!constraint.data.skinRequired || (this.skin != null && contains(this.skin.constraints, constraint.data)));
|
||||
if (!constraint.active)
|
||||
return;
|
||||
|
||||
sortBone(constraint.target);
|
||||
|
||||
var constrainedBones:Array<Bone> = constraint.bones;
|
||||
if (constraint.data.local) {
|
||||
for (bone in constrainedBones) {
|
||||
sortBone(bone.parent);
|
||||
sortBone(bone);
|
||||
}
|
||||
} else {
|
||||
for (bone in constrainedBones) {
|
||||
sortBone(bone);
|
||||
}
|
||||
}
|
||||
|
||||
_updateCache.push(constraint);
|
||||
for (bone in constrainedBones) {
|
||||
sortReset(bone.children);
|
||||
}
|
||||
for (bone in constrainedBones) {
|
||||
bone.sorted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function sortPathConstraintAttachment(skin:Skin, slotIndex:Int, slotBone:Bone):Void {
|
||||
var dict:StringMap<Attachment> = skin.attachments[slotIndex];
|
||||
if (dict != null) {
|
||||
for (attachment in dict.keyValueIterator()) {
|
||||
sortPathConstraintAttachment2(attachment.value, slotBone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sortPathConstraintAttachment2(attachment:Attachment, slotBone:Bone):Void {
|
||||
var pathAttachment:PathAttachment = cast(attachment, PathAttachment);
|
||||
if (pathAttachment == null)
|
||||
return;
|
||||
var pathBones:Array<Int> = pathAttachment.bones;
|
||||
if (pathBones == null) {
|
||||
sortBone(slotBone);
|
||||
} else {
|
||||
var i:Int = 0;
|
||||
var n:Int = pathBones.length;
|
||||
while (i < n) {
|
||||
var nn:Int = pathBones[i++];
|
||||
nn += i;
|
||||
while (i < nn) {
|
||||
sortBone(bones[pathBones[i++]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sortPhysicsConstraint (constraint: PhysicsConstraint) {
|
||||
var bone:Bone = constraint.bone;
|
||||
constraint.active = bone.active && (!constraint.data.skinRequired || (skin != null && contains(skin.constraints, constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
sortBone(bone);
|
||||
|
||||
_updateCache.push(constraint);
|
||||
|
||||
sortReset(bone.children);
|
||||
bone.sorted = true;
|
||||
}
|
||||
|
||||
private function sortBone(bone:Bone):Void {
|
||||
if (bone.sorted)
|
||||
return;
|
||||
var parent:Bone = bone.parent;
|
||||
if (parent != null)
|
||||
sortBone(parent);
|
||||
public function sortBone(bone:Bone):Void {
|
||||
if (bone.sorted || !bone.active) return;
|
||||
var parent = bone.parent;
|
||||
if (parent != null) sortBone(parent);
|
||||
bone.sorted = true;
|
||||
_updateCache.push(bone);
|
||||
}
|
||||
|
||||
private function sortReset(bones:Array<Bone>):Void {
|
||||
public function sortReset(bones:Array<Bone>):Void {
|
||||
for (bone in bones) {
|
||||
if (!bone.active)
|
||||
continue;
|
||||
if (bone.sorted)
|
||||
sortReset(bone.children);
|
||||
bone.sorted = false;
|
||||
if (bone.active) {
|
||||
if (bone.sorted) sortReset(bone.children);
|
||||
bone.sorted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,124 +230,58 @@ class Skeleton {
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
*/
|
||||
public function updateWorldTransform(physics:Physics):Void {
|
||||
if (physics == null) throw new SpineException("physics is undefined");
|
||||
for (bone in bones) {
|
||||
bone.ax = bone.x;
|
||||
bone.ay = bone.y;
|
||||
bone.arotation = bone.rotation;
|
||||
bone.ascaleX = bone.scaleX;
|
||||
bone.ascaleY = bone.scaleY;
|
||||
bone.ashearX = bone.shearX;
|
||||
bone.ashearY = bone.shearY;
|
||||
}
|
||||
_update++;
|
||||
|
||||
for (updatable in _updateCache) {
|
||||
updatable.update(physics);
|
||||
}
|
||||
}
|
||||
for (object in resetCache)
|
||||
object.applied.set(object.pose);
|
||||
|
||||
/** Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
|
||||
* all constraints.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
*/
|
||||
public function updateWorldTransformWith(physics:Physics, parent:Bone):Void {
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
var rootBone:Bone = rootBone;
|
||||
var pa:Float = parent.a,
|
||||
pb:Float = parent.b,
|
||||
pc:Float = parent.c,
|
||||
pd:Float = parent.d;
|
||||
rootBone.worldX = pa * x + pb * y + parent.worldX;
|
||||
rootBone.worldY = pc * x + pd * y + parent.worldY;
|
||||
|
||||
var rx:Float = (rootBone.rotation + rootBone.shearX) * MathUtils.degRad;
|
||||
var ry:Float = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.degRad;
|
||||
var la:Float = Math.cos(rx) * rootBone.scaleX;
|
||||
var lb:Float = Math.cos(ry) * rootBone.scaleY;
|
||||
var lc:Float = Math.sin(rx) * rootBone.scaleX;
|
||||
var ld:Float = Math.sin(ry) * rootBone.scaleY;
|
||||
rootBone.a = (pa * la + pb * lc) * scaleX;
|
||||
rootBone.b = (pa * lb + pb * ld) * scaleX;
|
||||
rootBone.c = (pc * la + pd * lc) * scaleY;
|
||||
rootBone.d = (pc * lb + pd * ld) * scaleY;
|
||||
|
||||
// Update everything except root bone.
|
||||
for (updatable in _updateCache) {
|
||||
if (updatable != rootBone)
|
||||
updatable.update(physics);
|
||||
}
|
||||
for (updatable in _updateCache)
|
||||
updatable.update(this, physics);
|
||||
}
|
||||
|
||||
/** Sets the bones, constraints, slots, and draw order to their setup pose values. */
|
||||
public function setToSetupPose():Void {
|
||||
setBonesToSetupPose();
|
||||
setSlotsToSetupPose();
|
||||
public function setupPose():Void {
|
||||
setupPoseBones();
|
||||
setupPoseSlots();
|
||||
}
|
||||
|
||||
/** Sets the bones and constraints to their setup pose values. */
|
||||
public function setBonesToSetupPose():Void {
|
||||
for (bone in this.bones) bone.setToSetupPose();
|
||||
for (constraint in this.ikConstraints) constraint.setToSetupPose();
|
||||
for (constraint in this.transformConstraints) constraint.setToSetupPose();
|
||||
for (constraint in this.pathConstraints) constraint.setToSetupPose();
|
||||
for (constraint in this.physicsConstraints) constraint.setToSetupPose();
|
||||
public function setupPoseBones():Void {
|
||||
for (bone in this.bones) bone.setupPose();
|
||||
for (constraint in this.constraints) constraint.setupPose();
|
||||
}
|
||||
|
||||
/** Sets the slots and draw order to their setup pose values. */
|
||||
public function setSlotsToSetupPose():Void {
|
||||
public function setupPoseSlots():Void {
|
||||
var i:Int = 0;
|
||||
for (slot in slots) {
|
||||
drawOrder[i++] = slot;
|
||||
slot.setToSetupPose();
|
||||
slot.setupPose();
|
||||
}
|
||||
}
|
||||
|
||||
/** The skeleton's setup pose data. */
|
||||
public var data(get, never):SkeletonData;
|
||||
|
||||
private function get_data():SkeletonData {
|
||||
return _data;
|
||||
}
|
||||
|
||||
/** The list of bones and constraints, sorted in the order they should be updated, as computed by Skeleton.updateCache(). */
|
||||
public var getUpdateCache(get, never):Array<Updatable>;
|
||||
|
||||
private function get_getUpdateCache():Array<Updatable> {
|
||||
return _updateCache;
|
||||
}
|
||||
|
||||
/** Returns the root bone, or null if the skeleton has no bones. */
|
||||
public var rootBone(get, never):Bone;
|
||||
|
||||
private function get_rootBone():Bone {
|
||||
if (bones.length == 0)
|
||||
return null;
|
||||
return bones[0];
|
||||
return bones.length == 0 ? null : bones[0];
|
||||
}
|
||||
|
||||
/** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
|
||||
* repeatedly. */
|
||||
public function findBone(boneName:String):Bone {
|
||||
if (boneName == null) {
|
||||
throw new SpineException("boneName cannot be null.");
|
||||
}
|
||||
for (bone in bones) {
|
||||
if (bone.data.name == boneName)
|
||||
return bone;
|
||||
}
|
||||
if (boneName == null) throw new SpineException("boneName cannot be null.");
|
||||
for (bone in bones)
|
||||
if (bone.data.name == boneName) return bone;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return -1 if the bone was not found. */
|
||||
public function findBoneIndex(boneName:String):Int {
|
||||
if (boneName == null) {
|
||||
throw new SpineException("boneName cannot be null.");
|
||||
}
|
||||
if (boneName == null) throw new SpineException("boneName cannot be null.");
|
||||
var i:Int = 0;
|
||||
for (bone in bones) {
|
||||
if (bone.data.name == boneName)
|
||||
return i;
|
||||
if (bone.data.name == boneName) return i;
|
||||
i++;
|
||||
}
|
||||
return -1;
|
||||
@ -509,13 +290,9 @@ class Skeleton {
|
||||
/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
|
||||
* repeatedly. */
|
||||
public function findSlot(slotName:String):Slot {
|
||||
if (slotName == null) {
|
||||
throw new SpineException("slotName cannot be null.");
|
||||
}
|
||||
for (slot in slots) {
|
||||
if (slot.data.name == slotName)
|
||||
return slot;
|
||||
}
|
||||
if (slotName == null) throw new SpineException("slotName cannot be null.");
|
||||
for (slot in slots)
|
||||
if (slot.data.name == slotName) return slot;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -535,14 +312,7 @@ class Skeleton {
|
||||
|
||||
/** @return May be null. */
|
||||
private function get_skinName():String {
|
||||
return _skin == null ? null : _skin.name;
|
||||
}
|
||||
|
||||
/** The skeleton's current skin. */
|
||||
public var skin(get, set):Skin;
|
||||
|
||||
private function get_skin():Skin {
|
||||
return _skin;
|
||||
return skin == null ? null : skin.name;
|
||||
}
|
||||
|
||||
/** Sets the skin used to look up attachments before looking in the spine.SkeletonData default skin. If the
|
||||
@ -556,7 +326,7 @@ class Skeleton {
|
||||
* skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new
|
||||
* skin. */
|
||||
private function set_skin(newSkin:Skin):Skin {
|
||||
if (newSkin == _skin)
|
||||
if (newSkin == skin)
|
||||
return null;
|
||||
if (newSkin != null) {
|
||||
if (skin != null) {
|
||||
@ -567,16 +337,15 @@ class Skeleton {
|
||||
var name:String = slot.data.attachmentName;
|
||||
if (name != null) {
|
||||
var attachment:Attachment = newSkin.getAttachment(i, name);
|
||||
if (attachment != null)
|
||||
slot.attachment = attachment;
|
||||
if (attachment != null) slot.pose.attachment = attachment;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
_skin = newSkin;
|
||||
skin = newSkin;
|
||||
updateCache();
|
||||
return _skin;
|
||||
return skin;
|
||||
}
|
||||
|
||||
/** Finds an attachment by looking in the Skeleton.skin and spine.SkeletonData defaultSkin using the slot name and attachment
|
||||
@ -621,7 +390,7 @@ class Skeleton {
|
||||
throw new SpineException("Attachment not found: " + attachmentName + ", for slot: " + slotName);
|
||||
}
|
||||
}
|
||||
slot.attachment = attachment;
|
||||
slot.pose.attachment = attachment;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
@ -629,110 +398,66 @@ class Skeleton {
|
||||
throw new SpineException("Slot not found: " + slotName);
|
||||
}
|
||||
|
||||
/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
|
||||
* than to call it repeatedly. */
|
||||
public function findIkConstraint(constraintName:String):IkConstraint {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (ikConstraint in ikConstraints) {
|
||||
if (ikConstraint.data.name == constraintName)
|
||||
return ikConstraint;
|
||||
}
|
||||
public function findConstraint<T:Constraint<Dynamic, Dynamic, Dynamic>>(constraintName:String, type:Class<T>):Null<T> {
|
||||
if (constraintName == null) throw new SpineException("constraintName cannot be null.");
|
||||
if (type == null) throw new SpineException("type cannot be null.");
|
||||
for (constraint in constraints)
|
||||
if (Std.isOfType(constraint, type) && constraint.data.name == constraintName) return Std.downcast(constraint, type);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
|
||||
* this method than to call it repeatedly. */
|
||||
public function findTransformConstraint(constraintName:String):TransformConstraint {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (transformConstraint in transformConstraints) {
|
||||
if (transformConstraint.data.name == constraintName)
|
||||
return transformConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
|
||||
* than to call it repeatedly. */
|
||||
public function findPathConstraint(constraintName:String):PathConstraint {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (pathConstraint in pathConstraints) {
|
||||
if (pathConstraint.data.name == constraintName)
|
||||
return pathConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
|
||||
* method than to call it repeatedly. */
|
||||
public function findPhysicsConstraint(constraintName:String):PhysicsConstraint {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (physicsConstraint in physicsConstraints) {
|
||||
if (physicsConstraint.data.name == constraintName)
|
||||
return physicsConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "Skeleton?";
|
||||
}
|
||||
|
||||
private var _tempVertices = new Array<Float>();
|
||||
private var _bounds = new Rectangle();
|
||||
|
||||
/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. Optionally applies
|
||||
* clipping. */
|
||||
public function getBounds(clipper: SkeletonClipping = null):Rectangle {
|
||||
var minX:Float = Math.POSITIVE_INFINITY;
|
||||
var minY:Float = Math.POSITIVE_INFINITY;
|
||||
var maxX:Float = Math.NEGATIVE_INFINITY;
|
||||
var maxY:Float = Math.NEGATIVE_INFINITY;
|
||||
var minX = Math.POSITIVE_INFINITY;
|
||||
var minY = Math.POSITIVE_INFINITY;
|
||||
var maxX = Math.NEGATIVE_INFINITY;
|
||||
var maxY = Math.NEGATIVE_INFINITY;
|
||||
for (slot in drawOrder) {
|
||||
var verticesLength:Int = 0;
|
||||
var vertices:Array<Float> = null;
|
||||
var triangles:Array<Int> = null;
|
||||
var attachment:Attachment = slot.attachment;
|
||||
|
||||
if (Std.isOfType(attachment, RegionAttachment)) {
|
||||
verticesLength = 8;
|
||||
_tempVertices.resize(verticesLength);
|
||||
vertices = _tempVertices;
|
||||
cast(attachment, RegionAttachment).computeWorldVertices(slot, vertices, 0, 2);
|
||||
triangles = Skeleton.quadTriangles;
|
||||
} else if (Std.isOfType(attachment, MeshAttachment)) {
|
||||
var mesh:MeshAttachment = cast(attachment, MeshAttachment);
|
||||
verticesLength = mesh.worldVerticesLength;
|
||||
_tempVertices.resize(verticesLength);
|
||||
vertices = _tempVertices;
|
||||
mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2);
|
||||
triangles = mesh.triangles;
|
||||
} else if (Std.isOfType(attachment, ClippingAttachment) && clipper != null) {
|
||||
clipper.clipStart(slot, cast(attachment, ClippingAttachment));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vertices != null) {
|
||||
if (clipper != null && clipper.isClipping()) {
|
||||
clipper.clipTriangles(vertices, triangles, triangles.length);
|
||||
vertices = clipper.clippedVertices;
|
||||
verticesLength = clipper.clippedVertices.length;
|
||||
var attachment:Attachment = slot.pose.attachment;
|
||||
if (attachment != null) {
|
||||
if (Std.isOfType(attachment, RegionAttachment)) {
|
||||
verticesLength = 8;
|
||||
_tempVertices.resize(verticesLength);
|
||||
vertices = _tempVertices;
|
||||
cast(attachment, RegionAttachment).computeWorldVertices(slot, vertices, 0, 2);
|
||||
triangles = Skeleton.quadTriangles;
|
||||
} else if (Std.isOfType(attachment, MeshAttachment)) {
|
||||
var mesh:MeshAttachment = cast(attachment, MeshAttachment);
|
||||
verticesLength = mesh.worldVerticesLength;
|
||||
_tempVertices.resize(verticesLength);
|
||||
vertices = _tempVertices;
|
||||
mesh.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2);
|
||||
triangles = mesh.triangles;
|
||||
} else if (Std.isOfType(attachment, ClippingAttachment) && clipper != null) {
|
||||
clipper.clipEnd(slot);
|
||||
clipper.clipStart(this, slot, cast(attachment, ClippingAttachment));
|
||||
continue;
|
||||
}
|
||||
var ii:Int = 0;
|
||||
var nn:Int = vertices.length;
|
||||
while (ii < nn) {
|
||||
var x:Float = vertices[ii], y:Float = vertices[ii + 1];
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
ii += 2;
|
||||
if (vertices != null) {
|
||||
if (clipper != null && clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) {
|
||||
vertices = clipper.clippedVertices;
|
||||
verticesLength = clipper.clippedVertices.length;
|
||||
}
|
||||
var ii:Int = 0;
|
||||
var nn:Int = vertices.length;
|
||||
while (ii < nn) {
|
||||
var x:Float = vertices[ii], y:Float = vertices[ii + 1];
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
ii += 2;
|
||||
}
|
||||
}
|
||||
if (clipper != null) clipper.clipEnd(slot);
|
||||
}
|
||||
if (clipper != null) clipper.clipEndWithSlot(slot);
|
||||
}
|
||||
if (clipper != null) clipper.clipEnd();
|
||||
_bounds.x = minX;
|
||||
@ -749,13 +474,17 @@ class Skeleton {
|
||||
|
||||
/** Calls spine.PhysicsConstraint.translate() for each physics constraint. */
|
||||
public function physicsTranslate (x:Float, y:Float):Void {
|
||||
for (physicsConstraint in physicsConstraints)
|
||||
for (physicsConstraint in physics)
|
||||
physicsConstraint.translate(x, y);
|
||||
}
|
||||
|
||||
/** Calls spine.PhysicsConstraint.rotate() for each physics constraint. */
|
||||
public function physicsRotate (x:Float, y:Float, degrees:Float):Void {
|
||||
for (physicsConstraint in physicsConstraints)
|
||||
for (physicsConstraint in physics)
|
||||
physicsConstraint.rotate(x, y, degrees);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return data.name != null ? data.name : "Skeleton?";
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,27 +32,29 @@ package spine;
|
||||
import spine.attachments.ClippingAttachment;
|
||||
|
||||
class SkeletonClipping {
|
||||
private var triangulator:Triangulator = new Triangulator();
|
||||
private var clippingPolygon:Array<Float> = new Array<Float>();
|
||||
private var clipOutput:Array<Float> = new Array<Float>();
|
||||
private var triangulator = new Triangulator();
|
||||
private var clippingPolygon = new Array<Float>();
|
||||
private var clipOutput = new Array<Float>();
|
||||
|
||||
public var clippedVertices:Array<Float> = new Array<Float>();
|
||||
public var clippedUvs:Array<Float> = new Array<Float>();
|
||||
public var clippedTriangles:Array<Int> = new Array<Int>();
|
||||
public var clippedVertices = new Array<Float>();
|
||||
public var clippedUvs = new Array<Float>();
|
||||
public var clippedTriangles = new Array<Int>();
|
||||
|
||||
private var scratch:Array<Float> = new Array<Float>();
|
||||
private var scratch = new Array<Float>();
|
||||
|
||||
private var clipAttachment:ClippingAttachment;
|
||||
private var clippingPolygons:Array<Array<Float>>;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function clipStart(slot:Slot, clip:ClippingAttachment):Int {
|
||||
if (clipAttachment != null)
|
||||
return 0;
|
||||
public function clipStart(skeleton:Skeleton, slot:Slot, clip:ClippingAttachment):Int {
|
||||
if (clipAttachment != null) return 0;
|
||||
var n = clip.worldVerticesLength;
|
||||
if (n < 6) return 0;
|
||||
clipAttachment = clip;
|
||||
clippingPolygon.resize(clip.worldVerticesLength);
|
||||
clip.computeWorldVertices(slot, 0, clippingPolygon.length, clippingPolygon, 0, 2);
|
||||
|
||||
clippingPolygon.resize(n);
|
||||
clip.computeWorldVertices(skeleton, slot, 0, n, clippingPolygon, 0, 2);
|
||||
SkeletonClipping.makeClockwise(clippingPolygon);
|
||||
clippingPolygons = triangulator.decompose(clippingPolygon, triangulator.triangulate(clippingPolygon));
|
||||
for (polygon in clippingPolygons) {
|
||||
@ -63,14 +65,8 @@ class SkeletonClipping {
|
||||
return clippingPolygons.length;
|
||||
}
|
||||
|
||||
public function clipEndWithSlot(slot:Slot):Void {
|
||||
if (clipAttachment != null && clipAttachment.endSlot == slot.data)
|
||||
clipEnd();
|
||||
}
|
||||
|
||||
public function clipEnd():Void {
|
||||
if (clipAttachment == null)
|
||||
return;
|
||||
public function clipEnd(?slot:Slot):Void {
|
||||
if (clipAttachment == null || (slot != null && clipAttachment.endSlot != slot.data)) return;
|
||||
clipAttachment = null;
|
||||
clippingPolygons = null;
|
||||
clippedVertices.resize(0);
|
||||
@ -84,36 +80,34 @@ class SkeletonClipping {
|
||||
return clipAttachment != null;
|
||||
}
|
||||
|
||||
private function clipTrianglesNoRender(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float):Void {
|
||||
private function clipTrianglesNoRender(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float):Bool {
|
||||
var polygonsCount:Int = clippingPolygons.length;
|
||||
var index:Int = 0;
|
||||
clippedVertices.resize(0);
|
||||
clippedTriangles.resize(0);
|
||||
var i:Int = 0;
|
||||
var clipOutputItems:Array<Float> = null;
|
||||
while (i < trianglesLength) {
|
||||
var vertexOffset:Int = triangles[i] << 1;
|
||||
var x1:Float = vertices[vertexOffset],
|
||||
y1:Float = vertices[vertexOffset + 1];
|
||||
var v:Int = triangles[i] << 1;
|
||||
var x1:Float = vertices[v], y1:Float = vertices[v + 1];
|
||||
|
||||
vertexOffset = triangles[i + 1] << 1;
|
||||
var x2:Float = vertices[vertexOffset],
|
||||
y2:Float = vertices[vertexOffset + 1];
|
||||
v = triangles[i + 1] << 1;
|
||||
var x2:Float = vertices[v], y2:Float = vertices[v + 1];
|
||||
|
||||
vertexOffset = triangles[i + 2] << 1;
|
||||
var x3:Float = vertices[vertexOffset],
|
||||
y3:Float = vertices[vertexOffset + 1];
|
||||
v = triangles[i + 2] << 1;
|
||||
var x3:Float = vertices[v], y3:Float = vertices[v + 1];
|
||||
|
||||
for (p in 0...polygonsCount) {
|
||||
var s:Int = clippedVertices.length;
|
||||
var clippedVerticesItems:Array<Float>;
|
||||
var clippedTrianglesItems:Array<Int>;
|
||||
if (this.clip(x1, y1, x2, y2, x3, y3, clippingPolygons[p], clipOutput)) {
|
||||
clipOutputItems = clipOutput;
|
||||
var clipOutputLength:Int = clipOutput.length;
|
||||
if (clipOutputLength == 0)
|
||||
continue;
|
||||
|
||||
var clipOutputCount:Int = clipOutputLength >> 1;
|
||||
var clipOutputItems:Array<Float> = clipOutput;
|
||||
clippedVerticesItems = clippedVertices;
|
||||
clippedVerticesItems.resize(s + clipOutputLength);
|
||||
var ii:Int = 0;
|
||||
@ -160,12 +154,12 @@ class SkeletonClipping {
|
||||
|
||||
i += 3;
|
||||
}
|
||||
return clipOutputItems != null;
|
||||
}
|
||||
|
||||
public function clipTriangles(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float, uvs:Array<Float> = null):Void {
|
||||
public function clipTriangles(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float, uvs:Array<Float> = null, stride:Int = 0):Bool {
|
||||
if (uvs == null) {
|
||||
clipTrianglesNoRender(vertices, triangles, trianglesLength);
|
||||
return;
|
||||
return clipTrianglesNoRender(vertices, triangles, trianglesLength);
|
||||
}
|
||||
|
||||
var polygonsCount:Int = clippingPolygons.length;
|
||||
@ -174,21 +168,19 @@ class SkeletonClipping {
|
||||
clippedUvs.resize(0);
|
||||
clippedTriangles.resize(0);
|
||||
var i:Int = 0;
|
||||
var clipOutputItems:Array<Float> = null;
|
||||
while (i < trianglesLength) {
|
||||
var vertexOffset:Int = triangles[i] << 1;
|
||||
var x1:Float = vertices[vertexOffset],
|
||||
y1:Float = vertices[vertexOffset + 1];
|
||||
var u1:Float = uvs[vertexOffset], v1:Float = uvs[vertexOffset + 1];
|
||||
var t:Int = triangles[i];
|
||||
var u1:Float = uvs[t << 1], v1:Float = uvs[(t << 1) + 1];
|
||||
var x1:Float = vertices[t * stride], y1:Float = vertices[t * stride + 1];
|
||||
|
||||
vertexOffset = triangles[i + 1] << 1;
|
||||
var x2:Float = vertices[vertexOffset],
|
||||
y2:Float = vertices[vertexOffset + 1];
|
||||
var u2:Float = uvs[vertexOffset], v2:Float = uvs[vertexOffset + 1];
|
||||
t = triangles[i + 1];
|
||||
var u2:Float = uvs[t << 1], v2:Float = uvs[(t << 1) + 1];
|
||||
var x2:Float = vertices[t * stride], y2:Float = vertices[t * stride + 1];
|
||||
|
||||
vertexOffset = triangles[i + 2] << 1;
|
||||
var x3:Float = vertices[vertexOffset],
|
||||
y3:Float = vertices[vertexOffset + 1];
|
||||
var u3:Float = uvs[vertexOffset], v3:Float = uvs[vertexOffset + 1];
|
||||
t = triangles[i + 2];
|
||||
var u3:Float = uvs[t << 1], v3:Float = uvs[(t << 1) + 1];
|
||||
var x3:Float = vertices[t * stride], y3:Float = vertices[t * stride + 1];
|
||||
|
||||
for (p in 0...polygonsCount) {
|
||||
var s:Int = clippedVertices.length;
|
||||
@ -196,6 +188,7 @@ class SkeletonClipping {
|
||||
var clippedUvsItems:Array<Float>;
|
||||
var clippedTrianglesItems:Array<Int>;
|
||||
if (this.clip(x1, y1, x2, y2, x3, y3, clippingPolygons[p], clipOutput)) {
|
||||
clipOutputItems = clipOutput;
|
||||
var clipOutputLength:Int = clipOutput.length;
|
||||
if (clipOutputLength == 0)
|
||||
continue;
|
||||
@ -206,9 +199,8 @@ class SkeletonClipping {
|
||||
var d:Float = 1 / (d0 * d2 + d1 * (y1 - y3));
|
||||
|
||||
var clipOutputCount:Int = clipOutputLength >> 1;
|
||||
var clipOutputItems:Array<Float> = clipOutput;
|
||||
clippedVerticesItems = clippedVertices;
|
||||
clippedVerticesItems.resize(s + clipOutputLength);
|
||||
clippedVerticesItems.resize(s + clipOutputLength * stride);
|
||||
clippedUvsItems = clippedUvs;
|
||||
clippedUvsItems.resize(s + clipOutputLength);
|
||||
|
||||
@ -242,7 +234,7 @@ class SkeletonClipping {
|
||||
index += clipOutputCount + 1;
|
||||
} else {
|
||||
clippedVerticesItems = clippedVertices;
|
||||
clippedVerticesItems.resize(s + 3 * 2);
|
||||
clippedVerticesItems.resize(s + 3 * stride);
|
||||
clippedVerticesItems[s] = x1;
|
||||
clippedVerticesItems[s + 1] = y1;
|
||||
clippedVerticesItems[s + 2] = x2;
|
||||
@ -272,13 +264,14 @@ class SkeletonClipping {
|
||||
|
||||
i += 3;
|
||||
}
|
||||
return clipOutputItems != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
|
||||
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list.
|
||||
*/
|
||||
public function clip(x1:Float, y1:Float, x2:Float, y2:Float, x3:Float, y3:Float, clippingArea:Array<Float>, output:Array<Float>):Bool {
|
||||
private function clip(x1:Float, y1:Float, x2:Float, y2:Float, x3:Float, y3:Float, clippingArea:Array<Float>, output:Array<Float>):Bool {
|
||||
var originalOutput:Array<Float> = output;
|
||||
var clipped:Bool = false;
|
||||
|
||||
|
||||
@ -37,55 +37,66 @@ import spine.attachments.AtlasAttachmentLoader;
|
||||
|
||||
/** Stores the setup pose and all of the stateless data for a skeleton.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-architecture#Data-objects Data objects in the Spine Runtimes
|
||||
* Guide. */
|
||||
class SkeletonData {
|
||||
/** The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
|
||||
* set. */
|
||||
public var name:String;
|
||||
public var name:String = null;
|
||||
|
||||
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
|
||||
public var bones:Array<BoneData> = new Array<BoneData>(); // Ordered parents first.
|
||||
public final bones = new Array<BoneData>(); // Ordered parents first.
|
||||
|
||||
/** The skeleton's slots in the setup pose draw order. */
|
||||
public var slots:Array<SlotData> = new Array<SlotData>(); // Setup pose draw order.
|
||||
public final slots = new Array<SlotData>(); // Setup pose draw order.
|
||||
|
||||
/** All skins, including the default skin. */
|
||||
public var skins:Array<Skin> = new Array<Skin>();
|
||||
public final skins = new Array<Skin>();
|
||||
|
||||
/** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine.
|
||||
*
|
||||
* See Skeleton#getAttachment(int, String). */
|
||||
public var defaultSkin:Skin;
|
||||
public var defaultSkin:Skin = null;
|
||||
|
||||
/** The skeleton's events. */
|
||||
public var events:Array<EventData> = new Array<EventData>();
|
||||
public var events = new Array<EventData>();
|
||||
|
||||
/** The skeleton's animations. */
|
||||
public var animations:Array<Animation> = new Array<Animation>();
|
||||
/** The skeleton's IK constraints. */
|
||||
public var ikConstraints:Array<IkConstraintData> = new Array<IkConstraintData>();
|
||||
/** The skeleton's transform constraints. */
|
||||
public var transformConstraints:Array<TransformConstraintData> = new Array<TransformConstraintData>();
|
||||
/** The skeleton's path constraints. */
|
||||
public var pathConstraints:Array<PathConstraintData> = new Array<PathConstraintData>();
|
||||
/** The skeleton's physics constraints. */
|
||||
public var physicsConstraints:Array<PhysicsConstraintData> = new Array<PhysicsConstraintData>();
|
||||
public var animations = new Array<Animation>();
|
||||
|
||||
/** The skeleton's constraints. */
|
||||
public var constraints = new Array<ConstraintData<Dynamic, Dynamic>>();
|
||||
|
||||
/** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */
|
||||
public var x:Float = 0;
|
||||
|
||||
/** The Y coordinate of the skeleton's axis aligned bounding box in the setup pose. */
|
||||
public var y:Float = 0;
|
||||
|
||||
/** The width of the skeleton's axis aligned bounding box in the setup pose. */
|
||||
public var width:Float = 0;
|
||||
|
||||
/** The height of the skeleton's axis aligned bounding box in the setup pose. */
|
||||
public var height:Float = 0;
|
||||
|
||||
/** Baseline scale factor for applying physics and other effects based on distance to non-scalable properties, such as angle or
|
||||
* scale. Default is 100. */
|
||||
public var referenceScale:Float = 100;
|
||||
|
||||
/** The Spine version used to export the skeleton data, or null. */
|
||||
public var version:String;
|
||||
|
||||
/** The skeleton data hash. This value will change if any of the skeleton data has changed. */
|
||||
public var hash:String;
|
||||
|
||||
// Nonessential.
|
||||
/** The dopesheet FPS in Spine, or zero if nonessential data was not exported. */
|
||||
public var fps:Float = 0;
|
||||
|
||||
/** The path to the images directory as defined in Spine, or null if nonessential data was not exported. */
|
||||
public var imagesPath:String;
|
||||
|
||||
/** The path to the audio directory as defined in Spine, or null if nonessential data was not exported. */
|
||||
public var audioPath:String;
|
||||
|
||||
@ -112,13 +123,9 @@ class SkeletonData {
|
||||
* @param boneName The name of the bone to find.
|
||||
* @return May be null. */
|
||||
public function findBone(boneName:String):BoneData {
|
||||
if (boneName == null)
|
||||
throw new SpineException("boneName cannot be null.");
|
||||
for (i in 0...bones.length) {
|
||||
var bone:BoneData = bones[i];
|
||||
if (bone.name == boneName)
|
||||
return bone;
|
||||
}
|
||||
if (boneName == null) throw new SpineException("boneName cannot be null.");
|
||||
for (bone in bones)
|
||||
if (bone.name == boneName) return bone;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -142,13 +149,9 @@ class SkeletonData {
|
||||
* @param slotName The name of the slot to find.
|
||||
* @return May be null. */
|
||||
public function findSlot(slotName:String):SlotData {
|
||||
if (slotName == null)
|
||||
throw new SpineException("slotName cannot be null.");
|
||||
for (i in 0...slots.length) {
|
||||
var slot:SlotData = slots[i];
|
||||
if (slot.name == slotName)
|
||||
return slot;
|
||||
}
|
||||
if (slotName == null) throw new SpineException("slotName cannot be null.");
|
||||
for (slot in slots)
|
||||
if (slot.name == slotName) return slot;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -159,12 +162,9 @@ class SkeletonData {
|
||||
* @param skinName The name of the skin to find.
|
||||
* @return May be null. */
|
||||
public function findSkin(skinName:String):Skin {
|
||||
if (skinName == null)
|
||||
throw new SpineException("skinName cannot be null.");
|
||||
for (skin in skins) {
|
||||
if (skin.name == skinName)
|
||||
return skin;
|
||||
}
|
||||
if (skinName == null) throw new SpineException("skinName cannot be null.");
|
||||
for (skin in skins)
|
||||
if (skin.name == skinName) return skin;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -175,12 +175,9 @@ class SkeletonData {
|
||||
* @param eventName The name of the event to find.
|
||||
* @return May be null. */
|
||||
public function findEvent(eventName:String):EventData {
|
||||
if (eventName == null)
|
||||
throw new SpineException("eventName cannot be null.");
|
||||
for (eventData in events) {
|
||||
if (eventData.name == eventName)
|
||||
return eventData;
|
||||
}
|
||||
if (eventName == null) throw new SpineException("eventName cannot be null.");
|
||||
for (eventData in events)
|
||||
if (eventData.name == eventName) return eventData;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -191,120 +188,25 @@ class SkeletonData {
|
||||
* @param animationName The name of the animation to find.
|
||||
* @return May be null. */
|
||||
public function findAnimation(animationName:String):Animation {
|
||||
if (animationName == null)
|
||||
throw new SpineException("animationName cannot be null.");
|
||||
for (animation in animations) {
|
||||
if (animation.name == animationName)
|
||||
return animation;
|
||||
}
|
||||
if (animationName == null) throw new SpineException("animationName cannot be null.");
|
||||
for (animation in animations)
|
||||
if (animation.name == animationName) return animation;
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- IK constraints.
|
||||
// --- Constraints.
|
||||
|
||||
/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
|
||||
* than to call it multiple times.
|
||||
* @param constraintName The name of the IK constraint to find.
|
||||
* @return May be null. */
|
||||
public function findIkConstraint(constraintName:String):IkConstraintData {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (ikConstraintData in ikConstraints) {
|
||||
if (ikConstraintData.name == constraintName)
|
||||
return ikConstraintData;
|
||||
public function findConstraint<T:ConstraintData<Dynamic, Dynamic>>(constraintName:String, type:Class<T>):Null<T> {
|
||||
if (constraintName == null) throw new SpineException("constraintName cannot be null.");
|
||||
if (type == null) throw new SpineException("type cannot be null.");
|
||||
|
||||
for (constraint in constraints) {
|
||||
if (Std.is(constraint, type) && constraint.name == constraintName)
|
||||
return Std.downcast(constraint, type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Transform constraints.
|
||||
|
||||
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
|
||||
* this method than to call it multiple times.
|
||||
* @param constraintName The name of the transform constraint to find.
|
||||
* @return May be null. */
|
||||
public function findTransformConstraint(constraintName:String):TransformConstraintData {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (transformConstraintData in transformConstraints) {
|
||||
if (transformConstraintData.name == constraintName)
|
||||
return transformConstraintData;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds the index of a transform constraint by comparing each transform constraint's name.
|
||||
* @param transformConstraintName The name of the transform constraint to find.
|
||||
* @return -1 if the transform constraint was not found. */
|
||||
public function findTransformConstraintIndex(transformConstraintName:String):Int {
|
||||
if (transformConstraintName == null)
|
||||
throw new SpineException("transformConstraintName cannot be null.");
|
||||
for (i in 0...transformConstraints.length) {
|
||||
if (transformConstraints[i].name == transformConstraintName)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// --- Path constraints.
|
||||
|
||||
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
|
||||
* than to call it multiple times.
|
||||
* @param constraintName The name of the path constraint to find.
|
||||
* @return May be null. */
|
||||
public function findPathConstraint(constraintName:String):PathConstraintData {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (i in 0...pathConstraints.length) {
|
||||
var constraint:PathConstraintData = pathConstraints[i];
|
||||
if (constraint.name == constraintName)
|
||||
return constraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds the index of a path constraint by comparing each path constraint's name.
|
||||
* @param pathConstraintName The name of the path constraint to find.
|
||||
* @return -1 if the path constraint was not found. */
|
||||
public function findPathConstraintIndex(pathConstraintName:String):Int {
|
||||
if (pathConstraintName == null)
|
||||
throw new SpineException("pathConstraintName cannot be null.");
|
||||
for (i in 0...pathConstraints.length) {
|
||||
if (pathConstraints[i].name == pathConstraintName)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// --- Physics constraints.
|
||||
|
||||
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
|
||||
* method than to call it multiple times.
|
||||
* @param constraintName The name of the physics constraint to find.
|
||||
* @return May be null. */
|
||||
public function findPhysicsConstraint(constraintName:String):PhysicsConstraintData {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (i in 0...physicsConstraints.length) {
|
||||
var constraint:PhysicsConstraintData = physicsConstraints[i];
|
||||
if (constraint.name == constraintName)
|
||||
return constraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds the index of a physics constraint by comparing each physics constraint's name.
|
||||
* @param constraintName The name of the physics constraint to find.
|
||||
* @return -1 if the physics constraint was not found. */
|
||||
public function findPhysicsConstraintIndex(constraintName:String):Int {
|
||||
if (constraintName == null)
|
||||
throw new SpineException("constraintName cannot be null.");
|
||||
for (i in 0...physicsConstraints.length) {
|
||||
if (physicsConstraints[i].name == constraintName)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -34,31 +34,35 @@ import spine.attachments.Attachment;
|
||||
import spine.attachments.MeshAttachment;
|
||||
|
||||
/** Stores attachments by slot index and attachment name.
|
||||
*
|
||||
*
|
||||
* See spine.SkeletonData.defaultSkin, spine.Skeleton.skin, and
|
||||
* Runtime skins at https://esotericsoftware.com/spine-runtime-skins in the Spine Runtimes Guide. */
|
||||
class Skin {
|
||||
private var _name:String;
|
||||
private var _attachments:Array<StringMap<Attachment>> = new Array<StringMap<Attachment>>();
|
||||
private var _bones:Array<BoneData> = new Array<BoneData>();
|
||||
private var _constraints:Array<ConstraintData> = new Array<ConstraintData>();
|
||||
private var _color:Color = new Color(0.99607843, 0.61960787, 0.30980393, 1); // fe9e4fff
|
||||
/** The skin's name, which is unique across all skins in the skeleton. */
|
||||
public final name:String;
|
||||
|
||||
/** Returns the attachment for the specified slot index and name, or null. */
|
||||
public final attachments:Array<StringMap<Attachment>> = new Array<StringMap<Attachment>>();
|
||||
|
||||
public final bones:Array<BoneData> = new Array<BoneData>();
|
||||
public final constraints = new Array<ConstraintData<Dynamic, Pose<Any>>>();
|
||||
|
||||
/** The color of the skin as it was in Spine, or a default color if nonessential data was not exported. */
|
||||
public final color:Color = new Color(0.99607843, 0.61960787, 0.30980393, 1); // fe9e4fff
|
||||
|
||||
public function new(name:String) {
|
||||
if (name == null)
|
||||
throw new SpineException("name cannot be null.");
|
||||
_name = name;
|
||||
if (name == null) throw new SpineException("name cannot be null.");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/** Adds an attachment to the skin for the specified slot index and name. */
|
||||
public function setAttachment(slotIndex:Int, name:String, attachment:Attachment):Void {
|
||||
if (attachment == null)
|
||||
throw new SpineException("attachment cannot be null.");
|
||||
if (slotIndex >= _attachments.length)
|
||||
_attachments.resize(slotIndex + 1);
|
||||
if (_attachments[slotIndex] == null)
|
||||
_attachments[slotIndex] = new StringMap<Attachment>();
|
||||
_attachments[slotIndex].set(name, attachment);
|
||||
if (attachment == null) throw new SpineException("attachment cannot be null.");
|
||||
if (slotIndex >= attachments.length)
|
||||
attachments.resize(slotIndex + 1);
|
||||
if (attachments[slotIndex] == null)
|
||||
attachments[slotIndex] = new StringMap<Attachment>();
|
||||
attachments[slotIndex].set(name, attachment);
|
||||
}
|
||||
|
||||
/** Adds all attachments, bones, and constraints from the specified skin to this skin. */
|
||||
@ -68,26 +72,26 @@ class Skin {
|
||||
var bone:BoneData = skin.bones[i];
|
||||
contained = false;
|
||||
for (j in 0...bones.length) {
|
||||
if (_bones[j] == bone) {
|
||||
if (bones[j] == bone) {
|
||||
contained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contained)
|
||||
_bones.push(bone);
|
||||
bones.push(bone);
|
||||
}
|
||||
|
||||
for (i in 0...skin.constraints.length) {
|
||||
var constraint:ConstraintData = skin.constraints[i];
|
||||
var constraint = skin.constraints[i];
|
||||
contained = false;
|
||||
for (j in 0..._constraints.length) {
|
||||
if (_constraints[j] == constraint) {
|
||||
for (j in 0...constraints.length) {
|
||||
if (constraints[j] == constraint) {
|
||||
contained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contained)
|
||||
_constraints.push(constraint);
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
var attachments:Array<SkinEntry> = skin.getAttachments();
|
||||
@ -106,27 +110,27 @@ class Skin {
|
||||
for (i in 0...skin.bones.length) {
|
||||
var bone:BoneData = skin.bones[i];
|
||||
contained = false;
|
||||
for (j in 0..._bones.length) {
|
||||
if (_bones[j] == bone) {
|
||||
for (j in 0...bones.length) {
|
||||
if (bones[j] == bone) {
|
||||
contained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contained)
|
||||
_bones.push(bone);
|
||||
bones.push(bone);
|
||||
}
|
||||
|
||||
for (i in 0...skin.constraints.length) {
|
||||
var constraint:ConstraintData = skin.constraints[i];
|
||||
var constraint = skin.constraints[i];
|
||||
contained = false;
|
||||
for (j in 0..._constraints.length) {
|
||||
if (_constraints[j] == constraint) {
|
||||
for (j in 0...constraints.length) {
|
||||
if (constraints[j] == constraint) {
|
||||
contained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contained)
|
||||
_constraints.push(constraint);
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
var attachments:Array<SkinEntry> = skin.getAttachments();
|
||||
@ -147,15 +151,15 @@ class Skin {
|
||||
|
||||
/** Returns the attachment for the specified slot index and name, or null. */
|
||||
public function getAttachment(slotIndex:Int, name:String):Attachment {
|
||||
if (slotIndex >= _attachments.length)
|
||||
if (slotIndex >= attachments.length)
|
||||
return null;
|
||||
var dictionary:StringMap<Attachment> = _attachments[slotIndex];
|
||||
var dictionary:StringMap<Attachment> = attachments[slotIndex];
|
||||
return dictionary != null ? dictionary.get(name) : null;
|
||||
}
|
||||
|
||||
/** Removes the attachment in the skin for the specified slot index and name, if any. */
|
||||
public function removeAttachment(slotIndex:Int, name:String):Void {
|
||||
var dictionary:StringMap<Attachment> = _attachments[slotIndex];
|
||||
var dictionary:StringMap<Attachment> = attachments[slotIndex];
|
||||
if (dictionary != null)
|
||||
dictionary.remove(name);
|
||||
}
|
||||
@ -163,8 +167,8 @@ class Skin {
|
||||
/** Returns all attachments in this skin. */
|
||||
public function getAttachments():Array<SkinEntry> {
|
||||
var entries:Array<SkinEntry> = new Array<SkinEntry>();
|
||||
for (slotIndex in 0..._attachments.length) {
|
||||
var attachments:StringMap<Attachment> = _attachments[slotIndex];
|
||||
for (slotIndex in 0...attachments.length) {
|
||||
var attachments:StringMap<Attachment> = attachments[slotIndex];
|
||||
if (attachments != null) {
|
||||
for (name in attachments.keys()) {
|
||||
var attachment:Attachment = attachments.get(name);
|
||||
@ -179,7 +183,7 @@ class Skin {
|
||||
/** Returns all attachments in this skin for the specified slot index. */
|
||||
public function getAttachmentsForSlot(slotIndex:Int):Array<SkinEntry> {
|
||||
var entries:Array<SkinEntry> = new Array<SkinEntry>();
|
||||
var attachments:StringMap<Attachment> = _attachments[slotIndex];
|
||||
var attachments:StringMap<Attachment> = attachments[slotIndex];
|
||||
if (attachments != null) {
|
||||
for (name in attachments.keys()) {
|
||||
var attachment:Attachment = attachments.get(name);
|
||||
@ -192,54 +196,21 @@ class Skin {
|
||||
|
||||
/** Clears all attachments, bones, and constraints. */
|
||||
public function clear():Void {
|
||||
_attachments.resize(0);
|
||||
_bones.resize(0);
|
||||
_constraints.resize(0);
|
||||
attachments.resize(0);
|
||||
bones.resize(0);
|
||||
constraints.resize(0);
|
||||
}
|
||||
|
||||
public var attachments(get, never):Array<StringMap<Attachment>>;
|
||||
|
||||
private function get_attachments():Array<StringMap<Attachment>> {
|
||||
return _attachments;
|
||||
public function toString():String {
|
||||
return name;
|
||||
}
|
||||
|
||||
public var bones(get, never):Array<BoneData>;
|
||||
|
||||
private function get_bones():Array<BoneData> {
|
||||
return _bones;
|
||||
}
|
||||
|
||||
public var constraints(get, never):Array<ConstraintData>;
|
||||
|
||||
private function get_constraints():Array<ConstraintData> {
|
||||
return _constraints;
|
||||
}
|
||||
|
||||
/** The skin's name, which is unique across all skins in the skeleton. */
|
||||
public var name(get, never):String;
|
||||
|
||||
private function get_name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/** The color of the skin as it was in Spine, or a default color if nonessential data was not exported. */
|
||||
public var color(get, never):Color;
|
||||
|
||||
private function get_color():Color {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*
|
||||
public function toString():String
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
*/
|
||||
/** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */
|
||||
public function attachAll(skeleton:Skeleton, oldSkin:Skin):Void {
|
||||
var slotIndex:Int = 0;
|
||||
for (slot in skeleton.slots) {
|
||||
var slotAttachment:Attachment = slot.attachment;
|
||||
for (element in skeleton.slots) {
|
||||
var slot = element.pose;
|
||||
var slotAttachment = slot.attachment;
|
||||
if (slotAttachment != null && slotIndex < oldSkin.attachments.length) {
|
||||
var dictionary:StringMap<Attachment> = oldSkin.attachments[slotIndex];
|
||||
if (null != dictionary) {
|
||||
@ -247,8 +218,7 @@ class Skin {
|
||||
var skinAttachment:Attachment = dictionary.get(name);
|
||||
if (slotAttachment == skinAttachment) {
|
||||
var attachment:Attachment = getAttachment(slotIndex, name);
|
||||
if (attachment != null)
|
||||
slot.attachment = attachment;
|
||||
if (attachment != null) slot.attachment = attachment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
112
spine-haxe/spine-haxe/spine/Slider.hx
Normal file
112
spine-haxe/spine-haxe/spine/Slider.hx
Normal file
@ -0,0 +1,112 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a {@link PhysicsConstraint}.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
class Slider extends Constraint<Slider, SliderData, SliderPose> {
|
||||
static private final offsets:Array<Float> = [for (i in 0...6) .0];
|
||||
|
||||
public var bone:Bone;
|
||||
|
||||
public function new (data:SliderData, skeleton:Skeleton) {
|
||||
super(data, new SliderPose(), new SliderPose());
|
||||
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
|
||||
|
||||
if (data.bone != null) bone = skeleton.bones.items[data.bone.index];
|
||||
}
|
||||
|
||||
public function copy (skeleton:Skeleton) {
|
||||
var copy = new Slider(data, skeleton);
|
||||
copy.pose.set(pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public function update (skeleton:Skeleton, physics:Physics) {
|
||||
var p = applied;
|
||||
if (p.mix == 0) return;
|
||||
|
||||
var animation = data.animation;
|
||||
if (bone != null) {
|
||||
if (!bone.active) return;
|
||||
if (data.local) bone.applied.validateLocalTransform(skeleton);
|
||||
p.time = (data.property.value(bone.applied, data.local, offsets) - data.property.offset) * data.scale;
|
||||
if (data.loop)
|
||||
p.time = animation.duration + (p.time % animation.duration);
|
||||
else
|
||||
p.time = Math.max(0, p.time);
|
||||
}
|
||||
|
||||
var bones = skeleton.bones;
|
||||
var indices = animation.bones;
|
||||
var i = 0, n = animation.bones.size;
|
||||
while (i < n)
|
||||
bones[indices[i++]].applied.modifyLocal(skeleton);
|
||||
|
||||
animation.apply(skeleton, p.time, p.time, data.loop, null, p.mix, data.additive ? MixBlend.add : MixBlend.replace,
|
||||
MixDirection.mixIn, true);
|
||||
}
|
||||
|
||||
function sort (skeleton:Skeleton) {
|
||||
if (bone != null && !data.local) skeleton.sortBone(bone);
|
||||
skeleton.updateCache.add(this);
|
||||
|
||||
var bones = skeleton.bones;
|
||||
var indices = data.animation.bones;
|
||||
var i = 0, n = data.animation.bones.length;
|
||||
while (i < n) {
|
||||
var bone = bones[indices[i++]];
|
||||
bone.sorted = false;
|
||||
skeleton.sortReset(bone.children);
|
||||
skeleton.constrained(bone);
|
||||
}
|
||||
|
||||
var timelines = data.animation.timelines;
|
||||
var slots = skeleton.slots;
|
||||
var constraints = skeleton.constraints;
|
||||
var physics = skeleton.physics;
|
||||
var physicsCount = skeleton.physics.length;
|
||||
var i = 0, n = data.animation.timelines.length;
|
||||
while (i < n) {
|
||||
var t = timelines[i++];
|
||||
if (std.isOfType(t, SlotTimeline))
|
||||
skeleton.constrained(slots[cast(t, SlotTimeline).getSlotIndex()]);
|
||||
else if (std.isOfType(t, PhysicsConstraintTimeline)) {
|
||||
if (cast(t, PhysicsConstraintTimeline).constraintIndex == -1) {
|
||||
for (ii in 0...physicsCount)
|
||||
skeleton.constrained(physics[ii]);
|
||||
} else
|
||||
skeleton.constrained(constraints[timeline.constraintIndex]);
|
||||
} else if (std.isOfType(t, ConstraintTimeline)) //
|
||||
skeleton.constrained(constraints[cast(t, ConstraintTimeline).getConstraintIndex()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
spine-haxe/spine-haxe/spine/SliderData.hx
Normal file
53
spine-haxe/spine-haxe/spine/SliderData.hx
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a PhysicsConstraint.
|
||||
*
|
||||
* See <a href="https://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
class SliderData extends ConstraintData<Slider, SliderPose> {
|
||||
public var animation:Animation;
|
||||
public var additive = false;
|
||||
public var loop = false;
|
||||
public var bone:BoneData = null;
|
||||
public var property:FromProperty = null;
|
||||
public var scale = 1.;
|
||||
public var local = false;
|
||||
|
||||
public function new (name:String) {
|
||||
super(name, new SliderPose());
|
||||
}
|
||||
|
||||
public function create (skeleton:Skeleton) {
|
||||
return new Slider(this, skeleton);
|
||||
}
|
||||
}
|
||||
41
spine-haxe/spine-haxe/spine/SliderPose.hx
Normal file
41
spine-haxe/spine-haxe/spine/SliderPose.hx
Normal file
@ -0,0 +1,41 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores a pose for a slider. */
|
||||
class SliderPose implements Pose<SliderPose> {
|
||||
public var time = 0.;
|
||||
public var mix = 0.;
|
||||
|
||||
public function set (pose:SliderPose) {
|
||||
time = pose.time;
|
||||
mix = pose.mix;
|
||||
}
|
||||
}
|
||||
@ -35,101 +35,51 @@ import spine.attachments.VertexAttachment;
|
||||
/** Stores a slot's current pose. Slots organize attachments for Skeleton.drawOrder purposes and provide a place to store
|
||||
* state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared
|
||||
* across multiple skeletons. */
|
||||
class Slot {
|
||||
private var _data:SlotData;
|
||||
private var _bone:Bone;
|
||||
class Slot extends Posed<SlotData, SlotPose, SlotPose> {
|
||||
|
||||
/** The color used to tint the slot's attachment. If darkColor is set, this is used as the light color for two
|
||||
* color tinting. */
|
||||
public var color:Color;
|
||||
/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
|
||||
* color's alpha is not used. */
|
||||
public var darkColor:Color;
|
||||
|
||||
private var _attachment:Attachment;
|
||||
|
||||
/** The index of the texture region to display when the slot's attachment has a spine.attachments.Sequence. -1 represents the
|
||||
* Sequence.getSetupIndex(). */
|
||||
public var sequenceIndex = -1;
|
||||
|
||||
public var attachmentState:Int = 0;
|
||||
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
|
||||
* weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
|
||||
* @see spine.attachments.VertexAttachment.computeWorldVertices()
|
||||
* @see spine.animation.DeformTimeline */
|
||||
public var deform:Array<Float> = new Array<Float>();
|
||||
|
||||
/** Copy constructor. */
|
||||
public function new(data:SlotData, bone:Bone) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (bone == null)
|
||||
throw new SpineException("bone cannot be null.");
|
||||
_data = data;
|
||||
_bone = bone;
|
||||
this.color = new Color(1, 1, 1, 1);
|
||||
this.darkColor = data.darkColor == null ? null : new Color(1, 1, 1, 1);
|
||||
setToSetupPose();
|
||||
}
|
||||
|
||||
/** The slot's setup pose data. */
|
||||
public var data(get, never):SlotData;
|
||||
|
||||
private function get_data():SlotData {
|
||||
return _data;
|
||||
}
|
||||
public var skeleton:Skeleton;
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
public var bone(get, never):Bone;
|
||||
public var bone:Bone;
|
||||
|
||||
private function get_bone():Bone {
|
||||
return _bone;
|
||||
}
|
||||
public var attachmentState:Int ;
|
||||
|
||||
/** The skeleton this slot belongs to. */
|
||||
public var skeleton(get, never):Skeleton;
|
||||
|
||||
private function get_skeleton():Skeleton {
|
||||
return _bone.skeleton;
|
||||
}
|
||||
|
||||
/** The current attachment for the slot, or null if the slot has no attachment. */
|
||||
public var attachment(get, set):Attachment;
|
||||
|
||||
private function get_attachment():Attachment {
|
||||
return _attachment;
|
||||
}
|
||||
|
||||
/** Sets the slot's attachment and, if the attachment changed, resets sequenceIndex and clears the deform.
|
||||
* The deform is not cleared if the old attachment has the same spine.attachments.VertexAttachment.timelineAttachment as the
|
||||
* specified attachment. */
|
||||
public function set_attachment(attachmentNew:Attachment):Attachment {
|
||||
if (attachment == attachmentNew)
|
||||
return attachmentNew;
|
||||
if (!Std.isOfType(attachmentNew, VertexAttachment)
|
||||
|| !Std.isOfType(attachment, VertexAttachment)
|
||||
|| cast(attachmentNew, VertexAttachment).timelineAttachment != cast(attachment, VertexAttachment).timelineAttachment) {
|
||||
deform = new Array<Float>();
|
||||
public function new(data:SlotData, skeleton:Skeleton) {
|
||||
super(data, new SlotPose(), new SlotPose());
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
this.skeleton = skeleton;
|
||||
bone = skeleton.bones[data.boneData.index];
|
||||
if (data.setup.darkColor != null) {
|
||||
pose.darkColor = new Color(1, 1, 1, 1);
|
||||
constrained.darkColor = new Color(1, 1, 1, 1);
|
||||
}
|
||||
_attachment = attachmentNew;
|
||||
sequenceIndex = -1;
|
||||
return attachmentNew;
|
||||
setupPose();
|
||||
}
|
||||
|
||||
/** Copy method. */
|
||||
public function copy(slot:Slot, bone:Bone, skeleton:Skeleton):Slot {
|
||||
var copy = new Slot(slot.data, skeleton);
|
||||
if (bone == null) throw new SpineException("bone cannot be null.");
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
this.bone = bone;
|
||||
if (data.setup.darkColor != null) {
|
||||
pose.darkColor = new Color(1, 1, 1, 1);
|
||||
constrained.darkColor = new Color(1, 1, 1, 1);
|
||||
}
|
||||
copy.pose.set(slot.pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Sets this slot to the setup pose. */
|
||||
public function setToSetupPose():Void {
|
||||
color.setFromColor(data.color);
|
||||
if (darkColor != null)
|
||||
darkColor.setFromColor(data.darkColor);
|
||||
if (_data.attachmentName == null) {
|
||||
attachment = null;
|
||||
override public function setupPose():Void {
|
||||
pose.color.setFromColor(data.setup.color);
|
||||
if (pose.darkColor != null) pose.darkColor.setFromColor(data.setup.darkColor);
|
||||
pose.sequenceIndex = data.setup.sequenceIndex;
|
||||
if (data.attachmentName == null) {
|
||||
pose.attachment = null;
|
||||
} else {
|
||||
_attachment = null;
|
||||
attachment = skeleton.getAttachmentForSlotIndex(data.index, data.attachmentName);
|
||||
pose.attachment = null;
|
||||
pose.attachment = skeleton.getAttachmentForSlotIndex(data.index, data.attachmentName);
|
||||
}
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "Slot?";
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,58 +30,29 @@
|
||||
package spine;
|
||||
|
||||
/** Stores the setup pose for a spine.Slot. */
|
||||
class SlotData {
|
||||
private var _index:Int;
|
||||
private var _name:String;
|
||||
private var _boneData:BoneData;
|
||||
class SlotData extends PosedData<SlotPose> {
|
||||
|
||||
/** The index of the slot in spine.Skeleton.getSlots(). */
|
||||
public final index:Int;
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
public final boneData:BoneData;
|
||||
|
||||
/** The color used to tint the slot's attachment. If SlotData.darkColor is set, this is used as the light color for two
|
||||
* color tinting. */
|
||||
public var color:Color = new Color(1, 1, 1, 1);
|
||||
/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
|
||||
* color's alpha is not used. */
|
||||
public var darkColor:Color = null;
|
||||
/** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */
|
||||
public var attachmentName:String;
|
||||
public var attachmentName:String = null;
|
||||
|
||||
/** The blend mode for drawing the slot's attachment. */
|
||||
public var blendMode:BlendMode = BlendMode.normal;
|
||||
|
||||
// Nonessential.
|
||||
/** False if the slot was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
|
||||
public var visible:Bool = true;
|
||||
|
||||
public function new(index:Int, name:String, boneData:BoneData) {
|
||||
if (index < 0)
|
||||
throw new SpineException("index must be >= 0.");
|
||||
if (name == null)
|
||||
throw new SpineException("name cannot be null.");
|
||||
if (boneData == null)
|
||||
throw new SpineException("boneData cannot be null.");
|
||||
_index = index;
|
||||
_name = name;
|
||||
_boneData = boneData;
|
||||
}
|
||||
|
||||
/** The index of the slot in spine.Skeleton.getSlots(). */
|
||||
public var index(get, never):Int;
|
||||
|
||||
private function get_index():Int {
|
||||
return _index;
|
||||
}
|
||||
|
||||
/** The name of the slot, which is unique across all slots in the skeleton. */
|
||||
public var name(get, never):String;
|
||||
|
||||
private function get_name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
public var boneData(get, never):BoneData;
|
||||
|
||||
private function get_boneData():BoneData {
|
||||
return _boneData;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _name;
|
||||
super(name, new SlotPose());
|
||||
if (index < 0) throw new SpineException("index must be >= 0.");
|
||||
if (boneData == null) throw new SpineException("boneData cannot be null.");
|
||||
this.index = index;
|
||||
this.boneData = boneData;
|
||||
}
|
||||
}
|
||||
|
||||
87
spine-haxe/spine-haxe/spine/SlotPose.hx
Normal file
87
spine-haxe/spine-haxe/spine/SlotPose.hx
Normal file
@ -0,0 +1,87 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
import spine.attachments.Attachment;
|
||||
import spine.attachments.VertexAttachment;
|
||||
|
||||
/** Stores a slot's pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store state
|
||||
* for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across
|
||||
* multiple skeletons. */
|
||||
class SlotPose implements Pose<SlotPose> {
|
||||
|
||||
/** The color used to tint the slot's attachment. If SlotData.darkColor is set, this is used as the light color for two
|
||||
* color tinting. */
|
||||
public final color:Color = new Color(1, 1, 1, 1);
|
||||
|
||||
/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
|
||||
* color's alpha is not used. */
|
||||
public var darkColor:Color = null;
|
||||
|
||||
public var attachment(default, set):Attachment; // Not used in setup pose.
|
||||
|
||||
/** The index of the texture region to display when the slot's attachment has a spine.attachments.Sequence. -1 represents the
|
||||
* Sequence.getSetupIndex(). */
|
||||
public var sequenceIndex = -1;
|
||||
|
||||
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
|
||||
* weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
|
||||
* @see spine.attachments.VertexAttachment.computeWorldVertices()
|
||||
* @see spine.animation.DeformTimeline */
|
||||
public var deform:Array<Float> = new Array<Float>();
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set (pose:SlotPose):Void {
|
||||
if (pose == null) throw new SpineException("pose cannot be null.");
|
||||
color.setFromColor(pose.color);
|
||||
if (darkColor != null) darkColor.setFromColor(pose.darkColor);
|
||||
attachment = pose.attachment;
|
||||
sequenceIndex = pose.sequenceIndex;
|
||||
deform.resize(0);
|
||||
for (e in pose.deform) deform.push(e);
|
||||
}
|
||||
|
||||
/** Sets the slot's attachment and, if the attachment changed, resets sequenceIndex and clears the deform.
|
||||
* The deform is not cleared if the old attachment has the same spine.attachments.VertexAttachment.timelineAttachment as the
|
||||
* specified attachment. */
|
||||
public function set_attachment(attachmentNew:Attachment):Attachment {
|
||||
if (attachment == attachmentNew) return attachment;
|
||||
if (!Std.isOfType(attachmentNew, VertexAttachment) || !Std.isOfType(attachment, VertexAttachment)
|
||||
|| cast(attachmentNew, VertexAttachment).timelineAttachment != cast(attachment, VertexAttachment).timelineAttachment) {
|
||||
deform = new Array<Float>();
|
||||
}
|
||||
attachment = attachmentNew;
|
||||
sequenceIndex = -1;
|
||||
return attachment;
|
||||
}
|
||||
|
||||
}
|
||||
@ -33,272 +33,96 @@ package spine;
|
||||
* bones to match that of the target bone.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-transform-constraints Transform constraints in the Spine User Guide */
|
||||
class TransformConstraint implements Updatable {
|
||||
private var _data:TransformConstraintData;
|
||||
private var _bones:Array<Bone>;
|
||||
class TransformConstraint extends Constraint<TransformConstraint, TransformConstraintData, TransformConstraintPose> {
|
||||
|
||||
/** The bones that will be modified by this transform constraint. */
|
||||
public final bones:Array<BonePose>;
|
||||
|
||||
/** The target bone whose world transform will be copied to the constrained bones. */
|
||||
public var target:Bone;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
public var mixX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
public var mixY:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. */
|
||||
public var mixScaleX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. */
|
||||
public var mixScaleY:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. */
|
||||
public var mixShearY:Float = 0;
|
||||
public var source:Bone;
|
||||
|
||||
private var _temp:Array<Float> = new Array<Float>();
|
||||
|
||||
public var active:Bool = false;
|
||||
|
||||
/** Copy constructor. */
|
||||
public function new(data:TransformConstraintData, skeleton:Skeleton) {
|
||||
if (data == null)
|
||||
throw new SpineException("data cannot be null.");
|
||||
if (skeleton == null)
|
||||
throw new SpineException("skeleton cannot be null.");
|
||||
_data = data;
|
||||
super(data, new TransformConstraintPose(), new TransformConstraintPose());
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
|
||||
_bones = new Array<Bone>();
|
||||
for (boneData in data.bones) {
|
||||
_bones.push(skeleton.findBone(boneData.name));
|
||||
}
|
||||
target = skeleton.findBone(data.target.name);
|
||||
|
||||
mixRotate = data.mixRotate;
|
||||
mixX = data.mixX;
|
||||
mixY = data.mixY;
|
||||
mixScaleX = data.mixScaleX;
|
||||
mixScaleY = data.mixScaleY;
|
||||
mixShearY = data.mixShearY;
|
||||
bones = new Array<BonePose>();
|
||||
for (boneData in data.bones)
|
||||
bones.push(skeleton.bones[boneData.index].constrained);
|
||||
source = skeleton.bones[data.source.index];
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return active;
|
||||
}
|
||||
|
||||
public function setToSetupPose () {
|
||||
var data:TransformConstraintData = _data;
|
||||
mixRotate = data.mixRotate;
|
||||
mixX = data.mixX;
|
||||
mixY = data.mixY;
|
||||
mixScaleX = data.mixScaleX;
|
||||
mixScaleY = data.mixScaleY;
|
||||
mixShearY = data.mixShearY;
|
||||
public function copy(skeleton:Skeleton) {
|
||||
var copy = new TransformConstraint(data, skeleton);
|
||||
copy.pose.set(pose);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
public function update(physics:Physics):Void {
|
||||
if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0)
|
||||
return;
|
||||
public function update(skeleton:Skeleton, physics:Physics):Void {
|
||||
var p = applied;
|
||||
if (p.mixRotate == 0 && p.mixX == 0 && p.mixY == 0 && p.mixScaleX == 0 && p.mixScaleY == 0 && p.mixShearY == 0) return;
|
||||
|
||||
if (data.local) {
|
||||
if (data.relative) {
|
||||
applyRelativeLocal();
|
||||
} else {
|
||||
applyAbsoluteLocal();
|
||||
}
|
||||
} else {
|
||||
if (data.relative) {
|
||||
applyRelativeWorld();
|
||||
} else {
|
||||
applyAbsoluteWorld();
|
||||
var localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
|
||||
var offsets = data.offsets;
|
||||
var source = this.source.applied;
|
||||
if (localSource) source.validateLocalTransform(skeleton);
|
||||
var fromItems = data.properties;
|
||||
var fn = data.properties.length, update = skeleton.update;
|
||||
var bones = this.bones;
|
||||
var i = 0, n = this.bones.length;
|
||||
var update = skeleton._update;
|
||||
while (i < n) {
|
||||
var bone = bones[i];
|
||||
if (localTarget)
|
||||
bone.modifyLocal(skeleton);
|
||||
else
|
||||
bone.modifyWorld(update);
|
||||
var f = 0;
|
||||
while (f < fn) {
|
||||
var from = fromItems[f];
|
||||
var value = from.value(source, localSource, offsets) - from.offset;
|
||||
var toItems = from.to;
|
||||
var t = 0, tn = from.to.length;
|
||||
while (t < tn) {
|
||||
var to = toItems[t];
|
||||
if (to.mix(p) != 0) {
|
||||
var clamped = to.offset + value * to.scale;
|
||||
if (clamp) {
|
||||
if (to.offset < to.max)
|
||||
clamped = MathUtils.clamp(clamped, to.offset, to.max);
|
||||
else
|
||||
clamped = MathUtils.clamp(clamped, to.max, to.offset);
|
||||
}
|
||||
to.apply(p, bone, clamped, localTarget, additive);
|
||||
}
|
||||
t++;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private function applyAbsoluteWorld():Void {
|
||||
var translate:Bool = mixX != 0 || mixY != 0;
|
||||
var ta:Float = target.a,
|
||||
tb:Float = target.b,
|
||||
tc:Float = target.c,
|
||||
td:Float = target.d;
|
||||
var degRadReflect:Float = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
||||
var offsetRotation:Float = data.offsetRotation * degRadReflect;
|
||||
var offsetShearY:Float = data.offsetShearY * degRadReflect;
|
||||
for (bone in bones) {
|
||||
if (mixRotate != 0) {
|
||||
var a:Float = bone.a,
|
||||
b:Float = bone.b,
|
||||
c:Float = bone.c,
|
||||
d:Float = bone.d;
|
||||
var r:Float = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation;
|
||||
if (r > Math.PI)
|
||||
r -= Math.PI * 2;
|
||||
else if (r < -Math.PI)
|
||||
r += Math.PI * 2;
|
||||
r *= mixRotate;
|
||||
var cos:Float = Math.cos(r), sin:Float = Math.sin(r);
|
||||
bone.a = cos * a - sin * c;
|
||||
bone.b = cos * b - sin * d;
|
||||
bone.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
}
|
||||
|
||||
if (translate) {
|
||||
_temp[0] = data.offsetX;
|
||||
_temp[1] = data.offsetY;
|
||||
target.localToWorld(_temp);
|
||||
bone.worldX += (_temp[0] - bone.worldX) * mixX;
|
||||
bone.worldY += (_temp[1] - bone.worldY) * mixY;
|
||||
}
|
||||
|
||||
if (mixScaleX != 0) {
|
||||
var s:Float = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
|
||||
if (s != 0)
|
||||
s = (s + (Math.sqrt(ta * ta + tc * tc) - s + _data.offsetScaleX) * mixScaleX) / s;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
|
||||
if (mixScaleY != 0) {
|
||||
var s:Float = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
|
||||
if (s != 0)
|
||||
s = (s + (Math.sqrt(tb * tb + td * td) - s + _data.offsetScaleY) * mixScaleY) / s;
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
}
|
||||
|
||||
if (mixShearY > 0) {
|
||||
var by:Float = Math.atan2(bone.d, bone.b);
|
||||
var r:Float = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a));
|
||||
if (r > Math.PI)
|
||||
r -= Math.PI * 2;
|
||||
else if (r < -Math.PI)
|
||||
r += Math.PI * 2;
|
||||
r = by + (r + offsetShearY) * mixShearY;
|
||||
var s:Float = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
|
||||
bone.b = Math.cos(r) * s;
|
||||
bone.d = Math.sin(r) * s;
|
||||
}
|
||||
|
||||
bone.updateAppliedTransform();
|
||||
public function sort (skeleton:Skeleton) {
|
||||
if (!data.localSource) skeleton.sortBone(source);
|
||||
var bones = this.bones;
|
||||
var boneCount = this.bones.length;
|
||||
var worldTarget = !data.localTarget;
|
||||
if (worldTarget) {
|
||||
for (i in 0...boneCount)
|
||||
skeleton.sortBone(bones[i].bone);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyRelativeWorld():Void {
|
||||
var translate:Bool = mixX != 0 || mixY != 0;
|
||||
var ta:Float = target.a,
|
||||
tb:Float = target.b,
|
||||
tc:Float = target.c,
|
||||
td:Float = target.d;
|
||||
var degRadReflect:Float = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
||||
var offsetRotation:Float = _data.offsetRotation * degRadReflect,
|
||||
offsetShearY:Float = _data.offsetShearY * degRadReflect;
|
||||
for (bone in bones) {
|
||||
if (mixRotate != 0) {
|
||||
var a:Float = bone.a,
|
||||
b:Float = bone.b,
|
||||
c:Float = bone.c,
|
||||
d:Float = bone.d;
|
||||
var r:Float = Math.atan2(tc, ta) + offsetRotation;
|
||||
if (r > MathUtils.PI)
|
||||
r -= MathUtils.PI2;
|
||||
else if (r < -MathUtils.PI)
|
||||
r += MathUtils.PI2;
|
||||
r *= mixRotate;
|
||||
var cos:Float = Math.cos(r), sin:Float = Math.sin(r);
|
||||
bone.a = cos * a - sin * c;
|
||||
bone.b = cos * b - sin * d;
|
||||
bone.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
}
|
||||
|
||||
if (translate) {
|
||||
var temp:Array<Float> = _temp;
|
||||
temp[0] = _data.offsetX;
|
||||
temp[1] = _data.offsetY;
|
||||
target.localToWorld(temp);
|
||||
bone.worldX += temp[0] * mixX;
|
||||
bone.worldY += temp[1] * mixY;
|
||||
}
|
||||
|
||||
if (mixScaleX != 0) {
|
||||
var s:Float = (Math.sqrt(ta * ta + tc * tc) - 1 + _data.offsetScaleX) * mixScaleX + 1;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
|
||||
if (mixScaleY != 0) {
|
||||
var s:Float = (Math.sqrt(tb * tb + td * td) - 1 + _data.offsetScaleY) * mixScaleY + 1;
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
}
|
||||
|
||||
if (mixShearY > 0) {
|
||||
var r = Math.atan2(td, tb) - Math.atan2(tc, ta);
|
||||
if (r > MathUtils.PI)
|
||||
r -= MathUtils.PI2;
|
||||
else if (r < -MathUtils.PI)
|
||||
r += MathUtils.PI2;
|
||||
var b = bone.b;
|
||||
var d = bone.d;
|
||||
r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
|
||||
var s = Math.sqrt(b * b + d * d);
|
||||
bone.b = Math.cos(r) * s;
|
||||
bone.d = Math.sin(r) * s;
|
||||
}
|
||||
|
||||
bone.updateAppliedTransform();
|
||||
skeleton._updateCache.push(this);
|
||||
for (i in 0...boneCount) {
|
||||
var bone = bones[i].bone;
|
||||
skeleton.sortReset(bone.children);
|
||||
skeleton.constrained(bone);
|
||||
}
|
||||
for (i in 0...boneCount)
|
||||
bones[i].bone.sorted = worldTarget;
|
||||
}
|
||||
|
||||
public function applyAbsoluteLocal():Void {
|
||||
for (bone in bones) {
|
||||
var rotation:Float = bone.arotation;
|
||||
if (mixRotate != 0) rotation += (target.arotation - rotation + _data.offsetRotation) * mixRotate;
|
||||
|
||||
var x:Float = bone.ax, y:Float = bone.ay;
|
||||
x += (target.ax - x + _data.offsetX) * mixX;
|
||||
y += (target.ay - y + _data.offsetY) * mixY;
|
||||
|
||||
var scaleX:Float = bone.ascaleX, scaleY:Float = bone.ascaleY;
|
||||
if (mixScaleX != 0 && scaleX != 0) {
|
||||
scaleX = (scaleX + (target.ascaleX - scaleX + _data.offsetScaleX) * mixScaleX) / scaleX;
|
||||
}
|
||||
if (mixScaleY != 0 && scaleY != 0) {
|
||||
scaleY = (scaleY + (target.ascaleY - scaleY + _data.offsetScaleY) * mixScaleX) / scaleY;
|
||||
}
|
||||
|
||||
var shearY:Float = bone.ashearY;
|
||||
if (mixShearY != 0) shearY += (target.ashearY - shearY + _data.offsetShearY) * mixShearY;
|
||||
|
||||
bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyRelativeLocal():Void {
|
||||
for (bone in bones) {
|
||||
var rotation:Float = bone.arotation + (target.arotation + _data.offsetRotation) * mixRotate;
|
||||
var x:Float = bone.ax + (target.ax + _data.offsetX) * mixX;
|
||||
var y:Float = bone.ay + (target.ay + _data.offsetY) * mixY;
|
||||
var scaleX:Float = bone.ascaleX * (((target.ascaleX - 1 + _data.offsetScaleX) * mixScaleX) + 1);
|
||||
var scaleY:Float = bone.ascaleY * (((target.ascaleY - 1 + _data.offsetScaleY) * mixScaleY) + 1);
|
||||
var shearY:Float = bone.ashearY + (target.ashearY + _data.offsetShearY) * mixShearY;
|
||||
bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
|
||||
}
|
||||
}
|
||||
|
||||
/** The transform constraint's setup pose data. */
|
||||
public var data(get, never):TransformConstraintData;
|
||||
|
||||
private function get_data():TransformConstraintData {
|
||||
return _data;
|
||||
}
|
||||
|
||||
/** The bones that will be modified by this transform constraint. */
|
||||
public var bones(get, never):Array<Bone>;
|
||||
|
||||
private function get_bones():Array<Bone> {
|
||||
return _bones;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _data.name != null ? _data.name : "TransformConstraint?";
|
||||
override public function isSourceActive () {
|
||||
return source.active;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,27 +31,34 @@ package spine;
|
||||
|
||||
/**
|
||||
* Stores the setup pose for a spine.TransformConstraint.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-transform-constraints Transform constraints in the Spine User Guide
|
||||
*/
|
||||
class TransformConstraintData extends ConstraintData {
|
||||
private var _bones:Array<BoneData> = new Array<BoneData>();
|
||||
class TransformConstraintData extends ConstraintData<TransformConstraint, TransformConstraintPose> {
|
||||
/** The bones that will be modified by this transform constraint. */
|
||||
public final bones:Array<BoneData> = new Array<BoneData>();
|
||||
|
||||
/** The bone whose world transform will be copied to the constrained bones. */
|
||||
public var source(default, set):BoneData;
|
||||
|
||||
public var offsets:Array<Float> = [for (_ in 0...6) 0.0];
|
||||
|
||||
/** Reads the source bone's local transform instead of its world transform. */
|
||||
public var localSource = false;
|
||||
|
||||
/** Sets the constrained bones' local transforms instead of their world transforms. */
|
||||
public var localTarget = false;
|
||||
|
||||
/** Adds the source bone transform to the constrained bones instead of setting it absolutely. */
|
||||
public var additive = false;
|
||||
|
||||
/** Prevents constrained bones from exceeding the ranged defined by ToProperty.offset and ToProperty.max. */
|
||||
public var clamp = false;
|
||||
|
||||
/** The mapping of transform properties to other transform properties. */
|
||||
public final properties = new Array<FromProperty>();
|
||||
|
||||
/** The target bone whose world transform will be copied to the constrained bones. */
|
||||
public var target:BoneData;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
public var mixX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
public var mixY:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. */
|
||||
public var mixScaleX:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. */
|
||||
public var mixScaleY:Float = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. */
|
||||
public var mixShearY:Float = 0;
|
||||
/** An offset added to the constrained bone rotation. */
|
||||
public var offsetRotation:Float = 0;
|
||||
/** An offset added to the constrained bone X translation. */
|
||||
@ -67,16 +74,286 @@ class TransformConstraintData extends ConstraintData {
|
||||
public var relative:Bool = false;
|
||||
public var local:Bool = false;
|
||||
|
||||
public function new(name:String) {
|
||||
super(name, 0, false);
|
||||
public function new (name:String) {
|
||||
super(name, new TransformConstraintPose());
|
||||
}
|
||||
|
||||
/**
|
||||
* The bones that will be modified by this transform constraint.
|
||||
*/
|
||||
public var bones(get, never):Array<BoneData>;
|
||||
public function create (skeleton:Skeleton) {
|
||||
return new TransformConstraint(this, skeleton);
|
||||
}
|
||||
|
||||
private function get_bones():Array<BoneData> {
|
||||
return _bones;
|
||||
public function set_source (source:BoneData):BoneData {
|
||||
if (source == null) throw new SpineException("source cannot be null.");
|
||||
this.source = source;
|
||||
return source;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone rotation. */
|
||||
public function getOffsetRotation ():Float {
|
||||
return offsets[0];
|
||||
}
|
||||
|
||||
public function setOffsetRotation (offsetRotation:Float):Float {
|
||||
offsets[0] = offsetRotation;
|
||||
return offsetRotation;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone X translation. */
|
||||
public function getOffsetX ():Float {
|
||||
return offsets[1];
|
||||
}
|
||||
|
||||
public function setOffsetX (offsetX:Float):Float {
|
||||
offsets[1] = offsetX;
|
||||
return offsetX;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone Y translation. */
|
||||
public function getOffsetY ():Float {
|
||||
return offsets[2];
|
||||
}
|
||||
|
||||
public function setOffsetY (offsetY:Float):Float {
|
||||
offsets[2] = offsetY;
|
||||
return offsetY;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone scaleX. */
|
||||
public function getOffsetScaleX ():Float {
|
||||
return offsets[3];
|
||||
}
|
||||
|
||||
public function setOffsetScaleX (offsetScaleX:Float):Float {
|
||||
offsets[3] = offsetScaleX;
|
||||
return offsetScaleX;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone scaleY. */
|
||||
public function getOffsetScaleY ():Float {
|
||||
return offsets[4];
|
||||
}
|
||||
|
||||
public function setOffsetScaleY (offsetScaleY:Float):Float {
|
||||
offsets[4] = offsetScaleY;
|
||||
return offsetScaleY;
|
||||
}
|
||||
|
||||
/** An offset added to the constrained bone shearY. */
|
||||
public function getOffsetShearY ():Float {
|
||||
return offsets[5];
|
||||
}
|
||||
|
||||
public function setOffsetShearY (offsetShearY:Float):Float {
|
||||
offsets[5] = offsetShearY;
|
||||
return offsetShearY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Source property for a {@link TransformConstraint}. */
|
||||
abstract class FromProperty {
|
||||
/** The value of this property that corresponds to ToProperty.offset. */
|
||||
public var offset:Float;
|
||||
|
||||
/** Constrained properties. */
|
||||
public final to = new Array<ToProperty>();
|
||||
|
||||
/** Reads this property from the specified bone. */
|
||||
abstract public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float;
|
||||
}
|
||||
|
||||
/** Constrained property for a TransformConstraint. */
|
||||
abstract class ToProperty {
|
||||
/** The value of this property that corresponds to FromProperty.offset. */
|
||||
public var offset:Float;
|
||||
|
||||
/** The maximum value of this property when is clamped (TransformConstraintData.clamp). */
|
||||
public var max:Float;
|
||||
|
||||
/** The scale of the FromProperty value in relation to this property. */
|
||||
public var scale:Float;
|
||||
|
||||
/** Reads the mix for this property from the specified pose. */
|
||||
abstract public function mix (pose:TransformConstraintPose):Float;
|
||||
|
||||
/** Applies the value to this property. */
|
||||
abstract public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void;
|
||||
}
|
||||
|
||||
class FromRotate extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
if (local) return source.rotation + offsets[0];
|
||||
var value = Math.atan2(source.c, source.a) * MathUtils.radDeg
|
||||
+ (source.a * source.d - source.b * source.c > 0 ? offsets[0] : -offsets[0]);
|
||||
if (value < 0) value += 360;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class ToRotate extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixRotate;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (!additive) value -= bone.rotation;
|
||||
bone.rotation += value * pose.mixRotate;
|
||||
} else {
|
||||
var a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||
value *= MathUtils.degRad;
|
||||
if (!additive) value -= Math.atan2(c, a);
|
||||
if (value > Math.PI)
|
||||
value -= MathUtils.PI2;
|
||||
else if (value < -Math.PI) //
|
||||
value += MathUtils.PI2;
|
||||
value *= pose.mixRotate;
|
||||
var cos = Math.cos(value), sin = Math.sin(value);
|
||||
bone.a = cos * a - sin * c;
|
||||
bone.b = cos * b - sin * d;
|
||||
bone.c = sin * a + cos * c;
|
||||
bone.d = sin * b + cos * d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FromX extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
return local ? source.x + offsets[1] : offsets[1] * source.a + offsets[2] * source.b + source.worldX;
|
||||
}
|
||||
}
|
||||
|
||||
class ToX extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixX;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (!additive) value -= bone.x;
|
||||
bone.x += value * pose.mixX;
|
||||
} else {
|
||||
if (!additive) value -= bone.worldX;
|
||||
bone.worldX += value * pose.mixX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FromY extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
return local ? source.y + offsets[2] : offsets[1] * source.c + offsets[2] * source.d + source.worldY;
|
||||
}
|
||||
}
|
||||
|
||||
class ToY extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixY;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (!additive) value -= bone.y;
|
||||
bone.y += value * pose.mixY;
|
||||
} else {
|
||||
if (!additive) value -= bone.worldY;
|
||||
bone.worldY += value * pose.mixY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FromScaleX extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
return (local ? source.scaleX : Math.sqrt(source.a * source.a + source.c * source.c)) + offsets[3];
|
||||
}
|
||||
}
|
||||
|
||||
class ToScaleX extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixScaleX;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (additive)
|
||||
bone.scaleX *= 1 + ((value - 1) * pose.mixScaleX);
|
||||
else if (bone.scaleX != 0) //
|
||||
bone.scaleX = 1 + (value / bone.scaleX - 1) * pose.mixScaleX;
|
||||
} else {
|
||||
var s:Float;
|
||||
if (additive)
|
||||
s = 1 + (value - 1) * pose.mixScaleX;
|
||||
else {
|
||||
s = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
|
||||
if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleX;
|
||||
}
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FromScaleY extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
return (local ? source.scaleY : Math.sqrt(source.b * source.b + source.d * source.d)) + offsets[4];
|
||||
}
|
||||
}
|
||||
|
||||
class ToScaleY extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixScaleY;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (additive)
|
||||
bone.scaleY *= 1 + ((value - 1) * pose.mixScaleY);
|
||||
else if (bone.scaleY != 0) //
|
||||
bone.scaleY = 1 + (value / bone.scaleY - 1) * pose.mixScaleY;
|
||||
} else {
|
||||
var s:Float;
|
||||
if (additive)
|
||||
s = 1 + (value - 1) * pose.mixScaleY;
|
||||
else {
|
||||
s = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
|
||||
if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleY;
|
||||
}
|
||||
bone.b *= s;
|
||||
bone.d *= s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FromShearY extends FromProperty {
|
||||
public function value (source:BonePose, local:Bool, offsets:Array<Float>):Float {
|
||||
return (local ? source.shearY : (Math.atan2(source.d, source.b) - Math.atan2(source.c, source.a)) * MathUtils.radDeg - 90) + offsets[5];
|
||||
}
|
||||
}
|
||||
|
||||
class ToShearY extends ToProperty {
|
||||
public function mix (pose:TransformConstraintPose):Float {
|
||||
return pose.mixShearY;
|
||||
}
|
||||
|
||||
public function apply (pose:TransformConstraintPose, bone:BonePose, value:Float, local:Bool, additive:Bool):Void {
|
||||
if (local) {
|
||||
if (!additive) value -= bone.shearY;
|
||||
bone.shearY += value * pose.mixShearY;
|
||||
} else {
|
||||
var b = bone.b, d = bone.d, by = Math.atan2(d, b);
|
||||
value = (value + 90) * MathUtils.degRad;
|
||||
if (additive)
|
||||
value -= Math.PI / 2;
|
||||
else {
|
||||
value -= by - Math.atan2(bone.c, bone.a);
|
||||
if (value > Math.PI)
|
||||
value -= MathUtils.PI2;
|
||||
else if (value < -Math.PI) //
|
||||
value += MathUtils.PI2;
|
||||
}
|
||||
value = by + value * pose.mixShearY;
|
||||
var s = Math.sqrt(b * b + d * d);
|
||||
bone.b = Math.cos(value) * s;
|
||||
bone.d = Math.sin(value) * s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
spine-haxe/spine-haxe/spine/TransformConstraintPose.hx
Normal file
65
spine-haxe/spine-haxe/spine/TransformConstraintPose.hx
Normal file
@ -0,0 +1,65 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine;
|
||||
|
||||
/** Stores a pose for a transform constraint. */
|
||||
class TransformConstraintPose implements Pose<TransformConstraintPose> {
|
||||
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
||||
public var mixRotate = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
||||
|
||||
public var mixX = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
||||
|
||||
public var mixY = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. */
|
||||
|
||||
public var mixScaleX = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. */
|
||||
|
||||
public var mixScaleY = 0.;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. */
|
||||
|
||||
public var mixShearY = 0.;
|
||||
|
||||
public function new () {
|
||||
}
|
||||
|
||||
public function set (pose:TransformConstraintPose) {
|
||||
mixRotate = pose.mixRotate;
|
||||
mixX = pose.mixX;
|
||||
mixY = pose.mixY;
|
||||
mixScaleX = pose.mixScaleX;
|
||||
mixScaleY = pose.mixScaleY;
|
||||
mixShearY = pose.mixShearY;
|
||||
}
|
||||
|
||||
}
|
||||
@ -30,16 +30,7 @@
|
||||
package spine;
|
||||
|
||||
/** The interface for items updated by spine.Skeleton.updateWorldTransform(). */
|
||||
interface Updatable {
|
||||
interface Update {
|
||||
/** @param physics Determines how physics and other non-deterministic updates are applied. */
|
||||
function update(physics:Physics):Void;
|
||||
|
||||
/** Returns false when this item won't be updated by
|
||||
* spine.Skeleton.updateWorldTransform() because a skin is required and the
|
||||
* active skin does not contain this item.
|
||||
* @see spine.Skin.getBones()
|
||||
* @see spine.Skin.getConstraints()
|
||||
* @see spine.BoneData.getSkinRequired()
|
||||
* @see spine.ConstraintData.getSkinRequired() */
|
||||
function isActive():Bool;
|
||||
function update(skeleton:Skeleton, physics:Physics):Void;
|
||||
}
|
||||
@ -43,7 +43,7 @@ class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
||||
private var slotIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.alpha + "|" + slotIndex]);
|
||||
super(frameCount, bezierCount, Property.alpha + "|" + slotIndex);
|
||||
this.slotIndex = slotIndex;
|
||||
}
|
||||
|
||||
@ -55,15 +55,15 @@ class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var color:Color = slot.color;
|
||||
var slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
|
||||
var color = (appliedPose ? slot.applied : slot.pose).color;
|
||||
if (time < frames[0]) {
|
||||
var setup:Color = slot.data.color;
|
||||
var setup:Color = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
color.a = setup.a;
|
||||
@ -78,7 +78,7 @@ class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
||||
color.a = a;
|
||||
} else {
|
||||
if (blend == MixBlend.setup)
|
||||
color.a = slot.data.color.a;
|
||||
color.a = slot.data.setup.color.a;
|
||||
color.a += (a - color.a) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,37 +29,52 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import haxe.ds.IntMap;
|
||||
import haxe.ds.StringMap;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Stores a list of timelines to animate a skeleton's pose over time. */
|
||||
class Animation {
|
||||
private var _name:String;
|
||||
private var _timelines:Array<Timeline>;
|
||||
private var _timelineIds:StringMap<Bool> = new StringMap<Bool>();
|
||||
|
||||
/** The animation's name, which is unique across all animations in the skeleton. */
|
||||
public final name:String;
|
||||
|
||||
/** The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is
|
||||
* used to know when it has completed and when it should loop back to the start. */
|
||||
* used to know when it has completed and when it should loop back to the start. */
|
||||
public var duration:Float = 0;
|
||||
|
||||
public var timelines:Array<Timeline>;
|
||||
public final timelineIds:StringMap<Bool> = new StringMap<Bool>();
|
||||
public final bones:Array<Int>;
|
||||
|
||||
public function new(name:String, timelines:Array<Timeline>, duration:Float) {
|
||||
if (name == null)
|
||||
throw new SpineException("name cannot be null.");
|
||||
_name = name;
|
||||
setTimelines(timelines);
|
||||
if (name == null) throw new SpineException("name cannot be null.");
|
||||
this.name = name;
|
||||
this.duration = duration;
|
||||
var n = timelines.length << 1;
|
||||
timelineIds = new StringMap<Bool>();
|
||||
bones = new Array<Int>();
|
||||
setTimelines(timelines);
|
||||
}
|
||||
|
||||
public function setTimelines(timelines:Array<Timeline>) {
|
||||
if (timelines == null)
|
||||
throw new SpineException("timelines cannot be null.");
|
||||
_timelines = timelines;
|
||||
_timelineIds = new StringMap<Bool>();
|
||||
if (timelines == null) throw new SpineException("timelines cannot be null.");
|
||||
this.timelines = timelines;
|
||||
|
||||
timelineIds.clear();
|
||||
bones.resize(0);
|
||||
var boneSet = new IntMap<Bool>();
|
||||
for (timeline in timelines) {
|
||||
var ids:Array<String> = timeline.propertyIds;
|
||||
for (id in ids) {
|
||||
_timelineIds.set(id, true);
|
||||
for (id in ids) timelineIds.set(id, true);
|
||||
if (Std.isOfType(timeline, BoneTimeline)) {
|
||||
var boneTimeline = cast(timeline, BoneTimeline);
|
||||
var boneIndex = boneTimeline.getBoneIndex();
|
||||
if (!boneSet.exists(boneIndex)) {
|
||||
boneSet.set(boneIndex, true);
|
||||
bones.push(boneIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,14 +82,14 @@ class Animation {
|
||||
/** Returns true if this animation contains a timeline with any of the specified property IDs. */
|
||||
public function hasTimeline(ids:Array<String>):Bool {
|
||||
for (id in ids) {
|
||||
if (_timelineIds.exists(id))
|
||||
if (timelineIds.exists(id))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Applies the animation's timelines to the specified skeleton.
|
||||
*
|
||||
*
|
||||
* See Timeline.apply().
|
||||
* @param skeleton The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton
|
||||
* components the timelines may change.
|
||||
@ -94,36 +109,16 @@ class Animation {
|
||||
* @param direction Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions,
|
||||
* such as DrawOrderTimeline or AttachmentTimeline. */
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, loop:Bool, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
if (skeleton == null)
|
||||
throw new SpineException("skeleton cannot be null.");
|
||||
direction:MixDirection, appliedPose:Bool):Void {
|
||||
if (skeleton == null) throw new SpineException("skeleton cannot be null.");
|
||||
|
||||
if (loop && duration != 0) {
|
||||
time %= duration;
|
||||
if (lastTime > 0)
|
||||
lastTime %= duration;
|
||||
if (lastTime > 0) lastTime %= duration;
|
||||
}
|
||||
|
||||
for (timeline in timelines) {
|
||||
timeline.apply(skeleton, lastTime, time, events, alpha, blend, direction);
|
||||
timeline.apply(skeleton, lastTime, time, events, alpha, blend, direction, appliedPose);
|
||||
}
|
||||
}
|
||||
|
||||
/** The animation's name, which is unique across all animations in the skeleton. */
|
||||
public var name(get, never):String;
|
||||
|
||||
private function get_name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/** If the returned array or the timelines it contains are modified, setTimelines() must be called. */
|
||||
public var timelines(get, never):Array<Timeline>;
|
||||
|
||||
private function get_timelines():Array<Timeline> {
|
||||
return _timelines;
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +298,7 @@ class AnimationState {
|
||||
for (slot in skeleton.slots) {
|
||||
if (slot.attachmentState == setupState) {
|
||||
var attachmentName:String = slot.data.attachmentName;
|
||||
slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slot.data.index, attachmentName);
|
||||
slot.pose.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slot.data.index, attachmentName);
|
||||
}
|
||||
}
|
||||
unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
|
||||
@ -437,28 +437,29 @@ class AnimationState {
|
||||
timelinesRotation[i] = 0;
|
||||
|
||||
if (alpha == 1) {
|
||||
timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn);
|
||||
timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var bone = skeleton.bones[timeline.boneIndex];
|
||||
if (!bone.active)
|
||||
return;
|
||||
var pose = bone.pose, setup = bone.data.setup;
|
||||
var frames = timeline.frames;
|
||||
var r1:Float = 0, r2:Float = 0;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.rotation = bone.data.rotation;
|
||||
pose.rotation = setup.rotation;
|
||||
default:
|
||||
return;
|
||||
case MixBlend.first:
|
||||
r1 = bone.rotation;
|
||||
r2 = bone.data.rotation;
|
||||
r1 = pose.rotation;
|
||||
r2 = setup.rotation;
|
||||
}
|
||||
} else {
|
||||
r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
|
||||
r2 = bone.data.rotation + timeline.getCurveValue(time);
|
||||
r1 = blend == MixBlend.setup ? setup.rotation : pose.rotation;
|
||||
r2 = setup.rotation + timeline.getCurveValue(time);
|
||||
}
|
||||
|
||||
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||
@ -492,11 +493,11 @@ class AnimationState {
|
||||
timelinesRotation[i] = total;
|
||||
}
|
||||
timelinesRotation[i + 1] = diff;
|
||||
bone.rotation = r1 + total * alpha;
|
||||
pose.rotation = r1 + total * alpha;
|
||||
}
|
||||
|
||||
private function setAttachment(skeleton:Skeleton, slot:Slot, attachmentName:String, attachments:Bool):Void {
|
||||
slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slot.data.index, attachmentName);
|
||||
slot.pose.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slot.data.index, attachmentName);
|
||||
if (attachments)
|
||||
slot.attachmentState = unkeyedState + CURRENT;
|
||||
}
|
||||
@ -629,9 +630,9 @@ class AnimationState {
|
||||
return setAnimation(trackIndex, animation, loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never
|
||||
* applied to a skeleton, it is replaced (not mixed from).
|
||||
/** Sets the current animation for a track, discarding any queued animations.
|
||||
* If the formerly current track entry is for the same animation and was never applied to a skeleton, it is replaced (not mixed
|
||||
* from).
|
||||
* @param loop If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its
|
||||
* duration. In either case spine.animation.TrackEntry.getTrackEnd() determines when the track is cleared.
|
||||
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
|
||||
@ -643,7 +644,7 @@ class AnimationState {
|
||||
var interrupt:Bool = true;
|
||||
var current:TrackEntry = expandToIndex(trackIndex);
|
||||
if (current != null) {
|
||||
if (current.nextTrackLast == -1) {
|
||||
if (current.nextTrackLast == -1 && current.animation == animation) {
|
||||
// Don't mix from an entry that was never applied.
|
||||
tracks[trackIndex] = current.mixingFrom;
|
||||
queue.interrupt(current);
|
||||
@ -673,8 +674,7 @@ class AnimationState {
|
||||
return addAnimation(trackIndex, animation, loop, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
|
||||
/** Adds an animation to be played after the current or last queued animation for a track. If the track has no entries, this is
|
||||
* equivalent to calling spine.animation.AnimationState.setAnimation().
|
||||
* @param delay If > 0, sets spine.animation.TrackEntry.getDelay(). If <= 0, the delay set is the duration of the previous track entry
|
||||
* minus any mix duration (from the spine.animation.AnimationStateData) plus the specified delay (ie the mix
|
||||
@ -726,7 +726,9 @@ class AnimationState {
|
||||
* animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
|
||||
* from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new
|
||||
* animation.
|
||||
*/
|
||||
*
|
||||
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
|
||||
* Runtimes Guide. */
|
||||
public function setEmptyAnimation(trackIndex:Int, mixDuration:Float):TrackEntry {
|
||||
var entry:TrackEntry = setAnimation(trackIndex, emptyAnimation, false);
|
||||
entry.mixDuration = mixDuration;
|
||||
@ -736,10 +738,12 @@ class AnimationState {
|
||||
|
||||
/**
|
||||
* Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
|
||||
* spine.animation.TrackEntry.getMixDuration(). If the track is empty, it is equivalent to calling
|
||||
* spine.animation.TrackEntry.getMixDuration(). If the track has no entries,, it is equivalent to calling
|
||||
* spine.animation.AnimationState.setEmptyAnimation().
|
||||
*
|
||||
* See spine.animation.AnimationState.setEmptyAnimation().
|
||||
* See spine.animation.AnimationState.setEmptyAnimation() and
|
||||
* <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
|
||||
* Runtimes Guide.
|
||||
* @param delay If > 0, sets spine.animation.TrackEntry.getDelay(). If <= 0, the delay set is the duration of the previous track entry
|
||||
* minus any mix duration plus the specified delay (ie the mix ends at (delay = 0) or
|
||||
* before (delay < 0) the previous track entry duration). If the previous entry is looping, its next
|
||||
@ -755,10 +759,10 @@ class AnimationState {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix
|
||||
* duration.
|
||||
*/
|
||||
/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
|
||||
*
|
||||
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
|
||||
* Runtimes Guide. */
|
||||
public function setEmptyAnimations(mixDuration:Float):Void {
|
||||
var oldDrainDisabled:Bool = queue.drainDisabled;
|
||||
queue.drainDisabled = true;
|
||||
|
||||
@ -41,7 +41,7 @@ class AttachmentTimeline extends Timeline implements SlotTimeline {
|
||||
public var attachmentNames:Array<String>;
|
||||
|
||||
public function new(frameCount:Int, slotIndex:Int) {
|
||||
super(frameCount, [Property.attachment + "|" + slotIndex]);
|
||||
super(frameCount, Property.attachment + "|" + slotIndex);
|
||||
this.slotIndex = slotIndex;
|
||||
attachmentNames = new Array<String>();
|
||||
attachmentNames.resize(frameCount);
|
||||
@ -63,30 +63,22 @@ class AttachmentTimeline extends Timeline implements SlotTimeline {
|
||||
attachmentNames[frame] = attachmentName;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
var pose = appliedPose ? slot.applied : slot.pose;
|
||||
|
||||
if (direction == MixDirection.mixOut) {
|
||||
if (blend == MixBlend.setup) {
|
||||
setAttachment(skeleton, slot, slot.data.attachmentName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (time < frames[0]) {
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) {
|
||||
setAttachment(skeleton, slot, slot.data.attachmentName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setAttachment(skeleton, slot, attachmentNames[Timeline.search1(frames, time)]);
|
||||
if (blend == MixBlend.setup) setAttachment(skeleton, pose, slot.data.attachmentName);
|
||||
} else if (time < frames[0]) {
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) setAttachment(skeleton, pose, slot.data.attachmentName);
|
||||
} else
|
||||
setAttachment(skeleton, pose, attachmentNames[Timeline.search1(frames, time)]);
|
||||
}
|
||||
|
||||
private function setAttachment(skeleton:Skeleton, slot:Slot, attachmentName:String):Void {
|
||||
slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName);
|
||||
private function setAttachment(skeleton:Skeleton, pose:SlotPose, attachmentName:String):Void {
|
||||
pose.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,5 +32,5 @@ package spine.animation;
|
||||
/** An interface for timelines which change the property of a bone. */
|
||||
interface BoneTimeline {
|
||||
/** The index of the bone in spine.Skeleton.getBones() that will be changed when this timeline is applied. */
|
||||
function getBoneIndex():Int;
|
||||
public function getBoneIndex():Int;
|
||||
}
|
||||
|
||||
52
spine-haxe/spine-haxe/spine/animation/BoneTimeline1.hx
Normal file
52
spine-haxe/spine-haxe/spine/animation/BoneTimeline1.hx
Normal file
@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
abstract class BoneTimeline1 extends CurveTimeline1 implements BoneTimeline {
|
||||
public final boneIndex:Int;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int, property:Property) {
|
||||
super(frameCount, bezierCount, property + "|" + boneIndex);
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public function getBoneIndex () {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
public function apply (skeleton: Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) apply1(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction);
|
||||
}
|
||||
|
||||
abstract function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection):Void;
|
||||
}
|
||||
@ -29,17 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** The base class for a spine.animation.CurveTimeline which sets two properties. */
|
||||
class CurveTimeline2 extends CurveTimeline {
|
||||
/** The base class for a spine.animation.CurveTimeline that is a spine.animation.BoneTimeline and sets two properties. */
|
||||
abstract class BoneTimeline2 extends CurveTimeline implements BoneTimeline {
|
||||
private static inline var ENTRIES:Int = 3;
|
||||
private static inline var VALUE1:Int = 1;
|
||||
private static inline var VALUE2:Int = 2;
|
||||
|
||||
/** @param frameCount The number of frames in the timeline.
|
||||
* @param bezierCount The maximum number of Bezier curves. See spine.animation.CurveTimeline.shrink().
|
||||
* @param propertyIds Array of unique identifiers for the properties the timeline modifies. */
|
||||
public function new(frameCount:Int, bezierCount:Int, propertyIds:Array<String>) {
|
||||
super(frameCount, bezierCount, propertyIds);
|
||||
public final boneIndex:Int;
|
||||
|
||||
/** @param bezierCount The maximum number of Bezier curves. See spine.animation.CurveTimeline.shrink(). */
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int, property1:Property, property2:Property) {
|
||||
super(frameCount, bezierCount, property1 + "|" + boneIndex, property2 + "|" + boneIndex);
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
@ -55,4 +56,17 @@ class CurveTimeline2 extends CurveTimeline {
|
||||
frames[frame + VALUE1] = value1;
|
||||
frames[frame + VALUE2] = value2;
|
||||
}
|
||||
|
||||
public function getBoneIndex () {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
public function apply (skeleton: Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) apply1(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction);
|
||||
}
|
||||
|
||||
abstract function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection):Void;
|
||||
}
|
||||
35
spine-haxe/spine-haxe/spine/animation/ConstraintTimeline.hx
Normal file
35
spine-haxe/spine-haxe/spine/animation/ConstraintTimeline.hx
Normal file
@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
interface ConstraintTimeline {
|
||||
/** The index of the constraint in spine.Skeleton.constraints that will be changed when this timeline is applied. */
|
||||
public function getConstraintIndex():Int;
|
||||
}
|
||||
43
spine-haxe/spine-haxe/spine/animation/ConstraintTimeline1.hx
Normal file
43
spine-haxe/spine-haxe/spine/animation/ConstraintTimeline1.hx
Normal file
@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
abstract class ConstraintTimeline1 extends CurveTimeline1 implements ConstraintTimeline {
|
||||
public final constraintIndex:Int;
|
||||
|
||||
public function new (frameCount:Int, bezierCount:Int, constraintIndex:Int, property:Property) {
|
||||
super(frameCount, bezierCount, property + "|" + constraintIndex);
|
||||
this.constraintIndex = constraintIndex;
|
||||
}
|
||||
|
||||
public function getConstraintIndex () {
|
||||
return constraintIndex;
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@
|
||||
package spine.animation;
|
||||
|
||||
/** The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. */
|
||||
class CurveTimeline extends Timeline {
|
||||
abstract class CurveTimeline extends Timeline {
|
||||
private static inline var LINEAR:Int = 0;
|
||||
private static inline var STEPPED:Int = 1;
|
||||
private static inline var BEZIER:Int = 2;
|
||||
@ -40,8 +40,8 @@ class CurveTimeline extends Timeline {
|
||||
|
||||
/** @param bezierCount The maximum number of Bezier curves. See CurveTimeline.shrink().
|
||||
* @param propertyIds Unique identifiers for the properties the timeline modifies. */
|
||||
public function new(frameCount:Int, bezierCount:Int, propertyIds:Array<String>) {
|
||||
super(frameCount, propertyIds);
|
||||
public function new(frameCount:Int, bezierCount:Int, propertyIds:...String) {
|
||||
super(frameCount, ...propertyIds);
|
||||
curves = new Array<Float>();
|
||||
curves.resize(frameCount + bezierCount * BEZIER_SIZE);
|
||||
curves[frameCount - 1] = STEPPED;
|
||||
|
||||
@ -30,15 +30,15 @@
|
||||
package spine.animation;
|
||||
|
||||
/** The base class for a spine.animation.CurveTimeline that sets one property. */
|
||||
class CurveTimeline1 extends CurveTimeline {
|
||||
abstract class CurveTimeline1 extends CurveTimeline {
|
||||
private static inline var ENTRIES:Int = 2;
|
||||
private static inline var VALUE:Int = 1;
|
||||
|
||||
/** @param frameCount The number of frames in the timeline.
|
||||
* @param bezierCount The maximum number of Bezier curves. See spine.animation.CurveTimeline.shrink().
|
||||
* @param propertyIds Unique identifiers for the properties the timeline modifies. */
|
||||
public function new(frameCount:Int, bezierCount:Int, propertyIds:Array<String>) {
|
||||
super(frameCount, bezierCount, propertyIds);
|
||||
public function new(frameCount:Int, bezierCount:Int, propertyId:String) {
|
||||
super(frameCount, bezierCount, propertyId);
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
@ -73,75 +73,69 @@ class CurveTimeline1 extends CurveTimeline {
|
||||
return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value);
|
||||
case CurveTimeline.STEPPED:
|
||||
return frames[i + VALUE];
|
||||
default:
|
||||
return getBezierValue(time, i, VALUE, curveType - CurveTimeline.BEZIER);
|
||||
}
|
||||
return getBezierValue(time, i, VALUE, curveType - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
public function getRelativeValue (time:Float, alpha:Float, blend: MixBlend, current:Float, setup:Float):Float {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
case MixBlend.setup: return setup;
|
||||
case MixBlend.first: return current + (setup - current) * alpha;
|
||||
default: return current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
var value:Float = getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup + value * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
value += setup - current;
|
||||
case MixBlend.setup: return setup + value * alpha;
|
||||
case MixBlend.first, MixBlend.replace: return current + (value + setup - current) * alpha;
|
||||
default: return current + value * alpha; // MixBlend.add
|
||||
}
|
||||
return current + value * alpha;
|
||||
}
|
||||
|
||||
public function getAbsoluteValue (time:Float, alpha:Float, blend: MixBlend, current:Float, setup:Float):Float {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
case MixBlend.setup: return setup;
|
||||
case MixBlend.first: return current + (setup - current) * alpha;
|
||||
default: return current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
var value:Float = getCurveValue(time);
|
||||
if (blend == MixBlend.setup) return setup + (value - setup) * alpha;
|
||||
return current + (value - current) * alpha;
|
||||
switch (blend) {
|
||||
case MixBlend.setup: return setup + (value - setup) * alpha;
|
||||
case MixBlend.first, MixBlend.replace: return current + (value - current) * alpha;
|
||||
default: return current + value * alpha; // MixBlend.add
|
||||
}
|
||||
}
|
||||
|
||||
public function getAbsoluteValue2 (time:Float, alpha:Float, blend: MixBlend, current:Float, setup:Float, value:Float):Float {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
case MixBlend.setup: return setup;
|
||||
case MixBlend.first: return current + (setup - current) * alpha;
|
||||
default: current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
if (blend == MixBlend.setup) return setup + (value - setup) * alpha;
|
||||
return current + (value - current) * alpha;
|
||||
switch (blend) {
|
||||
case MixBlend.setup: return setup + (value - setup) * alpha;
|
||||
case MixBlend.first, MixBlend.replace: return current + (value - current) * alpha;
|
||||
default: return current + value * alpha; // MixBlend.add
|
||||
}
|
||||
}
|
||||
|
||||
public function getScaleValue (time:Float, alpha:Float, blend: MixBlend, direction: MixDirection, current:Float, setup:Float):Float {
|
||||
var frames:Array<Float> = frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
case MixBlend.setup: return setup;
|
||||
case MixBlend.first: return current + (setup - current) * alpha;
|
||||
default: return current;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
var value:Float = getCurveValue(time) * setup;
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) return current + value - setup;
|
||||
return value;
|
||||
}
|
||||
if (alpha == 1) return blend == MixBlend.add ? current + value - setup : value;
|
||||
// Mixing out uses sign of setup or current pose, else use sign of key.
|
||||
if (direction == MixDirection.mixOut) {
|
||||
switch (blend) {
|
||||
|
||||
@ -37,19 +37,17 @@ import spine.Skeleton;
|
||||
import spine.Slot;
|
||||
|
||||
/** Changes a slot's spine.Slot.deform to deform a spine.attachments.VertexAttachment. */
|
||||
class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
public var slotIndex:Int = 0;
|
||||
|
||||
class DeformTimeline extends SlotCurveTimeline {
|
||||
/** The attachment that will be deformed.
|
||||
*
|
||||
*
|
||||
* @see spine.attachments.VertexAttachment.getTimelineAttachment() */
|
||||
public var attachment:VertexAttachment;
|
||||
public final attachment:VertexAttachment;
|
||||
|
||||
/** The vertices for each frame. */
|
||||
public var vertices:Array<Array<Float>>;
|
||||
public final vertices:Array<Array<Float>>;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int, attachment:VertexAttachment) {
|
||||
super(frameCount, bezierCount, [Property.deform + "|" + slotIndex + "|" + attachment.id]);
|
||||
super(frameCount, bezierCount, slotIndex, Property.deform + "|" + slotIndex + "|" + attachment.id);
|
||||
this.slotIndex = slotIndex;
|
||||
this.attachment = attachment;
|
||||
vertices = new Array<Array<Float>>();
|
||||
@ -60,10 +58,6 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
return frames.length;
|
||||
}
|
||||
|
||||
public function getSlotIndex():Int {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
/** Sets the time and vertices for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds.
|
||||
@ -145,110 +139,79 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
return y + (1 - y) * (time - x) / (frames[frame + getFrameEntries()] - x);
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
var slotAttachment:Attachment = slot.attachment;
|
||||
if (slotAttachment == null)
|
||||
return;
|
||||
if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).timelineAttachment != attachment)
|
||||
return;
|
||||
public function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) {
|
||||
if (!Std.isOfType(pose.attachment, VertexAttachment)) return;
|
||||
var vertexAttachment = cast(pose.attachment, VertexAttachment);
|
||||
if (vertexAttachment.timelineAttachment != attachment) return;
|
||||
|
||||
var deform:Array<Float> = slot.deform;
|
||||
if (deform.length == 0)
|
||||
blend = MixBlend.setup;
|
||||
var deform = pose.deform;
|
||||
if (deform.length == 0) blend = MixBlend.setup;
|
||||
|
||||
var vertexCount:Int = vertices[0].length;
|
||||
var i:Int, setupVertices:Array<Float>;
|
||||
var vertexCount = vertices[0].length;
|
||||
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
deform.resize(0);
|
||||
case MixBlend.setup: deform.resize(0);
|
||||
case MixBlend.first:
|
||||
if (alpha == 1) {
|
||||
deform.resize(0);
|
||||
return;
|
||||
}
|
||||
ArrayUtils.resize(deform, vertexCount, 0);
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += (setupVertices[i] - deform[i]) * alpha;
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets.
|
||||
} else { // Weighted deform offsets.
|
||||
alpha = 1 - alpha;
|
||||
for (i in 0...vertexCount) {
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayUtils.resize(deform, vertexCount, 0);
|
||||
var setup:Float;
|
||||
if (time >= frames[frames.length - 1]) {
|
||||
|
||||
if (time >= frames[frames.length - 1]) { // Time is after last frame.
|
||||
var lastVertices:Array<Float> = vertices[frames.length - 1];
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) {
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += lastVertices[i] - setupVertices[i];
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
} else { // Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += lastVertices[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i in 0...vertexCount) {
|
||||
} else
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] = lastVertices[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
setup = setupVertices[i];
|
||||
var setup = setupVertices[i];
|
||||
deform[i] = setup + (lastVertices[i] - setup) * alpha;
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
} else { // Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] = lastVertices[i] * alpha;
|
||||
}
|
||||
}
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
for (i in 0...vertexCount) {
|
||||
case MixBlend.first, MixBlend.replace: // Vertex positions or deform offsets, with alpha.
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += (lastVertices[i] - deform[i]) * alpha;
|
||||
}
|
||||
case MixBlend.add:
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += (lastVertices[i] - setupVertices[i]) * alpha;
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
} else { // Weighted deform offsets, alpha.
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] += lastVertices[i] * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,69 +221,63 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
var frame:Int = Timeline.search1(frames, time);
|
||||
var percent:Float = getCurvePercent(time, frame);
|
||||
var prevVertices:Array<Float> = vertices[frame], prev:Float;
|
||||
var prevVertices:Array<Float> = vertices[frame];
|
||||
var nextVertices:Array<Float> = vertices[frame + 1];
|
||||
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) {
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, no alpha.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
} else { // Weighted deform offsets, no alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] += prev + (nextVertices[i] - prev) * percent;
|
||||
}
|
||||
}
|
||||
} else if (percent == 0) {
|
||||
for (i in 0...vertexCount)
|
||||
deform[i] = prevVertices[i];
|
||||
} else {
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] = prev + (nextVertices[i] - prev) * percent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha.
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
setup = setupVertices[i];
|
||||
var prev = prevVertices[i], setup = setupVertices[i];
|
||||
deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
} else { // Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
|
||||
}
|
||||
}
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
case MixBlend.first, MixBlend.replace: // Vertex positions or deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha;
|
||||
}
|
||||
case MixBlend.add:
|
||||
var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
|
||||
if (vertexAttachment.bones == null) {
|
||||
if (vertexAttachment.bones == null) { // Unweighted vertex positions, with alpha.
|
||||
// Unweighted vertex positions, with alpha.
|
||||
setupVertices = vertexAttachment.vertices;
|
||||
var setupVertices = vertexAttachment.vertices;
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
|
||||
}
|
||||
} else {
|
||||
// Weighted deform offsets, with alpha.
|
||||
} else { // Weighted deform offsets, with alpha.
|
||||
for (i in 0...vertexCount) {
|
||||
prev = prevVertices[i];
|
||||
var prev = prevVertices[i];
|
||||
deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ class DrawOrderTimeline extends Timeline {
|
||||
public var drawOrders:Array<Array<Int>>;
|
||||
|
||||
public function new(frameCount:Int) {
|
||||
super(frameCount, [Std.string(Property.drawOrder)]);
|
||||
super(frameCount, Property.drawOrder);
|
||||
drawOrders = new Array<Array<Int>>();
|
||||
drawOrders.resize(frameCount);
|
||||
}
|
||||
@ -59,8 +59,9 @@ class DrawOrderTimeline extends Timeline {
|
||||
drawOrders[frame] = drawOrder;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var drawOrder:Array<Slot> = skeleton.drawOrder;
|
||||
var slots:Array<Slot> = skeleton.slots;
|
||||
var i:Int = 0, n:Int = slots.length;
|
||||
|
||||
@ -79,8 +79,7 @@ class EventQueue {
|
||||
}
|
||||
|
||||
public function drain():Void {
|
||||
if (drainDisabled)
|
||||
return; // Not reentrant.
|
||||
if (drainDisabled) return; // Not reentrant.
|
||||
drainDisabled = true;
|
||||
|
||||
var i:Int = 0;
|
||||
|
||||
@ -39,7 +39,7 @@ class EventTimeline extends Timeline {
|
||||
public var events:Array<Event>;
|
||||
|
||||
public function new(frameCount:Int) {
|
||||
super(frameCount, [Std.string(Property.event)]);
|
||||
super(frameCount, Property.event);
|
||||
events = new Array<Event>();
|
||||
events.resize(frameCount);
|
||||
}
|
||||
@ -56,22 +56,17 @@ class EventTimeline extends Timeline {
|
||||
}
|
||||
|
||||
/** Fires events for frames > lastTime and <= time. */
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
if (events == null)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, firedEvents:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
if (firedEvents == null) return;
|
||||
|
||||
var frameCount:Int = frames.length;
|
||||
|
||||
if (lastTime > time) // Apply events after lastTime for looped animations.
|
||||
{
|
||||
apply(skeleton, lastTime, 2147483647, events, alpha, blend, direction);
|
||||
if (lastTime > time) { // Apply events after lastTime for looped animations.
|
||||
apply(skeleton, lastTime, 2147483647, firedEvents, alpha, blend, direction, appliedPose);
|
||||
lastTime = -1;
|
||||
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (time < frames[0]) return;
|
||||
|
||||
var frame:Int;
|
||||
@ -87,7 +82,7 @@ class EventTimeline extends Timeline {
|
||||
}
|
||||
}
|
||||
while (i < frameCount && time >= frames[i]) {
|
||||
events.push(this.events[i]);
|
||||
firedEvents.push(this.events[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,13 +29,9 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Event;
|
||||
import spine.IkConstraint;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes an IK constraint's spine.IkConstraint.mix, spine.IkConstraint.softness,
|
||||
* spine.IkConstraint.bendDirection, spine.IkConstraint.stretch, and spine.IkConstraint.compress. */
|
||||
class IkConstraintTimeline extends CurveTimeline {
|
||||
/** Changes an IK constraint's spine.IkConstraintPose.mix, spine.IkConstraintPose.softness,
|
||||
* spine.IkConstraintPose.bendDirection, spine.IkConstraintPose.stretch, and spine.IkConstraintPose.compress. */
|
||||
class IkConstraintTimeline extends CurveTimeline implements ConstraintTimeline {
|
||||
private static inline var ENTRIES:Int = 6;
|
||||
private static inline var MIX:Int = 1;
|
||||
private static inline var SOFTNESS:Int = 2;
|
||||
@ -47,15 +43,19 @@ class IkConstraintTimeline extends CurveTimeline {
|
||||
* applied. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, ikConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.ikConstraint + "|" + ikConstraintIndex]);
|
||||
this.constraintIndex = ikConstraintIndex;
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, Property.ikConstraint + "|" + constraintIndex);
|
||||
this.constraintIndex = constraintIndex;
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getConstraintIndex () {
|
||||
return constraintIndex;
|
||||
}
|
||||
|
||||
/** Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds.
|
||||
@ -70,26 +70,28 @@ class IkConstraintTimeline extends CurveTimeline {
|
||||
frames[frame + STRETCH] = stretch ? 1 : 0;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var constraint:IkConstraint = skeleton.ikConstraints[constraintIndex];
|
||||
if (!constraint.active)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], IkConstraint);
|
||||
if (!constraint.active) return;
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
|
||||
if (time < frames[0]) {
|
||||
var setup = constraint.data.setup;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
constraint.mix = constraint.data.mix;
|
||||
constraint.softness = constraint.data.softness;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.compress = constraint.data.compress;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
pose.mix = setup.mix;
|
||||
pose.softness = setup.softness;
|
||||
pose.bendDirection = setup.bendDirection;
|
||||
pose.compress = setup.compress;
|
||||
pose.stretch = setup.stretch;
|
||||
case MixBlend.first:
|
||||
constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
|
||||
constraint.softness += (constraint.data.softness - constraint.softness) * alpha;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.compress = constraint.data.compress;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
pose.mix += (setup.mix - pose.mix) * alpha;
|
||||
pose.softness += (setup.softness - pose.softness) * alpha;
|
||||
pose.bendDirection = setup.bendDirection;
|
||||
pose.compress = setup.compress;
|
||||
pose.stretch = setup.stretch;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -113,26 +115,29 @@ class IkConstraintTimeline extends CurveTimeline {
|
||||
softness = getBezierValue(time, i, SOFTNESS, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
if (blend == MixBlend.setup) {
|
||||
constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha;
|
||||
constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha;
|
||||
if (direction == MixDirection.mixOut) {
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.compress = constraint.data.compress;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
} else {
|
||||
constraint.bendDirection = Std.int(frames[i + BEND_DIRECTION]);
|
||||
constraint.compress = frames[i + COMPRESS] != 0;
|
||||
constraint.stretch = frames[i + STRETCH] != 0;
|
||||
}
|
||||
} else {
|
||||
constraint.mix += (mix - constraint.mix) * alpha;
|
||||
constraint.softness += (softness - constraint.softness) * alpha;
|
||||
if (direction == MixDirection.mixIn) {
|
||||
constraint.bendDirection = Std.int(frames[i + BEND_DIRECTION]);
|
||||
constraint.compress = frames[i + COMPRESS] != 0;
|
||||
constraint.stretch = frames[i + STRETCH] != 0;
|
||||
}
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
var setup = constraint.data.setup;
|
||||
pose.mix = setup.mix + (mix - setup.mix) * alpha;
|
||||
pose.softness = setup.softness + (softness - setup.softness) * alpha;
|
||||
if (direction == MixDirection.mixOut) {
|
||||
pose.bendDirection = setup.bendDirection;
|
||||
pose.compress = setup.compress;
|
||||
pose.stretch = setup.stretch;
|
||||
return;
|
||||
}
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
pose.mix += (mix - pose.mix) * alpha;
|
||||
pose.softness += (softness - pose.softness) * alpha;
|
||||
if (direction == MixDirection.mixOut) return;
|
||||
case MixBlend.add:
|
||||
pose.mix += mix * alpha;
|
||||
pose.softness += softness * alpha;
|
||||
if (direction == MixDirection.mixOut) return;
|
||||
|
||||
}
|
||||
pose.bendDirection = Std.int(frames[i + BEND_DIRECTION]);
|
||||
pose.compress = frames[i + COMPRESS] != 0;
|
||||
pose.stretch = frames[i + STRETCH] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,18 +41,18 @@ class InheritTimeline extends Timeline implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, boneIndex:Int) {
|
||||
super(frameCount, [Property.inherit + "|" + boneIndex]);
|
||||
super(frameCount, Property.inherit + "|" + boneIndex);
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public function getBoneIndex () {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
/** Sets the transform mode for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -62,21 +62,22 @@ class InheritTimeline extends Timeline implements BoneTimeline {
|
||||
frames[frame + INHERIT] = inherit.ordinal;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool):Void {
|
||||
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (!bone.active) return;
|
||||
var pose = appliedPose ? bone.applied : bone.pose;
|
||||
|
||||
if (direction == MixDirection.mixOut) {
|
||||
if (blend == MixBlend.setup) bone.inherit = bone.data.inherit;
|
||||
if (blend == MixBlend.setup) pose.inherit = bone.data.setup.inherit;
|
||||
return;
|
||||
}
|
||||
|
||||
var frames:Array<Float> = frames;
|
||||
if (time < frames[0]) {
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) bone.inherit = bone.data.inherit;
|
||||
return;
|
||||
}
|
||||
bone.inherit = Inherit.values[Std.int(frames[Timeline.search(frames, time, ENTRIES) + INHERIT])];
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) pose.inherit = bone.data.setup.inherit;
|
||||
} else
|
||||
pose.inherit = Inherit.values[Std.int(frames[Timeline.search(frames, time, ENTRIES) + INHERIT])];
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ package spine.animation;
|
||||
|
||||
/** Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with
|
||||
* alpha < 1.
|
||||
*
|
||||
*
|
||||
* @see spine.animation.Timeline.apply() */
|
||||
class MixBlend {
|
||||
public var ordinal:Int = 0;
|
||||
@ -40,25 +40,29 @@ class MixBlend {
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
/** Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the
|
||||
* setup value is set. */
|
||||
/** Transitions between the setup and timeline values (the current value is not used). Before the first frame, the setup
|
||||
* value is used.
|
||||
*
|
||||
* `setup` is intended to transition to or from the setup pose, not for animations layered on top of others. */
|
||||
public static var setup(default, never):MixBlend = new MixBlend(0);
|
||||
/** Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to
|
||||
* the setup value. Timelines which perform instant transitions, such as spine.animation.DrawOrderTimeline or
|
||||
* spine.animation.AttachmentTimeline, use the setup value before the first frame.
|
||||
*
|
||||
* first is intended for the first animations applied, not for animations layered on top of those. */
|
||||
|
||||
/** Transitions between the current and timeline values. Before the first frame, transitions between the current and setup
|
||||
* values. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}, use
|
||||
* the setup value before the first frame.
|
||||
*
|
||||
* `first` is intended for the first animations applied, not for animations layered on top of others. */
|
||||
public static var first(default, never):MixBlend = new MixBlend(1);
|
||||
/** Transitions from the current value to the timeline value. No change is made before the first frame (the current value is
|
||||
* kept until the first frame).
|
||||
*
|
||||
* replace is intended for animations layered on top of others, not for the first animations applied. */
|
||||
|
||||
/** Transitions between the current and timeline values. No change is made before the first frame.
|
||||
*
|
||||
* `replace` is intended for animations layered on top of others, not for the first animations applied. */
|
||||
public static var replace(default, never):MixBlend = new MixBlend(2);
|
||||
/** Transitions from the current value to the current value plus the timeline value. No change is made before the first
|
||||
* frame (the current value is kept until the first frame).
|
||||
*
|
||||
* add is intended for animations layered on top of others, not for the first animations applied. Properties
|
||||
* set by additive animations must be set manually or by another animation before applying the additive animations, else the
|
||||
* property values will increase each time the additive animations are applied. */
|
||||
|
||||
/** Transitions between the current value and the current plus timeline values. No change is made before the first frame.
|
||||
*
|
||||
* `add` is intended for animations layered on top of others, not for the first animations applied.
|
||||
*
|
||||
* Properties set by additive animations must be set manually or by another animation before applying the additive
|
||||
* animations, else the property values will increase each time the additive animations are applied. */
|
||||
public static var add(default, never):MixBlend = new MixBlend(3);
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ package spine.animation;
|
||||
|
||||
/** Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or
|
||||
* mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied.
|
||||
*
|
||||
*
|
||||
* @see spine.animation.Timeline.apply()
|
||||
*/
|
||||
class MixDirection {
|
||||
|
||||
@ -35,7 +35,7 @@ import spine.Skeleton;
|
||||
|
||||
/** Changes a path constraint's PathConstraint.mixRotate, PathConstraint.mixX, and
|
||||
* PathConstraint.mixY. */
|
||||
class PathConstraintMixTimeline extends CurveTimeline {
|
||||
class PathConstraintMixTimeline extends CurveTimeline implements ConstraintTimeline {
|
||||
private static inline var ENTRIES:Int = 4;
|
||||
private static inline var ROTATE:Int = 1;
|
||||
private static inline var X:Int = 2;
|
||||
@ -45,15 +45,19 @@ class PathConstraintMixTimeline extends CurveTimeline {
|
||||
* applied. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, pathConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.pathConstraintMix + "|" + pathConstraintIndex]);
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, Property.pathConstraintMix + "|" + constraintIndex);
|
||||
this.constraintIndex = constraintIndex;
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getConstraintIndex () {
|
||||
return constraintIndex;
|
||||
}
|
||||
|
||||
/** Sets the time and color for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -65,24 +69,24 @@ class PathConstraintMixTimeline extends CurveTimeline {
|
||||
frames[frame + Y] = mixY;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var constraint:PathConstraint = skeleton.pathConstraints[constraintIndex];
|
||||
if (!constraint.active)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], PathConstraint);
|
||||
if (!constraint.active) return;
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
|
||||
var data:PathConstraintData;
|
||||
if (time < frames[0]) {
|
||||
data = constraint.data;
|
||||
var setup = constraint.data.setup;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
constraint.mixRotate = data.mixRotate;
|
||||
constraint.mixX = data.mixX;
|
||||
constraint.mixY = data.mixY;
|
||||
pose.mixRotate = setup.mixRotate;
|
||||
pose.mixX = setup.mixX;
|
||||
pose.mixY = setup.mixY;
|
||||
case MixBlend.first:
|
||||
constraint.mixRotate += (data.mixRotate - constraint.mixRotate) * alpha;
|
||||
constraint.mixX += (data.mixX - constraint.mixX) * alpha;
|
||||
constraint.mixY += (data.mixY - constraint.mixY) * alpha;
|
||||
pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha;
|
||||
pose.mixX += (setup.mixX - pose.mixX) * alpha;
|
||||
pose.mixY += (setup.mixY - pose.mixY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -110,15 +114,20 @@ class PathConstraintMixTimeline extends CurveTimeline {
|
||||
y = getBezierValue(time, i, Y, curveType + CurveTimeline.BEZIER_SIZE * 2 - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
if (blend == MixBlend.setup) {
|
||||
data = constraint.data;
|
||||
constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
|
||||
constraint.mixX = data.mixX + (x - data.mixX) * alpha;
|
||||
constraint.mixY = data.mixY + (y - data.mixY) * alpha;
|
||||
} else {
|
||||
constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
|
||||
constraint.mixX += (x - constraint.mixX) * alpha;
|
||||
constraint.mixY += (y - constraint.mixY) * alpha;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
var setup = constraint.data.setup;
|
||||
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
||||
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
||||
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
pose.mixRotate += (rotate - pose.mixRotate) * alpha;
|
||||
pose.mixX += (x - pose.mixX) * alpha;
|
||||
pose.mixY += (y - pose.mixY) * alpha;
|
||||
case MixBlend.add:
|
||||
pose.mixRotate += rotate * alpha;
|
||||
pose.mixX += x * alpha;
|
||||
pose.mixY += y * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,25 +29,20 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Event;
|
||||
import spine.PathConstraint;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a path constraint's spine.PathConstraint.position. */
|
||||
class PathConstraintPositionTimeline extends CurveTimeline1 {
|
||||
/** The index of the path constraint in spine.Skeleton.pathConstraints that will be changed when this timeline is
|
||||
* applied. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, pathConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.pathConstraintPosition + "|" + pathConstraintIndex]);
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
class PathConstraintPositionTimeline extends ConstraintTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.pathConstraintPosition);
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var constraint:PathConstraint = skeleton.pathConstraints[constraintIndex];
|
||||
if (constraint.active)
|
||||
constraint.position = getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position);
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], PathConstraint);
|
||||
if (constraint.active) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
pose.position = getAbsoluteValue(time, alpha, blend, pose.position, constraint.data.setup.position);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,19 +34,18 @@ import spine.PathConstraint;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a path constraint's PathConstraint#spacing. */
|
||||
class PathConstraintSpacingTimeline extends CurveTimeline1 {
|
||||
/** The index of the path constraint in Skeleton#pathConstraints that will be changed when this timeline is
|
||||
* applied. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, pathConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.pathConstraintSpacing + "|" + pathConstraintIndex]);
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
class PathConstraintSpacingTimeline extends ConstraintTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.pathConstraintSpacing);
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var constraint:PathConstraint = skeleton.pathConstraints[constraintIndex];
|
||||
if (constraint.active) constraint.spacing = getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing);
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], PathConstraint);
|
||||
if (constraint.active) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
pose.spacing = getAbsoluteValue(time, alpha, blend, pose.spacing, constraint.data.setup.spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.damping. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.damping. */
|
||||
class PhysicsConstraintDampingTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintDamping);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintDamping);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.damping;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.damping;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.damping;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.damping = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.damping = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.gravity. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.gravity. */
|
||||
class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintGravity);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintGravity);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.gravity;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.gravity;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.gravity;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.gravity = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.gravity = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.inertia. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.inertia. */
|
||||
class PhysicsConstraintInertiaTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintInertia);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintInertia);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.inertia;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.inertia;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.inertia;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.inertia = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.inertia = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.massInverse. The timeline values are not inverted. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.massInverse. The timeline values are not inverted. */
|
||||
class PhysicsConstraintMassTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMass);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintMass);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.massInverse;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.massInverse;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.massInverse;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.massInverse = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.massInverse = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.mix. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.mix. */
|
||||
class PhysicsConstraintMixTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMix);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintMix);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.mix;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.mix;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.mix;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.mix = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.mix = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -34,16 +34,17 @@ import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Resets a physics constraint when specific animation times are reached. */
|
||||
class PhysicsConstraintResetTimeline extends Timeline {
|
||||
/** The index of the physics constraint in Skeleton#physicsConstraints that will be reset when this timeline is
|
||||
* applied, or -1 if all physics constraints in the skeleton will be reset. */
|
||||
public var constraintIndex:Int = 0;
|
||||
class PhysicsConstraintResetTimeline extends Timeline implements ConstraintTimeline {
|
||||
public var constraintIndex:Int;
|
||||
|
||||
/** @param physicsConstraintIndex -1 for all physics constraints in the skeleton. */
|
||||
public function new(frameCount:Int, physicsConstraintIndex:Int) {
|
||||
propertyIds = [Std.string(Property.physicsConstraintReset)];
|
||||
super(frameCount, propertyIds);
|
||||
constraintIndex = physicsConstraintIndex;
|
||||
/** @param constraintIndex -1 for all physics constraints in the skeleton. */
|
||||
public function new(frameCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, Property.physicsConstraintReset);
|
||||
this.constraintIndex = constraintIndex;
|
||||
}
|
||||
|
||||
public function getConstraintIndex () {
|
||||
return constraintIndex;
|
||||
}
|
||||
|
||||
public override function getFrameCount():Int {
|
||||
@ -57,31 +58,30 @@ class PhysicsConstraintResetTimeline extends Timeline {
|
||||
}
|
||||
|
||||
/** Resets the physics constraint when frames > lastTime and <= time. */
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, firedEvents:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint:PhysicsConstraint = null;
|
||||
if (this.constraintIndex != -1) {
|
||||
constraint = skeleton.physicsConstraints[constraintIndex];
|
||||
if (constraintIndex != -1) {
|
||||
constraint = cast(skeleton.constraints[constraintIndex], PhysicsConstraint);
|
||||
if (!constraint.active) return;
|
||||
}
|
||||
|
||||
var frames:Array<Float> = this.frames;
|
||||
if (lastTime > time) // Apply events after lastTime for looped animations.
|
||||
{
|
||||
apply(skeleton, lastTime, 2147483647, [], alpha, blend, direction);
|
||||
|
||||
if (lastTime > time) { // Apply events after lastTime for looped animations.
|
||||
apply(skeleton, lastTime, 2147483647, [], alpha, blend, direction, appliedPose);
|
||||
lastTime = -1;
|
||||
} else if (lastTime >= frames[frames.length - 1]) // Last time is after last frame.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (time < frames[0]) return;
|
||||
|
||||
if (lastTime < frames[0] || time >= frames[Timeline.search1(frames, lastTime) + 1]) {
|
||||
if (constraint != null)
|
||||
constraint.reset();
|
||||
constraint.reset(skeleton);
|
||||
else {
|
||||
for (constraint in skeleton.physicsConstraints) {
|
||||
if (constraint.active) constraint.reset();
|
||||
for (constraint in skeleton.physics) {
|
||||
if (constraint.active) constraint.reset(skeleton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.strength. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.strength. */
|
||||
class PhysicsConstraintStrengthTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintStrength);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintStrength);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.strength;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.strength;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.strength;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.strength = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.strength = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -34,39 +34,37 @@ import spine.PathConstraint;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** The base class for most spine.PhysicsConstraint timelines. */
|
||||
abstract class PhysicsConstraintTimeline extends CurveTimeline1 {
|
||||
/** The index of the physics constraint in Skeleton.physicsConstraints that will be changed when this timeline
|
||||
* is applied, or -1 if all physics constraints in the skeleton will be changed. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 {
|
||||
/**
|
||||
* @param physicsConstraintIndex -1 for all physics constraints in the skeleton.
|
||||
* @param constraintIndex -1 for all physics constraints in the skeleton.
|
||||
*/
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int, property:Int) {
|
||||
super(frameCount, bezierCount, [property + "|" + physicsConstraintIndex]);
|
||||
constraintIndex = physicsConstraintIndex;
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int, property:Property) {
|
||||
super(frameCount, bezierCount, constraintIndex, property);
|
||||
}
|
||||
|
||||
public override function apply (skeleton:Skeleton, lastTime:Float, time:Float, firedEvents:Array<Event>, alpha:Float, blend:MixBlend, direction:MixDirection):Void {
|
||||
var constraint:PhysicsConstraint;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
if (constraintIndex == -1) {
|
||||
var value:Float = time >= frames[0] ? getCurveValue(time) : 0;
|
||||
|
||||
for (constraint in skeleton.physicsConstraints) {
|
||||
if (constraint.active && global(constraint.data))
|
||||
set(constraint, getAbsoluteValue2(time, alpha, blend, get(constraint), setup(constraint), value));
|
||||
for (constraint in skeleton.physics) {
|
||||
if (constraint.active && global(constraint.data)) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
set(pose, getAbsoluteValue2(time, alpha, blend, get(pose), get(constraint.data.setup), value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
constraint = skeleton.physicsConstraints[constraintIndex];
|
||||
if (constraint.active) set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint)));
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], PhysicsConstraint);
|
||||
if (constraint.active) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
set(pose, getAbsoluteValue(time, alpha, blend, get(pose), get(constraint.data.setup)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function setup (constraint: PhysicsConstraint):Float;
|
||||
abstract public function get (pose: PhysicsConstraintPose):Float;
|
||||
|
||||
abstract public function get (constraint: PhysicsConstraint):Float;
|
||||
|
||||
abstract public function set (constraint: PhysicsConstraint, value:Float):Void;
|
||||
abstract public function set (pose: PhysicsConstraintPose, value:Float):Void;
|
||||
|
||||
abstract public function global (constraint: PhysicsConstraintData):Bool;
|
||||
}
|
||||
|
||||
@ -29,22 +29,18 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a physics constraint's spine.PhysicsConstraint.wind. */
|
||||
/** Changes a physics constraint's spine.PhysicsConstraintPose.wind. */
|
||||
class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
||||
public function new(frameCount:Int, bezierCount:Int, physicsConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintWind);
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintWind);
|
||||
}
|
||||
|
||||
public function setup (constraint: PhysicsConstraint):Float {
|
||||
return constraint.data.wind;
|
||||
public function get (pose: PhysicsConstraintPose):Float {
|
||||
return pose.wind;
|
||||
}
|
||||
|
||||
public function get (constraint: PhysicsConstraint):Float {
|
||||
return constraint.wind;
|
||||
}
|
||||
|
||||
public function set (constraint: PhysicsConstraint, value:Float):Void {
|
||||
constraint.wind = value;
|
||||
public function set (pose: PhysicsConstraintPose, value:Float):Void {
|
||||
pose.wind = value;
|
||||
}
|
||||
|
||||
public function global (constraint: PhysicsConstraintData):Bool {
|
||||
|
||||
@ -32,43 +32,44 @@ package spine.animation;
|
||||
/**
|
||||
* Constants for animation property types.
|
||||
*/
|
||||
class Property {
|
||||
public static inline var rotate:Int = 0;
|
||||
public static inline var x:Int = 1;
|
||||
public static inline var y:Int = 2;
|
||||
public static inline var scaleX:Int = 3;
|
||||
public static inline var scaleY:Int = 4;
|
||||
public static inline var shearX:Int = 5;
|
||||
public static inline var shearY:Int = 6;
|
||||
public static inline var inherit:Int = 7;
|
||||
enum abstract Property(String) from String to String {
|
||||
var rotate = "0";
|
||||
var x = "1";
|
||||
var y = "2";
|
||||
var scaleX = "3";
|
||||
var scaleY = "4";
|
||||
var shearX = "5";
|
||||
var shearY = "6";
|
||||
var inherit = "7";
|
||||
|
||||
public static inline var rgb:Int = 8;
|
||||
public static inline var alpha:Int = 9;
|
||||
public static inline var rgb2:Int = 10;
|
||||
var rgb = "8";
|
||||
var alpha = "9";
|
||||
var rgb2 = "10";
|
||||
|
||||
public static inline var attachment:Int = 11;
|
||||
public static inline var deform:Int = 12;
|
||||
var attachment = "11";
|
||||
var deform = "12";
|
||||
|
||||
public static inline var event:Int = 13;
|
||||
public static inline var drawOrder:Int = 14;
|
||||
var event = "13";
|
||||
var drawOrder = "14";
|
||||
|
||||
public static inline var ikConstraint:Int = 15;
|
||||
public static inline var transformConstraint:Int = 16;
|
||||
var ikConstraint = "15";
|
||||
var transformConstraint = "16";
|
||||
|
||||
public static inline var pathConstraintPosition:Int = 17;
|
||||
public static inline var pathConstraintSpacing:Int = 18;
|
||||
public static inline var pathConstraintMix:Int = 19;
|
||||
var pathConstraintPosition = "17";
|
||||
var pathConstraintSpacing = "18";
|
||||
var pathConstraintMix = "19";
|
||||
|
||||
public static inline var physicsConstraintInertia:Int = 20;
|
||||
public static inline var physicsConstraintStrength:Int = 21;
|
||||
public static inline var physicsConstraintDamping:Int = 22;
|
||||
public static inline var physicsConstraintMass:Int = 23;
|
||||
public static inline var physicsConstraintWind:Int = 24;
|
||||
public static inline var physicsConstraintGravity:Int = 25;
|
||||
public static inline var physicsConstraintMix:Int = 26;
|
||||
public static inline var physicsConstraintReset:Int = 27;
|
||||
var physicsConstraintInertia = "20";
|
||||
var physicsConstraintStrength = "21";
|
||||
var physicsConstraintDamping = "22";
|
||||
var physicsConstraintMass = "23";
|
||||
var physicsConstraintWind = "24";
|
||||
var physicsConstraintGravity = "25";
|
||||
var physicsConstraintMix = "26";
|
||||
var physicsConstraintReset = "27";
|
||||
|
||||
public static inline var sequence:Int = 28;
|
||||
var sequence = "28";
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
var sliderTime = "29";
|
||||
var sliderMix = "30";
|
||||
}
|
||||
@ -30,7 +30,7 @@
|
||||
package spine.animation;
|
||||
|
||||
/** Changes the RGB for a slot's spine.Slot.color and spine.Slot.darkColor for two color tinting. */
|
||||
class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
class RGB2Timeline extends SlotCurveTimeline {
|
||||
private static inline var ENTRIES:Int = 7;
|
||||
private static inline var R:Int = 1;
|
||||
private static inline var G:Int = 2;
|
||||
@ -39,23 +39,16 @@ class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
private static inline var G2:Int = 5;
|
||||
private static inline var B2:Int = 6;
|
||||
|
||||
private var slotIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.rgb + "|" + slotIndex, Property.rgb2 + "|" + slotIndex]);
|
||||
this.slotIndex = slotIndex;
|
||||
super(frameCount, bezierCount, slotIndex,
|
||||
Property.rgb + "|" + slotIndex,
|
||||
Property.rgb2 + "|" + slotIndex);
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
/** The index of the slot in spine.Skeleton.slots that will be changed when this timeline is applied. The
|
||||
* spine.Slot.darkColor must not be null. */
|
||||
public function getSlotIndex():Int {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
/** Sets the time, light color, and dark color for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -70,17 +63,11 @@ class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
frames[frame + B2] = b2;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
|
||||
var light:Color = slot.color, dark:Color = slot.darkColor;
|
||||
var setupLight:Color, setupDark:Color;
|
||||
public function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) {
|
||||
var light:Color = pose.color, dark:Color = pose.darkColor;
|
||||
if (time < frames[0]) {
|
||||
setupLight = slot.data.color;
|
||||
setupDark = slot.data.darkColor;
|
||||
var setup = slot.data.setup;
|
||||
var setupLight = setup.color, setupDark = setup.darkColor;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
light.r = setupLight.r;
|
||||
@ -144,8 +131,8 @@ class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
dark.b = b2;
|
||||
} else {
|
||||
if (blend == MixBlend.setup) {
|
||||
setupLight = slot.data.color;
|
||||
setupDark = slot.data.darkColor;
|
||||
var setup = slot.data.setup;
|
||||
var setupLight = setup.color, setupDark = setup.darkColor;
|
||||
light.r = setupLight.r;
|
||||
light.g = setupLight.g;
|
||||
light.b = setupLight.b;
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a slot's spine.Slot.getColor() and spine.Slot.getDarkColor() for two color tinting. */
|
||||
class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
class RGBA2Timeline extends SlotCurveTimeline {
|
||||
private static inline var ENTRIES:Int = 8;
|
||||
private static inline var R:Int = 1;
|
||||
private static inline var G:Int = 2;
|
||||
@ -40,27 +40,18 @@ class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
private static inline var G2:Int = 6;
|
||||
private static inline var B2:Int = 7;
|
||||
|
||||
private var slotIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int) {
|
||||
super(frameCount, bezierCount, [
|
||||
super(frameCount, bezierCount, slotIndex,
|
||||
Property.rgb + "|" + slotIndex,
|
||||
Property.alpha + "|" + slotIndex,
|
||||
Property.rgb2 + "|" + slotIndex
|
||||
]);
|
||||
this.slotIndex = slotIndex;
|
||||
);
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
/** The index of the slot in spine.Skeleton.getSlots() that will be changed when this timeline is applied. The
|
||||
* spine.Slot.getDarkColor() must not be null. */
|
||||
public function getSlotIndex():Int {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
/** Sets the time, light color, and dark color for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -76,16 +67,11 @@ class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
frames[frame + B2] = b2;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
|
||||
var light:Color = slot.color, dark:Color = slot.darkColor;
|
||||
public function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) {
|
||||
var light = pose.color, dark = pose.darkColor;
|
||||
if (time < frames[0]) {
|
||||
var setupLight:Color = slot.data.color,
|
||||
setupDark:Color = slot.data.darkColor;
|
||||
var setup = slot.data.setup;
|
||||
var setupLight = setup.color, setupDark = setup.darkColor;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
light.setFromColor(setupLight);
|
||||
@ -148,8 +134,9 @@ class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
dark.b = b2;
|
||||
} else {
|
||||
if (blend == MixBlend.setup) {
|
||||
light.setFromColor(slot.data.color);
|
||||
dark.setFromColor(slot.data.darkColor);
|
||||
var setup = slot.data.setup;
|
||||
light.setFromColor(setup.color);
|
||||
dark.setFromColor(setup.darkColor);
|
||||
}
|
||||
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
|
||||
dark.r += (r2 - dark.r) * alpha;
|
||||
|
||||
@ -30,28 +30,23 @@
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a slot's spine.Slot.color. */
|
||||
class RGBATimeline extends CurveTimeline implements SlotTimeline {
|
||||
class RGBATimeline extends SlotCurveTimeline {
|
||||
private static inline var ENTRIES:Int = 5;
|
||||
private static inline var R:Int = 1;
|
||||
private static inline var G:Int = 2;
|
||||
private static inline var B:Int = 3;
|
||||
private static inline var A:Int = 4;
|
||||
|
||||
private var slotIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.rgb + "|" + slotIndex, Property.alpha + "|" + slotIndex]);
|
||||
this.slotIndex = slotIndex;
|
||||
super(frameCount, bezierCount, slotIndex,
|
||||
Property.rgb + "|" + slotIndex,
|
||||
Property.alpha + "|" + slotIndex);
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getSlotIndex():Int {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
/** Sets the time and color for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -64,15 +59,10 @@ class RGBATimeline extends CurveTimeline implements SlotTimeline {
|
||||
frames[frame + A] = a;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
|
||||
var color:Color = slot.color;
|
||||
public function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) {
|
||||
var color = pose.color;
|
||||
if (time < frames[0]) {
|
||||
var setup:Color = slot.data.color;
|
||||
var setup:Color = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
color.setFromColor(setup);
|
||||
@ -113,7 +103,7 @@ class RGBATimeline extends CurveTimeline implements SlotTimeline {
|
||||
color.set(r, g, b, a);
|
||||
} else {
|
||||
if (blend == MixBlend.setup)
|
||||
color.setFromColor(slot.data.color);
|
||||
color.setFromColor(slot.data.setup.color);
|
||||
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,27 +30,20 @@
|
||||
package spine.animation;
|
||||
|
||||
/** Changes the RGB for a slot's spine.Slot.color. */
|
||||
class RGBTimeline extends CurveTimeline implements SlotTimeline {
|
||||
class RGBTimeline extends SlotCurveTimeline {
|
||||
private static inline var ENTRIES:Int = 4;
|
||||
private static inline var R:Int = 1;
|
||||
private static inline var G:Int = 2;
|
||||
private static inline var B:Int = 3;
|
||||
|
||||
private var slotIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, slotIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.rgb + "|" + slotIndex]);
|
||||
this.slotIndex = slotIndex;
|
||||
super(frameCount, bezierCount, slotIndex, Property.rgb + "|" + slotIndex);
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getSlotIndex():Int {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
/** Sets the time and color for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -62,15 +55,10 @@ class RGBTimeline extends CurveTimeline implements SlotTimeline {
|
||||
frames[frame + B] = b;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var slot:Slot = skeleton.slots[slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
|
||||
var color:Color = slot.color, setup:Color;
|
||||
public function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend) {
|
||||
var color = pose.color;
|
||||
if (time < frames[0]) {
|
||||
setup = slot.data.color;
|
||||
var setup = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
color.r = setup.r;
|
||||
@ -112,7 +100,7 @@ class RGBTimeline extends CurveTimeline implements SlotTimeline {
|
||||
color.b = b;
|
||||
} else {
|
||||
if (blend == MixBlend.setup) {
|
||||
setup = slot.data.color;
|
||||
var setup = slot.data.setup.color;
|
||||
color.r = setup.r;
|
||||
color.g = setup.g;
|
||||
color.b = setup.b;
|
||||
|
||||
@ -29,27 +29,14 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local rotation. */
|
||||
class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
public var boneIndex:Int = 0;
|
||||
|
||||
class RotateTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.rotate + "|" + boneIndex]);
|
||||
super(frameCount, bezierCount, boneIndex, Property.rotate);
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active)
|
||||
bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection):Void {
|
||||
pose.rotation = getRelativeValue(time, alpha, blend, pose.rotation, setup.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,97 +35,86 @@ import spine.MathUtils;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.scaleX and spine.Bone.scaleY. */
|
||||
class ScaleTimeline extends CurveTimeline2 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ScaleTimeline extends BoneTimeline2 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.scaleX + "|" + boneIndex, Property.scaleY + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.scaleX, Property.scaleY);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (!bone.active)
|
||||
return;
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.scaleX = bone.data.scaleX;
|
||||
bone.scaleY = bone.data.scaleY;
|
||||
pose.scaleX = setup.scaleX;
|
||||
pose.scaleY = setup.scaleY;
|
||||
case MixBlend.first:
|
||||
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
|
||||
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
|
||||
pose.scaleX += (setup.scaleX - pose.scaleX) * alpha;
|
||||
pose.scaleY += (setup.scaleY - pose.scaleY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var x:Float = 0, y:Float = 0;
|
||||
var i:Int = Timeline.search(frames, time, CurveTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / CurveTimeline2.ENTRIES)]);
|
||||
var i:Int = Timeline.search(frames, time, BoneTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / BoneTimeline2.ENTRIES)]);
|
||||
switch (curveType) {
|
||||
case CurveTimeline.LINEAR:
|
||||
var before:Float = frames[i];
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + CurveTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE2] - y) * t;
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + BoneTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE2] - y) * t;
|
||||
case CurveTimeline.STEPPED:
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
default:
|
||||
x = getBezierValue(time, i, CurveTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, CurveTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
x = getBezierValue(time, i, BoneTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, BoneTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
}
|
||||
x *= bone.data.scaleX;
|
||||
y *= bone.data.scaleY;
|
||||
x *= setup.scaleX;
|
||||
y *= setup.scaleY;
|
||||
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) {
|
||||
bone.scaleX += x - bone.data.scaleX;
|
||||
bone.scaleY += y - bone.data.scaleY;
|
||||
pose.scaleX += x - setup.scaleX;
|
||||
pose.scaleY += y - setup.scaleY;
|
||||
} else {
|
||||
bone.scaleX = x;
|
||||
bone.scaleY = y;
|
||||
pose.scaleX = x;
|
||||
pose.scaleY = y;
|
||||
}
|
||||
} else {
|
||||
var bx:Float = 0, by:Float = 0;
|
||||
if (direction == MixDirection.mixOut) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bx = bone.data.scaleX;
|
||||
by = bone.data.scaleY;
|
||||
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
bx = setup.scaleX;
|
||||
by = setup.scaleY;
|
||||
pose.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
pose.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
bx = bone.scaleX;
|
||||
by = bone.scaleY;
|
||||
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
bx = pose.scaleX;
|
||||
by = pose.scaleY;
|
||||
pose.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
pose.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
case MixBlend.add:
|
||||
bone.scaleX = (x - bone.data.scaleX) * alpha;
|
||||
bone.scaleY = (y - bone.data.scaleY) * alpha;
|
||||
pose.scaleX = (x - setup.scaleX) * alpha;
|
||||
pose.scaleY = (y - setup.scaleY) * alpha;
|
||||
}
|
||||
} else {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
|
||||
by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
|
||||
bone.scaleX = bx + (x - bx) * alpha;
|
||||
bone.scaleY = by + (y - by) * alpha;
|
||||
bx = Math.abs(setup.scaleX) * MathUtils.signum(x);
|
||||
by = Math.abs(setup.scaleY) * MathUtils.signum(y);
|
||||
pose.scaleX = bx + (x - bx) * alpha;
|
||||
pose.scaleY = by + (y - by) * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
|
||||
by = Math.abs(bone.scaleY) * MathUtils.signum(y);
|
||||
bone.scaleX = bx + (x - bx) * alpha;
|
||||
bone.scaleY = by + (y - by) * alpha;
|
||||
bx = Math.abs(pose.scaleX) * MathUtils.signum(x);
|
||||
by = Math.abs(pose.scaleY) * MathUtils.signum(y);
|
||||
pose.scaleX = bx + (x - bx) * alpha;
|
||||
pose.scaleY = by + (y - by) * alpha;
|
||||
case MixBlend.add:
|
||||
bone.scaleX += (x - bone.data.scaleX) * alpha;
|
||||
bone.scaleY += (y - bone.data.scaleY) * alpha;
|
||||
pose.scaleX += (x - setup.scaleX) * alpha;
|
||||
pose.scaleY += (y - setup.scaleY) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,27 +29,13 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.MathUtils;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.scaleX. */
|
||||
class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ScaleXTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.scaleX + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.scaleX);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.scaleX = getScaleValue(time, alpha, blend, direction, pose.scaleX, setup.scaleX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,28 +29,13 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.MathUtils;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.scaleY. */
|
||||
class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ScaleYTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.scaleY + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.scaleY);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY);
|
||||
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.scaleY = getScaleValue(time, alpha, blend, direction, pose.scaleY, setup.scaleY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,9 +42,8 @@ class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
var attachment:HasTextureRegion;
|
||||
|
||||
public function new(frameCount:Int, slotIndex:Int, attachment:HasTextureRegion) {
|
||||
super(frameCount, [
|
||||
Std.string(Property.sequence) + "|" + Std.string(slotIndex) + "|" + Std.string(attachment.sequence.id)
|
||||
]);
|
||||
super(frameCount,
|
||||
Std.string(Property.sequence) + "|" + Std.string(slotIndex) + "|" + Std.string(attachment.sequence.id));
|
||||
this.slotIndex = slotIndex;
|
||||
this.attachment = attachment;
|
||||
}
|
||||
@ -71,12 +70,14 @@ class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
frames[frame + SequenceTimeline.DELAY] = delay;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var slot = skeleton.slots[this.slotIndex];
|
||||
if (!slot.bone.active)
|
||||
return;
|
||||
var slotAttachment = slot.attachment;
|
||||
if (!slot.bone.active) return;
|
||||
var pose = appliedPose ? slot.applied : slot.pose;
|
||||
|
||||
var slotAttachment = pose.attachment;
|
||||
var attachment = cast(this.attachment, Attachment);
|
||||
if (slotAttachment != attachment) {
|
||||
if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).timelineAttachment != attachment)
|
||||
@ -84,13 +85,12 @@ class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
}
|
||||
|
||||
if (direction == MixDirection.mixOut) {
|
||||
if (blend == MixBlend.setup) slot.sequenceIndex = -1;
|
||||
if (blend == MixBlend.setup) pose.sequenceIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time < frames[0]) {
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first)
|
||||
slot.sequenceIndex = -1;
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) pose.sequenceIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -127,6 +127,6 @@ class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
index = n - index;
|
||||
}
|
||||
}
|
||||
slot.sequenceIndex = index;
|
||||
pose.sequenceIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,70 +29,54 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.shearX and spine.Bone.shearY. */
|
||||
class ShearTimeline extends CurveTimeline2 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ShearTimeline extends BoneTimeline2 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.shearX + "|" + boneIndex, Property.shearY + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.shearX, Property.shearY);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (!bone.active)
|
||||
return;
|
||||
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearX = bone.data.shearX;
|
||||
bone.shearY = bone.data.shearY;
|
||||
pose.shearX = setup.shearX;
|
||||
pose.shearY = setup.shearY;
|
||||
case MixBlend.first:
|
||||
bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
|
||||
bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
|
||||
pose.shearX += (setup.shearX - pose.shearX) * alpha;
|
||||
pose.shearY += (setup.shearY - pose.shearY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var x:Float = 0, y:Float = 0;
|
||||
var i:Int = Timeline.search(frames, time, CurveTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / CurveTimeline2.ENTRIES)]);
|
||||
var i:Int = Timeline.search(frames, time, BoneTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / BoneTimeline2.ENTRIES)]);
|
||||
switch (curveType) {
|
||||
case CurveTimeline.LINEAR:
|
||||
var before:Float = frames[i];
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + CurveTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE2] - y) * t;
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + BoneTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE2] - y) * t;
|
||||
case CurveTimeline.STEPPED:
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
default:
|
||||
x = getBezierValue(time, i, CurveTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, CurveTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
x = getBezierValue(time, i, BoneTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, BoneTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearX = bone.data.shearX + x * alpha;
|
||||
bone.shearY = bone.data.shearY + y * alpha;
|
||||
pose.shearX = setup.shearX + x * alpha;
|
||||
pose.shearY = setup.shearY + y * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
|
||||
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
|
||||
pose.shearX += (setup.shearX + x - pose.shearX) * alpha;
|
||||
pose.shearY += (setup.shearY + y - pose.shearY) * alpha;
|
||||
case MixBlend.add:
|
||||
bone.shearX += x * alpha;
|
||||
bone.shearY += y * alpha;
|
||||
pose.shearX += x * alpha;
|
||||
pose.shearY += y * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,26 +29,13 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.shearX. */
|
||||
class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ShearXTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.shearX + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.shearX);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.shearX = getRelativeValue(time, alpha, blend, pose.shearX, setup.shearX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,26 +29,13 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local Bone.shearY. */
|
||||
class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
private var boneIndex:Int = 0;
|
||||
|
||||
class ShearYTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.shearY + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.shearY);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.shearY = getRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.shearY = getRelativeValue(time, alpha, blend, pose.shearY, setup.shearY);
|
||||
}
|
||||
}
|
||||
|
||||
49
spine-haxe/spine-haxe/spine/animation/SliderMixTimeline.hx
Normal file
49
spine-haxe/spine-haxe/spine/animation/SliderMixTimeline.hx
Normal file
@ -0,0 +1,49 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import haxe.macro.Type.VarAccess;
|
||||
|
||||
/** Changes a slider's spine.SliderPose.mix. */
|
||||
class SliderMixTimeline extends ConstraintTimeline1 {
|
||||
public function new (frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.sliderMix);
|
||||
}
|
||||
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], Slider);
|
||||
if (constraint.active) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
pose.mix = getAbsoluteValue(time, alpha, blend, pose.mix, constraint.data.setup.mix);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
spine-haxe/spine-haxe/spine/animation/SliderTimeline.hx
Normal file
47
spine-haxe/spine-haxe/spine/animation/SliderTimeline.hx
Normal file
@ -0,0 +1,47 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
/** Changes a slider's spine.SliderPose.time. */
|
||||
class SliderTimeline extends ConstraintTimeline1 {
|
||||
public function new (frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.sliderTime);
|
||||
}
|
||||
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], Slider);
|
||||
if (constraint.active) {
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
pose.time = getAbsoluteValue(time, alpha, blend, pose.time, constraint.data.setup.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
spine-haxe/spine-haxe/spine/animation/SlotCurveTimeline.hx
Normal file
52
spine-haxe/spine-haxe/spine/animation/SlotCurveTimeline.hx
Normal file
@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
package spine.animation;
|
||||
|
||||
abstract class SlotCurveTimeline extends CurveTimeline implements SlotTimeline {
|
||||
public final slotIndex:Int;
|
||||
|
||||
public function new (frameCount:Int, bezierCount:Int, slotIndex:Int, propertyIds:...String) {
|
||||
super(frameCount, bezierCount, ...propertyIds);
|
||||
this.slotIndex = slotIndex;
|
||||
}
|
||||
|
||||
public function getSlotIndex () {
|
||||
return slotIndex;
|
||||
}
|
||||
|
||||
public function apply (skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var slot = skeleton.slots[slotIndex];
|
||||
if (slot.bone.active) apply1(slot, appliedPose ? slot.applied : slot.pose, time, alpha, blend);
|
||||
}
|
||||
|
||||
abstract function apply1 (slot:Slot, pose:SlotPose, time:Float, alpha:Float, blend:MixBlend):Void;
|
||||
}
|
||||
@ -33,7 +33,7 @@ import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** The base class for all timelines. */
|
||||
class Timeline {
|
||||
abstract class Timeline {
|
||||
/** Uniquely encodes both the type of this timeline and the skeleton properties that it affects. */
|
||||
public var propertyIds:Array<String>;
|
||||
/** The time in seconds and any other values for each frame. */
|
||||
@ -42,7 +42,7 @@ class Timeline {
|
||||
/**
|
||||
* @param propertyIds Unique identifiers for the properties the timeline modifies.
|
||||
*/
|
||||
public function new(frameCount:Int, propertyIds:Array<String>) {
|
||||
public function new(frameCount:Int, propertyIds:...String) {
|
||||
this.propertyIds = propertyIds;
|
||||
frames = new Array<Float>();
|
||||
frames.resize(frameCount * getFrameEntries());
|
||||
@ -82,10 +82,9 @@ class Timeline {
|
||||
* @param blend Controls how mixing is applied when alpha < 1.
|
||||
* @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions,
|
||||
* such as spine.animation.DrawOrderTimeline or spine.animation.AttachmentTimeline, and others such as spine.animation.ScaleTimeline.
|
||||
*/
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend, direction:MixDirection):Void {
|
||||
throw new SpineException("Timeline implementations must override apply()");
|
||||
}
|
||||
* @param appliedPose True to to modify the applied pose. */
|
||||
abstract public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool):Void;
|
||||
|
||||
/** Linear search using a stride of 1.
|
||||
* @param time Must be >= the first value in frames.
|
||||
|
||||
@ -29,15 +29,10 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
import spine.TransformConstraint;
|
||||
import spine.TransformConstraintData;
|
||||
|
||||
/** Changes a transform constraint's spine.TransformConstraint.mixRotate, spine.TransformConstraint.mixX,
|
||||
* spine.TransformConstraint.mixY, spine.TransformConstraint.mixScaleX,
|
||||
* spine.TransformConstraint.mixScaleY, and spine.TransformConstraint.mixShearY. */
|
||||
class TransformConstraintTimeline extends CurveTimeline {
|
||||
class TransformConstraintTimeline extends CurveTimeline implements ConstraintTimeline {
|
||||
static public inline var ENTRIES:Int = 7;
|
||||
private static inline var ROTATE:Int = 1;
|
||||
private static inline var X:Int = 2;
|
||||
@ -50,15 +45,19 @@ class TransformConstraintTimeline extends CurveTimeline {
|
||||
* timeline is applied. */
|
||||
public var constraintIndex:Int = 0;
|
||||
|
||||
public function new(frameCount:Int, bezierCount:Int, transformConstraintIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.transformConstraint + "|" + transformConstraintIndex]);
|
||||
this.constraintIndex = transformConstraintIndex;
|
||||
public function new(frameCount:Int, bezierCount:Int, constraintIndex:Int) {
|
||||
super(frameCount, bezierCount, Property.transformConstraint + "|" + constraintIndex);
|
||||
this.constraintIndex = constraintIndex;
|
||||
}
|
||||
|
||||
public override function getFrameEntries():Int {
|
||||
return ENTRIES;
|
||||
}
|
||||
|
||||
public function getConstraintIndex () {
|
||||
return constraintIndex;
|
||||
}
|
||||
|
||||
/** Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame.
|
||||
* @param frame Between 0 and frameCount, inclusive.
|
||||
* @param time The frame time in seconds. */
|
||||
@ -73,30 +72,30 @@ class TransformConstraintTimeline extends CurveTimeline {
|
||||
frames[frame + SHEARY] = mixShearY;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var constraint:TransformConstraint = skeleton.transformConstraints[constraintIndex];
|
||||
if (!constraint.active)
|
||||
return;
|
||||
public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float,
|
||||
blend:MixBlend, direction:MixDirection, appliedPose:Bool) {
|
||||
|
||||
var constraint = cast(skeleton.constraints[constraintIndex], TransformConstraint);
|
||||
if (!constraint.active) return;
|
||||
var pose = appliedPose ? constraint.applied : constraint.pose;
|
||||
|
||||
var data:TransformConstraintData;
|
||||
if (time < frames[0]) {
|
||||
data = constraint.data;
|
||||
var setup = constraint.data.setup;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
constraint.mixRotate = data.mixRotate;
|
||||
constraint.mixX = data.mixX;
|
||||
constraint.mixY = data.mixY;
|
||||
constraint.mixScaleX = data.mixScaleX;
|
||||
constraint.mixScaleY = data.mixScaleY;
|
||||
constraint.mixShearY = data.mixShearY;
|
||||
pose.mixRotate = setup.mixRotate;
|
||||
pose.mixX = setup.mixX;
|
||||
pose.mixY = setup.mixY;
|
||||
pose.mixScaleX = setup.mixScaleX;
|
||||
pose.mixScaleY = setup.mixScaleY;
|
||||
pose.mixShearY = setup.mixShearY;
|
||||
case MixBlend.first:
|
||||
constraint.mixRotate += (data.mixRotate - constraint.mixRotate) * alpha;
|
||||
constraint.mixX += (data.mixX - constraint.mixX) * alpha;
|
||||
constraint.mixY += (data.mixY - constraint.mixY) * alpha;
|
||||
constraint.mixScaleX += (data.mixScaleX - constraint.mixScaleX) * alpha;
|
||||
constraint.mixScaleY += (data.mixScaleY - constraint.mixScaleY) * alpha;
|
||||
constraint.mixShearY += (data.mixShearY - constraint.mixShearY) * alpha;
|
||||
pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha;
|
||||
pose.mixX += (setup.mixX - pose.mixX) * alpha;
|
||||
pose.mixY += (setup.mixY - pose.mixY) * alpha;
|
||||
pose.mixScaleX += (setup.mixScaleX - pose.mixScaleX) * alpha;
|
||||
pose.mixScaleY += (setup.mixScaleY - pose.mixScaleY) * alpha;
|
||||
pose.mixShearY += (setup.mixShearY - pose.mixShearY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -136,21 +135,29 @@ class TransformConstraintTimeline extends CurveTimeline {
|
||||
shearY = getBezierValue(time, i, SHEARY, curveType + CurveTimeline.BEZIER_SIZE * 5 - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
if (blend == MixBlend.setup) {
|
||||
data = constraint.data;
|
||||
constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
|
||||
constraint.mixX = data.mixX + (x - data.mixX) * alpha;
|
||||
constraint.mixY = data.mixY + (y - data.mixY) * alpha;
|
||||
constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha;
|
||||
constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha;
|
||||
constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha;
|
||||
} else {
|
||||
constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
|
||||
constraint.mixX += (x - constraint.mixX) * alpha;
|
||||
constraint.mixY += (y - constraint.mixY) * alpha;
|
||||
constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha;
|
||||
constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha;
|
||||
constraint.mixShearY += (shearY - constraint.mixShearY) * alpha;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
var setup = constraint.data.setup;
|
||||
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
||||
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
||||
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
||||
pose.mixScaleX = setup.mixScaleX + (scaleX - setup.mixScaleX) * alpha;
|
||||
pose.mixScaleY = setup.mixScaleY + (scaleY - setup.mixScaleY) * alpha;
|
||||
pose.mixShearY = setup.mixShearY + (shearY - setup.mixShearY) * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
pose.mixRotate += (rotate - pose.mixRotate) * alpha;
|
||||
pose.mixX += (x - pose.mixX) * alpha;
|
||||
pose.mixY += (y - pose.mixY) * alpha;
|
||||
pose.mixScaleX += (scaleX - pose.mixScaleX) * alpha;
|
||||
pose.mixScaleY += (scaleY - pose.mixScaleY) * alpha;
|
||||
pose.mixShearY += (shearY - pose.mixShearY) * alpha;
|
||||
case MixBlend.add:
|
||||
pose.mixRotate += rotate * alpha;
|
||||
pose.mixX += x * alpha;
|
||||
pose.mixY += y * alpha;
|
||||
pose.mixScaleX += scaleX * alpha;
|
||||
pose.mixScaleY += scaleY * alpha;
|
||||
pose.mixShearY += shearY * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,66 +34,54 @@ import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.x and spine.Bone.y. */
|
||||
class TranslateTimeline extends CurveTimeline2 implements BoneTimeline {
|
||||
public var boneIndex:Int = 0;
|
||||
|
||||
class TranslateTimeline extends BoneTimeline2 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.x + "|" + boneIndex, Property.y + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.x, Property.y);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
override public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (!bone.active)
|
||||
return;
|
||||
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection):Void {
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.x = bone.data.x;
|
||||
bone.y = bone.data.y;
|
||||
pose.x = setup.x;
|
||||
pose.y = setup.y;
|
||||
case MixBlend.first:
|
||||
bone.x += (bone.data.x - bone.x) * alpha;
|
||||
bone.y += (bone.data.y - bone.y) * alpha;
|
||||
pose.x += (setup.x - pose.x) * alpha;
|
||||
pose.y += (setup.y - pose.y) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var x:Float = 0, y:Float = 0;
|
||||
var i:Int = Timeline.search(frames, time, CurveTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / CurveTimeline2.ENTRIES)]);
|
||||
var i:Int = Timeline.search(frames, time, BoneTimeline2.ENTRIES);
|
||||
var curveType:Int = Std.int(curves[Std.int(i / BoneTimeline2.ENTRIES)]);
|
||||
|
||||
switch (curveType) {
|
||||
case CurveTimeline.LINEAR:
|
||||
var before:Float = frames[i];
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + CurveTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + CurveTimeline2.ENTRIES + CurveTimeline2.VALUE2] - y) * t;
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
var t:Float = (time - before) / (frames[i + BoneTimeline2.ENTRIES] - before);
|
||||
x += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE1] - x) * t;
|
||||
y += (frames[i + BoneTimeline2.ENTRIES + BoneTimeline2.VALUE2] - y) * t;
|
||||
case CurveTimeline.STEPPED:
|
||||
x = frames[i + CurveTimeline2.VALUE1];
|
||||
y = frames[i + CurveTimeline2.VALUE2];
|
||||
x = frames[i + BoneTimeline2.VALUE1];
|
||||
y = frames[i + BoneTimeline2.VALUE2];
|
||||
default:
|
||||
x = getBezierValue(time, i, CurveTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, CurveTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
x = getBezierValue(time, i, BoneTimeline2.VALUE1, curveType - CurveTimeline.BEZIER);
|
||||
y = getBezierValue(time, i, BoneTimeline2.VALUE2, curveType + CurveTimeline.BEZIER_SIZE - CurveTimeline.BEZIER);
|
||||
}
|
||||
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.x = bone.data.x + x * alpha;
|
||||
bone.y = bone.data.y + y * alpha;
|
||||
pose.x = setup.x + x * alpha;
|
||||
pose.y = setup.y + y * alpha;
|
||||
case MixBlend.first, MixBlend.replace:
|
||||
bone.x += (bone.data.x + x - bone.x) * alpha;
|
||||
bone.y += (bone.data.y + y - bone.y) * alpha;
|
||||
pose.x += (setup.x + x - pose.x) * alpha;
|
||||
pose.y += (setup.y + y - pose.y) * alpha;
|
||||
case MixBlend.add:
|
||||
bone.x += x * alpha;
|
||||
bone.y += y * alpha;
|
||||
pose.x += x * alpha;
|
||||
pose.y += y * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,21 +34,13 @@ import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local spine.Bone.x. */
|
||||
class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
public var boneIndex:Int = 0;
|
||||
|
||||
class TranslateXTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.x + "|" + boneIndex]);
|
||||
super(frameCount, bezierCount, boneIndex, Property.x);
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.x = getRelativeValue(time, alpha, blend, pose.x, setup.x);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,26 +29,13 @@
|
||||
|
||||
package spine.animation;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.Event;
|
||||
import spine.Skeleton;
|
||||
|
||||
/** Changes a bone's local y translation. */
|
||||
class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
public var boneIndex:Int = 0;
|
||||
|
||||
class TranslateYTimeline extends BoneTimeline1 {
|
||||
public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
|
||||
super(frameCount, bezierCount, [Property.y + "|" + boneIndex]);
|
||||
this.boneIndex = boneIndex;
|
||||
super(frameCount, bezierCount, boneIndex, Property.y);
|
||||
}
|
||||
|
||||
public function getBoneIndex():Int {
|
||||
return boneIndex;
|
||||
}
|
||||
|
||||
public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Array<Event>, alpha:Float, blend:MixBlend,
|
||||
direction:MixDirection):Void {
|
||||
var bone:Bone = skeleton.bones[boneIndex];
|
||||
if (bone.active) bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
|
||||
public function apply1 (pose:BoneLocal, setup:BoneLocal, time:Float, alpha:Float, blend:MixBlend, direction:MixDirection) {
|
||||
pose.y = getRelativeValue(time, alpha, blend, pose.y, setup.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,11 +125,12 @@ class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
||||
i += 2;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
u -= region.offsetX / textureWidth;
|
||||
v -= (region.originalHeight - region.offsetY - region.height) / textureHeight;
|
||||
width = region.originalWidth / textureWidth;
|
||||
height = region.originalHeight / textureHeight;
|
||||
}
|
||||
u -= region.offsetX / textureWidth;
|
||||
v -= (region.originalHeight - region.offsetY - region.height) / textureHeight;
|
||||
width = region.originalWidth / textureWidth;
|
||||
height = region.originalHeight / textureHeight;
|
||||
} else if (region == null) {
|
||||
u = v = 0;
|
||||
width = height = 1;
|
||||
@ -194,10 +195,10 @@ class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
||||
}
|
||||
|
||||
/** If the attachment has a sequence, the region may be changed. */
|
||||
public override function computeWorldVertices(slot:Slot, start:Int, count:Int, worldVertices:Array<Float>, offset:Int, stride:Int):Void {
|
||||
public override function computeWorldVertices(skeleton:Skeleton, slot:Slot, start:Int, count:Int, worldVertices:Array<Float>, offset:Int, stride:Int):Void {
|
||||
if (sequence != null)
|
||||
sequence.apply(slot, this);
|
||||
super.computeWorldVertices(slot, start, count, worldVertices, offset, stride);
|
||||
sequence.apply(slot.applied, this);
|
||||
super.computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride);
|
||||
}
|
||||
|
||||
/** Returns a new mesh with the parentMesh set to this mesh's parent mesh, if any, else to this mesh. */
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
|
||||
package spine.attachments;
|
||||
|
||||
import spine.Bone;
|
||||
import spine.BonePose;
|
||||
import spine.Color;
|
||||
import spine.MathUtils;
|
||||
|
||||
@ -51,13 +51,13 @@ class PointAttachment extends VertexAttachment {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public function computeWorldPosition(bone:Bone, point:Array<Float>):Array<Float> {
|
||||
public function computeWorldPosition(bone:BonePose, point:Array<Float>):Array<Float> {
|
||||
point[0] = x * bone.a + y * bone.b + bone.worldX;
|
||||
point[1] = x * bone.c + y * bone.d + bone.worldY;
|
||||
return point;
|
||||
}
|
||||
|
||||
public function computeWorldRotation(bone:Bone):Float {
|
||||
public function computeWorldRotation(bone:BonePose):Float {
|
||||
var r:Float = this.rotation * MathUtils.degRad, cos:Float = Math.cos(r), sin:Float = Math.sin(r);
|
||||
var x:Float = cos * bone.a + sin * bone.b;
|
||||
var y:Float = cos * bone.c + sin * bone.d;
|
||||
|
||||
@ -66,13 +66,13 @@ class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
public var sequence:Sequence;
|
||||
|
||||
/** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex.
|
||||
*
|
||||
*
|
||||
* See RegionAttachment.updateRegion(). */
|
||||
private var offsets:Array<Float> = new Array<Float>();
|
||||
|
||||
public var uvs:Array<Float> = new Array<Float>();
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param name The attachment name.
|
||||
* @param path The path used to find the region for the attachment.
|
||||
*/
|
||||
@ -148,17 +148,16 @@ class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
|
||||
/** Transforms the attachment's four vertices to world coordinates. If the attachment has a RegionAttachment.sequence, the region may
|
||||
* be changed.
|
||||
*
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
* @param worldVertices The output world vertices. Must have a length >= offset + 8.
|
||||
* @param offset The worldVertices index to begin writing values.
|
||||
* @param stride The number of worldVertices entries between the value pairs written. */
|
||||
public function computeWorldVertices(slot:Slot, worldVertices:Array<Float>, offset:Int, stride:Int):Void {
|
||||
if (sequence != null)
|
||||
sequence.apply(slot, this);
|
||||
if (sequence != null) sequence.apply(slot.applied, this);
|
||||
|
||||
var bone = slot.bone;
|
||||
var vertexOffset = this.offsets;
|
||||
var bone = slot.bone.applied;
|
||||
var x = bone.worldX, y = bone.worldY;
|
||||
var a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||
var offsetX:Float = 0, offsetY:Float = 0;
|
||||
|
||||
@ -34,7 +34,7 @@ import spine.Skeleton;
|
||||
import spine.Slot;
|
||||
|
||||
/** Base class for an attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
|
||||
* spine.Slot.deform. */
|
||||
* spine.SlotPose.deform. */
|
||||
class VertexAttachment extends Attachment {
|
||||
private static var nextID:Int = 0;
|
||||
|
||||
@ -42,15 +42,19 @@ class VertexAttachment extends Attachment {
|
||||
* the vertex followed by that many bone indices, which is the index of the bone in spine.Skeleton.bones. Will be null
|
||||
* if this attachment has no weights. */
|
||||
public var bones:Array<Int>;
|
||||
|
||||
/** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y`
|
||||
* entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting
|
||||
* each vertex. */
|
||||
public var vertices = new Array<Float>();
|
||||
|
||||
/** The maximum number of world vertex values that can be output by
|
||||
* computeWorldVertices() using the `count` parameter. */
|
||||
public var worldVerticesLength:Int = 0;
|
||||
|
||||
/** Returns a unique ID for this attachment. */
|
||||
public var id:Int = nextID++;
|
||||
|
||||
/** Timelines for the timeline attachment are also applied to this attachment.
|
||||
* May be null if no attachment-specific timelines should be applied. */
|
||||
public var timelineAttachment:VertexAttachment;
|
||||
@ -60,7 +64,7 @@ class VertexAttachment extends Attachment {
|
||||
timelineAttachment = this;
|
||||
}
|
||||
|
||||
/** Transforms the attachment's local vertices to world coordinates. If the slot's spine.Slot.deform is
|
||||
/** Transforms the attachment's local vertices to world coordinates. If the slot's spine.SlotPose.deform is
|
||||
* not empty, it is used to deform the vertices.
|
||||
*
|
||||
* @see https://esotericsoftware.com/spine-runtime-skeletons#World-transforms World transforms in the Spine Runtimes Guide
|
||||
@ -70,61 +74,42 @@ class VertexAttachment extends Attachment {
|
||||
* `stride` / 2.
|
||||
* @param offset The `worldVertices` index to begin writing values.
|
||||
* @param stride The number of `worldVertices` entries between the value pairs written. */
|
||||
public function computeWorldVertices(slot:Slot, start:Int, count:Int, worldVertices:Array<Float>, offset:Int, stride:Int):Void {
|
||||
public function computeWorldVertices(skeleton:Skeleton, slot:Slot, start:Int, count:Int, worldVertices:Array<Float>, offset:Int, stride:Int):Void {
|
||||
count = offset + (count >> 1) * stride;
|
||||
var skeleton:Skeleton = slot.skeleton;
|
||||
var deform:Array<Float> = slot.deform;
|
||||
|
||||
var v:Int, w:Int, n:Int, i:Int, skip:Int, b:Int, f:Int;
|
||||
var vx:Float, vy:Float;
|
||||
var wx:Float, wy:Float;
|
||||
var bone:Bone;
|
||||
|
||||
var deform:Array<Float> = slot.applied.deform;
|
||||
var vertices = vertices;
|
||||
if (bones == null) {
|
||||
if (deform.length > 0)
|
||||
vertices = deform;
|
||||
bone = slot.bone;
|
||||
var x:Float = bone.worldX;
|
||||
var y:Float = bone.worldY;
|
||||
var a:Float = bone.a,
|
||||
bb:Float = bone.b,
|
||||
c:Float = bone.c,
|
||||
d:Float = bone.d;
|
||||
v = start;
|
||||
w = offset;
|
||||
if (deform.length > 0) vertices = deform;
|
||||
var bone = slot.bone.applied;
|
||||
var x = bone.worldX, y = bone.worldY;
|
||||
var a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||
var v = start, w = offset;
|
||||
while (w < count) {
|
||||
vx = vertices[v];
|
||||
vy = vertices[v + 1];
|
||||
worldVertices[w] = vx * a + vy * bb + x;
|
||||
var vx = vertices[v], vy = vertices[v + 1];
|
||||
worldVertices[w] = vx * a + vy * b + x;
|
||||
worldVertices[w + 1] = vx * c + vy * d + y;
|
||||
v += 2;
|
||||
w += stride;
|
||||
}
|
||||
return;
|
||||
}
|
||||
v = 0;
|
||||
skip = 0;
|
||||
i = 0;
|
||||
var v = 0, skip = 0, i = 0;
|
||||
while (i < start) {
|
||||
n = bones[v];
|
||||
var n = bones[v];
|
||||
v += n + 1;
|
||||
skip += n;
|
||||
i += 2;
|
||||
}
|
||||
var skeletonBones:Array<Bone> = skeleton.bones;
|
||||
var skeletonBones = skeleton.bones;
|
||||
if (deform.length == 0) {
|
||||
w = offset;
|
||||
b = skip * 3;
|
||||
var w = offset, b = skip * 3;
|
||||
while (w < count) {
|
||||
wx = 0;
|
||||
wy = 0;
|
||||
n = bones[v++];
|
||||
var wx = 0., wy = 0.;
|
||||
var n = bones[v++];
|
||||
n += v;
|
||||
while (v < n) {
|
||||
bone = skeletonBones[bones[v]];
|
||||
vx = vertices[b];
|
||||
vy = vertices[b + 1];
|
||||
var weight:Float = vertices[b + 2];
|
||||
var bone = skeletonBones[bones[v]].applied;
|
||||
var vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
|
||||
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
|
||||
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
|
||||
v++;
|
||||
@ -135,19 +120,14 @@ class VertexAttachment extends Attachment {
|
||||
w += stride;
|
||||
}
|
||||
} else {
|
||||
w = offset;
|
||||
b = skip * 3;
|
||||
f = skip << 1;
|
||||
var w = offset, b = skip * 3, f = skip << 1;
|
||||
while (w < count) {
|
||||
wx = 0;
|
||||
wy = 0;
|
||||
n = bones[v++];
|
||||
var wx = 0., wy = 0.;
|
||||
var n = bones[v++];
|
||||
n += v;
|
||||
while (v < n) {
|
||||
bone = skeletonBones[bones[v]];
|
||||
vx = vertices[b] + deform[f];
|
||||
vy = vertices[b + 1] + deform[f + 1];
|
||||
var weight = vertices[b + 2];
|
||||
var bone = skeletonBones[bones[v]].applied;
|
||||
var vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
|
||||
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
|
||||
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
|
||||
v++;
|
||||
|
||||
@ -201,7 +201,7 @@ class SkeletonSprite extends FlxObject
|
||||
for (slot in drawOrder) {
|
||||
var clippedVertexSize:Int = clipper.isClipping() ? 2 : vertexSize;
|
||||
if (!slot.bone.active) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -239,7 +239,7 @@ class SkeletonSprite extends FlxObject
|
||||
clipper.clipStart(slot, clip);
|
||||
continue;
|
||||
} else {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -312,7 +312,7 @@ class SkeletonSprite extends FlxObject
|
||||
mesh.draw();
|
||||
}
|
||||
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
}
|
||||
clipper.clipEnd();
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ class SkeletonSprite extends DisplayObject implements IAnimatable {
|
||||
|
||||
for (slot in drawOrder) {
|
||||
if (!slot.bone.active) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -178,13 +178,13 @@ class SkeletonSprite extends DisplayObject implements IAnimatable {
|
||||
clipper.clipStart(slot, clip);
|
||||
continue;
|
||||
} else {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
a = slot.color.a * attachmentColor.a;
|
||||
if (a == 0) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
rgb = Color.rgb(Std.int(r * slot.color.r * attachmentColor.r), Std.int(g * slot.color.g * attachmentColor.g),
|
||||
@ -230,7 +230,7 @@ class SkeletonSprite extends DisplayObject implements IAnimatable {
|
||||
painter.batchMesh(mesh);
|
||||
}
|
||||
|
||||
clipper.clipEndWithSlot(slot);
|
||||
clipper.clipEnd(slot);
|
||||
}
|
||||
painter.state.blendMode = originalBlendMode;
|
||||
clipper.clipEnd();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user