/** * @file asset.cpp * @brief LL GLTF Implementation * * $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 "asset.h" #include "llvolumeoctree.h" #include "../llviewershadermgr.h" #include "../llviewercontrol.h" #include "../llviewertexturelist.h" #include "../pipeline.h" #include "buffer_util.h" using namespace LL::GLTF; using namespace boost::json; namespace LL { namespace GLTF { Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) { if (alpha_mode == "OPAQUE") { return Material::AlphaMode::OPAQUE; } else if (alpha_mode == "MASK") { return Material::AlphaMode::MASK; } else if (alpha_mode == "BLEND") { return Material::AlphaMode::BLEND; } else { return Material::AlphaMode::OPAQUE; } } std::string enum_to_gltf_alpha_mode(Material::AlphaMode alpha_mode) { switch (alpha_mode) { case Material::AlphaMode::OPAQUE: return "OPAQUE"; case Material::AlphaMode::MASK: return "MASK"; case Material::AlphaMode::BLEND: return "BLEND"; default: return "OPAQUE"; } } template void copy(const std::vector& src, std::vector& 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 != glm::identity()) { dst.matrix.resize(16); const F32* m = glm::value_ptr(src.mMatrix); for (U32 i = 0; i < 16; ++i) { dst.matrix[i] = m[i]; } } } else if (src.mTRSValid) { if (src.mRotation != glm::identity()) { dst.rotation.resize(4); dst.rotation[0] = src.mRotation.x; dst.rotation[1] = src.mRotation.y; dst.rotation[2] = src.mRotation.z; dst.rotation[3] = src.mRotation.w; } if (src.mTranslation != vec3(0.f, 0.f, 0.f)) { dst.translation.resize(3); dst.translation[0] = src.mTranslation.x; dst.translation[1] = src.mTranslation.y; dst.translation[2] = src.mTranslation.z; } if (src.mScale != vec3(1.f, 1.f, 1.f)) { dst.scale.resize(3); dst.scale[0] = src.mScale.x; dst.scale[1] = src.mScale.y; dst.scale[2] = src.mScale.z; } } 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.r, src.mBaseColorFactor.g, src.mBaseColorFactor.b, src.mBaseColorFactor.a }; 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.r, src.mEmissiveFactor.g, src.mEmissiveFactor.b }; 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 = (S32) 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"; // NOTE: extras are lost in the conversion for now 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) { mat4 identity = glm::identity(); for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; node.updateTransforms(asset, identity); } } void Scene::updateRenderTransforms(Asset& asset, const mat4& modelview) { for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; node.updateRenderTransforms(asset, modelview); } } void Node::updateRenderTransforms(Asset& asset, const mat4& modelview) { mRenderMatrix = modelview * mMatrix; for (auto& childIndex : mChildren) { Node& child = asset.mNodes[childIndex]; child.updateRenderTransforms(asset, mRenderMatrix); } } void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) { makeMatrixValid(); mAssetMatrix = parentMatrix * mMatrix; mAssetMatrixInv = glm::inverse(mAssetMatrix); S32 my_index = this - &asset.mNodes[0]; for (auto& childIndex : mChildren) { Node& child = asset.mNodes[childIndex]; child.mParent = my_index; child.updateTransforms(asset, mAssetMatrix); } } void Asset::updateTransforms() { for (auto& scene : mScenes) { scene.updateTransforms(*this); } } void Asset::updateRenderTransforms(const mat4& modelview) { // use mAssetMatrix to update render transforms from node list for (auto& node : mNodes) { node.mRenderMatrix = modelview * node.mAssetMatrix; } } S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, 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 S32* primitive_hitp ) { S32 node_hit = -1; S32 primitive_hit = -1; LLVector4a local_start; LLVector4a asset_end = end; LLVector4a local_end; LLVector4a p; for (auto& node : mNodes) { if (node.mMesh != INVALID_INDEX) { bool newHit = false; LLMatrix4a ami; ami.loadu(glm::value_ptr(node.mAssetMatrixInv)); // transform start and end to this node's local space ami.affineTransform(start, local_start); ami.affineTransform(asset_end, local_end); Mesh& mesh = mMeshes[node.mMesh]; for (auto& primitive : mesh.mPrimitives) { const LLVolumeTriangle* tri = primitive.lineSegmentIntersect(local_start, local_end, &p, tex_coord, normal, tangent); if (tri) { newHit = true; local_end = p; // pointer math to get the node index node_hit = &node - &mNodes[0]; llassert(&mNodes[node_hit] == &node); //pointer math to get the primitive index primitive_hit = &primitive - &mesh.mPrimitives[0]; llassert(&mesh.mPrimitives[primitive_hit] == &primitive); } } if (newHit) { LLMatrix4a am; am.loadu(glm::value_ptr(node.mAssetMatrix)); // shorten line segment on hit am.affineTransform(p, asset_end); // transform results back to asset space if (intersection) { *intersection = asset_end; } if (normal || tangent) { mat4 normalMatrix = glm::transpose(node.mAssetMatrixInv); LLMatrix4a norm_mat; norm_mat.loadu(glm::value_ptr(normalMatrix)); if (normal) { LLVector4a n = *normal; F32 w = n.getF32ptr()[3]; n.getF32ptr()[3] = 0.0f; norm_mat.affineTransform(n, *normal); normal->getF32ptr()[3] = w; } if (tangent) { LLVector4a t = *tangent; F32 w = t.getF32ptr()[3]; t.getF32ptr()[3] = 0.0f; norm_mat.affineTransform(t, *tangent); tangent->getF32ptr()[3] = w; } } } } } if (node_hit != -1) { if (primitive_hitp) { *primitive_hitp = primitive_hit; } } return node_hit; } void Node::makeMatrixValid() { if (!mMatrixValid && mTRSValid) { mMatrix = glm::recompose(mScale, mRotation, mTranslation, vec3(0,0,0), vec4(0,0,0,1)); mMatrixValid = true; } llassert(mMatrixValid); } void Node::makeTRSValid() { if (!mTRSValid && mMatrixValid) { vec3 skew; vec4 perspective; glm::decompose(mMatrix, mScale, mRotation, mTranslation, skew, perspective); mTRSValid = true; } llassert(mTRSValid); } void Node::setRotation(const quat& q) { makeTRSValid(); mRotation = q; mMatrixValid = false; } void Node::setTranslation(const vec3& t) { makeTRSValid(); mTranslation = t; mMatrixValid = false; } void Node::setScale(const vec3& s) { makeTRSValid(); mScale = s; mMatrixValid = false; } void Node::serialize(object& dst) const { write(mName, "name", dst); write(mMatrix, "matrix", dst, glm::identity()); write(mRotation, "rotation", dst); write(mTranslation, "translation", dst); write(mScale, "scale", dst, vec3(1.f,1.f,1.f)); write(mChildren, "children", dst); write(mMesh, "mesh", dst, INVALID_INDEX); write(mSkin, "skin", dst, INVALID_INDEX); } const Node& Node::operator=(const Value& src) { copy(src, "name", mName); mMatrixValid = copy(src, "matrix", mMatrix); copy(src, "rotation", mRotation); copy(src, "translation", mTranslation); copy(src, "scale", mScale); copy(src, "children", mChildren); copy(src, "mesh", mMesh); copy(src, "skin", mSkin); if (!mMatrixValid) { mTRSValid = true; } return *this; } const Node& Node::operator=(const tinygltf::Node& src) { F32* dstMatrix = glm::value_ptr(mMatrix); if (src.matrix.size() == 16) { // Node has a transformation matrix, just copy it for (U32 i = 0; i < 16; ++i) { dstMatrix[i] = (F32)src.matrix[i]; } mMatrixValid = true; } else if (!src.rotation.empty() || !src.translation.empty() || !src.scale.empty()) { // node has rotation/translation/scale, convert to matrix if (src.rotation.size() == 4) { mRotation = quat((F32)src.rotation[3], (F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2]); } if (src.translation.size() == 3) { mTranslation = vec3((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]); } if (src.scale.size() == 3) { mScale = vec3((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]); } else { mScale = vec3(1.f, 1.f, 1.f); } mTRSValid = true; } else { // node specifies no transformation, set to identity mMatrix = glm::identity(); mMatrixValid = true; } mChildren = src.children; mMesh = src.mesh; mSkin = src.skin; mName = src.name; return *this; } void Image::serialize(object& dst) const { write(mUri, "uri", dst); write(mMimeType, "mimeType", dst); write(mBufferView, "bufferView", dst, INVALID_INDEX); write(mName, "name", dst); write(mWidth, "width", dst, -1); write(mHeight, "height", dst, -1); write(mComponent, "component", dst, -1); write(mBits, "bits", dst, -1); write(mPixelType, "pixelType", dst, -1); } const Image& Image::operator=(const Value& src) { copy(src, "uri", mUri); copy(src, "mimeType", mMimeType); copy(src, "bufferView", mBufferView); copy(src, "name", mName); copy(src, "width", mWidth); copy(src, "height", mHeight); copy(src, "component", mComponent); copy(src, "bits", mBits); copy(src, "pixelType", mPixelType); return *this; } const Image& Image::operator=(const tinygltf::Image& src) { mName = src.name; mWidth = src.width; mHeight = src.height; mComponent = src.component; mBits = src.bits; mPixelType = src.pixel_type; mUri = src.uri; mBufferView = src.bufferView; mMimeType = src.mimeType; mData = src.image; return *this; } void Asset::render(bool opaque, bool rigged) { if (rigged) { gGL.loadIdentity(); } for (auto& node : mNodes) { if (node.mSkin != INVALID_INDEX) { if (rigged) { Skin& skin = mSkins[node.mSkin]; skin.uploadMatrixPalette(*this, node); } else { //skip static nodes if we're rendering rigged continue; } } else if (rigged) { // skip rigged nodes if we're not rendering rigged continue; } if (node.mMesh != INVALID_INDEX) { Mesh& mesh = mMeshes[node.mMesh]; for (auto& primitive : mesh.mPrimitives) { if (!rigged) { gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix)); } bool cull = true; if (primitive.mMaterial != INVALID_INDEX) { Material& material = mMaterials[primitive.mMaterial]; bool mat_opaque = material.mAlphaMode != Material::AlphaMode::BLEND; if (mat_opaque != opaque) { continue; } if (mMaterials[primitive.mMaterial].mMaterial.notNull()) { material.mMaterial->bind(); } else { material.bind(*this); } cull = !material.mDoubleSided; } else { if (!opaque) { continue; } 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()); } } } } } void Asset::renderOpaque() { render(true); } void Asset::renderTransparent() { render(false); } void Asset::update() { F32 dt = gFrameTimeSeconds - mLastUpdateTime; if (dt > 0.f) { mLastUpdateTime = gFrameTimeSeconds; if (mAnimations.size() > 0) { static LLCachedControl anim_idx(gSavedSettings, "GLTFAnimationIndex", 0); static LLCachedControl anim_speed(gSavedSettings, "GLTFAnimationSpeed", 1.f); U32 idx = llclamp(anim_idx(), 0U, mAnimations.size() - 1); mAnimations[idx].update(*this, dt*anim_speed); } updateTransforms(); } } void Asset::allocateGLResources(const std::string& filename, const tinygltf::Model& model) { // do images first as materials may depend on images for (auto& image : mImages) { image.allocateGLResources(); } // do materials before meshes as meshes may depend on materials if (!filename.empty()) { for (U32 i = 0; i < mMaterials.size(); ++i) { // HACK: local preview mode, load material from model for now mMaterials[i].allocateGLResources(*this); LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); } } for (auto& mesh : mMeshes) { mesh.allocateGLResources(*this); } for (auto& animation : mAnimations) { animation.allocateGLResources(*this); } for (auto& skin : mSkins) { skin.allocateGLResources(*this); } } Asset::Asset(const tinygltf::Model& src) { *this = src; } Asset::Asset(const Value& src) { *this = src; } const Asset& Asset::operator=(const tinygltf::Model& src) { mVersion = src.asset.version; mMinVersion = src.asset.minVersion; mGenerator = src.asset.generator; mCopyright = src.asset.copyright; // note: extras are lost in the conversion for now mDefaultScene = src.defaultScene; mScenes.resize(src.scenes.size()); for (U32 i = 0; i < src.scenes.size(); ++i) { mScenes[i] = src.scenes[i]; } mNodes.resize(src.nodes.size()); for (U32 i = 0; i < src.nodes.size(); ++i) { mNodes[i] = src.nodes[i]; } mMeshes.resize(src.meshes.size()); for (U32 i = 0; i < src.meshes.size(); ++i) { mMeshes[i] = src.meshes[i]; } mMaterials.resize(src.materials.size()); for (U32 i = 0; i < src.materials.size(); ++i) { mMaterials[i] = src.materials[i]; } mBuffers.resize(src.buffers.size()); for (U32 i = 0; i < src.buffers.size(); ++i) { mBuffers[i] = src.buffers[i]; } mBufferViews.resize(src.bufferViews.size()); for (U32 i = 0; i < src.bufferViews.size(); ++i) { mBufferViews[i] = src.bufferViews[i]; } mTextures.resize(src.textures.size()); for (U32 i = 0; i < src.textures.size(); ++i) { mTextures[i] = src.textures[i]; } mSamplers.resize(src.samplers.size()); for (U32 i = 0; i < src.samplers.size(); ++i) { mSamplers[i] = src.samplers[i]; } mImages.resize(src.images.size()); for (U32 i = 0; i < src.images.size(); ++i) { mImages[i] = src.images[i]; } mAccessors.resize(src.accessors.size()); for (U32 i = 0; i < src.accessors.size(); ++i) { mAccessors[i] = src.accessors[i]; } mAnimations.resize(src.animations.size()); for (U32 i = 0; i < src.animations.size(); ++i) { mAnimations[i] = src.animations[i]; } mSkins.resize(src.skins.size()); for (U32 i = 0; i < src.skins.size(); ++i) { mSkins[i] = src.skins[i]; } return *this; } const Asset& Asset::operator=(const Value& src) { if (src.is_object()) { const object& obj = src.as_object(); const auto it = obj.find("asset"); if (it != obj.end()) { const Value& asset = it->value(); copy(asset, "version", mVersion); copy(asset, "minVersion", mMinVersion); copy(asset, "generator", mGenerator); copy(asset, "copyright", mCopyright); copy(asset, "extras", mExtras); } copy(obj, "defaultScene", mDefaultScene); copy(obj, "scenes", mScenes); copy(obj, "nodes", mNodes); copy(obj, "meshes", mMeshes); copy(obj, "materials", mMaterials); copy(obj, "buffers", mBuffers); copy(obj, "bufferViews", mBufferViews); copy(obj, "textures", mTextures); copy(obj, "samplers", mSamplers); copy(obj, "images", mImages); copy(obj, "accessors", mAccessors); copy(obj, "animations", mAnimations); copy(obj, "skins", mSkins); } return *this; } void Asset::save(tinygltf::Model& dst) { LL::GLTF::copy(*this, dst); } void Asset::serialize(object& dst) const { write(mVersion, "version", dst); write(mMinVersion, "minVersion", dst, std::string()); write(mGenerator, "generator", dst); write(mDefaultScene, "defaultScene", dst, 0); write(mScenes, "scenes", dst); write(mNodes, "nodes", dst); write(mMeshes, "meshes", dst); write(mMaterials, "materials", dst); write(mBuffers, "buffers", dst); write(mBufferViews, "bufferViews", dst); write(mTextures, "textures", dst); write(mSamplers, "samplers", dst); write(mImages, "images", dst); write(mAccessors, "accessors", dst); write(mAnimations, "animations", dst); write(mSkins, "skins", 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--; } } } LLViewerFetchedTexture* fetch_texture(const LLUUID& id); void Image::allocateGLResources() { LLUUID id; if (LLUUID::parseUUID(mUri, &id) && id.notNull()) { mTexture = fetch_texture(id); } } void Image::clearData(Asset& asset) { if (mBufferView != INVALID_INDEX) { // remove data from buffer BufferView& bufferView = asset.mBufferViews[mBufferView]; Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength); asset.eraseBufferView(mBufferView); } mData.clear(); mBufferView = INVALID_INDEX; mWidth = -1; mHeight = -1; mComponent = -1; mBits = -1; mPixelType = -1; mMimeType = ""; } 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); } #if 0 if (!mData.empty()) { // save j2c image std::string filename = folder + "/" + name + ".j2c"; LLPointer raw = new LLImageRaw(mWidth, mHeight, mComponent); U8* data = raw->allocateData(); llassert_always(mData.size() == raw->getDataSize()); memcpy(data, mData.data(), mData.size()); LLViewerTextureList::createUploadFile(raw, filename, 4096); mData.clear(); } #endif clearData(asset); } void Material::TextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); } const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) { if (src.is_object()) { copy(src, "index", mIndex); copy(src, "texCoord", mTexCoord); } return *this; } bool Material::TextureInfo::operator==(const Material::TextureInfo& rhs) const { return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord; } bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const { return !(*this == rhs); } const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) { mIndex = src.index; mTexCoord = src.texCoord; return *this; } void Material::OcclusionTextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); write(mStrength, "strength", dst, 1.f); } const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src) { if (src.is_object()) { copy(src, "index", mIndex); copy(src, "texCoord", mTexCoord); copy(src, "strength", mStrength); } return *this; } const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const tinygltf::OcclusionTextureInfo& src) { mIndex = src.index; mTexCoord = src.texCoord; mStrength = src.strength; return *this; } void Material::NormalTextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); write(mScale, "scale", dst, 1.f); } const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const Value& src) { if (src.is_object()) { copy(src, "index", mIndex); copy(src, "texCoord", mTexCoord); copy(src, "scale", mScale); } 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 Value& src) { if (src.is_object()) { copy(src, "baseColorFactor", mBaseColorFactor); copy(src, "baseColorTexture", mBaseColorTexture); copy(src, "metallicFactor", mMetallicFactor); copy(src, "roughnessFactor", mRoughnessFactor); copy(src, "metallicRoughnessTexture", mMetallicRoughnessTexture); } return *this; } void Material::PbrMetallicRoughness::serialize(object& dst) const { write(mBaseColorFactor, "baseColorFactor", dst, vec4(1.f, 1.f, 1.f, 1.f)); write(mBaseColorTexture, "baseColorTexture", dst); write(mMetallicFactor, "metallicFactor", dst, 1.f); write(mRoughnessFactor, "roughnessFactor", dst, 1.f); write(mMetallicRoughnessTexture, "metallicRoughnessTexture", dst); } bool Material::PbrMetallicRoughness::operator==(const Material::PbrMetallicRoughness& rhs) const { return mBaseColorFactor == rhs.mBaseColorFactor && mBaseColorTexture == rhs.mBaseColorTexture && mMetallicFactor == rhs.mMetallicFactor && mRoughnessFactor == rhs.mRoughnessFactor && mMetallicRoughnessTexture == rhs.mMetallicRoughnessTexture; } bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRoughness& rhs) const { return !(*this == rhs); } const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const tinygltf::PbrMetallicRoughness& src) { if (src.baseColorFactor.size() == 4) { mBaseColorFactor = vec4(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; } 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 Material::bind(Asset& asset) { // 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 || (mAlphaMode == Material::AlphaMode::BLEND)) { if (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 (mPbrMetallicRoughness.mBaseColorFactor.a > 0.f) { min_alpha = mAlphaCutoff / mPbrMetallicRoughness.mBaseColorFactor.a; } else { min_alpha = 1024.f; } } shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha); } bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, 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::BUMP_MAP, mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep); bindTexture(asset, LLShaderMgr::SPECULAR_MAP, mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep); bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep); // NOTE: base color factor is baked into vertex stream shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, mPbrMetallicRoughness.mRoughnessFactor); shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, mPbrMetallicRoughness.mMetallicFactor); shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(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); } } void Material::serialize(object& dst) const { write(mName, "name", dst); write(mEmissiveFactor, "emissiveFactor", dst, vec3(0.f, 0.f, 0.f)); write(mPbrMetallicRoughness, "pbrMetallicRoughness", dst); write(mNormalTexture, "normalTexture", dst); write(mOcclusionTexture, "occlusionTexture", dst); write(mEmissiveTexture, "emissiveTexture", dst); write(mAlphaMode, "alphaMode", dst, Material::AlphaMode::OPAQUE); write(mAlphaCutoff, "alphaCutoff", dst, 0.5f); write(mDoubleSided, "doubleSided", dst, false); } const Material& Material::operator=(const Value& src) { if (src.is_object()) { copy(src, "name", mName); copy(src, "emissiveFactor", mEmissiveFactor); copy(src, "pbrMetallicRoughness", mPbrMetallicRoughness); copy(src, "normalTexture", mNormalTexture); copy(src, "occlusionTexture", mOcclusionTexture); copy(src, "emissiveTexture", mEmissiveTexture); copy(src, "alphaMode", mAlphaMode); copy(src, "alphaCutoff", mAlphaCutoff); copy(src, "doubleSided", mDoubleSided); } return *this; } const Material& Material::operator=(const tinygltf::Material& src) { mName = src.name; if (src.emissiveFactor.size() == 3) { mEmissiveFactor = vec3(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); } mPbrMetallicRoughness = src.pbrMetallicRoughness; mNormalTexture = src.normalTexture; mOcclusionTexture = src.occlusionTexture; mEmissiveTexture = src.emissiveTexture; mAlphaMode = gltf_alpha_mode_to_enum(src.alphaMode); mAlphaCutoff = src.alphaCutoff; mDoubleSided = src.doubleSided; return *this; } void Material::allocateGLResources(Asset& asset) { // HACK: allocate an LLFetchedGLTFMaterial for now // later we'll render directly from the GLTF Images // and BufferViews mMaterial = new LLFetchedGLTFMaterial(); } void Mesh::serialize(object& dst) const { write(mPrimitives, "primitives", dst); write(mWeights, "weights", dst); write(mName, "name", dst); } const Mesh& Mesh::operator=(const Value& src) { if (src.is_object()) { copy(src, "primitives", mPrimitives); copy(src, "weights", mWeights); copy(src, "name", mName); } return *this; } const Mesh& Mesh::operator=(const tinygltf::Mesh& src) { mPrimitives.resize(src.primitives.size()); for (U32 i = 0; i < src.primitives.size(); ++i) { mPrimitives[i] = src.primitives[i]; } mWeights = src.weights; mName = src.name; return *this; } void Mesh::allocateGLResources(Asset& asset) { for (auto& primitive : mPrimitives) { primitive.allocateGLResources(asset); } } void Scene::serialize(object& dst) const { write(mNodes, "nodes", dst); write(mName, "name", dst); } const Scene& Scene::operator=(const Value& src) { copy(src, "nodes", mNodes); copy(src, "name", mName); return *this; } const Scene& Scene::operator=(const tinygltf::Scene& src) { mNodes = src.nodes; mName = src.name; return *this; } void Texture::serialize(object& dst) const { write(mSampler, "sampler", dst, INVALID_INDEX); write(mSource, "source", dst, INVALID_INDEX); write(mName, "name", dst); } const Texture& Texture::operator=(const Value& src) { if (src.is_object()) { copy(src, "sampler", mSampler); copy(src, "source", mSource); copy(src, "name", mName); } return *this; } const Texture& Texture::operator=(const tinygltf::Texture& src) { mSampler = src.sampler; mSource = src.source; mName = src.name; return *this; } void Sampler::serialize(object& dst) const { write(mMagFilter, "magFilter", dst, LINEAR); write(mMinFilter, "minFilter", dst, LINEAR_MIPMAP_LINEAR); write(mWrapS, "wrapS", dst, REPEAT); write(mWrapT, "wrapT", dst, REPEAT); write(mName, "name", dst); } const Sampler& Sampler::operator=(const Value& src) { copy(src, "magFilter", mMagFilter); copy(src, "minFilter", mMinFilter); copy(src, "wrapS", mWrapS); copy(src, "wrapT", mWrapT); copy(src, "name", mName); return *this; } const Sampler& Sampler::operator=(const tinygltf::Sampler& src) { mMagFilter = src.magFilter; mMinFilter = src.minFilter; mWrapS = src.wrapS; mWrapT = src.wrapT; mName = src.name; return *this; } void Skin::uploadMatrixPalette(Asset& asset, Node& node) { // prepare matrix palette // modelview will be applied by the shader, so assume matrix palette is in asset space std::vector t_mp; t_mp.resize(mJoints.size()); for (U32 i = 0; i < mJoints.size(); ++i) { Node& joint = asset.mNodes[mJoints[i]]; t_mp[i] = joint.mRenderMatrix * mInverseBindMatricesData[i]; } std::vector glmp; glmp.resize(mJoints.size() * 12); F32* mp = glmp.data(); for (U32 i = 0; i < mJoints.size(); ++i) { F32* m = glm::value_ptr(t_mp[i]); U32 idx = i * 12; mp[idx + 0] = m[0]; mp[idx + 1] = m[1]; mp[idx + 2] = m[2]; mp[idx + 3] = m[12]; mp[idx + 4] = m[4]; mp[idx + 5] = m[5]; mp[idx + 6] = m[6]; mp[idx + 7] = m[13]; mp[idx + 8] = m[8]; mp[idx + 9] = m[9]; mp[idx + 10] = m[10]; mp[idx + 11] = m[14]; } LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, mJoints.size(), GL_FALSE, (GLfloat*)glmp.data()); }