From 4cc5b258783190aa12eb2e46c75fdb382b3b4a2b Mon Sep 17 00:00:00 2001 From: Joel Kiptoo Date: Tue, 29 Sep 2026 13:00:00 +0300 Subject: [PATCH 1/2] WaveFrontReader: handle MTL xyz and spectral colors and 'd -halo' The color statements (Ka, Kd, Ks, Ke) read three floats directly from the stream, so 'Kd xyz x y z', 'Kd spectral file.rfl' or 'd -halo factor' put the stream into a failed state. LoadMTL then stopped reading the rest of the file but still returned S_OK, and for 'xyz' the color was set from uninitialized values. Each color statement now parses its own line: - 'r [g b]', where g and b default to r - 'xyz x [y z]', converted from CIE XYZ to linear sRGB (D65) - 'spectral file.rfl [factor]' is skipped (debug message) 'd -halo factor' uses the factor, and 'Tf' reports that it is not supported in debug builds. Fixes #229 --- Utilities/WaveFrontReader.h | 133 ++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 22 deletions(-) diff --git a/Utilities/WaveFrontReader.h b/Utilities/WaveFrontReader.h index 6633c041..f0d7edf5 100644 --- a/Utilities/WaveFrontReader.h +++ b/Utilities/WaveFrontReader.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -500,46 +501,59 @@ namespace DX if (0 == wcscmp(strCommand.c_str(), L"Ka")) { // Ambient color - // TODO: check for 'spectral' or 'xyz' - float r, g, b; - InFile >> r >> g >> b; - curMaterial->vAmbient = XMFLOAT3(r, g, b); + LoadColor(InFile, curMaterial->vAmbient); } else if (0 == wcscmp(strCommand.c_str(), L"Kd")) { // Diffuse color - // TODO: check for 'spectral' or 'xyz' - float r, g, b; - InFile >> r >> g >> b; - curMaterial->vDiffuse = XMFLOAT3(r, g, b); + LoadColor(InFile, curMaterial->vDiffuse); } else if (0 == wcscmp(strCommand.c_str(), L"Ks")) { // Specular color - // TODO: check for 'spectral' or 'xyz' - float r, g, b; - InFile >> r >> g >> b; - curMaterial->vSpecular = XMFLOAT3(r, g, b); + LoadColor(InFile, curMaterial->vSpecular); } else if (0 == wcscmp(strCommand.c_str(), L"Ke")) { // Emissive color - // TODO: check for 'spectral' or 'xyz' - float r, g, b; - InFile >> r >> g >> b; - curMaterial->vEmissive = XMFLOAT3(r, g, b); - if (r > 0.f || g > 0.f || b > 0.f) + XMFLOAT3 emissive; + if (LoadColor(InFile, emissive)) { - curMaterial->bEmissive = true; + curMaterial->vEmissive = emissive; + if (emissive.x > 0.f || emissive.y > 0.f || emissive.z > 0.f) + { + curMaterial->bEmissive = true; + } } } + else if (0 == wcscmp(strCommand.c_str(), L"Tf")) + { + // Transmission filter (not supported) +#ifdef _DEBUG + OutputDebugStringW(L"WaveFrontReader: MTL 'Tf' (transmission filter) is not supported\n"); +#endif + } else if (0 == wcscmp(strCommand.c_str(), L"d")) { // Alpha - // TODO: check for '-halo' + // The '-halo' form makes dissolve depend on the surface orientation, which is not + // supported, so only the factor is used. + std::wistringstream args(LoadLineArguments(InFile)); + args.imbue(std::locale::classic()); + + std::wstring token; + args >> token; + if (token != L"-halo") + { + args.clear(); + args.seekg(0); + } + float alpha; - InFile >> alpha; - curMaterial->fAlpha = std::min(1.f, std::max(0.f, alpha)); + if (args >> alpha) + { + curMaterial->fAlpha = std::min(1.f, std::max(0.f, alpha)); + } } else if (0 == wcscmp(strCommand.c_str(), L"Tr")) { @@ -599,7 +613,7 @@ namespace DX // bump, decal, disp, map_d, map_Ka, map_Ns // map_aat - // Ni, sharpness, Tf + // Ni, sharpness // refl } @@ -770,6 +784,81 @@ namespace DX return index; } + // Returns the rest of the current line without any end-of-line comment, + // leaving the newline in the stream + std::wstring LoadLineArguments(std::wifstream& InFile) + { + wchar_t buff[1024] = {}; + InFile.getline(buff, 1024, L'\n'); + InFile.putback(L'\n'); + + std::wstring args = buff; + size_t pos = args.find_first_of(L'#'); + if (pos != std::wstring::npos) + { + args.resize(pos); + } + + return args; + } + + // Reads the arguments of an MTL color statement (Ka, Kd, Ks, Ke, Tf), which can be + // r [g b] (g and b default to r) + // xyz x [y z] CIE XYZ, converted to linear sRGB (y and z default to x) + // spectral file.rfl [factor] spectral curve, not supported + // Returns false and leaves 'color' unchanged if the color could not be read. + bool LoadColor(std::wifstream& InFile, DirectX::XMFLOAT3& color) + { + std::wistringstream args(LoadLineArguments(InFile)); + args.imbue(std::locale::classic()); + + std::wstring token; + args >> token; + if (token == L"spectral") + { +#ifdef _DEBUG + OutputDebugStringW(L"WaveFrontReader: MTL 'spectral' colors are not supported\n"); +#endif + return false; + } + + const bool xyz = (token == L"xyz"); + if (!xyz) + { + args.clear(); + args.seekg(0); + } + + float c[3] = {}; + if (!(args >> c[0])) + return false; + + if (args >> c[1]) + { + if (!(args >> c[2])) + return false; + } + else + { + c[1] = c[2] = c[0]; + } + + if (xyz) + { + // CIE XYZ to linear sRGB (D65 white point), clamping out-of-gamut values + const float r = 3.2404542f * c[0] - 1.5371385f * c[1] - 0.4985314f * c[2]; + const float g = -0.9692660f * c[0] + 1.8760108f * c[1] + 0.0415560f * c[2]; + const float b = 0.0556434f * c[0] - 0.2040259f * c[1] + 1.0572252f * c[2]; + color = DirectX::XMFLOAT3(std::max(0.f, r), std::max(0.f, g), std::max(0.f, b)); + } + else + { + color = DirectX::XMFLOAT3(c[0], c[1], c[2]); + } + + return true; + } + void LoadTexturePath(std::wifstream& InFile, _Out_writes_(maxChar) wchar_t* texture, size_t maxChar) { wchar_t buff[1024] = {}; From d339f014411519c3c9044725526a0b87dc9b7ea7 Mon Sep 17 00:00:00 2001 From: Joel Kiptoo Date: Fri, 9 Oct 2026 09:18:45 +0300 Subject: [PATCH 2/2] WaveFrontReader: apply clang-format --- Utilities/WaveFrontReader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Utilities/WaveFrontReader.h b/Utilities/WaveFrontReader.h index f0d7edf5..485d4577 100644 --- a/Utilities/WaveFrontReader.h +++ b/Utilities/WaveFrontReader.h @@ -793,7 +793,7 @@ namespace DX InFile.putback(L'\n'); std::wstring args = buff; - size_t pos = args.find_first_of(L'#'); + size_t pos = args.find_first_of(L'#'); if (pos != std::wstring::npos) { args.resize(pos); @@ -849,7 +849,7 @@ namespace DX const float r = 3.2404542f * c[0] - 1.5371385f * c[1] - 0.4985314f * c[2]; const float g = -0.9692660f * c[0] + 1.8760108f * c[1] + 0.0415560f * c[2]; const float b = 0.0556434f * c[0] - 0.2040259f * c[1] + 1.0572252f * c[2]; - color = DirectX::XMFLOAT3(std::max(0.f, r), std::max(0.f, g), std::max(0.f, b)); + color = DirectX::XMFLOAT3(std::max(0.f, r), std::max(0.f, g), std::max(0.f, b)); } else {