summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorRunitaiLinden <davep@lindenlab.com>2024-04-09 19:21:10 -0500
committerGitHub <noreply@github.com>2024-04-09 19:21:10 -0500
commitb2a450a3087fb8393024876f6069a7cec9855bfd (patch)
tree05a08420a5f4797ad8ce4d63668d1bf688888704 /indra
parent5a47a3cb2366b9da9a595d37c88703497e111005 (diff)
#1126 gltf scene import prototype (#1172)
* #1126 GLTF Scene import initial prototype (working geometry import for some assets) * #1126 WIP -- Expand support for more vertex formats, PoC material import, shadow support, scale support * #1126 move GLTF implementation to newview/gltf * #1126 Refactor attribute loading to be less copy/pasta for each combination of types * #1126 Partially working object selection. Ability to have multiple scenes at once. Helpful message on how to use the preview button. * #1126 Add bounding box debug display and untangle GLTF raycast from LLVOVolume raycast * #1126 Working raycast on GLTF scenes. * #1126 Remove some #pragma optimize offs
Diffstat (limited to 'indra')
-rw-r--r--indra/llmath/llvolume.cpp85
-rw-r--r--indra/llmath/llvolume.h5
-rw-r--r--indra/llmath/llvolumeoctree.cpp8
-rw-r--r--indra/llmath/llvolumeoctree.h95
-rw-r--r--indra/llrender/llvertexbuffer.cpp2
-rw-r--r--indra/newview/CMakeLists.txt6
-rw-r--r--indra/newview/gltf/asset.cpp209
-rw-r--r--indra/newview/gltf/asset.h445
-rw-r--r--indra/newview/gltf/primitive.cpp480
-rw-r--r--indra/newview/gltf/primitive.h140
-rw-r--r--indra/newview/gltfscenemanager.cpp469
-rw-r--r--indra/newview/gltfscenemanager.h64
-rw-r--r--indra/newview/llappviewer.cpp6
-rw-r--r--indra/newview/lldrawpoolpbropaque.cpp4
-rw-r--r--indra/newview/llspatialpartition.cpp28
-rw-r--r--indra/newview/lltinygltfhelper.cpp27
-rw-r--r--indra/newview/lltinygltfhelper.h9
-rw-r--r--indra/newview/llviewermenu.cpp13
-rw-r--r--indra/newview/llviewerobject.h3
-rw-r--r--indra/newview/llvovolume.cpp4
-rw-r--r--indra/newview/pipeline.cpp17
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml13
23 files changed, 2015 insertions, 123 deletions
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index 29eeebcf67..2f25a4359d 100644
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -45,13 +45,13 @@
#include "llmatrix3a.h"
#include "lloctree.h"
#include "llvolume.h"
-#include "llvolumeoctree.h"
#include "llstl.h"
#include "llsdserialize.h"
#include "llvector4a.h"
#include "llmatrix4a.h"
#include "llmeshoptimizer.h"
#include "lltimer.h"
+#include "llvolumeoctree.h"
#include "mikktspace/mikktspace.h"
#include "mikktspace/mikktspace.c" // insert mikktspace implementation into llvolume object file
@@ -377,77 +377,6 @@ BOOL LLTriangleRayIntersect(const LLVector3& vert0, const LLVector3& vert1, cons
}
}
-class LLVolumeOctreeRebound : public LLOctreeTravelerDepthFirst<LLVolumeTriangle, LLVolumeTriangle*>
-{
-public:
- const LLVolumeFace* mFace;
-
- LLVolumeOctreeRebound(const LLVolumeFace* face)
- {
- mFace = face;
- }
-
- virtual void visit(const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* branch)
- { //this is a depth first traversal, so it's safe to assum all children have complete
- //bounding data
- LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME
-
- LLVolumeOctreeListener* node = (LLVolumeOctreeListener*) branch->getListener(0);
-
- LLVector4a& min = node->mExtents[0];
- LLVector4a& max = node->mExtents[1];
-
- if (!branch->isEmpty())
- { //node has data, find AABB that binds data set
- const LLVolumeTriangle* tri = *(branch->getDataBegin());
-
- //initialize min/max to first available vertex
- min = *(tri->mV[0]);
- max = *(tri->mV[0]);
-
- for (LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>::const_element_iter iter = branch->getDataBegin(); iter != branch->getDataEnd(); ++iter)
- { //for each triangle in node
-
- //stretch by triangles in node
- tri = *iter;
-
- min.setMin(min, *tri->mV[0]);
- min.setMin(min, *tri->mV[1]);
- min.setMin(min, *tri->mV[2]);
-
- max.setMax(max, *tri->mV[0]);
- max.setMax(max, *tri->mV[1]);
- max.setMax(max, *tri->mV[2]);
- }
- }
- else if (branch->getChildCount() > 0)
- { //no data, but child nodes exist
- LLVolumeOctreeListener* child = (LLVolumeOctreeListener*) branch->getChild(0)->getListener(0);
-
- //initialize min/max to extents of first child
- min = child->mExtents[0];
- max = child->mExtents[1];
- }
- else
- {
- llassert(!branch->isLeaf()); // Empty leaf
- }
-
- for (S32 i = 0; i < branch->getChildCount(); ++i)
- { //stretch by child extents
- LLVolumeOctreeListener* child = (LLVolumeOctreeListener*) branch->getChild(i)->getListener(0);
- min.setMin(min, child->mExtents[0]);
- max.setMax(max, child->mExtents[1]);
- }
-
- node->mBounds[0].setAdd(min, max);
- node->mBounds[0].mul(0.5f);
-
- node->mBounds[1].setSub(max,min);
- node->mBounds[1].mul(0.5f);
- }
-};
-
//-------------------------------------------------------------------
// statics
//-------------------------------------------------------------------
@@ -5509,7 +5438,6 @@ struct MikktData
}
};
-
bool LLVolumeFace::cacheOptimize(bool gen_tangents)
{ //optimize for vertex cache according to Forsyth method:
LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
@@ -5687,8 +5615,7 @@ void LLVolumeFace::createOctree(F32 scaler, const LLVector4a& center, const LLVe
llassert(mNumIndices % 3 == 0);
- mOctree = new LLOctreeRoot<LLVolumeTriangle, LLVolumeTriangle*>(center, size, NULL);
- new LLVolumeOctreeListener(mOctree);
+ mOctree = new LLVolumeOctree(center, size);
const U32 num_triangles = mNumIndices / 3;
// Initialize all the triangles we need
mOctreeTriangles = new LLVolumeTriangle[num_triangles];
@@ -5743,7 +5670,7 @@ void LLVolumeFace::createOctree(F32 scaler, const LLVector4a& center, const LLVe
while (!mOctree->balance()) { }
//calculate AABB for each node
- LLVolumeOctreeRebound rebound(this);
+ LLVolumeOctreeRebound rebound;
rebound.traverse(mOctree);
if (gDebugGL)
@@ -5756,12 +5683,12 @@ void LLVolumeFace::createOctree(F32 scaler, const LLVector4a& center, const LLVe
void LLVolumeFace::destroyOctree()
{
delete mOctree;
- mOctree = NULL;
+ mOctree = nullptr;
delete[] mOctreeTriangles;
- mOctreeTriangles = NULL;
+ mOctreeTriangles = nullptr;
}
-const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* LLVolumeFace::getOctree() const
+const LLVolumeOctree* LLVolumeFace::getOctree() const
{
return mOctree;
}
diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h
index c2586601ae..cca284d9bc 100644
--- a/indra/llmath/llvolume.h
+++ b/indra/llmath/llvolume.h
@@ -41,6 +41,7 @@ template <class T, typename T_PTR> class LLOctreeNode;
class LLVolumeFace;
class LLVolume;
class LLVolumeTriangle;
+class LLVolumeOctree;
#include "lluuid.h"
#include "v4color.h"
@@ -913,7 +914,7 @@ public:
void createOctree(F32 scaler = 0.25f, const LLVector4a& center = LLVector4a(0,0,0), const LLVector4a& size = LLVector4a(0.5f,0.5f,0.5f));
void destroyOctree();
// Get a reference to the octree, which may be null
- const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* getOctree() const;
+ const LLVolumeOctree* getOctree() const;
enum
{
@@ -987,7 +988,7 @@ public:
LLVector3 mNormalizedScale = LLVector3(1,1,1);
private:
- LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* mOctree;
+ LLVolumeOctree* mOctree;
LLVolumeTriangle* mOctreeTriangles;
BOOL createUnCutCubeCap(LLVolume* volume, BOOL partial_build = FALSE);
diff --git a/indra/llmath/llvolumeoctree.cpp b/indra/llmath/llvolumeoctree.cpp
index 6894d04d3c..95c7cb0b5c 100644
--- a/indra/llmath/llvolumeoctree.cpp
+++ b/indra/llmath/llvolumeoctree.cpp
@@ -92,15 +92,15 @@ void LLVolumeOctreeListener::handleChildAddition(const LLOctreeNode<LLVolumeTria
}
LLOctreeTriangleRayIntersect::LLOctreeTriangleRayIntersect(const LLVector4a& start, const LLVector4a& dir,
- const LLVolumeFace* face, F32* closest_t,
+ LLVolumeFace* face, F32* closest_t,
LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent)
- : mFace(face),
- mStart(start),
+ : mStart(start),
mDir(dir),
mIntersection(intersection),
mTexCoord(tex_coord),
mNormal(normal),
mTangent(tangent),
+ mFace(face),
mClosestT(closest_t),
mHitFace(false)
{
@@ -139,7 +139,7 @@ void LLOctreeTriangleRayIntersect::visit(const LLOctreeNode<LLVolumeTriangle, LL
{
*mClosestT = t;
mHitFace = true;
-
+ mHitTriangle = tri;
if (mIntersection != NULL)
{
LLVector4a intersect = mDir;
diff --git a/indra/llmath/llvolumeoctree.h b/indra/llmath/llvolumeoctree.h
index d65bca5e52..d6f536b9ca 100644
--- a/indra/llmath/llvolumeoctree.h
+++ b/indra/llmath/llvolumeoctree.h
@@ -112,7 +112,6 @@ public:
class LLOctreeTriangleRayIntersect : public LLOctreeTraveler<LLVolumeTriangle, LLVolumeTriangle*>
{
public:
- const LLVolumeFace* mFace;
LLVector4a mStart;
LLVector4a mDir;
LLVector4a mEnd;
@@ -121,10 +120,13 @@ public:
LLVector4a* mNormal;
LLVector4a* mTangent;
F32* mClosestT;
+ LLVolumeFace* mFace;
bool mHitFace;
+ const LLVolumeTriangle* mHitTriangle = nullptr;
- LLOctreeTriangleRayIntersect(const LLVector4a& start, const LLVector4a& dir,
- const LLVolumeFace* face, F32* closest_t,
+ LLOctreeTriangleRayIntersect(const LLVector4a& start, const LLVector4a& dir,
+ LLVolumeFace* face,
+ F32* closest_t,
LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent);
void traverse(const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* node);
@@ -137,4 +139,91 @@ class LLVolumeOctreeValidate : public LLOctreeTraveler<LLVolumeTriangle, LLVolum
virtual void visit(const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* branch);
};
+class LLVolumeOctreeRebound : public LLOctreeTravelerDepthFirst<LLVolumeTriangle, LLVolumeTriangle*>
+{
+public:
+ LLVolumeOctreeRebound()
+ {
+ }
+
+ virtual void visit(const LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>* branch)
+ { //this is a depth first traversal, so it's safe to assum all children have complete
+ //bounding data
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME
+
+ LLVolumeOctreeListener* node = (LLVolumeOctreeListener*)branch->getListener(0);
+
+ LLVector4a& min = node->mExtents[0];
+ LLVector4a& max = node->mExtents[1];
+
+ if (!branch->isEmpty())
+ { //node has data, find AABB that binds data set
+ const LLVolumeTriangle* tri = *(branch->getDataBegin());
+
+ //initialize min/max to first available vertex
+ min = *(tri->mV[0]);
+ max = *(tri->mV[0]);
+
+ for (LLOctreeNode<LLVolumeTriangle, LLVolumeTriangle*>::const_element_iter iter = branch->getDataBegin(); iter != branch->getDataEnd(); ++iter)
+ { //for each triangle in node
+
+ //stretch by triangles in node
+ tri = *iter;
+
+ min.setMin(min, *tri->mV[0]);
+ min.setMin(min, *tri->mV[1]);
+ min.setMin(min, *tri->mV[2]);
+
+ max.setMax(max, *tri->mV[0]);
+ max.setMax(max, *tri->mV[1]);
+ max.setMax(max, *tri->mV[2]);
+ }
+ }
+ else if (branch->getChildCount() > 0)
+ { //no data, but child nodes exist
+ LLVolumeOctreeListener* child = (LLVolumeOctreeListener*)branch->getChild(0)->getListener(0);
+
+ //initialize min/max to extents of first child
+ min = child->mExtents[0];
+ max = child->mExtents[1];
+ }
+ else
+ {
+ llassert(!branch->isLeaf()); // Empty leaf
+ }
+
+ for (S32 i = 0; i < branch->getChildCount(); ++i)
+ { //stretch by child extents
+ LLVolumeOctreeListener* child = (LLVolumeOctreeListener*)branch->getChild(i)->getListener(0);
+ min.setMin(min, child->mExtents[0]);
+ max.setMax(max, child->mExtents[1]);
+ }
+
+ node->mBounds[0].setAdd(min, max);
+ node->mBounds[0].mul(0.5f);
+
+ node->mBounds[1].setSub(max, min);
+ node->mBounds[1].mul(0.5f);
+ }
+};
+
+class LLVolumeOctree : public LLOctreeRoot<LLVolumeTriangle, LLVolumeTriangle*>, public LLRefCount
+{
+public:
+ LLVolumeOctree(const LLVector4a& center, const LLVector4a& size)
+ :
+ LLOctreeRoot<LLVolumeTriangle, LLVolumeTriangle*>(center, size, nullptr),
+ LLRefCount()
+ {
+ new LLVolumeOctreeListener(this);
+ }
+
+ LLVolumeOctree()
+ : LLOctreeRoot<LLVolumeTriangle, LLVolumeTriangle*>(LLVector4a::getZero(), LLVector4a(1.f,1.f,1.f), nullptr),
+ LLRefCount()
+ {
+ new LLVolumeOctreeListener(this);
+ }
+};
+
#endif
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index 4f5f30d7c2..dda3c1532d 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -657,7 +657,7 @@ void LLVertexBuffer::drawElements(U32 mode, const LLVector4a* pos, const LLVecto
U16 idx = indicesp[i];
gGL.vertex3fv(pos[idx].getF32ptr());
}
-}
+ }
gGL.end();
gGL.flush();
}
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 8494ba5b49..125f5b28ba 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -74,6 +74,9 @@ if (NOT HAVOK_TPV)
endif (NOT HAVOK_TPV)
set(viewer_SOURCE_FILES
+ gltfscenemanager.cpp
+ gltf/asset.cpp
+ gltf/primitive.cpp
groupchatlistener.cpp
llaccountingcostmanager.cpp
llaisapi.cpp
@@ -727,7 +730,10 @@ set(VIEWER_BINARY_NAME "secondlife-bin" CACHE STRING
set(viewer_HEADER_FILES
CMakeLists.txt
ViewerInstall.cmake
+ gltfscenemanager.h
groupchatlistener.h
+ gltf/asset.h
+ gltf/primitive.h
llaccountingcost.h
llaccountingcostmanager.h
llaisapi.h
diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp
new file mode 100644
index 0000000000..18f814c5b6
--- /dev/null
+++ b/indra/newview/gltf/asset.cpp
@@ -0,0 +1,209 @@
+/**
+ * @file asset.cpp
+ * @brief LL GLTF Implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "asset.h"
+#include "llvolumeoctree.h"
+
+using namespace LL::GLTF;
+
+void Scene::updateTransforms(Asset& asset)
+{
+ LLMatrix4a identity;
+ identity.setIdentity();
+ for (auto& nodeIndex : mNodes)
+ {
+ Node& node = asset.mNodes[nodeIndex];
+ node.updateTransforms(asset, identity);
+ }
+}
+
+void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
+{
+ for (auto& nodeIndex : mNodes)
+ {
+ Node& node = asset.mNodes[nodeIndex];
+ node.updateRenderTransforms(asset, modelview);
+ }
+}
+
+void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
+{
+ matMul(mMatrix, modelview, mRenderMatrix);
+
+ for (auto& childIndex : mChildren)
+ {
+ Node& child = asset.mNodes[childIndex];
+ child.updateRenderTransforms(asset, mRenderMatrix);
+ }
+}
+
+LLMatrix4a inverse(const LLMatrix4a& mat);
+
+void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix)
+{
+ matMul(mMatrix, parentMatrix, mAssetMatrix);
+ mAssetMatrixInv = inverse(mAssetMatrix);
+
+ for (auto& childIndex : mChildren)
+ {
+ Node& child = asset.mNodes[childIndex];
+ child.updateTransforms(asset, mAssetMatrix);
+ }
+}
+
+void Asset::updateTransforms()
+{
+ for (auto& scene : mScenes)
+ {
+ scene.updateTransforms(*this);
+ }
+}
+
+void Asset::updateRenderTransforms(const LLMatrix4a& modelview)
+{
+#if 0
+ // traverse hierarchy and update render transforms from scratch
+ for (auto& scene : mScenes)
+ {
+ scene.updateRenderTransforms(*this, modelview);
+ }
+#else
+ // use mAssetMatrix to update render transforms from node list
+ for (auto& node : mNodes)
+ {
+ if (node.mMesh != INVALID_INDEX)
+ {
+ matMul(node.mAssetMatrix, modelview, node.mRenderMatrix);
+ }
+ }
+
+#endif
+
+}
+
+S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ LLVector4a* intersection, // return the intersection point
+ LLVector2* tex_coord, // return the texture coordinates of the intersection point
+ LLVector4a* normal, // return the surface normal at the intersection point
+ LLVector4a* tangent, // return the surface tangent at the intersection point
+ S32* primitive_hitp
+)
+{
+ S32 node_hit = -1;
+ S32 primitive_hit = -1;
+
+ LLVector4a local_start;
+ LLVector4a asset_end = end;
+ LLVector4a local_end;
+ LLVector4a p;
+
+
+ for (auto& node : mNodes)
+ {
+ if (node.mMesh != INVALID_INDEX)
+ {
+
+ bool newHit = false;
+
+ // transform start and end to this node's local space
+ node.mAssetMatrixInv.affineTransform(start, local_start);
+ node.mAssetMatrixInv.affineTransform(asset_end, local_end);
+
+ Mesh& mesh = mMeshes[node.mMesh];
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ const LLVolumeTriangle* tri = primitive.lineSegmentIntersect(local_start, local_end, &p, tex_coord, normal, tangent);
+ if (tri)
+ {
+ newHit = true;
+ local_end = p;
+
+ // pointer math to get the node index
+ node_hit = &node - &mNodes[0];
+ llassert(&mNodes[node_hit] == &node);
+
+ //pointer math to get the primitive index
+ primitive_hit = &primitive - &mesh.mPrimitives[0];
+ llassert(&mesh.mPrimitives[primitive_hit] == &primitive);
+ }
+ }
+
+ if (newHit)
+ {
+ // shorten line segment on hit
+ node.mAssetMatrix.affineTransform(p, asset_end);
+
+ // transform results back to asset space
+ if (intersection)
+ {
+ *intersection = asset_end;
+ }
+
+ if (normal || tangent)
+ {
+ LLMatrix4 normalMatrix(node.mAssetMatrixInv.getF32ptr());
+
+ normalMatrix.transpose();
+
+ LLMatrix4a norm_mat;
+ norm_mat.loadu((F32*)normalMatrix.mMatrix);
+
+ if (normal)
+ {
+ LLVector4a n = *normal;
+ F32 w = n.getF32ptr()[3];
+ n.getF32ptr()[3] = 0.0f;
+
+ norm_mat.affineTransform(n, *normal);
+ normal->getF32ptr()[3] = w;
+ }
+
+ if (tangent)
+ {
+ LLVector4a t = *tangent;
+ F32 w = t.getF32ptr()[3];
+ t.getF32ptr()[3] = 0.0f;
+
+ norm_mat.affineTransform(t, *tangent);
+ tangent->getF32ptr()[3] = w;
+ }
+ }
+ }
+ }
+ }
+
+ if (node_hit != -1)
+ {
+ if (primitive_hitp)
+ {
+ *primitive_hitp = primitive_hit;
+ }
+ }
+
+ return node_hit;
+}
+
+
diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h
new file mode 100644
index 0000000000..0f1d4e8993
--- /dev/null
+++ b/indra/newview/gltf/asset.h
@@ -0,0 +1,445 @@
+#pragma once
+
+/**
+ * @file asset.h
+ * @brief LL GLTF Implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llvertexbuffer.h"
+#include "llvolumeoctree.h"
+#include "../lltinygltfhelper.h"
+#include "primitive.h"
+
+// LL GLTF Implementation
+namespace LL
+{
+ namespace GLTF
+ {
+ constexpr S32 INVALID_INDEX = -1;
+
+ class Asset;
+
+ class Buffer
+ {
+ public:
+ std::vector<U8> mData;
+ std::string mName;
+ std::string mUri;
+
+ const Buffer& operator=(const tinygltf::Buffer& src)
+ {
+ mData = src.data;
+ mName = src.name;
+ mUri = src.uri;
+ return *this;
+ }
+ };
+
+ class BufferView
+ {
+ public:
+ S32 mBuffer = INVALID_INDEX;
+ S32 mByteLength;
+ S32 mByteOffset;
+ S32 mByteStride;
+ S32 mTarget;
+ S32 mComponentType;
+
+ std::string mName;
+
+ const BufferView& operator=(const tinygltf::BufferView& src)
+ {
+ mBuffer = src.buffer;
+ mByteLength = src.byteLength;
+ mByteOffset = src.byteOffset;
+ mByteStride = src.byteStride;
+ mTarget = src.target;
+ mName = src.name;
+ return *this;
+ }
+ };
+
+ class Accessor
+ {
+ public:
+ S32 mBufferView = INVALID_INDEX;
+ S32 mByteOffset;
+ S32 mComponentType;
+ S32 mCount;
+ std::vector<double> mMax;
+ std::vector<double> mMin;
+ S32 mType;
+ bool mNormalized;
+ std::string mName;
+
+ const Accessor& operator=(const tinygltf::Accessor& src)
+ {
+ mBufferView = src.bufferView;
+ mByteOffset = src.byteOffset;
+ mComponentType = src.componentType;
+ mCount = src.count;
+ mType = src.type;
+ mNormalized = src.normalized;
+ mName = src.name;
+ mMax = src.maxValues;
+ mMin = src.maxValues;
+
+ return *this;
+ }
+ };
+
+ class Material
+ {
+ public:
+ // use LLFetchedGLTFMaterial for now, but eventually we'll want to use
+ // a more flexible GLTF material implementation instead of the fixed packing
+ // version we use for sharable GLTF material assets
+ LLPointer<LLFetchedGLTFMaterial> mMaterial;
+ std::string mName;
+
+ const Material& operator=(const tinygltf::Material& src)
+ {
+ mName = src.name;
+ return *this;
+ }
+
+ void allocateGLResources(Asset& asset)
+ {
+ // allocate material
+ mMaterial = new LLFetchedGLTFMaterial();
+ }
+ };
+
+ class Mesh
+ {
+ public:
+ std::vector<Primitive> mPrimitives;
+ std::vector<double> mWeights;
+ std::string mName;
+
+ const Mesh& operator=(const tinygltf::Mesh& src)
+ {
+ mPrimitives.resize(src.primitives.size());
+ for (U32 i = 0; i < src.primitives.size(); ++i)
+ {
+ mPrimitives[i] = src.primitives[i];
+ }
+
+ mWeights = src.weights;
+ mName = src.name;
+
+ return *this;
+ }
+
+ void allocateGLResources(Asset& asset)
+ {
+ for (auto& primitive : mPrimitives)
+ {
+ primitive.allocateGLResources(asset);
+ }
+ }
+
+ };
+
+ class Node
+ {
+ public:
+ LLMatrix4a mMatrix; //local transform
+ LLMatrix4a mRenderMatrix; //transform for rendering
+ LLMatrix4a mAssetMatrix; //transform from local to asset space
+ LLMatrix4a mAssetMatrixInv; //transform from asset to local space
+
+ std::vector<S32> mChildren;
+ S32 mMesh = INVALID_INDEX;
+ std::string mName;
+
+ const Node& operator=(const tinygltf::Node& src)
+ {
+ F32* dstMatrix = mMatrix.getF32ptr();
+
+ if (src.matrix.size() != 16)
+ {
+ mMatrix.setIdentity();
+ }
+ else
+ {
+ for (U32 i = 0; i < 16; ++i)
+ {
+ dstMatrix[i] = (F32)src.matrix[i];
+ }
+ }
+
+ mChildren = src.children;
+ mMesh = src.mesh;
+ mName = src.name;
+
+ return *this;
+ }
+
+ // Set mRenderMatrix to a transform that can be used for the current render pass
+ // modelview -- parent's render matrix
+ void updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview);
+
+ // update mAssetMatrix and mAssetMatrixInv
+ void updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix);
+
+ };
+
+ class Scene
+ {
+ public:
+ std::vector<S32> mNodes;
+ std::string mName;
+
+ const Scene& operator=(const tinygltf::Scene& src)
+ {
+ mNodes = src.nodes;
+ mName = src.name;
+
+ return *this;
+ }
+
+ void updateTransforms(Asset& asset);
+ void updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview);
+
+ };
+
+ class Texture
+ {
+ public:
+ S32 mSampler = INVALID_INDEX;
+ S32 mSource = INVALID_INDEX;
+ std::string mName;
+
+ const Texture& operator=(const tinygltf::Texture& src)
+ {
+ mSampler = src.sampler;
+ mSource = src.source;
+ mName = src.name;
+
+ return *this;
+ }
+ };
+
+ class Sampler
+ {
+ public:
+ S32 mMagFilter;
+ S32 mMinFilter;
+ S32 mWrapS;
+ S32 mWrapT;
+ std::string mName;
+
+ const Sampler& operator=(const tinygltf::Sampler& src)
+ {
+ mMagFilter = src.magFilter;
+ mMinFilter = src.minFilter;
+ mWrapS = src.wrapS;
+ mWrapT = src.wrapT;
+ mName = src.name;
+
+ return *this;
+ }
+ };
+
+ class Image
+ {
+ public:
+ std::string mName;
+ std::string mUri;
+ std::string mMimeType;
+ std::vector<U8> mData;
+ S32 mWidth;
+ S32 mHeight;
+ S32 mComponent;
+ S32 mBits;
+ LLPointer<LLViewerFetchedTexture> mTexture;
+
+ const Image& operator=(const tinygltf::Image& src)
+ {
+ mName = src.name;
+ mUri = src.uri;
+ mMimeType = src.mimeType;
+ mData = src.image;
+ mWidth = src.width;
+ mHeight = src.height;
+ mComponent = src.component;
+ mBits = src.bits;
+
+ return *this;
+ }
+
+ void allocateGLResources()
+ {
+ // allocate texture
+
+ }
+ };
+
+ // C++ representation of a GLTF Asset
+ class Asset : public LLRefCount
+ {
+ public:
+ std::vector<Scene> mScenes;
+ std::vector<Node> mNodes;
+ std::vector<Mesh> mMeshes;
+ std::vector<Material> mMaterials;
+ std::vector<Buffer> mBuffers;
+ std::vector<BufferView> mBufferViews;
+ std::vector<Texture> mTextures;
+ std::vector<Sampler> mSamplers;
+ std::vector<Image> mImages;
+ std::vector<Accessor> mAccessors;
+
+ void allocateGLResources(const std::string& filename, const tinygltf::Model& model)
+ {
+ for (auto& mesh : mMeshes)
+ {
+ mesh.allocateGLResources(*this);
+ }
+
+ for (auto& image : mImages)
+ {
+ image.allocateGLResources();
+ }
+
+ for (U32 i = 0; i < mMaterials.size(); ++i)
+ {
+ mMaterials[i].allocateGLResources(*this);
+ LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true);
+ }
+ }
+
+ // update asset-to-node and node-to-asset transforms
+ void updateTransforms();
+
+ // update node render transforms
+ void updateRenderTransforms(const LLMatrix4a& modelview);
+
+ void renderOpaque()
+ {
+ for (auto& node : mNodes)
+ {
+ if (node.mMesh != INVALID_INDEX)
+ {
+ Mesh& mesh = mMeshes[node.mMesh];
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix);
+ if (primitive.mMaterial != INVALID_INDEX)
+ {
+ Material& material = mMaterials[primitive.mMaterial];
+ material.mMaterial->bind();
+ }
+ primitive.mVertexBuffer->setBuffer();
+ if (primitive.mVertexBuffer->getNumIndices() > 0)
+ {
+ primitive.mVertexBuffer->draw(primitive.mGLMode, primitive.mVertexBuffer->getNumIndices(), 0);
+ }
+ else
+ {
+ primitive.mVertexBuffer->drawArrays(primitive.mGLMode, 0, primitive.mVertexBuffer->getNumVerts());
+ }
+ }
+ }
+ }
+ }
+
+ // return the index of the node that the line segment intersects with, or -1 if no hit
+ // input and output values must be in this asset's local coordinate frame
+ S32 lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ LLVector4a* intersection = nullptr, // return the intersection point
+ LLVector2* tex_coord = nullptr, // return the texture coordinates of the intersection point
+ LLVector4a* normal = nullptr, // return the surface normal at the intersection point
+ LLVector4a* tangent = nullptr, // return the surface tangent at the intersection point
+ S32* primitive_hitp = nullptr // return the index of the primitive that was hit
+ );
+
+ const Asset& operator=(const tinygltf::Model& src)
+ {
+ mScenes.resize(src.scenes.size());
+ for (U32 i = 0; i < src.scenes.size(); ++i)
+ {
+ mScenes[i] = src.scenes[i];
+ }
+
+ mNodes.resize(src.nodes.size());
+ for (U32 i = 0; i < src.nodes.size(); ++i)
+ {
+ mNodes[i] = src.nodes[i];
+ }
+
+ mMeshes.resize(src.meshes.size());
+ for (U32 i = 0; i < src.meshes.size(); ++i)
+ {
+ mMeshes[i] = src.meshes[i];
+ }
+
+ mMaterials.resize(src.materials.size());
+ for (U32 i = 0; i < src.materials.size(); ++i)
+ {
+ mMaterials[i] = src.materials[i];
+ }
+
+ mBuffers.resize(src.buffers.size());
+ for (U32 i = 0; i < src.buffers.size(); ++i)
+ {
+ mBuffers[i] = src.buffers[i];
+ }
+
+ mBufferViews.resize(src.bufferViews.size());
+ for (U32 i = 0; i < src.bufferViews.size(); ++i)
+ {
+ mBufferViews[i] = src.bufferViews[i];
+ }
+
+ mTextures.resize(src.textures.size());
+ for (U32 i = 0; i < src.textures.size(); ++i)
+ {
+ mTextures[i] = src.textures[i];
+ }
+
+ mSamplers.resize(src.samplers.size());
+ for (U32 i = 0; i < src.samplers.size(); ++i)
+ {
+ mSamplers[i] = src.samplers[i];
+ }
+
+ mImages.resize(src.images.size());
+ for (U32 i = 0; i < src.images.size(); ++i)
+ {
+ mImages[i] = src.images[i];
+ }
+
+ mAccessors.resize(src.accessors.size());
+ for (U32 i = 0; i < src.accessors.size(); ++i)
+ {
+ mAccessors[i] = src.accessors[i];
+ }
+
+ return *this;
+ }
+ };
+ }
+}
diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp
new file mode 100644
index 0000000000..dca3ccf706
--- /dev/null
+++ b/indra/newview/gltf/primitive.cpp
@@ -0,0 +1,480 @@
+/**
+ * @file primitive.cpp
+ * @brief LL GLTF Implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "asset.h"
+#include "../lltinygltfhelper.h"
+
+using namespace LL::GLTF;
+
+#ifndef __PRETTY_FUNCTION__
+#define __PRETTY_FUNCTION__ __FUNCSIG__
+#endif
+
+// copy one vec3 from src to dst
+template<class S, class T>
+void copyVec2(S* src, T& dst)
+{
+ LL_ERRS() << "TODO: implement " << __PRETTY_FUNCTION__ << LL_ENDL;
+}
+
+// copy one vec3 from src to dst
+template<class S, class T>
+void copyVec3(S* src, T& dst)
+{
+ LL_ERRS() << "TODO: implement " << __PRETTY_FUNCTION__ << LL_ENDL;
+}
+
+// copy one vec4 from src to dst
+template<class S, class T>
+void copyVec4(S* src, T& dst)
+{
+ LL_ERRS() << "TODO: implement " << __PRETTY_FUNCTION__ << LL_ENDL;
+}
+
+template<>
+void copyVec2<F32, LLVector2>(F32* src, LLVector2& dst)
+{
+ dst.set(src[0], src[1]);
+}
+
+template<>
+void copyVec3<F32, LLVector4a>(F32* src, LLVector4a& dst)
+{
+ dst.load3(src);
+}
+
+template<>
+void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
+{
+ dst.set(src[0], src[1], src[2], 255);
+}
+
+template<>
+void copyVec4<F32, LLVector4a>(F32* src, LLVector4a& dst)
+{
+ dst.loadua(src);
+}
+
+// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
+template<class S, class T>
+void copyVec2(S* src, LLStrider<T> dst, S32 stride, S32 count)
+{
+ for (S32 i = 0; i < count; ++i)
+ {
+ copyVec2(src, *dst);
+ dst++;
+ src = (S*)((U8*)src + stride);
+ }
+}
+
+// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
+template<class S, class T>
+void copyVec3(S* src, LLStrider<T> dst, S32 stride, S32 count)
+{
+ for (S32 i = 0; i < count; ++i)
+ {
+ copyVec3(src, *dst);
+ dst++;
+ src = (S*)((U8*)src + stride);
+ }
+}
+
+// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
+template<class S, class T>
+void copyVec4(S* src, LLStrider<T> dst, S32 stride, S32 count)
+{
+ for (S32 i = 0; i < count; ++i)
+ {
+ copyVec3(src, *dst);
+ dst++;
+ src = (S*)((U8*)src + stride);
+ }
+}
+
+template<class S, class T>
+void copyAttributeArray(Asset& asset, const Accessor& accessor, const S* src, LLStrider<T>& dst, S32 byteStride)
+{
+ if (accessor.mType == TINYGLTF_TYPE_VEC2)
+ {
+ S32 stride = byteStride == 0 ? sizeof(S) * 2 : byteStride;
+ copyVec2((S*)src, dst, stride, accessor.mCount);
+ }
+ else if (accessor.mType == TINYGLTF_TYPE_VEC3)
+ {
+ S32 stride = byteStride == 0 ? sizeof(S) * 3 : byteStride;
+ copyVec3((S*)src, dst, stride, accessor.mCount);
+ }
+ else if (accessor.mType == TINYGLTF_TYPE_VEC4)
+ {
+ S32 stride = byteStride == 0 ? sizeof(S) * 4 : byteStride;
+ copyVec4((S*)src, dst, stride, accessor.mCount);
+ }
+ else
+ {
+ LL_ERRS("GLTF") << "Unsupported accessor type" << LL_ENDL;
+ }
+}
+
+template <class T>
+void Primitive::copyAttribute(Asset& asset, S32 accessorIdx, LLStrider<T>& dst)
+{
+ const Accessor& accessor = asset.mAccessors[accessorIdx];
+ const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
+ const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
+ const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
+
+ if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_FLOAT)
+ {
+ copyAttributeArray(asset, accessor, (const F32*)src, dst, bufferView.mByteStride);
+ }
+ else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
+ {
+ copyAttributeArray(asset, accessor, (const U16*)src, dst, bufferView.mByteStride);
+ }
+ else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
+ {
+ copyAttributeArray(asset, accessor, (const U32*)src, dst, bufferView.mByteStride);
+ }
+ else
+
+ {
+ LL_ERRS() << "Unsupported component type" << LL_ENDL;
+ }
+}
+
+void Primitive::allocateGLResources(Asset& asset)
+{
+ // allocate vertex buffer
+ // We diverge from the intent of the GLTF format here to work with our existing render pipeline
+ // GLTF wants us to copy the buffer views into GPU storage as is and build render commands that source that data.
+ // For our engine, though, it's better to rearrange the buffers at load time into a layout that's more consistent.
+ // The GLTF native approach undoubtedly works well if you can count on VAOs, but VAOs perform much worse with our scenes.
+
+ // get the number of vertices
+ U32 numVertices = 0;
+ for (auto& it : mAttributes)
+ {
+ const Accessor& accessor = asset.mAccessors[it.second];
+ numVertices = accessor.mCount;
+ break;
+ }
+
+ // get the number of indices
+ U32 numIndices = 0;
+ if (mIndices != INVALID_INDEX)
+ {
+ const Accessor& accessor = asset.mAccessors[mIndices];
+ numIndices = accessor.mCount;
+ }
+
+ // create vertex buffer
+ mVertexBuffer = new LLVertexBuffer(ATTRIBUTE_MASK);
+ mVertexBuffer->allocateBuffer(numVertices, numIndices);
+
+ bool needs_color = true;
+ bool needs_texcoord = true;
+ bool needs_normal = true;
+ bool needs_tangent = true;
+
+ // load vertex data
+ for (auto& it : mAttributes)
+ {
+ const std::string& attribName = it.first;
+
+ // load vertex data
+ if (attribName == "POSITION")
+ {
+ // load position data
+ LLStrider<LLVector4a> dst;
+ mVertexBuffer->getVertexStrider(dst);
+
+ copyAttribute(asset, it.second, dst);
+ }
+ else if (attribName == "NORMAL")
+ {
+ needs_normal = false;
+ // load normal data
+ LLStrider<LLVector4a> dst;
+ mVertexBuffer->getNormalStrider(dst);
+
+ copyAttribute(asset, it.second, dst);
+ }
+ else if (attribName == "TANGENT")
+ {
+ needs_tangent = false;
+ // load tangent data
+
+ LLStrider<LLVector4a> dst;
+ mVertexBuffer->getTangentStrider(dst);
+
+ copyAttribute(asset, it.second, dst);
+ }
+ else if (attribName == "COLOR_0")
+ {
+ needs_color = false;
+ // load color data
+
+ LLStrider<LLColor4U> dst;
+ mVertexBuffer->getColorStrider(dst);
+
+ copyAttribute(asset, it.second, dst);
+ }
+ else if (attribName == "TEXCOORD_0")
+ {
+ needs_texcoord = false;
+ // load texcoord data
+ LLStrider<LLVector2> dst;
+ mVertexBuffer->getTexCoord0Strider(dst);
+
+ LLStrider<LLVector2> tc = dst;
+ copyAttribute(asset, it.second, dst);
+
+ // convert to OpenGL coordinate space
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ tc->mV[1] = 1.0f - tc->mV[1];;
+ tc++;
+ }
+ }
+ }
+
+ // copy index buffer
+ if (mIndices != INVALID_INDEX)
+ {
+ const Accessor& accessor = asset.mAccessors[mIndices];
+ const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
+ const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
+
+ const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
+
+ LLStrider<U16> dst;
+ mVertexBuffer->getIndexStrider(dst);
+ mIndexArray.resize(numIndices);
+
+ if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
+ {
+ for (U32 i = 0; i < numIndices; ++i)
+ {
+ *(dst++) = (U16) * (U32*)src;
+ src += sizeof(U32);
+ }
+ }
+ else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
+ {
+ for (U32 i = 0; i < numIndices; ++i)
+ {
+ *(dst++) = *(U16*)src;
+ src += sizeof(U16);
+ }
+ }
+ else
+ {
+ LL_ERRS("GLTF") << "Unsupported component type for indices" << LL_ENDL;
+ }
+
+ U16* idx = (U16*)mVertexBuffer->getMappedIndices();
+ for (U32 i = 0; i < numIndices; ++i)
+ {
+ mIndexArray[i] = idx[i];
+ }
+ }
+
+ // fill in default values for missing attributes
+ if (needs_color)
+ { // set default color
+ LLStrider<LLColor4U> dst;
+ mVertexBuffer->getColorStrider(dst);
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ *(dst++) = LLColor4U(255, 255, 255, 255);
+ }
+ }
+
+ if (needs_texcoord)
+ { // set default texcoord
+ LLStrider<LLVector2> dst;
+ mVertexBuffer->getTexCoord0Strider(dst);
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ *(dst++) = LLVector2(0.0f, 0.0f);
+ }
+ }
+
+ if (needs_normal)
+ { // set default normal
+ LLStrider<LLVector4a> dst;
+ mVertexBuffer->getNormalStrider(dst);
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ *(dst++) = LLVector4a(0.0f, 0.0f, 1.0f, 0.0f);
+ }
+ }
+
+ if (needs_tangent)
+ { // TODO: generate tangents if needed
+ LLStrider<LLVector4a> dst;
+ mVertexBuffer->getTangentStrider(dst);
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ *(dst++) = LLVector4a(1.0f, 0.0f, 0.0f, 1.0f);
+ }
+ }
+
+ mPositions.resize(numVertices);
+ mTexCoords.resize(numVertices);
+ mNormals.resize(numVertices);
+ mTangents.resize(numVertices);
+
+ LLVector4a* pos = (LLVector4a*)(mVertexBuffer->getMappedData() + mVertexBuffer->getOffset(LLVertexBuffer::TYPE_VERTEX));
+ LLVector2* tc = (LLVector2*)(mVertexBuffer->getMappedData() + mVertexBuffer->getOffset(LLVertexBuffer::TYPE_TEXCOORD0));
+ LLVector4a* norm = (LLVector4a*)(mVertexBuffer->getMappedData() + mVertexBuffer->getOffset(LLVertexBuffer::TYPE_NORMAL));
+ LLVector4a* tangent = (LLVector4a*)(mVertexBuffer->getMappedData() + mVertexBuffer->getOffset(LLVertexBuffer::TYPE_TANGENT));
+ for (U32 i = 0; i < numVertices; ++i)
+ {
+ mPositions[i] = pos[i];
+ mTexCoords[i] = tc[i];
+ mNormals[i] = norm[i];
+ mTangents[i] = tangent[i];
+ }
+ createOctree();
+
+ mVertexBuffer->unmapBuffer();
+}
+
+void Primitive::createOctree()
+{
+ // create octree
+ mOctree = new LLVolumeOctree();
+
+ if (mMode == TINYGLTF_MODE_TRIANGLES)
+ {
+ F32 scaler = 0.25f;
+
+ const U32 num_triangles = mVertexBuffer->getNumIndices() / 3;
+ // Initialize all the triangles we need
+ mOctreeTriangles.resize(num_triangles);
+
+ LLVector4a* pos = (LLVector4a*)(mVertexBuffer->getMappedData() + mVertexBuffer->getOffset(LLVertexBuffer::TYPE_VERTEX));
+ U16* indices = (U16*)mVertexBuffer->getMappedIndices();
+
+ for (U32 triangle_index = 0; triangle_index < num_triangles; ++triangle_index)
+ { //for each triangle
+ const U32 index = triangle_index * 3;
+ LLVolumeTriangle* tri = &mOctreeTriangles[triangle_index];
+ const LLVector4a& v0 = pos[indices[index]];
+ const LLVector4a& v1 = pos[indices[index + 1]];
+ const LLVector4a& v2 = pos[indices[index + 2]];
+
+ //store pointers to vertex data
+ tri->mV[0] = &v0;
+ tri->mV[1] = &v1;
+ tri->mV[2] = &v2;
+
+ //store indices
+ tri->mIndex[0] = indices[index];
+ tri->mIndex[1] = indices[index + 1];
+ tri->mIndex[2] = indices[index + 2];
+
+ //get minimum point
+ LLVector4a min = v0;
+ min.setMin(min, v1);
+ min.setMin(min, v2);
+
+ //get maximum point
+ LLVector4a max = v0;
+ max.setMax(max, v1);
+ max.setMax(max, v2);
+
+ //compute center
+ LLVector4a center;
+ center.setAdd(min, max);
+ center.mul(0.5f);
+
+ tri->mPositionGroup = center;
+
+ //compute "radius"
+ LLVector4a size;
+ size.setSub(max, min);
+
+ tri->mRadius = size.getLength3().getF32() * scaler;
+
+ //insert
+ mOctree->insert(tri);
+ }
+ }
+ else
+ {
+ LL_ERRS() << "Unsupported Primitive mode" << LL_ENDL;
+ }
+
+ //remove unneeded octree layers
+ while (!mOctree->balance()) {}
+
+ //calculate AABB for each node
+ LLVolumeOctreeRebound rebound;
+ rebound.traverse(mOctree);
+}
+
+const LLVolumeTriangle* Primitive::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent_out)
+{
+ if (mOctree.isNull())
+ {
+ return nullptr;
+ }
+
+ LLVector4a dir;
+ dir.setSub(end, start);
+
+ F32 closest_t = 2.f; // must be larger than 1
+
+ //create a proxy LLVolumeFace for the raycast
+ LLVolumeFace face;
+ face.mPositions = mPositions.data();
+ face.mTexCoords = mTexCoords.data();
+ face.mNormals = mNormals.data();
+ face.mTangents = mTangents.data();
+ face.mIndices = mIndexArray.data();
+
+ face.mNumIndices = mIndexArray.size();
+ face.mNumVertices = mPositions.size();
+
+ LLOctreeTriangleRayIntersect intersect(start, dir, &face, &closest_t, intersection, tex_coord, normal, tangent_out);
+ intersect.traverse(mOctree);
+
+ // null out proxy data so it doesn't get freed
+ face.mPositions = face.mNormals = face.mTangents = nullptr;
+ face.mIndices = nullptr;
+ face.mTexCoords = nullptr;
+
+ return intersect.mHitTriangle;
+}
+
+Primitive::~Primitive()
+{
+ mOctree = nullptr;
+}
+
diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h
new file mode 100644
index 0000000000..7c47d9dac5
--- /dev/null
+++ b/indra/newview/gltf/primitive.h
@@ -0,0 +1,140 @@
+#pragma once
+
+/**
+ * @file primitive.h
+ * @brief LL GLTF Implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llvertexbuffer.h"
+#include "llvolumeoctree.h"
+
+// LL GLTF Implementation
+namespace LL
+{
+ namespace GLTF
+ {
+ class Asset;
+
+ constexpr U32 ATTRIBUTE_MASK =
+ LLVertexBuffer::MAP_VERTEX |
+ LLVertexBuffer::MAP_NORMAL |
+ LLVertexBuffer::MAP_TEXCOORD0 |
+ LLVertexBuffer::MAP_TANGENT |
+ LLVertexBuffer::MAP_COLOR;
+
+ class Primitive
+ {
+ public:
+ ~Primitive();
+
+ // GPU copy of mesh data
+ LLPointer<LLVertexBuffer> mVertexBuffer;
+
+ // CPU copy of mesh data
+ std::vector<LLVector2> mTexCoords;
+ std::vector<LLVector4a> mNormals;
+ std::vector<LLVector4a> mTangents;
+ std::vector<LLVector4a> mPositions;
+ std::vector<U16> mIndexArray;
+
+ // raycast acceleration structure
+ LLPointer<LLVolumeOctree> mOctree;
+ std::vector<LLVolumeTriangle> mOctreeTriangles;
+
+ S32 mMaterial = -1;
+ U32 mMode = TINYGLTF_MODE_TRIANGLES; // default to triangles
+ U32 mGLMode = LLRender::TRIANGLES;
+ S32 mIndices = -1;
+ std::unordered_map<std::string, int> mAttributes;
+
+ // copy the attribute in the given BufferView to the given destination
+ // assumes destination has enough storage for the attribute
+ template<class T>
+ void copyAttribute(Asset& asset, S32 bufferViewIdx, LLStrider<T>& dst);
+
+ // create octree based on vertex buffer
+ // must be called before buffer is unmapped and after buffer is populated with good data
+ void createOctree();
+
+ //get the LLVolumeTriangle that intersects with the given line segment at the point
+ //closest to start. Moves end to the point of intersection. Returns nullptr if no intersection.
+ //Line segment must be in the same coordinate frame as this Primitive
+ const LLVolumeTriangle* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ LLVector4a* intersection = NULL, // return the intersection point
+ LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point
+ LLVector4a* normal = NULL, // return the surface normal at the intersection point
+ LLVector4a* tangent = NULL // return the surface tangent at the intersection point
+ );
+
+ const Primitive& operator=(const tinygltf::Primitive& src)
+ {
+ // load material
+ mMaterial = src.material;
+
+ // load mode
+ mMode = src.mode;
+
+ // load indices
+ mIndices = src.indices;
+
+ // load attributes
+ for (auto& it : src.attributes)
+ {
+ mAttributes[it.first] = it.second;
+ }
+
+ switch (mMode)
+ {
+ case TINYGLTF_MODE_POINTS:
+ mGLMode = LLRender::POINTS;
+ break;
+ case TINYGLTF_MODE_LINE:
+ mGLMode = LLRender::LINES;
+ break;
+ case TINYGLTF_MODE_LINE_LOOP:
+ mGLMode = LLRender::LINE_LOOP;
+ break;
+ case TINYGLTF_MODE_LINE_STRIP:
+ mGLMode = LLRender::LINE_STRIP;
+ break;
+ case TINYGLTF_MODE_TRIANGLES:
+ mGLMode = LLRender::TRIANGLES;
+ break;
+ case TINYGLTF_MODE_TRIANGLE_STRIP:
+ mGLMode = LLRender::TRIANGLE_STRIP;
+ break;
+ case TINYGLTF_MODE_TRIANGLE_FAN:
+ mGLMode = LLRender::TRIANGLE_FAN;
+ break;
+ default:
+ mGLMode = GL_TRIANGLES;
+ }
+
+ return *this;
+ }
+
+ void allocateGLResources(Asset& asset);
+ };
+ }
+}
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
new file mode 100644
index 0000000000..429c9118f8
--- /dev/null
+++ b/indra/newview/gltfscenemanager.cpp
@@ -0,0 +1,469 @@
+/**
+ * @file gltfscenemanager.cpp
+ * @brief Builds menus out of items.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "gltfscenemanager.h"
+#include "llviewermenufile.h"
+#include "llappviewer.h"
+#include "lltinygltfhelper.h"
+#include "llvertexbuffer.h"
+#include "llselectmgr.h"
+#include "llagent.h"
+#include "llnotificationsutil.h"
+#include "llvoavatarself.h"
+#include "llvolumeoctree.h"
+#include "gltf/asset.h"
+#include "pipeline.h"
+#include "llviewershadermgr.h"
+
+
+using namespace LL;
+
+// temporary location of LL GLTF Implementation
+using namespace LL::GLTF;
+
+void GLTFSceneManager::load()
+{
+ LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject();
+
+ if (obj)
+ {
+ // Load a scene from disk
+ LLFilePickerReplyThread::startPicker(
+ [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter)
+ {
+ if (LLAppViewer::instance()->quitRequested())
+ {
+ return;
+ }
+ if (filenames.size() > 0)
+ {
+ GLTFSceneManager::instance().load(filenames[0]);
+ }
+ },
+ LLFilePicker::FFLOAD_GLTF,
+ true);
+ }
+ else
+ {
+ LLNotificationsUtil::add("GLTFPreviewSelection");
+ }
+}
+
+void GLTFSceneManager::load(const std::string& filename)
+{
+ tinygltf::Model model;
+ LLTinyGLTFHelper::loadModel(filename, model);
+
+ LLPointer<Asset> asset = new Asset();
+ *asset = model;
+
+ asset->allocateGLResources(filename, model);
+ asset->updateTransforms();
+
+ // hang the asset off the currently selected object, or off of the avatar if no object is selected
+ LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject();
+
+ if (obj)
+ { // assign to self avatar
+ obj->mGLTFAsset = asset;
+ mObjects.push_back(obj);
+ }
+}
+
+GLTFSceneManager::~GLTFSceneManager()
+{
+ mObjects.clear();
+}
+
+LLMatrix4a getAssetToAgentTransform(LLViewerObject* obj)
+{
+ LLMatrix4 root;
+ root.initScale(obj->getScale());
+ root.rotate(obj->getRenderRotation());
+ root.translate(obj->getPositionAgent());
+
+ LLMatrix4a mat;
+ mat.loadu((F32*) root.mMatrix);
+
+ return mat;
+}
+
+LLMatrix4a getAgentToAssetTransform(LLViewerObject* obj)
+{
+ LLMatrix4 root;
+ LLVector3 scale = obj->getScale();
+ scale.mV[0] = 1.f / scale.mV[0];
+ scale.mV[1] = 1.f / scale.mV[1];
+ scale.mV[2] = 1.f / scale.mV[2];
+
+ root.translate(-obj->getPositionAgent());
+ root.rotate(~obj->getRenderRotation());
+
+ LLMatrix4 scale_mat;
+ scale_mat.initScale(scale);
+
+ root *= scale_mat;
+
+
+ LLMatrix4a mat;
+ mat.loadu((F32*) root.mMatrix);
+
+ return mat;
+}
+
+void GLTFSceneManager::renderOpaque()
+{
+ // for debugging, just render the whole scene as opaque
+ // by traversing the whole scenegraph
+ // Assumes camera transform is already set and
+ // appropriate shader is already bound
+
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+
+ for (U32 i = 0; i < mObjects.size(); ++i)
+ {
+ if (mObjects[i]->isDead() || mObjects[i]->mGLTFAsset == nullptr)
+ {
+ mObjects.erase(mObjects.begin() + i);
+ --i;
+ continue;
+ }
+
+ Asset* asset = mObjects[i]->mGLTFAsset;
+
+ gGL.pushMatrix();
+
+ LLMatrix4a mat = getAssetToAgentTransform(mObjects[i]);
+
+ LLMatrix4a modelview;
+ modelview.loadu(gGLModelView);
+
+ matMul(mat, modelview, modelview);
+
+ asset->updateRenderTransforms(modelview);
+ asset->renderOpaque();
+
+ gGL.popMatrix();
+ }
+}
+
+LLMatrix4a inverse(const LLMatrix4a& mat)
+{
+ glh::matrix4f m((F32*)mat.mMatrix);
+ m = m.inverse();
+ LLMatrix4a ret;
+ ret.loadu(m.m);
+ return ret;
+}
+
+bool GLTFSceneManager::lineSegmentIntersect(LLVOVolume* obj, Asset* asset, const LLVector4a& start, const LLVector4a& end, S32 face, BOOL pick_transparent, BOOL pick_rigged, BOOL pick_unselectable, S32* node_hit, S32* primitive_hit,
+ LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent)
+
+{
+ // line segment intersection test
+ // start and end should be in agent space
+ // volume space and asset space should be the same coordinate frame
+ // results should be transformed back to agent space
+
+ bool ret = false;
+
+ LLVector4a local_start;
+ LLVector4a local_end;
+
+ LLMatrix4a asset_to_agent = getAssetToAgentTransform(obj);
+ LLMatrix4a agent_to_asset = inverse(asset_to_agent);
+
+ agent_to_asset.affineTransform(start, local_start);
+ agent_to_asset.affineTransform(end, local_end);
+
+ LLVector4a p;
+ LLVector4a n;
+ LLVector2 tc;
+ LLVector4a tn;
+
+ if (intersection != NULL)
+ {
+ p = *intersection;
+ }
+
+ if (tex_coord != NULL)
+ {
+ tc = *tex_coord;
+ }
+
+ if (normal != NULL)
+ {
+ n = *normal;
+ }
+
+ if (tangent != NULL)
+ {
+ tn = *tangent;
+ }
+
+ S32 hit_node_index = asset->lineSegmentIntersect(local_start, local_end, &p, &tc, &n, &tn, primitive_hit);
+
+ if (hit_node_index >= 0)
+ {
+ local_end = p;
+ if (node_hit != NULL)
+ {
+ *node_hit = hit_node_index;
+ }
+
+ if (intersection != NULL)
+ {
+ asset_to_agent.affineTransform(p, *intersection);
+ }
+
+ if (normal != NULL)
+ {
+ LLVector3 v_n(n.getF32ptr());
+ normal->load3(obj->volumeDirectionToAgent(v_n).mV);
+ (*normal).normalize3fast();
+ }
+
+ if (tangent != NULL)
+ {
+ LLVector3 v_tn(tn.getF32ptr());
+
+ LLVector4a trans_tangent;
+ trans_tangent.load3(obj->volumeDirectionToAgent(v_tn).mV);
+
+ LLVector4Logical mask;
+ mask.clear();
+ mask.setElement<3>();
+
+ tangent->setSelectWithMask(mask, tn, trans_tangent);
+ (*tangent).normalize3fast();
+ }
+
+ if (tex_coord != NULL)
+ {
+ *tex_coord = tc;
+ }
+
+ ret = true;
+ }
+
+ return ret;
+}
+
+LLDrawable* GLTFSceneManager::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ BOOL pick_transparent,
+ BOOL pick_rigged,
+ BOOL pick_unselectable,
+ BOOL pick_reflection_probe,
+ S32* node_hit, // return the index of the node that was hit
+ S32* primitive_hit, // return the index of the primitive that was hit
+ LLVector4a* intersection, // return the intersection point
+ LLVector2* tex_coord, // return the texture coordinates of the intersection point
+ LLVector4a* normal, // return the surface normal at the intersection point
+ LLVector4a* tangent) // return the surface tangent at the intersection point
+{
+ LLDrawable* drawable = nullptr;
+
+ LLVector4a local_end = end;
+ LLVector4a position;
+
+ for (U32 i = 0; i < mObjects.size(); ++i)
+ {
+ if (mObjects[i]->isDead() || mObjects[i]->mGLTFAsset == nullptr || !mObjects[i]->getVolume())
+ {
+ mObjects.erase(mObjects.begin() + i);
+ --i;
+ continue;
+ }
+
+ // temporary debug -- always double check objects that have GLTF scenes hanging off of them even if the ray doesn't intersect the object bounds
+ if (lineSegmentIntersect((LLVOVolume*) mObjects[i].get(), mObjects[i]->mGLTFAsset, start, local_end, -1, pick_transparent, pick_rigged, pick_unselectable, node_hit, primitive_hit, &position, tex_coord, normal, tangent))
+ {
+ local_end = position;
+ if (intersection)
+ {
+ *intersection = position;
+ }
+ drawable = mObjects[i]->mDrawable;
+ }
+ }
+
+ return drawable;
+}
+
+void drawBoxOutline(const LLVector4a& pos, const LLVector4a& size);
+
+extern LLVector4a gDebugRaycastStart;
+extern LLVector4a gDebugRaycastEnd;
+
+void renderOctreeRaycast(const LLVector4a& start, const LLVector4a& end, const LLVolumeOctree* octree);
+
+void renderAssetDebug(LLViewerObject* obj, Asset* asset)
+{
+ // render debug
+ // assumes appropriate shader is already bound
+ // assumes modelview matrix is already set
+
+ gGL.pushMatrix();
+
+ // get raycast in asset space
+ LLMatrix4a asset_to_agent = getAssetToAgentTransform(obj);
+ LLMatrix4a agent_to_asset = getAgentToAssetTransform(obj);
+
+ LLVector4a start;
+ LLVector4a end;
+
+ agent_to_asset.affineTransform(gDebugRaycastStart, start);
+ agent_to_asset.affineTransform(gDebugRaycastEnd, end);
+
+
+ for (auto& node : asset->mNodes)
+ {
+ Mesh& mesh = asset->mMeshes[node.mMesh];
+
+ if (node.mMesh != INVALID_INDEX)
+ {
+ gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix);
+
+ // draw bounding box of mesh primitives
+ if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES))
+ {
+ gGL.color3f(0.f, 1.f, 1.f);
+
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ auto* listener = (LLVolumeOctreeListener*) primitive.mOctree->getListener(0);
+
+ LLVector4a center = listener->mBounds[0];
+ LLVector4a size = listener->mBounds[1];
+
+ drawBoxOutline(center, size);
+ }
+ }
+
+#if 0
+ if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST))
+ {
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+
+ // convert raycast to node local space
+ LLVector4a local_start;
+ LLVector4a local_end;
+
+ node.mAssetMatrixInv.affineTransform(start, local_start);
+ node.mAssetMatrixInv.affineTransform(end, local_end);
+
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mOctree.notNull())
+ {
+ renderOctreeRaycast(local_start, local_end, primitive.mOctree);
+ }
+ }
+
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+#endif
+ }
+ }
+
+ gGL.popMatrix();
+}
+
+void GLTFSceneManager::renderDebug()
+{
+ if (!gPipeline.hasRenderDebugMask(
+ LLPipeline::RENDER_DEBUG_BBOXES |
+ LLPipeline::RENDER_DEBUG_RAYCAST))
+ {
+ return;
+ }
+
+ gDebugProgram.bind();
+
+ LLGLDisable cullface(GL_CULL_FACE);
+ LLGLEnable blend(GL_BLEND);
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ gPipeline.disableLights();
+
+ for (auto& obj : mObjects)
+ {
+ if (obj->isDead() || obj->mGLTFAsset == nullptr)
+ {
+ continue;
+ }
+
+ Asset* asset = obj->mGLTFAsset;
+
+
+ LLMatrix4a mat = getAssetToAgentTransform(obj);
+
+ LLMatrix4a modelview;
+ modelview.loadu(gGLModelView);
+
+ matMul(mat, modelview, modelview);
+
+ asset->updateRenderTransforms(modelview);
+ renderAssetDebug(obj, asset);
+ }
+
+ if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST))
+ {
+ S32 node_hit = -1;
+ S32 primitive_hit = -1;
+ LLVector4a intersection;
+
+ LLDrawable* drawable = lineSegmentIntersect(gDebugRaycastStart, gDebugRaycastEnd, TRUE, TRUE, TRUE, TRUE, &node_hit, &primitive_hit, &intersection, nullptr, nullptr, nullptr);
+
+ if (drawable)
+ {
+ gGL.pushMatrix();
+ Asset* asset = drawable->getVObj()->mGLTFAsset;
+ Node* node = &asset->mNodes[node_hit];
+ Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit];
+
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ gGL.color3f(1, 0, 1);
+ drawBoxOutline(intersection, LLVector4a(0.1f, 0.1f, 0.1f, 0.f));
+
+ gGL.loadMatrix((F32*) node->mRenderMatrix.mMatrix);
+
+
+
+ auto* listener = (LLVolumeOctreeListener*) primitive->mOctree->getListener(0);
+ drawBoxOutline(listener->mBounds[0], listener->mBounds[1]);
+
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ gGL.popMatrix();
+ }
+ }
+ gDebugProgram.unbind();
+}
+
diff --git a/indra/newview/gltfscenemanager.h b/indra/newview/gltfscenemanager.h
new file mode 100644
index 0000000000..50e1dd93da
--- /dev/null
+++ b/indra/newview/gltfscenemanager.h
@@ -0,0 +1,64 @@
+#pragma once
+
+/**
+ * @file gltfscenemanager.h
+ * @brief Builds menus out of items.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llsingleton.h"
+#include "llviewerobject.h"
+
+namespace LL
+{
+ class GLTFSceneManager : public LLSimpleton<GLTFSceneManager>
+ {
+ public:
+ ~GLTFSceneManager();
+ // load GLTF file from disk
+ void load(); // open filepicker to choose asset
+ void load(const std::string& filename); // load asset from filename
+ void renderOpaque();
+
+ LLDrawable* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
+ BOOL pick_transparent,
+ BOOL pick_rigged,
+ BOOL pick_unselectable,
+ BOOL pick_reflection_probe,
+ S32* node_hit, // return the index of the node that was hit
+ S32* primitive_hit, // return the index of the primitive that was hit
+ LLVector4a* intersection, // return the intersection point
+ LLVector2* tex_coord, // return the texture coordinates of the intersection point
+ LLVector4a* normal, // return the surface normal at the intersection point
+ LLVector4a* tangent); // return the surface tangent at the intersection point
+
+ bool lineSegmentIntersect(LLVOVolume* obj, GLTF::Asset* asset, const LLVector4a& start, const LLVector4a& end, S32 face, BOOL pick_transparent, BOOL pick_rigged, BOOL pick_unselectable, S32* face_hitp, S32* primitive_hitp,
+ LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent);
+
+ void renderDebug();
+
+ std::vector<LLPointer<LLViewerObject>> mObjects;
+ };
+}
+
+
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 31d9804fe3..46b95601af 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -240,6 +240,8 @@
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
#include "llviewerassetstats.h"
+#include "gltfscenemanager.h"
+
#include "workqueue.h"
using namespace LL;
@@ -1280,6 +1282,8 @@ bool LLAppViewer::init()
LLWorld::createInstance();
LLSelectMgr::createInstance();
LLViewerCamera::createInstance();
+ LL::GLTFSceneManager::createInstance();
+
#if LL_WINDOWS
if (!mSecondInstance)
@@ -2155,7 +2159,7 @@ bool LLAppViewer::cleanup()
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
-
+ LL::GLTFSceneManager::deleteSingleton();
LLEnvironment::deleteSingleton();
LLSelectMgr::deleteSingleton();
LLViewerEventRecorder::deleteSingleton();
diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp
index 86b790e2c5..a32382af92 100644
--- a/indra/newview/lldrawpoolpbropaque.cpp
+++ b/indra/newview/lldrawpoolpbropaque.cpp
@@ -30,6 +30,7 @@
#include "lldrawpoolpbropaque.h"
#include "llviewershadermgr.h"
#include "pipeline.h"
+#include "gltfscenemanager.h"
LLDrawPoolGLTFPBR::LLDrawPoolGLTFPBR(U32 type) :
LLRenderPass(type)
@@ -54,8 +55,11 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass)
llassert(!LLPipeline::sRenderingHUDs);
gDeferredPBROpaqueProgram.bind();
+
+ LL::GLTFSceneManager::instance().renderOpaque();
pushGLTFBatches(mRenderType);
+
gDeferredPBROpaqueProgram.bind(true);
pushRiggedGLTFBatches(mRenderType + 1);
}
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index fec550ae50..f3b5bb0565 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -2866,10 +2866,8 @@ void renderLights(LLDrawable* drawablep)
class LLRenderOctreeRaycast : public LLOctreeTriangleRayIntersect
{
public:
-
-
LLRenderOctreeRaycast(const LLVector4a& start, const LLVector4a& dir, F32* closest_t)
- : LLOctreeTriangleRayIntersect(start, dir, NULL, closest_t, NULL, NULL, NULL, NULL)
+ : LLOctreeTriangleRayIntersect(start, dir, nullptr, closest_t, NULL, NULL, NULL, NULL)
{
}
@@ -2893,7 +2891,7 @@ public:
size.set(vl->mBounds[1].getF32ptr());
}
- drawBoxOutline(center, size);
+ drawBoxOutline(center, size);
for (U32 i = 0; i < 2; i++)
{
@@ -2937,6 +2935,13 @@ public:
}
};
+void renderOctreeRaycast(const LLVector4a& start, const LLVector4a& end, const LLVolumeOctree* octree)
+{
+ F32 t = 1.f;
+ LLRenderOctreeRaycast render(start, end, &t);
+ render.traverse(octree);
+}
+
void renderRaycast(LLDrawable* drawablep)
{
if (drawablep->getNumFaces())
@@ -2994,29 +2999,22 @@ void renderRaycast(LLDrawable* drawablep)
dir.setSub(end, start);
gGL.flush();
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
{
//render face positions
- LLVertexBuffer::unbind();
- gGL.diffuseColor4f(0,1,1,0.5f);
- glVertexPointer(3, GL_FLOAT, sizeof(LLVector4a), face.mPositions);
- gGL.syncMatrices();
- glDrawElements(GL_TRIANGLES, face.mNumIndices, GL_UNSIGNED_SHORT, face.mIndices);
+ //gGL.diffuseColor4f(0,1,1,0.5f);
+ //LLVertexBuffer::drawElements(LLRender::TRIANGLES, face.mPositions, nullptr, face.mNumIndices, face.mIndices);
}
if (!volume->isUnique())
{
- F32 t = 1.f;
-
if (!face.getOctree())
{
((LLVolumeFace*) &face)->createOctree();
}
- LLRenderOctreeRaycast render(start, dir, &t);
-
- render.traverse(face.getOctree());
+ renderOctreeRaycast(start, end, face.getOctree());
}
gGL.popMatrix();
diff --git a/indra/newview/lltinygltfhelper.cpp b/indra/newview/lltinygltfhelper.cpp
index 5b75db37d0..40e6f285a8 100644
--- a/indra/newview/lltinygltfhelper.cpp
+++ b/indra/newview/lltinygltfhelper.cpp
@@ -147,7 +147,7 @@ const tinygltf::Image * LLTinyGLTFHelper::getImageFromTextureIndex(const tinyglt
return nullptr;
}
-LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tinygltf::Model & model, S32 texture_index, std::string & name)
+LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tinygltf::Model & model, S32 texture_index, std::string & name, bool flip)
{
const tinygltf::Image* image = getImageFromTextureIndex(model, texture_index);
LLImageRaw* rawImage = nullptr;
@@ -159,14 +159,17 @@ LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tiny
{
name = image->name;
rawImage = new LLImageRaw(&image->image[0], image->width, image->height, image->component);
- rawImage->verticalFlip();
+ if (flip)
+ {
+ rawImage->verticalFlip();
+ }
rawImage->optimizeAwayAlpha();
}
return rawImage;
}
-LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tinygltf::Model & model, S32 texture_index)
+LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tinygltf::Model & model, S32 texture_index, bool flip)
{
const tinygltf::Image* image = getImageFromTextureIndex(model, texture_index);
LLImageRaw* rawImage = nullptr;
@@ -177,7 +180,10 @@ LLImageRaw * LLTinyGLTFHelper::getTexture(const std::string & folder, const tiny
image->component <= 4)
{
rawImage = new LLImageRaw(&image->image[0], image->width, image->height, image->component);
- rawImage->verticalFlip();
+ if (flip)
+ {
+ rawImage->verticalFlip();
+ }
rawImage->optimizeAwayAlpha();
}
@@ -237,7 +243,8 @@ bool LLTinyGLTFHelper::getMaterialFromModel(
const tinygltf::Model& model_in,
S32 mat_index,
LLFetchedGLTFMaterial* material,
- std::string& material_name)
+ std::string& material_name,
+ bool flip)
{
llassert(material);
@@ -256,18 +263,18 @@ bool LLTinyGLTFHelper::getMaterialFromModel(
material_name = material_in.name;
// get base color texture
- LLPointer<LLImageRaw> base_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index);
+ LLPointer<LLImageRaw> base_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index, flip);
// get normal map
- LLPointer<LLImageRaw> normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index);
+ LLPointer<LLImageRaw> normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index, flip);
// get metallic-roughness texture
- LLPointer<LLImageRaw> mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index);
+ LLPointer<LLImageRaw> mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index, flip);
// get emissive texture
- LLPointer<LLImageRaw> emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index);
+ LLPointer<LLImageRaw> emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index, flip);
// get occlusion map if needed
LLPointer<LLImageRaw> occlusion_img;
if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index)
{
- occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index);
+ occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index, flip);
}
LLPointer<LLViewerFetchedTexture> base_color_tex;
diff --git a/indra/newview/lltinygltfhelper.h b/indra/newview/lltinygltfhelper.h
index 256f6c854f..da505b41e9 100644
--- a/indra/newview/lltinygltfhelper.h
+++ b/indra/newview/lltinygltfhelper.h
@@ -38,10 +38,8 @@ namespace LLTinyGLTFHelper
{
LLColor4 getColor(const std::vector<double>& in);
const tinygltf::Image* getImageFromTextureIndex(const tinygltf::Model& model, S32 texture_index);
- LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index, std::string& name);
- LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index);
-
- LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index);
+ LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index, std::string& name, bool flip = true);
+ LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index, bool flip = true);
bool loadModel(const std::string& filename, tinygltf::Model& model_out);
@@ -50,7 +48,8 @@ namespace LLTinyGLTFHelper
const tinygltf::Model& model,
S32 mat_index,
LLFetchedGLTFMaterial* material,
- std::string& material_name);
+ std::string& material_name,
+ bool flip = true);
void initFetchedTextures(tinygltf::Material& material,
LLPointer<LLImageRaw>& base_color_img,
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 37d02cd911..4cf948f20d 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -140,6 +140,7 @@
#include <boost/algorithm/string.hpp>
#include "llcleanup.h"
#include "llviewershadermgr.h"
+#include "gltfscenemanager.h"
using namespace LLAvatarAppearanceDefines;
@@ -7921,6 +7922,17 @@ class LLAdvancedClickHDRIPreview: public view_listener_t
}
};
+
+class LLAdvancedClickGLTFScenePreview : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ // open personal lighting floater when previewing an HDRI (keeps HDRI from implicitly unloading when opening build tools)
+ LL::GLTFSceneManager::instance().load();
+ return true;
+ }
+};
+
// these are used in the gl menus to set control values that require shader recompilation
class LLToggleShaderControl : public view_listener_t
{
@@ -9568,6 +9580,7 @@ void initialize_menus()
view_listener_t::addMenu(new LLAdvancedClickRenderProfile(), "Advanced.ClickRenderProfile");
view_listener_t::addMenu(new LLAdvancedClickRenderBenchmark(), "Advanced.ClickRenderBenchmark");
view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview");
+ view_listener_t::addMenu(new LLAdvancedClickGLTFScenePreview(), "Advanced.ClickGLTFScenePreview");
view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache");
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index 0c4d958aed..3d6903d177 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -45,6 +45,7 @@
#include "llbbox.h"
#include "llrigginginfo.h"
#include "llreflectionmap.h"
+#include "gltf/asset.h"
class LLAgent; // TODO: Get rid of this.
class LLAudioSource;
@@ -722,6 +723,8 @@ public:
F32 mPhysicsDensity;
F32 mPhysicsRestitution;
+ // Associated GLTF Asset
+ LLPointer<LL::GLTF::Asset> mGLTFAsset;
// Pipeline classes
LLPointer<LLDrawable> mDrawable;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index d4390c46cb..bcecd3a7fe 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -4606,7 +4606,7 @@ LLVector3 LLVOVolume::volumeDirectionToAgent(const LLVector3& dir) const
BOOL LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, BOOL pick_transparent, BOOL pick_rigged, BOOL pick_unselectable, S32 *face_hitp,
- LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent)
+ LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent)
{
if (!mbCanSelect
@@ -4814,7 +4814,7 @@ BOOL LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a&
}
}
}
-
+
return ret;
}
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 85f4ccd86f..37bdcbf88c 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -112,6 +112,7 @@
#include "llscenemonitor.h"
#include "llprogressview.h"
#include "llcleanup.h"
+#include "gltfscenemanager.h"
#include "llenvironment.h"
#include "llsettingsvo.h"
@@ -4530,6 +4531,8 @@ void LLPipeline::renderDebug()
}
}
+ LL::GLTFSceneManager::instance().renderDebug();
+
if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION))
{ //render visible selected group occlusion geometry
gDebugProgram.bind();
@@ -6341,6 +6344,15 @@ LLViewerObject* LLPipeline::lineSegmentIntersectInWorld(const LLVector4a& start,
}
}
}
+
+ S32 node_hit = -1;
+ S32 primitive_hit = -1;
+ LLDrawable* hit = LL::GLTFSceneManager::instance().lineSegmentIntersect(start, local_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, &node_hit, &primitive_hit, &position, tex_coord, normal, tangent);
+ if (hit)
+ {
+ drawable = hit;
+ local_end = position;
+ }
if (!sPickAvatar)
{
@@ -6557,6 +6569,11 @@ void LLPipeline::renderGLTFObjects(U32 type, bool texture, bool rigged)
gGL.loadMatrix(gGLModelView);
gGLLastMatrix = NULL;
+
+ if (!rigged)
+ {
+ LL::GLTFSceneManager::instance().renderOpaque();
+ }
}
// Currently only used for shadows -Cosmic,2023-04-19
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 145276db57..c2f04231fc 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -2846,6 +2846,12 @@ function="World.EnvPreset"
<menu_item_call.on_click
function="Advanced.ClickHDRIPreview" />
</menu_item_call>
+ <menu_item_call
+ label="GLTF Scene Preview"
+ name="GLTF Scene Preview">
+ <menu_item_call.on_click
+ function="Advanced.ClickGLTFScenePreview" />
+ </menu_item_call>
</menu>
<menu
create_jump_keys="true"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 24036bd799..8015c7d875 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -12361,5 +12361,16 @@ Would you like to save them first?
name="okignore"
yestext="OK"/>
</notification>
-
+
+ <notification
+ icon="alertmodal.tga"
+ name="GLTFPreviewSelection"
+ type="alert">
+ You must select an object to act as a handle to the GLTF asset you are previewing.
+ <tag>fail</tag>
+ <usetemplate
+ name="okbutton"
+ yestext="OK"/>
+ </notification>
+
</notifications>