diff options
author | cosmic-linden <111533034+cosmic-linden@users.noreply.github.com> | 2024-05-02 10:31:52 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-02 10:31:52 -0700 |
commit | 37972cb76cd0459cd4105bbb09eb8f6cd1aa87f9 (patch) | |
tree | bfd85d36db258f46b74aa9989c1615d9bd7faf72 | |
parent | 7fc5f7e649c564fa8479a72a45459d0cc427d0f8 (diff) | |
parent | e8219cb0509d5aacc75cf862c6b8cad026590958 (diff) |
Merge pull request #1371 from secondlife/1357-gltf-asset-prototype
#1357 GLTF Export Prototype
-rw-r--r-- | indra/llmath/llvolumeoctree.h | 6 | ||||
-rw-r--r-- | indra/newview/gltf/accessor.cpp | 18 | ||||
-rw-r--r-- | indra/newview/gltf/accessor.h | 4 | ||||
-rw-r--r-- | indra/newview/gltf/animation.h | 2 | ||||
-rw-r--r-- | indra/newview/gltf/asset.cpp | 434 | ||||
-rw-r--r-- | indra/newview/gltf/asset.h | 81 | ||||
-rw-r--r-- | indra/newview/gltfscenemanager.cpp | 107 | ||||
-rw-r--r-- | indra/newview/gltfscenemanager.h | 15 | ||||
-rw-r--r-- | indra/newview/llfilepicker.cpp | 2 | ||||
-rw-r--r-- | indra/newview/lltinygltfhelper.cpp | 37 | ||||
-rw-r--r-- | indra/newview/lltinygltfhelper.h | 1 | ||||
-rw-r--r-- | indra/newview/llviewermenu.cpp | 26 | ||||
-rw-r--r-- | indra/newview/llviewerobject.cpp | 11 | ||||
-rw-r--r-- | indra/newview/llviewerobject.h | 11 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/menu_viewer.xml | 30 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/notifications.xml | 13 |
16 files changed, 757 insertions, 41 deletions
diff --git a/indra/llmath/llvolumeoctree.h b/indra/llmath/llvolumeoctree.h index 0bbb793896..1df2bf5390 100644 --- a/indra/llmath/llvolumeoctree.h +++ b/indra/llmath/llvolumeoctree.h @@ -48,12 +48,6 @@ public: *this = rhs; } - const LLVolumeTriangle& operator=(const LLVolumeTriangle& rhs) - { - LL_ERRS() << "Illegal operation!" << LL_ENDL; - return *this; - } - ~LLVolumeTriangle() { diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 55d36b7a32..9bfdc2afa6 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -30,6 +30,24 @@ using namespace LL::GLTF; +void Buffer::erase(Asset& asset, S32 offset, S32 length) +{ + S32 idx = this - &asset.mBuffers[0]; + + mData.erase(mData.begin() + offset, mData.begin() + offset + length); + + for (BufferView& view : asset.mBufferViews) + { + if (view.mBuffer == idx) + { + if (view.mByteOffset >= offset) + { + view.mByteOffset -= length; + } + } + } +} + const Buffer& Buffer::operator=(const tinygltf::Buffer& src) { mData = src.data; diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 9b8265d8da..6849cd8609 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -45,6 +45,10 @@ namespace LL std::string mName; std::string mUri; + // erase the given range from this buffer. + // also updates all buffer views in given asset that reference this buffer + void erase(Asset& asset, S32 offset, S32 length); + const Buffer& operator=(const tinygltf::Buffer& src); }; diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index 869eae963a..cc2045ebb2 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -81,8 +81,6 @@ namespace LL S32 mSampler = INVALID_INDEX; Target mTarget; - std::string mTargetPath; - std::string mName; const Channel& operator=(const tinygltf::AnimationChannel& src) { diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 313e82bf01..42f064699c 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -30,9 +30,271 @@ #include "llvolumeoctree.h" #include "../llviewershadermgr.h" #include "../llviewercontrol.h" +#include "../llviewertexturelist.h" using namespace LL::GLTF; +namespace LL +{ + namespace GLTF + { + template <typename T, typename U> + void copy(const std::vector<T>& src, std::vector<U>& dst) + { + dst.resize(src.size()); + for (U32 i = 0; i < src.size(); ++i) + { + copy(src[i], dst[i]); + } + } + + void copy(const Node& src, tinygltf::Node& dst) + { + if (src.mMatrixValid) + { + if (!src.mMatrix.asMatrix4().isIdentity()) + { + dst.matrix.resize(16); + for (U32 i = 0; i < 16; ++i) + { + dst.matrix[i] = src.mMatrix.getF32ptr()[i]; + } + } + } + else if (src.mTRSValid) + { + if (!src.mRotation.equals(glh::quaternionf::identity(), FLT_EPSILON)) + { + dst.rotation.resize(4); + for (U32 i = 0; i < 4; ++i) + { + dst.rotation[i] = src.mRotation.get_value()[i]; + } + } + + if (src.mTranslation != glh::vec3f(0.f, 0.f, 0.f)) + { + dst.translation.resize(3); + for (U32 i = 0; i < 3; ++i) + { + dst.translation[i] = src.mTranslation.v[i]; + } + } + + if (src.mScale != glh::vec3f(1.f, 1.f, 1.f)) + { + dst.scale.resize(3); + for (U32 i = 0; i < 3; ++i) + { + dst.scale[i] = src.mScale.v[i]; + } + } + } + + dst.children = src.mChildren; + dst.mesh = src.mMesh; + dst.skin = src.mSkin; + dst.name = src.mName; + } + + void copy(const Scene& src, tinygltf::Scene& dst) + { + dst.nodes = src.mNodes; + dst.name = src.mName; + } + + void copy(const Primitive& src, tinygltf::Primitive& dst) + { + for (auto& attrib : src.mAttributes) + { + dst.attributes[attrib.first] = attrib.second; + } + dst.indices = src.mIndices; + dst.material = src.mMaterial; + dst.mode = src.mMode; + } + + void copy(const Mesh& src, tinygltf::Mesh& mesh) + { + copy(src.mPrimitives, mesh.primitives); + mesh.weights = src.mWeights; + mesh.name = src.mName; + } + + void copy(const Material::TextureInfo& src, tinygltf::TextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + } + + void copy(const Material::OcclusionTextureInfo& src, tinygltf::OcclusionTextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + dst.strength = src.mStrength; + } + + void copy(const Material::NormalTextureInfo& src, tinygltf::NormalTextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + dst.scale = src.mScale; + } + + void copy(const Material::PbrMetallicRoughness& src, tinygltf::PbrMetallicRoughness& dst) + { + dst.baseColorFactor = { src.mBaseColorFactor.v[0], src.mBaseColorFactor.v[1], src.mBaseColorFactor.v[2], src.mBaseColorFactor.v[3] }; + copy(src.mBaseColorTexture, dst.baseColorTexture); + dst.metallicFactor = src.mMetallicFactor; + dst.roughnessFactor = src.mRoughnessFactor; + copy(src.mMetallicRoughnessTexture, dst.metallicRoughnessTexture); + } + + void copy(const Material& src, tinygltf::Material& material) + { + material.name = src.mName; + + material.emissiveFactor = { src.mEmissiveFactor.v[0], src.mEmissiveFactor.v[1], src.mEmissiveFactor.v[2] }; + copy(src.mPbrMetallicRoughness, material.pbrMetallicRoughness); + copy(src.mNormalTexture, material.normalTexture); + copy(src.mEmissiveTexture, material.emissiveTexture); + } + + void copy(const Texture& src, tinygltf::Texture& texture) + { + texture.sampler = src.mSampler; + texture.source = src.mSource; + texture.name = src.mName; + } + + void copy(const Sampler& src, tinygltf::Sampler& sampler) + { + sampler.magFilter = src.mMagFilter; + sampler.minFilter = src.mMinFilter; + sampler.wrapS = src.mWrapS; + sampler.wrapT = src.mWrapT; + sampler.name = src.mName; + } + + void copy(const Skin& src, tinygltf::Skin& skin) + { + skin.joints = src.mJoints; + skin.inverseBindMatrices = src.mInverseBindMatrices; + skin.skeleton = src.mSkeleton; + skin.name = src.mName; + } + + void copy(const Accessor& src, tinygltf::Accessor& accessor) + { + accessor.bufferView = src.mBufferView; + accessor.byteOffset = src.mByteOffset; + accessor.componentType = src.mComponentType; + accessor.minValues = src.mMin; + accessor.maxValues = src.mMax; + + accessor.count = src.mCount; + accessor.type = src.mType; + accessor.normalized = src.mNormalized; + accessor.name = src.mName; + } + + void copy(const Animation::Sampler& src, tinygltf::AnimationSampler& sampler) + { + sampler.input = src.mInput; + sampler.output = src.mOutput; + sampler.interpolation = src.mInterpolation; + } + + void copy(const Animation::Channel& src, tinygltf::AnimationChannel& channel) + { + channel.sampler = src.mSampler; + channel.target_node = src.mTarget.mNode; + channel.target_path = src.mTarget.mPath; + } + + void copy(const Animation& src, tinygltf::Animation& animation) + { + animation.name = src.mName; + + copy(src.mSamplers, animation.samplers); + + U32 channel_count = src.mRotationChannels.size() + src.mTranslationChannels.size() + src.mScaleChannels.size(); + + animation.channels.resize(channel_count); + + U32 idx = 0; + for (U32 i = 0; i < src.mTranslationChannels.size(); ++i) + { + copy(src.mTranslationChannels[i], animation.channels[idx++]); + } + + for (U32 i = 0; i < src.mRotationChannels.size(); ++i) + { + copy(src.mRotationChannels[i], animation.channels[idx++]); + } + + for (U32 i = 0; i < src.mScaleChannels.size(); ++i) + { + copy(src.mScaleChannels[i], animation.channels[idx++]); + } + } + + void copy(const Buffer& src, tinygltf::Buffer& buffer) + { + buffer.uri = src.mUri; + buffer.data = src.mData; + buffer.name = src.mName; + } + + void copy(const BufferView& src, tinygltf::BufferView& bufferView) + { + bufferView.buffer = src.mBuffer; + bufferView.byteOffset = src.mByteOffset; + bufferView.byteLength = src.mByteLength; + bufferView.byteStride = src.mByteStride; + bufferView.target = src.mTarget; + bufferView.name = src.mName; + } + + void copy(const Image& src, tinygltf::Image& image) + { + image.name = src.mName; + image.width = src.mWidth; + image.height = src.mHeight; + image.component = src.mComponent; + image.bits = src.mBits; + image.pixel_type = src.mPixelType; + + image.image = src.mData; + image.bufferView = src.mBufferView; + image.mimeType = src.mMimeType; + image.uri = src.mUri; + } + + void copy(const Asset & src, tinygltf::Model& dst) + { + dst.defaultScene = src.mDefaultScene; + dst.asset.copyright = src.mCopyright; + dst.asset.version = src.mVersion; + dst.asset.minVersion = src.mMinVersion; + dst.asset.generator = "Linden Lab Experimental GLTF Export"; + dst.asset.extras = src.mExtras; + + copy(src.mScenes, dst.scenes); + copy(src.mNodes, dst.nodes); + copy(src.mMeshes, dst.meshes); + copy(src.mMaterials, dst.materials); + copy(src.mBuffers, dst.buffers); + copy(src.mBufferViews, dst.bufferViews); + copy(src.mTextures, dst.textures); + copy(src.mSamplers, dst.samplers); + copy(src.mImages, dst.images); + copy(src.mAccessors, dst.accessors); + copy(src.mAnimations, dst.animations); + copy(src.mSkins, dst.skins); + } + } +} void Scene::updateTransforms(Asset& asset) { LLMatrix4a identity; @@ -237,6 +499,8 @@ void Node::makeMatrixValid() mMatrix.loadu(t.m); mMatrixValid = true; } + + llassert(mMatrixValid); } void Node::makeTRSValid() @@ -252,6 +516,8 @@ void Node::makeTRSValid() mRotation.set_value(t); mTRSValid = true; } + + llassert(mTRSValid); } void Node::setRotation(const glh::quaternionf& q) @@ -318,6 +584,7 @@ const Node& Node::operator=(const tinygltf::Node& src) { // node specifies no transformation, set to identity mMatrix.setIdentity(); + mMatrixValid = true; } mChildren = src.children; @@ -467,6 +734,15 @@ void Asset::allocateGLResources(const std::string& filename, const tinygltf::Mod const Asset& Asset::operator=(const tinygltf::Model& src) { + mVersion = src.asset.version; + mMinVersion = src.asset.minVersion; + mGenerator = src.asset.generator; + mCopyright = src.asset.copyright; + mExtras = src.asset.extras; + + mDefaultScene = src.defaultScene; + + mScenes.resize(src.scenes.size()); for (U32 i = 0; i < src.scenes.size(); ++i) { @@ -542,9 +818,167 @@ const Asset& Asset::operator=(const tinygltf::Model& src) return *this; } +void Asset::save(tinygltf::Model& dst) +{ + LL::GLTF::copy(*this, dst); +} + +void Asset::decompose(const std::string& filename) +{ + // get folder path + std::string folder = gDirUtilp->getDirName(filename); + + // decompose images + for (auto& image : mImages) + { + image.decompose(*this, folder); + } +} + +void Asset::eraseBufferView(S32 bufferView) +{ + mBufferViews.erase(mBufferViews.begin() + bufferView); + + for (auto& accessor : mAccessors) + { + if (accessor.mBufferView > bufferView) + { + accessor.mBufferView--; + } + } + + for (auto& image : mImages) + { + if (image.mBufferView > bufferView) + { + image.mBufferView--; + } + } + +} + +void Image::decompose(Asset& asset, const std::string& folder) +{ + std::string name = mName; + if (name.empty()) + { + S32 idx = this - asset.mImages.data(); + name = llformat("image_%d", idx); + } + + if (mBufferView != INVALID_INDEX) + { + // save original image + BufferView& bufferView = asset.mBufferViews[mBufferView]; + Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; + + std::string extension; + + if (mMimeType == "image/jpeg") + { + extension = ".jpg"; + } + else if (mMimeType == "image/png") + { + extension = ".png"; + } + else + { + extension = ".bin"; + } + + std::string filename = folder + "/" + name + "." + extension; + + // set URI to non-j2c file for now, but later we'll want to reference the j2c hash + mUri = name + "." + extension; + + std::ofstream file(filename, std::ios::binary); + file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength); + + buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength); + + asset.eraseBufferView(mBufferView); + } + + if (!mData.empty()) + { + // save j2c image + std::string filename = folder + "/" + name + ".j2c"; + + LLPointer<LLImageRaw> raw = new LLImageRaw(mWidth, mHeight, mComponent); + U8* data = raw->allocateData(); + llassert(mData.size() == raw->getDataSize()); + memcpy(data, mData.data(), mData.size()); + + LLViewerTextureList::createUploadFile(raw, filename, 4096); + + mData.clear(); + } + + mWidth = -1; + mHeight = -1; + mComponent = -1; + mBits = -1; + mPixelType = -1; + mMimeType = ""; + +} + +const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + return *this; +} + +const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const tinygltf::OcclusionTextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + mStrength = src.strength; + return *this; +} + +const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const tinygltf::NormalTextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + mScale = src.scale; + return *this; +} + +const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const tinygltf::PbrMetallicRoughness& src) +{ + if (src.baseColorFactor.size() == 4) + { + mBaseColorFactor.set_value(src.baseColorFactor[0], src.baseColorFactor[1], src.baseColorFactor[2], src.baseColorFactor[3]); + } + + mBaseColorTexture = src.baseColorTexture; + mMetallicFactor = src.metallicFactor; + mRoughnessFactor = src.roughnessFactor; + mMetallicRoughnessTexture = src.metallicRoughnessTexture; + + return *this; +} const Material& Material::operator=(const tinygltf::Material& src) { mName = src.name; + + if (src.emissiveFactor.size() == 3) + { + mEmissiveFactor.set_value(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); + } + + mPbrMetallicRoughness = src.pbrMetallicRoughness; + mNormalTexture = src.normalTexture; + mOcclusionTexture = src.occlusionTexture; + mEmissiveTexture = src.emissiveTexture; + + mAlphaMode = src.alphaMode; + mAlphaCutoff = src.alphaCutoff; + mDoubleSided = src.doubleSided; + return *this; } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 6e576a1ffe..5a62313705 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -45,11 +45,59 @@ namespace LL class Material { public: + class TextureInfo + { + public: + S32 mIndex = INVALID_INDEX; + S32 mTexCoord = 0; + + const TextureInfo& operator=(const tinygltf::TextureInfo& src); + }; + + class NormalTextureInfo : public TextureInfo + { + public: + F32 mScale = 1.0f; + + const NormalTextureInfo& operator=(const tinygltf::NormalTextureInfo& src); + }; + + class OcclusionTextureInfo : public TextureInfo + { + public: + F32 mStrength = 1.0f; + + const OcclusionTextureInfo& operator=(const tinygltf::OcclusionTextureInfo& src); + }; + + class PbrMetallicRoughness + { + public: + glh::vec4f mBaseColorFactor = glh::vec4f(1.f,1.f,1.f,1.f); + TextureInfo mBaseColorTexture; + F32 mMetallicFactor = 1.0f; + F32 mRoughnessFactor = 1.0f; + TextureInfo mMetallicRoughnessTexture; + const PbrMetallicRoughness& operator=(const tinygltf::PbrMetallicRoughness& src); + }; + + // use LLFetchedGLTFMaterial for now, but eventually we'll want to use // a more flexible GLTF material implementation instead of the fixed packing // version we use for sharable GLTF material assets LLPointer<LLFetchedGLTFMaterial> mMaterial; + PbrMetallicRoughness mPbrMetallicRoughness; + NormalTextureInfo mNormalTexture; + OcclusionTextureInfo mOcclusionTexture; + TextureInfo mEmissiveTexture; + + std::string mName; + glh::vec3f mEmissiveFactor = glh::vec3f(0.f, 0.f, 0.f); + std::string mAlphaMode = "OPAQUE"; + F32 mAlphaCutoff = 0.5f; + bool mDoubleSided = false; + const Material& operator=(const tinygltf::Material& src); @@ -179,11 +227,16 @@ namespace LL std::string mName; std::string mUri; std::string mMimeType; + + S32 mBufferView = INVALID_INDEX; + std::vector<U8> mData; S32 mWidth; S32 mHeight; S32 mComponent; S32 mBits; + S32 mPixelType; + LLPointer<LLViewerFetchedTexture> mTexture; const Image& operator=(const tinygltf::Image& src) @@ -196,10 +249,14 @@ namespace LL mHeight = src.height; mComponent = src.component; mBits = src.bits; - + mBufferView = src.bufferView; + mPixelType = src.pixel_type; return *this; } + // save image clear local data, and set uri + void decompose(Asset& asset, const std::string& filename); + void allocateGLResources() { // allocate texture @@ -208,7 +265,7 @@ namespace LL }; // C++ representation of a GLTF Asset - class Asset : public LLRefCount + class Asset { public: std::vector<Scene> mScenes; @@ -224,6 +281,15 @@ namespace LL std::vector<Animation> mAnimations; std::vector<Skin> mSkins; + std::string mVersion; + std::string mGenerator; + std::string mMinVersion; + std::string mCopyright; + + S32 mDefaultScene = INVALID_INDEX; + tinygltf::Value mExtras; + + // the last time update() was called according to gFrameTimeSeconds F32 mLastUpdateTime = gFrameTimeSeconds; @@ -258,7 +324,16 @@ namespace LL ); const Asset& operator=(const tinygltf::Model& src); - + + // save the asset to a tinygltf model + void save(tinygltf::Model& dst); + + // decompose the asset to the given .gltf file + void decompose(const std::string& filename); + + // remove the bufferview at the given index + // updates all bufferview indices in this Asset as needed + void eraseBufferView(S32 bufferView); }; } } diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 4e3439ea5c..6533f283e2 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -66,11 +66,95 @@ void GLTFSceneManager::load() } }, LLFilePicker::FFLOAD_GLTF, - true); + false); } else { - LLNotificationsUtil::add("GLTFPreviewSelection"); + 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::decomposeSelection() +{ + 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().decomposeSelection(filenames[0]); + } + }, + LLFilePicker::FFSAVE_GLTF, + "scene.gltf"); + } + else + { + LLNotificationsUtil::add("GLTFSaveSelection"); + } +} + +void GLTFSceneManager::decomposeSelection(const std::string& filename) +{ + LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); + if (obj && obj->mGLTFAsset) + { + // copy asset out for decomposition + Asset asset = *obj->mGLTFAsset; + + // decompose the asset into component parts + asset.decompose(filename); + + // copy decomposed asset into tinygltf for serialization + tinygltf::Model model; + asset.save(model); + + LLTinyGLTFHelper::saveModel(filename, model); + } +} + +void GLTFSceneManager::save(const std::string& filename) +{ + LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); + if (obj && obj->mGLTFAsset) + { + Asset* asset = obj->mGLTFAsset.get(); + tinygltf::Model model; + asset->save(model); + + LLTinyGLTFHelper::saveModel(filename, model); } } @@ -79,7 +163,7 @@ void GLTFSceneManager::load(const std::string& filename) tinygltf::Model model; LLTinyGLTFHelper::loadModel(filename, model); - LLPointer<Asset> asset = new Asset(); + std::shared_ptr<Asset> asset = std::make_shared<Asset>(); *asset = model; gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions @@ -126,10 +210,7 @@ void GLTFSceneManager::update() continue; } - Asset* asset = mObjects[i]->mGLTFAsset; - - asset->update(); - + mObjects[i]->mGLTFAsset->update(); } } @@ -151,7 +232,7 @@ void GLTFSceneManager::render(bool opaque, bool rigged) continue; } - Asset* asset = mObjects[i]->mGLTFAsset; + Asset* asset = mObjects[i]->mGLTFAsset.get(); gGL.pushMatrix(); @@ -298,7 +379,7 @@ LLDrawable* GLTFSceneManager::lineSegmentIntersect(const LLVector4a& start, cons } // 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)) + 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) @@ -425,7 +506,7 @@ void GLTFSceneManager::renderDebug() matMul(mat, modelview, modelview); - Asset* asset = obj->mGLTFAsset; + Asset* asset = obj->mGLTFAsset.get(); for (auto& node : asset->mNodes) { @@ -440,7 +521,7 @@ void GLTFSceneManager::renderDebug() continue; } - Asset* asset = obj->mGLTFAsset; + Asset* asset = obj->mGLTFAsset.get(); LLMatrix4a mat = obj->getGLTFAssetToAgentTransform(); @@ -477,7 +558,7 @@ void GLTFSceneManager::renderDebug() matMul(mat, modelview, modelview); - Asset* asset = obj->mGLTFAsset; + Asset* asset = obj->mGLTFAsset.get(); for (auto& node : asset->mNodes) { @@ -538,7 +619,7 @@ void GLTFSceneManager::renderDebug() if (drawable) { gGL.pushMatrix(); - Asset* asset = drawable->getVObj()->mGLTFAsset; + Asset* asset = drawable->getVObj()->mGLTFAsset.get(); Node* node = &asset->mNodes[node_hit]; Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit]; diff --git a/indra/newview/gltfscenemanager.h b/indra/newview/gltfscenemanager.h index d286f335e4..e380be7e3c 100644 --- a/indra/newview/gltfscenemanager.h +++ b/indra/newview/gltfscenemanager.h @@ -28,6 +28,16 @@ #include "llsingleton.h" #include "llviewerobject.h" +class LLVOVolume; +class LLDrawable; + +namespace LL +{ + namespace GLTF + { + class Asset; + } +} namespace LL { @@ -40,6 +50,11 @@ namespace LL void load(); // open filepicker to choose asset void load(const std::string& filename); // load asset from filename + void saveAs(); // open filepicker and choose file to save selected asset to + void save(const std::string& filename); // save selected asset to filename (suitable for use in external programs) + void decomposeSelection(); // open file picker and choose a location to decompose to + void decomposeSelection(const std::string& filename); // decompose selected asset into simulator-ready .gltf, .bin, and .j2c files + void update(); void render(bool opaque, bool rigged = false); void renderOpaque(); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index b82172c506..23d9b25344 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -790,7 +790,7 @@ void set_nav_save_data(LLFilePicker::ESaveFilter filter, std::string &extension, case LLFilePicker::FFSAVE_GLTF: type = "\?\?\?\?"; creator = "\?\?\?\?"; - extension = "glb"; + extension = "glb,gltf"; break; case LLFilePicker::FFSAVE_XML: diff --git a/indra/newview/lltinygltfhelper.cpp b/indra/newview/lltinygltfhelper.cpp index 49c35165e6..c8a9a36ad1 100644 --- a/indra/newview/lltinygltfhelper.cpp +++ b/indra/newview/lltinygltfhelper.cpp @@ -248,6 +248,43 @@ bool LLTinyGLTFHelper::loadModel(const std::string& filename, tinygltf::Model& m return false; } +bool LLTinyGLTFHelper::saveModel(const std::string& filename, tinygltf::Model& model_in) +{ + std::string exten = gDirUtilp->getExtension(filename); + + bool success = false; + + if (exten == "gltf" || exten == "glb") + { + tinygltf::TinyGLTF writer; + + std::string filename_lc = filename; + LLStringUtil::toLower(filename_lc); + + + bool embed_images = false; + bool embed_buffers = false; + bool pretty_print = true; + bool write_binary = false; + + + if (std::string::npos == filename_lc.rfind(".gltf")) + { // file is binary + embed_images = embed_buffers = write_binary = true; + } + + success = writer.WriteGltfSceneToFile(&model_in, filename, embed_images, embed_buffers, pretty_print, write_binary); + + if (!success) + { + LL_WARNS("GLTF") << "Failed to save" << LL_ENDL; + return false; + } + } + + return success; +} + bool LLTinyGLTFHelper::getMaterialFromModel( const std::string& filename, const tinygltf::Model& model_in, diff --git a/indra/newview/lltinygltfhelper.h b/indra/newview/lltinygltfhelper.h index da505b41e9..a259609404 100644 --- a/indra/newview/lltinygltfhelper.h +++ b/indra/newview/lltinygltfhelper.h @@ -42,6 +42,7 @@ namespace LLTinyGLTFHelper LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index, bool flip = true); bool loadModel(const std::string& filename, tinygltf::Model& model_out); + bool saveModel(const std::string& filename, tinygltf::Model& model_in); bool getMaterialFromModel( const std::string& filename, diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index c1a8c5df9e..631e1b57d9 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -7980,7 +7980,7 @@ class LLAdvancedClickHDRIPreview: public view_listener_t }; -class LLAdvancedClickGLTFScenePreview : public view_listener_t +class LLAdvancedClickGLTFOpen: public view_listener_t { bool handleEvent(const LLSD& userdata) { @@ -7990,6 +7990,26 @@ class LLAdvancedClickGLTFScenePreview : public view_listener_t } }; +class LLAdvancedClickGLTFSaveAs : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // open personal lighting floater when previewing an HDRI (keeps HDRI from implicitly unloading when opening build tools) + LL::GLTFSceneManager::instance().saveAs(); + return true; + } +}; + +class LLAdvancedClickGLTFDecompose : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // open personal lighting floater when previewing an HDRI (keeps HDRI from implicitly unloading when opening build tools) + LL::GLTFSceneManager::instance().decomposeSelection(); + return true; + } +}; + // these are used in the gl menus to set control values that require shader recompilation class LLToggleShaderControl : public view_listener_t { @@ -9637,7 +9657,9 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedClickRenderProfile(), "Advanced.ClickRenderProfile"); view_listener_t::addMenu(new LLAdvancedClickRenderBenchmark(), "Advanced.ClickRenderBenchmark"); view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview"); - view_listener_t::addMenu(new LLAdvancedClickGLTFScenePreview(), "Advanced.ClickGLTFScenePreview"); + view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen"); + view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs"); + view_listener_t::addMenu(new LLAdvancedClickGLTFDecompose(), "Advanced.ClickGLTFDecompose"); view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain"); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index b66d4b1dab..cc6f4403dd 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -107,6 +107,7 @@ #include "llmeshrepository.h" #include "llgltfmateriallist.h" #include "llgl.h" +#include "gltf/asset.h" //#define DEBUG_UPDATE_TYPE @@ -4379,7 +4380,7 @@ LLMatrix4a LLViewerObject::getGLTFAssetToAgentTransform() const LLMatrix4 root; root.initScale(getScale()); root.rotate(getRenderRotation()); - root.translate(getPositionAgent()); + root.translate(getRenderPosition()); LLMatrix4a mat; mat.loadu((F32*)root.mMatrix); @@ -4403,7 +4404,7 @@ LLMatrix4a LLViewerObject::getAgentToGLTFAssetTransform() const scale.mV[1] = 1.f / scale.mV[1]; scale.mV[2] = 1.f / scale.mV[2]; - root.translate(-getPositionAgent()); + root.translate(-getRenderPosition()); root.rotate(~getRenderRotation()); LLMatrix4 scale_mat; @@ -4420,7 +4421,7 @@ LLMatrix4a LLViewerObject::getGLTFNodeTransformAgent(S32 node_index) const { LLMatrix4a mat; - if (mGLTFAsset.notNull() && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) + if (mGLTFAsset && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) { auto& node = mGLTFAsset->mNodes[node_index]; @@ -4474,7 +4475,7 @@ void decomposeMatrix(const LLMatrix4a& mat, LLVector3& position, LLQuaternion& r void LLViewerObject::setGLTFNodeRotationAgent(S32 node_index, const LLQuaternion& rotation) { - if (mGLTFAsset.notNull() && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) + if (mGLTFAsset && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) { auto& node = mGLTFAsset->mNodes[node_index]; @@ -4506,7 +4507,7 @@ void LLViewerObject::setGLTFNodeRotationAgent(S32 node_index, const LLQuaternion void LLViewerObject::moveGLTFNode(S32 node_index, const LLVector3& offset) { - if (mGLTFAsset.notNull() && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) + if (mGLTFAsset && node_index >= 0 && node_index < mGLTFAsset->mNodes.size()) { auto& node = mGLTFAsset->mNodes[node_index]; diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 64c1ee6633..e314992532 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -45,7 +45,14 @@ #include "llbbox.h" #include "llrigginginfo.h" #include "llreflectionmap.h" -#include "gltf/asset.h" + +namespace LL +{ + namespace GLTF + { + class Asset; + } +} class LLAgent; // TODO: Get rid of this. class LLAudioSource; @@ -736,7 +743,7 @@ public: F32 mPhysicsRestitution; // Associated GLTF Asset - LLPointer<LL::GLTF::Asset> mGLTFAsset; + std::shared_ptr<LL::GLTF::Asset> mGLTFAsset; // Pipeline classes LLPointer<LLDrawable> mDrawable; diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 644553e64c..6238efe688 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2844,6 +2844,30 @@ function="World.EnvPreset" parameter="mem_leaking" /> </menu_item_call> </menu> + <menu + create_jump_keys="true" + label="GLTF" + name="GLTF" + tear_off="true"> + <menu_item_call + label="Open..." + name="Open..."> + <menu_item_call.on_click + function="Advanced.ClickGLTFOpen" /> + </menu_item_call> + <menu_item_call + label="Save As..." + name="Save As..."> + <menu_item_call.on_click + function="Advanced.ClickGLTFSaveAs" /> + </menu_item_call> + <menu_item_call + label="Decompose..." + name="Decompose..."> + <menu_item_call.on_click + function="Advanced.ClickGLTFDecompose" /> + </menu_item_call> + </menu> <menu create_jump_keys="true" label="Render Tests" @@ -2896,12 +2920,6 @@ function="World.EnvPreset" <menu_item_call.on_click function="Advanced.ClickHDRIPreview" /> </menu_item_call> - <menu_item_call - label="GLTF Scene Preview" - name="GLTF Scene Preview"> - <menu_item_call.on_click - function="Advanced.ClickGLTFScenePreview" /> - </menu_item_call> </menu> <menu create_jump_keys="true" diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index ad1f12002e..7d7ea986dd 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -12408,7 +12408,7 @@ are wearing now. <notification icon="alertmodal.tga" - name="GLTFPreviewSelection" + name="GLTFOpenSelection" type="alert"> You must select an object to act as a handle to the GLTF asset you are previewing. <tag>fail</tag> @@ -12417,4 +12417,15 @@ are wearing now. yestext="OK"/> </notification> + <notification + icon="alertmodal.tga" + name="GLTFSaveSelection" + type="alert"> + You must select an object that has a GLTF asset associated with it. + <tag>fail</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + </notifications> |