/** * @file accessor.cpp * @brief LL GLTF Implementation * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "../llviewerprecompiledheaders.h" #include "asset.h" #include "buffer_util.h" #include "llfilesystem.h" using namespace LL::GLTF; using namespace boost::json; namespace LL { namespace GLTF { Accessor::Type gltf_type_to_enum(const std::string& type) { if (type == "SCALAR") { return Accessor::Type::SCALAR; } else if (type == "VEC2") { return Accessor::Type::VEC2; } else if (type == "VEC3") { return Accessor::Type::VEC3; } else if (type == "VEC4") { return Accessor::Type::VEC4; } else if (type == "MAT2") { return Accessor::Type::MAT2; } else if (type == "MAT3") { return Accessor::Type::MAT3; } else if (type == "MAT4") { return Accessor::Type::MAT4; } LL_WARNS("GLTF") << "Unknown accessor type: " << type << LL_ENDL; llassert(false); return Accessor::Type::SCALAR; } std::string enum_to_gltf_type(Accessor::Type type) { switch (type) { case Accessor::Type::SCALAR: return "SCALAR"; case Accessor::Type::VEC2: return "VEC2"; case Accessor::Type::VEC3: return "VEC3"; case Accessor::Type::VEC4: return "VEC4"; case Accessor::Type::MAT2: return "MAT2"; case Accessor::Type::MAT3: return "MAT3"; case Accessor::Type::MAT4: return "MAT4"; } LL_WARNS("GLTF") << "Unknown accessor type: " << (S32)type << LL_ENDL; llassert(false); return "SCALAR"; } } } void Buffer::erase(Asset& asset, S32 offset, S32 length) { S32 idx = (S32)(this - &asset.mBuffers[0]); mData.erase(mData.begin() + offset, mData.begin() + offset + length); llassert(mData.size() <= size_t(INT_MAX)); mByteLength = S32(mData.size()); for (BufferView& view : asset.mBufferViews) { if (view.mBuffer == idx) { if (view.mByteOffset >= offset) { view.mByteOffset -= length; } } } } bool Buffer::prep(Asset& asset) { if (mByteLength == 0) { return false; } LLUUID id; if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) { // loaded from an asset, fetch the buffer data from the asset store LLFileSystem file(id, LLAssetType::AT_GLTF_BIN, LLFileSystem::READ); if (mByteLength > file.getSize()) { LL_WARNS("GLTF") << "Unexpected glbin size: " << id << " is " << file.getSize() << " bytes, expected " << mByteLength << LL_ENDL; return false; } mData.resize(mByteLength); if (!file.read((U8*)mData.data(), mByteLength)) { LL_WARNS("GLTF") << "Failed to load buffer data from asset: " << id << LL_ENDL; return false; } } else if (mUri.find("data:") == 0) { // loaded from a data URI, load the texture from the data LL_WARNS() << "Data URIs not yet supported" << LL_ENDL; return false; } else if (!asset.mFilename.empty() && !mUri.empty()) // <-- uri could be empty if we're loading from .glb { std::string dir = gDirUtilp->getDirName(asset.mFilename); std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri; std::ifstream file(bin_file, std::ios::binary); if (!file.is_open()) { LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL; return false; } file.seekg(0, std::ios::end); if (mByteLength > file.tellg()) { LL_WARNS("GLTF") << "Unexpected file size: " << bin_file << " is " << file.tellg() << " bytes, expected " << mByteLength << LL_ENDL; return false; } file.seekg(0, std::ios::beg); mData.resize(mByteLength); file.read((char*)mData.data(), mData.size()); } // POSTCONDITION: on success, mData.size == mByteLength llassert(mData.size() == mByteLength); return true; } bool Buffer::save(Asset& asset, const std::string& folder) { if (mUri.substr(0, 5) == "data:") { LL_WARNS("GLTF") << "Data URIs not yet supported" << LL_ENDL; return false; } std::string bin_file = folder + gDirUtilp->getDirDelimiter(); if (mUri.empty()) { if (mName.empty()) { S32 idx = (S32)(this - &asset.mBuffers[0]); mUri = llformat("buffer_%d.bin", idx); } else { mUri = mName + ".bin"; } } bin_file += mUri; std::ofstream file(bin_file, std::ios::binary); if (!file.is_open()) { LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL; return false; } file.write((char*)mData.data(), mData.size()); return true; } void Buffer::serialize(object& dst) const { write(mName, "name", dst); write(mUri, "uri", dst); write_always(mByteLength, "byteLength", dst); }; const Buffer& Buffer::operator=(const Value& src) { if (src.is_object()) { copy(src, "name", mName); copy(src, "uri", mUri); copy(src, "byteLength", mByteLength); // NOTE: DO NOT attempt to handle the uri here. // The uri is a reference to a file that is not loaded until // after the json document is parsed } return *this; } void BufferView::serialize(object& dst) const { write_always(mBuffer, "buffer", dst); write_always(mByteLength, "byteLength", dst); write(mByteOffset, "byteOffset", dst, 0); write(mByteStride, "byteStride", dst, 0); write(mTarget, "target", dst, -1); write(mName, "name", dst); } const BufferView& BufferView::operator=(const Value& src) { if (src.is_object()) { copy(src, "buffer", mBuffer); copy(src, "byteLength", mByteLength); copy(src, "byteOffset", mByteOffset); copy(src, "byteStride", mByteStride); copy(src, "target", mTarget); copy(src, "name", mName); } return *this; } void Accessor::serialize(object& dst) const { write(mName, "name", dst); write(mBufferView, "bufferView", dst, INVALID_INDEX); write(mByteOffset, "byteOffset", dst, 0); write_always(mComponentType, "componentType", dst); write_always(mCount, "count", dst); write_always(enum_to_gltf_type(mType), "type", dst); write(mNormalized, "normalized", dst, false); write(mMax, "max", dst); write(mMin, "min", dst); } const Accessor& Accessor::operator=(const Value& src) { if (src.is_object()) { copy(src, "name", mName); copy(src, "bufferView", mBufferView); copy(src, "byteOffset", mByteOffset); copy(src, "componentType", mComponentType); copy(src, "count", mCount); copy(src, "type", mType); copy(src, "normalized", mNormalized); copy(src, "max", mMax); copy(src, "min", mMin); } return *this; }