支持spine4.3

This commit is contained in:
tianmo committed 2026-09-02 13:53:12 +08:00
1 parent 63e4f61631
commit 0656684a92
206 files changed
+100597 -1320

No files matched your search

+483
View File
@@ -0,0 +1,483 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { ClippingAttachment } from "./attachments/ClippingAttachment.js";
import { MeshAttachment } from "./attachments/MeshAttachment.js";
import { RegionAttachment } from "./attachments/RegionAttachment.js";
import { Bone } from "./Bone.js";
import { DrawOrder } from "./DrawOrder.js";
import { PhysicsConstraint } from "./PhysicsConstraint.js";
import { Slot } from "./Slot.js";
import { Color, Utils, Vector2 } from "./Utils.js";
/** Stores bones and slots to be posed by animations and application code. Multiple skeleton instances can share the same
* {@link SkeletonData}, including animations, attachments, and skins.
*
* After posing, call {@link updateWorldTransform} to apply constraints and compute world transforms for rendering.
*
* See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */
export class Skeleton {
static quadTriangles = [0, 1, 2, 2, 3, 0];
static yDown = false;
static get yDir() {
return Skeleton.yDown ? -1 : 1;
}
/** The skeleton's setup pose data. */
data;
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
bones;
/** The skeleton's slots. To add a slot, also add it to {@link DrawOrder.pose}. */
slots;
/** The skeleton's draw order. Use {@link DrawOrder.appliedPose} for rendering and {@link DrawOrder.pose} for changing the draw
* order. */
drawOrder;
/** The skeleton's constraints. */
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
constraints;
/** The skeleton's physics constraints. */
physics;
/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link updateCache}. */
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
_updateCache = [];
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
resetCache = [];
/** The skeleton's current skin. May be null. */
skin = null;
/** The color to tint all the skeleton's attachments. */
color;
/** Scales the entire skeleton on the X axis.
*
* Bones that do not inherit scale are still affected by this property. */
scaleX = 1;
_scaleY = 1;
/** Scales the entire skeleton on the Y axis.
*
* Bones that do not inherit scale are still affected by this property. */
get scaleY() {
return this._scaleY * Skeleton.yDir;
}
set scaleY(scaleY) {
this._scaleY = scaleY;
}
/** Sets the skeleton X position, which is added to the root bone worldX position.
*
* Bones that do not inherit translation are still affected by this property. */
x = 0;
/** Sets the skeleton Y position, which is added to the root bone worldY position.
*
* Bones that do not inherit translation are still affected by this property. */
y = 0;
/** Returns the skeleton's time, is used for time-based manipulations, such as {@link PhysicsConstraint}.
*
* See {@link _update}. */
time = 0;
/** The x component of a vector that defines the direction {@link PhysicsConstraintPose.wind} is applied. */
windX = 1;
/** The y component of a vector that defines the direction {@link PhysicsConstraintPose.wind} is applied. */
windY = 0;
/** The x component of a vector that defines the direction {@link PhysicsConstraintPose.gravity} is applied. */
gravityX = 0;
/** The y component of a vector that defines the direction {@link PhysicsConstraintPose.gravity} is applied. */
gravityY = 1;
_update = 0;
constructor(data) {
if (!data)
throw new Error("data cannot be null.");
this.data = data;
this.bones = [];
for (let i = 0; i < data.bones.length; i++) {
const boneData = data.bones[i];
let bone;
if (!boneData.parent)
bone = new Bone(boneData, null);
else {
const parent = this.bones[boneData.parent.index];
bone = new Bone(boneData, parent);
parent.children.push(bone);
}
this.bones.push(bone);
}
this.slots = [];
for (const slotData of this.data.slots)
this.slots.push(new Slot(slotData, this));
this.drawOrder = new DrawOrder(this.slots);
this.physics = [];
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
this.constraints = [];
for (const constraintData of this.data.constraints) {
const constraint = constraintData.create(this);
if (constraint instanceof PhysicsConstraint)
this.physics.push(constraint);
this.constraints.push(constraint);
}
this.color = new Color(1, 1, 1, 1);
this.updateCache();
}
/** Caches information about bones and constraints. Must be called if the {@link skin} is modified or if bones, constraints,
* or weighted path attachments are added or removed. */
updateCache() {
this._updateCache.length = 0;
this.resetCache.length = 0;
this.drawOrder.unconstrained();
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
slots[i].unconstrained();
const bones = this.bones;
const boneCount = bones.length;
for (let i = 0, n = boneCount; i < n; i++) {
const bone = bones[i];
bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted;
bone.unconstrained();
}
if (this.skin) {
const skinBones = this.skin.bones;
for (let i = 0, n = this.skin.bones.length; i < n; i++) {
let bone = this.bones[skinBones[i].index];
do {
bone.sorted = false;
bone.active = true;
bone = bone.parent;
} while (bone);
}
}
const constraints = this.constraints;
let n = this.constraints.length;
for (let i = 0; i < n; i++)
constraints[i].unconstrained();
for (let i = 0; i < n; i++) {
const constraint = constraints[i];
constraint.active = constraint.isSourceActive()
// biome-ignore lint/complexity/useOptionalChain: changing to this might return undefined
&& (!constraint.data.skinRequired || (this.skin != null && this.skin.constraints.includes(constraint.data)));
if (constraint.active)
constraint.sort(this);
}
for (let i = 0; i < boneCount; i++)
this.sortBone(bones[i]);
n = this._updateCache.length;
for (let i = 0; i < n; i++) {
const updateable = this._updateCache[i];
if (updateable instanceof Bone)
this._updateCache[i] = updateable.appliedPose;
}
}
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
constrained(object) {
if (object.pose === object.appliedPose) {
object.constrained();
this.resetCache.push(object);
}
}
sortBone(bone) {
if (bone.sorted || !bone.active)
return;
const parent = bone.parent;
if (parent)
this.sortBone(parent);
bone.sorted = true;
this._updateCache.push(bone);
}
sortReset(bones) {
for (let i = 0, n = bones.length; i < n; i++) {
const bone = bones[i];
if (bone.active) {
if (bone.sorted)
this.sortReset(bone.children);
bone.sorted = false;
}
}
}
/** Updates the world transform for each bone and applies all constraints.
*
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
updateWorldTransform(physics) {
this._update++;
if (this.drawOrder.appliedPose === this.drawOrder.constrainedPose)
this.drawOrder.resetConstrained();
const resetCache = this.resetCache;
for (let i = 0, n = this.resetCache.length; i < n; i++)
resetCache[i].resetConstrained();
const updateCache = this._updateCache;
for (let i = 0, n = this._updateCache.length; i < n; i++)
updateCache[i].update(this, physics);
}
/** Sets the bones, constraints, and slots to their setup pose values. */
setupPose() {
this.setupPoseBones();
this.setupPoseSlots();
}
/** Sets the bones and constraints to their setup pose values. */
setupPoseBones() {
const bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
bones[i].setupPose();
const constraints = this.constraints;
for (let i = 0, n = constraints.length; i < n; i++)
constraints[i].setupPose();
}
/** Sets the slots and draw order to their setup pose values. */
setupPoseSlots() {
this.drawOrder.setupPose();
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
slots[i].setupPose();
}
/** Returns the root bone, or null if the skeleton has no bones. */
getRootBone() {
if (this.bones.length === 0)
return null;
return this.bones[0];
}
/** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
* repeatedly. */
findBone(boneName) {
if (!boneName)
throw new Error("boneName cannot be null.");
const bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
if (bones[i].data.name === boneName)
return bones[i];
return null;
}
/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
* repeatedly. */
findSlot(slotName) {
if (!slotName)
throw new Error("slotName cannot be null.");
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
if (slots[i].data.name === slotName)
return slots[i];
return null;
}
setSkin(newSkin) {
if (typeof newSkin === "string")
this.setSkinByName(newSkin);
else
this.setSkinBySkin(newSkin);
}
;
setSkinByName(skinName) {
const skin = this.data.findSkin(skinName);
if (!skin)
throw new Error(`Skin not found: ${skinName}`);
this.setSkin(skin);
}
setSkinBySkin(newSkin) {
if (newSkin === this.skin)
return;
if (newSkin) {
if (this.skin)
newSkin.attachAll(this, this.skin);
else {
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
const slot = slots[i];
const name = slot.data.attachmentName;
if (name) {
const attachment = newSkin.getAttachment(i, name);
if (attachment)
slot.pose.setAttachment(attachment);
}
}
}
}
this.skin = newSkin;
this.updateCache();
}
getAttachment(slotNameOrIndex, placeholder) {
if (typeof slotNameOrIndex === 'string')
return this.getAttachmentByName(slotNameOrIndex, placeholder);
return this.getAttachmentByIndex(slotNameOrIndex, placeholder);
}
/** Finds an attachment by looking in the {@link skin} and {@link SkeletonData.defaultSkin} using the slot name and attachment
* name.
*
* See {@link getAttachment}.
* @returns May be null. */
getAttachmentByName(slotName, placeholder) {
const slot = this.data.findSlot(slotName);
if (!slot)
throw new Error(`Can't find slot with name ${slotName}`);
return this.getAttachment(slot.index, placeholder);
}
/** Finds an attachment by looking in the {@link skin} and {@link SkeletonData.defaultSkin} using the slot index and
* attachment name. First the skin is checked and if the attachment was not found, the default skin is checked.
*
* See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide.
* @returns May be null. */
getAttachmentByIndex(slotIndex, placeholder) {
if (!placeholder)
throw new Error("placeholder cannot be null.");
if (this.skin) {
const attachment = this.skin.getAttachment(slotIndex, placeholder);
if (attachment)
return attachment;
}
if (this.data.defaultSkin)
return this.data.defaultSkin.getAttachment(slotIndex, placeholder);
return null;
}
/** A convenience method to set an attachment by finding the slot with {@link findSlot}, finding the attachment with
* {@link getAttachment}, then setting the slot's {@link Slot.attachment}.
* @param placeholder May be null to clear the slot's attachment. */
setAttachment(slotName, placeholder) {
if (!slotName)
throw new Error("slotName cannot be null.");
const slot = this.findSlot(slotName);
if (!slot)
throw new Error(`Slot not found: ${slotName}`);
let attachment = null;
if (placeholder) {
attachment = this.getAttachment(slot.data.index, placeholder);
if (!attachment)
throw new Error(`Attachment not found: ${placeholder}, for slot: ${slotName}`);
}
slot.pose.setAttachment(attachment);
}
/** Finds a constraint of the specified type by comparing each constraints's name. It is more efficient to cache the results of
* this method than to call it multiple times. */
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
findConstraint(constraintName, type) {
if (constraintName == null)
throw new Error("constraintName cannot be null.");
if (type == null)
throw new Error("type cannot be null.");
const constraints = this.constraints;
for (let i = 0, n = constraints.length; i < n; i++) {
const constraint = constraints[i];
if (constraint instanceof type && constraint.data.name === constraintName)
return constraint;
}
return null;
}
/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the applied pose.
* @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB.
* @param size An output value, the width and height of the AABB.
* @param temp Working memory to temporarily store attachments' computed world vertices. */
getBoundsRect(clipper) {
const offset = new Vector2();
const size = new Vector2();
this.getBounds(offset, size, undefined, clipper);
return { x: offset.x, y: offset.y, width: size.x, height: size.y };
}
/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the applied pose. Optionally applies
* clipping.
* @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB.
* @param size An output value, the width and height of the AABB.
* @param temp Working memory to temporarily store attachments' computed world vertices.
* @param clipper {@link SkeletonClipping} to use. If `null`, no clipping is applied. */
getBounds(offset, size, temp = new Array(2), clipper = null) {
if (!offset)
throw new Error("offset cannot be null.");
if (!size)
throw new Error("size cannot be null.");
const drawOrder = this.drawOrder.appliedPose;
const slots = drawOrder;
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = slots[i];
if (!slot.bone.active)
continue;
let verticesLength = 0;
let vertices = null;
let triangles = null;
const attachment = slot.appliedPose.attachment;
if (attachment) {
if (attachment instanceof RegionAttachment) {
verticesLength = 8;
vertices = Utils.setArraySize(temp, verticesLength, 0);
attachment.computeWorldVertices(slot, attachment.getOffsets(slot.appliedPose), vertices, 0, 2);
triangles = Skeleton.quadTriangles;
}
else if (attachment instanceof MeshAttachment) {
verticesLength = attachment.worldVerticesLength;
vertices = Utils.setArraySize(temp, verticesLength, 0);
attachment.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2);
triangles = attachment.triangles;
}
else if (attachment instanceof ClippingAttachment && clipper) {
clipper.clipEnd(slot);
clipper.clipStart(this, slot, attachment);
continue;
}
if (vertices && triangles) {
if (clipper?.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) {
vertices = clipper.clippedVertices;
verticesLength = clipper.clippedVertices.length;
}
for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) {
const x = vertices[ii], y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
if (clipper)
clipper.clipEnd(slot);
}
if (clipper)
clipper.clipEnd();
offset.set(minX, minY);
size.set(maxX - minX, maxY - minY);
}
/** Scales the entire skeleton on the X and Y axes.
*
* Bones that do not inherit scale are still affected by this property. */
setScale(scaleX, scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/** Sets the skeleton X and Y position, which is added to the root bone worldX and worldY position.
*
* Bones that do not inherit translation are still affected by this property. */
setPosition(x, y) {
this.x = x;
this.y = y;
}
/** Increments the skeleton's {@link time}. */
update(delta) {
this.time += delta;
}
/** Calls {@link PhysicsConstraint.translate} for each physics constraint. */
physicsTranslate(x, y) {
const constraints = this.physics;
for (let i = 0, n = constraints.length; i < n; i++)
constraints[i].translate(x, y);
}
/** Calls {@link PhysicsConstraint.rotate} for each physics constraint. */
physicsRotate(x, y, degrees) {
const constraints = this.physics;
for (let i = 0, n = constraints.length; i < n; i++)
constraints[i].rotate(x, y, degrees);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2tlbGV0b24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvU2tlbGV0b24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzsrRUEyQitFO0FBSS9FLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNyRSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBRWpDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUUzQyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQU0zRCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxLQUFLLEVBQXdCLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFekU7Ozs7O2tJQUtrSTtBQUNsSSxNQUFNLE9BQU8sUUFBUTtJQUNaLE1BQU0sQ0FBQyxhQUFhLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2xELE1BQU0sQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLE1BQU0sS0FBSyxJQUFJO1FBQ2QsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2hDLENBQUM7SUFFRCxzQ0FBc0M7SUFDN0IsSUFBSSxDQUFlO0lBRTVCLHlGQUF5RjtJQUNoRixLQUFLLENBQWM7SUFFNUIsa0ZBQWtGO0lBQ3pFLEtBQUssQ0FBYztJQUU1QjtnQkFDWTtJQUNILFNBQVMsQ0FBWTtJ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 Line truncated