/** * @file llgltfasset.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 "llgltfasset.h" #include "llgltfaccessor.h" #include "llshadermgr.h" #include "llvolumeoctree.h" // Global variable defined in newview (llappviewer.h) extern F32 gFrameTimeSeconds; // External functions from LLFetchedGLTFMaterial. HB extern LLGLTFMaterial* create_fetch_material(); extern void bind_fetched_material(LLGLTFMaterial* matp); // External function from LLTinyGLTFHelper. HB extern void get_material_from_model(const std::string& filename, const tinygltf::Model& model, S32 mat_idx, LLGLTFMaterial* matp, std::string& mat_name, bool flip); using namespace LLGLTF; /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Node class /////////////////////////////////////////////////////////////////////////////// Node::Node() : mParent(INVALID_INDEX), mMesh(INVALID_INDEX), mSkin(INVALID_INDEX), mMatrixValid(false), mTRSValid(false), mNeedsApplyMatrix(false) { } void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) { mRenderMatrix.matMul(mMatrix, modelview); for (auto& idx : mChildren) { Node& child = asset.mNodes[idx]; child.updateRenderTransforms(asset, mRenderMatrix); } } void Node::updateTransforms(Asset& asset, const LLMatrix4a& parent_mat) { makeMatrixValid(); mAssetMatrix.matMul(mMatrix, parent_mat); mAssetMatrixInv = mAssetMatrix; mAssetMatrixInv.invert(); S32 my_index = this - &asset.mNodes[0]; for (auto& idx : mChildren) { Node& child = asset.mNodes[idx]; child.mParent = my_index; child.updateTransforms(asset, mAssetMatrix); } } void Node::makeMatrixValid() { if (!mMatrixValid && mTRSValid) { // t = sc * rot * trans; // t = trans * rot * sc; // Best so far, still wrong on negative scale // t = sc * trans * rot; glh::matrix4f rot, trans, sc; mRotation.get_value(rot); trans.set_translate(mTranslation); sc.set_scale(mScale); glh::matrix4f t = trans * sc * rot; mMatrix.loadu(t.m); mMatrixValid = true; } } void Node::makeTRSValid() { if (!mTRSValid && mMatrixValid) { glh::matrix4f t(mMatrix.getF32ptr()); glh::vec4f p = t.get_column(3); mTranslation.set_value(p.v[0], p.v[1], p.v[2]); mScale.set_value(t.get_column(0).length(), t.get_column(1).length(), t.get_column(2).length()); mRotation.set_value(t); mTRSValid = true; } } void Node::setRotation(const glh::quaternionf& q) { makeTRSValid(); mRotation = q; mMatrixValid = false; } void Node::setTranslation(const glh::vec3f& t) { makeTRSValid(); mTranslation = t; mMatrixValid = false; } void Node::setScale(const glh::vec3f& s) { makeTRSValid(); mScale = s; mMatrixValid = false; } const Node& Node::operator=(const tinygltf::Node& src) { F32* dst_mat = mMatrix.getF32ptr(); if (src.matrix.size() == 16) { // Node has a transformation matrix, just copy it for (U32 i = 0; i < 16; ++i) { dst_mat[i] = (F32)src.matrix[i]; } mMatrixValid = true; } else if (!src.rotation.empty() || !src.translation.empty() || !src.scale.empty()) { // Node has rotation/translation/scale, convert to matrix if (src.rotation.size() == 4) { mRotation = glh::quaternionf((F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2], (F32)src.rotation[3]); } if (src.translation.size() == 3) { mTranslation = glh::vec3f((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]); } glh::vec3f scale; if (src.scale.size() == 3) { mScale = glh::vec3f((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]); } else { mScale.set_value(1.f, 1.f, 1.f); } mTRSValid = true; } else { // Node specifies no transformation, set to identity mMatrix.setIdentity(); } mChildren = src.children; mMesh = src.mesh; mSkin = src.skin; mName = src.name; return *this; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Asset class /////////////////////////////////////////////////////////////////////////////// Asset::Asset() { mLastUpdateTime = gFrameTimeSeconds; } 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) { node.mRenderMatrix.matMul(node.mAssetMatrix, modelview); } } #endif } S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersectp, LLVector2* tcoordp, LLVector4a* normalp, LLVector4a* tangentp, S32* prim_hitp) { S32 node_hit = -1; S32 primitive_hit = -1; LLVector4a p, local_start, local_end; LLVector4a asset_end = end; for (auto& node : mNodes) { if (node.mMesh != INVALID_INDEX) { bool new_hit = 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, tcoordp, normalp, tangentp); if (tri) { new_hit = 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 (new_hit) { // Shorten line segment on hit node.mAssetMatrix.affineTransform(p, asset_end); // Transform results back to asset space if (intersectp) { *intersectp = asset_end; } if (normalp || tangentp) { LLMatrix4 normal_mat4(node.mAssetMatrixInv.getF32ptr()); normal_mat4.transpose(); LLMatrix4a norm_mat; norm_mat.loadu(normal_mat4.getF32ptr()); if (normalp) { LLVector4a n = *normalp; F32 w = n.getF32ptr()[3]; n.getF32ptr()[3] = 0.0f; norm_mat.affineTransform(n, *normalp); normalp->getF32ptr()[3] = w; } if (tangentp) { LLVector4a t = *tangentp; F32 w = t.getF32ptr()[3]; t.getF32ptr()[3] = 0.0f; norm_mat.affineTransform(t, *tangentp); tangentp->getF32ptr()[3] = w; } } } } } if (node_hit != -1) { if (prim_hitp) { *prim_hitp = primitive_hit; } } return node_hit; } void Asset::render(bool opaque, bool rigged) { if (rigged) { gGL.loadIdentity(); } for (auto& node : mNodes) { if (node.mSkin != INVALID_INDEX) { if (rigged) { Skin& skin = mSkins[node.mSkin]; skin.uploadMatrixPalette(*this, node); } else { // Skip static nodes if we are rendering rigged continue; } } else if (rigged) { // Skip rigged nodes if we are not rendering rigged continue; } if (node.mMesh != INVALID_INDEX) { Mesh& mesh = mMeshes[node.mMesh]; for (auto& primitive : mesh.mPrimitives) { if (!rigged) { gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix); } bool cull = true; if (primitive.mMaterial != INVALID_INDEX) { Material& material = mMaterials[primitive.mMaterial]; if ((material.mMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) == opaque) { continue; } bind_fetched_material(material.mMaterial); cull = !material.mMaterial->mDoubleSided; } else { if (!opaque) { continue; } bind_fetched_material(NULL); } LLVertexBuffer* buffp = primitive.mVertexBuffer; if (!buffp) continue; LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0); buffp->setBuffer(); U32 num_indices = buffp->getNumIndices(); if (num_indices > 0) { buffp->draw(primitive.mGLMode, num_indices, 0); } else { buffp->drawArrays(primitive.mGLMode, 0, buffp->getNumVerts()); } } } } } void Asset::renderOpaque() { render(true); } void Asset::renderTransparent() { render(false); } void Asset::update() { F32 dt = gFrameTimeSeconds - mLastUpdateTime; if (dt <= 0.f) { return; } mLastUpdateTime = gFrameTimeSeconds; if (mAnimations.size() > 0) { #if 0 // For now, these settings are not even listed in LL's viewer // app_settings/settings.xml, so they default to 0 and 1.f // respectively. If configuration is ever to be provided, just use // static variables in the LLGLTF::Asset class and setter methods to // set them on viewer launch from llappviewer.cpp, and on change from // llviewercontrol.cpp... HB static LLCachedControl anim_idx(gSavedSettings, "GLTFAnimationIndex", 0); static LLCachedControl anim_speed(gSavedSettings, "GLTFAnimationSpeed", 1.f); U32 idx = llclamp(anim_idx(), 0U, mAnimations.size() - 1); #else constexpr U32 idx = 0; constexpr F32 anim_speed = 1.f; #endif mAnimations[idx].update(*this, dt * anim_speed); } updateTransforms(); } bool Asset::allocateGLResources(const std::string& filename, const tinygltf::Model& model) { // Do images first as materials may depend on images for (auto& image : mImages) { image.allocateGLResources(); } // Do materials before meshes as meshes may depend on materials for (size_t i = 0, count = mMaterials.size(); i < count; ++i) { mMaterials[i].allocateGLResources(*this); get_material_from_model(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); } for (auto& mesh : mMeshes) { if (!mesh.allocateGLResources(*this)) { // Too many vertice: aborted ! HB return false; } } for (auto& animation : mAnimations) { animation.allocateGLResources(*this); } for (auto& skin : mSkins) { skin.allocateGLResources(*this); } return true; } const Asset& Asset::operator=(const tinygltf::Model& src) { size_t count = src.scenes.size(); mScenes.resize(count); for (size_t i = 0; i < count; ++i) { mScenes[i] = src.scenes[i]; } count = src.nodes.size(); mNodes.resize(count); for (size_t i = 0; i < count; ++i) { mNodes[i] = src.nodes[i]; } count = src.meshes.size(); mMeshes.resize(count); for (size_t i = 0; i < count; ++i) { mMeshes[i] = src.meshes[i]; } count = src.materials.size(); mMaterials.resize(count); for (size_t i = 0; i < count; ++i) { mMaterials[i] = src.materials[i]; } count = src.buffers.size(); mBuffers.resize(count); for (size_t i = 0; i < count; ++i) { mBuffers[i] = src.buffers[i]; } count = src.bufferViews.size(); mBufferViews.resize(count); for (size_t i = 0; i < count; ++i) { mBufferViews[i] = src.bufferViews[i]; } count = src.textures.size(); mTextures.resize(count); for (size_t i = 0; i < count; ++i) { mTextures[i] = src.textures[i]; } count = src.samplers.size(); mSamplers.resize(count); for (size_t i = 0; i < count; ++i) { mSamplers[i] = src.samplers[i]; } count = src.images.size(); mImages.resize(count); for (size_t i = 0; i < count; ++i) { mImages[i] = src.images[i]; } count = src.accessors.size(); mAccessors.resize(count); for (size_t i = 0; i < count; ++i) { mAccessors[i] = src.accessors[i]; } count = src.animations.size(); mAnimations.resize(count); for (size_t i = 0; i < count; ++i) { mAnimations[i] = src.animations[i]; } count = src.skins.size(); mSkins.resize(count); for (size_t i = 0; i < count; ++i) { mSkins[i] = src.skins[i]; } return *this; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Material class /////////////////////////////////////////////////////////////////////////////// const Material& Material::operator=(const tinygltf::Material& src) { mName = src.name; return *this; } void Material::allocateGLResources(Asset& asset) { // Allocate material mMaterial = create_fetch_material(); } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Mesh class /////////////////////////////////////////////////////////////////////////////// const Mesh& Mesh::operator=(const tinygltf::Mesh& src) { size_t count = src.primitives.size(); mPrimitives.resize(count); for (size_t i = 0; i < count; ++i) { mPrimitives[i] = src.primitives[i]; } mWeights = src.weights; mName = src.name; return *this; } bool Mesh::allocateGLResources(Asset& asset) { for (auto& primitive : mPrimitives) { if (!primitive.allocateGLResources(asset)) { // Too many vertice: aborted ! HB return false; } } return true; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Scene class /////////////////////////////////////////////////////////////////////////////// void Scene::updateTransforms(Asset& asset) { LLMatrix4a identity; identity.setIdentity(); for (auto& idx : mNodes) { Node& node = asset.mNodes[idx]; node.updateTransforms(asset, identity); } } void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) { for (auto& idx : mNodes) { Node& node = asset.mNodes[idx]; node.updateRenderTransforms(asset, modelview); } } const Scene& Scene::operator=(const tinygltf::Scene& src) { mNodes = src.nodes; mName = src.name; return *this; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Texture class /////////////////////////////////////////////////////////////////////////////// Texture::Texture() : mSampler(INVALID_INDEX), mSource(INVALID_INDEX) { } const Texture& Texture::operator=(const tinygltf::Texture& src) { mSampler = src.sampler; mSource = src.source; mName = src.name; return *this; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Image class /////////////////////////////////////////////////////////////////////////////// const Image& 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; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Sampler class /////////////////////////////////////////////////////////////////////////////// const Sampler& Sampler::operator=(const tinygltf::Sampler& src) { mMagFilter = src.magFilter; mMinFilter = src.minFilter; mWrapS = src.wrapS; mWrapT = src.wrapT; mName = src.name; return *this; } /////////////////////////////////////////////////////////////////////////////// // LLGLTF::Skin class /////////////////////////////////////////////////////////////////////////////// Skin::Skin() : mInverseBindMatrices(INVALID_INDEX), mSkeleton(INVALID_INDEX) { } const Skin& Skin::operator=(const tinygltf::Skin& src) { mName = src.name; mSkeleton = src.skeleton; mInverseBindMatrices = src.inverseBindMatrices; mJoints = src.joints; return *this; } void Skin::uploadMatrixPalette(Asset& asset, Node& node) { // Prepare matrix palette // Model view will be applied by the shader, so assume matrix palette is in // asset space std::vector t_mp; t_mp.resize(mJoints.size()); for (U32 i = 0; i < mJoints.size(); ++i) { Node& joint = asset.mNodes[mJoints[i]]; //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr()); //t_mp[i] = t_mp[i] * mInverseBindMatricesData[i]; //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr()); //t_mp[i] = mInverseBindMatricesData[i] * t_mp[i]; t_mp[i].set_value(joint.mRenderMatrix.getF32ptr()); t_mp[i] = t_mp[i] * mInverseBindMatricesData[i]; } std::vector glmp; glmp.resize(mJoints.size() * 12); F32* mp = glmp.data(); for (U32 i = 0; i < mJoints.size(); ++i) { F32* m = (F32*)t_mp[i].m; U32 idx = i * 12; mp[idx + 0] = m[0]; mp[idx + 1] = m[1]; mp[idx + 2] = m[2]; mp[idx + 3] = m[12]; mp[idx + 4] = m[4]; mp[idx + 5] = m[5]; mp[idx + 6] = m[6]; mp[idx + 7] = m[13]; mp[idx + 8] = m[8]; mp[idx + 9] = m[9]; mp[idx + 10] = m[10]; mp[idx + 11] = m[14]; } LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLShaderMgr::AVATAR_MATRIX, mJoints.size(), GL_FALSE, (F32*)glmp.data()); }