From 8a80baa0acdf84c81a27b6259cab2cc918744cb1 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 27 Nov 2023 12:06:52 +0100 Subject: [PATCH] [cpp][ts] Improved test beds. --- spine-sfml/cpp/example/testbed.cpp | 8 +- .../spine-webgl/example/drag-and-drop.html | 36 +- spine-ts/spine-webgl/example/drag-and-drop.js | 458 ++++++++++-------- 3 files changed, 287 insertions(+), 215 deletions(-) diff --git a/spine-sfml/cpp/example/testbed.cpp b/spine-sfml/cpp/example/testbed.cpp index aa5767864..3da197883 100644 --- a/spine-sfml/cpp/example/testbed.cpp +++ b/spine-sfml/cpp/example/testbed.cpp @@ -70,11 +70,12 @@ class NullAttachmentLoader : public AttachmentLoader { }; int main(void) { - String atlasFile(""); - String skeletonFile("/Users/badlogic/workspaces/spine-runtimes/spine-haxe/example/assets/vine-pro.json"); + String atlasFile("/Users/badlogic/Desktop/basemodel-male/basemodel-male.atlas"); + String skeletonFile("/Users/badlogic/Desktop/basemodel-male/basemodel-male.skel"); String animation = ""; + String skin = "BasicBody"; - float scale = 1.0f; + float scale = 0.1f; SFMLTextureLoader textureLoader; NullAttachmentLoader nullLoader; Atlas *atlas = atlasFile.length() == 0 ? nullptr : new Atlas(atlasFile, &textureLoader); @@ -106,6 +107,7 @@ int main(void) { drawable.skeleton->updateWorldTransform(); drawable.skeleton->setPosition(320, 590); if (animation.length() > 0) drawable.state->setAnimation(0, animation, true); + if (skin.length() > 0) drawable.skeleton->setSkin(skin); sf::RenderWindow window(sf::VideoMode(640, 640), "Spine SFML - testbed"); window.setFramerateLimit(60); diff --git a/spine-ts/spine-webgl/example/drag-and-drop.html b/spine-ts/spine-webgl/example/drag-and-drop.html index c0fc4308e..bb6c2d0f1 100644 --- a/spine-ts/spine-webgl/example/drag-and-drop.html +++ b/spine-ts/spine-webgl/example/drag-and-drop.html @@ -1,21 +1,27 @@ - - + - - -
- - - - + + +
+ + + + + +
- - - \ No newline at end of file + + diff --git a/spine-ts/spine-webgl/example/drag-and-drop.js b/spine-ts/spine-webgl/example/drag-and-drop.js index 9b38c8784..b003681c6 100644 --- a/spine-ts/spine-webgl/example/drag-and-drop.js +++ b/spine-ts/spine-webgl/example/drag-and-drop.js @@ -1,230 +1,294 @@ class App { - constructor() { - this.skeleton = null; - this.animationState = null; - this.canvas = null; - this.pma = true; + constructor() { + this.skeleton = null; + this.animationState = null; + this.canvas = null; + this.pma = true; + } + + loadAssets(canvas) { + this.canvas = canvas; + + // Load assets of Spineboy. + canvas.assetManager.loadBinary("assets/spineboy-pro.skel"); + canvas.assetManager.loadTextureAtlas("assets/spineboy-pma.atlas"); + } + + initialize(canvas) { + // Load the Spineboy skeleton + this.loadSkeleton( + "assets/spineboy-pro.skel", + "assets/spineboy-pma.atlas", + "run" + ); + + // Setup listener for animation selection box + let animationSelectBox = document.body.querySelector("#animations"); + animationSelectBox.onchange = () => { + this.animationState.setAnimation(0, animationSelectBox.value, true); + }; + + // Setup listener for skin selection box + let skinSelectBox = document.body.querySelector("#skins"); + skinSelectBox.onchange = () => { + this.skeleton.setSkinByName(skinSelectBox.value); + }; + + // Setup listener for the PMA checkbox + let pmaCheckbox = document.body.querySelector("#pma"); + pmaCheckbox.onchange = () => { + this.pma = pmaCheckbox.checked; + }; + + // Setup the drag and drop listener + new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files)); + + // Setup a camera controller for paning and zooming + new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera); + } + + onDrop(files) { + let atlasFile; + let skeletonFile; + let pngs = []; + let assetManager = this.canvas.assetManager; + + // We use data URIs to load the dropped files. Some file types + // are binary, so we have to encode them to base64 for loading + // through AssetManager. + let bufferToBase64 = (buffer) => { + var binary = ""; + var bytes = new Uint8Array(buffer); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return window.btoa(binary); + }; + + for (var file of files) { + if (file.name.endsWith(".atlas") || file.name.endsWith(".atlas.txt")) { + atlasFile = file; + assetManager.setRawDataURI( + file.name, + "data:text/plain;," + file.contentText + ); + } else if (file.name.endsWith(".skel")) { + skeletonFile = file; + assetManager.setRawDataURI( + file.name, + "data:application/octet-stream;base64," + + bufferToBase64(file.contentBinary) + ); + assetManager.loadBinary(file.name); + } else if (file.name.endsWith(".json")) { + skeletonFile = file; + assetManager.setRawDataURI( + file.name, + "data:text/plain;," + file.contentText + ); + assetManager.loadJson(file.name); + } else if (file.name.endsWith(".png")) { + pngs.push(file); + assetManager.setRawDataURI( + file.name, + "data:image/png;base64," + bufferToBase64(file.contentBinary) + ); + } } - loadAssets(canvas) { - this.canvas = canvas; - - // Load assets of Spineboy. - canvas.assetManager.loadBinary("assets/spineboy-pro.skel"); - canvas.assetManager.loadTextureAtlas("assets/spineboy-pma.atlas"); + if (!atlasFile) { + alert("Please provide a .atlas or .atlas.txt atlas file."); + return; + } + if (pngs.length == 0) { + alert("Please provide the atlas page .png file(s)."); + } + if (!skeletonFile) { + alert("Please provide a .skel or .json skeleton file."); + return; } - initialize(canvas) { - // Load the Spineboy skeleton - this.loadSkeleton("assets/spineboy-pro.skel", "assets/spineboy-pma.atlas", "run"); + assetManager.loadTextureAtlas(atlasFile.name); - // Setup listener for animation selection box - let animationSelectBox = document.body.querySelector("#animations"); - animationSelectBox.onchange = () => { - this.animationState.setAnimation(0, animationSelectBox.value, true); - } + let waitForLoad = () => { + if (this.canvas.assetManager.isLoadingComplete()) { + this.loadSkeleton(skeletonFile.name, atlasFile.name); + } else { + requestAnimationFrame(waitForLoad); + } + }; + waitForLoad(); + } - // Setup listener for the PMA checkbox - let pmaCheckbox = document.body.querySelector("#pma"); - pmaCheckbox.onchange = () => { - this.pma = pmaCheckbox.checked; - } + loadSkeleton(skeletonFile, atlasFile, animationName) { + // Load the skeleton and setup the animation state + let assetManager = this.canvas.assetManager; + var atlas = assetManager.require(atlasFile); + var atlasLoader = new spine.AtlasAttachmentLoader(atlas); + var skeletonData; + var skeletonBinaryOrJson = skeletonFile.endsWith(".skel") + ? new spine.SkeletonBinary(atlasLoader) + : new spine.SkeletonJson(atlasLoader); + skeletonBinaryOrJson.scale = 1; + skeletonData = skeletonBinaryOrJson.readSkeletonData( + assetManager.require(skeletonFile) + ); + this.skeleton = new spine.Skeleton(skeletonData); + var animationStateData = new spine.AnimationStateData(skeletonData); + this.animationState = new spine.AnimationState(animationStateData); - // Setup the drag and drop listener - new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files)) + // Fill the animation selection box. + let animationSelectBox = document.body.querySelector("#animations"); + animationSelectBox.innerHTML = ""; + for (var animation of this.skeleton.data.animations) { + if (!animationName) animationName = animation.name; + let option = document.createElement("option"); + option.value = option.innerText = animation.name; + option.selected = animation.name == animationName; + animationSelectBox.appendChild(option); + } + this.animationState.setAnimation(0, animationName, true); - // Setup a camera controller for paning and zooming - new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera); + // Fill the skin selection box. + let skinSelectBox = document.body.querySelector("#skins"); + skinSelectBox.innerHTML = ""; + for (var skin of this.skeleton.data.skins) { + let option = document.createElement("option"); + option.value = option.innerText = skin.name; + skinSelectBox.appendChild(option); + } + if ( + !this.skeleton.data.defaultSkin || + (this.skeleton.data.defaultSkin.attachments.length == 0 && + this.skeleton.data.skins.length > 1) + ) { + this.skeleton.setSkin(this.skeleton.data.skins[0]); } - onDrop(files) { - let atlasFile; - let skeletonFile; - let pngs = []; - let assetManager = this.canvas.assetManager; + // Center the skeleton in the viewport + this.centerSkeleton(); + } - // We use data URIs to load the dropped files. Some file types - // are binary, so we have to encode them to base64 for loading - // through AssetManager. - let bufferToBase64 = (buffer) => { - var binary = ''; - var bytes = new Uint8Array(buffer); - var len = bytes.byteLength; - for (var i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); - } - return window.btoa(binary); - } + centerSkeleton() { + // Calculate the bounds of the skeleton + this.animationState.update(0); + this.animationState.apply(this.skeleton); + this.skeleton.updateWorldTransform(); + let offset = new spine.Vector2(), + size = new spine.Vector2(); + this.skeleton.getBounds(offset, size); - for (var file of files) { - if (file.name.endsWith(".atlas") || file.name.endsWith(".atlas.txt")) { - atlasFile = file; - assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); - } else if (file.name.endsWith(".skel")) { - skeletonFile = file; - assetManager.setRawDataURI(file.name, "data:application/octet-stream;base64," + bufferToBase64(file.contentBinary)); - assetManager.loadBinary(file.name); - } else if (file.name.endsWith(".json")) { - skeletonFile = file; - assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); - assetManager.loadJson(file.name); - } else if (file.name.endsWith(".png")) { - pngs.push(file); - assetManager.setRawDataURI(file.name, "data:image/png;base64," + bufferToBase64(file.contentBinary)); - } - } - - if (!atlasFile) { - alert("Please provide a .atlas or .atlas.txt atlas file."); - return; - } - if (pngs.length == 0) { - alert("Please provide the atlas page .png file(s)."); - } - if (!skeletonFile) { - alert("Please provide a .skel or .json skeleton file."); - return; - } - - assetManager.loadTextureAtlas(atlasFile.name); - - let waitForLoad = () => { - if (this.canvas.assetManager.isLoadingComplete()) { - this.loadSkeleton(skeletonFile.name, atlasFile.name); - } else { - requestAnimationFrame(waitForLoad); - } - } - waitForLoad(); + let camera = this.canvas.renderer.camera; + let renderer = this.canvas.renderer; + renderer.resize(spine.ResizeMode.Expand); + if ( + !Number.isFinite(size.x) || + !Number.isFinite(size.y) || + !Number.isFinite(offset.x) || + !Number.isFinite(offset.y) + ) { + camera.position.x = 0; + camera.position.y = 0; + camera.zoom = 1; + camera.update(); + return; } - loadSkeleton(skeletonFile, atlasFile, animationName) { - // Load the skeleton and setup the animation state - let assetManager = this.canvas.assetManager; - var atlas = assetManager.require(atlasFile); - var atlasLoader = new spine.AtlasAttachmentLoader(atlas); - var skeletonData; - var skeletonBinaryOrJson = skeletonFile.endsWith(".skel") ? - new spine.SkeletonBinary(atlasLoader) : - new spine.SkeletonJson(atlasLoader); - skeletonBinaryOrJson.scale = 1; - skeletonData = skeletonBinaryOrJson.readSkeletonData(assetManager.require(skeletonFile)); - this.skeleton = new spine.Skeleton(skeletonData); - var animationStateData = new spine.AnimationStateData(skeletonData); - this.animationState = new spine.AnimationState(animationStateData); + // Make sure the canvas is sized properly and position and zoom + // the camera so the skeleton is centered in the viewport. + camera.position.x = offset.x + size.x / 2; + camera.position.y = offset.y + size.y / 2; + camera.zoom = + size.x > size.y + ? (size.x / this.canvas.htmlCanvas.width) * 3 + : (size.y / this.canvas.htmlCanvas.height) * 3; + camera.update(); + } - // Fill the animation selection box. - let animationSelectBox = document.body.querySelector("#animations"); - animationSelectBox.innerHTML = ""; - for (var animation of this.skeleton.data.animations) { - if (!animationName) animationName = animation.name; - let option = document.createElement("option"); - option.value = option.innerText = animation.name; - option.selected = animation.name == animationName; - animationSelectBox.appendChild(option); - } - this.animationState.setAnimation(0, animationName, true); + update(canvas, delta) { + this.animationState.update(delta); + this.animationState.apply(this.skeleton); + this.skeleton.updateWorldTransform(); + } - // Center the skeleton in the viewport - this.centerSkeleton(); - } + render(canvas) { + let renderer = canvas.renderer; + renderer.resize(spine.ResizeMode.Expand); - centerSkeleton() { - // Calculate the bounds of the skeleton - this.animationState.update(0); - this.animationState.apply(this.skeleton); - this.skeleton.updateWorldTransform(); - let offset = new spine.Vector2(), size = new spine.Vector2(); - this.skeleton.getBounds(offset, size); + canvas.clear(0.2, 0.2, 0.2, 1); - // Make sure the canvas is sized properly and position and zoom - // the camera so the skeleton is centered in the viewport. - let renderer = this.canvas.renderer; - renderer.resize(spine.ResizeMode.Expand); - let camera = this.canvas.renderer.camera; - camera.position.x = offset.x + size.x / 2; - camera.position.y = offset.y + size.y / 2; - camera.zoom = size.x > size.y ? size.x / this.canvas.htmlCanvas.width * 3 : size.y / this.canvas.htmlCanvas.height * 3; - camera.update(); - } - - update(canvas, delta) { - this.animationState.update(delta); - this.animationState.apply(this.skeleton); - this.skeleton.updateWorldTransform(); - } - - render(canvas) { - let renderer = canvas.renderer; - renderer.resize(spine.ResizeMode.Expand); - - canvas.clear(0.2, 0.2, 0.2, 1); - - renderer.begin(); - renderer.line(-10000, 0, 10000, 0, spine.Color.RED); - renderer.line(0, -10000, 0, 10000, spine.Color.GREEN); - renderer.drawSkeleton(this.skeleton, this.pma); - renderer.end(); - } + renderer.begin(); + renderer.line(-10000, 0, 10000, 0, spine.Color.RED); + renderer.line(0, -10000, 0, 10000, spine.Color.GREEN); + renderer.drawSkeleton(this.skeleton, this.pma); + renderer.end(); + } } new spine.SpineCanvas(document.getElementById("canvas"), { - app: new App(), - webglConfig: { - alpha: false - } + app: new App(), + webglConfig: { + alpha: false, + }, }); class FileDragAndDrop { - constructor(element, callback) { - this.callback = callback; - element.ondrop = (ev) => this.onDrop(ev); - element.ondragover = (ev) => ev.preventDefault(); - } + constructor(element, callback) { + this.callback = callback; + element.ondrop = (ev) => this.onDrop(ev); + element.ondragover = (ev) => ev.preventDefault(); + } - async onDrop(event) { - event.preventDefault(); - event.stopPropagation(); + async onDrop(event) { + event.preventDefault(); + event.stopPropagation(); - const items = Object.keys(event.dataTransfer.items); - let files = []; - await Promise.all(items.map(async (key) => { - var file = event.dataTransfer.items[key].getAsFile(); - if (file.kind == "string") return; - let contentBinary = await file.arrayBuffer(); - let contentText = await file.text(); - files.push({ name: file.name, contentBinary: contentBinary, contentText: contentText }); - })); - this.callback(files); - } + const items = Object.keys(event.dataTransfer.items); + let files = []; + await Promise.all( + items.map(async (key) => { + var file = event.dataTransfer.items[key].getAsFile(); + if (file.kind == "string") return; + let contentBinary = await file.arrayBuffer(); + let contentText = await file.text(); + files.push({ + name: file.name, + contentBinary: contentBinary, + contentText: contentText, + }); + }) + ); + this.callback(files); + } } // Shim for older browsers for File/Blob.arrayBuffer() and .text() (function () { - function arrayBuffer() { - return new Promise(function () { - let fr = new FileReader(); - fr.onload = () => { - resolve(fr.result); - }; - fr.readAsArrayBuffer(); - }) - } + function arrayBuffer() { + return new Promise(function () { + let fr = new FileReader(); + fr.onload = () => { + resolve(fr.result); + }; + fr.readAsArrayBuffer(); + }); + } - function text() { - return new Promise(function () { - let fr = new FileReader(); - fr.onload = () => { - resolve(fr.result); - }; - fr.readAsText(this); - }) - } + function text() { + return new Promise(function () { + let fr = new FileReader(); + fr.onload = () => { + resolve(fr.result); + }; + fr.readAsText(this); + }); + } - if ('File' in self) { - File.prototype.arrayBuffer = File.prototype.arrayBuffer || arrayBuffer; - File.prototype.text = File.prototype.text || text; - } - Blob.prototype.arrayBuffer = Blob.prototype.arrayBuffer || arrayBuffer; - Blob.prototype.text = Blob.prototype.text || text; -})(); \ No newline at end of file + if ("File" in self) { + File.prototype.arrayBuffer = File.prototype.arrayBuffer || arrayBuffer; + File.prototype.text = File.prototype.text || text; + } + Blob.prototype.arrayBuffer = Blob.prototype.arrayBuffer || arrayBuffer; + Blob.prototype.text = Blob.prototype.text || text; +})();