mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[ts] Port latest libgdx timeline, sequence, and follow-up fixes.
This commit is contained in:
parent
cf45806bdd
commit
a5ac6e4dd5
@ -47,6 +47,7 @@
|
|||||||
<li><a href="/spine-pixi-v8/example/bounds.html">Bounds</a> - (<a href="/spine-pixi-v7/example/bounds.html">v7</a>)</li>
|
<li><a href="/spine-pixi-v8/example/bounds.html">Bounds</a> - (<a href="/spine-pixi-v7/example/bounds.html">v7</a>)</li>
|
||||||
<li><a href="/spine-pixi-v8/example/inline-loading.html">Inline loading</a> - (<a href="/spine-pixi-v7/example/inline-loading.html">v7</a>)</li>
|
<li><a href="/spine-pixi-v8/example/inline-loading.html">Inline loading</a> - (<a href="/spine-pixi-v7/example/inline-loading.html">v7</a>)</li>
|
||||||
<li><a href="/spine-pixi-v8/example/bunnymark.html?count=500&renderer=webgpu">Bunny Mark</a> - (<a href="/spine-pixi-v7/example/bunnymark.html?count=500">v7</a>)</li>
|
<li><a href="/spine-pixi-v8/example/bunnymark.html?count=500&renderer=webgpu">Bunny Mark</a> - (<a href="/spine-pixi-v7/example/bunnymark.html?count=500">v7</a>)</li>
|
||||||
|
<li><a href="/spine-pixi-v8/example/dragon.html">Dragon</a> - (<a href="/spine-pixi-v7/example/dragon.html">v7</a>)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>Phaser</li>
|
<li>Phaser</li>
|
||||||
<ul>
|
<ul>
|
||||||
@ -142,6 +143,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="/spine-webgl/example">Example</a></li>
|
<li><a href="/spine-webgl/example">Example</a></li>
|
||||||
<li><a href="/spine-webgl/example/barebones.html">Barebones</a></li>
|
<li><a href="/spine-webgl/example/barebones.html">Barebones</a></li>
|
||||||
|
<li><a href="/spine-webgl/example/dragon.html">Dragon</a></li>
|
||||||
<li><a href="/spine-webgl/example/physics.html">Physics</a></li>
|
<li><a href="/spine-webgl/example/physics.html">Physics</a></li>
|
||||||
<li><a href="/spine-webgl/example/physics2.html">Physics II</a></li>
|
<li><a href="/spine-webgl/example/physics2.html">Physics II</a></li>
|
||||||
<li><a href="/spine-webgl/example/physics3.html">Physics III</a></li>
|
<li><a href="/spine-webgl/example/physics3.html">Physics III</a></li>
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { type BlendMode, Color, MeshAttachment, RegionAttachment, type Skeleton, type Slot, type TextureRegion, Utils } from "@esotericsoftware/spine-core";
|
import { type BlendMode, Color, MeshAttachment, type NumberArrayLike, RegionAttachment, type Skeleton, type Slot, type TextureRegion, Utils } from "@esotericsoftware/spine-core";
|
||||||
import type { CanvasTexture } from "./CanvasTexture.js";
|
import type { CanvasTexture } from "./CanvasTexture.js";
|
||||||
|
|
||||||
const worldVertices = Utils.newFloatArray(8);
|
const worldVertices = Utils.newFloatArray(8);
|
||||||
@ -68,10 +68,14 @@ export class SkeletonRenderer {
|
|||||||
const pose = slot.applied;
|
const pose = slot.applied;
|
||||||
const attachment = pose.attachment;
|
const attachment = pose.attachment;
|
||||||
if (!(attachment instanceof RegionAttachment)) continue;
|
if (!(attachment instanceof RegionAttachment)) continue;
|
||||||
attachment.computeWorldVertices(slot, worldVertices, 0, 2);
|
|
||||||
const region: TextureRegion = <TextureRegion>attachment.region;
|
|
||||||
|
|
||||||
const image: HTMLImageElement = (<CanvasTexture>region.texture).getImage() as HTMLImageElement;
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), worldVertices, 0, 2);
|
||||||
|
|
||||||
|
const region = sequence.regions[sequenceIndex] as TextureRegion;
|
||||||
|
|
||||||
|
const image: HTMLImageElement = region.texture.getImage() as HTMLImageElement;
|
||||||
|
|
||||||
const slotColor = pose.color;
|
const slotColor = pose.color;
|
||||||
const regionColor = attachment.color;
|
const regionColor = attachment.color;
|
||||||
@ -83,7 +87,8 @@ export class SkeletonRenderer {
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
const boneApplied = bone.applied;
|
const boneApplied = bone.applied;
|
||||||
ctx.transform(boneApplied.a, boneApplied.c, boneApplied.b, boneApplied.d, boneApplied.worldX, boneApplied.worldY);
|
ctx.transform(boneApplied.a, boneApplied.c, boneApplied.b, boneApplied.d, boneApplied.worldX, boneApplied.worldY);
|
||||||
ctx.translate(attachment.offset[0], attachment.offset[1]);
|
const offsets = attachment.getOffsets(pose);
|
||||||
|
ctx.translate(offsets[0], offsets[1]);
|
||||||
ctx.rotate(attachment.rotation * Math.PI / 180);
|
ctx.rotate(attachment.rotation * Math.PI / 180);
|
||||||
|
|
||||||
const atlasScale = attachment.width / region.originalWidth;
|
const atlasScale = attachment.width / region.originalWidth;
|
||||||
@ -91,7 +96,7 @@ export class SkeletonRenderer {
|
|||||||
|
|
||||||
let w = region.width, h = region.height;
|
let w = region.width, h = region.height;
|
||||||
ctx.translate(w / 2, h / 2);
|
ctx.translate(w / 2, h / 2);
|
||||||
if (attachment.region?.degrees === 90) {
|
if (region.degrees === 90) {
|
||||||
const t = w;
|
const t = w;
|
||||||
w = h;
|
w = h;
|
||||||
h = t;
|
h = t;
|
||||||
@ -124,15 +129,25 @@ export class SkeletonRenderer {
|
|||||||
|
|
||||||
let texture: HTMLImageElement;
|
let texture: HTMLImageElement;
|
||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
const regionAttachment = <RegionAttachment>attachment;
|
const sequence = attachment.sequence;
|
||||||
vertices = this.computeRegionVertices(slot, regionAttachment, false);
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
|
const uvs = sequence.getUVs(sequenceIndex);
|
||||||
|
const offsets = attachment.getOffsets(pose);
|
||||||
|
|
||||||
|
vertices = this.computeRegionVertices(slot, attachment, offsets, uvs, false);
|
||||||
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
||||||
texture = (<CanvasTexture>regionAttachment.region?.texture).getImage() as HTMLImageElement;
|
|
||||||
|
texture = (sequence.regions[sequenceIndex]?.texture as CanvasTexture).getImage();
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
const mesh = <MeshAttachment>attachment;
|
const sequence = attachment.sequence;
|
||||||
vertices = this.computeMeshVertices(slot, mesh, false);
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
triangles = mesh.triangles;
|
|
||||||
texture = (<CanvasTexture>mesh.region?.texture).getImage() as HTMLImageElement;
|
const uvs = sequence.getUVs(sequenceIndex);
|
||||||
|
vertices = this.computeMeshVertices(slot, attachment, uvs, false);
|
||||||
|
triangles = attachment.triangles;
|
||||||
|
|
||||||
|
texture = (sequence.regions[sequenceIndex]?.texture as CanvasTexture).getImage();
|
||||||
} else
|
} else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -226,7 +241,7 @@ export class SkeletonRenderer {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeRegionVertices (slot: Slot, region: RegionAttachment, pma: boolean) {
|
private computeRegionVertices (slot: Slot, region: RegionAttachment, offsets: NumberArrayLike, uvs: NumberArrayLike, pma: boolean) {
|
||||||
const skeletonColor = slot.skeleton.color;
|
const skeletonColor = slot.skeleton.color;
|
||||||
const slotColor = slot.applied.color;
|
const slotColor = slot.applied.color;
|
||||||
const regionColor = region.color;
|
const regionColor = region.color;
|
||||||
@ -238,10 +253,9 @@ export class SkeletonRenderer {
|
|||||||
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
|
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
|
||||||
alpha);
|
alpha);
|
||||||
|
|
||||||
region.computeWorldVertices(slot, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
region.computeWorldVertices(slot, offsets, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
||||||
|
|
||||||
const vertices = this.vertices;
|
const vertices = this.vertices;
|
||||||
const uvs = region.uvs;
|
|
||||||
|
|
||||||
vertices[RegionAttachment.C1R] = color.r;
|
vertices[RegionAttachment.C1R] = color.r;
|
||||||
vertices[RegionAttachment.C1G] = color.g;
|
vertices[RegionAttachment.C1G] = color.g;
|
||||||
@ -274,7 +288,7 @@ export class SkeletonRenderer {
|
|||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeMeshVertices (slot: Slot, mesh: MeshAttachment, pma: boolean) {
|
private computeMeshVertices (slot: Slot, mesh: MeshAttachment, uvs: NumberArrayLike, pma: boolean) {
|
||||||
const skeleton = slot.skeleton;
|
const skeleton = slot.skeleton;
|
||||||
const skeletonColor = skeleton.color;
|
const skeletonColor = skeleton.color;
|
||||||
const slotColor = slot.applied.color;
|
const slotColor = slot.applied.color;
|
||||||
@ -292,7 +306,7 @@ export class SkeletonRenderer {
|
|||||||
if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength);
|
if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength);
|
||||||
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE);
|
||||||
|
|
||||||
const uvs = mesh.uvs;
|
|
||||||
for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
|
for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
|
||||||
vertices[v++] = color.r;
|
vertices[v++] = color.r;
|
||||||
vertices[v++] = color.g;
|
vertices[v++] = color.g;
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { type Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
import { type Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
||||||
import type { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
import { type HasSequence, isHasSequence } from "./attachments/HasSequence.js";
|
||||||
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.js";
|
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.js";
|
||||||
import type { Inherit } from "./BoneData.js";
|
import type { Inherit } from "./BoneData.js";
|
||||||
import type { BoneLocal } from "./BoneLocal.js";
|
import type { BoneLocal } from "./BoneLocal.js";
|
||||||
@ -933,8 +933,8 @@ export class RGBATimeline extends SlotCurveTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
||||||
const frames = this.frames;
|
|
||||||
const color = pose.color;
|
const color = pose.color;
|
||||||
|
const frames = this.frames;
|
||||||
if (time < frames[0]) {
|
if (time < frames[0]) {
|
||||||
const setup = slot.data.setup.color;
|
const setup = slot.data.setup.color;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
@ -977,8 +977,12 @@ export class RGBATimeline extends SlotCurveTimeline {
|
|||||||
if (alpha === 1)
|
if (alpha === 1)
|
||||||
color.set(r, g, b, a);
|
color.set(r, g, b, a);
|
||||||
else {
|
else {
|
||||||
if (blend === MixBlend.setup) color.setFromColor(slot.data.setup.color);
|
if (blend === MixBlend.setup) {
|
||||||
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
|
const setup = slot.data.setup.color;
|
||||||
|
color.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha,
|
||||||
|
setup.a + (a - setup.a) * alpha);
|
||||||
|
} else
|
||||||
|
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1003,64 +1007,67 @@ export class RGBTimeline extends SlotCurveTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
||||||
const frames = this.frames;
|
|
||||||
const color = pose.color;
|
const color = pose.color;
|
||||||
|
let r = 0, g = 0, b = 0;
|
||||||
|
const frames = this.frames;
|
||||||
if (time < frames[0]) {
|
if (time < frames[0]) {
|
||||||
const setup = slot.data.setup.color;
|
const setup = slot.data.setup.color;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||||
case MixBlend.setup:
|
case MixBlend.setup:
|
||||||
color.r = setup.r;
|
color.r = setup.r;
|
||||||
color.g = setup.g;
|
color.g = setup.g;
|
||||||
color.b = setup.b;
|
color.b = setup.b;
|
||||||
|
// Fall through.
|
||||||
|
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
case MixBlend.first:
|
case MixBlend.first:
|
||||||
color.r += (setup.r - color.r) * alpha;
|
r = color.r + (setup.r - color.r) * alpha;
|
||||||
color.g += (setup.g - color.g) * alpha;
|
g = color.g + (setup.g - color.g) * alpha;
|
||||||
color.b += (setup.b - color.b) * alpha;
|
b = color.b + (setup.b - color.b) * alpha;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = 0, g = 0, b = 0;
|
|
||||||
const i = Timeline.search(frames, time, 4/*ENTRIES*/);
|
|
||||||
const curveType = this.curves[i >> 2];
|
|
||||||
switch (curveType) {
|
|
||||||
case 0/*LINEAR*/: {
|
|
||||||
const before = frames[i];
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
const t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
|
|
||||||
r += (frames[i + 4/*ENTRIES*/ + 1/*R*/] - r) * t;
|
|
||||||
g += (frames[i + 4/*ENTRIES*/ + 2/*G*/] - g) * t;
|
|
||||||
b += (frames[i + 4/*ENTRIES*/ + 3/*B*/] - b) * t;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1/*STEPPED*/:
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
|
||||||
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
|
||||||
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
|
||||||
}
|
|
||||||
if (alpha === 1) {
|
|
||||||
color.r = r;
|
|
||||||
color.g = g;
|
|
||||||
color.b = b;
|
|
||||||
} else {
|
} else {
|
||||||
if (blend === MixBlend.setup) {
|
const i = Timeline.search(frames, time, 4/*ENTRIES*/);
|
||||||
const setup = slot.data.setup.color;
|
const curveType = this.curves[i >> 2];
|
||||||
color.r = setup.r;
|
switch (curveType) {
|
||||||
color.g = setup.g;
|
case 0/*LINEAR*/: {
|
||||||
color.b = setup.b;
|
const before = frames[i];
|
||||||
|
r = frames[i + 1/*R*/];
|
||||||
|
g = frames[i + 2/*G*/];
|
||||||
|
b = frames[i + 3/*B*/];
|
||||||
|
const t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
|
||||||
|
r += (frames[i + 4/*ENTRIES*/ + 1/*R*/] - r) * t;
|
||||||
|
g += (frames[i + 4/*ENTRIES*/ + 2/*G*/] - g) * t;
|
||||||
|
b += (frames[i + 4/*ENTRIES*/ + 3/*B*/] - b) * t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1/*STEPPED*/:
|
||||||
|
r = frames[i + 1/*R*/];
|
||||||
|
g = frames[i + 2/*G*/];
|
||||||
|
b = frames[i + 3/*B*/];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
||||||
|
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
||||||
|
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
||||||
|
}
|
||||||
|
if (alpha !== 1) {
|
||||||
|
if (blend === MixBlend.setup) {
|
||||||
|
const setup = slot.data.setup.color;
|
||||||
|
r = setup.r + (r - setup.r) * alpha;
|
||||||
|
g = setup.g + (g - setup.g) * alpha;
|
||||||
|
b = setup.b + (b - setup.b) * alpha;
|
||||||
|
} else {
|
||||||
|
r = color.r + (r - color.r) * alpha;
|
||||||
|
g = color.g + (g - color.g) * alpha;
|
||||||
|
b = color.b + (b - color.b) * alpha;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
color.r += (r - color.r) * alpha;
|
|
||||||
color.g += (g - color.g) * alpha;
|
|
||||||
color.b += (b - color.b) * alpha;
|
|
||||||
}
|
}
|
||||||
|
color.r = r < 0 ? 0 : (r > 1 ? 1 : r);
|
||||||
|
color.g = g < 0 ? 0 : (g > 1 ? 1 : g);
|
||||||
|
color.b = b < 0 ? 0 : (b > 1 ? 1 : b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1080,23 +1087,31 @@ export class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
|||||||
if (!slot.bone.active) return;
|
if (!slot.bone.active) return;
|
||||||
|
|
||||||
const color = (appliedPose ? slot.applied : slot.pose).color;
|
const color = (appliedPose ? slot.applied : slot.pose).color;
|
||||||
|
let a = 0;
|
||||||
const frames = this.frames;
|
const frames = this.frames;
|
||||||
if (time < frames[0]) {
|
if (time < frames[0]) {
|
||||||
const setup = slot.data.setup.color;
|
const setup = slot.data.setup.color;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
case MixBlend.setup: color.a = setup.a; break;
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||||
case MixBlend.first: color.a += (setup.a - color.a) * alpha; break;
|
case MixBlend.setup:
|
||||||
}
|
color.a = setup.a;
|
||||||
return;
|
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||||
}
|
default:
|
||||||
|
return;
|
||||||
|
case MixBlend.first: a = color.a + (setup.a - color.a) * alpha; break;
|
||||||
|
|
||||||
const a = this.getCurveValue(time);
|
}
|
||||||
if (alpha === 1)
|
} else {
|
||||||
color.a = a;
|
a = this.getCurveValue(time);
|
||||||
else {
|
if (alpha !== 1) {
|
||||||
if (blend === MixBlend.setup) color.a = slot.data.setup.color.a;
|
if (blend === MixBlend.setup) {
|
||||||
color.a += (a - color.a) * alpha;
|
const setup = slot.data.setup.color;
|
||||||
|
a = setup.a + (a - setup.a) * alpha;
|
||||||
|
} else
|
||||||
|
a = color.a + (a - color.a) * alpha;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
color.a = a < 0 ? 0 : (a > 1 ? 1 : a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1127,92 +1142,97 @@ export class RGBA2Timeline extends SlotCurveTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
||||||
const frames = this.frames;
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
|
||||||
const light = pose.color, dark = pose.darkColor!;
|
const light = pose.color, dark = pose.darkColor!;
|
||||||
|
let r2 = 0, g2 = 0, b2 = 0
|
||||||
|
const frames = this.frames;
|
||||||
if (time < frames[0]) {
|
if (time < frames[0]) {
|
||||||
const setup = slot.data.setup;
|
const setup = slot.data.setup;
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
const setupLight = setup.color, setupDark = setup.darkColor!;
|
const setupLight = setup.color, setupDark = setup.darkColor!;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||||
case MixBlend.setup:
|
case MixBlend.setup:
|
||||||
light.setFromColor(setupLight);
|
light.setFromColor(setupLight);
|
||||||
dark.r = setupDark.r;
|
dark.r = setupDark.r;
|
||||||
dark.g = setupDark.g;
|
dark.g = setupDark.g;
|
||||||
dark.b = setupDark.b;
|
dark.b = setupDark.b;
|
||||||
|
// Fall through.
|
||||||
|
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
case MixBlend.first:
|
case MixBlend.first:
|
||||||
light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
|
light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
|
||||||
(setupLight.a - light.a) * alpha);
|
(setupLight.a - light.a) * alpha);
|
||||||
dark.r += (setupDark.r - dark.r) * alpha;
|
r2 = dark.r + (setupDark.r - dark.r) * alpha;
|
||||||
dark.g += (setupDark.g - dark.g) * alpha;
|
g2 = dark.g + (setupDark.g - dark.g) * alpha;
|
||||||
dark.b += (setupDark.b - dark.b) * alpha;
|
b2 = dark.b + (setupDark.b - dark.b) * alpha;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
|
|
||||||
const i = Timeline.search(frames, time, 8/*ENTRIES*/);
|
|
||||||
const curveType = this.curves[i >> 3];
|
|
||||||
switch (curveType) {
|
|
||||||
case 0/*LINEAR*/: {
|
|
||||||
const before = frames[i];
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
a = frames[i + 4/*A*/];
|
|
||||||
r2 = frames[i + 5/*R2*/];
|
|
||||||
g2 = frames[i + 6/*G2*/];
|
|
||||||
b2 = frames[i + 7/*B2*/];
|
|
||||||
const t = (time - before) / (frames[i + 8/*ENTRIES*/] - before);
|
|
||||||
r += (frames[i + 8/*ENTRIES*/ + 1/*R*/] - r) * t;
|
|
||||||
g += (frames[i + 8/*ENTRIES*/ + 2/*G*/] - g) * t;
|
|
||||||
b += (frames[i + 8/*ENTRIES*/ + 3/*B*/] - b) * t;
|
|
||||||
a += (frames[i + 8/*ENTRIES*/ + 4/*A*/] - a) * t;
|
|
||||||
r2 += (frames[i + 8/*ENTRIES*/ + 5/*R2*/] - r2) * t;
|
|
||||||
g2 += (frames[i + 8/*ENTRIES*/ + 6/*G2*/] - g2) * t;
|
|
||||||
b2 += (frames[i + 8/*ENTRIES*/ + 7/*B2*/] - b2) * t;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1/*STEPPED*/:
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
a = frames[i + 4/*A*/];
|
|
||||||
r2 = frames[i + 5/*R2*/];
|
|
||||||
g2 = frames[i + 6/*G2*/];
|
|
||||||
b2 = frames[i + 7/*B2*/];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
|
||||||
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
|
||||||
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
|
||||||
a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
|
|
||||||
r2 = this.getBezierValue(time, i, 5/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
|
|
||||||
g2 = this.getBezierValue(time, i, 6/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
|
|
||||||
b2 = this.getBezierValue(time, i, 7/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 6 - 2/*BEZIER*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alpha === 1) {
|
|
||||||
light.set(r, g, b, a);
|
|
||||||
dark.r = r2;
|
|
||||||
dark.g = g2;
|
|
||||||
dark.b = b2;
|
|
||||||
} else {
|
} else {
|
||||||
if (blend === MixBlend.setup) {
|
let r = 0, g = 0, b = 0, a = 0;
|
||||||
const setup = slot.data.setup;
|
const i = Timeline.search(frames, time, 8/*ENTRIES*/);
|
||||||
light.setFromColor(setup.color);
|
const curveType = this.curves[i >> 3];
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
switch (curveType) {
|
||||||
const setupDark = setup.darkColor!;
|
case 0/*LINEAR*/: {
|
||||||
dark.r = setupDark.r;
|
const before = frames[i];
|
||||||
dark.g = setupDark.g;
|
r = frames[i + 1/*R*/];
|
||||||
dark.b = setupDark.b;
|
g = frames[i + 2/*G*/];
|
||||||
|
b = frames[i + 3/*B*/];
|
||||||
|
a = frames[i + 4/*A*/];
|
||||||
|
r2 = frames[i + 5/*R2*/];
|
||||||
|
g2 = frames[i + 6/*G2*/];
|
||||||
|
b2 = frames[i + 7/*B2*/];
|
||||||
|
const t = (time - before) / (frames[i + 8/*ENTRIES*/] - before);
|
||||||
|
r += (frames[i + 8/*ENTRIES*/ + 1/*R*/] - r) * t;
|
||||||
|
g += (frames[i + 8/*ENTRIES*/ + 2/*G*/] - g) * t;
|
||||||
|
b += (frames[i + 8/*ENTRIES*/ + 3/*B*/] - b) * t;
|
||||||
|
a += (frames[i + 8/*ENTRIES*/ + 4/*A*/] - a) * t;
|
||||||
|
r2 += (frames[i + 8/*ENTRIES*/ + 5/*R2*/] - r2) * t;
|
||||||
|
g2 += (frames[i + 8/*ENTRIES*/ + 6/*G2*/] - g2) * t;
|
||||||
|
b2 += (frames[i + 8/*ENTRIES*/ + 7/*B2*/] - b2) * t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1/*STEPPED*/:
|
||||||
|
r = frames[i + 1/*R*/];
|
||||||
|
g = frames[i + 2/*G*/];
|
||||||
|
b = frames[i + 3/*B*/];
|
||||||
|
a = frames[i + 4/*A*/];
|
||||||
|
r2 = frames[i + 5/*R2*/];
|
||||||
|
g2 = frames[i + 6/*G2*/];
|
||||||
|
b2 = frames[i + 7/*B2*/];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
||||||
|
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
||||||
|
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
||||||
|
a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
|
||||||
|
r2 = this.getBezierValue(time, i, 5/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
|
||||||
|
g2 = this.getBezierValue(time, i, 6/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
|
||||||
|
b2 = this.getBezierValue(time, i, 7/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 6 - 2/*BEZIER*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alpha === 1)
|
||||||
|
light.set(r, g, b, a);
|
||||||
|
else if (blend === MixBlend.setup) {
|
||||||
|
const setupPose = slot.data.setup;
|
||||||
|
let setup = setupPose.color;
|
||||||
|
light.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha,
|
||||||
|
setup.a + (a - setup.a) * alpha);
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
|
setup = setupPose.darkColor!;
|
||||||
|
r2 = setup.r + (r2 - setup.r) * alpha;
|
||||||
|
g2 = setup.g + (g2 - setup.g) * alpha;
|
||||||
|
b2 = setup.b + (b2 - setup.b) * alpha;
|
||||||
|
} else {
|
||||||
|
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
|
||||||
|
r2 = dark.r + (r2 - dark.r) * alpha;
|
||||||
|
g2 = dark.g + (g2 - dark.g) * alpha;
|
||||||
|
b2 = dark.b + (b2 - dark.b) * alpha;
|
||||||
}
|
}
|
||||||
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
|
|
||||||
dark.r += (r2 - dark.r) * alpha;
|
|
||||||
dark.g += (g2 - dark.g) * alpha;
|
|
||||||
dark.b += (b2 - dark.b) * alpha;
|
|
||||||
}
|
}
|
||||||
|
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
|
||||||
|
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
|
||||||
|
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1241,14 +1261,16 @@ export class RGB2Timeline extends SlotCurveTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
protected apply1 (slot: Slot, pose: SlotPose, time: number, alpha: number, blend: MixBlend) {
|
||||||
const frames = this.frames;
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
|
||||||
const light = pose.color, dark = pose.darkColor!;
|
const light = pose.color, dark = pose.darkColor!;
|
||||||
|
let r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0
|
||||||
|
const frames = this.frames;
|
||||||
if (time < frames[0]) {
|
if (time < frames[0]) {
|
||||||
const setup = slot.data.setup;
|
const setup = slot.data.setup;
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
const setupLight = setup.color, setupDark = setup.darkColor!;
|
const setupLight = setup.color, setupDark = setup.darkColor!;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||||
case MixBlend.setup:
|
case MixBlend.setup:
|
||||||
light.r = setupLight.r;
|
light.r = setupLight.r;
|
||||||
light.g = setupLight.g;
|
light.g = setupLight.g;
|
||||||
@ -1256,82 +1278,84 @@ export class RGB2Timeline extends SlotCurveTimeline {
|
|||||||
dark.r = setupDark.r;
|
dark.r = setupDark.r;
|
||||||
dark.g = setupDark.g;
|
dark.g = setupDark.g;
|
||||||
dark.b = setupDark.b;
|
dark.b = setupDark.b;
|
||||||
|
// Fall through.
|
||||||
|
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
case MixBlend.first:
|
case MixBlend.first:
|
||||||
light.r += (setupLight.r - light.r) * alpha;
|
r = light.r + (setupLight.r - light.r) * alpha;
|
||||||
light.g += (setupLight.g - light.g) * alpha;
|
g = light.g + (setupLight.g - light.g) * alpha;
|
||||||
light.b += (setupLight.b - light.b) * alpha;
|
b = light.b + (setupLight.b - light.b) * alpha;
|
||||||
dark.r += (setupDark.r - dark.r) * alpha;
|
r2 = dark.r + (setupDark.r - dark.r) * alpha;
|
||||||
dark.g += (setupDark.g - dark.g) * alpha;
|
g2 = dark.g + (setupDark.g - dark.g) * alpha;
|
||||||
dark.b += (setupDark.b - dark.b) * alpha;
|
b2 = dark.b + (setupDark.b - dark.b) * alpha;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0;
|
|
||||||
const i = Timeline.search(frames, time, 7/*ENTRIES*/);
|
|
||||||
const curveType = this.curves[i / 7/*ENTRIES*/];
|
|
||||||
switch (curveType) {
|
|
||||||
case 0/*LINEAR*/: {
|
|
||||||
const before = frames[i];
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
r2 = frames[i + 4/*R2*/];
|
|
||||||
g2 = frames[i + 5/*G2*/];
|
|
||||||
b2 = frames[i + 6/*B2*/];
|
|
||||||
const t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
|
|
||||||
r += (frames[i + 7/*ENTRIES*/ + 1/*R*/] - r) * t;
|
|
||||||
g += (frames[i + 7/*ENTRIES*/ + 2/*G*/] - g) * t;
|
|
||||||
b += (frames[i + 7/*ENTRIES*/ + 3/*B*/] - b) * t;
|
|
||||||
r2 += (frames[i + 7/*ENTRIES*/ + 4/*R2*/] - r2) * t;
|
|
||||||
g2 += (frames[i + 7/*ENTRIES*/ + 5/*G2*/] - g2) * t;
|
|
||||||
b2 += (frames[i + 7/*ENTRIES*/ + 6/*B2*/] - b2) * t;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1/*STEPPED*/:
|
|
||||||
r = frames[i + 1/*R*/];
|
|
||||||
g = frames[i + 2/*G*/];
|
|
||||||
b = frames[i + 3/*B*/];
|
|
||||||
r2 = frames[i + 4/*R2*/];
|
|
||||||
g2 = frames[i + 5/*G2*/];
|
|
||||||
b2 = frames[i + 6/*B2*/];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
|
||||||
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
|
||||||
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
|
||||||
r2 = this.getBezierValue(time, i, 4/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
|
|
||||||
g2 = this.getBezierValue(time, i, 5/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
|
|
||||||
b2 = this.getBezierValue(time, i, 6/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alpha === 1) {
|
|
||||||
light.r = r;
|
|
||||||
light.g = g;
|
|
||||||
light.b = b;
|
|
||||||
dark.r = r2;
|
|
||||||
dark.g = g2;
|
|
||||||
dark.b = b2;
|
|
||||||
} else {
|
} else {
|
||||||
if (blend === MixBlend.setup) {
|
const i = Timeline.search(frames, time, 7/*ENTRIES*/);
|
||||||
const setup = slot.data.setup;
|
const curveType = this.curves[i / 7/*ENTRIES*/];
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
switch (curveType) {
|
||||||
const setupLight = setup.color, setupDark = setup.darkColor!;
|
case 0/*LINEAR*/: {
|
||||||
light.r = setupLight.r;
|
const before = frames[i];
|
||||||
light.g = setupLight.g;
|
r = frames[i + 1/*R*/];
|
||||||
light.b = setupLight.b;
|
g = frames[i + 2/*G*/];
|
||||||
dark.r = setupDark.r;
|
b = frames[i + 3/*B*/];
|
||||||
dark.g = setupDark.g;
|
r2 = frames[i + 4/*R2*/];
|
||||||
dark.b = setupDark.b;
|
g2 = frames[i + 5/*G2*/];
|
||||||
|
b2 = frames[i + 6/*B2*/];
|
||||||
|
const t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
|
||||||
|
r += (frames[i + 7/*ENTRIES*/ + 1/*R*/] - r) * t;
|
||||||
|
g += (frames[i + 7/*ENTRIES*/ + 2/*G*/] - g) * t;
|
||||||
|
b += (frames[i + 7/*ENTRIES*/ + 3/*B*/] - b) * t;
|
||||||
|
r2 += (frames[i + 7/*ENTRIES*/ + 4/*R2*/] - r2) * t;
|
||||||
|
g2 += (frames[i + 7/*ENTRIES*/ + 5/*G2*/] - g2) * t;
|
||||||
|
b2 += (frames[i + 7/*ENTRIES*/ + 6/*B2*/] - b2) * t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1/*STEPPED*/:
|
||||||
|
r = frames[i + 1/*R*/];
|
||||||
|
g = frames[i + 2/*G*/];
|
||||||
|
b = frames[i + 3/*B*/];
|
||||||
|
r2 = frames[i + 4/*R2*/];
|
||||||
|
g2 = frames[i + 5/*G2*/];
|
||||||
|
b2 = frames[i + 6/*B2*/];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
|
||||||
|
g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
||||||
|
b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
||||||
|
r2 = this.getBezierValue(time, i, 4/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
|
||||||
|
g2 = this.getBezierValue(time, i, 5/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
|
||||||
|
b2 = this.getBezierValue(time, i, 6/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alpha !== 1) {
|
||||||
|
if (blend === MixBlend.setup) {
|
||||||
|
const setupPose = slot.data.setup;
|
||||||
|
let setup = setupPose.color;
|
||||||
|
r = setup.r + (r - setup.r) * alpha;
|
||||||
|
g = setup.g + (g - setup.g) * alpha;
|
||||||
|
b = setup.b + (b - setup.b) * alpha;
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
|
setup = setupPose.darkColor!;
|
||||||
|
r2 = setup.r + (r2 - setup.r) * alpha;
|
||||||
|
g2 = setup.g + (g2 - setup.g) * alpha;
|
||||||
|
b2 = setup.b + (b2 - setup.b) * alpha;
|
||||||
|
} else {
|
||||||
|
r = light.r + (r - light.r) * alpha;
|
||||||
|
g = light.g + (g - light.g) * alpha;
|
||||||
|
b = light.b + (b - light.b) * alpha;
|
||||||
|
r2 = dark.r + (r2 - dark.r) * alpha;
|
||||||
|
g2 = dark.g + (g2 - dark.g) * alpha;
|
||||||
|
b2 = dark.b + (b2 - dark.b) * alpha;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
light.r += (r - light.r) * alpha;
|
|
||||||
light.g += (g - light.g) * alpha;
|
|
||||||
light.b += (b - light.b) * alpha;
|
|
||||||
dark.r += (r2 - dark.r) * alpha;
|
|
||||||
dark.g += (g2 - dark.g) * alpha;
|
|
||||||
dark.b += (b2 - dark.b) * alpha;
|
|
||||||
}
|
}
|
||||||
|
light.r = r < 0 ? 0 : (r > 1 ? 1 : r);
|
||||||
|
light.g = g < 0 ? 0 : (g > 1 ? 1 : g);
|
||||||
|
light.b = b < 0 ? 0 : (b > 1 ? 1 : b);
|
||||||
|
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
|
||||||
|
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
|
||||||
|
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1619,10 +1643,10 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
static DELAY = 2;
|
static DELAY = 2;
|
||||||
|
|
||||||
readonly slotIndex: number;
|
readonly slotIndex: number;
|
||||||
readonly attachment: HasTextureRegion;
|
readonly attachment: HasSequence;
|
||||||
|
|
||||||
constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) {
|
constructor (frameCount: number, slotIndex: number, attachment: HasSequence) {
|
||||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
super(frameCount, `${Property.sequence}|${slotIndex}|${attachment.sequence!.id}`);
|
super(frameCount, `${Property.sequence}|${slotIndex}|${attachment.sequence!.id}`);
|
||||||
this.slotIndex = slotIndex;
|
this.slotIndex = slotIndex;
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
@ -1636,6 +1660,9 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
return this.slotIndex;
|
return this.slotIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The attachment for which the {@link SlotPose#getSequenceIndex()} will be set.
|
||||||
|
* <p>
|
||||||
|
* See {@link VertexAttachment.timelineAttachment}. */
|
||||||
getAttachment () {
|
getAttachment () {
|
||||||
return this.attachment as unknown as Attachment;
|
return this.attachment as unknown as Attachment;
|
||||||
}
|
}
|
||||||
@ -1658,15 +1685,10 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
if (!slot.bone.active) return;
|
if (!slot.bone.active) return;
|
||||||
const pose = appliedPose ? slot.applied : slot.pose;
|
const pose = appliedPose ? slot.applied : slot.pose;
|
||||||
|
|
||||||
const slotAttachment = pose.attachment;
|
const slotAttachment = pose.attachment as Attachment;
|
||||||
const attachment = this.attachment as unknown as Attachment;
|
const attachment = this.attachment as unknown as Attachment;
|
||||||
if (slotAttachment !== attachment) {
|
|
||||||
if (!(slotAttachment instanceof VertexAttachment)
|
|
||||||
|| slotAttachment.timelineAttachment !== attachment) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sequence = (slotAttachment as unknown as HasTextureRegion).sequence;
|
if (!(isHasSequence(slotAttachment)) || slotAttachment.timelineAttachment !== attachment) return;
|
||||||
if (!sequence) return;
|
|
||||||
|
|
||||||
if (direction === MixDirection.out) {
|
if (direction === MixDirection.out) {
|
||||||
if (blend === MixBlend.setup) pose.sequenceIndex = -1;
|
if (blend === MixBlend.setup) pose.sequenceIndex = -1;
|
||||||
@ -1684,7 +1706,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
const modeAndIndex = frames[i + SequenceTimeline.MODE];
|
const modeAndIndex = frames[i + SequenceTimeline.MODE];
|
||||||
const delay = frames[i + SequenceTimeline.DELAY];
|
const delay = frames[i + SequenceTimeline.DELAY];
|
||||||
|
|
||||||
let index = modeAndIndex >> 4, count = sequence.regions.length;
|
let index = modeAndIndex >> 4, count = slotAttachment.sequence.regions.length;
|
||||||
const mode = SequenceModeValues[modeAndIndex & 0xf];
|
const mode = SequenceModeValues[modeAndIndex & 0xf];
|
||||||
if (mode !== SequenceMode.hold) {
|
if (mode !== SequenceMode.hold) {
|
||||||
index += (((time - before) / delay + 0.00001) | 0);
|
index += (((time - before) / delay + 0.00001) | 0);
|
||||||
@ -1904,30 +1926,20 @@ export class IkConstraintTimeline extends CurveTimeline implements ConstraintTim
|
|||||||
softness = this.getBezierValue(time, i, 2/*SOFTNESS*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
softness = this.getBezierValue(time, i, 2/*SOFTNESS*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (blend) {
|
if (blend === MixBlend.setup) {
|
||||||
case MixBlend.setup: {
|
const setup = constraint.data.setup;
|
||||||
const setup = constraint.data.setup;
|
pose.mix = setup.mix + (mix - setup.mix) * alpha;
|
||||||
pose.mix = setup.mix + (mix - setup.mix) * alpha;
|
pose.softness = setup.softness + (softness - setup.softness) * alpha;
|
||||||
pose.softness = setup.softness + (softness - setup.softness) * alpha;
|
if (direction === MixDirection.out) {
|
||||||
if (direction === MixDirection.out) {
|
pose.bendDirection = setup.bendDirection;
|
||||||
pose.bendDirection = setup.bendDirection;
|
pose.compress = setup.compress;
|
||||||
pose.compress = setup.compress;
|
pose.stretch = setup.stretch;
|
||||||
pose.stretch = setup.stretch;
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case MixBlend.first:
|
} else {
|
||||||
case MixBlend.replace:
|
pose.mix += (mix - pose.mix) * alpha;
|
||||||
pose.mix += (mix - pose.mix) * alpha;
|
pose.softness += (softness - pose.softness) * alpha;
|
||||||
pose.softness += (softness - pose.softness) * alpha;
|
if (direction === MixDirection.out) return;
|
||||||
if (direction === MixDirection.out) return;
|
|
||||||
break;
|
|
||||||
case MixBlend.add:
|
|
||||||
pose.mix += mix * alpha;
|
|
||||||
pose.softness += softness * alpha;
|
|
||||||
if (direction === MixDirection.out) return;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
pose.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
|
pose.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
|
||||||
pose.compress = frames[i + 4/*COMPRESS*/] !== 0;
|
pose.compress = frames[i + 4/*COMPRESS*/] !== 0;
|
||||||
@ -2105,7 +2117,8 @@ export class PathConstraintSpacingTimeline extends ConstraintTimeline1 {
|
|||||||
const constraint = skeleton.constraints[this.constraintIndex];
|
const constraint = skeleton.constraints[this.constraintIndex];
|
||||||
if (constraint.active) {
|
if (constraint.active) {
|
||||||
const pose = appliedPose ? constraint.applied : constraint.pose;
|
const pose = appliedPose ? constraint.applied : constraint.pose;
|
||||||
pose.spacing = this.getAbsoluteValue(time, alpha, blend, pose.spacing, constraint.data.setup.spacing);
|
pose.spacing = this.getAbsoluteValue(time, alpha, blend === MixBlend.add ? MixBlend.replace : blend, pose.spacing,
|
||||||
|
constraint.data.setup.spacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2186,31 +2199,23 @@ export class PathConstraintMixTimeline extends CurveTimeline implements Constrai
|
|||||||
y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (blend) {
|
if (blend === MixBlend.setup) {
|
||||||
case MixBlend.setup: {
|
const setup = constraint.data.setup;
|
||||||
const setup = constraint.data.setup;
|
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
||||||
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
||||||
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
||||||
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
} else {
|
||||||
break;
|
pose.mixRotate += (rotate - pose.mixRotate) * alpha;
|
||||||
}
|
pose.mixX += (x - pose.mixX) * alpha;
|
||||||
case MixBlend.first:
|
pose.mixY += (y - pose.mixY) * alpha;
|
||||||
case MixBlend.replace:
|
|
||||||
pose.mixRotate += (rotate - pose.mixRotate) * alpha;
|
|
||||||
pose.mixX += (x - pose.mixX) * alpha;
|
|
||||||
pose.mixY += (y - pose.mixY) * alpha;
|
|
||||||
break;
|
|
||||||
case MixBlend.add:
|
|
||||||
pose.mixRotate += rotate * alpha;
|
|
||||||
pose.mixX += x * alpha;
|
|
||||||
pose.mixY += y * alpha;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The base class for most {@link PhysicsConstraint} timelines. */
|
/** The base class for most {@link PhysicsConstraint} timelines. */
|
||||||
export abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 {
|
export abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 {
|
||||||
|
additive = false;
|
||||||
|
|
||||||
/** @param constraintIndex -1 for all physics constraints in the skeleton. */
|
/** @param constraintIndex -1 for all physics constraints in the skeleton. */
|
||||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number, property: number) {
|
constructor (frameCount: number, bezierCount: number, constraintIndex: number, property: number) {
|
||||||
super(frameCount, bezierCount, constraintIndex, property);
|
super(frameCount, bezierCount, constraintIndex, property);
|
||||||
@ -2219,6 +2224,7 @@ export abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 {
|
|||||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend,
|
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend,
|
||||||
direction: MixDirection, appliedPose: boolean) {
|
direction: MixDirection, appliedPose: boolean) {
|
||||||
|
|
||||||
|
if (blend === MixBlend.add && !this.additive) blend = MixBlend.replace;
|
||||||
if (this.constraintIndex === -1) {
|
if (this.constraintIndex === -1) {
|
||||||
const value = time >= this.frames[0] ? this.getCurveValue(time) : 0;
|
const value = time >= this.frames[0] ? this.getCurveValue(time) : 0;
|
||||||
const constraints = skeleton.physics;
|
const constraints = skeleton.physics;
|
||||||
@ -2323,6 +2329,7 @@ export class PhysicsConstraintMassTimeline extends PhysicsConstraintTimeline {
|
|||||||
export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
||||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
||||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintWind);
|
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintWind);
|
||||||
|
this.additive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get (pose: PhysicsConstraintPose): number {
|
get (pose: PhysicsConstraintPose): number {
|
||||||
@ -2342,6 +2349,7 @@ export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
|||||||
export class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline {
|
export class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline {
|
||||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
||||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintGravity);
|
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintGravity);
|
||||||
|
this.additive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get (pose: PhysicsConstraintPose): number {
|
get (pose: PhysicsConstraintPose): number {
|
||||||
|
|||||||
@ -198,7 +198,7 @@ export class AnimationState {
|
|||||||
Utils.webkit602BugfixHelper(alpha, blend);
|
Utils.webkit602BugfixHelper(alpha, blend);
|
||||||
const timeline = timelines[ii];
|
const timeline = timelines[ii];
|
||||||
if (timeline instanceof AttachmentTimeline)
|
if (timeline instanceof AttachmentTimeline)
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, false, attachments);
|
||||||
else
|
else
|
||||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false);
|
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false);
|
||||||
}
|
}
|
||||||
@ -215,7 +215,7 @@ export class AnimationState {
|
|||||||
if (!shortestRotation && timeline instanceof RotateTimeline) {
|
if (!shortestRotation && timeline instanceof RotateTimeline) {
|
||||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
|
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
|
||||||
} else if (timeline instanceof AttachmentTimeline) {
|
} else if (timeline instanceof AttachmentTimeline) {
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, false, attachments);
|
||||||
} else {
|
} else {
|
||||||
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
||||||
Utils.webkit602BugfixHelper(alpha, blend);
|
Utils.webkit602BugfixHelper(alpha, blend);
|
||||||
@ -286,7 +286,6 @@ export class AnimationState {
|
|||||||
from.totalAlpha = 0;
|
from.totalAlpha = 0;
|
||||||
for (let i = 0; i < timelineCount; i++) {
|
for (let i = 0; i < timelineCount; i++) {
|
||||||
const timeline = timelines[i];
|
const timeline = timelines[i];
|
||||||
let direction = MixDirection.out;
|
|
||||||
let timelineBlend: MixBlend;
|
let timelineBlend: MixBlend;
|
||||||
let alpha = 0;
|
let alpha = 0;
|
||||||
switch (timelineMode[i]) {
|
switch (timelineMode[i]) {
|
||||||
@ -319,8 +318,9 @@ export class AnimationState {
|
|||||||
if (!shortestRotation && timeline instanceof RotateTimeline)
|
if (!shortestRotation && timeline instanceof RotateTimeline)
|
||||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
||||||
else if (timeline instanceof AttachmentTimeline)
|
else if (timeline instanceof AttachmentTimeline)
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments && alpha >= from.alphaAttachmentThreshold);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, true, attachments && alpha >= from.alphaAttachmentThreshold);
|
||||||
else {
|
else {
|
||||||
|
let direction = MixDirection.out;
|
||||||
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
||||||
Utils.webkit602BugfixHelper(alpha, blend);
|
Utils.webkit602BugfixHelper(alpha, blend);
|
||||||
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend === MixBlend.setup)
|
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend === MixBlend.setup)
|
||||||
@ -338,11 +338,13 @@ export class AnimationState {
|
|||||||
return mix;
|
return mix;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAttachmentTimeline (timeline: AttachmentTimeline, skeleton: Skeleton, time: number, blend: MixBlend, attachments: boolean) {
|
applyAttachmentTimeline (timeline: AttachmentTimeline, skeleton: Skeleton, time: number, blend: MixBlend, out: boolean, attachments: boolean) {
|
||||||
const slot = skeleton.slots[timeline.slotIndex];
|
const slot = skeleton.slots[timeline.slotIndex];
|
||||||
if (!slot.bone.active) return;
|
if (!slot.bone.active) return;
|
||||||
|
|
||||||
if (time < timeline.frames[0]) { // Time is before first frame.
|
if (out) {
|
||||||
|
if (blend === MixBlend.setup) this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
|
||||||
|
} else if (time < timeline.frames[0]) { // Time is before first frame.
|
||||||
if (blend === MixBlend.setup || blend === MixBlend.first)
|
if (blend === MixBlend.setup || blend === MixBlend.first)
|
||||||
this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
|
this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
|
||||||
} else
|
} else
|
||||||
@ -965,7 +967,8 @@ export class TrackEntry {
|
|||||||
* to 1, which overwrites the skeleton's current pose with this animation.
|
* to 1, which overwrites the skeleton's current pose with this animation.
|
||||||
*
|
*
|
||||||
* Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
|
* Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
|
||||||
* use alpha on track 0 if the skeleton pose is from the last frame render. */
|
* use alpha on track 0 if the skeleton pose is from the last frame render.
|
||||||
|
* @see alphaAttachmentThreshold */
|
||||||
alpha: number = 0;
|
alpha: number = 0;
|
||||||
|
|
||||||
/** Seconds from 0 to the {@link #getMixDuration()} when mixing from the previous animation to this animation. May be
|
/** Seconds from 0 to the {@link #getMixDuration()} when mixing from the previous animation to this animation. May be
|
||||||
|
|||||||
@ -51,40 +51,27 @@ export class AtlasAttachmentLoader implements AttachmentLoader {
|
|||||||
this.allowMissingRegions = allowMissingRegions;
|
this.allowMissingRegions = allowMissingRegions;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSequence (name: string, basePath: string, sequence: Sequence) {
|
protected findRegions (name: string, basePath: string, sequence: Sequence) {
|
||||||
const regions = sequence.regions;
|
const regions = sequence.regions;
|
||||||
for (let i = 0, n = regions.length; i < n; i++) {
|
for (let i = 0, n = regions.length; i < n; i++)
|
||||||
const path = sequence.getPath(basePath, i);
|
regions[i] = this.findRegion(name, sequence.getPath(basePath, i));
|
||||||
regions[i] = this.atlas.findRegion(path);
|
}
|
||||||
if (regions[i] == null && !this.allowMissingRegions)
|
|
||||||
throw new Error(`Region not found in atlas: ${path} (sequence: ${name})`);
|
protected findRegion (name: string, path: string) {
|
||||||
}
|
const region = this.atlas.findRegion(path);
|
||||||
|
if (!region && !this.allowMissingRegions)
|
||||||
|
throw new Error(`Region not found in atlas: ${path} (attachment: ${name})`);
|
||||||
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment {
|
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment {
|
||||||
const attachment = new RegionAttachment(name, path);
|
this.findRegions(name, path, sequence);
|
||||||
if (sequence != null) {
|
return new RegionAttachment(name, sequence);
|
||||||
this.loadSequence(name, path, sequence);
|
|
||||||
} else {
|
|
||||||
const region = this.atlas.findRegion(path);
|
|
||||||
if (region == null && !this.allowMissingRegions)
|
|
||||||
throw new Error(`Region not found in atlas: ${path} (region attachment: ${name})`);
|
|
||||||
attachment.region = region;
|
|
||||||
}
|
|
||||||
return attachment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
|
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
|
||||||
const attachment = new MeshAttachment(name, path);
|
this.findRegions(name, path, sequence);
|
||||||
if (sequence != null) {
|
return new MeshAttachment(name, sequence);
|
||||||
this.loadSequence(name, path, sequence);
|
|
||||||
} else {
|
|
||||||
const region = this.atlas.findRegion(path);
|
|
||||||
if (region == null && !this.allowMissingRegions)
|
|
||||||
throw new Error(`Region not found in atlas: ${path} (mesh attachment: ${name})`);
|
|
||||||
attachment.region = region;
|
|
||||||
}
|
|
||||||
return attachment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment {
|
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment {
|
||||||
|
|||||||
@ -210,7 +210,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
|
|||||||
cwx = a * child.x + b * child.y + parent.worldX;
|
cwx = a * child.x + b * child.y + parent.worldX;
|
||||||
cwy = c * child.x + d * child.y + parent.worldY;
|
cwy = c * child.x + d * child.y + parent.worldY;
|
||||||
}
|
}
|
||||||
// biome-ignore lint/style/noNonNullAssertion: reference-runtime
|
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||||
const pp = parent.bone.parent!.applied;
|
const pp = parent.bone.parent!.applied;
|
||||||
a = pp.a;
|
a = pp.a;
|
||||||
b = pp.b;
|
b = pp.b;
|
||||||
|
|||||||
@ -462,7 +462,7 @@ export class Skeleton {
|
|||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
verticesLength = 8;
|
verticesLength = 8;
|
||||||
vertices = Utils.setArraySize(temp, verticesLength, 0);
|
vertices = Utils.setArraySize(temp, verticesLength, 0);
|
||||||
attachment.computeWorldVertices(slot, vertices, 0, 2);
|
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
triangles = Skeleton.quadTriangles;
|
triangles = Skeleton.quadTriangles;
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
verticesLength = attachment.worldVerticesLength;
|
verticesLength = attachment.worldVerticesLength;
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
import { AlphaTimeline, Animation, AttachmentTimeline, type BoneTimeline2, type CurveTimeline, CurveTimeline1, DeformTimeline, DrawOrderTimeline, EventTimeline, IkConstraintTimeline, InheritTimeline, PathConstraintMixTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PhysicsConstraintDampingTimeline, PhysicsConstraintGravityTimeline, PhysicsConstraintInertiaTimeline, PhysicsConstraintMassTimeline, PhysicsConstraintMixTimeline, PhysicsConstraintResetTimeline, PhysicsConstraintStrengthTimeline, PhysicsConstraintWindTimeline, RGB2Timeline, RGBA2Timeline, RGBATimeline, RGBTimeline, RotateTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, SequenceTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, SliderMixTimeline, SliderTimeline, type Timeline, TransformConstraintTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline } from "./Animation.js";
|
import { AlphaTimeline, Animation, AttachmentTimeline, type BoneTimeline2, type CurveTimeline, CurveTimeline1, DeformTimeline, DrawOrderTimeline, EventTimeline, IkConstraintTimeline, InheritTimeline, PathConstraintMixTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PhysicsConstraintDampingTimeline, PhysicsConstraintGravityTimeline, PhysicsConstraintInertiaTimeline, PhysicsConstraintMassTimeline, PhysicsConstraintMixTimeline, PhysicsConstraintResetTimeline, PhysicsConstraintStrengthTimeline, PhysicsConstraintWindTimeline, RGB2Timeline, RGBA2Timeline, RGBATimeline, RGBTimeline, RotateTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, SequenceTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, SliderMixTimeline, SliderTimeline, type Timeline, TransformConstraintTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline } from "./Animation.js";
|
||||||
import type { Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
import type { Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
||||||
import type { AttachmentLoader } from "./attachments/AttachmentLoader.js";
|
import type { AttachmentLoader } from "./attachments/AttachmentLoader.js";
|
||||||
import type { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
import type { HasSequence } from "./attachments/HasSequence.js";
|
||||||
import type { MeshAttachment } from "./attachments/MeshAttachment.js";
|
import type { MeshAttachment } from "./attachments/MeshAttachment.js";
|
||||||
import { Sequence, SequenceModeValues } from "./attachments/Sequence.js";
|
import { Sequence, SequenceModeValues } from "./attachments/Sequence.js";
|
||||||
import { BoneData } from "./BoneData.js";
|
import { BoneData } from "./BoneData.js";
|
||||||
@ -261,9 +261,9 @@ export class SkeletonBinary {
|
|||||||
data.slot = skeletonData.slots[input.readInt(true)];
|
data.slot = skeletonData.slots[input.readInt(true)];
|
||||||
const flags = input.readByte();
|
const flags = input.readByte();
|
||||||
data.skinRequired = (flags & 1) !== 0;
|
data.skinRequired = (flags & 1) !== 0;
|
||||||
data.positionMode = (flags >> 1) & 2;
|
data.positionMode = (flags >> 1) & 0b1;
|
||||||
data.spacingMode = (flags >> 2) & 3;
|
data.spacingMode = (flags >> 2) & 0b11;
|
||||||
data.rotateMode = (flags >> 4) & 3;
|
data.rotateMode = (flags >> 4) & 0b11;
|
||||||
if ((flags & 128) !== 0) data.offsetRotation = input.readFloat();
|
if ((flags & 128) !== 0) data.offsetRotation = input.readFloat();
|
||||||
const setup = data.setup;
|
const setup = data.setup;
|
||||||
setup.position = input.readFloat();
|
setup.position = input.readFloat();
|
||||||
@ -375,7 +375,7 @@ export class SkeletonBinary {
|
|||||||
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
||||||
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
|
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
|
||||||
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
|
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
|
||||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
linkedMesh.mesh.updateSequence();
|
||||||
}
|
}
|
||||||
this.linkedMeshes.length = 0;
|
this.linkedMeshes.length = 0;
|
||||||
|
|
||||||
@ -465,7 +465,7 @@ export class SkeletonBinary {
|
|||||||
case AttachmentType.Region: {
|
case AttachmentType.Region: {
|
||||||
let path = (flags & 16) !== 0 ? input.readStringRef() : null;
|
let path = (flags & 16) !== 0 ? input.readStringRef() : null;
|
||||||
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
||||||
const sequence = (flags & 64) !== 0 ? this.readSequence(input) : null;
|
const sequence = this.readSequence(input, (flags & 64) !== 0);
|
||||||
const rotation = (flags & 128) !== 0 ? input.readFloat() : 0;
|
const rotation = (flags & 128) !== 0 ? input.readFloat() : 0;
|
||||||
const x = input.readFloat();
|
const x = input.readFloat();
|
||||||
const y = input.readFloat();
|
const y = input.readFloat();
|
||||||
@ -486,8 +486,7 @@ export class SkeletonBinary {
|
|||||||
region.width = width * scale;
|
region.width = width * scale;
|
||||||
region.height = height * scale;
|
region.height = height * scale;
|
||||||
Color.rgba8888ToColor(region.color, color);
|
Color.rgba8888ToColor(region.color, color);
|
||||||
region.sequence = sequence;
|
region.updateSequence();
|
||||||
if (region.region != null) region.updateRegion();
|
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
case AttachmentType.BoundingBox: {
|
case AttachmentType.BoundingBox: {
|
||||||
@ -505,7 +504,7 @@ export class SkeletonBinary {
|
|||||||
case AttachmentType.Mesh: {
|
case AttachmentType.Mesh: {
|
||||||
let path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
let path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
||||||
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
||||||
const sequence = (flags & 64) !== 0 ? this.readSequence(input) : null;
|
const sequence = this.readSequence(input, (flags & 64) !== 0);
|
||||||
const hullLength = input.readInt(true);
|
const hullLength = input.readInt(true);
|
||||||
const vertices = this.readVertices(input, (flags & 128) !== 0);
|
const vertices = this.readVertices(input, (flags & 128) !== 0);
|
||||||
const uvs = this.readFloatArray(input, vertices.length, 1);
|
const uvs = this.readFloatArray(input, vertices.length, 1);
|
||||||
@ -523,26 +522,25 @@ export class SkeletonBinary {
|
|||||||
if (!mesh) return null;
|
if (!mesh) return null;
|
||||||
mesh.path = path;
|
mesh.path = path;
|
||||||
Color.rgba8888ToColor(mesh.color, color);
|
Color.rgba8888ToColor(mesh.color, color);
|
||||||
|
mesh.hullLength = hullLength << 1;
|
||||||
mesh.bones = vertices.bones;
|
mesh.bones = vertices.bones;
|
||||||
mesh.vertices = vertices.vertices;
|
mesh.vertices = vertices.vertices;
|
||||||
mesh.worldVerticesLength = vertices.length;
|
mesh.worldVerticesLength = vertices.length;
|
||||||
mesh.triangles = triangles;
|
|
||||||
mesh.regionUVs = uvs;
|
mesh.regionUVs = uvs;
|
||||||
if (mesh.region != null) mesh.updateRegion();
|
mesh.triangles = triangles;
|
||||||
mesh.hullLength = hullLength << 1;
|
|
||||||
mesh.sequence = sequence;
|
|
||||||
if (nonessential) {
|
if (nonessential) {
|
||||||
mesh.edges = edges;
|
mesh.edges = edges;
|
||||||
mesh.width = width * scale;
|
mesh.width = width * scale;
|
||||||
mesh.height = height * scale;
|
mesh.height = height * scale;
|
||||||
}
|
}
|
||||||
|
mesh.updateSequence();
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
case AttachmentType.LinkedMesh: {
|
case AttachmentType.LinkedMesh: {
|
||||||
const path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
const path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
||||||
if (path == null) throw new Error("Path of linked mesh must not be null");
|
if (path == null) throw new Error("Path of linked mesh must not be null");
|
||||||
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
const color = (flags & 32) !== 0 ? input.readInt32() : 0xffffffff;
|
||||||
const sequence = (flags & 64) !== 0 ? this.readSequence(input) : null;
|
const sequence = this.readSequence(input, (flags & 64) !== 0);
|
||||||
const inheritTimelines = (flags & 128) !== 0;
|
const inheritTimelines = (flags & 128) !== 0;
|
||||||
const skinIndex = input.readInt(true);
|
const skinIndex = input.readInt(true);
|
||||||
const parent = input.readStringRef();
|
const parent = input.readStringRef();
|
||||||
@ -556,7 +554,6 @@ export class SkeletonBinary {
|
|||||||
if (!mesh) return null;
|
if (!mesh) return null;
|
||||||
mesh.path = path;
|
mesh.path = path;
|
||||||
Color.rgba8888ToColor(mesh.color, color);
|
Color.rgba8888ToColor(mesh.color, color);
|
||||||
mesh.sequence = sequence;
|
|
||||||
if (nonessential) {
|
if (nonessential) {
|
||||||
mesh.width = width * scale;
|
mesh.width = width * scale;
|
||||||
mesh.height = height * scale;
|
mesh.height = height * scale;
|
||||||
@ -616,8 +613,9 @@ export class SkeletonBinary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readSequence (input: BinaryInput) {
|
private readSequence (input: BinaryInput, hasPathSuffix: boolean) {
|
||||||
const sequence = new Sequence(input.readInt(true));
|
if (!hasPathSuffix) return new Sequence(1, false);
|
||||||
|
const sequence = new Sequence(input.readInt(true), true);
|
||||||
sequence.start = input.readInt(true);
|
sequence.start = input.readInt(true);
|
||||||
sequence.digits = input.readInt(true);
|
sequence.digits = input.readInt(true);
|
||||||
sequence.setupIndex = input.readInt(true);
|
sequence.setupIndex = input.readInt(true);
|
||||||
@ -632,19 +630,20 @@ export class SkeletonBinary {
|
|||||||
if (!weighted)
|
if (!weighted)
|
||||||
return new Vertices(null, this.readFloatArray(input, length, scale), length);
|
return new Vertices(null, this.readFloatArray(input, length, scale), length);
|
||||||
|
|
||||||
|
const n = input.readInt(true);
|
||||||
|
const bones: number[] = [];
|
||||||
const weights: number[] = [];
|
const weights: number[] = [];
|
||||||
const bonesArray: number[] = [];
|
for (let b = 0, w = 0; b < n;) {
|
||||||
for (let i = 0; i < vertexCount; i++) {
|
|
||||||
const boneCount = input.readInt(true);
|
const boneCount = input.readInt(true);
|
||||||
bonesArray.push(boneCount);
|
bones[b++] = boneCount;
|
||||||
for (let ii = 0; ii < boneCount; ii++) {
|
for (let ii = 0; ii < boneCount; ii++, w += 3) {
|
||||||
bonesArray.push(input.readInt(true));
|
bones[b++] = input.readInt(true);
|
||||||
weights.push(input.readFloat() * scale);
|
weights[w] = input.readFloat() * scale;
|
||||||
weights.push(input.readFloat() * scale);
|
weights[w + 1] = input.readFloat() * scale;
|
||||||
weights.push(input.readFloat());
|
weights[w + 2] = input.readFloat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Vertices(bonesArray, Utils.toFloatArray(weights), length);
|
return new Vertices(bones, Utils.toFloatArray(weights), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readFloatArray (input: BinaryInput, n: number, scale: number): number[] {
|
private readFloatArray (input: BinaryInput, n: number, scale: number): number[] {
|
||||||
@ -1115,7 +1114,7 @@ export class SkeletonBinary {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ATTACHMENT_SEQUENCE: {
|
case ATTACHMENT_SEQUENCE: {
|
||||||
const timeline = new SequenceTimeline(frameCount, slotIndex, attachment as unknown as HasTextureRegion);
|
const timeline = new SequenceTimeline(frameCount, slotIndex, attachment as unknown as HasSequence);
|
||||||
for (let frame = 0; frame < frameCount; frame++) {
|
for (let frame = 0; frame < frameCount; frame++) {
|
||||||
const time = input.readFloat();
|
const time = input.readFloat();
|
||||||
const modeAndIndex = input.readInt32();
|
const modeAndIndex = input.readInt32();
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
import { AlphaTimeline, Animation, AttachmentTimeline, type BoneTimeline2, type CurveTimeline, type CurveTimeline1, DeformTimeline, DrawOrderTimeline, EventTimeline, IkConstraintTimeline, InheritTimeline, PathConstraintMixTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PhysicsConstraintDampingTimeline, PhysicsConstraintGravityTimeline, PhysicsConstraintInertiaTimeline, PhysicsConstraintMassTimeline, PhysicsConstraintMixTimeline, PhysicsConstraintResetTimeline, PhysicsConstraintStrengthTimeline, PhysicsConstraintWindTimeline, RGB2Timeline, RGBA2Timeline, RGBATimeline, RGBTimeline, RotateTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, SequenceTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, SliderMixTimeline, SliderTimeline, type Timeline, TransformConstraintTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline } from "./Animation.js";
|
import { AlphaTimeline, Animation, AttachmentTimeline, type BoneTimeline2, type CurveTimeline, type CurveTimeline1, DeformTimeline, DrawOrderTimeline, EventTimeline, IkConstraintTimeline, InheritTimeline, PathConstraintMixTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PhysicsConstraintDampingTimeline, PhysicsConstraintGravityTimeline, PhysicsConstraintInertiaTimeline, PhysicsConstraintMassTimeline, PhysicsConstraintMixTimeline, PhysicsConstraintResetTimeline, PhysicsConstraintStrengthTimeline, PhysicsConstraintWindTimeline, RGB2Timeline, RGBA2Timeline, RGBATimeline, RGBTimeline, RotateTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, SequenceTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, SliderMixTimeline, SliderTimeline, type Timeline, TransformConstraintTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline } from "./Animation.js";
|
||||||
import type { Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
import type { Attachment, VertexAttachment } from "./attachments/Attachment.js";
|
||||||
import type { AttachmentLoader } from "./attachments/AttachmentLoader.js";
|
import type { AttachmentLoader } from "./attachments/AttachmentLoader.js";
|
||||||
import type { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
import type { HasSequence } from "./attachments/HasSequence.js";
|
||||||
import type { MeshAttachment } from "./attachments/MeshAttachment.js";
|
import type { MeshAttachment } from "./attachments/MeshAttachment.js";
|
||||||
import { Sequence, SequenceMode } from "./attachments/Sequence.js";
|
import { Sequence, SequenceMode } from "./attachments/Sequence.js";
|
||||||
import { BoneData, Inherit } from "./BoneData.js";
|
import { BoneData, Inherit } from "./BoneData.js";
|
||||||
@ -440,7 +440,7 @@ export class SkeletonJson {
|
|||||||
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
||||||
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
||||||
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
|
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
|
||||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
linkedMesh.mesh.updateSequence();
|
||||||
}
|
}
|
||||||
this.linkedMeshes.length = 0;
|
this.linkedMeshes.length = 0;
|
||||||
|
|
||||||
@ -528,12 +528,11 @@ export class SkeletonJson {
|
|||||||
region.rotation = getValue(map, "rotation", 0);
|
region.rotation = getValue(map, "rotation", 0);
|
||||||
region.width = map.width * scale;
|
region.width = map.width * scale;
|
||||||
region.height = map.height * scale;
|
region.height = map.height * scale;
|
||||||
region.sequence = sequence;
|
|
||||||
|
|
||||||
const color: string = getValue(map, "color", null);
|
const color: string = getValue(map, "color", null);
|
||||||
if (color) region.color.setFromString(color);
|
if (color) region.color.setFromString(color);
|
||||||
|
|
||||||
if (region.region != null) region.updateRegion();
|
region.updateSequence();
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
case "boundingbox": {
|
case "boundingbox": {
|
||||||
@ -557,7 +556,6 @@ export class SkeletonJson {
|
|||||||
|
|
||||||
mesh.width = getValue(map, "width", 0) * scale;
|
mesh.width = getValue(map, "width", 0) * scale;
|
||||||
mesh.height = getValue(map, "height", 0) * scale;
|
mesh.height = getValue(map, "height", 0) * scale;
|
||||||
mesh.sequence = sequence;
|
|
||||||
|
|
||||||
const parent: string = getValue(map, "parent", null);
|
const parent: string = getValue(map, "parent", null);
|
||||||
if (parent) {
|
if (parent) {
|
||||||
@ -569,10 +567,10 @@ export class SkeletonJson {
|
|||||||
this.readVertices(map, mesh, uvs.length);
|
this.readVertices(map, mesh, uvs.length);
|
||||||
mesh.triangles = map.triangles;
|
mesh.triangles = map.triangles;
|
||||||
mesh.regionUVs = uvs;
|
mesh.regionUVs = uvs;
|
||||||
if (mesh.region != null) mesh.updateRegion();
|
|
||||||
|
|
||||||
mesh.edges = getValue(map, "edges", null);
|
mesh.edges = getValue(map, "edges", null);
|
||||||
mesh.hullLength = getValue(map, "hull", 0) * 2;
|
mesh.hullLength = getValue(map, "hull", 0) * 2;
|
||||||
|
mesh.updateSequence();
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
case "path": {
|
case "path": {
|
||||||
@ -623,8 +621,8 @@ export class SkeletonJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readSequence (map: object) {
|
readSequence (map: object) {
|
||||||
if (map == null) return null;
|
if (map == null) return new Sequence(1, false);
|
||||||
const sequence = new Sequence(getValue(map, "count", 0));
|
const sequence = new Sequence(getValue(map, "count", 0), true);
|
||||||
sequence.start = getValue(map, "start", 1);
|
sequence.start = getValue(map, "start", 1);
|
||||||
sequence.digits = getValue(map, "digits", 0);
|
sequence.digits = getValue(map, "digits", 0);
|
||||||
sequence.setupIndex = getValue(map, "setup", 0);
|
sequence.setupIndex = getValue(map, "setup", 0);
|
||||||
@ -1155,7 +1153,7 @@ export class SkeletonJson {
|
|||||||
}
|
}
|
||||||
timelines.push(timeline);
|
timelines.push(timeline);
|
||||||
} else if (timelineMapName === "sequence") {
|
} else if (timelineMapName === "sequence") {
|
||||||
const timeline = new SequenceTimeline(timelineMap.length, slotIndex, attachment as unknown as HasTextureRegion);
|
const timeline = new SequenceTimeline(timelineMap.length, slotIndex, attachment as unknown as HasSequence);
|
||||||
let lastDelay = 0;
|
let lastDelay = 0;
|
||||||
for (let frame = 0; frame < timelineMap.length; frame++) {
|
for (let frame = 0; frame < timelineMap.length; frame++) {
|
||||||
const delay = getValue(keyMap, "delay", lastDelay);
|
const delay = getValue(keyMap, "delay", lastDelay);
|
||||||
|
|||||||
@ -55,8 +55,8 @@ export class SkeletonRendererCore {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotApplied = slot.applied;
|
const pose = slot.applied;
|
||||||
const slotColor = slotApplied.color;
|
const slotColor = pose.color;
|
||||||
const alpha = slotColor.a;
|
const alpha = slotColor.a;
|
||||||
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
||||||
clipper.clipEnd(slot);
|
clipper.clipEnd(slot);
|
||||||
@ -80,13 +80,16 @@ export class SkeletonRendererCore {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment.computeWorldVertices(slot, this.worldVertices, 0, stride);
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), this.worldVertices, 0, stride);
|
||||||
|
|
||||||
vertices = this.worldVertices;
|
vertices = this.worldVertices;
|
||||||
verticesCount = 4;
|
verticesCount = 4;
|
||||||
uvs = attachment.uvs as Float32Array;
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
indices = this.quadIndices;
|
indices = this.quadIndices;
|
||||||
indicesCount = 6;
|
indicesCount = 6;
|
||||||
texture = attachment.region?.texture;
|
texture = sequence.regions[sequenceIndex]?.texture;
|
||||||
|
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
attachmentColor = attachment.color;
|
attachmentColor = attachment.color;
|
||||||
@ -102,10 +105,14 @@ export class SkeletonRendererCore {
|
|||||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride);
|
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride);
|
||||||
vertices = this.worldVertices;
|
vertices = this.worldVertices;
|
||||||
verticesCount = attachment.worldVerticesLength >> 1;
|
verticesCount = attachment.worldVerticesLength >> 1;
|
||||||
uvs = attachment.uvs as Float32Array;
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
indices = attachment.triangles;
|
indices = attachment.triangles;
|
||||||
indicesCount = indices.length;
|
indicesCount = indices.length;
|
||||||
texture = attachment.region?.texture;
|
texture = sequence.regions[sequenceIndex]?.texture;
|
||||||
|
|
||||||
} else if (attachment instanceof ClippingAttachment) {
|
} else if (attachment instanceof ClippingAttachment) {
|
||||||
clipper.clipStart(skeleton, slot, attachment);
|
clipper.clipStart(skeleton, slot, attachment);
|
||||||
@ -133,8 +140,8 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
darkColor = 0xff000000;
|
darkColor = 0xff000000;
|
||||||
if (slotApplied.darkColor) {
|
if (pose.darkColor) {
|
||||||
const { r, g, b } = slotApplied.darkColor;
|
const { r, g, b } = pose.darkColor;
|
||||||
darkColor = 0xff000000 |
|
darkColor = 0xff000000 |
|
||||||
(Math.floor(r * a) << 16) |
|
(Math.floor(r * a) << 16) |
|
||||||
(Math.floor(g * a) << 8) |
|
(Math.floor(g * a) << 8) |
|
||||||
@ -156,8 +163,8 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
darkColor = 0;
|
darkColor = 0;
|
||||||
if (slotApplied.darkColor) {
|
if (pose.darkColor) {
|
||||||
const { r, g, b } = slotApplied.darkColor;
|
const { r, g, b } = pose.darkColor;
|
||||||
darkColor = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
|
darkColor = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,9 +35,14 @@ import { type NumberArrayLike, Utils } from "../Utils.js";
|
|||||||
export abstract class Attachment {
|
export abstract class Attachment {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/** Timelines for the timeline attachment are also applied to this attachment.
|
||||||
|
* @return May be null if no attachment-specific timelines should be applied. */
|
||||||
|
timelineAttachment?: Attachment;
|
||||||
|
|
||||||
constructor (name: string) {
|
constructor (name: string) {
|
||||||
if (!name) throw new Error("name cannot be null.");
|
if (!name) throw new Error("name cannot be null.");
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.timelineAttachment = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract copy (): Attachment;
|
abstract copy (): Attachment;
|
||||||
@ -65,10 +70,6 @@ export abstract class VertexAttachment extends Attachment {
|
|||||||
* {@link computeWorldVertices} using the `count` parameter. */
|
* {@link computeWorldVertices} using the `count` parameter. */
|
||||||
worldVerticesLength = 0;
|
worldVerticesLength = 0;
|
||||||
|
|
||||||
/** Timelines for the timeline attachment are also applied to this attachment.
|
|
||||||
* May be null if no attachment-specific timelines should be applied. */
|
|
||||||
timelineAttachment: Attachment = this;
|
|
||||||
|
|
||||||
constructor (name: string) {
|
constructor (name: string) {
|
||||||
super(name);
|
super(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,10 +42,10 @@ import type { Sequence } from "./Sequence.js";
|
|||||||
* Runtimes Guide. */
|
* Runtimes Guide. */
|
||||||
export interface AttachmentLoader {
|
export interface AttachmentLoader {
|
||||||
/** @return May be null to not load an attachment. */
|
/** @return May be null to not load an attachment. */
|
||||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): RegionAttachment;
|
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment;
|
||||||
|
|
||||||
/** @return May be null to not load an attachment. */
|
/** @return May be null to not load an attachment. */
|
||||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): MeshAttachment;
|
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment;
|
||||||
|
|
||||||
/** @return May be null to not load an attachment. */
|
/** @return May be null to not load an attachment. */
|
||||||
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
|
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
|
||||||
|
|||||||
@ -27,24 +27,20 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import type { TextureRegion } from "../Texture.js"
|
|
||||||
import type { Color } from "../Utils.js"
|
import type { Color } from "../Utils.js"
|
||||||
import type { Sequence } from "./Sequence.js"
|
import type { Sequence } from "./Sequence.js"
|
||||||
|
|
||||||
export interface HasTextureRegion {
|
export function isHasSequence (obj: unknown): obj is HasSequence {
|
||||||
/** The name used to find the {@link #region()}. */
|
return !!obj && typeof obj === "object" && "sequence" in obj && "updateSequence" in obj;
|
||||||
path: string;
|
}
|
||||||
|
|
||||||
/** The region used to draw the attachment. After setting the region or if the region's properties are changed,
|
export interface HasSequence {
|
||||||
* {@link #updateRegion()} must be called. */
|
path?: string;
|
||||||
region: TextureRegion | null;
|
|
||||||
|
|
||||||
/** 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;
|
color: Color;
|
||||||
|
|
||||||
sequence: Sequence | null;
|
/** Calls {@link Sequence#update(HasSequence)} on this attachment's sequence. */
|
||||||
|
updateSequence (): void;
|
||||||
|
|
||||||
|
sequence: Sequence;
|
||||||
}
|
}
|
||||||
@ -27,74 +27,123 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import type { Skeleton } from "src/Skeleton.js";
|
|
||||||
import type { Slot } from "../Slot.js";
|
|
||||||
import type { TextureRegion } from "../Texture.js";
|
import type { TextureRegion } from "../Texture.js";
|
||||||
import { TextureAtlasRegion } from "../TextureAtlas.js";
|
import { TextureAtlasRegion } from "../TextureAtlas.js";
|
||||||
import { Color, type NumberArrayLike, Utils } from "../Utils.js";
|
import { Color, type NumberArrayLike, Utils } from "../Utils.js";
|
||||||
import { type Attachment, VertexAttachment } from "./Attachment.js";
|
import { type Attachment, VertexAttachment } from "./Attachment.js";
|
||||||
import type { HasTextureRegion } from "./HasTextureRegion.js";
|
import type { HasSequence } from "./HasSequence.js";
|
||||||
import type { Sequence } from "./Sequence.js";
|
import type { Sequence } from "./Sequence.js";
|
||||||
|
|
||||||
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
|
/** 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.
|
* 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. */
|
* See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
|
||||||
export class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
export class MeshAttachment extends VertexAttachment implements HasSequence {
|
||||||
region: TextureRegion | null = null;
|
readonly sequence: Sequence;
|
||||||
|
|
||||||
/** The name of the texture region for this attachment. */
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
/** The UV pair for each vertex, normalized within the texture region. */
|
/** The UV pair for each vertex, normalized within the texture region. */
|
||||||
regionUVs: NumberArrayLike = [];
|
regionUVs: NumberArrayLike = [];
|
||||||
|
|
||||||
/** The UV pair for each vertex, normalized within the entire texture.
|
|
||||||
*
|
|
||||||
* See {@link #updateUVs}. */
|
|
||||||
uvs: NumberArrayLike = [];
|
|
||||||
|
|
||||||
/** Triplets of vertex indices which describe the mesh's triangulation. */
|
/** Triplets of vertex indices which describe the mesh's triangulation. */
|
||||||
triangles: Array<number> = [];
|
triangles: Array<number> = [];
|
||||||
|
|
||||||
|
/** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */
|
||||||
|
hullLength: number = 0;
|
||||||
|
|
||||||
|
/** The name of the texture region for this attachment. */
|
||||||
|
path?: string;
|
||||||
|
|
||||||
/** The color to tint the mesh. */
|
/** The color to tint the mesh. */
|
||||||
color = new Color(1, 1, 1, 1);
|
color = new Color(1, 1, 1, 1);
|
||||||
|
|
||||||
|
private parentMesh: MeshAttachment | null = null;
|
||||||
|
|
||||||
|
/** Vertex index pairs describing edges for controlling triangulation, or be null if nonessential data was not exported. Mesh
|
||||||
|
* triangles never cross edges. Triangulation is not performed at runtime. */
|
||||||
|
edges: Array<number> = [];
|
||||||
|
|
||||||
/** The width of the mesh's image. Available only when nonessential data was exported. */
|
/** The width of the mesh's image. Available only when nonessential data was exported. */
|
||||||
width: number = 0;
|
width: number = 0;
|
||||||
|
|
||||||
/** The height of the mesh's image. Available only when nonessential data was exported. */
|
/** The height of the mesh's image. Available only when nonessential data was exported. */
|
||||||
height: number = 0;
|
height: number = 0;
|
||||||
|
|
||||||
/** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */
|
|
||||||
hullLength: number = 0;
|
|
||||||
|
|
||||||
/** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if
|
|
||||||
* nonessential data was exported. Triangulation is not performed at runtime. */
|
|
||||||
edges: Array<number> = [];
|
|
||||||
|
|
||||||
private parentMesh: MeshAttachment | null = null;
|
|
||||||
|
|
||||||
sequence: Sequence | null = null;
|
|
||||||
|
|
||||||
tempColor = new Color(0, 0, 0, 0);
|
tempColor = new Color(0, 0, 0, 0);
|
||||||
|
|
||||||
constructor (name: string, path: string) {
|
constructor (name: string, sequence: Sequence) {
|
||||||
super(name);
|
super(name);
|
||||||
this.path = path;
|
this.sequence = sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or
|
copy (): Attachment {
|
||||||
* the {@link #regionUVs} are changed. */
|
if (this.parentMesh) return this.newLinkedMesh();
|
||||||
updateRegion () {
|
|
||||||
if (!this.region) throw new Error("Region not set.");
|
const copy = new MeshAttachment(this.name, this.sequence.copy());
|
||||||
const regionUVs = this.regionUVs;
|
copy.path = this.path;
|
||||||
if (!this.uvs || this.uvs.length !== regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
|
copy.color.setFromColor(this.color);
|
||||||
const uvs = this.uvs;
|
|
||||||
const n = this.uvs.length;
|
this.copyTo(copy);
|
||||||
let u = this.region.u, v = this.region.v, width = 0, height = 0;
|
copy.regionUVs = [];
|
||||||
if (this.region instanceof TextureAtlasRegion) {
|
Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
|
||||||
const region = this.region, page = region.page;
|
copy.triangles = [];
|
||||||
|
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
|
||||||
|
copy.hullLength = this.hullLength;
|
||||||
|
|
||||||
|
// Nonessential.
|
||||||
|
if (this.edges) {
|
||||||
|
copy.edges = [];
|
||||||
|
Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length);
|
||||||
|
}
|
||||||
|
copy.width = this.width;
|
||||||
|
copy.height = this.height;
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSequence () {
|
||||||
|
this.sequence.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices},
|
||||||
|
* {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the
|
||||||
|
* parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */
|
||||||
|
getParentMesh () {
|
||||||
|
return this.parentMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param parentMesh May be null. */
|
||||||
|
setParentMesh (parentMesh: MeshAttachment) {
|
||||||
|
this.parentMesh = parentMesh;
|
||||||
|
if (parentMesh) {
|
||||||
|
this.bones = parentMesh.bones;
|
||||||
|
this.vertices = parentMesh.vertices;
|
||||||
|
this.worldVerticesLength = parentMesh.worldVerticesLength;
|
||||||
|
this.regionUVs = parentMesh.regionUVs;
|
||||||
|
this.triangles = parentMesh.triangles;
|
||||||
|
this.hullLength = parentMesh.hullLength;
|
||||||
|
this.worldVerticesLength = parentMesh.worldVerticesLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
|
||||||
|
newLinkedMesh (): MeshAttachment {
|
||||||
|
const copy = new MeshAttachment(this.name, this.sequence.copy());
|
||||||
|
copy.timelineAttachment = this.timelineAttachment;
|
||||||
|
copy.path = this.path;
|
||||||
|
copy.color.setFromColor(this.color);
|
||||||
|
copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
|
||||||
|
copy.updateSequence();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Computes {@link Sequence#getUVs(int) UVs} for a mesh attachment.
|
||||||
|
* @param uvs Output array for the computed UVs, same length as regionUVs. */
|
||||||
|
static computeUVs (region: TextureRegion | null, regionUVs: NumberArrayLike, uvs: NumberArrayLike): void {
|
||||||
|
if (!region) throw new Error("Region not set.");
|
||||||
|
const n = uvs.length;
|
||||||
|
let u = region.u, v = region.v, width = 0, height = 0;
|
||||||
|
if (region instanceof TextureAtlasRegion) {
|
||||||
|
const page = region.page;
|
||||||
const textureWidth = page.width, textureHeight = page.height;
|
const textureWidth = page.width, textureHeight = page.height;
|
||||||
switch (region.degrees) {
|
switch (region.degrees) {
|
||||||
case 90:
|
case 90:
|
||||||
@ -133,12 +182,12 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
|||||||
width = region.originalWidth / textureWidth;
|
width = region.originalWidth / textureWidth;
|
||||||
height = region.originalHeight / textureHeight;
|
height = region.originalHeight / textureHeight;
|
||||||
}
|
}
|
||||||
} else if (!this.region) {
|
} else if (!region) {
|
||||||
u = v = 0;
|
u = v = 0;
|
||||||
width = height = 1;
|
width = height = 1;
|
||||||
} else {
|
} else {
|
||||||
width = this.region.u2 - u;
|
width = region.u2 - u;
|
||||||
height = this.region.v2 - v;
|
height = region.v2 - v;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < n; i += 2) {
|
for (let i = 0; i < n; i += 2) {
|
||||||
@ -146,70 +195,4 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
|||||||
uvs[i + 1] = v + regionUVs[i + 1] * height;
|
uvs[i + 1] = v + regionUVs[i + 1] * height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices},
|
|
||||||
* {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the
|
|
||||||
* parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */
|
|
||||||
getParentMesh () {
|
|
||||||
return this.parentMesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param parentMesh May be null. */
|
|
||||||
setParentMesh (parentMesh: MeshAttachment) {
|
|
||||||
this.parentMesh = parentMesh;
|
|
||||||
if (parentMesh) {
|
|
||||||
this.bones = parentMesh.bones;
|
|
||||||
this.vertices = parentMesh.vertices;
|
|
||||||
this.worldVerticesLength = parentMesh.worldVerticesLength;
|
|
||||||
this.regionUVs = parentMesh.regionUVs;
|
|
||||||
this.triangles = parentMesh.triangles;
|
|
||||||
this.hullLength = parentMesh.hullLength;
|
|
||||||
this.worldVerticesLength = parentMesh.worldVerticesLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copy (): Attachment {
|
|
||||||
if (this.parentMesh) return this.newLinkedMesh();
|
|
||||||
|
|
||||||
const copy = new MeshAttachment(this.name, this.path);
|
|
||||||
copy.region = this.region;
|
|
||||||
copy.color.setFromColor(this.color);
|
|
||||||
|
|
||||||
this.copyTo(copy);
|
|
||||||
copy.regionUVs = [];
|
|
||||||
Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
|
|
||||||
copy.uvs = this.uvs instanceof Float32Array ? Utils.newFloatArray(this.uvs.length) : [];
|
|
||||||
Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, this.uvs.length);
|
|
||||||
copy.triangles = [];
|
|
||||||
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
|
|
||||||
copy.hullLength = this.hullLength;
|
|
||||||
|
|
||||||
copy.sequence = this.sequence != null ? this.sequence.copy() : null;
|
|
||||||
|
|
||||||
// Nonessential.
|
|
||||||
if (this.edges) {
|
|
||||||
copy.edges = [];
|
|
||||||
Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length);
|
|
||||||
}
|
|
||||||
copy.width = this.width;
|
|
||||||
copy.height = this.height;
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeWorldVertices (skeleton: Skeleton, slot: Slot, start: number, count: number, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
|
||||||
if (this.sequence != null) this.sequence.apply(slot.applied, this);
|
|
||||||
super.computeWorldVertices(skeleton, 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 {
|
|
||||||
const copy = new MeshAttachment(this.name, this.path);
|
|
||||||
copy.region = this.region;
|
|
||||||
copy.color.setFromColor(this.color);
|
|
||||||
copy.timelineAttachment = this.timelineAttachment;
|
|
||||||
copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
|
|
||||||
if (copy.region != null) copy.updateRegion();
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,17 +27,20 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import type { SlotPose } from "src/SlotPose.js";
|
||||||
import type { Slot } from "../Slot.js";
|
import type { Slot } from "../Slot.js";
|
||||||
import type { TextureRegion } from "../Texture.js";
|
import type { TextureRegion } from "../Texture.js";
|
||||||
import { Color, MathUtils, type NumberArrayLike, Utils } from "../Utils.js";
|
import { Color, MathUtils, type NumberArrayLike } from "../Utils.js";
|
||||||
import { Attachment } from "./Attachment.js";
|
import { Attachment } from "./Attachment.js";
|
||||||
import type { HasTextureRegion } from "./HasTextureRegion.js";
|
import type { HasSequence } from "./HasSequence.js";
|
||||||
import type { Sequence } from "./Sequence.js";
|
import type { Sequence } from "./Sequence.js";
|
||||||
|
|
||||||
/** An attachment that displays a textured quadrilateral.
|
/** An attachment that displays a textured quadrilateral.
|
||||||
*
|
*
|
||||||
* See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
|
* See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
|
||||||
export class RegionAttachment extends Attachment implements HasTextureRegion {
|
export class RegionAttachment extends Attachment implements HasSequence {
|
||||||
|
readonly sequence: Sequence;
|
||||||
|
|
||||||
/** The local x translation. */
|
/** The local x translation. */
|
||||||
x = 0;
|
x = 0;
|
||||||
|
|
||||||
@ -59,44 +62,95 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
/** The height of the region attachment in Spine. */
|
/** The height of the region attachment in Spine. */
|
||||||
height = 0;
|
height = 0;
|
||||||
|
|
||||||
|
/** The name of the texture region for this attachment. */
|
||||||
|
path?: string;
|
||||||
|
|
||||||
/** The color to tint the region attachment. */
|
/** The color to tint the region attachment. */
|
||||||
color = new Color(1, 1, 1, 1);
|
color = new Color(1, 1, 1, 1);
|
||||||
|
|
||||||
/** The name of the texture region for this attachment. */
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
region: TextureRegion | null = null;
|
|
||||||
sequence: Sequence | null = null;
|
|
||||||
|
|
||||||
/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
|
|
||||||
*
|
|
||||||
* See {@link #updateRegion()}. */
|
|
||||||
offset = Utils.newFloatArray(8);
|
|
||||||
|
|
||||||
uvs = Utils.newFloatArray(8);
|
|
||||||
|
|
||||||
tempColor = new Color(1, 1, 1, 1);
|
tempColor = new Color(1, 1, 1, 1);
|
||||||
|
|
||||||
constructor (name: string, path: string) {
|
constructor (name: string, sequence: Sequence) {
|
||||||
super(name);
|
super(name);
|
||||||
this.path = path;
|
this.sequence = sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
|
copy (): Attachment {
|
||||||
updateRegion (): void {
|
const copy = new RegionAttachment(this.name, this.sequence.copy());
|
||||||
if (!this.region) throw new Error("Region not set.");
|
copy.path = this.path;
|
||||||
const region = this.region;
|
copy.x = this.x;
|
||||||
const uvs = this.uvs;
|
copy.y = this.y;
|
||||||
const regionScaleX = this.width / this.region.originalWidth * this.scaleX;
|
copy.scaleX = this.scaleX;
|
||||||
const regionScaleY = this.height / this.region.originalHeight * this.scaleY;
|
copy.scaleY = this.scaleY;
|
||||||
const localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
|
copy.rotation = this.rotation;
|
||||||
const localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
|
copy.width = this.width;
|
||||||
const localX2 = localX + this.region.width * regionScaleX;
|
copy.height = this.height;
|
||||||
const localY2 = localY + this.region.height * regionScaleY;
|
copy.color.setFromColor(this.color);
|
||||||
const radians = this.rotation * MathUtils.degRad;
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Transforms the attachment's four vertices to world coordinates.
|
||||||
|
* <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 >= <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, vertexOffsets: NumberArrayLike, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
||||||
|
|
||||||
|
const bone = slot.bone.applied;
|
||||||
|
const x = bone.worldX, y = bone.worldY;
|
||||||
|
const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
||||||
|
|
||||||
|
let offsetX = vertexOffsets[0];
|
||||||
|
let offsetY = vertexOffsets[1];
|
||||||
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // br
|
||||||
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
|
offset += stride;
|
||||||
|
|
||||||
|
offsetX = vertexOffsets[2];
|
||||||
|
offsetY = vertexOffsets[3];
|
||||||
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
|
||||||
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
|
offset += stride;
|
||||||
|
|
||||||
|
offsetX = vertexOffsets[4];
|
||||||
|
offsetY = vertexOffsets[5];
|
||||||
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
|
||||||
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
|
offset += stride;
|
||||||
|
|
||||||
|
offsetX = vertexOffsets[6];
|
||||||
|
offsetY = vertexOffsets[7];
|
||||||
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
|
||||||
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOffsets (pose: SlotPose): number[] {
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: offsets are always defined after updateSequence
|
||||||
|
return this.sequence.offsets![this.sequence.resolveIndex(pose)];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSequence () {
|
||||||
|
this.sequence.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Computes {@link Sequence#getUVs(int) UVs} and {@link Sequence#getOffsets(int) offsets} for a region attachment.
|
||||||
|
* @param uvs Output array for the computed UVs, length of 8.
|
||||||
|
* @param offset Output array for the computed vertex offsets, length of 8. */
|
||||||
|
static computeUVs (region: TextureRegion | null, x: number, y: number, scaleX: number, scaleY: number, rotation: number, width: number,
|
||||||
|
height: number, offset: number[], uvs: NumberArrayLike): void {
|
||||||
|
|
||||||
|
if (!region) throw new Error("Region not set.");
|
||||||
|
const regionScaleX = width / region.originalWidth * scaleX;
|
||||||
|
const regionScaleY = height / region.originalHeight * scaleY;
|
||||||
|
const localX = -width / 2 * scaleX + region.offsetX * regionScaleX;
|
||||||
|
const localY = -height / 2 * scaleY + region.offsetY * regionScaleY;
|
||||||
|
const localX2 = localX + region.width * regionScaleX;
|
||||||
|
const localY2 = localY + region.height * regionScaleY;
|
||||||
|
const radians = rotation * MathUtils.degRad;
|
||||||
const cos = Math.cos(radians);
|
const cos = Math.cos(radians);
|
||||||
const sin = Math.sin(radians);
|
const sin = Math.sin(radians);
|
||||||
const x = this.x, y = this.y;
|
|
||||||
const localXCos = localX * cos + x;
|
const localXCos = localX * cos + x;
|
||||||
const localXSin = localX * sin;
|
const localXSin = localX * sin;
|
||||||
const localYCos = localY * cos + y;
|
const localYCos = localY * cos + y;
|
||||||
@ -105,7 +159,6 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
const localX2Sin = localX2 * sin;
|
const localX2Sin = localX2 * sin;
|
||||||
const localY2Cos = localY2 * cos + y;
|
const localY2Cos = localY2 * cos + y;
|
||||||
const localY2Sin = localY2 * sin;
|
const localY2Sin = localY2 * sin;
|
||||||
const offset = this.offset;
|
|
||||||
offset[0] = localXCos - localYSin;
|
offset[0] = localXCos - localYSin;
|
||||||
offset[1] = localYCos + localXSin;
|
offset[1] = localYCos + localXSin;
|
||||||
offset[2] = localXCos - localY2Sin;
|
offset[2] = localXCos - localY2Sin;
|
||||||
@ -124,85 +177,25 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
uvs[5] = 1;
|
uvs[5] = 1;
|
||||||
uvs[6] = 1;
|
uvs[6] = 1;
|
||||||
uvs[7] = 0;
|
uvs[7] = 0;
|
||||||
} else if (region.degrees === 90) {
|
|
||||||
uvs[0] = region.u2;
|
|
||||||
uvs[1] = region.v2;
|
|
||||||
uvs[2] = region.u;
|
|
||||||
uvs[3] = region.v2;
|
|
||||||
uvs[4] = region.u;
|
|
||||||
uvs[5] = region.v;
|
|
||||||
uvs[6] = region.u2;
|
|
||||||
uvs[7] = region.v;
|
|
||||||
} else {
|
} else {
|
||||||
uvs[0] = region.u;
|
|
||||||
uvs[1] = region.v2;
|
uvs[1] = region.v2;
|
||||||
uvs[2] = region.u;
|
uvs[2] = region.u;
|
||||||
uvs[3] = region.v;
|
|
||||||
uvs[4] = region.u2;
|
|
||||||
uvs[5] = region.v;
|
uvs[5] = region.v;
|
||||||
uvs[6] = region.u2;
|
uvs[6] = region.u2;
|
||||||
uvs[7] = region.v2;
|
if (region.degrees === 90) {
|
||||||
|
uvs[0] = region.u2;
|
||||||
|
uvs[3] = region.v2;
|
||||||
|
uvs[4] = region.u;
|
||||||
|
uvs[7] = region.v;
|
||||||
|
} else {
|
||||||
|
uvs[0] = region.u;
|
||||||
|
uvs[3] = region.v;
|
||||||
|
uvs[4] = region.u2;
|
||||||
|
uvs[7] = region.v2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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 >= <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) this.sequence.apply(slot.applied, this);
|
|
||||||
|
|
||||||
const bone = slot.bone.applied;
|
|
||||||
const vertexOffset = this.offset;
|
|
||||||
const x = bone.worldX, y = bone.worldY;
|
|
||||||
const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
|
|
||||||
let offsetX = 0, offsetY = 0;
|
|
||||||
|
|
||||||
offsetX = vertexOffset[0];
|
|
||||||
offsetY = vertexOffset[1];
|
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // br
|
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
|
||||||
offset += stride;
|
|
||||||
|
|
||||||
offsetX = vertexOffset[2];
|
|
||||||
offsetY = vertexOffset[3];
|
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
|
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
|
||||||
offset += stride;
|
|
||||||
|
|
||||||
offsetX = vertexOffset[4];
|
|
||||||
offsetY = vertexOffset[5];
|
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
|
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
|
||||||
offset += stride;
|
|
||||||
|
|
||||||
offsetX = vertexOffset[6];
|
|
||||||
offsetY = vertexOffset[7];
|
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
|
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
|
||||||
}
|
|
||||||
|
|
||||||
copy (): Attachment {
|
|
||||||
const copy = new RegionAttachment(this.name, this.path);
|
|
||||||
copy.region = this.region;
|
|
||||||
copy.x = this.x;
|
|
||||||
copy.y = this.y;
|
|
||||||
copy.scaleX = this.scaleX;
|
|
||||||
copy.scaleY = this.scaleY;
|
|
||||||
copy.rotation = this.rotation;
|
|
||||||
copy.width = this.width;
|
|
||||||
copy.height = this.height;
|
|
||||||
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 != null ? this.sequence.copy() : null;
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
static X1 = 0;
|
static X1 = 0;
|
||||||
static Y1 = 1;
|
static Y1 = 1;
|
||||||
static C1R = 2;
|
static C1R = 2;
|
||||||
|
|||||||
@ -29,45 +29,103 @@
|
|||||||
|
|
||||||
import type { SlotPose } from "src/SlotPose.js";
|
import type { SlotPose } from "src/SlotPose.js";
|
||||||
import type { TextureRegion } from "../Texture.js";
|
import type { TextureRegion } from "../Texture.js";
|
||||||
import { Utils } from "../Utils.js";
|
import { type NumberArrayLike, Utils } from "../Utils.js";
|
||||||
import type { HasTextureRegion } from "./HasTextureRegion.js";
|
import type { HasSequence } from "./HasSequence.js";
|
||||||
|
import { MeshAttachment } from "./MeshAttachment.js";
|
||||||
|
import { RegionAttachment } from "./RegionAttachment.js";
|
||||||
|
|
||||||
|
/** Holds texture regions, UVs, and vertex offsets for rendering a region or mesh attachment. {@link #getRegions() Regions} must
|
||||||
|
* be populated and {@link #update(HasSequence)} called before use. */
|
||||||
export class Sequence {
|
export class Sequence {
|
||||||
private static _nextID = 0;
|
private static _nextID = 0;
|
||||||
|
|
||||||
id = Sequence.nextID();
|
id = Sequence.nextID();
|
||||||
regions: Array<TextureRegion | null>;
|
regions: Array<TextureRegion | null>;
|
||||||
|
readonly pathSuffix: boolean;
|
||||||
|
uvs?: NumberArrayLike[];
|
||||||
|
|
||||||
|
/** Returns vertex offsets from the center of a {@link RegionAttachment}. Invalid to call for a {@link MeshAttachment}. */
|
||||||
|
offsets?: number[][];
|
||||||
|
|
||||||
start = 0;
|
start = 0;
|
||||||
digits = 0;
|
digits = 0;
|
||||||
/** The index of the region to show for the setup pose. */
|
/** The index of the region to show for the setup pose. */
|
||||||
setupIndex = 0;
|
setupIndex = 0;
|
||||||
|
|
||||||
constructor (count: number) {
|
constructor (count: number, pathSuffix: boolean) {
|
||||||
this.regions = new Array<TextureRegion>(count);
|
this.regions = new Array<TextureRegion>(count);
|
||||||
|
this.pathSuffix = pathSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy (): Sequence {
|
copy (): Sequence {
|
||||||
const copy = new Sequence(this.regions.length);
|
const regionCount = this.regions.length;
|
||||||
Utils.arrayCopy(this.regions, 0, copy.regions, 0, this.regions.length);
|
const copy = new Sequence(regionCount, this.pathSuffix);
|
||||||
|
Utils.arrayCopy(this.regions, 0, copy.regions, 0, regionCount);
|
||||||
copy.start = this.start;
|
copy.start = this.start;
|
||||||
copy.digits = this.digits;
|
copy.digits = this.digits;
|
||||||
copy.setupIndex = this.setupIndex;
|
copy.setupIndex = this.setupIndex;
|
||||||
|
|
||||||
|
if (this.uvs != null) {
|
||||||
|
const length = this.uvs[0].length;
|
||||||
|
copy.uvs = [];
|
||||||
|
for (let i = 0; i < regionCount; i++) {
|
||||||
|
copy.uvs[i] = Utils.newFloatArray(length);
|
||||||
|
Utils.arrayCopy(this.uvs[i], 0, copy.uvs[i], 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.offsets != null) {
|
||||||
|
copy.offsets = [];
|
||||||
|
for (let i = 0; i < regionCount; i++) {
|
||||||
|
copy.offsets[i] = [];
|
||||||
|
Utils.arrayCopy(this.offsets[i], 0, copy.offsets[i], 0, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply (slot: SlotPose, attachment: HasTextureRegion) {
|
/** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are
|
||||||
let index = slot.sequenceIndex;
|
* changed. */
|
||||||
if (index === -1) index = this.setupIndex;
|
public update (attachment: HasSequence) {
|
||||||
if (index >= this.regions.length) index = this.regions.length - 1;
|
const regionCount = this.regions.length;
|
||||||
const region = this.regions[index];
|
if (attachment instanceof RegionAttachment) {
|
||||||
if (attachment.region !== region) {
|
this.uvs = [];
|
||||||
attachment.region = region;
|
this.offsets = [];
|
||||||
attachment.updateRegion();
|
for (let i = 0; i < regionCount; i++) {
|
||||||
|
this.uvs[i] = Utils.newFloatArray(8);
|
||||||
|
this.offsets[i] = [];
|
||||||
|
RegionAttachment.computeUVs(this.regions[i], attachment.x, attachment.y, attachment.scaleX, attachment.scaleY, attachment.rotation,
|
||||||
|
attachment.width, attachment.height, this.offsets[i], this.uvs[i]);
|
||||||
|
}
|
||||||
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
|
const regionUVs = attachment.regionUVs;
|
||||||
|
this.uvs = [];
|
||||||
|
this.offsets = undefined;
|
||||||
|
for (let i = 0; i < regionCount; i++) {
|
||||||
|
this.uvs[i] = Utils.newFloatArray(regionUVs.length);
|
||||||
|
MeshAttachment.computeUVs(this.regions[i], regionUVs, this.uvs[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveIndex (pose: SlotPose): number {
|
||||||
|
let index = pose.sequenceIndex;
|
||||||
|
if (index === -1) index = this.setupIndex;
|
||||||
|
if (index >= this.regions.length) index = this.regions.length - 1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUVs (index: number): Float32Array {
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: uvs are always defined after updateSequence
|
||||||
|
return this.uvs![index] as Float32Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPathSuffix (): boolean {
|
||||||
|
return this.pathSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
getPath (basePath: string, index: number): string {
|
getPath (basePath: string, index: number): string {
|
||||||
|
if (!this.pathSuffix) return basePath;
|
||||||
let result = basePath;
|
let result = basePath;
|
||||||
const frame = (this.start + index).toString();
|
const frame = (this.start + index).toString();
|
||||||
for (let i = this.digits - frame.length; i > 0; i--)
|
for (let i = this.digits - frame.length; i > 0; i--)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export * from './attachments/Attachment.js';
|
|||||||
export * from './attachments/AttachmentLoader.js';
|
export * from './attachments/AttachmentLoader.js';
|
||||||
export * from './attachments/BoundingBoxAttachment.js';
|
export * from './attachments/BoundingBoxAttachment.js';
|
||||||
export * from './attachments/ClippingAttachment.js';
|
export * from './attachments/ClippingAttachment.js';
|
||||||
export * from './attachments/HasTextureRegion.js';
|
export * from './attachments/HasSequence.js';
|
||||||
export * from './attachments/MeshAttachment.js';
|
export * from './attachments/MeshAttachment.js';
|
||||||
export * from './attachments/PathAttachment.js';
|
export * from './attachments/PathAttachment.js';
|
||||||
export * from './attachments/PointAttachment.js';
|
export * from './attachments/PointAttachment.js';
|
||||||
|
|||||||
74
spine-ts/spine-pixi-v7/example/dragon.html
Normal file
74
spine-ts/spine-pixi-v7/example/dragon.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>spine-pixi-v7</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.4.2/dist/pixi.min.js"></script>
|
||||||
|
<script src="../dist/iife/spine-pixi-v7.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/lil-gui@0.20.0/dist/lil-gui.umd.min.js"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/lil-gui@0.20.0/dist/lil-gui.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="../../index.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
(async function () {
|
||||||
|
var app = new PIXI.Application({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resolution: window.devicePixelRatio || 1,
|
||||||
|
autoDensity: true,
|
||||||
|
resizeTo: window,
|
||||||
|
backgroundColor: 0x2c3e50,
|
||||||
|
hello: true,
|
||||||
|
});
|
||||||
|
document.body.appendChild(app.view);
|
||||||
|
|
||||||
|
PIXI.Assets.add("spineboyData", "/assets/dragon-ess.skel");
|
||||||
|
PIXI.Assets.add("spineboyAtlas", "/assets/dragon-pma.atlas");
|
||||||
|
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
|
||||||
|
|
||||||
|
const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 });
|
||||||
|
const spineboy2 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 });
|
||||||
|
spineboy.autoUpdate = false;
|
||||||
|
spineboy2.autoUpdate = false;
|
||||||
|
|
||||||
|
spineboy.state.data.defaultMix = 0.2;
|
||||||
|
|
||||||
|
spineboy.x = window.innerWidth / 2;
|
||||||
|
spineboy.y = window.innerHeight / 2 - 30;
|
||||||
|
|
||||||
|
spineboy2.x = window.innerWidth / 2;
|
||||||
|
spineboy2.y = window.innerHeight / 2 + 200;
|
||||||
|
spineboy2.state.setAnimation(0, "flying", true);
|
||||||
|
|
||||||
|
spineboy.state.setAnimation(0, "flying", true);
|
||||||
|
|
||||||
|
app.stage.addChild(spineboy);
|
||||||
|
app.stage.addChild(spineboy2);
|
||||||
|
|
||||||
|
const myObject = { time: 0, time2: 0 };
|
||||||
|
let prevValue = myObject.time;
|
||||||
|
let prevValue2 = myObject.time2;
|
||||||
|
spineboy.update(prevValue / 10)
|
||||||
|
spineboy2.update(prevValue2 / 10)
|
||||||
|
|
||||||
|
const gui = new lil.GUI({});
|
||||||
|
gui
|
||||||
|
.add(myObject, 'time').min(0).max(10).step(0.01)
|
||||||
|
.name( 'time' )
|
||||||
|
.onChange(value => {
|
||||||
|
spineboy.update((value - prevValue) / 10)
|
||||||
|
prevValue = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
gui
|
||||||
|
.add(myObject, 'time2').min(0).max(10).step(0.01)
|
||||||
|
.name( 'time2' )
|
||||||
|
.onChange(value => {
|
||||||
|
spineboy2.update((value - prevValue2) / 10)
|
||||||
|
prevValue2 = value;
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -568,7 +568,7 @@ export class Spine extends Container {
|
|||||||
slotObject.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue;
|
slotObject.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue;
|
||||||
|
|
||||||
if (slotObject.visible) {
|
if (slotObject.visible) {
|
||||||
let applied = slot.bone.applied;
|
const applied = slot.bone.applied;
|
||||||
|
|
||||||
const matrix = slotObject.localTransform;
|
const matrix = slotObject.localTransform;
|
||||||
matrix.a = applied.a;
|
matrix.a = applied.a;
|
||||||
@ -659,10 +659,14 @@ export class Spine extends Container {
|
|||||||
const region = attachment;
|
const region = attachment;
|
||||||
attachmentColor = region.color;
|
attachmentColor = region.color;
|
||||||
numFloats = vertexSize * 4;
|
numFloats = vertexSize * 4;
|
||||||
region.computeWorldVertices(slot, this.verticesCache, 0, vertexSize);
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), this.verticesCache, 0, vertexSize);
|
||||||
|
|
||||||
triangles = Spine.QUAD_TRIANGLES;
|
triangles = Spine.QUAD_TRIANGLES;
|
||||||
uvs = region.uvs;
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
texture = <SpineTexture>region.region?.texture;
|
texture = sequence.regions[sequenceIndex]?.texture as SpineTexture;
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
const mesh = attachment;
|
const mesh = attachment;
|
||||||
attachmentColor = mesh.color;
|
attachmentColor = mesh.color;
|
||||||
@ -672,8 +676,12 @@ export class Spine extends Container {
|
|||||||
}
|
}
|
||||||
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, this.verticesCache, 0, vertexSize);
|
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, this.verticesCache, 0, vertexSize);
|
||||||
triangles = mesh.triangles;
|
triangles = mesh.triangles;
|
||||||
uvs = mesh.uvs;
|
|
||||||
texture = <SpineTexture>mesh.region?.texture;
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
|
texture = sequence.regions[sequenceIndex]?.texture as SpineTexture;
|
||||||
} else if (attachment instanceof ClippingAttachment) {
|
} else if (attachment instanceof ClippingAttachment) {
|
||||||
Spine.clipper.clipStart(skeleton, slot, attachment);
|
Spine.clipper.clipStart(skeleton, slot, attachment);
|
||||||
pixiMaskSource = { slot, computed: false };
|
pixiMaskSource = { slot, computed: false };
|
||||||
|
|||||||
@ -345,11 +345,9 @@ export class SpineDebugRenderer implements ISpineDebugRenderer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regionAttachment = attachment;
|
|
||||||
|
|
||||||
const vertices = new Float32Array(8);
|
const vertices = new Float32Array(8);
|
||||||
|
|
||||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
debugDisplayObjects.regionAttachmentsShape.drawPolygon(Array.from(vertices.slice(0, 8)));
|
debugDisplayObjects.regionAttachmentsShape.drawPolygon(Array.from(vertices.slice(0, 8)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,6 @@ import {
|
|||||||
Container,
|
Container,
|
||||||
type ContainerOptions,
|
type ContainerOptions,
|
||||||
type DestroyOptions,
|
type DestroyOptions,
|
||||||
fastCopy,
|
|
||||||
Graphics,
|
Graphics,
|
||||||
type PointData,
|
type PointData,
|
||||||
Texture,
|
Texture,
|
||||||
@ -66,6 +65,7 @@ import {
|
|||||||
ViewContainer,
|
ViewContainer,
|
||||||
} from 'pixi.js';
|
} from 'pixi.js';
|
||||||
import type { ISpineDebugRenderer } from './SpineDebugRenderer.js';
|
import type { ISpineDebugRenderer } from './SpineDebugRenderer.js';
|
||||||
|
import type { SpineTexture } from './SpineTexture.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options to create a {@link Spine} using {@link Spine.from}.
|
* Options to create a {@link Spine} using {@link Spine.from}.
|
||||||
@ -662,8 +662,11 @@ export class Spine extends ViewContainer {
|
|||||||
if (attachment instanceof MeshAttachment || attachment instanceof RegionAttachment) {
|
if (attachment instanceof MeshAttachment || attachment instanceof RegionAttachment) {
|
||||||
const cacheData = this._getCachedData(slot, attachment);
|
const cacheData = this._getCachedData(slot, attachment);
|
||||||
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
attachment.computeWorldVertices(slot, cacheData.vertices, 0, 2);
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), cacheData.vertices, 0, 2);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
attachment.computeWorldVertices(
|
attachment.computeWorldVertices(
|
||||||
@ -677,13 +680,7 @@ export class Spine extends ViewContainer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sequences uvs are known only after computeWorldVertices is invoked
|
cacheData.uvs = sequence.getUVs(sequenceIndex);
|
||||||
if (cacheData.uvs.length < attachment.uvs.length) {
|
|
||||||
cacheData.uvs = new Float32Array(attachment.uvs.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to copy because attachments uvs are shared among skeletons using the same atlas
|
|
||||||
fastCopy((attachment.uvs as Float32Array).buffer, cacheData.uvs.buffer);
|
|
||||||
|
|
||||||
const skeletonColor = skeleton.color;
|
const skeletonColor = skeleton.color;
|
||||||
const slotColor = pose.color;
|
const slotColor = pose.color;
|
||||||
@ -708,7 +705,7 @@ export class Spine extends ViewContainer {
|
|||||||
cacheData.darkColor.setFromColor(pose.darkColor);
|
cacheData.darkColor.setFromColor(pose.darkColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const texture = attachment.region?.texture.texture || Texture.EMPTY;
|
const texture = (sequence.regions[sequenceIndex]?.texture as SpineTexture)?.texture || Texture.EMPTY;
|
||||||
|
|
||||||
if (cacheData.texture !== texture) {
|
if (cacheData.texture !== texture) {
|
||||||
cacheData.texture = texture;
|
cacheData.texture = texture;
|
||||||
@ -834,33 +831,35 @@ export class Spine extends ViewContainer {
|
|||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
vertices = new Float32Array(8);
|
vertices = new Float32Array(8);
|
||||||
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
||||||
id: `${slot.data.index}-${attachment.name}`,
|
id: `${slot.data.index}-${attachment.name}`,
|
||||||
vertices,
|
vertices,
|
||||||
clipped: false,
|
clipped: false,
|
||||||
indices: [0, 1, 2, 0, 2, 3],
|
indices: [0, 1, 2, 0, 2, 3],
|
||||||
uvs: new Float32Array(attachment.uvs.length),
|
uvs: new Float32Array(sequence.getUVs(0).length),
|
||||||
color: new Color(1, 1, 1, 1),
|
color: new Color(1, 1, 1, 1),
|
||||||
darkColor: new Color(0, 0, 0, 0),
|
darkColor: new Color(0, 0, 0, 0),
|
||||||
darkTint: this.darkTint,
|
darkTint: this.darkTint,
|
||||||
skipRender: false,
|
skipRender: false,
|
||||||
texture: attachment.region?.texture.texture,
|
texture: (sequence.regions[0]?.texture as SpineTexture)?.texture,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
vertices = new Float32Array(attachment.worldVerticesLength);
|
vertices = new Float32Array(attachment.worldVerticesLength);
|
||||||
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
||||||
id: `${slot.data.index}-${attachment.name}`,
|
id: `${slot.data.index}-${attachment.name}`,
|
||||||
vertices,
|
vertices,
|
||||||
clipped: false,
|
clipped: false,
|
||||||
indices: attachment.triangles,
|
indices: attachment.triangles,
|
||||||
uvs: new Float32Array(attachment.uvs.length),
|
uvs: new Float32Array(sequence.getUVs(0).length),
|
||||||
color: new Color(1, 1, 1, 1),
|
color: new Color(1, 1, 1, 1),
|
||||||
darkColor: new Color(0, 0, 0, 0),
|
darkColor: new Color(0, 0, 0, 0),
|
||||||
darkTint: this.darkTint,
|
darkTint: this.darkTint,
|
||||||
skipRender: false,
|
skipRender: false,
|
||||||
texture: attachment.region?.texture.texture,
|
texture: (sequence.regions[0]?.texture as SpineTexture)?.texture,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -366,12 +366,9 @@ export class SpineDebugRenderer implements ISpineDebugRenderer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regionAttachment = attachment;
|
|
||||||
|
|
||||||
const vertices = new Float32Array(8);
|
const vertices = new Float32Array(8);
|
||||||
|
|
||||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
|
|
||||||
debugDisplayObjects.regionAttachmentsShape.poly(Array.from(vertices.slice(0, 8)));
|
debugDisplayObjects.regionAttachmentsShape.poly(Array.from(vertices.slice(0, 8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -216,8 +216,10 @@ export class SpinePipe implements RenderPipe<Spine> {
|
|||||||
|
|
||||||
if (!cacheData.skipRender) {
|
if (!cacheData.skipRender) {
|
||||||
const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id];
|
const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id];
|
||||||
// we didn't figure out why batchableSpineSlot might be undefined: https://github.com/EsotericSoftware/spine-runtimes/issues/2991
|
if (batchableSpineSlot) {
|
||||||
batchableSpineSlot?._batcher?.updateElement(batchableSpineSlot);
|
batchableSpineSlot.uvs = cacheData.uvs;
|
||||||
|
batchableSpineSlot._batcher?.updateElement(batchableSpineSlot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -246,10 +246,14 @@ export class SkeletonMesh extends THREE.Object3D {
|
|||||||
attachmentColor = attachment.color;
|
attachmentColor = attachment.color;
|
||||||
vertices = this.vertices;
|
vertices = this.vertices;
|
||||||
numFloats = vertexSize * 4;
|
numFloats = vertexSize * 4;
|
||||||
attachment.computeWorldVertices(slot, vertices, 0, vertexSize);
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), vertices, 0, vertexSize);
|
||||||
|
|
||||||
triangles = SkeletonMesh.QUAD_TRIANGLES;
|
triangles = SkeletonMesh.QUAD_TRIANGLES;
|
||||||
uvs = attachment.uvs;
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
texture = <ThreeJsTexture>attachment.region?.texture;
|
texture = sequence.regions[sequenceIndex]?.texture;
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
attachmentColor = attachment.color;
|
attachmentColor = attachment.color;
|
||||||
vertices = this.vertices;
|
vertices = this.vertices;
|
||||||
@ -267,8 +271,12 @@ export class SkeletonMesh extends THREE.Object3D {
|
|||||||
vertexSize
|
vertexSize
|
||||||
);
|
);
|
||||||
triangles = attachment.triangles;
|
triangles = attachment.triangles;
|
||||||
uvs = attachment.uvs;
|
|
||||||
texture = <ThreeJsTexture>attachment.region?.texture;
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
|
texture = sequence.regions[sequenceIndex]?.texture;
|
||||||
} else if (attachment instanceof ClippingAttachment) {
|
} else if (attachment instanceof ClippingAttachment) {
|
||||||
clipper.clipEnd(slot);
|
clipper.clipEnd(slot);
|
||||||
clipper.clipStart(skeleton, slot, attachment);
|
clipper.clipStart(skeleton, slot, attachment);
|
||||||
|
|||||||
@ -1160,12 +1160,10 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
|
|
||||||
// we could probably cache the vertices from rendering if interaction with this slot is enabled
|
// we could probably cache the vertices from rendering if interaction with this slot is enabled
|
||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
const regionAttachment = <RegionAttachment>attachment;
|
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
const mesh = <MeshAttachment>attachment;
|
attachment.computeWorldVertices(this.skeleton as Skeleton, slot, 0, attachment.worldVerticesLength, vertices, 0, 2);
|
||||||
mesh.computeWorldVertices(this.skeleton as Skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, 2);
|
hullLength = attachment.hullLength;
|
||||||
hullLength = mesh.hullLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we have only "move" and "drag" events
|
// here we have only "move" and "drag" events
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
</head>
|
|
||||||
<script src="../dist/iife/spine-webgl.js"></script>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<canvas id="canvas" style="position: absolute; width: 100%; height: 100%;"></canvas>
|
|
||||||
<script>
|
|
||||||
class App {
|
|
||||||
constructor() {
|
|
||||||
this.skeleton = null;
|
|
||||||
this.animationState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAssets(canvas) {
|
|
||||||
// Load the skeleton file.
|
|
||||||
canvas.assetManager.loadBinary("/assets/dragon-ess.skel");
|
|
||||||
// Load the atlas and its pages.
|
|
||||||
canvas.assetManager.loadTextureAtlas("/assets/dragon-pma.atlas");
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(canvas) {
|
|
||||||
let assetManager = canvas.assetManager;
|
|
||||||
|
|
||||||
// Create the texture atlas.
|
|
||||||
var atlas = assetManager.require("/assets/dragon-pma.atlas");
|
|
||||||
|
|
||||||
// Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
|
|
||||||
var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
|
||||||
|
|
||||||
// Create a SkeletonBinary instance for parsing the .skel file.
|
|
||||||
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
|
||||||
|
|
||||||
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
|
||||||
skeletonBinary.scale = 1;
|
|
||||||
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/dragon-ess.skel"));
|
|
||||||
this.skeleton = new spine.Skeleton(skeletonData);
|
|
||||||
|
|
||||||
// Create an AnimationState, and set the "run" animation in looping mode.
|
|
||||||
var animationStateData = new spine.AnimationStateData(skeletonData);
|
|
||||||
this.animationState = new spine.AnimationState(animationStateData);
|
|
||||||
this.animationState.setAnimation(0, "flying", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(canvas, delta) {
|
|
||||||
// Update the animation state using the delta time.
|
|
||||||
this.animationState.update(delta);
|
|
||||||
// Apply the animation state to the skeleton.
|
|
||||||
this.animationState.apply(this.skeleton);
|
|
||||||
// Let the skeleton update the transforms of its bones.
|
|
||||||
this.skeleton.updateWorldTransform(spine.Physics.update);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(canvas) {
|
|
||||||
let renderer = canvas.renderer;
|
|
||||||
// Resize the viewport to the full canvas.
|
|
||||||
renderer.resize(spine.ResizeMode.Expand);
|
|
||||||
|
|
||||||
// Clear the canvas with a light gray color.
|
|
||||||
canvas.clear(0.2, 0.2, 0.2, 1);
|
|
||||||
|
|
||||||
// Begin rendering.
|
|
||||||
renderer.begin();
|
|
||||||
// Draw the skeleton
|
|
||||||
renderer.drawSkeleton(this.skeleton);
|
|
||||||
// Complete rendering.
|
|
||||||
renderer.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new spine.SpineCanvas(document.getElementById("canvas"), {
|
|
||||||
app: new App()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
122
spine-ts/spine-webgl/example/dragon.html
Normal file
122
spine-ts/spine-webgl/example/dragon.html
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<script src="../dist/iife/spine-webgl.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/lil-gui@0.20.0/dist/lil-gui.umd.min.js"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/lil-gui@0.20.0/dist/lil-gui.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas" style="position: absolute; width: 100%; height: 100%;"></canvas>
|
||||||
|
<script>
|
||||||
|
class App {
|
||||||
|
constructor() {
|
||||||
|
this.skeleton1 = null;
|
||||||
|
this.skeleton2 = null;
|
||||||
|
this.animationState1 = null;
|
||||||
|
this.animationState2 = null;
|
||||||
|
this.manualControl = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAssets(canvas) {
|
||||||
|
canvas.assetManager.loadBinary("/assets/dragon-ess.skel");
|
||||||
|
canvas.assetManager.loadTextureAtlas("/assets/dragon-pma.atlas");
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(canvas) {
|
||||||
|
let assetManager = canvas.assetManager;
|
||||||
|
|
||||||
|
var atlas = assetManager.require("/assets/dragon-pma.atlas");
|
||||||
|
var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
||||||
|
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||||
|
|
||||||
|
skeletonBinary.scale = 0.5;
|
||||||
|
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/dragon-ess.skel"));
|
||||||
|
|
||||||
|
// First skeleton
|
||||||
|
this.skeleton1 = new spine.Skeleton(skeletonData);
|
||||||
|
var animationStateData1 = new spine.AnimationStateData(skeletonData);
|
||||||
|
this.animationState1 = new spine.AnimationState(animationStateData1);
|
||||||
|
this.animationState1.setAnimation(0, "flying", true);
|
||||||
|
|
||||||
|
// Second skeleton sharing the same skeletonData
|
||||||
|
this.skeleton2 = new spine.Skeleton(skeletonData);
|
||||||
|
var animationStateData2 = new spine.AnimationStateData(skeletonData);
|
||||||
|
this.animationState2 = new spine.AnimationState(animationStateData2);
|
||||||
|
this.animationState2.setAnimation(0, "flying", true);
|
||||||
|
|
||||||
|
// GUI for manual time control
|
||||||
|
const myObject = { time1: 0, time2: 0 };
|
||||||
|
let prevValue1 = 0;
|
||||||
|
let prevValue2 = 0;
|
||||||
|
|
||||||
|
const gui = new lil.GUI({});
|
||||||
|
gui.add(myObject, 'time1').min(0).max(10).step(0.01)
|
||||||
|
.name('time dragon 1')
|
||||||
|
.onChange(value => {
|
||||||
|
this.manualControl = true;
|
||||||
|
const delta = value - prevValue1;
|
||||||
|
prevValue1 = value;
|
||||||
|
this.animationState1.update(delta / 10);
|
||||||
|
this.animationState1.apply(this.skeleton1);
|
||||||
|
this.skeleton1.updateWorldTransform(spine.Physics.update);
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.add(myObject, 'time2').min(0).max(10).step(0.01)
|
||||||
|
.name('time dragon 2')
|
||||||
|
.onChange(value => {
|
||||||
|
this.manualControl = true;
|
||||||
|
const delta = value - prevValue2;
|
||||||
|
prevValue2 = value;
|
||||||
|
this.animationState2.update(delta / 10);
|
||||||
|
this.animationState2.apply(this.skeleton2);
|
||||||
|
this.skeleton2.updateWorldTransform(spine.Physics.update);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(canvas, delta) {
|
||||||
|
if (this.manualControl) return;
|
||||||
|
|
||||||
|
this.animationState1.update(delta);
|
||||||
|
this.animationState1.apply(this.skeleton1);
|
||||||
|
this.skeleton1.updateWorldTransform(spine.Physics.update);
|
||||||
|
|
||||||
|
this.animationState2.update(delta);
|
||||||
|
this.animationState2.apply(this.skeleton2);
|
||||||
|
this.skeleton2.updateWorldTransform(spine.Physics.update);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas) {
|
||||||
|
let renderer = canvas.renderer;
|
||||||
|
renderer.resize(spine.ResizeMode.Expand);
|
||||||
|
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||||
|
|
||||||
|
// Position the two skeletons
|
||||||
|
this.skeleton1.x = 0;
|
||||||
|
this.skeleton1.y = -100;
|
||||||
|
|
||||||
|
this.skeleton2.x = 0;
|
||||||
|
this.skeleton2.y = 200;
|
||||||
|
|
||||||
|
renderer.begin();
|
||||||
|
renderer.drawSkeleton(this.skeleton1);
|
||||||
|
renderer.drawSkeleton(this.skeleton2);
|
||||||
|
renderer.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new spine.SpineCanvas(document.getElementById("canvas"), {
|
||||||
|
app: new App()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -91,7 +91,8 @@ export class SkeletonDebugRenderer implements Disposable {
|
|||||||
const attachment = slot.applied.attachment;
|
const attachment = slot.applied.attachment;
|
||||||
if (attachment instanceof RegionAttachment) {
|
if (attachment instanceof RegionAttachment) {
|
||||||
const vertices = this.vertices;
|
const vertices = this.vertices;
|
||||||
attachment.computeWorldVertices(slot, vertices, 0, 2);
|
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
|
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
|
||||||
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
|
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
|
||||||
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
|
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { type BlendMode, ClippingAttachment, Color, MeshAttachment, type NumberArrayLike, RegionAttachment, type Skeleton, SkeletonClipping, type TextureRegion, Utils, Vector2 } from "@esotericsoftware/spine-core";
|
import { type BlendMode, ClippingAttachment, Color, MeshAttachment, type NumberArrayLike, RegionAttachment, type Skeleton, SkeletonClipping, Utils, Vector2 } from "@esotericsoftware/spine-core";
|
||||||
import type { GLTexture } from "./GLTexture.js";
|
import type { GLTexture } from "./GLTexture.js";
|
||||||
import type { PolygonBatcher } from "./PolygonBatcher.js";
|
import type { PolygonBatcher } from "./PolygonBatcher.js";
|
||||||
import type { ManagedWebGLRenderingContext } from "./WebGL.js";
|
import type { ManagedWebGLRenderingContext } from "./WebGL.js";
|
||||||
@ -102,10 +102,14 @@ export class SkeletonRenderer {
|
|||||||
renderable.vertices = this.vertices;
|
renderable.vertices = this.vertices;
|
||||||
renderable.numVertices = 4;
|
renderable.numVertices = 4;
|
||||||
renderable.numFloats = vertexSize << 2;
|
renderable.numFloats = vertexSize << 2;
|
||||||
attachment.computeWorldVertices(slot, renderable.vertices, 0, vertexSize);
|
|
||||||
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), renderable.vertices, 0, vertexSize);
|
||||||
|
|
||||||
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
||||||
uvs = attachment.uvs;
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
texture = (attachment.region as TextureRegion).texture as GLTexture;
|
texture = sequence.regions[sequenceIndex]?.texture as GLTexture;
|
||||||
attachmentColor = attachment.color;
|
attachmentColor = attachment.color;
|
||||||
} else if (attachment instanceof MeshAttachment) {
|
} else if (attachment instanceof MeshAttachment) {
|
||||||
renderable.vertices = this.vertices;
|
renderable.vertices = this.vertices;
|
||||||
@ -117,8 +121,12 @@ export class SkeletonRenderer {
|
|||||||
}
|
}
|
||||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, renderable.vertices, 0, vertexSize);
|
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, renderable.vertices, 0, vertexSize);
|
||||||
triangles = attachment.triangles;
|
triangles = attachment.triangles;
|
||||||
texture = (attachment.region as TextureRegion).texture as GLTexture;
|
|
||||||
uvs = attachment.uvs;
|
const sequence = attachment.sequence;
|
||||||
|
const sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
|
||||||
|
texture = sequence.regions[sequenceIndex]?.texture as GLTexture;
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
attachmentColor = attachment.color;
|
attachmentColor = attachment.color;
|
||||||
} else if (attachment instanceof ClippingAttachment) {
|
} else if (attachment instanceof ClippingAttachment) {
|
||||||
clipper.clipEnd(slot);
|
clipper.clipEnd(slot);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user