From db81b925d776103326128bf629cbdda576a223e7 Mon Sep 17 00:00:00 2001 From: sanine Date: Sat, 16 Apr 2022 11:55:09 -0500 Subject: move 3rd-party librarys into libs/ and add built-in honeysuckle --- libs/assimp/code/AssetLib/glTF/glTFAsset.h | 1017 +++++++++++++++ libs/assimp/code/AssetLib/glTF/glTFAsset.inl | 1316 ++++++++++++++++++++ libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h | 96 ++ libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl | 716 +++++++++++ libs/assimp/code/AssetLib/glTF/glTFCommon.cpp | 117 ++ libs/assimp/code/AssetLib/glTF/glTFCommon.h | 520 ++++++++ libs/assimp/code/AssetLib/glTF/glTFExporter.cpp | 1065 ++++++++++++++++ libs/assimp/code/AssetLib/glTF/glTFExporter.h | 118 ++ libs/assimp/code/AssetLib/glTF/glTFImporter.cpp | 725 +++++++++++ libs/assimp/code/AssetLib/glTF/glTFImporter.h | 89 ++ 10 files changed, 5779 insertions(+) create mode 100644 libs/assimp/code/AssetLib/glTF/glTFAsset.h create mode 100644 libs/assimp/code/AssetLib/glTF/glTFAsset.inl create mode 100644 libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h create mode 100644 libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl create mode 100644 libs/assimp/code/AssetLib/glTF/glTFCommon.cpp create mode 100644 libs/assimp/code/AssetLib/glTF/glTFCommon.h create mode 100644 libs/assimp/code/AssetLib/glTF/glTFExporter.cpp create mode 100644 libs/assimp/code/AssetLib/glTF/glTFExporter.h create mode 100644 libs/assimp/code/AssetLib/glTF/glTFImporter.cpp create mode 100644 libs/assimp/code/AssetLib/glTF/glTFImporter.h (limited to 'libs/assimp/code/AssetLib/glTF') diff --git a/libs/assimp/code/AssetLib/glTF/glTFAsset.h b/libs/assimp/code/AssetLib/glTF/glTFAsset.h new file mode 100644 index 0000000..1a42e90 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAsset.h @@ -0,0 +1,1017 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file glTFAsset.h + * Declares a glTF class to handle gltf/glb files + * + * glTF Extensions Support: + * KHR_binary_glTF: full + * KHR_materials_common: full + */ +#ifndef GLTFASSET_H_INC +#define GLTFASSET_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "glTFCommon.h" +#include +#include +#include +#include +#include +#include +#include + +// clang-format off + +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +#include +#include +#include + +#if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) +#pragma GCC diagnostic pop +#endif + +#ifdef ASSIMP_API +# include +# include +# include +#else +# include +# define AI_SWAP4(p) +# define ai_assert +#endif + + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# else +# define gltf_unordered_map map +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define gltf_unordered_map tr1::unordered_map +# else +# define gltf_unordered_map unordered_map +# endif +#endif + +// clang-format on + +#include "AssetLib/glTF/glTFCommon.h" + +namespace glTF { + +using glTFCommon::IOStream; +using glTFCommon::IOSystem; +using glTFCommon::Nullable; +using glTFCommon::Ref; +using glTFCommon::shared_ptr; + +using rapidjson::Document; +using rapidjson::Value; + +class Asset; +class AssetWriter; + +struct BufferView; // here due to cross-reference +struct Texture; +struct Light; +struct Skin; + +using glTFCommon::mat4; +using glTFCommon::vec3; +using glTFCommon::vec4; + +//! Magic number for GLB files +#define AI_GLB_MAGIC_NUMBER "glTF" + +// clang-format off +#ifdef ASSIMP_API +# include +#endif +// clang-format on + +//! For the KHR_binary_glTF extension (binary .glb file) +//! 20-byte header (+ the JSON + a "body" data section) +struct GLB_Header { + uint8_t magic[4]; //!< Magic number: "glTF" + uint32_t version; //!< Version number (always 1 as of the last update) + uint32_t length; //!< Total length of the Binary glTF, including header, scene, and body, in bytes + uint32_t sceneLength; //!< Length, in bytes, of the glTF scene + uint32_t sceneFormat; //!< Specifies the format of the glTF scene (see the SceneFormat enum) +} PACK_STRUCT; + +// clang-format off +#ifdef ASSIMP_API +# include +#endif +// clang-format on + +//! Values for the GLB_Header::sceneFormat field +enum SceneFormat { + SceneFormat_JSON = 0 +}; + +//! Values for the mesh primitive modes +enum PrimitiveMode { + PrimitiveMode_POINTS = 0, + PrimitiveMode_LINES = 1, + PrimitiveMode_LINE_LOOP = 2, + PrimitiveMode_LINE_STRIP = 3, + PrimitiveMode_TRIANGLES = 4, + PrimitiveMode_TRIANGLE_STRIP = 5, + PrimitiveMode_TRIANGLE_FAN = 6 +}; + +//! Values for the Accessor::componentType field +enum ComponentType { + ComponentType_BYTE = 5120, + ComponentType_UNSIGNED_BYTE = 5121, + ComponentType_SHORT = 5122, + ComponentType_UNSIGNED_SHORT = 5123, + ComponentType_UNSIGNED_INT = 5125, + ComponentType_FLOAT = 5126 +}; + +inline unsigned int ComponentTypeSize(ComponentType t) { + switch (t) { + case ComponentType_SHORT: + case ComponentType_UNSIGNED_SHORT: + return 2; + + case ComponentType_UNSIGNED_INT: + case ComponentType_FLOAT: + return 4; + + case ComponentType_BYTE: + case ComponentType_UNSIGNED_BYTE: + return 1; + default: + std::string err = "GLTF: Unsupported Component Type "; + err += std::to_string(t); + throw DeadlyImportError(err); + } +} + +//! Values for the BufferView::target field +enum BufferViewTarget { + BufferViewTarget_NONE = 0, + BufferViewTarget_ARRAY_BUFFER = 34962, + BufferViewTarget_ELEMENT_ARRAY_BUFFER = 34963 +}; + +//! Values for the Sampler::magFilter field +enum SamplerMagFilter { + SamplerMagFilter_Nearest = 9728, + SamplerMagFilter_Linear = 9729 +}; + +//! Values for the Sampler::minFilter field +enum SamplerMinFilter { + SamplerMinFilter_Nearest = 9728, + SamplerMinFilter_Linear = 9729, + SamplerMinFilter_Nearest_Mipmap_Nearest = 9984, + SamplerMinFilter_Linear_Mipmap_Nearest = 9985, + SamplerMinFilter_Nearest_Mipmap_Linear = 9986, + SamplerMinFilter_Linear_Mipmap_Linear = 9987 +}; + +//! Values for the Sampler::wrapS and Sampler::wrapT field +enum SamplerWrap { + SamplerWrap_Clamp_To_Edge = 33071, + SamplerWrap_Mirrored_Repeat = 33648, + SamplerWrap_Repeat = 10497 +}; + +//! Values for the Texture::format and Texture::internalFormat fields +enum TextureFormat { + TextureFormat_ALPHA = 6406, + TextureFormat_RGB = 6407, + TextureFormat_RGBA = 6408, + TextureFormat_LUMINANCE = 6409, + TextureFormat_LUMINANCE_ALPHA = 6410 +}; + +//! Values for the Texture::target field +enum TextureTarget { + TextureTarget_TEXTURE_2D = 3553 +}; + +//! Values for the Texture::type field +enum TextureType { + TextureType_UNSIGNED_BYTE = 5121, + TextureType_UNSIGNED_SHORT_5_6_5 = 33635, + TextureType_UNSIGNED_SHORT_4_4_4_4 = 32819, + TextureType_UNSIGNED_SHORT_5_5_5_1 = 32820 +}; + +//! Values for the Accessor::type field (helper class) +class AttribType { +public: + enum Value { SCALAR, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 + }; + + inline static Value FromString(const char *str) { + for (size_t i = 0; i < NUM_VALUES; ++i) { + if (strcmp(data<0>::infos[i].name, str) == 0) { + return static_cast(i); + } + } + return SCALAR; + } + + inline static const char *ToString(Value type) { + return data<0>::infos[static_cast(type)].name; + } + + inline static unsigned int GetNumComponents(Value type) { + return data<0>::infos[static_cast(type)].numComponents; + } + +private: + static const size_t NUM_VALUES = static_cast(MAT4) + 1; + struct Info { + const char *name; + unsigned int numComponents; + }; + + template + struct data { + static const Info infos[NUM_VALUES]; + }; +}; + +// must match the order of the AttribTypeTraits::Value enum! +template +const AttribType::Info AttribType::data::infos[AttribType::NUM_VALUES] = { + { "SCALAR", 1 }, + { "VEC2", 2 }, + { "VEC3", 3 }, + { "VEC4", 4 }, + { "MAT2", 4 }, + { "MAT3", 9 }, + { "MAT4", 16 } +}; + +//! Base class for all glTF top-level objects +struct Object { + std::string id; //!< The globally unique ID used to reference this object + std::string name; //!< The user-defined name of this object + + //! Objects marked as special are not exported (used to emulate the binary body buffer) + virtual bool IsSpecial() const { return false; } + + Object() = default; + virtual ~Object() {} + + //! Maps special IDs to another ID, where needed. Subclasses may override it (statically) + static const char *TranslateId(Asset & /*r*/, const char *id) { return id; } +}; + +// +// Classes for each glTF top-level object type +// + +//! A typed view into a BufferView. A BufferView contains raw binary data. +//! An accessor provides a typed view into a BufferView or a subset of a BufferView +//! similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer. +struct Accessor : public Object { + Ref bufferView; //!< The ID of the bufferView. (required) + unsigned int byteOffset; //!< The offset relative to the start of the bufferView in bytes. (required) + unsigned int byteStride; //!< The stride, in bytes, between attributes referenced by this accessor. (default: 0) + ComponentType componentType; //!< The datatype of components in the attribute. (required) + unsigned int count; //!< The number of attributes referenced by this accessor. (required) + AttribType::Value type; //!< Specifies if the attribute is a scalar, vector, or matrix. (required) + std::vector max; //!< Maximum value of each component in this attribute. + std::vector min; //!< Minimum value of each component in this attribute. + + unsigned int GetNumComponents(); + unsigned int GetBytesPerComponent(); + unsigned int GetElementSize(); + + inline uint8_t *GetPointer(); + + template + bool ExtractData(T *&outData); + + void WriteData(size_t count, const void *src_buffer, size_t src_stride); + + //! Helper class to iterate the data + class Indexer { + friend struct Accessor; + + // This field is reported as not used, making it protectd is the easiest way to work around it without going to the bottom of what the problem is: + // ../code/glTF2/glTF2Asset.h:392:19: error: private field 'accessor' is not used [-Werror,-Wunused-private-field] + protected: + Accessor &accessor; + + private: + uint8_t *data; + size_t elemSize, stride; + + Indexer(Accessor &acc); + + public: + //! Accesses the i-th value as defined by the accessor + template + T GetValue(int i); + + //! Accesses the i-th value as defined by the accessor + inline unsigned int GetUInt(int i) { + return GetValue(i); + } + + inline bool IsValid() const { + return data != 0; + } + }; + + inline Indexer GetIndexer() { + return Indexer(*this); + } + + Accessor() = default; + void Read(Value &obj, Asset &r); +}; + +//! A buffer points to binary geometry, animation, or skins. +struct Buffer : public Object { + /********************* Types *********************/ + enum Type { + Type_arraybuffer, + Type_text + }; + + /// @brief Descriptor of encoded region in "bufferView". + struct SEncodedRegion { + const size_t Offset; ///< Offset from begin of "bufferView" to encoded region, in bytes. + const size_t EncodedData_Length; ///< Size of encoded region, in bytes. + uint8_t *const DecodedData; ///< Cached encoded data. + const size_t DecodedData_Length; ///< Size of decoded region, in bytes. + const std::string ID; ///< ID of the region. + + /// @brief Constructor. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : + Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) {} + + /// Destructor. + ~SEncodedRegion() { delete[] DecodedData; } + }; + + /******************* Variables *******************/ + + size_t byteLength; //!< The length of the buffer in bytes. (default: 0) + + Type type; + + /// \var EncodedRegion_Current + /// Pointer to currently active encoded region. + /// Why not decoding all regions at once and not to set one buffer with decoded data? + /// Yes, why not? Even "accessor" point to decoded data. I mean that fields "byteOffset", "byteStride" and "count" has values which describes decoded + /// data array. But only in range of mesh while is active parameters from "compressedData". For another mesh accessors point to decoded data too. But + /// offset is counted for another regions is encoded. + /// Example. You have two meshes. For every of it you have 4 bytes of data. That data compressed to 2 bytes. So, you have buffer with encoded data: + /// M1_E0, M1_E1, M2_E0, M2_E1. + /// After decoding you'll get: + /// M1_D0, M1_D1, M1_D2, M1_D3, M2_D0, M2_D1, M2_D2, M2_D3. + /// "accessors" must to use values that point to decoded data - obviously. So, you'll expect "accessors" like + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 4, byteLength: 4} + /// but in real life you'll get: + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 2, byteLength: 4} + /// Yes, accessor of next mesh has offset and length which mean: current mesh data is decoded, all other data is encoded. + /// And when before you start to read data of current mesh (with encoded data ofcourse) you must decode region of "bufferView", after read finished + /// delete encoding mark. And after that you can repeat process: decode data of mesh, read, delete decoded data. + /// + /// Remark. Encoding all data at once is good in world with computers which do not has RAM limitation. So, you must use step by step encoding in + /// exporter and importer. And, thanks to such way, there is no need to load whole file into memory. + SEncodedRegion *EncodedRegion_Current; + +private: + shared_ptr mData; //!< Pointer to the data + bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) + size_t capacity = 0; //!< The capacity of the buffer in bytes. (default: 0) + /// \var EncodedRegion_List + /// List of encoded regions. + std::list EncodedRegion_List; + + /******************* Functions *******************/ + +public: + Buffer(); + ~Buffer(); + + void Read(Value &obj, Asset &r); + + bool LoadFromStream(IOStream &stream, size_t length = 0, size_t baseOffset = 0); + + /// Mark region of "bufferView" as encoded. When data is request from such region then "bufferView" use decoded data. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID); + + /// Select current encoded region by ID. \sa EncodedRegion_Current. + /// \param [in] pID - ID of the region. + void EncodedRegion_SetCurrent(const std::string &pID); + + /// Replace part of buffer data. Pay attention that function work with original array of data (\ref mData) not with encoded regions. + /// \param [in] pBufferData_Offset - index of first element in buffer from which new data will be placed. + /// \param [in] pBufferData_Count - count of bytes in buffer which will be replaced. + /// \param [in] pReplace_Data - pointer to array with new data for buffer. + /// \param [in] pReplace_Count - count of bytes in new data. + /// \return true - if successfully replaced, false if input arguments is out of range. + bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count); + + size_t AppendData(uint8_t *data, size_t length); + void Grow(size_t amount); + + uint8_t *GetPointer() { return mData.get(); } + + void MarkAsSpecial() { mIsSpecial = true; } + + bool IsSpecial() const { return mIsSpecial; } + + std::string GetURI() { return std::string(this->id) + ".bin"; } + + static const char *TranslateId(Asset &r, const char *id); +}; + +//! A view into a buffer generally representing a subset of the buffer. +struct BufferView : public Object { + Ref buffer; //! The ID of the buffer. (required) + size_t byteOffset; //! The offset into the buffer in bytes. (required) + size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + + BufferViewTarget target; //! The target that the WebGL buffer should be bound to. + + void Read(Value &obj, Asset &r); +}; + +struct Camera : public Object { + enum Type { + Perspective, + Orthographic + }; + + Type type; + + union { + struct { + float aspectRatio; //! bufferView; + std::string mimeType; + int width, height; + +public: + Image(); + void Read(Value &obj, Asset &r); + inline bool HasData() const { return mDataLength > 0; } + inline size_t GetDataLength() const { return mDataLength; } + inline const uint8_t *GetData() const { return mData.get(); } + inline uint8_t *StealData(); + inline void SetData(uint8_t *data, size_t length, Asset &r); + +private: + std::unique_ptr mData; + size_t mDataLength; +}; + +//! Holds a material property that can be a texture or a color +struct TexProperty { + Ref texture; + vec4 color; +}; + +//! The material appearance of a primitive. +struct Material : public Object { + //Ref source; //!< The ID of the technique. + //std::gltf_unordered_map values; //!< A dictionary object of parameter values. + + //! Techniques defined by KHR_materials_common + enum Technique { + Technique_undefined = 0, + Technique_BLINN, + Technique_PHONG, + Technique_LAMBERT, + Technique_CONSTANT + }; + + TexProperty ambient; + TexProperty diffuse; + TexProperty specular; + TexProperty emission; + + bool doubleSided; + bool transparent; + float transparency; + float shininess; + + Technique technique; + + Material() { SetDefaults(); } + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +//! A set of primitives to be rendered. A node can contain one or more meshes. A node's transform places the mesh in the scene. +struct Mesh : public Object { + typedef std::vector> AccessorList; + + struct Primitive { + PrimitiveMode mode; + + struct Attributes { + AccessorList position, normal, texcoord, color, joint, jointmatrix, weight; + } attributes; + + Ref indices; + + Ref material; + }; + + /// \struct SExtension + /// Extension used for mesh. + struct SExtension { + /// \enum EType + /// Type of extension. + enum EType { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + Compression_Open3DGC, ///< Compression of mesh data using Open3DGC algorithm. +#endif + + Unknown + }; + + EType Type; ///< Type of extension. + + /// \fn SExtension + /// Constructor. + /// \param [in] pType - type of extension. + SExtension(const EType pType) : + Type(pType) {} + + virtual ~SExtension() { + // empty + } + }; + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + + /// \struct SCompression_Open3DGC + /// Compression of mesh data using Open3DGC algorithm. + struct SCompression_Open3DGC : public SExtension { + using SExtension::Type; + + std::string Buffer; ///< ID of "buffer" used for storing compressed data. + size_t Offset; ///< Offset in "bufferView" where compressed data are stored. + size_t Count; ///< Count of elements in compressed data. Is always equivalent to size in bytes: look comments for "Type" and "Component_Type". + bool Binary; ///< If true then "binary" mode is used for coding, if false - "ascii" mode. + size_t IndicesCount; ///< Count of indices in mesh. + size_t VerticesCount; ///< Count of vertices in mesh. + // AttribType::Value Type;///< Is always "SCALAR". + // ComponentType Component_Type;///< Is always "ComponentType_UNSIGNED_BYTE" (5121). + + /// \fn SCompression_Open3DGC + /// Constructor. + SCompression_Open3DGC() : + SExtension(Compression_Open3DGC) { + // empty + } + + virtual ~SCompression_Open3DGC() { + // empty + } + }; +#endif + + std::vector primitives; + std::list Extension; ///< List of extensions used in mesh. + + Mesh() {} + + /// Destructor. + ~Mesh() { + for (std::list::iterator it = Extension.begin(), it_end = Extension.end(); it != it_end; it++) { + delete *it; + }; + } + + /// @brief Get mesh data from JSON-object and place them to root asset. + /// \param [in] pJSON_Object - reference to pJSON-object from which data are read. + /// \param [out] pAsset_Root - reference to root asset where data will be stored. + void Read(Value &pJSON_Object, Asset &pAsset_Root); + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + /// @brief Decode part of "buffer" which encoded with Open3DGC algorithm. + /// \param [in] pCompression_Open3DGC - reference to structure which describe encoded region. + /// \param [out] pAsset_Root - reference to root assed where data will be stored. + void Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root); +#endif +}; + +struct Node : public Object { + std::vector> children; + std::vector> meshes; + + Nullable matrix; + Nullable translation; + Nullable rotation; + Nullable scale; + + Ref camera; + Ref light; + + std::vector> skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy. + Ref skin; //!< The ID of the skin referenced by this node. + std::string jointName; //!< Name used when this node is a joint in a skin. + + Ref parent; //!< This is not part of the glTF specification. Used as a helper. + + Node() {} + void Read(Value &obj, Asset &r); +}; + +struct Program : public Object { + Program() {} + void Read(Value &obj, Asset &r); +}; + +struct Sampler : public Object { + SamplerMagFilter magFilter; //!< The texture magnification filter. (required) + SamplerMinFilter minFilter; //!< The texture minification filter. (required) + SamplerWrap wrapS; //!< The texture wrapping in the S direction. (required) + SamplerWrap wrapT; //!< The texture wrapping in the T direction. (required) + + Sampler() = default; + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +struct Scene : public Object { + std::vector> nodes; + + Scene() = default; + void Read(Value &obj, Asset &r); +}; + +struct Shader : public Object { + Shader() = default; + void Read(Value &obj, Asset &r); +}; + +struct Skin : public Object { + Nullable bindShapeMatrix; //!< Floating-point 4x4 transformation matrix stored in column-major order. + Ref inverseBindMatrices; //!< The ID of the accessor containing the floating-point 4x4 inverse-bind matrices. + std::vector> jointNames; //!< Joint names of the joints (nodes with a jointName property) in this skin. + std::string name; //!< The user-defined name of this object. + + Skin() = default; + void Read(Value &obj, Asset &r); +}; + +struct Technique : public Object { + struct Parameters { + }; + + struct States { + }; + + struct Functions { + }; + + Technique() = default; + void Read(Value &obj, Asset &r); +}; + +//! A texture and its sampler. +struct Texture : public Object { + Ref sampler; //!< The ID of the sampler used by this texture. (required) + Ref source; //!< The ID of the image used by this texture. (required) + + Texture() = default; + void Read(Value &obj, Asset &r); +}; + +//! A light (from KHR_materials_common extension) +struct Light : public Object { + enum Type { + Type_undefined, + Type_ambient, + Type_directional, + Type_point, + Type_spot + }; + + Type type; + vec4 color; + float distance; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + float falloffAngle; + float falloffExponent; + + Light() = default; + void Read(Value &obj, Asset &r); + void SetDefaults(); +}; + +struct Animation : public Object { + struct AnimSampler { + std::string id; //!< The ID of this sampler. + std::string input; //!< The ID of a parameter in this animation to use as key-frame input. + std::string interpolation; //!< Type of interpolation algorithm to use between key-frames. + std::string output; //!< The ID of a parameter in this animation to use as key-frame output. + }; + + struct AnimChannel { + std::string sampler; //!< The ID of one sampler present in the containing animation's samplers property. + + struct AnimTarget { + Ref id; //!< The ID of the node to animate. + std::string path; //!< The name of property of the node to animate ("translation", "rotation", or "scale"). + } target; + }; + + struct AnimParameters { + Ref TIME; //!< Accessor reference to a buffer storing a array of floating point scalar values. + Ref rotation; //!< Accessor reference to a buffer storing a array of four-component floating-point vectors. + Ref scale; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + Ref translation; //!< Accessor reference to a buffer storing a array of three-component floating-point vectors. + }; + + std::vector Channels; //!< Connect the output values of the key-frame animation to a specific node in the hierarchy. + AnimParameters Parameters; //!< The samplers that interpolate between the key-frames. + std::vector Samplers; //!< The parameterized inputs representing the key-frame data. + + Animation() = default; + void Read(Value &obj, Asset &r); +}; + +//! Base class for LazyDict that acts as an interface +class LazyDictBase { +public: + virtual ~LazyDictBase() {} + + virtual void AttachToDocument(Document &doc) = 0; + virtual void DetachFromDocument() = 0; + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + virtual void WriteObjects(AssetWriter &writer) = 0; +#endif +}; + +template +class LazyDict; + +//! (Implemented in glTFAssetWriter.h) +template +void WriteLazyDict(LazyDict &d, AssetWriter &w); + +//! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID +//! It is the owner the loaded objects, so when it is destroyed it also deletes them +template +class LazyDict : public LazyDictBase { + friend class Asset; + friend class AssetWriter; + + typedef typename std::gltf_unordered_map Dict; + + std::vector mObjs; //! The read objects + Dict mObjsById; //! The read objects accessible by id + const char *mDictId; //! ID of the dictionary object + const char *mExtId; //! ID of the extension defining the dictionary + Value *mDict; //! JSON dictionary object + Asset &mAsset; //! The asset instance + + void AttachToDocument(Document &doc); + void DetachFromDocument(); + +#if !defined(ASSIMP_BUILD_NO_EXPORT) + void WriteObjects(AssetWriter &writer) { WriteLazyDict(*this, writer); } +#endif + + Ref Add(T *obj); + +public: + LazyDict(Asset &asset, const char *dictId, const char *extId = 0); + ~LazyDict(); + + Ref Get(const char *id); + Ref Get(unsigned int i); + Ref Get(const std::string &pID) { return Get(pID.c_str()); } + + Ref Create(const char *id); + Ref Create(const std::string &id) { return Create(id.c_str()); } + + inline unsigned int Size() const { return unsigned(mObjs.size()); } + + inline T &operator[](size_t i) { return *mObjs[i]; } +}; + +struct AssetMetadata { + std::string copyright; //!< A copyright message suitable for display to credit the content creator. + std::string generator; //!< Tool that generated this glTF model.Useful for debugging. + bool premultipliedAlpha; //!< Specifies if the shaders were generated with premultiplied alpha. (default: false) + + struct { + std::string api; //!< Specifies the target rendering API (default: "WebGL") + std::string version; //!< Specifies the target rendering API (default: "1.0.3") + } profile; //!< Specifies the target rendering API and version, e.g., WebGL 1.0.3. (default: {}) + + std::string version; //!< The glTF format version (should be 1.0) + + void Read(Document &doc); + + AssetMetadata() : + premultipliedAlpha(false), version() { + } +}; + +// +// glTF Asset class +// + +//! Root object for a glTF asset +class Asset { + using IdMap = std::gltf_unordered_map; + + template + friend class LazyDict; + friend struct Buffer; // To access OpenFile + friend class AssetWriter; + +private: + IOSystem *mIOSystem; + + std::string mCurrentAssetDir; + + size_t mSceneLength; + size_t mBodyOffset, mBodyLength; + + std::vector mDicts; + + IdMap mUsedIds; + + Ref mBodyBuffer; + + Asset(Asset &); + Asset &operator=(const Asset &); + +public: + //! Keeps info about the enabled extensions + struct Extensions { + bool KHR_binary_glTF; + bool KHR_materials_common; + + } extensionsUsed; + + AssetMetadata asset; + + // Dictionaries for each type of object + + LazyDict accessors; + LazyDict animations; + LazyDict buffers; + LazyDict bufferViews; + LazyDict cameras; + LazyDict images; + LazyDict materials; + LazyDict meshes; + LazyDict nodes; + LazyDict samplers; + LazyDict scenes; + LazyDict skins; + LazyDict textures; + + LazyDict lights; // KHR_materials_common ext + + Ref scene; + +public: + Asset(IOSystem *io = 0) : + mIOSystem(io), + asset(), + accessors(*this, "accessors"), + animations(*this, "animations"), + buffers(*this, "buffers"), + bufferViews(*this, "bufferViews"), + cameras(*this, "cameras"), + images(*this, "images"), + materials(*this, "materials"), + meshes(*this, "meshes"), + nodes(*this, "nodes"), + samplers(*this, "samplers"), + scenes(*this, "scenes"), + skins(*this, "skins"), + textures(*this, "textures"), + lights(*this, "lights", "KHR_materials_common") { + memset(&extensionsUsed, 0, sizeof(extensionsUsed)); + } + + //! Main function + void Load(const std::string &file, bool isBinary = false); + + //! Enables the "KHR_binary_glTF" extension on the asset + void SetAsBinary(); + + //! Search for an available name, starting from the given strings + std::string FindUniqueID(const std::string &str, const char *suffix); + + Ref GetBodyBuffer() { return mBodyBuffer; } + +private: + void ReadBinaryHeader(IOStream &stream); + + void ReadExtensionsUsed(Document &doc); + + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); +}; + +} // namespace glTF + +// Include the implementation of the methods +#include "glTFAsset.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTFASSET_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFAsset.inl b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl new file mode 100644 index 0000000..2b76a30 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAsset.inl @@ -0,0 +1,1316 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#include +#include +#include + +// Header files, Assimp +#include +#include + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +// Header files, Open3DGC. +#include +#endif + +using namespace Assimp; +using namespace glTFCommon; + +namespace glTF { + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +// +// LazyDict methods +// + +template +inline LazyDict::LazyDict(Asset &asset, const char *dictId, const char *extId) : + mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) { + asset.mDicts.push_back(this); // register to the list of dictionaries +} + +template +inline LazyDict::~LazyDict() { + for (size_t i = 0; i < mObjs.size(); ++i) { + delete mObjs[i]; + } +} + +template +inline void LazyDict::AttachToDocument(Document &doc) { + Value *container = 0; + + if (mExtId) { + if (Value *exts = FindObject(doc, "extensions")) { + container = FindObject(*exts, mExtId); + } + } else { + container = &doc; + } + + if (container) { + mDict = FindObject(*container, mDictId); + } +} + +template +inline void LazyDict::DetachFromDocument() { + mDict = 0; +} + +template +Ref LazyDict::Get(unsigned int i) { + return Ref(mObjs, i); +} + +template +Ref LazyDict::Get(const char *id) { + id = T::TranslateId(mAsset, id); + + typename Dict::iterator it = mObjsById.find(id); + if (it != mObjsById.end()) { // already created? + return Ref(mObjs, it->second); + } + + // read it from the JSON object + if (!mDict) { + throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\""); + } + + Value::MemberIterator obj = mDict->FindMember(id); + if (obj == mDict->MemberEnd()) { + throw DeadlyImportError("GLTF: Missing object with id \"", id, "\" in \"", mDictId, "\""); + } + if (!obj->value.IsObject()) { + throw DeadlyImportError("GLTF: Object with id \"", id, "\" is not a JSON object"); + } + + // create an instance of the given type + T *inst = new T(); + inst->id = id; + ReadMember(obj->value, "name", inst->name); + inst->Read(obj->value, mAsset); + return Add(inst); +} + +template +Ref LazyDict::Add(T *obj) { + unsigned int idx = unsigned(mObjs.size()); + mObjs.push_back(obj); + mObjsById[obj->id] = idx; + mAsset.mUsedIds[obj->id] = true; + return Ref(mObjs, idx); +} + +template +Ref LazyDict::Create(const char *id) { + Asset::IdMap::iterator it = mAsset.mUsedIds.find(id); + if (it != mAsset.mUsedIds.end()) { + throw DeadlyImportError("GLTF: two objects with the same ID exist"); + } + T *inst = new T(); + inst->id = id; + return Add(inst); +} + +// +// glTF dictionary objects methods +// + +inline Buffer::Buffer() : + byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) {} + +inline Buffer::~Buffer() { + for (SEncodedRegion *reg : EncodedRegion_List) + delete reg; +} + +inline const char *Buffer::TranslateId(Asset &r, const char *id) { + // Compatibility with old spec + if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { + return "binary_glTF"; + } + + return id; +} + +inline void Buffer::Read(Value &obj, Asset &r) { + size_t statedLength = MemberOrDefault(obj, "byteLength", 0); + byteLength = statedLength; + + Value *it = FindString(obj, "uri"); + if (!it) { + if (statedLength > 0) { + throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); + } + return; + } + + const char *uri = it->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { + if (dataURI.base64) { + uint8_t *data = 0; + this->byteLength = Base64::Decode(dataURI.data, dataURI.dataLength, data); + this->mData.reset(data, std::default_delete()); + + if (statedLength > 0 && this->byteLength != statedLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + } else { // assume raw data + if (statedLength != dataURI.dataLength) { + throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(statedLength), + " bytes, but found ", ai_to_string(dataURI.dataLength)); + } + + this->mData.reset(new uint8_t[dataURI.dataLength], std::default_delete()); + memcpy(this->mData.get(), dataURI.data, dataURI.dataLength); + } + } else { // Local file + if (byteLength > 0) { + std::string dir = !r.mCurrentAssetDir.empty() ? ( + r.mCurrentAssetDir.back() == '/' ? + r.mCurrentAssetDir : + r.mCurrentAssetDir + '/') : + ""; + + IOStream *file = r.OpenFile(dir + uri, "rb"); + if (file) { + bool ok = LoadFromStream(*file, byteLength); + delete file; + + if (!ok) + throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\""); + } else { + throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\""); + } + } + } +} + +inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) { + byteLength = length ? length : stream.FileSize(); + + if (baseOffset) { + stream.Seek(baseOffset, aiOrigin_SET); + } + + mData.reset(new uint8_t[byteLength], std::default_delete()); + + if (stream.Read(mData.get(), byteLength, 1) != 1) { + return false; + } + return true; +} + +inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) { + // Check pointer to data + if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); + + // Check offset + if (pOffset > byteLength) { + const uint8_t val_size = 32; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT, pOffset); + throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region."); + } + + // Check length + if ((pOffset + pEncodedData_Length) > byteLength) { + const uint8_t val_size = 64; + + char val[val_size]; + + ai_snprintf(val, val_size, AI_SIZEFMT "/" AI_SIZEFMT, pOffset, pEncodedData_Length); + throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range."); + } + + // Add new region + EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); + // And set new value for "byteLength" + byteLength += (pDecodedData_Length - pEncodedData_Length); +} + +inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + + for (SEncodedRegion *reg : EncodedRegion_List) { + if (reg->ID == pID) { + EncodedRegion_Current = reg; + + return; + } + } + + throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found."); +} + +inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { + const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; + + uint8_t *new_data; + + if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false; + + new_data = new uint8_t[new_data_size]; + // Copy data which place before replacing part. + memcpy(new_data, mData.get(), pBufferData_Offset); + // Copy new data. + memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); + // Copy data which place after replacing part. + memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset); + // Apply new data + mData.reset(new_data, std::default_delete()); + byteLength = new_data_size; + + return true; +} + +inline size_t Buffer::AppendData(uint8_t *data, size_t length) { + size_t offset = this->byteLength; + Grow(length); + memcpy(mData.get() + offset, data, length); + return offset; +} + +inline void Buffer::Grow(size_t amount) { + if (amount <= 0) return; + if (capacity >= byteLength + amount) { + byteLength += amount; + return; + } + + // Shift operation is standard way to divide integer by 2, it doesn't cast it to float back and forth, also works for odd numbers, + // originally it would look like: static_cast(capacity * 1.5f) + capacity = std::max(capacity + (capacity >> 1), byteLength + amount); + + uint8_t *b = new uint8_t[capacity]; + if (mData) memcpy(b, mData.get(), byteLength); + mData.reset(b, std::default_delete()); + byteLength += amount; +} + +// +// struct BufferView +// + +inline void BufferView::Read(Value &obj, Asset &r) { + const char *bufferId = MemberOrDefault(obj, "buffer", 0); + if (bufferId) { + buffer = r.buffers.Get(bufferId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteLength = MemberOrDefault(obj, "byteLength", 0u); +} + +// +// struct Accessor +// + +inline void Accessor::Read(Value &obj, Asset &r) { + const char *bufferViewId = MemberOrDefault(obj, "bufferView", 0); + if (bufferViewId) { + bufferView = r.bufferViews.Get(bufferViewId); + } + + byteOffset = MemberOrDefault(obj, "byteOffset", 0u); + byteStride = MemberOrDefault(obj, "byteStride", 0u); + componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); + count = MemberOrDefault(obj, "count", 0u); + + const char *typestr; + type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR; +} + +inline unsigned int Accessor::GetNumComponents() { + return AttribType::GetNumComponents(type); +} + +inline unsigned int Accessor::GetBytesPerComponent() { + return int(ComponentTypeSize(componentType)); +} + +inline unsigned int Accessor::GetElementSize() { + return GetNumComponents() * GetBytesPerComponent(); +} + +inline uint8_t *Accessor::GetPointer() { + if (!bufferView || !bufferView->buffer) return 0; + uint8_t *basePtr = bufferView->buffer->GetPointer(); + if (!basePtr) return 0; + + size_t offset = byteOffset + bufferView->byteOffset; + + // Check if region is encoded. + if (bufferView->buffer->EncodedRegion_Current != nullptr) { + const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; + const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; + + if ((offset >= begin) && (offset < end)) + return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } + + return basePtr + offset; +} + +namespace { +inline void CopyData(size_t count, + const uint8_t *src, size_t src_stride, + uint8_t *dst, size_t dst_stride) { + if (src_stride == dst_stride) { + memcpy(dst, src, count * src_stride); + } else { + size_t sz = std::min(src_stride, dst_stride); + for (size_t i = 0; i < count; ++i) { + memcpy(dst, src, sz); + if (sz < dst_stride) { + memset(dst + sz, 0, dst_stride - sz); + } + src += src_stride; + dst += dst_stride; + } + } +} +} // namespace + +template +bool Accessor::ExtractData(T *&outData) { + uint8_t *data = GetPointer(); + if (!data) return false; + + const size_t elemSize = GetElementSize(); + const size_t totalSize = elemSize * count; + + const size_t stride = byteStride ? byteStride : elemSize; + + const size_t targetElemSize = sizeof(T); + ai_assert(elemSize <= targetElemSize); + + ai_assert(count * stride <= bufferView->byteLength); + + outData = new T[count]; + if (stride == elemSize && targetElemSize == elemSize) { + memcpy(outData, data, totalSize); + } else { + for (size_t i = 0; i < count; ++i) { + memcpy(outData + i, data + i * stride, elemSize); + } + } + + return true; +} + +inline void Accessor::WriteData(size_t cnt, const void *src_buffer, size_t src_stride) { + uint8_t *buffer_ptr = bufferView->buffer->GetPointer(); + size_t offset = byteOffset + bufferView->byteOffset; + + size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); + + const uint8_t *src = reinterpret_cast(src_buffer); + uint8_t *dst = reinterpret_cast(buffer_ptr + offset); + + ai_assert(dst + count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength); + CopyData(cnt, src, src_stride, dst, dst_stride); +} + +inline Accessor::Indexer::Indexer(Accessor &acc) : + accessor(acc), data(acc.GetPointer()), elemSize(acc.GetElementSize()), stride(acc.byteStride ? acc.byteStride : elemSize) { +} + +//! Accesses the i-th value as defined by the accessor +template +T Accessor::Indexer::GetValue(int i) { + ai_assert(data); + ai_assert(i * stride < accessor.bufferView->byteLength); + T value = T(); + memcpy(&value, data + i * stride, elemSize); + //value >>= 8 * (sizeof(T) - elemSize); + return value; +} + +inline Image::Image() : + width(0), height(0), mDataLength(0) { +} + +inline void Image::Read(Value &obj, Asset &r) { + // Check for extensions first (to detect binary embedded data) + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_binary_glTF) { + if (Value *ext = FindObject(*extensions, "KHR_binary_glTF")) { + + width = MemberOrDefault(*ext, "width", 0); + height = MemberOrDefault(*ext, "height", 0); + + ReadMember(*ext, "mimeType", mimeType); + + const char *bufferViewId; + if (ReadMember(*ext, "bufferView", bufferViewId)) { + Ref bv = r.bufferViews.Get(bufferViewId); + if (bv) { + mDataLength = bv->byteLength; + mData.reset(new uint8_t[mDataLength]); + memcpy(mData.get(), bv->buffer->GetPointer() + bv->byteOffset, mDataLength); + } + } + } + } + } + + if (!mDataLength) { + Value *curUri = FindString(obj, "uri"); + if (nullptr != curUri) { + const char *uristr = curUri->GetString(); + + glTFCommon::Util::DataURI dataURI; + if (ParseDataURI(uristr, curUri->GetStringLength(), dataURI)) { + mimeType = dataURI.mediaType; + if (dataURI.base64) { + uint8_t *ptr = nullptr; + mDataLength = Base64::Decode(dataURI.data, dataURI.dataLength, ptr); + mData.reset(ptr); + } + } else { + this->uri = uristr; + } + } + } +} + +inline uint8_t *Image::StealData() { + mDataLength = 0; + return mData.release(); +} + +inline void Image::SetData(uint8_t *data, size_t length, Asset &r) { + Ref b = r.GetBodyBuffer(); + if (b) { // binary file: append to body + std::string bvId = r.FindUniqueID(this->id, "imgdata"); + bufferView = r.bufferViews.Create(bvId); + + bufferView->buffer = b; + bufferView->byteLength = length; + bufferView->byteOffset = b->AppendData(data, length); + } else { // text file: will be stored as a data uri + uint8_t *temp = new uint8_t[length]; + memcpy(temp, data, length); + this->mData.reset(temp); + this->mDataLength = length; + } +} + +inline void Sampler::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + ReadMember(obj, "magFilter", magFilter); + ReadMember(obj, "minFilter", minFilter); + ReadMember(obj, "wrapS", wrapS); + ReadMember(obj, "wrapT", wrapT); +} + +inline void Sampler::SetDefaults() { + magFilter = SamplerMagFilter_Linear; + minFilter = SamplerMinFilter_Linear; + wrapS = SamplerWrap_Repeat; + wrapT = SamplerWrap_Repeat; +} + +inline void Texture::Read(Value &obj, Asset &r) { + const char *sourcestr; + if (ReadMember(obj, "source", sourcestr)) { + source = r.images.Get(sourcestr); + } + + const char *samplerstr; + if (ReadMember(obj, "sampler", samplerstr)) { + sampler = r.samplers.Get(samplerstr); + } +} + +namespace { +inline void ReadMaterialProperty(Asset &r, Value &vals, const char *propName, TexProperty &out) { + if (Value *prop = FindMember(vals, propName)) { + if (prop->IsString()) { + out.texture = r.textures.Get(prop->GetString()); + } else { + ReadValue(*prop, out.color); + } + } +} +} // namespace + +inline void Material::Read(Value &material, Asset &r) { + SetDefaults(); + + if (Value *values = FindObject(material, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + + if (Value *extensions = FindObject(material, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + if (Value *tnq = FindString(*ext, "technique")) { + const char *t = tnq->GetString(); + if (strcmp(t, "BLINN") == 0) + technique = Technique_BLINN; + else if (strcmp(t, "PHONG") == 0) + technique = Technique_PHONG; + else if (strcmp(t, "LAMBERT") == 0) + technique = Technique_LAMBERT; + else if (strcmp(t, "CONSTANT") == 0) + technique = Technique_CONSTANT; + } + + if (Value *values = FindObject(*ext, "values")) { + ReadMaterialProperty(r, *values, "ambient", this->ambient); + ReadMaterialProperty(r, *values, "diffuse", this->diffuse); + ReadMaterialProperty(r, *values, "specular", this->specular); + + ReadMember(*values, "doubleSided", doubleSided); + ReadMember(*values, "transparent", transparent); + ReadMember(*values, "transparency", transparency); + ReadMember(*values, "shininess", shininess); + } + } + } + } +} + +namespace { +void SetVector(vec4 &v, float x, float y, float z, float w) { + v[0] = x; + v[1] = y; + v[2] = z; + v[3] = w; +} +} // namespace + +inline void Material::SetDefaults() { + SetVector(ambient.color, 0, 0, 0, 1); + SetVector(diffuse.color, 0, 0, 0, 1); + SetVector(specular.color, 0, 0, 0, 1); + SetVector(emission.color, 0, 0, 0, 1); + + doubleSided = false; + transparent = false; + transparency = 1.0; + shininess = 0.0; + + technique = Technique_undefined; +} + +namespace { + +template +inline int Compare(const char *attr, const char (&str)[N]) { + return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; +} + +inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.attributes.position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.attributes.normal); + } else if ((pos = Compare(attr, "TEXCOORD"))) { + v = &(p.attributes.texcoord); + } else if ((pos = Compare(attr, "COLOR"))) { + v = &(p.attributes.color); + } else if ((pos = Compare(attr, "JOINT"))) { + v = &(p.attributes.joint); + } else if ((pos = Compare(attr, "JOINTMATRIX"))) { + v = &(p.attributes.jointmatrix); + } else if ((pos = Compare(attr, "WEIGHT"))) { + v = &(p.attributes.weight); + } else + return false; + return true; +} +} // namespace + +inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { + /****************** Mesh primitives ******************/ + Value *curPrimitives = FindArray(pJSON_Object, "primitives"); + if (nullptr != curPrimitives) { + this->primitives.resize(curPrimitives->Size()); + for (unsigned int i = 0; i < curPrimitives->Size(); ++i) { + Value &primitive = (*curPrimitives)[i]; + + Primitive &prim = this->primitives[i]; + prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES); + + if (Value *attrs = FindObject(primitive, "attributes")) { + for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { + if (!it->value.IsString()) continue; + const char *attr = it->name.GetString(); + // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX, + // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. + + int undPos = 0; + Mesh::AccessorList *vec = 0; + if (GetAttribVector(prim, attr, vec, undPos)) { + size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; + if ((*vec).size() <= idx) (*vec).resize(idx + 1); + (*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString()); + } + } + } + + if (Value *indices = FindString(primitive, "indices")) { + prim.indices = pAsset_Root.accessors.Get(indices->GetString()); + } + + if (Value *material = FindString(primitive, "material")) { + prim.material = pAsset_Root.materials.Get(material->GetString()); + } + } + } + + /****************** Mesh extensions ******************/ + Value *json_extensions = FindObject(pJSON_Object, "extensions"); + + if (json_extensions == nullptr) goto mr_skip_extensions; + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for (Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) { + if (it_memb->name.GetString() == std::string("Open3DGC-compression")) { + // Search for compressed data. + // Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and + // new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure. + Value *comp_data = FindObject(it_memb->value, "compressedData"); + + if (comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); + + ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data."); + +/************** Read data from JSON-document **************/ +#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ + if (!ReadMember(*comp_data, pFieldName, pOut)) { \ + throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \ + } + + const char *mode_str; + const char *type_str; + ComponentType component_type; + SCompression_Open3DGC *ext_o3dgc = new SCompression_Open3DGC; + + MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer); + MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset); + MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type); + MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str); + MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count); + MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str); + MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount); + MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount); + +#undef MESH_READ_COMPRESSEDDATA_MEMBER + + // Check some values + if (strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data."); + if (component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data."); + + // Set read/write data mode. + if (strcmp(mode_str, "binary") == 0) + ext_o3dgc->Binary = true; + else if (strcmp(mode_str, "ascii") == 0) + ext_o3dgc->Binary = false; + else + throw DeadlyImportError("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"", mode_str, "\"."); + + /************************ Decoding ************************/ + Decode_O3DGC(*ext_o3dgc, pAsset_Root); + Extension.push_back(ext_o3dgc); // store info in mesh extensions list. + } // if(it_memb->name.GetString() == "Open3DGC-compression") + else { + throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\"."); + } + } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) +#endif + +mr_skip_extensions: + + return; // After label some operators must be present. +} + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC +inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DGC, Asset &pAsset_Root) { + typedef unsigned short IndicesType; ///< \sa glTFExporter::ExportMeshes. + + o3dgc::SC3DMCDecoder decoder; + o3dgc::IndexedFaceSet ifs; + o3dgc::BinaryStream bstream; + uint8_t *decoded_data; + size_t decoded_data_size = 0; + Ref buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer); + + // Read data from buffer and place it in BinaryStream for decoder. + // Just "Count" because always is used type equivalent to uint8_t. + bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast(pCompression_Open3DGC.Count)); + + // After decoding header we can get size of primitives. + if (decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header."); + + /****************** Get sizes of arrays and check sizes ******************/ + // Note. See "Limitations for meshes when using Open3DGC-compression". + + // Indices + size_t size_coordindex = ifs.GetNCoordIndex() * 3; // See float attributes note. + + if (primitives[0].indices->count != size_coordindex) + throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (", ai_to_string(size_coordindex), + ") not equal to uncompressed (", ai_to_string(primitives[0].indices->count), ")."); + + size_coordindex *= sizeof(IndicesType); + // Coordinates + size_t size_coord = ifs.GetNCoord(); // See float attributes note. + + if (primitives[0].attributes.position[0]->count != size_coord) + throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (", ai_to_string(size_coord), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.position[0]->count), ")."); + + size_coord *= 3 * sizeof(float); + // Normals + size_t size_normal = ifs.GetNNormal(); // See float attributes note. + + if (primitives[0].attributes.normal[0]->count != size_normal) + throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (", ai_to_string(size_normal), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.normal[0]->count), ")."); + + size_normal *= 3 * sizeof(float); + // Additional attributes. + std::vector size_floatattr; + std::vector size_intattr; + + size_floatattr.resize(ifs.GetNumFloatAttributes()); + size_intattr.resize(ifs.GetNumIntAttributes()); + + decoded_data_size = size_coordindex + size_coord + size_normal; + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. + // Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects... + size_t tval = ifs.GetNFloatAttribute(static_cast(idx)); + + switch (ifs.GetFloatAttributeType(static_cast(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + // Check situation when encoded data contain texture coordinates but primitive not. + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + if (primitives[0].attributes.texcoord[idx]->count != tval) + throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); + + idx_texcoord++; + } else { + ifs.SetNFloatAttribute(static_cast(idx), 0ul); // Disable decoding this attribute. + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); + } + + tval *= ifs.GetFloatAttributeDim(static_cast(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array. + size_floatattr[idx] = tval; + decoded_data_size += tval; + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + // size = number_of_elements * components_per_element * size_of_component. See float attributes note. + size_t tval = ifs.GetNIntAttribute(static_cast(idx)); + switch (ifs.GetIntAttributeType(static_cast(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); + } + + tval *= ifs.GetIntAttributeDim(static_cast(idx)) * sizeof(long); // See float attributes note. + size_intattr[idx] = tval; + decoded_data_size += tval; + } + + // Create array for decoded data. + decoded_data = new uint8_t[decoded_data_size]; + + /****************** Set right array regions for decoder ******************/ + + auto get_buf_offset = [](Ref &pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; }; + + // Indices + ifs.SetCoordIndex((IndicesType *const)(decoded_data + get_buf_offset(primitives[0].indices))); + // Coordinates + ifs.SetCoord((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0]))); + // Normals + if (size_normal) { + ifs.SetNormal((o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0]))); + } + + for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { + switch (ifs.GetFloatAttributeType(static_cast(idx))) { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + // See above about absent attributes. + ifs.SetFloatAttribute(static_cast(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); + idx_texcoord++; + } + + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); + } + } + + for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { + switch (ifs.GetIntAttributeType(static_cast(idx))) { + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; + + // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); + } + } + + // + // Decode data + // + if (decoder.DecodePayload(ifs, bstream) != o3dgc::O3DGC_OK) { + throw DeadlyImportError("GLTF: can not decode Open3DGC data."); + } + + // Set encoded region for "buffer". + buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id); + // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data". + // "delete [] output_data;" +} +#endif + +inline void Camera::Read(Value &obj, Asset & /*r*/) { + type = MemberOrDefault(obj, "type", Camera::Perspective); + + const char *subobjId = (type == Camera::Orthographic) ? "orthographic" : "perspective"; + + Value *it = FindObject(obj, subobjId); + if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); + + if (type == Camera::Perspective) { + perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f); + perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f / 2.f); + perspective.zfar = MemberOrDefault(*it, "zfar", 100.f); + perspective.znear = MemberOrDefault(*it, "znear", 0.01f); + } else { + ortographic.xmag = MemberOrDefault(*it, "xmag", 1.f); + ortographic.ymag = MemberOrDefault(*it, "ymag", 1.f); + ortographic.zfar = MemberOrDefault(*it, "zfar", 100.f); + ortographic.znear = MemberOrDefault(*it, "znear", 0.01f); + } +} + +inline void Light::Read(Value &obj, Asset & /*r*/) { + SetDefaults(); + + Value *curType = FindString(obj, "type"); + if (nullptr != curType) { + const char *t = curType->GetString(); + if (strcmp(t, "ambient") == 0) + this->type = Type_ambient; + else if (strcmp(t, "directional") == 0) + this->type = Type_directional; + else if (strcmp(t, "point") == 0) + this->type = Type_point; + else if (strcmp(t, "spot") == 0) + this->type = Type_spot; + + if (this->type != Type_undefined) { + if (Value *vals = FindString(obj, t)) { + ReadMember(*vals, "color", color); + + ReadMember(*vals, "constantAttenuation", constantAttenuation); + ReadMember(*vals, "linearAttenuation", linearAttenuation); + ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation); + ReadMember(*vals, "distance", distance); + + ReadMember(*vals, "falloffAngle", falloffAngle); + ReadMember(*vals, "falloffExponent", falloffExponent); + } + } + } +} + +inline void Light::SetDefaults() { +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + type = Type_undefined; + + SetVector(color, 0.f, 0.f, 0.f, 1.f); + + constantAttenuation = 0.f; + linearAttenuation = 1.f; + quadraticAttenuation = 1.f; + distance = 0.f; + + falloffAngle = static_cast(M_PI / 2.f); + falloffExponent = 0.f; +} + +inline void Node::Read(Value &obj, Asset &r) { + if (name.empty()) { + name = id; + } + + Value *curChildren = FindArray(obj, "children"); + if (nullptr != curChildren) { + this->children.reserve(curChildren->Size()); + for (unsigned int i = 0; i < curChildren->Size(); ++i) { + Value &child = (*curChildren)[i]; + if (child.IsString()) { + // get/create the child node + Ref chn = r.nodes.Get(child.GetString()); + if (chn) this->children.push_back(chn); + } + } + } + + Value *curMatrix = FindArray(obj, "matrix"); + if (nullptr != curMatrix) { + ReadValue(*curMatrix, this->matrix); + } else { + ReadMember(obj, "translation", translation); + ReadMember(obj, "scale", scale); + ReadMember(obj, "rotation", rotation); + } + + Value *curMeshes = FindArray(obj, "meshes"); + if (nullptr != curMeshes) { + unsigned int numMeshes = (unsigned int)curMeshes->Size(); + + std::vector meshList; + + this->meshes.reserve(numMeshes); + for (unsigned i = 0; i < numMeshes; ++i) { + if ((*curMeshes)[i].IsString()) { + Ref mesh = r.meshes.Get((*curMeshes)[i].GetString()); + if (mesh) { + this->meshes.push_back(mesh); + } + } + } + } + + Value *curCamera = FindString(obj, "camera"); + if (nullptr != curCamera) { + this->camera = r.cameras.Get(curCamera->GetString()); + if (this->camera) { + this->camera->id = this->id; + } + } + + // TODO load "skeletons", "skin", "jointName" + + if (Value *extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_materials_common) { + + if (Value *ext = FindObject(*extensions, "KHR_materials_common")) { + Value *curLight = FindString(*ext, "light"); + if (nullptr != curLight) { + this->light = r.lights.Get(curLight->GetString()); + } + } + } + } +} + +inline void Scene::Read(Value &obj, Asset &r) { + if (Value *array = FindArray(obj, "nodes")) { + for (unsigned int i = 0; i < array->Size(); ++i) { + if (!(*array)[i].IsString()) continue; + Ref node = r.nodes.Get((*array)[i].GetString()); + if (node) + this->nodes.push_back(node); + } + } +} + +inline void AssetMetadata::Read(Document &doc) { + // read the version, etc. + if (Value *obj = FindObject(doc, "asset")) { + ReadMember(*obj, "copyright", copyright); + ReadMember(*obj, "generator", generator); + + premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false); + + if (Value *versionString = FindString(*obj, "version")) { + version = versionString->GetString(); + } else if (Value *versionNumber = FindNumber(*obj, "version")) { + char buf[4]; + + ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble()); + + version = buf; + } + + Value *curProfile = FindObject(*obj, "profile"); + if (nullptr != curProfile) { + ReadMember(*curProfile, "api", this->profile.api); + ReadMember(*curProfile, "version", this->profile.version); + } + } + + if (version.empty() || version[0] != '1') { + throw DeadlyImportError("GLTF: Unsupported glTF version: ", version); + } +} + +// +// Asset methods implementation +// + +inline void Asset::ReadBinaryHeader(IOStream &stream) { + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + throw DeadlyImportError("GLTF: Unable to read the file header"); + } + + if (strncmp((char *)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + throw DeadlyImportError("GLTF: Invalid binary glTF file"); + } + + AI_SWAP4(header.version); + asset.version = ai_to_string(header.version); + if (header.version != 1) { + throw DeadlyImportError("GLTF: Unsupported binary glTF version"); + } + + AI_SWAP4(header.sceneFormat); + if (header.sceneFormat != SceneFormat_JSON) { + throw DeadlyImportError("GLTF: Unsupported binary glTF scene format"); + } + + AI_SWAP4(header.length); + AI_SWAP4(header.sceneLength); + + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "size_t must be at least 32bits"); + mSceneLength = static_cast(header.sceneLength); // Can't be larger than 4GB (max. uint32_t) + + mBodyOffset = sizeof(header) + mSceneLength; + mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 + + mBodyLength = header.length - mBodyOffset; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) { + mCurrentAssetDir.clear(); + + /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1);*/ + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = getCurrentAssetDir(pFile); + } + + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + // is binary? then read the header + if (isBinary) { + SetAsBinary(); // also creates the body buffer + ReadBinaryHeader(*stream); + } else { + mSceneLength = stream->FileSize(); + mBodyLength = 0; + } + + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + + // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation + if (mSceneLength >= std::numeric_limits::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } + + // read the scene data, ensure null termination + std::vector sceneData(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("GLTF: Could not read the file contents"); + } + + // parse the JSON document + + Document doc; + doc.ParseInsitu(&sceneData[0]); + + if (doc.HasParseError()) { + char buffer[32]; + ai_snprintf(buffer, 32, "%d", static_cast(doc.GetErrorOffset())); + throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); + } + + // Fill the buffer instance for the current file embedded contents + if (mBodyLength > 0) { + if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { + throw DeadlyImportError("GLTF: Unable to read gltf file"); + } + } + + // Load the metadata + asset.Read(doc); + ReadExtensionsUsed(doc); + + // Prepare the dictionaries + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->AttachToDocument(doc); + } + + // Read the "scene" property, which specifies which scene to load + // and recursively load everything referenced by it + Value *curScene = FindString(doc, "scene"); + if (nullptr != curScene) { + this->scene = scenes.Get(curScene->GetString()); + } + + // Clean up + for (size_t i = 0; i < mDicts.size(); ++i) { + mDicts[i]->DetachFromDocument(); + } +} + +inline void Asset::SetAsBinary() { + if (!extensionsUsed.KHR_binary_glTF) { + extensionsUsed.KHR_binary_glTF = true; + mBodyBuffer = buffers.Create("binary_glTF"); + mBodyBuffer->MarkAsSpecial(); + } +} + +inline void Asset::ReadExtensionsUsed(Document &doc) { + Value *extsUsed = FindArray(doc, "extensionsUsed"); + if (!extsUsed) return; + + std::gltf_unordered_map exts; + + for (unsigned int i = 0; i < extsUsed->Size(); ++i) { + if ((*extsUsed)[i].IsString()) { + exts[(*extsUsed)[i].GetString()] = true; + } + } + + CHECK_EXT(KHR_binary_glTF); + CHECK_EXT(KHR_materials_common); + +#undef CHECK_EXT +} + +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) { +#ifdef ASSIMP_API + (void)absolute; + return mIOSystem->Open(path, mode); +#else + if (path.size() < 2) return 0; + if (!absolute && path[1] != ':' && path[0] != '/') { // relative? + path = mCurrentAssetDir + path; + } + FILE *f = fopen(path.c_str(), mode); + return f ? new IOStream(f) : 0; +#endif +} + +inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) { + std::string id = str; + + if (!id.empty()) { + if (mUsedIds.find(id) == mUsedIds.end()) + return id; + + id += "_"; + } + + id += suffix; + + Asset::IdMap::iterator it = mUsedIds.find(id); + if (it == mUsedIds.end()) + return id; + + char buffer[1024]; + int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str()); + for (int i = 0; it != mUsedIds.end(); ++i) { + ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i); + id = buffer; + it = mUsedIds.find(id); + } + + return id; +} + +#if _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +} // namespace glTF diff --git a/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h new file mode 100644 index 0000000..6dbc424 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.h @@ -0,0 +1,96 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file glTFWriter.h + * Declares a class to write gltf/glb files + * + * glTF Extensions Support: + * KHR_binary_glTF: full + * KHR_materials_common: full + */ +#ifndef GLTFASSETWRITER_H_INC +#define GLTFASSETWRITER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "glTFAsset.h" + +namespace glTF +{ + +using rapidjson::MemoryPoolAllocator; + +class AssetWriter +{ + template + friend void WriteLazyDict(LazyDict& d, AssetWriter& w); + +private: + + void WriteBinaryData(IOStream* outfile, size_t sceneLength); + + void WriteMetadata(); + void WriteExtensionsUsed(); + + template + void WriteObjects(LazyDict& d); + +public: + Document mDoc; + Asset& mAsset; + + MemoryPoolAllocator<>& mAl; + + AssetWriter(Asset& asset); + + void WriteFile(const char* path); + void WriteGLBFile(const char* path); +}; + +} + +// Include the implementation of the methods +#include "glTFAssetWriter.inl" + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // GLTFASSETWRITER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl new file mode 100644 index 0000000..a1265fb --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFAssetWriter.inl @@ -0,0 +1,716 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#include + +#include +#include +#include + +#if _MSC_VER +# pragma warning(push) +# pragma warning( disable : 4706) +#endif // _MSC_VER + +namespace glTF { + + using rapidjson::StringBuffer; + using rapidjson::PrettyWriter; + using rapidjson::Writer; + using rapidjson::StringRef; + using rapidjson::StringRef; + + namespace { + + template + inline + Value& MakeValue(Value& val, T(&r)[N], MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(N, al); + for (decltype(N) i = 0; i < N; ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template + inline + Value& MakeValue(Value& val, const std::vector & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(r[i], al); + } + return val; + } + + template + inline Value& MakeValueCast(Value& val, const std::vector & r, MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(static_cast(r.size()), al); + for (unsigned int i = 0; i < r.size(); ++i) { + val.PushBack(static_cast(r[i]), al); + } + return val; + } + + template + inline void AddRefsVector(Value& obj, const char* fieldId, std::vector< Ref >& v, MemoryPoolAllocator<>& al) { + if (v.empty()) return; + Value lst; + lst.SetArray(); + lst.Reserve(unsigned(v.size()), al); + for (size_t i = 0; i < v.size(); ++i) { + lst.PushBack(StringRef(v[i]->id), al); + } + obj.AddMember(StringRef(fieldId), lst, al); + } + + + } + + inline void Write(Value& obj, Accessor& a, AssetWriter& w) + { + obj.AddMember("bufferView", Value(a.bufferView->id, w.mAl).Move(), w.mAl); + obj.AddMember("byteOffset", a.byteOffset, w.mAl); + obj.AddMember("byteStride", a.byteStride, w.mAl); + obj.AddMember("componentType", int(a.componentType), w.mAl); + obj.AddMember("count", a.count, w.mAl); + obj.AddMember("type", StringRef(AttribType::ToString(a.type)), w.mAl); + + Value vTmpMax, vTmpMin; + if (a.componentType == ComponentType_FLOAT) { + obj.AddMember("max", MakeValue(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValue(vTmpMin, a.min, w.mAl), w.mAl); + } else { + obj.AddMember("max", MakeValueCast(vTmpMax, a.max, w.mAl), w.mAl); + obj.AddMember("min", MakeValueCast(vTmpMin, a.min, w.mAl), w.mAl); + } + } + + inline void Write(Value& obj, Animation& a, AssetWriter& w) + { + /****************** Channels *******************/ + Value channels; + channels.SetArray(); + channels.Reserve(unsigned(a.Channels.size()), w.mAl); + + for (size_t i = 0; i < unsigned(a.Channels.size()); ++i) { + Animation::AnimChannel& c = a.Channels[i]; + Value valChannel; + valChannel.SetObject(); + { + valChannel.AddMember("sampler", c.sampler, w.mAl); + + Value valTarget; + valTarget.SetObject(); + { + valTarget.AddMember("id", StringRef(c.target.id->id), w.mAl); + valTarget.AddMember("path", c.target.path, w.mAl); + } + valChannel.AddMember("target", valTarget, w.mAl); + } + channels.PushBack(valChannel, w.mAl); + } + obj.AddMember("channels", channels, w.mAl); + + /****************** Parameters *******************/ + Value valParameters; + valParameters.SetObject(); + { + if (a.Parameters.TIME) { + valParameters.AddMember("TIME", StringRef(a.Parameters.TIME->id), w.mAl); + } + if (a.Parameters.rotation) { + valParameters.AddMember("rotation", StringRef(a.Parameters.rotation->id), w.mAl); + } + if (a.Parameters.scale) { + valParameters.AddMember("scale", StringRef(a.Parameters.scale->id), w.mAl); + } + if (a.Parameters.translation) { + valParameters.AddMember("translation", StringRef(a.Parameters.translation->id), w.mAl); + } + } + obj.AddMember("parameters", valParameters, w.mAl); + + /****************** Samplers *******************/ + Value valSamplers; + valSamplers.SetObject(); + + for (size_t i = 0; i < unsigned(a.Samplers.size()); ++i) { + Animation::AnimSampler& s = a.Samplers[i]; + Value valSampler; + valSampler.SetObject(); + { + valSampler.AddMember("input", s.input, w.mAl); + valSampler.AddMember("interpolation", s.interpolation, w.mAl); + valSampler.AddMember("output", s.output, w.mAl); + } + valSamplers.AddMember(StringRef(s.id), valSampler, w.mAl); + } + obj.AddMember("samplers", valSamplers, w.mAl); + } + + inline void Write(Value& obj, Buffer& b, AssetWriter& w) + { + const char* type; + switch (b.type) { + case Buffer::Type_text: + type = "text"; break; + default: + type = "arraybuffer"; + } + + obj.AddMember("byteLength", static_cast(b.byteLength), w.mAl); + obj.AddMember("type", StringRef(type), w.mAl); + obj.AddMember("uri", Value(b.GetURI(), w.mAl).Move(), w.mAl); + } + + inline void Write(Value& obj, BufferView& bv, AssetWriter& w) + { + obj.AddMember("buffer", Value(bv.buffer->id, w.mAl).Move(), w.mAl); + obj.AddMember("byteOffset", static_cast(bv.byteOffset), w.mAl); + obj.AddMember("byteLength", static_cast(bv.byteLength), w.mAl); + if (bv.target != BufferViewTarget_NONE) { + obj.AddMember("target", int(bv.target), w.mAl); + } + } + + inline void Write(Value& /*obj*/, Camera& /*c*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Image& img, AssetWriter& w) + { + std::string uri; + if (w.mAsset.extensionsUsed.KHR_binary_glTF && img.bufferView) { + Value exts, ext; + exts.SetObject(); + ext.SetObject(); + + ext.AddMember("bufferView", StringRef(img.bufferView->id), w.mAl); + + if (!img.mimeType.empty()) + ext.AddMember("mimeType", StringRef(img.mimeType), w.mAl); + + exts.AddMember("KHR_binary_glTF", ext, w.mAl); + obj.AddMember("extensions", exts, w.mAl); + return; + } + else if (img.HasData()) { + uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType); + uri += ";base64,"; + Base64::Encode(img.GetData(), img.GetDataLength(), uri); + } + else { + uri = img.uri; + } + + obj.AddMember("uri", Value(uri, w.mAl).Move(), w.mAl); + } + + namespace { + inline void WriteColorOrTex(Value& obj, TexProperty& prop, const char* propName, MemoryPoolAllocator<>& al) + { + if (prop.texture) + obj.AddMember(StringRef(propName), Value(prop.texture->id, al).Move(), al); + else { + Value col; + obj.AddMember(StringRef(propName), MakeValue(col, prop.color, al), al); + } + } + } + + inline void Write(Value& obj, Material& m, AssetWriter& w) + { + Value v; + v.SetObject(); + { + WriteColorOrTex(v, m.ambient, "ambient", w.mAl); + WriteColorOrTex(v, m.diffuse, "diffuse", w.mAl); + WriteColorOrTex(v, m.specular, "specular", w.mAl); + WriteColorOrTex(v, m.emission, "emission", w.mAl); + + if (m.transparent) + v.AddMember("transparency", m.transparency, w.mAl); + + v.AddMember("shininess", m.shininess, w.mAl); + } + obj.AddMember("values", v, w.mAl); + } + + namespace { + inline void WriteAttrs(AssetWriter& w, Value& attrs, Mesh::AccessorList& lst, + const char* semantic, bool forceNumber = false) + { + if (lst.empty()) return; + if (lst.size() == 1 && !forceNumber) { + attrs.AddMember(StringRef(semantic), Value(lst[0]->id, w.mAl).Move(), w.mAl); + } + else { + for (size_t i = 0; i < lst.size(); ++i) { + char buffer[32]; + ai_snprintf(buffer, 32, "%s_%d", semantic, int(i)); + attrs.AddMember(Value(buffer, w.mAl).Move(), Value(lst[i]->id, w.mAl).Move(), w.mAl); + } + } + } + } + + inline void Write(Value& obj, Mesh& m, AssetWriter& w) + { + /********************* Name **********************/ + obj.AddMember("name", m.name, w.mAl); + + /**************** Mesh extensions ****************/ + if(m.Extension.size() > 0) + { + Value json_extensions; + + json_extensions.SetObject(); +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for(Mesh::SExtension* ptr_ext : m.Extension) + { + switch(ptr_ext->Type) + { + case Mesh::SExtension::EType::Compression_Open3DGC: + { + Value json_comp_data; + Mesh::SCompression_Open3DGC* ptr_ext_comp = (Mesh::SCompression_Open3DGC*)ptr_ext; + + // filling object "compressedData" + json_comp_data.SetObject(); + json_comp_data.AddMember("buffer", ptr_ext_comp->Buffer, w.mAl); + json_comp_data.AddMember("byteOffset", static_cast(ptr_ext_comp->Offset), w.mAl); + json_comp_data.AddMember("componentType", 5121, w.mAl); + json_comp_data.AddMember("type", "SCALAR", w.mAl); + json_comp_data.AddMember("count", static_cast(ptr_ext_comp->Count), w.mAl); + if(ptr_ext_comp->Binary) + json_comp_data.AddMember("mode", "binary", w.mAl); + else + json_comp_data.AddMember("mode", "ascii", w.mAl); + + json_comp_data.AddMember("indicesCount", static_cast(ptr_ext_comp->IndicesCount), w.mAl); + json_comp_data.AddMember("verticesCount", static_cast(ptr_ext_comp->VerticesCount), w.mAl); + // filling object "Open3DGC-compression" + Value json_o3dgc; + + json_o3dgc.SetObject(); + json_o3dgc.AddMember("compressedData", json_comp_data, w.mAl); + // add member to object "extensions" + json_extensions.AddMember("Open3DGC-compression", json_o3dgc, w.mAl); + } + + break; + default: + throw DeadlyImportError("GLTF: Can not write mesh: unknown mesh extension, only Open3DGC is supported."); + }// switch(ptr_ext->Type) + }// for(Mesh::SExtension* ptr_ext : m.Extension) +#endif + + // Add extensions to mesh + obj.AddMember("extensions", json_extensions, w.mAl); + }// if(m.Extension.size() > 0) + + /****************** Primitives *******************/ + Value primitives; + primitives.SetArray(); + primitives.Reserve(unsigned(m.primitives.size()), w.mAl); + + for (size_t i = 0; i < m.primitives.size(); ++i) { + Mesh::Primitive& p = m.primitives[i]; + Value prim; + prim.SetObject(); + { + prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl); + + if (p.material) + prim.AddMember("material", p.material->id, w.mAl); + + if (p.indices) + prim.AddMember("indices", Value(p.indices->id, w.mAl).Move(), w.mAl); + + Value attrs; + attrs.SetObject(); + { + WriteAttrs(w, attrs, p.attributes.position, "POSITION"); + WriteAttrs(w, attrs, p.attributes.normal, "NORMAL"); + WriteAttrs(w, attrs, p.attributes.texcoord, "TEXCOORD", true); + WriteAttrs(w, attrs, p.attributes.color, "COLOR"); + WriteAttrs(w, attrs, p.attributes.joint, "JOINT"); + WriteAttrs(w, attrs, p.attributes.jointmatrix, "JOINTMATRIX"); + WriteAttrs(w, attrs, p.attributes.weight, "WEIGHT"); + } + prim.AddMember("attributes", attrs, w.mAl); + } + primitives.PushBack(prim, w.mAl); + } + + obj.AddMember("primitives", primitives, w.mAl); + } + + inline void Write(Value& obj, Node& n, AssetWriter& w) + { + + if (n.matrix.isPresent) { + Value val; + obj.AddMember("matrix", MakeValue(val, n.matrix.value, w.mAl).Move(), w.mAl); + } + + if (n.translation.isPresent) { + Value val; + obj.AddMember("translation", MakeValue(val, n.translation.value, w.mAl).Move(), w.mAl); + } + + if (n.scale.isPresent) { + Value val; + obj.AddMember("scale", MakeValue(val, n.scale.value, w.mAl).Move(), w.mAl); + } + if (n.rotation.isPresent) { + Value val; + obj.AddMember("rotation", MakeValue(val, n.rotation.value, w.mAl).Move(), w.mAl); + } + + AddRefsVector(obj, "children", n.children, w.mAl); + + AddRefsVector(obj, "meshes", n.meshes, w.mAl); + + AddRefsVector(obj, "skeletons", n.skeletons, w.mAl); + + if (n.skin) { + obj.AddMember("skin", Value(n.skin->id, w.mAl).Move(), w.mAl); + } + + if (!n.jointName.empty()) { + obj.AddMember("jointName", n.jointName, w.mAl); + } + } + + inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Sampler& b, AssetWriter& w) + { + if (b.wrapS) { + obj.AddMember("wrapS", b.wrapS, w.mAl); + } + if (b.wrapT) { + obj.AddMember("wrapT", b.wrapT, w.mAl); + } + if (b.magFilter) { + obj.AddMember("magFilter", b.magFilter, w.mAl); + } + if (b.minFilter) { + obj.AddMember("minFilter", b.minFilter, w.mAl); + } + } + + inline void Write(Value& scene, Scene& s, AssetWriter& w) + { + AddRefsVector(scene, "nodes", s.nodes, w.mAl); + } + + inline void Write(Value& /*obj*/, Shader& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Skin& b, AssetWriter& w) + { + /****************** jointNames *******************/ + Value vJointNames; + vJointNames.SetArray(); + vJointNames.Reserve(unsigned(b.jointNames.size()), w.mAl); + + for (size_t i = 0; i < unsigned(b.jointNames.size()); ++i) { + vJointNames.PushBack(StringRef(b.jointNames[i]->jointName), w.mAl); + } + obj.AddMember("jointNames", vJointNames, w.mAl); + + if (b.bindShapeMatrix.isPresent) { + Value val; + obj.AddMember("bindShapeMatrix", MakeValue(val, b.bindShapeMatrix.value, w.mAl).Move(), w.mAl); + } + + if (b.inverseBindMatrices) { + obj.AddMember("inverseBindMatrices", Value(b.inverseBindMatrices->id, w.mAl).Move(), w.mAl); + } + + } + + inline void Write(Value& /*obj*/, Technique& /*b*/, AssetWriter& /*w*/) + { + + } + + inline void Write(Value& obj, Texture& tex, AssetWriter& w) + { + if (tex.source) { + obj.AddMember("source", Value(tex.source->id, w.mAl).Move(), w.mAl); + } + if (tex.sampler) { + obj.AddMember("sampler", Value(tex.sampler->id, w.mAl).Move(), w.mAl); + } + } + + inline void Write(Value& /*obj*/, Light& /*b*/, AssetWriter& /*w*/) + { + + } + + + inline AssetWriter::AssetWriter(Asset& a) + : mDoc() + , mAsset(a) + , mAl(mDoc.GetAllocator()) + { + mDoc.SetObject(); + + WriteMetadata(); + WriteExtensionsUsed(); + + // Dump the contents of the dictionaries + for (size_t i = 0; i < a.mDicts.size(); ++i) { + a.mDicts[i]->WriteObjects(*this); + } + + // Add the target scene field + if (mAsset.scene) { + mDoc.AddMember("scene", StringRef(mAsset.scene->id), mAl); + } + } + + inline void AssetWriter::WriteFile(const char* path) + { + std::unique_ptr jsonOutFile(mAsset.OpenFile(path, "wt", true)); + + if (jsonOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + StringBuffer docBuffer; + + PrettyWriter writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + // Write buffer data to separate .bin files + for (unsigned int i = 0; i < mAsset.buffers.Size(); ++i) { + Ref b = mAsset.buffers.Get(i); + + std::string binPath = b->GetURI(); + + std::unique_ptr binOutFile(mAsset.OpenFile(binPath, "wb", true)); + + if (binOutFile == 0) { + throw DeadlyExportError("Could not open output file: " + binPath); + } + + if (b->byteLength > 0) { + if (binOutFile->Write(b->GetPointer(), b->byteLength, 1) != 1) { + throw DeadlyExportError("Failed to write binary file: " + binPath); + } + } + } + } + + inline void AssetWriter::WriteGLBFile(const char* path) + { + std::unique_ptr outfile(mAsset.OpenFile(path, "wb", true)); + + if (outfile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + // we will write the header later, skip its size + outfile->Seek(sizeof(GLB_Header), aiOrigin_SET); + + StringBuffer docBuffer; + Writer writer(docBuffer); + if (!mDoc.Accept(writer)) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + WriteBinaryData(outfile.get(), docBuffer.GetSize()); + } + + inline void AssetWriter::WriteBinaryData(IOStream* outfile, size_t sceneLength) + { + // + // write the body data + // + + size_t bodyLength = 0; + if (Ref b = mAsset.GetBodyBuffer()) { + bodyLength = b->byteLength; + + if (bodyLength > 0) { + size_t bodyOffset = sizeof(GLB_Header) + sceneLength; + bodyOffset = (bodyOffset + 3) & ~3; // Round up to next multiple of 4 + + outfile->Seek(bodyOffset, aiOrigin_SET); + + if (outfile->Write(b->GetPointer(), b->byteLength, 1) != 1) { + throw DeadlyExportError("Failed to write body data!"); + } + } + } + + // + // write the header + // + + GLB_Header header; + memcpy(header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)); + + header.version = 1; + AI_SWAP4(header.version); + + header.length = uint32_t(sizeof(header) + sceneLength + bodyLength); + AI_SWAP4(header.length); + + header.sceneLength = uint32_t(sceneLength); + AI_SWAP4(header.sceneLength); + + header.sceneFormat = SceneFormat_JSON; + AI_SWAP4(header.sceneFormat); + + outfile->Seek(0, aiOrigin_SET); + + if (outfile->Write(&header, 1, sizeof(header)) != sizeof(header)) { + throw DeadlyExportError("Failed to write the header!"); + } + } + + + inline void AssetWriter::WriteMetadata() + { + Value asset; + asset.SetObject(); + asset.AddMember("version", Value(mAsset.asset.version, mAl).Move(), mAl); + asset.AddMember("generator", Value(mAsset.asset.generator, mAl).Move(), mAl); + if (!mAsset.asset.copyright.empty()) + asset.AddMember("copyright", Value(mAsset.asset.copyright, mAl).Move(), mAl); + + mDoc.AddMember("asset", asset, mAl); + } + + inline void AssetWriter::WriteExtensionsUsed() + { + Value exts; + exts.SetArray(); + { + if (false) + exts.PushBack(StringRef("KHR_binary_glTF"), mAl); + + if (false) + exts.PushBack(StringRef("KHR_materials_common"), mAl); + } + + if (!exts.Empty()) + mDoc.AddMember("extensionsUsed", exts, mAl); + } + + template + void AssetWriter::WriteObjects(LazyDict& d) + { + if (d.mObjs.empty()) return; + + Value* container = &mDoc; + + if (d.mExtId) { + Value* exts = FindObject(mDoc, "extensions"); + if (!exts) { + mDoc.AddMember("extensions", Value().SetObject().Move(), mDoc.GetAllocator()); + exts = FindObject(mDoc, "extensions"); + } + + if (!(container = FindObject(*exts, d.mExtId))) { + exts->AddMember(StringRef(d.mExtId), Value().SetObject().Move(), mDoc.GetAllocator()); + container = FindObject(*exts, d.mExtId); + } + } + + Value* dict; + if (!(dict = FindObject(*container, d.mDictId))) { + container->AddMember(StringRef(d.mDictId), Value().SetObject().Move(), mDoc.GetAllocator()); + dict = FindObject(*container, d.mDictId); + } + + for (size_t i = 0; i < d.mObjs.size(); ++i) { + if (d.mObjs[i]->IsSpecial()) continue; + + Value obj; + obj.SetObject(); + + if (!d.mObjs[i]->name.empty()) { + obj.AddMember("name", StringRef(d.mObjs[i]->name.c_str()), mAl); + } + + Write(obj, *d.mObjs[i], *this); + + dict->AddMember(StringRef(d.mObjs[i]->id), obj, mAl); + } + } + + template + void WriteLazyDict(LazyDict& d, AssetWriter& w) + { + w.WriteObjects(d); + } + +#if _MSC_VER +# pragma warning(pop) +#endif // _WIN32 + +} diff --git a/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp b/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp new file mode 100644 index 0000000..fea680c --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFCommon.cpp @@ -0,0 +1,117 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER + +#include "AssetLib/glTF/glTFCommon.h" + +namespace glTFCommon { + +using namespace glTFCommon::Util; + +namespace Util { + +bool ParseDataURI(const char *const_uri, size_t uriLen, DataURI &out) { + if (nullptr == const_uri) { + return false; + } + + if (const_uri[0] != 0x10) { // we already parsed this uri? + if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? + return false; + } + + // set defaults + out.mediaType = "text/plain"; + out.charset = "US-ASCII"; + out.base64 = false; + + char *uri = const_cast(const_uri); + if (uri[0] != 0x10) { + uri[0] = 0x10; + uri[1] = uri[2] = uri[3] = uri[4] = 0; + + size_t i = 5, j; + if (uri[i] != ';' && uri[i] != ',') { // has media type? + uri[1] = char(i); + for (;i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) { + // nothing to do! + } + } + while (i < uriLen && uri[i] == ';') { + uri[i++] = '\0'; + for (j = i; i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) { + // nothing to do! + } + + if (strncmp(uri + j, "charset=", 8) == 0) { + uri[2] = char(j + 8); + } else if (strncmp(uri + j, "base64", 6) == 0) { + uri[3] = char(j); + } + } + if (i < uriLen) { + uri[i++] = '\0'; + uri[4] = char(i); + } else { + uri[1] = uri[2] = uri[3] = 0; + uri[4] = 5; + } + } + + if (uri[1] != 0) { + out.mediaType = uri + uri[1]; + } + if (uri[2] != 0) { + out.charset = uri + uri[2]; + } + if (uri[3] != 0) { + out.base64 = true; + } + out.data = uri + uri[4]; + out.dataLength = (uri + uriLen) - out.data; + + return true; +} + +} // namespace Util +} // namespace glTFCommon + +#endif diff --git a/libs/assimp/code/AssetLib/glTF/glTFCommon.h b/libs/assimp/code/AssetLib/glTF/glTFCommon.h new file mode 100644 index 0000000..edc3c7e --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFCommon.h @@ -0,0 +1,520 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLFTCOMMON_H_INC +#define AI_GLFTCOMMON_H_INC + +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// clang-format off + +#ifdef ASSIMP_API +# include +# include +# include +#else +# include +# define AI_SWAP4(p) +# define ai_assert +#endif + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +#else +# define gltf_unordered_map map +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include +# if defined(_MSC_VER) && _MSC_VER <= 1600 +# define gltf_unordered_map tr1::unordered_map +# else +# define gltf_unordered_map unordered_map +# endif +#endif +// clang-format on + + +namespace glTFCommon { + +using rapidjson::Document; +using rapidjson::Value; + +#ifdef ASSIMP_API +using Assimp::IOStream; +using Assimp::IOSystem; +using std::shared_ptr; +#else +using std::shared_ptr; + +typedef std::runtime_error DeadlyImportError; +typedef std::runtime_error DeadlyExportError; + +enum aiOrigin { + aiOrigin_SET = 0, + aiOrigin_CUR = 1, + aiOrigin_END = 2 +}; + +class IOSystem; + +class IOStream { +public: + IOStream(FILE *file) : + f(file) {} + ~IOStream() { + fclose(f); + } + + size_t Read(void *b, size_t sz, size_t n) { return fread(b, sz, n, f); } + size_t Write(const void *b, size_t sz, size_t n) { return fwrite(b, sz, n, f); } + int Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); } + size_t Tell() const { return ftell(f); } + + size_t FileSize() { + long p = Tell(), len = (Seek(0, aiOrigin_END), Tell()); + return size_t((Seek(p, aiOrigin_SET), len)); + } + +private: + FILE *f; +}; +#endif + +// Vec/matrix types, as raw float arrays +typedef float(vec3)[3]; +typedef float(vec4)[4]; +typedef float(mat4)[16]; + +inline void CopyValue(const glTFCommon::vec3 &v, aiColor4D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = 1.0; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiColor4D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = v[3]; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiColor3D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; +} + +inline void CopyValue(const glTFCommon::vec3 &v, aiColor3D &out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; +} + +inline void CopyValue(const glTFCommon::vec3 &v, aiVector3D &out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; +} + +inline void CopyValue(const glTFCommon::vec4 &v, aiQuaternion &out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; + out.w = v[3]; +} + +inline void CopyValue(const glTFCommon::mat4 &v, aiMatrix4x4 &o) { + o.a1 = v[0]; + o.b1 = v[1]; + o.c1 = v[2]; + o.d1 = v[3]; + o.a2 = v[4]; + o.b2 = v[5]; + o.c2 = v[6]; + o.d2 = v[7]; + o.a3 = v[8]; + o.b3 = v[9]; + o.c3 = v[10]; + o.d3 = v[11]; + o.a4 = v[12]; + o.b4 = v[13]; + o.c4 = v[14]; + o.d4 = v[15]; +} + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4310) +#endif // _MSC_VER + +inline std::string getCurrentAssetDir(const std::string &pFile) { + int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); + if (pos == int(std::string::npos)) { + return std::string(); + } + + return pFile.substr(0, pos + 1); +} +#if _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +namespace Util { + +void EncodeBase64(const uint8_t *in, size_t inLength, std::string &out); + +size_t DecodeBase64(const char *in, size_t inLength, uint8_t *&out); + +inline size_t DecodeBase64(const char *in, uint8_t *&out) { + return DecodeBase64(in, strlen(in), out); +} + +struct DataURI { + const char *mediaType; + const char *charset; + bool base64; + const char *data; + size_t dataLength; +}; + +//! Check if a uri is a data URI +bool ParseDataURI(const char *const_uri, size_t uriLen, DataURI &out); + +} // namespace Util + +#define CHECK_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; + +//! Helper struct to represent values that might not be present +template +struct Nullable { + T value; + bool isPresent; + + Nullable() : + isPresent(false) {} + Nullable(T &val) : + value(val), + isPresent(true) {} +}; + +//! A reference to one top-level object, which is valid +//! until the Asset instance is destroyed +template +class Ref { + std::vector *vector; + unsigned int index; + +public: + Ref() : + vector(0), + index(0) {} + Ref(std::vector &vec, unsigned int idx) : + vector(&vec), + index(idx) {} + + inline unsigned int GetIndex() const { return index; } + + operator bool() const { return vector != nullptr && index < vector->size(); } + + T *operator->() { return (*vector)[index]; } + + T &operator*() { return *((*vector)[index]); } +}; + +// +// JSON Value reading helpers +// + +template +struct ReadHelper { + static bool Read(Value &val, T &out) { + return val.IsInt() ? out = static_cast(val.GetInt()), true : false; + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, bool &out) { + return val.IsBool() ? out = val.GetBool(), true : false; + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, float &out) { + return val.IsNumber() ? out = static_cast(val.GetDouble()), true : false; + } +}; + +template +struct ReadHelper { + static bool Read(Value &val, float (&out)[N]) { + if (!val.IsArray() || val.Size() != N) return false; + for (unsigned int i = 0; i < N; ++i) { + if (val[i].IsNumber()) + out[i] = static_cast(val[i].GetDouble()); + } + return true; + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, const char *&out) { + return val.IsString() ? (out = val.GetString(), true) : false; + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, std::string &out) { + return val.IsString() ? (out = std::string(val.GetString(), val.GetStringLength()), true) : false; + } +}; + +template +struct ReadHelper> { + static bool Read(Value &val, Nullable &out) { + return out.isPresent = ReadHelper::Read(val, out.value); + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, uint64_t &out) { + return val.IsUint64() ? out = val.GetUint64(), true : false; + } +}; + +template <> +struct ReadHelper { + static bool Read(Value &val, int64_t &out) { + return val.IsInt64() ? out = val.GetInt64(), true : false; + } +}; + +template +inline static bool ReadValue(Value &val, T &out) { + return ReadHelper::Read(val, out); +} + +template +inline static bool ReadMember(Value &obj, const char *id, T &out) { + if (!obj.IsObject()) { + return false; + } + Value::MemberIterator it = obj.FindMember(id); + if (it != obj.MemberEnd()) { + return ReadHelper::Read(it->value, out); + } + return false; +} + +template +inline static T MemberOrDefault(Value &obj, const char *id, T defaultValue) { + T out; + return ReadMember(obj, id, out) ? out : defaultValue; +} + +inline Value *FindMember(Value &val, const char *id) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd()) ? &it->value : nullptr; +} + +template +inline void throwUnexpectedTypeError(const char (&expectedTypeName)[N], const char *memberId, const char *context, const char *extraContext) { + std::string fullContext = context; + if (extraContext && (strlen(extraContext) > 0)) { + fullContext = fullContext + " (" + extraContext + ")"; + } + throw DeadlyImportError("Member \"", memberId, "\" was not of type \"", expectedTypeName, "\" when reading ", fullContext); +} + +// Look-up functions with type checks. Context and extra context help the user identify the problem if there's an error. + +inline Value *FindStringInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsString()) { + throwUnexpectedTypeError("string", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindNumberInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsNumber()) { + throwUnexpectedTypeError("number", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindUIntInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsUint()) { + throwUnexpectedTypeError("uint", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindArrayInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsArray()) { + throwUnexpectedTypeError("array", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindObjectInContext(Value &val, const char *memberId, const char *context, const char *extraContext = nullptr) { + if (!val.IsObject()) { + return nullptr; + } + Value::MemberIterator it = val.FindMember(memberId); + if (it == val.MemberEnd()) { + return nullptr; + } + if (!it->value.IsObject()) { + throwUnexpectedTypeError("object", memberId, context, extraContext); + } + return &it->value; +} + +inline Value *FindExtensionInContext(Value &val, const char *extensionId, const char *context, const char *extraContext = nullptr) { + if (Value *extensionList = FindObjectInContext(val, "extensions", context, extraContext)) { + if (Value *extension = FindObjectInContext(*extensionList, extensionId, context, extraContext)) { + return extension; + } + } + return nullptr; +} + +// Overloads when the value is the document. + +inline Value *FindString(Document &doc, const char *memberId) { + return FindStringInContext(doc, memberId, "the document"); +} + +inline Value *FindNumber(Document &doc, const char *memberId) { + return FindNumberInContext(doc, memberId, "the document"); +} + +inline Value *FindUInt(Document &doc, const char *memberId) { + return FindUIntInContext(doc, memberId, "the document"); +} + +inline Value *FindArray(Document &val, const char *memberId) { + return FindArrayInContext(val, memberId, "the document"); +} + +inline Value *FindObject(Document &doc, const char *memberId) { + return FindObjectInContext(doc, memberId, "the document"); +} + +inline Value *FindExtension(Value &val, const char *extensionId) { + return FindExtensionInContext(val, extensionId, "the document"); +} + +inline Value *FindString(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0; +} + +inline Value *FindObject(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : 0; +} + +inline Value *FindArray(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : 0; +} + +inline Value *FindNumber(Value &val, const char *id) { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0; +} + +} // namespace glTFCommon + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // AI_GLFTCOMMON_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp b/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp new file mode 100644 index 0000000..afcfb12 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFExporter.cpp @@ -0,0 +1,1065 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER + +#include "AssetLib/glTF/glTFExporter.h" +#include "AssetLib/glTF/glTFAssetWriter.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Header files, standard library. +#include +#include +#include + +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + // Header files, Open3DGC. +# include +#endif + +using namespace rapidjson; + +using namespace Assimp; +using namespace glTF; + +namespace Assimp { + + // ------------------------------------------------------------------------------------------------ + // Worker function for exporting a scene to GLTF. Prototyped and registered in Exporter.cpp + void ExportSceneGLTF(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) + { + // invoke the exporter + glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, false); + } + + // ------------------------------------------------------------------------------------------------ + // Worker function for exporting a scene to GLB. Prototyped and registered in Exporter.cpp + void ExportSceneGLB(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) + { + // invoke the exporter + glTFExporter exporter(pFile, pIOSystem, pScene, pProperties, true); + } + +} // end of namespace Assimp + +glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiScene* pScene, + const ExportProperties* pProperties, bool isBinary) + : mFilename(filename) + , mIOSystem(pIOSystem) + , mProperties(pProperties) +{ + aiScene* sceneCopy_tmp; + SceneCombiner::CopyScene(&sceneCopy_tmp, pScene); + + SplitLargeMeshesProcess_Triangle tri_splitter; + tri_splitter.SetLimit(0xffff); + tri_splitter.Execute(sceneCopy_tmp); + + SplitLargeMeshesProcess_Vertex vert_splitter; + vert_splitter.SetLimit(0xffff); + vert_splitter.Execute(sceneCopy_tmp); + + mScene.reset(sceneCopy_tmp); + + mAsset.reset( new glTF::Asset( pIOSystem ) ); + + if (isBinary) { + mAsset->SetAsBinary(); + } + + ExportMetadata(); + + //for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {} + + //for (unsigned int i = 0; i < pScene->mNumLights; ++i) {} + + ExportMaterials(); + + if (mScene->mRootNode) { + ExportNodeHierarchy(mScene->mRootNode); + } + + ExportMeshes(); + + //for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {} + + ExportScene(); + + ExportAnimations(); + + glTF::AssetWriter writer(*mAsset); + + if (isBinary) { + writer.WriteGLBFile(filename); + } else { + writer.WriteFile(filename); + } +} + +/* + * Copy a 4x4 matrix from struct aiMatrix to typedef mat4. + * Also converts from row-major to column-major storage. + */ +static void CopyValue(const aiMatrix4x4& v, glTF::mat4& o) +{ + o[ 0] = v.a1; o[ 1] = v.b1; o[ 2] = v.c1; o[ 3] = v.d1; + o[ 4] = v.a2; o[ 5] = v.b2; o[ 6] = v.c2; o[ 7] = v.d2; + o[ 8] = v.a3; o[ 9] = v.b3; o[10] = v.c3; o[11] = v.d3; + o[12] = v.a4; o[13] = v.b4; o[14] = v.c4; o[15] = v.d4; +} + +static void CopyValue(const aiMatrix4x4& v, aiMatrix4x4& o) +{ + memcpy(&o, &v, sizeof(aiMatrix4x4)); +} + +static void IdentityMatrix4(glTF::mat4& o) +{ + o[ 0] = 1; o[ 1] = 0; o[ 2] = 0; o[ 3] = 0; + o[ 4] = 0; o[ 5] = 1; o[ 6] = 0; o[ 7] = 0; + o[ 8] = 0; o[ 9] = 0; o[10] = 1; o[11] = 0; + o[12] = 0; o[13] = 0; o[14] = 0; o[15] = 1; +} + +template +void SetAccessorRange(Ref acc, void* data, unsigned int count, + unsigned int numCompsIn, unsigned int numCompsOut) +{ + ai_assert(numCompsOut <= numCompsIn); + + // Allocate and initialize with large values. + for (unsigned int i = 0 ; i < numCompsOut ; i++) { + acc->min.push_back( std::numeric_limits::max()); + acc->max.push_back(-std::numeric_limits::max()); + } + + size_t totalComps = count * numCompsIn; + T* buffer_ptr = static_cast(data); + T* buffer_end = buffer_ptr + totalComps; + + // Search and set extreme values. + for (; buffer_ptr < buffer_end ; buffer_ptr += numCompsIn) { + for (unsigned int j = 0 ; j < numCompsOut ; j++) { + double valueTmp = buffer_ptr[j]; + + if (valueTmp < acc->min[j]) { + acc->min[j] = valueTmp; + } + if (valueTmp > acc->max[j]) { + acc->max[j] = valueTmp; + } + } + } +} + +inline void SetAccessorRange(ComponentType compType, Ref acc, void* data, + unsigned int count, unsigned int numCompsIn, unsigned int numCompsOut) +{ + switch (compType) { + case ComponentType_SHORT: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_SHORT: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_INT: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_FLOAT: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_BYTE: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + case ComponentType_UNSIGNED_BYTE: + SetAccessorRange(acc, data, count, numCompsIn, numCompsOut); + return; + } +} + +inline Ref ExportData(Asset &a, std::string &meshName, Ref &buffer, + unsigned int count, void *data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, BufferViewTarget target = BufferViewTarget_NONE) { + if (!count || !data) return Ref(); + + unsigned int numCompsIn = AttribType::GetNumComponents(typeIn); + unsigned int numCompsOut = AttribType::GetNumComponents(typeOut); + unsigned int bytesPerComp = ComponentTypeSize(compType); + + size_t offset = buffer->byteLength; + // make sure offset is correctly byte-aligned, as required by spec + size_t padding = offset % bytesPerComp; + offset += padding; + size_t length = count * numCompsOut * bytesPerComp; + buffer->Grow(length + padding); + + // bufferView + Ref bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + bv->buffer = buffer; + bv->byteOffset = unsigned(offset); + bv->byteLength = length; //! The target that the WebGL buffer should be bound to. + bv->target = target; + + // accessor + Ref acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor")); + acc->bufferView = bv; + acc->byteOffset = 0; + acc->byteStride = 0; + acc->componentType = compType; + acc->count = count; + acc->type = typeOut; + + // calculate min and max values + SetAccessorRange(compType, acc, data, count, numCompsIn, numCompsOut); + + // copy the data + acc->WriteData(count, data, numCompsIn*bytesPerComp); + + return acc; +} + +namespace { + void GetMatScalar(const aiMaterial* mat, float& val, const char* propName, int type, int idx) { + ai_assert( nullptr != mat ); + if ( nullptr != mat ) { + mat->Get(propName, type, idx, val); + } + } +} + +void glTFExporter::GetTexSampler(const aiMaterial* mat, glTF::TexProperty& prop) +{ + std::string samplerId = mAsset->FindUniqueID("", "sampler"); + prop.texture->sampler = mAsset->samplers.Create(samplerId); + + aiTextureMapMode mapU, mapV; + aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0),(int*)&mapU); + aiGetMaterialInteger(mat,AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0),(int*)&mapV); + + switch (mapU) { + case aiTextureMapMode_Wrap: + prop.texture->sampler->wrapS = SamplerWrap_Repeat; + break; + case aiTextureMapMode_Clamp: + prop.texture->sampler->wrapS = SamplerWrap_Clamp_To_Edge; + break; + case aiTextureMapMode_Mirror: + prop.texture->sampler->wrapS = SamplerWrap_Mirrored_Repeat; + break; + case aiTextureMapMode_Decal: + default: + prop.texture->sampler->wrapS = SamplerWrap_Repeat; + break; + }; + + switch (mapV) { + case aiTextureMapMode_Wrap: + prop.texture->sampler->wrapT = SamplerWrap_Repeat; + break; + case aiTextureMapMode_Clamp: + prop.texture->sampler->wrapT = SamplerWrap_Clamp_To_Edge; + break; + case aiTextureMapMode_Mirror: + prop.texture->sampler->wrapT = SamplerWrap_Mirrored_Repeat; + break; + case aiTextureMapMode_Decal: + default: + prop.texture->sampler->wrapT = SamplerWrap_Repeat; + break; + }; + + // Hard coded Texture filtering options because I do not know where to find them in the aiMaterial. + prop.texture->sampler->magFilter = SamplerMagFilter_Linear; + prop.texture->sampler->minFilter = SamplerMinFilter_Linear; +} + +void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, + const char* propName, int type, int idx, aiTextureType tt) { + aiString tex; + aiColor4D col; + if (mat->GetTextureCount(tt) > 0) { + if (mat->Get(AI_MATKEY_TEXTURE(tt, 0), tex) == AI_SUCCESS) { + std::string path = tex.C_Str(); + + if (path.size() > 0) { + if (path[0] != '*') { + std::map::iterator it = mTexturesByPath.find(path); + if (it != mTexturesByPath.end()) { + prop.texture = mAsset->textures.Get(it->second); + } + } + + if (!prop.texture) { + std::string texId = mAsset->FindUniqueID("", "texture"); + prop.texture = mAsset->textures.Create(texId); + mTexturesByPath[path] = prop.texture.GetIndex(); + + std::string imgId = mAsset->FindUniqueID("", "image"); + prop.texture->source = mAsset->images.Create(imgId); + + if (path[0] == '*') { // embedded + aiTexture* curTex = mScene->mTextures[atoi(&path[1])]; + + prop.texture->source->name = curTex->mFilename.C_Str(); + + uint8_t *data = reinterpret_cast(curTex->pcData); + prop.texture->source->SetData(data, curTex->mWidth, *mAsset); + + if (curTex->achFormatHint[0]) { + std::string mimeType = "image/"; + mimeType += (memcmp(curTex->achFormatHint, "jpg", 3) == 0) ? "jpeg" : curTex->achFormatHint; + prop.texture->source->mimeType = mimeType; + } + } else { + prop.texture->source->uri = path; + } + + GetTexSampler(mat, prop); + } + } + } + } + + if (mat->Get(propName, type, idx, col) == AI_SUCCESS) { + prop.color[0] = col.r; + prop.color[1] = col.g; + prop.color[2] = col.b; + prop.color[3] = col.a; + } +} + + +void glTFExporter::ExportMaterials() +{ + aiString aiName; + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + const aiMaterial* mat = mScene->mMaterials[i]; + + + std::string name; + if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + name = aiName.C_Str(); + } + name = mAsset->FindUniqueID(name, "material"); + + Ref m = mAsset->materials.Create(name); + + GetMatColorOrTex(mat, m->ambient, AI_MATKEY_COLOR_AMBIENT, aiTextureType_AMBIENT); + GetMatColorOrTex(mat, m->diffuse, AI_MATKEY_COLOR_DIFFUSE, aiTextureType_DIFFUSE); + GetMatColorOrTex(mat, m->specular, AI_MATKEY_COLOR_SPECULAR, aiTextureType_SPECULAR); + GetMatColorOrTex(mat, m->emission, AI_MATKEY_COLOR_EMISSIVE, aiTextureType_EMISSIVE); + + m->transparent = mat->Get(AI_MATKEY_OPACITY, m->transparency) == aiReturn_SUCCESS && m->transparency != 1.0; + + GetMatScalar(mat, m->shininess, AI_MATKEY_SHININESS); + } +} + +/* + * Search through node hierarchy and find the node containing the given meshID. + * Returns true on success, and false otherwise. + */ +bool FindMeshNode(Ref &nodeIn, Ref &meshNode, const std::string &meshID) { + for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { + if (meshID.compare(nodeIn->meshes[i]->id) == 0) { + meshNode = nodeIn; + return true; + } + } + + for (unsigned int i = 0; i < nodeIn->children.size(); ++i) { + if(FindMeshNode(nodeIn->children[i], meshNode, meshID)) { + return true; + } + } + + return false; +} + +/* + * Find the root joint of the skeleton. + * Starts will any joint node and traces up the tree, + * until a parent is found that does not have a jointName. + * Returns the first parent Ref found that does not have a jointName. + */ +Ref FindSkeletonRootJoint(Ref& skinRef) +{ + Ref startNodeRef; + Ref parentNodeRef; + + // Arbitrarily use the first joint to start the search. + startNodeRef = skinRef->jointNames[0]; + parentNodeRef = skinRef->jointNames[0]; + + do { + startNodeRef = parentNodeRef; + parentNodeRef = startNodeRef->parent; + } while (!parentNodeRef->jointName.empty()); + + return parentNodeRef; +} + +void ExportSkin(Asset& mAsset, const aiMesh* aimesh, Ref& meshRef, Ref& bufferRef, Ref& skinRef, std::vector& inverseBindMatricesData) +{ + if (aimesh->mNumBones < 1) { + return; + } + + // Store the vertex joint and weight data. + const size_t NumVerts( aimesh->mNumVertices ); + vec4* vertexJointData = new vec4[ NumVerts ]; + vec4* vertexWeightData = new vec4[ NumVerts ]; + int* jointsPerVertex = new int[ NumVerts ]; + for (size_t i = 0; i < NumVerts; ++i) { + jointsPerVertex[i] = 0; + for (size_t j = 0; j < 4; ++j) { + vertexJointData[i][j] = 0; + vertexWeightData[i][j] = 0; + } + } + + for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) { + const aiBone* aib = aimesh->mBones[idx_bone]; + + // aib->mName =====> skinRef->jointNames + // Find the node with id = mName. + Ref nodeRef = mAsset.nodes.Get(aib->mName.C_Str()); + nodeRef->jointName = nodeRef->id; + + unsigned int jointNamesIndex = 0; + bool addJointToJointNames = true; + for ( unsigned int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) { + if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) { + addJointToJointNames = false; + jointNamesIndex = idx_joint; + } + } + + if (addJointToJointNames) { + skinRef->jointNames.push_back(nodeRef); + + // aib->mOffsetMatrix =====> skinRef->inverseBindMatrices + aiMatrix4x4 tmpMatrix4; + CopyValue(aib->mOffsetMatrix, tmpMatrix4); + inverseBindMatricesData.push_back(tmpMatrix4); + jointNamesIndex = static_cast(inverseBindMatricesData.size() - 1); + } + + // aib->mWeights =====> vertexWeightData + for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) { + unsigned int vertexId = aib->mWeights[idx_weights].mVertexId; + float vertWeight = aib->mWeights[idx_weights].mWeight; + + // A vertex can only have at most four joint weights. Ignore all others. + if (jointsPerVertex[vertexId] > 3) { + continue; + } + + vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast(jointNamesIndex); + vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight; + + jointsPerVertex[vertexId] += 1; + } + + } // End: for-loop mNumMeshes + + Mesh::Primitive& p = meshRef->primitives.back(); + Ref vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( vertexJointAccessor ) { + p.attributes.joint.push_back( vertexJointAccessor ); + } + + Ref vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices, vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( vertexWeightAccessor ) { + p.attributes.weight.push_back( vertexWeightAccessor ); + } + delete[] jointsPerVertex; + delete[] vertexWeightData; + delete[] vertexJointData; +} + +#if defined(__has_warning) +#if __has_warning("-Wunused-but-set-variable") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#endif + +void glTFExporter::ExportMeshes() +{ + // Not for + // using IndicesType = decltype(aiFace::mNumIndices); + // But yes for + // using IndicesType = unsigned short; + // because "ComponentType_UNSIGNED_SHORT" used for indices. And it's a maximal type according to glTF specification. + typedef unsigned short IndicesType; + + // Variables needed for compression. BEGIN. + // Indices, not pointers - because pointer to buffer is changing while writing to it. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + size_t idx_srcdata_begin = 0; // Index of buffer before writing mesh data. Also, index of begin of coordinates array in buffer. + size_t idx_srcdata_normal = SIZE_MAX;// Index of begin of normals array in buffer. SIZE_MAX - mean that mesh has no normals. + size_t idx_srcdata_ind;// Index of begin of coordinates indices array in buffer. +#endif + std::vector idx_srcdata_tc;// Array of indices. Every index point to begin of texture coordinates array in buffer. + bool comp_allow;// Point that data of current mesh can be compressed. + // Variables needed for compression. END. + + std::string fname = std::string(mFilename); + std::string bufferIdPrefix = fname.substr(0, fname.rfind(".gltf")); + std::string bufferId = mAsset->FindUniqueID("", bufferIdPrefix.c_str()); + + Ref b = mAsset->GetBodyBuffer(); + if (!b) { + b = mAsset->buffers.Create(bufferId); + } + + //---------------------------------------- + // Initialize variables for the skin + bool createSkin = false; + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; + if(aim->HasBones()) { + createSkin = true; + break; + } + } + + Ref skinRef; + std::string skinName = mAsset->FindUniqueID("skin", "skin"); + std::vector inverseBindMatricesData; + if(createSkin) { + skinRef = mAsset->skins.Create(skinName); + skinRef->name = skinName; + } + //---------------------------------------- + + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; + + // Check if compressing requested and mesh can be encoded. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + comp_allow = mProperties->GetPropertyBool("extensions.Open3DGC.use", false); +#else + comp_allow = false; +#endif + + if(comp_allow && (aim->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) && (aim->mNumVertices > 0) && (aim->mNumFaces > 0)) + { + idx_srcdata_tc.clear(); + idx_srcdata_tc.reserve(AI_MAX_NUMBER_OF_TEXTURECOORDS); + } + else + { + std::string msg; + + if(aim->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) + msg = "all primitives of the mesh must be a triangles."; + else + msg = "mesh must has vertices and faces."; + + ASSIMP_LOG_WARN("GLTF: can not use Open3DGC-compression: ", msg); + comp_allow = false; + } + + std::string meshId = mAsset->FindUniqueID(aim->mName.C_Str(), "mesh"); + Ref m = mAsset->meshes.Create(meshId); + m->primitives.resize(1); + Mesh::Primitive& p = m->primitives.back(); + + p.material = mAsset->materials.Get(aim->mMaterialIndex); + + /******************* Vertices ********************/ + // If compression is used then you need parameters of uncompressed region: begin and size. At this step "begin" is stored. +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + if(comp_allow) idx_srcdata_begin = b->byteLength; +#endif + + Ref v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (v) p.attributes.position.push_back(v); + + /******************** Normals ********************/ +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + if(comp_allow && (aim->mNormals != 0)) idx_srcdata_normal = b->byteLength;// Store index of normals array. +#endif + + Ref n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (n) p.attributes.normal.push_back(n); + + /************** Texture coordinates **************/ + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + // Flip UV y coords + if (aim -> mNumUVComponents[i] > 1) { + for (unsigned int j = 0; j < aim->mNumVertices; ++j) { + aim->mTextureCoords[i][j].y = 1 - aim->mTextureCoords[i][j].y; + } + } + + if (aim->mNumUVComponents[i] > 0) { + AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3; + + if(comp_allow) idx_srcdata_tc.push_back(b->byteLength);// Store index of texture coordinates array. + + Ref tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); + if (tc) p.attributes.texcoord.push_back(tc); + } + } + + /*************** Vertices indices ****************/ +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + idx_srcdata_ind = b->byteLength;// Store index of indices array. +#endif + + if (aim->mNumFaces > 0) { + std::vector indices; + unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices; + indices.resize(aim->mNumFaces * nIndicesPerFace); + for (size_t i = 0; i < aim->mNumFaces; ++i) { + for (size_t j = 0; j < nIndicesPerFace; ++j) { + indices[i*nIndicesPerFace + j] = uint16_t(aim->mFaces[i].mIndices[j]); + } + } + + p.indices = ExportData(*mAsset, meshId, b, unsigned(indices.size()), &indices[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_UNSIGNED_SHORT, BufferViewTarget_ELEMENT_ARRAY_BUFFER); + } + + switch (aim->mPrimitiveTypes) { + case aiPrimitiveType_POLYGON: + p.mode = PrimitiveMode_TRIANGLES; break; // TODO implement this + case aiPrimitiveType_LINE: + p.mode = PrimitiveMode_LINES; break; + case aiPrimitiveType_POINT: + p.mode = PrimitiveMode_POINTS; break; + default: // aiPrimitiveType_TRIANGLE + p.mode = PrimitiveMode_TRIANGLES; + } + + /*************** Skins ****************/ + if(aim->HasBones()) { + ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); + } + + /****************** Compression ******************/ + ///TODO: animation: weights, joints. + if(comp_allow) + { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + // Only one type of compression supported at now - Open3DGC. + // + o3dgc::BinaryStream bs; + o3dgc::SC3DMCEncoder encoder; + o3dgc::IndexedFaceSet comp_o3dgc_ifs; + o3dgc::SC3DMCEncodeParams comp_o3dgc_params; + + // + // Fill data for encoder. + // + // Quantization + unsigned quant_coord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.POSITION", 12); + unsigned quant_normal = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.NORMAL", 10); + unsigned quant_texcoord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.TEXCOORD", 10); + + // Prediction + o3dgc::O3DGCSC3DMCPredictionMode prediction_position = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_normal = o3dgc::O3DGC_SC3DMC_SURF_NORMALS_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_texcoord = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; + + // IndexedFacesSet: "Crease angle", "solid", "convex" are set to default. + comp_o3dgc_ifs.SetCCW(true); + comp_o3dgc_ifs.SetIsTriangularMesh(true); + comp_o3dgc_ifs.SetNumFloatAttributes(0); + // Coordinates + comp_o3dgc_params.SetCoordQuantBits(quant_coord); + comp_o3dgc_params.SetCoordPredMode(prediction_position); + comp_o3dgc_ifs.SetNCoord(aim->mNumVertices); + comp_o3dgc_ifs.SetCoord((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_begin]); + // Normals + if(idx_srcdata_normal != SIZE_MAX) + { + comp_o3dgc_params.SetNormalQuantBits(quant_normal); + comp_o3dgc_params.SetNormalPredMode(prediction_normal); + comp_o3dgc_ifs.SetNNormal(aim->mNumVertices); + comp_o3dgc_ifs.SetNormal((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_normal]); + } + + // Texture coordinates + for(size_t num_tc = 0; num_tc < idx_srcdata_tc.size(); num_tc++) + { + size_t num = comp_o3dgc_ifs.GetNumFloatAttributes(); + + comp_o3dgc_params.SetFloatAttributeQuantBits(static_cast(num), quant_texcoord); + comp_o3dgc_params.SetFloatAttributePredMode(static_cast(num), prediction_texcoord); + comp_o3dgc_ifs.SetNFloatAttribute(static_cast(num), aim->mNumVertices);// number of elements. + comp_o3dgc_ifs.SetFloatAttributeDim(static_cast(num), aim->mNumUVComponents[num_tc]);// components per element: aiVector3D => x * float + comp_o3dgc_ifs.SetFloatAttributeType(static_cast(num), o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD); + comp_o3dgc_ifs.SetFloatAttribute(static_cast(num), (o3dgc::Real* const)&b->GetPointer()[idx_srcdata_tc[num_tc]]); + comp_o3dgc_ifs.SetNumFloatAttributes(static_cast(num + 1)); + } + + // Coordinates indices + comp_o3dgc_ifs.SetNCoordIndex(aim->mNumFaces); + comp_o3dgc_ifs.SetCoordIndex((IndicesType* const)&b->GetPointer()[idx_srcdata_ind]); + // Prepare to encoding + comp_o3dgc_params.SetNumFloatAttributes(comp_o3dgc_ifs.GetNumFloatAttributes()); + if(mProperties->GetPropertyBool("extensions.Open3DGC.binary", true)) + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_BINARY); + else + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_ASCII); + + comp_o3dgc_ifs.ComputeMinMax(o3dgc::O3DGC_SC3DMC_MAX_ALL_DIMS); + // + // Encoding + // + encoder.Encode(comp_o3dgc_params, comp_o3dgc_ifs, bs); + // Replace data in buffer. + b->ReplaceData(idx_srcdata_begin, b->byteLength - idx_srcdata_begin, bs.GetBuffer(), bs.GetSize()); + // + // Add information about extension to mesh. + // + // Create extension structure. + Mesh::SCompression_Open3DGC* ext = new Mesh::SCompression_Open3DGC; + + // Fill it. + ext->Buffer = b->id; + ext->Offset = idx_srcdata_begin; + ext->Count = b->byteLength - idx_srcdata_begin; + ext->Binary = mProperties->GetPropertyBool("extensions.Open3DGC.binary"); + ext->IndicesCount = comp_o3dgc_ifs.GetNCoordIndex() * 3; + ext->VerticesCount = comp_o3dgc_ifs.GetNCoord(); + // And assign to mesh. + m->Extension.push_back(ext); +#endif + }// if(comp_allow) + }// for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) + + //---------------------------------------- + // Finish the skin + // Create the Accessor for skinRef->inverseBindMatrices + if (createSkin) { + mat4* invBindMatrixData = new mat4[inverseBindMatricesData.size()]; + for ( unsigned int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) { + CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]); + } + + Ref invBindMatrixAccessor = ExportData(*mAsset, skinName, b, static_cast(inverseBindMatricesData.size()), invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT); + if (invBindMatrixAccessor) skinRef->inverseBindMatrices = invBindMatrixAccessor; + + // Identity Matrix =====> skinRef->bindShapeMatrix + // Temporary. Hard-coded identity matrix here + skinRef->bindShapeMatrix.isPresent = true; + IdentityMatrix4(skinRef->bindShapeMatrix.value); + + // Find node that contains this mesh and add "skeletons" and "skin" attributes to that node. + Ref rootNode = mAsset->nodes.Get(unsigned(0)); + Ref meshNode; + std::string meshID = mAsset->meshes.Get(unsigned(0))->id; + FindMeshNode(rootNode, meshNode, meshID); + + Ref rootJoint = FindSkeletonRootJoint(skinRef); + meshNode->skeletons.push_back(rootJoint); + meshNode->skin = skinRef; + } +} + +#if defined(__has_warning) +#if __has_warning("-Wunused-but-set-variable") +#pragma GCC diagnostic pop +#endif +#endif + +/* + * Export the root node of the node hierarchy. + * Calls ExportNode for all children. + */ +unsigned int glTFExporter::ExportNodeHierarchy(const aiNode* n) +{ + Ref node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.push_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + +/* + * Export node and recursively calls ExportNode for all children. + * Since these nodes are not the root node, we also export the parent Ref + */ +unsigned int glTFExporter::ExportNode(const aiNode* n, Ref& parent) +{ + Ref node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + node->parent = parent; + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.push_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + + +void glTFExporter::ExportScene() +{ + const char* sceneName = "defaultScene"; + Ref scene = mAsset->scenes.Create(sceneName); + + // root node will be the first one exported (idx 0) + if (mAsset->nodes.Size() > 0) { + scene->nodes.push_back(mAsset->nodes.Get(0u)); + } + + // set as the default scene + mAsset->scene = scene; +} + +void glTFExporter::ExportMetadata() +{ + glTF::AssetMetadata& asset = mAsset->asset; + asset.version = "1.0"; + + char buffer[256]; + ai_snprintf(buffer, 256, "Open Asset Import Library (assimp v%d.%d.%x)", + aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision()); + + asset.generator = buffer; + + // Copyright + aiString copyright_str; + if (mScene->mMetaData != nullptr && mScene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, copyright_str)) { + asset.copyright = copyright_str.C_Str(); + } +} + +inline void ExtractAnimationData(Asset& mAsset, std::string& animId, Ref& animRef, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond) +{ + // Loop over the data and check to see if it exactly matches an existing buffer. + // If yes, then reference the existing corresponding accessor. + // Otherwise, add to the buffer and create a new accessor. + + size_t counts[3] = { + nodeChannel->mNumPositionKeys, + nodeChannel->mNumScalingKeys, + nodeChannel->mNumRotationKeys, + }; + size_t numKeyframes = 1; + for (int i = 0; i < 3; ++i) { + if (counts[i] > numKeyframes) { + numKeyframes = counts[i]; + } + } + + //------------------------------------------------------- + // Extract TIME parameter data. + // Check if the timeStamps are the same for mPositionKeys, mRotationKeys, and mScalingKeys. + if(nodeChannel->mNumPositionKeys > 0) { + typedef float TimeType; + std::vector timeData; + timeData.resize(numKeyframes); + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes; + // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. + // Check if we have to cast type here. e.g. uint16_t() + timeData[i] = static_cast(nodeChannel->mPositionKeys[frameIndex].mTime / ticksPerSecond); + } + + Ref timeAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), &timeData[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); + if (timeAccessor) animRef->Parameters.TIME = timeAccessor; + } + + //------------------------------------------------------- + // Extract translation parameter data + if(nodeChannel->mNumPositionKeys > 0) { + C_STRUCT aiVector3D* translationData = new aiVector3D[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumPositionKeys / numKeyframes; + translationData[i] = nodeChannel->mPositionKeys[frameIndex].mValue; + } + + Ref tranAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), translationData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if ( tranAccessor ) { + animRef->Parameters.translation = tranAccessor; + } + delete[] translationData; + } + + //------------------------------------------------------- + // Extract scale parameter data + if(nodeChannel->mNumScalingKeys > 0) { + C_STRUCT aiVector3D* scaleData = new aiVector3D[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumScalingKeys / numKeyframes; + scaleData[i] = nodeChannel->mScalingKeys[frameIndex].mValue; + } + + Ref scaleAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), scaleData, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if ( scaleAccessor ) { + animRef->Parameters.scale = scaleAccessor; + } + delete[] scaleData; + } + + //------------------------------------------------------- + // Extract rotation parameter data + if(nodeChannel->mNumRotationKeys > 0) { + vec4* rotationData = new vec4[numKeyframes]; + for (size_t i = 0; i < numKeyframes; ++i) { + size_t frameIndex = i * nodeChannel->mNumRotationKeys / numKeyframes; + rotationData[i][0] = nodeChannel->mRotationKeys[frameIndex].mValue.x; + rotationData[i][1] = nodeChannel->mRotationKeys[frameIndex].mValue.y; + rotationData[i][2] = nodeChannel->mRotationKeys[frameIndex].mValue.z; + rotationData[i][3] = nodeChannel->mRotationKeys[frameIndex].mValue.w; + } + + Ref rotAccessor = ExportData(mAsset, animId, buffer, static_cast(numKeyframes), rotationData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + if ( rotAccessor ) { + animRef->Parameters.rotation = rotAccessor; + } + delete[] rotationData; + } +} + +void glTFExporter::ExportAnimations() +{ + Ref bufferRef = mAsset->buffers.Get(unsigned (0)); + + for (unsigned int i = 0; i < mScene->mNumAnimations; ++i) { + const aiAnimation* anim = mScene->mAnimations[i]; + + std::string nameAnim = "anim"; + if (anim->mName.length > 0) { + nameAnim = anim->mName.C_Str(); + } + + for (unsigned int channelIndex = 0; channelIndex < anim->mNumChannels; ++channelIndex) { + const aiNodeAnim* nodeChannel = anim->mChannels[channelIndex]; + + // It appears that assimp stores this type of animation as multiple animations. + // where each aiNodeAnim in mChannels animates a specific node. + std::string name = nameAnim + "_" + ai_to_string(channelIndex); + name = mAsset->FindUniqueID(name, "animation"); + Ref animRef = mAsset->animations.Create(name); + + /******************* Parameters ********************/ + ExtractAnimationData(*mAsset, name, animRef, bufferRef, nodeChannel, static_cast(anim->mTicksPerSecond)); + + for (unsigned int j = 0; j < 3; ++j) { + std::string channelType; + int channelSize=0; + switch (j) { + case 0: + channelType = "rotation"; + channelSize = nodeChannel->mNumRotationKeys; + break; + case 1: + channelType = "scale"; + channelSize = nodeChannel->mNumScalingKeys; + break; + case 2: + channelType = "translation"; + channelSize = nodeChannel->mNumPositionKeys; + break; + } + + if (channelSize < 1) { continue; } + + Animation::AnimChannel tmpAnimChannel; + Animation::AnimSampler tmpAnimSampler; + + tmpAnimChannel.sampler = name + "_" + channelType; + tmpAnimChannel.target.path = channelType; + tmpAnimSampler.output = channelType; + tmpAnimSampler.id = name + "_" + channelType; + + tmpAnimChannel.target.id = mAsset->nodes.Get(nodeChannel->mNodeName.C_Str()); + + tmpAnimSampler.input = "TIME"; + tmpAnimSampler.interpolation = "LINEAR"; + + animRef->Channels.push_back(tmpAnimChannel); + animRef->Samplers.push_back(tmpAnimSampler); + } + + } + + // Assimp documentation staes this is not used (not implemented) + // for (unsigned int channelIndex = 0; channelIndex < anim->mNumMeshChannels; ++channelIndex) { + // const aiMeshAnim* meshChannel = anim->mMeshChannels[channelIndex]; + // } + + } // End: for-loop mNumAnimations +} + + +#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/glTF/glTFExporter.h b/libs/assimp/code/AssetLib/glTF/glTFExporter.h new file mode 100644 index 0000000..a526954 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFExporter.h @@ -0,0 +1,118 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file GltfExporter.h + * Declares the exporter class to write a scene to a gltf/glb file + */ +#pragma once +#ifndef AI_GLTFEXPORTER_H_INC +#define AI_GLTFEXPORTER_H_INC + +#if !defined(ASSIMP_BUILD_NO_GLTF_EXPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_EXPORTER) + +#include +#include + +#include +#include +#include +#include + +struct aiScene; +struct aiNode; + +namespace glTFCommon { +template +class Ref; + +} + +namespace glTF { +class Asset; +struct TexProperty; +struct Node; + +} // namespace glTF + +namespace Assimp { +class IOSystem; +class IOStream; +class ExportProperties; + +// ------------------------------------------------------------------------------------------------ +/** Helper class to export a given scene to an glTF file. */ +// ------------------------------------------------------------------------------------------------ +class glTFExporter { +public: + /// Constructor for a specific scene to export + glTFExporter(const char *filename, IOSystem *pIOSystem, const aiScene *pScene, + const ExportProperties *pProperties, bool binary); + +private: + const char *mFilename; + IOSystem *mIOSystem; + std::shared_ptr mScene; + const ExportProperties *mProperties; + + std::map mTexturesByPath; + + std::shared_ptr mAsset; + + std::vector mBodyData; + + void WriteBinaryData(IOStream *outfile, std::size_t sceneLength); + + void GetTexSampler(const aiMaterial *mat, glTF::TexProperty &prop); + void GetMatColorOrTex(const aiMaterial *mat, glTF::TexProperty &prop, const char *propName, int type, int idx, aiTextureType tt); + void ExportMetadata(); + void ExportMaterials(); + void ExportMeshes(); + unsigned int ExportNodeHierarchy(const aiNode *n); + unsigned int ExportNode(const aiNode *node, glTFCommon::Ref & parent); + void ExportScene(); + void ExportAnimations(); +}; + +} // namespace Assimp + +#endif // ASSIMP_BUILD_NO_GLTF_EXPORTER + +#endif // AI_GLTFEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp b/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp new file mode 100644 index 0000000..81db12e --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFImporter.cpp @@ -0,0 +1,725 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#if !defined(ASSIMP_BUILD_NO_GLTF_IMPORTER) && !defined(ASSIMP_BUILD_NO_GLTF1_IMPORTER) + +#include "AssetLib/glTF/glTFImporter.h" +#include "AssetLib/glTF/glTFAsset.h" +#if !defined(ASSIMP_BUILD_NO_EXPORT) +#include "AssetLib/glTF/glTFAssetWriter.h" +#endif +#include "PostProcessing/MakeVerboseFormat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Assimp; +using namespace glTF; + +// +// glTFImporter +// + +static const aiImporterDesc desc = { + "glTF Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" +}; + +glTFImporter::glTFImporter() : + BaseImporter(), meshOffsets(), embeddedTexIdxs(), mScene(nullptr) { + // empty +} + +glTFImporter::~glTFImporter() { + // empty +} + +const aiImporterDesc *glTFImporter::GetInfo() const { + return &desc; +} + +bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { + glTF::Asset asset(pIOHandler); + try { + asset.Load(pFile, GetExtension(pFile) == "glb"); + std::string version = asset.asset.version; + return !version.empty() && version[0] == '1'; + } catch (...) { + return false; + } +} + +inline void SetMaterialColorProperty(std::vector &embeddedTexIdxs, Asset & /*r*/, glTF::TexProperty prop, aiMaterial *mat, + aiTextureType texType, const char *pKey, unsigned int type, unsigned int idx) { + if (prop.texture) { + if (prop.texture->source) { + aiString uri(prop.texture->source->uri); + + int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; + if (texIdx != -1) { // embedded + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + uri.data[0] = '*'; + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + } + + mat->AddProperty(&uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); + } + } else { + aiColor4D col; + CopyValue(prop.color, col); + mat->AddProperty(&col, 1, pKey, type, idx); + } +} + +void glTFImporter::ImportMaterials(glTF::Asset &r) { + mScene->mNumMaterials = unsigned(r.materials.Size()); + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + aiMaterial *aimat = mScene->mMaterials[i] = new aiMaterial(); + + Material &mat = r.materials[i]; + + /*if (!mat.name.empty())*/ { + aiString str(mat.id /*mat.name*/); + aimat->AddProperty(&str, AI_MATKEY_NAME); + } + + SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.specular, aimat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.emission, aimat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE); + + aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + + if (mat.transparent && (mat.transparency != 1.0f)) { + aimat->AddProperty(&mat.transparency, 1, AI_MATKEY_OPACITY); + } + + if (mat.shininess > 0.f) { + aimat->AddProperty(&mat.shininess, 1, AI_MATKEY_SHININESS); + } + } + + if (mScene->mNumMaterials == 0) { + mScene->mNumMaterials = 1; + // Delete the array of length zero created above. + delete[] mScene->mMaterials; + mScene->mMaterials = new aiMaterial *[1]; + mScene->mMaterials[0] = new aiMaterial(); + } +} + +static inline void SetFace(aiFace &face, int a) { + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; +} + +static inline void SetFace(aiFace &face, int a, int b) { + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = a; + face.mIndices[1] = b; +} + +static inline void SetFace(aiFace &face, int a, int b, int c) { + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = a; + face.mIndices[1] = b; + face.mIndices[2] = c; +} + +static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) { + for (unsigned i = 0; i < nFaces; ++i) { + for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { + unsigned idx = faces[i].mIndices[j]; + if (idx >= nVerts) + return false; + } + } + return true; +} + +void glTFImporter::ImportMeshes(glTF::Asset &r) { + std::vector meshes; + + unsigned int k = 0; + meshOffsets.clear(); + + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh &mesh = r.meshes[m]; + + // Check if mesh extensions is used + if (mesh.Extension.size() > 0) { +#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC + for (Mesh::SExtension *cur_ext : mesh.Extension) { + if (cur_ext->Type == Mesh::SExtension::EType::Compression_Open3DGC) { + // Limitations for meshes when using Open3DGC-compression. + // It's a current limitation of sp... Specification have not this part still - about mesh compression. Why only one primitive? + // Because glTF is very flexibly. But in fact it ugly flexible. Every primitive can has own set of accessors and accessors can + // point to a-a-a-a-any part of buffer (through bufferview of course) and even to another buffer. We know that "Open3DGC-compression" + // is applicable only to part of buffer. As we can't guaranty continuity of the data for decoder, we will limit quantity of primitives. + // Yes indices, coordinates etc. still can br stored in different buffers, but with current specification it's a exporter problem. + // Also primitive can has only one of "POSITION", "NORMAL" and less then "AI_MAX_NUMBER_OF_TEXTURECOORDS" of "TEXCOORD". All accessor + // of primitive must point to one continuous region of the buffer. + if (mesh.primitives.size() > 2) throw DeadlyImportError("GLTF: When using Open3DGC compression then only one primitive per mesh are allowed."); + + Mesh::SCompression_Open3DGC *o3dgc_ext = (Mesh::SCompression_Open3DGC *)cur_ext; + Ref buf = r.buffers.Get(o3dgc_ext->Buffer); + + buf->EncodedRegion_SetCurrent(mesh.id); + } else + { + throw DeadlyImportError("GLTF: Can not import mesh: unknown mesh extension (code: \"", ai_to_string(cur_ext->Type), + "\"), only Open3DGC is supported."); + } + } +#endif + } // if(mesh.Extension.size() > 0) + + meshOffsets.push_back(k); + k += unsigned(mesh.primitives.size()); + + for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { + Mesh::Primitive &prim = mesh.primitives[p]; + + aiMesh *aim = new aiMesh(); + meshes.push_back(aim); + + aim->mName = mesh.id; + if (mesh.primitives.size() > 1) { + ai_uint32 &len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + } + + switch (prim.mode) { + case PrimitiveMode_POINTS: + aim->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aim->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } + + Mesh::Primitive::Attributes &attr = prim.attributes; + + if (attr.position.size() > 0 && attr.position[0]) { + aim->mNumVertices = attr.position[0]->count; + attr.position[0]->ExtractData(aim->mVertices); + } + + if (attr.normal.size() > 0 && attr.normal[0]) attr.normal[0]->ExtractData(aim->mNormals); + + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); + + aiVector3D *values = aim->mTextureCoords[tc]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + values[i].y = 1 - values[i].y; // Flip Y coords + } + } + + aiFace *faces = 0; + unsigned int nFaces = 0; + + if (prim.indices) { + unsigned int count = prim.indices->count; + + Accessor::Indexer data = prim.indices->GetIndexer(); + ai_assert(data.IsValid()); + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], data.GetUInt(i)); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], data.GetUInt(i)); + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i)); + } + break; + } + } else { // no indices provided so directly generate from counts + + // use the already determined count as it includes checks + unsigned int count = aim->mNumVertices; + + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], i); + } + break; + } + + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], i, i + 1); + } + break; + } + + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } + + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], i, i + 1, i + 2); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], i); + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 3; i < count; ++i) { + SetFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], i); + } + break; + } + } + + if (faces) { + aim->mFaces = faces; + aim->mNumFaces = nFaces; + const bool validRes = CheckValidFacesIndices(faces, nFaces, aim->mNumVertices); + if (!validRes) { + ai_assert(validRes); + ASSIMP_LOG_WARN("Invalid number of faces detected."); + } + } + + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } + } + } + + meshOffsets.push_back(k); + + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); +} + +void glTFImporter::ImportCameras(glTF::Asset &r) { + if (!r.cameras.Size()) { + return; + } + + mScene->mNumCameras = r.cameras.Size(); + mScene->mCameras = new aiCamera *[r.cameras.Size()]; + for (size_t i = 0; i < r.cameras.Size(); ++i) { + Camera &cam = r.cameras[i]; + + aiCamera *aicam = mScene->mCameras[i] = new aiCamera(); + + if (cam.type == Camera::Perspective) { + aicam->mAspect = cam.perspective.aspectRatio; + aicam->mHorizontalFOV = cam.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mClipPlaneFar = cam.perspective.zfar; + aicam->mClipPlaneNear = cam.perspective.znear; + } else { + aicam->mClipPlaneFar = cam.ortographic.zfar; + aicam->mClipPlaneNear = cam.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mAspect = 1.0f; + if (0.f != cam.ortographic.ymag) { + aicam->mAspect = cam.ortographic.xmag / cam.ortographic.ymag; + } + } + } +} + +void glTFImporter::ImportLights(glTF::Asset &r) { + if (!r.lights.Size()) return; + + mScene->mNumLights = r.lights.Size(); + mScene->mLights = new aiLight *[r.lights.Size()]; + + for (size_t i = 0; i < r.lights.Size(); ++i) { + Light &l = r.lights[i]; + + aiLight *ail = mScene->mLights[i] = new aiLight(); + + switch (l.type) { + case Light::Type_directional: + ail->mType = aiLightSource_DIRECTIONAL; + break; + + case Light::Type_spot: + ail->mType = aiLightSource_SPOT; + break; + + case Light::Type_ambient: + ail->mType = aiLightSource_AMBIENT; + break; + + default: // Light::Type_point + ail->mType = aiLightSource_POINT; + break; + } + + CopyValue(l.color, ail->mColorAmbient); + CopyValue(l.color, ail->mColorDiffuse); + CopyValue(l.color, ail->mColorSpecular); + + ail->mAngleOuterCone = l.falloffAngle; + ail->mAngleInnerCone = l.falloffExponent; // TODO fix this, it does not look right at all + + ail->mAttenuationConstant = l.constantAttenuation; + ail->mAttenuationLinear = l.linearAttenuation; + ail->mAttenuationQuadratic = l.quadraticAttenuation; + } +} + +aiNode *ImportNode(aiScene *pScene, glTF::Asset &r, std::vector &meshOffsets, glTF::Ref &ptr) { + Node &node = *ptr; + + aiNode *ainode = new aiNode(node.id); + + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode *[ainode->mNumChildren]; + + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + aiMatrix4x4 &matrix = ainode->mTransformation; + if (node.matrix.isPresent) { + CopyValue(node.matrix.value, matrix); + } else { + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + aiMatrix4x4 t; + aiMatrix4x4::Translation(trans, t); + matrix = t * matrix; + } + + if (node.scale.isPresent) { + aiVector3D scal(1.f); + CopyValue(node.scale.value, scal); + aiMatrix4x4 s; + aiMatrix4x4::Scaling(scal, s); + matrix = s * matrix; + } + + if (node.rotation.isPresent) { + aiQuaternion rot; + CopyValue(node.rotation.value, rot); + matrix = aiMatrix4x4(rot.GetMatrix()) * matrix; + } + } + + if (!node.meshes.empty()) { + int count = 0; + for (size_t i = 0; i < node.meshes.size(); ++i) { + int idx = node.meshes[i].GetIndex(); + count += meshOffsets[idx + 1] - meshOffsets[idx]; + } + + ainode->mNumMeshes = count; + ainode->mMeshes = new unsigned int[count]; + + int k = 0; + for (size_t i = 0; i < node.meshes.size(); ++i) { + int idx = node.meshes[i].GetIndex(); + for (unsigned int j = meshOffsets[idx]; j < meshOffsets[idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } + } + + if (node.camera) { + pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; + } + + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + } + + return ainode; +} + +void glTFImporter::ImportNodes(glTF::Asset &r) { + if (!r.scene) return; + + std::vector> rootNodes = r.scene->nodes; + + // The root nodes + unsigned int numRootNodes = unsigned(rootNodes.size()); + if (numRootNodes == 1) { // a single root node: use it + mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + } else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode *root = new aiNode("ROOT"); + root->mChildren = new aiNode *[numRootNodes]; + for (unsigned int i = 0; i < numRootNodes; ++i) { + aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + mScene->mRootNode = root; + } + + //if (!mScene->mRootNode) { + // mScene->mRootNode = new aiNode("EMPTY"); + //} +} + +void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) { + embeddedTexIdxs.resize(r.images.Size(), -1); + + int numEmbeddedTexs = 0; + for (size_t i = 0; i < r.images.Size(); ++i) { + if (r.images[i].HasData()) + numEmbeddedTexs += 1; + } + + if (numEmbeddedTexs == 0) + return; + + mScene->mTextures = new aiTexture *[numEmbeddedTexs]; + + // Add the embedded textures + for (size_t i = 0; i < r.images.Size(); ++i) { + Image &img = r.images[i]; + if (!img.HasData()) continue; + + int idx = mScene->mNumTextures++; + embeddedTexIdxs[i] = idx; + + aiTexture *tex = mScene->mTextures[idx] = new aiTexture(); + + size_t length = img.GetDataLength(); + void *data = img.StealData(); + + tex->mFilename = img.name; + tex->mWidth = static_cast(length); + tex->mHeight = 0; + tex->pcData = reinterpret_cast(data); + + if (!img.mimeType.empty()) { + const char *ext = strchr(img.mimeType.c_str(), '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + + size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + } +} + +void glTFImporter::ImportCommonMetadata(glTF::Asset &a) { + ai_assert(mScene->mMetaData == nullptr); + const bool hasVersion = !a.asset.version.empty(); + const bool hasGenerator = !a.asset.generator.empty(); + const bool hasCopyright = !a.asset.copyright.empty(); + if (hasVersion || hasGenerator || hasCopyright) { + mScene->mMetaData = new aiMetadata; + if (hasVersion) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version)); + } + if (hasGenerator) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_GENERATOR, aiString(a.asset.generator)); + } + if (hasCopyright) { + mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); + } + } +} + +void glTFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + // clean all member arrays + meshOffsets.clear(); + embeddedTexIdxs.clear(); + + this->mScene = pScene; + + // read the asset file + glTF::Asset asset(pIOHandler); + asset.Load(pFile, GetExtension(pFile) == "glb"); + + // + // Copy the data out + // + + ImportEmbeddedTextures(asset); + ImportMaterials(asset); + + ImportMeshes(asset); + + ImportCameras(asset); + ImportLights(asset); + + ImportNodes(asset); + ImportCommonMetadata(asset); + + if (pScene->mNumMeshes == 0) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } +} + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER diff --git a/libs/assimp/code/AssetLib/glTF/glTFImporter.h b/libs/assimp/code/AssetLib/glTF/glTFImporter.h new file mode 100644 index 0000000..529da53 --- /dev/null +++ b/libs/assimp/code/AssetLib/glTF/glTFImporter.h @@ -0,0 +1,89 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#pragma once +#ifndef AI_GLTFIMPORTER_H_INC +#define AI_GLTFIMPORTER_H_INC + +#include +#include + +struct aiNode; + +namespace glTF { + class Asset; + +} + +namespace Assimp { + +/** + * Load the glTF format. + * https://github.com/KhronosGroup/glTF/tree/master/specification + */ +class glTFImporter : public BaseImporter { +public: + glTFImporter(); + ~glTFImporter() override; + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + const aiImporterDesc *GetInfo() const override; + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + +private: + void ImportEmbeddedTextures(glTF::Asset &a); + void ImportMaterials(glTF::Asset &a); + void ImportMeshes(glTF::Asset &a); + void ImportCameras(glTF::Asset &a); + void ImportLights(glTF::Asset &a); + void ImportNodes(glTF::Asset &a); + void ImportCommonMetadata(glTF::Asset &a); + +private: + std::vector meshOffsets; + std::vector embeddedTexIdxs; + aiScene *mScene; +}; + +} // namespace Assimp + +#endif // AI_GLTFIMPORTER_H_INC -- cgit v1.2.1