spine-runtimes/spine-ts/core/src/SkeletonBinary.ts

1144 lines
43 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, 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.
*****************************************************************************/
module spine {
/** Loads skeleton data in the Spine binary format.
*
* See [Spine binary format](http://esotericsoftware.com/spine-binary-format) and
* [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine
* Runtimes Guide. */
export class SkeletonBinary {
static AttachmentTypeValues = [ 0 /*AttachmentType.Region*/, 1/*AttachmentType.BoundingBox*/, 2/*AttachmentType.Mesh*/, 3/*AttachmentType.LinkedMesh*/, 4/*AttachmentType.Path*/, 5/*AttachmentType.Point*/, 6/*AttachmentType.Clipping*/ ];
static TransformModeValues = [TransformMode.Normal, TransformMode.OnlyTranslation, TransformMode.NoRotationOrReflection, TransformMode.NoScale, TransformMode.NoScaleOrReflection];
static PositionModeValues = [ PositionMode.Fixed, PositionMode.Percent ];
static SpacingModeValues = [ SpacingMode.Length, SpacingMode.Fixed, SpacingMode.Percent];
static RotateModeValues = [ RotateMode.Tangent, RotateMode.Chain, RotateMode.ChainScale ];
static BlendModeValues = [ BlendMode.Normal, BlendMode.Additive, BlendMode.Multiply, BlendMode.Screen];
static BONE_ROTATE = 0;
static BONE_TRANSLATE = 1;
static BONE_TRANSLATEX = 2;
static BONE_TRANSLATEY = 3;
static BONE_SCALE = 4;
static BONE_SCALEX = 5;
static BONE_SCALEY = 6;
static BONE_SHEAR = 7;
static BONE_SHEARX = 8;
static BONE_SHEARY = 9;
static SLOT_ATTACHMENT = 0;
static SLOT_RGBA = 1;
static SLOT_RGB = 2;
static SLOT_RGBA2 = 3;
static SLOT_RGB2 = 4;
static SLOT_ALPHA = 5;
static PATH_POSITION = 0;
static PATH_SPACING = 1;
static PATH_MIX = 2;
static CURVE_LINEAR = 0;
static CURVE_STEPPED = 1;
static CURVE_BEZIER = 2;
/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
* runtime than were used in Spine.
*
* See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */
scale = 1;
attachmentLoader: AttachmentLoader;
private linkedMeshes = new Array<LinkedMesh>();
constructor (attachmentLoader: AttachmentLoader) {
this.attachmentLoader = attachmentLoader;
}
readSkeletonData (binary: Uint8Array): SkeletonData {
let scale = this.scale;
let skeletonData = new SkeletonData();
skeletonData.name = ""; // BOZO
let input = new BinaryInput(binary);
let lowHash = input.readInt32();
let highHash = input.readInt32();
skeletonData.hash = highHash == 0 && lowHash == 0 ? null : highHash.toString(16) + lowHash.toString(16);
skeletonData.version = input.readString();
skeletonData.x = input.readFloat();
skeletonData.y = input.readFloat();
skeletonData.width = input.readFloat();
skeletonData.height = input.readFloat();
let nonessential = input.readBoolean();
if (nonessential) {
skeletonData.fps = input.readFloat();
skeletonData.imagesPath = input.readString();
skeletonData.audioPath = input.readString();
}
let n = 0;
// Strings.
n = input.readInt(true)
for (let i = 0; i < n; i++)
input.strings.push(input.readString());
// Bones.
n = input.readInt(true)
for (let i = 0; i < n; i++) {
let name = input.readString();
let parent = i == 0 ? null : skeletonData.bones[input.readInt(true)];
let data = new BoneData(i, name, parent);
data.rotation = input.readFloat();
data.x = input.readFloat() * scale;
data.y = input.readFloat() * scale;
data.scaleX = input.readFloat();
data.scaleY = input.readFloat();
data.shearX = input.readFloat();
data.shearY = input.readFloat();
data.length = input.readFloat() * scale;
data.transformMode = SkeletonBinary.TransformModeValues[input.readInt(true)];
data.skinRequired = input.readBoolean();
if (nonessential) Color.rgba8888ToColor(data.color, input.readInt32());
skeletonData.bones.push(data);
}
// Slots.
n = input.readInt(true);
for (let i = 0; i < n; i++) {
let slotName = input.readString();
let boneData = skeletonData.bones[input.readInt(true)];
let data = new SlotData(i, slotName, boneData);
Color.rgba8888ToColor(data.color, input.readInt32());
let darkColor = input.readInt32();
if (darkColor != -1) Color.rgb888ToColor(data.darkColor = new Color(), darkColor);
data.attachmentName = input.readStringRef();
data.blendMode = SkeletonBinary.BlendModeValues[input.readInt(true)];
skeletonData.slots.push(data);
}
// IK constraints.
n = input.readInt(true);
for (let i = 0, nn; i < n; i++) {
let data = new IkConstraintData(input.readString());
data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
nn = input.readInt(true);
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.bones[input.readInt(true)];
data.mix = input.readFloat();
data.softness = input.readFloat() * scale;
data.bendDirection = input.readByte();
data.compress = input.readBoolean();
data.stretch = input.readBoolean();
data.uniform = input.readBoolean();
skeletonData.ikConstraints.push(data);
}
// Transform constraints.
n = input.readInt(true);
for (let i = 0, nn; i < n; i++) {
let data = new TransformConstraintData(input.readString());
data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
nn = input.readInt(true);
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.bones[input.readInt(true)];
data.local = input.readBoolean();
data.relative = input.readBoolean();
data.offsetRotation = input.readFloat();
data.offsetX = input.readFloat() * scale;
data.offsetY = input.readFloat() * scale;
data.offsetScaleX = input.readFloat();
data.offsetScaleY = input.readFloat();
data.offsetShearY = input.readFloat();
data.mixRotate = input.readFloat();
data.mixX = input.readFloat();
data.mixY = input.readFloat();
data.mixScaleX = input.readFloat();
data.mixScaleY = input.readFloat();
data.mixShearY = input.readFloat();
skeletonData.transformConstraints.push(data);
}
// Path constraints.
n = input.readInt(true);
for (let i = 0, nn; i < n; i++) {
let data = new PathConstraintData(input.readString());
data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
nn = input.readInt(true);
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.slots[input.readInt(true)];
data.positionMode = SkeletonBinary.PositionModeValues[input.readInt(true)];
data.spacingMode = SkeletonBinary.SpacingModeValues[input.readInt(true)];
data.rotateMode = SkeletonBinary.RotateModeValues[input.readInt(true)];
data.offsetRotation = input.readFloat();
data.position = input.readFloat();
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = input.readFloat();
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.mixRotate = input.readFloat();
data.mixX = input.readFloat();
data.mixY = input.readFloat();
skeletonData.pathConstraints.push(data);
}
// Default skin.
let defaultSkin = this.readSkin(input, skeletonData, true, nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.skins.push(defaultSkin);
}
// Skins.
{
let i = skeletonData.skins.length;
Utils.setArraySize(skeletonData.skins, n = i + input.readInt(true));
for (; i < n; i++)
skeletonData.skins[i] = this.readSkin(input, skeletonData, false, nonessential);
}
// Linked meshes.
n = this.linkedMeshes.length;
for (let i = 0; i < n; i++) {
let linkedMesh = this.linkedMeshes[i];
let skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (skin == null) throw new Error("Skin not found: " + linkedMesh.skin);
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Error("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? parent as VertexAttachment : linkedMesh.mesh;
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
linkedMesh.mesh.updateUVs();
}
this.linkedMeshes.length = 0;
// Events.
n = input.readInt(true);
for (let i = 0; i < n; i++) {
let data = new EventData(input.readStringRef());
data.intValue = input.readInt(false);
data.floatValue = input.readFloat();
data.stringValue = input.readString();
data.audioPath = input.readString();
if (data.audioPath != null) {
data.volume = input.readFloat();
data.balance = input.readFloat();
}
skeletonData.events.push(data);
}
// Animations.
n = input.readInt(true);
for (let i = 0; i < n; i++)
skeletonData.animations.push(this.readAnimation(input, input.readString(), skeletonData));
return skeletonData;
}
private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin {
let skin = null;
let slotCount = 0;
if (defaultSkin) {
slotCount = input.readInt(true)
if (slotCount == 0) return null;
skin = new Skin("default");
} else {
skin = new Skin(input.readStringRef());
skin.bones.length = input.readInt(true);
for (let i = 0, n = skin.bones.length; i < n; i++)
skin.bones[i] = skeletonData.bones[input.readInt(true)];
for (let i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.push(skeletonData.ikConstraints[input.readInt(true)]);
for (let i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.push(skeletonData.transformConstraints[input.readInt(true)]);
for (let i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.push(skeletonData.pathConstraints[input.readInt(true)]);
slotCount = input.readInt(true);
}
for (let i = 0; i < slotCount; i++) {
let slotIndex = input.readInt(true);
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
let name = input.readStringRef();
let attachment = this.readAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
if (attachment != null) skin.setAttachment(slotIndex, name, attachment);
}
}
return skin;
}
private readAttachment(input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
let scale = this.scale;
let name = input.readStringRef();
if (name == null) name = attachmentName;
let typeIndex = input.readByte();
switch (SkeletonBinary.AttachmentTypeValues[typeIndex]) {
case AttachmentType.Region: {
let path = input.readStringRef();
let rotation = input.readFloat();
let x = input.readFloat();
let y = input.readFloat();
let scaleX = input.readFloat();
let scaleY = input.readFloat();
let width = input.readFloat();
let height = input.readFloat();
let color = input.readInt32();
if (path == null) path = name;
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
if (region == null) return null;
region.path = path;
region.x = x * scale;
region.y = y * scale;
region.scaleX = scaleX;
region.scaleY = scaleY;
region.rotation = rotation;
region.width = width * scale;
region.height = height * scale;
Color.rgba8888ToColor(region.color, color);
region.updateOffset();
return region;
}
case AttachmentType.BoundingBox: {
let vertexCount = input.readInt(true);
let vertices = this.readVertices(input, vertexCount);
let color = nonessential ? input.readInt32() : 0;
let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
if (box == null) return null;
box.worldVerticesLength = vertexCount << 1;
box.vertices = vertices.vertices;
box.bones = vertices.bones;
if (nonessential) Color.rgba8888ToColor(box.color, color);
return box;
}
case AttachmentType.Mesh: {
let path = input.readStringRef();
let color = input.readInt32();
let vertexCount = input.readInt(true);
let uvs = this.readFloatArray(input, vertexCount << 1, 1);
let triangles = this.readShortArray(input);
let vertices = this.readVertices(input, vertexCount);
let hullLength = input.readInt(true);
let edges = null;
let width = 0, height = 0;
if (nonessential) {
edges = this.readShortArray(input);
width = input.readFloat();
height = input.readFloat();
}
if (path == null) path = name;
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.path = path;
Color.rgba8888ToColor(mesh.color, color);
mesh.bones = vertices.bones;
mesh.vertices = vertices.vertices;
mesh.worldVerticesLength = vertexCount << 1;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.updateUVs();
mesh.hullLength = hullLength << 1;
if (nonessential) {
mesh.edges = edges;
mesh.width = width * scale;
mesh.height = height * scale;
}
return mesh;
}
case AttachmentType.LinkedMesh: {
let path = input.readStringRef();
let color = input.readInt32();
let skinName = input.readStringRef();
let parent = input.readStringRef();
let inheritDeform = input.readBoolean();
let width = 0, height = 0;
if (nonessential) {
width = input.readFloat();
height = input.readFloat();
}
if (path == null) path = name;
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.path = path;
Color.rgba8888ToColor(mesh.color, color);
if (nonessential) {
mesh.width = width * scale;
mesh.height = height * scale;
}
this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
return mesh;
}
case AttachmentType.Path: {
let closed = input.readBoolean();
let constantSpeed = input.readBoolean();
let vertexCount = input.readInt(true);
let vertices = this.readVertices(input, vertexCount);
let lengths = Utils.newArray(vertexCount / 3, 0);
for (let i = 0, n = lengths.length; i < n; i++)
lengths[i] = input.readFloat() * scale;
let color = nonessential ? input.readInt32() : 0;
let path = this.attachmentLoader.newPathAttachment(skin, name);
if (path == null) return null;
path.closed = closed;
path.constantSpeed = constantSpeed;
path.worldVerticesLength = vertexCount << 1;
path.vertices = vertices.vertices;
path.bones = vertices.bones;
path.lengths = lengths;
if (nonessential) Color.rgba8888ToColor(path.color, color);
return path;
}
case AttachmentType.Point: {
let rotation = input.readFloat();
let x = input.readFloat();
let y = input.readFloat();
let color = nonessential ? input.readInt32() : 0;
let point = this.attachmentLoader.newPointAttachment(skin, name);
if (point == null) return null;
point.x = x * scale;
point.y = y * scale;
point.rotation = rotation;
if (nonessential) Color.rgba8888ToColor(point.color, color);
return point;
}
case AttachmentType.Clipping: {
let endSlotIndex = input.readInt(true);
let vertexCount = input.readInt(true);
let vertices = this.readVertices(input, vertexCount);
let color = nonessential ? input.readInt32() : 0;
let clip = this.attachmentLoader.newClippingAttachment(skin, name);
if (clip == null) return null;
clip.endSlot = skeletonData.slots[endSlotIndex];
clip.worldVerticesLength = vertexCount << 1;
clip.vertices = vertices.vertices;
clip.bones = vertices.bones;
if (nonessential) Color.rgba8888ToColor(clip.color, color);
return clip;
}
}
return null;
}
private readVertices (input: BinaryInput, vertexCount: number): Vertices {
let scale = this.scale;
let verticesLength = vertexCount << 1;
let vertices = new Vertices();
if (!input.readBoolean()) {
vertices.vertices = this.readFloatArray(input, verticesLength, scale);
return vertices;
}
let weights = new Array<number>();
let bonesArray = new Array<number>();
for (let i = 0; i < vertexCount; i++) {
let boneCount = input.readInt(true);
bonesArray.push(boneCount);
for (let ii = 0; ii < boneCount; ii++) {
bonesArray.push(input.readInt(true));
weights.push(input.readFloat() * scale);
weights.push(input.readFloat() * scale);
weights.push(input.readFloat());
}
}
vertices.vertices = Utils.toFloatArray(weights);
vertices.bones = bonesArray;
return vertices;
}
private readFloatArray (input: BinaryInput, n: number, scale: number): number[] {
let array = new Array<number>(n);
if (scale == 1) {
for (let i = 0; i < n; i++)
array[i] = input.readFloat();
} else {
for (let i = 0; i < n; i++)
array[i] = input.readFloat() * scale;
}
return array;
}
private readShortArray (input: BinaryInput): number[] {
let n = input.readInt(true);
let array = new Array<number>(n);
for (let i = 0; i < n; i++)
array[i] = input.readShort();
return array;
}
private readAnimation (input: BinaryInput, name: string, skeletonData: SkeletonData): Animation {
let numTimelines = input.readInt(true);
let timelines = new Array<Timeline>();
let scale = this.scale;
let tempColor1 = new Color();
let tempColor2 = new Color();
// Slot timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let slotIndex = input.readInt(true);
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
let timelineType = input.readByte();
let frameCount = input.readInt(true);
let frameLast = frameCount - 1;
switch (timelineType) {
case SkeletonBinary.SLOT_ATTACHMENT: {
let timeline = new AttachmentTimeline(frameCount, slotIndex);
for (let frame = 0; frame < frameCount; frame++)
timeline.setFrame(frame, input.readFloat(), input.readStringRef());
timelines.push(timeline);
break;
}
case SkeletonBinary.SLOT_RGBA: {
let bezierCount = input.readInt(true);
let timeline = new RGBATimeline(frameCount, bezierCount, slotIndex);
let time = input.readFloat();
let r = input.readUnsignedByte() / 255.0;
let g = input.readUnsignedByte() / 255.0;
let b = input.readUnsignedByte() / 255.0;
let a = input.readUnsignedByte() / 255.0;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, r, g, b, a);
if (frame == frameLast) break;
let time2 = input.readFloat();
let r2 = input.readUnsignedByte() / 255.0;
let g2 = input.readUnsignedByte() / 255.0;
let b2 = input.readUnsignedByte() / 255.0;
let a2 = input.readUnsignedByte() / 255.0;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1);
}
time = time2;
r = r2;
g = g2;
b = b2;
a = a2;
}
timelines.push(timeline);
break;
}
case SkeletonBinary.SLOT_RGB: {
let bezierCount = input.readInt(true);
let timeline = new RGBTimeline(frameCount, bezierCount, slotIndex);
let time = input.readFloat();
let r = input.readUnsignedByte() / 255.0;
let g = input.readUnsignedByte() / 255.0;
let b = input.readUnsignedByte() / 255.0;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, r, g, b);
if (frame == frameLast) break;
let time2 = input.readFloat();
let r2 = input.readUnsignedByte() / 255.0;
let g2 = input.readUnsignedByte() / 255.0;
let b2 = input.readUnsignedByte() / 255.0;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1);
}
time = time2;
r = r2;
g = g2;
b = b2;
}
timelines.push(timeline);
break;
}
case SkeletonBinary.SLOT_RGBA2: {
let bezierCount = input.readInt(true);
let timeline = new RGBA2Timeline(frameCount, bezierCount, slotIndex);
let time = input.readFloat();
let r = input.readUnsignedByte() / 255.0;
let g = input.readUnsignedByte() / 255.0;
let b = input.readUnsignedByte() / 255.0;
let a = input.readUnsignedByte() / 255.0;
let r2 = input.readUnsignedByte() / 255.0;
let g2 = input.readUnsignedByte() / 255.0;
let b2 = input.readUnsignedByte() / 255.0;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, r, g, b, a, r2, g2, b2);
if (frame == frameLast) break;
let time2 = input.readFloat();
let nr = input.readUnsignedByte() / 255.0;
let ng = input.readUnsignedByte() / 255.0;
let nb = input.readUnsignedByte() / 255.0;
let na = input.readUnsignedByte() / 255.0;
let nr2 = input.readUnsignedByte() / 255.0;
let ng2 = input.readUnsignedByte() / 255.0;
let nb2 = input.readUnsignedByte() / 255.0;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1);
}
time = time2;
r = nr;
g = ng;
b = nb;
a = na;
r2 = nr2;
g2 = ng2;
b2 = nb2;
}
timelines.push(timeline);
break;
}
case SkeletonBinary.SLOT_RGB2: {
let bezierCount = input.readInt(true);
let timeline = new RGB2Timeline(frameCount, bezierCount, slotIndex);
let time = input.readFloat();
let r = input.readUnsignedByte() / 255.0;
let g = input.readUnsignedByte() / 255.0;
let b = input.readUnsignedByte() / 255.0;
let r2 = input.readUnsignedByte() / 255.0;
let g2 = input.readUnsignedByte() / 255.0;
let b2 = input.readUnsignedByte() / 255.0;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, r, g, b, r2, g2, b2);
if (frame == frameLast) break;
let time2 = input.readFloat();
let nr = input.readUnsignedByte() / 255.0;
let ng = input.readUnsignedByte() / 255.0;
let nb = input.readUnsignedByte() / 255.0;
let nr2 = input.readUnsignedByte() / 255.0;
let ng2 = input.readUnsignedByte() / 255.0;
let nb2 = input.readUnsignedByte() / 255.0;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 3, time, time2, r2, nr2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 4, time, time2, g2, ng2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 5, time, time2, b2, nb2, 1);
}
time = time2;
r = nr;
g = ng;
b = nb;
r2 = nr2;
g2 = ng2;
b2 = nb2;
}
timelines.push(timeline);
break;
}
case SkeletonBinary.SLOT_ALPHA: {
let timeline = new AlphaTimeline(frameCount, input.readInt(true), slotIndex);
let time = input.readFloat(), a = input.readUnsignedByte() / 255;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, a);
if (frame == frameLast) break;
let time2 = input.readFloat();
let a2 = input.readUnsignedByte() / 255;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1);
}
time = time2;
a = a2;
}
timelines.push(timeline);
break;
}
}
}
}
// Bone timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let boneIndex = input.readInt(true);
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
let type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true);
switch (type) {
case SkeletonBinary.BONE_ROTATE:
timelines.push(SkeletonBinary.readTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_TRANSLATE:
timelines.push(SkeletonBinary.readTimeline2(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale));
break;
case SkeletonBinary.BONE_TRANSLATEX:
timelines.push(SkeletonBinary.readTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale));
break;
case SkeletonBinary.BONE_TRANSLATEY:
timelines.push(SkeletonBinary.readTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale));
break;
case SkeletonBinary.BONE_SCALE:
timelines.push(SkeletonBinary.readTimeline2(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_SCALEX:
timelines.push(SkeletonBinary.readTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_SCALEY:
timelines.push(SkeletonBinary.readTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_SHEAR:
timelines.push(SkeletonBinary.readTimeline2(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_SHEARX:
timelines.push(SkeletonBinary.readTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1));
break;
case SkeletonBinary.BONE_SHEARY:
timelines.push(SkeletonBinary.readTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1));
}
}
}
// IK constraint timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1;
let timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index);
let time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale;
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, mix, softness, input.readByte(), input.readBoolean(), input.readBoolean());
if (frame == frameLast) break;
let time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
}
time = time2;
mix = mix2;
softness = softness2;
}
timelines.push(timeline);
}
// Transform constraint timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1;
let timeline = new TransformConstraintTimeline(frameCount, input.readInt(true), index);
let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(),
mixScaleX = input.readFloat(), mixScaleY = input.readFloat(), mixShearY = input.readFloat();
for (let frame = 0, bezier = 0;; frame++) {
timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY);
if (frame == frameLast) break;
let time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(),
mixScaleX2 = input.readFloat(), mixScaleY2 = input.readFloat(), mixShearY2 = input.readFloat();
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
mixScaleX = mixScaleX2;
mixScaleY = mixScaleY2;
mixShearY = mixShearY2;
}
timelines.push(timeline);
}
// Path constraint timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let index = input.readInt(true);
let data = skeletonData.pathConstraints[index];
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
switch (input.readByte()) {
case SkeletonBinary.PATH_POSITION:
timelines
.push(SkeletonBinary.readTimeline(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index),
data.positionMode == PositionMode.Fixed ? scale : 1));
break;
case SkeletonBinary.PATH_SPACING:
timelines
.push(SkeletonBinary.readTimeline(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index),
data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
break;
case SkeletonBinary.PATH_MIX:
let timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true),
index);
let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat();
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, mixRotate, mixX, mixY);
if (frame == frameLast) break;
let time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(),
mixY2 = input.readFloat();
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
}
time = time2;
mixRotate = mixRotate2;
mixX = mixX2;
mixY = mixY2;
}
timelines.push(timeline);
}
}
}
// Deform timelines.
for (let i = 0, n = input.readInt(true); i < n; i++) {
let skin = skeletonData.skins[input.readInt(true)];
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
let slotIndex = input.readInt(true);
for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
let attachmentName = input.readStringRef();
let attachment = skin.getAttachment(slotIndex, attachmentName) as VertexAttachment;
if (attachment == null) throw Error("Vertex attachment not found: " + attachmentName);
let weighted = attachment.bones != null;
let vertices = attachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
let frameCount = input.readInt(true);
let frameLast = frameCount - 1;
let bezierCount = input.readInt(true);
let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
let time = input.readFloat();
for (let frame = 0, bezier = 0;; frame++) {
let deform;
let end = input.readInt(true);
if (end == 0)
deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
else {
deform = Utils.newFloatArray(deformLength);
let start = input.readInt(true);
end += start;
if (scale == 1) {
for (let v = start; v < end; v++)
deform[v] = input.readFloat();
} else {
for (let v = start; v < end; v++)
deform[v] = input.readFloat() * scale;
}
if (!weighted) {
for (let v = 0, vn = deform.length; v < vn; v++)
deform[v] += vertices[v];
}
}
timeline.setFrame(frame, time, deform);
if (frame == frameLast) break;
let time2 = input.readFloat();
switch(input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
}
timelines.push(timeline);
}
}
}
// Draw order timeline.
let drawOrderCount = input.readInt(true);
if (drawOrderCount > 0) {
let timeline = new DrawOrderTimeline(drawOrderCount);
let slotCount = skeletonData.slots.length;
for (let i = 0; i < drawOrderCount; i++) {
let time = input.readFloat();
let offsetCount = input.readInt(true);
let drawOrder = Utils.newArray(slotCount, 0);
for (let ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
let unchanged = Utils.newArray(slotCount - offsetCount, 0);
let originalIndex = 0, unchangedIndex = 0;
for (let ii = 0; ii < offsetCount; ii++) {
let slotIndex = input.readInt(true);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + input.readInt(true)] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (let ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.setFrame(i, time, drawOrder);
}
timelines.push(timeline);
}
// Event timeline.
let eventCount = input.readInt(true);
if (eventCount > 0) {
let timeline = new EventTimeline(eventCount);
for (let i = 0; i < eventCount; i++) {
let time = input.readFloat();
let eventData = skeletonData.events[input.readInt(true)];
let event = new Event(time, eventData);
event.intValue = input.readInt(false);
event.floatValue = input.readFloat();
event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue;
if (event.data.audioPath != null) {
event.volume = input.readFloat();
event.balance = input.readFloat();
}
timeline.setFrame(i, event);
}
timelines.push(timeline);
}
let duration = 0;
for (let i = 0, n = timelines.length; i < n; i++)
duration = Math.max(duration, (timelines[i]).getDuration());
return new Animation(name, timelines, duration);
}
static readTimeline (input: BinaryInput, timeline: CurveTimeline1, scale: number): Timeline {
let time = input.readFloat(), value = input.readFloat() * scale;
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, value);
if (frame == frameLast) break;
let time2 = input.readFloat(), value2 = input.readFloat() * scale;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
SkeletonBinary.setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, 1);
}
time = time2;
value = value2;
}
return timeline;
}
static readTimeline2 (input: BinaryInput, timeline: CurveTimeline2, scale: number): Timeline {
let time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale;
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, value1, value2);
if (frame == frameLast) break;
let time2 = input.readFloat(), nvalue1 = input.readFloat() * scale, nvalue2 = input.readFloat() * scale;
switch (input.readByte()) {
case SkeletonBinary.CURVE_STEPPED:
timeline.setStepped(frame);
break;
case SkeletonBinary.CURVE_BEZIER:
this.setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale);
this.setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
}
time = time2;
value1 = nvalue1;
value2 = nvalue2;
}
return timeline;
}
static setBezier (input: BinaryInput, timeline: CurveTimeline, bezier: number, frame: number, value: number, time1: number, time2: number, value1: number, value2: number, scale: number) {
timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(), input.readFloat() * scale, time2, value2);
}
}
class BinaryInput {
constructor(data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) { 
}
readByte(): number {
return this.buffer.getInt8(this.index++);
}
readUnsignedByte(): number {
return this.buffer.getUint8(this.index++);
}
readShort(): number {
let value = this.buffer.getInt16(this.index);
this.index += 2;
return value;
}
readInt32(): number {
let value = this.buffer.getInt32(this.index)
this.index += 4;
return value;
}
readInt(optimizePositive: boolean) {
let b = this.readByte();
let result = b & 0x7F;
if ((b & 0x80) != 0) {
b = this.readByte();
result |= (b & 0x7F) << 7;
if ((b & 0x80) != 0) {
b = this.readByte();
result |= (b & 0x7F) << 14;
if ((b & 0x80) != 0) {
b = this.readByte();
result |= (b & 0x7F) << 21;
if ((b & 0x80) != 0) {
b = this.readByte();
result |= (b & 0x7F) << 28;
}
}
}
}
return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));
}
readStringRef (): string {
let index = this.readInt(true);
return index == 0 ? null : this.strings[index - 1];
}
readString (): string {
let byteCount = this.readInt(true);
switch (byteCount) {
case 0:
return null;
case 1:
return "";
}
byteCount--;
let chars = "";
let charCount = 0;
for (let i = 0; i < byteCount;) {
let b = this.readByte();
switch (b >> 4) {
case 12:
case 13:
chars += String.fromCharCode(((b & 0x1F) << 6 | this.readByte() & 0x3F));
i += 2;
break;
case 14:
chars += String.fromCharCode(((b & 0x0F) << 12 | (this.readByte() & 0x3F) << 6 | this.readByte() & 0x3F));
i += 3;
break;
default:
chars += String.fromCharCode(b);
i++;
}
}
return chars;
}
readFloat (): number {
let value = this.buffer.getFloat32(this.index);
this.index += 4;
return value;
}
readBoolean (): boolean {
return this.readByte() != 0;
}
}
class LinkedMesh {
parent: string; skin: string;
slotIndex: number;
mesh: MeshAttachment;
inheritDeform: boolean;
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
}
}
class Vertices {
constructor(public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
}
}