diff --git a/spine-ts/package.json b/spine-ts/package.json index 92a10b9a8..d4fcd8afe 100644 --- a/spine-ts/package.json +++ b/spine-ts/package.json @@ -17,7 +17,7 @@ "build:player": "npx copyfiles -f spine-player/css/spine-player.css spine-player/dist/ && npx npx esbuild --bundle spine-player/src/index.ts --tsconfig=spine-player/tsconfig.json --sourcemap --outfile=spine-player/dist/iife/spine-player.js --format=iife --global-name=spine", "build:phaser": "npx esbuild --bundle spine-phaser/src/index.ts --tsconfig=spine-phaser/tsconfig.json --sourcemap --outfile=spine-phaser/dist/iife/spine-phaser.js --external:Phaser --alias:phaser=Phaser --format=iife --global-name=spine", "build:threejs": "npx esbuild --bundle spine-threejs/src/index.ts --tsconfig=spine-threejs/tsconfig.json --sourcemap --outfile=spine-threejs/dist/iife/spine-threejs.js --external:three --format=iife --global-name=spine", - "minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js", + "minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-phaser/dist/iife/spine-phaser.js --outfile=spine-phaser/dist/iife/spine-phaser.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js", "dev": "concurrently \"npx live-server --no-browser\" \"npm run dev:canvas\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\"", "dev:modules": "npm run build:modules -- --watch", "dev:canvas": "npm run build:canvas -- --watch", @@ -54,13 +54,12 @@ "spine-webgl" ], "devDependencies": { - "concurrently": "^6.2.1", + "@types/offscreencanvas": "^2019.6.4", + "concurrently": "^7.6.0", "copyfiles": "^2.4.1", - "esbuild": "^0.16.3", - "live-server": "^1.2.1", - "npx": "^10.2.2", + "esbuild": "^0.16.4", + "live-server": "^1.2.2", "rimraf": "^3.0.2", - "typescript": "^4.3.5", - "@types/offscreencanvas": "^2019.6.4" + "typescript": "^4.9.4" } } diff --git a/spine-ts/spine-phaser/example/index.js b/spine-ts/spine-phaser/example/index.js index 36ee2498d..d9a465510 100644 --- a/spine-ts/spine-phaser/example/index.js +++ b/spine-ts/spine-phaser/example/index.js @@ -8,10 +8,11 @@ var config = { type: Phaser.AUTO, width: 800, height: 600, - type: Phaser.CANVAS, + type: Phaser.WEBGL, scene: { preload: preload, create: create, + update: update, }, plugins: { scene: [ @@ -20,18 +21,35 @@ var config = { } }; -var game = new Phaser.Game(config); +let game = new Phaser.Game(config); +let debug; function preload () { this.load.spineJson("raptor-data", "assets/raptor-pro.json"); this.load.spineAtlas("raptor-atlas", "assets/raptor-pma.atlas"); this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel"); this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas"); + this.load.image("nyan", "nyan.png"); + let canvas = document.querySelector("#game-canvas"); } function create () { let plugin = this.spine; - var raptor = this.add.spine(400, 600, 'raptor-data', "raptor-atlas"); - var spineboy = this.add.spine(400, 600, 'spineboy-data', "spineboy-atlas"); - this.add.text(10, 10, "Spine", { font: '16px Courier', fill: '#00ff00' }); + let x = 25; + let y = 60; + for (let j = 0; j < 10; j++, y+= 600 / 10) { + for (let i = 0; i < 20; i++, x += 800 / 20) { + let obj = Math.random() > 0.5 + ? this.add.spine(x, y, 'spineboy-data', "spineboy-atlas") + : this.add.spine(x, y, 'raptor-data', "raptor-atlas"); + obj.animationState.setAnimation(0, "walk", true); + obj.scale = 0.1; + } + x = 25; + } + debug = this.add.text(0, 600 - 40, "FPS: "); +} + +function update () { + debug.setText("draw calls: " + spine.PolygonBatcher.getAndResetGlobalDrawCalls() + "\ndelta: " + game.loop.delta); } \ No newline at end of file diff --git a/spine-ts/spine-phaser/example/nyan.png b/spine-ts/spine-phaser/example/nyan.png new file mode 100644 index 000000000..506cafe21 Binary files /dev/null and b/spine-ts/spine-phaser/example/nyan.png differ diff --git a/spine-ts/spine-phaser/src/SpineGameObject.ts b/spine-ts/spine-phaser/src/SpineGameObject.ts index ee0db4bbc..3d35a5218 100644 --- a/spine-ts/spine-phaser/src/SpineGameObject.ts +++ b/spine-ts/spine-phaser/src/SpineGameObject.ts @@ -1,7 +1,8 @@ import { SPINE_GAME_OBJECT_TYPE } from "./keys"; import { SpinePlugin } from "./SpinePlugin"; import { ComputedSizeMixin, DepthMixin, FlipMixin, ScrollFactorMixin, TransformMixin, VisibleMixin } from "./mixins"; -import { AnimationState, AnimationStateData, Skeleton } from "@esotericsoftware/spine-core"; +import { AnimationState, AnimationStateData, MathUtils, Skeleton } from "@esotericsoftware/spine-core"; +import { Matrix4, Vector3 } from "@esotericsoftware/spine-webgl"; class BaseSpineGameObject extends Phaser.GameObjects.GameObject { constructor (scene: Phaser.Scene, type: string) { @@ -17,6 +18,7 @@ export class SpineGameObject extends ComputedSizeMixin(DepthMixin(FlipMixin(Scro blendMode = -1; skeleton: Skeleton | null = null; animationState: AnimationState | null = null; + private premultipliedAlpha = false; constructor (scene: Phaser.Scene, private plugin: SpinePlugin, x: number, y: number, dataKey: string, atlasKey: string) { super(scene, SPINE_GAME_OBJECT_TYPE); @@ -26,21 +28,94 @@ export class SpineGameObject extends ComputedSizeMixin(DepthMixin(FlipMixin(Scro setSkeleton (dataKey: string, atlasKey: string) { if (dataKey && atlasKey) { + this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey); this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey); this.animationState = new AnimationState(new AnimationStateData(this.skeleton.data)); } else { this.skeleton = null; + this.animationState = null; } } preUpdate (time: number, delta: number) { + if (!this.skeleton || !this.animationState) return; + + this.animationState.update(delta / 1000); + this.animationState.apply(this.skeleton); + this.skeleton.updateWorldTransform(); } - renderWebGL (renderer: Phaser.Renderer.WebGL.WebGLRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix, container: SpineContainer) { + preDestroy () { + this.skeleton = null; + this.animationState = null; + // FIXME tear down any event emitters + } + willRender (camera: Phaser.Cameras.Scene2D.Camera) { + // FIXME + return true; + } + + renderWebGL (renderer: Phaser.Renderer.WebGL.WebGLRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) { + if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer) return; + + let sceneRenderer = this.plugin.webGLRenderer; + if (renderer.newType) { + renderer.pipelines.clear(); + sceneRenderer.begin(); + } + + camera.addToRenderList(src); + let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc; + let x = transform.tx; + let y = transform.ty; + let scaleX = transform.scaleX; + let scaleY = transform.scaleY; + let rotation = transform.rotationNormalized; + let cosRotation = Math.cos(rotation); + let sinRotation = Math.sin(rotation); + + sceneRenderer.drawSkeleton(this.skeleton, this.premultipliedAlpha, -1, -1, (vertices, numVertices, stride) => { + for (let i = 0; i < numVertices; i += stride) { + let vx = vertices[i]; + let vy = vertices[i + 1]; + let vxOld = vx * scaleX, vyOld = vy * scaleY; + vx = vxOld * cosRotation - vyOld * sinRotation; + vy = vxOld * sinRotation + vyOld * cosRotation; + vx += x; + vy += y; + vertices[i] = vx; + vertices[i + 1] = vy; + } + }); + + if (!renderer.nextTypeMatch) { + sceneRenderer.end(); + renderer.pipelines.rebind(); + console.log("Draw calls: " + sceneRenderer.batcher.getDrawCalls()); + } } renderCanvas (renderer: Phaser.Renderer.Canvas.CanvasRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) { + if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) return; + let context = renderer.currentContext; + let skeletonRenderer = this.plugin.canvasRenderer; + (skeletonRenderer as any).ctx = context; + + camera.addToRenderList(src); + let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc; + let skeleton = this.skeleton; + skeleton.x = transform.tx; + skeleton.y = transform.ty; + skeleton.scaleX = transform.scaleX; + skeleton.scaleY = transform.scaleY; + let root = skeleton.getRootBone()!; + root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized; + this.skeleton.updateWorldTransform(); + + context.save(); + skeletonRenderer.draw(skeleton); + context.restore(); } } \ No newline at end of file diff --git a/spine-ts/spine-phaser/src/SpinePlugin.ts b/spine-ts/spine-phaser/src/SpinePlugin.ts index f0a81879a..d0b94e77a 100644 --- a/spine-ts/spine-phaser/src/SpinePlugin.ts +++ b/spine-ts/spine-phaser/src/SpinePlugin.ts @@ -29,9 +29,9 @@ import Phaser from "phaser"; import { SPINE_ATLAS_CACHE_KEY, SPINE_CONTAINER_TYPE, SPINE_GAME_OBJECT_TYPE, SPINE_ATLAS_TEXTURE_CACHE_KEY, SPINE_SKELETON_DATA_FILE_TYPE, SPINE_ATLAS_FILE_TYPE, SPINE_SKELETON_FILE_CACHE_KEY as SPINE_SKELETON_DATA_CACHE_KEY } from "./keys"; -import { AtlasAttachmentLoader, GLTexture, SceneRenderer, Skeleton, SkeletonData, SkeletonDebugRenderer, SkeletonJson, SkeletonRenderer, TextureAtlas } from "@esotericsoftware/spine-webgl" +import { AtlasAttachmentLoader, Bone, GLTexture, SceneRenderer, Skeleton, SkeletonBinary, SkeletonData, SkeletonJson, TextureAtlas } from "@esotericsoftware/spine-webgl" import { SpineGameObject } from "./SpineGameObject"; -import { CanvasTexture } from "@esotericsoftware/spine-canvas"; +import { CanvasTexture, SkeletonRenderer } from "@esotericsoftware/spine-canvas"; export class SpinePlugin extends Phaser.Plugins.ScenePlugin { game: Phaser.Game; @@ -39,7 +39,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { gl: WebGLRenderingContext | null; textureManager: Phaser.Textures.TextureManager; phaserRenderer: Phaser.Renderer.Canvas.CanvasRenderer | Phaser.Renderer.WebGL.WebGLRenderer | null; - sceneRenderer: SceneRenderer | null; + webGLRenderer: SceneRenderer | null; + canvasRenderer: SkeletonRenderer | null; skeletonDataCache: Phaser.Cache.BaseCache; atlasCache: Phaser.Cache.BaseCache; @@ -50,7 +51,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { this.gl = this.isWebGL ? (this.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl : null; this.textureManager = this.game.textures; this.phaserRenderer = this.game.renderer; - this.sceneRenderer = null; + this.webGLRenderer = null; + this.canvasRenderer = null; this.skeletonDataCache = this.game.cache.addCustom(SPINE_SKELETON_DATA_CACHE_KEY); this.atlasCache = this.game.cache.addCustom(SPINE_ATLAS_CACHE_KEY); @@ -116,28 +118,16 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { } boot () { + Skeleton.yDown = true; if (this.isWebGL) { - // Monkeypatch the Spine setBlendMode functions, or batching is destroyed! - let setBlendMode = function (this: any, srcBlend: any, dstBlend: any) { - if (srcBlend !== this.srcBlend || dstBlend !== this.dstBlend) { - let gl = this.context.gl; - this.srcBlend = srcBlend; - this.dstBlend = dstBlend; - if (this.isDrawing) { - this.flush(); - gl.blendFunc(this.srcBlend, this.dstBlend); - } - } - }; - - var sceneRenderer = this.sceneRenderer; - if (!sceneRenderer) { - sceneRenderer = new SceneRenderer((this.phaserRenderer! as Phaser.Renderer.WebGL.WebGLRenderer).canvas, this.gl!, true); - sceneRenderer.batcher.setBlendMode = setBlendMode; - (sceneRenderer as any).shapes.setBlendMode = setBlendMode; + if (!this.webGLRenderer) { + this.webGLRenderer = new SceneRenderer((this.phaserRenderer! as Phaser.Renderer.WebGL.WebGLRenderer).canvas, this.gl!, true); + } + this.game.scale.on(Phaser.Scale.Events.RESIZE, this.onResize, this); + } else { + if (!this.canvasRenderer) { + this.canvasRenderer = new SkeletonRenderer(this.scene.sys.context); } - - this.sceneRenderer = sceneRenderer; } var eventEmitter = this.systems.events; @@ -148,13 +138,15 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { onResize () { var phaserRenderer = this.phaserRenderer; - var sceneRenderer = this.sceneRenderer; + var sceneRenderer = this.webGLRenderer; if (phaserRenderer && sceneRenderer) { var viewportWidth = phaserRenderer.width; var viewportHeight = phaserRenderer.height; sceneRenderer.camera.position.x = viewportWidth / 2; sceneRenderer.camera.position.y = viewportHeight / 2; + sceneRenderer.camera.up.y = -1; + sceneRenderer.camera.direction.z = 1; sceneRenderer.camera.setViewport(viewportWidth, viewportHeight); } } @@ -173,7 +165,13 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { gameDestroy () { this.pluginManager.removeGameObject(SPINE_GAME_OBJECT_TYPE, true, true); this.pluginManager.removeGameObject(SPINE_CONTAINER_TYPE, true, true); - if (this.sceneRenderer) this.sceneRenderer.dispose(); + if (this.webGLRenderer) this.webGLRenderer.dispose(); + } + + isAtlasPremultiplied(atlasKey: string) { + let atlasFile = this.game.cache.text.get(atlasKey); + if (!atlasFile) return false; + return atlasFile.premultipliedAlpha; } createSkeleton (dataKey: string, atlasKey: string) { @@ -181,8 +179,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { if (this.atlasCache.exists(atlasKey)) { atlas = this.atlasCache.get(atlasKey); } else { - let atlasFile = this.game.cache.text.get(atlasKey) as string; - atlas = new TextureAtlas(atlasFile); + let atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean }; + atlas = new TextureAtlas(atlasFile.data); if (this.isWebGL) { let gl = this.gl!; gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); @@ -207,8 +205,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { skeletonData = json.readSkeletonData(jsonFile); } else { let binaryFile = this.game.cache.binary.get(dataKey) as ArrayBuffer; - let binary = new SkeletonJson(new AtlasAttachmentLoader(atlas)); - skeletonData = binary.readSkeletonData(binaryFile); + let binary = new SkeletonBinary(new AtlasAttachmentLoader(atlas)); + skeletonData = binary.readSkeletonData(new Uint8Array(binaryFile)); } this.skeletonDataCache.add(dataKey, skeletonData); } @@ -254,7 +252,7 @@ export class SpineSkeletonDataFile extends Phaser.Loader.MultiFile { } export class SpineAtlasFile extends Phaser.Loader.MultiFile { - constructor (loader: Phaser.Loader.LoaderPlugin, key: string, url: string, public premultipliedAlpha: boolean, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) { + constructor (loader: Phaser.Loader.LoaderPlugin, key: string, url: string, public premultipliedAlpha: boolean = true, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) { super(loader, SPINE_ATLAS_FILE_TYPE, key, [ new Phaser.Loader.FileTypes.TextFile(loader, { key: key, @@ -305,6 +303,10 @@ export class SpineAtlasFile extends Phaser.Loader.MultiFile { textureManager.addImage(file.key, file.data); } } else { + file.data = { + data: file.data, + premultipliedAlpha: this.premultipliedAlpha || file.data.indexOf("pma: true") >= 0 + }; file.addToCache(); } } diff --git a/spine-ts/spine-phaser/src/index.ts b/spine-ts/spine-phaser/src/index.ts index 0284b7bff..37eb557e8 100644 --- a/spine-ts/spine-phaser/src/index.ts +++ b/spine-ts/spine-phaser/src/index.ts @@ -2,6 +2,6 @@ export * from "./require-shim" export * from "./SpinePlugin" export * from "./mixins" export * from "@esotericsoftware/spine-core"; -export * from "@esotericsoftware/spine-canvas"; +export * from "@esotericsoftware/spine-webgl"; import { SpinePlugin } from "./SpinePlugin"; (window as any).spine = { SpinePlugin: SpinePlugin }; diff --git a/spine-ts/spine-phaser/src/mixins.ts b/spine-ts/spine-phaser/src/mixins.ts index e38206562..63ab0810d 100644 --- a/spine-ts/spine-phaser/src/mixins.ts +++ b/spine-ts/spine-phaser/src/mixins.ts @@ -26,7 +26,7 @@ SOFTWARE. let components = (Phaser.GameObjects.Components as any); export const ComputedSize = components.ComputedSize; -export const Depth = components.ComputedSize; +export const Depth = components.Depth; export const Flip = components.Flip; export const ScrollFactor = components.ScrollFactor; export const Transform = components.Transform; @@ -52,24 +52,11 @@ export function createMixin< ...component: GameObjectComponent[] ): Mixin { return (BaseGameObject) => { - applyMixins(BaseGameObject, component); + (Phaser as any).Class.mixin(BaseGameObject, component); return BaseGameObject as any; }; } -function applyMixins (derivedCtor: any, constructors: any[]) { - constructors.forEach((baseCtor) => { - Object.getOwnPropertyNames(baseCtor.prototype || baseCtor).forEach((name) => { - Object.defineProperty( - derivedCtor.prototype, - name, - Object.getOwnPropertyDescriptor(baseCtor.prototype || baseCtor, name) || - Object.create(null) - ); - }); - }); -} - type ComputedSizeMixin = Mixin; export const ComputedSizeMixin: ComputedSizeMixin = createMixin(ComputedSize); @@ -77,14 +64,14 @@ type DepthMixin = Mixin(Depth); type FlipMixin = Mixin; -export const FlipMixin: FlipMixin = createMixin(Depth); +export const FlipMixin: FlipMixin = createMixin(Flip); type ScrollFactorMixin = Mixin; -export const ScrollFactorMixin: ScrollFactorMixin = createMixin(Depth); +export const ScrollFactorMixin: ScrollFactorMixin = createMixin(ScrollFactor); type TransformMixin = Mixin; export const TransformMixin: TransformMixin = createMixin(Transform); type VisibleMixin = Mixin; -export const VisibleMixin: VisibleMixin = createMixin(Depth); +export const VisibleMixin: VisibleMixin = createMixin(Visible); diff --git a/spine-ts/spine-webgl/src/PolygonBatcher.ts b/spine-ts/spine-webgl/src/PolygonBatcher.ts index 921c6f378..e59490516 100644 --- a/spine-ts/spine-webgl/src/PolygonBatcher.ts +++ b/spine-ts/spine-webgl/src/PolygonBatcher.ts @@ -36,6 +36,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL"; export class PolygonBatcher implements Disposable { private context: ManagedWebGLRenderingContext; private drawCalls = 0; + private static globalDrawCalls = 0; private isDrawing = false; private mesh: Mesh; private shader: Shader | null = null; @@ -120,6 +121,7 @@ export class PolygonBatcher implements Disposable { this.mesh.setVerticesLength(0); this.mesh.setIndicesLength(0); this.drawCalls++; + PolygonBatcher.globalDrawCalls++; } end () { @@ -138,6 +140,12 @@ export class PolygonBatcher implements Disposable { return this.drawCalls; } + static getAndResetGlobalDrawCalls () { + let result = PolygonBatcher.globalDrawCalls; + PolygonBatcher.globalDrawCalls = 0; + return result; + } + dispose () { this.mesh.dispose(); } diff --git a/spine-ts/spine-webgl/src/SceneRenderer.ts b/spine-ts/spine-webgl/src/SceneRenderer.ts index 4fb1cd390..feef35a3e 100644 --- a/spine-ts/spine-webgl/src/SceneRenderer.ts +++ b/spine-ts/spine-webgl/src/SceneRenderer.ts @@ -34,7 +34,7 @@ import { PolygonBatcher } from "./PolygonBatcher"; import { Shader } from "./Shader"; import { ShapeRenderer } from "./ShapeRenderer"; import { SkeletonDebugRenderer } from "./SkeletonDebugRenderer"; -import { SkeletonRenderer } from "./SkeletonRenderer"; +import { SkeletonRenderer, VertexTransformer } from "./SkeletonRenderer"; import { ManagedWebGLRenderingContext } from "./WebGL"; ; @@ -86,10 +86,10 @@ export class SceneRenderer implements Disposable { this.enableRenderer(this.batcher); } - drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1) { + drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) { this.enableRenderer(this.batcher); this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha; - this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd); + this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd, transform); } drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array) { diff --git a/spine-ts/spine-webgl/src/SkeletonRenderer.ts b/spine-ts/spine-webgl/src/SkeletonRenderer.ts index 4de06291c..2a557b9ba 100644 --- a/spine-ts/spine-webgl/src/SkeletonRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonRenderer.ts @@ -37,6 +37,8 @@ class Renderable { constructor (public vertices: NumberArrayLike, public numVertices: number, public numFloats: number) { } }; +export type VertexTransformer = (vertices: NumberArrayLike, numVertices: number, stride: number) => void; + export class SkeletonRenderer { static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; @@ -60,7 +62,7 @@ export class SkeletonRenderer { this.vertices = Utils.newFloatArray(this.vertexSize * 1024); } - draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1) { + draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) { let clipper = this.clipper; let premultipliedAlpha = this.premultipliedAlpha; let twoColorTint = this.twoColorTint; @@ -174,6 +176,7 @@ export class SkeletonRenderer { clipper.clipTriangles(renderable.vertices, renderable.numFloats, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint); let clippedVertices = new Float32Array(clipper.clippedVertices); let clippedTriangles = clipper.clippedTriangles; + if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize); batcher.draw(texture, clippedVertices, clippedTriangles); } else { let verts = renderable.vertices; @@ -201,6 +204,7 @@ export class SkeletonRenderer { } } let view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats); + if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize); batcher.draw(texture, view, triangles); } }