载入中...
搜索中...
未找到
SpineAtlas.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "common/Module.h"
7
8#include <cctype>
9#include <memory>
10#include <sstream>
11
12namespace eve::animation {
13namespace {
14
15std::string trim(const std::string &s) {
16 size_t a = 0;
17 while (a < s.size() && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
18 size_t b = s.size();
19 while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
20 return s.substr(a, b - a);
21}
22
23bool startsWithIndent(const std::string &line) {
24 return !line.empty() && (line[0] == ' ' || line[0] == '\t');
25}
26
27bool parsePair(const std::string &value, int &a, int &b) {
28 auto comma = value.find(',');
29 if (comma == std::string::npos) return false;
30 try {
31 a = std::stoi(trim(value.substr(0, comma)));
32 b = std::stoi(trim(value.substr(comma + 1)));
33 return true;
34 } catch (...) {
35 return false;
36 }
37}
38
39bool parseBool(const std::string &value, bool &out) {
40 std::string v = trim(value);
41 for (char &c : v) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
42 if (v == "true" || v == "1" || v == "yes") {
43 out = true;
44 return true;
45 }
46 if (v == "false" || v == "0" || v == "no") {
47 out = false;
48 return true;
49 }
50 // Spine sometimes uses degrees for rotate (90)
51 try {
52 int n = std::stoi(v);
53 out = n != 0;
54 return true;
55 } catch (...) {
56 return false;
57 }
58}
59
60} // namespace
61
62void SpineAtlas::checkPage(int index) const {
63 if (index < 0 || index >= getPageCount())
64 throw Exception("SpineAtlas: page index %d out of range (count=%d)", index, getPageCount());
65}
66
67void SpineAtlas::checkRegion(int index) const {
68 if (index < 0 || index >= getRegionCount())
69 throw Exception("SpineAtlas: region index %d out of range (count=%d)", index,
71}
72
74 pages_.clear();
75 regions_.clear();
76 regionByName_.clear();
77}
78
79bool SpineAtlas::loadFromFile(const std::string &path, std::string *error) {
80 if (path.empty()) {
81 if (error) *error = "empty path";
82 return false;
83 }
84 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
85 if (!fs) fs = eve::filesystem::Filesystem::create();
86 std::unique_ptr<eve::filesystem::FileData> data;
87 try {
88 data.reset(fs->read(path));
89 } catch (...) {
90 if (error) *error = "read failed: " + path;
91 return false;
92 }
93 if (!data || data->getSize() == 0) {
94 if (error) *error = "empty file: " + path;
95 return false;
96 }
97 std::string text(static_cast<const char *>(data->getData()),
98 static_cast<size_t>(data->getSize()));
99 return loadFromText(text, error);
100}
101
102bool SpineAtlas::loadFromText(const std::string &text, std::string *error) {
103 clear();
104 std::istringstream in(text);
105 std::string line;
106 int pageIndex = -1;
107 Region *cur = nullptr;
108
109 auto applyKeyValue = [&](const std::string &keyIn, const std::string &val) {
110 std::string key = keyIn;
111 for (char &c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
112 if (cur) {
113 if (key == "rotate") {
114 bool b = false;
115 parseBool(val, b);
116 cur->rotate = b;
117 } else if (key == "xy") {
118 parsePair(val, cur->x, cur->y);
119 } else if (key == "size") {
120 parsePair(val, cur->w, cur->h);
121 } else if (key == "orig" || key == "originalsize") {
122 parsePair(val, cur->origW, cur->origH);
123 } else if (key == "offset") {
124 parsePair(val, cur->offsetX, cur->offsetY);
125 }
126 } else if (pageIndex >= 0) {
127 Page &p = pages_[static_cast<size_t>(pageIndex)];
128 if (key == "size") parsePair(val, p.width, p.height);
129 }
130 };
131
132 while (std::getline(in, line)) {
133 if (!line.empty() && line.back() == '\r') line.pop_back();
134 std::string raw = line;
135 std::string t = trim(line);
136 if (t.empty()) {
137 cur = nullptr;
138 continue;
139 }
140
141 const bool indented = startsWithIndent(raw);
142 auto colon = t.find(':');
143
144 // Spine atlas: page keys may be unindented ("size: W,H"); region keys are indented.
145 if (colon != std::string::npos && (indented || cur == nullptr)) {
146 // Unindented key:value while no region → page property (ends region context).
147 if (!indented) cur = nullptr;
148 applyKeyValue(trim(t.substr(0, colon)), trim(t.substr(colon + 1)));
149 continue;
150 }
151
152 if (!indented && colon == std::string::npos) {
153 std::string lower = t;
154 for (char &c : lower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
155 const bool looksLikeImage =
156 lower.find(".png") != std::string::npos || lower.find(".jpg") != std::string::npos ||
157 lower.find(".jpeg") != std::string::npos || lower.find(".webp") != std::string::npos ||
158 lower.find(".ktx") != std::string::npos || lower.find(".pvr") != std::string::npos;
159
160 if (pageIndex < 0 || looksLikeImage) {
161 Page p;
162 p.name = t;
163 pages_.push_back(p);
164 pageIndex = static_cast<int>(pages_.size()) - 1;
165 cur = nullptr;
166 continue;
167 }
168 if (regionByName_.count(t)) {
169 if (error) *error = "duplicate region: " + t;
170 clear();
171 return false;
172 }
173 Region r;
174 r.name = t;
175 r.pageIndex = pageIndex;
176 regions_.push_back(r);
177 regionByName_[t] = static_cast<int>(regions_.size()) - 1;
178 cur = &regions_.back();
179 continue;
180 }
181
182 if (colon != std::string::npos)
183 applyKeyValue(trim(t.substr(0, colon)), trim(t.substr(colon + 1)));
184 }
185
186 for (Region &r : regions_) {
187 if (r.origW <= 0) r.origW = r.w;
188 if (r.origH <= 0) r.origH = r.h;
189 }
190
191 if (pages_.empty()) {
192 if (error) *error = "no atlas pages";
193 clear();
194 return false;
195 }
196 return true;
197}
198
199std::string SpineAtlas::getPageName(int pageIndex) const {
200 checkPage(pageIndex);
201 return pages_[static_cast<size_t>(pageIndex)].name;
202}
203
204int SpineAtlas::getPageWidth(int pageIndex) const {
205 checkPage(pageIndex);
206 return pages_[static_cast<size_t>(pageIndex)].width;
207}
208
209int SpineAtlas::getPageHeight(int pageIndex) const {
210 checkPage(pageIndex);
211 return pages_[static_cast<size_t>(pageIndex)].height;
212}
213
214int SpineAtlas::findRegion(const std::string &name) const {
215 auto it = regionByName_.find(name);
216 return it == regionByName_.end() ? -1 : it->second;
217}
218
219std::string SpineAtlas::getRegionName(int index) const {
220 checkRegion(index);
221 return regions_[static_cast<size_t>(index)].name;
222}
223
225 checkRegion(index);
226 return regions_[static_cast<size_t>(index)].pageIndex;
227}
228
230 checkRegion(index);
231 return regions_[static_cast<size_t>(index)].x;
232}
233
235 checkRegion(index);
236 return regions_[static_cast<size_t>(index)].y;
237}
238
240 checkRegion(index);
241 return regions_[static_cast<size_t>(index)].w;
242}
243
245 checkRegion(index);
246 return regions_[static_cast<size_t>(index)].h;
247}
248
250 checkRegion(index);
251 return regions_[static_cast<size_t>(index)].origW;
252}
253
255 checkRegion(index);
256 return regions_[static_cast<size_t>(index)].origH;
257}
258
260 checkRegion(index);
261 return regions_[static_cast<size_t>(index)].offsetX;
262}
263
265 checkRegion(index);
266 return regions_[static_cast<size_t>(index)].offsetY;
267}
268
270 checkRegion(index);
271 return regions_[static_cast<size_t>(index)].rotate;
272}
273
274void SpineAtlas::getRegionUV(int index, int texW, int texH, float &u0, float &v0, float &u1,
275 float &v1) const {
276 checkRegion(index);
277 if (texW <= 0 || texH <= 0) throw Exception("SpineAtlas.getRegionUV: invalid texture size");
278 const Region &r = regions_[static_cast<size_t>(index)];
279 // Packed size: if rotate, the region is stored 90°-rotated so the packed
280 // rectangle spans r.h across and r.w down (matches spine-runtimes spAtlas).
281 float x = static_cast<float>(r.x);
282 float y = static_cast<float>(r.y);
283 u0 = x / static_cast<float>(texW);
284 v0 = y / static_cast<float>(texH);
285 if (r.rotate) {
286 u1 = (x + static_cast<float>(r.h)) / static_cast<float>(texW);
287 v1 = (y + static_cast<float>(r.w)) / static_cast<float>(texH);
288 } else {
289 u1 = (x + static_cast<float>(r.w)) / static_cast<float>(texW);
290 v1 = (y + static_cast<float>(r.h)) / static_cast<float>(texH);
291 }
292}
293
294} // namespace eve::animation
double value
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
std::uint32_t key
glm::vec3 n
Definition Grass.cpp:63
double r
float v
std::int32_t c
int h
std::uint32_t height
std::uint32_t width
std::string text
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
float t
float offsetX
float offsetY
uint32_t index
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
int getRegionY(int index) const
Returns the region y.
int getPageHeight(int pageIndex) const
Returns the page height.
int getRegionOriginalWidth(int index) const
Returns the region original width.
std::string getPageName(int pageIndex) const
Returns the page name.
std::string getRegionName(int index) const
Returns the region name.
bool loadFromFile(const std::string &path, std::string *error=nullptr)
Loads from file.
int getPageWidth(int pageIndex) const
Returns the page width.
void getRegionUV(int index, int texW, int texH, float &u0, float &v0, float &u1, float &v1) const
Normalized UVs for a texture of texW×texH (usually page size).
int getRegionOriginalHeight(int index) const
Returns the region original height.
int getRegionOffsetY(int index) const
Returns the region offset y.
bool getRegionRotate(int index) const
Returns the region rotate.
int getPageCount() const
Returns the page count.
Definition SpineAtlas.h:35
int getRegionPage(int index) const
Returns the region page.
int getRegionWidth(int index) const
Returns the region width.
int getRegionX(int index) const
Returns the region x.
int getRegionHeight(int index) const
Returns the region height.
int getRegionCount() const
Returns the region count.
Definition SpineAtlas.h:44
int findRegion(const std::string &name) const
Finds region.
bool loadFromText(const std::string &text, std::string *error=nullptr)
Loads from text.
int getRegionOffsetX(int index) const
Returns the region offset x.