Merge branch '4.1' into 4.2-beta

This commit is contained in:
Mario Zechner 2023-09-04 10:56:38 +02:00
commit 3b66796bb7
4 changed files with 541 additions and 542 deletions

View File

@ -30,137 +30,136 @@
import { SPINE_GAME_OBJECT_TYPE } from "./keys"; import { SPINE_GAME_OBJECT_TYPE } from "./keys";
import { SpinePlugin } from "./SpinePlugin"; import { SpinePlugin } from "./SpinePlugin";
import { import {
ComputedSizeMixin, ComputedSizeMixin,
DepthMixin, DepthMixin,
FlipMixin, FlipMixin,
ScrollFactorMixin, ScrollFactorMixin,
TransformMixin, TransformMixin,
VisibleMixin, VisibleMixin,
AlphaMixin, AlphaMixin,
OriginMixin, OriginMixin,
} from "./mixins"; } from "./mixins";
import { import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
Bone, Bone,
MathUtils, MathUtils,
Skeleton, Skeleton,
Skin, Skin,
Vector2, Vector2,
} from "@esotericsoftware/spine-core"; } from "@esotericsoftware/spine-core";
class BaseSpineGameObject extends Phaser.GameObjects.GameObject { class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
constructor(scene: Phaser.Scene, type: string) { constructor (scene: Phaser.Scene, type: string) {
super(scene, type); super(scene, type);
} }
} }
/** A bounds provider calculates the bounding box for a skeleton, which is then assigned as the size of the SpineGameObject. */ /** A bounds provider calculates the bounding box for a skeleton, which is then assigned as the size of the SpineGameObject. */
export interface SpineGameObjectBoundsProvider { export interface SpineGameObjectBoundsProvider {
// Returns the bounding box for the skeleton, in skeleton space. // Returns the bounding box for the skeleton, in skeleton space.
calculateBounds(gameObject: SpineGameObject): { calculateBounds (gameObject: SpineGameObject): {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
}; };
} }
/** A bounds provider that calculates the bounding box from the setup pose. */ /** A bounds provider that calculates the bounding box from the setup pose. */
export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider { export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
calculateBounds(gameObject: SpineGameObject) { calculateBounds (gameObject: SpineGameObject) {
if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 }; if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while // Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not // the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state. // reconstruct that state.
const skeleton = new Skeleton(gameObject.skeleton.data); const skeleton = new Skeleton(gameObject.skeleton.data);
skeleton.setToSetupPose(); skeleton.setToSetupPose();
skeleton.updateWorldTransform(); skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect(); const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
} }
/** A bounds provider that calculates the bounding box by taking the maximumg bounding box for a combination of skins and specific animation. */ /** A bounds provider that calculates the bounding box by taking the maximumg bounding box for a combination of skins and specific animation. */
export class SkinsAndAnimationBoundsProvider export class SkinsAndAnimationBoundsProvider
implements SpineGameObjectBoundsProvider implements SpineGameObjectBoundsProvider {
{ /**
/** * @param animation The animation to use for calculating the bounds. If null, the setup pose is used.
* @param animation The animation to use for calculating the bounds. If null, the setup pose is used. * @param skins The skins to use for calculating the bounds. If empty, the default skin is used.
* @param skins The skins to use for calculating the bounds. If empty, the default skin is used. * @param timeStep The time step to use for calculating the bounds. A smaller time step means more precision, but slower calculation.
* @param timeStep The time step to use for calculating the bounds. A smaller time step means more precision, but slower calculation. */
*/ constructor (
constructor( private animation: string | null,
private animation: string | null, private skins: string[] = [],
private skins: string[] = [], private timeStep: number = 0.05
private timeStep: number = 0.05 ) { }
) {}
calculateBounds(gameObject: SpineGameObject): { calculateBounds (gameObject: SpineGameObject): {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
} { } {
if (!gameObject.skeleton || !gameObject.animationState) if (!gameObject.skeleton || !gameObject.animationState)
return { x: 0, y: 0, width: 0, height: 0 }; return { x: 0, y: 0, width: 0, height: 0 };
// Make a copy of animation state and skeleton as this might be called while // Make a copy of animation state and skeleton as this might be called while
// the skeleton in the GameObject has already been heavily modified. We can not // the skeleton in the GameObject has already been heavily modified. We can not
// reconstruct that state. // reconstruct that state.
const animationState = new AnimationState(gameObject.animationState.data); const animationState = new AnimationState(gameObject.animationState.data);
const skeleton = new Skeleton(gameObject.skeleton.data); const skeleton = new Skeleton(gameObject.skeleton.data);
const data = skeleton.data; const data = skeleton.data;
if (this.skins.length > 0) { if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin"); let customSkin = new Skin("custom-skin");
for (const skinName of this.skins) { for (const skinName of this.skins) {
const skin = data.findSkin(skinName); const skin = data.findSkin(skinName);
if (skin == null) continue; if (skin == null) continue;
customSkin.addSkin(skin); customSkin.addSkin(skin);
} }
skeleton.setSkin(customSkin); skeleton.setSkin(customSkin);
} }
skeleton.setToSetupPose(); skeleton.setToSetupPose();
const animation = const animation =
this.animation != null ? data.findAnimation(this.animation!) : null; this.animation != null ? data.findAnimation(this.animation!) : null;
if (animation == null) { if (animation == null) {
skeleton.updateWorldTransform(); skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect(); const bounds = skeleton.getBoundsRect();
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} else { } else {
let minX = Number.POSITIVE_INFINITY, let minX = Number.POSITIVE_INFINITY,
minY = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY,
maxX = Number.NEGATIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY,
maxY = Number.NEGATIVE_INFINITY; maxY = Number.NEGATIVE_INFINITY;
animationState.clearTracks(); animationState.clearTracks();
animationState.setAnimationWith(0, animation, false); animationState.setAnimationWith(0, animation, false);
const steps = Math.max(animation.duration / this.timeStep, 1.0); const steps = Math.max(animation.duration / this.timeStep, 1.0);
for (let i = 0; i < steps; i++) { for (let i = 0; i < steps; i++) {
animationState.update(i > 0 ? this.timeStep : 0); animationState.update(i > 0 ? this.timeStep : 0);
animationState.apply(skeleton); animationState.apply(skeleton);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform();
const bounds = skeleton.getBoundsRect(); const bounds = skeleton.getBoundsRect();
minX = Math.min(minX, bounds.x); minX = Math.min(minX, bounds.x);
minY = Math.min(minY, bounds.y); minY = Math.min(minY, bounds.y);
maxX = Math.max(maxX, minX + bounds.width); maxX = Math.max(maxX, minX + bounds.width);
maxY = Math.max(maxY, minY + bounds.height); maxY = Math.max(maxY, minY + bounds.height);
} }
const bounds = { const bounds = {
x: minX, x: minX,
y: minY, y: minY,
width: maxX - minX, width: maxX - minX,
height: maxY - minY, height: maxY - minY,
}; };
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width == Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
} }
} }
/** /**
@ -185,214 +184,214 @@ export class SkinsAndAnimationBoundsProvider
* See {@link skeletonToPhaserWorldCoordinates}, {@link phaserWorldCoordinatesToSkeleton}, and {@link phaserWorldCoordinatesToBoneLocal.} * See {@link skeletonToPhaserWorldCoordinates}, {@link phaserWorldCoordinatesToSkeleton}, and {@link phaserWorldCoordinatesToBoneLocal.}
*/ */
export class SpineGameObject extends DepthMixin( export class SpineGameObject extends DepthMixin(
OriginMixin( OriginMixin(
ComputedSizeMixin( ComputedSizeMixin(
FlipMixin( FlipMixin(
ScrollFactorMixin( ScrollFactorMixin(
TransformMixin(VisibleMixin(AlphaMixin(BaseSpineGameObject))) TransformMixin(VisibleMixin(AlphaMixin(BaseSpineGameObject)))
) )
) )
) )
) )
) { ) {
blendMode = -1; blendMode = -1;
skeleton: Skeleton; skeleton: Skeleton;
animationStateData: AnimationStateData; animationStateData: AnimationStateData;
animationState: AnimationState; animationState: AnimationState;
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => {}; beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => {}; afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
private premultipliedAlpha = false; private premultipliedAlpha = false;
constructor( constructor (
scene: Phaser.Scene, scene: Phaser.Scene,
private plugin: SpinePlugin, private plugin: SpinePlugin,
x: number, x: number,
y: number, y: number,
dataKey: string, dataKey: string,
atlasKey: string, atlasKey: string,
public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider() public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()
) { ) {
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE); super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
this.setPosition(x, y); this.setPosition(x, y);
this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey); this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey); this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
this.animationStateData = new AnimationStateData(this.skeleton.data); this.animationStateData = new AnimationStateData(this.skeleton.data);
this.animationState = new AnimationState(this.animationStateData); this.animationState = new AnimationState(this.animationStateData);
this.skeleton.updateWorldTransform(); this.skeleton.updateWorldTransform();
this.updateSize(); this.updateSize();
} }
updateSize() { updateSize () {
if (!this.skeleton) return; if (!this.skeleton) return;
let bounds = this.boundsProvider.calculateBounds(this); let bounds = this.boundsProvider.calculateBounds(this);
// For some reason the TS compiler and the ComputedSize mixin don't work well together and we have // For some reason the TS compiler and the ComputedSize mixin don't work well together and we have
// to cast to any. // to cast to any.
let self = this as any; let self = this as any;
self.width = bounds.width; self.width = bounds.width;
self.height = bounds.height; self.height = bounds.height;
this.displayOriginX = -bounds.x; this.displayOriginX = -bounds.x;
this.displayOriginY = -bounds.y; this.displayOriginY = -bounds.y;
} }
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */ /** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates(point: { x: number; y: number }) { skeletonToPhaserWorldCoordinates (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); let transform = this.getWorldTransformMatrix();
let a = transform.a, let a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; let x = point.x;
let y = point.y; let y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
/** Converts a point from the Phaser world coordinate system to the skeleton coordinate system. */ /** Converts a point from the Phaser world coordinate system to the skeleton coordinate system. */
phaserWorldCoordinatesToSkeleton(point: { x: number; y: number }) { phaserWorldCoordinatesToSkeleton (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); let transform = this.getWorldTransformMatrix();
transform = transform.invert(); transform = transform.invert();
let a = transform.a, let a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; let x = point.x;
let y = point.y; let y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
/** Converts a point from the Phaser world coordinate system to the bone's local coordinate system. */ /** Converts a point from the Phaser world coordinate system to the bone's local coordinate system. */
phaserWorldCoordinatesToBone(point: { x: number; y: number }, bone: Bone) { phaserWorldCoordinatesToBone (point: { x: number; y: number }, bone: Bone) {
this.phaserWorldCoordinatesToSkeleton(point); this.phaserWorldCoordinatesToSkeleton(point);
if (bone.parent) { if (bone.parent) {
bone.parent.worldToLocal(point as Vector2); bone.parent.worldToLocal(point as Vector2);
} else { } else {
bone.worldToLocal(point as Vector2); bone.worldToLocal(point as Vector2);
} }
} }
/** /**
* Updates the {@link AnimationState}, applies it to the {@link Skeleton}, then updates the world transforms of all bones. * Updates the {@link AnimationState}, applies it to the {@link Skeleton}, then updates the world transforms of all bones.
* @param delta The time delta in milliseconds * @param delta The time delta in milliseconds
*/ */
updatePose(delta: number) { updatePose (delta: number) {
this.animationState.update(delta / 1000); this.animationState.update(delta / 1000);
this.animationState.apply(this.skeleton); this.animationState.apply(this.skeleton);
this.beforeUpdateWorldTransforms(this); this.beforeUpdateWorldTransforms(this);
this.skeleton.updateWorldTransform(); this.skeleton.updateWorldTransform();
this.afterUpdateWorldTransforms(this); this.afterUpdateWorldTransforms(this);
} }
preUpdate(time: number, delta: number) { preUpdate (time: number, delta: number) {
if (!this.skeleton || !this.animationState) return; if (!this.skeleton || !this.animationState) return;
this.updatePose(delta); this.updatePose(delta);
} }
preDestroy() { preDestroy () {
// FIXME tear down any event emitters // FIXME tear down any event emitters
} }
willRender(camera: Phaser.Cameras.Scene2D.Camera) { willRender (camera: Phaser.Cameras.Scene2D.Camera) {
if (!this.visible) return false; if (!this.visible) return false;
var GameObjectRenderMask = 0xf; var GameObjectRenderMask = 0xf;
var result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id)); var result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id));
if (!result && this.parentContainer && this.plugin.webGLRenderer) { if (!result && this.parentContainer && this.plugin.webGLRenderer) {
var sceneRenderer = this.plugin.webGLRenderer; var sceneRenderer = this.plugin.webGLRenderer;
if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) { if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) {
sceneRenderer.end(); sceneRenderer.end();
this.plugin.phaserRenderer.pipelines.rebind(); this.plugin.phaserRenderer.pipelines.rebind();
} }
} }
return result; return result;
} }
renderWebGL( renderWebGL (
renderer: Phaser.Renderer.WebGL.WebGLRenderer, renderer: Phaser.Renderer.WebGL.WebGLRenderer,
src: SpineGameObject, src: SpineGameObject,
camera: Phaser.Cameras.Scene2D.Camera, camera: Phaser.Cameras.Scene2D.Camera,
parentMatrix: Phaser.GameObjects.Components.TransformMatrix parentMatrix: Phaser.GameObjects.Components.TransformMatrix
) { ) {
if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer) if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer)
return; return;
let sceneRenderer = this.plugin.webGLRenderer; let sceneRenderer = this.plugin.webGLRenderer;
if (renderer.newType) { if (renderer.newType) {
renderer.pipelines.clear(); renderer.pipelines.clear();
sceneRenderer.begin(); sceneRenderer.begin();
} }
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( let transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let a = transform.a, let a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
sceneRenderer.drawSkeleton( sceneRenderer.drawSkeleton(
this.skeleton, this.skeleton,
this.premultipliedAlpha, this.premultipliedAlpha,
-1, -1,
-1, -1,
(vertices, numVertices, stride) => { (vertices, numVertices, stride) => {
for (let i = 0; i < numVertices; i += stride) { for (let i = 0; i < numVertices; i += stride) {
let vx = vertices[i]; let vx = vertices[i];
let vy = vertices[i + 1]; let vy = vertices[i + 1];
vertices[i] = vx * a + vy * c + tx; vertices[i] = vx * a + vy * c + tx;
vertices[i + 1] = vx * b + vy * d + ty; vertices[i + 1] = vx * b + vy * d + ty;
} }
} }
); );
if (!renderer.nextTypeMatch) { if (!renderer.nextTypeMatch) {
sceneRenderer.end(); sceneRenderer.end();
renderer.pipelines.rebind(); renderer.pipelines.rebind();
} }
} }
renderCanvas( renderCanvas (
renderer: Phaser.Renderer.Canvas.CanvasRenderer, renderer: Phaser.Renderer.Canvas.CanvasRenderer,
src: SpineGameObject, src: SpineGameObject,
camera: Phaser.Cameras.Scene2D.Camera, camera: Phaser.Cameras.Scene2D.Camera,
parentMatrix: Phaser.GameObjects.Components.TransformMatrix parentMatrix: Phaser.GameObjects.Components.TransformMatrix
) { ) {
if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer)
return; return;
let context = renderer.currentContext; let context = renderer.currentContext;
let skeletonRenderer = this.plugin.canvasRenderer; let skeletonRenderer = this.plugin.canvasRenderer;
(skeletonRenderer as any).ctx = context; (skeletonRenderer as any).ctx = context;
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( let transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let skeleton = this.skeleton; let skeleton = this.skeleton;
skeleton.x = transform.tx; skeleton.x = transform.tx;
skeleton.y = transform.ty; skeleton.y = transform.ty;
skeleton.scaleX = transform.scaleX; skeleton.scaleX = transform.scaleX;
skeleton.scaleY = transform.scaleY; skeleton.scaleY = transform.scaleY;
let root = skeleton.getRootBone()!; let root = skeleton.getRootBone()!;
root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized; root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
this.skeleton.updateWorldTransform(); this.skeleton.updateWorldTransform();
context.save(); context.save();
skeletonRenderer.draw(skeleton); skeletonRenderer.draw(skeleton);
context.restore(); context.restore();
} }
} }

View File

@ -271,7 +271,7 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
url = config.url; url = config.url;
fileType = config.type === "spineJson" ? SpineSkeletonDataFileType.json : SpineSkeletonDataFileType.binary; fileType = config.type === "spineJson" ? SpineSkeletonDataFileType.json : SpineSkeletonDataFileType.binary;
xhrSettings = config.xhrSettings; xhrSettings = config.xhrSettings;
} }
let file = null; let file = null;
let isJson = fileType == SpineSkeletonDataFileType.json; let isJson = fileType == SpineSkeletonDataFileType.json;
if (isJson) { if (isJson) {

View File

@ -28,32 +28,32 @@
*****************************************************************************/ *****************************************************************************/
import { import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
BlendMode, BlendMode,
ClippingAttachment, ClippingAttachment,
Color, Color,
MeshAttachment, MeshAttachment,
NumberArrayLike, NumberArrayLike,
RegionAttachment, RegionAttachment,
Skeleton, Skeleton,
SkeletonClipping, SkeletonClipping,
SkeletonData, SkeletonData,
TextureAtlasRegion, TextureAtlasRegion,
Utils, Utils,
Vector2, Vector2,
} from "@esotericsoftware/spine-core"; } from "@esotericsoftware/spine-core";
import { MeshBatcher } from "./MeshBatcher"; import { MeshBatcher } from "./MeshBatcher";
import * as THREE from "three"; import * as THREE from "three";
import { ThreeJsTexture } from "./ThreeJsTexture"; import { ThreeJsTexture } from "./ThreeJsTexture";
export type SkeletonMeshMaterialParametersCustomizer = ( export type SkeletonMeshMaterialParametersCustomizer = (
materialParameters: THREE.ShaderMaterialParameters materialParameters: THREE.ShaderMaterialParameters
) => void; ) => void;
export class SkeletonMeshMaterial extends THREE.ShaderMaterial { export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
constructor(customizer: SkeletonMeshMaterialParametersCustomizer) { constructor (customizer: SkeletonMeshMaterialParametersCustomizer) {
let vertexShader = ` let vertexShader = `
attribute vec4 color; attribute vec4 color;
varying vec2 vUv; varying vec2 vUv;
varying vec4 vColor; varying vec4 vColor;
@ -63,7 +63,7 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
} }
`; `;
let fragmentShader = ` let fragmentShader = `
uniform sampler2D map; uniform sampler2D map;
#ifdef USE_SPINE_ALPHATEST #ifdef USE_SPINE_ALPHATEST
uniform float alphaTest; uniform float alphaTest;
@ -78,253 +78,253 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
} }
`; `;
let parameters: THREE.ShaderMaterialParameters = { let parameters: THREE.ShaderMaterialParameters = {
uniforms: { uniforms: {
map: { value: null }, map: { value: null },
}, },
vertexShader: vertexShader, vertexShader: vertexShader,
fragmentShader: fragmentShader, fragmentShader: fragmentShader,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
depthWrite: true, depthWrite: true,
alphaTest: 0.0, alphaTest: 0.0,
}; };
customizer(parameters); customizer(parameters);
if (parameters.alphaTest && parameters.alphaTest > 0) { if (parameters.alphaTest && parameters.alphaTest > 0) {
parameters.defines = { USE_SPINE_ALPHATEST: 1 }; parameters.defines = { USE_SPINE_ALPHATEST: 1 };
if (!parameters.uniforms) parameters.uniforms = {}; if (!parameters.uniforms) parameters.uniforms = {};
parameters.uniforms["alphaTest"] = { value: parameters.alphaTest }; parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
} }
super(parameters); super(parameters);
} }
} }
export class SkeletonMesh extends THREE.Object3D { export class SkeletonMesh extends THREE.Object3D {
tempPos: Vector2 = new Vector2(); tempPos: Vector2 = new Vector2();
tempUv: Vector2 = new Vector2(); tempUv: Vector2 = new Vector2();
tempLight = new Color(); tempLight = new Color();
tempDark = new Color(); tempDark = new Color();
skeleton: Skeleton; skeleton: Skeleton;
state: AnimationState; state: AnimationState;
zOffset: number = 0.1; zOffset: number = 0.1;
private batches = new Array<MeshBatcher>(); private batches = new Array<MeshBatcher>();
private nextBatchIndex = 0; private nextBatchIndex = 0;
private clipper: SkeletonClipping = new SkeletonClipping(); private clipper: SkeletonClipping = new SkeletonClipping();
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
static VERTEX_SIZE = 2 + 2 + 4; static VERTEX_SIZE = 2 + 2 + 4;
private vertices = Utils.newFloatArray(1024); private vertices = Utils.newFloatArray(1024);
private tempColor = new Color(); private tempColor = new Color();
constructor( constructor (
skeletonData: SkeletonData, skeletonData: SkeletonData,
private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = ( private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (
material material
) => {} ) => { }
) { ) {
super(); super();
this.skeleton = new Skeleton(skeletonData); this.skeleton = new Skeleton(skeletonData);
let animData = new AnimationStateData(skeletonData); let animData = new AnimationStateData(skeletonData);
this.state = new AnimationState(animData); this.state = new AnimationState(animData);
} }
update(deltaTime: number) { update (deltaTime: number) {
let state = this.state; let state = this.state;
let skeleton = this.skeleton; let skeleton = this.skeleton;
state.update(deltaTime); state.update(deltaTime);
state.apply(skeleton); state.apply(skeleton);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform();
this.updateGeometry(); this.updateGeometry();
} }
dispose() { dispose () {
for (var i = 0; i < this.batches.length; i++) { for (var i = 0; i < this.batches.length; i++) {
this.batches[i].dispose(); this.batches[i].dispose();
} }
} }
private clearBatches() { private clearBatches () {
for (var i = 0; i < this.batches.length; i++) { for (var i = 0; i < this.batches.length; i++) {
this.batches[i].clear(); this.batches[i].clear();
this.batches[i].visible = false; this.batches[i].visible = false;
} }
this.nextBatchIndex = 0; this.nextBatchIndex = 0;
} }
private nextBatch() { private nextBatch () {
if (this.batches.length == this.nextBatchIndex) { if (this.batches.length == this.nextBatchIndex) {
let batch = new MeshBatcher(10920, this.materialCustomerizer); let batch = new MeshBatcher(10920, this.materialCustomerizer);
this.add(batch); this.add(batch);
this.batches.push(batch); this.batches.push(batch);
} }
let batch = this.batches[this.nextBatchIndex++]; let batch = this.batches[this.nextBatchIndex++];
batch.visible = true; batch.visible = true;
return batch; return batch;
} }
private updateGeometry() { private updateGeometry () {
this.clearBatches(); this.clearBatches();
let tempPos = this.tempPos; let tempPos = this.tempPos;
let tempUv = this.tempUv; let tempUv = this.tempUv;
let tempLight = this.tempLight; let tempLight = this.tempLight;
let tempDark = this.tempDark; let tempDark = this.tempDark;
let clipper = this.clipper; let clipper = this.clipper;
let vertices: NumberArrayLike = this.vertices; let vertices: NumberArrayLike = this.vertices;
let triangles: Array<number> | null = null; let triangles: Array<number> | null = null;
let uvs: NumberArrayLike | null = null; let uvs: NumberArrayLike | null = null;
let drawOrder = this.skeleton.drawOrder; let drawOrder = this.skeleton.drawOrder;
let batch = this.nextBatch(); let batch = this.nextBatch();
batch.begin(); batch.begin();
let z = 0; let z = 0;
let zOffset = this.zOffset; let zOffset = this.zOffset;
for (let i = 0, n = drawOrder.length; i < n; i++) { for (let i = 0, n = drawOrder.length; i < n; i++) {
let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE; let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
let slot = drawOrder[i]; let slot = drawOrder[i];
if (!slot.bone.active) { if (!slot.bone.active) {
clipper.clipEndWithSlot(slot); clipper.clipEndWithSlot(slot);
continue; continue;
} }
let attachment = slot.getAttachment(); let attachment = slot.getAttachment();
let attachmentColor: Color | null; let attachmentColor: Color | null;
let texture: ThreeJsTexture | null; let texture: ThreeJsTexture | null;
let numFloats = 0; let numFloats = 0;
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
let region = <RegionAttachment>attachment; let region = <RegionAttachment>attachment;
attachmentColor = region.color; attachmentColor = region.color;
vertices = this.vertices; vertices = this.vertices;
numFloats = vertexSize * 4; numFloats = vertexSize * 4;
region.computeWorldVertices(slot, vertices, 0, vertexSize); region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = SkeletonMesh.QUAD_TRIANGLES; triangles = SkeletonMesh.QUAD_TRIANGLES;
uvs = region.uvs; uvs = region.uvs;
texture = <ThreeJsTexture>region.region!.texture; texture = <ThreeJsTexture>region.region!.texture;
} else if (attachment instanceof MeshAttachment) { } else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment; let mesh = <MeshAttachment>attachment;
attachmentColor = mesh.color; attachmentColor = mesh.color;
vertices = this.vertices; vertices = this.vertices;
numFloats = (mesh.worldVerticesLength >> 1) * vertexSize; numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
if (numFloats > vertices.length) { if (numFloats > vertices.length) {
vertices = this.vertices = Utils.newFloatArray(numFloats); vertices = this.vertices = Utils.newFloatArray(numFloats);
} }
mesh.computeWorldVertices( mesh.computeWorldVertices(
slot, slot,
0, 0,
mesh.worldVerticesLength, mesh.worldVerticesLength,
vertices, vertices,
0, 0,
vertexSize vertexSize
); );
triangles = mesh.triangles; triangles = mesh.triangles;
uvs = mesh.uvs; uvs = mesh.uvs;
texture = <ThreeJsTexture>mesh.region!.texture; texture = <ThreeJsTexture>mesh.region!.texture;
} else if (attachment instanceof ClippingAttachment) { } else if (attachment instanceof ClippingAttachment) {
let clip = <ClippingAttachment>attachment; let clip = <ClippingAttachment>attachment;
clipper.clipStart(slot, clip); clipper.clipStart(slot, clip);
continue; continue;
} else { } else {
clipper.clipEndWithSlot(slot); clipper.clipEndWithSlot(slot);
continue; continue;
} }
if (texture != null) { if (texture != null) {
let skeleton = slot.bone.skeleton; let skeleton = slot.bone.skeleton;
let skeletonColor = skeleton.color; let skeletonColor = skeleton.color;
let slotColor = slot.color; let slotColor = slot.color;
let alpha = skeletonColor.a * slotColor.a * attachmentColor.a; let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
let color = this.tempColor; let color = this.tempColor;
color.set( color.set(
skeletonColor.r * slotColor.r * attachmentColor.r, skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g, skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b, skeletonColor.b * slotColor.b * attachmentColor.b,
alpha alpha
); );
let finalVertices: NumberArrayLike; let finalVertices: NumberArrayLike;
let finalVerticesLength: number; let finalVerticesLength: number;
let finalIndices: NumberArrayLike; let finalIndices: NumberArrayLike;
let finalIndicesLength: number; let finalIndicesLength: number;
if (clipper.isClipping()) { if (clipper.isClipping()) {
clipper.clipTriangles( clipper.clipTriangles(
vertices, vertices,
numFloats, numFloats,
triangles, triangles,
triangles.length, triangles.length,
uvs, uvs,
color, color,
tempLight, tempLight,
false false
); );
let clippedVertices = clipper.clippedVertices; let clippedVertices = clipper.clippedVertices;
let clippedTriangles = clipper.clippedTriangles; let clippedTriangles = clipper.clippedTriangles;
finalVertices = clippedVertices; finalVertices = clippedVertices;
finalVerticesLength = clippedVertices.length; finalVerticesLength = clippedVertices.length;
finalIndices = clippedTriangles; finalIndices = clippedTriangles;
finalIndicesLength = clippedTriangles.length; finalIndicesLength = clippedTriangles.length;
} else { } else {
let verts = vertices; let verts = vertices;
for ( for (
let v = 2, u = 0, n = numFloats; let v = 2, u = 0, n = numFloats;
v < n; v < n;
v += vertexSize, u += 2 v += vertexSize, u += 2
) { ) {
verts[v] = color.r; verts[v] = color.r;
verts[v + 1] = color.g; verts[v + 1] = color.g;
verts[v + 2] = color.b; verts[v + 2] = color.b;
verts[v + 3] = color.a; verts[v + 3] = color.a;
verts[v + 4] = uvs[u]; verts[v + 4] = uvs[u];
verts[v + 5] = uvs[u + 1]; verts[v + 5] = uvs[u + 1];
} }
finalVertices = vertices; finalVertices = vertices;
finalVerticesLength = numFloats; finalVerticesLength = numFloats;
finalIndices = triangles; finalIndices = triangles;
finalIndicesLength = triangles.length; finalIndicesLength = triangles.length;
} }
if (finalVerticesLength == 0 || finalIndicesLength == 0) { if (finalVerticesLength == 0 || finalIndicesLength == 0) {
clipper.clipEndWithSlot(slot); clipper.clipEndWithSlot(slot);
continue; continue;
} }
// Start new batch if this one can't hold vertices/indices // Start new batch if this one can't hold vertices/indices
if ( if (
!batch.canBatch( !batch.canBatch(
finalVerticesLength / SkeletonMesh.VERTEX_SIZE, finalVerticesLength / SkeletonMesh.VERTEX_SIZE,
finalIndicesLength finalIndicesLength
) )
) { ) {
batch.end(); batch.end();
batch = this.nextBatch(); batch = this.nextBatch();
batch.begin(); batch.begin();
} }
const slotBlendMode = slot.data.blendMode; const slotBlendMode = slot.data.blendMode;
const slotTexture = texture.texture; const slotTexture = texture.texture;
const materialGroup = batch.findMaterialGroup( const materialGroup = batch.findMaterialGroup(
slotTexture, slotTexture,
slotBlendMode slotBlendMode
); );
batch.addMaterialGroup(finalIndicesLength, materialGroup); batch.addMaterialGroup(finalIndicesLength, materialGroup);
batch.batch( batch.batch(
finalVertices, finalVertices,
finalVerticesLength, finalVerticesLength,
finalIndices, finalIndices,
finalIndicesLength, finalIndicesLength,
z z
); );
z += zOffset; z += zOffset;
} }
clipper.clipEndWithSlot(slot); clipper.clipEndWithSlot(slot);
} }
clipper.clipEnd(); clipper.clipEnd();
batch.end(); batch.end();
} }
} }

View File

@ -170,7 +170,7 @@ export class SkeletonRenderer {
WebGLBlendModeConverter.getSourceColorGLBlendMode(blendMode, premultipliedAlpha), WebGLBlendModeConverter.getSourceColorGLBlendMode(blendMode, premultipliedAlpha),
WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode, premultipliedAlpha), WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode, premultipliedAlpha),
WebGLBlendModeConverter.getDestColorGLBlendMode(blendMode), WebGLBlendModeConverter.getDestColorGLBlendMode(blendMode),
WebGLBlendModeConverter.getDestAlphaGLBlendMode(blendMode, premultipliedAlpha) ); WebGLBlendModeConverter.getDestAlphaGLBlendMode(blendMode, premultipliedAlpha));
} }
if (clipper.isClipping()) { if (clipper.isClipping()) {