Improved reset bounds management.

This commit is contained in:
Davide Tantillo 2025-08-14 17:37:34 +02:00
parent 45d94a3201
commit 46920ff54f
7 changed files with 262 additions and 176 deletions

View File

@ -32,32 +32,30 @@ import { C3Texture, C3TextureEditor } from "./C3Texture";
export class AssetLoader { export class AssetLoader {
constructor (private type: "editor" | "runtime") {
}
public async loadSkeletonEditor (path: string, textureAtlas: TextureAtlas, instance: SDK.IWorldInstance) { public async loadSkeletonEditor (sid: number, textureAtlas: TextureAtlas, scale = 1, instance: SDK.IWorldInstance) {
const projectFile = instance.GetProject().GetProjectFileByName(path); const projectFile = instance.GetProject().GetProjectFileBySID(sid);
if (!projectFile) return null; if (!projectFile) return null;
const blob = projectFile.GetBlob(); const blob = projectFile.GetBlob();
const atlasLoader = new AtlasAttachmentLoader(textureAtlas); const atlasLoader = new AtlasAttachmentLoader(textureAtlas);
const isBinary = path.endsWith(".skel"); const isBinary = projectFile.GetName().endsWith(".skel");
if (isBinary) { if (isBinary) {
const skeletonFile = await blob.arrayBuffer(); const skeletonFile = await blob.arrayBuffer();
const skeletonLoader = new SkeletonBinary(atlasLoader); const skeletonLoader = new SkeletonBinary(atlasLoader);
skeletonLoader.scale = 1; skeletonLoader.scale = scale;
return skeletonLoader.readSkeletonData(skeletonFile); return skeletonLoader.readSkeletonData(skeletonFile);
} }
const skeletonFile = await blob.text(); const skeletonFile = await blob.text();
const skeletonLoader = new SkeletonJson(atlasLoader); const skeletonLoader = new SkeletonJson(atlasLoader);
skeletonLoader.scale = 1; skeletonLoader.scale = scale;
return skeletonLoader.readSkeletonData(skeletonFile); return skeletonLoader.readSkeletonData(skeletonFile);
} }
public async loadAtlasEditor (path: string, instance: SDK.IWorldInstance, renderer: SDK.Gfx.IWebGLRenderer) { public async loadAtlasEditor (sid: number, instance: SDK.IWorldInstance, renderer: SDK.Gfx.IWebGLRenderer) {
const projectFile = instance.GetProject().GetProjectFileByName(path); const projectFile = instance.GetProject().GetProjectFileBySID(sid);
if (!projectFile) return null; if (!projectFile) return null;
const blob = projectFile.GetBlob(); const blob = projectFile.GetBlob();
@ -77,31 +75,31 @@ export class AssetLoader {
} }
public async loadSpineTextureEditor (pageName: string, pma = false, instance: SDK.IWorldInstance) { public async loadSpineTextureEditor (pageName: string, pma = false, instance: SDK.IWorldInstance) {
const projectFile = instance.GetProject().GetProjectFileByName(pageName); const projectFile = instance.GetProject().GetProjectFileByExportPath(pageName);
if (!projectFile) { if (!projectFile) {
throw new Error(`An error occured while loading the texture: ${pageName}`); throw new Error(`An error occured while loading the texture: ${pageName}`);
} }
const content = projectFile.GetBlob(); const content = projectFile.GetBlob();
return this.createImageBitmapFromBlob(content, pma); return AssetLoader.createImageBitmapFromBlob(content, pma);
} }
public async loadSkeletonRuntime (path: string, textureAtlas: TextureAtlas, scale = 1, instance: IRuntime) { public async loadSkeletonRuntime (path: string, textureAtlas: TextureAtlas, scale = 1, instance: IRuntime) {
const fullPath = await instance.assets.getProjectFileUrl(path); const fullPath = await instance.assets.getProjectFileUrl(path);
if (!fullPath) return null; if (!fullPath) return null;
const content = await instance.assets.fetchArrayBuffer(fullPath);
if (!content) return null;
const atlasLoader = new AtlasAttachmentLoader(textureAtlas); const atlasLoader = new AtlasAttachmentLoader(textureAtlas);
const isBinary = path.endsWith(".skel"); const isBinary = path.endsWith(".skel");
if (isBinary) { if (isBinary) {
const content = await instance.assets.fetchArrayBuffer(fullPath);
if (!content) return null;
const skeletonLoader = new SkeletonBinary(atlasLoader); const skeletonLoader = new SkeletonBinary(atlasLoader);
skeletonLoader.scale = scale; skeletonLoader.scale = scale;
return skeletonLoader.readSkeletonData(content); return skeletonLoader.readSkeletonData(content);
} }
const content = await instance.assets.fetchJson(fullPath);
if (!content) return null;
const skeletonLoader = new SkeletonJson(atlasLoader); const skeletonLoader = new SkeletonJson(atlasLoader);
skeletonLoader.scale = scale; skeletonLoader.scale = scale;
return skeletonLoader.readSkeletonData(content); return skeletonLoader.readSkeletonData(content);
@ -133,10 +131,10 @@ export class AssetLoader {
const content = await instance.assets.fetchBlob(fullPath); const content = await instance.assets.fetchBlob(fullPath);
if (!content) return null; if (!content) return null;
return this.createImageBitmapFromBlob(content, pma); return AssetLoader.createImageBitmapFromBlob(content, pma);
} }
private async createImageBitmapFromBlob (blob: Blob, pma: boolean): Promise<ImageBitmap | null> { static async createImageBitmapFromBlob (blob: Blob, pma: boolean): Promise<ImageBitmap | null> {
try { try {
return createImageBitmap(blob, { premultiplyAlpha: pma ? "none" : "premultiply" }); return createImageBitmap(blob, { premultiplyAlpha: pma ? "none" : "premultiply" });
} catch (e) { } catch (e) {

View File

@ -8,6 +8,7 @@ spine.Skeleton.yDown = true;
class DrawingInstance extends globalThis.ISDKWorldInstanceBase { class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
propAtlas = ""; propAtlas = "";
propSkel = ""; propSkel = "";
propLoaderScale = 1;
propSkin: string[] = []; propSkin: string[] = [];
propAnimation?: string; propAnimation?: string;
propOffsetX = 0; propOffsetX = 0;
@ -33,21 +34,23 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
const properties = this._getInitProperties(); const properties = this._getInitProperties();
if (properties) { if (properties) {
console.log(properties);
this.propAtlas = properties[0] as string; this.propAtlas = properties[0] as string;
this.propSkel = properties[1] as string; this.propSkel = properties[1] as string;
const skinProp = properties[2] as string; this.propLoaderScale = properties[2] as number;
const skinProp = properties[3] as string;
this.propSkin = skinProp === "" ? [] : skinProp.split(","); this.propSkin = skinProp === "" ? [] : skinProp.split(",");
this.propAnimation = properties[3] as string; this.propAnimation = properties[4] as string;
this.propOffsetX = properties[7] as number; this.propOffsetX = properties[7] as number;
this.propOffsetY = properties[8] as number; this.propOffsetY = properties[8] as number;
this.propOffsetAngle = properties[9] as number; this.propOffsetAngle = properties[9] as number;
this.propScaleX = properties[10] as number; this.propScaleX = properties[10] as number;
this.propScaleY = properties[11] as number; this.propScaleY = properties[11] as number;
console.log(properties);
} }
this.assetLoader = new spine.AssetLoader("runtime"); this.assetLoader = new spine.AssetLoader();
this.skeletonRenderer = new spine.SkeletonRendererCore(); this.skeletonRenderer = new spine.SkeletonRendererCore();
this._setTicking(true); this._setTicking(true);
@ -81,7 +84,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
const propValue = this.propSkel; const propValue = this.propSkel;
if (this.atlasLoaded && this.textureAtlas) { if (this.atlasLoaded && this.textureAtlas) {
const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, 1, this.plugin.runtime); const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, this.propLoaderScale, this.plugin.runtime);
if (!skeletonData) return; if (!skeletonData) return;
this.skeleton = new spine.Skeleton(skeletonData); this.skeleton = new spine.Skeleton(skeletonData);
@ -96,13 +99,9 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase {
this.update(0); this.update(0);
// Initially, width and height are values set on C3 Editor side that allows to determine the right scale
this.skeleton.scaleX = this.propScaleX; this.skeleton.scaleX = this.propScaleX;
this.skeleton.scaleY = this.propScaleY; 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.skeletonLoaded = true;
this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded); this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded);
} }

View File

@ -14,52 +14,53 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
private layoutView?: SDK.UI.ILayoutView; private layoutView?: SDK.UI.ILayoutView;
private renderer?: SDK.Gfx.IWebGLRenderer; private renderer?: SDK.Gfx.IWebGLRenderer;
private currentSkelName = ""; private currentAtlasFileSID = -1;
private currentAtlasName = "";
private textureAtlas?: TextureAtlas; private textureAtlas?: TextureAtlas;
skeleton?: Skeleton; skeleton?: Skeleton;
state?: AnimationState; state?: AnimationState;
skins: string[] = []; skins: string[] = [];
currentSkinString?: string;
animation?: string; animation?: string;
private assetLoader: AssetLoader; private assetLoader: AssetLoader;
private skeletonRenderer: SkeletonRendererCore; private skeletonRenderer: SkeletonRendererCore;
private positioningBounds = false;
private spineBoundsProviderType: SpineBoundsProviderType = "setup";
private spineBoundsProvider?: SpineBoundsProvider;
private initialBounds = {
x: 0,
y: 0,
width: 0,
height: 0,
};
// position mode // position mode
private positioningBounds = false;
private positionModePrevX = 0; private positionModePrevX = 0;
private positionModePrevY = 0; private positionModePrevY = 0;
private positionModePrevAngle = 0; private positionModePrevAngle = 0;
private spineBounds = {
x: 0,
y: 0,
width: 100,
height: 100,
};
// utils for drawing
private tempVertices = new Float32Array(4096); private tempVertices = new Float32Array(4096);
// errors
private errors: Record<string, string> = {};
constructor (sdkType: SDK.ITypeBase, inst: SDK.IWorldInstance) { constructor (sdkType: SDK.ITypeBase, inst: SDK.IWorldInstance) {
super(sdkType, inst); super(sdkType, inst);
if (!spine) spine = globalThis.spine; if (!spine) spine = globalThis.spine;
spine.Skeleton.yDown = true; spine.Skeleton.yDown = true;
this.assetLoader = new spine.AssetLoader("editor"); this.assetLoader = new spine.AssetLoader();
this.skeletonRenderer = new spine.SkeletonRendererCore(); this.skeletonRenderer = new spine.SkeletonRendererCore();
(this._inst as any).errors = this.errors;
} }
Release () { Release () {
} }
OnCreate () { OnCreate () {
console.log("OnCreate");
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE, false); this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE, false);
this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as number;
} }
OnPlacedInLayout () { OnPlacedInLayout () {
@ -70,7 +71,6 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
this.layoutView ||= iDrawParams.GetLayoutView(); this.layoutView ||= iDrawParams.GetLayoutView();
this.renderer ||= iRenderer; this.renderer ||= iRenderer;
this.loadAtlas(); this.loadAtlas();
this.loadSkeleton(); this.loadSkeleton();
@ -89,8 +89,8 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
offsetY += rectY; offsetY += rectY;
offsetAngle += rectAngle; offsetAngle += rectAngle;
const baseScaleX = this._inst.GetWidth() / this.initialBounds.width; const baseScaleX = this._inst.GetWidth() / this.spineBounds.width;
const baseScaleY = this._inst.GetHeight() / this.initialBounds.height; const baseScaleY = this._inst.GetHeight() / this.spineBounds.height;
this.skeleton.scaleX = baseScaleX; this.skeleton.scaleX = baseScaleX;
this.skeleton.scaleY = baseScaleY; this.skeleton.scaleY = baseScaleY;
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X, baseScaleX); this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X, baseScaleX);
@ -129,7 +129,9 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
vertices[dstIndex + 2] = 0; vertices[dstIndex + 2] = 0;
} }
iRenderer.ResetColor();
iRenderer.SetAlphaBlend(); iRenderer.SetAlphaBlend();
iRenderer.SetTextureFillMode();
iRenderer.SetTexture(command.texture.texture); iRenderer.SetTexture(command.texture.texture);
iRenderer.DrawMesh( iRenderer.DrawMesh(
vertices.subarray(0, numVertices * 3), vertices.subarray(0, numVertices * 3),
@ -145,59 +147,96 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
iRenderer.SetColorRgba(0.25, 0, 0, 0.25); iRenderer.SetColorRgba(0.25, 0, 0, 0.25);
iRenderer.LineQuad(this._inst.GetQuad()); iRenderer.LineQuad(this._inst.GetQuad());
iRenderer.Line(rectX, rectY, offsetX, offsetY); iRenderer.Line(rectX, rectY, offsetX, offsetY);
console.log(offsetX, offsetY);
if (this.hasErrors()) {
iRenderer.SetColorFillMode();
iRenderer.SetColorRgba(1, 0, 0, .5);
iRenderer.Quad(this._inst.GetQuad());
}
} else { } else {
// render placeholder
iRenderer.SetAlphaBlend();
iRenderer.SetColorFillMode();
if (this.HadTextureError()) const sdkType = (this._sdkType as any);
iRenderer.SetColorRgba(0.25, 0, 0, 0.25);
else
iRenderer.SetColorRgba(0, 0, 0.1, 0.1); const logo = sdkType.getSpineLogo(iRenderer);
if (logo) {
iRenderer.ResetColor();
iRenderer.SetAlphaBlend();
iRenderer.SetTexture(logo);
iRenderer.Quad(this._inst.GetQuad());
} else {
iRenderer.SetAlphaBlend();
iRenderer.SetColorFillMode();
if (this.HadTextureError())
iRenderer.SetColorRgba(0.25, 0, 0, 0.25);
else
iRenderer.SetColorRgba(0, 0, 0.1, 0.1);
iRenderer.Quad(this._inst.GetQuad());
}
iRenderer.Quad(this._inst.GetQuad());
} }
} }
async OnPropertyChanged (id: string, value: EditorPropertyValueType) { async OnPropertyChanged (id: string, value: EditorPropertyValueType) {
console.log(`Prop change - Name: ${id} - Value: ${value}`); console.log(`Prop change - Name: ${id} - Value: ${value}`);
switch (id) { if (id === PLUGIN_CLASS.PROP_ATLAS) {
case PLUGIN_CLASS.PROP_ATLAS: this.textureAtlas?.dispose();
this.layoutView?.Refresh(); this.textureAtlas = undefined;
break; this.skins = [];
case PLUGIN_CLASS.PROP_SKELETON: this.layoutView?.Refresh();
this.layoutView?.Refresh(); return;
break; }
case PLUGIN_CLASS.PROP_SKIN:
this.layoutView?.Refresh(); if (id === PLUGIN_CLASS.PROP_SKELETON) {
break; this.skeleton = undefined;
case PLUGIN_CLASS.PROP_ANIMATION: this.skins = [];
this.layoutView?.Refresh(); this.layoutView?.Refresh();
break; return;
case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER: }
this.setC3Bounds(true);
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, 0); if (id === PLUGIN_CLASS.PROP_LOADER_SCALE) {
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, 0); this.skeleton = undefined;
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, 0); this.skins = [];
this.layoutView?.Refresh(); this.layoutView?.Refresh();
break; return;
case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE: { }
value = value as boolean
if (value) { if (id === PLUGIN_CLASS.PROP_SKIN) {
this.positionModePrevX = this._inst.GetX(); this.skins = [];
this.positionModePrevY = this._inst.GetY(); this.setSkin();
this.positionModePrevAngle = this._inst.GetAngle(); this.layoutView?.Refresh();
} else { return;
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; if (id === PLUGIN_CLASS.PROP_ANIMATION) {
this.initialBounds.height = this._inst.GetHeight() / scaleY; this.layoutView?.Refresh();
} return;
this.positioningBounds = value; }
break;
if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) {
this.resetBounds();
this.layoutView?.Refresh();
return
}
if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE) {
value = value as boolean
if (value) {
this.positionModePrevX = this._inst.GetX();
this.positionModePrevY = this._inst.GetY();
this.positionModePrevAngle = this._inst.GetAngle();
} else {
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.spineBounds.width = this._inst.GetWidth() / scaleX;
this.spineBounds.height = this._inst.GetHeight() / scaleY;
} }
this.positioningBounds = value;
return
} }
console.log("Prop change end"); console.log("Prop change end");
@ -209,13 +248,12 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKIN) as string; const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKIN) as string;
if (this.currentSkinString === propValue) return;
this.currentSkinString = propValue;
const skins = propValue === "" ? [] : propValue.split(","); const skins = propValue === "" ? [] : propValue.split(",");
this.skins = skins; this.skins = skins;
if (skins.length === 1) { if (skins.length === 0) {
skeleton.setSkin(null);
} else if (skins.length === 1) {
const skinName = skins[0]; const skinName = skins[0];
const skin = skeleton.data.findSkin(skinName); const skin = skeleton.data.findSkin(skinName);
if (!skin) { if (!skin) {
@ -223,7 +261,7 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
return; return;
} }
skeleton.setSkin(skins[0]); skeleton.setSkin(skins[0]);
} else if (skins.length > 1) { } else {
const customSkin = new spine.Skin(propValue); const customSkin = new spine.Skin(propValue);
for (const s of skins) { for (const s of skins) {
const skin = skeleton.data.findSkin(s) const skin = skeleton.data.findSkin(s)
@ -238,71 +276,103 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
skeleton.setupPose(); skeleton.setupPose();
this.update(0); this.update(0);
this.setC3Bounds();
} }
private async loadSkeleton () { private async loadSkeleton () {
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as string; if (!this.renderer || !this.textureAtlas) return;
const projectFile = this._inst.GetProject().GetProjectFileByName(propValue); if (this.skeleton) return;
if (projectFile && this.textureAtlas) { console.log("Loading skeleton");
if (this.currentSkelName === propValue) return;
this.currentSkelName = propValue;
const skeletonData = await this.assetLoader.loadSkeletonEditor(propValue, this.textureAtlas, this._inst); const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as number;
if (!skeletonData) return; const loaderScale = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_LOADER_SCALE) as number;
const skeletonData = await this.assetLoader.loadSkeletonEditor(propValue, this.textureAtlas, loaderScale, this._inst);
console.log(skeletonData);
if (!skeletonData) return;
this.skeleton = new spine.Skeleton(skeletonData); this.skeleton = new spine.Skeleton(skeletonData);
const animationStateData = new spine.AnimationStateData(skeletonData); const animationStateData = new spine.AnimationStateData(skeletonData);
this.state = new spine.AnimationState(animationStateData); this.state = new spine.AnimationState(animationStateData);
this.update(0); this.setSkin();
this.update(0);
this.setBoundsFromBoundsProvider();
this.initBounds();
this.setSkin(); this.layoutView?.Refresh();
console.log("SKELETON LOADED");
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");
}
} }
private setC3Bounds (init = false) { private async loadAtlas () {
if (!this.renderer) return;
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ATLAS) as number;
console.log("Loading atlas");
if (this.currentAtlasFileSID === propValue) return;
this.currentAtlasFileSID = propValue;
const textureAtlas = await this.assetLoader.loadAtlasEditor(propValue, this._inst, this.renderer);
if (!textureAtlas) return;
this.textureAtlas = textureAtlas;
this.layoutView?.Refresh();
}
private setBoundsFromBoundsProvider () {
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType; const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType;
let spineBoundsProvider: SpineBoundsProvider;
if (propValue === "animation-skin") { if (propValue === "animation-skin") {
const { skins, animation } = this; const { skins, animation } = this;
if ((skins && skins.length > 0) || animation) { if ((skins && skins.length > 0) || animation) {
this.spineBoundsProvider = new spine.SkinsAndAnimationBoundsProvider(animation, skins); spineBoundsProvider = new spine.SkinsAndAnimationBoundsProvider(animation, skins);
} else { } else {
throw new Error("One among skin and animation needs to have a value to set this bounds provider."); return false;
} }
} else if (propValue === "setup") { } else if (propValue === "setup") {
this.spineBoundsProvider = new spine.SetupPoseBoundsProvider(); spineBoundsProvider = new spine.SetupPoseBoundsProvider();
} else { } else {
this.spineBoundsProvider = new spine.AABBRectangleBoundsProvider(0, 0, 100, 100); spineBoundsProvider = new spine.AABBRectangleBoundsProvider(0, 0, 100, 100);
} }
this.initialBounds = this.spineBoundsProvider.calculateBounds(this); this.spineBounds = spineBoundsProvider.calculateBounds(this);
const { x, y, width, height } = this.initialBounds; return true;
}
if (width <= 0 || height <= 0 || !init) return; private resetBounds () {
this.setBoundsFromBoundsProvider();
if (this.hasErrors()) return;
const { x, y, width, height } = this.spineBounds;
this._inst.SetOrigin(-x / width, -y / height);
this._inst.SetSize(width, height); this._inst.SetSize(width, height);
if (propValue === "AABB") { this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, 0);
this._inst.SetOrigin(.5, .5); this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, 0);
} else { this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, 0);
this._inst.SetOrigin(-x / width, -y / height); return;
}
private initBounds () {
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 shiftedBounds = offsetX !== 0 || offsetY !== 0 || offsetAngle !== 0;
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 scaledBounds = scaleX !== 1 || scaleY !== 1;
if (shiftedBounds || scaledBounds) {
this.spineBounds.width = this._inst.GetWidth() / scaleX;
this.spineBounds.height = this._inst.GetHeight() / scaleY;
return;
} }
this.resetBounds();
} }
private update (delta: number) { private update (delta: number) {
@ -316,19 +386,31 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
skeleton.updateWorldTransform(spine.Physics.update); skeleton.updateWorldTransform(spine.Physics.update);
} }
private async loadAtlas () { private setError (key: string, condition: boolean, message: string) {
if (!this.renderer) return; if (condition) {
this.errors[key] = message;
return;
}
delete this.errors[key];
}
private hasErrors () {
const { errors, skins, animation, spineBounds } = this;
const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ATLAS) as string; const boundsType = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType;
this.setError(
"boundsAnimationSkinType",
boundsType === "animation-skin" && ((!skins || skins.length === 0) && !animation),
"Animation/Skin bounds provider requires one between skin and animation to be set."
);
if (this.currentAtlasName === propValue) return; const { width, height } = spineBounds;
this.currentAtlasName = propValue; this.setError(
"boundsNoDimension",
width <= 0 || height <= 0,
"A bounds cannot have negative dimension"
);
const textureAtlas = await this.assetLoader.loadAtlasEditor(propValue, this._inst, this.renderer); return Object.keys(errors).length > 0;
if (!textureAtlas) return;
this.textureAtlas = textureAtlas;
this.layoutView?.Refresh();
} }
GetTexture () { GetTexture () {
@ -341,20 +423,15 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase {
} }
GetOriginalWidth () { GetOriginalWidth () {
if (!this.initialBounds) return 100; return this.spineBounds.width;
return this.initialBounds.width;
} }
GetOriginalHeight () { GetOriginalHeight () {
if (!this.initialBounds) return 100; return this.spineBounds.height;
return this.initialBounds.height;
} }
OnMakeOriginalSize () { OnMakeOriginalSize () {
if (!this.initialBounds) this._inst.SetSize(this.spineBounds.width, this.spineBounds.height);
this._inst.SetSize(100, 100);
else
this._inst.SetSize(this.initialBounds.width, this.initialBounds.height);
} }
HasDoubleTapHandler () { HasDoubleTapHandler () {

View File

@ -18,9 +18,9 @@
"name": "Atlas", "name": "Atlas",
"desc": "Atlas file" "desc": "Atlas file"
}, },
"spine-skeleton-file-blob": { "spine-loader-scale": {
"name": "Skel Blob", "name": "Loader scale",
"desc": "Skel file blob" "desc": "Loader scale"
}, },
"spine-animation": { "spine-animation": {
"name": "animation", "name": "animation",
@ -30,6 +30,10 @@
"name": "skin", "name": "skin",
"desc": "skin" "desc": "skin"
}, },
"spine-errors": {
"name": "Errors",
"desc": "errors"
},
"spine-bounds-provider-group": { "spine-bounds-provider-group": {
"name": "Bounds provider", "name": "Bounds provider",
"desc": "Select the desired buound provider and fill the respective properties." "desc": "Select the desired buound provider and fill the respective properties."
@ -38,9 +42,8 @@
"name": "Bounds provider", "name": "Bounds provider",
"desc": "The bounds provider to use.", "desc": "The bounds provider to use.",
"items": { "items": {
"AABB": "AABB Rectangle",
"setup": "Setup pose bounds", "setup": "Setup pose bounds",
"animation-skin": "Animation + Skin bounds" "animation-skin": "Animation/Skin bounds"
} }
}, },
"spine-bounds-provider-move": { "spine-bounds-provider-move": {

View File

@ -15,17 +15,15 @@ const PLUGIN_ID = "EsotericSoftware_SpineConstruct3";
const PLUGIN_CATEGORY = "general"; const PLUGIN_CATEGORY = "general";
let app = null;
const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase { const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase {
static PROP_ATLAS = "spine-atlas-file"; static PROP_ATLAS = "spine-atlas-file";
static PROP_SKELETON = "spine-skeleton-file"; static PROP_SKELETON = "spine-skeleton-file";
static PROP_LOADER_SCALE = "spine-loader-scale";
static PROP_SKIN = "spine-skin"; static PROP_SKIN = "spine-skin";
static PROP_ANIMATION = "spine-animation"; static PROP_ANIMATION = "spine-animation";
static PROP_ERRORS = "spine-errors"; static PROP_ERRORS = "spine-errors";
static PROP_RATIO_WIDTH = "spine-restore-ratio-width"; static PROP_RATIO_WIDTH = "spine-restore-ratio-width";
static PROP_RATIO_HEIGHT = "spine-restore-ratio-height"; static PROP_RATIO_HEIGHT = "spine-restore-ratio-height";
static PROP_SKELETON_BLOB = "spine-skeleton-file-blob";
static PROP_BOUNDS_PROVIDER_GROUP = "spine-bounds-provider-group"; static PROP_BOUNDS_PROVIDER_GROUP = "spine-bounds-provider-group";
static PROP_BOUNDS_PROVIDER = "spine-bounds-provider"; static PROP_BOUNDS_PROVIDER = "spine-bounds-provider";
static PROP_BOUNDS_PROVIDER_MOVE = "spine-bounds-provider-move"; static PROP_BOUNDS_PROVIDER_MOVE = "spine-bounds-provider-move";
@ -37,12 +35,10 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase {
static TYPE_BOUNDS_SETUP = "setup"; static TYPE_BOUNDS_SETUP = "setup";
static TYPE_BOUNDS_ANIMATION_SKIN = "animation-skin"; static TYPE_BOUNDS_ANIMATION_SKIN = "animation-skin";
static TYPE_BOUNDS_AABB = "AABB";
constructor () { constructor () {
super(PLUGIN_ID); super(PLUGIN_ID);
SDK.Lang.PushContext("plugins." + PLUGIN_ID.toLowerCase()); SDK.Lang.PushContext("plugins." + PLUGIN_ID.toLowerCase());
// @ts-ignore // @ts-ignore
@ -69,37 +65,26 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase {
SDK.Lang.PushContext(".properties"); SDK.Lang.PushContext(".properties");
this._info.SetProperties([ this._info.SetProperties([
new SDK.PluginProperty("text", MyDrawingPlugin.PROP_ATLAS, ""), new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_ATLAS, ""),
new SDK.PluginProperty("text", MyDrawingPlugin.PROP_SKELETON, ""), new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_SKELETON, ""),
new SDK.PluginProperty("float", MyDrawingPlugin.PROP_LOADER_SCALE, 1),
new SDK.PluginProperty("text", MyDrawingPlugin.PROP_SKIN, ""), new SDK.PluginProperty("text", MyDrawingPlugin.PROP_SKIN, ""),
new SDK.PluginProperty("text", MyDrawingPlugin.PROP_ANIMATION, ""), new SDK.PluginProperty("text", MyDrawingPlugin.PROP_ANIMATION, ""),
new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_SKELETON_BLOB, ""),
new SDK.PluginProperty("info", MyDrawingPlugin.PROP_ERRORS, { new SDK.PluginProperty("info", MyDrawingPlugin.PROP_ERRORS, {
infoCallback (inst) { infoCallback (inst) {
const atlas = inst.GetInstance().GetPropertyValue(MyDrawingPlugin.PROP_ATLAS); const errors = (inst.GetInstance() as unknown as { errors: Record<string, string> }).errors;
const skeleton = inst.GetInstance().GetPropertyValue(MyDrawingPlugin.PROP_SKELETON); return Object.values(errors).reduce((acc, next) => {
return acc === "" ? next : `${acc}\n${next}`;
let error = ""; }, "");
if (atlas && skeleton) {
error = "You can't set both .skel and .json skeleton file.";
}
if (!atlas && !skeleton) {
error = "Missing skeleton file.";
}
return error;
}, },
}), }),
new SDK.PluginProperty("group", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_GROUP), new SDK.PluginProperty("group", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_GROUP),
new SDK.PluginProperty("combo", MyDrawingPlugin.PROP_BOUNDS_PROVIDER, { new SDK.PluginProperty("combo", MyDrawingPlugin.PROP_BOUNDS_PROVIDER, {
initialValue: "setup", initialValue: "setup",
items: [ items: [
MyDrawingPlugin.TYPE_BOUNDS_SETUP, MyDrawingPlugin.TYPE_BOUNDS_SETUP,
MyDrawingPlugin.TYPE_BOUNDS_ANIMATION_SKIN, MyDrawingPlugin.TYPE_BOUNDS_ANIMATION_SKIN,
MyDrawingPlugin.TYPE_BOUNDS_AABB
], ],
}), }),
new SDK.PluginProperty("check", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_MOVE, false), new SDK.PluginProperty("check", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_MOVE, false),

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long