mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
Refactored rendering and matrix into specific class defined in c3 lib.
This commit is contained in:
parent
86a0950a5f
commit
683574fb41
26
spine-ts/spine-construct3/LICENSE
Normal file
26
spine-ts/spine-construct3/LICENSE
Normal file
@ -0,0 +1,26 @@
|
||||
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.
|
||||
3
spine-ts/spine-construct3/README.md
Normal file
3
spine-ts/spine-construct3/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# spine-ts Construct3
|
||||
|
||||
Please see the top-level [README.md](../README.md) for more information.
|
||||
@ -1,3 +1,3 @@
|
||||
# spine-ts THREE.JS
|
||||
# spine-ts Construct3 Lib
|
||||
|
||||
Please see the top-level [README.md](../README.md) for more information.
|
||||
This is just an internal lib that contains spine-core and additional basic functions for C3.
|
||||
@ -28,7 +28,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import { AtlasAttachmentLoader, SkeletonBinary, type SkeletonData, SkeletonJson, TextureAtlas, TextureAtlasPage } from "@esotericsoftware/spine-core";
|
||||
import { C3Texture, C3TextureEditor } from "./C3Texture";
|
||||
import { C3TextureEditor, C3TextureRuntime } from "./C3Texture";
|
||||
|
||||
|
||||
interface CacheEntry<T> {
|
||||
@ -40,7 +40,7 @@ export class AssetLoader {
|
||||
|
||||
private static CacheSkeleton = new Map<string, CacheEntry<SkeletonData>>();
|
||||
private static CacheAtlas = new Map<string, CacheEntry<TextureAtlas>>();
|
||||
private static CacheTexture = new Map<string, CacheEntry<C3Texture>>();
|
||||
private static CacheTexture = new Map<string, CacheEntry<C3TextureRuntime>>();
|
||||
|
||||
public async loadSkeletonEditor (sid: number, textureAtlas: TextureAtlas, scale = 1, instance: SDK.IWorldInstance) {
|
||||
const projectFile = instance.GetProject().GetProjectFileBySID(sid);
|
||||
@ -167,7 +167,7 @@ export class AssetLoader {
|
||||
const image = await AssetLoader.createImageBitmapFromBlob(content, page.pma);
|
||||
if (!image) return null;
|
||||
|
||||
const spineTexture = new C3Texture(image, renderer, page);
|
||||
const spineTexture = new C3TextureRuntime(image, renderer, page);
|
||||
|
||||
this.addToCache(AssetLoader.CacheTexture, cacheKey, spineTexture);
|
||||
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
/******************************************************************************
|
||||
* 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 { type Bone, Vector2 } from "@esotericsoftware/spine-core";
|
||||
|
||||
export class C3Matrix {
|
||||
|
||||
public a = 0;
|
||||
public b = 0;
|
||||
public c = 0;
|
||||
public d = 0;
|
||||
public tx = 0;
|
||||
public ty = 0;
|
||||
|
||||
private tempPoint = new Vector2();
|
||||
|
||||
public update (x: number, y: number, angle: number) {
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
this.a = cos;
|
||||
this.b = sin;
|
||||
this.c = -sin;
|
||||
this.d = cos;
|
||||
this.tx = x;
|
||||
this.ty = y;
|
||||
}
|
||||
|
||||
public gameToSkeleton (x: number, y: number) {
|
||||
const tx = x - this.tx;
|
||||
const ty = y - this.ty;
|
||||
const { a, b, c, d, tempPoint } = this;
|
||||
const delta = a * d - b * c;
|
||||
tempPoint.x = (d * tx - c * ty) / delta;
|
||||
tempPoint.y = (a * ty - b * tx) / delta;
|
||||
return tempPoint;
|
||||
}
|
||||
|
||||
public gameToBone (x: number, y: number, bone: Bone) {
|
||||
const point = this.gameToSkeleton(x, y);
|
||||
if (bone.parent)
|
||||
return bone.parent.applied.worldToLocal(point);
|
||||
return bone.applied.worldToLocal(point);
|
||||
}
|
||||
|
||||
public skeletonToGame = (skeletonX: number, skeletonY: number) => {
|
||||
const { a, b, c, d, tempPoint } = this;
|
||||
tempPoint.x = a * skeletonX + c * skeletonY + this.tx;
|
||||
tempPoint.y = b * skeletonX + d * skeletonY + this.ty;
|
||||
return tempPoint;
|
||||
}
|
||||
|
||||
public boneToGame (bone: Bone) {
|
||||
const { applied } = bone;
|
||||
return this.skeletonToGame(applied.worldX, applied.worldY);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,239 @@
|
||||
/******************************************************************************
|
||||
* 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 { type BlendMode, type Bone, MathUtils, type Skeleton, SkeletonRendererCore, Vector2 } from "@esotericsoftware/spine-core";
|
||||
import type { C3Matrix } from "./C3Matrix";
|
||||
import { BlendingModeSpineToC3, type C3TextureEditor, type C3TextureRuntime } from "./C3Texture";
|
||||
|
||||
type C3Renderer = IRenderer | SDK.Gfx.IWebGLRenderer;
|
||||
type C3Texture = C3TextureRuntime | C3TextureEditor;
|
||||
type C3Quad = DOMQuad | SDK.Quad;
|
||||
|
||||
abstract class C3SkeletonRenderer<
|
||||
Renderer extends C3Renderer,
|
||||
Texture extends C3Texture,
|
||||
> extends SkeletonRendererCore {
|
||||
|
||||
private tempVertices = new Float32Array(4096);
|
||||
private tempColors = new Float32Array(4096);
|
||||
private tempPoint = new Vector2();
|
||||
private inv255 = 1 / 255;
|
||||
|
||||
constructor (
|
||||
protected renderer: Renderer,
|
||||
private skeleton: Skeleton,
|
||||
protected matrix: C3Matrix,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
draw (opacity = 1) {
|
||||
const { skeleton, matrix, inv255 } = this;
|
||||
|
||||
let command = this.render(skeleton);
|
||||
while (command) {
|
||||
const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command;
|
||||
|
||||
const vertices = this.tempVertices.length < numVertices * 3
|
||||
? (this.tempVertices = new Float32Array(numVertices * 3))
|
||||
: this.tempVertices;
|
||||
|
||||
const c3colors = this.tempColors.length < numVertices * 4
|
||||
? (this.tempColors = new Float32Array(numVertices * 4))
|
||||
: this.tempColors;
|
||||
|
||||
for (let i = 0; i < numVertices; i++) {
|
||||
const srcIndex = i * 2;
|
||||
const { x, y } = matrix.skeletonToGame(positions[srcIndex], positions[srcIndex + 1]);
|
||||
|
||||
const dstIndex = i * 3;
|
||||
vertices[dstIndex] = x;
|
||||
vertices[dstIndex + 1] = y;
|
||||
vertices[dstIndex + 2] = 0;
|
||||
|
||||
const color = colors[i];
|
||||
const colorDst = i * 4;
|
||||
const alpha = (color >>> 24 & 0xFF) * inv255 * opacity;
|
||||
const alphaInverse = inv255 * alpha;
|
||||
c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 3] = alpha;
|
||||
}
|
||||
|
||||
this.renderSkeleton(
|
||||
vertices.subarray(0, numVertices * 3),
|
||||
uvs.subarray(0, numVertices * 2),
|
||||
indices.subarray(0, numIndices),
|
||||
c3colors.subarray(0, numVertices * 4),
|
||||
command.texture,
|
||||
blendMode)
|
||||
command = command.next;
|
||||
}
|
||||
}
|
||||
|
||||
drawDebug (x: number, y: number, quad: C3Quad) {
|
||||
const { skeleton, matrix } = this;
|
||||
|
||||
const bones = skeleton.bones;
|
||||
for (let i = 0, n = bones.length; i < n; i++) {
|
||||
const bone = bones[i];
|
||||
if (!bone.parent) continue;
|
||||
const boneApplied = bone.applied;
|
||||
const { x: x1, y: y1 } = matrix.skeletonToGame(boneApplied.worldX, boneApplied.worldY);
|
||||
const x2 = bone.data.length * boneApplied.a + x1;
|
||||
const y2 = bone.data.length * boneApplied.c + y1;
|
||||
|
||||
this.setColor(1, 0, 0, 1);
|
||||
this.setColorFillMode();
|
||||
|
||||
const t = this.tempPoint.set(y2 - y1, x1 - x2);
|
||||
t.normalize();
|
||||
const width = 1 * 0.5;
|
||||
const tx = t.x * width;
|
||||
const ty = t.y * width;
|
||||
this.poly([
|
||||
x1 + tx, y1 + ty,
|
||||
x1 - tx, y1 - ty,
|
||||
x2 + tx, y2 + ty,
|
||||
x2 - tx, y2 - ty,
|
||||
x2 + tx, y2 + ty,
|
||||
x1 - tx, y1 - ty,
|
||||
]);
|
||||
|
||||
this.setColor(0, 1, 0, 1);
|
||||
this.poly(this.circle(x1, y1, 2));
|
||||
}
|
||||
|
||||
this.renderGameObjectBounds(x, y, quad);
|
||||
}
|
||||
|
||||
protected abstract setColor (r: number, g: number, b: number, a: number): void;
|
||||
protected abstract setColorFillMode (): void;
|
||||
protected abstract poly (points: number[]): void;
|
||||
|
||||
protected abstract renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: Texture, blendMode: BlendMode): void;
|
||||
public abstract renderGameObjectBounds (x: number, y: number, quad: DOMQuad | SDK.Quad): void;
|
||||
|
||||
protected circle (x: number, y: number, radius: number) {
|
||||
let segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
||||
if (segments <= 0) throw new Error("segments must be > 0.");
|
||||
const angle = 2 * MathUtils.PI / segments;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let cx = radius, cy = 0;
|
||||
segments--;
|
||||
const poly = [];
|
||||
for (let i = 0; i < segments; i++) {
|
||||
poly.push(x, y);
|
||||
poly.push(x + cx, y + cy);
|
||||
const temp = cx;
|
||||
cx = cos * cx - sin * cy;
|
||||
cy = sin * temp + cos * cy;
|
||||
poly.push(x + cx, y + cy);
|
||||
}
|
||||
poly.push(x, y, x + cx, y + cy);
|
||||
cx = radius;
|
||||
cy = 0;
|
||||
poly.push(x + cx, y + cy);
|
||||
return poly;
|
||||
}
|
||||
}
|
||||
|
||||
export class C3RendererRuntime extends C3SkeletonRenderer<IRenderer, C3TextureRuntime> {
|
||||
constructor (renderer: IRenderer, skeleton: Skeleton, matrix: C3Matrix) {
|
||||
super(renderer, skeleton, matrix);
|
||||
}
|
||||
|
||||
protected setColor (r: number, g: number, b: number, a: number): void {
|
||||
this.renderer.setColor([r, g, b, a]);
|
||||
}
|
||||
|
||||
protected setColorFillMode (): void {
|
||||
this.renderer.setColorFillMode();
|
||||
}
|
||||
|
||||
protected poly (points: number[]): void {
|
||||
this.renderer.convexPoly(points);
|
||||
}
|
||||
|
||||
protected renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: C3TextureRuntime, blendMode: BlendMode) {
|
||||
this.renderer.setTexture(texture.texture);
|
||||
this.renderer.setBlendMode(BlendingModeSpineToC3[blendMode]);
|
||||
this.renderer.drawMesh(vertices, uvs, indices, colors);
|
||||
};
|
||||
|
||||
public renderDragHandles (bone: Bone, radius: number) {
|
||||
const boneApplied = bone.applied;
|
||||
const { x: x1, y: y1 } = this.matrix.skeletonToGame(boneApplied.worldX, boneApplied.worldY);
|
||||
this.renderer.setColorFillMode();
|
||||
this.renderer.setColor([1, 0, 0, .2]);
|
||||
this.renderer.convexPoly(this.circle(x1, y1, radius));
|
||||
}
|
||||
|
||||
public renderGameObjectBounds (x: number, y: number, quad: DOMQuad) {
|
||||
const { renderer, matrix } = this;
|
||||
renderer.setAlphaBlendMode();
|
||||
renderer.setColorFillMode();
|
||||
renderer.setColorRgba(0.25, 0, 0, 0.25);
|
||||
renderer.lineQuad(quad);
|
||||
renderer.line(x, y, matrix.tx, matrix.ty);
|
||||
};
|
||||
}
|
||||
|
||||
export class C3RendererEditor extends C3SkeletonRenderer<SDK.Gfx.IWebGLRenderer, C3TextureEditor> {
|
||||
protected setColor (r: number, g: number, b: number, a: number): void {
|
||||
this.renderer.SetColorRgba(r, g, b, a);
|
||||
}
|
||||
|
||||
protected setColorFillMode (): void {
|
||||
this.renderer.SetColorFillMode();
|
||||
}
|
||||
|
||||
protected poly (points: number[]): void {
|
||||
this.renderer.ConvexPoly(points);
|
||||
}
|
||||
|
||||
protected renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: C3TextureEditor, blendMode: BlendMode) {
|
||||
this.renderer.ResetColor();
|
||||
this.renderer.SetBlendMode(BlendingModeSpineToC3[blendMode]);
|
||||
this.renderer.SetTextureFillMode();
|
||||
this.renderer.SetTexture(texture.texture);
|
||||
this.renderer.DrawMesh(vertices, uvs, indices, colors);
|
||||
};
|
||||
|
||||
public renderGameObjectBounds (x: number, y: number, quad: SDK.Quad): void {
|
||||
const { renderer, matrix } = this;
|
||||
renderer.SetAlphaBlend();
|
||||
renderer.SetColorFillMode();
|
||||
renderer.SetColorRgba(0.25, 0, 0, 0.25);
|
||||
renderer.LineQuad(quad);
|
||||
renderer.Line(x, y, matrix.tx, matrix.ty);
|
||||
}
|
||||
}
|
||||
@ -61,7 +61,7 @@ export class C3TextureEditor extends Texture {
|
||||
}
|
||||
}
|
||||
|
||||
export class C3Texture extends Texture {
|
||||
export class C3TextureRuntime extends Texture {
|
||||
texture: ITexture;
|
||||
renderer?: IRenderer;
|
||||
|
||||
|
||||
@ -1,3 +1,32 @@
|
||||
/******************************************************************************
|
||||
* 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 { AnimationState, Physics, Skeleton, SkeletonClipping, Skin } from "@esotericsoftware/spine-core";
|
||||
|
||||
interface Rectangle {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export * from "@esotericsoftware/spine-core";
|
||||
export * from './AssetLoader.js';
|
||||
export * from './C3Matrix.js';
|
||||
export * from './C3SkeletonRenderer.js';
|
||||
export * from './C3Texture.js';
|
||||
export * from './SpineBoundsProvider.js';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { AnimationState, AnimationStateListener, AssetLoader, Bone, Event, NumberArrayLike, RegionAttachment, Skeleton, SkeletonRendererCore, Skin, Slot, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib";
|
||||
import type { AnimationState, AnimationStateListener, AssetLoader, Bone, C3Matrix, C3RendererRuntime, Event, NumberArrayLike, RegionAttachment, Skeleton, Skin, Slot, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib";
|
||||
|
||||
const C3 = globalThis.C3;
|
||||
const spine = globalThis.spine;
|
||||
@ -39,12 +39,8 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
public triggeredEventData?: Event;
|
||||
|
||||
private assetLoader: AssetLoader;
|
||||
private skeletonRenderer: SkeletonRendererCore;
|
||||
|
||||
private a = 0;
|
||||
private b = 0;
|
||||
private c = 0;
|
||||
private d = 0;
|
||||
private skeletonRenderer?: C3RendererRuntime;
|
||||
private matrix: C3Matrix;
|
||||
|
||||
private tempVertices = new Float32Array(4096);
|
||||
private tempColors = new Float32Array(4096);
|
||||
@ -86,7 +82,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
}
|
||||
|
||||
this.assetLoader = new spine.AssetLoader();
|
||||
this.skeletonRenderer = new spine.SkeletonRendererCore();
|
||||
this.matrix = new spine.C3Matrix();
|
||||
|
||||
this._setTicking(true);
|
||||
}
|
||||
@ -125,7 +121,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
}
|
||||
|
||||
private update (delta: number) {
|
||||
const { state, skeleton, animationSpeed, physicsMode } = this;
|
||||
const { state, skeleton, animationSpeed, physicsMode, matrix } = this;
|
||||
|
||||
if (!skeleton || !state) return;
|
||||
|
||||
@ -133,17 +129,13 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
state.update(adjustedDelta);
|
||||
skeleton.update(adjustedDelta);
|
||||
state.apply(skeleton);
|
||||
|
||||
const cos = Math.cos(this.angle + this.propOffsetAngle);
|
||||
const sin = Math.sin(this.angle + this.propOffsetAngle);
|
||||
this.a = cos;
|
||||
this.b = sin;
|
||||
this.c = -sin;
|
||||
this.d = cos;
|
||||
|
||||
matrix.update(
|
||||
this.x + this.propOffsetX,
|
||||
this.y + this.propOffsetY,
|
||||
this.angle + this.propOffsetAngle);
|
||||
skeleton.updateWorldTransform(physicsMode);
|
||||
|
||||
this.updateHandles(skeleton);
|
||||
this.updateHandles(skeleton, matrix);
|
||||
this.updateBoneFollowers();
|
||||
}
|
||||
|
||||
@ -156,134 +148,19 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
const { skeleton } = this;
|
||||
if (!skeleton) return;
|
||||
|
||||
this.renderSkeleton(renderer, skeleton);
|
||||
this.renderDragHandles(renderer);
|
||||
this.renderDebugSkeleton(renderer, skeleton);
|
||||
this.skeletonRenderer ||= new spine.C3RendererRuntime(renderer, skeleton, this.matrix);
|
||||
this.skeletonRenderer.draw(this.opacity);
|
||||
if (this.propDebugSkeleton) this.skeletonRenderer.drawDebug(this.x, this.y, this.getBoundingQuad(false));
|
||||
this.renderDragHandles();
|
||||
}
|
||||
|
||||
private renderSkeleton (renderer: IRenderer, skeleton: Skeleton) {
|
||||
let command = this.skeletonRenderer.render(skeleton);
|
||||
const opacity = this.opacity;
|
||||
const inv255 = 1 / 255;
|
||||
while (command) {
|
||||
const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command;
|
||||
|
||||
const vertices = this.tempVertices.length < numVertices * 3
|
||||
? (this.tempVertices = new Float32Array(numVertices * 3))
|
||||
: this.tempVertices;
|
||||
|
||||
const c3colors = this.tempColors.length < numVertices * 4
|
||||
? (this.tempColors = new Float32Array(numVertices * 4))
|
||||
: this.tempColors;
|
||||
|
||||
for (let i = 0; i < numVertices; i++) {
|
||||
const srcIndex = i * 2;
|
||||
const { x, y } = this.skeletonToC3WorldCoordinates(positions[srcIndex], positions[srcIndex + 1]);
|
||||
|
||||
const dstIndex = i * 3;
|
||||
vertices[dstIndex] = x;
|
||||
vertices[dstIndex + 1] = y;
|
||||
vertices[dstIndex + 2] = 0;
|
||||
|
||||
const color = colors[i];
|
||||
const colorDst = i * 4;
|
||||
const alpha = (color >>> 24 & 0xFF) * inv255 * opacity;
|
||||
const alphaInverse = inv255 * alpha;
|
||||
c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 3] = alpha;
|
||||
}
|
||||
|
||||
renderer.setTexture(command.texture.texture);
|
||||
renderer.setBlendMode(spine.BlendingModeSpineToC3[blendMode]);
|
||||
renderer.drawMesh(
|
||||
vertices.subarray(0, numVertices * 3),
|
||||
uvs.subarray(0, numVertices * 2),
|
||||
indices.subarray(0, numIndices),
|
||||
c3colors.subarray(0, numVertices * 4),
|
||||
);
|
||||
|
||||
command = command.next;
|
||||
}
|
||||
}
|
||||
|
||||
private renderDragHandles (renderer: IRenderer) {
|
||||
private renderDragHandles () {
|
||||
for (const { bone, radius, debug } of this.dragHandles) {
|
||||
if (!debug) continue;
|
||||
const boneApplied = bone.applied;
|
||||
const { x: x1, y: y1 } = this.skeletonToC3WorldCoordinates(boneApplied.worldX, boneApplied.worldY);
|
||||
renderer.setColorFillMode();
|
||||
renderer.setColor([1, 0, 0, .2]);
|
||||
renderer.convexPoly(this.circle(x1, y1, radius));
|
||||
this.skeletonRenderer?.renderDragHandles(bone, radius);
|
||||
}
|
||||
}
|
||||
|
||||
private renderDebugSkeleton (renderer: IRenderer, skeleton: Skeleton) {
|
||||
if (!this.propDebugSkeleton) return;
|
||||
|
||||
const bones = skeleton.bones;
|
||||
for (let i = 0, n = bones.length; i < n; i++) {
|
||||
const bone = bones[i];
|
||||
if (!bone.parent) continue;
|
||||
const boneApplied = bone.applied;
|
||||
const { x: x1, y: y1 } = this.skeletonToC3WorldCoordinates(boneApplied.worldX, boneApplied.worldY);
|
||||
const x2 = bone.data.length * boneApplied.a + x1;
|
||||
const y2 = bone.data.length * boneApplied.c + y1;
|
||||
|
||||
renderer.setColor([1, 0, 0, 1]);
|
||||
renderer.setColorFillMode();
|
||||
|
||||
const t = this.tempPoint.set(y2 - y1, x1 - x2);
|
||||
t.normalize();
|
||||
const width = 1 * 0.5;
|
||||
const tx = t.x * width;
|
||||
const ty = t.y * width;
|
||||
renderer.convexPoly([
|
||||
x1 + tx, y1 + ty,
|
||||
x1 - tx, y1 - ty,
|
||||
x2 + tx, y2 + ty,
|
||||
x2 - tx, y2 - ty,
|
||||
x2 + tx, y2 + ty,
|
||||
x1 - tx, y1 - ty,
|
||||
]);
|
||||
|
||||
renderer.setColor([0, 1, 0, 1]);
|
||||
renderer.convexPoly(this.circle(x1, y1, 2));
|
||||
}
|
||||
|
||||
// debug bounds
|
||||
renderer.setAlphaBlendMode();
|
||||
renderer.setColorFillMode();
|
||||
renderer.setColorRgba(0.25, 0, 0, 0.25);
|
||||
renderer.lineQuad(this.getBoundingQuad(false));
|
||||
renderer.line(this.x, this.y, this.x + this.propOffsetX, this.y + this.propOffsetY);
|
||||
}
|
||||
|
||||
private circle (x: number, y: number, radius: number) {
|
||||
let segments = Math.max(1, (6 * spine.MathUtils.cbrt(radius)) | 0);
|
||||
if (segments <= 0) throw new Error("segments must be > 0.");
|
||||
const angle = 2 * spine.MathUtils.PI / segments;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
let cx = radius, cy = 0;
|
||||
segments--;
|
||||
const poly = [];
|
||||
for (let i = 0; i < segments; i++) {
|
||||
poly.push(x, y);
|
||||
poly.push(x + cx, y + cy);
|
||||
const temp = cx;
|
||||
cx = cos * cx - sin * cy;
|
||||
cy = sin * temp + cos * cy;
|
||||
poly.push(x + cx, y + cy);
|
||||
}
|
||||
poly.push(x, y, x + cx, y + cy);
|
||||
cx = radius;
|
||||
cy = 0;
|
||||
poly.push(x + cx, y + cy);
|
||||
return poly;
|
||||
}
|
||||
|
||||
/**********/
|
||||
|
||||
|
||||
@ -327,7 +204,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
}
|
||||
}
|
||||
|
||||
private updateHandles (skeleton: Skeleton) {
|
||||
private updateHandles (skeleton: Skeleton, matrix: C3Matrix) {
|
||||
if (this.dragHandles.size === 0) return;
|
||||
|
||||
// accessing mouse without having a mouse object will throw an error
|
||||
@ -357,16 +234,16 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
|
||||
if (handleObject.dragging) {
|
||||
if (bone.parent) {
|
||||
const { x, y } = this.c3WorldCoordinatesToBone(mx - handleObject.offsetX, my - handleObject.offsetY, bone);
|
||||
const { x, y } = matrix.gameToBone(mx - handleObject.offsetX, my - handleObject.offsetY, bone);
|
||||
boneApplied.x = x;
|
||||
boneApplied.y = y;
|
||||
} else {
|
||||
const { x, y } = this.c3WorldCoordinatesToSkeleton(mx - handleObject.offsetX, my - handleObject.offsetY);
|
||||
const { x, y } = matrix.gameToSkeleton(mx - handleObject.offsetX, my - handleObject.offsetY);
|
||||
boneApplied.x = x / skeleton.scaleX;
|
||||
boneApplied.y = -y / skeleton.scaleY * spine.Skeleton.yDir;
|
||||
}
|
||||
} else if (!this.prevLeftClickDown) {
|
||||
const { x, y } = this.c3WorldCoordinatesToSkeleton(mx, my);
|
||||
const { x, y } = matrix.gameToSkeleton(mx, my);
|
||||
const inside = handleObject.slot
|
||||
? this.isInsideSlot(x, y, handleObject.slot, true)
|
||||
: this.inRadius(x, y, boneApplied.worldX, boneApplied.worldY, handleObject.radius);
|
||||
@ -402,7 +279,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
|
||||
if (skeletonCoordinate) return this.isPointInPolygon(vertices, hullLength, x, y);
|
||||
|
||||
const coords = this.c3WorldCoordinatesToSkeleton(x, y);
|
||||
const coords = this.matrix.gameToSkeleton(x, y);
|
||||
return this.isPointInPolygon(vertices, hullLength, coords.x, coords.y);
|
||||
}
|
||||
|
||||
@ -806,7 +683,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
const instance = this.runtime.getInstanceByUid(follower.uid) as IWorldInstance;
|
||||
if (!instance) continue;
|
||||
|
||||
const { x, y } = this.boneToC3WorldCoordinates(bone);
|
||||
const { x, y } = this.matrix.boneToGame(bone);
|
||||
const boneRotation = bone.applied.getWorldRotationX();
|
||||
|
||||
// Apply rotation to offset
|
||||
@ -824,41 +701,6 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
|
||||
/**********/
|
||||
|
||||
/*
|
||||
* Coordinates transformation
|
||||
*/
|
||||
|
||||
private c3WorldCoordinatesToSkeleton (x: number, y: number) {
|
||||
const tx = x - (this.x + this.propOffsetX);
|
||||
const ty = y - (this.y + this.propOffsetY);
|
||||
const { a: ta, b: tb, c: tc, d: td, tempPoint } = this;
|
||||
const delta = ta * td - tb * tc;
|
||||
tempPoint.x = (td * tx - tc * ty) / delta;
|
||||
tempPoint.y = (ta * ty - tb * tx) / delta;
|
||||
return this.tempPoint;
|
||||
}
|
||||
|
||||
private c3WorldCoordinatesToBone (x: number, y: number, bone: Bone) {
|
||||
const point = this.c3WorldCoordinatesToSkeleton(x, y);
|
||||
if (bone.parent)
|
||||
return bone.parent.applied.worldToLocal(point);
|
||||
return bone.applied.worldToLocal(point);
|
||||
}
|
||||
|
||||
private skeletonToC3WorldCoordinates (skeletonX: number, skeletonY: number) {
|
||||
const { a, b, c, d, tempPoint } = this;
|
||||
tempPoint.x = a * skeletonX + c * skeletonY + this.x + this.propOffsetX;
|
||||
tempPoint.y = b * skeletonX + d * skeletonY + this.y + this.propOffsetY;
|
||||
return tempPoint;
|
||||
}
|
||||
|
||||
private boneToC3WorldCoordinates (bone: Bone) {
|
||||
const { applied } = bone;
|
||||
return this.skeletonToC3WorldCoordinates(applied.worldX, applied.worldY);
|
||||
}
|
||||
|
||||
/**********/
|
||||
|
||||
/*
|
||||
* Bone
|
||||
*/
|
||||
@ -947,7 +789,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const point = this.boneToC3WorldCoordinates(bone);
|
||||
const point = this.matrix.boneToGame(bone);
|
||||
return point.x;
|
||||
}
|
||||
|
||||
@ -964,7 +806,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const point = this.boneToC3WorldCoordinates(bone);
|
||||
const point = this.matrix.boneToGame(bone);
|
||||
return point.y;
|
||||
}
|
||||
|
||||
@ -972,7 +814,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
const bone = this.getBone(boneName);
|
||||
if (!bone) return;
|
||||
|
||||
const { x, y } = this.c3WorldCoordinatesToBone(c3X, c3Y, bone);
|
||||
const { x, y } = this.matrix.gameToBone(c3X, c3Y, bone);
|
||||
bone.applied.x = x;
|
||||
bone.applied.y = y;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// / <reference types="editor/sdk" />
|
||||
|
||||
import type { AnimationState, AssetLoader, Skeleton, SkeletonRendererCore, SpineBoundsProvider, TextureAtlas } from "@esotericsoftware/spine-construct3-lib";
|
||||
import type { AnimationState, AssetLoader, C3Matrix, C3RendererEditor, Skeleton, SpineBoundsProvider, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib";
|
||||
import type { SpineC3PluginType } from "./type";
|
||||
|
||||
const SDK = globalThis.SDK;
|
||||
@ -25,7 +25,8 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
animation?: string;
|
||||
|
||||
private assetLoader: AssetLoader;
|
||||
private skeletonRenderer: SkeletonRendererCore;
|
||||
private skeletonRenderer?: C3RendererEditor;
|
||||
private matrix: C3Matrix;
|
||||
|
||||
// position mode
|
||||
private positioningBounds = false;
|
||||
@ -40,10 +41,6 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
height: 200,
|
||||
};
|
||||
|
||||
// utils for drawing
|
||||
private tempVertices = new Float32Array(4096);
|
||||
private tempColors = new Float32Array(4096);
|
||||
|
||||
// errors
|
||||
private errorTextureAtlas?: string;
|
||||
private errorSkeleton?: string;
|
||||
@ -56,7 +53,7 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
spine.Skeleton.yDown = true;
|
||||
|
||||
this.assetLoader = new spine.AssetLoader();
|
||||
this.skeletonRenderer = new spine.SkeletonRendererCore();
|
||||
this.matrix = new spine.C3Matrix();
|
||||
}
|
||||
|
||||
Release () {
|
||||
@ -118,64 +115,13 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
skeleton.scaleY = _inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number;
|
||||
}
|
||||
|
||||
const opacity = _inst.GetOpacity();
|
||||
const cos = Math.cos(offsetAngle);
|
||||
const sin = Math.sin(offsetAngle);
|
||||
const inv255 = 1 / 255;
|
||||
|
||||
this.update(0);
|
||||
let command = this.skeletonRenderer.render(skeleton);
|
||||
while (command) {
|
||||
const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command;
|
||||
|
||||
const vertices = this.tempVertices.length < numVertices * 3
|
||||
? (this.tempVertices = new Float32Array(numVertices * 3))
|
||||
: this.tempVertices;
|
||||
|
||||
const c3colors = this.tempColors.length < numVertices * 4
|
||||
? (this.tempColors = new Float32Array(numVertices * 4))
|
||||
: this.tempColors;
|
||||
|
||||
for (let i = 0; i < numVertices; i++) {
|
||||
const srcIndex = i * 2;
|
||||
const dstIndex = i * 3;
|
||||
const x = positions[srcIndex];
|
||||
const y = positions[srcIndex + 1];
|
||||
vertices[dstIndex] = x * cos - y * sin + offsetX;
|
||||
vertices[dstIndex + 1] = x * sin + y * cos + offsetY;
|
||||
vertices[dstIndex + 2] = 0;
|
||||
|
||||
const color = colors[i];
|
||||
const colorDst = i * 4;
|
||||
const alpha = (color >>> 24 & 0xFF) * inv255 * opacity;
|
||||
const alphaInverse = inv255 * alpha;
|
||||
c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse;
|
||||
c3colors[colorDst + 3] = alpha;
|
||||
}
|
||||
|
||||
iRenderer.ResetColor();
|
||||
iRenderer.SetBlendMode(spine.BlendingModeSpineToC3[blendMode]);
|
||||
iRenderer.SetTextureFillMode();
|
||||
iRenderer.SetTexture(command.texture.texture);
|
||||
|
||||
iRenderer.DrawMesh(
|
||||
vertices.subarray(0, numVertices * 3),
|
||||
uvs.subarray(0, numVertices * 2),
|
||||
indices.subarray(0, numIndices),
|
||||
c3colors,
|
||||
);
|
||||
|
||||
|
||||
command = command.next;
|
||||
}
|
||||
|
||||
iRenderer.SetAlphaBlend();
|
||||
iRenderer.SetColorFillMode();
|
||||
iRenderer.SetColorRgba(0.25, 0, 0, 0.25);
|
||||
iRenderer.LineQuad(_inst.GetQuad());
|
||||
iRenderer.Line(rectX, rectY, offsetX, offsetY);
|
||||
this.skeletonRenderer ||= new spine.C3RendererEditor(iRenderer, skeleton, this.matrix);
|
||||
this.skeletonRenderer.draw(_inst.GetOpacity());
|
||||
const quad = _inst.GetQuad();
|
||||
if (_inst.GetPropertyValue(PLUGIN_CLASS.PROP_DEBUG_SKELETON) as boolean)
|
||||
this.skeletonRenderer.drawDebug(rectX, rectY, quad);
|
||||
this.skeletonRenderer.renderGameObjectBounds(rectX, rectY, quad);
|
||||
|
||||
} else {
|
||||
iRenderer.SetAlphaBlend();
|
||||
@ -501,6 +447,10 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
state.update(delta);
|
||||
skeleton.update(delta);
|
||||
state.apply(skeleton);
|
||||
this.matrix.update(
|
||||
this._inst.GetX() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number),
|
||||
this._inst.GetY() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number),
|
||||
this._inst.GetAngle() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number));
|
||||
skeleton.updateWorldTransform(spine.Physics.update);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user