phoenix-firestorm/indra/llprimitive/llgltfloader.cpp

408 lines
14 KiB
C++

/**
* @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 <boost/lexical_cast.hpp>
#include "llsdserialize.h"
#include "lljoint.h"
#include "glh/glh_linear.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> &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 = 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;
}