@pixi-spine/base
Version:
Base of pixi-spine integration, common files for spine runtimes of different versions
635 lines (631 loc) • 22.1 kB
JavaScript
'use strict';
var AttachmentType = require('./core/AttachmentType.js');
var TextureRegion = require('./core/TextureRegion.js');
var Utils = require('./core/Utils.js');
var core = require('@pixi/core');
var display = require('@pixi/display');
var sprite = require('@pixi/sprite');
var meshExtras = require('@pixi/mesh-extras');
var graphics = require('@pixi/graphics');
var settings = require('./settings.js');
const tempRgb = [0, 0, 0];
class SpineSprite extends sprite.Sprite {
constructor() {
super(...arguments);
this.region = null;
this.attachment = null;
}
}
class SpineMesh extends meshExtras.SimpleMesh {
constructor(texture, vertices, uvs, indices, drawMode) {
super(texture, vertices, uvs, indices, drawMode);
this.region = null;
this.attachment = null;
}
}
const _SpineBase = class extends display.Container {
constructor(spineData) {
super();
if (!spineData) {
throw new Error("The spineData param is required.");
}
if (typeof spineData === "string") {
throw new Error('spineData param cant be string. Please use spine.Spine.fromAtlas("YOUR_RESOURCE_NAME") from now on.');
}
this.spineData = spineData;
this.createSkeleton(spineData);
this.slotContainers = [];
this.tempClipContainers = [];
for (let i = 0, n = this.skeleton.slots.length; i < n; i++) {
const slot = this.skeleton.slots[i];
const attachment = slot.getAttachment();
const slotContainer = this.newContainer();
this.slotContainers.push(slotContainer);
this.addChild(slotContainer);
this.tempClipContainers.push(null);
if (!attachment) {
continue;
}
if (attachment.type === AttachmentType.AttachmentType.Region) {
const spriteName = attachment.name;
const sprite = this.createSprite(slot, attachment, spriteName);
slot.currentSprite = sprite;
slot.currentSpriteName = spriteName;
slotContainer.addChild(sprite);
} else if (attachment.type === AttachmentType.AttachmentType.Mesh) {
const mesh = this.createMesh(slot, attachment);
slot.currentMesh = mesh;
slot.currentMeshId = attachment.id;
slot.currentMeshName = attachment.name;
slotContainer.addChild(mesh);
} else if (attachment.type === AttachmentType.AttachmentType.Clipping) {
this.createGraphics(slot, attachment);
slotContainer.addChild(slot.clippingContainer);
slotContainer.addChild(slot.currentGraphics);
}
}
this.tintRgb = new Float32Array([1, 1, 1]);
this.autoUpdate = true;
this.visible = true;
}
get debug() {
return this._debug;
}
set debug(value) {
if (value == this._debug) {
return;
}
this._debug?.unregisterSpine(this);
value?.registerSpine(this);
this._debug = value;
}
/**
* If this flag is set to true, the spine animation will be automatically updated every
* time the object id drawn. The down side of this approach is that the delta time is
* automatically calculated and you could miss out on cool effects like slow motion,
* pause, skip ahead and the sorts. Most of these effects can be achieved even with
* autoUpdate enabled but are harder to achieve.
*
* @member {boolean}
* @memberof spine.Spine#
* @default true
*/
get autoUpdate() {
return this._autoUpdate;
}
set autoUpdate(value) {
if (value !== this._autoUpdate) {
this._autoUpdate = value;
this.updateTransform = value ? _SpineBase.prototype.autoUpdateTransform : display.Container.prototype.updateTransform;
}
}
/**
* The tint applied to the spine object. This is a hex value. A value of 0xFFFFFF will remove any tint effect.
*
* @member {number}
* @memberof spine.Spine#
* @default 0xFFFFFF
*/
get tint() {
return core.utils.rgb2hex(this.tintRgb);
}
set tint(value) {
this.tintRgb = core.utils.hex2rgb(value, this.tintRgb);
}
/**
* Limit value for the update dt with Spine.globalDelayLimit
* that can be overridden with localDelayLimit
* @return {number} - Maximum processed dt value for the update
*/
get delayLimit() {
const limit = typeof this.localDelayLimit !== "undefined" ? this.localDelayLimit : settings.settings.GLOBAL_DELAY_LIMIT;
return limit || Number.MAX_VALUE;
}
/**
* Update the spine skeleton and its animations by delta time (dt)
*
* @param dt {number} Delta time. Time by which the animation should be updated
*/
update(dt) {
const delayLimit = this.delayLimit;
if (dt > delayLimit)
dt = delayLimit;
this.state.update(dt);
this.state.apply(this.skeleton);
if (!this.skeleton) {
return;
}
this.skeleton.updateWorldTransform();
const slots = this.skeleton.slots;
const globalClr = this.color;
let light = null;
let dark = null;
if (globalClr) {
light = globalClr.light;
dark = globalClr.dark;
} else {
light = this.tintRgb;
}
for (let i = 0, n = slots.length; i < n; i++) {
const slot = slots[i];
const attachment = slot.getAttachment();
const slotContainer = this.slotContainers[i];
if (!attachment) {
slotContainer.visible = false;
continue;
}
let spriteColor = null;
if (attachment.sequence) {
attachment.sequence.apply(slot, attachment);
}
let region = attachment.region;
const attColor = attachment.color;
switch (attachment != null && attachment.type) {
case AttachmentType.AttachmentType.Region:
const transform = slotContainer.transform;
transform.setFromMatrix(slot.bone.matrix);
region = attachment.region;
if (slot.currentMesh) {
slot.currentMesh.visible = false;
slot.currentMesh = null;
slot.currentMeshId = void 0;
slot.currentMeshName = void 0;
}
if (!region) {
if (slot.currentSprite) {
slot.currentSprite.renderable = false;
}
break;
}
if (!slot.currentSpriteName || slot.currentSpriteName !== attachment.name) {
const spriteName = attachment.name;
if (slot.currentSprite) {
slot.currentSprite.visible = false;
}
slot.sprites = slot.sprites || {};
if (slot.sprites[spriteName] !== void 0) {
slot.sprites[spriteName].visible = true;
} else {
const sprite = this.createSprite(slot, attachment, spriteName);
slotContainer.addChild(sprite);
}
slot.currentSprite = slot.sprites[spriteName];
slot.currentSpriteName = spriteName;
}
slot.currentSprite.renderable = true;
if (!slot.hackRegion) {
this.setSpriteRegion(attachment, slot.currentSprite, region);
}
if (slot.currentSprite.color) {
spriteColor = slot.currentSprite.color;
} else {
tempRgb[0] = light[0] * slot.color.r * attColor.r;
tempRgb[1] = light[1] * slot.color.g * attColor.g;
tempRgb[2] = light[2] * slot.color.b * attColor.b;
slot.currentSprite.tint = core.utils.rgb2hex(tempRgb);
}
slot.currentSprite.blendMode = slot.blendMode;
break;
case AttachmentType.AttachmentType.Mesh:
if (slot.currentSprite) {
slot.currentSprite.visible = false;
slot.currentSprite = null;
slot.currentSpriteName = void 0;
const transform2 = new core.Transform();
transform2._parentID = -1;
transform2._worldID = slotContainer.transform._worldID;
slotContainer.transform = transform2;
}
if (!region) {
if (slot.currentMesh) {
slot.currentMesh.renderable = false;
}
break;
}
const id = attachment.id;
if (slot.currentMeshId === void 0 || slot.currentMeshId !== id) {
const meshId = id;
if (slot.currentMesh) {
slot.currentMesh.visible = false;
}
slot.meshes = slot.meshes || {};
if (slot.meshes[meshId] !== void 0) {
slot.meshes[meshId].visible = true;
} else {
const mesh = this.createMesh(slot, attachment);
slotContainer.addChild(mesh);
}
slot.currentMesh = slot.meshes[meshId];
slot.currentMeshName = attachment.name;
slot.currentMeshId = meshId;
}
slot.currentMesh.renderable = true;
attachment.computeWorldVerticesOld(slot, slot.currentMesh.vertices);
if (slot.currentMesh.color) {
spriteColor = slot.currentMesh.color;
} else {
tempRgb[0] = light[0] * slot.color.r * attColor.r;
tempRgb[1] = light[1] * slot.color.g * attColor.g;
tempRgb[2] = light[2] * slot.color.b * attColor.b;
slot.currentMesh.tint = core.utils.rgb2hex(tempRgb);
}
slot.currentMesh.blendMode = slot.blendMode;
if (!slot.hackRegion) {
this.setMeshRegion(attachment, slot.currentMesh, region);
}
break;
case AttachmentType.AttachmentType.Clipping:
if (!slot.currentGraphics) {
this.createGraphics(slot, attachment);
slotContainer.addChild(slot.clippingContainer);
slotContainer.addChild(slot.currentGraphics);
}
this.updateGraphics(slot, attachment);
slotContainer.alpha = 1;
slotContainer.visible = true;
continue;
default:
slotContainer.visible = false;
continue;
}
slotContainer.visible = true;
if (spriteColor) {
let r0 = slot.color.r * attColor.r;
let g0 = slot.color.g * attColor.g;
let b0 = slot.color.b * attColor.b;
spriteColor.setLight(light[0] * r0 + dark[0] * (1 - r0), light[1] * g0 + dark[1] * (1 - g0), light[2] * b0 + dark[2] * (1 - b0));
if (slot.darkColor) {
r0 = slot.darkColor.r;
g0 = slot.darkColor.g;
b0 = slot.darkColor.b;
} else {
r0 = 0;
g0 = 0;
b0 = 0;
}
spriteColor.setDark(light[0] * r0 + dark[0] * (1 - r0), light[1] * g0 + dark[1] * (1 - g0), light[2] * b0 + dark[2] * (1 - b0));
}
slotContainer.alpha = slot.color.a;
}
const drawOrder = this.skeleton.drawOrder;
let clippingAttachment = null;
let clippingContainer = null;
for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = slots[drawOrder[i].data.index];
const slotContainer = this.slotContainers[drawOrder[i].data.index];
if (!clippingContainer) {
if (slotContainer.parent !== null && slotContainer.parent !== this) {
slotContainer.parent.removeChild(slotContainer);
slotContainer.parent = this;
}
}
if (slot.currentGraphics && slot.getAttachment()) {
clippingContainer = slot.clippingContainer;
clippingAttachment = slot.getAttachment();
clippingContainer.children.length = 0;
this.children[i] = slotContainer;
if (clippingAttachment.endSlot === slot.data) {
clippingAttachment.endSlot = null;
}
} else if (clippingContainer) {
let c = this.tempClipContainers[i];
if (!c) {
c = this.tempClipContainers[i] = this.newContainer();
c.visible = false;
}
this.children[i] = c;
slotContainer.parent = null;
clippingContainer.addChild(slotContainer);
if (clippingAttachment.endSlot == slot.data) {
clippingContainer.renderable = true;
clippingContainer = null;
clippingAttachment = null;
}
} else {
this.children[i] = slotContainer;
}
}
this._debug?.renderDebug(this);
}
setSpriteRegion(attachment, sprite, region) {
if (sprite.attachment === attachment && sprite.region === region) {
return;
}
sprite.region = region;
sprite.attachment = attachment;
sprite.texture = region.texture;
sprite.rotation = attachment.rotation * Utils.MathUtils.degRad;
sprite.position.x = attachment.x;
sprite.position.y = attachment.y;
sprite.alpha = attachment.color.a;
if (!region.size) {
sprite.scale.x = attachment.scaleX * attachment.width / region.originalWidth;
sprite.scale.y = -attachment.scaleY * attachment.height / region.originalHeight;
} else {
sprite.scale.x = region.size.width / region.originalWidth;
sprite.scale.y = -region.size.height / region.originalHeight;
}
}
setMeshRegion(attachment, mesh, region) {
if (mesh.attachment === attachment && mesh.region === region) {
return;
}
mesh.region = region;
mesh.attachment = attachment;
mesh.texture = region.texture;
region.texture.updateUvs();
mesh.uvBuffer.update(attachment.regionUVs);
}
/**
* When autoupdate is set to yes this function is used as pixi's updateTransform function
*
* @private
*/
autoUpdateTransform() {
if (settings.settings.GLOBAL_AUTO_UPDATE) {
this.lastTime = this.lastTime || Date.now();
const timeDelta = (Date.now() - this.lastTime) * 1e-3;
this.lastTime = Date.now();
this.update(timeDelta);
} else {
this.lastTime = 0;
}
display.Container.prototype.updateTransform.call(this);
}
/**
* Create a new sprite to be used with core.RegionAttachment
*
* @param slot {spine.Slot} The slot to which the attachment is parented
* @param attachment {spine.RegionAttachment} The attachment that the sprite will represent
* @private
*/
createSprite(slot, attachment, defName) {
let region = attachment.region;
if (slot.hackAttachment === attachment) {
region = slot.hackRegion;
}
const texture = region ? region.texture : null;
const sprite = this.newSprite(texture);
sprite.anchor.set(0.5);
if (region) {
this.setSpriteRegion(attachment, sprite, attachment.region);
}
slot.sprites = slot.sprites || {};
slot.sprites[defName] = sprite;
return sprite;
}
/**
* Creates a Strip from the spine data
* @param slot {spine.Slot} The slot to which the attachment is parented
* @param attachment {spine.RegionAttachment} The attachment that the sprite will represent
* @private
*/
createMesh(slot, attachment) {
if (!attachment.region && attachment.sequence) {
attachment.sequence.apply(slot, attachment);
}
let region = attachment.region;
if (slot.hackAttachment === attachment) {
region = slot.hackRegion;
slot.hackAttachment = null;
slot.hackRegion = null;
}
const strip = this.newMesh(
region ? region.texture : null,
new Float32Array(attachment.regionUVs.length),
attachment.regionUVs,
new Uint16Array(attachment.triangles),
core.DRAW_MODES.TRIANGLES
);
if (typeof strip._canvasPadding !== "undefined") {
strip._canvasPadding = 1.5;
}
strip.alpha = attachment.color.a;
strip.region = attachment.region;
if (region) {
this.setMeshRegion(attachment, strip, region);
}
slot.meshes = slot.meshes || {};
slot.meshes[attachment.id] = strip;
return strip;
}
// @ts-ignore
createGraphics(slot, clip) {
const graphics = this.newGraphics();
const poly = new core.Polygon([]);
graphics.clear();
graphics.beginFill(16777215, 1);
graphics.drawPolygon(poly);
graphics.renderable = false;
slot.currentGraphics = graphics;
slot.clippingContainer = this.newContainer();
slot.clippingContainer.mask = slot.currentGraphics;
return graphics;
}
updateGraphics(slot, clip) {
const geom = slot.currentGraphics.geometry;
const vertices = geom.graphicsData[0].shape.points;
const n = clip.worldVerticesLength;
vertices.length = n;
clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
geom.invalidate();
}
/**
* Changes texture in attachment in specific slot.
*
* PIXI runtime feature, it was made to satisfy our users.
*
* @param slotIndex {number}
* @param [texture = null] {PIXI.Texture} If null, take default (original) texture
* @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there
* @returns {boolean} Success flag
*/
hackTextureBySlotIndex(slotIndex, texture = null, size = null) {
const slot = this.skeleton.slots[slotIndex];
if (!slot) {
return false;
}
const attachment = slot.getAttachment();
let region = attachment.region;
if (texture) {
region = new TextureRegion.TextureRegion();
region.texture = texture;
region.size = size;
slot.hackRegion = region;
slot.hackAttachment = attachment;
} else {
slot.hackRegion = null;
slot.hackAttachment = null;
}
if (slot.currentSprite) {
this.setSpriteRegion(attachment, slot.currentSprite, region);
} else if (slot.currentMesh) {
this.setMeshRegion(attachment, slot.currentMesh, region);
}
return true;
}
/**
* Changes texture in attachment in specific slot.
*
* PIXI runtime feature, it was made to satisfy our users.
*
* @param slotName {string}
* @param [texture = null] {PIXI.Texture} If null, take default (original) texture
* @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there
* @returns {boolean} Success flag
*/
hackTextureBySlotName(slotName, texture = null, size = null) {
const index = this.skeleton.findSlotIndex(slotName);
if (index == -1) {
return false;
}
return this.hackTextureBySlotIndex(index, texture, size);
}
/**
* Changes texture of an attachment
*
* PIXI runtime feature, it was made to satisfy our users.
*
* @param slotName {string}
* @param attachmentName {string}
* @param [texture = null] {PIXI.Texture} If null, take default (original) texture
* @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there
* @returns {boolean} Success flag
*/
hackTextureAttachment(slotName, attachmentName, texture, size = null) {
const slotIndex = this.skeleton.findSlotIndex(slotName);
const attachment = this.skeleton.getAttachmentByName(slotName, attachmentName);
attachment.region.texture = texture;
const slot = this.skeleton.slots[slotIndex];
if (!slot) {
return false;
}
const currentAttachment = slot.getAttachment();
if (attachmentName === currentAttachment.name) {
let region = attachment.region;
if (texture) {
region = new TextureRegion.TextureRegion();
region.texture = texture;
region.size = size;
slot.hackRegion = region;
slot.hackAttachment = currentAttachment;
} else {
slot.hackRegion = null;
slot.hackAttachment = null;
}
if (slot.currentSprite && slot.currentSprite.region != region) {
this.setSpriteRegion(currentAttachment, slot.currentSprite, region);
slot.currentSprite.region = region;
} else if (slot.currentMesh && slot.currentMesh.region != region) {
this.setMeshRegion(currentAttachment, slot.currentMesh, region);
}
return true;
}
return false;
}
// those methods can be overriden to spawn different classes
newContainer() {
return new display.Container();
}
newSprite(tex) {
return new SpineSprite(tex);
}
newGraphics() {
return new graphics.Graphics();
}
newMesh(texture, vertices, uvs, indices, drawMode) {
return new SpineMesh(texture, vertices, uvs, indices, drawMode);
}
transformHack() {
return 1;
}
/**
* Hack for pixi-display and pixi-lights. Every attachment name ending with a suffix will be added to different layer
* @param nameSuffix
* @param group
* @param outGroup
*/
hackAttachmentGroups(nameSuffix, group, outGroup) {
if (!nameSuffix) {
return void 0;
}
const list_d = [];
const list_n = [];
for (let i = 0, len = this.skeleton.slots.length; i < len; i++) {
const slot = this.skeleton.slots[i];
const name = slot.currentSpriteName || slot.currentMeshName || "";
const target = slot.currentSprite || slot.currentMesh;
if (name.endsWith(nameSuffix)) {
target.parentGroup = group;
list_n.push(target);
} else if (outGroup && target) {
target.parentGroup = outGroup;
list_d.push(target);
}
}
return [list_d, list_n];
}
destroy(options) {
this.debug = null;
for (let i = 0, n = this.skeleton.slots.length; i < n; i++) {
const slot = this.skeleton.slots[i];
for (const name in slot.meshes) {
slot.meshes[name].destroy(options);
}
slot.meshes = null;
for (const name in slot.sprites) {
slot.sprites[name].destroy(options);
}
slot.sprites = null;
}
for (let i = 0, n = this.slotContainers.length; i < n; i++) {
this.slotContainers[i].destroy(options);
}
this.spineData = null;
this.skeleton = null;
this.slotContainers = null;
this.stateData = null;
this.state = null;
this.tempClipContainers = null;
super.destroy(options);
}
};
let SpineBase = _SpineBase;
SpineBase.clippingPolygon = [];
Object.defineProperty(SpineBase.prototype, "visible", {
get() {
return this._visible;
},
set(value) {
if (value !== this._visible) {
this._visible = value;
if (value) {
this.lastTime = 0;
}
}
}
});
exports.SpineBase = SpineBase;
exports.SpineMesh = SpineMesh;
exports.SpineSprite = SpineSprite;
//# sourceMappingURL=SpineBase.js.map