2025-06-12 15:17:06 +02:00

1464 lines
59 KiB
Haxe

/******************************************************************************
* 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.animation.BoneTimeline2;
import spine.animation.SliderMixTimeline;
import spine.animation.SliderTimeline;
import spine.TransformConstraintData;
import Reflect;
import haxe.Json;
import spine.animation.AlphaTimeline;
import spine.animation.Animation;
import spine.animation.AttachmentTimeline;
import spine.animation.CurveTimeline1;
import spine.animation.CurveTimeline;
import spine.animation.DeformTimeline;
import spine.animation.DrawOrderTimeline;
import spine.animation.EventTimeline;
import spine.animation.IkConstraintTimeline;
import spine.animation.InheritTimeline;
import spine.animation.PathConstraintMixTimeline;
import spine.animation.PathConstraintPositionTimeline;
import spine.animation.PathConstraintSpacingTimeline;
import spine.animation.PhysicsConstraintDampingTimeline;
import spine.animation.PhysicsConstraintGravityTimeline;
import spine.animation.PhysicsConstraintInertiaTimeline;
import spine.animation.PhysicsConstraintMassTimeline;
import spine.animation.PhysicsConstraintMixTimeline;
import spine.animation.PhysicsConstraintResetTimeline;
import spine.animation.PhysicsConstraintStrengthTimeline;
import spine.animation.PhysicsConstraintWindTimeline;
import spine.animation.RGB2Timeline;
import spine.animation.RGBA2Timeline;
import spine.animation.RGBATimeline;
import spine.animation.RGBTimeline;
import spine.animation.RotateTimeline;
import spine.animation.ScaleTimeline;
import spine.animation.ScaleXTimeline;
import spine.animation.ScaleYTimeline;
import spine.animation.SequenceTimeline;
import spine.animation.ShearTimeline;
import spine.animation.ShearXTimeline;
import spine.animation.ShearYTimeline;
import spine.animation.Timeline;
import spine.animation.TransformConstraintTimeline;
import spine.animation.TranslateTimeline;
import spine.animation.TranslateXTimeline;
import spine.animation.TranslateYTimeline;
import spine.attachments.Attachment;
import spine.attachments.AttachmentLoader;
import spine.attachments.AttachmentType;
import spine.attachments.BoundingBoxAttachment;
import spine.attachments.ClippingAttachment;
import spine.attachments.MeshAttachment;
import spine.attachments.PathAttachment;
import spine.attachments.PointAttachment;
import spine.attachments.RegionAttachment;
import spine.attachments.VertexAttachment;
class SkeletonJson {
public var attachmentLoader:AttachmentLoader;
public var scale:Float = 1;
private var linkedMeshes:Array<LinkedMesh> = new Array<LinkedMesh>();
public function new(attachmentLoader:AttachmentLoader) {
this.attachmentLoader = attachmentLoader;
}
public function readSkeletonData(json:String):SkeletonData {
if (json == null)
throw new SpineException("object cannot be null.");
var root = Json.parse(json);
var skeletonData:SkeletonData = new SkeletonData();
// Skeleton.
var skeletonMap = getString(root, "skeleton", "");
if (skeletonMap != null) {
skeletonData.hash = getString(skeletonMap, "hash", "");
skeletonData.version = getString(skeletonMap, "spine", "");
skeletonData.x = getFloat(skeletonMap, "x");
skeletonData.y = getFloat(skeletonMap, "y");
skeletonData.width = getFloat(skeletonMap, "width");
skeletonData.height = getFloat(skeletonMap, "height");
skeletonData.referenceScale = getFloat(skeletonMap, "referenceScale", 100) * scale;
skeletonData.fps = getFloat(skeletonMap, "fps");
skeletonData.imagesPath = getString(skeletonMap, "images", "");
skeletonData.audioPath = getString(skeletonMap, "audio", "");
}
// Bones.
for (boneMap in cast(Reflect.getProperty(root, "bones"), Array<Dynamic>)) {
var parent:BoneData = null;
var parentName:String = Reflect.getProperty(boneMap, "parent");
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null)
throw new SpineException("Parent bone not found: " + parentName);
}
var data = new BoneData(skeletonData.bones.length, Reflect.getProperty(boneMap, "name"), parent);
data.length = getFloat(boneMap, "length") * scale;
var setup = data.setup;
setup.x = getFloat(boneMap, "x") * scale;
setup.y = getFloat(boneMap, "y") * scale;
setup.rotation = getFloat(boneMap, "rotation");
setup.scaleX = getFloat(boneMap, "scaleX", 1);
setup.scaleY = getFloat(boneMap, "scaleY", 1);
setup.shearX = getFloat(boneMap, "shearX");
setup.shearY = getFloat(boneMap, "shearY");
setup.inherit = Reflect.hasField(boneMap, "inherit") ? Inherit.fromName(Reflect.getProperty(boneMap, "inherit")) : Inherit.normal;
data.skinRequired = Reflect.hasField(boneMap, "skin") ? cast(Reflect.getProperty(boneMap, "skin"), Bool) : false;
var color:String = Reflect.getProperty(boneMap, "color");
if (color != null) data.color.setFromString(color);
skeletonData.bones.push(data);
}
// Slots.
for (slotMap in cast(Reflect.getProperty(root, "slots"), Array<Dynamic>)) {
var slotName:String = Reflect.getProperty(slotMap, "name");
var boneName:String = Reflect.getProperty(slotMap, "bone");
var boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new SpineException("Slot bone not found: " + boneName);
var data = new SlotData(skeletonData.slots.length, slotName, boneData);
var color:String = Reflect.getProperty(slotMap, "color");
if (color != null) data.setup.color.setFromString(color);
var dark:String = Reflect.getProperty(slotMap, "dark");
if (dark != null) data.setup.darkColor = new Color(0, 0, 0).setFromString(dark);
data.attachmentName = Reflect.getProperty(slotMap, "attachment");
data.blendMode = Reflect.hasField(slotMap, "blend") ? BlendMode.fromName(Reflect.getProperty(slotMap, "blend")) : BlendMode.normal;
data.visible = getValue(slotMap, "visible", true);
skeletonData.slots.push(data);
}
// Constraints.
if (Reflect.hasField(root, "constraints")) {
for (constraintMap in cast(Reflect.getProperty(root, "constraints"), Array<Dynamic>)) {
var name:String = Reflect.getProperty(constraintMap, "name");
var skinRequired:Bool = Reflect.getProperty(constraintMap, "skinRequired");
var type:String = Reflect.getProperty(constraintMap, "type");
switch (type) {
case "ik":
var data = new IkConstraintData(name);
data.skinRequired = skinRequired;
for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
var bone = skeletonData.findBone(boneName);
if (bone == null) throw new SpineException("IK constraint bone not found: " + boneName);
data.bones.push(bone);
}
data.target = skeletonData.findBone(Reflect.getProperty(constraintMap, "target"));
if (data.target == null) throw new SpineException("Target bone not found: " + Reflect.getProperty(constraintMap, "target"));
data.uniform = (Reflect.hasField(constraintMap, "uniform") && cast(Reflect.getProperty(constraintMap, "uniform"), Bool));
var setup = data.setup;
setup.mix = getFloat(constraintMap, "mix", 1);
setup.softness = getFloat(constraintMap, "softness", 0) * scale;
setup.bendDirection = (!Reflect.hasField(constraintMap, "bendPositive") || cast(Reflect.getProperty(constraintMap, "bendPositive"), Bool)) ? 1 : -1;
setup.compress = (Reflect.hasField(constraintMap, "compress") && cast(Reflect.getProperty(constraintMap, "compress"), Bool));
setup.stretch = (Reflect.hasField(constraintMap, "stretch") && cast(Reflect.getProperty(constraintMap, "stretch"), Bool));
skeletonData.constraints.push(data);
case "transform":
var data = new TransformConstraintData(name);
data.skinRequired = skinRequired;
for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
var bone = skeletonData.findBone(boneName);
if (bone == null) throw new SpineException("Transform constraint bone not found: " + boneName);
data.bones.push(bone);
}
data.source = skeletonData.findBone(Reflect.getProperty(constraintMap, "source"));
if (data.source == null) throw new SpineException("Transform constraint source bone not found: " + Reflect.getProperty(constraintMap, "source"));
data.localSource = Reflect.hasField(constraintMap, "localSource") ? cast(Reflect.getProperty(constraintMap, "localSource"), Bool) : false;
data.localTarget = Reflect.hasField(constraintMap, "localTarget") ? cast(Reflect.getProperty(constraintMap, "localTarget"), Bool) : false;
data.additive = Reflect.hasField(constraintMap, "additive") ? cast(Reflect.getProperty(constraintMap, "additive"), Bool) : false;
data.clamp = Reflect.hasField(constraintMap, "clamp") ? cast(Reflect.getProperty(constraintMap, "clamp"), Bool) : false;
var rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
var propertiesMap:Dynamic = Reflect.getProperty(constraintMap, "properties");
for (name in Reflect.fields(propertiesMap)) {
var fromEntry = Reflect.field(propertiesMap, name);
var from = fromProperty(name);
var fromScale = propertyScale(name, scale);
from.offset = getFloat(fromEntry, "offset", 0) * fromScale;
var toMap:Dynamic = Reflect.getProperty(fromEntry, "to");
for (name in Reflect.fields(toMap)) {
var toEntry = Reflect.field(toMap, name);
var toScale = 1.;
var to:ToProperty;
switch (name) {
case "rotate": {
rotate = true;
to = new ToRotate();
}
case "x": {
x = true;
to = new ToX();
toScale = scale;
}
case "y": {
y = true;
to = new ToY();
toScale = scale;
}
case "scaleX": {
scaleX = true;
to = new ToScaleX();
}
case "scaleY": {
scaleY = true;
to = new ToScaleY();
}
case "shearY": {
shearY = true;
to = new ToShearY();
}
default: throw new SpineException("Invalid transform constraint to property: " + toEntry.name);
}
to.offset = getFloat(toEntry, "offset", 0) * toScale;
to.max = getFloat(toEntry, "max", 1) * toScale;
to.scale = getFloat(toEntry, "scale") * toScale / fromScale;
from.to.push(to);
}
if (from.to.length > 0) data.properties.push(from);
}
data.offsets[0] = getFloat(constraintMap, "rotation", 0);
data.offsets[1] = getFloat(constraintMap, "x", 0) * scale;
data.offsets[2] = getFloat(constraintMap, "y", 0) * scale;
data.offsets[3] = getFloat(constraintMap, "scaleX", 0);
data.offsets[4] = getFloat(constraintMap, "scaleY", 0);
data.offsets[5] = getFloat(constraintMap, "shearY", 0);
var setup = data.setup;
if (rotate) setup.mixRotate = getFloat(constraintMap, "mixRotate", 1);
if (x) setup.mixX = getFloat(constraintMap, "mixX", 1);
if (y) setup.mixY = getFloat(constraintMap, "mixY", setup.mixX);
if (scaleX) setup.mixScaleX = getFloat(constraintMap, "mixScaleX", 1);
if (scaleY) setup.mixScaleY = getFloat(constraintMap, "mixScaleY", setup.mixScaleX);
if (shearY) setup.mixShearY = getFloat(constraintMap, "mixShearY", 1);
skeletonData.constraints.push(data);
case "path":
var data = new PathConstraintData(name);
data.skinRequired = skinRequired;
for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
var bone = skeletonData.findBone(boneName);
if (bone == null) throw new SpineException("Path bone not found: " + boneName);
data.bones.push(bone);
}
var slotName = getString(constraintMap, "slot", "");
data.slot = skeletonData.findSlot(slotName);
if (data.slot == null) throw new SpineException("Path slot not found: " + slotName);
data.positionMode = Reflect.hasField(constraintMap, "positionMode") ? PositionMode.fromName(Reflect.getProperty(constraintMap, "positionMode")) : PositionMode.percent;
data.spacingMode = Reflect.hasField(constraintMap, "spacingMode") ? SpacingMode.fromName(Reflect.getProperty(constraintMap, "spacingMode")) : SpacingMode.length;
data.rotateMode = Reflect.hasField(constraintMap, "rotateMode") ? RotateMode.fromName(Reflect.getProperty(constraintMap, "rotateMode")) : RotateMode.tangent;
data.offsetRotation = getFloat(constraintMap, "rotation", 0);
var setup = data.setup;
setup.position = getFloat(constraintMap, "position", 0);
if (data.positionMode == PositionMode.fixed) setup.position *= scale;
setup.spacing = getFloat(constraintMap, "spacing", 0);
if (data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed) setup.spacing *= scale;
setup.mixRotate = getFloat(constraintMap, "mixRotate", 1);
setup.mixX = getFloat(constraintMap, "mixX", 1);
setup.mixY = getFloat(constraintMap, "mixY", setup.mixX);
skeletonData.constraints.push(data);
case "physics":
var data = new PhysicsConstraintData(name);
data.skinRequired = skinRequired;
var boneName:String = getString(constraintMap, "bone");
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SpineException("Physics bone not found: " + boneName);
data.x = getFloat(constraintMap, "x");
data.y = getFloat(constraintMap, "y");
data.rotate = getFloat(constraintMap, "rotate");
data.scaleX = getFloat(constraintMap, "scaleX");
data.shearX = getFloat(constraintMap, "shearX");
data.limit = getFloat(constraintMap, "limit", 5000) * scale;
data.step = 1 / getFloat(constraintMap, "fps", 60);
var setup = data.setup;
setup.inertia = getFloat(constraintMap, "inertia", .5);
setup.strength = getFloat(constraintMap, "strength", 100);
setup.damping = getFloat(constraintMap, "damping", .85);
setup.massInverse = 1 / getFloat(constraintMap, "mass", 1);
setup.wind = getFloat(constraintMap, "wind", 0);
setup.gravity = getFloat(constraintMap, "gravity", 0);
setup.mix = getValue(constraintMap, "mix", 1);
data.inertiaGlobal = Reflect.hasField(constraintMap, "inertiaGlobal") ? cast(Reflect.getProperty(constraintMap, "inertiaGlobal"), Bool) : false;
data.strengthGlobal = Reflect.hasField(constraintMap, "strengthGlobal") ? cast(Reflect.getProperty(constraintMap, "strengthGlobal"), Bool) : false;
data.dampingGlobal = Reflect.hasField(constraintMap, "dampingGlobal") ? cast(Reflect.getProperty(constraintMap, "dampingGlobal"), Bool) : false;
data.dampingGlobal = Reflect.hasField(constraintMap, "dampingGlobal") ? cast(Reflect.getProperty(constraintMap, "dampingGlobal"), Bool) : false;
data.windGlobal = Reflect.hasField(constraintMap, "windGlobal") ? cast(Reflect.getProperty(constraintMap, "windGlobal"), Bool) : false;
data.gravityGlobal = Reflect.hasField(constraintMap, "gravityGlobal") ? cast(Reflect.getProperty(constraintMap, "gravityGlobal"), Bool) : false;
data.mixGlobal = Reflect.hasField(constraintMap, "mixGlobal") ? cast(Reflect.getProperty(constraintMap, "mixGlobal"), Bool) : false;
skeletonData.constraints.push(data);
case "slider":
var data = new SliderData(name);
data.skinRequired = skinRequired;
data.additive = getBoolean(constraintMap, "additive", false);
data.loop = getBoolean(constraintMap, "loop", false);
data.setup.time = getFloat(constraintMap, "time", 0);
data.setup.mix = getFloat(constraintMap, "mix", 1);
var boneName = constraintMap.getString("bone", null);
if (boneName != null) {
data.bone = skeletonData.findBone(boneName);
if (data.bone == null) throw new SpineException("Slider bone not found: " + boneName);
var property = getString(constraintMap, "property");
data.property = fromProperty(property);
data.property.offset = getFloat(constraintMap, "offset", 0) * propertyScale(property, scale);
data.scale = getFloat(constraintMap, "scale");
data.local = getBoolean(constraintMap, "local", false);
}
skeletonData.constraints.push(data);
}
}
}
// Skins.
if (Reflect.hasField(root, "skins")) {
for (skinMap in cast(Reflect.getProperty(root, "skins"), Array<Dynamic>)) {
var skin:Skin = new Skin(Reflect.getProperty(skinMap, "name"));
if (Reflect.hasField(skinMap, "bones")) {
var bones:Array<Dynamic> = cast(Reflect.getProperty(skinMap, "bones"), Array<Dynamic>);
for (ii in 0...bones.length) {
var boneData:BoneData = skeletonData.findBone(bones[ii]);
if (boneData == null)
throw new SpineException("Skin bone not found: " + bones[ii]);
skin.bones.push(boneData);
}
}
if (Reflect.hasField(skinMap, "ik")) {
var ik = cast(Reflect.getProperty(skinMap, "ik"), Array<Dynamic>);
for (ii in 0...ik.length) {
var constraint = skeletonData.findConstraint(ik[ii], IkConstraintData);
if (constraint == null)
throw new SpineException("Skin IK constraint not found: " + ik[ii]);
skin.constraints.push(constraint);
}
}
if (Reflect.hasField(skinMap, "transform")) {
var transform = cast(Reflect.getProperty(skinMap, "transform"), Array<Dynamic>);
for (ii in 0...transform.length) {
var constraint = skeletonData.findConstraint(transform[ii], TransformConstraintData);
if (constraint == null)
throw new SpineException("Skin transform constraint not found: " + transform[ii]);
skin.constraints.push(constraint);
}
}
if (Reflect.hasField(skinMap, "path")) {
var path = cast(Reflect.getProperty(skinMap, "path"), Array<Dynamic>);
for (ii in 0...path.length) {
var constraint = skeletonData.findConstraint(path[ii], PathConstraintData);
if (constraint == null)
throw new SpineException("Skin path constraint not found: " + path[ii]);
skin.constraints.push(constraint);
}
}
if (Reflect.hasField(skinMap, "physics")) {
var physics = cast(Reflect.getProperty(skinMap, "physics"), Array<Dynamic>);
for (ii in 0...physics.length) {
var constraint = skeletonData.findConstraint(physics[ii], PhysicsConstraintData);
if (constraint == null)
throw new SpineException("Skin physics constraint not found: " + physics[ii]);
skin.constraints.push(constraint);
}
}
if (Reflect.hasField(skinMap, "slider")) {
var slider = cast(Reflect.getProperty(skinMap, "slider"), Array<Dynamic>);
for (ii in 0...slider.length) {
var constraint = skeletonData.findConstraint(slider[ii], SliderData);
if (constraint == null) throw new SpineException("Skin slider constraint not found: " + slider[ii]);
skin.constraints.push(constraint);
}
}
if (Reflect.hasField(skinMap, "attachments")) {
var attachments:Dynamic = Reflect.getProperty(skinMap, "attachments");
for (slotName in Reflect.fields(attachments)) {
var slot:SlotData = skeletonData.findSlot(slotName);
var slotEntry:Dynamic = Reflect.getProperty(attachments, slotName);
for (attachmentName in Reflect.fields(slotEntry)) {
var attachment:Attachment = readAttachment(Reflect.getProperty(slotEntry, attachmentName), skin, slot.index, attachmentName,
skeletonData);
if (attachment != null) {
skin.setAttachment(slot.index, attachmentName, attachment);
}
}
}
}
skeletonData.skins.push(skin);
if (skin.name == "default") {
skeletonData.defaultSkin = skin;
}
}
}
// Linked meshes.
for (linkedMesh in linkedMeshes) {
var parentSkin:Skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (parentSkin == null)
throw new SpineException("Skin not found: " + linkedMesh.skin);
var parentMesh:Attachment = parentSkin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parentMesh == null)
throw new SpineException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? cast(parentMesh, VertexAttachment) : linkedMesh.mesh;
linkedMesh.mesh.parentMesh = cast(parentMesh, MeshAttachment);
if (linkedMesh.mesh.region != null)
linkedMesh.mesh.updateRegion();
}
linkedMeshes.resize(0);
// Events.
var events:Dynamic = Reflect.getProperty(root, "events");
for (eventName in Reflect.fields(events)) {
var eventMap:Map<String, Dynamic> = Reflect.field(events, eventName);
var eventData:EventData = new EventData(eventName);
eventData.intValue = getInt(eventMap, "int");
eventData.floatValue = getFloat(eventMap, "float");
eventData.stringValue = getString(eventMap, "string", "");
eventData.audioPath = getString(eventMap, "audio", "");
if (eventData.audioPath != null) {
eventData.volume = getFloat(eventMap, "volume", 1);
eventData.balance = getFloat(eventMap, "balance");
}
skeletonData.events.push(eventData);
}
// Animations.
var animations:Dynamic = Reflect.getProperty(root, "animations");
for (animationName in Reflect.fields(animations)) {
readAnimation(Reflect.field(animations, animationName), animationName, skeletonData);
}
// Slider animations.
if (Reflect.hasField(root, "constraints")) {
for (constraintMap in cast(Reflect.getProperty(root, "constraints"), Array<Dynamic>)) {
if (Reflect.getProperty(constraintMap, "type") == "slider") {
var data = skeletonData.findConstraint(getString(constraintMap, "name"), SliderData);
var animationName = getString(constraintMap, "animation", "");
data.animation = skeletonData.findAnimation(animationName);
if (data.animation == null) throw new SpineException("Slider animation not found: " + animationName);
}
}
}
return skeletonData;
}
private function fromProperty (type:String): FromProperty {
var property:FromProperty;
switch (type) {
case "rotate": property = new FromRotate();
case "x": property = new FromX();
case "y": property = new FromY();
case "scaleX": property = new FromScaleX();
case "scaleY": property = new FromScaleY();
case "shearY": property = new FromShearY();
default: throw new SpineException("Invalid from property: " + type);
};
return property;
}
private function propertyScale (type:String, scale:Float):Float {
var scaleValue:Float;
switch (type) {
case "x", "y": scaleValue = scale;
default: scaleValue = 1;
};
return scaleValue;
}
private function readAttachment(map:Dynamic, skin:Skin, slotIndex:Int, name:String, skeletonData:SkeletonData):Attachment {
if (Reflect.field(map, "name") != null)
name = Reflect.field(map, "name");
var color:String;
switch (AttachmentType.fromName(Reflect.hasField(map, "type") ? Reflect.getProperty(map, "type") : "region")) {
case AttachmentType.region:
var path = getString(map, "path", name);
var sequence = readSequence(Reflect.field(map, "sequence"));
var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
if (region == null)
return null;
region.path = path;
region.x = getFloat(map, "x") * scale;
region.y = getFloat(map, "y") * scale;
region.scaleX = getFloat(map, "scaleX", 1);
region.scaleY = getFloat(map, "scaleY", 1);
region.rotation = getFloat(map, "rotation");
region.width = getFloat(map, "width") * scale;
region.height = getFloat(map, "height") * scale;
region.sequence = sequence;
color = Reflect.getProperty(map, "color");
if (color != null) {
region.color.setFromString(color);
}
if (region.region != null)
region.updateRegion();
return region;
case AttachmentType.mesh, AttachmentType.linkedmesh:
var path = getString(map, "path", name);
var sequence = readSequence(Reflect.field(map, "sequence"));
var mesh:MeshAttachment = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null)
return null;
mesh.path = path;
color = Reflect.getProperty(map, "color");
if (color != null) {
mesh.color.setFromString(color);
}
mesh.width = getFloat(map, "width") * scale;
mesh.height = getFloat(map, "height") * scale;
mesh.sequence = sequence;
if (Reflect.field(map, "parent") != null) {
var inheritTimelines:Bool = Reflect.hasField(map, "timelines") ? cast(Reflect.field(map, "timelines"), Bool) : true;
linkedMeshes.push(new LinkedMesh(mesh, Reflect.field(map, "skin"), slotIndex, Reflect.field(map, "parent"), inheritTimelines));
return mesh;
}
var uvs:Array<Float> = getFloatArray(map, "uvs");
readVertices(map, mesh, uvs.length);
mesh.triangles = getIntArray(map, "triangles");
mesh.regionUVs = uvs;
if (mesh.region != null)
mesh.updateRegion();
if (Reflect.field(map, "edges") != null)
mesh.edges = getIntArray(map, "edges");
mesh.hullLength = getInt(map, "hull") * 2;
return mesh;
case AttachmentType.boundingbox:
var box:BoundingBoxAttachment = attachmentLoader.newBoundingBoxAttachment(skin, name);
if (box == null)
return null;
readVertices(map, box, Std.parseInt(Reflect.field(map, "vertexCount")) << 1);
return box;
case AttachmentType.path:
var path:PathAttachment = attachmentLoader.newPathAttachment(skin, name);
if (path == null)
return null;
path.closed = Reflect.hasField(map, "closed") ? cast(Reflect.field(map, "closed"), Bool) : false;
path.constantSpeed = Reflect.hasField(map, "constantSpeed") ? cast(Reflect.field(map, "constantSpeed"), Bool) : true;
var vertexCount:Int = Std.parseInt(Reflect.field(map, "vertexCount"));
readVertices(map, path, vertexCount << 1);
var lengths:Array<Float> = new Array<Float>();
for (curves in cast(Reflect.field(map, "lengths"), Array<Dynamic>)) {
lengths.push(Std.parseFloat(curves) * scale);
}
path.lengths = lengths;
return path;
case AttachmentType.point:
var point:PointAttachment = attachmentLoader.newPointAttachment(skin, name);
if (point == null)
return null;
point.x = getFloat(map, "x", 0) * scale;
point.y = getFloat(map, "y", 0) * scale;
point.rotation = getFloat(map, "rotation", 0);
color = Reflect.getProperty(map, "color");
if (color != null) {
point.color.setFromString(color);
}
return point;
case AttachmentType.clipping:
var clip:ClippingAttachment = attachmentLoader.newClippingAttachment(skin, name);
if (clip == null)
return null;
var end:String = getString(map, "end", null);
if (end != null) {
var slot:SlotData = skeletonData.findSlot(end);
if (slot == null)
throw new SpineException("Clipping end slot not found: " + end);
clip.endSlot = slot;
}
var vertexCount:Int = getInt(map, "vertexCount", 0);
readVertices(map, clip, vertexCount << 1);
color = Reflect.getProperty(map, "color");
if (color != null) {
clip.color.setFromString(color);
}
return clip;
}
return null;
}
private function readSequence(map:Dynamic) {
if (map == null)
return null;
var sequence = new Sequence(getInt(map, "count", 0));
sequence.start = getInt(map, "start", 1);
sequence.digits = getInt(map, "digits", 0);
sequence.setupIndex = getInt(map, "setup", 0);
return sequence;
}
private function readVertices(map:Dynamic, attachment:VertexAttachment, verticesLength:Int):Void {
attachment.worldVerticesLength = verticesLength;
var vertices:Array<Float> = getFloatArray(map, "vertices");
if (verticesLength == vertices.length) {
if (scale != 1) {
for (i in 0...vertices.length) {
vertices[i] *= scale;
}
}
attachment.vertices = vertices;
return;
}
var weights:Array<Float> = new Array<Float>();
var bones:Array<Int> = new Array<Int>();
var i:Int = 0;
var n:Int = vertices.length;
while (i < n) {
var boneCount:Int = Std.int(vertices[i++]);
bones.push(boneCount);
var nn:Int = i + boneCount * 4;
while (i < nn) {
bones.push(Std.int(vertices[i]));
weights.push(vertices[i + 1] * scale);
weights.push(vertices[i + 2] * scale);
weights.push(vertices[i + 3]);
i += 4;
}
}
attachment.bones = bones;
attachment.vertices = weights;
}
private function readAnimation(map:Dynamic, name:String, skeletonData:SkeletonData):Void {
var timelines:Array<Timeline> = new Array<Timeline>();
var slotMap:Dynamic;
var slotIndex:Int;
var slotName:String;
var timelineMap:Array<Dynamic>;
var keyMap:Dynamic;
var nextMap:Dynamic;
var frame:Int, bezier:Int;
var time:Float, time2:Float;
var curve:Dynamic;
var timelineName:String;
// Slot timelines.
var slots:Dynamic = Reflect.getProperty(map, "slots");
for (slotName in Reflect.fields(slots)) {
slotMap = Reflect.field(slots, slotName);
slotIndex = skeletonData.findSlot(slotName).index;
for (timelineName in Reflect.fields(slotMap)) {
timelineMap = Reflect.field(slotMap, timelineName);
if (timelineMap == null) continue;
switch (timelineName) {
case "attachment":
var attachmentTimeline = new AttachmentTimeline(timelineMap.length, slotIndex);
for (frame in 0...timelineMap.length) {
keyMap = timelineMap[frame];
attachmentTimeline.setFrame(frame, getFloat(keyMap, "time"), getString(keyMap, "name", null));
}
timelines.push(attachmentTimeline);
case "rgba":
var rgbaTimeline = new RGBATimeline(timelineMap.length, timelineMap.length << 2, slotIndex);
keyMap = timelineMap[0];
time = getFloat(keyMap, "time");
var rgba:Color = Color.fromString(keyMap.color);
frame = 0;
bezier = 0;
while (true) {
rgbaTimeline.setFrame(frame, time, rgba.r, rgba.g, rgba.b, rgba.a);
if (timelineMap.length == frame + 1)
break;
nextMap = timelineMap[frame + 1];
time2 = getFloat(nextMap, "time");
var newRgba:Color = Color.fromString(nextMap.color);
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, rgbaTimeline, bezier, frame, 0, time, time2, rgba.r, newRgba.r, 1);
bezier = readCurve(curve, rgbaTimeline, bezier, frame, 1, time, time2, rgba.g, newRgba.g, 1);
bezier = readCurve(curve, rgbaTimeline, bezier, frame, 2, time, time2, rgba.b, newRgba.b, 1);
bezier = readCurve(curve, rgbaTimeline, bezier, frame, 3, time, time2, rgba.a, newRgba.a, 1);
}
time = time2;
rgba = newRgba;
keyMap = nextMap;
frame++;
}
timelines.push(rgbaTimeline);
case "rgb":
var rgbTimeline = new RGBTimeline(timelineMap.length, timelineMap.length * 3, slotIndex);
keyMap = timelineMap[0];
time = getFloat(keyMap, "time");
var rgb:Color = Color.fromString(keyMap.color);
frame = 0;
bezier = 0;
while (true) {
rgbTimeline.setFrame(frame, time, rgb.r, rgb.g, rgb.b);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
rgbTimeline.shrink(bezier);
break;
}
time2 = getFloat(nextMap, "time");
var newRgb:Color = Color.fromString(nextMap.color);
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, rgbTimeline, bezier, frame, 0, time, time2, rgb.r, newRgb.r, 1);
bezier = readCurve(curve, rgbTimeline, bezier, frame, 1, time, time2, rgb.g, newRgb.g, 1);
bezier = readCurve(curve, rgbTimeline, bezier, frame, 2, time, time2, rgb.b, newRgb.b, 1);
}
time = time2;
rgb = newRgb;
keyMap = nextMap;
frame++;
}
timelines.push(rgbTimeline);
case "alpha": readTimeline(timelines, timelineMap, new AlphaTimeline(timelineMap.length, timelineMap.length, slotIndex), 0, 1);
case "rgba2":
var rgba2Timeline = new RGBA2Timeline(timelineMap.length, timelineMap.length * 7, slotIndex);
keyMap = timelineMap[0];
time = getFloat(keyMap, "time");
var lighta:Color = Color.fromString(keyMap.light);
var darka:Color = Color.fromString(keyMap.dark);
frame = 0;
bezier = 0;
while (true) {
rgba2Timeline.setFrame(frame, time, lighta.r, lighta.g, lighta.b, lighta.a, darka.r, darka.g, darka.b);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
rgba2Timeline.shrink(bezier);
break;
}
time2 = getFloat(nextMap, "time");
var newLighta:Color = Color.fromString(nextMap.light);
var newDarka:Color = Color.fromString(nextMap.dark);
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 0, time, time2, lighta.r, newLighta.r, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 1, time, time2, lighta.g, newLighta.g, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 2, time, time2, lighta.b, newLighta.b, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 3, time, time2, lighta.a, newLighta.a, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 4, time, time2, darka.r, newDarka.r, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 5, time, time2, darka.g, newDarka.g, 1);
bezier = readCurve(curve, rgba2Timeline, bezier, frame, 6, time, time2, darka.b, newDarka.b, 1);
}
time = time2;
lighta = newLighta;
darka = newDarka;
keyMap = nextMap;
frame++;
}
timelines.push(rgba2Timeline);
case "rgb2":
var rgb2Timeline = new RGB2Timeline(timelineMap.length, timelineMap.length * 6, slotIndex);
keyMap = timelineMap[0];
time = getFloat(keyMap, "time");
var light:Color = Color.fromString(keyMap.light);
var dark:Color = Color.fromString(keyMap.dark);
frame = 0;
bezier = 0;
while (true) {
rgb2Timeline.setFrame(frame, time, light.r, light.g, light.b, dark.r, dark.g, dark.b);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
rgb2Timeline.shrink(bezier);
break;
}
time2 = getFloat(nextMap, "time");
var newLight:Color = Color.fromString(nextMap.light);
var newDark:Color = Color.fromString(nextMap.dark);
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 0, time, time2, light.r, newLight.r, 1);
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 1, time, time2, light.g, newLight.g, 1);
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 2, time, time2, light.b, newLight.b, 1);
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 3, time, time2, dark.r, newDark.r, 1);
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 4, time, time2, dark.g, newDark.g, 1);
bezier = readCurve(curve, rgb2Timeline, bezier, frame, 5, time, time2, dark.b, newDark.b, 1);
}
time = time2;
light = newLight;
dark = newDark;
keyMap = nextMap;
frame++;
}
timelines.push(rgb2Timeline);
default: throw new SpineException("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
}
}
}
// Bone timelines.
var bones = Reflect.getProperty(map, "bones");
for (boneName in Reflect.fields(bones)) {
var boneIndex:Int = skeletonData.findBoneIndex(boneName);
if (boneIndex == -1) throw new SpineException("Bone not found: " + boneName);
var boneMap:Dynamic = Reflect.field(bones, boneName);
for (timelineName in Reflect.fields(boneMap)) {
timelineMap = Reflect.field(boneMap, timelineName);
var frames = timelineMap.length;
if (frames == 0) continue;
switch (timelineName) {
case "rotate": readTimeline(timelines, timelineMap, new RotateTimeline(frames, frames, boneIndex), 0, 1);
case "translate": readTimeline2(timelines, timelineMap, new TranslateTimeline(frames, frames << 1, boneIndex), "x", "y", 0, scale);
case "translatex": readTimeline(timelines, timelineMap, new TranslateXTimeline(frames, frames, boneIndex), 0, scale);
case "translatey": readTimeline(timelines, timelineMap, new TranslateYTimeline(frames, frames, boneIndex), 0, scale);
case "scale": readTimeline2(timelines, timelineMap, new ScaleTimeline(frames, frames << 1, boneIndex), "x", "y", 1, 1);
case "scalex": readTimeline(timelines, timelineMap, new ScaleXTimeline(frames, frames, boneIndex), 1, 1);
case "scaley": readTimeline(timelines, timelineMap, new ScaleYTimeline(frames, frames, boneIndex), 1, 1);
case "shear": readTimeline2(timelines, timelineMap, new ShearTimeline(frames, frames << 1, boneIndex), "x", "y", 0, 1);
case "shearx": readTimeline(timelines, timelineMap, new ShearXTimeline(frames, frames, boneIndex), 0, 1);
case "sheary": readTimeline(timelines, timelineMap, new ShearYTimeline(frames, frames, boneIndex), 0, 1);
case "inherit":
var timeline = new InheritTimeline(frames, boneIndex);
for (frame in 0...frames) {
var aFrame:Dynamic = timelineMap[frame];
timeline.setFrame(frame, getFloat(aFrame, "time"), Inherit.fromName(getValue(aFrame, "inherit", "Normal")));
}
timelines.push(timeline);
default: throw new SpineException("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
}
}
}
// IK constraint timelines.
var iks:Dynamic = Reflect.getProperty(map, "ik");
for (ikConstraintName in Reflect.fields(iks)) {
timelineMap = Reflect.field(iks, ikConstraintName);
keyMap = timelineMap[0];
if (keyMap == null) continue;
var constraint = skeletonData.findConstraint(ikConstraintName, IkConstraintData);
if (constraint == null) throw new SpineException("IK constraint not found: " + ikConstraintName);
var timeline = new IkConstraintTimeline(timelineMap.length, timelineMap.length << 1,
skeletonData.constraints.indexOf(constraint));
time = getFloat(keyMap, "time");
var mix:Float = getFloat(keyMap, "mix", 1);
var softness:Float = getFloat(keyMap, "softness") * scale;
frame = 0;
bezier = 0;
while (true) {
timeline.setFrame(frame, time, mix, softness,
Reflect.hasField(keyMap, "bendPositive") ? (cast(Reflect.getProperty(keyMap, "bendPositive"), Bool) ? 1 : -1) : 1,
Reflect.hasField(keyMap, "compress") ? cast(Reflect.getProperty(keyMap, "compress"), Bool) : false,
Reflect.hasField(keyMap, "stretch") ? cast(Reflect.getProperty(keyMap, "stretch"), Bool) : false);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
timeline.shrink(bezier);
break;
}
time2 = getFloat(nextMap, "time");
var mix2:Float = getFloat(nextMap, "mix", 1);
var softness2:Float = getFloat(nextMap, "softness") * scale;
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale);
}
time = time2;
mix = mix2;
softness = softness2;
keyMap = nextMap;
frame++;
}
timelines.push(timeline);
}
// Transform constraint timelines.
var transforms:Dynamic = Reflect.getProperty(map, "transform");
for (transformName in Reflect.fields(transforms)) {
timelineMap = Reflect.field(transforms, transformName);
keyMap = timelineMap[0];
if (keyMap == null) continue;
var constraint = skeletonData.findConstraint(transformName, TransformConstraintData);
if (constraint == null) throw new SpineException("Transform constraint not found: " + transformName);
var timeline = new TransformConstraintTimeline(timelineMap.length, timelineMap.length * 6,
skeletonData.constraints.indexOf(constraint));
var time = getFloat(keyMap, "time");
var mixRotate = getFloat(keyMap, "mixRotate", 0);
var mixX = getFloat(keyMap, "mixX", 0);
var mixY = getFloat(keyMap, "mixY", mixX);
var mixScaleX:Float = getFloat(keyMap, "mixScaleX", 0);
var mixScaleY:Float = getFloat(keyMap, "mixScaleY", mixScaleX);
var mixShearY:Float = getFloat(keyMap, "mixShearY", 0);
frame = 0;
bezier = 0;
while (true) {
timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
timeline.shrink(bezier);
break;
}
var time2 = getFloat(nextMap, "time");
var mixRotate2 = getFloat(nextMap, "mixRotate", 1);
var mixShearY2:Float = getFloat(nextMap, "mixShearY", 1);
var mixX2 = getFloat(nextMap, "mixX", 1);
var mixY2 = getFloat(nextMap, "mixY", mixX2);
var mixScaleX2:Float = getFloat(nextMap, "mixScaleX", 1);
var mixScaleY2:Float = getFloat(nextMap, "mixScaleY", mixScaleX2);
var curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
mixScaleX = mixScaleX2;
mixScaleY = mixScaleY2;
mixScaleX = mixScaleX2;
keyMap = nextMap;
frame++;
}
timelines.push(timeline);
}
// Path constraint timelines.
var paths:Dynamic = Reflect.getProperty(map, "path");
for (pathName in Reflect.fields(paths)) {
var constraint = skeletonData.findConstraint(pathName, PathConstraintData);
if (constraint == null) throw new SpineException("Path constraint not found: " + pathName);
var index = skeletonData.constraints.indexOf(constraint);
var pathMap:Dynamic = Reflect.field(paths, pathName);
for (timelineName in Reflect.fields(pathMap)) {
timelineMap = Reflect.field(pathMap, timelineName);
keyMap = timelineMap[0];
if (keyMap == null) continue;
switch (timelineName) {
case "position":
var timeline = new PathConstraintPositionTimeline(timelineMap.length, timelineMap.length, index);
readTimeline(timelines, timelineMap, timeline, 0, constraint.positionMode == PositionMode.fixed ? scale : 1);
case "spacing":
var timeline = new PathConstraintSpacingTimeline(timelineMap.length, timelineMap.length, index);
readTimeline(timelines, timelineMap, timeline, 0,
constraint.spacingMode == SpacingMode.length || constraint.spacingMode == SpacingMode.fixed ? scale : 1);
case "mix":
var timeline = new PathConstraintMixTimeline(timelineMap.length, timelineMap.length * 3, index);
var time = getFloat(keyMap, "time");
var mixRotate = getFloat(keyMap, "mixRotate", 1);
var mixX = getFloat(keyMap, "mixX", 1);
var mixY = getFloat(keyMap, "mixY", mixX);
frame = 0;
bezier = 0;
while (true) {
timeline.setFrame(frame, time, mixRotate, mixX, mixY);
var nextMap = timelineMap[frame + 1];
if (nextMap == null) {
timeline.shrink(bezier);
break;
}
var time2 = getFloat(nextMap, "time");
var mixRotate2 = getFloat(nextMap, "mixRotate", 1);
var mixX2 = getFloat(nextMap, "mixX", 1);
var mixY2 = getFloat(nextMap, "mixY", mixX2);
var curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
keyMap = nextMap;
frame++;
}
timelines.push(timeline);
}
}
}
// Physics constraint timelines.
var physics:Dynamic = Reflect.getProperty(map, "physics");
for (physicsName in Reflect.fields(physics)) {
var index = -1;
if (physicsName.length > 0) {
var constraint = skeletonData.findConstraint(physicsName, PhysicsConstraintData);
if (constraint == null) throw new SpineException("Physics constraint not found: " + physicsName);
index = skeletonData.constraints.indexOf(constraint);
}
var physicsMap:Dynamic = Reflect.field(physics, physicsName);
for (timelineName in Reflect.fields(physicsMap)) {
timelineMap = Reflect.field(physicsMap, timelineName);
keyMap = timelineMap[0];
if (keyMap == null) continue;
var frames = timelineMap.length;
var timeline: CurveTimeline1;
var defaultValue = 0.;
switch (timelineName) {
case "reset":
var resetTimeline = new PhysicsConstraintResetTimeline(frames, index);
for (frame => keyMap in timelineMap)
resetTimeline.setFrame(frame, getFloat(keyMap, "time"));
timelines.push(resetTimeline);
continue;
case "inertia": timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index);
case "strength": timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index);
case "damping": timeline = new PhysicsConstraintDampingTimeline(frames, frames, index);
case "mass": timeline = new PhysicsConstraintMassTimeline(frames, frames, index);
case "wind": timeline = new PhysicsConstraintWindTimeline(frames, frames, index);
case "gravity": timeline = new PhysicsConstraintGravityTimeline(frames, frames, index);
case "mix": {
defaultValue = 1;
timeline = new PhysicsConstraintMixTimeline(frames, frames, index);
}
default: continue;
}
readTimeline(timelines, timelineMap, timeline, defaultValue, 1);
}
}
// Slider timelines.
var sliders:Dynamic = Reflect.getProperty(map, "slider");
for (sliderName in Reflect.fields(sliders)) {
var constraint = skeletonData.findConstraint(sliderName, SliderData);
if (constraint == null) throw new SpineException("Slider not found: " + sliderName);
var index = skeletonData.constraints.indexOf(constraint);
var timelineMap:Dynamic = Reflect.field(physics, sliderName);
for (timelineName in Reflect.fields(timelineMap)) {
timelineMap = Reflect.field(timelineMap, timelineName);
keyMap = timelineMap[0];
if (keyMap == null) continue;
var frames = timelineMap.length;
switch (timelineName) {
case "time": readTimeline(timelines, keyMap, new SliderTimeline(frames, frames, index), 1, 1);
case "mix": readTimeline(timelines, keyMap, new SliderMixTimeline(frames, frames, index), 1, 1);
}
}
}
// Attachment timelines.
var attachments:Dynamic = Reflect.getProperty(map, "attachments");
for (attachmentsName in Reflect.fields(attachments)) {
var attachmentsMap:Dynamic = Reflect.field(attachments, attachmentsName);
var skin:Skin = skeletonData.findSkin(attachmentsName);
if (skin == null)
throw new SpineException("Skin not found: " + attachmentsName);
for (slotMapName in Reflect.fields(attachmentsMap)) {
slotMap = Reflect.field(attachmentsMap, slotMapName);
slotIndex = skeletonData.findSlot(slotMapName).index;
if (slotIndex == -1)
throw new SpineException("Slot not found: " + slotMapName);
for (attachmentMapName in Reflect.fields(slotMap)) {
var attachmentMap = Reflect.field(slotMap, attachmentMapName);
var attachment:Attachment = skin.getAttachment(slotIndex, attachmentMapName);
if (attachment == null)
throw new SpineException("Timeline attachment not found: " + attachmentMapName);
for (timelineMapName in Reflect.fields(attachmentMap)) {
var timelineMap = Reflect.field(attachmentMap, timelineMapName);
var keyMap = timelineMap[0];
if (keyMap == null)
continue;
switch (timelineMapName) {
case "deform":
var vertexAttachment = cast(attachment, VertexAttachment);
var weighted:Bool = vertexAttachment.bones != null;
var vertices:Array<Float> = vertexAttachment.vertices;
var deformLength:Int = weighted ? Std.int(vertices.length / 3 * 2) : vertices.length;
var deformTimeline:DeformTimeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, vertexAttachment);
time = getFloat(keyMap, "time");
frame = 0;
bezier = 0;
while (true) {
var deform:Array<Float>;
var verticesValue:Array<Float> = Reflect.getProperty(keyMap, "vertices");
if (verticesValue == null) {
if (weighted) {
deform = new Array<Float>();
ArrayUtils.resize(deform, deformLength, 0);
} else {
deform = vertices;
}
} else {
deform = new Array<Float>();
ArrayUtils.resize(deform, deformLength, 0);
var start:Int = getInt(keyMap, "offset");
var temp:Array<Float> = getFloatArray(keyMap, "vertices");
for (i in 0...temp.length) {
deform[start + i] = temp[i];
}
if (scale != 1) {
for (i in start...start + temp.length) {
deform[i] *= scale;
}
}
if (!weighted) {
for (i in 0...deformLength) {
deform[i] += vertices[i];
}
}
}
deformTimeline.setFrame(frame, time, deform);
nextMap = timelineMap[frame + 1];
if (nextMap == null) {
deformTimeline.shrink(bezier);
break;
}
time2 = getFloat(nextMap, "time");
curve = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, deformTimeline, bezier, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
keyMap = nextMap;
frame++;
}
timelines.push(deformTimeline);
case "sequence":
var timeline = new SequenceTimeline(timelineMap.length, slotIndex, cast(attachment, HasTextureRegion));
var lastDelay:Float = 0;
var frame:Int = 0;
while (frame < timelineMap.length) {
var delay = getFloat(keyMap, "delay", lastDelay);
var time = getFloat(keyMap, "time", 0);
var mode = SequenceMode.fromName(getString(keyMap, "mode", "hold"));
var index = getInt(keyMap, "index", 0);
timeline.setFrame(frame, time, mode, index, delay);
lastDelay = delay;
keyMap = timelineMap[frame + 1];
frame++;
}
timelines.push(timeline);
}
}
}
}
}
// Draw order timelines.
if (Reflect.hasField(map, "drawOrder")) {
var drawOrders:Array<Dynamic> = cast(Reflect.field(map, "drawOrder"), Array<Dynamic>);
if (drawOrders != null) {
var drawOrderTimeline:DrawOrderTimeline = new DrawOrderTimeline(drawOrders.length);
var slotCount:Int = skeletonData.slots.length;
frame = 0;
for (drawOrderMap in drawOrders) {
var drawOrder:Array<Int> = null;
var offsets:Array<Dynamic> = Reflect.getProperty(drawOrderMap, "offsets");
if (offsets != null) {
drawOrder = new Array<Int>();
drawOrder.resize(slotCount);
var i = slotCount - 1;
while (i >= 0) {
drawOrder[i--] = -1;
}
var unchanged:Array<Int> = new Array<Int>();
unchanged.resize(slotCount - offsets.length);
var originalIndex:Int = 0, unchangedIndex:Int = 0;
for (offsetMap in offsets) {
slotIndex = skeletonData.findSlot(Reflect.getProperty(offsetMap, "slot")).index;
if (slotIndex == -1)
throw new SpineException("Slot not found: " + Reflect.getProperty(offsetMap, "slot"));
// Collect unchanged items.
while (originalIndex != slotIndex) {
unchanged[unchangedIndex++] = originalIndex++;
}
// Set changed items.
drawOrder[originalIndex + Reflect.getProperty(offsetMap, "offset")] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount) {
unchanged[unchangedIndex++] = originalIndex++;
}
// Fill in unchanged items.
i = slotCount - 1;
while (i >= 0) {
if (drawOrder[i] == -1)
drawOrder[i] = unchanged[--unchangedIndex];
i--;
}
}
drawOrderTimeline.setFrame(frame++, getFloat(drawOrderMap, "time"), drawOrder);
}
timelines.push(drawOrderTimeline);
}
}
// Event timelines.
if (Reflect.hasField(map, "events")) {
var eventsMap:Array<Dynamic> = cast(Reflect.field(map, "events"), Array<Dynamic>);
if (eventsMap != null) {
var eventTimeline:EventTimeline = new EventTimeline(eventsMap.length);
frame = 0;
for (eventMap in eventsMap) {
var eventData:EventData = skeletonData.findEvent(Reflect.getProperty(eventMap, "name"));
if (eventData == null)
throw new SpineException("Event not found: " + Reflect.getProperty(eventMap, "name"));
var event:Event = new Event(getFloat(eventMap, "time"), eventData);
event.intValue = Reflect.hasField(eventMap, "int") ? getInt(eventMap, "int") : eventData.intValue;
event.floatValue = Reflect.hasField(eventMap, "float") ? getFloat(eventMap, "float") : eventData.floatValue;
event.stringValue = Reflect.hasField(eventMap, "string") ? Reflect.getProperty(eventMap, "string") : eventData.stringValue;
if (event.data.audioPath != null) {
event.volume = getFloat(eventMap, "volume", 1);
event.balance = getFloat(eventMap, "balance");
}
eventTimeline.setFrame(frame++, event);
}
timelines.push(eventTimeline);
}
}
var duration:Float = 0;
for (i in 0...timelines.length) {
duration = Math.max(duration, timelines[i].getDuration());
}
skeletonData.animations.push(new Animation(name, timelines, duration));
}
static private function readTimeline(timelines:Array<Timeline>, keys:Array<Dynamic>, timeline:CurveTimeline1, defaultValue:Float, scale:Float) {
var keyMap:Dynamic = keys[0];
var time:Float = getFloat(keyMap, "time");
var value:Float = getFloat(keyMap, "value", defaultValue) * scale;
var bezier:Int = 0;
var frame:Int = 0;
while (true) {
timeline.setFrame(frame, time, value);
var nextMap:Dynamic = keys[frame + 1];
if (nextMap == null) {
timeline.shrink(bezier);
timelines.push(timeline);
return;
}
var time2:Float = getFloat(nextMap, "time");
var value2:Float = getFloat(nextMap, "value", defaultValue) * scale;
var curve:Dynamic = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
}
time = time2;
value = value2;
keyMap = nextMap;
frame++;
}
}
static private function readTimeline2(timelines:Array<Timeline>, keys:Array<Dynamic>, timeline:BoneTimeline2, name1:String, name2:String, defaultValue:Float,
scale:Float) {
var keyMap:Dynamic = keys[0];
var time:Float = getFloat(keyMap, "time");
var value1:Float = getFloat(keyMap, name1, defaultValue) * scale;
var value2:Float = getFloat(keyMap, name2, defaultValue) * scale;
var bezier:Int = 0;
var frame:Int = 0;
while (true) {
timeline.setFrame(frame, time, value1, value2);
var nextMap:Dynamic = keys[frame + 1];
if (nextMap == null) {
timeline.shrink(bezier);
timelines.push(timeline);
return;
}
var time2:Float = getFloat(nextMap, "time");
var nvalue1:Float = getFloat(nextMap, name1, defaultValue) * scale;
var nvalue2:Float = getFloat(nextMap, name2, defaultValue) * scale;
var curve:Dynamic = keyMap.curve;
if (curve != null) {
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
}
time = time2;
value1 = nvalue1;
value2 = nvalue2;
keyMap = nextMap;
frame++;
}
}
static private function readCurve(curve:Dynamic, timeline:CurveTimeline, bezier:Int, frame:Int, value:Int, time1:Float, time2:Float, value1:Float,
value2:Float, scale:Float):Int {
if (curve == "stepped") {
timeline.setStepped(frame);
return bezier;
}
var i:Int = value << 2;
var cx1:Float = curve[i];
var cy1:Float = curve[i + 1] * scale;
var cx2:Float = curve[i + 2];
var cy2:Float = curve[i + 3] * scale;
timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
return bezier + 1;
}
static private function getValue(map:Dynamic, name:String, defaultValue:Dynamic):Dynamic {
if (Reflect.hasField(map, name))
return Reflect.field(map, name);
return defaultValue;
}
static private function getString(value:Dynamic, name:String, defaultValue:String = ""):String {
if (Std.isOfType(Reflect.field(value, name), String))
return cast(Reflect.field(value, name), String);
return defaultValue;
}
static private function getInt(value:Dynamic, name:String, defaultValue:Int = 0):Int {
if (Std.isOfType(Reflect.field(value, name), Int))
return cast(Reflect.field(value, name), Int);
return defaultValue;
}
static private function getFloat(value:Dynamic, name:String, defaultValue:Float = 0.):Float {
if (Std.isOfType(Reflect.field(value, name), Float))
return cast(Reflect.field(value, name), Float);
return defaultValue;
}
static private function getBoolean(value:Dynamic, name:String, defaultValue:Bool = false):Bool {
if (Std.isOfType(Reflect.field(value, name), Bool))
return cast(Reflect.field(value, name), Bool);
return defaultValue;
}
static private function getFloatArray(map:Dynamic, name:String):Array<Float> {
var list:Array<Dynamic> = cast(Reflect.field(map, name), Array<Dynamic>);
var values:Array<Float> = new Array<Float>();
values.resize(list.length);
for (i in 0...list.length) {
values[i] = cast(list[i], Float);
}
return values;
}
static private function getIntArray(map:Dynamic, name:String):Array<Int> {
var list:Array<Dynamic> = cast(Reflect.field(map, name), Array<Dynamic>);
var values:Array<Int> = new Array<Int>();
values.resize(list.length);
for (i in 0...list.length) {
values[i] = Std.int(list[i]);
}
return values;
}
}
class LinkedMesh {
public var parent(default, null):String;
public var skin(default, null):String;
public var slotIndex(default, null):Int;
public var mesh(default, null):MeshAttachment;
public var inheritTimeline(default, null):Bool;
public function new(mesh:MeshAttachment, skin:String, slotIndex:Int, parent:String, inheritTimeline:Bool) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritTimeline = inheritTimeline;
}
}