载入中...
搜索中...
未找到
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}
106float SpineSkeletonData::getBoneX(int index) const {
107 checkBone(index);
108 return bones_[static_cast<size_t>(index)].x;
109}
110float SpineSkeletonData::getBoneY(int index) const {
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}
118float SpineSkeletonData::getBoneScaleX(int index) const {
119 checkBone(index);
120 return bones_[static_cast<size_t>(index)].scaleX;
121}
122float SpineSkeletonData::getBoneScaleY(int index) const {
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}
131int SpineSkeletonData::getSlotBone(int index) const {
132 checkSlot(index);
133 return slots_[static_cast<size_t>(index)].bone;
134}
135std::string SpineSkeletonData::getSlotAttachment(int index) const {
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}
144std::string SpineSkeletonData::getAnimationName(int index) const {
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
std::string type
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
std::string error
uint32_t b
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
int v
int parent
Definition TreeMesh.cpp:175
uint32_t s
Definition Weather.cpp:28
float getAnimationDuration(int index) const
bool loadFromFile(const std::string &path, std::string *error=nullptr)
int findSkin(const std::string &name) const
std::string getSkinName(int index) const
bool loadFromJson(const std::string &json, std::string *error=nullptr)
const RegionAttachment * findAttachment(int skinIndex, int slotIndex, const std::string &name) const
int findAnimation(const std::string &name) const
std::string getSlotAttachment(int index) const
std::string getSlotName(int index) const
int findSlot(const std::string &name) const
std::string getBoneName(int index) const
const SkinData & skin(int i) const
std::string getAnimationName(int index) const
int findBone(const std::string &name) const
const BoneData & bone(int i) const