mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-14 02:58:44 +08:00
[ts] Port of sequence attachments, see #1956
SkeletonJson parsing of sequence timelines incomplete. Untested.
This commit is contained in:
parent
7876897c59
commit
81927051ff
5
.gitignore
vendored
5
.gitignore
vendored
@ -151,4 +151,7 @@ spine-ts/spine-core/dist
|
||||
spine-ts/spine-canvas/dist
|
||||
spine-ts/spine-webgl/dist
|
||||
spine-ts/spine-player/dist
|
||||
spine-ts/spine-threejs/dist
|
||||
spine-ts/spine-threejs/dist
|
||||
spine-libgdx/gradle
|
||||
spine-libgdx/gradlew
|
||||
spine-libgdx/gradlew.bat
|
||||
|
||||
@ -241,7 +241,7 @@ export class SkeletonRenderer {
|
||||
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
|
||||
alpha);
|
||||
|
||||
region.computeWorldVertices(slot.bone, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
||||
region.computeWorldVertices(slot, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
||||
|
||||
let vertices = this.vertices;
|
||||
let uvs = region.uvs;
|
||||
|
||||
@ -35,6 +35,8 @@ import { Slot } from "./Slot";
|
||||
import { TransformConstraint } from "./TransformConstraint";
|
||||
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils";
|
||||
import { Event } from "./Event";
|
||||
import { HasTextureRegion } from "./attachments/HasTextureRegion";
|
||||
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence";
|
||||
|
||||
/** A simple container for a list of timelines and a name. */
|
||||
export class Animation {
|
||||
@ -146,7 +148,9 @@ const Property = {
|
||||
|
||||
pathConstraintPosition: 16,
|
||||
pathConstraintSpacing: 17,
|
||||
pathConstraintMix: 18
|
||||
pathConstraintMix: 18,
|
||||
|
||||
sequence: 19
|
||||
}
|
||||
|
||||
/** The interface for all timelines. */
|
||||
@ -1505,7 +1509,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
let slot: Slot = skeleton.slots[this.slotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
let slotAttachment: Attachment = slot.getAttachment();
|
||||
if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).deformAttachment != this.attachment) return;
|
||||
if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).timelineAttahment != this.attachment) return;
|
||||
|
||||
let deform: Array<number> = slot.deform;
|
||||
if (deform.length == 0) blend = MixBlend.setup;
|
||||
@ -1515,7 +1519,6 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
let vertexAttachment = <VertexAttachment>slotAttachment;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
deform.length = 0;
|
||||
@ -1526,6 +1529,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
return;
|
||||
}
|
||||
deform.length = vertexCount;
|
||||
let vertexAttachment = <VertexAttachment>slotAttachment;
|
||||
if (!vertexAttachment.bones) {
|
||||
// Unweighted vertex positions.
|
||||
let setupVertices = vertexAttachment.vertices;
|
||||
@ -2141,3 +2145,91 @@ export class PathConstraintMixTimeline extends CurveTimeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
|
||||
export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
static ENTRIES = 3;
|
||||
static MODE = 1;
|
||||
static DELAY = 2;
|
||||
|
||||
slotIndex: number;
|
||||
attachment: HasTextureRegion;
|
||||
|
||||
constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) {
|
||||
super(frameCount, [
|
||||
Property.sequence + "|" + slotIndex + "|" + attachment.sequence.id
|
||||
]);
|
||||
this.slotIndex = slotIndex;
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
getFrameEntries () {
|
||||
return SequenceTimeline.ENTRIES;
|
||||
}
|
||||
|
||||
getSlotIndex () {
|
||||
return this.slotIndex;
|
||||
}
|
||||
|
||||
getAttachment () {
|
||||
return this.attachment as unknown as Attachment;
|
||||
}
|
||||
|
||||
/** Sets the time, mode, index, and frame time for the specified frame.
|
||||
* @param frame Between 0 and <code>frameCount</code>, inclusive.
|
||||
* @param time Seconds between frames. */
|
||||
setFrame (frame: number, time: number, mode: SequenceMode, index: number, delay: number) {
|
||||
let frames = this.frames;
|
||||
frame *= SequenceTimeline.ENTRIES;
|
||||
frames[frame] = time;
|
||||
frames[frame + SequenceTimeline.MODE] = mode | (index << 4);
|
||||
frames[frame + SequenceTimeline.DELAY] = delay;
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let slot = skeleton.slots[this.slotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
let slotAttachment = slot.attachment;
|
||||
let attachment = this.attachment as unknown as Attachment;
|
||||
if (slotAttachment != attachment) {
|
||||
if (!(slotAttachment instanceof VertexAttachment)
|
||||
|| (slotAttachment as VertexAttachment).timelineAttahment != attachment) return;
|
||||
}
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) { // Time is before first frame.
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) slot.sequenceIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
let i = Timeline.search(frames, time, SequenceTimeline.ENTRIES);
|
||||
let before = frames[i];
|
||||
let modeAndIndex = frames[i + SequenceTimeline.MODE];
|
||||
let delay = frames[i + SequenceTimeline.DELAY];
|
||||
|
||||
let index = modeAndIndex >> 4, count = this.attachment.sequence.regions.length;
|
||||
let mode = SequenceModeValues[modeAndIndex & 0xf];
|
||||
if (mode != SequenceMode.hold) {
|
||||
index += (time - before) / delay + 0.00001;
|
||||
switch (mode) {
|
||||
case SequenceMode.once:
|
||||
index = Math.min(count - 1, index);
|
||||
break;
|
||||
case SequenceMode.loop:
|
||||
index %= count;
|
||||
break;
|
||||
case SequenceMode.pingpong:
|
||||
let n = (count << 1) - 2;
|
||||
index %= n;
|
||||
if (index >= count) index = n - index;
|
||||
break;
|
||||
case SequenceMode.onceReverse:
|
||||
index = Math.max(count - 1 - index, 0);
|
||||
break;
|
||||
case SequenceMode.loopReverse:
|
||||
index = count - 1 - (index % count);
|
||||
}
|
||||
}
|
||||
slot.sequenceIndex = index;
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@ import { PointAttachment } from "./attachments/PointAttachment";
|
||||
import { RegionAttachment } from "./attachments/RegionAttachment";
|
||||
import { Skin } from "./Skin";
|
||||
import { TextureAtlas } from "./TextureAtlas";
|
||||
import { Sequence } from "./attachments/Sequence"
|
||||
|
||||
/** An {@link AttachmentLoader} that configures attachments using texture regions from an {@link TextureAtlas}.
|
||||
*
|
||||
@ -48,21 +49,39 @@ export class AtlasAttachmentLoader implements AttachmentLoader {
|
||||
this.atlas = atlas;
|
||||
}
|
||||
|
||||
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment {
|
||||
let region = this.atlas.findRegion(path);
|
||||
if (!region) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")");
|
||||
region.renderObject = region;
|
||||
loadSequence (name: string, basePath: string, sequence: Sequence) {
|
||||
let regions = sequence.regions;
|
||||
for (let i = 0, n = regions.length; i < n; i++) {
|
||||
let path = sequence.getPath(basePath, i);
|
||||
regions[i] = this.atlas.findRegion(path);
|
||||
regions[i].renderObject = regions[i];
|
||||
if (regions[i] == null) throw new Error("Region not found in atlas: " + path + " (sequence: " + name + ")");
|
||||
}
|
||||
}
|
||||
|
||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment {
|
||||
let attachment = new RegionAttachment(name);
|
||||
attachment.setRegion(region);
|
||||
if (sequence != null) {
|
||||
this.loadSequence(name, path, sequence);
|
||||
} else {
|
||||
let region = this.atlas.findRegion(path);
|
||||
if (!region) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")");
|
||||
region.renderObject = region;
|
||||
attachment.region = region;
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment {
|
||||
let region = this.atlas.findRegion(path);
|
||||
if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
|
||||
region.renderObject = region;
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
|
||||
let attachment = new MeshAttachment(name);
|
||||
attachment.region = region;
|
||||
if (sequence != null) {
|
||||
this.loadSequence(name, path, sequence);
|
||||
} else {
|
||||
let region = this.atlas.findRegion(path);
|
||||
if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
|
||||
region.renderObject = region;
|
||||
attachment.region = region;
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
|
||||
@ -75,11 +75,6 @@ export class Skeleton {
|
||||
/** The color to tint all the skeleton's attachments. */
|
||||
color: Color;
|
||||
|
||||
/** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#attachmentTime}.
|
||||
* <p>
|
||||
* See {@link #update()}. */
|
||||
time = 0;
|
||||
|
||||
/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
|
||||
* inheritance. */
|
||||
scaleX = 1;
|
||||
@ -603,7 +598,7 @@ export class Skeleton {
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
verticesLength = 8;
|
||||
vertices = Utils.setArraySize(temp, verticesLength, 0);
|
||||
(<RegionAttachment>attachment).computeWorldVertices(slot.bone, vertices, 0, 2);
|
||||
(<RegionAttachment>attachment).computeWorldVertices(slot, vertices, 0, 2);
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
let mesh = (<MeshAttachment>attachment);
|
||||
verticesLength = mesh.worldVerticesLength;
|
||||
@ -623,9 +618,4 @@ export class Skeleton {
|
||||
offset.set(minX, minY);
|
||||
size.set(maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/** Increments the skeleton's {@link #time}. */
|
||||
update (delta: number) {
|
||||
this.time += delta;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,10 +27,12 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { Animation, Timeline, AttachmentTimeline, RGBATimeline, RGBTimeline, RGBA2Timeline, RGB2Timeline, AlphaTimeline, RotateTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, IkConstraintTimeline, TransformConstraintTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PathConstraintMixTimeline, DeformTimeline, DrawOrderTimeline, EventTimeline, CurveTimeline1, CurveTimeline2, CurveTimeline } from "./Animation";
|
||||
import { Animation, Timeline, AttachmentTimeline, RGBATimeline, RGBTimeline, RGBA2Timeline, RGB2Timeline, AlphaTimeline, RotateTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, IkConstraintTimeline, TransformConstraintTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PathConstraintMixTimeline, DeformTimeline, DrawOrderTimeline, EventTimeline, CurveTimeline1, CurveTimeline2, CurveTimeline, SequenceTimeline } from "./Animation";
|
||||
import { VertexAttachment, Attachment } from "./attachments/Attachment";
|
||||
import { AttachmentLoader } from "./attachments/AttachmentLoader";
|
||||
import { HasTextureRegion } from "./attachments/HasTextureRegion";
|
||||
import { MeshAttachment } from "./attachments/MeshAttachment";
|
||||
import { Sequence, SequenceModeValues } from "./attachments/Sequence";
|
||||
import { BoneData } from "./BoneData";
|
||||
import { Event } from "./Event";
|
||||
import { EventData } from "./EventData";
|
||||
@ -219,9 +221,9 @@ export class SkeletonBinary {
|
||||
let linkedMesh = this.linkedMeshes[i];
|
||||
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
|
||||
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? parent as VertexAttachment : linkedMesh.mesh;
|
||||
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
|
||||
linkedMesh.mesh.updateUVs();
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
}
|
||||
this.linkedMeshes.length = 0;
|
||||
|
||||
@ -299,9 +301,10 @@ export class SkeletonBinary {
|
||||
let width = input.readFloat();
|
||||
let height = input.readFloat();
|
||||
let color = input.readInt32();
|
||||
let sequence = this.readSequence(input);
|
||||
|
||||
if (!path) path = name;
|
||||
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
|
||||
let region = this.attachmentLoader.newRegionAttachment(skin, name, path, sequence);
|
||||
if (!region) return null;
|
||||
region.path = path;
|
||||
region.x = x * scale;
|
||||
@ -312,7 +315,8 @@ export class SkeletonBinary {
|
||||
region.width = width * scale;
|
||||
region.height = height * scale;
|
||||
Color.rgba8888ToColor(region.color, color);
|
||||
region.updateOffset();
|
||||
region.sequence = sequence;
|
||||
if (sequence == null) region.updateRegion();
|
||||
return region;
|
||||
}
|
||||
case AttachmentType.BoundingBox: {
|
||||
@ -336,6 +340,7 @@ export class SkeletonBinary {
|
||||
let triangles = this.readShortArray(input);
|
||||
let vertices = this.readVertices(input, vertexCount);
|
||||
let hullLength = input.readInt(true);
|
||||
let sequence = this.readSequence(input);
|
||||
let edges = null;
|
||||
let width = 0, height = 0;
|
||||
if (nonessential) {
|
||||
@ -345,7 +350,7 @@ export class SkeletonBinary {
|
||||
}
|
||||
|
||||
if (!path) path = name;
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
|
||||
if (!mesh) return null;
|
||||
mesh.path = path;
|
||||
Color.rgba8888ToColor(mesh.color, color);
|
||||
@ -354,8 +359,9 @@ export class SkeletonBinary {
|
||||
mesh.worldVerticesLength = vertexCount << 1;
|
||||
mesh.triangles = triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
mesh.updateUVs();
|
||||
if (sequence == null) mesh.updateRegion();
|
||||
mesh.hullLength = hullLength << 1;
|
||||
mesh.sequence = sequence;
|
||||
if (nonessential) {
|
||||
mesh.edges = edges;
|
||||
mesh.width = width * scale;
|
||||
@ -368,7 +374,8 @@ export class SkeletonBinary {
|
||||
let color = input.readInt32();
|
||||
let skinName = input.readStringRef();
|
||||
let parent = input.readStringRef();
|
||||
let inheritDeform = input.readBoolean();
|
||||
let inheritTimelines = input.readBoolean();
|
||||
let sequence = this.readSequence(input);
|
||||
let width = 0, height = 0;
|
||||
if (nonessential) {
|
||||
width = input.readFloat();
|
||||
@ -376,15 +383,16 @@ export class SkeletonBinary {
|
||||
}
|
||||
|
||||
if (!path) path = name;
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
|
||||
if (!mesh) return null;
|
||||
mesh.path = path;
|
||||
Color.rgba8888ToColor(mesh.color, color);
|
||||
mesh.sequence = sequence;
|
||||
if (nonessential) {
|
||||
mesh.width = width * scale;
|
||||
mesh.height = height * scale;
|
||||
}
|
||||
this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
|
||||
this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines));
|
||||
return mesh;
|
||||
}
|
||||
case AttachmentType.Path: {
|
||||
@ -441,6 +449,15 @@ export class SkeletonBinary {
|
||||
return null;
|
||||
}
|
||||
|
||||
private readSequence (input: BinaryInput) {
|
||||
if (!input.readBoolean()) return null;
|
||||
let sequence = new Sequence(input.readInt(true));
|
||||
sequence.start = input.readInt(true);
|
||||
sequence.digits = input.readInt(true);
|
||||
sequence.setupIndex = input.readInt(true);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private readVertices (input: BinaryInput, vertexCount: number): Vertices {
|
||||
let scale = this.scale;
|
||||
let verticesLength = vertexCount << 1;
|
||||
@ -697,7 +714,6 @@ export class SkeletonBinary {
|
||||
a = a2;
|
||||
}
|
||||
timelines.push(timeline);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -850,112 +866,130 @@ export class SkeletonBinary {
|
||||
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;
|
||||
let weighted = attachment.bones;
|
||||
let vertices = attachment.vertices;
|
||||
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
|
||||
|
||||
let attachment = skin.getAttachment(slotIndex, attachmentName);
|
||||
let timelineType = input.readByte();
|
||||
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];
|
||||
}
|
||||
}
|
||||
switch (timelineType) {
|
||||
case ATTACHMENT_DEFORM: {
|
||||
let vertexAttachment = attachment as VertexAttachment;
|
||||
let weighted = vertexAttachment.bones;
|
||||
let vertices = vertexAttachment.vertices;
|
||||
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
|
||||
|
||||
timeline.setFrame(frame, time, deform);
|
||||
if (frame == frameLast) break;
|
||||
let time2 = input.readFloat();
|
||||
switch (input.readByte()) {
|
||||
case CURVE_STEPPED:
|
||||
timeline.setStepped(frame);
|
||||
break;
|
||||
case CURVE_BEZIER:
|
||||
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
|
||||
|
||||
let bezierCount = input.readInt(true);
|
||||
let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, vertexAttachment);
|
||||
|
||||
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 CURVE_STEPPED:
|
||||
timeline.setStepped(frame);
|
||||
break;
|
||||
case CURVE_BEZIER:
|
||||
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
|
||||
}
|
||||
time = time2;
|
||||
}
|
||||
timelines.push(timeline);
|
||||
}
|
||||
case ATTACHMENT_SEQUENCE: {
|
||||
let timeline = new SequenceTimeline(frameCount, slotIndex, attachment as unknown as HasTextureRegion);
|
||||
for (let frame = 0; frame < frameCount; frame++) {
|
||||
let time = input.readFloat();
|
||||
let modeAndIndex = input.readInt32();
|
||||
timeline.setFrame(frame, time, SequenceModeValues[modeAndIndex & 0xf], modeAndIndex >> 4,
|
||||
input.readFloat());
|
||||
}
|
||||
timelines.push(timeline);
|
||||
}
|
||||
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)
|
||||
// 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++;
|
||||
// Set changed items.
|
||||
drawOrder[originalIndex + input.readInt(true)] = 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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
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) {
|
||||
event.volume = input.readFloat();
|
||||
event.balance = input.readFloat();
|
||||
// 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) {
|
||||
event.volume = input.readFloat();
|
||||
event.balance = input.readFloat();
|
||||
}
|
||||
timeline.setFrame(i, event);
|
||||
}
|
||||
timeline.setFrame(i, event);
|
||||
timelines.push(timeline);
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1056,14 +1090,14 @@ class LinkedMesh {
|
||||
parent: string; skin: string;
|
||||
slotIndex: number;
|
||||
mesh: MeshAttachment;
|
||||
inheritDeform: boolean;
|
||||
inheritTimeline: 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;
|
||||
this.inheritTimeline = inheritDeform;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1136,6 +1170,9 @@ const SLOT_RGBA2 = 3;
|
||||
const SLOT_RGB2 = 4;
|
||||
const SLOT_ALPHA = 5;
|
||||
|
||||
const ATTACHMENT_DEFORM = 0;
|
||||
const ATTACHMENT_SEQUENCE = 1;
|
||||
|
||||
const PATH_POSITION = 0;
|
||||
const PATH_SPACING = 1;
|
||||
const PATH_MIX = 2;
|
||||
|
||||
@ -41,6 +41,7 @@ import { Skin } from "./Skin";
|
||||
import { SlotData, BlendMode } from "./SlotData";
|
||||
import { TransformConstraintData } from "./TransformConstraintData";
|
||||
import { Utils, Color, NumberArrayLike } from "./Utils";
|
||||
import { Sequence } from "./attachments/Sequence";
|
||||
|
||||
/** Loads skeleton data in the Spine JSON format.
|
||||
*
|
||||
@ -257,9 +258,9 @@ export class SkeletonJson {
|
||||
let linkedMesh = this.linkedMeshes[i];
|
||||
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
|
||||
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
||||
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
|
||||
linkedMesh.mesh.updateUVs();
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
}
|
||||
this.linkedMeshes.length = 0;
|
||||
|
||||
@ -298,7 +299,8 @@ export class SkeletonJson {
|
||||
switch (getValue(map, "type", "region")) {
|
||||
case "region": {
|
||||
let path = getValue(map, "path", name);
|
||||
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
|
||||
let sequence = this.readSequence(getValue(map, "sequence", null));
|
||||
let region = this.attachmentLoader.newRegionAttachment(skin, name, path, sequence);
|
||||
if (!region) return null;
|
||||
region.path = path;
|
||||
region.x = getValue(map, "x", 0) * scale;
|
||||
@ -308,11 +310,12 @@ export class SkeletonJson {
|
||||
region.rotation = getValue(map, "rotation", 0);
|
||||
region.width = map.width * scale;
|
||||
region.height = map.height * scale;
|
||||
region.sequence = sequence;
|
||||
|
||||
let color: string = getValue(map, "color", null);
|
||||
if (color) region.color.setFromString(color);
|
||||
|
||||
region.updateOffset();
|
||||
if (region.region != null) region.updateRegion();
|
||||
return region;
|
||||
}
|
||||
case "boundingbox": {
|
||||
@ -326,7 +329,8 @@ export class SkeletonJson {
|
||||
case "mesh":
|
||||
case "linkedmesh": {
|
||||
let path = getValue(map, "path", name);
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
|
||||
let sequence = this.readSequence(getValue(map, "sequence", null));
|
||||
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
|
||||
if (!mesh) return null;
|
||||
mesh.path = path;
|
||||
|
||||
@ -335,10 +339,11 @@ export class SkeletonJson {
|
||||
|
||||
mesh.width = getValue(map, "width", 0) * scale;
|
||||
mesh.height = getValue(map, "height", 0) * scale;
|
||||
mesh.sequence = sequence;
|
||||
|
||||
let parent: string = getValue(map, "parent", null);
|
||||
if (parent) {
|
||||
this.linkedMeshes.push(new LinkedMesh(mesh, <string>getValue(map, "skin", null), slotIndex, parent, getValue(map, "deform", true)));
|
||||
this.linkedMeshes.push(new LinkedMesh(mesh, <string>getValue(map, "skin", null), slotIndex, parent, getValue(map, "timelines", true)));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@ -346,7 +351,7 @@ export class SkeletonJson {
|
||||
this.readVertices(map, mesh, uvs.length);
|
||||
mesh.triangles = map.triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
mesh.updateUVs();
|
||||
if (mesh.region != null) mesh.updateRegion();
|
||||
|
||||
mesh.edges = getValue(map, "edges", null);
|
||||
mesh.hullLength = getValue(map, "hull", 0) * 2;
|
||||
@ -399,6 +404,15 @@ export class SkeletonJson {
|
||||
return null;
|
||||
}
|
||||
|
||||
readSequence (map: any) {
|
||||
if (map == null) return null;
|
||||
let sequence = new Sequence(getValue(map, "count", 0));
|
||||
sequence.start = getValue(map, "start", 1);
|
||||
sequence.digits = getValue(map, "digits", 0);
|
||||
sequence.setupIndex = getValue(map, "setup", 0);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
readVertices (map: any, attachment: VertexAttachment, verticesLength: number) {
|
||||
let scale = this.scale;
|
||||
attachment.worldVerticesLength = verticesLength;
|
||||
@ -445,7 +459,7 @@ export class SkeletonJson {
|
||||
let timeline = new AttachmentTimeline(frames, slotIndex);
|
||||
for (let frame = 0; frame < frames; frame++) {
|
||||
let keyMap = timelineMap[frame];
|
||||
timeline.setFrame(frame, getValue(keyMap, "time", 0), keyMap.name);
|
||||
timeline.setFrame(frame, getValue(keyMap, "time", 0), getValue(keyMap, "name", null));
|
||||
}
|
||||
timelines.push(timeline);
|
||||
|
||||
@ -778,17 +792,18 @@ export class SkeletonJson {
|
||||
}
|
||||
}
|
||||
|
||||
// Deform timelines.
|
||||
if (map.deform) {
|
||||
for (let deformName in map.deform) {
|
||||
let deformMap = map.deform[deformName];
|
||||
let skin = skeletonData.findSkin(deformName);
|
||||
for (let slotName in deformMap) {
|
||||
let slotMap = deformMap[slotName];
|
||||
// Attachment timelines.
|
||||
if (map.attachments) {
|
||||
for (let attachmentsName in map.attachments) {
|
||||
let attachmentsMap = map.attachments[attachmentsName];
|
||||
let skin = skeletonData.findSkin(attachmentsName);
|
||||
for (let slotName in attachmentsMap) {
|
||||
let slotMap = attachmentsMap[slotName];
|
||||
let slotIndex = skeletonData.findSlot(slotName).index;
|
||||
for (let timelineName in slotMap) {
|
||||
let timelineMap = slotMap[timelineName];
|
||||
let keyMap = timelineMap[0];
|
||||
let attachmentMap = slotMap[timelineName];
|
||||
let attachmentMapName = timelineName;
|
||||
let keyMap = attachmentMap[0];
|
||||
if (!keyMap) continue;
|
||||
|
||||
let attachment = <VertexAttachment>skin.getAttachment(slotIndex, timelineName);
|
||||
@ -796,7 +811,7 @@ export class SkeletonJson {
|
||||
let vertices = attachment.vertices;
|
||||
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
|
||||
|
||||
let timeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
|
||||
let timeline = new DeformTimeline(attachmentMap.length, attachmentMap.length, slotIndex, attachment);
|
||||
let time = getValue(keyMap, "time", 0);
|
||||
for (let frame = 0, bezier = 0; ; frame++) {
|
||||
let deform: NumberArrayLike;
|
||||
@ -818,7 +833,7 @@ export class SkeletonJson {
|
||||
}
|
||||
|
||||
timeline.setFrame(frame, time, deform);
|
||||
let nextMap = timelineMap[frame + 1];
|
||||
let nextMap = attachmentMap[frame + 1];
|
||||
if (!nextMap) {
|
||||
timeline.shrink(bezier);
|
||||
break;
|
||||
@ -900,14 +915,14 @@ class LinkedMesh {
|
||||
parent: string; skin: string;
|
||||
slotIndex: number;
|
||||
mesh: MeshAttachment;
|
||||
inheritDeform: boolean;
|
||||
inheritTimeline: 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;
|
||||
this.inheritTimeline = inheritDeform;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,10 +53,12 @@ export class Slot {
|
||||
|
||||
attachment: Attachment;
|
||||
|
||||
private attachmentTime: number;
|
||||
|
||||
attachmentState: number;
|
||||
|
||||
/** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the
|
||||
* {@link Sequence#getSetupIndex()}. */
|
||||
sequenceIndex: number;
|
||||
|
||||
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
|
||||
* weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
|
||||
*
|
||||
@ -83,28 +85,17 @@ export class Slot {
|
||||
return this.attachment;
|
||||
}
|
||||
|
||||
/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}.
|
||||
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getDeformAttachment()} as the specified
|
||||
* attachment.
|
||||
* @param attachment May be null. */
|
||||
/** Sets the slot's attachment and, if the attachment changed, resets {@link #sequenceIndex} and clears the {@link #deform}.
|
||||
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getTimelineAttachment()} as the
|
||||
* specified attachment. */
|
||||
setAttachment (attachment: Attachment) {
|
||||
if (this.attachment == attachment) return;
|
||||
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|
||||
|| (<VertexAttachment>attachment).deformAttachment != (<VertexAttachment>this.attachment).deformAttachment) {
|
||||
|| (<VertexAttachment>attachment).timelineAttahment != (<VertexAttachment>this.attachment).timelineAttahment) {
|
||||
this.deform.length = 0;
|
||||
}
|
||||
this.attachment = attachment;
|
||||
this.attachmentTime = this.bone.skeleton.time;
|
||||
}
|
||||
|
||||
setAttachmentTime (time: number) {
|
||||
this.attachmentTime = this.bone.skeleton.time - time;
|
||||
}
|
||||
|
||||
/** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
|
||||
* {@link Skeleton#time}. */
|
||||
getAttachmentTime (): number {
|
||||
return this.bone.skeleton.time - this.attachmentTime;
|
||||
this.sequenceIndex = -1;
|
||||
}
|
||||
|
||||
/** Sets this slot to the setup pose. */
|
||||
|
||||
@ -64,8 +64,9 @@ export abstract class VertexAttachment extends Attachment {
|
||||
* {@link #computeWorldVertices()} using the `count` parameter. */
|
||||
worldVerticesLength = 0;
|
||||
|
||||
/** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */
|
||||
deformAttachment: VertexAttachment = this;
|
||||
/** Timelines for the timeline attachment are also applied to this attachment.
|
||||
* May be null if no attachment-specific timelines should be applied. */
|
||||
timelineAttahment: Attachment = this;
|
||||
|
||||
constructor (name: string) {
|
||||
super(name);
|
||||
@ -155,6 +156,6 @@ export abstract class VertexAttachment extends Attachment {
|
||||
attachment.vertices = null;
|
||||
|
||||
attachment.worldVerticesLength = this.worldVerticesLength;
|
||||
attachment.deformAttachment = this.deformAttachment;
|
||||
attachment.timelineAttahment = this.timelineAttahment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import { MeshAttachment } from "./MeshAttachment";
|
||||
import { PathAttachment } from "./PathAttachment";
|
||||
import { PointAttachment } from "./PointAttachment";
|
||||
import { RegionAttachment } from "./RegionAttachment";
|
||||
import { Sequence } from "./Sequence";
|
||||
|
||||
/** The interface which can be implemented to customize creating and populating attachments.
|
||||
*
|
||||
@ -41,10 +42,10 @@ import { RegionAttachment } from "./RegionAttachment";
|
||||
* Runtimes Guide. */
|
||||
export interface AttachmentLoader {
|
||||
/** @return May be null to not load an attachment. */
|
||||
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment;
|
||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment;
|
||||
|
||||
/** @return May be null to not load an attachment. */
|
||||
newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment;
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment;
|
||||
|
||||
/** @return May be null to not load an attachment. */
|
||||
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
|
||||
|
||||
50
spine-ts/spine-core/src/attachments/HasTextureRegion.ts
Normal file
50
spine-ts/spine-core/src/attachments/HasTextureRegion.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated September 24, 2021. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { TextureRegion } from "../Texture"
|
||||
import { Color } from "../Utils"
|
||||
import { Sequence } from "./Sequence"
|
||||
|
||||
export interface HasTextureRegion {
|
||||
/** The name used to find the {@link #region()}. */
|
||||
path: string;
|
||||
|
||||
/** The region used to draw the attachment. After setting the region or if the region's properties are changed,
|
||||
* {@link #updateRegion()} must be called. */
|
||||
region: TextureRegion;
|
||||
|
||||
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
|
||||
* {@link #getRegion()} or if the region's properties are changed. */
|
||||
updateRegion (): void;
|
||||
|
||||
/** The color to tint the attachment. */
|
||||
color: Color;
|
||||
|
||||
sequence: Sequence;
|
||||
}
|
||||
@ -31,12 +31,15 @@ import { TextureRegion } from "../Texture";
|
||||
import { TextureAtlasRegion } from "../TextureAtlas";
|
||||
import { Color, NumberArrayLike, Utils } from "../Utils";
|
||||
import { VertexAttachment, Attachment } from "./Attachment";
|
||||
import { HasTextureRegion } from "./HasTextureRegion";
|
||||
import { Sequence } from "./Sequence";
|
||||
import { Slot } from "../Slot";
|
||||
|
||||
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
|
||||
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
|
||||
*
|
||||
* See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
|
||||
export class MeshAttachment extends VertexAttachment {
|
||||
export class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
||||
region: TextureRegion;
|
||||
|
||||
/** The name of the texture region for this attachment. */
|
||||
@ -70,15 +73,18 @@ export class MeshAttachment extends VertexAttachment {
|
||||
edges: Array<number>;
|
||||
|
||||
private parentMesh: MeshAttachment;
|
||||
|
||||
sequence: Sequence;
|
||||
|
||||
tempColor = new Color(0, 0, 0, 0);
|
||||
|
||||
constructor (name: string) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
|
||||
* region. */
|
||||
updateUVs () {
|
||||
/** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or
|
||||
* the {@link #regionUVs} are changed. */
|
||||
updateRegion () {
|
||||
let regionUVs = this.regionUVs;
|
||||
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
|
||||
let uvs = this.uvs;
|
||||
@ -175,6 +181,8 @@ export class MeshAttachment extends VertexAttachment {
|
||||
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
|
||||
copy.hullLength = this.hullLength;
|
||||
|
||||
copy.sequence = this.sequence.copy();
|
||||
|
||||
// Nonessential.
|
||||
if (this.edges) {
|
||||
copy.edges = new Array<number>(this.edges.length);
|
||||
@ -186,15 +194,20 @@ export class MeshAttachment extends VertexAttachment {
|
||||
return copy;
|
||||
}
|
||||
|
||||
computeWorldVertices (slot: Slot, start: number, count: number, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
||||
if (this.sequence != null) this.sequence.apply(slot, this);
|
||||
super.computeWorldVertices(slot, start, count, worldVertices, offset, stride);
|
||||
}
|
||||
|
||||
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
|
||||
newLinkedMesh (): MeshAttachment {
|
||||
let copy = new MeshAttachment(this.name);
|
||||
copy.region = this.region;
|
||||
copy.path = this.path;
|
||||
copy.color.setFromColor(this.color);
|
||||
copy.deformAttachment = this.deformAttachment;
|
||||
copy.timelineAttahment = this.timelineAttahment;
|
||||
copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
|
||||
copy.updateUVs();
|
||||
if (copy.region != null) copy.updateRegion();
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,11 +31,14 @@ import { Bone } from "../Bone";
|
||||
import { TextureRegion } from "../Texture";
|
||||
import { Color, NumberArrayLike, Utils } from "../Utils";
|
||||
import { Attachment } from "./Attachment";
|
||||
import { HasTextureRegion } from "./HasTextureRegion";
|
||||
import { Sequence } from "./Sequence";
|
||||
import { Slot } from "../Slot";
|
||||
|
||||
/** An attachment that displays a textured quadrilateral.
|
||||
*
|
||||
* See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
|
||||
export class RegionAttachment extends Attachment {
|
||||
export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
/** The local x translation. */
|
||||
x = 0;
|
||||
|
||||
@ -63,8 +66,9 @@ export class RegionAttachment extends Attachment {
|
||||
/** The name of the texture region for this attachment. */
|
||||
path: string;
|
||||
|
||||
rendererObject: any;
|
||||
private rendererObject: any;
|
||||
region: TextureRegion;
|
||||
sequence: Sequence;
|
||||
|
||||
/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
|
||||
*
|
||||
@ -80,7 +84,7 @@ export class RegionAttachment extends Attachment {
|
||||
}
|
||||
|
||||
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
|
||||
updateOffset (): void {
|
||||
updateRegion (): void {
|
||||
let region = this.region;
|
||||
let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
|
||||
let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
|
||||
@ -109,10 +113,7 @@ export class RegionAttachment extends Attachment {
|
||||
offset[5] = localY2Cos + localX2Sin;
|
||||
offset[6] = localX2Cos - localYSin;
|
||||
offset[7] = localYCos + localX2Sin;
|
||||
}
|
||||
|
||||
setRegion (region: TextureRegion): void {
|
||||
this.region = region;
|
||||
let uvs = this.uvs;
|
||||
if (region.degrees == 90) {
|
||||
uvs[2] = region.u;
|
||||
@ -135,14 +136,18 @@ export class RegionAttachment extends Attachment {
|
||||
}
|
||||
}
|
||||
|
||||
/** Transforms the attachment's four vertices to world coordinates.
|
||||
*
|
||||
* See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
|
||||
/** Transforms the attachment's four vertices to world coordinates. If the attachment has a {@link #sequence}, the region may
|
||||
* be changed.
|
||||
* <p>
|
||||
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||
* Runtimes Guide.
|
||||
* @param worldVertices The output world vertices. Must have a length >= `offset` + 8.
|
||||
* @param offset The `worldVertices` index to begin writing values.
|
||||
* @param stride The number of `worldVertices` entries between the value pairs written. */
|
||||
computeWorldVertices (bone: Bone, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
||||
* @param worldVertices The output world vertices. Must have a length >= <code>offset</code> + 8.
|
||||
* @param offset The <code>worldVertices</code> index to begin writing values.
|
||||
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
|
||||
computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
||||
if (this.sequence != null) this.sequence.apply(slot, this);
|
||||
|
||||
let bone = slot.bone;
|
||||
let vertexOffset = this.offset;
|
||||
let x = bone.worldX, y = bone.worldY;
|
||||
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||
@ -187,6 +192,7 @@ export class RegionAttachment extends Attachment {
|
||||
Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8);
|
||||
Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8);
|
||||
copy.color.setFromColor(this.color);
|
||||
copy.sequence = this.sequence.copy();
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
102
spine-ts/spine-core/src/attachments/Sequence.ts
Normal file
102
spine-ts/spine-core/src/attachments/Sequence.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated September 24, 2021. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { TextureRegion } from "../Texture";
|
||||
import { Slot } from "../Slot";
|
||||
import { HasTextureRegion } from "./HasTextureRegion";
|
||||
import { Utils } from "src";
|
||||
|
||||
|
||||
export class Sequence {
|
||||
private static _nextID = 0;
|
||||
|
||||
id = Sequence.nextID();
|
||||
regions: TextureRegion[];
|
||||
start = 0;
|
||||
digits = 0;
|
||||
/** The index of the region to show for the setup pose. */
|
||||
setupIndex = 0;
|
||||
|
||||
constructor (count: number) {
|
||||
this.regions = new Array<TextureRegion>(count);
|
||||
}
|
||||
|
||||
copy (): Sequence {
|
||||
let copy = new Sequence(this.regions.length);
|
||||
Utils.arrayCopy(this.regions, 0, copy.regions, 0, this.regions.length);
|
||||
copy.start = this.start;
|
||||
copy.digits = this.digits;
|
||||
copy.setupIndex = this.setupIndex;
|
||||
return copy;
|
||||
}
|
||||
|
||||
apply (slot: Slot, attachment: HasTextureRegion) {
|
||||
let index = slot.sequenceIndex;
|
||||
if (index == -1) index = this.setupIndex;
|
||||
if (index >= this.regions.length) index = this.regions.length - 1;
|
||||
let region = this.regions[index];
|
||||
if (attachment.region != region) {
|
||||
attachment.region = region;
|
||||
attachment.updateRegion();
|
||||
}
|
||||
}
|
||||
|
||||
getPath (basePath: string, index: number): string {
|
||||
let result = basePath;
|
||||
let frame = (this.start + index).toString();
|
||||
for (let i = this.digits - frame.length; i > 0; i--)
|
||||
result += "0";
|
||||
result += frame;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static nextID (): number {
|
||||
return Sequence._nextID++;
|
||||
}
|
||||
}
|
||||
|
||||
export enum SequenceMode {
|
||||
hold = 0,
|
||||
once = 1,
|
||||
loop = 2,
|
||||
pingpong = 3,
|
||||
onceReverse = 4,
|
||||
loopReverse = 5,
|
||||
pingpongReverse = 6
|
||||
}
|
||||
|
||||
export const SequenceModeValues = [
|
||||
SequenceMode.hold,
|
||||
SequenceMode.once,
|
||||
SequenceMode.loop,
|
||||
SequenceMode.pingpong,
|
||||
SequenceMode.onceReverse,
|
||||
SequenceMode.loopReverse,
|
||||
SequenceMode.pingpongReverse
|
||||
];
|
||||
@ -176,7 +176,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
attachmentColor = region.color;
|
||||
vertices = this.vertices;
|
||||
numFloats = vertexSize * 4;
|
||||
region.computeWorldVertices(slot.bone, vertices, 0, vertexSize);
|
||||
region.computeWorldVertices(slot, vertices, 0, vertexSize);
|
||||
triangles = SkeletonMesh.QUAD_TRIANGLES;
|
||||
uvs = region.uvs;
|
||||
texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
|
||||
|
||||
@ -92,7 +92,7 @@ export class SkeletonDebugRenderer implements Disposable {
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
let regionAttachment = <RegionAttachment>attachment;
|
||||
let vertices = this.vertices;
|
||||
regionAttachment.computeWorldVertices(slot.bone, vertices, 0, 2);
|
||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
||||
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
|
||||
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
|
||||
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
|
||||
|
||||
@ -109,7 +109,7 @@ export class SkeletonRenderer {
|
||||
renderable.vertices = this.vertices;
|
||||
renderable.numVertices = 4;
|
||||
renderable.numFloats = clippedVertexSize << 2;
|
||||
region.computeWorldVertices(slot.bone, renderable.vertices, 0, clippedVertexSize);
|
||||
region.computeWorldVertices(slot, renderable.vertices, 0, clippedVertexSize);
|
||||
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
||||
uvs = region.uvs;
|
||||
texture = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
|
||||
// Create the Spine canvas which runs the app
|
||||
new spine.SpineCanvas(document.getElementById("canvas"), {
|
||||
pathPrefix: "assets/",
|
||||
pathPrefix: "../example/assets/",
|
||||
app: new App()
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user