diff options
Diffstat (limited to 'indra/llprimitive')
21 files changed, 2791 insertions, 76 deletions
| diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index d69cd958a0..2bd1edaacc 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -8,9 +8,12 @@ include(LLCoreHttp)  include(LLPhysicsExtensions)  include(LLPrimitive)  include(GLH) +include(TinyGLTF)  set(llprimitive_SOURCE_FILES      lldaeloader.cpp +    llgltfloader.cpp +    llgltfmaterial.cpp      llmaterialid.cpp      llmaterial.cpp      llmaterialtable.cpp @@ -29,6 +32,9 @@ set(llprimitive_SOURCE_FILES  set(llprimitive_HEADER_FILES      CMakeLists.txt      lldaeloader.h +    llgltfloader.h +    llgltfmaterial.h +    llgltfmaterial_templates.h      legacy_object_types.h      llmaterial.h      llmaterialid.h @@ -60,6 +66,7 @@ target_link_libraries(llprimitive          llcorehttp          llxml          llcharacter +        llrender          llphysicsextensions_impl          ll::colladadom          ll::pcre @@ -71,6 +78,10 @@ if (LL_TESTS)      INCLUDE(LLAddBuildTest)      SET(llprimitive_TEST_SOURCE_FILES        llmediaentry.cpp +      llprimitive.cpp +      llgltfmaterial.cpp        ) + +    set_property(SOURCE llprimitive.cpp PROPERTY LL_TEST_ADDITIONAL_LIBRARIES llmessage)      LL_ADD_PROJECT_UNIT_TESTS(llprimitive "${llprimitive_TEST_SOURCE_FILES}")  endif (LL_TESTS) diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 752a850d42..46e1cb4922 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -2507,20 +2507,6 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD&  	return (status == LLModel::NO_ERRORS);  } -//static  -LLModel* LLDAELoader::loadModelFromDomMesh(domMesh *mesh) -{ -	LLVolumeParams volume_params; -	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); -	LLModel* ret = new LLModel(volume_params, 0.f);  -	createVolumeFacesFromDomMesh(ret, mesh); -    if (ret->mLabel.empty()) -    { -	    ret->mLabel = getElementLabel(mesh); -    } -    return ret; -} -  //static diff version supports creating multiple models when material counts spill  // over the 8 face server-side limit  // @@ -2578,6 +2564,7 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo  		ret->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); +        // remove unused/redundant vertices after normalizing  		if (!mNoOptimize)  		{  			ret->remapVolumeFaces(); @@ -2597,7 +2584,7 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo  			next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];  			next->getVolumeFaces() = remainder;  			next->mNormalizedScale = ret->mNormalizedScale; -			next->mNormalizedTranslation = ret->mNormalizedTranslation; +			  			if ( ret->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES)  			{  				next->mMaterialList.assign(ret->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, ret->mMaterialList.end()); @@ -2611,31 +2598,3 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo  	return true;  } - -bool LLDAELoader::createVolumeFacesFromDomMesh(LLModel* pModel, domMesh* mesh) -{ -	if (mesh) -	{ -		pModel->ClearFacesAndMaterials(); - -		LLSD placeholder; -		addVolumeFacesFromDomMesh(pModel, mesh, placeholder); - -		if (pModel->getNumVolumeFaces() > 0) -		{ -			pModel->normalizeVolumeFaces(); -			pModel->optimizeVolumeFaces(); - -			if (pModel->getNumVolumeFaces() > 0) -			{ -				return true; -			} -		} -	} -	else -	{	 -		LL_WARNS() << "no mesh found" << LL_ENDL; -	} - -	return false; -} diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index 2b211343e1..52ad908870 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -90,9 +90,6 @@ protected:  	bool verifyController( domController* pController );  	static bool addVolumeFacesFromDomMesh(LLModel* model, domMesh* mesh, LLSD& log_msg); -	static bool createVolumeFacesFromDomMesh(LLModel* model, domMesh *mesh); - -	static LLModel* loadModelFromDomMesh(domMesh* mesh);  	// Loads a mesh breaking it into one or more models as necessary  	// to get around volume face limitations while retaining >8 materials diff --git a/indra/llprimitive/llgltfloader.cpp b/indra/llprimitive/llgltfloader.cpp new file mode 100644 index 0000000000..7394f99794 --- /dev/null +++ b/indra/llprimitive/llgltfloader.cpp @@ -0,0 +1,407 @@ +/** + * @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; +} diff --git a/indra/llprimitive/llgltfloader.h b/indra/llprimitive/llgltfloader.h new file mode 100644 index 0000000000..b4d6ca1940 --- /dev/null +++ b/indra/llprimitive/llgltfloader.h @@ -0,0 +1,206 @@ +/** + * @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> &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 diff --git a/indra/llprimitive/llgltfmaterial.cpp b/indra/llprimitive/llgltfmaterial.cpp new file mode 100644 index 0000000000..ae165f7fa4 --- /dev/null +++ b/indra/llprimitive/llgltfmaterial.cpp @@ -0,0 +1,820 @@ +/** + * @file llgltfmaterial.cpp + * @brief Material 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$ + */ + + +#include "linden_common.h" + +#include "llgltfmaterial.h" + +#include "llsdserialize.h" + +// NOTE -- this should be the one and only place tiny_gltf.h is included +#include "tinygltf/tiny_gltf.h" +#include "llgltfmaterial_templates.h" + +const char* const LLGLTFMaterial::ASSET_VERSION = "1.1"; +const char* const LLGLTFMaterial::ASSET_TYPE = "GLTF 2.0"; +const std::array<std::string, 2> LLGLTFMaterial::ACCEPTED_ASSET_VERSIONS = { "1.0", "1.1" }; + +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_ROTATION = "rotation"; + +// special UUID that indicates a null UUID in override data +const LLUUID LLGLTFMaterial::GLTF_OVERRIDE_NULL_UUID = LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff"); + +void LLGLTFMaterial::TextureTransform::getPacked(F32 (&packed)[8]) const +{ +    packed[0] = mScale.mV[VX]; +    packed[1] = mScale.mV[VY]; +    packed[2] = mRotation; +    // packed[3] = unused +    packed[4] = mOffset.mV[VX]; +    packed[5] = mOffset.mV[VY]; +    // packed[6] = unused +    // packed[7] = unused +} + +bool LLGLTFMaterial::TextureTransform::operator==(const TextureTransform& other) const +{ +    return mOffset == other.mOffset && mScale == other.mScale && mRotation == other.mRotation; +} + +LLGLTFMaterial::LLGLTFMaterial(const LLGLTFMaterial& rhs) +{ +    *this = rhs; +} + +LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs) +{ +    //have to do a manual operator= because of LLRefCount +    mTextureId = rhs.mTextureId; + +    mTextureTransform = rhs.mTextureTransform; + +    mBaseColor = rhs.mBaseColor; +    mEmissiveColor = rhs.mEmissiveColor; + +    mMetallicFactor = rhs.mMetallicFactor; +    mRoughnessFactor = rhs.mRoughnessFactor; +    mAlphaCutoff = rhs.mAlphaCutoff; + +    mDoubleSided = rhs.mDoubleSided; +    mAlphaMode = rhs.mAlphaMode; + +    mOverrideDoubleSided = rhs.mOverrideDoubleSided; +    mOverrideAlphaMode = rhs.mOverrideAlphaMode; + +    mTrackingIdToLocalTexture = rhs.mTrackingIdToLocalTexture; + +    updateTextureTracking(); + +    return *this; +} + +bool LLGLTFMaterial::operator==(const LLGLTFMaterial& rhs) const +{ +    return mTextureId == rhs.mTextureId && + +        mTextureTransform == rhs.mTextureTransform && + +        mBaseColor == rhs.mBaseColor && +        mEmissiveColor == rhs.mEmissiveColor && + +        mMetallicFactor == rhs.mMetallicFactor && +        mRoughnessFactor == rhs.mRoughnessFactor && +        mAlphaCutoff == rhs.mAlphaCutoff && + +        mDoubleSided == rhs.mDoubleSided && +        mAlphaMode == rhs.mAlphaMode && + +        mOverrideDoubleSided == rhs.mOverrideDoubleSided && +        mOverrideAlphaMode == rhs.mOverrideAlphaMode; +} + +bool LLGLTFMaterial::fromJSON(const std::string& json, std::string& warn_msg, std::string& error_msg) +{ +    LL_PROFILE_ZONE_SCOPED; +    tinygltf::TinyGLTF gltf; + +    tinygltf::Model model_in; + +    if (gltf.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, json.c_str(), json.length(), "")) +    { +        setFromModel(model_in, 0); + +        return true; +    } + +    return false; +} + +std::string LLGLTFMaterial::asJSON(bool prettyprint) const +{ +    LL_PROFILE_ZONE_SCOPED; +    tinygltf::TinyGLTF gltf; + +    tinygltf::Model model_out; + +    std::ostringstream str; + +    writeToModel(model_out, 0); + +    // To ensure consistency in asset upload, this should be the only reference +    // to WriteGltfSceneToStream in the viewer. +    gltf.WriteGltfSceneToStream(&model_out, str, prettyprint, false); + +    return str.str(); +} + +void LLGLTFMaterial::setFromModel(const tinygltf::Model& model, S32 mat_index) +{ +    LL_PROFILE_ZONE_SCOPED; +    if (model.materials.size() <= mat_index) +    { +        return; +    } + +    const tinygltf::Material& material_in = model.materials[mat_index]; + +    // Apply base color texture +    setFromTexture(model, material_in.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR); +    // Apply normal map +    setFromTexture(model, material_in.normalTexture, GLTF_TEXTURE_INFO_NORMAL); +    // Apply metallic-roughness texture +    setFromTexture(model, material_in.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS); +    // Apply emissive texture +    setFromTexture(model, material_in.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE); + +    setAlphaMode(material_in.alphaMode); +    mAlphaCutoff = llclamp((F32)material_in.alphaCutoff, 0.f, 1.f); + +    mBaseColor.set(material_in.pbrMetallicRoughness.baseColorFactor); +    mEmissiveColor.set(material_in.emissiveFactor); + +    mMetallicFactor = llclamp((F32)material_in.pbrMetallicRoughness.metallicFactor, 0.f, 1.f); +    mRoughnessFactor = llclamp((F32)material_in.pbrMetallicRoughness.roughnessFactor, 0.f, 1.f); + +    mDoubleSided = material_in.doubleSided; + +    if (material_in.extras.IsObject()) +    { +        tinygltf::Value::Object extras = material_in.extras.Get<tinygltf::Value::Object>(); +        const auto& alpha_mode = extras.find("override_alpha_mode"); +        if (alpha_mode != extras.end()) +        { +            mOverrideAlphaMode = alpha_mode->second.Get<bool>(); +        } + +        const auto& double_sided = extras.find("override_double_sided"); +        if (double_sided != extras.end()) +        { +            mOverrideDoubleSided = double_sided->second.Get<bool>(); +        } +    } +} + +// static +LLVector2 LLGLTFMaterial::vec2FromJson(const tinygltf::Value::Object& object, const char* key, const LLVector2& default_value) +{ +    const auto it = object.find(key); +    if (it == object.end()) +    { +        return default_value; +    } +    const tinygltf::Value& vec2_json = std::get<1>(*it); +    if (!vec2_json.IsArray() || vec2_json.ArrayLen() < LENGTHOFVECTOR2) +    { +        return default_value; +    } +    LLVector2 value; +    for (U32 i = 0; i < LENGTHOFVECTOR2; ++i) +    { +        const tinygltf::Value& real_json = vec2_json.Get(i); +        if (!real_json.IsReal()) +        { +            return default_value; +        } +        value.mV[i] = (F32)real_json.Get<double>(); +    } +    return value; +} + +// static +F32 LLGLTFMaterial::floatFromJson(const tinygltf::Value::Object& object, const char* key, const F32 default_value) +{ +    const auto it = object.find(key); +    if (it == object.end()) +    { +        return default_value; +    } +    const tinygltf::Value& real_json = std::get<1>(*it); +    if (!real_json.IsReal()) +    { +        return default_value; +    } +    return (F32)real_json.GetNumberAsDouble(); +} + +void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const +{ +    LL_PROFILE_ZONE_SCOPED; +    if (model.materials.size() < mat_index+1) +    { +        model.materials.resize(mat_index + 1); +    } + +    tinygltf::Material& material_out = model.materials[mat_index]; + +    // set base color texture +    writeToTexture(model, material_out.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR); +    // set normal texture +    writeToTexture(model, material_out.normalTexture, GLTF_TEXTURE_INFO_NORMAL); +    // set metallic-roughness texture +    writeToTexture(model, material_out.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS); +    // set emissive texture +    writeToTexture(model, material_out.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE); +    // set occlusion texture +    // *NOTE: This is required for ORM materials for GLTF compliance. +    // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_material_occlusiontexture +    writeToTexture(model, material_out.occlusionTexture, GLTF_TEXTURE_INFO_OCCLUSION); + + +    material_out.alphaMode = getAlphaMode(); +    material_out.alphaCutoff = mAlphaCutoff; + +    mBaseColor.write(material_out.pbrMetallicRoughness.baseColorFactor); + +    if (mEmissiveColor != LLGLTFMaterial::getDefaultEmissiveColor()) +    { +        material_out.emissiveFactor.resize(3); +        mEmissiveColor.write(material_out.emissiveFactor); +    } + +    material_out.pbrMetallicRoughness.metallicFactor = mMetallicFactor; +    material_out.pbrMetallicRoughness.roughnessFactor = mRoughnessFactor; + +    material_out.doubleSided = mDoubleSided; + +    // generate "extras" string +    tinygltf::Value::Object extras; +    bool write_extras = false; +    if (mOverrideAlphaMode && mAlphaMode == getDefaultAlphaMode()) +    { +        extras["override_alpha_mode"] = tinygltf::Value(mOverrideAlphaMode); +        write_extras = true; +    } + +    if (mOverrideDoubleSided && mDoubleSided == getDefaultDoubleSided()) +    { +        extras["override_double_sided"] = tinygltf::Value(mOverrideDoubleSided); +        write_extras = true; +    } + +    if (write_extras) +    { +        material_out.extras = tinygltf::Value(extras); +    } + +    model.asset.version = "2.0"; +} + +void LLGLTFMaterial::sanitizeAssetMaterial() +{ +    mTextureTransform = sDefault.mTextureTransform; +} + +bool LLGLTFMaterial::setBaseMaterial() +{ +    const LLGLTFMaterial old_override = *this; +    *this = sDefault; +    setBaseMaterial(old_override); +    return *this != old_override; +} + +// For material overrides only. Copies transforms from the old override. +void LLGLTFMaterial::setBaseMaterial(const LLGLTFMaterial& old_override_mat) +{ +    mTextureTransform = old_override_mat.mTextureTransform; +} + +bool LLGLTFMaterial::isClearedForBaseMaterial() const +{ +    LLGLTFMaterial cleared_override = sDefault; +    cleared_override.setBaseMaterial(*this); +    return *this == cleared_override; +} + + +// static +void LLGLTFMaterial::hackOverrideUUID(LLUUID& id) +{ +    if (id == LLUUID::null) +    { +        id = GLTF_OVERRIDE_NULL_UUID; +    } +} + +void LLGLTFMaterial::setTextureId(TextureInfo texture_info, const LLUUID& id, bool for_override) +{ +    mTextureId[texture_info] = id; +    if (for_override) +    { +        hackOverrideUUID(mTextureId[texture_info]); +    } +} + +void LLGLTFMaterial::setBaseColorId(const LLUUID& id, bool for_override) +{ +    setTextureId(GLTF_TEXTURE_INFO_BASE_COLOR, id, for_override); +} + +void LLGLTFMaterial::setNormalId(const LLUUID& id, bool for_override) +{ +    setTextureId(GLTF_TEXTURE_INFO_NORMAL, id, for_override); +} + +void LLGLTFMaterial::setOcclusionRoughnessMetallicId(const LLUUID& id, bool for_override) +{ +    setTextureId(GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, id, for_override); +} + +void LLGLTFMaterial::setEmissiveId(const LLUUID& id, bool for_override) +{ +    setTextureId(GLTF_TEXTURE_INFO_EMISSIVE, id, for_override); +} + +void LLGLTFMaterial::setBaseColorFactor(const LLColor4& baseColor, bool for_override) +{ +    mBaseColor.set(baseColor); +    mBaseColor.clamp(); + +    if (for_override) +    { // hack -- nudge off of default value +        if (mBaseColor == getDefaultBaseColor()) +        { +            mBaseColor.mV[3] -= FLT_EPSILON; +        } +    } +} + +void LLGLTFMaterial::setAlphaCutoff(F32 cutoff, bool for_override) +{ +    mAlphaCutoff = llclamp(cutoff, 0.f, 1.f); +    if (for_override) +    { // hack -- nudge off of default value +        if (mAlphaCutoff == getDefaultAlphaCutoff()) +        { +            mAlphaCutoff -= FLT_EPSILON; +        } +    } +} + +void LLGLTFMaterial::setEmissiveColorFactor(const LLColor3& emissiveColor, bool for_override) +{ +    mEmissiveColor = emissiveColor; +    mEmissiveColor.clamp(); + +    if (for_override) +    { // hack -- nudge off of default value +        if (mEmissiveColor == getDefaultEmissiveColor()) +        { +            mEmissiveColor.mV[0] += FLT_EPSILON; +        } +    } +} + +void LLGLTFMaterial::setMetallicFactor(F32 metallic, bool for_override) +{ +    mMetallicFactor = llclamp(metallic, 0.f, for_override ? 1.f - FLT_EPSILON : 1.f); +} + +void LLGLTFMaterial::setRoughnessFactor(F32 roughness, bool for_override) +{ +    mRoughnessFactor = llclamp(roughness, 0.f, for_override ? 1.f - FLT_EPSILON : 1.f); +} + +void LLGLTFMaterial::setAlphaMode(const std::string& mode, bool for_override) +{ +    S32 m = getDefaultAlphaMode(); +    if (mode == "MASK") +    { +        m = ALPHA_MODE_MASK; +    } +    else if (mode == "BLEND") +    { +        m = ALPHA_MODE_BLEND; +    } + +    setAlphaMode(m, for_override); +} + +const char* LLGLTFMaterial::getAlphaMode() const +{ +    switch (mAlphaMode) +    { +    case ALPHA_MODE_MASK: return "MASK"; +    case ALPHA_MODE_BLEND: return "BLEND"; +    default: return "OPAQUE"; +    } +} + +void LLGLTFMaterial::setAlphaMode(S32 mode, bool for_override) +{ +    mAlphaMode = (AlphaMode) llclamp(mode, (S32) ALPHA_MODE_OPAQUE, (S32) ALPHA_MODE_MASK); +    mOverrideAlphaMode = for_override && mAlphaMode == getDefaultAlphaMode(); +} + +void LLGLTFMaterial::setDoubleSided(bool double_sided, bool for_override) +{ +    // sure, no clamping will ever be needed for a bool, but include the +    // setter for consistency with the clamping API +    mDoubleSided = double_sided; +    mOverrideDoubleSided = for_override && mDoubleSided == getDefaultDoubleSided(); +} + +void LLGLTFMaterial::setTextureOffset(TextureInfo texture_info, const LLVector2& offset) +{ +    mTextureTransform[texture_info].mOffset = offset; +} + +void LLGLTFMaterial::setTextureScale(TextureInfo texture_info, const LLVector2& scale) +{ +    mTextureTransform[texture_info].mScale = scale; +} + +void LLGLTFMaterial::setTextureRotation(TextureInfo texture_info, float rotation) +{ +    mTextureTransform[texture_info].mRotation = rotation; +} + +// Default value accessors (NOTE: these MUST match the GLTF specification) + +// Make a static default material for accessors +const LLGLTFMaterial LLGLTFMaterial::sDefault; + +F32 LLGLTFMaterial::getDefaultAlphaCutoff() +{ +    return sDefault.mAlphaCutoff; +} + +S32 LLGLTFMaterial::getDefaultAlphaMode() +{ +    return (S32) sDefault.mAlphaMode; +} + +F32 LLGLTFMaterial::getDefaultMetallicFactor() +{ +    return sDefault.mMetallicFactor; +} + +F32 LLGLTFMaterial::getDefaultRoughnessFactor() +{ +    return sDefault.mRoughnessFactor; +} + +LLColor4 LLGLTFMaterial::getDefaultBaseColor() +{ +    return sDefault.mBaseColor; +} + +LLColor3 LLGLTFMaterial::getDefaultEmissiveColor() +{ +    return sDefault.mEmissiveColor; +} + +bool LLGLTFMaterial::getDefaultDoubleSided() +{ +    return sDefault.mDoubleSided; +} + +LLVector2 LLGLTFMaterial::getDefaultTextureOffset() +{ +    return sDefault.mTextureTransform[0].mOffset; +} + +LLVector2 LLGLTFMaterial::getDefaultTextureScale() +{ +    return sDefault.mTextureTransform[0].mScale; +} + +F32 LLGLTFMaterial::getDefaultTextureRotation() +{ +    return sDefault.mTextureTransform[0].mRotation; +} + +// static +void LLGLTFMaterial::applyOverrideUUID(LLUUID& dst_id, const LLUUID& override_id) +{ +    if (override_id != GLTF_OVERRIDE_NULL_UUID) +    { +        if (override_id != LLUUID::null) +        { +            dst_id = override_id; +        } +    } +    else +    { +        dst_id = LLUUID::null; +    } +} + +void LLGLTFMaterial::applyOverride(const LLGLTFMaterial& override_mat) +{ +    LL_PROFILE_ZONE_SCOPED; + +    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +    { +        LLUUID& texture_id = mTextureId[i]; +        const LLUUID& override_texture_id = override_mat.mTextureId[i]; +        applyOverrideUUID(texture_id, override_texture_id); +    } + +    if (override_mat.mBaseColor != getDefaultBaseColor()) +    { +        mBaseColor = override_mat.mBaseColor; +    } + +    if (override_mat.mEmissiveColor != getDefaultEmissiveColor()) +    { +        mEmissiveColor = override_mat.mEmissiveColor; +    } + +    if (override_mat.mMetallicFactor != getDefaultMetallicFactor()) +    { +        mMetallicFactor = override_mat.mMetallicFactor; +    } + +    if (override_mat.mRoughnessFactor != getDefaultRoughnessFactor()) +    { +        mRoughnessFactor = override_mat.mRoughnessFactor; +    } + +    if (override_mat.mAlphaMode != getDefaultAlphaMode() || override_mat.mOverrideAlphaMode) +    { +        mAlphaMode = override_mat.mAlphaMode; +    } +    if (override_mat.mAlphaCutoff != getDefaultAlphaCutoff()) +    { +        mAlphaCutoff = override_mat.mAlphaCutoff; +    } + +    if (override_mat.mDoubleSided != getDefaultDoubleSided() || override_mat.mOverrideDoubleSided) +    { +        mDoubleSided = override_mat.mDoubleSided; +    } + +    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +    { +        if (override_mat.mTextureTransform[i].mOffset != getDefaultTextureOffset()) +        { +            mTextureTransform[i].mOffset = override_mat.mTextureTransform[i].mOffset; +        } + +        if (override_mat.mTextureTransform[i].mScale != getDefaultTextureScale()) +        { +            mTextureTransform[i].mScale = override_mat.mTextureTransform[i].mScale; +        } + +        if (override_mat.mTextureTransform[i].mRotation != getDefaultTextureRotation()) +        { +            mTextureTransform[i].mRotation = override_mat.mTextureTransform[i].mRotation; +        } +    } + +    mTrackingIdToLocalTexture.insert(override_mat.mTrackingIdToLocalTexture.begin(), override_mat.mTrackingIdToLocalTexture.begin()); + +    updateTextureTracking(); +} + +void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data) +{ +    LL_PROFILE_ZONE_SCOPED; +    llassert(data.isUndefined()); + +    // make every effort to shave bytes here + +    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +    { +        LLUUID& texture_id = mTextureId[i]; +        const LLUUID& override_texture_id = override_mat.mTextureId[i]; +        if (override_texture_id.notNull() && override_texture_id != texture_id) +        { +            data["tex"][i] = LLSD::UUID(override_texture_id); +        } +    } + +    if (override_mat.mBaseColor != getDefaultBaseColor()) +    { +        data["bc"] = override_mat.mBaseColor.getValue(); +    } + +    if (override_mat.mEmissiveColor != getDefaultEmissiveColor()) +    { +        data["ec"] = override_mat.mEmissiveColor.getValue(); +    } + +    if (override_mat.mMetallicFactor != getDefaultMetallicFactor()) +    { +        data["mf"] = override_mat.mMetallicFactor; +    } + +    if (override_mat.mRoughnessFactor != getDefaultRoughnessFactor()) +    { +        data["rf"] = override_mat.mRoughnessFactor; +    } + +    if (override_mat.mAlphaMode != getDefaultAlphaMode() || override_mat.mOverrideAlphaMode) +    { +        data["am"] = override_mat.mAlphaMode; +    } + +    if (override_mat.mAlphaCutoff != getDefaultAlphaCutoff()) +    { +        data["ac"] = override_mat.mAlphaCutoff; +    } + +    if (override_mat.mDoubleSided != getDefaultDoubleSided() || override_mat.mOverrideDoubleSided) +    { +        data["ds"] = override_mat.mDoubleSided; +    } + +    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +    { +        if (override_mat.mTextureTransform[i].mOffset != getDefaultTextureOffset()) +        { +            data["ti"][i]["o"] = override_mat.mTextureTransform[i].mOffset.getValue(); +        } + +        if (override_mat.mTextureTransform[i].mScale != getDefaultTextureScale()) +        { +            data["ti"][i]["s"] = override_mat.mTextureTransform[i].mScale.getValue(); +        } + +        if (override_mat.mTextureTransform[i].mRotation != getDefaultTextureRotation()) +        { +            data["ti"][i]["r"] = override_mat.mTextureTransform[i].mRotation; +        } +    } +} + + +void LLGLTFMaterial::applyOverrideLLSD(const LLSD& data) +{ +    const LLSD& tex = data["tex"]; + +    if (tex.isArray()) +    { +        for (int i = 0; i < tex.size(); ++i) +        { +            mTextureId[i] = tex[i].asUUID(); +        } +    } + +    const LLSD& bc = data["bc"]; +    if (bc.isDefined()) +    { +        mBaseColor.setValue(bc); +    } + +    const LLSD& ec = data["ec"]; +    if (ec.isDefined()) +    { +        mEmissiveColor.setValue(ec); +    } + +    const LLSD& mf = data["mf"]; +    if (mf.isReal()) +    { +        mMetallicFactor = mf.asReal(); +    } + +    const LLSD& rf = data["rf"]; +    if (rf.isReal()) +    { +        mRoughnessFactor = rf.asReal(); +    } + +    const LLSD& am = data["am"]; +    if (am.isInteger()) +    { +        mAlphaMode = (AlphaMode) am.asInteger(); +        mOverrideAlphaMode = true; +    } + +    const LLSD& ac = data["ac"]; +    if (ac.isReal()) +    { +        mAlphaCutoff = ac.asReal(); +    } + +    const LLSD& ds = data["ds"]; +    if (ds.isBoolean()) +    { +        mDoubleSided = ds.asBoolean(); +        mOverrideDoubleSided = true; +    } + +    const LLSD& ti = data["ti"]; +    if (ti.isArray()) +    { +        for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +        { +            const LLSD& o = ti[i]["o"]; +            if (o.isDefined()) +            { +                mTextureTransform[i].mOffset.setValue(o); +            } + +            const LLSD& s = ti[i]["s"]; +            if (s.isDefined()) +            { +                mTextureTransform[i].mScale.setValue(s); +            } + +            const LLSD& r = ti[i]["r"]; +            if (r.isReal()) +            { +                mTextureTransform[i].mRotation = r.asReal(); +            } +        } +    } +} + +LLUUID LLGLTFMaterial::getHash() const +{ +    LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; +    // HACK - hash the bytes of this object but don't include the ref count +    LLUUID hash; +    HBXXH128::digest(hash, (unsigned char*)this + sizeof(LLRefCount), sizeof(*this) - sizeof(LLRefCount)); +    return hash; +} + +void LLGLTFMaterial::addLocalTextureTracking(const LLUUID& tracking_id, const LLUUID& tex_id) +{ +    mTrackingIdToLocalTexture[tracking_id] = tex_id; +} + +void LLGLTFMaterial::removeLocalTextureTracking(const LLUUID& tracking_id) +{ +    mTrackingIdToLocalTexture.erase(tracking_id); +} + +bool LLGLTFMaterial::replaceLocalTexture(const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id) +{ +    bool res = false; + +    for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i) +    { +        if (mTextureId[i] == old_id) +        { +            mTextureId[i] = new_id; +            res = true; +        } +        else if (mTextureId[i] == new_id) +        { +            res = true; +        } +    } + +    if (res) +    { +        mTrackingIdToLocalTexture[tracking_id] = new_id; +    } +    else +    { +        mTrackingIdToLocalTexture.erase(tracking_id); +    } + +    return res; +} + +void LLGLTFMaterial::updateTextureTracking() +{ +    // setTEGLTFMaterialOverride is responsible for tracking +    // for material overrides editor will set it +} diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h new file mode 100644 index 0000000000..02f62fb08c --- /dev/null +++ b/indra/llprimitive/llgltfmaterial.h @@ -0,0 +1,252 @@ +/** + * @file llgltfmaterial.h + * @brief Material 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$ + */ + +#pragma once + +#include "llrefcount.h" +#include "llmemory.h" +#include "v4color.h" +#include "v3color.h" +#include "v2math.h" +#include "lluuid.h" +#include "hbxxh.h" + +#include <array> +#include <string> +#include <map> + +namespace tinygltf +{ +    class Model; +    struct TextureInfo; +    class Value; +} + +class LLTextureEntry; + +class LLGLTFMaterial : public LLRefCount +{ +public: + +    // default material for reference +    static const LLGLTFMaterial sDefault; + +    static const char* const ASSET_VERSION; +    static const char* const ASSET_TYPE; +    // Max allowed size of a GLTF material asset or override, when serialized +    // as a minified JSON string +    static constexpr size_t MAX_ASSET_LENGTH = 2048; +    static const std::array<std::string, 2> ACCEPTED_ASSET_VERSIONS; +    static bool isAcceptedVersion(const std::string& version) { return std::find(ACCEPTED_ASSET_VERSIONS.cbegin(), ACCEPTED_ASSET_VERSIONS.cend(), version) != ACCEPTED_ASSET_VERSIONS.cend(); } + +    struct TextureTransform +    { +        LLVector2 mOffset = { 0.f, 0.f }; +        LLVector2 mScale = { 1.f, 1.f }; +        F32 mRotation = 0.f; + +        void getPacked(F32 (&packed)[8]) const; + +        bool operator==(const TextureTransform& other) const; +        bool operator!=(const TextureTransform& other) const { return !(*this == other); } +    }; + +    enum AlphaMode +    { +        ALPHA_MODE_OPAQUE = 0, +        ALPHA_MODE_BLEND, +        ALPHA_MODE_MASK +    }; + +    LLGLTFMaterial() {} +    LLGLTFMaterial(const LLGLTFMaterial& rhs); + +    LLGLTFMaterial& operator=(const LLGLTFMaterial& rhs); +    bool operator==(const LLGLTFMaterial& rhs) const; +    bool operator!=(const LLGLTFMaterial& rhs) const { return !(*this == rhs); } + +    enum TextureInfo : U32 +    { +        GLTF_TEXTURE_INFO_BASE_COLOR, +        GLTF_TEXTURE_INFO_NORMAL, +        GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, +        // *NOTE: GLTF_TEXTURE_INFO_OCCLUSION is currently ignored, in favor of +        // the values specified with GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS. +        // Currently, only ORM materials are supported (materials which define +        // occlusion, roughness, and metallic in the same texture). +        // -Cosmic,2023-01-26 +        GLTF_TEXTURE_INFO_OCCLUSION = GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, +        GLTF_TEXTURE_INFO_EMISSIVE, + +        GLTF_TEXTURE_INFO_COUNT +    }; + +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_SCALE; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_OFFSET; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_ROTATION; +    static const LLUUID GLTF_OVERRIDE_NULL_UUID; + +    std::array<LLUUID, GLTF_TEXTURE_INFO_COUNT> mTextureId; +    std::array<TextureTransform, GLTF_TEXTURE_INFO_COUNT> mTextureTransform; + +    // NOTE: initialize values to defaults according to the GLTF spec +    // NOTE: these values should be in linear color space +    LLColor4 mBaseColor = LLColor4(1, 1, 1, 1); +    LLColor3 mEmissiveColor = LLColor3(0, 0, 0); + +    F32 mMetallicFactor = 1.f; +    F32 mRoughnessFactor = 1.f; +    F32 mAlphaCutoff = 0.5f; + +    bool mDoubleSided = false; +    AlphaMode mAlphaMode = ALPHA_MODE_OPAQUE; + +    // override specific flags for state that can't use off-by-epsilon or UUID hack +    bool mOverrideDoubleSided = false; +    bool mOverrideAlphaMode = false; + +    // get a UUID based on a hash of this LLGLTFMaterial +    LLUUID getHash() const; + +    //setters for various members (will clamp to acceptable ranges) +    // for_override - set to true if this value is being set as part of an override (important for handling override to default value) + +    void setTextureId(TextureInfo texture_info, const LLUUID& id, bool for_override = false); + +    void setBaseColorId(const LLUUID& id, bool for_override = false); +    void setNormalId(const LLUUID& id, bool for_override = false); +    void setOcclusionRoughnessMetallicId(const LLUUID& id, bool for_override = false); +    void setEmissiveId(const LLUUID& id, bool for_override = false); + +    void setBaseColorFactor(const LLColor4& baseColor, bool for_override = false); +    void setAlphaCutoff(F32 cutoff, bool for_override = false); +    void setEmissiveColorFactor(const LLColor3& emissiveColor, bool for_override = false); +    void setMetallicFactor(F32 metallic, bool for_override = false); +    void setRoughnessFactor(F32 roughness, bool for_override = false); +    void setAlphaMode(S32 mode, bool for_override = false); +    void setDoubleSided(bool double_sided, bool for_override = false); + +    // *NOTE: texture offsets only exist in overrides, so "for_override" is not needed + +    void setTextureOffset(TextureInfo texture_info, const LLVector2& offset); +    void setTextureScale(TextureInfo texture_info, const LLVector2& scale); +    void setTextureRotation(TextureInfo texture_info, float rotation); + +    // Default value accessors +    static F32 getDefaultAlphaCutoff(); +    static S32 getDefaultAlphaMode(); +    static F32 getDefaultMetallicFactor(); +    static F32 getDefaultRoughnessFactor(); +    static LLColor4 getDefaultBaseColor(); +    static LLColor3 getDefaultEmissiveColor(); +    static bool getDefaultDoubleSided(); +    static LLVector2 getDefaultTextureOffset(); +    static LLVector2 getDefaultTextureScale(); +    static F32 getDefaultTextureRotation(); + +    static void hackOverrideUUID(LLUUID& id); +    static void applyOverrideUUID(LLUUID& dst_id, const LLUUID& override_id); + +    // set mAlphaMode from string. +    // Anything otherthan "MASK" or "BLEND" sets mAlphaMode to ALPHA_MODE_OPAQUE +    void setAlphaMode(const std::string& mode, bool for_override = false); + +    const char* getAlphaMode() const; + +    // set the contents of this LLGLTFMaterial from the given json +    // returns true if successful +    // if unsuccessful, the contents of this LLGLTFMaterial should be left unchanged and false is returned +    // json - the json text to load from +    // warn_msg - warning message from TinyGLTF if any +    // error_msg - error_msg from TinyGLTF if any +    bool fromJSON(const std::string& json, std::string& warn_msg, std::string& error_msg); + +    // get the contents of this LLGLTFMaterial as a json string +    std::string asJSON(bool prettyprint = false) const; + +    // initialize from given tinygltf::Model +    // model - the model to reference +    // mat_index - index of material in model's material array +    void setFromModel(const tinygltf::Model& model, S32 mat_index); + +    // write to given tinygltf::Model +    void writeToModel(tinygltf::Model& model, S32 mat_index) const; + +    virtual void applyOverride(const LLGLTFMaterial& override_mat); +     +    // apply the given LLSD override data +    void applyOverrideLLSD(const LLSD& data); + +    // Get the given override on this LLGLTFMaterial as LLSD +    // override_mat -- the override source data +    // data -- output LLSD object (should be passed in empty) +    void getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data); + +    // For base materials only (i.e. assets). Clears transforms to +    // default since they're not supported in assets yet. +    void sanitizeAssetMaterial(); + +    // For material overrides only. Clears most properties to +    // default/fallthrough, but preserves the transforms. +    bool setBaseMaterial(); +    void setBaseMaterial(const LLGLTFMaterial& old_override_mat); +    // True if setBaseMaterial() was just called +    bool isClearedForBaseMaterial() const; + +    // For local materials, they have to keep track of where +    // they are assigned to for full updates +    virtual void addTextureEntry(LLTextureEntry* te) {}; +    virtual void removeTextureEntry(LLTextureEntry* te) {}; + +    // For local textures so that editor will know to track changes +    void addLocalTextureTracking(const LLUUID& tracking_id, const LLUUID &tex_id); +    void removeLocalTextureTracking(const LLUUID& tracking_id); +    bool hasLocalTextures() { return !mTrackingIdToLocalTexture.empty(); } +    virtual bool replaceLocalTexture(const LLUUID& tracking_id, const LLUUID &old_id, const LLUUID& new_id); +    virtual void updateTextureTracking(); + +    // These fields are local to viewer and are a part of local bitmap support +    typedef std::map<LLUUID, LLUUID> local_tex_map_t; +    local_tex_map_t mTrackingIdToLocalTexture; + +protected: +    static LLVector2 vec2FromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const LLVector2& default_value); +    static F32 floatFromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const F32 default_value); + +    template<typename T> +    static void allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri); + +    template<typename T> +    void setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id); +    template<typename T> +    static void setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform); + +    template<typename T> +    void writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write = false) const; +    template<typename T> +    static void writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write = false); +}; diff --git a/indra/llprimitive/llgltfmaterial_templates.h b/indra/llprimitive/llgltfmaterial_templates.h new file mode 100644 index 0000000000..f607dfe967 --- /dev/null +++ b/indra/llprimitive/llgltfmaterial_templates.h @@ -0,0 +1,142 @@ +/** + * @file llgltfmaterial_templates.h + * @brief Material template definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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$ + */ + +#pragma once + +#include "llgltfmaterial.h" + +// Use templates here as workaround for the different similar texture info classes in tinygltf +// Includer must first include tiny_gltf.h with the desired flags + +template<typename T> +std::string gltf_get_texture_image(const tinygltf::Model& model, const T& texture_info) +{ +    const S32 texture_idx = texture_info.index; +    if (texture_idx < 0 || texture_idx >= model.textures.size()) +    { +        return ""; +    } +    const tinygltf::Texture& texture = model.textures[texture_idx]; + +    // Ignore texture.sampler for now + +    const S32 image_idx = texture.source; +    if (image_idx < 0 || image_idx >= model.images.size()) +    { +        return ""; +    } +    const tinygltf::Image& image = model.images[image_idx]; + +    return image.uri; +} + +template<typename T> +void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id) +{ +    setFromTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id]); +    const std::string uri = gltf_get_texture_image(model, texture_info); +} + +// static +template<typename T> +void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform) +{ +    LL_PROFILE_ZONE_SCOPED; +    const std::string uri = gltf_get_texture_image(model, texture_info); +    texture_id.set(uri); + +    const tinygltf::Value::Object& extensions_object = texture_info.extensions; +    const auto transform_it = extensions_object.find(GLTF_FILE_EXTENSION_TRANSFORM); +    if (transform_it != extensions_object.end()) +    { +        const tinygltf::Value& transform_json = std::get<1>(*transform_it); +        if (transform_json.IsObject()) +        { +            const tinygltf::Value::Object& transform_object = transform_json.Get<tinygltf::Value::Object>(); +            transform.mOffset = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_OFFSET, getDefaultTextureOffset()); +            transform.mScale = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_SCALE, getDefaultTextureScale()); +            transform.mRotation = floatFromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_ROTATION, getDefaultTextureRotation()); +        } +    } +} + +// static +template<typename T> +void LLGLTFMaterial::allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri) +{ +    const S32 image_idx = model.images.size(); +    model.images.emplace_back(); +    model.images[image_idx].uri = uri; + +    // The texture, not to be confused with the texture info +    const S32 texture_idx = model.textures.size(); +    model.textures.emplace_back(); +    tinygltf::Texture& texture = model.textures[texture_idx]; +    texture.source = image_idx; + +    texture_info.index = texture_idx; +} + +// static +template<typename T> +void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write) const +{ +    writeToTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id], force_write); +} + +// static +template<typename T> +void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write) +{ +    LL_PROFILE_ZONE_SCOPED; +    const bool is_blank_transform = transform == sDefault.mTextureTransform[0]; +    // Check if this material matches all the fallback values, and if so, then +    // skip including it to reduce material size +    if (!force_write && texture_id.isNull() && is_blank_transform) +    { +        return; +    } + +    // tinygltf will discard this texture info if there is no valid texture, +    // causing potential loss of information for overrides, so ensure one is +    // defined. -Cosmic,2023-01-30 +    allocateTextureImage(model, texture_info, texture_id.asString()); + +    if (!is_blank_transform) +    { +        tinygltf::Value::Object transform_map; +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_OFFSET] = tinygltf::Value(tinygltf::Value::Array({ +            tinygltf::Value(transform.mOffset.mV[VX]), +            tinygltf::Value(transform.mOffset.mV[VY]) +        })); +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_SCALE] = tinygltf::Value(tinygltf::Value::Array({ +            tinygltf::Value(transform.mScale.mV[VX]), +            tinygltf::Value(transform.mScale.mV[VY]) +        })); +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_ROTATION] = tinygltf::Value(transform.mRotation); +        texture_info.extensions[GLTF_FILE_EXTENSION_TRANSFORM] = tinygltf::Value(transform_map); +    } +} diff --git a/indra/llprimitive/llmaterial.cpp b/indra/llprimitive/llmaterial.cpp index fa22145972..0d146de949 100644 --- a/indra/llprimitive/llmaterial.cpp +++ b/indra/llprimitive/llmaterial.cpp @@ -27,6 +27,7 @@  #include "linden_common.h"  #include "llmaterial.h" +#include "hbxxh.h"  /**   * Materials cap parameters @@ -464,4 +465,12 @@ U32 LLMaterial::getShaderMask(U32 alpha_mode, BOOL is_alpha)      return ret;  } +LLUUID LLMaterial::getHash() const +{ +    LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; +    // HACK - hash the bytes of this LLMaterial, but trim off the S32 in LLRefCount +    LLUUID id; +    HBXXH128::digest(id, (unsigned char*)this + sizeof(LLRefCount), sizeof(*this) - sizeof(LLRefCount)); +    return id; +} diff --git a/indra/llprimitive/llmaterial.h b/indra/llprimitive/llmaterial.h index d92ef1dfba..81f41ddc51 100644 --- a/indra/llprimitive/llmaterial.h +++ b/indra/llprimitive/llmaterial.h @@ -27,8 +27,6 @@  #ifndef LL_LLMATERIAL_H  #define LL_LLMATERIAL_H -#include <boost/shared_ptr.hpp> -  #include "llmaterialid.h"  #include "llsd.h"  #include "v4coloru.h" @@ -54,8 +52,6 @@ public:          ALPHA_SHADER_COUNT = 4      } eShaderCount; -     -          static const U8         DEFAULT_SPECULAR_LIGHT_EXPONENT = ((U8)(0.2f * 255));      static const LLColor4U  DEFAULT_SPECULAR_LIGHT_COLOR;      static const U8         DEFAULT_ENV_INTENSITY = 0; @@ -127,6 +123,7 @@ public:      bool        operator != (const LLMaterial& rhs) const;      U32         getShaderMask(U32 alpha_mode, BOOL is_alpha); +    LLUUID      getHash() const;  protected:      LLUUID      mNormalID; diff --git a/indra/llprimitive/llmaterialid.cpp b/indra/llprimitive/llmaterialid.cpp index f88a607c4f..340a83801c 100644 --- a/indra/llprimitive/llmaterialid.cpp +++ b/indra/llprimitive/llmaterialid.cpp @@ -155,6 +155,13 @@ std::string LLMaterialID::asString() const  	return materialIDString;  } +LLUUID LLMaterialID::asUUID() const +{ +    LLUUID ret; +    memcpy(ret.mData, mID, MATERIAL_ID_SIZE); +    return ret; +} +  std::ostream& operator<<(std::ostream& s, const LLMaterialID &material_id)  {  	s << material_id.asString(); diff --git a/indra/llprimitive/llmaterialid.h b/indra/llprimitive/llmaterialid.h index c66e3e30a3..5eb463b0fd 100644 --- a/indra/llprimitive/llmaterialid.h +++ b/indra/llprimitive/llmaterialid.h @@ -61,6 +61,7 @@ public:  	LLSD          asLLSD() const;  	std::string   asString() const; +    LLUUID        asUUID() const;  	friend std::ostream& operator<<(std::ostream& s, const LLMaterialID &material_id); diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index fbd97b3de7..ee493968de 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -53,7 +53,6 @@ const int MODEL_NAMES_LENGTH = sizeof(model_names) / sizeof(std::string);  LLModel::LLModel(LLVolumeParams& params, F32 detail)  	: LLVolume(params, detail),         mNormalizedScale(1,1,1),  -      mNormalizedTranslation(0,0,0),         mPelvisOffset( 0.0f ),         mStatus(NO_ERRORS),         mSubmodelID(0) @@ -296,6 +295,7 @@ void LLModel::normalizeVolumeFaces()  			// the positions to fit within the unit cube.  			LLVector4a* pos = (LLVector4a*) face.mPositions;  			LLVector4a* norm = (LLVector4a*) face.mNormals; +            LLVector4a* t = (LLVector4a*)face.mTangents;  			for (U32 j = 0; j < face.mNumVertices; ++j)  			{ @@ -306,6 +306,14 @@ void LLModel::normalizeVolumeFaces()  					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; +                }  			}  		} @@ -319,6 +327,12 @@ void LLModel::normalizeVolumeFaces()  		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; +        }  	}  } @@ -728,10 +742,12 @@ LLSD LLModel::writeModel(  				LLSD::Binary verts(face.mNumVertices*3*2);  				LLSD::Binary tc(face.mNumVertices*2*2);  				LLSD::Binary normals(face.mNumVertices*3*2); +                LLSD::Binary tangents(face.mNumVertices * 4 * 2);  				LLSD::Binary indices(face.mNumIndices*2);  				U32 vert_idx = 0;  				U32 norm_idx = 0; +                //U32 tan_idx = 0;  				U32 tc_idx = 0;  				LLVector2* ftc = (LLVector2*) face.mTexCoords; @@ -784,6 +800,24 @@ LLSD LLModel::writeModel(  							normals[norm_idx++] = buff[1];  						}  					} + +#if 0 // keep this code for now in case we want to support transporting tangents with mesh assets +                    if (face.mTangents) +                    { //normals +                        F32* tangent = face.mTangents[j].getF32ptr(); + +                        for (U32 k = 0; k < 4; ++k) +                        { //for each component +                            //convert to 16-bit normalized +                            U16 val = (U16)((tangent[k] + 1.f) * 0.5f * 65535); +                            U8* buff = (U8*)&val; + +                            //write to binary buffer +                            tangents[tan_idx++] = buff[0]; +                            tangents[tan_idx++] = buff[1]; +                        } +                    } +#endif  					//texcoord  					if (face.mTexCoords) @@ -814,6 +848,8 @@ LLSD LLModel::writeModel(  				//write out face data  				mdl[model_names[idx]][i]["PositionDomain"]["Min"] = min_pos.getValue();  				mdl[model_names[idx]][i]["PositionDomain"]["Max"] = max_pos.getValue(); +                mdl[model_names[idx]][i]["NormalizedScale"] = face.mNormalizedScale.getValue(); +  				mdl[model_names[idx]][i]["Position"] = verts;  				if (face.mNormals) @@ -821,6 +857,13 @@ LLSD LLModel::writeModel(  					mdl[model_names[idx]][i]["Normal"] = normals;  				} +#if 0 // keep this code for now in case we decide to transport tangents with mesh assets +                if (face.mTangents) +                { +                    mdl[model_names[idx]][i]["Tangent"] = tangents; +                } +#endif +  				if (face.mTexCoords)  				{  					mdl[model_names[idx]][i]["TexCoord0Domain"]["Min"] = min_tc.getValue(); diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index 3dd1652154..fc826727e1 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -162,7 +162,8 @@ bool LLModelLoader::getSLMFilename(const std::string& model_filename, std::strin      std::string::size_type i = model_filename.rfind(".");      if (i != std::string::npos)      { -        slm_filename.replace(i, model_filename.size()-1, ".slm"); +        slm_filename.resize(i, '\0'); +        slm_filename.append(".slm");          return true;      }      else @@ -174,7 +175,7 @@ bool LLModelLoader::getSLMFilename(const std::string& model_filename, std::strin  bool LLModelLoader::doLoadModel()  {  	//first, look for a .slm file of the same name that was modified later -	//than the .dae +	//than the specified model file  	if (mTrySLM)  	{ @@ -184,13 +185,13 @@ bool LLModelLoader::doLoadModel()  			llstat slm_status;  			if (LLFile::stat(slm_filename, &slm_status) == 0)  			{ //slm file exists -				llstat dae_status; -				if (LLFile::stat(mFilename, &dae_status) != 0 || -					dae_status.st_mtime < slm_status.st_mtime) +				llstat model_file_status; +				if (LLFile::stat(mFilename, &model_file_status) != 0 || +					model_file_status.st_mtime < slm_status.st_mtime)  				{  					if (loadFromSLM(slm_filename))  					{ //slm successfully loaded, if this fails, fall through and -						//try loading from dae +						//try loading from model file  						mLod = -1; //successfully loading from an slm implicitly sets all   									//LoDs diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index bfc9bc0861..91b9e4f84e 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -39,6 +39,7 @@  #include "llsdutil_math.h"  #include "llprimtexturelist.h"  #include "llmaterialid.h" +#include "llsdutil.h"  /**   * exported constants @@ -80,6 +81,16 @@ const F32 LIGHT_MIN_CUTOFF = 0.0f;  const F32 LIGHT_DEFAULT_CUTOFF = 0.0f;  const F32 LIGHT_MAX_CUTOFF = 180.f; +// reflection probes +const F32 REFLECTION_PROBE_MIN_AMBIANCE = 0.f; +const F32 REFLECTION_PROBE_MAX_AMBIANCE = 100.f; +const F32 REFLECTION_PROBE_DEFAULT_AMBIANCE = 0.f; +// *NOTE: Clip distances are clamped in LLCamera::setNear. The max clip +// distance is currently limited by the skybox +const F32 REFLECTION_PROBE_MIN_CLIP_DISTANCE = 0.f; +const F32 REFLECTION_PROBE_MAX_CLIP_DISTANCE = 1024.f; +const F32 REFLECTION_PROBE_DEFAULT_CLIP_DISTANCE = 0.f; +  // "Tension" => [0,10], increments of 0.1  const F32 FLEXIBLE_OBJECT_MIN_TENSION = 0.0f;  const F32 FLEXIBLE_OBJECT_DEFAULT_TENSION = 1.0f; @@ -1690,6 +1701,10 @@ BOOL LLNetworkData::isValid(U16 param_type, U32 size)  		return (size == 28);      case PARAMS_EXTENDED_MESH:          return (size == 4); +    case PARAMS_RENDER_MATERIAL: +        return (size > 1); +    case PARAMS_REFLECTION_PROBE: +        return (size == 9);  	}  	return FALSE; @@ -1808,6 +1823,118 @@ bool LLLightParams::fromLLSD(LLSD& sd)  //============================================================================ +//============================================================================ + +LLReflectionProbeParams::LLReflectionProbeParams() +{ +    mType = PARAMS_REFLECTION_PROBE; +} + +BOOL LLReflectionProbeParams::pack(LLDataPacker &dp) const +{ +	dp.packF32(mAmbiance, "ambiance"); +    dp.packF32(mClipDistance, "clip_distance"); +    dp.packU8(mFlags, "flags"); +	return TRUE; +} + +BOOL LLReflectionProbeParams::unpack(LLDataPacker &dp) +{ +	F32 ambiance; +    F32 clip_distance; + +	dp.unpackF32(ambiance, "ambiance"); +	setAmbiance(ambiance); +	 +    dp.unpackF32(clip_distance, "clip_distance"); +	setClipDistance(clip_distance); +	 +    dp.unpackU8(mFlags, "flags"); +	 +	return TRUE; +} + +bool LLReflectionProbeParams::operator==(const LLNetworkData& data) const +{ +	if (data.mType != PARAMS_REFLECTION_PROBE) +	{ +		return false; +	} +	const LLReflectionProbeParams *param = (const LLReflectionProbeParams*)&data; +	if (param->mAmbiance != mAmbiance) +	{ +		return false; +	} +    if (param->mClipDistance != mClipDistance) +    { +        return false; +    } +    if (param->mFlags != mFlags) +    { +        return false; +    } +	return true; +} + +void LLReflectionProbeParams::copy(const LLNetworkData& data) +{ +	const LLReflectionProbeParams *param = (LLReflectionProbeParams*)&data; +	mType = param->mType; +	mAmbiance = param->mAmbiance; +    mClipDistance = param->mClipDistance; +    mFlags = param->mFlags; +} + +LLSD LLReflectionProbeParams::asLLSD() const +{ +	LLSD sd; +	sd["ambiance"] = getAmbiance(); +    sd["clip_distance"] = getClipDistance(); +    sd["flags"] = mFlags; +	return sd; +} + +bool LLReflectionProbeParams::fromLLSD(LLSD& sd) +{ +    if (!sd.has("ambiance") || +        !sd.has("clip_distance") || +        !sd.has("flags")) +    { +        return false; +    } + +	setAmbiance((F32)sd["ambiance"].asReal()); +    setClipDistance((F32)sd["clip_distance"].asReal()); +    mFlags = (U8) sd["flags"].asInteger(); +	 +    return true; +} + +void LLReflectionProbeParams::setIsBox(bool is_box) +{ +    if (is_box) +    { +        mFlags |= FLAG_BOX_VOLUME; +    } +    else +    { +        mFlags &= ~FLAG_BOX_VOLUME; +    } +} + +void LLReflectionProbeParams::setIsDynamic(bool is_dynamic) +{ +    if (is_dynamic) +    { +        mFlags |= FLAG_DYNAMIC; +    } +    else +    { +        mFlags &= ~FLAG_DYNAMIC; +    } +} + +//============================================================================  LLFlexibleObjectData::LLFlexibleObjectData()  {  	mSimulateLOD				= FLEXIBLE_OBJECT_DEFAULT_NUM_SECTIONS; @@ -2181,3 +2308,106 @@ bool LLExtendedMeshParams::fromLLSD(LLSD& sd)  	return false;  } + +//============================================================================ + +LLRenderMaterialParams::LLRenderMaterialParams() +{ +    mType = PARAMS_RENDER_MATERIAL; +} + +BOOL LLRenderMaterialParams::pack(LLDataPacker& dp) const +{ +    U8 count = (U8)llmin((S32)mEntries.size(), 14); //limited to 255 bytes, no more than 14 material ids + +    dp.packU8(count, "count"); +    for (auto& entry : mEntries) +    { +        dp.packU8(entry.te_idx, "te_idx"); +        dp.packUUID(entry.id, "id"); +    } + +    return TRUE; +} + +BOOL LLRenderMaterialParams::unpack(LLDataPacker& dp) +{ +    U8 count; +    dp.unpackU8(count, "count"); +    mEntries.resize(count); +    for (auto& entry : mEntries) +    { +        dp.unpackU8(entry.te_idx, "te_idx"); +        dp.unpackUUID(entry.id, "te_id"); +    } + +    return TRUE; +} + +bool LLRenderMaterialParams::operator==(const LLNetworkData& data) const +{ +    if (data.mType != PARAMS_RENDER_MATERIAL) +    { +        return false; +    } + +    const LLRenderMaterialParams& param = static_cast<const LLRenderMaterialParams&>(data); + +    if (param.mEntries.size() != mEntries.size()) +    { +        return false; +    } + +    for (auto& entry : mEntries) +    { +        if (param.getMaterial(entry.te_idx) != entry.id) +        { +            return false; +        } +    } + +    return true; +} + +void LLRenderMaterialParams::copy(const LLNetworkData& data) +{ +    llassert_always(data.mType == PARAMS_RENDER_MATERIAL); +    const LLRenderMaterialParams& param = static_cast<const LLRenderMaterialParams&>(data); +    mEntries = param.mEntries; +} + + +void LLRenderMaterialParams::setMaterial(U8 te, const LLUUID& id) +{ +    for (int i = 0; i < mEntries.size(); ++i) +    { +        if (mEntries[i].te_idx == te) +        { +            if (id.isNull()) +            { +                mEntries.erase(mEntries.begin() + i); +            } +            else +            { +                mEntries[i].id = id; +            } +            return; +        } +    } + +    mEntries.push_back({ te, id }); +} + +const LLUUID& LLRenderMaterialParams::getMaterial(U8 te) const +{ +    for (int i = 0; i < mEntries.size(); ++i) +    { +        if (mEntries[i].te_idx == te) +        { +            return mEntries[i].id; +        } +    } + +    return LLUUID::null; +} + diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h index ba76fe1ac5..0d287bc7ca 100644 --- a/indra/llprimitive/llprimitive.h +++ b/indra/llprimitive/llprimitive.h @@ -107,6 +107,8 @@ public:  		PARAMS_RESERVED = 0x50, // Used on server-side  		PARAMS_MESH     = 0x60,          PARAMS_EXTENDED_MESH = 0x70, +        PARAMS_RENDER_MATERIAL = 0x80, +        PARAMS_REFLECTION_PROBE = 0x90,  	};  public: @@ -170,6 +172,50 @@ public:  	F32 getCutoff() const					{ return mCutoff; }  }; +extern const F32 REFLECTION_PROBE_MIN_AMBIANCE; +extern const F32 REFLECTION_PROBE_MAX_AMBIANCE; +extern const F32 REFLECTION_PROBE_DEFAULT_AMBIANCE; +extern const F32 REFLECTION_PROBE_MIN_CLIP_DISTANCE; +extern const F32 REFLECTION_PROBE_MAX_CLIP_DISTANCE; +extern const F32 REFLECTION_PROBE_DEFAULT_CLIP_DISTANCE; + +class LLReflectionProbeParams : public LLNetworkData +{ +public: +    enum EFlags : U8 +    { +        FLAG_BOX_VOLUME     = 0x01, // use a box influence volume +        FLAG_DYNAMIC        = 0x02, // render dynamic objects (avatars) into this Reflection Probe +    }; + +protected: +    F32 mAmbiance = REFLECTION_PROBE_DEFAULT_AMBIANCE; +    F32 mClipDistance = REFLECTION_PROBE_DEFAULT_CLIP_DISTANCE; +    U8 mFlags = 0; + +public: +    LLReflectionProbeParams(); +    /*virtual*/ BOOL pack(LLDataPacker& dp) const; +    /*virtual*/ BOOL unpack(LLDataPacker& dp); +    /*virtual*/ bool operator==(const LLNetworkData& data) const; +    /*virtual*/ void copy(const LLNetworkData& data); +    // LLSD implementations here are provided by Eddy Stryker. +    // NOTE: there are currently unused in protocols +    LLSD asLLSD() const; +    operator LLSD() const { return asLLSD(); } +    bool fromLLSD(LLSD& sd); + +    void setAmbiance(F32 ambiance) { mAmbiance = llclamp(ambiance, REFLECTION_PROBE_MIN_AMBIANCE, REFLECTION_PROBE_MAX_AMBIANCE); } +    void setClipDistance(F32 distance) { mClipDistance = llclamp(distance, REFLECTION_PROBE_MIN_CLIP_DISTANCE, REFLECTION_PROBE_MAX_CLIP_DISTANCE); } +    void setIsBox(bool is_box); +    void setIsDynamic(bool is_dynamic); + +    F32 getAmbiance() const { return mAmbiance; } +    F32 getClipDistance() const { return mClipDistance; } +    bool getIsBox() const { return (mFlags & FLAG_BOX_VOLUME) != 0; } +    bool getIsDynamic() const { return (mFlags & FLAG_DYNAMIC) != 0; } +}; +  //-------------------------------------------------  // This structure is also used in the part of the   // code that creates new flexible objects. @@ -320,6 +366,30 @@ public:  }; +class LLRenderMaterialParams : public LLNetworkData +{ +private: +    struct Entry +    { +        U8 te_idx; +        LLUUID id; +    }; +    std::vector< Entry > mEntries; + +public: +    LLRenderMaterialParams(); +    BOOL pack(LLDataPacker& dp) const override; +    BOOL unpack(LLDataPacker& dp) override; +    bool operator==(const LLNetworkData& data) const override; +    void copy(const LLNetworkData& data) override; +     +    void setMaterial(U8 te_idx, const LLUUID& id); +    const LLUUID& getMaterial(U8 te_idx) const; + +    bool isEmpty() { return mEntries.empty(); } +}; + +  // This code is not naming-standards compliant. Leaving it like this for  // now to make the connection to code in  // 	BOOL packTEMessage(LLDataPacker &dp) const; @@ -415,12 +485,12 @@ public:  	virtual S32 setTEMediaFlags(const U8 te, const U8 flags);  	virtual S32 setTEGlow(const U8 te, const F32 glow);  	virtual S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID); -	virtual S32 setTEMaterialParams(const U8 index, const LLMaterialPtr pMaterialParams); +    virtual S32 setTEMaterialParams(const U8 index, const LLMaterialPtr pMaterialParams);  	virtual BOOL setMaterial(const U8 material); // returns TRUE if material changed  	virtual void setTESelected(const U8 te, bool sel);  	LLMaterialPtr getTEMaterialParams(const U8 index); - +      	void copyTEs(const LLPrimitive *primitive);  	S32 packTEField(U8 *cur_ptr, U8 *data_ptr, U8 data_size, U8 last_face_index, EMsgVariableType type) const;  	BOOL packTEMessage(LLMessageSystem *mesgsys) const; @@ -498,6 +568,8 @@ public:  	static LLPCode legacyToPCode(const U8 legacy);  	static U8 pCodeToLegacy(const LLPCode pcode);  	static bool getTESTAxes(const U8 face, U32* s_axis, U32* t_axis); + +    BOOL hasRenderMaterialParams() const;  	inline static BOOL isPrimitive(const LLPCode pcode);  	inline static BOOL isApp(const LLPCode pcode); diff --git a/indra/llprimitive/lltextureentry.cpp b/indra/llprimitive/lltextureentry.cpp index 284dfc15f4..71caff1686 100644 --- a/indra/llprimitive/lltextureentry.cpp +++ b/indra/llprimitive/lltextureentry.cpp @@ -80,22 +80,7 @@ LLTextureEntry::LLTextureEntry(const LLTextureEntry &rhs)    , mSelected(false)    , mMaterialUpdatePending(false)  { -	mID = rhs.mID; -	mScaleS = rhs.mScaleS; -	mScaleT = rhs.mScaleT; -	mOffsetS = rhs.mOffsetS; -	mOffsetT = rhs.mOffsetT; -	mRotation = rhs.mRotation; -	mColor = rhs.mColor; -	mBump = rhs.mBump; -	mMediaFlags = rhs.mMediaFlags; -	mGlow = rhs.mGlow; -	mMaterialID = rhs.mMaterialID; -	mMaterial = rhs.mMaterial; -	if (rhs.mMediaEntry != NULL) { -		// Make a copy -		mMediaEntry = new LLMediaEntry(*rhs.mMediaEntry); -	} +    *this = rhs;  }  LLTextureEntry &LLTextureEntry::operator=(const LLTextureEntry &rhs) @@ -124,6 +109,27 @@ LLTextureEntry &LLTextureEntry::operator=(const LLTextureEntry &rhs)  		else {  			mMediaEntry = NULL;  		} + +        mMaterialID = rhs.mMaterialID; + +        if (mGLTFMaterial) +        { +            mGLTFMaterial->removeTextureEntry(this); +        } +        mGLTFMaterial = rhs.mGLTFMaterial; +        if (mGLTFMaterial) +        { +            mGLTFMaterial->addTextureEntry(this); +        } +         +        if (rhs.mGLTFMaterialOverrides.notNull()) +        { +            mGLTFMaterialOverrides = new LLGLTFMaterial(*rhs.mGLTFMaterialOverrides); +        } +        else +        { +            mGLTFMaterialOverrides = nullptr; +        }  	}  	return *this; @@ -157,6 +163,12 @@ LLTextureEntry::~LLTextureEntry()  		delete mMediaEntry;  		mMediaEntry = NULL;  	} + +    if (mGLTFMaterial) +    { +        mGLTFMaterial->removeTextureEntry(this); +        mGLTFMaterial = NULL; +    }  }  bool LLTextureEntry::operator!=(const LLTextureEntry &rhs) const @@ -200,6 +212,7 @@ LLSD LLTextureEntry::asLLSD() const  void LLTextureEntry::asLLSD(LLSD& sd) const  { +    LL_PROFILE_ZONE_SCOPED;  	sd["imageid"] = mID;  	sd["colors"] = ll_sd_from_color4(mColor);  	sd["scales"] = mScaleS; @@ -218,10 +231,16 @@ void LLTextureEntry::asLLSD(LLSD& sd) const  		sd[TEXTURE_MEDIA_DATA_KEY] = mediaData;  	}  	sd["glow"] = mGlow; + +    if (mGLTFMaterialOverrides.notNull()) +    { +        sd["gltf_override"] = mGLTFMaterialOverrides->asJSON(); +    }  }  bool LLTextureEntry::fromLLSD(const LLSD& sd)  { +    LL_PROFILE_ZONE_SCOPED;  	const char *w, *x;  	w = "imageid";  	if (sd.has(w)) @@ -282,6 +301,24 @@ bool LLTextureEntry::fromLLSD(const LLSD& sd)  		setGlow((F32)sd[w].asReal() );  	} +    w = "gltf_override"; +    if (sd.has(w)) +    { +        if (mGLTFMaterialOverrides.isNull()) +        { +            mGLTFMaterialOverrides = new LLGLTFMaterial(); +        } + +        std::string warn_msg, error_msg; +        if (!mGLTFMaterialOverrides->fromJSON(sd[w].asString(), warn_msg, error_msg)) +        { +            LL_WARNS() << llformat("Failed to parse GLTF json: %s -- %s", warn_msg.c_str(), error_msg.c_str()) << LL_ENDL; +            LL_WARNS() << sd[w].asString() << LL_ENDL; + +            mGLTFMaterialOverrides = nullptr; +        } +    } +  	return true;  fail:  	return false; @@ -491,6 +528,92 @@ S32 LLTextureEntry::setBumpShiny(U8 bump_shiny)  	return TEM_CHANGE_NONE;  } +void LLTextureEntry::setGLTFMaterial(LLGLTFMaterial* material, bool local_origin) +{  +    if (material != getGLTFMaterial()) +    { +        // assert on precondtion: +        // whether or not mGLTFMaterial is null, any existing override should have been cleared +        // before calling setGLTFMaterial +        // NOTE: if you're hitting this assert, try to make sure calling code is using LLViewerObject::setRenderMaterialID +        //llassert(!local_origin || getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); + +        if (mGLTFMaterial) +        { +            // Local materials have to keep track +            // due to update mechanics +            mGLTFMaterial->removeTextureEntry(this); +        } + +        mGLTFMaterial = material; + +        if (mGLTFMaterial) +        { +            mGLTFMaterial->addTextureEntry(this); +        } + +        if (mGLTFMaterial == nullptr) +        { +            setGLTFRenderMaterial(nullptr); +        } +    } +} + +S32 LLTextureEntry::setGLTFMaterialOverride(LLGLTFMaterial* mat) +{  +    llassert(mat == nullptr || getGLTFMaterial() != nullptr); // if override is not null, base material must not be null +    if (mat == mGLTFMaterialOverrides) +    { +        return TEM_CHANGE_NONE; +    } + +    mGLTFMaterialOverrides = mat; + +    return TEM_CHANGE_TEXTURE; +} + +S32 LLTextureEntry::setBaseMaterial() +{ +    S32 changed = TEM_CHANGE_NONE; + +    if (mGLTFMaterialOverrides) +    { +        if (mGLTFMaterialOverrides->setBaseMaterial()) +        { +            changed = TEM_CHANGE_TEXTURE; +        } + +        if (LLGLTFMaterial::sDefault == *mGLTFMaterialOverrides) +        { +            mGLTFMaterialOverrides = nullptr; +            changed = TEM_CHANGE_TEXTURE; +        } +    } + +    return changed; +} + +LLGLTFMaterial* LLTextureEntry::getGLTFRenderMaterial() const +{  +    if (mGLTFRenderMaterial.notNull()) +    { +        return mGLTFRenderMaterial; +    } +     +    llassert(getGLTFMaterialOverride() == nullptr || getGLTFMaterialOverride()->isClearedForBaseMaterial()); +    return getGLTFMaterial(); +} + +S32 LLTextureEntry::setGLTFRenderMaterial(LLGLTFMaterial* mat) +{ +    if (mGLTFRenderMaterial != mat) +    { +        mGLTFRenderMaterial = mat; +        return TEM_CHANGE_TEXTURE; +    } +    return TEM_CHANGE_NONE; +} +  S32 LLTextureEntry::setMediaFlags(U8 media_flags)  {  	media_flags &= TEM_MEDIA_MASK; diff --git a/indra/llprimitive/lltextureentry.h b/indra/llprimitive/lltextureentry.h index dc2e201044..f5f2c0172d 100644 --- a/indra/llprimitive/lltextureentry.h +++ b/indra/llprimitive/lltextureentry.h @@ -32,6 +32,7 @@  #include "llsd.h"  #include "llmaterialid.h"  #include "llmaterial.h" +#include "llgltfmaterial.h"  // These bits are used while unpacking TEM messages to tell which aspects of  // the texture entry changed. @@ -193,6 +194,23 @@ public:  	// Media flags  	enum { MF_NONE = 0x0, MF_HAS_MEDIA = 0x1 }; +    // GLTF asset +    void setGLTFMaterial(LLGLTFMaterial* material, bool local_origin = true); +    LLGLTFMaterial* getGLTFMaterial() const { return mGLTFMaterial; } + +    // GLTF override +    LLGLTFMaterial* getGLTFMaterialOverride() const { return mGLTFMaterialOverrides; } +    S32 setGLTFMaterialOverride(LLGLTFMaterial* mat); +    // Clear most overrides so the render material better matches the material +    // ID (preserve transforms). If the overrides become passthrough, set the +    // overrides to nullptr. +    S32 setBaseMaterial(); + +    // GLTF render material +    // nuanced behavior here -- if there is no render material, fall back to getGLTFMaterial, but ONLY for the getter, not the setter +    LLGLTFMaterial* getGLTFRenderMaterial() const; +    S32 setGLTFRenderMaterial(LLGLTFMaterial* mat); +  public:  	F32                 mScaleS;                // S, T offset  	F32                 mScaleT;                // S, T offset @@ -219,6 +237,17 @@ protected:  	bool                mMaterialUpdatePending;  	LLMaterialID        mMaterialID;  	LLMaterialPtr		mMaterial; +     +    // Reference to GLTF material asset state +    // On the viewer, this should be the same LLGLTFMaterial instance that exists in LLGLTFMaterialList +    LLPointer<LLGLTFMaterial> mGLTFMaterial;   + +    // GLTF material parameter overrides -- the viewer will use this data to override material parameters +    // set by the asset and store the results in mRenderGLTFMaterial +    LLPointer<LLGLTFMaterial> mGLTFMaterialOverrides; + +    // GLTF material to use for rendering -- will always be an LLFetchedGLTFMaterial +    LLPointer<LLGLTFMaterial> mGLTFRenderMaterial;   	// Note the media data is not sent via the same message structure as the rest of the TE  	LLMediaEntry*		mMediaEntry;			// The media data for the face diff --git a/indra/llprimitive/tests/llgltfmaterial_test.cpp b/indra/llprimitive/tests/llgltfmaterial_test.cpp new file mode 100644 index 0000000000..88b6fae3a7 --- /dev/null +++ b/indra/llprimitive/tests/llgltfmaterial_test.cpp @@ -0,0 +1,369 @@ +/**  + * @file llgltfmaterial_test.cpp + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$                                + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "linden_common.h" +#include "lltut.h" + +#include "../llgltfmaterial.h" +#include "lluuid.cpp" + +// 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) + +// Disable reading external images to prevent warnings and speed up the tests. +// We don't need this for the tests, but still need the filesystem +// implementation to be defined in order for llprimitive to link correctly. +#define TINYGLTF_NO_EXTERNAL_IMAGE 1 + +#include "tinygltf/tiny_gltf.h" + +namespace tut +{ +    struct llgltfmaterial +    { +    }; +    typedef test_group<llgltfmaterial> llgltfmaterial_t; +    typedef llgltfmaterial_t::object llgltfmaterial_object_t; +    tut::llgltfmaterial_t tut_llgltfmaterial("llgltfmaterial"); + +    // A positive 32-bit float with a long string representation +    constexpr F32 test_fraction = 1.09045365e-32; +    // A larger positive 32-bit float for values that get zeroed if below a threshold +    constexpr F32 test_fraction_big = 0.109045; + +    void apply_test_material_texture_ids(LLGLTFMaterial& material) +    { +        material.setBaseColorId(LLUUID::generateNewID()); +        material.setNormalId(LLUUID::generateNewID()); +        material.setOcclusionRoughnessMetallicId(LLUUID::generateNewID()); +        material.setEmissiveId(LLUUID::generateNewID()); +    } + +    void apply_test_material_texture_transforms(LLGLTFMaterial& material) +    { +        LLGLTFMaterial::TextureTransform test_transform; +        test_transform.mOffset.mV[VX] = test_fraction; +        test_transform.mOffset.mV[VY] = test_fraction; +        test_transform.mScale.mV[VX] = test_fraction; +        test_transform.mScale.mV[VY] = test_fraction; +        test_transform.mRotation = test_fraction; +        for (LLGLTFMaterial::TextureInfo i = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; i = LLGLTFMaterial::TextureInfo((U32)i + 1)) +        { +            material.setTextureOffset(i, test_transform.mOffset); +            material.setTextureScale(i, test_transform.mScale); +            material.setTextureRotation(i, test_transform.mRotation); +        } +    } + +    void apply_test_material_factors(LLGLTFMaterial& material) +    { +        material.setBaseColorFactor(LLColor4(test_fraction_big, test_fraction_big, test_fraction_big, test_fraction_big)); +        material.setEmissiveColorFactor(LLColor3(test_fraction_big, test_fraction_big, test_fraction_big)); +        material.setMetallicFactor(test_fraction); +        material.setRoughnessFactor(test_fraction); +    } + +    LLGLTFMaterial create_test_material() +    { +        LLGLTFMaterial material; + +        apply_test_material_texture_ids(material); + +        apply_test_material_texture_transforms(material); + +        apply_test_material_factors(material); + +        material.setAlphaCutoff(test_fraction); +        // Because this is the default value, it should append to the extras field to mark it as an override +        material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_OPAQUE); +        // Because this is the default value, it should append to the extras field to mark it as an override +        material.setDoubleSided(false); + +        return material; +    } + +    void ensure_gltf_material_serialize(const std::string& ensure_suffix, const LLGLTFMaterial& material_in) +    { +        const std::string json_in = material_in.asJSON(); +        LLGLTFMaterial material_out; +        std::string warn_msg; +        std::string error_msg; +        bool serialize_success = material_out.fromJSON(json_in, warn_msg, error_msg); +        ensure_equals("LLGLTFMaterial serialization has no warnings: " + ensure_suffix, "", warn_msg); +        ensure_equals("LLGLTFMaterial serialization has no errors: " + ensure_suffix, "", error_msg); +        ensure("LLGLTFMaterial serializes successfully: " + ensure_suffix, serialize_success); +        ensure("LLGLTFMaterial is preserved when deserialized: " + ensure_suffix, material_in == material_out); +        const std::string json_out = material_out.asJSON(); +        ensure_equals("LLGLTFMaterial is preserved when serialized: " + ensure_suffix, json_in, json_out); +    } + +    void ensure_gltf_material_trimmed(const std::string& material_json, const std::string& must_not_contain) +    { +        ensure("LLGLTFMaterial serialization trims property '" + must_not_contain + "'", material_json.find(must_not_contain) == std::string::npos); +    } + +    // Test that GLTF material fields have not changed since these tests were written +    template<> template<> +    void llgltfmaterial_object_t::test<1>() +    { +#if ADDRESS_SIZE != 32 +#if LL_WINDOWS +        // If any fields are added/changed, these tests should be updated (consider also updating ASSET_VERSION in LLGLTFMaterial) +        // This test result will vary between compilers, so only test a single platform +        ensure_equals("fields supported for GLTF (sizeof check)", sizeof(LLGLTFMaterial), 216); +#endif +#endif +        ensure_equals("LLGLTFMaterial texture info count", (U32)LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT, 4); +    } + +    // Test that occlusion and metallicRoughness are the same (They are different for asset validation. See lluploadmaterial.cpp) +    template<> template<> +    void llgltfmaterial_object_t::test<2>() +    { +        ensure_equals("LLGLTFMaterial occlusion does not differ from metallic roughness", LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, LLGLTFMaterial::GLTF_TEXTURE_INFO_OCCLUSION); +    } + +    // Ensure double sided and alpha mode overrides serialize as expected +    template<> template<> +    void llgltfmaterial_object_t::test<3>() +    { +        const bool doubleSideds[] { false, true }; +        const LLGLTFMaterial::AlphaMode alphaModes[] { LLGLTFMaterial::ALPHA_MODE_OPAQUE, LLGLTFMaterial::ALPHA_MODE_BLEND, LLGLTFMaterial::ALPHA_MODE_MASK }; +        const bool forOverrides[] { false, true }; + +        for (bool doubleSided : doubleSideds) +        { +            for (bool forOverride : forOverrides) +            { +                LLGLTFMaterial material; +                material.setDoubleSided(doubleSided, forOverride); +                const bool overrideBit = (doubleSided == false) && forOverride; +                ensure_equals("LLGLTFMaterial: double sided = " + std::to_string(doubleSided) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideDoubleSided, overrideBit); +                ensure_gltf_material_serialize("double sided = " + std::to_string(doubleSided), material); +            } +        } + +        for (LLGLTFMaterial::AlphaMode alphaMode : alphaModes) +        { +            for (bool forOverride : forOverrides) +            { +                LLGLTFMaterial material; +                material.setAlphaMode(alphaMode, forOverride); +                const bool overrideBit = (alphaMode == LLGLTFMaterial::ALPHA_MODE_OPAQUE) && forOverride; +                ensure_equals("LLGLTFMaterial: alpha mode = " + std::to_string(alphaMode) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideAlphaMode, overrideBit); +                ensure_gltf_material_serialize("alpha mode = " + std::to_string(alphaMode), material); +            } +        } +    } + +    // Test that a GLTF material's transform components serialize as expected +    template<> template<> +    void llgltfmaterial_object_t::test<4>() +    { +        LLGLTFMaterial material; +        LLGLTFMaterial::TextureTransform& transform = material.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]; +        transform.mOffset[VX] = 1.f; +        transform.mOffset[VY] = 2.f; +        transform.mScale[VX] = 0.05f; +        transform.mScale[VY] = 100.f; +        transform.mRotation = 1.571f; +        ensure_gltf_material_serialize("material with transform", material); +    } + +    // Test that a GLTF material avoids serializing a material unnecessarily +    template<> template<> +    void llgltfmaterial_object_t::test<5>() +    { +        { +            const LLGLTFMaterial material; +            const std::string material_json = material.asJSON(); +            ensure_gltf_material_trimmed(material_json, "pbrMetallicRoughness"); +            ensure_gltf_material_trimmed(material_json, "normalTexture"); +            ensure_gltf_material_trimmed(material_json, "emissiveTexture"); +            ensure_gltf_material_trimmed(material_json, "occlusionTexture"); +        } + +        { +            LLGLTFMaterial metallic_factor_material; +            metallic_factor_material.setMetallicFactor(0.5); +            const std::string metallic_factor_material_json = metallic_factor_material.asJSON(); +            ensure_gltf_material_trimmed(metallic_factor_material_json, "baseColorTexture"); +            ensure_gltf_material_trimmed(metallic_factor_material_json, "metallicRoughnessTexture"); +        } +    } + +    // Test that a GLTF material preserves values on serialization +    template<> template<> +    void llgltfmaterial_object_t::test<6>() +    { +        { +            const LLGLTFMaterial full_material = create_test_material(); +            ensure_gltf_material_serialize("full material", full_material); +        } + +        { +            LLGLTFMaterial texture_ids_only_material; +            apply_test_material_texture_ids(texture_ids_only_material); +            ensure_gltf_material_serialize("material with texture IDs only", texture_ids_only_material); +        } + +        { +            LLGLTFMaterial texture_transforms_only_material; +            apply_test_material_texture_ids(texture_transforms_only_material); +            ensure_gltf_material_serialize("material with texture transforms only", texture_transforms_only_material); +        } + +        { +            LLGLTFMaterial factors_only_material; +            apply_test_material_factors(factors_only_material); +            ensure_gltf_material_serialize("material with scaling/tint factors only", factors_only_material); +        } +    } + +    // Test that sDefault is a no-op override +    template<> template<> +    void llgltfmaterial_object_t::test<7>() +    { +        const LLGLTFMaterial material_asset = create_test_material(); +        LLGLTFMaterial render_material = material_asset; +        render_material.applyOverride(LLGLTFMaterial::sDefault); +        ensure("LLGLTFMaterial: sDefault is a no-op override", material_asset == render_material); +    } + +    // Test application of transform overrides +    template<> template<> +    void llgltfmaterial_object_t::test<8>() +    { +        LLGLTFMaterial override_material; +        apply_test_material_texture_transforms(override_material); +        LLGLTFMaterial render_material; +        render_material.applyOverride(override_material); +        ensure("LLGLTFMaterial: transform overrides", render_material == override_material); +    } + +    // Test application of flag-based overrides +    template<> template<> +    void llgltfmaterial_object_t::test<9>() +    { +        { +            LLGLTFMaterial override_material; +            override_material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_BLEND, true); +            override_material.setDoubleSided(true, true); + +            LLGLTFMaterial render_material; + +            render_material.applyOverride(override_material); + +            ensure("LLGLTFMaterial: extra overrides with non-default values applied over default", render_material == override_material); +        } +        { +            LLGLTFMaterial override_material; +            override_material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_OPAQUE, true); +            override_material.setDoubleSided(false, true); + +            LLGLTFMaterial render_material; +            override_material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_BLEND, false); +            override_material.setDoubleSided(true, false); + +            render_material.applyOverride(override_material); +            // Not interested in these flags for equality comparison +            override_material.mOverrideDoubleSided = false; +            override_material.mOverrideAlphaMode = false; + +            ensure("LLGLTFMaterial: extra overrides with default values applied over non-default", render_material == override_material); +        } +    } + +    // Test application of texture overrides +    template<> template<> +    void llgltfmaterial_object_t::test<10>() +    { +        const U32 texture_count = 2; +        const LLUUID override_textures[texture_count] = { LLUUID::null, LLUUID::generateNewID() }; +        const LLUUID asset_textures[texture_count] = { LLUUID::generateNewID(), LLUUID::null }; +        for (U32 i = 0; i < texture_count; ++i) +        { +            LLGLTFMaterial override_material; +            const LLUUID& override_texture = override_textures[i]; +            for (LLGLTFMaterial::TextureInfo j = LLGLTFMaterial::TextureInfo(0); j  < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; j = LLGLTFMaterial::TextureInfo(U32(j) + 1)) +            { +                override_material.setTextureId(j, override_texture, true); +            } + +            LLGLTFMaterial render_material; +            const LLUUID& asset_texture = asset_textures[i]; +            for (LLGLTFMaterial::TextureInfo j = LLGLTFMaterial::TextureInfo(0); j  < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; j = LLGLTFMaterial::TextureInfo(U32(j) + 1)) +            { +                render_material.setTextureId(j, asset_texture, false); +            } + +            render_material.applyOverride(override_material); + +            for (LLGLTFMaterial::TextureInfo j = LLGLTFMaterial::TextureInfo(0); j  < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; j = LLGLTFMaterial::TextureInfo(U32(j) + 1)) +            { +                const LLUUID& render_texture = render_material.mTextureId[j]; +                ensure_equals("LLGLTFMaterial: Override texture ID " + override_texture.asString() + " replaces underlying texture ID " + asset_texture.asString(), render_texture, override_texture); +            } +        } +    } + +    // Test non-persistence of default value flags in overrides +    template<> template<> +    void llgltfmaterial_object_t::test<11>() +    { +        const S32 non_default_alpha_modes[] = { LLGLTFMaterial::ALPHA_MODE_BLEND, LLGLTFMaterial::ALPHA_MODE_MASK }; +        for (S32 non_default_alpha_mode : non_default_alpha_modes) +        { +            LLGLTFMaterial material; +            // Set default alpha mode +            material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_OPAQUE, true); +            ensure_equals("LLGLTFMaterial: alpha mode override flag set", material.mOverrideAlphaMode, true); +            // Set non-default alpha mode +            material.setAlphaMode(non_default_alpha_mode, true); +            ensure_equals("LLGLTFMaterial: alpha mode override flag unset", material.mOverrideAlphaMode, false); +        } + +        { +            // Set default double sided +            LLGLTFMaterial material; +            material.setDoubleSided(false, true); +            ensure_equals("LLGLTFMaterial: double sided override flag set", material.mOverrideDoubleSided, true); +            // Set non-default double sided +            material.setDoubleSided(true, true); +            ensure_equals("LLGLTFMaterial: double sided override flag unset", material.mOverrideDoubleSided, false); +        } +    } +} diff --git a/indra/llprimitive/tests/llmessagesystem_stub.cpp b/indra/llprimitive/tests/llmessagesystem_stub.cpp index 04e70945c4..9006833054 100644 --- a/indra/llprimitive/tests/llmessagesystem_stub.cpp +++ b/indra/llprimitive/tests/llmessagesystem_stub.cpp @@ -25,7 +25,7 @@  #include "linden_common.h" -char * _PREHASH_TextureEntry; +const char * const _PREHASH_TextureEntry = "TextureEntry";  S32 LLMessageSystem::getSizeFast(char const*, char const*) const  { diff --git a/indra/llprimitive/tests/llprimitive_test.cpp b/indra/llprimitive/tests/llprimitive_test.cpp index 0d60c7cd15..0ff0795fdc 100644 --- a/indra/llprimitive/tests/llprimitive_test.cpp +++ b/indra/llprimitive/tests/llprimitive_test.cpp @@ -71,6 +71,46 @@ private:  	S32 mCurrDetailTest;  }; +LLMaterialID::LLMaterialID() {} +LLMaterialID::LLMaterialID(LLMaterialID const &m) = default; +LLMaterialID::~LLMaterialID() {} +void LLMaterialID::set(void const*) { } +U8 const * LLMaterialID::get() const { return mID; } + +LLPrimTextureList::LLPrimTextureList() { } +LLPrimTextureList::~LLPrimTextureList() { } +S32 LLPrimTextureList::setBumpMap(const U8 index, const U8 bump) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setOffsetS(const U8 index, const F32 s) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setOffsetT(const U8 index, const F32 t) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::copyTexture(const U8 index, const LLTextureEntry &te) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setRotation(const U8 index, const F32 r) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setBumpShiny(const U8 index, const U8 bump_shiny) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setFullbright(const U8 index, const U8 t) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setMaterialID(const U8 index, const LLMaterialID& pMaterialID) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setMediaFlags(const U8 index, const U8 media_flags) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setMediaTexGen(const U8 index, const U8 media) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setMaterialParams(const U8 index, const LLMaterialPtr pMaterialParams) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setBumpShinyFullbright(const U8 index, const U8 bump) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setID(const U8 index, const LLUUID& id) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setGlow(const U8 index, const F32 glow) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setAlpha(const U8 index, const F32 alpha) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setColor(const U8 index, const LLColor3& color) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setColor(const U8 index, const LLColor4& color) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setScale(const U8 index, const F32 s, const F32 t) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setScaleS(const U8 index, const F32 s) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setScaleT(const U8 index, const F32 t) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setShiny(const U8 index, const U8 shiny) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setOffset(const U8 index, const F32 s, const F32 t) { return TEM_CHANGE_NONE; } +S32 LLPrimTextureList::setTexGen(const U8 index, const U8 texgen) { return TEM_CHANGE_NONE; } + +LLMaterialPtr LLPrimTextureList::getMaterialParams(const U8 index) { return LLMaterialPtr(); } +void LLPrimTextureList::copy(LLPrimTextureList const & ptl) { mEntryList = ptl.mEntryList; } // do we need to call getTexture()->newCopy()? +void LLPrimTextureList::take(LLPrimTextureList &other_list) { } +void LLPrimTextureList::setSize(S32 new_size) { mEntryList.resize(new_size); } +void LLPrimTextureList::setAllIDs(const LLUUID &id) { } +LLTextureEntry * LLPrimTextureList::getTexture(const U8 index) const { return nullptr; } +S32 LLPrimTextureList::size() const { return mEntryList.size(); } +  class PRIMITIVE_TEST_SETUP  {  public: | 
