|
8 | 8 | #include "borealis/debug/debug.hpp" |
9 | 9 | #include "borealis/gfx/engine.hpp" |
10 | 10 | #include "borealis/gfx/shader.hpp" |
| 11 | +#include "glm/gtx/euler_angles.hpp" |
11 | 12 | #include "glm/gtx/matrix_decompose.hpp" |
12 | 13 |
|
13 | 14 | static brl::GfxShaderProgram* defaultSkinningShader = nullptr; |
@@ -104,6 +105,9 @@ brl::GfxShaderProgram* GetSkinningShader() |
104 | 105 | return defaultSkinningShader; |
105 | 106 | } |
106 | 107 |
|
| 108 | +std::map<std::string, brl::GfxAnimation*> brl::GfxAnimation::cachedAnimations; |
| 109 | + |
| 110 | + |
107 | 111 | brl::GfxAnimation *brl::GfxAnimation::loadAnimation(std::string path) |
108 | 112 | { |
109 | 113 | if (cachedAnimations.contains(path)) |
@@ -132,53 +136,146 @@ std::vector<std::string> splitFileByCharacter(const std::string& data, char deli |
132 | 136 | brl::GfxAnimation::GfxAnimation(std::string path) |
133 | 137 | { |
134 | 138 |
|
135 | | - if (path.ends_with(".smd")) { |
| 139 | +if (path.ends_with(".smd")) |
| 140 | + { |
136 | 141 | // smd loading |
137 | | - |
138 | 142 | std::string data = brl::readFileString(path); |
139 | | - |
140 | 143 | const auto& lines = splitFileByCharacter(data); |
141 | 144 |
|
142 | | - int version = 0; |
143 | | - |
144 | | - enum smd_stage { |
| 145 | + enum smd_stage |
| 146 | + { |
145 | 147 | none, |
146 | | - nodes |
| 148 | + nodes, |
| 149 | + skeleton |
147 | 150 | }; |
148 | 151 | smd_stage cur_stage = none; |
149 | 152 |
|
150 | | - for (const auto & line : lines) { |
151 | | - if (line.starts_with("version")) { |
152 | | - using namespace std::literals::string_literals; // Bring the "s" suffix into scope |
| 153 | + struct smd_frame |
| 154 | + { |
| 155 | + int frame; |
| 156 | + glm::vec3 position; |
| 157 | + glm::quat rotation; |
| 158 | + }; |
| 159 | + |
| 160 | + struct smd_channel |
| 161 | + { |
| 162 | + std::vector<smd_frame> frames; |
| 163 | + }; |
| 164 | + |
| 165 | + struct smd_bone |
| 166 | + { |
| 167 | + int id = 0; |
| 168 | + std::string name; |
| 169 | + int parent_bone = -1; |
| 170 | + smd_channel channel; |
| 171 | + }; |
| 172 | + |
| 173 | + std::vector<smd_bone> bones; |
| 174 | + bones.reserve(64); // Reserve reasonable initial capacity |
153 | 175 |
|
154 | | - std::string verStr = line.substr(("version "s).length(),1); |
| 176 | + int max_frame = 0; |
| 177 | + int current_time = 0; |
155 | 178 |
|
156 | | - version = std::atoi(verStr.c_str()); |
| 179 | + for (const auto& line : lines) |
| 180 | + { |
| 181 | + if (line.empty()) |
157 | 182 | continue; |
158 | | - } |
159 | 183 |
|
160 | | - if (line.starts_with("nodes")) { |
| 184 | + // Fast stage detection using first character |
| 185 | + char first_char = line[0]; |
| 186 | + |
| 187 | + if (first_char == 'v') |
| 188 | + { // version |
| 189 | + continue; // Version not used, skip parsing |
| 190 | + } |
| 191 | + else if (first_char == 'n') |
| 192 | + { // nodes |
161 | 193 | cur_stage = nodes; |
162 | 194 | continue; |
163 | 195 | } |
164 | | - |
165 | | - if (line.starts_with("end")) { |
| 196 | + else if (first_char == 's') |
| 197 | + { // skeleton |
| 198 | + cur_stage = skeleton; |
| 199 | + continue; |
| 200 | + } |
| 201 | + else if (first_char == 'e') |
| 202 | + { // end |
166 | 203 | cur_stage = none; |
167 | 204 | continue; |
168 | 205 | } |
169 | 206 |
|
170 | | - if (cur_stage == nodes) { |
171 | | - auto data = splitFileByCharacter(line,' '); |
| 207 | + if (cur_stage == nodes) |
| 208 | + { |
| 209 | + auto data = splitFileByCharacter(line, ' '); |
172 | 210 |
|
173 | | - int id = std::atoi(data[0].c_str()); |
174 | | - std::string name = data[1].c_str(); |
175 | | - int parentId = std::atoi(data[2].c_str()); |
| 211 | + smd_bone bone; |
| 212 | + bone.id = std::atoi(data[0].c_str()); |
| 213 | + bone.name = std::move(data[1]); |
| 214 | + bone.parent_bone = std::atoi(data[2].c_str()); |
| 215 | + bone.channel.frames.reserve(max_frame > 0 ? max_frame + 1 : 32); |
176 | 216 |
|
| 217 | + bones.push_back(std::move(bone)); |
| 218 | + } |
| 219 | + else if (cur_stage == skeleton) |
| 220 | + { |
| 221 | + if (first_char == 't') |
| 222 | + { // time |
| 223 | + // Fast parse: skip "time " prefix |
| 224 | + current_time = std::atoi(line.c_str() + 5); |
| 225 | + max_frame = std::max(max_frame, current_time); |
| 226 | + } |
| 227 | + else |
| 228 | + { |
| 229 | + auto data = splitFileByCharacter(line, ' '); |
| 230 | + int id = std::atoi(data[0].c_str()); |
| 231 | + |
| 232 | + smd_frame f; |
| 233 | + f.frame = current_time; |
| 234 | + f.position = |
| 235 | + glm::vec3(std::atof(data[1].c_str()), std::atof(data[2].c_str()), std::atof(data[3].c_str())); |
| 236 | + |
| 237 | + glm::vec3 angles(std::atof(data[4].c_str()), std::atof(data[5].c_str()), |
| 238 | + std::atof(data[6].c_str())); |
| 239 | + |
| 240 | + // Correct SMD quaternion conversion (XYZ order) |
| 241 | + f.rotation = glm::quat(angles); |
| 242 | + |
| 243 | + bones[id].channel.frames.push_back(std::move(f)); |
| 244 | + } |
177 | 245 | } |
178 | 246 | } |
179 | 247 |
|
180 | | - } |
| 248 | + length = 1.0f; |
| 249 | + |
| 250 | + // Reserve space for channels |
| 251 | + channels.reserve(bones.size() * 2); |
| 252 | + |
| 253 | + const float inv_max_frame = max_frame > 0 ? 1.0f / static_cast<float>(max_frame) : 0.0f; |
181 | 254 |
|
| 255 | + for (auto& bone : bones) |
| 256 | + { |
| 257 | + if (bone.channel.frames.empty()) |
| 258 | + continue; |
| 259 | + |
| 260 | + Channel positionChannel{bone.id, ChannelInterpolation::LINEAR, TRANSLATION}; |
| 261 | + Channel rotationChannel{bone.id, ChannelInterpolation::LINEAR, ROTATION}; |
| 262 | + |
| 263 | + size_t frame_count = bone.channel.frames.size(); |
| 264 | + positionChannel.frames.reserve(frame_count); |
| 265 | + rotationChannel.frames.reserve(frame_count); |
| 266 | + |
| 267 | + for (const smd_frame& frame : bone.channel.frames) |
| 268 | + { |
| 269 | + float time = static_cast<float>(frame.frame) * inv_max_frame; |
| 270 | + |
| 271 | + positionChannel.frames.push_back(new Vec3AnimationFrame{time, frame.position}); |
| 272 | + rotationChannel.frames.push_back(new QuatAnimationFrame{time, frame.rotation}); |
| 273 | + } |
| 274 | + |
| 275 | + channels.push_back(std::move(positionChannel)); |
| 276 | + channels.push_back(std::move(rotationChannel)); |
| 277 | + } |
| 278 | + } |
182 | 279 | } |
183 | 280 |
|
184 | 281 | brl::GfxSubMesh::~GfxSubMesh() |
|
0 commit comments