/** * @file llgltfscenemanager.cpp * @brief LLGLTFSceneManager class 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 "llviewerprecompiledheaders.h" #include "tinygltf/tiny_gltf.h" #include "llgltfscenemanager.h" #include "llrender.h" #include "llrenderutils.h" // For gl_draw_box_outline() #include "llappviewer.h" #include "llpipeline.h" #include "lltinygltfhelper.h" #include "llviewerobjectlist.h" #include "llviewershadermgr.h" #include "llviewerwindow.h" // For gDebugRaycast* using namespace LLGLTF; //static LLGLTFSceneManager::objects_set_t LLGLTFSceneManager::sObjects; //static void LLGLTFSceneManager::cleanup() { sObjects.clear(); } //static std::string LLGLTFSceneManager::load(const std::string& filename, const LLUUID& handle_obj_id) { LLViewerObject* objp = gObjectList.findObject(handle_obj_id); if (!objp || objp->isDead() || !objp->asVolume() || objp->mDrawable.isNull() || objp->isAttachment()) { // *TODO: translate return "No valid object to act as a handle for the gLTF scene."; } if (filename.empty()) { objp->mGLTFAsset = NULL; objects_set_t::iterator it = sObjects.find(objp); if (it != sObjects.end()) { sObjects.erase(it); } // Ensure the draw info will be regenerated, so to show the handle // object. HB objp->markForUpdate(true); // No error return ""; } tinygltf::Model model; if (!LLTinyGLTFHelper::loadModel(filename, model)) { // *TODO: translate return "TinyGLTF could not load this model."; } LLPointer assetp = new Asset(); *assetp = model; // Bind a shader to satisfy LLVertexBuffer assertions gDebugProgram.bind(); if (!assetp->allocateGLResources(filename, model)) { // For now, this is the only identified cause for an failure. HB // *TODO: translate return "Too many vertices used in a primitive."; } assetp->updateTransforms(); objp->mGLTFAsset = assetp; sObjects.emplace(objp); // Ensure the draw info will be regenerated, so to hide the handle // object. HB objp->markForUpdate(true); // No error return ""; } //static bool LLGLTFSceneManager::lineSegmentIntersect(LLVOVolume* volp, Asset* assetp, const LLVector4a& start, const LLVector4a& end, S32 face, bool, bool, S32* node_hitp, S32* prim_hitp, LLVector4a* interp, LLVector2* tcoordp, LLVector4a* normp, LLVector4a* tgtp) { LLVector4a local_start, local_end, p, n, tn; LLVector2 tc; if (interp) { p = *interp; } if (tcoordp) { tc = *tcoordp; } if (normp) { n = *normp; } if (tgtp) { tn = *tgtp; } // 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. LLMatrix4a asset_to_agent = volp->getGLTFAssetToAgentTransform(); LLMatrix4a agent_to_asset = asset_to_agent; agent_to_asset.invert(); agent_to_asset.affineTransform(start, local_start); agent_to_asset.affineTransform(end, local_end); S32 hit_node_index = assetp->lineSegmentIntersect(local_start, local_end, &p, &tc, &n, &tn, prim_hitp); if (hit_node_index < 0) { return false; } local_end = p; if (node_hitp) { *node_hitp = hit_node_index; } if (interp) { asset_to_agent.affineTransform(p, *interp); } if (normp) { LLVector3 v_n(n.getF32ptr()); normp->load3(volp->volumeDirectionToAgent(v_n).mV); normp->normalize3fast(); } if (tgtp) { LLVector3 v_tn(tn.getF32ptr()); LLVector4a trans_tangent; trans_tangent.load3(volp->volumeDirectionToAgent(v_tn).mV); LLVector4Logical mask; mask.clear(); mask.setElement<3>(); tgtp->setSelectWithMask(mask, tn, trans_tangent); tgtp->normalize3fast(); } if (tcoordp) { *tcoordp = tc; } return true; } //static LLDrawable* LLGLTFSceneManager::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, bool pick_transparent, bool pick_rigged, S32* node_hitp, S32* prim_hitp, LLVector4a* interp, LLVector2* tcoordp, LLVector4a* normp, LLVector4a* tgtp) { LLDrawable* drawp = NULL; LLVector4a local_end = end; LLVector4a position; for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ) { LLVOVolume* volp = it->get()->asVolume(); if (!volp || volp->isDead() || volp->mGLTFAsset.isNull()) { it = sObjects.erase(it); continue; } // Temporary debug: always double check objects that have GLTF scenes // hanging off of them even if the ray does not intersect the object // bounds. if (lineSegmentIntersect(volp, volp->mGLTFAsset,start, local_end, -1, pick_transparent, pick_rigged, node_hitp, prim_hitp, &position, tcoordp, normp, tgtp)) { local_end = position; if (interp) { *interp = position; } drawp = volp->mDrawable; } ++it; } return drawp; } //static void LLGLTFSceneManager::update() { for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ) { LLViewerObject* objp = it->get(); if (objp->isDead() || objp->mGLTFAsset.isNull()) { it = sObjects.erase(it); } else { objp->mGLTFAsset->update(); ++it; } } } //static void LLGLTFSceneManager::render(bool opaque, bool rigged) { gGL.matrixMode(LLRender::MM_MODELVIEW); for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ) { LLViewerObject* objp = it->get(); if (objp->isDead() || objp->mGLTFAsset.isNull()) { it = sObjects.erase(it); continue; } LLMatrix4a mat = objp->getGLTFAssetToAgentTransform(); mat.matMul(mat, gGLModelView); gGL.pushMatrix(); Asset* assetp = objp->mGLTFAsset; assetp->updateRenderTransforms(mat); assetp->render(opaque, rigged); gGL.popMatrix(); ++it; } } // This function assumes that the appropriate shader is already bound and the // model view matrix for the asset is already set. static void draw_asset_box(LLViewerObject* objp, Asset* assetp) { gGL.pushMatrix(); for (auto& node : assetp->mNodes) { if (node.mMesh == INVALID_INDEX) { continue; } Mesh& mesh = assetp->mMeshes[node.mMesh]; gGL.loadMatrix(node.mRenderMatrix.getF32ptr()); // Draw bounding box of mesh primitives gGL.color3f(0.f, 1.f, 1.f); for (auto& primitive : mesh.mPrimitives) { LLVolumeOctree* octreep = primitive.mOctree.get(); if (octreep) { LLVolumeOctreeListenerNoOwnership* listenerp = (LLVolumeOctreeListenerNoOwnership*)octreep->getListener(0); if (listenerp) { LLVector4a center = listenerp->mBounds[0]; LLVector4a size = listenerp->mBounds[1]; gl_draw_box_outline(center, size); } } } } gGL.popMatrix(); } //static void LLGLTFSceneManager::renderDebug() { 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(); // Force update all mRenderMatrix, not just nodes with meshes for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ++it) { LLViewerObject* objp = it->get(); if (objp->isDead() || objp->mGLTFAsset.isNull()) { continue; } LLMatrix4a mat = objp->getGLTFAssetToAgentTransform(); mat.matMul(mat, gGLModelView); for (auto& node : objp->mGLTFAsset->mNodes) { node.mRenderMatrix.matMul(node.mAssetMatrix, mat); } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) { for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ++it) { LLViewerObject* objp = it->get(); if (!objp->isDead() && objp->mGLTFAsset.notNull()) { draw_asset_box(objp, objp->mGLTFAsset); } } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_NODES)) { // Render nodes hierarchy for (U32 i = 0; i < 2; ++i) { LLGLDepthTest depth(GL_TRUE, i ? GL_TRUE : GL_FALSE, i ? GL_LEQUAL : GL_GREATER); LLGLState blend(GL_BLEND, i ? GL_FALSE : GL_TRUE); gGL.pushMatrix(); for (objects_set_t::iterator it = sObjects.begin(), end = sObjects.end(); it != end; ++it) { LLViewerObject* objp = it->get(); if (objp->isDead() || objp->mGLTFAsset.isNull()) { continue; } Asset* assetp = objp->mGLTFAsset; LLMatrix4a mat = objp->getGLTFAssetToAgentTransform(); mat.matMul(mat, gGLModelView); for (auto& node : assetp->mNodes) { node.mRenderMatrix.matMul(node.mAssetMatrix, mat); gGL.loadMatrix(node.mRenderMatrix.getF32ptr()); // Render x-axis red, y-axis green, z-axis blue gGL.color4f(1.f, 0.f, 0.f, 0.5f); gGL.begin(LLRender::LINES); gGL.vertex3f(0.f, 0.f, 0.f); gGL.vertex3f(1.f, 0.f, 0.f); gGL.end(true); gGL.color4f(0.f, 1.f, 0.f, 0.5f); gGL.begin(LLRender::LINES); gGL.vertex3f(0.f, 0.f, 0.f); gGL.vertex3f(0.f, 1.f, 0.f); gGL.end(true); gGL.color4f(0.f, 0.f, 1.f, 0.5f); gGL.begin(LLRender::LINES); gGL.vertex3f(0.f, 0.f, 0.f); gGL.vertex3f(0.f, 0.f, 1.f); gGL.end(true); // Render path to child nodes cyan gGL.color4f(0.f, 1.f, 1.f, 0.5f); gGL.begin(LLRender::LINES); for (auto& child_idx : node.mChildren) { Node& child = assetp->mNodes[child_idx]; gGL.vertex3f(0.f, 0.f, 0.f); gGL.vertex3fv(child.mMatrix.getTranslation().getF32ptr()); } gGL.end(true); } } gGL.popMatrix(); } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST)) { S32 node_hit = -1; S32 prim_hit = -1; LLVector4a inter; LLDrawable* drawp = lineSegmentIntersect(gDebugRaycastStart, gDebugRaycastEnd, true, true, &node_hit, &prim_hit, &inter, NULL, NULL, NULL); if (drawp && !drawp->isDead() && drawp->getVObj() && node_hit >= 0 && prim_hit >= 0) { Asset* assetp = drawp->getVObj()->mGLTFAsset; Node* nodep = &assetp->mNodes[node_hit]; Primitive* primp = &assetp->mMeshes[nodep->mMesh].mPrimitives[prim_hit]; gGL.pushMatrix(); gGL.flush(); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); gGL.color3f(1.f, 0.f, 1.f); gl_draw_box_outline(inter, LLVector4a(0.1f, 0.1f, 0.1f, 0.f)); gGL.loadMatrix(nodep->mRenderMatrix.getF32ptr()); LLVolumeOctree* octreep = primp->mOctree.get(); if (octreep) { LLVolumeOctreeListenerNoOwnership* listenerp = (LLVolumeOctreeListenerNoOwnership*)octreep->getListener(0); if (listenerp) { gl_draw_box_outline(listenerp->mBounds[0], listenerp->mBounds[1]); } } gGL.flush(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); gGL.popMatrix(); } } gDebugProgram.unbind(); }