95 std::array<std::string, 3> fields{};
96 std::size_t field_index = 0;
97 std::size_t field_begin = 0;
99 if (field_index >= fields.size()) {
102 const std::size_t slash = token.find(
'/', field_begin);
103 fields[field_index++] = token.substr(field_begin, slash == std::string::npos ? std::string::npos : slash - field_begin);
104 if (slash == std::string::npos) {
107 field_begin = slash + 1;
162 [[nodiscard]]
inline bool load_mtl_file(
const std::string &path, std::vector<OBJMaterial> &materials, std::string &error) {
163 std::ifstream file(path);
164 if (!file.is_open()) {
165 error =
"could not open material library '" + path +
"'";
169 std::vector<OBJMaterial> loaded_materials;
172 std::size_t line_number = 0;
173 while (std::getline(file, line)) {
180 std::istringstream stream(line);
183 if (tag ==
"newmtl") {
184 loaded_materials.emplace_back();
185 current = &loaded_materials.back();
186 std::getline(stream, current->
name);
188 if (current->
name.empty()) {
189 error =
"unnamed material at line " + std::to_string(line_number) +
" in '" + path +
"'";
194 if (current ==
nullptr) {
200 parsed =
static_cast<bool>(stream >> current->
ambient[0] >> current->
ambient[1] >> current->
ambient[2]);
201 }
else if (tag ==
"Kd") {
202 parsed =
static_cast<bool>(stream >> current->
diffuse[0] >> current->
diffuse[1] >> current->
diffuse[2]);
203 }
else if (tag ==
"Ks") {
205 }
else if (tag ==
"Ns") {
206 parsed =
static_cast<bool>(stream >> current->
shininess);
207 }
else if (tag ==
"d") {
208 parsed =
static_cast<bool>(stream >> current->
dissolve);
209 }
else if (tag ==
"Tr") {
210 float transparency = 0.0f;
211 parsed =
static_cast<bool>(stream >> transparency);
212 current->
dissolve = 1.0f - transparency;
213 }
else if (tag ==
"illum") {
215 }
else if (tag ==
"map_Kd") {
217 if (!texture_path.empty()) {
223 error =
"invalid '" + tag +
"' value at line " + std::to_string(line_number) +
" in '" + path +
"'";
229 for (
float &component : material.ambient) {
230 component = std::clamp(component, 0.0f, 1.0f);
232 for (
float &component : material.diffuse) {
233 component = std::clamp(component, 0.0f, 1.0f);
235 for (
float &component : material.specular) {
236 component = std::clamp(component, 0.0f, 1.0f);
238 material.dissolve = std::clamp(material.dissolve, 0.0f, 1.0f);
241 materials = std::move(loaded_materials);
283 inline void append_obj_triangle(
const std::vector<OBJVertex> &face, std::size_t a, std::size_t b, std::size_t c,
const std::string &material_name,
const std::string &object_name, std::vector<OBJTriangle> &triangles) {
284 triangles.push_back({{face[a], face[b], face[c]}, material_name, object_name});
287 inline void triangulate_obj_face(
const std::vector<OBJVertex> &face,
const std::string &material_name,
const std::string &object_name, std::vector<OBJTriangle> &triangles) {
288 if (face.size() < 3) {
291 if (face.size() == 3) {
296 const std::array<float, 3> normal =
obj_face_normal(face[0], face[1], face[2]);
298 if (std::fabs(normal[1]) > std::fabs(normal[0]) && std::fabs(normal[1]) >= std::fabs(normal[2])) {
300 }
else if (std::fabs(normal[2]) > std::fabs(normal[0]) && std::fabs(normal[2]) > std::fabs(normal[1])) {
305 if (std::fabs(area) <= 1.0e-6f) {
306 for (std::size_t index = 2; index < face.size(); ++index) {
311 const float winding = area >= 0.0f ? 1.0f : -1.0f;
313 std::vector<std::size_t> remaining(face.size());
314 for (std::size_t index = 0; index < remaining.size(); ++index) {
315 remaining[index] = index;
317 while (remaining.size() > 3) {
318 bool clipped_ear =
false;
319 for (std::size_t index = 0; index < remaining.size(); ++index) {
320 const std::size_t previous = remaining[(index + remaining.size() - 1) % remaining.size()];
321 const std::size_t current = remaining[index];
322 const std::size_t next = remaining[(index + 1) % remaining.size()];
323 if (
obj_edge_cross(face[previous], face[current], face[next], drop_axis) * winding <= 1.0e-6f) {
327 bool contains_point =
false;
328 for (
const std::size_t test : remaining) {
329 if (test != previous && test != current && test != next &&
331 contains_point =
true;
335 if (contains_point) {
340 remaining.erase(remaining.begin() +
static_cast<std::ptrdiff_t
>(index));
346 for (std::size_t index = 2; index < remaining.size(); ++index) {
347 append_obj_triangle(face, remaining[0], remaining[index - 1], remaining[index], material_name, object_name, triangles);
352 append_obj_triangle(face, remaining[0], remaining[1], remaining[2], material_name, object_name, triangles);
356 std::ifstream file(path);
357 if (!file.is_open()) {
358 error =
"could not open file";
362 std::vector<std::array<float, 3>> positions;
363 std::vector<std::array<float, 2>> texcoords;
364 std::vector<std::array<float, 3>> normals;
366 loaded.
object_name = std::filesystem::path(path).stem().string();
368 std::string current_material;
370 std::size_t line_number = 0;
372 while (std::getline(file, line)) {
379 std::istringstream stream(line);
383 std::array<float, 3> position{};
384 if (!(stream >> position[0] >> position[1] >> position[2]) ||
385 !std::isfinite(position[0]) || !std::isfinite(position[1]) || !std::isfinite(position[2])) {
386 error =
"invalid vertex at line " + std::to_string(line_number);
389 positions.push_back(position);
390 }
else if (tag ==
"vt") {
391 std::array<float, 2> texcoord{};
392 if (!(stream >> texcoord[0] >> texcoord[1]) ||
393 !std::isfinite(texcoord[0]) || !std::isfinite(texcoord[1])) {
394 error =
"invalid texture coordinate at line " + std::to_string(line_number);
397 texcoords.push_back(texcoord);
398 }
else if (tag ==
"vn") {
399 std::array<float, 3> normal{};
400 if (!(stream >> normal[0] >> normal[1] >> normal[2]) ||
401 !std::isfinite(normal[0]) || !std::isfinite(normal[1]) || !std::isfinite(normal[2])) {
402 error =
"invalid normal at line " + std::to_string(line_number);
405 normals.push_back(normal);
406 }
else if (tag ==
"o") {
408 std::getline(stream, name);
411 current_object = name;
414 }
else if (tag ==
"mtllib") {
416 std::getline(stream, library);
421 }
else if (tag ==
"usemtl") {
422 std::getline(stream, current_material);
424 }
else if (tag ==
"f") {
425 std::vector<OBJVertex> face;
427 while (stream >> token) {
430 error =
"malformed face token '" + token +
"' at line " + std::to_string(line_number);
435 if (position_index < 0 || position_index >=
static_cast<int>(positions.size())) {
436 error =
"face position index out of range at line " + std::to_string(line_number);
441 vertex.
position = positions[
static_cast<std::size_t
>(position_index)];
444 if (texcoord_index < 0 || texcoord_index >=
static_cast<int>(texcoords.size())) {
445 error =
"face texture index out of range at line " + std::to_string(line_number);
448 vertex.
texcoord = texcoords[
static_cast<std::size_t
>(texcoord_index)];
452 if (normal_index < 0 || normal_index >=
static_cast<int>(normals.size())) {
453 error =
"face normal index out of range at line " + std::to_string(line_number);
457 face.push_back(vertex);
460 if (face.size() < 3) {
461 error =
"face has fewer than three vertices at line " + std::to_string(line_number);
469 error =
"no geometry found";
477 result = std::move(loaded);