载入中...
搜索中...
未找到
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
224int SpineAtlas::getRegionPage(int index) const {
225 checkRegion(index);
226 return regions_[static_cast<size_t>(index)].pageIndex;
227}
228
229int SpineAtlas::getRegionX(int index) const {
230 checkRegion(index);
231 return regions_[static_cast<size_t>(index)].x;
232}
233
234int SpineAtlas::getRegionY(int index) const {
235 checkRegion(index);
236 return regions_[static_cast<size_t>(index)].y;
237}
238
239int SpineAtlas::getRegionWidth(int index) const {
240 checkRegion(index);
241 return regions_[static_cast<size_t>(index)].w;
242}
243
244int SpineAtlas::getRegionHeight(int index) const {
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
259int SpineAtlas::getRegionOffsetX(int index) const {
260 checkRegion(index);
261 return regions_[static_cast<size_t>(index)].offsetX;
262}
263
264int SpineAtlas::getRegionOffsetY(int index) const {
265 checkRegion(index);
266 return regions_[static_cast<size_t>(index)].offsetY;
267}
268
269bool SpineAtlas::getRegionRotate(int index) const {
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
int line
std::string value
int y
Definition Grass.cpp:135
float height
Definition Grass.cpp:235
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
std::string error
uint32_t a
uint32_t b
uint32_t c
int width
glm::vec4 p[6]
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
int v
uint32_t s
Definition Weather.cpp:28
int getRegionY(int index) const
int getPageHeight(int pageIndex) const
int getRegionOriginalWidth(int index) const
std::string getPageName(int pageIndex) const
std::string getRegionName(int index) const
bool loadFromFile(const std::string &path, std::string *error=nullptr)
int getPageWidth(int pageIndex) const
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
int getRegionOffsetY(int index) const
bool getRegionRotate(int index) const
int getRegionPage(int index) const
int getRegionWidth(int index) const
int getRegionX(int index) const
int getRegionHeight(int index) const
int findRegion(const std::string &name) const
bool loadFromText(const std::string &text, std::string *error=nullptr)
int getRegionOffsetX(int index) const