载入中...
搜索中...
未找到
SpineSkeletonData.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "common/Module.h"
5#include "data/DataModule.h"
6#include "data/JsonDocument.h"
9
10#include <Poco/Dynamic/Var.h>
11#include <Poco/JSON/Array.h>
12#include <Poco/JSON/Object.h>
13
14#include <algorithm>
15#include <cmath>
16#include <memory>
17
18namespace eve::animation {
19namespace {
20
21float asFloat(const Poco::Dynamic::Var &v, float fallback = 0.f) {
22 try {
23 if (v.isEmpty()) return fallback;
24 return v.convert<float>();
25 } catch (...) {
26 return fallback;
27 }
28}
29
30std::string asString(const Poco::Dynamic::Var &v, const std::string &fallback = {}) {
31 try {
32 if (v.isEmpty()) return fallback;
33 return v.convert<std::string>();
34 } catch (...) {
35 return fallback;
36 }
37}
38
39bool keyStepped(Poco::JSON::Object::Ptr obj) {
40 if (!obj || !obj->has("curve")) return false;
41 try {
42 return asString(obj->get("curve")) == "stepped";
43 } catch (...) {
44 return false;
45 }
46}
47
48} // namespace
49
50
51void SpineSkeletonData::checkBone(int index) const {
52 if (index < 0 || index >= getBoneCount())
53 throw Exception("SpineSkeletonData: bone index %d out of range", index);
54}
55void SpineSkeletonData::checkSlot(int index) const {
56 if (index < 0 || index >= getSlotCount())
57 throw Exception("SpineSkeletonData: slot index %d out of range", index);
58}
59void SpineSkeletonData::checkSkin(int index) const {
60 if (index < 0 || index >= getSkinCount())
61 throw Exception("SpineSkeletonData: skin index %d out of range", index);
62}
63void SpineSkeletonData::checkAnim(int index) const {
64 if (index < 0 || index >= getAnimationCount())
65 throw Exception("SpineSkeletonData: animation index %d out of range", index);
66}
67
69 spineVersion_.clear();
70 bones_.clear();
71 slots_.clear();
72 skins_.clear();
73 anims_.clear();
74 boneByName_.clear();
75 slotByName_.clear();
76 skinByName_.clear();
77 animByName_.clear();
78 defaultSkin_ = -1;
79}
80
81int SpineSkeletonData::findBone(const std::string &name) const {
82 auto it = boneByName_.find(name);
83 return it == boneByName_.end() ? -1 : it->second;
84}
85int SpineSkeletonData::findSlot(const std::string &name) const {
86 auto it = slotByName_.find(name);
87 return it == slotByName_.end() ? -1 : it->second;
88}
89int SpineSkeletonData::findSkin(const std::string &name) const {
90 auto it = skinByName_.find(name);
91 return it == skinByName_.end() ? -1 : it->second;
92}
93int SpineSkeletonData::findAnimation(const std::string &name) const {
94 auto it = animByName_.find(name);
95 return it == animByName_.end() ? -1 : it->second;
96}
97
98std::string SpineSkeletonData::getBoneName(int index) const {
99 checkBone(index);
100 return bones_[static_cast<size_t>(index)].name;
101}
103 checkBone(index);
104 return bones_[static_cast<size_t>(index)].parent;
105}
107 checkBone(index);
108 return bones_[static_cast<size_t>(index)].x;
109}
111 checkBone(index);
112 return bones_[static_cast<size_t>(index)].y;
113}
115 checkBone(index);
116 return bones_[static_cast<size_t>(index)].rotation;
117}
119 checkBone(index);
120 return bones_[static_cast<size_t>(index)].scaleX;
121}
123 checkBone(index);
124 return bones_[static_cast<size_t>(index)].scaleY;
125}
126
127std::string SpineSkeletonData::getSlotName(int index) const {
128 checkSlot(index);
129 return slots_[static_cast<size_t>(index)].name;
130}
132 checkSlot(index);
133 return slots_[static_cast<size_t>(index)].bone;
134}
136 checkSlot(index);
137 return slots_[static_cast<size_t>(index)].attachment;
138}
139
140std::string SpineSkeletonData::getSkinName(int index) const {
141 checkSkin(index);
142 return skins_[static_cast<size_t>(index)].name;
143}
145 checkAnim(index);
146 return anims_[static_cast<size_t>(index)].name;
147}
149 checkAnim(index);
150 return anims_[static_cast<size_t>(index)].duration;
151}
152
154 int skinIndex, int slotIndex, const std::string &name) const {
155 if (skinIndex < 0 || skinIndex >= getSkinCount()) return nullptr;
156 if (name.empty()) return nullptr;
157 const SkinData &sk = skins_[static_cast<size_t>(skinIndex)];
158 auto sit = sk.attachments.find(slotIndex);
159 if (sit == sk.attachments.end()) return nullptr;
160 auto ait = sit->second.find(name);
161 if (ait == sit->second.end()) return nullptr;
162 return &ait->second;
163}
164
165bool SpineSkeletonData::loadFromFile(const std::string &path, std::string *error) {
166 if (path.empty()) {
167 if (error) *error = "empty path";
168 return false;
169 }
170 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
171 if (!fs) fs = eve::filesystem::Filesystem::create();
172 std::unique_ptr<eve::filesystem::FileData> data;
173 try {
174 data.reset(fs->read(path));
175 } catch (...) {
176 if (error) *error = "read failed: " + path;
177 return false;
178 }
179 if (!data || data->getSize() == 0) {
180 if (error) *error = "empty file: " + path;
181 return false;
182 }
183 std::string text(static_cast<const char *>(data->getData()),
184 static_cast<size_t>(data->getSize()));
185 return loadFromJson(text, error);
186}
187
188bool SpineSkeletonData::loadFromJson(const std::string &json, std::string *error) {
189 clear();
190 auto *dm = eve::data::DataModule::create();
191 std::string err;
192 std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
193 if (!doc || !doc->isObject()) {
194 if (error) *error = err.empty() ? "invalid json" : err;
195 return false;
196 }
197 Poco::JSON::Object::Ptr root = doc->object();
198
199 try {
200 if (root->has("skeleton")) {
201 auto skel = root->getObject("skeleton");
202 if (skel) spineVersion_ = asString(skel->get("spine"));
203 }
204
205 // Bones
206 if (root->has("bones")) {
207 auto arr = root->getArray("bones");
208 if (arr) {
209 for (size_t i = 0; i < arr->size(); ++i) {
210 auto obj = arr->getObject(static_cast<unsigned>(i));
211 if (!obj) continue;
212 BoneData b;
213 b.name = asString(obj->get("name"));
214 if (b.name.empty()) continue;
215 if (obj->has("parent")) {
216 std::string parent = asString(obj->get("parent"));
217 b.parent = findBone(parent);
218 if (b.parent < 0) {
219 if (error) *error = "unknown bone parent: " + parent;
220 clear();
221 return false;
222 }
223 }
224 b.x = asFloat(obj->get("x"));
225 b.y = asFloat(obj->get("y"));
226 b.rotation = asFloat(obj->get("rotation"));
227 b.scaleX = obj->has("scaleX") ? asFloat(obj->get("scaleX"), 1.f) : 1.f;
228 b.scaleY = obj->has("scaleY") ? asFloat(obj->get("scaleY"), 1.f) : 1.f;
229 boneByName_[b.name] = static_cast<int>(bones_.size());
230 bones_.push_back(std::move(b));
231 }
232 }
233 }
234 if (bones_.empty()) {
235 BoneData rootBone;
236 rootBone.name = "root";
237 boneByName_["root"] = 0;
238 bones_.push_back(rootBone);
239 }
240
241 // Slots
242 if (root->has("slots")) {
243 auto arr = root->getArray("slots");
244 if (arr) {
245 for (size_t i = 0; i < arr->size(); ++i) {
246 auto obj = arr->getObject(static_cast<unsigned>(i));
247 if (!obj) continue;
248 SlotData s;
249 s.name = asString(obj->get("name"));
250 std::string boneName = asString(obj->get("bone"));
251 s.bone = findBone(boneName);
252 if (s.bone < 0) s.bone = 0;
253 if (obj->has("attachment")) s.attachment = asString(obj->get("attachment"));
254 slotByName_[s.name] = static_cast<int>(slots_.size());
255 slots_.push_back(std::move(s));
256 }
257 }
258 }
259
260 auto parseRegion = [&](const std::string &attName, Poco::JSON::Object::Ptr att)
263 r.name = attName;
264 r.path = att->has("path") ? asString(att->get("path")) : attName;
265 r.x = asFloat(att->get("x"));
266 r.y = asFloat(att->get("y"));
267 r.rotation = asFloat(att->get("rotation"));
268 r.width = asFloat(att->get("width"));
269 r.height = asFloat(att->get("height"));
270 r.scaleX = att->has("scaleX") ? asFloat(att->get("scaleX"), 1.f) : 1.f;
271 r.scaleY = att->has("scaleY") ? asFloat(att->get("scaleY"), 1.f) : 1.f;
272 return r;
273 };
274
275 auto addSkinAttachments = [&](SkinData &skin, Poco::JSON::Object::Ptr attachmentsRoot) {
276 if (!attachmentsRoot) return;
277 // Format A (3.8+): attachments: { slot: { att: {...} } }
278 // Format B (older skins object): same without wrapper
279 for (const auto &slotName : attachmentsRoot->getNames()) {
280 int slotIndex = findSlot(slotName);
281 if (slotIndex < 0) continue;
282 auto slotObj = attachmentsRoot->getObject(slotName);
283 if (!slotObj) continue;
284 for (const auto &attName : slotObj->getNames()) {
285 auto attObj = slotObj->getObject(attName);
286 if (!attObj) continue;
287 // Skip non-region types (mesh, boundingbox, ...)
288 std::string type = attObj->has("type") ? asString(attObj->get("type")) : "region";
289 if (!type.empty() && type != "region") continue;
290 skin.attachments[slotIndex][attName] = parseRegion(attName, attObj);
291 }
292 }
293 };
294
295 // Skins — array form (3.8+) or object form
296 if (root->has("skins")) {
297 if (root->isArray("skins")) {
298 auto arr = root->getArray("skins");
299 for (size_t i = 0; i < arr->size(); ++i) {
300 auto obj = arr->getObject(static_cast<unsigned>(i));
301 if (!obj) continue;
303 skin.name = asString(obj->get("name"));
304 if (skin.name.empty()) skin.name = "default";
305 if (obj->has("attachments"))
306 addSkinAttachments(skin, obj->getObject("attachments"));
307 skinByName_[skin.name] = static_cast<int>(skins_.size());
308 skins_.push_back(std::move(skin));
309 }
310 } else {
311 auto obj = root->getObject("skins");
312 if (obj) {
313 for (const auto &skinName : obj->getNames()) {
315 skin.name = skinName;
316 addSkinAttachments(skin, obj->getObject(skinName));
317 skinByName_[skin.name] = static_cast<int>(skins_.size());
318 skins_.push_back(std::move(skin));
319 }
320 }
321 }
322 }
323 if (skins_.empty()) {
325 skin.name = "default";
326 skinByName_["default"] = 0;
327 skins_.push_back(skin);
328 }
329 defaultSkin_ = findSkin("default");
330 if (defaultSkin_ < 0) defaultSkin_ = 0;
331
332 // Animations
333 if (root->has("animations")) {
334 auto animsObj = root->getObject("animations");
335 if (animsObj) {
336 for (const auto &animName : animsObj->getNames()) {
337 auto animObj = animsObj->getObject(animName);
338 if (!animObj) continue;
339 AnimationData anim;
340 anim.name = animName;
341 float maxT = 0.f;
342
343 if (animObj->has("bones")) {
344 auto bonesObj = animObj->getObject("bones");
345 if (bonesObj) {
346 for (const auto &boneName : bonesObj->getNames()) {
347 int bi = findBone(boneName);
348 if (bi < 0) continue;
349 auto btObj = bonesObj->getObject(boneName);
350 if (!btObj) continue;
351 BoneTimeline bt;
352 bt.boneIndex = bi;
353
354 if (btObj->has("rotate")) {
355 auto arr = btObj->getArray("rotate");
356 if (arr) {
357 for (size_t i = 0; i < arr->size(); ++i) {
358 auto k = arr->getObject(static_cast<unsigned>(i));
359 if (!k) continue;
360 FloatKey fk;
361 fk.time = asFloat(k->get("time"));
362 fk.value = asFloat(k->get("value"),
363 asFloat(k->get("angle")));
364 fk.stepped = keyStepped(k);
365 maxT = std::max(maxT, fk.time);
366 bt.rotate.push_back(fk);
367 }
368 }
369 }
370 if (btObj->has("translate")) {
371 auto arr = btObj->getArray("translate");
372 if (arr) {
373 for (size_t i = 0; i < arr->size(); ++i) {
374 auto k = arr->getObject(static_cast<unsigned>(i));
375 if (!k) continue;
376 TranslateKey tk;
377 tk.time = asFloat(k->get("time"));
378 tk.x = asFloat(k->get("x"));
379 tk.y = asFloat(k->get("y"));
380 tk.stepped = keyStepped(k);
381 maxT = std::max(maxT, tk.time);
382 bt.translate.push_back(tk);
383 }
384 }
385 }
386 if (btObj->has("scale")) {
387 auto arr = btObj->getArray("scale");
388 if (arr) {
389 for (size_t i = 0; i < arr->size(); ++i) {
390 auto k = arr->getObject(static_cast<unsigned>(i));
391 if (!k) continue;
392 ScaleKey sk;
393 sk.time = asFloat(k->get("time"));
394 sk.x = k->has("x") ? asFloat(k->get("x"), 1.f) : 1.f;
395 sk.y = k->has("y") ? asFloat(k->get("y"), 1.f) : 1.f;
396 sk.stepped = keyStepped(k);
397 maxT = std::max(maxT, sk.time);
398 bt.scale.push_back(sk);
399 }
400 }
401 }
402 anim.bones.push_back(std::move(bt));
403 }
404 }
405 }
406
407 if (animObj->has("slots")) {
408 auto slotsObj = animObj->getObject("slots");
409 if (slotsObj) {
410 for (const auto &slotName : slotsObj->getNames()) {
411 int si = findSlot(slotName);
412 if (si < 0) continue;
413 auto stObj = slotsObj->getObject(slotName);
414 if (!stObj) continue;
415 SlotTimeline st;
416 st.slotIndex = si;
417 if (stObj->has("attachment")) {
418 auto arr = stObj->getArray("attachment");
419 if (arr) {
420 for (size_t i = 0; i < arr->size(); ++i) {
421 auto k = arr->getObject(static_cast<unsigned>(i));
422 if (!k) continue;
423 AttachmentKey ak;
424 ak.time = asFloat(k->get("time"));
425 ak.name = k->has("name") ? asString(k->get("name")) : "";
426 maxT = std::max(maxT, ak.time);
427 st.attachment.push_back(ak);
428 }
429 }
430 }
431 anim.slots.push_back(std::move(st));
432 }
433 }
434 }
435
436 anim.duration = maxT;
437 animByName_[anim.name] = static_cast<int>(anims_.size());
438 anims_.push_back(std::move(anim));
439 }
440 }
441 }
442 } catch (const std::exception &ex) {
443 if (error) *error = ex.what();
444 clear();
445 return false;
446 }
447
448 return true;
449}
450
451} // namespace eve::animation
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float duration
int root
Definition AnimSmr.cpp:119
const std::string & s
double r
float v
std::string text
std::array< float, 4 > rotation
std::uint32_t bone
std::int32_t parent
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
uint32_t index
float getAnimationDuration(int index) const
Returns the animation duration.
int getSlotBone(int index) const
Returns the slot bone.
int getSkinCount() const
Returns the skin count.
float getBoneScaleX(int index) const
Returns the bone scale x.
bool loadFromFile(const std::string &path, std::string *error=nullptr)
Loads from file.
int findSkin(const std::string &name) const
Finds skin.
std::string getSkinName(int index) const
Returns the skin name.
bool loadFromJson(const std::string &json, std::string *error=nullptr)
Loads from json.
const RegionAttachment * findAttachment(int skinIndex, int slotIndex, const std::string &name) const
int getBoneParent(int index) const
Returns the bone parent.
float getBoneX(int index) const
Returns the bone x.
int findAnimation(const std::string &name) const
Finds animation.
float getBoneScaleY(int index) const
Returns the bone scale y.
std::string getSlotAttachment(int index) const
Returns the slot attachment.
int getBoneCount() const
Returns the bone count.
std::string getSlotName(int index) const
Returns the slot name.
int findSlot(const std::string &name) const
Finds slot.
std::string getBoneName(int index) const
Returns the bone name.
const SkinData & skin(int i) const
std::string getAnimationName(int index) const
Returns the animation name.
int getAnimationCount() const
Returns the animation count.
float getBoneRotation(int index) const
Returns the bone rotation.
int getSlotCount() const
Returns the slot count.
float getBoneY(int index) const
Returns the bone y.
int findBone(const std::string &name) const
Finds bone.