Merge pull request #1371 from secondlife/1357-gltf-asset-prototype

#1357 GLTF Export Prototype
master
cosmic-linden 2024-05-02 10:31:52 -07:00 committed by GitHub
commit 37972cb76c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 757 additions and 41 deletions

View File

@ -48,12 +48,6 @@ public:
*this = rhs;
}
const LLVolumeTriangle& operator=(const LLVolumeTriangle& rhs)
{
LL_ERRS() << "Illegal operation!" << LL_ENDL;
return *this;
}
~LLVolumeTriangle()
{

View File

@ -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;

View File

@ -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);
};

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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);
};
}
}

View File

@ -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];

View File

@ -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();

View File

@ -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:

View File

@ -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,

View File

@ -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,

View File

@ -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");

View File

@ -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];

View File

@ -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;

View File

@ -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"

View File

@ -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>