665 lines
17 KiB
C++
665 lines
17 KiB
C++
/**
|
|
* @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"
|
|
|
|
using namespace LL::GLTF;
|
|
|
|
void Scene::updateTransforms(Asset& asset)
|
|
{
|
|
LLMatrix4a identity;
|
|
identity.setIdentity();
|
|
for (auto& nodeIndex : mNodes)
|
|
{
|
|
Node& node = asset.mNodes[nodeIndex];
|
|
node.updateTransforms(asset, identity);
|
|
}
|
|
}
|
|
|
|
void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
|
|
{
|
|
for (auto& nodeIndex : mNodes)
|
|
{
|
|
Node& node = asset.mNodes[nodeIndex];
|
|
node.updateRenderTransforms(asset, modelview);
|
|
}
|
|
}
|
|
|
|
void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
|
|
{
|
|
matMul(mMatrix, modelview, mRenderMatrix);
|
|
|
|
for (auto& childIndex : mChildren)
|
|
{
|
|
Node& child = asset.mNodes[childIndex];
|
|
child.updateRenderTransforms(asset, mRenderMatrix);
|
|
}
|
|
}
|
|
|
|
LLMatrix4a inverse(const LLMatrix4a& mat);
|
|
|
|
void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix)
|
|
{
|
|
makeMatrixValid();
|
|
matMul(mMatrix, parentMatrix, mAssetMatrix);
|
|
mAssetMatrixInv = 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 LLMatrix4a& modelview)
|
|
{
|
|
#if 0
|
|
// traverse hierarchy and update render transforms from scratch
|
|
for (auto& scene : mScenes)
|
|
{
|
|
scene.updateRenderTransforms(*this, modelview);
|
|
}
|
|
#else
|
|
// use mAssetMatrix to update render transforms from node list
|
|
for (auto& node : mNodes)
|
|
{
|
|
//if (node.mMesh != INVALID_INDEX)
|
|
{
|
|
matMul(node.mAssetMatrix, modelview, node.mRenderMatrix);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
// transform start and end to this node's local space
|
|
node.mAssetMatrixInv.affineTransform(start, local_start);
|
|
node.mAssetMatrixInv.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)
|
|
{
|
|
// shorten line segment on hit
|
|
node.mAssetMatrix.affineTransform(p, asset_end);
|
|
|
|
// transform results back to asset space
|
|
if (intersection)
|
|
{
|
|
*intersection = asset_end;
|
|
}
|
|
|
|
if (normal || tangent)
|
|
{
|
|
LLMatrix4 normalMatrix(node.mAssetMatrixInv.getF32ptr());
|
|
|
|
normalMatrix.transpose();
|
|
|
|
LLMatrix4a norm_mat;
|
|
norm_mat.loadu((F32*)normalMatrix.mMatrix);
|
|
|
|
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)
|
|
{
|
|
glh::matrix4f rot;
|
|
mRotation.get_value(rot);
|
|
|
|
glh::matrix4f trans;
|
|
trans.set_translate(mTranslation);
|
|
|
|
glh::matrix4f sc;
|
|
sc.set_scale(mScale);
|
|
|
|
glh::matrix4f t;
|
|
//t = sc * rot * trans;
|
|
//t = trans * rot * sc; // best so far, still wrong on negative scale
|
|
//t = sc * trans * rot;
|
|
t = trans * sc * rot;
|
|
|
|
mMatrix.loadu(t.m);
|
|
mMatrixValid = true;
|
|
}
|
|
}
|
|
|
|
void Node::makeTRSValid()
|
|
{
|
|
if (!mTRSValid && mMatrixValid)
|
|
{
|
|
glh::matrix4f t(mMatrix.getF32ptr());
|
|
|
|
glh::vec4f p = t.get_column(3);
|
|
mTranslation.set_value(p.v[0], p.v[1], p.v[2]);
|
|
|
|
mScale.set_value(t.get_column(0).length(), t.get_column(1).length(), t.get_column(2).length());
|
|
mRotation.set_value(t);
|
|
mTRSValid = true;
|
|
}
|
|
}
|
|
|
|
void Node::setRotation(const glh::quaternionf& q)
|
|
{
|
|
makeTRSValid();
|
|
mRotation = q;
|
|
mMatrixValid = false;
|
|
}
|
|
|
|
void Node::setTranslation(const glh::vec3f& t)
|
|
{
|
|
makeTRSValid();
|
|
mTranslation = t;
|
|
mMatrixValid = false;
|
|
}
|
|
|
|
void Node::setScale(const glh::vec3f& s)
|
|
{
|
|
makeTRSValid();
|
|
mScale = s;
|
|
mMatrixValid = false;
|
|
}
|
|
|
|
const Node& Node::operator=(const tinygltf::Node& src)
|
|
{
|
|
F32* dstMatrix = mMatrix.getF32ptr();
|
|
|
|
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 = glh::quaternionf((F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2], (F32)src.rotation[3]);
|
|
}
|
|
|
|
if (src.translation.size() == 3)
|
|
{
|
|
mTranslation = glh::vec3f((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]);
|
|
}
|
|
|
|
glh::vec3f scale;
|
|
if (src.scale.size() == 3)
|
|
{
|
|
mScale = glh::vec3f((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]);
|
|
}
|
|
else
|
|
{
|
|
mScale.set_value(1.f, 1.f, 1.f);
|
|
}
|
|
|
|
mTRSValid = true;
|
|
}
|
|
else
|
|
{
|
|
// node specifies no transformation, set to identity
|
|
mMatrix.setIdentity();
|
|
}
|
|
|
|
mChildren = src.children;
|
|
mMesh = src.mesh;
|
|
mSkin = src.skin;
|
|
mName = src.name;
|
|
|
|
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*)node.mRenderMatrix.mMatrix);
|
|
}
|
|
bool cull = true;
|
|
if (primitive.mMaterial != INVALID_INDEX)
|
|
{
|
|
Material& material = mMaterials[primitive.mMaterial];
|
|
|
|
if ((material.mMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) == opaque)
|
|
{
|
|
continue;
|
|
}
|
|
material.mMaterial->bind();
|
|
cull = !material.mMaterial->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<U32> anim_idx(gSavedSettings, "GLTFAnimationIndex", 0);
|
|
static LLCachedControl<F32> 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
|
|
for (U32 i = 0; i < mMaterials.size(); ++i)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
const Asset& Asset::operator=(const tinygltf::Model& src)
|
|
{
|
|
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 Material& Material::operator=(const tinygltf::Material& src)
|
|
{
|
|
mName = src.name;
|
|
return *this;
|
|
}
|
|
|
|
void Material::allocateGLResources(Asset& asset)
|
|
{
|
|
// allocate material
|
|
mMaterial = new LLFetchedGLTFMaterial();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
const Scene& Scene::operator=(const tinygltf::Scene& src)
|
|
{
|
|
mNodes = src.nodes;
|
|
mName = src.name;
|
|
|
|
return *this;
|
|
}
|
|
|
|
const Texture& Texture::operator=(const tinygltf::Texture& src)
|
|
{
|
|
mSampler = src.sampler;
|
|
mSource = src.source;
|
|
mName = src.name;
|
|
|
|
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<glh::matrix4f> t_mp;
|
|
|
|
t_mp.resize(mJoints.size());
|
|
|
|
for (U32 i = 0; i < mJoints.size(); ++i)
|
|
{
|
|
Node& joint = asset.mNodes[mJoints[i]];
|
|
|
|
//t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
|
|
//t_mp[i] = t_mp[i] * mInverseBindMatricesData[i];
|
|
|
|
//t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
|
|
//t_mp[i] = mInverseBindMatricesData[i] * t_mp[i];
|
|
|
|
t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
|
|
t_mp[i] = t_mp[i] * mInverseBindMatricesData[i];
|
|
|
|
}
|
|
|
|
std::vector<F32> glmp;
|
|
|
|
glmp.resize(mJoints.size() * 12);
|
|
|
|
F32* mp = glmp.data();
|
|
|
|
for (U32 i = 0; i < mJoints.size(); ++i)
|
|
{
|
|
F32* m = (F32*)t_mp[i].m;
|
|
|
|
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,
|
|
(U32)mJoints.size(),
|
|
GL_FALSE,
|
|
(GLfloat*)glmp.data());
|
|
}
|
|
|