From 8c0163bcb48df56112a625550d411741c20c5846 Mon Sep 17 00:00:00 2001
From: Dave Houlton <euclid@lindenlab.com>
Date: Wed, 13 Apr 2022 12:32:58 -0600
Subject: SL-17214 initial loader class skeleton

---
 indra/llmath/v4color.h                  |  12 +-
 indra/llprimitive/CMakeLists.txt        |   2 +
 indra/llprimitive/lldaeloader.cpp       |  26 +--
 indra/llprimitive/lldaeloader.h         |   2 +-
 indra/llprimitive/llgltfloader.cpp      | 336 ++++++++++++++++++++++++++++++++
 indra/llprimitive/llgltfloader.h        | 207 ++++++++++++++++++++
 indra/llprimitive/llmodelloader.cpp     |  13 +-
 indra/newview/llfilepicker.cpp          |  54 +++--
 indra/newview/llfilepicker.h            |   8 +-
 indra/newview/llfloatermodelpreview.cpp |   2 +-
 indra/newview/llmodelpreview.cpp        |  50 +++--
 indra/newview/llviewermenu.cpp          |   5 -
 indra/newview/llviewermenufile.cpp      |   7 -
 13 files changed, 640 insertions(+), 84 deletions(-)
 create mode 100644 indra/llprimitive/llgltfloader.cpp
 create mode 100644 indra/llprimitive/llgltfloader.h

(limited to 'indra')

diff --git a/indra/llmath/v4color.h b/indra/llmath/v4color.h
index 175edf1471..f2863be531 100644
--- a/indra/llmath/v4color.h
+++ b/indra/llmath/v4color.h
@@ -88,7 +88,8 @@ class LLColor4
 		const LLColor4&	set(const LLColor3 &vec);	// Sets LLColor4 to LLColor3 vec (no change in alpha)
 		const LLColor4&	set(const LLColor3 &vec, F32 a);	// Sets LLColor4 to LLColor3 vec, with alpha specified
 		const LLColor4&	set(const F32 *vec);			// Sets LLColor4 to vec
-		const LLColor4&	set(const LLColor4U& color4u); // Sets LLColor4 to color4u, rescaled.
+        const LLColor4&	set(const F64 *vec);			// Sets LLColor4 to (double)vec
+        const LLColor4&	set(const LLColor4U& color4u); // Sets LLColor4 to color4u, rescaled.
 
 
 		const LLColor4&    setAlpha(F32 a);
@@ -334,6 +335,15 @@ inline const LLColor4&	LLColor4::set(const F32 *vec)
 	return (*this);
 }
 
+inline const LLColor4&	LLColor4::set(const F64 *vec)
+{
+    mV[VX] = static_cast<F32>(vec[VX]);
+    mV[VY] = static_cast<F32>(vec[VY]);
+    mV[VZ] = static_cast<F32>(vec[VZ]);
+    mV[VW] = static_cast<F32>(vec[VW]);
+    return (*this);
+}
+
 // deprecated
 inline const LLColor4&	LLColor4::setVec(F32 x, F32 y, F32 z)
 {
diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index fff4d8ef0a..9d75dab31e 100644
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -28,6 +28,7 @@ include_directories(SYSTEM
 
 set(llprimitive_SOURCE_FILES
     lldaeloader.cpp
+    llgltfloader.cpp
     llmaterialid.cpp
     llmaterial.cpp
     llmaterialtable.cpp
@@ -46,6 +47,7 @@ set(llprimitive_SOURCE_FILES
 set(llprimitive_HEADER_FILES
     CMakeLists.txt
     lldaeloader.h
+    llgltfloader.h
     legacy_object_types.h
     llmaterial.h
     llmaterialid.h
diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp
index e89690438e..94f8500dab 100644
--- a/indra/llprimitive/lldaeloader.cpp
+++ b/indra/llprimitive/lldaeloader.cpp
@@ -2504,19 +2504,19 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD&
 	return (status == LLModel::NO_ERRORS);
 }
 
-//static 
-LLModel* LLDAELoader::loadModelFromDomMesh(domMesh *mesh)
-{
-	LLVolumeParams volume_params;
-	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
-	LLModel* ret = new LLModel(volume_params, 0.f); 
-	createVolumeFacesFromDomMesh(ret, mesh);
-    if (ret->mLabel.empty())
-    {
-	    ret->mLabel = getElementLabel(mesh);
-    }
-    return ret;
-}
+////static 
+//LLModel* LLDAELoader::loadModelFromDomMesh(domMesh *mesh)
+//{
+//	LLVolumeParams volume_params;
+//	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+//	LLModel* ret = new LLModel(volume_params, 0.f); 
+//	createVolumeFacesFromDomMesh(ret, mesh);
+//    if (ret->mLabel.empty())
+//    {
+//	    ret->mLabel = getElementLabel(mesh);
+//    }
+//    return ret;
+//}
 
 //static diff version supports creating multiple models when material counts spill
 // over the 8 face server-side limit
diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h
index 2b211343e1..9e80980ddf 100644
--- a/indra/llprimitive/lldaeloader.h
+++ b/indra/llprimitive/lldaeloader.h
@@ -92,7 +92,7 @@ protected:
 	static bool addVolumeFacesFromDomMesh(LLModel* model, domMesh* mesh, LLSD& log_msg);
 	static bool createVolumeFacesFromDomMesh(LLModel* model, domMesh *mesh);
 
-	static LLModel* loadModelFromDomMesh(domMesh* mesh);
+	//static LLModel* loadModelFromDomMesh(domMesh* mesh);
 
 	// Loads a mesh breaking it into one or more models as necessary
 	// to get around volume face limitations while retaining >8 materials
diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp
new file mode 100644
index 0000000000..001623ac9e
--- /dev/null
+++ b/indra/llprimitive/llgltfloader.cpp
@@ -0,0 +1,336 @@
+/**
+ * @file LLGLTFLoader.cpp
+ * @brief LLGLTFLoader class implementation
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "LLGLTFLoader.h"
+
+// Import & define single-header gltf import/export lib
+#define TINYGLTF_IMPLEMENTATION
+#define TINYGLTF_USE_CPP14  // default is C++ 11
+
+// tinygltf by default loads image files using STB
+#define STB_IMAGE_IMPLEMENTATION
+// to use our own image loading:
+// 1. replace this definition with TINYGLTF_NO_STB_IMAGE
+// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)
+
+// tinygltf saves image files using STB
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data)
+
+// Additionally, disable inclusion of STB header files entirely with
+// TINYGLTF_NO_INCLUDE_STB_IMAGE
+// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
+#include "tinygltf\tiny_gltf.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include "llsdserialize.h"
+#include "lljoint.h"
+
+#include "glh/glh_linear.h"
+#include "llmatrix4a.h"
+
+#include <boost/regex.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+static const std::string lod_suffix[LLModel::NUM_LODS] =
+{
+	"_LOD0",
+	"_LOD1",
+	"_LOD2",
+	"",
+	"_PHYS",
+};
+
+const U32 LIMIT_MATERIALS_OUTPUT = 12;
+
+LLGLTFLoader::LLGLTFLoader(std::string filename,
+    S32                                 lod,
+    LLModelLoader::load_callback_t      load_cb,
+    LLModelLoader::joint_lookup_func_t  joint_lookup_func,
+    LLModelLoader::texture_load_func_t  texture_load_func,
+    LLModelLoader::state_callback_t     state_cb,
+    void *                              opaque_userdata,
+    JointTransformMap &                 jointTransformMap,
+    JointNameSet &                      jointsFromNodes,
+    std::map<std::string, std::string> &jointAliasMap,
+    U32                                 maxJointsPerMesh,
+    U32                                 modelLimit) //,
+    //bool                                preprocess)
+    : LLModelLoader( filename,
+                     lod,
+                     load_cb,
+                     joint_lookup_func,
+                     texture_load_func,
+                     state_cb,
+                     opaque_userdata,
+                     jointTransformMap,
+                     jointsFromNodes,
+                     jointAliasMap,
+                     maxJointsPerMesh ),
+    mGeneratedModelLimit(modelLimit),
+    //mPreprocessGLTF(preprocess),
+    mMeshesLoaded(false),
+    mMaterialsLoaded(false)
+{
+}
+
+LLGLTFLoader::~LLGLTFLoader() {}
+
+bool LLGLTFLoader::OpenFile(const std::string &filename)
+{
+    tinygltf::TinyGLTF loader;
+    std::string        error_msg;
+    std::string        warn_msg;
+
+    // Load a tinygltf model fom a file. Assumes that the input filename has already been
+    // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
+    if (std::string::npos == filename.rfind(".gltf"))
+    {  // file is binary
+        mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
+    }
+    else
+    {  // file is ascii
+        mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
+    }
+
+    if (!mGltfLoaded)
+    {
+        if (!warn_msg.empty())
+            LL_WARNS() << "gltf load warning: " << warn_msg.c_str() << LL_ENDL;
+        if (!error_msg.empty())
+            LL_WARNS() << "gltf load error: " << error_msg.c_str() << LL_ENDL;
+        return false;
+    }
+
+    mMeshesLoaded = parseMeshes();
+    if (mMeshesLoaded) uploadMeshes();
+
+    mMaterialsLoaded = parseMaterials();
+    if (mMaterialsLoaded) uploadMaterials();
+
+    return (mMeshesLoaded || mMaterialsLoaded);
+}
+
+bool LLGLTFLoader::parseMeshes()
+{
+    if (!mGltfLoaded) return false;
+
+    // 2022-04 DJH Volume params from dae example. TODO understand PCODE
+    LLVolumeParams volume_params;
+    volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);    
+    
+    for (tinygltf::Mesh mesh : mGltfModel.meshes)
+    {
+        LLModel *pModel = new LLModel(volume_params, 0.f);
+
+        if (populateModelFromMesh(pModel, mesh)         && 
+            (LLModel::NO_ERRORS == pModel->getStatus()) &&
+            validate_model(pModel))
+        {
+            mModelList.push_back(pModel);
+        }
+        else
+        {
+            setLoadState(ERROR_MODEL + pModel->getStatus());
+            delete(pModel);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh)
+{
+    pModel->mLabel = mesh.name;
+    int pos_idx, norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx;
+    tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a;
+
+    auto prims = mesh.primitives;
+    for (auto prim : prims)
+    {
+        if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices];
+
+        pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1;
+        if (pos_idx >= 0)
+        {
+            positions_a = mGltfModel.accessors[pos_idx];
+            if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType) 
+                continue;
+            auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView];
+            auto positions_buf = mGltfModel.buffers[positions_bv.buffer];
+            //auto type = positions_vb.
+            //if (positions_buf.name
+        }
+
+        norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1;
+        tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1;
+        uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1;
+        uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1;
+        color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1;
+        color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1;
+
+        if (prim.mode == TINYGLTF_MODE_TRIANGLES)
+        {
+            //auto pos = mesh.    TODO resume here DJH 2022-04
+        }
+    }
+    
+    //pModel->addFace()
+    return false;
+}
+
+bool LLGLTFLoader::parseMaterials() 
+{
+    if (!mGltfLoaded) return false;
+
+    // fill local texture data structures
+    mSamplers.clear();
+    for (auto in_sampler : mGltfModel.samplers)
+    {
+        gltf_sampler sampler{ 0 };
+        sampler.magFilter = in_sampler.magFilter;
+        sampler.minFilter = in_sampler.minFilter;
+        sampler.wrapS     = in_sampler.wrapS;
+        sampler.wrapT     = in_sampler.wrapT;
+        sampler.name      = in_sampler.name; // unused
+        mSamplers.push_back(sampler);
+    }
+
+    mImages.clear();
+    for (auto in_image : mGltfModel.images)
+    {
+        gltf_image image{ 0 };
+        image.numChannels     = in_image.component;
+        image.bytesPerChannel = in_image.bits >> 3;
+        image.pixelType       = in_image.pixel_type;
+        image.size            = in_image.image.size();
+        image.height          = in_image.height;
+        image.width           = in_image.width;
+        image.data            = in_image.image.data();
+
+        if (in_image.as_is)
+        {
+            LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL;
+            return false;
+        }
+        
+        if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel)
+        {
+            LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL;
+            return false;
+        }
+
+        mImages.push_back(image);
+    }
+
+    mTextures.clear();
+    for (auto in_tex : mGltfModel.textures)
+    {
+        gltf_texture tex{ 0 };
+        tex.image_idx   = in_tex.source;
+        tex.sampler_idx = in_tex.sampler;
+
+        if (tex.image_idx >= mImages.size() || tex.sampler_idx >= mSamplers.size())
+        {
+            LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL;
+            return false;
+        }
+
+        mTextures.push_back(tex);
+    }
+
+    // parse each material
+    for (tinygltf::Material gltf_material : mGltfModel.materials)
+    {
+        gltf_render_material mat{ 0 };
+        mat.name = gltf_material.name;
+
+        mat.normalScale = gltf_material.normalTexture.scale;
+        mat.normalTexIdx = gltf_material.normalTexture.index;
+        mat.normalTexCoordIdx = gltf_material.normalTexture.texCoord;
+
+        mat.occlusionScale = gltf_material.occlusionTexture.strength;
+        mat.occlusionTexIdx = gltf_material.occlusionTexture.index;
+        mat.occlusionTexCoordIdx = gltf_material.occlusionTexture.texCoord;
+
+        mat.emissiveColor.set(gltf_material.emissiveFactor.data());
+        mat.emissiveColorTexIdx = gltf_material.emissiveTexture.index;
+        mat.emissiveColorTexCoordIdx = gltf_material.emissiveTexture.texCoord;
+
+        mat.alphaMode = gltf_material.alphaMode;
+        mat.alphaMask = gltf_material.alphaCutoff;
+
+        tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness;
+        mat.hasPBR = true;
+
+        mat.pbr.baseColor.set(pbr.baseColorFactor.data());
+        mat.pbr.baseColorTexIdx = pbr.baseColorTexture.index;
+        mat.pbr.baseColorTexCoordIdx = pbr.baseColorTexture.texCoord;
+
+        mat.pbr.metalness = pbr.metallicFactor;
+        mat.pbr.roughness = pbr.roughnessFactor;
+        mat.pbr.metalRoughTexIdx = pbr.metallicRoughnessTexture.index;
+        mat.pbr.metalRoughTexCoordIdx = pbr.metallicRoughnessTexture.texCoord;
+
+        if (mat.normalTexIdx         >= mTextures.size() ||
+            mat.occlusionTexIdx      >= mTextures.size() ||
+            mat.emissiveColorTexIdx  >= mTextures.size() ||
+            mat.pbr.baseColorTexIdx  >= mTextures.size() ||
+            mat.pbr.metalRoughTexIdx >= mTextures.size())
+        {
+            LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL;
+            return false;
+        }
+
+        if (mat.normalTexCoordIdx         > 0 ||    // May have to loosen this condition
+            mat.occlusionTexCoordIdx      > 0 ||
+            mat.emissiveColorTexCoordIdx  > 0 ||
+            mat.pbr.baseColorTexCoordIdx  > 0 ||
+            mat.pbr.metalRoughTexCoordIdx > 0)
+        {
+            LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL;
+            return false;
+        }
+
+        mMaterials.push_back(mat);
+    }
+
+    return true; 
+}
+
+// TODO: convert raw vertex buffers to UUIDs
+void LLGLTFLoader::uploadMeshes()
+{
+    llassert(0);
+}
+
+// TODO: convert raw index buffers to UUIDs
+void LLGLTFLoader::uploadMaterials()
+{
+    llassert(0);
+}
+
diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h
new file mode 100644
index 0000000000..9bffeef4ab
--- /dev/null
+++ b/indra/llprimitive/llgltfloader.h
@@ -0,0 +1,207 @@
+/**
+ * @file LLGLTFLoader.h
+ * @brief LLGLTFLoader class definition
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLGLTFLoader_H
+#define LL_LLGLTFLoader_H
+
+#include "tinygltf\tiny_gltf.h"
+
+#include "llmodelloader.h"
+
+typedef struct // gltf sampler
+{   // Uses GL enums
+    S32 minFilter;      // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR
+    S32 magFilter;      // GL_NEAREST or GL_LINEAR
+    S32 wrapS;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
+    S32 wrapT;          // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
+    //S32 wrapR;        // seen in some sample files, but not part of glTF 2.0 spec. Ignored.
+    std::string name;   // optional, currently unused
+    // extensions and extras are sampler optional fields that we don't support - at least initially 
+} gltf_sampler;
+
+typedef struct // gltf image
+{   // Note that glTF images are defined with row 0 at the top
+    U8* data;               // ptr to decoded image data
+    U32 size;               // in bytes, regardless of channel width
+    U32 width;
+    U32 height;
+    U32 numChannels;        // range 1..4
+    U32 bytesPerChannel;    // converted from gltf "bits", expects only 8, 16 or 32 as input
+    U32 pixelType;          // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT
+} gltf_image;
+
+typedef struct // texture
+{
+    U32 image_idx;
+    U32 sampler_idx;
+} gltf_texture;
+
+
+// TODO: 2022-05 DJH add UUIDs for each texture
+typedef struct  // gltf_pbrMR_material
+{
+    // scalar values
+    LLColor4    baseColor;      // linear encoding. Multiplied with vertex color, if present.
+    double      metalness;
+    double      roughness;
+
+    // textures
+    U32 baseColorTexIdx;        // always sRGB encoded
+    U32 baseColorTexCoordIdx;
+
+    U32 metalRoughTexIdx;       // always linear, roughness in G channel, metalness in B channel
+    U32 metalRoughTexCoordIdx;
+} gltf_pbr;
+
+typedef struct // render material
+{
+    std::string name;
+
+    // scalar values
+    double      normalScale;    // scale applies only to X,Y components of normal
+    double      occlusionScale; // strength multiplier for occlusion
+    LLColor4    emissiveColor;  // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent)
+    std::string alphaMode;      // "OPAQUE", "MASK" or "BLEND"
+    double      alphaMask;      
+
+    // textures
+    U32 normalTexIdx;           // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0)   
+    U32 normalTexCoordIdx;
+
+    U32 occlusionTexIdx;        // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded
+    U32 occlusionTexCoordIdx;
+
+    U32 emissiveColorTexIdx;    // always stored as sRGB, in nits (candela / meter^2)
+    U32 emissiveColorTexCoordIdx;
+
+    // TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry??
+
+    bool        hasPBR;
+    gltf_pbr    pbr;
+
+} gltf_render_material;
+
+typedef struct  // gltf_mesh
+{
+    std::string name;
+
+    // TODO DJH 2022-04
+
+} gltf_mesh;
+
+class LLGLTFLoader : public LLModelLoader
+{
+  public:
+    typedef std::map<std::string, LLImportMaterial> material_map;
+    typedef void gltfElement;   // TBD
+    typedef void GLTF;          // TBD
+
+    // typedef std::map<gltfElement*, std::vector<LLPointer<LLModel> > >	gltf_model_map;
+    // gltf_model_map	mModelsMap;
+
+    LLGLTFLoader(std::string filename,
+                    S32                                 lod,
+                    LLModelLoader::load_callback_t      load_cb,
+                    LLModelLoader::joint_lookup_func_t  joint_lookup_func,
+                    LLModelLoader::texture_load_func_t  texture_load_func,
+                    LLModelLoader::state_callback_t     state_cb,
+                    void *                              opaque_userdata,
+                    JointTransformMap &                 jointTransformMap,
+                    JointNameSet &                      jointsFromNodes,
+                    std::map<std::string, std::string> &jointAliasMap,
+                    U32                                 maxJointsPerMesh,
+                    U32                                 modelLimit); //,
+                    //bool                                preprocess );
+    virtual ~LLGLTFLoader();
+
+    virtual bool OpenFile(const std::string &filename);
+
+protected:
+    tinygltf::Model mGltfModel;
+    bool            mGltfLoaded;
+    bool            mMeshesLoaded;
+    bool            mMaterialsLoaded;
+
+    std::vector<gltf_mesh>              mMeshes;
+    std::vector<gltf_render_material>   mMaterials;
+
+    std::vector<gltf_texture>           mTextures;
+    std::vector<gltf_image>             mImages;
+    std::vector<gltf_sampler>           mSamplers;
+
+private:
+    U32  mGeneratedModelLimit;  // Attempt to limit amount of generated submodels
+//    bool mPreprocessGLTF;
+    
+    bool parseMeshes();
+    void uploadMeshes();
+    bool parseMaterials();
+    void uploadMaterials();
+    bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh);
+
+    /*
+    void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
+    void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);
+
+    material_map     getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
+    LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
+    LLColor4         getGltfColor(gltfElement *element);
+
+    gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);
+
+    bool isNodeAJoint(gltfNode *pNode);
+    void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
+    void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
+    void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
+    void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
+    void buildJointToNodeMappingFromScene(gltfElement *pRoot);
+    void processJointToNodeMapping(gltfNode *pNode);
+    void processChildJoints(gltfNode *pParentNode);
+
+    bool verifyCount(int expected, int result);
+
+    // Verify that a controller matches vertex counts
+    bool verifyController(gltfController *pController);
+
+    static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
+    static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);
+
+    static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);
+
+    // Loads a mesh breaking it into one or more models as necessary
+    // to get around volume face limitations while retaining >8 materials
+    //
+    bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);
+
+    static std::string getElementLabel(gltfElement *element);
+    static size_t      getSuffixPosition(std::string label);
+    static std::string getLodlessLabel(gltfElement *element);
+
+    static std::string preprocessGLTF(std::string filename);
+    */
+    
+};
+#endif  // LL_LLGLTFLLOADER_H
diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp
index 5171621007..554ed54de1 100644
--- a/indra/llprimitive/llmodelloader.cpp
+++ b/indra/llprimitive/llmodelloader.cpp
@@ -160,7 +160,8 @@ bool LLModelLoader::getSLMFilename(const std::string& model_filename, std::strin
     std::string::size_type i = model_filename.rfind(".");
     if (i != std::string::npos)
     {
-        slm_filename.replace(i, model_filename.size()-1, ".slm");
+        slm_filename.resize(i, '\0');
+        slm_filename.append(".slm");
         return true;
     }
     else
@@ -172,7 +173,7 @@ bool LLModelLoader::getSLMFilename(const std::string& model_filename, std::strin
 bool LLModelLoader::doLoadModel()
 {
 	//first, look for a .slm file of the same name that was modified later
-	//than the .dae
+	//than the specified model file
 
 	if (mTrySLM)
 	{
@@ -182,13 +183,13 @@ bool LLModelLoader::doLoadModel()
 			llstat slm_status;
 			if (LLFile::stat(slm_filename, &slm_status) == 0)
 			{ //slm file exists
-				llstat dae_status;
-				if (LLFile::stat(mFilename, &dae_status) != 0 ||
-					dae_status.st_mtime < slm_status.st_mtime)
+				llstat model_file_status;
+				if (LLFile::stat(mFilename, &model_file_status) != 0 ||
+					model_file_status.st_mtime < slm_status.st_mtime)
 				{
 					if (loadFromSLM(slm_filename))
 					{ //slm successfully loaded, if this fails, fall through and
-						//try loading from dae
+						//try loading from model file
 
 						mLod = -1; //successfully loading from an slm implicitly sets all 
 									//LoDs
diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp
index 3669fb1eeb..4e2cc34207 100644
--- a/indra/newview/llfilepicker.cpp
+++ b/indra/newview/llfilepicker.cpp
@@ -55,13 +55,11 @@ LLFilePicker LLFilePicker::sInstance;
 #define IMAGE_FILTER L"Images (*.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
 #define ANIM_FILTER L"Animations (*.bvh; *.anim)\0*.bvh;*.anim\0"
 #define COLLADA_FILTER L"Scene (*.dae)\0*.dae\0"
-#ifdef _CORY_TESTING
-#define GEOMETRY_FILTER L"SL Geometry (*.slg)\0*.slg\0"
-#endif
+#define GLTF_FILTER L"glTF (*.gltf; *.glb)\0*.gltf;*.glb\0"
 #define XML_FILTER L"XML files (*.xml)\0*.xml\0"
 #define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"
 #define RAW_FILTER L"RAW files (*.raw)\0*.raw\0"
-#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
+#define MODEL_FILTER L"Model files (*.dae; *.gltf; *.glb)\0*.dae;*.gltf;*.glb\0"
 #define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0"
 #define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0"
 #endif
@@ -193,16 +191,14 @@ BOOL LLFilePicker::setupFilter(ELoadFilter filter)
 		mOFN.lpstrFilter = ANIM_FILTER \
 			L"\0";
 		break;
-	case FFLOAD_COLLADA:
+    case FFLOAD_GLTF:
+        mOFN.lpstrFilter = GLTF_FILTER \
+            L"\0";
+        break;
+    case FFLOAD_COLLADA:
 		mOFN.lpstrFilter = COLLADA_FILTER \
 			L"\0";
 		break;
-#ifdef _CORY_TESTING
-	case FFLOAD_GEOMETRY:
-		mOFN.lpstrFilter = GEOMETRY_FILTER \
-			L"\0";
-		break;
-#endif
 	case FFLOAD_XML:
 		mOFN.lpstrFilter = XML_FILTER \
 			L"\0";
@@ -480,18 +476,16 @@ BOOL LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename,
 			L"XAF Anim File (*.xaf)\0*.xaf\0" \
 			L"\0";
 		break;
-#ifdef _CORY_TESTING
-	case FFSAVE_GEOMETRY:
+	case FFSAVE_GLTF:
 		if (filename.empty())
 		{
-			wcsncpy( mFilesW,L"untitled.slg", FILENAME_BUFFER_SIZE);	/*Flawfinder: ignore*/
+			wcsncpy( mFilesW,L"untitled.glb", FILENAME_BUFFER_SIZE);	/*Flawfinder: ignore*/
 		}
-		mOFN.lpstrDefExt = L"slg";
+		mOFN.lpstrDefExt = L"glb";
 		mOFN.lpstrFilter =
-			L"SLG SL Geometry File (*.slg)\0*.slg\0" \
+			L"glTF Asset File (*.gltf *.glb)\0*.gltf;*.glb\0" \
 			L"\0";
 		break;
-#endif
 	case FFSAVE_XML:
 		if (filename.empty())
 		{
@@ -621,14 +615,13 @@ std::vector<std::string>* LLFilePicker::navOpenFilterProc(ELoadFilter filter) //
             allowedv->push_back("bvh");
             allowedv->push_back("anim");
             break;
+        case FFLOAD_GLTF:
+            allowedv->push_back("gltf");
+            allowedv->push_back("glb");
+            break;
         case FFLOAD_COLLADA:
             allowedv->push_back("dae");
             break;
-#ifdef _CORY_TESTING
-        case FFLOAD_GEOMETRY:
-            allowedv->push_back("slg");
-            break;
-#endif
         case FFLOAD_XML:
             allowedv->push_back("xml");
             break;
@@ -728,13 +721,11 @@ bool	LLFilePicker::doNavSaveDialog(ESaveFilter filter, const std::string& filena
 			extension = "xaf";
 			break;
 
-#ifdef _CORY_TESTING
-		case FFSAVE_GEOMETRY:
+		case FFSAVE_GLTF:
 			type = "\?\?\?\?";
 			creator = "\?\?\?\?";
-			extension = "slg";
+			extension = "glb";
 			break;
-#endif	
 			
 		case FFSAVE_XML:
 			type = "\?\?\?\?";
@@ -1354,10 +1345,13 @@ BOOL LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking )
 		case FFLOAD_XML:
 			filtername = add_xml_filter_to_gtkchooser(picker);
 			break;
-		case FFLOAD_COLLADA:
-			filtername = add_collada_filter_to_gtkchooser(picker);
-			break;
-		case FFLOAD_IMAGE:
+        case FFLOAD_GLTF:
+            filtername = dead_code_should_blow_up_here(picker);
+            break;
+        case FFLOAD_COLLADA:
+            filtername = add_collada_filter_to_gtkchooser(picker);
+            break;
+        case FFLOAD_IMAGE:
 			filtername = add_imageload_filter_to_gtkchooser(picker);
 			break;
 		case FFLOAD_SCRIPT:
diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h
index 04ba4416d7..a314207da6 100644
--- a/indra/newview/llfilepicker.h
+++ b/indra/newview/llfilepicker.h
@@ -77,9 +77,7 @@ public:
 		FFLOAD_WAV = 2,
 		FFLOAD_IMAGE = 3,
 		FFLOAD_ANIM = 4,
-#ifdef _CORY_TESTING
-		FFLOAD_GEOMETRY = 5,
-#endif
+		FFLOAD_GLTF = 5,
 		FFLOAD_XML = 6,
 		FFLOAD_SLOBJECT = 7,
 		FFLOAD_RAW = 8,
@@ -99,9 +97,7 @@ public:
 		FFSAVE_BMP = 5,
 		FFSAVE_AVI = 6,
 		FFSAVE_ANIM = 7,
-#ifdef _CORY_TESTING
-		FFSAVE_GEOMETRY = 8,
-#endif
+		FFSAVE_GLTF = 8,
 		FFSAVE_XML = 9,
 		FFSAVE_COLLADA = 10,
 		FFSAVE_RAW = 11,
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 7279e1ad6d..b77341f806 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -97,7 +97,7 @@ private:
 };
 
 LLMeshFilePicker::LLMeshFilePicker(LLModelPreview* mp, S32 lod)
-: LLFilePickerThread(LLFilePicker::FFLOAD_COLLADA)
+: LLFilePickerThread(LLFilePicker::FFLOAD_MODEL)
 	{
 		mMP = mp;
 		mLOD = lod;
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index 859d987fc3..e67bd6468e 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -30,6 +30,7 @@
 
 #include "llmodelloader.h"
 #include "lldaeloader.h"
+#include "llgltfloader.h"
 #include "llfloatermodelpreview.h"
 
 #include "llagent.h"
@@ -732,20 +733,41 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
     std::map<std::string, std::string> joint_alias_map;
     getJointAliases(joint_alias_map);
 
-    mModelLoader = new LLDAELoader(
-        filename,
-        lod,
-        &LLModelPreview::loadedCallback,
-        &LLModelPreview::lookupJointByName,
-        &LLModelPreview::loadTextures,
-        &LLModelPreview::stateChangedCallback,
-        this,
-        mJointTransformMap,
-        mJointsFromNode,
-        joint_alias_map,
-        LLSkinningUtil::getMaxJointCount(),
-        gSavedSettings.getU32("ImporterModelLimit"),
-        gSavedSettings.getBOOL("ImporterPreprocessDAE"));
+    // three possible file extensions, .dae .gltf .glb
+    // check for .dae and if not then assume one of the .gl??
+    if (std::string::npos != filename.rfind(".dae"))
+    {
+        mModelLoader = new LLDAELoader(
+            filename,
+            lod,
+            &LLModelPreview::loadedCallback,
+            &LLModelPreview::lookupJointByName,
+            &LLModelPreview::loadTextures,
+            &LLModelPreview::stateChangedCallback,
+            this,
+            mJointTransformMap,
+            mJointsFromNode,
+            joint_alias_map,
+            LLSkinningUtil::getMaxJointCount(),
+            gSavedSettings.getU32("ImporterModelLimit"),
+            gSavedSettings.getBOOL("ImporterPreprocessDAE"));
+    }
+    else
+    {
+        mModelLoader = new LLGLTFLoader(
+            filename,
+            lod,
+            &LLModelPreview::loadedCallback,
+            &LLModelPreview::lookupJointByName,
+            &LLModelPreview::loadTextures,
+            &LLModelPreview::stateChangedCallback,
+            this,
+            mJointTransformMap,
+            mJointsFromNode,
+            joint_alias_map,
+            LLSkinningUtil::getMaxJointCount(),
+            gSavedSettings.getU32("ImporterModelLimit"));
+    }
 
     if (force_disable_slm)
     {
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 8732bde35c..9c8a666185 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -267,16 +267,11 @@ void handle_reset_view();
 
 void handle_duplicate_in_place(void*);
 
-
 void handle_object_owner_self(void*);
 void handle_object_owner_permissive(void*);
 void handle_object_lock(void*);
 void handle_object_asset_ids(void*);
 void force_take_copy(void*);
-#ifdef _CORY_TESTING
-void force_export_copy(void*);
-void force_import_geometry(void*);
-#endif
 
 void handle_force_parcel_owner_to_me(void*);
 void handle_force_parcel_to_content(void*);
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index 28ff69eaf5..32fdfe282d 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -277,9 +277,6 @@ void LLMediaFilePicker::notify(const std::vector<std::string>& filenames)
 static std::string SOUND_EXTENSIONS = "wav";
 static std::string IMAGE_EXTENSIONS = "tga bmp jpg jpeg png";
 static std::string ANIM_EXTENSIONS =  "bvh anim";
-#ifdef _CORY_TESTING
-static std::string GEOMETRY_EXTENSIONS = "slg";
-#endif
 static std::string XML_EXTENSIONS = "xml";
 static std::string SLOBJECT_EXTENSIONS = "slobject";
 #endif
@@ -301,10 +298,6 @@ std::string build_extensions_string(LLFilePicker::ELoadFilter filter)
 		return SLOBJECT_EXTENSIONS;
 	case LLFilePicker::FFLOAD_MODEL:
 		return MODEL_EXTENSIONS;
-#ifdef _CORY_TESTING
-	case LLFilePicker::FFLOAD_GEOMETRY:
-		return GEOMETRY_EXTENSIONS;
-#endif
 	case LLFilePicker::FFLOAD_XML:
 	    return XML_EXTENSIONS;
     case LLFilePicker::FFLOAD_ALL:
-- 
cgit v1.2.3