From da12c23c73888232dca89228f61e4c20eddca635 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 13 Aug 2025 17:58:23 +0200 Subject: [PATCH] Bounds added as properties. Works also in runtime. --- .../src/c3runtime/instance.ts | 83 ++++++------ spine-ts/spine-construct3/src/instance.ts | 120 +++++++++++------- spine-ts/spine-construct3/src/lang/en-US.json | 20 +++ spine-ts/spine-construct3/src/plugin.ts | 10 ++ 4 files changed, 144 insertions(+), 89 deletions(-) diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index ff749dbd5..a2eb93971 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -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 diff --git a/spine-ts/spine-construct3/src/instance.ts b/spine-ts/spine-construct3/src/instance.ts index 02f4b6728..41f94cdde 100644 --- a/spine-ts/spine-construct3/src/instance.ts +++ b/spine-ts/spine-construct3/src/instance.ts @@ -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 () { diff --git a/spine-ts/spine-construct3/src/lang/en-US.json b/spine-ts/spine-construct3/src/lang/en-US.json index ff53a7f39..e88eee863 100644 --- a/spine-ts/spine-construct3/src/lang/en-US.json +++ b/spine-ts/spine-construct3/src/lang/en-US.json @@ -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": { diff --git a/spine-ts/spine-construct3/src/plugin.ts b/spine-ts/spine-construct3/src/plugin.ts index b0bd067e3..e8bf2a29a 100644 --- a/spine-ts/spine-construct3/src/plugin.ts +++ b/spine-ts/spine-construct3/src/plugin.ts @@ -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), ]);