diff --git a/code/BaseImporter.h b/code/BaseImporter.h index cb6394ebc..7c34ac5fe 100644 --- a/code/BaseImporter.h +++ b/code/BaseImporter.h @@ -359,6 +359,27 @@ public: // static utilities IOStream* stream, std::vector& data); + // ------------------------------------------------------------------- + /** Utility function to move a std::vector into a aiScene array + * @param vec The vector to be moved + * @param out The output pointer to the allocated array. + * @param numOut The output count of elements copied. */ + template + AI_FORCE_INLINE + static void CopyVector( + std::vector& vec, + T*& out, + unsigned int& outLength) + { + outLength = vec.size(); + if (outLength) { + out = new T[outLength]; + std::swap_ranges(vec.begin(), vec.end(), out); + } + } + + + protected: /** Error description in case there was one. */ diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 94298f0f9..a778d57f1 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -563,12 +563,14 @@ ADD_ASSIMP_IMPORTER(X ) ADD_ASSIMP_IMPORTER(GLTF + glTFAsset.h + glTFAsset.inl + glTFAssetWriter.h + glTFAssetWriter.inl + glTFImporter.cpp glTFImporter.h - glTFUtil.cpp - glTFUtil.h - glTFFileData.h - + glTFExporter.h glTFExporter.cpp ) @@ -655,7 +657,6 @@ SET ( openddl_parser_SRCS ) SOURCE_GROUP( openddl_parser FILES ${openddl_parser_SRCS}) - INCLUDE_DIRECTORIES( "../contrib/rapidjson/include" ) # VC2010 fixes diff --git a/code/Exporter.cpp b/code/Exporter.cpp index 1d84e0dd0..215fa671c 100644 --- a/code/Exporter.cpp +++ b/code/Exporter.cpp @@ -139,9 +139,9 @@ Exporter::ExportFormatEntry gExporters[] = #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, - aiProcess_JoinIdenticalVertices), + aiProcess_JoinIdenticalVertices | aiProcess_SortByPType), Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, - aiProcess_JoinIdenticalVertices), + aiProcess_JoinIdenticalVertices | aiProcess_SortByPType), #endif #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER diff --git a/code/glTFAsset.h b/code/glTFAsset.h new file mode 100644 index 000000000..6f407bf7f --- /dev/null +++ b/code/glTFAsset.h @@ -0,0 +1,945 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, 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 + +#include +#include +#include +#include +#include + +#define RAPIDJSON_HAS_STDSTRING 1 +#include +#include +#include + +#ifdef ASSIMP_API +# include "boost/shared_ptr.hpp" +# include "DefaultIOSystem.h" +# include "ByteSwapper.h" +#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 _MSC_VER > 1600 +# define gltf_unordered_map unordered_map +# else +# define gltf_unordered_map tr1::unordered_map +# endif +#endif + +namespace glTF +{ +#ifdef ASSIMP_API + using Assimp::IOStream; + using Assimp::IOSystem; + using boost::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 + { + FILE* f; + public: + IOStream(FILE* file) : f(file) {} + ~IOStream() { fclose(f); f = 0; } + + 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)); + } + }; +#endif + + using rapidjson::Value; + using rapidjson::Document; + + class Asset; + class AssetWriter; + + struct BufferView; // here due to cross-reference + struct Texture; + struct Light; + + + // Vec/matrix types, as raw float arrays + typedef float (vec3)[3]; + typedef float (vec4)[4]; + typedef float (mat4)[16]; + + + 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 + inline bool ParseDataURI(const char* uri, size_t uriLen, DataURI& out); + } + + + //! Magic number for GLB files + #define AI_GLB_MAGIC_NUMBER "glTF" + + #ifdef ASSIMP_API + #include "./../include/assimp/Compiler/pushpack1.h" + #endif + + //! 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; + + #ifdef ASSIMP_API + #include "./../include/assimp/Compiler/poppack1.h" + #endif + + + //! 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_FLOAT = 5126 + }; + + inline size_t ComponentTypeSize(ComponentType t) + { + switch (t) { + case ComponentType_SHORT: + case ComponentType_UNSIGNED_SHORT: + return 2; + + case ComponentType_FLOAT: + return 4; + + //case Accessor::ComponentType_BYTE: + //case Accessor::ComponentType_UNSIGNED_BYTE: + default: + return 1; + } + } + + //! Values for the BufferView::target field + enum BufferViewTarget + { + BufferViewTarget_ARRAY_BUFFER = 34962, + BufferViewTarget_ELEMENT_ARRAY_BUFFER = 34963 + }; + + //! 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 }; + + 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]; }; + + public: + 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; + } + }; + + // 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 } + }; + + + + //! A reference to one top-level object, which is valid + //! until the Asset instance is destroyed + template + class Ref + { + std::vector* vector; + int index; + + public: + Ref() : vector(0), index(0) {} + Ref(std::vector& vec, int idx) : vector(&vec), index(idx) {} + + inline size_t GetIndex() const + { return index; } + + operator bool() const + { return vector != 0; } + + T* operator->() + { return (*vector)[index]; } + + T& operator*() + { return *((*vector)[index]); } + }; + + //! 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) {} + }; + + + //! Base classe 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; } + + virtual ~Object() {} + }; + + + + // + // 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 + void 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; + + Accessor& accessor; + 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 Indexer GetIndexer() + { + return Indexer(*this); + } + + Accessor() {} + void Read(Value& obj, Asset& r); + }; + + + struct Animation : public Object + { + struct Channel + { + + }; + + struct Target + { + + }; + + struct Sampler + { + + }; + }; + + //! A buffer points to binary geometry, animation, or skins. + struct Buffer : public Object + { + public: + + enum Type + { + Type_arraybuffer, + Type_text + }; + + //std::string uri; //!< The uri of the buffer. Can be a filepath, a data uri, etc. (required) + size_t byteLength; //!< The length of the buffer in bytes. (default: 0) + //std::string type; //!< XMLHttpRequest responseType (default: "arraybuffer") + + Type type; + + private: + shared_ptr mData; //!< Pointer to the data + bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) + + public: + Buffer(); + + void Read(Value& obj, Asset& r); + + void LoadFromStream(IOStream& stream, size_t length = 0, size_t baseOffset = 0); + + 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; } + }; + + + //! 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. + + BufferView() {} + 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; + + private: + uint8_t* mData; + size_t mDataLength; + + 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; } + + inline uint8_t* StealData(); + + inline void SetData(uint8_t* data, size_t length, Asset& r); + }; + + //! 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< Ref > AccessorList; + + struct Primitive + { + PrimitiveMode mode; + + struct Attributes { + AccessorList position, normal, texcoord, color, joint, jointmatrix, weight; + } attributes; + + Ref indices; + + Ref material; + }; + + std::vector primitives; + + Mesh() {} + void Read(Value& obj, Asset& r); + }; + + struct Node : public Object + { + std::vector< Ref > children; + std::vector< Ref > meshes; + + Nullable matrix; + Nullable translation; + Nullable rotation; + Nullable scale; + + Ref camera; + Ref light; + + Node() {} + void Read(Value& obj, Asset& r); + }; + + struct Program : public Object + { + Program() {} + void Read(Value& obj, Asset& r); + }; + + + struct Sampler : public Object + { + Sampler() {} + void Read(Value& obj, Asset& r); + }; + + struct Scene : public Object + { + std::vector< Ref > nodes; + + Scene() {} + void Read(Value& obj, Asset& r); + }; + + struct Shader : public Object + { + Shader() {} + void Read(Value& obj, Asset& r); + }; + + struct Skin : public Object + { + Skin() {} + void Read(Value& obj, Asset& r); + }; + + struct Technique : public Object + { + struct Parameters + { + + }; + + struct States + { + + }; + + struct Functions + { + + }; + + Technique() {} + void Read(Value& obj, Asset& r); + }; + + //! A texture and its sampler. + struct Texture : public Object + { + //Ref source; //!< The ID of the sampler used by this texture. (required) + Ref source; //!< The ID of the image used by this texture. (required) + + //TextureFormat format; //!< The texture's format. (default: TextureFormat_RGBA) + //TextureFormat internalFormat; //!< The texture's internal format. (default: TextureFormat_RGBA) + + //TextureTarget target; //!< The target that the WebGL texture should be bound to. (default: TextureTarget_TEXTURE_2D) + //TextureType type; //!< Texel datatype. (default: TextureType_UNSIGNED_BYTE) + + Texture() {} + 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() {} + void Read(Value& obj, Asset& r); + + void SetDefaults(); + }; + + //! Base class for LazyDict that acts as an interface + class LazyDictBase + { + public: + virtual ~LazyDictBase() {} + + virtual void AttachToDocument(Document& doc) = 0; + virtual void DetachFromDocument() = 0; + + virtual void WriteObjects(AssetWriter& writer) = 0; + }; + + //! (Stub class that is specialized in glTFAssetWriter.h) + template + struct LazyDictWriter + { + static void Write(T& 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< std::string, size_t > Dict; + + std::vector mObjs; //! The read objects + Dict mObjsById; //! The read objects accesible 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(); + + void WriteObjects(AssetWriter& writer) + { LazyDictWriter< LazyDict >::Write(*this, writer); } + + Ref Add(T* obj); + + public: + LazyDict(Asset& asset, const char* dictId, const char* extId = 0); + ~LazyDict(); + + Ref Get(const char* id); + Ref Get(size_t i); + + Ref Create(const char* id); + Ref Create(const std::string& id) + { return Create(id.c_str()); } + + inline size_t Size() const + { return 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: {}) + + int version; //!< The glTF format version (should be 1) + + void Read(Document& doc); + }; + + // + // glTF Asset class + // + + //! Root object for a glTF asset + class Asset + { + typedef std::gltf_unordered_map IdMap; + + 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 programs; + //LazyDict samplers; + LazyDict scenes; + //LazyDict shaders; + //LazyDict skins; + //LazyDict techniques; + LazyDict textures; + + LazyDict lights; // KHR_materials_common ext + + Ref scene; + + public: + Asset(IOSystem* io = 0) + : mIOSystem(io) + , 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") + //, programs (*this, "programs") + //, samplers (*this, "samplers") + , scenes (*this, "scenes") + //, shaders (*this, "shaders") + //, skins (*this, "skins") + //, techniques (*this, "techniques") + , textures (*this, "textures") + , lights (*this, "lights", "KHR_materials_common") + { + memset(&extensionsUsed, 0, sizeof(extensionsUsed)); + memset(&asset, 0, sizeof(asset)); + } + + //! 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(std::string path, const char* mode, bool absolute = false); + }; + +} + +// Include the implementation of the methods +#include "glTFAsset.inl" + +#endif diff --git a/code/glTFAsset.inl b/code/glTFAsset.inl new file mode 100644 index 000000000..df5fa8721 --- /dev/null +++ b/code/glTFAsset.inl @@ -0,0 +1,1215 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, 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. + +---------------------------------------------------------------------- +*/ + +namespace glTF { + +namespace { + + // + // 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 (size_t 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 = val.GetString(), true : false; + }}; + + template struct ReadHelper< Nullable > { static bool Read(Value& val, Nullable& out) { + return out.isPresent = ReadHelper::Read(val, out.value); + }}; + + 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) + { + 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) + { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd()) ? &it->value : 0; + } + + 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* FindArray(Value& val, const char* id) + { + Value::MemberIterator it = val.FindMember(id); + return (it != val.MemberEnd() && it->value.IsArray()) ? &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; + } +} + +// +// 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(size_t i) +{ + return Ref(mObjs, i); +} + +template +Ref LazyDict::Get(const char* 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) { + return Ref(); // section is missing + } + + Value::MemberIterator obj = mDict->FindMember(id); + if (obj == mDict->MemberEnd()) { + throw DeadlyImportError("Missing object with id \"" + std::string(id) + "\" in \"" + mDictId + "\""); + } + if (!obj->value.IsObject()) { + throw DeadlyImportError("Object with id \"" + std::string(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) +{ + size_t idx = 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("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), mIsSpecial(false) +{ } + + +inline void Buffer::Read(Value& obj, Asset& r) +{ + size_t statedLength = MemberOrDefault(obj, "byteLength", 0); + byteLength = statedLength; + + Value* it = FindString(obj, "uri"); + if (!it) return; + + const char* uri = it->GetString(); + + Util::DataURI dataURI; + if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { + if (dataURI.base64) { + uint8_t* data = 0; + this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data); + this->mData.reset(data); + + if (statedLength > 0 && this->byteLength != statedLength) { + // error? + } + } + } + else { // Local file + if (byteLength > 0) { + IOStream* file = r.OpenFile(uri, "rb"); + if (file) { + LoadFromStream(*file, byteLength); + delete file; + } + } + } +} + +inline void 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]); + + if (stream.Read(mData.get(), byteLength, 1) != 1) { + throw DeadlyImportError("Unable to load buffer from file!"); + } +} + +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; + uint8_t* b = new uint8_t[byteLength + amount]; + if (mData) memcpy(b, mData.get(), byteLength); + mData.reset(b); + byteLength += amount; +} + + +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); +} + + + +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 ComponentTypeSize(componentType); +} + +inline unsigned int Accessor::GetElementSize() +{ + return GetNumComponents() * GetBytesPerComponent(); +} + +inline uint8_t* Accessor::GetPointer() +{ + if (!bufferView || !bufferView->buffer) return 0; + + size_t offset = byteOffset + bufferView->byteOffset; + return bufferView->buffer->GetPointer() + 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; + } + } + } +} + +template +void Accessor::ExtractData(T*& outData) +{ + uint8_t* data = GetPointer(); + ai_assert(data); + + 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); + } + } +} + +inline void Accessor::WriteData(size_t count, 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< uint8_t*>(buffer_ptr + offset); + + ai_assert(dst + count*dst_stride <= buffer_ptr + bufferView->buffer->byteLength); + CopyData(count, 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) + , mData(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 = new uint8_t[mDataLength]; + memcpy(mData, bv->buffer->GetPointer() + bv->byteOffset, mDataLength); + } + } + } + } + } + + if (!mDataLength) { + if (Value* uri = FindString(obj, "uri")) { + const char* uristr = uri->GetString(); + + Util::DataURI dataURI; + if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) { + mimeType = dataURI.mediaType; + if (dataURI.base64) { + mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, mData); + } + } + else { + this->uri = uristr; + } + } + } +} + +inline uint8_t* Image::StealData() +{ + uint8_t* data = mData; + mDataLength = 0; + mData = 0; + return data; +} + +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 + this->mData = data; + this->mDataLength = length; + } +} + +inline void Texture::Read(Value& obj, Asset& r) +{ + const char* sourcestr; + if (ReadMember(obj, "source", sourcestr)) { + source = r.images.Get(sourcestr); + } +} + +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); + } + } + } +} + +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, "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; + } + + ReadMaterialProperty(r, *ext, "ambient", this->ambient); + ReadMaterialProperty(r, *ext, "diffuse", this->diffuse); + ReadMaterialProperty(r, *ext, "specular", this->specular); + + ReadMember(*ext, "doubleSided", doubleSided); + ReadMember(*ext, "transparent", transparent); + ReadMember(*ext, "transparency", transparency); + ReadMember(*ext, "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; } +} + +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; + } +} + +inline void Mesh::Read(Value& obj, Asset& r) +{ + if (Value* primitives = FindArray(obj, "primitives")) { + this->primitives.resize(primitives->Size()); + for (unsigned int i = 0; i < primitives->Size(); ++i) { + Value& primitive = (*primitives)[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] = r.accessors.Get(it->value.GetString()); + } + } + } + + if (Value* indices = FindString(primitive, "indices")) { + prim.indices = r.accessors.Get(indices->GetString()); + } + + if (Value* material = FindString(primitive, "material")) { + prim.material = r.materials.Get(material->GetString()); + } + } + } +} + + +inline void Camera::Read(Value& obj, Asset& r) +{ + type = MemberOrDefault(obj, "type", Camera::Perspective); + + const char* subobjId = (type == Camera::Orthographic) ? "ortographic" : "perspective"; + + Value* it = FindObject(obj, subobjId); + if (!it) throw DeadlyImportError("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(obj, "xmag", 1.f); + ortographic.ymag = MemberOrDefault(obj, "ymag", 1.f); + ortographic.zfar = MemberOrDefault(obj, "zfar", 100.f); + ortographic.znear = MemberOrDefault(obj, "znear", 0.01f); + } +} + +inline void Light::Read(Value& obj, Asset& r) +{ + SetDefaults(); + + if (Value* type = FindString(obj, "type")) { + const char* t = type->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 (Value* children = FindArray(obj, "children")) { + this->children.reserve(children->Size()); + for (unsigned int i = 0; i < children->Size(); ++i) { + Value& child = (*children)[i]; + if (child.IsString()) { + // get/create the child node + Ref chn = r.nodes.Get(child.GetString()); + if (chn) this->children.push_back(chn); + } + } + } + + + if (Value* matrix = FindArray(obj, "matrix")) { + ReadValue(*matrix, this->matrix); + } + else { + ReadMember(obj, "translation", translation); + ReadMember(obj, "scale", scale); + ReadMember(obj, "rotation", rotation); + } + + if (Value* meshes = FindArray(obj, "meshes")) { + size_t numMeshes = (size_t)meshes->Size(); + + std::vector meshList; + + this->meshes.reserve(numMeshes); + for (size_t i = 0; i < numMeshes; ++i) { + if ((*meshes)[i].IsString()) { + Ref mesh = r.meshes.Get((*meshes)[i].GetString()); + if (mesh) this->meshes.push_back(mesh); + } + } + } + + if (Value* camera = FindString(obj, "camera")) { + this->camera = r.cameras.Get(camera->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")) { + if (Value* light = FindString(*ext, "light")) { + this->light = r.lights.Get(light->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. + int statedVersion = 0; + if (Value* obj = FindObject(doc, "asset")) { + ReadMember(*obj, "copyright", copyright); + ReadMember(*obj, "generator", generator); + + premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false); + statedVersion = MemberOrDefault(*obj, "version", 0); + + if (Value* profile = FindObject(*obj, "profile")) { + ReadMember(*profile, "api", this->profile.api); + ReadMember(*profile, "version", this->profile.version); + } + } + + version = std::max(statedVersion, version); + if (version == 0) { + // if missing version, we'll assume version 1... + version = 1; + } + + if (version != 1) { + char msg[128]; + sprintf(msg, "Unsupported glTF version: %d", version); + throw DeadlyImportError(msg); + } +} + + + +// +// Asset methods implementation +// + +inline void Asset::ReadBinaryHeader(IOStream& stream) +{ + GLB_Header header; + if (stream.Read(&header, sizeof(header), 1) != 1) { + throw DeadlyImportError("Unable to read the file header"); + } + + if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { + throw DeadlyImportError("Invalid binary glTF file"); + } + + AI_SWAP4(header.version); + asset.version = header.version; + if (header.version != 1) { + throw DeadlyImportError("Unsupported binary glTF version"); + } + + AI_SWAP4(header.sceneFormat); + if (header.sceneFormat != SceneFormat_JSON) { + throw DeadlyImportError("Unsupported binary glTF scene format"); + } + + AI_SWAP4(header.length); + AI_SWAP4(header.sceneLength); + + mSceneLength = static_cast(header.sceneLength); + + 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); + + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("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; + } + + + // read the scene data + + std::vector sceneData(mSceneLength + 1); + sceneData[mSceneLength] = '\0'; + + if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + throw DeadlyImportError("Could not read the file contents"); + } + + + // parse the JSON document + + Document doc; + doc.ParseInsitu(&sceneData[0]); + + if (doc.HasParseError()) { + char buffer[32]; + sprintf(buffer, "%d", static_cast(doc.GetErrorOffset())); + throw DeadlyImportError(std::string("JSON parse error, offset ") + buffer + ": " + + GetParseError_En(doc.GetParseError())); + } + + if (!doc.IsObject()) { + throw DeadlyImportError("gltf file must be a JSON object!"); + } + + // Fill the buffer instance for the current file embedded contents + if (mBodyLength > 0) { + mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset); + } + + + // 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 + if (Value* scene = FindString(doc, "scene")) { + this->scene = scenes.Get(scene->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("KHR_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; + } + } + + #define CHECK_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; + + CHECK_EXT(KHR_binary_glTF); + CHECK_EXT(KHR_materials_common); + + #undef CHECK_EXT +} + +inline IOStream* Asset::OpenFile(std::string path, const char* mode, bool absolute) +{ + #ifdef ASSIMP_API + 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; + + Asset::IdMap::iterator it; + + do { + if (!id.empty()) { + it = mUsedIds.find(id); + if (it == mUsedIds.end()) break; + + id += "_"; + } + + id += suffix; + + it = mUsedIds.find(id); + if (it == mUsedIds.end()) break; + + char buffer[256]; + int offset = sprintf(buffer, "%s_", id.c_str()); + for (int i = 0; it != mUsedIds.end(); ++i) { + sprintf(buffer + offset, "%d", i); + + id = buffer; + it = mUsedIds.find(id); + } + } while (false); // fake loop to allow using "break" + + return id; +} + +namespace Util +{ + + inline bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) + { + 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] = i; + for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {} + } + while (uri[i] == ';' && i < uriLen) { + uri[i++] = '\0'; + for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {} + + if (strncmp(uri + j, "charset=", 8) == 0) uri[2] = j + 8; + else if (strncmp(uri + j, "base64", 6) == 0) uri[3] = j; + } + if (i < uriLen) { + uri[i++] = '\0'; + uri[4] = 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; + } + + template + struct DATA + { + static const uint8_t tableDecodeBase64[128]; + }; + + template + const uint8_t DATA::tableDecodeBase64[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 + }; + + inline char EncodeCharBase64(uint8_t b) + { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; + } + + inline uint8_t DecodeCharBase64(char c) + { + return DATA::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? + /*if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return 64; // '-' */ + } + + inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out) + { + ai_assert(inLength % 4 == 0); + + if (inLength < 4) { + out = 0; + return 0; + } + + int nEquals = int(in[inLength - 1] == '=') + + int(in[inLength - 2] == '='); + + size_t outLength = (inLength * 3) / 4 - nEquals; + out = new uint8_t[outLength]; + memset(out, 0, outLength); + + size_t i, j = 0; + + for (i = 0; i + 4 < inLength; i += 4) { + uint8_t b0 = DecodeCharBase64(in[i]); + uint8_t b1 = DecodeCharBase64(in[i + 1]); + uint8_t b2 = DecodeCharBase64(in[i + 2]); + uint8_t b3 = DecodeCharBase64(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + out[j++] = (uint8_t)((b2 << 6) | b3); + } + + { + uint8_t b0 = DecodeCharBase64(in[i]); + uint8_t b1 = DecodeCharBase64(in[i + 1]); + uint8_t b2 = DecodeCharBase64(in[i + 2]); + uint8_t b3 = DecodeCharBase64(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); + } + + return outLength; + } + + + + inline void EncodeBase64( + const uint8_t* in, size_t inLength, + std::string& out) + { + size_t outLength = ((inLength + 2) / 3) * 4; + + size_t j = out.size(); + out.resize(j + outLength); + + for (size_t i = 0; i < inLength; i += 3) { + uint8_t b = (in[i] & 0xFC) >> 2; + out[j++] = EncodeCharBase64(b); + + b = (in[i] & 0x03) << 4; + if (i + 1 < inLength) { + b |= (in[i + 1] & 0xF0) >> 4; + out[j++] = EncodeCharBase64(b); + + b = (in[i + 1] & 0x0F) << 2; + if (i + 2 < inLength) { + b |= (in[i + 2] & 0xC0) >> 6; + out[j++] = EncodeCharBase64(b); + + b = in[i + 2] & 0x3F; + out[j++] = EncodeCharBase64(b); + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + } + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + out[j++] = '='; + } + } + } + +} + +} + + diff --git a/code/glTFUtil.h b/code/glTFAssetWriter.h similarity index 62% rename from code/glTFUtil.h rename to code/glTFAssetWriter.h index 089632ca1..f934288b5 100644 --- a/code/glTFUtil.h +++ b/code/glTFAssetWriter.h @@ -37,49 +37,53 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef AI_GLTFUTIL_H_INC -#define AI_GLTFUTIL_H_INC -//#include "StreamReader.h" -//#include "MemoryIOWrapper.h" -#include "StringComparison.h" +/** @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 _MSC_VER > 1500 || (defined __GNUC___) -# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP -# else -# define gltf_unordered_map map -# define gltf_unordered_multimap multimap -#endif +#include "glTFAsset.h" -#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP -# include -# if _MSC_VER > 1600 -# define gltf_unordered_map unordered_map -# define gltf_unordered_multimap unordered_multimap -# else -# define gltf_unordered_map tr1::unordered_map -# define gltf_unordered_multimap tr1::unordered_multimap -# endif -#endif +namespace glTF +{ -namespace Assimp { -namespace glTF { +using rapidjson::MemoryPoolAllocator; - // - // Misc - // +class AssetWriter +{ + template + friend struct LazyDictWriter; - std::size_t DecodeBase64(const char* in, uint8_t*& out); - std::size_t DecodeBase64(const char* in, std::size_t inLength, uint8_t*& out); +private: - void EncodeBase64(const uint8_t* in, std::size_t inLength, std::string& out); + void WriteBinaryData(IOStream* outfile, size_t sceneLength); + void WriteMetadata(); + void WriteExtensionsUsed(); - bool IsDataURI(const char* uri); + template + void WriteObjects(LazyDict& d); + +public: + Document mDoc; + Asset& mAsset; + + MemoryPoolAllocator<>& mAl; + + AssetWriter(Asset& asset); + + void WriteFile(const char* path); +}; } -} +// Include the implementation of the methods +#include "glTFAssetWriter.inl" -#endif // AI_GLTFUTIL_H_INC - +#endif diff --git a/code/glTFAssetWriter.inl b/code/glTFAssetWriter.inl new file mode 100644 index 000000000..1d067809c --- /dev/null +++ b/code/glTFAssetWriter.inl @@ -0,0 +1,497 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2015, 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 + +namespace glTF { + + using rapidjson::StringBuffer; + using rapidjson::PrettyWriter; + using rapidjson::Writer; + using rapidjson::StringRef; + using rapidjson::StringRef; + + namespace { + + template + inline Value& MakeValue(Value& val, float(&r)[N], MemoryPoolAllocator<>& al) { + val.SetArray(); + val.Reserve(N, al); + for (int i = 0; i < N; ++i) { + val.PushBack(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(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); + } + + inline void Write(Value& obj, Animation& a, AssetWriter& w) + { + + } + + inline void Write(Value& obj, Buffer& b, AssetWriter& w) + { + std::string dataURI = "data:application/octet-stream;base64,"; + Util::EncodeBase64(b.GetPointer(), b.byteLength, dataURI); + + const char* type; + switch (b.type) { + case Buffer::Type_text: + type = "text"; break; + default: + type = "arraybuffer"; + } + + obj.AddMember("byteLength", b.byteLength, w.mAl); + obj.AddMember("type", StringRef(type), w.mAl); + obj.AddMember("uri", Value(dataURI, 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", bv.byteOffset, w.mAl); + obj.AddMember("byteLength", bv.byteLength, w.mAl); + 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,"; + Util::EncodeBase64(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); + + 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]; + sprintf(buffer, "%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) + { + Value primitives; + primitives.SetArray(); + primitives.Reserve(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); + } + + inline void Write(Value& obj, Program& b, AssetWriter& w) + { + + } + + inline void Write(Value& obj, Sampler& b, AssetWriter& w) + { + + } + + 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) + { + + } + + 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); + } + } + + inline void Write(Value& obj, Light& b, AssetWriter& w) + { + + } + + + 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); + } + } + + void AssetWriter::WriteFile(const char* path) + { + bool isBinary = mAsset.extensionsUsed.KHR_binary_glTF; + + boost::scoped_ptr outfile + (mAsset.OpenFile(path, isBinary ? "wb" : "wt", true)); + + if (outfile == 0) { + throw DeadlyExportError("Could not open output file: " + std::string(path)); + } + + if (isBinary) { + // we will write the header later, skip its size + outfile->Seek(sizeof(GLB_Header), aiOrigin_SET); + } + + StringBuffer docBuffer; + + bool pretty = true; + if (!isBinary && pretty) { + PrettyWriter writer(docBuffer); + mDoc.Accept(writer); + } + else { + Writer writer(docBuffer); + mDoc.Accept(writer); + } + + if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { + throw DeadlyExportError("Failed to write scene data!"); + } + + if (isBinary) { + WriteBinaryData(outfile.get(), docBuffer.GetSize()); + } + } + + 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 = sizeof(header) + sceneLength + bodyLength; + AI_SWAP4(header.length); + + header.sceneLength = 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!"); + } + } + + + void AssetWriter::WriteMetadata() + { + Value asset; + asset.SetObject(); + { + asset.AddMember("version", mAsset.asset.version, mAl); + + asset.AddMember("generator", Value(mAsset.asset.generator, mAl).Move(), mAl); + } + mDoc.AddMember("asset", asset, mAl); + } + + 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 + struct LazyDictWriter< LazyDict > + { + static void Write(LazyDict& d, AssetWriter& w) + { + w.WriteObjects(d); + } + }; + +} + + diff --git a/code/glTFExporter.cpp b/code/glTFExporter.cpp index eb133cccb..f11c51362 100644 --- a/code/glTFExporter.cpp +++ b/code/glTFExporter.cpp @@ -56,20 +56,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include -#define RAPIDJSON_HAS_STDSTRING 1 -#include -#include -#include -#include - -#include "glTFFileData.h" -#include "glTFUtil.h" +#include "glTFAssetWriter.h" using namespace rapidjson; using namespace Assimp; -using namespace Assimp::glTF; +using namespace glTF; namespace Assimp { @@ -93,307 +87,279 @@ namespace Assimp { -class glTFSceneExporter -{ - typedef std::gltf_unordered_map IdMap; - - Document& mDoc; - MemoryPoolAllocator<>& mAl; - - const aiScene* mScene; - - std::string mRootNodeId; - - std::vector mMeshIds; - - IdMap mUsedIds; - -public: - glTFSceneExporter(Document& doc, const aiScene* pScene) - : mDoc(doc) - , mAl(doc.GetAllocator()) - , mScene(pScene) - { - doc.SetObject(); - - for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) { - - } - - for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { - - } - - for (unsigned int i = 0; i < pScene->mNumLights; ++i) { - - } - - for (unsigned int i = 0; i < pScene->mNumLights; ++i) { - - } - - for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { - - } - - - AddMeshes(); - - for (unsigned int i = 0; i < pScene->mNumTextures; ++i) { - - } - - AddNodes(); - - CreateScene(); - } - - inline void Pushf(Value& val, float f) - { - val.PushBack(Value(f).Move(), mAl); - } - - inline void SetMatrix(Value& v, const aiMatrix4x4& m) - { - v.SetArray(); - v.Reserve(16, mAl); - - Pushf(v, m.a1); Pushf(v, m.b1); Pushf(v, m.c1); Pushf(v, m.d1); - Pushf(v, m.a2); Pushf(v, m.b2); Pushf(v, m.c2); Pushf(v, m.d2); - Pushf(v, m.a3); Pushf(v, m.b3); Pushf(v, m.c3); Pushf(v, m.d3); - Pushf(v, m.a4); Pushf(v, m.b4); Pushf(v, m.c4); Pushf(v, m.d4); - } - - void AddMeshes() - { - if (mScene->mNumMeshes == 0) return; - - Value meshes; - meshes.SetObject(); - - mMeshIds.reserve(mScene->mNumMeshes); - for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) { - aiMesh* m = mScene->mMeshes[i]; - std::string meshId = FindID(m->mName, "mesh"); - mMeshIds.push_back(meshId); - - Value mesh; - mesh.SetObject(); - { - Value primitives; - primitives.SetObject(); - - - mesh.AddMember("primitives", primitives, mAl); - } - - meshes.AddMember(StringRef(mMeshIds.back()), mesh, mAl); - } - - mDoc.AddMember("meshes", meshes, mAl); - } - - void AddNodes() - { - if (!mScene->mRootNode) return; - - Value nodes; - nodes.SetObject(); - - mRootNodeId = AddNode(nodes, mScene->mRootNode); - - mDoc.AddMember("nodes", nodes, mAl); - } - - - std::string AddNode(Value& nodes, const aiNode* n) - { - std::string nodeId = FindID(n->mName, "node"); - - Value node; - node.SetObject(); - - if (!n->mTransformation.IsIdentity()) { - Value matrix; - SetMatrix(matrix, n->mTransformation); - node.AddMember("matrix", matrix, mAl); - } - - if (n->mNumMeshes > 0) { - Value meshes; - meshes.SetArray(); - for (unsigned int i = 0; i < n->mNumMeshes; ++i) { - meshes.PushBack(StringRef(mMeshIds[n->mMeshes[i]]), mAl); - } - node.AddMember("meshes", meshes, mAl); - } - - if (n->mNumChildren > 0) { - Value children; - children.SetArray(); - for (unsigned int i = 0; i < n->mNumChildren; ++i) { - std::string id = AddNode(nodes, n->mChildren[i]); - children.PushBack(Value(id, mAl).Move(), mAl); - } - node.AddMember("children", children, mAl); - } - - nodes.AddMember(Value(nodeId, mAl).Move(), node, mAl); - - return nodeId; - } - - void CreateScene() - { - const char* sceneName = "defaultScene"; - - mDoc.AddMember("scene", Value(sceneName, mAl).Move(), mAl); - - Value scenes; - scenes.SetObject(); - { - Value scene; - scene.SetObject(); - { - Value nodes; - nodes.SetArray(); - - if (!mRootNodeId.empty()) { - nodes.PushBack(StringRef(mRootNodeId), mAl); - } - - scene.AddMember("nodes", nodes, mAl); - } - scenes.AddMember(Value(sceneName, mAl).Move(), scene, mAl); - } - mDoc.AddMember("scenes", scenes, mAl); - } - - std::string FindID(const aiString& str, const char* suffix) - { - std::string id = str.C_Str(); - - IdMap::iterator it; - - do { - if (!id.empty()) { - it = mUsedIds.find(id); - if (it == mUsedIds.end()) break; - - id += "-"; - } - - id += suffix; - - it = mUsedIds.find(id); - if (it == mUsedIds.end()) break; - - char buffer[256]; - int offset = sprintf(buffer, "%s-", id.c_str()); - for (int i = 0; it == mUsedIds.end(); ++i) { - ASSIMP_itoa10(buffer + offset, sizeof(buffer), i); - - id = buffer; - it = mUsedIds.find(id); - } - } while (false); // fake loop to allow using "break" - - mUsedIds[id] = true; - return id; - } -}; - - - glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties, bool isBinary) : mFilename(filename) , mIOSystem(pIOSystem) , mScene(pScene) , mProperties(pProperties) - , mIsBinary(isBinary) { - boost::scoped_ptr outfile(pIOSystem->Open(mFilename, "wt")); - if (outfile == 0) { - throw DeadlyExportError("Could not open output file: " + std::string(mFilename)); - } + boost::scoped_ptr asset(new glTF::Asset(pIOSystem)); + mAsset = asset.get(); if (isBinary) { - // we will write the header later, skip its size - outfile->Seek(sizeof(GLB_Header), aiOrigin_SET); + asset->SetAsBinary(); } + ExportMetadata(); - Document doc; - StringBuffer docBuffer; - { - glTFSceneExporter exportScene(doc, mScene); + //for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) {} - bool pretty = true; - if (!isBinary && pretty) { - PrettyWriter writer(docBuffer); - doc.Accept(writer); - } - else { - Writer writer(docBuffer); - doc.Accept(writer); - } + //for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {} + + //for (unsigned int i = 0; i < pScene->mNumLights; ++i) {} + + + ExportMaterials(); + + ExportMeshes(); + + //for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {} + + + if (mScene->mRootNode) { + ExportNode(mScene->mRootNode); } - if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) { - throw DeadlyExportError("Failed to write scene data!"); - } + ExportScene(); - if (isBinary) { - WriteBinaryData(outfile.get(), docBuffer.GetSize()); - } + + glTF::AssetWriter writer(*mAsset); + writer.WriteFile(filename); } -void glTFExporter::WriteBinaryData(IOStream* outfile, std::size_t sceneLength) +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; +} + +inline Ref ExportData(Asset& a, std::string& meshName, Ref& buffer, + unsigned int count, void* data, AttribType::Value typeIn, AttribType::Value typeOut, ComponentType compType, bool isIndices = false) { - // - // write the body data - // + if (!count || !data) return Ref(); - if (!mBodyData.empty()) { - std::size_t bodyOffset = sizeof(GLB_Header) + sceneLength; - bodyOffset = (bodyOffset + 3) & ~3; // Round up to next multiple of 4 + unsigned int numCompsIn = AttribType::GetNumComponents(typeIn); + unsigned int numCompsOut = AttribType::GetNumComponents(typeOut); + unsigned int bytesPerComp = ComponentTypeSize(compType); - outfile->Seek(bodyOffset, aiOrigin_SET); + size_t offset = buffer->byteLength; + size_t length = count * numCompsOut * bytesPerComp; + buffer->Grow(length); - if (outfile->Write(&mBodyData[0], mBodyData.size(), 1) != 1) { - throw DeadlyExportError("Failed to write body data!"); + // bufferView + Ref bv = a.bufferViews.Create(a.FindUniqueID(meshName, "view")); + bv->buffer = buffer; + bv->byteOffset = 0; + bv->byteLength = length; //! The target that the WebGL buffer should be bound to. + bv->target = isIndices ? BufferViewTarget_ELEMENT_ARRAY_BUFFER : BufferViewTarget_ARRAY_BUFFER; + + // accessor + Ref acc = a.accessors.Create(a.FindUniqueID(meshName, "accessor")); + acc->bufferView = bv; + acc->byteOffset = offset; + acc->byteStride = 0; + acc->componentType = compType; + acc->count = count; + acc->type = typeOut; + + // 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) { + if (mat->Get(propName, type, idx, val) == AI_SUCCESS) {} + } +} + +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* tex = mScene->mTextures[atoi(&path[1])]; + + uint8_t* data = reinterpret_cast(tex->pcData); + prop.texture->source->SetData(data, tex->mWidth, *mAsset); + + if (tex->achFormatHint[0]) { + std::string mimeType = "image/"; + mimeType += (memcmp(tex->achFormatHint, "jpg", 3) == 0) ? "jpeg" : tex->achFormatHint; + prop.texture->source->mimeType = mimeType; + } + } + else { + prop.texture->source->uri = path; + } + } + } } } - - // - // write the header - // - - outfile->Seek(0, aiOrigin_SET); - - GLB_Header header; - memcpy(header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)); - - header.version = 1; - AI_SWAP4(header.version); - - header.length = sizeof(header) + sceneLength + mBodyData.size(); - AI_SWAP4(header.length); - - header.sceneLength = sceneLength; - AI_SWAP4(header.sceneLength); - - header.sceneFormat = SceneFormat_JSON; - AI_SWAP4(header.sceneFormat); - - if (outfile->Write(&header, sizeof(header), 1) != 1) { - throw DeadlyExportError("Failed to write the header!"); + 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); + + GetMatScalar(mat, m->shininess, AI_MATKEY_SHININESS); + } +} + +void glTFExporter::ExportMeshes() +{ + for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) { + const aiMesh* aim = mScene->mMeshes[i]; + + 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); + + std::string bufferId = mAsset->FindUniqueID(meshId, "buffer"); + + Ref b = mAsset->GetBodyBuffer(); + if (!b) { + b = mAsset->buffers.Create(bufferId); + } + + Ref v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if (v) p.attributes.position.push_back(v); + + Ref n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT); + if (n) p.attributes.normal.push_back(n); + + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (aim->mNumUVComponents[i] > 0) { + AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3; + Ref tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i], AttribType::VEC3, type, ComponentType_FLOAT, true); + if (tc) p.attributes.texcoord.push_back(tc); + } + } + + if (aim->mNumFaces > 0) { + unsigned int nIndicesPerFace = aim->mFaces[0].mNumIndices; + std::vector indices; + 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, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_UNSIGNED_SHORT); + } + + 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; + } + } +} + +size_t glTFExporter::ExportNode(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) { + size_t idx = ExportNode(n->mChildren[i]); + 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(size_t(0))); + } + + // set as the default scene + mAsset->scene = scene; +} + +void glTFExporter::ExportMetadata() +{ + glTF::AssetMetadata& asset = mAsset->asset; + asset.version = 1; + + char buffer[256]; + sprintf(buffer, "Open Asset Import Library (assimp v%d.%d.%d)", + aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision()); + + asset.generator = buffer; +} + + + + diff --git a/code/glTFExporter.h b/code/glTFExporter.h index 39dbf04d6..4b916cc83 100644 --- a/code/glTFExporter.h +++ b/code/glTFExporter.h @@ -45,11 +45,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_GLTFEXPORTER_H_INC #include +#include #include #include #include +#include "boost/scoped_ptr.hpp" + + struct aiScene; +struct aiNode; +struct aiMaterial; + +namespace glTF +{ + class Asset; + + struct TexProperty; +} namespace Assimp { @@ -57,7 +70,6 @@ namespace Assimp class IOStream; class ExportProperties; - // ------------------------------------------------------------------------------------------------ /** Helper class to export a given scene to an glTF file. */ // ------------------------------------------------------------------------------------------------ @@ -74,12 +86,21 @@ namespace Assimp IOSystem* mIOSystem; const aiScene* mScene; const ExportProperties* mProperties; - bool mIsBinary; + + std::map mTexturesByPath; + + glTF::Asset* mAsset; std::vector mBodyData; void WriteBinaryData(IOStream* outfile, std::size_t sceneLength); + void GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, const char* propName, int type, int idx, aiTextureType tt); + void ExportMetadata(); + void ExportMaterials(); + void ExportMeshes(); + size_t ExportNode(const aiNode* node); + void ExportScene(); }; } diff --git a/code/glTFFileData.h b/code/glTFFileData.h deleted file mode 100644 index 2a124d377..000000000 --- a/code/glTFFileData.h +++ /dev/null @@ -1,122 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2015, 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_GLTFFILEDATA_H_INC -#define AI_GLTFFILEDATA_H_INC - -#include - -namespace Assimp { -namespace glTF { - - -//! Magic number for GLB files -#define AI_GLB_MAGIC_NUMBER "glTF" - - -#include "./../include/assimp/Compiler/pushpack1.h" - -// KHR_binary_glTF (binary .glb file) -// 20-byte header (+ the JSON + a "body" data section) -struct GLB_Header -{ - //! Magic number: "glTF" - unsigned char magic[4]; // "glTF" - - //! Version number (always 1 as of the last update) - uint32_t version; - - //! Total length of the Binary glTF, including header, scene, and body, in bytes - uint32_t length; - - //! Length, in bytes, of the glTF scene - uint32_t sceneLength; - - //! Specifies the format of the glTF scene (see the SceneFormat enum) - uint32_t sceneFormat; -} PACK_STRUCT; - -#include "./../include/assimp/Compiler/poppack1.h" - - - -//! 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 accessors component type field -enum ComponentType -{ - ComponentType_BYTE = 5120, - ComponentType_UNSIGNED_BYTE = 5121, - ComponentType_SHORT = 5122, - ComponentType_UNSIGNED_SHORT = 5123, - ComponentType_FLOAT = 5126 -}; - - -//! Will hold the enabled extensions -struct Extensions -{ - bool KHR_binary_glTF; -}; - - -} // end namespaces -} - - -#endif // AI_GLTFFILEDATA_H_INC - diff --git a/code/glTFImporter.cpp b/code/glTFImporter.cpp index 2df1ea458..e8f8db96b 100644 --- a/code/glTFImporter.cpp +++ b/code/glTFImporter.cpp @@ -41,1059 +41,34 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER #include "glTFImporter.h" -#include "StreamReader.h" -#include "DefaultIOSystem.h" -#include +#include "StringComparison.h" + +#include "boost/scoped_ptr.hpp" #include #include #include #include +#include -#include "glTFFileData.h" -#include "glTFUtil.h" - -#define RAPIDJSON_HAS_STDSTRING 1 -#include -#include -#include - -using namespace rapidjson; +#include "glTFAsset.h" using namespace Assimp; -using namespace Assimp::glTF; - - -using boost::shared_ptr; -using boost::scoped_ptr; - -// (cannot typedef' templated classes, and "using" is c++11) -#define Ptr shared_ptr - -// (used everywhere, and cannot use "auto") -typedef rapidjson::Value::MemberIterator MemIt; +using namespace glTF; // -// JSON Value reading helpers +// glTFImporter // - -#define GETF(VAL, OUT) { if ((VAL).IsNumber()) (OUT) = static_cast((VAL).GetDouble()); } - -template -struct ReadHelper { }; - -template<> struct ReadHelper { static bool Read(Value& val, int& out) { - return val.IsInt() ? val.GetInt(), true : false; -}}; - -template<> struct ReadHelper { static bool Read(Value& val, unsigned int& out) { - return val.IsInt() ? out = static_cast(val.GetInt()), 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, 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 = val.GetString(), true : false; -}}; - -template<> struct ReadHelper { static bool Read(Value& v, aiColor3D& out) { - if (!v.IsArray() || v.Size() < 3) return false; - GETF(v[0], out.r); GETF(v[1], out.g); GETF(v[2], out.b); - return true; -}}; - -template<> struct ReadHelper { static bool Read(Value& v, aiVector3D& out) { - if (!v.IsArray() || v.Size() != 3) return false; - GETF(v[0], out.x); GETF(v[1], out.y); GETF(v[2], out.z); - return true; -}}; - -template<> struct ReadHelper { static bool Read(Value& v, aiQuaternion& out) { - if (!v.IsArray() || v.Size() != 4) return false; - GETF(v[0], out.x); GETF(v[1], out.y); GETF(v[2], out.z); GETF(v[3], out.w); - return true; -}}; - -template<> struct ReadHelper { static bool Read(Value& v, aiMatrix4x4& o) { - if (!v.IsArray() || v.Size() != 16) return false; - GETF(v[ 0], o.a1); GETF(v[ 1], o.b1); GETF(v[ 2], o.c1); GETF(v[ 3], o.d1); - GETF(v[ 4], o.a2); GETF(v[ 5], o.b2); GETF(v[ 6], o.c2); GETF(v[ 7], o.d2); - GETF(v[ 8], o.a3); GETF(v[ 9], o.b3); GETF(v[10], o.c3); GETF(v[11], o.d3); - GETF(v[12], o.a4); GETF(v[13], o.b4); GETF(v[14], o.c4); GETF(v[15], o.d4); - return true; -}}; - -template -inline static bool Read(Value& val, T& out) -{ - return ReadHelper::Read(val, out); -} - -template -inline static bool ReadMember(Value& obj, const char* id, T& out) -{ - MemIt it = obj.FindMember(id); - if (it != obj.MemberEnd()) { - return ReadHelper::Read(it->value, out); - } - return false; -} - -template -inline static T TryReadMember(Value& obj, const char* id, T defaultValue) -{ - T out; - return ReadMember(obj, id, out) ? out : defaultValue; -} - - -//! References a sequence of loaded elements (e.g. meshes) -typedef std::pair Range; - - - -// -// glTFReader class -// - -//! Manages lazy loading of the glTF top-level objects, and keeps a reference to them by ID -template< class T, class INST, T(INST::*FACTORY_FN)(const char*, Value&)> -class LazyDict -{ - typedef typename std::gltf_unordered_map Map; - - Value* mDict; //! JSON dictionary object - const char* mDictId; //! ID of the dictionary object - INST& mInstance; //! The reader object instance - Map mReadObjs; //! The read objects - -public: - LazyDict(INST& instance, const char* dictId) - : mDictId(dictId), mInstance(instance) - { - Document& doc = mInstance.GetDocument(); - - MemIt it = doc.FindMember(dictId); - mDict = (it != doc.MemberEnd() && it->value.IsObject()) ? &it->value : 0; - } - - T Get(const char* id) - { - if (!mDict) return T(); // section was missing - - typename Map::iterator it = mReadObjs.find(id); - if (it != mReadObjs.end()) { // already created? - return it->second; - } - - // read it from the JSON object - MemIt obj = mDict->FindMember(id); - if (obj == mDict->MemberEnd()) { - throw DeadlyImportError("Missing object with id \"" + std::string(id) + "\" in \"" + mDictId + "\""); - } - - // create an instance of the given type - T val = (mInstance.*FACTORY_FN)(id, obj->value); - mReadObjs[id] = val; - return val; - } -}; - -struct Buffer; -struct BufferView; -struct Accessor; - -struct Image; -struct Texture; - -//! Handles the reading of the glTF JSON document -class glTFReader -{ - aiScene* mScene; - Document& mDoc; - IOSystem& mIO; - - // Vectors of imported objects, will be copied to mScene - std::vector mImpMaterials; - std::vector mImpMeshes; - std::vector mImpTextures; - - Extensions mExtensions; - - Ptr mBodyBuffer; //! Special buffer containing the body data - - - Ptr LoadBuffer(const char* id, Value& obj); - Ptr LoadBufferView(const char* id, Value& obj); - Ptr LoadAccessor(const char* id, Value& obj); - - Ptr LoadImage(const char* id, Value& obj); - Ptr LoadTexture(const char* id, Value& obj); - - aiNode* LoadNode(const char* id, Value& node); - Range LoadMesh(const char* id, Value& mesh); - unsigned int LoadMaterial(const char* id, Value& material); - - typedef glTFReader T; // (to shorten next declarations) - - LazyDict, T, &T::LoadAccessor> mAccessors; - //LazyDict mAnimations; - //LazyDict mAssets; - LazyDict, T, &T::LoadBuffer> mBuffers; - LazyDict, T, &T::LoadBufferView> mBufferViews; - //LazyDict mCameras; - LazyDict, T, &T::LoadImage> mImages; - LazyDict mMaterials; - LazyDict mMeshes; - LazyDict mNodes; - //LazyDict, T, &T::LoadProgram> mPrograms; - //LazyDict, T, &T::LoadSampler> mSamplers; - //LazyDict, T, &T::LoadShader> mShaders; - //LazyDict, T, &T::LoadSkin> mSkins; - //LazyDict,T, &T::LoadTechnique> mTechniques; - LazyDict, T, &T::LoadTexture> mTextures; - - - void LoadScene(Value& scene) - { - MemIt nodesm = scene.FindMember("nodes"); - if (nodesm != scene.MemberEnd() && nodesm->value.IsArray()) { - Value& nodes = nodesm->value; - - unsigned int numRootNodes = nodes.Size(); - if (numRootNodes == 1) { // a single root node: use it - if (nodes[0].IsString()) { - mScene->mRootNode = mNodes.Get(nodes[0].GetString()); - } - } - 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) { - if (nodes[i].IsString()) { - aiNode* node = mNodes.Get(nodes[i].GetString()); - if (node) { - node->mParent = root; - root->mChildren[root->mNumChildren++] = node; - } - } - } - mScene->mRootNode = root; - } - } - - //if (!mScene->mRootNode) { - //mScene->mRootNode = new aiNode("EMPTY"); - //} - } - - void SetMaterialColorProperty(aiMaterial* mat, Value& vals, const char* propName, aiTextureType texType, - const char* pKey, unsigned int type, unsigned int idx); - - void CopyData() - { - // TODO: it does not split the loaded vertices, should it? - mScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; - - if (mImpMaterials.empty()) { - mImpMaterials.push_back(new aiMaterial()); - } - - if (mImpMaterials.size()) { - mScene->mNumMaterials = mImpMaterials.size(); - mScene->mMaterials = new aiMaterial*[mImpMaterials.size()]; - std::swap_ranges(mImpMaterials.begin(), mImpMaterials.end(), mScene->mMaterials); - } - - if (mImpMeshes.size()) { - mScene->mNumMeshes = mImpMeshes.size(); - mScene->mMeshes = new aiMesh*[mImpMeshes.size()]; - std::swap_ranges(mImpMeshes.begin(), mImpMeshes.end(), mScene->mMeshes); - } - - if (mImpTextures.size()) { - mScene->mNumTextures = mImpTextures.size(); - mScene->mTextures = new aiTexture*[mImpTextures.size()]; - std::swap_ranges(mImpTextures.begin(), mImpTextures.end(), mScene->mTextures); - } - } - -public: - glTFReader(aiScene* scene, Document& document, IOSystem& iohandler, shared_ptr& bodyBuff) : - mScene(scene), - mDoc(document), - mIO(iohandler), - mBodyBuffer(bodyBuff), - mAccessors(*this, "accessors"), - //mAnimations(*this, "animations"), - //mAssets(*this, "assets"), - mBuffers(*this, "buffers"), - mBufferViews(*this, "bufferViews"), - //mCameras(*this, "cameras"), - mImages(*this, "images"), - mMaterials(*this, "materials"), - mMeshes(*this, "meshes"), - mNodes(*this, "nodes"), - //mPrograms(*this, "programs"), - //mSamplers(*this, "samplers"), - //mShaders(*this, "shaders"), - //mSkins(*this, "skins"), - //mTechniques(*this, "techniques"), - mTextures(*this, "textures") - { - memset(&mExtensions, 0, sizeof(mExtensions)); - } - - Document& GetDocument() - { - return mDoc; - } - - //! Main function - void Load() - { - // read the used extensions - MemIt extensionsUsed = mDoc.FindMember("extensionsUsed"); - if (extensionsUsed != mDoc.MemberEnd() && extensionsUsed->value.IsArray()) { - std::gltf_unordered_map exts; - - for (unsigned int i = 0; i < extensionsUsed->value.Size(); ++i) { - if (extensionsUsed->value[i].IsString()) { - exts[extensionsUsed->value[i].GetString()] = true; - } - } - - if (exts.find("KHR_binary_glTF") != exts.end()) { - mExtensions.KHR_binary_glTF = true; - } - } - - - const char* sceneId = 0; - - // the "scene" property specifies which scene to load - { - MemIt scene = mDoc.FindMember("scene"); - if (scene != mDoc.MemberEnd() && scene->value.IsString()) { - sceneId = scene->value.GetString(); - } - } - - MemIt scene; - - MemIt scenes = mDoc.FindMember("scenes"); - if (scenes != mDoc.MemberEnd() && scenes->value.IsObject()) { - if (sceneId) { - scene = scenes->value.FindMember(sceneId); - if (scene == scenes->value.MemberEnd()) { - //ThrowException("Missing scene!"); - } - } - else { // if not specified, use the first one - scene = scenes->value.MemberBegin(); - } - } - - if (scene != scenes->value.MemberEnd()) { - LoadScene(scene->value); - } - - CopyData(); - } -}; - -struct Buffer -{ -private: - std::size_t byteLength; - shared_ptr data; - -public: - Buffer(shared_ptr& d, std::size_t length) - : data(d), byteLength(length) - { } - - std::size_t GetLength() const - { - return byteLength; - } - - uint8_t* GetPointer() - { - return data.get(); - } - - static Buffer* FromStream(IOStream& stream, std::size_t length = 0, std::size_t baseOffset = 0) - { - if (!length) { - length = stream.FileSize(); - } - - if (baseOffset) { - stream.Seek(baseOffset, aiOrigin_SET); - } - - shared_ptr data(new uint8_t[length]); - - - if (stream.Read(data.get(), length, 1) != 1) { - throw DeadlyImportError("Unable to load buffer from file!"); - } - - return new Buffer(data, length); - } -}; - - -Ptr glTFReader::LoadBuffer(const char* id, Value& obj) -{ - if (!obj.IsObject()) return Ptr(); - - if (mExtensions.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { - return mBodyBuffer; - } - else { - const char* uri = TryReadMember(obj, "uri", 0); - - Buffer* b = 0; - if (IsDataURI(uri)) { - const char* comma = strchr(uri, ','); - *const_cast(comma) = '\0'; - - bool isBase64 = (strstr(uri, "base64") != 0); - if (isBase64) { - uint8_t* data; - std::size_t dataLen = DecodeBase64(comma + 1, data); - shared_ptr dataptr(data); - b = new Buffer(dataptr, dataLen); - } - } - else if (uri) { // Local file - unsigned int byteLength = TryReadMember(obj, "byteLength", 0u); - - scoped_ptr file(mIO.Open(uri)); - b = Buffer::FromStream(*file.get(), byteLength); - } - return Ptr(b); - } -} - - -struct BufferView -{ - Ptr buffer; - unsigned int byteOffset; - unsigned int byteLength; - - BufferView() {} - - BufferView(Value& obj) - { - Read(obj); - } - - void Read(Value& obj) - { - if (!obj.IsObject()) return; - - } - -}; - -Ptr glTFReader::LoadBufferView(const char* id, Value& obj) -{ - if (!obj.IsObject()) return Ptr(); - - - const char* bufferId = TryReadMember(obj, "buffer", 0); - if (!bufferId) return Ptr(); - - BufferView* bv = new BufferView(); - - bv->buffer = mBuffers.Get(bufferId); - bv->byteOffset = TryReadMember(obj, "byteOffset", 0u); - bv->byteLength = TryReadMember(obj, "byteLength", 0u); - - return Ptr(bv); -} - - -struct Accessor -{ - Ptr bufferView; - unsigned int byteOffset; - unsigned int byteStride; - ComponentType componentType; - unsigned int count; - std::string type; // "SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4" - //unsigned int max; - ///unsigned int min; - - unsigned int numComponents; - unsigned int bytesPerComponent; - unsigned int elemSize; - - uint8_t* data; - - inline uint8_t* GetPointer() - { - if (!bufferView || !bufferView->buffer) return 0; - - std::size_t offset = byteOffset + bufferView->byteOffset; - return bufferView->buffer->GetPointer() + offset; - } - - template - void ExtractData(T*& outData, unsigned int* outCount = 0, unsigned int* outComponents = 0) - { - ai_assert(data); - - const std::size_t totalSize = elemSize * count; - - const std::size_t targetElemSize = sizeof(T); - ai_assert(elemSize <= targetElemSize); - - ai_assert(count*byteStride <= bufferView->byteLength); - - outData = new T[count]; - if (byteStride == elemSize && targetElemSize == elemSize) { - memcpy(outData, data, totalSize); - } - else { - for (std::size_t i = 0; i < count; ++i) { - memcpy(outData + i, data + i*byteStride, elemSize); - } - } - - if (outCount) *outCount = count; - if (outComponents) *outComponents = numComponents; - } - - //! Gets the i-th value as defined by the accessor - template - T GetValue(int i) - { - ai_assert(data); - ai_assert(i*byteStride < bufferView->byteLength); - T value = T(); - memcpy(&value, data + i*byteStride, elemSize); - //value >>= 8 * (sizeof(T) - elemSize); - return value; - } - - //! Gets the i-th value as defined by the accessor - unsigned int GetUInt(int i) - { - return GetValue(i); - } -}; - -Ptr glTFReader::LoadAccessor(const char* id, Value& obj) -{ - if (!obj.IsObject()) return Ptr(); - - Accessor* a = new Accessor(); - - const char* bufferViewId = TryReadMember(obj, "bufferView", 0); - if (bufferViewId) { - a->bufferView = mBufferViews.Get(bufferViewId); - } - - int compType = TryReadMember(obj, "componentType", unsigned(ComponentType_BYTE)); - - a->byteOffset = TryReadMember(obj, "byteOffset", 0u); - a->byteStride = TryReadMember(obj, "byteStride", 0u); - a->componentType = static_cast(compType); - a->count = TryReadMember(obj, "count", 0u); - a->type = TryReadMember(obj, "type", ""); - - a->numComponents = 1; // "SCALAR" - if (a->type == "VEC2") a->numComponents = 2; - else if (a->type == "VEC3") a->numComponents = 3; - else if (a->type == "VEC4") a->numComponents = 4; - else if (a->type == "MAT2") a->numComponents = 4; - else if (a->type == "MAT3") a->numComponents = 9; - else if (a->type == "MAT4") a->numComponents = 16; - - switch (a->componentType) { - case ComponentType_SHORT: - case ComponentType_UNSIGNED_SHORT: - a->bytesPerComponent = 2; - break; - - case ComponentType_FLOAT: - a->bytesPerComponent = 4; - break; - - //case Accessor::ComponentType_BYTE: - //case Accessor::ComponentType_UNSIGNED_BYTE: - default: - a->bytesPerComponent = 1; - } - - a->elemSize = a->numComponents * a->bytesPerComponent; - if (!a->byteStride) a->byteStride = a->elemSize; - - a->data = a->GetPointer(); - - return Ptr(a); -} - -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; -} - -Range glTFReader::LoadMesh(const char* id, Value& mesh) -{ - Range range; - range.first = mImpMeshes.size(); - range.second = mImpMeshes.size(); - - MemIt primitives = mesh.FindMember("primitives"); - if (primitives != mesh.MemberEnd() && primitives->value.IsArray()) { - for (unsigned int i = 0; i < primitives->value.Size(); ++i) { - Value& primitive = primitives->value[i]; - - aiMesh* aimesh = new aiMesh(); - mImpMeshes.push_back(aimesh); - ++range.second; - - MemIt mode = primitive.FindMember("mode"); - if (mode != primitive.MemberEnd() && mode->value.IsInt()) { - switch (mode->value.GetInt()) { - case PrimitiveMode_POINTS: - aimesh->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; - - case PrimitiveMode_LINES: - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: - aimesh->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; - - case PrimitiveMode_TRIANGLES: - case PrimitiveMode_TRIANGLE_STRIP: - case PrimitiveMode_TRIANGLE_FAN: - aimesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; - } - } - - MemIt attrs = primitive.FindMember("attributes"); - if (attrs != primitive.MemberEnd() && attrs->value.IsObject()) { - for (MemIt it = attrs->value.MemberBegin(); it != attrs->value.MemberEnd(); ++it) { - if (!it->value.IsString()) continue; - const char* attr = it->name.GetString(); - const char* accessorId = it->value.GetString(); - - Ptr accessor = mAccessors.Get(accessorId); - if (!accessor) continue; - - if (strcmp(attr, "POSITION") == 0) { - accessor->ExtractData(aimesh->mVertices, &aimesh->mNumVertices); - } - else if (strcmp(attr, "NORMAL") == 0) { - accessor->ExtractData(aimesh->mNormals); - } - else if (strncmp(attr, "TEXCOORD_", 9) == 0) { - int idx = attr[9] - '0'; - if (idx >= 0 && idx <= AI_MAX_NUMBER_OF_TEXTURECOORDS) { - accessor->ExtractData(aimesh->mTextureCoords[idx], 0, &aimesh->mNumUVComponents[idx]); - } - } - } - } - - MemIt indices = primitive.FindMember("indices"); - if (indices != primitive.MemberEnd() && indices->value.IsString()) { - Ptr acc = mAccessors.Get(indices->value.GetString()); - if (acc) { - aiFace* faces = 0; - std::size_t nFaces = 0; - - int primitiveMode = mode->value.GetInt(); - switch (primitiveMode) { - case PrimitiveMode_POINTS: { - nFaces = acc->count; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < acc->count; ++i) { - setFace(faces[i], acc->GetUInt(i)); - } - break; - } - - case PrimitiveMode_LINES: { - nFaces = acc->count / 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < acc->count; i += 2) { - setFace(faces[i / 2], acc->GetUInt(i), acc->GetUInt(i + 1)); - } - break; - } - - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: { - nFaces = acc->count - ((primitiveMode == PrimitiveMode_LINE_STRIP) ? 1 : 0); - faces = new aiFace[nFaces]; - setFace(faces[0], acc->GetUInt(0), acc->GetUInt(1)); - for (unsigned int i = 2; i < acc->count; ++i) { - setFace(faces[i - 1], faces[i - 2].mIndices[1], acc->GetUInt(i)); - } - if (primitiveMode == PrimitiveMode_LINE_LOOP) { // close the loop - setFace(faces[acc->count - 1], faces[acc->count - 2].mIndices[1], faces[0].mIndices[0]); - } - break; - } - - case PrimitiveMode_TRIANGLES: { - nFaces = acc->count / 3; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < acc->count; i += 3) { - setFace(faces[i / 3], acc->GetUInt(i), acc->GetUInt(i + 1), acc->GetUInt(i + 2)); - } - break; - } - case PrimitiveMode_TRIANGLE_STRIP: { - nFaces = acc->count - 2; - faces = new aiFace[nFaces]; - setFace(faces[0], acc->GetUInt(0), acc->GetUInt(1), acc->GetUInt(2)); - for (unsigned int i = 3; i < acc->count; ++i) { - setFace(faces[i - 2], faces[i - 1].mIndices[1], faces[i - 1].mIndices[2], acc->GetUInt(i)); - } - break; - } - case PrimitiveMode_TRIANGLE_FAN: - nFaces = acc->count - 2; - faces = new aiFace[nFaces]; - setFace(faces[0], acc->GetUInt(0), acc->GetUInt(1), acc->GetUInt(2)); - for (unsigned int i = 3; i < acc->count; ++i) { - setFace(faces[i - 2], faces[0].mIndices[0], faces[i - 1].mIndices[2], acc->GetUInt(i)); - } - break; - } - - if (faces) { - aimesh->mFaces = faces; - aimesh->mNumFaces = nFaces; - } - } - } - - - MemIt material = primitive.FindMember("material"); - if (material != primitive.MemberEnd() && material->value.IsString()) { - aimesh->mMaterialIndex = mMaterials.Get(material->value.GetString()); - } - } - } - - return range; -} - - -struct Image -{ - aiString uri; -}; - -struct Texture -{ - Ptr source; -}; - -Ptr glTFReader::LoadImage(const char* id, Value& obj) -{ - - Image* img = new Image(); - - std::size_t embeddedDataLen = 0; - uint8_t* embeddedData = 0; - const char* mimeType = 0; - - // Check for extensions first (to detect binary embedded data) - MemIt extensions = obj.FindMember("extensions"); - if (extensions != obj.MemberEnd()) { - Value& exts = extensions->value; - - MemIt KHR_binary_glTF = exts.FindMember("KHR_binary_glTF"); - if (KHR_binary_glTF != exts.MemberEnd() && KHR_binary_glTF->value.IsObject()) { - - int width = TryReadMember(KHR_binary_glTF->value, "width", 0); - int height = TryReadMember(KHR_binary_glTF->value, "height", 0); - - ReadMember(KHR_binary_glTF->value, "mimeType", mimeType); - - const char* bufferViewId; - if (ReadMember(KHR_binary_glTF->value, "bufferView", bufferViewId)) { - Ptr bv = mBufferViews.Get(bufferViewId); - if (bv) { - embeddedDataLen = bv->byteLength; - embeddedData = new uint8_t[embeddedDataLen]; - memcpy(embeddedData, bv->buffer->GetPointer() + bv->byteOffset, embeddedDataLen); - } - } - } - } - - if (!embeddedDataLen) { - const char* uri; - if (ReadMember(obj, "uri", uri)) { - if (IsDataURI(uri)) { - const char* comma = strchr(uri, ','); - *const_cast(comma) = '\0'; - - bool isBase64 = (strstr(uri, "base64") != 0); - if (isBase64) { - embeddedDataLen = DecodeBase64(comma + 1, embeddedData); - } - - const char* sc = strchr(uri, ';'); - if (sc != 0) { - *const_cast(sc) = '\0'; - mimeType = uri; - } - } - else { - img->uri = uri; - } - } - } - - // Add the embedded texture - if (embeddedDataLen > 0) { - aiTexture* tex = new aiTexture(); - mImpTextures.push_back(tex); - - tex->mWidth = static_cast(embeddedDataLen); - tex->mHeight = 0; - tex->pcData = reinterpret_cast(embeddedData); - - if (mimeType) { - const char* ext = strchr(mimeType, '/') + 1; - if (ext) { - if (strcmp(ext, "jpeg") == 0) ext = "jpg"; - - std::size_t len = strlen(ext); - if (len <= 3) { - strcpy(tex->achFormatHint, ext); - } - } - } - - // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) - img->uri.data[0] = '*'; - img->uri.length = 1 + ASSIMP_itoa10(img->uri.data + 1, MAXLEN - 1, mImpTextures.size() - 1); - } - - return Ptr(img); -} - -Ptr glTFReader::LoadTexture(const char* id, Value& obj) -{ - Texture* tex = new Texture(); - - const char* source; - if (ReadMember(obj, "source", source)) { - tex->source = mImages.Get(source); - } - - return Ptr(tex); -} - -void glTFReader::SetMaterialColorProperty(aiMaterial* mat, Value& vals, const char* propName, aiTextureType texType, const char* pKey, unsigned int type, unsigned int idx) -{ - MemIt prop = vals.FindMember(propName); - if (prop != vals.MemberEnd()) { - aiColor3D col; - if (Read(prop->value, col)) { - mat->AddProperty(&col, 1, pKey, type, idx); - } - else if (prop->value.IsString()) { - Ptr tex = mTextures.Get(prop->value.GetString()); - if (tex && tex->source) { - mat->AddProperty(&tex->source->uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); - } - } - } -} - -unsigned int glTFReader::LoadMaterial(const char* id, Value& material) -{ - aiMaterial* mat = new aiMaterial(); - mImpMaterials.push_back(mat); - - const char* name; - if (ReadMember(material, "name", name)) { - aiString str(name); - mat->AddProperty(&str, AI_MATKEY_NAME); - } - - MemIt values = material.FindMember("values"); - if (values != material.MemberEnd() && values->value.IsObject()) { - Value& vals = values->value; - - SetMaterialColorProperty(mat, vals, "diffuse", aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(mat, vals, "specular", aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR); - SetMaterialColorProperty(mat, vals, "ambient", aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT); - - float shininess; - if (ReadMember(vals, "shininess", shininess)) { - mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS); - } - } - - MemIt extensions = material.FindMember("values"); - if (extensions != material.MemberEnd() && extensions->value.IsObject()) { - Value& exts = extensions->value; - - MemIt KHR_materials_common = exts.FindMember("KHR_materials_common"); - if (KHR_materials_common != exts.MemberEnd() && KHR_materials_common->value.IsObject()) { - // TODO: support KHR_materials_common (https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common) - } - } - - return static_cast(mImpMaterials.size() - 1); -} - -aiNode* glTFReader::LoadNode(const char* id, Value& node) -{ - aiNode* ainode = new aiNode(id); - - //MemIt name = node.FindMember("name"); - //if (name != node.MemberEnd() && name->value.IsString()) { - // strcpy(ainode->mName.data, name->value.GetString()); - //} - - MemIt children = node.FindMember("children"); - if (children != node.MemberEnd() && children->value.IsArray()) { - - ainode->mChildren = new aiNode*[children->value.Size()]; - //ainode->mNumChildren = 0; - - for (unsigned int i = 0; i < children->value.Size(); ++i) { - Value& child = children->value[i]; - if (child.IsString()) { - // get/create the child node - aiNode* aichild = mNodes.Get(child.GetString()); - if (aichild) { - aichild->mParent = ainode; - ainode->mChildren[ainode->mNumChildren++] = aichild; - } - } - } - } - - aiMatrix4x4& transf = ainode->mTransformation; - - MemIt matrix = node.FindMember("matrix"); - if (matrix != node.MemberEnd()) { - Read(matrix->value, transf); - } - else { - MemIt translation = node.FindMember("translation"); - if (translation != node.MemberEnd()) { - aiVector3D trans; - if (Read(matrix->value, trans)) { - aiMatrix4x4 m; - aiMatrix4x4::Translation(trans, m); - transf = m * transf; - } - } - - MemIt scale = node.FindMember("scale"); - if (scale != node.MemberEnd()) { - aiVector3D scal(1.f); - if (Read(matrix->value, scal)) { - aiMatrix4x4 m; - aiMatrix4x4::Scaling(scal, m); - transf = m * transf; - } - } - - MemIt rotation = node.FindMember("rotation"); - if (rotation != node.MemberEnd()) { - aiQuaternion rot; - if (Read(matrix->value, rot)) { - transf = aiMatrix4x4(rot.GetMatrix()) * transf; - } - } - } - - MemIt meshes = node.FindMember("meshes"); - if (meshes != node.MemberEnd() && meshes->value.IsArray()) { - std::size_t numMeshes = (std::size_t)meshes->value.Size(); - - std::vector meshList; - - for (std::size_t i = 0; i < numMeshes; ++i) { - if (meshes->value[i].IsString()) { - Range range = mMeshes.Get(meshes->value[i].GetString()); - for (unsigned int m = range.first; m < range.second; ++m) { - meshList.push_back(m); - } - } - } - - if (meshList.size()) { - ainode->mNumMeshes = meshList.size(); - ainode->mMeshes = new unsigned int[meshList.size()]; - std::swap_ranges(meshList.begin(), meshList.end(), ainode->mMeshes); - } - } - - // TODO load "skeletons", "skin", "jointName", "camera" - - return ainode; -} - - - - - -// -// glTFImporter methods -// - -template<> const std::string LogFunctions::log_prefix = "glTF: "; - - - static const aiImporterDesc desc = { "glTF Importer", "", "", "", aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour - | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, 0, 0, 0, @@ -1107,106 +82,543 @@ glTFImporter::glTFImporter() } -glTFImporter::~glTFImporter() { +glTFImporter::~glTFImporter() +{ } -bool glTFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig ) const { - const std::string& extension = GetExtension(pFile); - if (extension == "gltf" || extension == "glb") { - return true; - } - return false; -} - -const aiImporterDesc* glTFImporter::GetInfo() const { +const aiImporterDesc* glTFImporter::GetInfo() const +{ return &desc; } -void glTFImporter::ReadBinaryHeader(IOStream& stream) +bool glTFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { - GLB_Header header; - if (stream.Read(&header, sizeof(header), 1) != 1) { - ThrowException("Unable to read the file header"); + const std::string& extension = GetExtension(pFile); + + if (extension == "gltf" || extension == "glb") + return true; + + if ((checkSig || !extension.length()) && pIOHandler) { + char buffer[4]; + + boost::scoped_ptr pStream(pIOHandler->Open(pFile)); + if (pStream && pStream->Read(buffer, sizeof(buffer), 1) == 1) { + if (memcmp(buffer, AI_GLB_MAGIC_NUMBER, sizeof(buffer)) == 0) { + return true; // Has GLB header + } + else if (memcmp(buffer, "{\r\n ", sizeof(buffer)) == 0 + || memcmp(buffer, "{\n ", sizeof(buffer)) == 0) { + // seems a JSON file, and we're the only format that can read them + return true; + } + } } - if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { - ThrowException("Invalid binary glTF file"); - } - - AI_SWAP4(header.version); - if (header.version != 1) { - ThrowException("Unsupported binary glTF version"); - } - - AI_SWAP4(header.sceneFormat); - if (header.sceneFormat != SceneFormat_JSON) { - ThrowException("Unsupported binary glTF scene format"); - } - - AI_SWAP4(header.length); - AI_SWAP4(header.sceneLength); - - mSceneLength = static_cast(header.sceneLength); - - mBodyOffset = sizeof(header) + mSceneLength; - mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 - - mBodyLength = header.length - mBodyOffset; + return false; } -void glTFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) { - scoped_ptr stream(pIOHandler->Open(pFile, "rb")); - if (!stream) { - ThrowException("Could not open file for reading"); - } - // is binary? then read the header - if (GetExtension(pFile) == "glb") { - ReadBinaryHeader(*stream); + +//static void CopyValue(const glTF::vec3& v, aiColor3D& out) +//{ +// out.r = v[0]; out.g = v[1]; out.b = v[2]; +//} + +static void CopyValue(const glTF::vec4& v, aiColor4D& out) +{ + out.r = v[0]; out.g = v[1]; out.b = v[2]; out.a = v[3]; +} + +static void CopyValue(const glTF::vec4& v, aiColor3D& out) +{ + out.r = v[0]; out.g = v[1]; out.b = v[2]; +} + +static void CopyValue(const glTF::vec3& v, aiVector3D& out) +{ + out.x = v[0]; out.y = v[1]; out.z = v[2]; +} + +static void CopyValue(const glTF::vec4& v, aiQuaternion& out) +{ + out.x = v[0]; out.y = v[1]; out.z = v[2]; out.w = v[3]; +} + +static void CopyValue(const glTF::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]; +} + +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 { - mSceneLength = stream->FileSize(); - mBodyLength = 0; + aiColor4D col; + CopyValue(prop.color, col); + if (col.r != 1.f || col.g != 1.f || col.b != 1.f || col.a != 1.f) { + mat->AddProperty(&col, 1, pKey, type, idx); + } + } +} + +void glTFImporter::ImportMaterials(glTF::Asset& r) +{ + mScene->mNumMaterials = 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.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.specular, aimat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR); + SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT); + + if (mat.shininess > 0.f) { + aimat->AddProperty(&mat.shininess, 1, AI_MATKEY_SHININESS); + } } + if (mScene->mNumMaterials == 0) { + mScene->mNumMaterials = 1; + mScene->mMaterials = new aiMaterial*[1]; + mScene->mMaterials[0] = new aiMaterial(); + } +} - // read the scene data - scoped_ptr sceneData = new char[mSceneLength + 1]; - sceneData[mSceneLength] = '\0'; +inline void SetFace(aiFace& face, int a) +{ + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; +} - if (stream->Read(sceneData, 1, mSceneLength) != mSceneLength) { - ThrowException("Could not read the file contents"); +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; +} + +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; +} + +void glTFImporter::ImportMeshes(glTF::Asset& r) +{ + std::vector meshes; + + unsigned int k = 0; + + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh& mesh = r.meshes[m]; + + meshOffsets.push_back(k); + k += 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) { + size_t& len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, 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(); + } + + + if (prim.indices) { + aiFace* faces = 0; + size_t nFaces = 0; + + unsigned int count = prim.indices->count; + + Accessor::Indexer data = prim.indices->GetIndexer(); + + 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; + 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; + 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; + } + + if (faces) { + aim->mFaces = faces; + aim->mNumFaces = nFaces; + } + } + + + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } + } } + meshOffsets.push_back(k); + + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); +} - // parse the JSON document +void glTFImporter::ImportCameras(glTF::Asset& r) +{ + if (!r.cameras.Size()) return; - Document doc; - doc.ParseInsitu(sceneData); + mScene->mNumCameras = r.cameras.Size(); + mScene->mCameras = new aiCamera*[r.cameras.Size()]; - if (doc.HasParseError()) { - char buffer[32]; - ASSIMP_itoa10(buffer, doc.GetErrorOffset()); - ThrowException(std::string("JSON parse error, offset ") + buffer + ": " - + GetParseError_En(doc.GetParseError())); + 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; + aicam->mClipPlaneFar = cam.perspective.zfar; + aicam->mClipPlaneNear = cam.perspective.znear; + } + else { + // assimp does not support orthographic cameras + } + } +} + +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 = 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; + } } - if (!doc.IsObject()) { - ThrowException("gltf file must be a JSON object!"); + 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]; + } - // Buffer instance for the current file embedded contents - shared_ptr bodyBuffer; - if (mBodyLength > 0) { - bodyBuffer.reset(Buffer::FromStream(*stream, mBodyLength, mBodyOffset)); + 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 (size_t j = meshOffsets[idx]; j < meshOffsets[idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } } - // import the data - glTFReader reader(pScene, doc, *pIOHandler, bodyBuffer); - reader.Load(); + 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< Ref > rootNodes = r.scene->nodes; + + // The root nodes + unsigned int numRootNodes = 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->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::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { + + 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); + + // TODO: it does not split the loaded vertices, should it? + pScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; if (pScene->mNumMeshes == 0) { pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; diff --git a/code/glTFImporter.h b/code/glTFImporter.h index 3de899915..b0b20c0b9 100644 --- a/code/glTFImporter.h +++ b/code/glTFImporter.h @@ -41,16 +41,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_GLTFIMPORTER_H_INC #include "BaseImporter.h" -#include "LogAux.h" #include "DefaultIOSystem.h" +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 LogFunctions { +class glTFImporter : public BaseImporter{ public: glTFImporter(); virtual ~glTFImporter(); @@ -61,10 +68,20 @@ protected: virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ); private: - void ReadBinaryHeader(IOStream& stream); - std::size_t mSceneLength; - std::size_t mBodyOffset, mBodyLength; + std::vector meshOffsets; + + std::vector embeddedTexIdxs; + + aiScene* mScene; + + 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); + }; } // Namespace assimp diff --git a/code/glTFUtil.cpp b/code/glTFUtil.cpp deleted file mode 100644 index 46ef32a42..000000000 --- a/code/glTFUtil.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2015, 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 "glTFUtil.h" - - -using namespace Assimp; -using namespace Assimp::glTF; - - -bool Assimp::glTF::IsDataURI(const char* uri) -{ - return strncmp(uri, "data:", 5) == 0; -} - - -static const uint8_t tableDecodeBase64[128] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, - 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, - 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -static inline char EncodeCharBase64(uint8_t b) -{ - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[b]; -} - -static inline uint8_t DecodeCharBase64(char c) -{ - return tableDecodeBase64[c]; // TODO faster with lookup table or ifs? - /*if (c >= 'A' && c <= 'Z') return c - 'A'; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - return c == '+' ? 62 : 63;*/ -} - -std::size_t Assimp::glTF::DecodeBase64( - const char* in, uint8_t*& out) -{ - return DecodeBase64(in, strlen(in), out); -} - -std::size_t Assimp::glTF::DecodeBase64( - const char* in, std::size_t inLength, uint8_t*& out) -{ - ai_assert(dataLen % 4 == 0); - - if (inLength < 4) { - out = 0; - return 0; - } - - int nEquals = int(in[inLength - 1] == '=') + - int(in[inLength - 2] == '='); - - std::size_t outLength = (inLength * 3) / 4 - nEquals; - out = new uint8_t[outLength]; - memset(out, 0, outLength); - - std::size_t j = 0; - - for (std::size_t i = 0; i < inLength; i += 4) { - uint8_t b0 = DecodeCharBase64(in[i]); - uint8_t b1 = DecodeCharBase64(in[i + 1]); - uint8_t b2 = DecodeCharBase64(in[i + 2]); - uint8_t b3 = DecodeCharBase64(in[i + 3]); - - out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); - out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); - out[j++] = (uint8_t)((b2 << 6) | b3); - } - - return outLength; -} - - - -void Assimp::glTF::EncodeBase64( - const uint8_t* in, std::size_t inLength, - std::string& out) -{ - std::size_t outLength = ((inLength + 2) / 3) * 4; - - out.resize(outLength); - - std::size_t j = 0; - for (std::size_t i = 0; i < inLength; i += 3) { - uint8_t b = (in[i] & 0xFC) >> 2; - out[j++] = EncodeCharBase64(b); - - b = (in[i] & 0x03) << 4; - if (i + 1 < inLength) { - b |= (in[i + 1] & 0xF0) >> 4; - out[j++] = EncodeCharBase64(b); - - b = (in[i + 1] & 0x0F) << 2; - if (i + 2 < inLength) { - b |= (in[i + 2] & 0xC0) >> 6; - out[j++] = EncodeCharBase64(b); - - b = in[i + 2] & 0x3F; - out[j++] = EncodeCharBase64(b); - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - } - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - out[j++] = '='; - } - } -}