/** * @file llgltfprimitive.cpp * @brief LL GLTF Implementation * * $LicenseInfo:firstyear=2024&license=viewergpl$ * * Copyright (c) 2024, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "tinygltf/tiny_gltf.h" #include "llgltfprimitive.h" #include "llgltfasset.h" #include "llgltfbufferutil.h" using namespace LLGLTF; Primitive::Primitive() : mMaterial(-1), mMode(TINYGLTF_MODE_TRIANGLES), mGLMode(LLRender::TRIANGLES), mIndices(-1) { } Primitive::~Primitive() { mOctree = NULL; } // Allocates a 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 is better to rearrange the buffers at // load time into a layout which is more consistent. The GLTF native approach // undoubtedly works well if you can count on VAOs, but VAOs perform much worse // with our scenes. // Returns true on success, or false on failure. HB bool Primitive::allocateGLResources(Asset& asset) { // Load vertex data for (auto& it : mAttributes) { const std::string& attribName = it.first; Accessor& accessor = asset.mAccessors[it.second]; // load vertex data if (attribName == "POSITION") { copy(asset, accessor, mPositions); } else if (attribName == "NORMAL") { copy(asset, accessor, mNormals); } else if (attribName == "TANGENT") { copy(asset, accessor, mTangents); } else if (attribName == "COLOR_0") { copy(asset, accessor, mColors); } else if (attribName == "TEXCOORD_0") { copy(asset, accessor, mTexCoords); } else if (attribName == "JOINTS_0") { copy(asset, accessor, mJoints); } else if (attribName == "WEIGHTS_0") { copy(asset, accessor, mWeights); } } if (mPositions.size() > 65536) { // This would trigger an llerrs in LLVertexBuffer::allocateBuffer(). HB llwarns << "Too many vertices: " << mPositions.size() << ". Aborted." << llendl return false; } // Copy index buffer if (mIndices != INVALID_INDEX) { Accessor& accessor = asset.mAccessors[mIndices]; copy(asset, accessor, mIndexArray); } U32 mask = ATTRIBUTE_MASK; if (!mWeights.empty()) { mask |= LLVertexBuffer::MAP_WEIGHT4; } mVertexBuffer = new LLVertexBuffer(mask); // Double the size of the index buffer for 32-bit indices mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size() * 2); mVertexBuffer->setBuffer(); mVertexBuffer->setPositionData(mPositions.data()); if (!mIndexArray.empty()) { mVertexBuffer->setIndexData(mIndexArray.data()); } if (mTexCoords.empty()) { mTexCoords.resize(mPositions.size()); } // Flip texcoord y, upload, then flip back (keep the off-spec data in VRAM //only) for (auto& tc : mTexCoords) { tc[1] = 1.f - tc[1]; } mVertexBuffer->setTexCoord0Data(mTexCoords.data()); for (auto& tc : mTexCoords) { tc[1] = 1.f - tc[1]; } if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { const Material& material = asset.mMaterials[mMaterial]; LLColor4 baseColor = material.mMaterial->mBaseColor; for (auto& dst : mColors) { dst = LLColor4U(baseColor * LLColor4(dst)); } } mVertexBuffer->setColorData(mColors.data()); if (mNormals.empty()) { mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); } mVertexBuffer->setNormalData(mNormals.data()); if (mTangents.empty()) { // TODO: generate tangents if needed mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1)); } mVertexBuffer->setTangentData(mTangents.data()); if (!mWeights.empty()) { std::vector weight_data; weight_data.resize(mWeights.size()); F32 max_weight = 1.f - FLT_EPSILON * 100.f; LLVector4a maxw(max_weight, max_weight, max_weight, max_weight); for (U32 i = 0; i < mWeights.size(); ++i) { LLVector4a& w = weight_data[i]; w.setMin(mWeights[i], maxw); w.add(mJoints[i]); } mVertexBuffer->setWeight4Data(weight_data.data()); } createOctree(); mVertexBuffer->unbind(); return true; } static void init_octree_triangle(LLVolumeTriangle* trianglep, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2) { // Store pointers to vertex data trianglep->mV[0] = &v0; trianglep->mV[1] = &v1; trianglep->mV[2] = &v2; // Store indices trianglep->mIndex[0] = i0; trianglep->mIndex[1] = i1; trianglep->mIndex[2] = i2; // 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); trianglep->mPositionGroup = center; // Compute "radius" LLVector4a size; size.setSub(max, min); constexpr F32 scaler = 0.25f; trianglep->mRadius = size.getLength3().getF32() * scaler; } void Primitive::createOctree() { mOctree = new LLVolumeOctree(); const U32 num_indices = mVertexBuffer->getNumIndices(); if (num_indices < 3) { // Degenerate triangle: no volume ! HB llwarns << "Degenerate triangle found" << llendl; } else if (mMode == TINYGLTF_MODE_TRIANGLES) { const U32 num_triangles = num_indices / 3; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); for (U32 tri_idx = 0; tri_idx < num_triangles; ++tri_idx) { const U32 index = tri_idx * 3; S32 i0 = mIndexArray[index]; S32 i1 = mIndexArray[index + 1]; S32 i2 = mIndexArray[index + 2]; const LLVector4a& v0 = mPositions[i0]; const LLVector4a& v1 = mPositions[i1]; const LLVector4a& v2 = mPositions[i2]; LLVolumeTriangle* trianglep = &mOctreeTriangles[tri_idx]; init_octree_triangle(trianglep, i0, i1, i2, v0, v1, v2); mOctree->insert(trianglep); } } else if (mMode == TINYGLTF_MODE_TRIANGLE_STRIP) { const U32 num_triangles = num_indices - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); for (U32 tri_idx = 0; tri_idx < num_triangles; ++tri_idx) { const U32 index = tri_idx + 2; S32 i0 = mIndexArray[index]; S32 i1 = mIndexArray[index - 1]; S32 i2 = mIndexArray[index - 2]; const LLVector4a& v0 = mPositions[i0]; const LLVector4a& v1 = mPositions[i1]; const LLVector4a& v2 = mPositions[i2]; LLVolumeTriangle* trianglep = &mOctreeTriangles[tri_idx]; init_octree_triangle(trianglep, i0, i1, i2, v0, v1, v2); mOctree->insert(trianglep); } } else if (mMode == TINYGLTF_MODE_TRIANGLE_FAN) { const U32 num_triangles = num_indices - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); for (U32 tri_idx = 0; tri_idx < num_triangles; ++tri_idx) { const U32 index = tri_idx + 2; S32 i0 = mIndexArray[0]; S32 i1 = mIndexArray[index - 1]; S32 i2 = mIndexArray[index - 2]; const LLVector4a& v0 = mPositions[i0]; const LLVector4a& v1 = mPositions[i1]; const LLVector4a& v2 = mPositions[i2]; LLVolumeTriangle* trianglep = &mOctreeTriangles[tri_idx]; init_octree_triangle(trianglep, i0, i1, i2, v0, v1, v2); mOctree->insert(trianglep); } } else { llwarns << "Unsupported primitive mode: " << mMode << llendl; } // 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* interp, LLVector2* tcoordp, LLVector4a* normp, LLVector4a* tgtp) { if (mOctree.isNull()) { return NULL; } 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 = NULL; // unreferenced face.mNumIndices = mIndexArray.size(); face.mNumVertices = mPositions.size(); LLOctreeTriangleRayIntersectNoOwnership intersect(start, dir, &face, &closest_t, interp, tcoordp, normp, tgtp); intersect.traverse(mOctree); // Null out proxy data so it does not get freed face.mPositions = face.mNormals = face.mTangents = NULL; face.mIndices = NULL; face.mTexCoords = NULL; return intersect.mHitTriangle; } const Primitive& 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; }