Bounds added as properties. Works also in runtime.

This commit is contained in:
Davide Tantillo 2025-08-13 17:58:23 +02:00
parent 8dec202923
commit da12c23c73
4 changed files with 144 additions and 89 deletions

View File

@ -1,4 +1,4 @@
import { AnimationEventType, AnimationStateListener, BlendingModeSpineToC3, EventType, TrackEntry, type AnimationState, type AssetLoader, type Event, type Skeleton, type SkeletonRendererCore, type SpineBoundsProvider, type TextureAtlas } from "@esotericsoftware/spine-construct3-lib";
import { AnimationEventType, type AnimationState, AnimationStateListener, type AssetLoader, BlendingModeSpineToC3, type Event, EventType, type Skeleton, type SkeletonRendererCore, type SpineBoundsProvider, type TextureAtlas, TrackEntry } from "@esotericsoftware/spine-construct3-lib";
const C3 = globalThis.C3;
const spine = globalThis.spine;
@ -6,10 +6,15 @@ const spine = globalThis.spine;
spine.Skeleton.yDown = true;
class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
atlasProp = "";
skelProp = "";
skinProp: string[] = [];
animationProp?: string;
propAtlas = "";
propSkel = "";
propSkin: string[] = [];
propAnimation?: string;
propOffsetX = 0;
propOffsetY = 0;
propOffsetAngle = 0;
propScaleX = 1;
propScaleY = 1;
textureAtlas?: TextureAtlas;
renderer?: IRenderer;
@ -20,42 +25,31 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
skeleton?: Skeleton;
state?: AnimationState;
private ratio: number;
private assetLoader: AssetLoader;
private skeletonRenderer: SkeletonRendererCore;
private spineBoundsProvider: SpineBoundsProvider;
private _spineBounds?: {
x: number;
y: number;
width: number;
height: number;
};
constructor () {
super();
const properties = this._getInitProperties();
if (properties) {
this.atlasProp = properties[0] as string;
this.skelProp = properties[1] as string;
this.propAtlas = properties[0] as string;
this.propSkel = properties[1] as string;
const skinProp = properties[2] as string;
this.skinProp = skinProp === "" ? [] : skinProp.split(",");
this.animationProp = properties[3] as string;
this.propSkin = skinProp === "" ? [] : skinProp.split(",");
this.propAnimation = properties[3] as string;
this.propOffsetX = properties[7] as number;
this.propOffsetY = properties[8] as number;
this.propOffsetAngle = properties[9] as number;
this.propScaleX = properties[10] as number;
this.propScaleY = properties[11] as number;
console.log(properties);
}
this.assetLoader = new spine.AssetLoader("runtime");
this.skeletonRenderer = new spine.SkeletonRendererCore();
if (this.animationProp || (this.skinProp && this.skinProp.length > 0)) {
this.spineBoundsProvider = new spine.SkinsAndAnimationBoundsProvider(this.animationProp, this.skinProp);
} else {
this.spineBoundsProvider = new spine.SetupPoseBoundsProvider();
}
this.ratio = this.width / this.height;
this._setTicking(true);
}
@ -84,33 +78,30 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
if (!this.atlasLoaded) return;
this.skeletonLoading = true;
const propValue = this.skelProp;
const propValue = this.propSkel;
if (this.atlasLoaded && this.textureAtlas) {
const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, 0.25, this.plugin.runtime);
const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, 1, this.plugin.runtime);
if (!skeletonData) return;
this.skeleton = new spine.Skeleton(skeletonData);
const animationStateData = new spine.AnimationStateData(skeletonData);
this.state = new spine.AnimationState(animationStateData);
if (this.animationProp) {
this.setAnimation(0, this.animationProp, true);
if (this.propAnimation) {
this.setAnimation(0, this.propAnimation, true);
}
this._setSkin();
this.update(0);
this._spineBounds = this.spineBoundsProvider.calculateBounds(this);
// Initially, width and height are values set on C3 Editor side that allows to determine the right scale
this.skeleton.scaleX = this.width / this._spineBounds.width;
this.skeleton.scaleY = this.height / this._spineBounds.height;
this.setSize(this._spineBounds.width * this.skeleton.scaleX, this._spineBounds.height * -this.skeleton.scaleY);
this.setOrigin(-this._spineBounds.x * this.skeleton.scaleX / this.width, this._spineBounds.y * this.skeleton.scaleY / this.height);
this.skeleton.scaleX = this.propScaleX;
this.skeleton.scaleY = this.propScaleY;
// this.setSize(this._spineBounds.width * this.skeleton.scaleX, this._spineBounds.height * -this.skeleton.scaleY);
// this.setOrigin(-this._spineBounds.x * this.skeleton.scaleX / this.width, this._spineBounds.y * this.skeleton.scaleY / this.height);
this.skeletonLoaded = true;
this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded);
@ -156,7 +147,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
}
public setSkin (skins: string[]) {
this.skinProp = skins;
this.propSkin = skins;
this._setSkin();
}
@ -164,7 +155,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
const { skeleton } = this;
if (!skeleton) return;
const skins = this.skinProp;
const skins = this.propSkin;
if (skins.length === 0) {
skeleton.skin = null;
@ -208,7 +199,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
if (this.atlasLoading || !this.renderer) return;
this.atlasLoading = true;
const textureAtlas = await this.assetLoader.loadAtlasRuntime(this.atlasProp, this.plugin.runtime, this.renderer);
const textureAtlas = await this.assetLoader.loadAtlasRuntime(this.propAtlas, this.plugin.runtime, this.renderer);
if (!textureAtlas) return;
this.textureAtlas = textureAtlas;
@ -230,6 +221,12 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
let command = this.skeletonRenderer.render(this.skeleton);
const inv255 = 1 / 255;
const offsetX = this.x + this.propOffsetX;
const offsetY = this.y + this.propOffsetY;
const offsetAngle = this.angle + this.propOffsetAngle;
const cos = Math.cos(offsetAngle);
const sin = Math.sin(offsetAngle);
while (command) {
const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command;
@ -238,8 +235,10 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
for (let i = 0; i < numVertices; i++) {
const srcIndex = i * 2;
const dstIndex = i * 3;
vertices[dstIndex] = positions[srcIndex] + this.x;
vertices[dstIndex + 1] = positions[srcIndex + 1] + this.y;
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;
// there's something wrong with the hand after adding the colors on spineboy portal animation

View File

@ -30,12 +30,6 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
private positioningBounds = false;
private spineBoundsProviderType: SpineBoundsProviderType = "setup";
private spineBoundsProvider?: SpineBoundsProvider;
private spineBounds?: {
x: number;
y: number;
width: number;
height: number;
};
private initialBounds = {
x: 0,
y: 0,
@ -43,6 +37,12 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
height: 0,
};
// position mode
private positionModePrevX = 0;
private positionModePrevY = 0;
private positionModePrevAngle = 0;
private tempVertices = new Float32Array(4096);
constructor (sdkType: SDK.ITypeBase, inst: SDK.IWorldInstance) {
super(sdkType, inst);
@ -51,6 +51,7 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
this.assetLoader = new spine.AssetLoader("editor");
this.skeletonRenderer = new spine.SkeletonRendererCore();
}
Release () {
@ -58,36 +59,56 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
OnCreate () {
console.log("OnCreate");
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE, false);
}
OnPlacedInLayout () {
this.OnMakeOriginalSize();
}
private tempVertices = new Float32Array(4096);
Draw (iRenderer: SDK.Gfx.IWebGLRenderer, iDrawParams: SDK.Gfx.IDrawParams) {
this.layoutView ||= iDrawParams.GetLayoutView();
this.renderer ||= iRenderer;
this.loadAtlas();
this.loadSkeleton();
if (this.skeleton) {
this.setSkin();
let offsetX = this.baseOffsetX;
let offsetY = this.baseOffsetY;
let offsetAngle = this.baseAngleOffset;
if (!this.positioningBounds) {
this.baseScaleX = this._inst.GetWidth() / this.initialBounds.width;
this.baseScaleY = this._inst.GetHeight() / this.initialBounds.height;
offsetX += this._inst.GetX();
offsetY += this._inst.GetY();
offsetAngle += this._inst.GetAngle();
}
const rectX = this._inst.GetX();
const rectY = this._inst.GetY();
const rectAngle = this._inst.GetAngle();
let offsetX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number;
let offsetY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number;
let offsetAngle = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number;
this.skeleton.scaleX = this.baseScaleX;
this.skeleton.scaleY = this.baseScaleY;
if (!this.positioningBounds) {
offsetX += rectX;
offsetY += rectY;
offsetAngle += rectAngle;
const baseScaleX = this._inst.GetWidth() / this.initialBounds.width;
const baseScaleY = this._inst.GetHeight() / this.initialBounds.height;
this.skeleton.scaleX = baseScaleX;
this.skeleton.scaleY = baseScaleY;
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X, baseScaleX);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y, baseScaleY);
} else {
offsetX += this.positionModePrevX;
offsetY += this.positionModePrevY;
offsetAngle += this.positionModePrevAngle;
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, offsetX - rectX);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, offsetY - rectY);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, offsetAngle - rectAngle);
this.positionModePrevX = rectX;
this.positionModePrevY = rectY;
this.positionModePrevAngle = rectAngle;
this.skeleton.scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number;
this.skeleton.scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number;
}
const cos = Math.cos(offsetAngle);
const sin = Math.sin(offsetAngle);
@ -123,7 +144,8 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
iRenderer.SetColorFillMode();
iRenderer.SetColorRgba(0.25, 0, 0, 0.25);
iRenderer.LineQuad(this._inst.GetQuad());
iRenderer.Line(rectX, rectY, offsetX, offsetY);
console.log(offsetX, offsetY);
} else {
// render placeholder
iRenderer.SetAlphaBlend();
@ -138,12 +160,6 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
}
}
private baseOffsetX = 0;
private baseOffsetY = 0;
private baseAngleOffset = 0;
private baseScaleX = 0;
private baseScaleY = 0;
async OnPropertyChanged (id: string, value: EditorPropertyValueType) {
console.log(`Prop change - Name: ${id} - Value: ${value}`);
@ -162,20 +178,22 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
break;
case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER:
this.setC3Bounds(true);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, 0);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, 0);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, 0);
this.layoutView?.Refresh();
break;
case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE: {
value = value as boolean
if (value) {
this.baseOffsetX += this._inst.GetX();
this.baseOffsetY += this._inst.GetY();
this.baseAngleOffset += this._inst.GetAngle();
this.positionModePrevX = this._inst.GetX();
this.positionModePrevY = this._inst.GetY();
this.positionModePrevAngle = this._inst.GetAngle();
} else {
this.initialBounds.width = this._inst.GetWidth() / this.baseScaleX;
this.initialBounds.height = this._inst.GetHeight() / this.baseScaleY;
this.baseOffsetX -= this._inst.GetX();
this.baseOffsetY -= this._inst.GetY();
this.baseAngleOffset -= this._inst.GetAngle();
const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number;
const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number;
this.initialBounds.width = this._inst.GetWidth() / scaleX;
this.initialBounds.height = this._inst.GetHeight() / scaleY;
}
this.positioningBounds = value;
break;
@ -242,7 +260,14 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
this.update(0);
this.setSkin();
this.setC3Bounds(true);
const offsetX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number;
const offsetY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number;
const offsetAngle = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number;
const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number;
const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number;
const init = offsetX !== 0 && offsetY !== 0 && offsetAngle !== 0 && scaleX !== 1 && scaleY !== 1;
this.setC3Bounds(init);
this.layoutView?.Refresh();
console.log("SKELETON LOADED");
@ -252,8 +277,6 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
private setC3Bounds (init = false) {
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType;
console.log(propValue);
if (propValue === "animation-skin") {
const { skins, animation } = this;
if ((skins && skins.length > 0) || animation) {
@ -267,16 +290,19 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
this.spineBoundsProvider = new spine.AABBRectangleBoundsProvider(0, 0, 100, 100);
}
this.spineBounds = this.spineBoundsProvider.calculateBounds(this);
this.initialBounds = this.spineBoundsProvider.calculateBounds(this);
const { x, y, width, height } = this.spineBounds;
const { x, y, width, height } = this.initialBounds;
if (width <= 0 || height <= 0 || !init) return;
this._inst.SetSize(width, height);
this._inst.SetOrigin(-x / width, -y / height);
if (propValue === "AABB") {
this._inst.SetOrigin(.5, .5);
} else {
this._inst.SetOrigin(-x / width, -y / height);
}
}
private update (delta: number) {
@ -315,20 +341,20 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
}
GetOriginalWidth () {
if (!this.spineBounds) return 100;
return this.spineBounds.width;
if (!this.initialBounds) return 100;
return this.initialBounds.width;
}
GetOriginalHeight () {
if (!this.spineBounds) return 100;
return this.spineBounds.height;
if (!this.initialBounds) return 100;
return this.initialBounds.height;
}
OnMakeOriginalSize () {
if (!this.spineBounds)
if (!this.initialBounds)
this._inst.SetSize(100, 100);
else
this._inst.SetSize(this.spineBounds.width, this.spineBounds.height);
this._inst.SetSize(this.initialBounds.width, this.initialBounds.height);
}
HasDoubleTapHandler () {

View File

@ -46,6 +46,26 @@
"spine-bounds-provider-move": {
"name": "Position bounds",
"desc": "Keep the skeleton in a fixed position while adjusting the size and position of the bounds."
},
"spine-scale-x": {
"name": "Scale X",
"desc": "Scale X"
},
"spine-scale-y": {
"name": "Scale Y",
"desc": "Scale Y"
},
"spine-bounds-offset-x": {
"name": "Offset X",
"desc": "Offset X"
},
"spine-bounds-offset-y": {
"name": "Offset Y",
"desc": "Offset Y"
},
"spine-bounds-offset-angle": {
"name": "Offset Angle",
"desc": "Offset Angle"
}
},
"aceCategories": {

View File

@ -29,6 +29,11 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase {
static PROP_BOUNDS_PROVIDER_GROUP = "spine-bounds-provider-group";
static PROP_BOUNDS_PROVIDER = "spine-bounds-provider";
static PROP_BOUNDS_PROVIDER_MOVE = "spine-bounds-provider-move";
static PROP_BOUNDS_OFFSET_X = "spine-bounds-offset-x";
static PROP_BOUNDS_OFFSET_Y = "spine-bounds-offset-y";
static PROP_BOUNDS_OFFSET_ANGLE = "spine-bounds-offset-angle";
static PROP_SKELETON_SCALE_X = "spine-scale-x";
static PROP_SKELETON_SCALE_Y = "spine-scale-y";
static TYPE_BOUNDS_SETUP = "setup";
static TYPE_BOUNDS_ANIMATION_SKIN = "animation-skin";
@ -98,6 +103,11 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase {
],
}),
new SDK.PluginProperty("check", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_MOVE, false),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_BOUNDS_OFFSET_X, 0),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_BOUNDS_OFFSET_Y, 0),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_BOUNDS_OFFSET_ANGLE, 0),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_SKELETON_SCALE_X, 1),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_SKELETON_SCALE_Y, 1),
]);