[cpp][ts] Improved test beds.

This commit is contained in:
Mario Zechner 2023-11-27 12:06:52 +01:00
parent 3dabb07561
commit 8a80baa0ac
3 changed files with 287 additions and 215 deletions

View File

@ -70,11 +70,12 @@ class NullAttachmentLoader : public AttachmentLoader {
}; };
int main(void) { int main(void) {
String atlasFile(""); String atlasFile("/Users/badlogic/Desktop/basemodel-male/basemodel-male.atlas");
String skeletonFile("/Users/badlogic/workspaces/spine-runtimes/spine-haxe/example/assets/vine-pro.json"); String skeletonFile("/Users/badlogic/Desktop/basemodel-male/basemodel-male.skel");
String animation = ""; String animation = "";
String skin = "BasicBody";
float scale = 1.0f; float scale = 0.1f;
SFMLTextureLoader textureLoader; SFMLTextureLoader textureLoader;
NullAttachmentLoader nullLoader; NullAttachmentLoader nullLoader;
Atlas *atlas = atlasFile.length() == 0 ? nullptr : new Atlas(atlasFile, &textureLoader); Atlas *atlas = atlasFile.length() == 0 ? nullptr : new Atlas(atlasFile, &textureLoader);
@ -106,6 +107,7 @@ int main(void) {
drawable.skeleton->updateWorldTransform(); drawable.skeleton->updateWorldTransform();
drawable.skeleton->setPosition(320, 590); drawable.skeleton->setPosition(320, 590);
if (animation.length() > 0) drawable.state->setAnimation(0, animation, true); 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"); sf::RenderWindow window(sf::VideoMode(640, 640), "Spine SFML - testbed");
window.setFramerateLimit(60); window.setFramerateLimit(60);

View File

@ -1,21 +1,27 @@
<html> <html>
<script src="../dist/iife/spine-webgl.js"></script> <script src="../dist/iife/spine-webgl.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
</style> </style>
<body> <body>
<canvas id="canvas" style="position: absolute; width: 100%; height: 100%;"></canvas> <canvas
<div style="position: absolute; top: 1em; left: 1em; z-index: 1; color: #ccc;"> id="canvas"
<label style="margin-right: 0.5em;">Animations</label> style="position: absolute; width: 100%; height: 100%"
<select id="animations"></select> ></canvas>
<label>PMA</label> <div
<input type="checkbox" id="pma" checked> style="position: absolute; top: 1em; left: 1em; z-index: 1; color: #ccc"
>
<label style="margin-right: 0.5em"> Animations</label>
<select id="animations"></select>
<label style="margin-right: 0.5em">Skins</label>
<select id="skins"></select>
<label>PMA</label>
<input type="checkbox" id="pma" checked />
</div> </div>
<script src="drag-and-drop.js"></script> <script src="drag-and-drop.js"></script>
</body> </body>
</html>
</html>

View File

@ -1,230 +1,294 @@
class App { class App {
constructor() { constructor() {
this.skeleton = null; this.skeleton = null;
this.animationState = null; this.animationState = null;
this.canvas = null; this.canvas = null;
this.pma = true; 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) { if (!atlasFile) {
this.canvas = canvas; alert("Please provide a .atlas or .atlas.txt atlas file.");
return;
// Load assets of Spineboy. }
canvas.assetManager.loadBinary("assets/spineboy-pro.skel"); if (pngs.length == 0) {
canvas.assetManager.loadTextureAtlas("assets/spineboy-pma.atlas"); alert("Please provide the atlas page .png file(s).");
}
if (!skeletonFile) {
alert("Please provide a .skel or .json skeleton file.");
return;
} }
initialize(canvas) { assetManager.loadTextureAtlas(atlasFile.name);
// Load the Spineboy skeleton
this.loadSkeleton("assets/spineboy-pro.skel", "assets/spineboy-pma.atlas", "run");
// Setup listener for animation selection box let waitForLoad = () => {
let animationSelectBox = document.body.querySelector("#animations"); if (this.canvas.assetManager.isLoadingComplete()) {
animationSelectBox.onchange = () => { this.loadSkeleton(skeletonFile.name, atlasFile.name);
this.animationState.setAnimation(0, animationSelectBox.value, true); } else {
} requestAnimationFrame(waitForLoad);
}
};
waitForLoad();
}
// Setup listener for the PMA checkbox loadSkeleton(skeletonFile, atlasFile, animationName) {
let pmaCheckbox = document.body.querySelector("#pma"); // Load the skeleton and setup the animation state
pmaCheckbox.onchange = () => { let assetManager = this.canvas.assetManager;
this.pma = pmaCheckbox.checked; 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 // Fill the animation selection box.
new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files)) 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 // Fill the skin selection box.
new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera); 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) { // Center the skeleton in the viewport
let atlasFile; this.centerSkeleton();
let skeletonFile; }
let pngs = [];
let assetManager = this.canvas.assetManager;
// We use data URIs to load the dropped files. Some file types centerSkeleton() {
// are binary, so we have to encode them to base64 for loading // Calculate the bounds of the skeleton
// through AssetManager. this.animationState.update(0);
let bufferToBase64 = (buffer) => { this.animationState.apply(this.skeleton);
var binary = ''; this.skeleton.updateWorldTransform();
var bytes = new Uint8Array(buffer); let offset = new spine.Vector2(),
var len = bytes.byteLength; size = new spine.Vector2();
for (var i = 0; i < len; i++) { this.skeleton.getBounds(offset, size);
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
for (var file of files) { let camera = this.canvas.renderer.camera;
if (file.name.endsWith(".atlas") || file.name.endsWith(".atlas.txt")) { let renderer = this.canvas.renderer;
atlasFile = file; renderer.resize(spine.ResizeMode.Expand);
assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); if (
} else if (file.name.endsWith(".skel")) { !Number.isFinite(size.x) ||
skeletonFile = file; !Number.isFinite(size.y) ||
assetManager.setRawDataURI(file.name, "data:application/octet-stream;base64," + bufferToBase64(file.contentBinary)); !Number.isFinite(offset.x) ||
assetManager.loadBinary(file.name); !Number.isFinite(offset.y)
} else if (file.name.endsWith(".json")) { ) {
skeletonFile = file; camera.position.x = 0;
assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); camera.position.y = 0;
assetManager.loadJson(file.name); camera.zoom = 1;
} else if (file.name.endsWith(".png")) { camera.update();
pngs.push(file); return;
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();
} }
loadSkeleton(skeletonFile, atlasFile, animationName) { // Make sure the canvas is sized properly and position and zoom
// Load the skeleton and setup the animation state // the camera so the skeleton is centered in the viewport.
let assetManager = this.canvas.assetManager; camera.position.x = offset.x + size.x / 2;
var atlas = assetManager.require(atlasFile); camera.position.y = offset.y + size.y / 2;
var atlasLoader = new spine.AtlasAttachmentLoader(atlas); camera.zoom =
var skeletonData; size.x > size.y
var skeletonBinaryOrJson = skeletonFile.endsWith(".skel") ? ? (size.x / this.canvas.htmlCanvas.width) * 3
new spine.SkeletonBinary(atlasLoader) : : (size.y / this.canvas.htmlCanvas.height) * 3;
new spine.SkeletonJson(atlasLoader); camera.update();
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);
// Fill the animation selection box. update(canvas, delta) {
let animationSelectBox = document.body.querySelector("#animations"); this.animationState.update(delta);
animationSelectBox.innerHTML = ""; this.animationState.apply(this.skeleton);
for (var animation of this.skeleton.data.animations) { this.skeleton.updateWorldTransform();
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);
// Center the skeleton in the viewport render(canvas) {
this.centerSkeleton(); let renderer = canvas.renderer;
} renderer.resize(spine.ResizeMode.Expand);
centerSkeleton() { canvas.clear(0.2, 0.2, 0.2, 1);
// 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);
// Make sure the canvas is sized properly and position and zoom renderer.begin();
// the camera so the skeleton is centered in the viewport. renderer.line(-10000, 0, 10000, 0, spine.Color.RED);
let renderer = this.canvas.renderer; renderer.line(0, -10000, 0, 10000, spine.Color.GREEN);
renderer.resize(spine.ResizeMode.Expand); renderer.drawSkeleton(this.skeleton, this.pma);
let camera = this.canvas.renderer.camera; renderer.end();
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();
}
} }
new spine.SpineCanvas(document.getElementById("canvas"), { new spine.SpineCanvas(document.getElementById("canvas"), {
app: new App(), app: new App(),
webglConfig: { webglConfig: {
alpha: false alpha: false,
} },
}); });
class FileDragAndDrop { class FileDragAndDrop {
constructor(element, callback) { constructor(element, callback) {
this.callback = callback; this.callback = callback;
element.ondrop = (ev) => this.onDrop(ev); element.ondrop = (ev) => this.onDrop(ev);
element.ondragover = (ev) => ev.preventDefault(); element.ondragover = (ev) => ev.preventDefault();
} }
async onDrop(event) { async onDrop(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const items = Object.keys(event.dataTransfer.items); const items = Object.keys(event.dataTransfer.items);
let files = []; let files = [];
await Promise.all(items.map(async (key) => { await Promise.all(
var file = event.dataTransfer.items[key].getAsFile(); items.map(async (key) => {
if (file.kind == "string") return; var file = event.dataTransfer.items[key].getAsFile();
let contentBinary = await file.arrayBuffer(); if (file.kind == "string") return;
let contentText = await file.text(); let contentBinary = await file.arrayBuffer();
files.push({ name: file.name, contentBinary: contentBinary, contentText: contentText }); let contentText = await file.text();
})); files.push({
this.callback(files); name: file.name,
} contentBinary: contentBinary,
contentText: contentText,
});
})
);
this.callback(files);
}
} }
// Shim for older browsers for File/Blob.arrayBuffer() and .text() // Shim for older browsers for File/Blob.arrayBuffer() and .text()
(function () { (function () {
function arrayBuffer() { function arrayBuffer() {
return new Promise(function () { return new Promise(function () {
let fr = new FileReader(); let fr = new FileReader();
fr.onload = () => { fr.onload = () => {
resolve(fr.result); resolve(fr.result);
}; };
fr.readAsArrayBuffer(); fr.readAsArrayBuffer();
}) });
} }
function text() { function text() {
return new Promise(function () { return new Promise(function () {
let fr = new FileReader(); let fr = new FileReader();
fr.onload = () => { fr.onload = () => {
resolve(fr.result); resolve(fr.result);
}; };
fr.readAsText(this); fr.readAsText(this);
}) });
} }
if ('File' in self) { if ("File" in self) {
File.prototype.arrayBuffer = File.prototype.arrayBuffer || arrayBuffer; File.prototype.arrayBuffer = File.prototype.arrayBuffer || arrayBuffer;
File.prototype.text = File.prototype.text || text; File.prototype.text = File.prototype.text || text;
} }
Blob.prototype.arrayBuffer = Blob.prototype.arrayBuffer || arrayBuffer; Blob.prototype.arrayBuffer = Blob.prototype.arrayBuffer || arrayBuffer;
Blob.prototype.text = Blob.prototype.text || text; Blob.prototype.text = Blob.prototype.text || text;
})(); })();