From b2a450a3087fb8393024876f6069a7cec9855bfd Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 9 Apr 2024 19:21:10 -0500 Subject: #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 --- indra/newview/gltfscenemanager.cpp | 469 +++++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 indra/newview/gltfscenemanager.cpp (limited to 'indra/newview/gltfscenemanager.cpp') 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& 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 = 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(); +} + -- cgit v1.2.3