Merge branch 'project/gltf_mesh_import' into geenz/develop-to-gltf-mesh
commit
6dd8a02ed1
|
|
@ -218,8 +218,10 @@ jobs:
|
|||
prefix=${ba[0]}
|
||||
if [ "$prefix" == "project" ]; then
|
||||
IFS='_' read -ra prj <<< "${ba[1]}"
|
||||
prj_str="${prj[*]}"
|
||||
# uppercase first letter of each word
|
||||
export viewer_channel="Second Life Project ${prj[*]^}"
|
||||
capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}')
|
||||
export viewer_channel="Second Life Project $capitalized"
|
||||
elif [[ "$prefix" == "release" || "$prefix" == "main" ]];
|
||||
then
|
||||
export viewer_channel="Second Life Release"
|
||||
|
|
@ -455,7 +457,6 @@ jobs:
|
|||
prerelease: true
|
||||
generate_release_notes: true
|
||||
target_commitish: ${{ github.sha }}
|
||||
previous_tag: release
|
||||
append_body: true
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ set(llappearance_SOURCE_FILES
|
|||
llavatarjoint.cpp
|
||||
llavatarjointmesh.cpp
|
||||
lldriverparam.cpp
|
||||
lljointdata.h
|
||||
lllocaltextureobject.cpp
|
||||
llpolyskeletaldistortion.cpp
|
||||
llpolymesh.cpp
|
||||
|
|
|
|||
|
|
@ -29,16 +29,17 @@
|
|||
#include "llavatarappearance.h"
|
||||
#include "llavatarappearancedefines.h"
|
||||
#include "llavatarjointmesh.h"
|
||||
#include "lljointdata.h"
|
||||
#include "llstl.h"
|
||||
#include "lldir.h"
|
||||
#include "llpolymorph.h"
|
||||
#include "llpolymesh.h"
|
||||
#include "llpolyskeletaldistortion.h"
|
||||
#include "llstl.h"
|
||||
#include "lltexglobalcolor.h"
|
||||
#include "llwearabledata.h"
|
||||
#include "boost/bind.hpp"
|
||||
#include "boost/tokenizer.hpp"
|
||||
#include "v4math.h"
|
||||
|
||||
using namespace LLAvatarAppearanceDefines;
|
||||
|
||||
|
|
@ -71,11 +72,13 @@ public:
|
|||
mChildren.clear();
|
||||
}
|
||||
bool parseXml(LLXmlTreeNode* node);
|
||||
glm::mat4 getJointMatrix();
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mSupport;
|
||||
std::string mAliases;
|
||||
std::string mGroup;
|
||||
bool mIsJoint;
|
||||
LLVector3 mPos;
|
||||
LLVector3 mEnd;
|
||||
|
|
@ -105,11 +108,17 @@ public:
|
|||
S32 getNumBones() const { return mNumBones; }
|
||||
S32 getNumCollisionVolumes() const { return mNumCollisionVolumes; }
|
||||
|
||||
private:
|
||||
typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
|
||||
static void getJointMatricesAndHierarhy(
|
||||
LLAvatarBoneInfo* bone_info,
|
||||
LLJointData& data,
|
||||
const glm::mat4& parent_mat);
|
||||
|
||||
private:
|
||||
S32 mNumBones;
|
||||
S32 mNumCollisionVolumes;
|
||||
LLAvatarAppearance::joint_alias_map_t mJointAliasMap;
|
||||
typedef std::vector<LLAvatarBoneInfo*> bone_info_list_t;
|
||||
bone_info_list_t mBoneInfoList;
|
||||
};
|
||||
|
||||
|
|
@ -1598,6 +1607,15 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)
|
|||
mSupport = "base";
|
||||
}
|
||||
|
||||
// Skeleton has 133 bones, but shader only allows 110 (LL_MAX_JOINTS_PER_MESH_OBJECT)
|
||||
// Groups can be used by importer to cut out unused groups of joints
|
||||
static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group");
|
||||
if (!node->getFastAttributeString(group_string, mGroup))
|
||||
{
|
||||
LL_WARNS() << "Bone without group " << mName << LL_ENDL;
|
||||
mGroup = "global";
|
||||
}
|
||||
|
||||
if (mIsJoint)
|
||||
{
|
||||
static LLStdStringHandle pivot_string = LLXmlTree::addAttributeString("pivot");
|
||||
|
|
@ -1623,6 +1641,21 @@ bool LLAvatarBoneInfo::parseXml(LLXmlTreeNode* node)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
glm::mat4 LLAvatarBoneInfo::getJointMatrix()
|
||||
{
|
||||
glm::mat4 mat(1.0f);
|
||||
// 1. Scaling
|
||||
mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2]));
|
||||
// 2. Rotation (Euler angles rad)
|
||||
mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0));
|
||||
mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0));
|
||||
mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1));
|
||||
// 3. Position
|
||||
mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2]));
|
||||
return mat;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// LLAvatarSkeletonInfo::parseXml()
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
@ -1653,6 +1686,25 @@ bool LLAvatarSkeletonInfo::parseXml(LLXmlTreeNode* node)
|
|||
return true;
|
||||
}
|
||||
|
||||
void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(
|
||||
LLAvatarBoneInfo* bone_info,
|
||||
LLJointData& data,
|
||||
const glm::mat4& parent_mat)
|
||||
{
|
||||
data.mName = bone_info->mName;
|
||||
data.mJointMatrix = bone_info->getJointMatrix();
|
||||
data.mScale = glm::vec3(bone_info->mScale[0], bone_info->mScale[1], bone_info->mScale[2]);
|
||||
data.mRotation = bone_info->mRot;
|
||||
data.mRestMatrix = parent_mat * data.mJointMatrix;
|
||||
data.mIsJoint = bone_info->mIsJoint;
|
||||
data.mGroup = bone_info->mGroup;
|
||||
for (LLAvatarBoneInfo* child_info : bone_info->mChildren)
|
||||
{
|
||||
LLJointData& child_data = data.mChildren.emplace_back();
|
||||
getJointMatricesAndHierarhy(child_info, child_data, data.mRestMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
//Make aliases for joint and push to map.
|
||||
void LLAvatarAppearance::makeJointAliases(LLAvatarBoneInfo *bone_info)
|
||||
{
|
||||
|
|
@ -1714,6 +1766,16 @@ const LLAvatarAppearance::joint_alias_map_t& LLAvatarAppearance::getJointAliases
|
|||
return mJointAliasMap;
|
||||
}
|
||||
|
||||
void LLAvatarAppearance::getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const
|
||||
{
|
||||
glm::mat4 identity(1.f);
|
||||
for (LLAvatarBoneInfo* bone_info : sAvatarSkeletonInfo->mBoneInfoList)
|
||||
{
|
||||
LLJointData& child_data = data.emplace_back();
|
||||
LLAvatarSkeletonInfo::getJointMatricesAndHierarhy(bone_info, child_data, identity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// parseXmlSkeletonNode(): parses <skeleton> nodes from XML tree
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "lltexlayer.h"
|
||||
#include "llviewervisualparam.h"
|
||||
#include "llxmltree.h"
|
||||
#include "v4math.h"
|
||||
|
||||
class LLTexLayerSet;
|
||||
class LLTexGlobalColor;
|
||||
|
|
@ -41,6 +42,7 @@ class LLTexGlobalColorInfo;
|
|||
class LLWearableData;
|
||||
class LLAvatarBoneInfo;
|
||||
class LLAvatarSkeletonInfo;
|
||||
class LLJointData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// LLAvatarAppearance
|
||||
|
|
@ -153,7 +155,9 @@ public:
|
|||
const avatar_joint_list_t& getSkeleton() { return mSkeleton; }
|
||||
typedef std::map<std::string, std::string, std::less<>> joint_alias_map_t;
|
||||
const joint_alias_map_t& getJointAliases();
|
||||
|
||||
typedef std::map<std::string, std::string> joint_parent_map_t; // matrix plus parent
|
||||
typedef std::map<std::string, glm::mat4> joint_rest_map_t;
|
||||
void getJointMatricesAndHierarhy(std::vector<LLJointData> &data) const;
|
||||
|
||||
protected:
|
||||
static bool parseSkeletonFile(const std::string& filename, LLXmlTree& skeleton_xml_tree);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @file lljointdata.h
|
||||
* @brief LLJointData class for holding individual joint data and skeleton
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLJOINTDATA_H
|
||||
#define LL_LLJOINTDATA_H
|
||||
|
||||
#include "v4math.h"
|
||||
|
||||
// may be just move LLAvatarBoneInfo
|
||||
class LLJointData
|
||||
{
|
||||
public:
|
||||
std::string mName;
|
||||
std::string mGroup;
|
||||
glm::mat4 mJointMatrix;
|
||||
glm::mat4 mRestMatrix;
|
||||
glm::vec3 mScale;
|
||||
LLVector3 mRotation;
|
||||
|
||||
typedef std::vector<LLJointData> bones_t;
|
||||
bones_t mChildren;
|
||||
|
||||
bool mIsJoint; // if not, collision_volume
|
||||
enum SupportCategory
|
||||
{
|
||||
SUPPORT_BASE,
|
||||
SUPPORT_EXTENDED
|
||||
};
|
||||
SupportCategory mSupport;
|
||||
void setSupport(const std::string& support)
|
||||
{
|
||||
if (support == "extended")
|
||||
{
|
||||
mSupport = SUPPORT_EXTENDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSupport = SUPPORT_BASE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif //LL_LLJOINTDATA_H
|
||||
|
|
@ -12,7 +12,6 @@ include(TinyGLTF)
|
|||
|
||||
set(llprimitive_SOURCE_FILES
|
||||
lldaeloader.cpp
|
||||
llgltfloader.cpp
|
||||
llgltfmaterial.cpp
|
||||
llmaterialid.cpp
|
||||
llmaterial.cpp
|
||||
|
|
@ -32,7 +31,6 @@ set(llprimitive_SOURCE_FILES
|
|||
set(llprimitive_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
lldaeloader.h
|
||||
llgltfloader.h
|
||||
llgltfmaterial.h
|
||||
llgltfmaterial_templates.h
|
||||
legacy_object_types.h
|
||||
|
|
|
|||
|
|
@ -1,404 +0,0 @@
|
|||
/**
|
||||
* @file LLGLTFLoader.cpp
|
||||
* @brief LLGLTFLoader class implementation
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2022, 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 "llgltfloader.h"
|
||||
|
||||
// Import & define single-header gltf import/export lib
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define TINYGLTF_USE_CPP14 // default is C++ 11
|
||||
|
||||
// tinygltf by default loads image files using STB
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
// to use our own image loading:
|
||||
// 1. replace this definition with TINYGLTF_NO_STB_IMAGE
|
||||
// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)
|
||||
|
||||
// tinygltf saves image files using STB
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data)
|
||||
|
||||
// Additionally, disable inclusion of STB header files entirely with
|
||||
// TINYGLTF_NO_INCLUDE_STB_IMAGE
|
||||
// TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
|
||||
#include "tinygltf/tiny_gltf.h"
|
||||
|
||||
|
||||
// TODO: includes inherited from dae loader. Validate / prune
|
||||
|
||||
#include "llsdserialize.h"
|
||||
#include "lljoint.h"
|
||||
|
||||
#include "llmatrix4a.h"
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
static const std::string lod_suffix[LLModel::NUM_LODS] =
|
||||
{
|
||||
"_LOD0",
|
||||
"_LOD1",
|
||||
"_LOD2",
|
||||
"",
|
||||
"_PHYS",
|
||||
};
|
||||
|
||||
|
||||
LLGLTFLoader::LLGLTFLoader(std::string filename,
|
||||
S32 lod,
|
||||
LLModelLoader::load_callback_t load_cb,
|
||||
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
||||
LLModelLoader::texture_load_func_t texture_load_func,
|
||||
LLModelLoader::state_callback_t state_cb,
|
||||
void * opaque_userdata,
|
||||
JointTransformMap & jointTransformMap,
|
||||
JointNameSet & jointsFromNodes,
|
||||
std::map<std::string, std::string, std::less<>> & jointAliasMap,
|
||||
U32 maxJointsPerMesh,
|
||||
U32 modelLimit) //,
|
||||
//bool preprocess)
|
||||
: LLModelLoader( filename,
|
||||
lod,
|
||||
load_cb,
|
||||
joint_lookup_func,
|
||||
texture_load_func,
|
||||
state_cb,
|
||||
opaque_userdata,
|
||||
jointTransformMap,
|
||||
jointsFromNodes,
|
||||
jointAliasMap,
|
||||
maxJointsPerMesh ),
|
||||
//mPreprocessGLTF(preprocess),
|
||||
mMeshesLoaded(false),
|
||||
mMaterialsLoaded(false)
|
||||
{
|
||||
}
|
||||
|
||||
LLGLTFLoader::~LLGLTFLoader() {}
|
||||
|
||||
bool LLGLTFLoader::OpenFile(const std::string &filename)
|
||||
{
|
||||
tinygltf::TinyGLTF loader;
|
||||
std::string error_msg;
|
||||
std::string warn_msg;
|
||||
std::string filename_lc(filename);
|
||||
LLStringUtil::toLower(filename_lc);
|
||||
|
||||
// Load a tinygltf model fom a file. Assumes that the input filename has already been
|
||||
// been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
|
||||
if (std::string::npos == filename_lc.rfind(".gltf"))
|
||||
{ // file is binary
|
||||
mGltfLoaded = loader.LoadBinaryFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
|
||||
}
|
||||
else
|
||||
{ // file is ascii
|
||||
mGltfLoaded = loader.LoadASCIIFromFile(&mGltfModel, &error_msg, &warn_msg, filename);
|
||||
}
|
||||
|
||||
if (!mGltfLoaded)
|
||||
{
|
||||
if (!warn_msg.empty())
|
||||
LL_WARNS("GLTF_IMPORT") << "gltf load warning: " << warn_msg.c_str() << LL_ENDL;
|
||||
if (!error_msg.empty())
|
||||
LL_WARNS("GLTF_IMPORT") << "gltf load error: " << error_msg.c_str() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mMeshesLoaded = parseMeshes();
|
||||
if (mMeshesLoaded) uploadMeshes();
|
||||
|
||||
mMaterialsLoaded = parseMaterials();
|
||||
if (mMaterialsLoaded) uploadMaterials();
|
||||
|
||||
return (mMeshesLoaded || mMaterialsLoaded);
|
||||
}
|
||||
|
||||
bool LLGLTFLoader::parseMeshes()
|
||||
{
|
||||
if (!mGltfLoaded) return false;
|
||||
|
||||
// 2022-04 DJH Volume params from dae example. TODO understand PCODE
|
||||
LLVolumeParams volume_params;
|
||||
volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
|
||||
|
||||
for (tinygltf::Mesh mesh : mGltfModel.meshes)
|
||||
{
|
||||
LLModel *pModel = new LLModel(volume_params, 0.f);
|
||||
|
||||
if (populateModelFromMesh(pModel, mesh) &&
|
||||
(LLModel::NO_ERRORS == pModel->getStatus()) &&
|
||||
validate_model(pModel))
|
||||
{
|
||||
mModelList.push_back(pModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
setLoadState(ERROR_MODEL + pModel->getStatus());
|
||||
delete(pModel);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh)
|
||||
{
|
||||
pModel->mLabel = mesh.name;
|
||||
int pos_idx;
|
||||
tinygltf::Accessor indices_a, positions_a, normals_a, uv0_a, color0_a;
|
||||
|
||||
auto prims = mesh.primitives;
|
||||
for (auto prim : prims)
|
||||
{
|
||||
if (prim.indices >= 0) indices_a = mGltfModel.accessors[prim.indices];
|
||||
|
||||
pos_idx = (prim.attributes.count("POSITION") > 0) ? prim.attributes.at("POSITION") : -1;
|
||||
if (pos_idx >= 0)
|
||||
{
|
||||
positions_a = mGltfModel.accessors[pos_idx];
|
||||
if (TINYGLTF_COMPONENT_TYPE_FLOAT != positions_a.componentType)
|
||||
continue;
|
||||
auto positions_bv = mGltfModel.bufferViews[positions_a.bufferView];
|
||||
auto positions_buf = mGltfModel.buffers[positions_bv.buffer];
|
||||
//auto type = positions_vb.
|
||||
//if (positions_buf.name
|
||||
}
|
||||
|
||||
#if 0
|
||||
int norm_idx, tan_idx, uv0_idx, uv1_idx, color0_idx, color1_idx;
|
||||
norm_idx = (prim.attributes.count("NORMAL") > 0) ? prim.attributes.at("NORMAL") : -1;
|
||||
tan_idx = (prim.attributes.count("TANGENT") > 0) ? prim.attributes.at("TANGENT") : -1;
|
||||
uv0_idx = (prim.attributes.count("TEXCOORDS_0") > 0) ? prim.attributes.at("TEXCOORDS_0") : -1;
|
||||
uv1_idx = (prim.attributes.count("TEXCOORDS_1") > 0) ? prim.attributes.at("TEXCOORDS_1") : -1;
|
||||
color0_idx = (prim.attributes.count("COLOR_0") > 0) ? prim.attributes.at("COLOR_0") : -1;
|
||||
color1_idx = (prim.attributes.count("COLOR_1") > 0) ? prim.attributes.at("COLOR_1") : -1;
|
||||
#endif
|
||||
|
||||
if (prim.mode == TINYGLTF_MODE_TRIANGLES)
|
||||
{
|
||||
//auto pos = mesh. TODO resume here DJH 2022-04
|
||||
}
|
||||
}
|
||||
|
||||
//pModel->addFace()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LLGLTFLoader::parseMaterials()
|
||||
{
|
||||
if (!mGltfLoaded) return false;
|
||||
|
||||
// fill local texture data structures
|
||||
mSamplers.clear();
|
||||
for (auto in_sampler : mGltfModel.samplers)
|
||||
{
|
||||
gltf_sampler sampler;
|
||||
sampler.magFilter = in_sampler.magFilter > 0 ? in_sampler.magFilter : GL_LINEAR;
|
||||
sampler.minFilter = in_sampler.minFilter > 0 ? in_sampler.minFilter : GL_LINEAR;;
|
||||
sampler.wrapS = in_sampler.wrapS;
|
||||
sampler.wrapT = in_sampler.wrapT;
|
||||
sampler.name = in_sampler.name; // unused
|
||||
mSamplers.push_back(sampler);
|
||||
}
|
||||
|
||||
mImages.clear();
|
||||
for (auto in_image : mGltfModel.images)
|
||||
{
|
||||
gltf_image image;
|
||||
image.numChannels = in_image.component;
|
||||
image.bytesPerChannel = in_image.bits >> 3; // Convert bits to bytes
|
||||
image.pixelType = in_image.pixel_type; // Maps exactly, i.e. TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE == GL_UNSIGNED_BYTE, etc
|
||||
image.size = static_cast<U32>(in_image.image.size());
|
||||
image.height = in_image.height;
|
||||
image.width = in_image.width;
|
||||
image.data = in_image.image.data();
|
||||
|
||||
if (in_image.as_is)
|
||||
{
|
||||
LL_WARNS("GLTF_IMPORT") << "Unsupported image encoding" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (image.size != image.height * image.width * image.numChannels * image.bytesPerChannel)
|
||||
{
|
||||
LL_WARNS("GLTF_IMPORT") << "Image size error" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mImages.push_back(image);
|
||||
}
|
||||
|
||||
mTextures.clear();
|
||||
for (auto in_tex : mGltfModel.textures)
|
||||
{
|
||||
gltf_texture tex;
|
||||
tex.imageIdx = in_tex.source;
|
||||
tex.samplerIdx = in_tex.sampler;
|
||||
tex.imageUuid.setNull();
|
||||
|
||||
if (tex.imageIdx >= mImages.size() || tex.samplerIdx >= mSamplers.size())
|
||||
{
|
||||
LL_WARNS("GLTF_IMPORT") << "Texture sampler/image index error" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mTextures.push_back(tex);
|
||||
}
|
||||
|
||||
// parse each material
|
||||
for (tinygltf::Material gltf_material : mGltfModel.materials)
|
||||
{
|
||||
gltf_render_material mat;
|
||||
mat.name = gltf_material.name;
|
||||
|
||||
tinygltf::PbrMetallicRoughness& pbr = gltf_material.pbrMetallicRoughness;
|
||||
mat.hasPBR = true; // Always true, for now
|
||||
|
||||
mat.baseColor.set(pbr.baseColorFactor.data());
|
||||
mat.hasBaseTex = pbr.baseColorTexture.index >= 0;
|
||||
mat.baseColorTexIdx = pbr.baseColorTexture.index;
|
||||
mat.baseColorTexCoords = pbr.baseColorTexture.texCoord;
|
||||
|
||||
mat.metalness = pbr.metallicFactor;
|
||||
mat.roughness = pbr.roughnessFactor;
|
||||
mat.hasMRTex = pbr.metallicRoughnessTexture.index >= 0;
|
||||
mat.metalRoughTexIdx = pbr.metallicRoughnessTexture.index;
|
||||
mat.metalRoughTexCoords = pbr.metallicRoughnessTexture.texCoord;
|
||||
|
||||
mat.normalScale = gltf_material.normalTexture.scale;
|
||||
mat.hasNormalTex = gltf_material.normalTexture.index >= 0;
|
||||
mat.normalTexIdx = gltf_material.normalTexture.index;
|
||||
mat.normalTexCoords = gltf_material.normalTexture.texCoord;
|
||||
|
||||
mat.occlusionScale = gltf_material.occlusionTexture.strength;
|
||||
mat.hasOcclusionTex = gltf_material.occlusionTexture.index >= 0;
|
||||
mat.occlusionTexIdx = gltf_material.occlusionTexture.index;
|
||||
mat.occlusionTexCoords = gltf_material.occlusionTexture.texCoord;
|
||||
|
||||
mat.emissiveColor.set(gltf_material.emissiveFactor.data());
|
||||
mat.hasEmissiveTex = gltf_material.emissiveTexture.index >= 0;
|
||||
mat.emissiveTexIdx = gltf_material.emissiveTexture.index;
|
||||
mat.emissiveTexCoords = gltf_material.emissiveTexture.texCoord;
|
||||
|
||||
mat.alphaMode = gltf_material.alphaMode;
|
||||
mat.alphaMask = gltf_material.alphaCutoff;
|
||||
|
||||
if ((mat.hasNormalTex && (mat.normalTexIdx >= mTextures.size())) ||
|
||||
(mat.hasOcclusionTex && (mat.occlusionTexIdx >= mTextures.size())) ||
|
||||
(mat.hasEmissiveTex && (mat.emissiveTexIdx >= mTextures.size())) ||
|
||||
(mat.hasBaseTex && (mat.baseColorTexIdx >= mTextures.size())) ||
|
||||
(mat.hasMRTex && (mat.metalRoughTexIdx >= mTextures.size())))
|
||||
{
|
||||
LL_WARNS("GLTF_IMPORT") << "Texture resource index error" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mat.hasNormalTex && (mat.normalTexCoords > 2)) || // mesh can have up to 3 sets of UV
|
||||
(mat.hasOcclusionTex && (mat.occlusionTexCoords > 2)) ||
|
||||
(mat.hasEmissiveTex && (mat.emissiveTexCoords > 2)) ||
|
||||
(mat.hasBaseTex && (mat.baseColorTexCoords > 2)) ||
|
||||
(mat.hasMRTex && (mat.metalRoughTexCoords > 2)))
|
||||
{
|
||||
LL_WARNS("GLTF_IMPORT") << "Image texcoord index error" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mMaterials.push_back(mat);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: convert raw vertex buffers to UUIDs
|
||||
void LLGLTFLoader::uploadMeshes()
|
||||
{
|
||||
llassert(0);
|
||||
}
|
||||
|
||||
// convert raw image buffers to texture UUIDs & assemble into a render material
|
||||
void LLGLTFLoader::uploadMaterials()
|
||||
{
|
||||
for (gltf_render_material mat : mMaterials) // Initially 1 material per gltf file, but design for multiple
|
||||
{
|
||||
if (mat.hasBaseTex)
|
||||
{
|
||||
gltf_texture& gtex = mTextures[mat.baseColorTexIdx];
|
||||
if (gtex.imageUuid.isNull())
|
||||
{
|
||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
||||
}
|
||||
}
|
||||
|
||||
if (mat.hasMRTex)
|
||||
{
|
||||
gltf_texture& gtex = mTextures[mat.metalRoughTexIdx];
|
||||
if (gtex.imageUuid.isNull())
|
||||
{
|
||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
||||
}
|
||||
}
|
||||
|
||||
if (mat.hasNormalTex)
|
||||
{
|
||||
gltf_texture& gtex = mTextures[mat.normalTexIdx];
|
||||
if (gtex.imageUuid.isNull())
|
||||
{
|
||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
||||
}
|
||||
}
|
||||
|
||||
if (mat.hasOcclusionTex)
|
||||
{
|
||||
gltf_texture& gtex = mTextures[mat.occlusionTexIdx];
|
||||
if (gtex.imageUuid.isNull())
|
||||
{
|
||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
||||
}
|
||||
}
|
||||
|
||||
if (mat.hasEmissiveTex)
|
||||
{
|
||||
gltf_texture& gtex = mTextures[mat.emissiveTexIdx];
|
||||
if (gtex.imageUuid.isNull())
|
||||
{
|
||||
gtex.imageUuid = imageBufferToTextureUUID(gtex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LLUUID LLGLTFLoader::imageBufferToTextureUUID(const gltf_texture& tex)
|
||||
{
|
||||
//gltf_image& image = mImages[tex.imageIdx];
|
||||
//gltf_sampler& sampler = mSamplers[tex.samplerIdx];
|
||||
|
||||
// fill an LLSD container with image+sampler data
|
||||
|
||||
// upload texture
|
||||
|
||||
// retrieve UUID
|
||||
|
||||
return LLUUID::null;
|
||||
}
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
/**
|
||||
* @file LLGLTFLoader.h
|
||||
* @brief LLGLTFLoader class definition
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2022, 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLGLTFLoader_H
|
||||
#define LL_LLGLTFLoader_H
|
||||
|
||||
#include "tinygltf/tiny_gltf.h"
|
||||
|
||||
#include "llglheaders.h"
|
||||
#include "llmodelloader.h"
|
||||
|
||||
// gltf_* structs are temporary, used to organize the subset of data that eventually goes into the material LLSD
|
||||
|
||||
class gltf_sampler
|
||||
{
|
||||
public:
|
||||
// Uses GL enums
|
||||
S32 minFilter; // GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR
|
||||
S32 magFilter; // GL_NEAREST or GL_LINEAR
|
||||
S32 wrapS; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
|
||||
S32 wrapT; // GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT
|
||||
//S32 wrapR; // Found in some sample files, but not part of glTF 2.0 spec. Ignored.
|
||||
std::string name; // optional, currently unused
|
||||
// extensions and extras are sampler optional fields that we don't support - at least initially
|
||||
};
|
||||
|
||||
class gltf_image
|
||||
{
|
||||
public:// Note that glTF images are defined with row 0 at the top (opposite of OpenGL)
|
||||
U8* data; // ptr to decoded image data
|
||||
U32 size; // in bytes, regardless of channel width
|
||||
U32 width;
|
||||
U32 height;
|
||||
U32 numChannels; // range 1..4
|
||||
U32 bytesPerChannel; // converted from gltf "bits", expects only 8, 16 or 32 as input
|
||||
U32 pixelType; // one of (TINYGLTF_COMPONENT_TYPE)_UNSIGNED_BYTE, _UNSIGNED_SHORT, _UNSIGNED_INT, or _FLOAT
|
||||
};
|
||||
|
||||
class gltf_texture
|
||||
{
|
||||
public:
|
||||
U32 imageIdx;
|
||||
U32 samplerIdx;
|
||||
LLUUID imageUuid = LLUUID::null;
|
||||
};
|
||||
|
||||
class gltf_render_material
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
|
||||
// scalar values
|
||||
LLColor4 baseColor; // linear encoding. Multiplied with vertex color, if present.
|
||||
double metalness;
|
||||
double roughness;
|
||||
double normalScale; // scale applies only to X,Y components of normal
|
||||
double occlusionScale; // strength multiplier for occlusion
|
||||
LLColor4 emissiveColor; // emissive mulitiplier, assumed linear encoding (spec 2.0 is silent)
|
||||
std::string alphaMode; // "OPAQUE", "MASK" or "BLEND"
|
||||
double alphaMask; // alpha cut-off
|
||||
|
||||
// textures
|
||||
U32 baseColorTexIdx; // always sRGB encoded
|
||||
U32 metalRoughTexIdx; // always linear, roughness in G channel, metalness in B channel
|
||||
U32 normalTexIdx; // linear, valid range R[0-1], G[0-1], B[0.5-1]. Normal = texel * 2 - vec3(1.0)
|
||||
U32 occlusionTexIdx; // linear, occlusion in R channel, 0 meaning fully occluded, 1 meaning not occluded
|
||||
U32 emissiveTexIdx; // always stored as sRGB, in nits (candela / meter^2)
|
||||
|
||||
// texture coordinates
|
||||
U32 baseColorTexCoords;
|
||||
U32 metalRoughTexCoords;
|
||||
U32 normalTexCoords;
|
||||
U32 occlusionTexCoords;
|
||||
U32 emissiveTexCoords;
|
||||
|
||||
// TODO: Add traditional (diffuse, normal, specular) UUIDs here, or add this struct to LL_TextureEntry??
|
||||
|
||||
bool hasPBR;
|
||||
bool hasBaseTex, hasMRTex, hasNormalTex, hasOcclusionTex, hasEmissiveTex;
|
||||
|
||||
// This field is populated after upload
|
||||
LLUUID material_uuid = LLUUID::null;
|
||||
|
||||
};
|
||||
|
||||
class gltf_mesh
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
|
||||
// TODO add mesh import DJH 2022-04
|
||||
|
||||
};
|
||||
|
||||
class LLGLTFLoader : public LLModelLoader
|
||||
{
|
||||
public:
|
||||
typedef std::map<std::string, LLImportMaterial> material_map;
|
||||
|
||||
LLGLTFLoader(std::string filename,
|
||||
S32 lod,
|
||||
LLModelLoader::load_callback_t load_cb,
|
||||
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
||||
LLModelLoader::texture_load_func_t texture_load_func,
|
||||
LLModelLoader::state_callback_t state_cb,
|
||||
void * opaque_userdata,
|
||||
JointTransformMap & jointTransformMap,
|
||||
JointNameSet & jointsFromNodes,
|
||||
std::map<std::string, std::string,std::less<>> &jointAliasMap,
|
||||
U32 maxJointsPerMesh,
|
||||
U32 modelLimit); //,
|
||||
//bool preprocess );
|
||||
virtual ~LLGLTFLoader();
|
||||
|
||||
virtual bool OpenFile(const std::string &filename);
|
||||
|
||||
protected:
|
||||
tinygltf::Model mGltfModel;
|
||||
bool mGltfLoaded;
|
||||
bool mMeshesLoaded;
|
||||
bool mMaterialsLoaded;
|
||||
|
||||
std::vector<gltf_mesh> mMeshes;
|
||||
std::vector<gltf_render_material> mMaterials;
|
||||
|
||||
std::vector<gltf_texture> mTextures;
|
||||
std::vector<gltf_image> mImages;
|
||||
std::vector<gltf_sampler> mSamplers;
|
||||
|
||||
private:
|
||||
bool parseMeshes();
|
||||
void uploadMeshes();
|
||||
bool parseMaterials();
|
||||
void uploadMaterials();
|
||||
bool populateModelFromMesh(LLModel* pModel, const tinygltf::Mesh &mesh);
|
||||
LLUUID imageBufferToTextureUUID(const gltf_texture& tex);
|
||||
|
||||
// bool mPreprocessGLTF;
|
||||
|
||||
/* Below inherited from dae loader - unknown if/how useful here
|
||||
|
||||
void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
|
||||
void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);
|
||||
|
||||
material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
|
||||
LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
|
||||
LLColor4 getGltfColor(gltfElement *element);
|
||||
|
||||
gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);
|
||||
|
||||
bool isNodeAJoint(gltfNode *pNode);
|
||||
void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
|
||||
void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
|
||||
void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
|
||||
void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
|
||||
void buildJointToNodeMappingFromScene(gltfElement *pRoot);
|
||||
void processJointToNodeMapping(gltfNode *pNode);
|
||||
void processChildJoints(gltfNode *pParentNode);
|
||||
|
||||
bool verifyCount(int expected, int result);
|
||||
|
||||
// Verify that a controller matches vertex counts
|
||||
bool verifyController(gltfController *pController);
|
||||
|
||||
static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
|
||||
static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);
|
||||
|
||||
static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);
|
||||
|
||||
// Loads a mesh breaking it into one or more models as necessary
|
||||
// to get around volume face limitations while retaining >8 materials
|
||||
//
|
||||
bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);
|
||||
|
||||
static std::string getElementLabel(gltfElement *element);
|
||||
static size_t getSuffixPosition(std::string label);
|
||||
static std::string getLodlessLabel(gltfElement *element);
|
||||
|
||||
static std::string preprocessGLTF(std::string filename);
|
||||
*/
|
||||
|
||||
};
|
||||
#endif // LL_LLGLTFLLOADER_H
|
||||
|
|
@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces()
|
|||
}
|
||||
}
|
||||
|
||||
void LLModel::normalizeVolumeFacesAndWeights()
|
||||
{
|
||||
if (!mVolumeFaces.empty())
|
||||
{
|
||||
LLVector4a min, max;
|
||||
|
||||
// For all of the volume faces
|
||||
// in the model, loop over
|
||||
// them and see what the extents
|
||||
// of the volume along each axis.
|
||||
min = mVolumeFaces[0].mExtents[0];
|
||||
max = mVolumeFaces[0].mExtents[1];
|
||||
|
||||
for (U32 i = 1; i < mVolumeFaces.size(); ++i)
|
||||
{
|
||||
LLVolumeFace& face = mVolumeFaces[i];
|
||||
|
||||
update_min_max(min, max, face.mExtents[0]);
|
||||
update_min_max(min, max, face.mExtents[1]);
|
||||
|
||||
if (face.mTexCoords)
|
||||
{
|
||||
LLVector2& min_tc = face.mTexCoordExtents[0];
|
||||
LLVector2& max_tc = face.mTexCoordExtents[1];
|
||||
|
||||
min_tc = face.mTexCoords[0];
|
||||
max_tc = face.mTexCoords[0];
|
||||
|
||||
for (S32 j = 1; j < face.mNumVertices; ++j)
|
||||
{
|
||||
update_min_max(min_tc, max_tc, face.mTexCoords[j]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
face.mTexCoordExtents[0].set(0, 0);
|
||||
face.mTexCoordExtents[1].set(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the extents of the model
|
||||
// we can compute the offset needed to center
|
||||
// the model at the origin.
|
||||
|
||||
// Compute center of the model
|
||||
// and make it negative to get translation
|
||||
// needed to center at origin.
|
||||
LLVector4a trans;
|
||||
trans.setAdd(min, max);
|
||||
trans.mul(-0.5f);
|
||||
|
||||
// Compute the total size along all
|
||||
// axes of the model.
|
||||
LLVector4a size;
|
||||
size.setSub(max, min);
|
||||
|
||||
// Prevent division by zero.
|
||||
F32 x = size[0];
|
||||
F32 y = size[1];
|
||||
F32 z = size[2];
|
||||
F32 w = size[3];
|
||||
if (fabs(x) < F_APPROXIMATELY_ZERO)
|
||||
{
|
||||
x = 1.0;
|
||||
}
|
||||
if (fabs(y) < F_APPROXIMATELY_ZERO)
|
||||
{
|
||||
y = 1.0;
|
||||
}
|
||||
if (fabs(z) < F_APPROXIMATELY_ZERO)
|
||||
{
|
||||
z = 1.0;
|
||||
}
|
||||
size.set(x, y, z, w);
|
||||
|
||||
// Compute scale as reciprocal of size
|
||||
LLVector4a scale;
|
||||
scale.splat(1.f);
|
||||
scale.div(size);
|
||||
|
||||
LLVector4a inv_scale(1.f);
|
||||
inv_scale.div(scale);
|
||||
|
||||
for (U32 i = 0; i < mVolumeFaces.size(); ++i)
|
||||
{
|
||||
LLVolumeFace& face = mVolumeFaces[i];
|
||||
|
||||
// We shrink the extents so
|
||||
// that they fall within
|
||||
// the unit cube.
|
||||
// VFExtents change
|
||||
face.mExtents[0].add(trans);
|
||||
face.mExtents[0].mul(scale);
|
||||
|
||||
face.mExtents[1].add(trans);
|
||||
face.mExtents[1].mul(scale);
|
||||
|
||||
// For all the positions, we scale
|
||||
// the positions to fit within the unit cube.
|
||||
LLVector4a* pos = (LLVector4a*)face.mPositions;
|
||||
LLVector4a* norm = (LLVector4a*)face.mNormals;
|
||||
LLVector4a* t = (LLVector4a*)face.mTangents;
|
||||
|
||||
for (S32 j = 0; j < face.mNumVertices; ++j)
|
||||
{
|
||||
pos[j].add(trans);
|
||||
pos[j].mul(scale);
|
||||
if (norm && !norm[j].equals3(LLVector4a::getZero()))
|
||||
{
|
||||
norm[j].mul(inv_scale);
|
||||
norm[j].normalize3();
|
||||
}
|
||||
|
||||
if (t)
|
||||
{
|
||||
F32 w = t[j].getF32ptr()[3];
|
||||
t[j].mul(inv_scale);
|
||||
t[j].normalize3();
|
||||
t[j].getF32ptr()[3] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weight_map old_weights = mSkinWeights;
|
||||
mSkinWeights.clear();
|
||||
mPosition.clear();
|
||||
|
||||
for (auto& weights : old_weights)
|
||||
{
|
||||
LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]);
|
||||
pos.add(trans);
|
||||
pos.mul(scale);
|
||||
LLVector3 scaled_pos(pos.getF32ptr());
|
||||
mPosition.push_back(scaled_pos);
|
||||
mSkinWeights[scaled_pos] = weights.second;
|
||||
}
|
||||
|
||||
// mNormalizedScale is the scale at which
|
||||
// we would need to multiply the model
|
||||
// by to get the original size of the
|
||||
// model instead of the normalized size.
|
||||
LLVector4a normalized_scale;
|
||||
normalized_scale.splat(1.f);
|
||||
normalized_scale.div(scale);
|
||||
mNormalizedScale.set(normalized_scale.getF32ptr());
|
||||
mNormalizedTranslation.set(trans.getF32ptr());
|
||||
mNormalizedTranslation *= -1.f;
|
||||
|
||||
// remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation)
|
||||
for (auto& face : mVolumeFaces)
|
||||
{
|
||||
face.mNormalizedScale = mNormalizedScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const
|
||||
{
|
||||
scale_out = mNormalizedScale;
|
||||
|
|
@ -1561,11 +1717,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
|
|||
{
|
||||
ret["joint_names"][i] = mJointNames[i];
|
||||
|
||||
// For model to work at all there must be a matching bind matrix,
|
||||
// so supply an indentity one if it isn't true
|
||||
// Note: can build an actual bind matrix from joints
|
||||
const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity();
|
||||
if (i >= mInvBindMatrix.size())
|
||||
{
|
||||
LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size "
|
||||
<< mInvBindMatrix.size() << LL_ENDL;
|
||||
}
|
||||
|
||||
for (U32 j = 0; j < 4; j++)
|
||||
{
|
||||
for (U32 k = 0; k < 4; k++)
|
||||
{
|
||||
ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k];
|
||||
ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1578,15 +1744,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi
|
|||
}
|
||||
}
|
||||
|
||||
if ( include_joints && mAlternateBindMatrix.size() > 0 )
|
||||
// optional 'joint overrides'
|
||||
if (include_joints && mAlternateBindMatrix.size() > 0)
|
||||
{
|
||||
for (U32 i = 0; i < mJointNames.size(); ++i)
|
||||
{
|
||||
// If there is not enough to match mJointNames,
|
||||
// either supply no alternate matrixes at all or supply
|
||||
// replacements
|
||||
const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity();
|
||||
if (i >= mAlternateBindMatrix.size())
|
||||
{
|
||||
LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size "
|
||||
<< mAlternateBindMatrix.size() << LL_ENDL;
|
||||
}
|
||||
for (U32 j = 0; j < 4; j++)
|
||||
{
|
||||
for (U32 k = 0; k < 4; k++)
|
||||
{
|
||||
ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k];
|
||||
ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ public:
|
|||
|
||||
void sortVolumeFacesByMaterialName();
|
||||
void normalizeVolumeFaces();
|
||||
void normalizeVolumeFacesAndWeights();
|
||||
void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);
|
||||
void remapVolumeFaces();
|
||||
void optimizeVolumeFaces();
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ void LLModelLoader::run()
|
|||
{
|
||||
mWarningsArray.clear();
|
||||
doLoadModel();
|
||||
// todo: we are inside of a thread, push this into main thread worker,
|
||||
// not into doOnIdleOneTime that laks tread safety
|
||||
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
|
||||
}
|
||||
|
||||
|
|
@ -466,6 +468,58 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std::
|
|||
return true;
|
||||
}
|
||||
|
||||
void LLModelLoader::dumpDebugData()
|
||||
{
|
||||
std::string log_file = mFilename + "_importer.txt";
|
||||
LLStringUtil::toLower(log_file);
|
||||
llofstream file;
|
||||
file.open(log_file.c_str());
|
||||
if (!file)
|
||||
{
|
||||
LL_WARNS() << "dumpDebugData failed to open file " << log_file << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
file << "Importing: " << mFilename << "\n";
|
||||
|
||||
std::map<std::string, LLMatrix4a> inv_bind;
|
||||
std::map<std::string, LLMatrix4a> alt_bind;
|
||||
for (LLPointer<LLModel>& mdl : mModelList)
|
||||
{
|
||||
|
||||
file << "Model name: " << mdl->mLabel << "\n";
|
||||
const LLMeshSkinInfo& skin_info = mdl->mSkinInfo;
|
||||
file << "Shape Bind matrix: " << skin_info.mBindShapeMatrix << "\n";
|
||||
file << "Skin Weights count: " << (S32)mdl->mSkinWeights.size() << "\n";
|
||||
|
||||
// some objects might have individual bind matrices,
|
||||
// but for now it isn't accounted for
|
||||
size_t joint_count = skin_info.mJointNames.size();
|
||||
for (size_t i = 0; i< joint_count;i++)
|
||||
{
|
||||
const std::string& joint = skin_info.mJointNames[i];
|
||||
if (skin_info.mInvBindMatrix.size() > i)
|
||||
{
|
||||
inv_bind[joint] = skin_info.mInvBindMatrix[i];
|
||||
}
|
||||
if (skin_info.mAlternateBindMatrix.size() > i)
|
||||
{
|
||||
alt_bind[joint] = skin_info.mAlternateBindMatrix[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file << "Inv Bind matrices.\n";
|
||||
for (auto& bind : inv_bind)
|
||||
{
|
||||
file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n";
|
||||
}
|
||||
|
||||
file << "Alt Bind matrices.\n";
|
||||
for (auto& bind : alt_bind)
|
||||
{
|
||||
file << "Joint: " << bind.first << " Matrix: " << bind.second << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
//called in the main thread
|
||||
void LLModelLoader::loadTextures()
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ public:
|
|||
bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)
|
||||
|
||||
model_list mModelList;
|
||||
// The scene is pretty much what ends up getting loaded for upload. Basically assign things to this guy if you want something uploaded.
|
||||
scene mScene;
|
||||
|
||||
typedef std::queue<LLPointer<LLModel> > model_queue;
|
||||
|
|
@ -119,9 +120,14 @@ public:
|
|||
model_queue mPhysicsQ;
|
||||
|
||||
//map of avatar joints as named in COLLADA assets to internal joint names
|
||||
// Do not use this for anything other than looking up the name of a joint. This is populated elsewhere.
|
||||
JointMap mJointMap;
|
||||
|
||||
// The joint list is what you want to use to actually setup the specific joint transformations.
|
||||
JointTransformMap& mJointList;
|
||||
JointNameSet& mJointsFromNode;
|
||||
|
||||
|
||||
U32 mMaxJointsPerMesh;
|
||||
|
||||
LLModelLoader(
|
||||
|
|
@ -192,6 +198,7 @@ public:
|
|||
|
||||
const LLSD logOut() const { return mWarningsArray; }
|
||||
void clearLog() { mWarningsArray.clear(); }
|
||||
void dumpDebugData();
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ set(viewer_SOURCE_FILES
|
|||
gltf/accessor.cpp
|
||||
gltf/primitive.cpp
|
||||
gltf/animation.cpp
|
||||
gltf/llgltfloader.cpp
|
||||
groupchatlistener.cpp
|
||||
llaccountingcostmanager.cpp
|
||||
llaisapi.cpp
|
||||
|
|
@ -746,6 +747,7 @@ set(viewer_HEADER_FILES
|
|||
gltf/buffer_util.h
|
||||
gltf/primitive.h
|
||||
gltf/animation.h
|
||||
gltf/llgltfloader.h
|
||||
llaccountingcost.h
|
||||
llaccountingcostmanager.h
|
||||
llaisapi.h
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ namespace LL
|
|||
"KHR_texture_transform"
|
||||
};
|
||||
|
||||
static std::unordered_set<std::string> ExtensionsIgnored = {
|
||||
"KHR_materials_pbrSpecularGlossiness"
|
||||
};
|
||||
|
||||
Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)
|
||||
{
|
||||
if (alpha_mode == "OPAQUE")
|
||||
|
|
@ -472,11 +476,14 @@ void Asset::update()
|
|||
|
||||
for (auto& image : mImages)
|
||||
{
|
||||
if (image.mTexture.notNull())
|
||||
{ // HACK - force texture to be loaded full rez
|
||||
// TODO: calculate actual vsize
|
||||
image.mTexture->addTextureStats(2048.f * 2048.f);
|
||||
image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH);
|
||||
if (image.mLoadIntoTexturePipe)
|
||||
{
|
||||
if (image.mTexture.notNull())
|
||||
{ // HACK - force texture to be loaded full rez
|
||||
// TODO: calculate actual vsize
|
||||
image.mTexture->addTextureStats(2048.f * 2048.f);
|
||||
image.mTexture->setBoostLevel(LLViewerTexture::BOOST_HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -486,18 +493,23 @@ void Asset::update()
|
|||
bool Asset::prep()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
||||
// check required extensions and fail if not supported
|
||||
bool unsupported = false;
|
||||
// check required extensions
|
||||
for (auto& extension : mExtensionsRequired)
|
||||
{
|
||||
if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())
|
||||
{
|
||||
LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
|
||||
unsupported = true;
|
||||
if (ExtensionsIgnored.find(extension) == ExtensionsIgnored.end())
|
||||
{
|
||||
LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
|
||||
mUnsupportedExtensions.push_back(extension);
|
||||
}
|
||||
else
|
||||
{
|
||||
mIgnoredExtensions.push_back(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unsupported)
|
||||
if (mUnsupportedExtensions.size() > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -513,7 +525,7 @@ bool Asset::prep()
|
|||
|
||||
for (auto& image : mImages)
|
||||
{
|
||||
if (!image.prep(*this))
|
||||
if (!image.prep(*this, mLoadIntoVRAM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -542,102 +554,106 @@ bool Asset::prep()
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// prepare vertex buffers
|
||||
|
||||
// material count is number of materials + 1 for default material
|
||||
U32 mat_count = (U32) mMaterials.size() + 1;
|
||||
|
||||
if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
|
||||
{ // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
|
||||
gDebugProgram.bind();
|
||||
}
|
||||
|
||||
for (S32 double_sided = 0; double_sided < 2; ++double_sided)
|
||||
if (mLoadIntoVRAM)
|
||||
{
|
||||
RenderData& rd = mRenderData[double_sided];
|
||||
for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
|
||||
{
|
||||
rd.mBatches[i].resize(mat_count);
|
||||
// prepare vertex buffers
|
||||
|
||||
// material count is number of materials + 1 for default material
|
||||
U32 mat_count = (U32) mMaterials.size() + 1;
|
||||
|
||||
if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
|
||||
{ // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
|
||||
gDebugProgram.bind();
|
||||
}
|
||||
|
||||
// for each material
|
||||
for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
|
||||
for (S32 double_sided = 0; double_sided < 2; ++double_sided)
|
||||
{
|
||||
// for each shader variant
|
||||
U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
|
||||
U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
|
||||
|
||||
S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
|
||||
if (ds_mat != double_sided)
|
||||
RenderData& rd = mRenderData[double_sided];
|
||||
for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
|
||||
{
|
||||
continue;
|
||||
rd.mBatches[i].resize(mat_count);
|
||||
}
|
||||
|
||||
for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
|
||||
// for each material
|
||||
for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
|
||||
{
|
||||
U32 attribute_mask = 0;
|
||||
// for each mesh
|
||||
for (auto& mesh : mMeshes)
|
||||
// for each shader variant
|
||||
U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
|
||||
U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
|
||||
|
||||
S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
|
||||
if (ds_mat != double_sided)
|
||||
{
|
||||
// for each primitive
|
||||
for (auto& primitive : mesh.mPrimitives)
|
||||
{
|
||||
if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
|
||||
{
|
||||
// accumulate vertex and index counts
|
||||
primitive.mVertexOffset = vertex_count[variant];
|
||||
primitive.mIndexOffset = index_count[variant];
|
||||
|
||||
vertex_count[variant] += primitive.getVertexCount();
|
||||
index_count[variant] += primitive.getIndexCount();
|
||||
|
||||
// all primitives of a given variant and material should all have the same attribute mask
|
||||
llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
|
||||
attribute_mask |= primitive.mAttributeMask;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// allocate vertex buffer and pack it
|
||||
if (vertex_count[variant] > 0)
|
||||
for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
|
||||
{
|
||||
U32 mat_idx = mat_id + 1;
|
||||
LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
|
||||
|
||||
rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
|
||||
vb->allocateBuffer(vertex_count[variant],
|
||||
index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
|
||||
vb->setBuffer();
|
||||
|
||||
U32 attribute_mask = 0;
|
||||
// for each mesh
|
||||
for (auto& mesh : mMeshes)
|
||||
{
|
||||
// for each primitive
|
||||
for (auto& primitive : mesh.mPrimitives)
|
||||
{
|
||||
if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
|
||||
{
|
||||
primitive.upload(vb);
|
||||
// accumulate vertex and index counts
|
||||
primitive.mVertexOffset = vertex_count[variant];
|
||||
primitive.mIndexOffset = index_count[variant];
|
||||
|
||||
vertex_count[variant] += primitive.getVertexCount();
|
||||
index_count[variant] += primitive.getIndexCount();
|
||||
|
||||
// all primitives of a given variant and material should all have the same attribute mask
|
||||
llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
|
||||
attribute_mask |= primitive.mAttributeMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vb->unmapBuffer();
|
||||
// allocate vertex buffer and pack it
|
||||
if (vertex_count[variant] > 0)
|
||||
{
|
||||
U32 mat_idx = mat_id + 1;
|
||||
#if 0
|
||||
LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
|
||||
|
||||
vb->unbind();
|
||||
rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
|
||||
vb->allocateBuffer(vertex_count[variant],
|
||||
index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
|
||||
vb->setBuffer();
|
||||
|
||||
for (auto& mesh : mMeshes)
|
||||
{
|
||||
for (auto& primitive : mesh.mPrimitives)
|
||||
{
|
||||
if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
|
||||
{
|
||||
primitive.upload(vb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vb->unmapBuffer();
|
||||
|
||||
vb->unbind();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sanity check that all primitives have a vertex buffer
|
||||
for (auto& mesh : mMeshes)
|
||||
{
|
||||
for (auto& primitive : mesh.mPrimitives)
|
||||
// sanity check that all primitives have a vertex buffer
|
||||
for (auto& mesh : mMeshes)
|
||||
{
|
||||
llassert(primitive.mVertexBuffer.notNull());
|
||||
for (auto& primitive : mesh.mPrimitives)
|
||||
{
|
||||
//llassert(primitive.mVertexBuffer.notNull());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// build render batches
|
||||
for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
|
||||
{
|
||||
|
|
@ -664,6 +680,7 @@ bool Asset::prep()
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -672,9 +689,10 @@ Asset::Asset(const Value& src)
|
|||
*this = src;
|
||||
}
|
||||
|
||||
bool Asset::load(std::string_view filename)
|
||||
bool Asset::load(std::string_view filename, bool loadIntoVRAM)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
|
||||
mLoadIntoVRAM = loadIntoVRAM;
|
||||
mFilename = filename;
|
||||
std::string ext = gDirUtilp->getExtension(mFilename);
|
||||
|
||||
|
|
@ -692,7 +710,7 @@ bool Asset::load(std::string_view filename)
|
|||
}
|
||||
else if (ext == "glb")
|
||||
{
|
||||
return loadBinary(str);
|
||||
return loadBinary(str, mLoadIntoVRAM);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -709,8 +727,9 @@ bool Asset::load(std::string_view filename)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Asset::loadBinary(const std::string& data)
|
||||
bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM)
|
||||
{
|
||||
mLoadIntoVRAM = loadIntoVRAM;
|
||||
// load from binary gltf
|
||||
const U8* ptr = (const U8*)data.data();
|
||||
const U8* end = ptr + data.size();
|
||||
|
|
@ -935,8 +954,9 @@ void Asset::eraseBufferView(S32 bufferView)
|
|||
|
||||
LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
|
||||
|
||||
bool Image::prep(Asset& asset)
|
||||
bool Image::prep(Asset& asset, bool loadIntoVRAM)
|
||||
{
|
||||
mLoadIntoTexturePipe = loadIntoVRAM;
|
||||
LLUUID id;
|
||||
if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
|
||||
{ // loaded from an asset, fetch the texture from the asset system
|
||||
|
|
@ -951,12 +971,12 @@ bool Image::prep(Asset& asset)
|
|||
{ // embedded in a buffer, load the texture from the buffer
|
||||
BufferView& bufferView = asset.mBufferViews[mBufferView];
|
||||
Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
||||
|
||||
U8* data = buffer.mData.data() + bufferView.mByteOffset;
|
||||
|
||||
mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
|
||||
|
||||
if (mTexture.isNull())
|
||||
if (mLoadIntoTexturePipe)
|
||||
{
|
||||
U8* data = buffer.mData.data() + bufferView.mByteOffset;
|
||||
mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
|
||||
}
|
||||
else if (mTexture.isNull() && mLoadIntoTexturePipe)
|
||||
{
|
||||
LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;
|
||||
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
||||
|
|
@ -971,12 +991,12 @@ bool Image::prep(Asset& asset)
|
|||
std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
|
||||
|
||||
LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
|
||||
if (tracking_id.notNull())
|
||||
if (tracking_id.notNull() && mLoadIntoTexturePipe)
|
||||
{
|
||||
LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);
|
||||
mTexture = LLViewerTextureManager::getFetchedTexture(world_id);
|
||||
}
|
||||
else
|
||||
else if (mLoadIntoTexturePipe)
|
||||
{
|
||||
LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;
|
||||
LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
|
||||
|
|
@ -991,7 +1011,7 @@ bool Image::prep(Asset& asset)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!asset.mFilename.empty())
|
||||
if (!asset.mFilename.empty() && mLoadIntoTexturePipe)
|
||||
{ // local preview, boost image so it doesn't discard and force to save raw image in case we save out or upload
|
||||
mTexture->setBoostLevel(LLViewerTexture::BOOST_PREVIEW);
|
||||
mTexture->forceToSaveRawImage(0, F32_MAX);
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ namespace LL
|
|||
void serialize(boost::json::object& dst) const;
|
||||
};
|
||||
|
||||
// Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe.
|
||||
class Image
|
||||
{
|
||||
public:
|
||||
|
|
@ -301,6 +302,8 @@ namespace LL
|
|||
S32 mBits = -1;
|
||||
S32 mPixelType = -1;
|
||||
|
||||
bool mLoadIntoTexturePipe = false;
|
||||
|
||||
LLPointer<LLViewerFetchedTexture> mTexture;
|
||||
|
||||
const Image& operator=(const Value& src);
|
||||
|
|
@ -316,7 +319,7 @@ namespace LL
|
|||
// preserve only uri and name
|
||||
void clearData(Asset& asset);
|
||||
|
||||
bool prep(Asset& asset);
|
||||
bool prep(Asset& asset, bool loadIntoVRAM);
|
||||
};
|
||||
|
||||
// Render Batch -- vertex buffer and list of primitives to render using
|
||||
|
|
@ -391,6 +394,10 @@ namespace LL
|
|||
|
||||
// UBO for storing material data
|
||||
U32 mMaterialsUBO = 0;
|
||||
bool mLoadIntoVRAM = false;
|
||||
|
||||
std::vector<std::string> mUnsupportedExtensions;
|
||||
std::vector<std::string> mIgnoredExtensions;
|
||||
|
||||
// prepare for first time use
|
||||
bool prep();
|
||||
|
|
@ -428,12 +435,12 @@ namespace LL
|
|||
// accepts .gltf and .glb files
|
||||
// Any existing data will be lost
|
||||
// returns result of prep() on success
|
||||
bool load(std::string_view filename);
|
||||
bool load(std::string_view filename, bool loadIntoVRAM);
|
||||
|
||||
// load .glb contents from memory
|
||||
// data - binary contents of .glb file
|
||||
// returns result of prep() on success
|
||||
bool loadBinary(const std::string& data);
|
||||
bool loadBinary(const std::string& data, bool loadIntoVRAM);
|
||||
|
||||
const Asset& operator=(const Value& src);
|
||||
void serialize(boost::json::object& dst) const;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,12 @@ namespace LL
|
|||
dst.load3(src);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void copyVec3<F32, LLColor4U>(F32* src, LLColor4U& dst)
|
||||
{
|
||||
dst.set((U8)(src[0] * 255.f), (U8)(src[1] * 255.f), (U8)(src[2] * 255.f), 255);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
|
||||
{
|
||||
|
|
@ -369,6 +375,11 @@ namespace LL
|
|||
template<class T>
|
||||
inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)
|
||||
{
|
||||
if (accessor.mBufferView == INVALID_INDEX)
|
||||
{
|
||||
LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
|
||||
const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
|
||||
const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* @file LLGLTFLoader.h
|
||||
* @brief LLGLTFLoader class definition
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2022, 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLGLTFLoader_H
|
||||
#define LL_LLGLTFLoader_H
|
||||
|
||||
#include "tinygltf/tiny_gltf.h"
|
||||
|
||||
#include "asset.h"
|
||||
|
||||
#include "llglheaders.h"
|
||||
#include "lljointdata.h"
|
||||
#include "llmodelloader.h"
|
||||
|
||||
class LLGLTFLoader : public LLModelLoader
|
||||
{
|
||||
public:
|
||||
typedef std::map<std::string, LLImportMaterial> material_map;
|
||||
typedef std::map<std::string, std::string> joint_viewer_parent_map_t;
|
||||
typedef std::map<std::string, glm::mat4> joint_viewer_rest_map_t;
|
||||
typedef std::map<S32, glm::mat4> joint_node_mat4_map_t;
|
||||
|
||||
struct JointNodeData
|
||||
{
|
||||
JointNodeData()
|
||||
: mJointListIdx(-1)
|
||||
, mNodeIdx(-1)
|
||||
, mParentNodeIdx(-1)
|
||||
, mIsValidViewerJoint(false)
|
||||
, mIsParentValidViewerJoint(false)
|
||||
, mIsOverrideValid(false)
|
||||
{
|
||||
|
||||
}
|
||||
S32 mJointListIdx;
|
||||
S32 mNodeIdx;
|
||||
S32 mParentNodeIdx;
|
||||
glm::mat4 mGltfRestMatrix;
|
||||
glm::mat4 mViewerRestMatrix;
|
||||
glm::mat4 mOverrideRestMatrix;
|
||||
glm::mat4 mGltfMatrix;
|
||||
glm::mat4 mOverrideMatrix;
|
||||
std::string mName;
|
||||
bool mIsValidViewerJoint;
|
||||
bool mIsParentValidViewerJoint;
|
||||
bool mIsOverrideValid;
|
||||
};
|
||||
typedef std::map <S32, JointNodeData> joints_data_map_t;
|
||||
typedef std::map <std::string, S32> joints_name_to_node_map_t;
|
||||
|
||||
LLGLTFLoader(std::string filename,
|
||||
S32 lod,
|
||||
LLModelLoader::load_callback_t load_cb,
|
||||
LLModelLoader::joint_lookup_func_t joint_lookup_func,
|
||||
LLModelLoader::texture_load_func_t texture_load_func,
|
||||
LLModelLoader::state_callback_t state_cb,
|
||||
void * opaque_userdata,
|
||||
JointTransformMap & jointTransformMap,
|
||||
JointNameSet & jointsFromNodes,
|
||||
std::map<std::string, std::string> &jointAliasMap,
|
||||
U32 maxJointsPerMesh,
|
||||
U32 modelLimit,
|
||||
std::vector<LLJointData> viewer_skeleton); //,
|
||||
//bool preprocess );
|
||||
virtual ~LLGLTFLoader();
|
||||
|
||||
virtual bool OpenFile(const std::string &filename);
|
||||
|
||||
struct GLTFVertex
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv0;
|
||||
glm::u16vec4 joints;
|
||||
glm::vec4 weights;
|
||||
};
|
||||
|
||||
protected:
|
||||
LL::GLTF::Asset mGLTFAsset;
|
||||
tinygltf::Model mGltfModel;
|
||||
bool mGltfLoaded;
|
||||
bool mApplyXYRotation = false;
|
||||
U32 mGeneratedModelLimit;
|
||||
|
||||
// GLTF isn't aware of viewer's skeleton and uses it's own,
|
||||
// so need to take viewer's joints and use them to
|
||||
// recalculate iverse bind matrices
|
||||
std::vector<LLJointData> mViewerJointData;
|
||||
|
||||
// vector of vectors because of a posibility of having more than one skin
|
||||
typedef std::vector<LLMeshSkinInfo::matrix_list_t> bind_matrices_t;
|
||||
typedef std::vector<std::vector<std::string> > joint_names_t;
|
||||
bind_matrices_t mInverseBindMatrices;
|
||||
bind_matrices_t mAlternateBindMatrices;
|
||||
joint_names_t mJointNames; // empty string when no legal name for a given idx
|
||||
|
||||
// what group a joint belongs to.
|
||||
// For purpose of stripping unused groups when joints are over limit.
|
||||
struct JointGroups
|
||||
{
|
||||
std::string mGroup;
|
||||
std::string mParentGroup;
|
||||
};
|
||||
typedef std::map<std::string, JointGroups> joint_to_group_map_t;
|
||||
joint_to_group_map_t mJointGroups;
|
||||
|
||||
// per skin joint count, needs to be tracked for the sake of limits check.
|
||||
std::vector<S32> mValidJointsCount;
|
||||
|
||||
private:
|
||||
bool parseMeshes();
|
||||
void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform) const;
|
||||
void processNodeHierarchy(S32 node_idx, std::map<std::string, S32>& mesh_name_counts, U32 submodel_limit, const LLVolumeParams& volume_params);
|
||||
bool addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const;
|
||||
bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats, S32 instance_count);
|
||||
void populateJointsFromSkin(S32 skin_idx);
|
||||
void populateJointGroups();
|
||||
void addModelToScene(LLModel* pModel, U32 submodel_limit, const LLMatrix4& transformation, const LLVolumeParams& volume_params, const material_map& mats);
|
||||
S32 findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const;
|
||||
S32 findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const;
|
||||
S32 findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const; // if there are multiple roots, gltf stores them under one commor joint
|
||||
S32 findParentNode(S32 node) const;
|
||||
void buildJointGroup(LLJointData& viewer_data, const std::string& parent_group);
|
||||
void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const;
|
||||
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const;
|
||||
glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const;
|
||||
glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const;
|
||||
bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx);
|
||||
void checkForXYrotation(const LL::GLTF::Skin& gltf_skin);
|
||||
|
||||
std::string extractTextureToTempFile(S32 textureIndex, const std::string& texture_type);
|
||||
|
||||
void notifyUnsupportedExtension(bool unsupported);
|
||||
|
||||
// bool mPreprocessGLTF;
|
||||
|
||||
/* Below inherited from dae loader - unknown if/how useful here
|
||||
|
||||
void processElement(gltfElement *element, bool &badElement, GLTF *gltf);
|
||||
void processGltfModel(LLModel *model, GLTF *gltf, gltfElement *pRoot, gltfMesh *mesh, gltfSkin *skin);
|
||||
|
||||
material_map getMaterials(LLModel *model, gltfInstance_geometry *instance_geo, GLTF *gltf);
|
||||
LLImportMaterial profileToMaterial(gltfProfile_COMMON *material, GLTF *gltf);
|
||||
LLColor4 getGltfColor(gltfElement *element);
|
||||
|
||||
gltfElement *getChildFromElement(gltfElement *pElement, std::string const &name);
|
||||
|
||||
bool isNodeAJoint(gltfNode *pNode);
|
||||
void processJointNode(gltfNode *pNode, std::map<std::string, LLMatrix4> &jointTransforms);
|
||||
void extractTranslation(gltfTranslate *pTranslate, LLMatrix4 &transform);
|
||||
void extractTranslationViaElement(gltfElement *pTranslateElement, LLMatrix4 &transform);
|
||||
void extractTranslationViaSID(gltfElement *pElement, LLMatrix4 &transform);
|
||||
void buildJointToNodeMappingFromScene(gltfElement *pRoot);
|
||||
void processJointToNodeMapping(gltfNode *pNode);
|
||||
void processChildJoints(gltfNode *pParentNode);
|
||||
|
||||
bool verifyCount(int expected, int result);
|
||||
|
||||
// Verify that a controller matches vertex counts
|
||||
bool verifyController(gltfController *pController);
|
||||
|
||||
static bool addVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh, LLSD &log_msg);
|
||||
static bool createVolumeFacesFromGltfMesh(LLModel *model, gltfMesh *mesh);
|
||||
|
||||
static LLModel *loadModelFromGltfMesh(gltfMesh *mesh);
|
||||
|
||||
// Loads a mesh breaking it into one or more models as necessary
|
||||
// to get around volume face limitations while retaining >8 materials
|
||||
//
|
||||
bool loadModelsFromGltfMesh(gltfMesh *mesh, std::vector<LLModel *> &models_out, U32 submodel_limit);
|
||||
|
||||
static std::string getElementLabel(gltfElement *element);
|
||||
static size_t getSuffixPosition(std::string label);
|
||||
static std::string getLodlessLabel(gltfElement *element);
|
||||
|
||||
static std::string preprocessGLTF(std::string filename);
|
||||
*/
|
||||
|
||||
};
|
||||
#endif // LL_LLGLTFLLOADER_H
|
||||
|
|
@ -317,7 +317,7 @@ void GLTFSceneManager::load(const std::string& filename)
|
|||
{
|
||||
std::shared_ptr<Asset> asset = std::make_shared<Asset>();
|
||||
|
||||
if (asset->load(filename))
|
||||
if (asset->load(filename, true))
|
||||
{
|
||||
gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions
|
||||
asset->updateTransforms();
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ LLFilePicker LLFilePicker::sInstance;
|
|||
#define XML_FILTER L"XML files (*.xml)\0*.xml\0"
|
||||
#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0"
|
||||
#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0"
|
||||
#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
|
||||
#define MODEL_FILTER L"Model files (*.dae, *.gltf, *.glb)\0*.dae;*.gltf;*.glb\0"
|
||||
#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0"
|
||||
#define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"
|
||||
#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
|
||||
|
|
@ -217,6 +217,8 @@ bool LLFilePicker::setupFilter(ELoadFilter filter)
|
|||
break;
|
||||
case FFLOAD_MODEL:
|
||||
mOFN.lpstrFilter = MODEL_FILTER \
|
||||
COLLADA_FILTER \
|
||||
MATERIAL_FILTER \
|
||||
L"\0";
|
||||
break;
|
||||
case FFLOAD_MATERIAL:
|
||||
|
|
@ -671,6 +673,8 @@ std::unique_ptr<std::vector<std::string>> LLFilePicker::navOpenFilterProc(ELoadF
|
|||
case FFLOAD_HDRI:
|
||||
allowedv->push_back("exr");
|
||||
case FFLOAD_MODEL:
|
||||
allowedv->push_back("gltf");
|
||||
allowedv->push_back("glb");
|
||||
case FFLOAD_COLLADA:
|
||||
allowedv->push_back("dae");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
#include "llcallbacklist.h"
|
||||
#include "llviewertexteditor.h"
|
||||
#include "llviewernetwork.h"
|
||||
#include "llmaterialeditor.h"
|
||||
|
||||
|
||||
//static
|
||||
|
|
@ -619,11 +620,9 @@ void LLFloaterModelPreview::onJointListSelection()
|
|||
LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
|
||||
LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
|
||||
LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list");
|
||||
LLScrollListCtrl *joints_scale = panel->getChild<LLScrollListCtrl>("scale_overrides_list");
|
||||
LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");
|
||||
|
||||
joints_pos->deleteAllItems();
|
||||
joints_scale->deleteAllItems();
|
||||
|
||||
LLScrollListItem *selected = joints_list->getFirstSelected();
|
||||
if (selected)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ LLFloaterComboOptions* LLFloaterComboOptions::showUI(
|
|||
{
|
||||
combo_picker->mComboOptions->addSimpleElement(*iter);
|
||||
}
|
||||
combo_picker->mComboOptions->selectFirstItem();
|
||||
// select 'Bulk Upload All' option
|
||||
combo_picker->mComboOptions->selectNthItem((S32)options.size() - 1);
|
||||
|
||||
combo_picker->openFloater(LLSD(title));
|
||||
combo_picker->setFocus(true);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#include "llmodelloader.h"
|
||||
#include "lldaeloader.h"
|
||||
#include "llgltfloader.h"
|
||||
#include "gltf/llgltfloader.h"
|
||||
#include "llfloatermodelpreview.h"
|
||||
|
||||
#include "llagent.h"
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
#include "lldrawable.h"
|
||||
#include "llface.h"
|
||||
#include "lliconctrl.h"
|
||||
#include "lljointdata.h"
|
||||
#include "llmatrix4a.h"
|
||||
#include "llmeshrepository.h"
|
||||
#include "llmeshoptimizer.h"
|
||||
|
|
@ -810,6 +811,9 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
|
|||
}
|
||||
else
|
||||
{
|
||||
LLVOAvatar* av = getPreviewAvatar();
|
||||
std::vector<LLJointData> viewer_skeleton;
|
||||
av->getJointMatricesAndHierarhy(viewer_skeleton);
|
||||
mModelLoader = new LLGLTFLoader(
|
||||
filename,
|
||||
lod,
|
||||
|
|
@ -822,7 +826,8 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable
|
|||
mJointsFromNode,
|
||||
joint_alias_map,
|
||||
LLSkinningUtil::getMaxJointCount(),
|
||||
gSavedSettings.getU32("ImporterModelLimit"));
|
||||
gSavedSettings.getU32("ImporterModelLimit"),
|
||||
viewer_skeleton);
|
||||
}
|
||||
|
||||
if (force_disable_slm)
|
||||
|
|
@ -3090,25 +3095,48 @@ void LLModelPreview::lookupLODModelFiles(S32 lod)
|
|||
S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS;
|
||||
|
||||
std::string lod_filename = mLODFile[LLModel::LOD_HIGH];
|
||||
std::string ext = ".dae";
|
||||
std::string lod_filename_lower(lod_filename);
|
||||
LLStringUtil::toLower(lod_filename_lower);
|
||||
std::string::size_type i = lod_filename_lower.rfind(ext);
|
||||
if (i != std::string::npos)
|
||||
|
||||
// Check for each supported file extension
|
||||
std::vector<std::string> supported_exts = { ".dae", ".gltf", ".glb" };
|
||||
std::string found_ext;
|
||||
std::string::size_type ext_pos = std::string::npos;
|
||||
|
||||
for (const auto& ext : supported_exts)
|
||||
{
|
||||
lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext);
|
||||
}
|
||||
if (gDirUtilp->fileExists(lod_filename))
|
||||
{
|
||||
LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
|
||||
if (fmp)
|
||||
std::string::size_type i = lod_filename_lower.rfind(ext);
|
||||
if (i != std::string::npos)
|
||||
{
|
||||
fmp->setCtrlLoadFromFile(next_lod);
|
||||
ext_pos = i;
|
||||
found_ext = ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext_pos != std::string::npos)
|
||||
{
|
||||
// Replace extension with LOD suffix + original extension
|
||||
std::string lod_file_to_check = lod_filename;
|
||||
lod_file_to_check.replace(ext_pos, found_ext.size(), getLodSuffix(next_lod) + found_ext);
|
||||
|
||||
if (gDirUtilp->fileExists(lod_file_to_check))
|
||||
{
|
||||
LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
|
||||
if (fmp)
|
||||
{
|
||||
fmp->setCtrlLoadFromFile(next_lod);
|
||||
}
|
||||
loadModel(lod_file_to_check, next_lod);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookupLODModelFiles(next_lod);
|
||||
}
|
||||
loadModel(lod_filename, next_lod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No recognized extension found, continue with next LOD
|
||||
lookupLODModelFiles(next_lod);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,12 @@ void LLSkinningUtil::initSkinningMatrixPalette(
|
|||
|
||||
initJointNums(const_cast<LLMeshSkinInfo*>(skin), avatar);
|
||||
|
||||
if (skin->mInvBindMatrix.size() < count )
|
||||
{
|
||||
// faulty model? mInvBindMatrix.size() should have matched mJointNames.size()
|
||||
return;
|
||||
}
|
||||
|
||||
LLMatrix4a world[LL_CHARACTER_MAX_ANIMATED_JOINTS];
|
||||
|
||||
for (S32 j = 0; j < count; ++j)
|
||||
|
|
@ -354,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a
|
|||
{
|
||||
rig_info_tab[joint_num].setIsRiggedTo(true);
|
||||
|
||||
const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index];
|
||||
size_t bind_poses_size = skin->mBindPoseMatrix.size();
|
||||
const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity();
|
||||
LLVector4a pos_joint_space;
|
||||
|
||||
mat.affineTransform(pos, pos_joint_space);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
legacy_header_height="25">
|
||||
|
||||
<string name="status_idle"></string>
|
||||
<string name="status_parse_error">Error: Dae parsing issue - see log for details.</string>
|
||||
<string name="status_parse_error">Error: Model parsing issue - see log for details.</string>
|
||||
<string name="status_bind_shape_orientation">Warning: bind shape matrix is not in standard X-forward orientation.</string>
|
||||
<string name="status_material_mismatch">Error: Material of model is not a subset of reference model.</string>
|
||||
<string name="status_reading_file">Loading...</string>
|
||||
|
|
@ -45,6 +45,7 @@
|
|||
<string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>
|
||||
<string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>
|
||||
<string name="ModelLoaded">Model [MODEL_NAME] loaded</string>
|
||||
<string name="InvBindCountMismatch">Bind matrices count mismatch joints count</string>
|
||||
|
||||
<string name="IncompleteTC">Texture coordinates data is not complete.</string>
|
||||
<string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string>
|
||||
|
|
@ -61,6 +62,22 @@
|
|||
<string name="ParsingErrorNoScene">Document has no visual_scene</string>
|
||||
<string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string>
|
||||
|
||||
<!-- GLTF specific messages -->
|
||||
<string name="NoScenesFound">No scenes defined in GLTF file</string>
|
||||
<string name="InvalidMeshReference">Node [NODE_NAME] references invalid mesh [MESH_INDEX] (total meshes: [TOTAL_MESHES])</string>
|
||||
<string name="InvalidGeometryNonTriangulated">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Invalid geometry with [INDEX_COUNT] indices (must be triangulated)</string>
|
||||
<string name="EmptyVertexArray">Mesh [MESH_NAME] primitive [PRIMITIVE_INDEX]: Empty vertex array</string>
|
||||
<string name="ErrorIndexLimit">Unable to process mesh [MESH_NAME] due to 65,534 vertex limit. Vertex count: [VERTEX_COUNT]</string>
|
||||
<string name="TextureFound">Found texture: [TEXTURE_NAME] for material: [MATERIAL_NAME]</string>
|
||||
<string name="IgnoredExtension">Model uses unsupported extension: [EXT], related material properties are ignored</string>
|
||||
<string name="UnsupportedExtension">Unable to load model, unsupported extension: [EXT]</string>
|
||||
<string name="TooManyMeshParts">Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT]</string>
|
||||
<string name="FailedToCreateTempFile">Failed to create temporary file for embedded [TEXTURE_TYPE] texture [TEXTURE_INDEX]: [TEMP_FILE]</string>
|
||||
<string name="SkinJointsOverLimit">Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis.</string>
|
||||
<string name="ModelTooManyJoint">Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail</string>
|
||||
<string name="ParsingErrorMissingBuffer">Buffer is either missing or empty [BUFFER_NAME].</string>
|
||||
<string name="ParsingErrorMissingBufferBin">Buffer is either missing or empty. Check presence of [BUFFER_URI] file.</string>
|
||||
|
||||
<panel
|
||||
follows="top|left"
|
||||
height="595"
|
||||
|
|
@ -1706,7 +1723,6 @@ Analysed:
|
|||
height="408"/>
|
||||
<panel
|
||||
follows="right|bottom"
|
||||
can_resize="false"
|
||||
height="140"
|
||||
layout="topleft"
|
||||
name="right_panel"
|
||||
|
|
|
|||
|
|
@ -9453,8 +9453,11 @@ Unable to upload texture: '[NAME]'
|
|||
icon="alertmodal.tga"
|
||||
name="CannotUploadMaterial"
|
||||
type="alertmodal">
|
||||
There was a problem uploading the file
|
||||
Unable to upload material file. The file may be corrupted, in an unsupported format, or contain invalid data. Please check that you're using a valid GLTF/GLB file with proper material definitions.
|
||||
<tag>fail</tag>
|
||||
<usetemplate
|
||||
name="okbutton"
|
||||
yestext="OK"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
|
|
|
|||
Loading…
Reference in New Issue