summaryrefslogtreecommitdiff
path: root/indra/newview/gltfscenemanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/gltfscenemanager.cpp')
-rw-r--r--indra/newview/gltfscenemanager.cpp1139
1 files changed, 1139 insertions, 0 deletions
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
new file mode 100644
index 0000000000..d7eb605489
--- /dev/null
+++ b/indra/newview/gltfscenemanager.cpp
@@ -0,0 +1,1139 @@
+/**
+ * @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 "llviewerprecompiledheaders.h"
+
+#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"
+#include "llviewertexturelist.h"
+#include "llimagej2c.h"
+#include "llfloaterperms.h"
+#include "llagentbenefits.h"
+#include "llfilesystem.h"
+#include "boost/json.hpp"
+
+#define GLTF_SIM_SUPPORT 1
+
+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<std::string>& 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,
+ false);
+ }
+ else
+ {
+ LLNotificationsUtil::add("GLTFOpenSelection");
+ }
+}
+
+void GLTFSceneManager::saveAs()
+{
+ LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject();
+ if (obj && obj->mGLTFAsset)
+ {
+ LLFilePickerReplyThread::startPicker(
+ [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter)
+ {
+ if (LLAppViewer::instance()->quitRequested())
+ {
+ return;
+ }
+ if (filenames.size() > 0)
+ {
+ GLTFSceneManager::instance().save(filenames[0]);
+ }
+ },
+ LLFilePicker::FFSAVE_GLTF,
+ "scene.gltf");
+ }
+ else
+ {
+ LLNotificationsUtil::add("GLTFSaveSelection");
+ }
+}
+
+void GLTFSceneManager::uploadSelection()
+{
+ if (mUploadingAsset)
+ { // upload already in progress
+ LLNotificationsUtil::add("GLTFUploadInProgress");
+ return;
+ }
+
+ LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject();
+ if (obj && obj->mGLTFAsset)
+ {
+ // make a copy of the asset prior to uploading
+ mUploadingAsset = std::make_shared<Asset>();
+ mUploadingObject = obj;
+ *mUploadingAsset = *obj->mGLTFAsset;
+
+ GLTF::Asset& asset = *mUploadingAsset;
+
+ for (auto& image : asset.mImages)
+ {
+ if (image.mTexture.notNull())
+ {
+ mPendingImageUploads++;
+
+ LLPointer<LLImageRaw> raw;
+
+ if (image.mBufferView != INVALID_INDEX)
+ {
+ BufferView& view = asset.mBufferViews[image.mBufferView];
+ Buffer& buffer = asset.mBuffers[view.mBuffer];
+
+ raw = LLViewerTextureManager::getRawImageFromMemory(buffer.mData.data() + view.mByteOffset, view.mByteLength, image.mMimeType);
+
+ image.clearData(asset);
+ }
+ else
+ {
+ raw = image.mTexture->getCachedRawImage();
+ }
+
+ if (raw.notNull())
+ {
+ LLPointer<LLImageJ2C> j2c = LLViewerTextureList::convertToUploadFile(raw);
+
+ std::string buffer;
+ buffer.assign((const char*)j2c->getData(), j2c->getDataSize());
+
+ LLUUID asset_id = LLUUID::generateNewID();
+
+ std::string name;
+ S32 idx = (S32)(&image - &asset.mImages[0]);
+
+ if (image.mName.empty())
+ {
+
+ name = llformat("Image_%d", idx);
+ }
+ else
+ {
+ name = image.mName;
+ }
+
+ LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason)
+ {
+ // TODO: handle failure
+ mPendingImageUploads--;
+ return false;
+ };
+
+
+ LLNewBufferedResourceUploadInfo::uploadFinish_f finish = [this, idx, raw, j2c](LLUUID assetId, LLSD response)
+ {
+ if (mUploadingAsset && mUploadingAsset->mImages.size() > idx)
+ {
+ mUploadingAsset->mImages[idx].mUri = assetId.asString();
+ mPendingImageUploads--;
+ }
+ };
+
+ S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(j2c);
+
+ LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>(
+ buffer,
+ asset_id,
+ name,
+ name,
+ 0,
+ LLFolderType::FT_TEXTURE,
+ LLInventoryType::IT_TEXTURE,
+ LLAssetType::AT_TEXTURE,
+ LLFloaterPerms::getNextOwnerPerms("Uploads"),
+ LLFloaterPerms::getGroupPerms("Uploads"),
+ LLFloaterPerms::getEveryonePerms("Uploads"),
+ expected_upload_cost,
+ false,
+ finish,
+ failure));
+
+ upload_new_resource(uploadInfo);
+ }
+ }
+ }
+
+ // upload .bin
+ for (auto& bin : asset.mBuffers)
+ {
+ mPendingBinaryUploads++;
+
+ S32 idx = (S32)(&bin - &asset.mBuffers[0]);
+
+ std::string buffer;
+ buffer.assign((const char*)bin.mData.data(), bin.mData.size());
+
+ LLUUID asset_id = LLUUID::generateNewID();
+
+ LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason)
+ {
+ // TODO: handle failure
+ mPendingBinaryUploads--;
+ mUploadingAsset = nullptr;
+ mUploadingObject = nullptr;
+ LL_WARNS("GLTF") << "Failed to upload GLTF binary: " << reason << LL_ENDL;
+ LL_WARNS("GLTF") << response << LL_ENDL;
+ return false;
+ };
+
+ LLNewBufferedResourceUploadInfo::uploadFinish_f finish = [this, idx](LLUUID assetId, LLSD response)
+ {
+ if (mUploadingAsset && mUploadingAsset->mBuffers.size() > idx)
+ {
+ mUploadingAsset->mBuffers[idx].mUri = assetId.asString();
+ mPendingBinaryUploads--;
+
+ // HACK: save buffer to cache to emulate a successful download
+ LLFileSystem cache(assetId, LLAssetType::AT_GLTF_BIN, LLFileSystem::WRITE);
+ auto& data = mUploadingAsset->mBuffers[idx].mData;
+
+ cache.write((const U8*)data.data(), data.size());
+ }
+ };
+#if GLTF_SIM_SUPPORT
+ S32 expected_upload_cost = 1;
+
+ LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>(
+ buffer,
+ asset_id,
+ "",
+ "",
+ 0,
+ LLFolderType::FT_NONE,
+ LLInventoryType::IT_GLTF_BIN,
+ LLAssetType::AT_GLTF_BIN,
+ LLFloaterPerms::getNextOwnerPerms("Uploads"),
+ LLFloaterPerms::getGroupPerms("Uploads"),
+ LLFloaterPerms::getEveryonePerms("Uploads"),
+ expected_upload_cost,
+ false,
+ finish,
+ failure));
+
+ upload_new_resource(uploadInfo);
+#else
+ // dummy finish
+ finish(LLUUID::generateNewID(), LLSD());
+#endif
+ }
+ }
+ else
+ {
+ LLNotificationsUtil::add("GLTFUploadSelection");
+ }
+}
+
+void GLTFSceneManager::save(const std::string& filename)
+{
+ LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject();
+ if (obj && obj->mGLTFAsset)
+ {
+ Asset* asset = obj->mGLTFAsset.get();
+ if (!asset->save(filename))
+ {
+ LLNotificationsUtil::add("GLTFSaveFailed");
+ }
+ }
+}
+
+void GLTFSceneManager::load(const std::string& filename)
+{
+ std::shared_ptr<Asset> asset = std::make_shared<Asset>();
+
+ if (asset->load(filename))
+ {
+ gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions
+ 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;
+ obj->markForUpdate();
+ if (std::find(mObjects.begin(), mObjects.end(), obj) == mObjects.end())
+ {
+ mObjects.push_back(obj);
+ }
+ }
+ }
+ else
+ {
+ LLNotificationsUtil::add("GLTFLoadFailed");
+ }
+}
+
+GLTFSceneManager::~GLTFSceneManager()
+{
+ mObjects.clear();
+}
+
+void GLTFSceneManager::renderOpaque()
+{
+ render(true);
+}
+
+void GLTFSceneManager::renderAlpha()
+{
+ render(false);
+}
+
+void GLTFSceneManager::addGLTFObject(LLViewerObject* obj, LLUUID gltf_id)
+{
+ llassert(obj->getVolume()->getParams().getSculptID() == gltf_id);
+ llassert(obj->getVolume()->getParams().getSculptType() == LL_SCULPT_TYPE_GLTF);
+
+ obj->ref();
+ gAssetStorage->getAssetData(gltf_id, LLAssetType::AT_GLTF, onGLTFLoadComplete, obj);
+}
+
+//static
+void GLTFSceneManager::onGLTFBinLoadComplete(const LLUUID& id, LLAssetType::EType asset_type, void* user_data, S32 status, LLExtStat ext_status)
+{
+ LLViewerObject* obj = (LLViewerObject*)user_data;
+ llassert(asset_type == LLAssetType::AT_GLTF_BIN);
+
+ if (status == LL_ERR_NOERR)
+ {
+ if (obj)
+ {
+ // find the Buffer with the given id in the asset
+ if (obj->mGLTFAsset)
+ {
+ obj->mGLTFAsset->mPendingBuffers--;
+
+
+ if (obj->mGLTFAsset->mPendingBuffers == 0)
+ {
+ if (obj->mGLTFAsset->prep())
+ {
+ GLTFSceneManager& mgr = GLTFSceneManager::instance();
+ if (std::find(mgr.mObjects.begin(), mgr.mObjects.end(), obj) == mgr.mObjects.end())
+ {
+ GLTFSceneManager::instance().mObjects.push_back(obj);
+ }
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Failed to prepare GLTF asset: " << id << LL_ENDL;
+ obj->mGLTFAsset = nullptr;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL;
+ obj->unref();
+ }
+}
+
+//static
+void GLTFSceneManager::onGLTFLoadComplete(const LLUUID& id, LLAssetType::EType asset_type, void* user_data, S32 status, LLExtStat ext_status)
+{
+ LLViewerObject* obj = (LLViewerObject*)user_data;
+ llassert(asset_type == LLAssetType::AT_GLTF);
+
+ if (status == LL_ERR_NOERR)
+ {
+ if (obj)
+ {
+ LLFileSystem file(id, asset_type, LLFileSystem::READ);
+ std::string data;
+ data.resize(file.getSize());
+ file.read((U8*)data.data(), data.size());
+
+ boost::json::value json = boost::json::parse(data);
+
+ std::shared_ptr<Asset> asset = std::make_shared<Asset>(json);
+ obj->mGLTFAsset = asset;
+
+ for (auto& buffer : asset->mBuffers)
+ {
+ // for now just assume the buffer is already in the asset cache
+ LLUUID buffer_id;
+ if (LLUUID::parseUUID(buffer.mUri, &buffer_id))
+ {
+ asset->mPendingBuffers++;
+
+ gAssetStorage->getAssetData(buffer_id, LLAssetType::AT_GLTF_BIN, onGLTFBinLoadComplete, obj);
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Buffer URI is not a valid UUID: " << buffer.mUri << LL_ENDL;
+ obj->unref();
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL;
+ obj->unref();
+ }
+}
+
+void GLTFSceneManager::update()
+{
+ for (U32 i = 0; i < mObjects.size(); ++i)
+ {
+ if (mObjects[i]->isDead() || mObjects[i]->mGLTFAsset == nullptr)
+ {
+ mObjects.erase(mObjects.begin() + i);
+ --i;
+ continue;
+ }
+
+ mObjects[i]->mGLTFAsset->update();
+ }
+
+ // process pending uploads
+ if (mUploadingAsset && !mGLTFUploadPending)
+ {
+ if (mPendingImageUploads == 0 && mPendingBinaryUploads == 0)
+ {
+ boost::json::object obj;
+ mUploadingAsset->serialize(obj);
+ std::string buffer = boost::json::serialize(obj, {});
+
+ LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason)
+ {
+ // TODO: handle failure
+ LL_WARNS("GLTF") << "Failed to upload GLTF json: " << reason << LL_ENDL;
+ LL_WARNS("GLTF") << response << LL_ENDL;
+
+ mUploadingAsset = nullptr;
+ mUploadingObject = nullptr;
+ mGLTFUploadPending = false;
+ return false;
+ };
+
+ LLNewBufferedResourceUploadInfo::uploadFinish_f finish = [this, buffer](LLUUID assetId, LLSD response)
+ {
+ LLAppViewer::instance()->postToMainCoro(
+ [=]()
+ {
+ if (mUploadingAsset)
+ {
+ // HACK: save buffer to cache to emulate a successful upload
+ LLFileSystem cache(assetId, LLAssetType::AT_GLTF, LLFileSystem::WRITE);
+
+ LL_INFOS("GLTF") << "Uploaded GLTF json: " << assetId << LL_ENDL;
+ cache.write((const U8 *) buffer.c_str(), buffer.size());
+
+ mUploadingAsset = nullptr;
+ }
+
+ if (mUploadingObject)
+ {
+ mUploadingObject->mGLTFAsset = nullptr;
+ mUploadingObject->setGLTFAsset(assetId);
+ mUploadingObject->markForUpdate();
+ mUploadingObject = nullptr;
+ }
+
+ mGLTFUploadPending = false;
+ });
+ };
+
+#if GLTF_SIM_SUPPORT
+ S32 expected_upload_cost = 1;
+ LLUUID asset_id = LLUUID::generateNewID();
+
+ mGLTFUploadPending = true;
+
+ LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>(
+ buffer,
+ asset_id,
+ "",
+ "",
+ 0,
+ LLFolderType::FT_NONE,
+ LLInventoryType::IT_GLTF,
+ LLAssetType::AT_GLTF,
+ LLFloaterPerms::getNextOwnerPerms("Uploads"),
+ LLFloaterPerms::getGroupPerms("Uploads"),
+ LLFloaterPerms::getEveryonePerms("Uploads"),
+ expected_upload_cost,
+ false,
+ finish,
+ failure));
+
+ upload_new_resource(uploadInfo);
+#else
+ // dummy finish
+ finish(LLUUID::generateNewID(), LLSD());
+#endif
+ }
+ }
+}
+
+void GLTFSceneManager::render(bool opaque, bool rigged, bool unlit)
+{
+ U8 variant = 0;
+ if (rigged)
+ {
+ variant |= LLGLSLShader::GLTFVariant::RIGGED;
+ }
+ if (!opaque)
+ {
+ variant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND;
+ }
+ if (unlit)
+ {
+ variant |= LLGLSLShader::GLTFVariant::UNLIT;
+ }
+
+ render(variant);
+}
+
+void GLTFSceneManager::render(U8 variant)
+{
+ // 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 boundd
+
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+
+ bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+
+ 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.get();
+ gGL.pushMatrix();
+
+ LLMatrix4a mat = mObjects[i]->getGLTFAssetToAgentTransform();
+
+ LLMatrix4a modelview;
+ modelview.loadu(gGLModelView);
+
+ matMul(mat, modelview, modelview);
+
+ mat4 mdv = glm::make_mat4(modelview.getF32ptr());
+ asset->updateRenderTransforms(mdv);
+
+ if (rigged)
+ { // provide a modelview matrix that goes from asset to camera space for rigged render passes
+ // (matrix palettes are in asset space)
+ gGL.loadMatrix(glm::value_ptr(mdv));
+ }
+ render(*asset, variant);
+
+ gGL.popMatrix();
+ }
+}
+
+void GLTFSceneManager::render(Asset& asset, U8 variant)
+{
+ bool opaque = !(variant & LLGLSLShader::GLTFVariant::ALPHA_BLEND);
+ bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+
+ if (opaque)
+ {
+ gGLTFPBRMetallicRoughnessProgram.bind(variant);
+ }
+ else
+ { // alpha shaders need all the shadow map setup etc
+ gPipeline.bindDeferredShader(gGLTFPBRMetallicRoughnessProgram.mGLTFVariants[variant]);
+ }
+
+ for (auto& node : asset.mNodes)
+ {
+ if (node.mSkin != INVALID_INDEX)
+ {
+ if (rigged)
+ {
+ Skin& skin = asset.mSkins[node.mSkin];
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_JOINTS, skin.mUBO);
+ }
+ }
+
+ if (node.mMesh != INVALID_INDEX)
+ {
+ Mesh& mesh = asset.mMeshes[node.mMesh];
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mShaderVariant != variant)
+ {
+ continue;
+ }
+
+ if (!rigged)
+ {
+ gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+ }
+ bool cull = true;
+ if (primitive.mMaterial != INVALID_INDEX)
+ {
+ Material& material = asset.mMaterials[primitive.mMaterial];
+ bind(asset, material);
+
+ cull = !material.mDoubleSided;
+ }
+ else
+ {
+ LLFetchedGLTFMaterial::sDefault.bind();
+ }
+
+ LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0);
+
+ 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());
+ }
+ }
+ }
+ }
+}
+
+static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback)
+{
+ if (info.mIndex != INVALID_INDEX)
+ {
+ LLViewerTexture* tex = asset.mImages[asset.mTextures[info.mIndex].mSource].mTexture;
+ if (tex)
+ {
+ tex->addTextureStats(2048.f * 2048.f);
+ LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex);
+ }
+ else
+ {
+ LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
+ }
+ }
+ else
+ {
+ LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
+ }
+}
+
+
+void GLTFSceneManager::bind(Asset& asset, Material& material)
+{
+ // bind for rendering (derived from LLFetchedGLTFMaterial::bind)
+ // glTF 2.0 Specification 3.9.4. Alpha Coverage
+ // mAlphaCutoff is only valid for LLGLTFMaterial::ALPHA_MODE_MASK
+ F32 min_alpha = -1.0;
+
+ LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+
+ if (!LLPipeline::sShadowRender || (material.mAlphaMode == Material::AlphaMode::BLEND))
+ {
+ if (material.mAlphaMode == Material::AlphaMode::MASK)
+ {
+ // dividing the alpha cutoff by transparency here allows the shader to compare against
+ // the alpha value of the texture without needing the transparency value
+ if (material.mPbrMetallicRoughness.mBaseColorFactor.a > 0.f)
+ {
+ min_alpha = material.mAlphaCutoff / material.mPbrMetallicRoughness.mBaseColorFactor.a;
+ }
+ else
+ {
+ min_alpha = 1024.f;
+ }
+ }
+ shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha);
+ }
+
+ bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep);
+
+ F32 base_color_packed[8];
+ //mTextureTransform[GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed);
+ LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed);
+ shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, (F32*)base_color_packed);
+
+ if (!LLPipeline::sShadowRender)
+ {
+ bindTexture(asset, LLShaderMgr::NORMAL_MAP, material.mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep);
+ bindTexture(asset, LLShaderMgr::METALLIC_ROUGHNESS_MAP, material.mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, LLShaderMgr::OCCLUSION_MAP, material.mOcclusionTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, material.mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep);
+
+ // NOTE: base color factor is baked into vertex stream
+
+ shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, material.mPbrMetallicRoughness.mRoughnessFactor);
+ shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, material.mPbrMetallicRoughness.mMetallicFactor);
+ shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(material.mEmissiveFactor));
+
+ F32 normal_packed[8];
+ //mTextureTransform[GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed);
+ LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed);
+ shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, (F32*)normal_packed);
+
+ F32 metallic_roughness_packed[8];
+ //mTextureTransform[GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed);
+ LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed);
+ shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, (F32*)metallic_roughness_packed);
+
+ F32 emissive_packed[8];
+ //mTextureTransform[GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed);
+ LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed);
+ shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, (F32*)emissive_packed);
+ }
+}
+
+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 = obj->getGLTFAssetToAgentTransform();
+ 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.get(), 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 agent_to_asset = obj->getAgentToGLTFAssetTransform();
+
+ vec4 start;
+ vec4 end;
+
+ LLVector4a t;
+ agent_to_asset.affineTransform(gDebugRaycastStart, t);
+ start = glm::make_vec4(t.getF32ptr());
+ agent_to_asset.affineTransform(gDebugRaycastEnd, t);
+ end = glm::make_vec4(t.getF32ptr());
+
+ start.w = end.w = 1.0;
+
+ for (auto& node : asset->mNodes)
+ {
+ Mesh& mesh = asset->mMeshes[node.mMesh];
+
+ if (node.mMesh != INVALID_INDEX)
+ {
+ gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+
+ // 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 1
+ if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST))
+ {
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+
+ // convert raycast to node local space
+ vec4 local_start = node.mAssetMatrixInv * start;
+ vec4 local_end = node.mAssetMatrixInv * end;
+
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mOctree.notNull())
+ {
+ LLVector4a s, e;
+ s.load3(glm::value_ptr(local_start));
+ e.load3(glm::value_ptr(local_end));
+ renderOctreeRaycast(s, e, 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 |
+ LLPipeline::RENDER_DEBUG_NODES))
+ {
+ 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();
+
+ // force update all mRenderMatrix, not just nodes with meshes
+ for (auto& obj : mObjects)
+ {
+ if (obj->isDead() || obj->mGLTFAsset == nullptr)
+ {
+ continue;
+ }
+
+ mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
+
+ mat4 modelview = glm::make_mat4(gGLModelView);
+
+
+ modelview = modelview * mat;
+
+ Asset* asset = obj->mGLTFAsset.get();
+
+ for (auto& node : asset->mNodes)
+ {
+ node.mRenderMatrix = modelview * node.mAssetMatrix;
+ }
+ }
+
+ for (auto& obj : mObjects)
+ {
+ if (obj->isDead() || obj->mGLTFAsset == nullptr)
+ {
+ continue;
+ }
+
+ Asset* asset = obj->mGLTFAsset.get();
+
+ renderAssetDebug(obj, asset);
+ }
+
+ if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_NODES))
+ { //render node hierarchy
+
+ for (U32 i = 0; i < 2; ++i)
+ {
+ LLGLDepthTest depth(GL_TRUE, i == 0 ? GL_FALSE : GL_TRUE, i == 0 ? GL_GREATER : GL_LEQUAL);
+ LLGLState blend(GL_BLEND, i == 0 ? GL_TRUE : GL_FALSE);
+
+
+ gGL.pushMatrix();
+
+ for (auto& obj : mObjects)
+ {
+ if (obj->isDead() || obj->mGLTFAsset == nullptr)
+ {
+ continue;
+ }
+
+ mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
+
+ mat4 modelview = glm::make_mat4(gGLModelView);
+
+ modelview = modelview * mat;
+
+ Asset* asset = obj->mGLTFAsset.get();
+
+ for (auto& node : asset->mNodes)
+ {
+ // force update all mRenderMatrix, not just nodes with meshes
+ node.mRenderMatrix = modelview * node.mAssetMatrix;
+
+ gGL.loadMatrix(glm::value_ptr(node.mRenderMatrix));
+ // 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();
+ gGL.flush();
+
+ 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();
+ gGL.flush();
+
+ gGL.begin(LLRender::LINES);
+ gGL.color4f(0.f, 0.f, 1.f, 0.5f);
+ gGL.vertex3f(0.f, 0.f, 0.f);
+ gGL.vertex3f(0.f, 0.f, 1.f);
+ gGL.end();
+ gGL.flush();
+
+ // 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 = asset->mNodes[child_idx];
+ gGL.vertex3f(0.f, 0.f, 0.f);
+
+
+ gGL.vertex3fv(glm::value_ptr(child.mMatrix[3]));
+ }
+ gGL.end();
+ gGL.flush();
+ }
+ }
+
+ gGL.popMatrix();
+ }
+
+ }
+
+
+ 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.get();
+ 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(glm::value_ptr(node->mRenderMatrix));
+
+
+ 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();
+}
+
+
+