diff options
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llprimitive/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/llprimitive/llmodelloader.h | 6 | ||||
| -rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/newview/gltf/asset.cpp | 20 | ||||
| -rw-r--r-- | indra/newview/gltf/asset.h | 3 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.cpp (renamed from indra/llprimitive/llgltfloader.cpp) | 301 | ||||
| -rw-r--r-- | indra/newview/gltf/llgltfloader.h (renamed from indra/llprimitive/llgltfloader.h) | 15 | ||||
| -rw-r--r-- | indra/newview/llfilepicker.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llfloatermodelpreview.cpp | 6 | ||||
| -rw-r--r-- | indra/newview/llmodelpreview.cpp | 2 | 
10 files changed, 291 insertions, 68 deletions
| diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 3d8e02cb16..e13f0bbd96 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -12,7 +12,6 @@ include(TinyGLTF)  set(llprimitive_SOURCE_FILES      lldaeloader.cpp -    llgltfloader.cpp      llgltfmaterial.cpp      llmaterialid.cpp      llmaterial.cpp @@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES  set(llprimitive_HEADER_FILES      CMakeLists.txt      lldaeloader.h -    llgltfloader.h      llgltfmaterial.h      llgltfmaterial_templates.h      legacy_object_types.h diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 530e61e2b8..73ec0ed1f4 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -111,6 +111,7 @@ public:      bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)      model_list      mModelList; +    // The scene is pretty much what ends up getting loaded for upload.  Basically assign things to this guy if you want something uploaded.      scene               mScene;      typedef std::queue<LLPointer<LLModel> > model_queue; @@ -119,9 +120,14 @@ public:      model_queue mPhysicsQ;      //map of avatar joints as named in COLLADA assets to internal joint names +    // Do not use this for anything other than looking up the name of a joint.  This is populated elsewhere.      JointMap            mJointMap; + +    // The joint list is what you want to use to actually setup the specific joint transformations.      JointTransformMap&  mJointList;      JointNameSet&       mJointsFromNode; + +      U32                 mMaxJointsPerMesh;      LLModelLoader( diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 98151e2f4d..dee3d5ed59 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES      gltf/accessor.cpp      gltf/primitive.cpp      gltf/animation.cpp +    gltf/llgltfloader.cpp      groupchatlistener.cpp      llaccountingcostmanager.cpp      llaisapi.cpp @@ -746,6 +747,7 @@ set(viewer_HEADER_FILES      gltf/buffer_util.h      gltf/primitive.h      gltf/animation.h +    gltf/llgltfloader.h      llaccountingcost.h      llaccountingcostmanager.h      llaisapi.h diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index c210b9c61d..beccb02bd4 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -472,11 +472,14 @@ void Asset::update()              for (auto& image : mImages)              { -                if (image.mTexture.notNull()) -                { // HACK - force texture to be loaded full rez -                    // TODO: calculate actual vsize -                    image.mTexture->addTextureStats(2048.f * 2048.f); -                    image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                if (image.mLoadIntoTexturePipe) +                { +                    if (image.mTexture.notNull()) +                    { // HACK - force texture to be loaded full rez +                        // TODO: calculate actual vsize +                        image.mTexture->addTextureStats(2048.f * 2048.f); +                        image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH); +                    }                  }              }          } @@ -603,6 +606,7 @@ bool Asset::prep()                  if (vertex_count[variant] > 0)                  {                      U32 mat_idx = mat_id + 1; +                    #if 0                      LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);                      rd.mBatches[variant][mat_idx].mVertexBuffer = vb; @@ -624,6 +628,7 @@ bool Asset::prep()                      vb->unmapBuffer();                      vb->unbind(); +                    #endif                  }              }          } @@ -634,10 +639,10 @@ bool Asset::prep()      {          for (auto& primitive : mesh.mPrimitives)          { -            llassert(primitive.mVertexBuffer.notNull()); +            //llassert(primitive.mVertexBuffer.notNull());          }      } - +    #if 0      // build render batches      for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)      { @@ -664,6 +669,7 @@ bool Asset::prep()              }          }      } +    #endif      return true;  } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 27821659db..e70fffa986 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -286,6 +286,7 @@ namespace LL              void serialize(boost::json::object& dst) const;          }; +        // Image is for images that we want to load for the given asset.  This acts as an interface into the viewer's texture pipe.          class Image          {          public: @@ -301,6 +302,8 @@ namespace LL              S32 mBits = -1;              S32 mPixelType = -1; +            bool mLoadIntoTexturePipe = false; +              LLPointer<LLViewerFetchedTexture> mTexture;              const Image& operator=(const Value& src); diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 480012699a..2db803ef3e 100644 --- a/indra/llprimitive/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -25,6 +25,8 @@   */  #include "llgltfloader.h" +#include "meshoptimizer.h" +#include <glm/gtc/packing.hpp>  // Import & define single-header gltf import/export lib  #define TINYGLTF_IMPLEMENTATION @@ -106,16 +108,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename)      std::string filename_lc(filename);      LLStringUtil::toLower(filename_lc); -    // 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_lc.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); -    } +    mGltfLoaded = mGLTFAsset.load(filename);      if (!mGltfLoaded)      { @@ -129,10 +122,14 @@ bool LLGLTFLoader::OpenFile(const std::string &filename)      mMeshesLoaded = parseMeshes();      if (mMeshesLoaded) uploadMeshes(); +    /*      mMaterialsLoaded = parseMaterials();      if (mMaterialsLoaded) uploadMaterials(); +    */ + +    setLoadState(DONE); -    return (mMeshesLoaded || mMaterialsLoaded); +    return (mMeshesLoaded);  }  bool LLGLTFLoader::parseMeshes() @@ -143,71 +140,262 @@ bool LLGLTFLoader::parseMeshes()      LLVolumeParams volume_params;      volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); -    for (tinygltf::Mesh mesh : mGltfModel.meshes) +    mTransform.setIdentity(); + +    // Populate the joints from skins first. +    // There's not many skins - and you can pretty easily iterate through the nodes from that. +    for (auto skin : mGLTFAsset.mSkins) +    { +        populateJointFromSkin(skin); +    } + +    for (auto node : mGLTFAsset.mNodes)      { -        LLModel *pModel = new LLModel(volume_params, 0.f); +        LLMatrix4    transformation; +        material_map mats; +        auto meshidx = node.mMesh; -        if (populateModelFromMesh(pModel, mesh)         && -            (LLModel::NO_ERRORS == pModel->getStatus()) && -            validate_model(pModel)) +        if (meshidx >= 0)          { -            mModelList.push_back(pModel); -        } -        else -        { -            setLoadState(ERROR_MODEL + pModel->getStatus()); -            delete(pModel); -            return false; +            if (mGLTFAsset.mMeshes.size() > meshidx) +            { +                LLModel* pModel = new LLModel(volume_params, 0.f); +                auto mesh = mGLTFAsset.mMeshes[meshidx]; +                if (populateModelFromMesh(pModel, mesh, node, mats) && (LLModel::NO_ERRORS == pModel->getStatus()) && validate_model(pModel)) +                { +                    mModelList.push_back(pModel); +                    LLMatrix4 saved_transform = mTransform; + +                    // This will make sure the matrix is always valid from the node. +                    node.makeMatrixValid(); + +                    LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(node.mMatrix)); +                    mTransform = gltf_transform; + +                    // GLTF is +Y up, SL is +Z up +                    LLMatrix4 rotation; +                    rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); +                    mTransform *= rotation; + +                    transformation = mTransform; +                     +                    // adjust the transformation to compensate for mesh normalization +                    LLVector3 mesh_scale_vector; +                    LLVector3 mesh_translation_vector; +                    pModel->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + +                    LLMatrix4 mesh_translation; +                    mesh_translation.setTranslation(mesh_translation_vector); +                    mesh_translation *= transformation; +                    transformation = mesh_translation; + +                    LLMatrix4 mesh_scale; +                    mesh_scale.initScale(mesh_scale_vector); +                    mesh_scale *= transformation; +                    transformation = mesh_scale; +                     +                    if (transformation.determinant() < 0) +                    { // negative scales are not supported +                        LL_INFOS() << "Negative scale detected, unsupported post-normalization transform.  domInstance_geometry: " +                                   << pModel->mLabel << LL_ENDL; +                        LLSD args; +                        args["Message"] = "NegativeScaleNormTrans"; +                        args["LABEL"]   = pModel->mLabel; +                        mWarningsArray.append(args); + +                    } + +                    mScene[transformation].push_back(LLModelInstance(pModel, pModel->mLabel, transformation, mats)); +                    stretch_extents(pModel, transformation); +                    mTransform = saved_transform; +                } +                else +                { +                    setLoadState(ERROR_MODEL + pModel->getStatus()); +                    delete (pModel); +                    return false; +                } +            }          }      } +      return true;  } -bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh) +void LLGLTFLoader::populateJointFromSkin(const LL::GLTF::Skin& skin) +{ +    for (auto joint : skin.mJoints) +    { +        auto jointNode = mGLTFAsset.mNodes[joint]; +        jointNode.makeMatrixValid(); + +        mJointList[jointNode.mName] = LLMatrix4(glm::value_ptr(jointNode.mMatrix)); +        mJointsFromNode.push_front(jointNode.mName); +    } +} +  +bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats)  { -    pModel->mLabel = mesh.name; -    int pos_idx; -    tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a; +    pModel->mLabel = mesh.mName; +    pModel->ClearFacesAndMaterials(); -    auto prims = mesh.primitives; +    auto skinIdx = nodeno.mSkin; + +    auto prims = mesh.mPrimitives;      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) +        // Unfortunately, SLM does not support 32 bit indices.  Filter out anything that goes beyond 16 bit. +        if (prim.getVertexCount() < USHRT_MAX)          { -            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 -        } +            // So primitives already have all of the data we need for a given face in SL land. +            // Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call +            // count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07 +            LLVolumeFace                          face; +            LLVolumeFace::VertexMapData::PointMap point_map; +             +            std::vector<GLTFVertex> vertices; +            std::vector<U16>        indices; +             +            LLImportMaterial impMat; -#if 0 -        int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx; -        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; -#endif - -        if (prim.mode == TINYGLTF_MODE_TRIANGLES) -        { -            //auto pos = mesh.    TODO resume here DJH 2022-04 +            LL::GLTF::Material* material = nullptr; + +            if (prim.mMaterial >= 0) +                material = &mGLTFAsset.mMaterials[prim.mMaterial]; + +            impMat.mDiffuseColor = LLColor4::white; + +            for (U32 i = 0; i < prim.getVertexCount(); i++) +            { +                GLTFVertex vert; +                vert.position = glm::vec3(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2]); +                vert.normal   = glm::vec3(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]); +                vert.uv0      = glm::vec2(prim.mTexCoords0[i][0],-prim.mTexCoords0[i][1]); + +                if (skinIdx >= 0) +                { +                    auto accessorIdx = prim.mAttributes["JOINTS_0"]; +                    LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; +                    if (accessorIdx >= 0) +                    { +                        auto accessor   = mGLTFAsset.mAccessors[accessorIdx]; +                        componentType = accessor.mComponentType; +                    } + +                    // The GLTF spec allows for either an unsigned byte for joint indices, or an unsigned short. +                    // Detect and unpack accordingly. +                    if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE) +                    { +                        auto ujoint = glm::unpackUint4x8((U32)(prim.mJoints[i] & 0xFFFFFFFF)); +                        vert.joints = glm::u16vec4(ujoint.x, ujoint.y, ujoint.z, ujoint.w); +                    } +                    else if (componentType == LL::GLTF::Accessor::ComponentType::UNSIGNED_SHORT) +                    { +                        vert.joints = glm::unpackUint4x16(prim.mJoints[i]); +                    } + +                    vert.weights = glm::vec4(prim.mWeights[i]); +                } + +                vertices.push_back(vert); +            } + +            for (U32 i = 0; i < prim.getIndexCount(); i++) +            { +                indices.push_back(prim.mIndexArray[i]); +            } + +            std::vector<LLVolumeFace::VertexData> faceVertices; +            glm::vec3 min = glm::vec3(0); +            glm::vec3 max = glm::vec3(0); + +            for (U32 i = 0; i < vertices.size(); i++) +            { +                LLVolumeFace::VertexData vert; + +                if (vertices[i].position.x > max.x) +                    max.x = vertices[i].position.x; +                if (vertices[i].position.y > max.y) +                    max.y = vertices[i].position.y; +                if (vertices[i].position.z > max.z) +                    max.z = vertices[i].position.z; + +                 +                if (vertices[i].position.x < min.x) +                    min.x = vertices[i].position.x; +                if (vertices[i].position.y < min.y) +                    min.y = vertices[i].position.y; +                if (vertices[i].position.z < min.z) +                    min.z = vertices[i].position.z; + +                LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z); +                LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z); +                vert.setPosition(position); +                vert.setNormal(normal); +                vert.mTexCoord = LLVector2(vertices[i].uv0.x, vertices[i].uv0.y); +                faceVertices.push_back(vert); + +                 +                // create list of weights that influence this vertex +                LLModel::weight_list weight_list; + +                weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); +                weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); +                weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); +                weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + +                std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); + +                 +                std::vector<LLModel::JointWeight> wght; +                F32                               total = 0.f; + +                for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i) +                { // take up to 4 most significant weights +                    // Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex. +                    if (weight_list[i].mWeight > 0.f) +                    { +                        wght.push_back(weight_list[i]); +                        total += weight_list[i].mWeight; +                    } +                } + +                F32 scale = 1.f / total; +                if (scale != 1.f) +                { // normalize weights +                    for (U32 i = 0; i < wght.size(); ++i) +                    { +                        wght[i].mWeight *= scale; +                    } +                } + +                pModel->mSkinWeights[LLVector3(vertices[i].position)] = wght; +            } + +            face.fillFromLegacyData(faceVertices, indices); +            face.mExtents[0] = LLVector4a(min.x, min.y, min.z, 0); +            face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0); + +            pModel->getVolumeFaces().push_back(face); +            pModel->getMaterialList().push_back("mat" + std::to_string(prim.mMaterial)); +            mats["mat" + std::to_string(prim.mMaterial)] = impMat; +        } +        else { +            LL_INFOS() << "Unable to process mesh due to 16-bit index limits" << LL_ENDL; +            LLSD args; +            args["Message"] = "ParsingErrorBadElement"; +            mWarningsArray.append(args); +            return false;          }      } -    //pModel->addFace() -    return false; +    return true;  }  bool LLGLTFLoader::parseMaterials()  { +    return true; +    /*      if (!mGltfLoaded) return false;      // fill local texture data structures @@ -329,12 +517,13 @@ bool LLGLTFLoader::parseMaterials()      }      return true; +    */  }  // TODO: convert raw vertex buffers to UUIDs  void LLGLTFLoader::uploadMeshes()  { -    llassert(0); +    //llassert(0);  }  // convert raw image buffers to texture UUIDs & assemble into a render material diff --git a/indra/llprimitive/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 66671d1c5a..519f2d8a07 100644 --- a/indra/llprimitive/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -29,6 +29,8 @@  #include "tinygltf/tiny_gltf.h" +#include "asset.h" +  #include "llglheaders.h"  #include "llmodelloader.h" @@ -137,7 +139,17 @@ class LLGLTFLoader : public LLModelLoader      virtual bool OpenFile(const std::string &filename); +    struct GLTFVertex +    { +        glm::vec3 position; +        glm::vec3 normal; +        glm::vec2 uv0; +        glm::u16vec4 joints; +        glm::vec4 weights; +    }; +  protected: +    LL::GLTF::Asset mGLTFAsset;      tinygltf::Model mGltfModel;      bool            mGltfLoaded;      bool            mMeshesLoaded; @@ -155,7 +167,8 @@ private:      void uploadMeshes();      bool parseMaterials();      void uploadMaterials(); -    bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh); +    bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats); +    void populateJointFromSkin(const LL::GLTF::Skin& skin);      LLUUID imageBufferToTextureUUID(const gltf_texture& tex);      //    bool mPreprocessGLTF; diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 716e6cd9e3..09a2206cd7 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance;  #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 MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"  #define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"  #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 8332a430e6..96f9ddbf8a 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -64,6 +64,7 @@  #include "llcallbacklist.h"  #include "llviewertexteditor.h"  #include "llviewernetwork.h" +#include "llmaterialeditor.h"  //static @@ -113,6 +114,11 @@ void LLMeshFilePicker::notify(const std::vector<std::string>& filenames)      if (filenames.size() > 0)      {          mMP->loadModel(filenames[0], mLOD); + +        if (filenames[0].substr(filenames[0].length() - 4) == ".glb" || filenames[0].substr(filenames[0].length() - 5) == ".gltf") +        { +            LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); +        }      }      else      { diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 0dbfa50769..7ccd299135 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -30,7 +30,7 @@  #include "llmodelloader.h"  #include "lldaeloader.h" -#include "llgltfloader.h" +#include "gltf/llgltfloader.h"  #include "llfloatermodelpreview.h"  #include "llagent.h" | 
