summaryrefslogtreecommitdiff
path: root/indra/newview/gltf
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/newview/gltf
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/newview/gltf')
-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
4 files changed, 1274 insertions, 0 deletions
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);
+ };
+ }
+}