From 9df1b18482964d1940524e03806317234aedc42e Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Fri, 17 Nov 2023 11:23:06 -0600 Subject: [PATCH 01/26] Fix incorrect texture flip on load in UDIM and reuse - Texture cache reuse may end up with an incorrectly flipped texture in obj/mtl models. - UDIM textures loaded from obj/mtl models were also incorrectly flipped. --- sg/texture/Texture2D.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sg/texture/Texture2D.cpp b/sg/texture/Texture2D.cpp index b2926647..67c6fcdd 100644 --- a/sg/texture/Texture2D.cpp +++ b/sg/texture/Texture2D.cpp @@ -464,6 +464,8 @@ void Texture2D::loadUDIM_tiles(const FileName &fileName) // Use the same params as parent texture // but, mark as "loading" textures to skip re-checking udim tiles work->params = params; + // Don't flip the work tiles, the atlas will be flipped if necessary. + work->params.flip = false; work->udim_params.loading = true; // Load the first tile to establish tile parameters @@ -480,6 +482,8 @@ void Texture2D::loadUDIM_tiles(const FileName &fileName) // Allocate space large enough to hold all tiles (all tiles guaranteed to be // of equal size and format) atlas->params = work->params; + // If requested, flip the entire atlas after tiles have been assembled. + atlas->params.flip = params.flip; atlas->udim_params = work->udim_params; atlas->params.size *= udim_params.dims; std::shared_ptr data( @@ -559,7 +563,8 @@ bool Texture2D::load(const FileName &_fileName, // already in memory), but a unique name for the texture cache. fileName = _fileName; - // Check the cache before creating a new texture + // Check the cache before creating a new texture might be able to share the + // cached texelData. if (textureCache.find(fileName) != textureCache.end()) { std::shared_ptr cache = textureCache[fileName].lock(); if (cache) { @@ -567,6 +572,8 @@ bool Texture2D::load(const FileName &_fileName, udim_params = cache->udim_params; // Copy shared_ptr ownership texelData = cache->texelData; + // the same texelData is being reused, so use the existing isFlipped state + isFlipped = cache->isFlipped; // If texture is cached and all parameters match, just add existing child // parameters. @@ -637,15 +644,13 @@ bool Texture2D::load(const FileName &_fileName, createChild("filename", "filename", fileName).setSGOnly(); createChild("isFlipped", "bool", params.flip).setSGOnly(); - // Since UDIM is a compiled atlas, user can't just flip image - if (hasUDIM()) - child("isFlipped").setReadOnly(); - // XXX Running MPI, simply changing the texture data is not enough to +#if 1 // XXX Running MPI, simply changing the texture data is not enough to // trigger the flip, therefore do not expose an option for the user // to change it. Find a lightweight means to signal MPI to update // texture data to all ranks. (note: The below in preCommit doesn't work) child("isFlipped").setReadOnly(); +#endif child("format").setMinMax(OSP_TEXTURE_RGBA8, OSP_TEXTURE_R16); child("filter").setMinMax( From e544b5fd60821c35bbaa77932ee59e06c3357583 Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Mon, 27 Nov 2023 19:04:43 -0600 Subject: [PATCH 02/26] Add debug message if EXR or TIFF images fail to load --- sg/texture/Texture2D.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sg/texture/Texture2D.cpp b/sg/texture/Texture2D.cpp index 67c6fcdd..eb657473 100644 --- a/sg/texture/Texture2D.cpp +++ b/sg/texture/Texture2D.cpp @@ -299,7 +299,8 @@ void Texture2D::loadTexture_EXR(const std::string &fileName) params.depth = 4; // always float size_t size = params.size.product() * params.components * params.depth; - std::shared_ptr data(new float[size], std::default_delete()); + std::shared_ptr data( + new float[size], std::default_delete()); std::memcpy(data.get(), texels, size); // Move shared_ptr ownership @@ -307,6 +308,11 @@ void Texture2D::loadTexture_EXR(const std::string &fileName) free(texels); // release memory of image data } + + if (!texelData.get()) { + std::cerr << "#osp:sg: EXR failed to load texture '" << fileName << "'" + << std::endl; + } } // @@ -319,7 +325,8 @@ void Texture2D::loadTexture_TIFF(const std::string &fileName) // Loads all images(IFD) in the DNG file to `images` array. std::vector custom_field_lists; - bool ret = tinydng::LoadDNG(fileName.c_str(), custom_field_lists, &images, &warn, &err); + bool ret = tinydng::LoadDNG( + fileName.c_str(), custom_field_lists, &images, &warn, &err); if (!warn.empty()) { std::cout << "Warn: " << warn << std::endl; @@ -348,6 +355,11 @@ void Texture2D::loadTexture_TIFF(const std::string &fileName) // Move shared_ptr ownership texelData = data; } + + if (!texelData.get()) { + std::cerr << "#osp:sg: TIFF failed to load texture '" << fileName << "'" + << std::endl; + } } // From 4c821628a8b4bfdad1c48315536ae286eeaa61ed Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Mon, 4 Dec 2023 19:07:16 -0600 Subject: [PATCH 03/26] Add helper code to enable loading pre-OSPRayStudio v0.13 sg files --- sg/JSONDefs.h | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sg/JSONDefs.h b/sg/JSONDefs.h index f3964e8c..376f9cef 100644 --- a/sg/JSONDefs.h +++ b/sg/JSONDefs.h @@ -106,13 +106,20 @@ inline void from_json(const JSON &, Node &) {} // Helper for converting ambiguous json types to stronger "subType" #define CONVERT_TYPE(X) \ { \ - if (j["subType"] == #X) { \ + if (subType == #X) { \ n->setValue(X(n->valueAs())); \ if (n->hasMinMax()) \ n->setMinMax(X(n->minAs()), X(n->maxAs())); \ } \ } +// Helper for correcting OSP* enum types when not supplied by json +#define FIX_SUBTYPE(X, OSPSUBTYPE) \ + { \ + if (j["name"] == #X) \ + subType = #OSPSUBTYPE; \ + } + inline OSPSG_INTERFACE NodePtr createNodeFromJSON(const JSON &j) { NodePtr n = nullptr; @@ -130,6 +137,17 @@ inline OSPSG_INTERFACE NodePtr createNodeFromJSON(const JSON &j) { && j["value"].get() == ":^)")) return nullptr; + // Original json subType, may need to be corrected + std::string subType = j["subType"]; + + // Handle pre-OSPRay3 Studio sg files without strict OSP* enum types + // These are the common troublemakers in older sg files. + FIX_SUBTYPE(intensityQuantity, OSPIntensityQuantity); + FIX_SUBTYPE(shutterType, OSPShutterType); + FIX_SUBTYPE(stereoMode, OSPStereoMode); + FIX_SUBTYPE(filter, OSPTextureFilter); + FIX_SUBTYPE(format, OSPTextureFormat); + if (j.contains("value")) { Any value; @@ -148,9 +166,9 @@ inline OSPSG_INTERFACE NodePtr createNodeFromJSON(const JSON &j) { // Create node with optional description if (j.contains("description")) - n = createNode(j["name"], j["subType"], j["description"], value); + n = createNode(j["name"], subType, j["description"], value); else - n = createNode(j["name"], j["subType"], value); + n = createNode(j["name"], subType, value); if (j.contains("sgOnly") && j["sgOnly"].get()) n->setSGOnly(); @@ -197,7 +215,7 @@ inline OSPSG_INTERFACE NodePtr createNodeFromJSON(const JSON &j) { n->setMinMax(range1f(n->minAs()), range1f(n->maxAs())); } } else { - n = createNode(j["name"], j["subType"]); + n = createNode(j["name"], subType); } if (n != nullptr) { From 5cea18ea9de62d32b7250ecbae5fe1b94f10c714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 08:23:34 +0000 Subject: [PATCH 04/26] Bump numpy from 1.26.1 to 1.26.2 Bumps [numpy](https://github.com/numpy/numpy) from 1.26.1 to 1.26.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.26.1...v1.26.2) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index de2d18cd..1337eb70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ mpi4py==3.1.5 -numpy==1.26.1 +numpy==1.26.2 From 3c7a59b4b0d931bbfae66e250120680555eaadfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 08:44:20 +0000 Subject: [PATCH 05/26] Bump numpy from 1.26.2 to 1.26.3 Bumps [numpy](https://github.com/numpy/numpy) from 1.26.2 to 1.26.3. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.26.2...v1.26.3) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1337eb70..b920f51c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ mpi4py==3.1.5 -numpy==1.26.2 +numpy==1.26.3 From 3aa4e44720bd98b7f631d247afa556e61f0804d5 Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Fri, 7 Jul 2023 14:52:51 -0500 Subject: [PATCH 06/26] Add emissiveColor to Principled material emissiveColor can be a constant and/or a texture and is used to provide emissivity to Principled material --- sg/importer/glTF.cpp | 843 ++++++++++++--------------- sg/renderer/materials/Luminous.cpp | 30 +- sg/renderer/materials/Principled.cpp | 131 +++-- 3 files changed, 482 insertions(+), 522 deletions(-) diff --git a/sg/importer/glTF.cpp b/sg/importer/glTF.cpp index 311dac8b..8e8ca19c 100644 --- a/sg/importer/glTF.cpp +++ b/sg/importer/glTF.cpp @@ -1175,515 +1175,442 @@ NodePtr GLTFData::createOSPMaterial(const tinygltf::Material &mat) // << " .baseColorFactor.alpha:" << (float)pbr.baseColorFactor[4] // << "\n"; + auto ospMat = createNode(matName, "principled"); + ospMat->createChild("baseColor", "rgb") = rgb( + pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2]); + auto alpha = (float)pbr.baseColorFactor[3]; + ospMat->createChild("metallic", "float") = (float)pbr.metallicFactor; + ospMat->createChild("roughness", "float") = (float)pbr.roughnessFactor; + + // Material Extensions + const auto &exts = mat.extensions; + + // KHR_materials_emissive_strength + float emissiveStrength = 1.f; + if (exts.find("KHR_materials_emissive_strength") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_emissive_strength + auto params = exts.find("KHR_materials_emissive_strength")->second; + + // default: 1.0 + emissiveStrength = 1.f; + if (params.Has("emissiveStrength")) { + emissiveStrength = (float)params.Get("emissiveStrength").Get(); + } + } + auto emissiveColor = rgb(mat.emissiveFactor[0], mat.emissiveFactor[1], mat.emissiveFactor[2]); + ospMat->createChild("emissiveColor", "rgb") = emissiveColor; + ospMat->createChild("intensity", "float") = + emissiveColor == rgb(0.f) ? 0.f : emissiveStrength; + + // Unspec'd defaults + ospMat->createChild("diffuse", "float") = 1.0f; + ospMat->createChild("ior", "float") = 1.5f; + + // XXX will require texture tweaks to get closer to glTF spec, if needed + // BLEND is always used, so OPAQUE can be achieved by setting all texture + // texels alpha to 1.0. OSPRay doesn't support alphaCutoff for MASK mode. + if (mat.alphaMode == "OPAQUE") + ospMat->createChild("opacity", "float") = 1.f; + else if (mat.alphaMode == "BLEND") + ospMat->createChild("opacity", "float") = alpha; + else if (mat.alphaMode == "MASK") + ospMat->createChild("opacity", "float") = 1.f - (float)mat.alphaCutoff; + + // All textures *can* specify a texcoord other than 0. OSPRay only + // supports one set of texcoords (TEXCOORD_0). + if (pbr.baseColorTexture.texCoord != 0 + || pbr.metallicRoughnessTexture.texCoord != 0 + || mat.normalTexture.texCoord != 0) { + WARN << "gltf found TEXCOOR_1 attribute. Not supported...\n"; + WARN << std::endl; + } - // We can emulate a constant colored emissive texture - // XXX this is a workaround - auto constColor = true; - if (emissiveColor != rgb(0.f) && mat.emissiveTexture.index != -1) { - const auto &tex = model.textures[mat.emissiveTexture.index]; - const auto &img = model.images[tex.source]; - if (img.image.size() > 0) { - const auto *data = img.image.data(); - - const rgb color0 = rgb(data[0], data[1], data[2]); - auto i = 1; - WARN << "Material emissiveTexture #" << mat.emissiveTexture.index - << std::endl; - WARN << " color0 : " << color0 << std::endl; - while (constColor && (i < img.width * img.height)) { - const rgb color = - rgb(data[4 * i + 0], data[4 * i + 1], data[4 * i + 2]); - if (color0 != color) { - WARN << " color @ " << i << " : " << color << std::endl; - WARN << " !!! non constant color, skipping emissive" << std::endl; - constColor = false; - break; - } - i++; - } - // Module the emissiveColor with the texture value. - emissiveColor *= color0; - } + if (pbr.baseColorTexture.index != -1 && pbr.baseColorTexture.texCoord == 0) { + // Used as a color texture, must be sRGB space, not linear + setOSPTexture(ospMat, + "baseColor", + pbr.baseColorTexture.index, + pbr.baseColorTexture.extensions, + false); } - if ((emissiveColor == rgb(0.f)) || (constColor == false)) { - auto ospMat = createNode(matName, "principled"); - ospMat->createChild("baseColor", "rgb") = rgb( - pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2]); - auto alpha = (float)pbr.baseColorFactor[3]; - ospMat->createChild("metallic", "float") = (float)pbr.metallicFactor; - ospMat->createChild("roughness", "float") = (float)pbr.roughnessFactor; - - // Unspec'd defaults - ospMat->createChild("diffuse", "float") = 1.0f; - ospMat->createChild("ior", "float") = 1.5f; - - // XXX will require texture tweaks to get closer to glTF spec, if needed - // BLEND is always used, so OPAQUE can be achieved by setting all texture - // texels alpha to 1.0. OSPRay doesn't support alphaCutoff for MASK mode. - // Masking turned off atm due to issues with unwanted opacity values, - // specially for Valerie scenes(some large city buildings) - if (mat.alphaMode == "OPAQUE") - ospMat->createChild("opacity", "float") = 1.f; - else if (mat.alphaMode == "BLEND") - ospMat->createChild("opacity", "float") = alpha; - // else if (mat.alphaMode == "MASK") - // ospMat->createChild("opacity", "float") = 1.f - (float)mat.alphaCutoff; - - // All textures *can* specify a texcoord other than 0. OSPRay only - // supports one set of texcoords (TEXCOORD_0). - if (pbr.baseColorTexture.texCoord != 0 - || pbr.metallicRoughnessTexture.texCoord != 0 - || mat.normalTexture.texCoord != 0) { - WARN << "gltf found TEXCOOR_1 attribute. Not supported...\n"; - WARN << std::endl; - } + if (pbr.metallicRoughnessTexture.index != -1 + && pbr.metallicRoughnessTexture.texCoord == 0) { + // metallic in Blue(2) channel, roughness in Green(1) + setOSPTexture(ospMat, + "metallic", + pbr.metallicRoughnessTexture.index, + pbr.metallicRoughnessTexture.extensions, + 2); + setOSPTexture(ospMat, + "roughness", + pbr.metallicRoughnessTexture.index, + pbr.metallicRoughnessTexture.extensions, + 1); + } - if (pbr.baseColorTexture.index != -1 - && pbr.baseColorTexture.texCoord == 0) { - // Used as a color texture, must be sRGB space, not linear - setOSPTexture(ospMat, - "baseColor", - pbr.baseColorTexture.index, - pbr.baseColorTexture.extensions, - false); - } + if (mat.normalTexture.index != -1 && mat.normalTexture.texCoord == 0) { + // NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {} + setOSPTexture(ospMat, + "normal", + mat.normalTexture.index, + mat.normalTexture.extensions); + ospMat->createChild("normal", "float", (float)mat.normalTexture.scale); + } - if (pbr.metallicRoughnessTexture.index != -1 - && pbr.metallicRoughnessTexture.texCoord == 0) { - // metallic in Blue(2) channel, roughness in Green(1) - setOSPTexture(ospMat, - "metallic", - pbr.metallicRoughnessTexture.index, - pbr.metallicRoughnessTexture.extensions, - 2); - setOSPTexture(ospMat, - "roughness", - pbr.metallicRoughnessTexture.index, - pbr.metallicRoughnessTexture.extensions, - 1); - } + if (mat.emissiveTexture.index != -1 && mat.emissiveTexture.texCoord == 0) { + setOSPTexture(ospMat, + "emissiveColor", + mat.emissiveTexture.index, + mat.emissiveTexture.extensions, + true); + } - if (mat.normalTexture.index != -1 && mat.normalTexture.texCoord == 0) { - // NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {} + // KHR_materials_specular + if (exts.find("KHR_materials_specular") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular + auto params = exts.find("KHR_materials_specular")->second; + + // specularFactor: The strength of the specular reflection. + // default: 1.0 + float specular = 1.f; + if (params.Has("specularFactor")) + specular = (float)params.Get("specularFactor").Get(); + ospMat->createChild("specular", "float") = specular; + + // specularTexture: A texture that defines the strength of the specular + // reflection, stored in the alpha (A) channel. This will be multiplied + // by specularFactor. + if (params.Has("specularTexture")) { setOSPTexture(ospMat, - "normal", - mat.normalTexture.index, - mat.normalTexture.extensions); - ospMat->createChild("normal", "float", (float)mat.normalTexture.scale); + "specular", + params.Get("specularTexture").Get("index").Get(), + params.Get("specularTexture") + .Get("extensions") + .Get(), + 3); } - // Material Extensions - const auto &exts = mat.extensions; - - // KHR_materials_specular - if (exts.find("KHR_materials_specular") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular - auto params = exts.find("KHR_materials_specular")->second; - - // specularFactor: The strength of the specular reflection. - // default: 1.0 - float specular = 1.f; - if (params.Has("specularFactor")) - specular = (float)params.Get("specularFactor").Get(); - ospMat->createChild("specular", "float") = specular; - - // specularTexture: A texture that defines the strength of the specular - // reflection, stored in the alpha (A) channel. This will be multiplied - // by specularFactor. - if (params.Has("specularTexture")) { - setOSPTexture(ospMat, - "specular", - params.Get("specularTexture").Get("index").Get(), - params.Get("specularTexture") - .Get("extensions") - .Get(), - 3); - } - #if 0 // XXX OSPRay is missing the F0 color, always assumes white? - // specularColorFactor The F0 color of the specular reflection - // (linear RGB). - // default: [1.0, 1.0, 1.0] - rgb specularColorFactor = rgb(1.f); - if (params.Has("specularColorFactor")) { - std::vector sv = - params.Get("specularColorFactor").Get(); - specularColorFactor = rgb( - sv[0].Get(), sv[1].Get(), sv[2].Get()); - } - ospMat->createChild("specularColor", "rgb") = specularColorFactor; - - // specularColorTexture: A texture that defines the F0 color of the - // specular reflection, stored in the RGB channels and encoded in sRGB. - // This texture will be multiplied by specularColorFactor. - if (params.Has("specularColorTexture")) { - setOSPTexture(ospMat, - "specularColor", - params.Get("specularColorTexture").Get("index").Get(), - false); - } -#endif - } - - // KHR_materials_pbrSpecularGlossiness - if (exts.find("KHR_materials_pbrSpecularGlossiness") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - auto params = exts.find("KHR_materials_pbrSpecularGlossiness")->second; - - // diffuseFactor: The reflected diffuse factor of the material. - // default:[1.0,1.0,1.0,1.0] - rgb diffuse = rgb(1.f); - float opacity = 1.f; - if (params.Has("diffuseFactor")) { - std::vector dv = - params.Get("diffuseFactor").Get(); - diffuse = - rgb(dv[0].Get(), dv[1].Get(), dv[2].Get()); - opacity = (float)dv[3].Get(); + // specularColorFactor The F0 color of the specular reflection + // (linear RGB). + // default: [1.0, 1.0, 1.0] + rgb specularColorFactor = rgb(1.f); + if (params.Has("specularColorFactor")) { + std::vector sv = + params.Get("specularColorFactor").Get(); + specularColorFactor = rgb( + sv[0].Get(), sv[1].Get(), sv[2].Get()); } - ospMat->createChild("baseColor", "rgb") = diffuse; - ospMat->createChild("opacity", "float") = opacity; + ospMat->createChild("specularColor", "rgb") = specularColorFactor; - // diffuseTexture: The diffuse texture. - if (params.Has("diffuseTexture")) { - // RGB or RGBA + // specularColorTexture: A texture that defines the F0 color of the + // specular reflection, stored in the RGB channels and encoded in sRGB. + // This texture will be multiplied by specularColorFactor. + if (params.Has("specularColorTexture")) { setOSPTexture(ospMat, - "baseColor", - params.Get("diffuseTexture").Get("index").Get(), - params.Get("diffuseTexture") - .Get("extensions") - .Get(), + "specularColor", + params.Get("specularColorTexture").Get("index").Get(), false); } +#endif + } - // specularFactor: The specular RGB color of the material. - // default:[1.0,1.0,1.0] - rgb specular = rgb(1.f); - if (params.Has("specularFactor")) { - std::vector sv = - params.Get("specularFactor").Get(); - specular = - rgb(sv[0].Get(), sv[1].Get(), sv[2].Get()); - } - // XXX this can't simply overwrite baseColor - ospMat->createChild("baseColor", "rgb") = specular; - - // glossinessFactor: The glossiness or smoothness of the material. - // default:1.0 - float gloss = 1.f; - if (params.Has("glossinessFactor")) { - gloss = (float)params.Get("glossinessFactor").Get(); - } - ospMat->createChild("roughness", "float") = 1.f - gloss; + // KHR_materials_pbrSpecularGlossiness + if (exts.find("KHR_materials_pbrSpecularGlossiness") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + auto params = exts.find("KHR_materials_pbrSpecularGlossiness")->second; + + // diffuseFactor: The reflected diffuse factor of the material. + // default:[1.0,1.0,1.0,1.0] + rgb diffuse = rgb(1.f); + float opacity = 1.f; + if (params.Has("diffuseFactor")) { + std::vector dv = + params.Get("diffuseFactor").Get(); + diffuse = + rgb(dv[0].Get(), dv[1].Get(), dv[2].Get()); + opacity = (float)dv[3].Get(); + } + ospMat->createChild("baseColor", "rgb") = diffuse; + ospMat->createChild("opacity", "float") = opacity; - // specularGlossinessTexture: The specular-glossiness texture. -#if 0 - // XXX texture isn't simply RGBA!!! - // texture containing the sRGB encoded specular color and the linear - // glossiness value (A). - if (params.Has("specularGlossinessTexture")) { - // XXX this can't simply overwrite baseColor - setOSPTexture(ospMat, - "baseColor", - params.Get("specularGlossinessTexture").Get("index").Get(), - params.Get("specularGlossinessTexture") - .Get("extensions") - .Get(), - false); - } -#endif + // diffuseTexture: The diffuse texture. + if (params.Has("diffuseTexture")) { + // RGB or RGBA + setOSPTexture(ospMat, + "baseColor", + params.Get("diffuseTexture").Get("index").Get(), + params.Get("diffuseTexture") + .Get("extensions") + .Get(), + false); } - // KHR_materials_clearcoat - if (exts.find("KHR_materials_clearcoat") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_clearcoat - auto params = exts.find("KHR_materials_clearcoat")->second; - - // clearcoatFactor: The clearcoat layer intensity. default:0.0 - float coat = 0.f; - if (params.Has("clearcoatFactor")) - coat = (float)params.Get("clearcoatFactor").Get(); - ospMat->createChild("coat", "float") = coat; - ospMat->createChild("coatThickness", "float") = 1.f; // (not in spec) - - // clearcoatRoughnessFactor: The clearcoat layer roughness. - // default:0.0 - float coatRoughness = 0.f; - if (params.Has("clearcoatRoughnessFactor")) - coatRoughness = - (float)params.Get("clearcoatRoughnessFactor").Get(); - ospMat->createChild("coatRoughness", "float") = coatRoughness; - - // clearcoatTexture: The clearcoat layer intensity texture. - // clearcoatRoughnessTexture: The clearcoat layer roughness texture. -#if 1 - // XXX textures aren't simply RGB!!! - // clearcoat = clearcoatFactor * clearcoatTexture.r - // clearcoatRoughness = clearcoatRoughnessFactor * - // clearcoatRoughnessTexture.g - if (params.Has("clearcoatTexture")) { - // Red channel - setOSPTexture(ospMat, - "coat", - params.Get("clearcoatTexture").Get("index").Get(), - params.Get("clearcoatTexture") - .Get("extensions") - .Get(), - 0); - } - if (params.Has("clearcoatRoughnessTexture")) { - // Green channel + // specularFactor: The specular RGB color of the material. + // default:[1.0,1.0,1.0] + rgb specular = rgb(1.f); + if (params.Has("specularFactor")) { + std::vector sv = + params.Get("specularFactor").Get(); + specular = + rgb(sv[0].Get(), sv[1].Get(), sv[2].Get()); + } + // XXX this can't simply overwrite baseColor + ospMat->createChild("baseColor", "rgb") = specular; + + // glossinessFactor: The glossiness or smoothness of the material. + // default:1.0 + float gloss = 1.f; + if (params.Has("glossinessFactor")) { + gloss = (float)params.Get("glossinessFactor").Get(); + } + ospMat->createChild("roughness", "float") = 1.f - gloss; + + // specularGlossinessTexture: The specular-glossiness texture. +#if 0 + // XXX texture isn't simply RGBA!!! + // texture containing the sRGB encoded specular color and the linear + // glossiness value (A). + if (params.Has("specularGlossinessTexture")) { + // XXX this can't simply overwrite baseColor setOSPTexture(ospMat, - "coatRoughness", - params.Get("clearcoatRoughnessTexture").Get("index").Get(), - params.Get("clearcoatRoughnessTexture") + "baseColor", + params.Get("specularGlossinessTexture").Get("index").Get(), + params.Get("specularGlossinessTexture") .Get("extensions") .Get(), - 1); + false); } #endif + } - // clearcoatNormalTexture: The clearcoat normal map texture. - if (params.Has("clearcoatNormalTexture")) { - setOSPTexture(ospMat, - "coatNormal", - params.Get("clearcoatNormalTexture").Get("index").Get(), - params.Get("clearcoatNormalTexture") - .Get("extensions") - .Get()); - } + // KHR_materials_clearcoat + if (exts.find("KHR_materials_clearcoat") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_clearcoat + auto params = exts.find("KHR_materials_clearcoat")->second; + + // clearcoatFactor: The clearcoat layer intensity. default:0.0 + float coat = 0.f; + if (params.Has("clearcoatFactor")) + coat = (float)params.Get("clearcoatFactor").Get(); + ospMat->createChild("coat", "float") = coat; + ospMat->createChild("coatThickness", "float") = 1.f; // (not in spec) + + // clearcoatRoughnessFactor: The clearcoat layer roughness. + // default:0.0 + float coatRoughness = 0.f; + if (params.Has("clearcoatRoughnessFactor")) + coatRoughness = + (float)params.Get("clearcoatRoughnessFactor").Get(); + ospMat->createChild("coatRoughness", "float") = coatRoughness; + + // clearcoatTexture: The clearcoat layer intensity texture. + // clearcoatRoughnessTexture: The clearcoat layer roughness texture. +#if 1 + // XXX textures aren't simply RGB!!! + // clearcoat = clearcoatFactor * clearcoatTexture.r + // clearcoatRoughness = clearcoatRoughnessFactor * + // clearcoatRoughnessTexture.g + if (params.Has("clearcoatTexture")) { + // Red channel + setOSPTexture(ospMat, + "coat", + params.Get("clearcoatTexture").Get("index").Get(), + params.Get("clearcoatTexture") + .Get("extensions") + .Get(), + 0); } + if (params.Has("clearcoatRoughnessTexture")) { + // Green channel + setOSPTexture(ospMat, + "coatRoughness", + params.Get("clearcoatRoughnessTexture").Get("index").Get(), + params.Get("clearcoatRoughnessTexture") + .Get("extensions") + .Get(), + 1); + } +#endif - // KHR_materials_transmission - if (exts.find("KHR_materials_transmission") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_transmission - auto params = exts.find("KHR_materials_transmission")->second; - - // (this extension deals exclusively with infinitely thin surfaces) - ospMat->createChild("thin", "bool") = true; - ospMat->createChild("transmissionDepth", "float") = 1.f; // OSPRay xtra - - // transmissionFactor: The base percentage of light that is transmitted - // through the surface. Default:0.0 - float transmission = 0.f; - if (params.Has("transmissionFactor")) - transmission = (float)params.Get("transmissionFactor").Get(); - ospMat->createChild("transmission", "float") = transmission; - - rgb tinting = rgb(pbr.baseColorFactor[0], - pbr.baseColorFactor[1], - pbr.baseColorFactor[2]); - ospMat->createChild("transmissionColor", "rgb") = tinting; - - // Use the baseColorTexture to also tint the transmissionColor - // otherwise, any texture is just a surface color. - // (allows for experiments with thickness (thin = false). - if (pbr.baseColorTexture.index != -1 - && pbr.baseColorTexture.texCoord == 0) { - // Used as a color texture, must be sRGB space, not linear - setOSPTexture(ospMat, - "transmissionColor", - pbr.baseColorTexture.index, - pbr.baseColorTexture.extensions, - false); - } - - // transmissionTexture: A texture that defines the transmission - // percentage of the surface, stored in the R channel. This will be - // multiplied by transmissionFactor. - if (params.Has("transmissionTexture")) { - setOSPTexture(ospMat, - "transmission", - params.Get("transmissionTexture").Get("index").Get(), - params.Get("transmissionTexture") - .Get("extensions") - .Get(), - 0); - } + // clearcoatNormalTexture: The clearcoat normal map texture. + if (params.Has("clearcoatNormalTexture")) { + setOSPTexture(ospMat, + "coatNormal", + params.Get("clearcoatNormalTexture").Get("index").Get(), + params.Get("clearcoatNormalTexture") + .Get("extensions") + .Get()); } + } - // KHR_materials_sheen - if (exts.find("KHR_materials_sheen") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen/ - auto params = exts.find("KHR_materials_sheen")->second; + // KHR_materials_transmission + if (exts.find("KHR_materials_transmission") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_transmission + auto params = exts.find("KHR_materials_transmission")->second; - // sheen weight (not in spec) - ospMat->createChild("sheen", "float") = 1.f; + // (this extension deals exclusively with infinitely thin surfaces) + ospMat->createChild("thin", "bool") = true; + ospMat->createChild("transmissionDepth", "float") = 1.f; // OSPRay xtra - // sheenColorFactor: The sheen color in linear space - // default:[0.0, 0.0, 0.0] - rgb sheen = rgb(0.f); - if (params.Has("sheenColorFactor")) { - std::vector sv = - params.Get("sheenColorFactor").Get(); - sheen = - rgb(sv[0].Get(), sv[1].Get(), sv[2].Get()); - } - ospMat->createChild("sheenColor", "rgb") = sheen; + // transmissionFactor: The base percentage of light that is transmitted + // through the surface. Default:0.0 + float transmission = 0.f; + if (params.Has("transmissionFactor")) + transmission = (float)params.Get("transmissionFactor").Get(); + ospMat->createChild("transmission", "float") = transmission; - // sheenColorTexture: The sheen color (sRGB). - if (params.Has("sheenColorTexture")) { - setOSPTexture(ospMat, - "sheen", - params.Get("sheenColorTexture").Get("index").Get(), - params.Get("sheenColorTexture") - .Get("extensions") - .Get()); - } + rgb tinting = rgb( + pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2]); + ospMat->createChild("transmissionColor", "rgb") = tinting; - // sheenRoughnessFactor: The sheen roughness. default:0.0 - float sheenRoughness = 0.f; - if (params.Has("sheenRoughnessFactor")) { - sheenRoughness = - (float)params.Get("sheenRoughnessFactor").Get(); - } - ospMat->createChild("sheenRoughness", "float") = sheenRoughness; + // Use the baseColorTexture to also tint the transmissionColor + // otherwise, any texture is just a surface color. + // (allows for experiments with thickness (thin = false). + if (pbr.baseColorTexture.index != -1 + && pbr.baseColorTexture.texCoord == 0) { + // Used as a color texture, must be sRGB space, not linear + setOSPTexture(ospMat, + "transmissionColor", + pbr.baseColorTexture.index, + pbr.baseColorTexture.extensions, + false); + } - // sheenRoughnessTexture: The sheen roughness (Alpha) texture. - if (params.Has("sheenRoughnessTexture")) { - setOSPTexture(ospMat, - "sheenRoughness", - params.Get("sheenRoughnessTexture").Get("index").Get(), - params.Get("sheenRoughnessTexture") - .Get("extensions") - .Get(), - 3); - } + // transmissionTexture: A texture that defines the transmission + // percentage of the surface, stored in the R channel. This will be + // multiplied by transmissionFactor. + if (params.Has("transmissionTexture")) { + setOSPTexture(ospMat, + "transmission", + params.Get("transmissionTexture").Get("index").Get(), + params.Get("transmissionTexture") + .Get("extensions") + .Get(), + 0); } + } - // KHR_materials_ior - if (exts.find("KHR_materials_ior") != exts.end()) { - // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_ior - auto params = exts.find("KHR_materials_ior")->second; + // KHR_materials_sheen + if (exts.find("KHR_materials_sheen") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen/ + auto params = exts.find("KHR_materials_sheen")->second; + + // sheen weight (not in spec) + ospMat->createChild("sheen", "float") = 1.f; + + // sheenColorFactor: The sheen color in linear space + // default:[0.0, 0.0, 0.0] + rgb sheen = rgb(0.f); + if (params.Has("sheenColorFactor")) { + std::vector sv = + params.Get("sheenColorFactor").Get(); + sheen = + rgb(sv[0].Get(), sv[1].Get(), sv[2].Get()); + } + ospMat->createChild("sheenColor", "rgb") = sheen; - // ior: The index of refraction. default:1.5 - float ior = 1.5f; - if (params.Has("ior")) { - ior = (float)params.Get("ior").Get(); - } - ospMat->createChild("ior", "float") = ior; + // sheenColorTexture: The sheen color (sRGB). + if (params.Has("sheenColorTexture")) { + setOSPTexture(ospMat, + "sheen", + params.Get("sheenColorTexture").Get("index").Get(), + params.Get("sheenColorTexture") + .Get("extensions") + .Get()); } - // KHR_materials_volume - if (exts.find("KHR_materials_volume") != exts.end()) { - // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume - auto params = exts.find("KHR_materials_volume")->second; + // sheenRoughnessFactor: The sheen roughness. default:0.0 + float sheenRoughness = 0.f; + if (params.Has("sheenRoughnessFactor")) { + sheenRoughness = (float)params.Get("sheenRoughnessFactor").Get(); + } + ospMat->createChild("sheenRoughness", "float") = sheenRoughness; - // thicknessFactor: The thickness of the volume beneath the surface. - // default: 0. - float thickness = 0.f; - if (params.Has("thicknessFactor")) { - thickness = (float)params.Get("thicknessFactor").Get(); - } - ospMat->createChild("thin", "bool") = thickness > 0 ? false : true; - ospMat->createChild("thickness", "float") = thickness; + // sheenRoughnessTexture: The sheen roughness (Alpha) texture. + if (params.Has("sheenRoughnessTexture")) { + setOSPTexture(ospMat, + "sheenRoughness", + params.Get("sheenRoughnessTexture").Get("index").Get(), + params.Get("sheenRoughnessTexture") + .Get("extensions") + .Get(), + 3); + } + } - // thicknessTexture A texture that defines the thickness, - // stored in the G channel. This will be multiplied by thicknessFactor. - // Default: No - if (params.Has("thicknessTexture")) { - setOSPTexture(ospMat, - "thickness", - params.Get("thicknessTexture").Get("index").Get(), - params.Get("thicknessTexture") - .Get("extensions") - .Get(), - 1); - } + // KHR_materials_ior + if (exts.find("KHR_materials_ior") != exts.end()) { + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_ior + auto params = exts.find("KHR_materials_ior")->second; - // attenuationDistance Density of the medium given as the - // average distance that light travels in the medium before interacting - // with a particle. The value is given in world space. - // No, default: +Infinity (inf doesn't behave well with UI sliders) - float attenuationDistance = std::numeric_limits::max(); - if (params.Has("attenuationDistance")) { - attenuationDistance = - (float)params.Get("attenuationDistance").Get(); - } - ospMat->createChild("transmissionDepth", "float") = attenuationDistance; - - // attenuationColor The color that white light turns into due - // to absorption when reaching the attenuation distance. - // No, default: [1, 1, 1] - rgb attenuationColor = rgb(1.f); - if (params.Has("attenuationColor")) { - std::vector ac = - params.Get("attenuationColor").Get(); - attenuationColor = - rgb(ac[0].Get(), ac[1].Get(), ac[2].Get()); - // XXX Setting transmissionColor to default attenuationColor would - // result in overwriting the tinting specified by - // KHR_materials_transmission. Only set transmissionColor if - // attenuationColor is present. - ospMat->createChild("transmissionColor", "rgb") = attenuationColor; - } + // ior: The index of refraction. default:1.5 + float ior = 1.5f; + if (params.Has("ior")) { + ior = (float)params.Get("ior").Get(); } + ospMat->createChild("ior", "float") = ior; + } - return ospMat; - - } else { - // XXX TODO Principled material doesn't have emissive params yet - // So, use a luminous instead - auto ospMat = createNode(matName, "luminous"); - - if (emissiveColor != rgb(0.f)) { - // Material Extensions - const auto &exts = mat.extensions; - - float intensity = 1.f; - - // KHR_materials_emissive_strength - if (exts.find("KHR_materials_emissive_strength") != exts.end()) { - // (Not yet ratified) - // https://github.com/KhronosGroup/glTF/tree/KHR_materials_emissive_strength/extensions/2.0/Khronos/KHR_materials_emissive_strength - auto params = exts.find("KHR_materials_emissive_strength")->second; - - // default: 1.0 - intensity = 1.f; - if (params.Has("emissiveStrength")) { - intensity = (float)params.Get("emissiveStrength").Get(); - } - } + // KHR_materials_volume + if (exts.find("KHR_materials_volume") != exts.end()) { + // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume + auto params = exts.find("KHR_materials_volume")->second; - ospMat->createChild("color", "rgb") = emissiveColor; - ospMat->createChild("intensity", "float") = intensity; - - // Already checked for constant color above. - if (mat.emissiveTexture.index != -1) { - const auto &tex = model.textures[mat.emissiveTexture.index]; - const auto &img = model.images[tex.source]; - if (img.image.size() > 0) { - const auto *data = img.image.data(); - const rgb color0 = rgb(data[0], data[1], data[2]); - WARN << " name: " << img.name << std::endl; - WARN << " img: (" << img.width << ", " << img.height << ")"; - WARN << std::endl; - WARN << " emulating with solid color : " << color0 << std::endl; - ospMat->child("color") = emissiveColor * (color0 / 255.f); - } - } + // thicknessFactor: The thickness of the volume beneath the surface. + // default: 0. + float thickness = 0.f; + if (params.Has("thicknessFactor")) { + thickness = (float)params.Get("thicknessFactor").Get(); } + ospMat->createChild("thin", "bool") = thickness > 0 ? false : true; + ospMat->createChild("thickness", "float") = thickness; -#if 0 // OSPRay Luminous doesn't support textured params yet. - if (mat.emissiveTexture.texCoord != 0) { - WARN << "gltf found TEXCOOR_1 attribute. Not supported...\n" - << std::endl; - } - - if (mat.emissiveTexture.index != -1 && - mat.emissiveTexture.texCoord == 0) { - // EmissiveTextureInfo() : index(-1), texCoord(0) {} - setOSPTexture(ospMat, "color", mat.emissiveTexture, false); - WARN << "Material has emissiveTexture #" << mat.emissiveTexture.index - << "\n"; - } -#endif + // thicknessTexture A texture that defines the thickness, + // stored in the G channel. This will be multiplied by thicknessFactor. + // Default: No + if (params.Has("thicknessTexture")) { + setOSPTexture(ospMat, + "thickness", + params.Get("thicknessTexture").Get("index").Get(), + params.Get("thicknessTexture") + .Get("extensions") + .Get(), + 1); + } - return ospMat; + // attenuationDistance Density of the medium given as the + // average distance that light travels in the medium before interacting + // with a particle. The value is given in world space. + // No, default: +Infinity + float attenuationDistance = std::numeric_limits::max(); + if (params.Has("attenuationDistance")) { + attenuationDistance = + (float)params.Get("attenuationDistance").Get(); + } + ospMat->createChild("transmissionDepth", "float") = attenuationDistance; + + // attenuationColor The color that white light turns into due + // to absorption when reaching the attenuation distance. + // No, default: [1, 1, 1] + rgb attenuationColor = rgb(1.f); + if (params.Has("attenuationColor")) { + std::vector ac = + params.Get("attenuationColor").Get(); + attenuationColor = + rgb(ac[0].Get(), ac[1].Get(), ac[2].Get()); + // XXX Setting transmissionColor to default attenuationColor would + // result in overwriting the tinting specified by + // KHR_materials_transmission. Only set transmissionColor if + // attenuationColor is present. + ospMat->createChild("transmissionColor", "rgb") = attenuationColor; + } } + + return ospMat; } NodePtr GLTFData::createOSPTexture(const std::string &texParam, diff --git a/sg/renderer/materials/Luminous.cpp b/sg/renderer/materials/Luminous.cpp index 318e5002..d1b43ccc 100644 --- a/sg/renderer/materials/Luminous.cpp +++ b/sg/renderer/materials/Luminous.cpp @@ -4,24 +4,24 @@ #include "../Material.h" namespace ospray { - namespace sg { +namespace sg { - struct OSPSG_INTERFACE Luminous : public Material - { - Luminous(); - ~Luminous() override = default; - }; +struct OSPSG_INTERFACE Luminous : public Material +{ + Luminous(); + ~Luminous() override = default; +}; - OSP_REGISTER_SG_NODE_NAME(Luminous, luminous); +OSP_REGISTER_SG_NODE_NAME(Luminous, luminous); - // Luminous definitions ////////////////////////////////////////////////// +// Luminous definitions ////////////////////////////////////////////////// - Luminous::Luminous() : Material("luminous") - { - createChild("color", "rgb", "light emission color", vec3f(1.f)); - createChild("intensity", "float", "light brightness", 1.f); - createChild("transparency", "float", "material transparency", 1.f); - } +Luminous::Luminous() : Material("luminous") +{ + createChild("color", "rgb", "color of the emitted light", vec3f(1.f)); + createChild("intensity", "float", "intensity of the light", 1.f); + createChild("transparency", "float", "material transparency", 1.f); +} - } // namespace sg +} // namespace sg } // namespace ospray diff --git a/sg/renderer/materials/Principled.cpp b/sg/renderer/materials/Principled.cpp index 510b5f51..f0b8f053 100644 --- a/sg/renderer/materials/Principled.cpp +++ b/sg/renderer/materials/Principled.cpp @@ -4,53 +4,86 @@ #include "../Material.h" namespace ospray { - namespace sg { - - struct OSPSG_INTERFACE Principled : public Material - { - Principled(); - ~Principled() override = default; - }; - - OSP_REGISTER_SG_NODE_NAME(Principled, principled); - - // Principled definitions ////////////////////////////////////////////////// - - Principled::Principled() : Material("principled") - { - createChild("baseColor", "rgb", "diffuse reflectivity (or metal color)", - vec3f(0.8)); - createChild("edgeColor", "rgb", "grazing edge tint (metallic only)", - vec3f(1.f)); - createChild("metallic", "float", - "mix between dieletric (diffuse+specular) and metallic (specular only) [0-1]", - 0.f) - .setMinMax(0.f, 1.f); - createChild("diffuse", "float", "diffuse reflection weight [0-1]", 1.f).setMinMax(0.f, 1.f); - createChild("specular", "float", "specular reflection weight [0-1]", 1.f).setMinMax(0.f, 1.f); - createChild("ior", "float", "index of refraction", 1.f); - createChild("transmission", "float", "light tranmissivity [0-1]", 0.f).setMinMax(0.f, 1.f); - createChild("transmissionColor", "rgb", "attenuated color in transmission", vec3f(1.f)); - createChild("transmissionDepth", "float", "distance until transmissionColor", 1.f); - createChild("roughness", "float", "surface roughness [0-1] (0 is smooth)", 0.f).setMinMax(0.f, 1.f); - createChild("anisotropy", "float", "amount of specular anisotropy [0-1]", 0.f).setMinMax(0.f, 1.f); - createChild("rotation", "float", "anisotropy rotation [0-1]", 0.f).setMinMax(0.f, 1.f); - createChild("normal", "float", "normal map/scale for all layers [0-1]", 1.f).setMinMax(0.f, 1.f); - createChild("thin", "bool", "whether material is thin or solid", false); - createChild("thickness", "float", "with thin, amount of attenuation", 1.f); - createChild("backlight", "float", "with thin, amount of diffuse transmission [0-2] (1 is 50/50 reflection/transmission)", 0.f).setMinMax(0.f, 2.f); - createChild("coat", "float", "clearcoat layer weight [0-1]", 0.f).setMinMax(0.f, 1.f); - createChild("coatIor", "float", "clearcoat layer index of refraction", 1.5f); - createChild("coatColor", "rgb", "clearcoat tint", vec3f(1.f)); - createChild("coatThickness", "float", "clearcoat attenuation amount", 1.f); - createChild("coatRoughness", "float", "clearcoat roughness [0-1] (0 is smooth)", 0.f).setMinMax(0.f, 1.f); - createChild("coatNormal", "float", "clearcoat normal [0-1]", 1.f).setMinMax(0.f, 1.f); - createChild("sheen", "float", "sheen layer weight [0-1] (emulates velvet)", 0.f).setMinMax(0.f, 1.f); - createChild("sheenColor", "rgb", "sheen color", vec3f(1.f)); - createChild("sheenTint", "float", "amount of tint from sheenColor to baseColor", 0.f); - createChild("sheenRoughness", "float", "sheen roughness [0-1] (0 is smooth)", 0.2f).setMinMax(0.f, 1.f); - createChild("opacity", "float", "alpha cut-out transparency [0-1] (0 is transparent)", 1.f).setMinMax(0.f, 1.f); - } - - } // namespace sg +namespace sg { + +struct OSPSG_INTERFACE Principled : public Material +{ + Principled(); + ~Principled() override = default; +}; + +OSP_REGISTER_SG_NODE_NAME(Principled, principled); + +// Principled definitions ////////////////////////////////////////////////// + +Principled::Principled() : Material("principled") +{ + createChild( + "baseColor", "rgb", "diffuse reflectivity (or metal color)", vec3f(0.8)); + createChild( + "edgeColor", "rgb", "grazing edge tint (metallic only)", vec3f(1.f)); + createChild("metallic", + "float", + "mix between dieletric (diffuse+specular) and metallic (specular only) [0-1]", + 0.f) + .setMinMax(0.f, 1.f); + createChild("diffuse", "float", "diffuse reflection weight [0-1]", 1.f) + .setMinMax(0.f, 1.f); + createChild("specular", "float", "specular reflection weight [0-1]", 1.f) + .setMinMax(0.f, 1.f); + createChild("ior", "float", "index of refraction", 1.f); + createChild("transmission", "float", "light tranmissivity [0-1]", 0.f) + .setMinMax(0.f, 1.f); + createChild("transmissionColor", + "rgb", + "attenuated color in transmission", + vec3f(1.f)); + createChild( + "transmissionDepth", "float", "distance until transmissionColor", 1.f); + createChild( + "roughness", "float", "surface roughness [0-1] (0 is smooth)", 0.f) + .setMinMax(0.f, 1.f); + createChild("anisotropy", "float", "amount of specular anisotropy [0-1]", 0.f) + .setMinMax(0.f, 1.f); + createChild("rotation", "float", "anisotropy rotation [0-1]", 0.f) + .setMinMax(0.f, 1.f); + createChild("normal", "float", "normal map/scale for all layers [0-1]", 1.f) + .setMinMax(0.f, 1.f); + createChild("thin", "bool", "whether material is thin or solid", false); + createChild("thickness", "float", "with thin, amount of attenuation", 1.f); + createChild("backlight", + "float", + "with thin, amount of diffuse transmission [0-2] (1 is 50/50 reflection/transmission)", + 0.f) + .setMinMax(0.f, 2.f); + createChild("coat", "float", "clearcoat layer weight [0-1]", 0.f) + .setMinMax(0.f, 1.f); + createChild("coatIor", "float", "clearcoat layer index of refraction", 1.5f); + createChild("coatColor", "rgb", "clearcoat tint", vec3f(1.f)); + createChild("coatThickness", "float", "clearcoat attenuation amount", 1.f); + createChild( + "coatRoughness", "float", "clearcoat roughness [0-1] (0 is smooth)", 0.f) + .setMinMax(0.f, 1.f); + createChild("coatNormal", "float", "clearcoat normal [0-1]", 1.f) + .setMinMax(0.f, 1.f); + createChild( + "sheen", "float", "sheen layer weight [0-1] (emulates velvet)", 0.f) + .setMinMax(0.f, 1.f); + createChild("sheenColor", "rgb", "sheen color", vec3f(1.f)); + createChild( + "sheenTint", "float", "amount of tint from sheenColor to baseColor", 0.f); + createChild( + "sheenRoughness", "float", "sheen roughness [0-1] (0 is smooth)", 0.2f) + .setMinMax(0.f, 1.f); + createChild("opacity", + "float", + "alpha cut-out transparency [0-1] (0 is transparent)", + 1.f) + .setMinMax(0.f, 1.f); + + createChild("emissiveColor", "rgb", "emissive color", vec3f(0.f)); + createChild("intensity", "float", "emissive intensity", 1.f); +} + +} // namespace sg } // namespace ospray From dee924910c458889766c5a3708b8c907cfa67da0 Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Fri, 26 Jan 2024 14:02:31 -0600 Subject: [PATCH 07/26] For Windows builds, add dependent load for dll injection security --- CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 819a3490..96fb5c12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,20 @@ else() set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CONFIGURATION_TYPES}) endif() +## Add dependent load for Windows dll injection load security +if (WIN32) + get_filename_component(COMPILER_NAME ${CMAKE_CXX_COMPILER} NAME_WE) + if (COMPILER_NAME STREQUAL "icx" OR COMPILER_NAME STREQUAL "icpx") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /Qoption,link,/DEPENDENTLOADFLAG:0x2000") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /Qoption,link,/DEPENDENTLOADFLAG:0x2000") + elseif (MSVC) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEPENDENTLOADFLAG:0x2000") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEPENDENTLOADFLAG:0x2000") + else() + message(WARNING "Urecognized compiler, DEPENDENTLOADFLAG can't be set") + endif() +endif() + ## global options and variables option(OSPRAY_INSTALL "Install OSPRay libraries in addition to OSPRay Studio libraries / binaries" ON) set(OSPRAY_STUDIO_RESOURCE_FILE "${PROJECT_SOURCE_DIR}/resources/ospray_studio.rc") From d8fcb0f70d49fd8f2e42bf5f6055c2e6f7385145 Mon Sep 17 00:00:00 2001 From: Bruce Cherniak Date: Mon, 5 Feb 2024 18:35:45 -0600 Subject: [PATCH 08/26] Update 3rd party dependencies --- CHANGELOG.md | 19 + CMakeLists.txt | 2 +- README.md | 2 +- app/WindowsBuilder.h | 11 +- app/ospStudio.cpp | 2 +- app/widgets/FileBrowserWidget.cpp | 18 +- cmake/draco.cmake | 8 +- cmake/pybind11.cmake | 2 +- cmake/tbb.cmake | 2 +- external/cli11/CLI11.hpp | 1928 +++- external/dirent/dirent.h | 45 +- external/imgui/imconfig.h | 31 +- external/imgui/imgui.cpp | 4615 +++++--- external/imgui/imgui.h | 954 +- external/imgui/imgui_demo.cpp | 1202 +- external/imgui/imgui_draw.cpp | 332 +- external/imgui/imgui_internal.h | 916 +- external/imgui/imgui_tables.cpp | 579 +- external/imgui/imgui_widgets.cpp | 1186 +- external/imgui/imstb_textedit.h | 148 +- external/imguiFileDialog/CMakeLists.txt | 3 +- external/imguiFileDialog/ImGuiFileDialog.cpp | 9790 ++++++++--------- external/imguiFileDialog/ImGuiFileDialog.h | 2880 +++-- .../imguiFileDialog/ImGuiFileDialogConfig.h | 20 +- external/json/README.md | 2 +- external/json/json.hpp | 1547 +-- external/stb_image/stb_image.h | 348 +- external/tiny_dng/tiny_dng_loader.h | 211 +- external/tiny_exr/tinyexr.h | 127 +- external/tiny_gltf/tiny_gltf.h | 2294 ++-- external/tiny_obj_loader/tiny_obj_loader.h | 188 +- sg/importer/glTF.cpp | 13 +- 32 files changed, 17230 insertions(+), 12195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9ca72d..ed6dc1c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ Version History --------------- +### Changes in OSPRay Studio v1.0.0 + +- Update 3rd party dependencies + - Implicitly included in this repo: + - CLI11 v2.4.0 (github.com/CLIUtils/CLI11) + - dear imgui v1.90.2 WIP (docking branch) (github.com/ocornut/imgui) + - dirent v1.24 (github.com/tronkko/dirent) + - ImGuiFileDialog v0.6.6.1 (github.com/aiekick/ImGuiFileDialog) + - imGuIZMO.quat v3.0 (github.com/BrutPitt/imGuIZMO.quat) + - JSON for Modern C++ 3.11.3 (github.com/nlohmann/json) + - stb_image v2.29 (github.com/nothings/stb) + - tinydng v0.1.0 (github.com/syoyo/tinydng) + - tinyexr v1.0.7 (github.com/syoyo/tinyexr) + - tinygltf v2.8.20 (github.com/syoyo/tinygltf) + - tinyobjloader v2.0.0rc13 (github.com/tinyobjloader/tinyobjloader) + - via FetchContent: + - pybind11 v2.11.1 (https://github.com/pybind/pybind11) + - draco v1.5.7 (https://github.com/google/draco) + ### Changes in OSPRay Studio v0.13.0 - Compatible with OSPRay release v3.0.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 96fb5c12..cca5a1e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15) -project(ospray_studio VERSION 0.13.0 LANGUAGES CXX C) +project(ospray_studio VERSION 1.0.0 LANGUAGES CXX C) include(GNUInstallDirs) include(ProcessorCount) diff --git a/README.md b/README.md index 5bc08c33..ee486f22 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OSPRay Studio -This is release v0.13.0 of Intel® OSPRay Studio. It is released under the +This is release v1.0.0 of Intel® OSPRay Studio. It is released under the Apache 2.0 license. Visit [**OSPRay Studio**](http://www.ospray.org/ospray_studio) diff --git a/app/WindowsBuilder.h b/app/WindowsBuilder.h index 1ca95c30..b29f2574 100644 --- a/app/WindowsBuilder.h +++ b/app/WindowsBuilder.h @@ -486,7 +486,7 @@ void WindowsBuilder::buildWindowKeyframes() } } - if (ImGui::ListBoxHeader("##")) { + if (ImGui::BeginListBox("##")) { for (int i = 0; i < cameraStack->size(); i++) { if (ImGui::Selectable( (std::to_string(i) + ": " + to_string(cameraStack->at(i))).c_str(), @@ -496,7 +496,7 @@ void WindowsBuilder::buildWindowKeyframes() ctx->updateCamera(); } } - ImGui::ListBoxFooter(); + ImGui::EndListBox(); } ImGui::End(); @@ -805,7 +805,10 @@ void WindowsBuilder::buildWindowTransferFunctionEditor() range1f(0.f, 1.f), "TransferFunctionEditor"); - if (ImGui::ListBoxHeader("##transferFunction", transferFunctions.size())) { + if (ImGui::BeginListBox("##transferFunction", + ImVec2(-FLT_MIN, + ImGui::GetTextLineHeightWithSpacing() + * transferFunctions.size()))) { int i = 0; for (auto t : transferFunctions) { if (ImGui::Selectable(t.first.c_str(), (whichTFn == i))) { @@ -834,7 +837,7 @@ void WindowsBuilder::buildWindowTransferFunctionEditor() } i++; } - ImGui::ListBoxFooter(); + ImGui::EndListBox(); ImGui::Separator(); if (whichTFn != -1) { diff --git a/app/ospStudio.cpp b/app/ospStudio.cpp index bc5c385a..ccdc7dd7 100644 --- a/app/ospStudio.cpp +++ b/app/ospStudio.cpp @@ -66,7 +66,7 @@ void StudioContext::addToCommandLine(std::shared_ptr app) "The list of files to import" ); app->add_option( - "reload assets", + "--reload", optReloadAssets, "reload asset file contents, rather than creating an instance" ); diff --git a/app/widgets/FileBrowserWidget.cpp b/app/widgets/FileBrowserWidget.cpp index 0487d9e5..9933beac 100644 --- a/app/widgets/FileBrowserWidget.cpp +++ b/app/widgets/FileBrowserWidget.cpp @@ -29,17 +29,15 @@ bool fileBrowser(FileList &fileList, ImVec2 maxSize = ImGui::GetIO().DisplaySize; ImVec2 minSize(maxSize.x * 0.5, maxSize.y * 0.5); - auto fd = ImGuiFileDialog::Instance(); + // Allow multiple selections if requested (pass 0 as the countSelectionMax) + IGFD::FileDialogConfig fdConfig{}; + fdConfig.path = defaultPath; + fdConfig.fileName = ""; + fdConfig.countSelectionMax = allowMultipleSelection ? 0 : 1; + fdConfig.flags = ImGuiFileDialogFlags_Modal; - // Allow multiple selections if requested (pass 0 as the vCountSelectionMax) - fd->OpenDialog(prompt.c_str(), - prompt.c_str(), - filters.c_str(), - defaultPath, - "", - allowMultipleSelection ? 0 : 1, - nullptr, - ImGuiFileDialogFlags_Modal); + auto fd = ImGuiFileDialog::Instance(); + fd->OpenDialog(prompt.c_str(), prompt.c_str(), filters.c_str(), fdConfig); if (fd->Display( prompt.c_str(), ImGuiWindowFlags_NoCollapse, minSize, maxSize)) { diff --git a/cmake/draco.cmake b/cmake/draco.cmake index 1ade66aa..8d6bc67b 100644 --- a/cmake/draco.cmake +++ b/cmake/draco.cmake @@ -6,7 +6,7 @@ ## if(NOT DEFINED DRACO_VERSION) - set(DRACO_VERSION 1.5.6) + set(DRACO_VERSION 1.5.7) endif() ## @@ -27,7 +27,7 @@ if(NOT "${draco_FOUND}") # Hide advanced options from main CMake list mark_as_advanced(DRACO_FAST) - mark_as_advanced(DRACO_GLTF) + mark_as_advanced(DRACO_GLTF_BITSTREAM) mark_as_advanced(DRACO_ANIMATION_ENCODING) mark_as_advanced(DRACO_BACKWARDS_COMPATIBILITY) mark_as_advanced(DRACO_DECODER_ATTRIBUTE_DEDUPLICATION) @@ -45,10 +45,10 @@ if(NOT "${draco_FOUND}") mark_as_advanced(DRACO_DEBUG_GLTF_BITSTREAM) mark_as_advanced(DRACO_TRANSCODER_SUPPORTED) - # draco needs to be build position independent to link with ospray_sg + # draco needs to be built position independent to link with ospray_sg set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(DRACO_FAST ON) - set(DRACO_GLTF ON) + set(DRACO_GLTF_BITSTREAM ON) set(DRACO_ANIMATION_ENCODING ON) set(DRACO_JS_GLUE OFF) diff --git a/cmake/pybind11.cmake b/cmake/pybind11.cmake index abdcce3d..68b4ff9f 100644 --- a/cmake/pybind11.cmake +++ b/cmake/pybind11.cmake @@ -6,7 +6,7 @@ ## if(NOT DEFINED PYBIND11_VERSION) - set(PYBIND11_VERSION 2.10.3) + set(PYBIND11_VERSION 2.11.1) endif() ## diff --git a/cmake/tbb.cmake b/cmake/tbb.cmake index 1270ed10..bd6d063e 100644 --- a/cmake/tbb.cmake +++ b/cmake/tbb.cmake @@ -6,7 +6,7 @@ if(TBB_FOUND) endif() if(NOT DEFINED TBB_VERSION) - set(TBB_VERSION 2021.10.0) + set(TBB_VERSION 2021.11.0) endif() option(FORCE_TBB_VERSION "Force CMake to find ${TBB_VERSION} (typically for compatibility with pre-built OSPRay)" OFF) diff --git a/external/cli11/CLI11.hpp b/external/cli11/CLI11.hpp index 3913fa9c..02b9e0b6 100644 --- a/external/cli11/CLI11.hpp +++ b/external/cli11/CLI11.hpp @@ -1,11 +1,11 @@ -// CLI11: Version 2.3.2 +// CLI11: Version 2.4.0 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v2.3.2 +// from: v2.4.0 // -// CLI11 2.3.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +// CLI11 2.4.0 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without @@ -34,34 +34,40 @@ #pragma once // Standard combined includes: -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include -#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include #define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 3 -#define CLI11_VERSION_PATCH 2 -#define CLI11_VERSION "2.3.2" +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.4.0" @@ -124,17 +130,7 @@ #endif #endif -/** Inline macro **/ -#ifdef CLI11_COMPILE -#define CLI11_INLINE -#else -#define CLI11_INLINE inline -#endif - - - -// C standard library -// Only needed for existence checking +/** availability */ #if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM #if __has_include() // Filesystem cannot be used if targeting macOS < 10.15 @@ -161,6 +157,44 @@ #endif #endif +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #include // NOLINT(build/include) #else @@ -170,9 +204,244 @@ + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + namespace CLI { +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + /// Include the items in this namespace to get free conversion of enums to/from streams. /// (This is available inside CLI as well, so CLI11 will use this without a using statement). namespace enums { @@ -270,6 +539,9 @@ inline std::string trim_copy(const std::string &str) { /// remove quotes at the front and back of a string either '"' or '\'' CLI11_INLINE std::string &remove_quotes(std::string &str); +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + /// Add a leader to the beginning of all new lines (nothing is added /// at the start of the first line). `"; "` would be for ini files /// @@ -290,14 +562,16 @@ CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); } +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} /// Verify following characters of an option template bool valid_later_char(T c) { // = and : are value separators, { has special meaning for option defaults, - // and \n would just be annoying to deal with in many places allowing space here has too much potential for - // inadvertent entry errors and bugs - return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n'); + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); } /// Verify an option/subcommand name @@ -360,18 +634,46 @@ template inline std::string find_and_modify(std::string str, return str; } +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + /// Split a string '"one two" "three"' into 'one two', 'three' -/// Quote characters can be ` ' or " +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + /// This function detects an equal or colon followed by an escaped quote after an argument /// then modifies the string to replace the equality with a space. This is needed /// to allow the split up function to work properly and is intended to be used with the find_and_modify function /// the return value is the offset+1 which is required by the find_and_modify function. CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); -/// Add quotes if the string contains spaces -CLI11_INLINE std::string &add_quotes_if_needed(std::string &str); +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); } // namespace detail @@ -421,7 +723,17 @@ CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { } CLI11_INLINE std::string &remove_quotes(std::string &str) { - if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { if(str.front() == str.back()) { str.pop_back(); str.erase(str.begin(), str.begin() + 1); @@ -541,37 +853,220 @@ find_member(std::string name, const std::vector names, bool ignore_ return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + CLI11_INLINE std::vector split_up(std::string str, char delimiter) { - const std::string delims("\'\"`"); auto find_ws = [delimiter](char ch) { return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); }; trim(str); std::vector output; - bool embeddedQuote = false; - char keyChar = ' '; while(!str.empty()) { - if(delims.find_first_of(str[0]) != std::string::npos) { - keyChar = str[0]; - auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes - end = str.find_first_of(keyChar, end + 1); - embeddedQuote = true; - } - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); if(end + 2 < str.size()) { str = str.substr(end + 2); } else { str.clear(); } - - } else { - output.push_back(str.substr(1)); - str = ""; } + } else { auto it = std::find_if(std::begin(str), std::end(str), find_ws); if(it != std::end(str)) { @@ -580,14 +1075,9 @@ CLI11_INLINE std::vector split_up(std::string str, char delimiter) str = std::string(it + 1, str.end()); } else { output.push_back(str); - str = ""; + str.clear(); } } - // transform any embedded quotes into the regular character - if(embeddedQuote) { - output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); - embeddedQuote = false; - } trim(str); } return output; @@ -605,15 +1095,140 @@ CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { return offset + 1; } -CLI11_INLINE std::string &add_quotes_if_needed(std::string &str) { - if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { - char quote = str.find('"') < str.find('\'') ? '\'' : '"'; - if(str.find(' ') != std::string::npos) { - str.insert(0, 1, quote); - str.append(1, quote); +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); } } - return str; + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; } } // namespace detail @@ -722,7 +1337,13 @@ class BadNameString : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } static BadNameString DashesOnly(std::string name) { return BadNameString("Must have a name, not just dashes: " + name); } @@ -1087,11 +1708,19 @@ template class is_direct_constructible { static auto test(int, std::true_type) -> decltype( // NVCC warns about narrowing conversions here #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else #pragma diag_suppress 2361 +#endif #endif TT{std::declval()} #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else #pragma diag_default 2361 +#endif #endif , std::is_move_assignable()); @@ -1171,8 +1800,10 @@ struct is_mutable_container< decltype(std::declval().clear()), decltype(std::declval().insert(std::declval().end())>(), std::declval()))>, - void>> - : public conditional_t::value, std::false_type, std::true_type> {}; + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; // check to see if an object is a mutable container (fail by default) template struct is_readable_container : std::false_type {}; @@ -1475,6 +2106,8 @@ enum class object_category : int { // string like types string_assignable = 23, string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, other = 45, // special wrapper or container types wrapper_value = 50, @@ -1523,12 +2156,23 @@ template struct classify_object struct classify_object::value>::type> { static constexpr object_category value{object_category::floating_point}; }; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif /// String and similar direct assignment template -struct classify_object::value && !std::is_integral::value && - std::is_assignable::value>::type> { +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { static constexpr object_category value{object_category::string_assignable}; }; @@ -1538,10 +2182,27 @@ struct classify_object< T, typename std::enable_if::value && !std::is_integral::value && !std::is_assignable::value && (type_count::value == 1) && - std::is_constructible::value>::type> { + WIDE_STRING_CHECK && std::is_constructible::value>::type> { static constexpr object_category value{object_category::string_constructible}; }; +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + /// Enumerations template struct classify_object::value>::type> { static constexpr object_category value{object_category::enumeration}; @@ -1554,12 +2215,13 @@ template struct classify_object struct uncommon_type { - using type = typename std::conditional::value && !std::is_integral::value && - !std::is_assignable::value && - !std::is_constructible::value && !is_complex::value && - !is_mutable_container::value && !std::is_enum::value, - std::true_type, - std::false_type>::type; + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; static constexpr bool value = type::value; }; @@ -1748,7 +2410,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept { if(input.empty() || input.front() == '-') { return false; } - char *val = nullptr; + char *val{nullptr}; errno = 0; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); if(errno == ERANGE) { @@ -1764,6 +2426,33 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); return (static_cast(output) == output_sll); } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } return false; } @@ -1788,11 +2477,38 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(1); return true; } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } return false; } -/// Convert a flag into an integer value typically binary flags -inline std::int64_t to_flag_value(std::string val) { +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { @@ -1820,7 +2536,8 @@ inline std::int64_t to_flag_value(std::string val) { ret = 1; break; default: - throw std::invalid_argument("unrecognized character"); + errno = EINVAL; + return -1; } return ret; } @@ -1829,7 +2546,11 @@ inline std::int64_t to_flag_value(std::string val) { } else if(val == falseString || val == "off" || val == "no" || val == "disable") { ret = -1; } else { - ret = std::stoll(val); + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } } return ret; } @@ -1858,18 +2579,16 @@ bool lexical_cast(const std::string &input, T &output) { template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - auto out = to_flag_value(input); + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { output = (out > 0); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still - // valid all we care about the sign + } else if(errno == ERANGE) { output = (input[0] != '-'); - return true; + } else { + return false; } + return true; } /// Floats @@ -1882,7 +2601,17 @@ bool lexical_cast(const std::string &input, T &output) { char *val = nullptr; auto output_ld = std::strtold(input.c_str(), &val); output = static_cast(output_ld); - return val == (input.c_str() + input.size()); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; } /// complex @@ -1934,6 +2663,23 @@ bool lexical_cast(const std::string &input, T &output) { return true; } +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + /// Enumerations template ::value == object_category::enumeration, detail::enabler> = detail::dummy> @@ -2062,7 +2808,9 @@ template ::value && (classify_object::value == object_category::string_assignable || - classify_object::value == object_category::string_constructible), + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { return lexical_cast(input, output); @@ -2073,7 +2821,9 @@ template ::value && std::is_assignable::value && classify_object::value != object_category::string_assignable && - classify_object::value != object_category::string_constructible, + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { if(input.empty()) { @@ -2112,9 +2862,17 @@ bool lexical_assign(const std::string &input, AssignTo &output) { output = 0; return true; } - int val = 0; + int val{0}; if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif return true; } return false; @@ -2169,12 +2927,12 @@ template = detail::dummy> bool lexical_conversion(const std::vector &strings, AssignTo &output) { // the remove const is to handle pair types coming from a container - typename std::remove_const::type>::type v1; - typename std::tuple_element<1, ConvertTo>::type v2; - bool retval = lexical_assign(strings[0], v1); - if(strings.size() > 1) { - retval = retval && lexical_assign(strings[1], v2); - } + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); if(retval) { output = AssignTo{v1, v2}; } @@ -2189,6 +2947,9 @@ template = detail::dummy> bool lexical_conversion(const std::vector &strings, AssignTo &output) { output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } if(strings.size() == 1 && strings[0] == "{}") { return true; } @@ -2333,7 +3094,7 @@ tuple_type_conversion(std::vector &strings, AssignTo &output) { std::size_t index{subtype_count_min::value}; const std::size_t mx_count{subtype_count::value}; - const std::size_t mx{(std::max)(mx_count, strings.size())}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; while(index < mx) { if(is_separator(strings[index])) { @@ -2343,7 +3104,11 @@ tuple_type_conversion(std::vector &strings, AssignTo &output) { } bool retval = lexical_conversion( std::vector(strings.begin(), strings.begin() + static_cast(index)), output); - strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } return retval; } @@ -2487,12 +3252,13 @@ inline std::string sum_string_vector(const std::vector &values) { double tv{0.0}; auto comp = lexical_cast(arg, tv); if(!comp) { - try { - tv = static_cast(detail::to_flag_value(arg)); - } catch(const std::exception &) { - fail = true; + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { break; } + tv = static_cast(fv); } val += tv; } @@ -2501,13 +3267,10 @@ inline std::string sum_string_vector(const std::vector &values) { output.append(arg); } } else { - if(val <= static_cast((std::numeric_limits::min)()) || - val >= static_cast((std::numeric_limits::max)()) || - std::ceil(val) == std::floor(val)) { - output = detail::value_string(static_cast(val)); - } else { - output = detail::value_string(val); - } + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); } return output; } @@ -2553,7 +3316,7 @@ CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std } CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { - if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); @@ -2625,7 +3388,6 @@ get_names(const std::vector &input) { std::vector short_names; std::vector long_names; std::string pos_name; - for(std::string name : input) { if(name.length() == 0) { continue; @@ -2633,6 +3395,8 @@ get_names(const std::vector &input) { if(name.length() > 1 && name[0] == '-' && name[1] != '-') { if(name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); else throw BadNameString::OneCharName(name); } else if(name.length() > 2 && name.substr(0, 2) == "--") { @@ -2644,12 +3408,15 @@ get_names(const std::vector &input) { } else if(name == "-" || name == "--") { throw BadNameString::DashesOnly(name); } else { - if(pos_name.length() > 0) + if(!pos_name.empty()) throw BadNameString::MultiPositionalNames(name); - pos_name = name; + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } } } - return std::make_tuple(short_names, long_names, pos_name); } @@ -2666,7 +3433,6 @@ struct ConfigItem { /// This is the name std::string name{}; - /// Listing of inputs std::vector inputs{}; @@ -2729,8 +3495,8 @@ class ConfigBase : public Config { char valueDelimiter = '='; /// the character to use around strings char stringQuote = '"'; - /// the character to use around single characters - char characterQuote = '\''; + /// the character to use around single characters and literal strings + char literalQuote = '\''; /// the maximum number of layers to allow uint8_t maximumLayers{255}; /// the separator used to separator parent layers @@ -2766,10 +3532,10 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } - /// Specify the quote characters used around strings and characters - ConfigBase *quoteCharacter(char qString, char qChar) { + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { stringQuote = qString; - characterQuote = qChar; + literalQuote = literalChar; return this; } /// Specify the maximum number of parents @@ -3001,6 +3767,11 @@ class IPV4Validator : public Validator { IPV4Validator(); }; +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + } // namespace detail // Static is not needed here, because global const implies static. @@ -3020,6 +3791,9 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + /// Validate the input as a particular type template class TypeValidator : public Validator { public: @@ -3772,14 +4546,14 @@ namespace detail { #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 CLI11_INLINE path_type check_path(const char *file) noexcept { std::error_code ec; - auto stat = std::filesystem::status(file, ec); + auto stat = std::filesystem::status(to_path(file), ec); if(ec) { return path_type::nonexistent; } switch(stat.type()) { case std::filesystem::file_type::none: // LCOV_EXCL_LINE case std::filesystem::file_type::not_found: - return path_type::nonexistent; + return path_type::nonexistent; // LCOV_EXCL_LINE case std::filesystem::file_type::directory: return path_type::directory; case std::filesystem::file_type::symlink: @@ -3873,10 +4647,29 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { return std::string("Each IP number must be between 0 and 255 ") + var; } } - return std::string(); + return std::string{}; }; } +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} } // namespace detail CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) @@ -4175,7 +4968,8 @@ enum class MultiOptionPolicy : char { TakeFirst, //!< take only the first Expected number of arguments Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') TakeAll, //!< just get all the passed argument regardless - Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter + Sum, //!< sum all the arguments together if numerical or concatenate directly without delimiter + Reverse, //!< take only the last Expected number of arguments in reverse order }; /// This is the CRTP base class for Option and OptionDefaults. It was designed this way @@ -4683,12 +5477,12 @@ class Option : public OptionBase