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/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/dragon.html">Dragon</a> - (<a href="/spine-pixi-v7/example/dragon.html">v7</a>)</li>
|
||||
</ul>
|
||||
<li>Phaser</li>
|
||||
<ul>
|
||||
@ -142,6 +143,7 @@
|
||||
<ul>
|
||||
<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/dragon.html">Dragon</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/physics3.html">Physics III</a></li>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
* 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";
|
||||
|
||||
const worldVertices = Utils.newFloatArray(8);
|
||||
@ -68,10 +68,14 @@ export class SkeletonRenderer {
|
||||
const pose = slot.applied;
|
||||
const attachment = pose.attachment;
|
||||
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 regionColor = attachment.color;
|
||||
@ -83,7 +87,8 @@ export class SkeletonRenderer {
|
||||
ctx.save();
|
||||
const boneApplied = bone.applied;
|
||||
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);
|
||||
|
||||
const atlasScale = attachment.width / region.originalWidth;
|
||||
@ -91,7 +96,7 @@ export class SkeletonRenderer {
|
||||
|
||||
let w = region.width, h = region.height;
|
||||
ctx.translate(w / 2, h / 2);
|
||||
if (attachment.region?.degrees === 90) {
|
||||
if (region.degrees === 90) {
|
||||
const t = w;
|
||||
w = h;
|
||||
h = t;
|
||||
@ -124,15 +129,25 @@ export class SkeletonRenderer {
|
||||
|
||||
let texture: HTMLImageElement;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
const regionAttachment = <RegionAttachment>attachment;
|
||||
vertices = this.computeRegionVertices(slot, regionAttachment, false);
|
||||
const sequence = attachment.sequence;
|
||||
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;
|
||||
texture = (<CanvasTexture>regionAttachment.region?.texture).getImage() as HTMLImageElement;
|
||||
|
||||
texture = (sequence.regions[sequenceIndex]?.texture as CanvasTexture).getImage();
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
const mesh = <MeshAttachment>attachment;
|
||||
vertices = this.computeMeshVertices(slot, mesh, false);
|
||||
triangles = mesh.triangles;
|
||||
texture = (<CanvasTexture>mesh.region?.texture).getImage() as HTMLImageElement;
|
||||
const sequence = attachment.sequence;
|
||||
const sequenceIndex = sequence.resolveIndex(pose);
|
||||
|
||||
const uvs = sequence.getUVs(sequenceIndex);
|
||||
vertices = this.computeMeshVertices(slot, attachment, uvs, false);
|
||||
triangles = attachment.triangles;
|
||||
|
||||
texture = (sequence.regions[sequenceIndex]?.texture as CanvasTexture).getImage();
|
||||
} else
|
||||
continue;
|
||||
|
||||
@ -226,7 +241,7 @@ export class SkeletonRenderer {
|
||||
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 slotColor = slot.applied.color;
|
||||
const regionColor = region.color;
|
||||
@ -238,10 +253,9 @@ export class SkeletonRenderer {
|
||||
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
|
||||
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 uvs = region.uvs;
|
||||
|
||||
vertices[RegionAttachment.C1R] = color.r;
|
||||
vertices[RegionAttachment.C1G] = color.g;
|
||||
@ -274,7 +288,7 @@ export class SkeletonRenderer {
|
||||
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 skeletonColor = skeleton.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);
|
||||
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++) {
|
||||
vertices[v++] = color.r;
|
||||
vertices[v++] = color.g;
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
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 type { Inherit } from "./BoneData.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) {
|
||||
const frames = this.frames;
|
||||
const color = pose.color;
|
||||
const frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
const setup = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
@ -977,8 +977,12 @@ export class RGBATimeline extends SlotCurveTimeline {
|
||||
if (alpha === 1)
|
||||
color.set(r, g, b, a);
|
||||
else {
|
||||
if (blend === MixBlend.setup) color.setFromColor(slot.data.setup.color);
|
||||
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
|
||||
if (blend === MixBlend.setup) {
|
||||
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) {
|
||||
const frames = this.frames;
|
||||
const color = pose.color;
|
||||
let r = 0, g = 0, b = 0;
|
||||
const frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
const setup = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||
case MixBlend.setup:
|
||||
color.r = setup.r;
|
||||
color.g = setup.g;
|
||||
color.b = setup.b;
|
||||
// Fall through.
|
||||
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||
default:
|
||||
return;
|
||||
case MixBlend.first:
|
||||
color.r += (setup.r - color.r) * alpha;
|
||||
color.g += (setup.g - color.g) * alpha;
|
||||
color.b += (setup.b - color.b) * alpha;
|
||||
r = color.r + (setup.r - color.r) * alpha;
|
||||
g = color.g + (setup.g - color.g) * 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 {
|
||||
if (blend === MixBlend.setup) {
|
||||
const setup = slot.data.setup.color;
|
||||
color.r = setup.r;
|
||||
color.g = setup.g;
|
||||
color.b = setup.b;
|
||||
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) {
|
||||
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;
|
||||
|
||||
const color = (appliedPose ? slot.applied : slot.pose).color;
|
||||
let a = 0;
|
||||
const frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
const setup = slot.data.setup.color;
|
||||
switch (blend) {
|
||||
case MixBlend.setup: color.a = setup.a; break;
|
||||
case MixBlend.first: color.a += (setup.a - color.a) * alpha; break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||
case MixBlend.setup:
|
||||
color.a = setup.a;
|
||||
// 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)
|
||||
color.a = a;
|
||||
else {
|
||||
if (blend === MixBlend.setup) color.a = slot.data.setup.color.a;
|
||||
color.a += (a - color.a) * alpha;
|
||||
}
|
||||
} else {
|
||||
a = this.getCurveValue(time);
|
||||
if (alpha !== 1) {
|
||||
if (blend === MixBlend.setup) {
|
||||
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) {
|
||||
const frames = this.frames;
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
||||
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||
const light = pose.color, dark = pose.darkColor!;
|
||||
let r2 = 0, g2 = 0, b2 = 0
|
||||
const frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
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!;
|
||||
switch (blend) {
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||
case MixBlend.setup:
|
||||
light.setFromColor(setupLight);
|
||||
dark.r = setupDark.r;
|
||||
dark.g = setupDark.g;
|
||||
dark.b = setupDark.b;
|
||||
// Fall through.
|
||||
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||
default:
|
||||
return;
|
||||
case MixBlend.first:
|
||||
light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
|
||||
(setupLight.a - light.a) * alpha);
|
||||
dark.r += (setupDark.r - dark.r) * alpha;
|
||||
dark.g += (setupDark.g - dark.g) * alpha;
|
||||
dark.b += (setupDark.b - dark.b) * alpha;
|
||||
r2 = dark.r + (setupDark.r - dark.r) * alpha;
|
||||
g2 = dark.g + (setupDark.g - dark.g) * 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 {
|
||||
if (blend === MixBlend.setup) {
|
||||
const setup = slot.data.setup;
|
||||
light.setFromColor(setup.color);
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
||||
const setupDark = setup.darkColor!;
|
||||
dark.r = setupDark.r;
|
||||
dark.g = setupDark.g;
|
||||
dark.b = setupDark.b;
|
||||
let r = 0, g = 0, b = 0, a = 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);
|
||||
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) {
|
||||
const frames = this.frames;
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
||||
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||
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]) {
|
||||
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!;
|
||||
switch (blend) {
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
|
||||
case MixBlend.setup:
|
||||
light.r = setupLight.r;
|
||||
light.g = setupLight.g;
|
||||
@ -1256,82 +1278,84 @@ export class RGB2Timeline extends SlotCurveTimeline {
|
||||
dark.r = setupDark.r;
|
||||
dark.g = setupDark.g;
|
||||
dark.b = setupDark.b;
|
||||
// Fall through.
|
||||
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: reference runtime
|
||||
default:
|
||||
return;
|
||||
case MixBlend.first:
|
||||
light.r += (setupLight.r - light.r) * alpha;
|
||||
light.g += (setupLight.g - light.g) * alpha;
|
||||
light.b += (setupLight.b - light.b) * alpha;
|
||||
dark.r += (setupDark.r - dark.r) * alpha;
|
||||
dark.g += (setupDark.g - dark.g) * alpha;
|
||||
dark.b += (setupDark.b - dark.b) * alpha;
|
||||
r = light.r + (setupLight.r - light.r) * alpha;
|
||||
g = light.g + (setupLight.g - light.g) * alpha;
|
||||
b = light.b + (setupLight.b - light.b) * alpha;
|
||||
r2 = dark.r + (setupDark.r - dark.r) * alpha;
|
||||
g2 = dark.g + (setupDark.g - dark.g) * 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 {
|
||||
if (blend === MixBlend.setup) {
|
||||
const setup = slot.data.setup;
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
||||
const setupLight = setup.color, setupDark = setup.darkColor!;
|
||||
light.r = setupLight.r;
|
||||
light.g = setupLight.g;
|
||||
light.b = setupLight.b;
|
||||
dark.r = setupDark.r;
|
||||
dark.g = setupDark.g;
|
||||
dark.b = setupDark.b;
|
||||
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) {
|
||||
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;
|
||||
|
||||
readonly slotIndex: number;
|
||||
readonly attachment: HasTextureRegion;
|
||||
readonly attachment: HasSequence;
|
||||
|
||||
constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected behavior from reference runtime
|
||||
constructor (frameCount: number, slotIndex: number, attachment: HasSequence) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: reference runtime
|
||||
super(frameCount, `${Property.sequence}|${slotIndex}|${attachment.sequence!.id}`);
|
||||
this.slotIndex = slotIndex;
|
||||
this.attachment = attachment;
|
||||
@ -1636,6 +1660,9 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
return this.slotIndex;
|
||||
}
|
||||
|
||||
/** The attachment for which the {@link SlotPose#getSequenceIndex()} will be set.
|
||||
* <p>
|
||||
* See {@link VertexAttachment.timelineAttachment}. */
|
||||
getAttachment () {
|
||||
return this.attachment as unknown as Attachment;
|
||||
}
|
||||
@ -1658,15 +1685,10 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
if (!slot.bone.active) return;
|
||||
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;
|
||||
if (slotAttachment !== attachment) {
|
||||
if (!(slotAttachment instanceof VertexAttachment)
|
||||
|| slotAttachment.timelineAttachment !== attachment) return;
|
||||
}
|
||||
|
||||
const sequence = (slotAttachment as unknown as HasTextureRegion).sequence;
|
||||
if (!sequence) return;
|
||||
if (!(isHasSequence(slotAttachment)) || slotAttachment.timelineAttachment !== attachment) return;
|
||||
|
||||
if (direction === MixDirection.out) {
|
||||
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 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];
|
||||
if (mode !== SequenceMode.hold) {
|
||||
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*/);
|
||||
}
|
||||
|
||||
switch (blend) {
|
||||
case MixBlend.setup: {
|
||||
const setup = constraint.data.setup;
|
||||
pose.mix = setup.mix + (mix - setup.mix) * alpha;
|
||||
pose.softness = setup.softness + (softness - setup.softness) * alpha;
|
||||
if (direction === MixDirection.out) {
|
||||
pose.bendDirection = setup.bendDirection;
|
||||
pose.compress = setup.compress;
|
||||
pose.stretch = setup.stretch;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
if (blend === MixBlend.setup) {
|
||||
const setup = constraint.data.setup;
|
||||
pose.mix = setup.mix + (mix - setup.mix) * alpha;
|
||||
pose.softness = setup.softness + (softness - setup.softness) * alpha;
|
||||
if (direction === MixDirection.out) {
|
||||
pose.bendDirection = setup.bendDirection;
|
||||
pose.compress = setup.compress;
|
||||
pose.stretch = setup.stretch;
|
||||
return;
|
||||
}
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
pose.mix += (mix - pose.mix) * alpha;
|
||||
pose.softness += (softness - pose.softness) * alpha;
|
||||
if (direction === MixDirection.out) return;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
pose.mix += mix * alpha;
|
||||
pose.softness += softness * alpha;
|
||||
if (direction === MixDirection.out) return;
|
||||
break;
|
||||
} else {
|
||||
pose.mix += (mix - pose.mix) * alpha;
|
||||
pose.softness += (softness - pose.softness) * alpha;
|
||||
if (direction === MixDirection.out) return;
|
||||
}
|
||||
pose.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
|
||||
pose.compress = frames[i + 4/*COMPRESS*/] !== 0;
|
||||
@ -2105,7 +2117,8 @@ export class PathConstraintSpacingTimeline extends ConstraintTimeline1 {
|
||||
const constraint = skeleton.constraints[this.constraintIndex];
|
||||
if (constraint.active) {
|
||||
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*/);
|
||||
}
|
||||
|
||||
switch (blend) {
|
||||
case MixBlend.setup: {
|
||||
const setup = constraint.data.setup;
|
||||
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
||||
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
||||
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
||||
break;
|
||||
}
|
||||
case MixBlend.first:
|
||||
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;
|
||||
if (blend === MixBlend.setup) {
|
||||
const setup = constraint.data.setup;
|
||||
pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha;
|
||||
pose.mixX = setup.mixX + (x - setup.mixX) * alpha;
|
||||
pose.mixY = setup.mixY + (y - setup.mixY) * alpha;
|
||||
} else {
|
||||
pose.mixRotate += (rotate - pose.mixRotate) * alpha;
|
||||
pose.mixX += (x - pose.mixX) * alpha;
|
||||
pose.mixY += (y - pose.mixY) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The base class for most {@link PhysicsConstraint} timelines. */
|
||||
export abstract class PhysicsConstraintTimeline extends ConstraintTimeline1 {
|
||||
additive = false;
|
||||
|
||||
/** @param constraintIndex -1 for all physics constraints in the skeleton. */
|
||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number, property: number) {
|
||||
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,
|
||||
direction: MixDirection, appliedPose: boolean) {
|
||||
|
||||
if (blend === MixBlend.add && !this.additive) blend = MixBlend.replace;
|
||||
if (this.constraintIndex === -1) {
|
||||
const value = time >= this.frames[0] ? this.getCurveValue(time) : 0;
|
||||
const constraints = skeleton.physics;
|
||||
@ -2323,6 +2329,7 @@ export class PhysicsConstraintMassTimeline extends PhysicsConstraintTimeline {
|
||||
export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintWind);
|
||||
this.additive = true;
|
||||
}
|
||||
|
||||
get (pose: PhysicsConstraintPose): number {
|
||||
@ -2342,6 +2349,7 @@ export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
||||
export class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, constraintIndex: number) {
|
||||
super(frameCount, bezierCount, constraintIndex, Property.physicsConstraintGravity);
|
||||
this.additive = true;
|
||||
}
|
||||
|
||||
get (pose: PhysicsConstraintPose): number {
|
||||
|
||||
@ -198,7 +198,7 @@ export class AnimationState {
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
const timeline = timelines[ii];
|
||||
if (timeline instanceof AttachmentTimeline)
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, false, attachments);
|
||||
else
|
||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false);
|
||||
}
|
||||
@ -215,7 +215,7 @@ export class AnimationState {
|
||||
if (!shortestRotation && timeline instanceof RotateTimeline) {
|
||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
|
||||
} else if (timeline instanceof AttachmentTimeline) {
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, false, attachments);
|
||||
} else {
|
||||
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
@ -286,7 +286,6 @@ export class AnimationState {
|
||||
from.totalAlpha = 0;
|
||||
for (let i = 0; i < timelineCount; i++) {
|
||||
const timeline = timelines[i];
|
||||
let direction = MixDirection.out;
|
||||
let timelineBlend: MixBlend;
|
||||
let alpha = 0;
|
||||
switch (timelineMode[i]) {
|
||||
@ -319,8 +318,9 @@ export class AnimationState {
|
||||
if (!shortestRotation && timeline instanceof RotateTimeline)
|
||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
||||
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 {
|
||||
let direction = MixDirection.out;
|
||||
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend === MixBlend.setup)
|
||||
@ -338,11 +338,13 @@ export class AnimationState {
|
||||
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];
|
||||
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)
|
||||
this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
|
||||
} else
|
||||
@ -965,7 +967,8 @@ export class TrackEntry {
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
loadSequence (name: string, basePath: string, sequence: Sequence) {
|
||||
protected findRegions (name: string, basePath: string, sequence: Sequence) {
|
||||
const regions = sequence.regions;
|
||||
for (let i = 0, n = regions.length; i < n; i++) {
|
||||
const path = 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})`);
|
||||
}
|
||||
for (let i = 0, n = regions.length; i < n; i++)
|
||||
regions[i] = this.findRegion(name, sequence.getPath(basePath, i));
|
||||
}
|
||||
|
||||
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 {
|
||||
const attachment = new RegionAttachment(name, path);
|
||||
if (sequence != null) {
|
||||
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;
|
||||
this.findRegions(name, path, sequence);
|
||||
return new RegionAttachment(name, sequence);
|
||||
}
|
||||
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
|
||||
const attachment = new MeshAttachment(name, path);
|
||||
if (sequence != null) {
|
||||
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;
|
||||
this.findRegions(name, path, sequence);
|
||||
return new MeshAttachment(name, sequence);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
a = pp.a;
|
||||
b = pp.b;
|
||||
|
||||
@ -462,7 +462,7 @@ export class Skeleton {
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
verticesLength = 8;
|
||||
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;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
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 type { Attachment, VertexAttachment } from "./attachments/Attachment.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 { Sequence, SequenceModeValues } from "./attachments/Sequence.js";
|
||||
import { BoneData } from "./BoneData.js";
|
||||
@ -261,9 +261,9 @@ export class SkeletonBinary {
|
||||
data.slot = skeletonData.slots[input.readInt(true)];
|
||||
const flags = input.readByte();
|
||||
data.skinRequired = (flags & 1) !== 0;
|
||||
data.positionMode = (flags >> 1) & 2;
|
||||
data.spacingMode = (flags >> 2) & 3;
|
||||
data.rotateMode = (flags >> 4) & 3;
|
||||
data.positionMode = (flags >> 1) & 0b1;
|
||||
data.spacingMode = (flags >> 2) & 0b11;
|
||||
data.rotateMode = (flags >> 4) & 0b11;
|
||||
if ((flags & 128) !== 0) data.offsetRotation = input.readFloat();
|
||||
const setup = data.setup;
|
||||
setup.position = input.readFloat();
|
||||
@ -375,7 +375,7 @@ export class SkeletonBinary {
|
||||
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
||||
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
linkedMesh.mesh.updateSequence();
|
||||
}
|
||||
this.linkedMeshes.length = 0;
|
||||
|
||||
@ -465,7 +465,7 @@ export class SkeletonBinary {
|
||||
case AttachmentType.Region: {
|
||||
let path = (flags & 16) !== 0 ? input.readStringRef() : null;
|
||||
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 x = input.readFloat();
|
||||
const y = input.readFloat();
|
||||
@ -486,8 +486,7 @@ export class SkeletonBinary {
|
||||
region.width = width * scale;
|
||||
region.height = height * scale;
|
||||
Color.rgba8888ToColor(region.color, color);
|
||||
region.sequence = sequence;
|
||||
if (region.region != null) region.updateRegion();
|
||||
region.updateSequence();
|
||||
return region;
|
||||
}
|
||||
case AttachmentType.BoundingBox: {
|
||||
@ -505,7 +504,7 @@ export class SkeletonBinary {
|
||||
case AttachmentType.Mesh: {
|
||||
let path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
||||
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 vertices = this.readVertices(input, (flags & 128) !== 0);
|
||||
const uvs = this.readFloatArray(input, vertices.length, 1);
|
||||
@ -523,26 +522,25 @@ export class SkeletonBinary {
|
||||
if (!mesh) return null;
|
||||
mesh.path = path;
|
||||
Color.rgba8888ToColor(mesh.color, color);
|
||||
mesh.hullLength = hullLength << 1;
|
||||
mesh.bones = vertices.bones;
|
||||
mesh.vertices = vertices.vertices;
|
||||
mesh.worldVerticesLength = vertices.length;
|
||||
mesh.triangles = triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
if (mesh.region != null) mesh.updateRegion();
|
||||
mesh.hullLength = hullLength << 1;
|
||||
mesh.sequence = sequence;
|
||||
mesh.triangles = triangles;
|
||||
if (nonessential) {
|
||||
mesh.edges = edges;
|
||||
mesh.width = width * scale;
|
||||
mesh.height = height * scale;
|
||||
}
|
||||
mesh.updateSequence();
|
||||
return mesh;
|
||||
}
|
||||
case AttachmentType.LinkedMesh: {
|
||||
const path = (flags & 16) !== 0 ? input.readStringRef() : name;
|
||||
if (path == null) throw new Error("Path of linked mesh must not be null");
|
||||
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 skinIndex = input.readInt(true);
|
||||
const parent = input.readStringRef();
|
||||
@ -556,7 +554,6 @@ export class SkeletonBinary {
|
||||
if (!mesh) return null;
|
||||
mesh.path = path;
|
||||
Color.rgba8888ToColor(mesh.color, color);
|
||||
mesh.sequence = sequence;
|
||||
if (nonessential) {
|
||||
mesh.width = width * scale;
|
||||
mesh.height = height * scale;
|
||||
@ -616,8 +613,9 @@ export class SkeletonBinary {
|
||||
}
|
||||
}
|
||||
|
||||
private readSequence (input: BinaryInput) {
|
||||
const sequence = new Sequence(input.readInt(true));
|
||||
private readSequence (input: BinaryInput, hasPathSuffix: boolean) {
|
||||
if (!hasPathSuffix) return new Sequence(1, false);
|
||||
const sequence = new Sequence(input.readInt(true), true);
|
||||
sequence.start = input.readInt(true);
|
||||
sequence.digits = input.readInt(true);
|
||||
sequence.setupIndex = input.readInt(true);
|
||||
@ -632,19 +630,20 @@ export class SkeletonBinary {
|
||||
if (!weighted)
|
||||
return new Vertices(null, this.readFloatArray(input, length, scale), length);
|
||||
|
||||
const n = input.readInt(true);
|
||||
const bones: number[] = [];
|
||||
const weights: number[] = [];
|
||||
const bonesArray: number[] = [];
|
||||
for (let i = 0; i < vertexCount; i++) {
|
||||
for (let b = 0, w = 0; b < n;) {
|
||||
const boneCount = input.readInt(true);
|
||||
bonesArray.push(boneCount);
|
||||
for (let ii = 0; ii < boneCount; ii++) {
|
||||
bonesArray.push(input.readInt(true));
|
||||
weights.push(input.readFloat() * scale);
|
||||
weights.push(input.readFloat() * scale);
|
||||
weights.push(input.readFloat());
|
||||
bones[b++] = boneCount;
|
||||
for (let ii = 0; ii < boneCount; ii++, w += 3) {
|
||||
bones[b++] = input.readInt(true);
|
||||
weights[w] = input.readFloat() * scale;
|
||||
weights[w + 1] = input.readFloat() * scale;
|
||||
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[] {
|
||||
@ -1115,7 +1114,7 @@ export class SkeletonBinary {
|
||||
break;
|
||||
}
|
||||
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++) {
|
||||
const time = input.readFloat();
|
||||
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 type { Attachment, VertexAttachment } from "./attachments/Attachment.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 { Sequence, SequenceMode } from "./attachments/Sequence.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}`);
|
||||
linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
linkedMesh.mesh.updateSequence();
|
||||
}
|
||||
this.linkedMeshes.length = 0;
|
||||
|
||||
@ -528,12 +528,11 @@ export class SkeletonJson {
|
||||
region.rotation = getValue(map, "rotation", 0);
|
||||
region.width = map.width * scale;
|
||||
region.height = map.height * scale;
|
||||
region.sequence = sequence;
|
||||
|
||||
const color: string = getValue(map, "color", null);
|
||||
if (color) region.color.setFromString(color);
|
||||
|
||||
if (region.region != null) region.updateRegion();
|
||||
region.updateSequence();
|
||||
return region;
|
||||
}
|
||||
case "boundingbox": {
|
||||
@ -557,7 +556,6 @@ export class SkeletonJson {
|
||||
|
||||
mesh.width = getValue(map, "width", 0) * scale;
|
||||
mesh.height = getValue(map, "height", 0) * scale;
|
||||
mesh.sequence = sequence;
|
||||
|
||||
const parent: string = getValue(map, "parent", null);
|
||||
if (parent) {
|
||||
@ -569,10 +567,10 @@ export class SkeletonJson {
|
||||
this.readVertices(map, mesh, uvs.length);
|
||||
mesh.triangles = map.triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
if (mesh.region != null) mesh.updateRegion();
|
||||
|
||||
mesh.edges = getValue(map, "edges", null);
|
||||
mesh.hullLength = getValue(map, "hull", 0) * 2;
|
||||
mesh.updateSequence();
|
||||
return mesh;
|
||||
}
|
||||
case "path": {
|
||||
@ -623,8 +621,8 @@ export class SkeletonJson {
|
||||
}
|
||||
|
||||
readSequence (map: object) {
|
||||
if (map == null) return null;
|
||||
const sequence = new Sequence(getValue(map, "count", 0));
|
||||
if (map == null) return new Sequence(1, false);
|
||||
const sequence = new Sequence(getValue(map, "count", 0), true);
|
||||
sequence.start = getValue(map, "start", 1);
|
||||
sequence.digits = getValue(map, "digits", 0);
|
||||
sequence.setupIndex = getValue(map, "setup", 0);
|
||||
@ -1155,7 +1153,7 @@ export class SkeletonJson {
|
||||
}
|
||||
timelines.push(timeline);
|
||||
} 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;
|
||||
for (let frame = 0; frame < timelineMap.length; frame++) {
|
||||
const delay = getValue(keyMap, "delay", lastDelay);
|
||||
|
||||
@ -55,8 +55,8 @@ export class SkeletonRendererCore {
|
||||
continue;
|
||||
}
|
||||
|
||||
const slotApplied = slot.applied;
|
||||
const slotColor = slotApplied.color;
|
||||
const pose = slot.applied;
|
||||
const slotColor = pose.color;
|
||||
const alpha = slotColor.a;
|
||||
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
||||
clipper.clipEnd(slot);
|
||||
@ -80,13 +80,16 @@ export class SkeletonRendererCore {
|
||||
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;
|
||||
verticesCount = 4;
|
||||
uvs = attachment.uvs as Float32Array;
|
||||
uvs = sequence.getUVs(sequenceIndex);
|
||||
indices = this.quadIndices;
|
||||
indicesCount = 6;
|
||||
texture = attachment.region?.texture;
|
||||
texture = sequence.regions[sequenceIndex]?.texture;
|
||||
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
attachmentColor = attachment.color;
|
||||
@ -102,10 +105,14 @@ export class SkeletonRendererCore {
|
||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride);
|
||||
vertices = this.worldVertices;
|
||||
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;
|
||||
indicesCount = indices.length;
|
||||
texture = attachment.region?.texture;
|
||||
texture = sequence.regions[sequenceIndex]?.texture;
|
||||
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
clipper.clipStart(skeleton, slot, attachment);
|
||||
@ -133,8 +140,8 @@ export class SkeletonRendererCore {
|
||||
}
|
||||
|
||||
darkColor = 0xff000000;
|
||||
if (slotApplied.darkColor) {
|
||||
const { r, g, b } = slotApplied.darkColor;
|
||||
if (pose.darkColor) {
|
||||
const { r, g, b } = pose.darkColor;
|
||||
darkColor = 0xff000000 |
|
||||
(Math.floor(r * a) << 16) |
|
||||
(Math.floor(g * a) << 8) |
|
||||
@ -156,8 +163,8 @@ export class SkeletonRendererCore {
|
||||
}
|
||||
|
||||
darkColor = 0;
|
||||
if (slotApplied.darkColor) {
|
||||
const { r, g, b } = slotApplied.darkColor;
|
||||
if (pose.darkColor) {
|
||||
const { r, g, b } = pose.darkColor;
|
||||
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 {
|
||||
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) {
|
||||
if (!name) throw new Error("name cannot be null.");
|
||||
this.name = name;
|
||||
this.timelineAttachment = this;
|
||||
}
|
||||
|
||||
abstract copy (): Attachment;
|
||||
@ -65,10 +70,6 @@ export abstract class VertexAttachment extends Attachment {
|
||||
* {@link computeWorldVertices} using the `count` parameter. */
|
||||
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) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@ -42,10 +42,10 @@ import type { Sequence } from "./Sequence.js";
|
||||
* Runtimes Guide. */
|
||||
export interface AttachmentLoader {
|
||||
/** @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. */
|
||||
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. */
|
||||
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
|
||||
|
||||
@ -27,24 +27,20 @@
|
||||
* 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 { Sequence } from "./Sequence.js"
|
||||
|
||||
export interface HasTextureRegion {
|
||||
/** The name used to find the {@link #region()}. */
|
||||
path: string;
|
||||
export function isHasSequence (obj: unknown): obj is HasSequence {
|
||||
return !!obj && typeof obj === "object" && "sequence" in obj && "updateSequence" in obj;
|
||||
}
|
||||
|
||||
/** The region used to draw the attachment. After setting the region or if the region's properties are changed,
|
||||
* {@link #updateRegion()} must be called. */
|
||||
region: TextureRegion | null;
|
||||
export interface HasSequence {
|
||||
path?: string;
|
||||
|
||||
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
|
||||
* {@link #getRegion()} or if the region's properties are changed. */
|
||||
updateRegion (): void;
|
||||
|
||||
/** The color to tint the attachment. */
|
||||
color: Color;
|
||||
|
||||
sequence: Sequence | 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import type { Skeleton } from "src/Skeleton.js";
|
||||
import type { Slot } from "../Slot.js";
|
||||
import type { TextureRegion } from "../Texture.js";
|
||||
import { TextureAtlasRegion } from "../TextureAtlas.js";
|
||||
import { Color, type NumberArrayLike, Utils } from "../Utils.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";
|
||||
|
||||
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
|
||||
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
|
||||
*
|
||||
* See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
|
||||
export class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
||||
region: TextureRegion | null = null;
|
||||
|
||||
/** The name of the texture region for this attachment. */
|
||||
path: string;
|
||||
export class MeshAttachment extends VertexAttachment implements HasSequence {
|
||||
readonly sequence: Sequence;
|
||||
|
||||
/** The UV pair for each vertex, normalized within the texture region. */
|
||||
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. */
|
||||
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. */
|
||||
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. */
|
||||
width: number = 0;
|
||||
|
||||
/** The height of the mesh's image. Available only when nonessential data was exported. */
|
||||
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);
|
||||
|
||||
constructor (name: string, path: string) {
|
||||
constructor (name: string, sequence: Sequence) {
|
||||
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
|
||||
* the {@link #regionUVs} are changed. */
|
||||
updateRegion () {
|
||||
if (!this.region) throw new Error("Region not set.");
|
||||
const regionUVs = this.regionUVs;
|
||||
if (!this.uvs || this.uvs.length !== regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
|
||||
const uvs = this.uvs;
|
||||
const n = this.uvs.length;
|
||||
let u = this.region.u, v = this.region.v, width = 0, height = 0;
|
||||
if (this.region instanceof TextureAtlasRegion) {
|
||||
const region = this.region, page = region.page;
|
||||
copy (): Attachment {
|
||||
if (this.parentMesh) return this.newLinkedMesh();
|
||||
|
||||
const copy = new MeshAttachment(this.name, this.sequence.copy());
|
||||
copy.path = this.path;
|
||||
copy.color.setFromColor(this.color);
|
||||
|
||||
this.copyTo(copy);
|
||||
copy.regionUVs = [];
|
||||
Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
|
||||
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;
|
||||
switch (region.degrees) {
|
||||
case 90:
|
||||
@ -133,12 +182,12 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
||||
width = region.originalWidth / textureWidth;
|
||||
height = region.originalHeight / textureHeight;
|
||||
}
|
||||
} else if (!this.region) {
|
||||
} else if (!region) {
|
||||
u = v = 0;
|
||||
width = height = 1;
|
||||
} else {
|
||||
width = this.region.u2 - u;
|
||||
height = this.region.v2 - v;
|
||||
width = region.u2 - u;
|
||||
height = region.v2 - v;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/** 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import type { SlotPose } from "src/SlotPose.js";
|
||||
import type { Slot } from "../Slot.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 type { HasTextureRegion } from "./HasTextureRegion.js";
|
||||
import type { HasSequence } from "./HasSequence.js";
|
||||
import type { Sequence } from "./Sequence.js";
|
||||
|
||||
/** An attachment that displays a textured quadrilateral.
|
||||
*
|
||||
* See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
|
||||
export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
export class RegionAttachment extends Attachment implements HasSequence {
|
||||
readonly sequence: Sequence;
|
||||
|
||||
/** The local x translation. */
|
||||
x = 0;
|
||||
|
||||
@ -59,44 +62,95 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
/** The height of the region attachment in Spine. */
|
||||
height = 0;
|
||||
|
||||
/** The name of the texture region for this attachment. */
|
||||
path?: string;
|
||||
|
||||
/** The color to tint the region attachment. */
|
||||
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);
|
||||
|
||||
constructor (name: string, path: string) {
|
||||
constructor (name: string, sequence: Sequence) {
|
||||
super(name);
|
||||
this.path = path;
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
|
||||
updateRegion (): void {
|
||||
if (!this.region) throw new Error("Region not set.");
|
||||
const region = this.region;
|
||||
const uvs = this.uvs;
|
||||
const regionScaleX = this.width / this.region.originalWidth * this.scaleX;
|
||||
const regionScaleY = this.height / this.region.originalHeight * this.scaleY;
|
||||
const localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
|
||||
const localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
|
||||
const localX2 = localX + this.region.width * regionScaleX;
|
||||
const localY2 = localY + this.region.height * regionScaleY;
|
||||
const radians = this.rotation * MathUtils.degRad;
|
||||
copy (): Attachment {
|
||||
const copy = new RegionAttachment(this.name, this.sequence.copy());
|
||||
copy.path = this.path;
|
||||
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;
|
||||
copy.color.setFromColor(this.color);
|
||||
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 sin = Math.sin(radians);
|
||||
const x = this.x, y = this.y;
|
||||
const localXCos = localX * cos + x;
|
||||
const localXSin = localX * sin;
|
||||
const localYCos = localY * cos + y;
|
||||
@ -105,7 +159,6 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
const localX2Sin = localX2 * sin;
|
||||
const localY2Cos = localY2 * cos + y;
|
||||
const localY2Sin = localY2 * sin;
|
||||
const offset = this.offset;
|
||||
offset[0] = localXCos - localYSin;
|
||||
offset[1] = localYCos + localXSin;
|
||||
offset[2] = localXCos - localY2Sin;
|
||||
@ -124,85 +177,25 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
uvs[5] = 1;
|
||||
uvs[6] = 1;
|
||||
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 {
|
||||
uvs[0] = region.u;
|
||||
uvs[1] = region.v2;
|
||||
uvs[2] = region.u;
|
||||
uvs[3] = region.v;
|
||||
uvs[4] = region.u2;
|
||||
uvs[5] = region.v;
|
||||
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 Y1 = 1;
|
||||
static C1R = 2;
|
||||
|
||||
@ -29,45 +29,103 @@
|
||||
|
||||
import type { SlotPose } from "src/SlotPose.js";
|
||||
import type { TextureRegion } from "../Texture.js";
|
||||
import { Utils } from "../Utils.js";
|
||||
import type { HasTextureRegion } from "./HasTextureRegion.js";
|
||||
|
||||
import { type NumberArrayLike, Utils } from "../Utils.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 {
|
||||
private static _nextID = 0;
|
||||
|
||||
id = Sequence.nextID();
|
||||
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;
|
||||
digits = 0;
|
||||
/** The index of the region to show for the setup pose. */
|
||||
setupIndex = 0;
|
||||
|
||||
constructor (count: number) {
|
||||
constructor (count: number, pathSuffix: boolean) {
|
||||
this.regions = new Array<TextureRegion>(count);
|
||||
this.pathSuffix = pathSuffix;
|
||||
}
|
||||
|
||||
copy (): Sequence {
|
||||
const copy = new Sequence(this.regions.length);
|
||||
Utils.arrayCopy(this.regions, 0, copy.regions, 0, this.regions.length);
|
||||
const regionCount = this.regions.length;
|
||||
const copy = new Sequence(regionCount, this.pathSuffix);
|
||||
Utils.arrayCopy(this.regions, 0, copy.regions, 0, regionCount);
|
||||
copy.start = this.start;
|
||||
copy.digits = this.digits;
|
||||
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;
|
||||
}
|
||||
|
||||
apply (slot: SlotPose, attachment: HasTextureRegion) {
|
||||
let index = slot.sequenceIndex;
|
||||
if (index === -1) index = this.setupIndex;
|
||||
if (index >= this.regions.length) index = this.regions.length - 1;
|
||||
const region = this.regions[index];
|
||||
if (attachment.region !== region) {
|
||||
attachment.region = region;
|
||||
attachment.updateRegion();
|
||||
/** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are
|
||||
* changed. */
|
||||
public update (attachment: HasSequence) {
|
||||
const regionCount = this.regions.length;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
this.uvs = [];
|
||||
this.offsets = [];
|
||||
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 {
|
||||
if (!this.pathSuffix) return basePath;
|
||||
let result = basePath;
|
||||
const frame = (this.start + index).toString();
|
||||
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/BoundingBoxAttachment.js';
|
||||
export * from './attachments/ClippingAttachment.js';
|
||||
export * from './attachments/HasTextureRegion.js';
|
||||
export * from './attachments/HasSequence.js';
|
||||
export * from './attachments/MeshAttachment.js';
|
||||
export * from './attachments/PathAttachment.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;
|
||||
|
||||
if (slotObject.visible) {
|
||||
let applied = slot.bone.applied;
|
||||
const applied = slot.bone.applied;
|
||||
|
||||
const matrix = slotObject.localTransform;
|
||||
matrix.a = applied.a;
|
||||
@ -659,10 +659,14 @@ export class Spine extends Container {
|
||||
const region = attachment;
|
||||
attachmentColor = region.color;
|
||||
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;
|
||||
uvs = region.uvs;
|
||||
texture = <SpineTexture>region.region?.texture;
|
||||
uvs = sequence.getUVs(sequenceIndex);
|
||||
texture = sequence.regions[sequenceIndex]?.texture as SpineTexture;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
const mesh = attachment;
|
||||
attachmentColor = mesh.color;
|
||||
@ -672,8 +676,12 @@ export class Spine extends Container {
|
||||
}
|
||||
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, this.verticesCache, 0, vertexSize);
|
||||
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) {
|
||||
Spine.clipper.clipStart(skeleton, slot, attachment);
|
||||
pixiMaskSource = { slot, computed: false };
|
||||
|
||||
@ -345,11 +345,9 @@ export class SpineDebugRenderer implements ISpineDebugRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
const regionAttachment = attachment;
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,6 @@ import {
|
||||
Container,
|
||||
type ContainerOptions,
|
||||
type DestroyOptions,
|
||||
fastCopy,
|
||||
Graphics,
|
||||
type PointData,
|
||||
Texture,
|
||||
@ -66,6 +65,7 @@ import {
|
||||
ViewContainer,
|
||||
} from 'pixi.js';
|
||||
import type { ISpineDebugRenderer } from './SpineDebugRenderer.js';
|
||||
import type { SpineTexture } from './SpineTexture.js';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const cacheData = this._getCachedData(slot, attachment);
|
||||
|
||||
const sequence = attachment.sequence;
|
||||
const sequenceIndex = sequence.resolveIndex(pose);
|
||||
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
attachment.computeWorldVertices(slot, cacheData.vertices, 0, 2);
|
||||
attachment.computeWorldVertices(slot, attachment.getOffsets(pose), cacheData.vertices, 0, 2);
|
||||
}
|
||||
else {
|
||||
attachment.computeWorldVertices(
|
||||
@ -677,13 +680,7 @@ export class Spine extends ViewContainer {
|
||||
);
|
||||
}
|
||||
|
||||
// sequences uvs are known only after computeWorldVertices is invoked
|
||||
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);
|
||||
cacheData.uvs = sequence.getUVs(sequenceIndex);
|
||||
|
||||
const skeletonColor = skeleton.color;
|
||||
const slotColor = pose.color;
|
||||
@ -708,7 +705,7 @@ export class Spine extends ViewContainer {
|
||||
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) {
|
||||
cacheData.texture = texture;
|
||||
@ -834,33 +831,35 @@ export class Spine extends ViewContainer {
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
vertices = new Float32Array(8);
|
||||
|
||||
const sequence = attachment.sequence;
|
||||
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
||||
id: `${slot.data.index}-${attachment.name}`,
|
||||
vertices,
|
||||
clipped: false,
|
||||
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),
|
||||
darkColor: new Color(0, 0, 0, 0),
|
||||
darkTint: this.darkTint,
|
||||
skipRender: false,
|
||||
texture: attachment.region?.texture.texture,
|
||||
texture: (sequence.regions[0]?.texture as SpineTexture)?.texture,
|
||||
};
|
||||
}
|
||||
else {
|
||||
vertices = new Float32Array(attachment.worldVerticesLength);
|
||||
|
||||
const sequence = attachment.sequence;
|
||||
this.attachmentCacheData[slot.data.index][attachment.name] = {
|
||||
id: `${slot.data.index}-${attachment.name}`,
|
||||
vertices,
|
||||
clipped: false,
|
||||
indices: attachment.triangles,
|
||||
uvs: new Float32Array(attachment.uvs.length),
|
||||
uvs: new Float32Array(sequence.getUVs(0).length),
|
||||
color: new Color(1, 1, 1, 1),
|
||||
darkColor: new Color(0, 0, 0, 0),
|
||||
darkTint: this.darkTint,
|
||||
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;
|
||||
}
|
||||
|
||||
const regionAttachment = attachment;
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
|
||||
@ -216,8 +216,10 @@ export class SpinePipe implements RenderPipe<Spine> {
|
||||
|
||||
if (!cacheData.skipRender) {
|
||||
const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id];
|
||||
// we didn't figure out why batchableSpineSlot might be undefined: https://github.com/EsotericSoftware/spine-runtimes/issues/2991
|
||||
batchableSpineSlot?._batcher?.updateElement(batchableSpineSlot);
|
||||
if (batchableSpineSlot) {
|
||||
batchableSpineSlot.uvs = cacheData.uvs;
|
||||
batchableSpineSlot._batcher?.updateElement(batchableSpineSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,10 +246,14 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
attachmentColor = attachment.color;
|
||||
vertices = this.vertices;
|
||||
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;
|
||||
uvs = attachment.uvs;
|
||||
texture = <ThreeJsTexture>attachment.region?.texture;
|
||||
uvs = sequence.getUVs(sequenceIndex);
|
||||
texture = sequence.regions[sequenceIndex]?.texture;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
attachmentColor = attachment.color;
|
||||
vertices = this.vertices;
|
||||
@ -267,8 +271,12 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
vertexSize
|
||||
);
|
||||
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) {
|
||||
clipper.clipEnd(slot);
|
||||
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
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
const regionAttachment = <RegionAttachment>attachment;
|
||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
||||
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.applied), vertices, 0, 2);
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
const mesh = <MeshAttachment>attachment;
|
||||
mesh.computeWorldVertices(this.skeleton as Skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, 2);
|
||||
hullLength = mesh.hullLength;
|
||||
attachment.computeWorldVertices(this.skeleton as Skeleton, slot, 0, attachment.worldVerticesLength, vertices, 0, 2);
|
||||
hullLength = attachment.hullLength;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
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[2], vertices[3], vertices[4], vertices[5]);
|
||||
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.
|
||||
*****************************************************************************/
|
||||
|
||||
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 { PolygonBatcher } from "./PolygonBatcher.js";
|
||||
import type { ManagedWebGLRenderingContext } from "./WebGL.js";
|
||||
@ -102,10 +102,14 @@ export class SkeletonRenderer {
|
||||
renderable.vertices = this.vertices;
|
||||
renderable.numVertices = 4;
|
||||
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;
|
||||
uvs = attachment.uvs;
|
||||
texture = (attachment.region as TextureRegion).texture as GLTexture;
|
||||
uvs = sequence.getUVs(sequenceIndex);
|
||||
texture = sequence.regions[sequenceIndex]?.texture as GLTexture;
|
||||
attachmentColor = attachment.color;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
renderable.vertices = this.vertices;
|
||||
@ -117,8 +121,12 @@ export class SkeletonRenderer {
|
||||
}
|
||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, renderable.vertices, 0, vertexSize);
|
||||
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;
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
clipper.clipEnd(slot);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user