/* Assimp2Json Copyright (c) 2011, Alexander C. Gessler Licensed under a 3-clause BSD license. See the LICENSE file for more information. */ #ifndef ASSIMP_BUILD_NO_EXPORT #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER #include #include #include #include #include #include #include #include #include #define CURRENT_FORMAT_VERSION 100 // grab scoped_ptr from assimp to avoid a dependency on boost. //#include #include "mesh_splitter.h" extern "C" { #include "cencode.h" } namespace Assimp { void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*); // small utility class to simplify serializing the aiScene to Json class JSONWriter { public: enum { Flag_DoNotIndent = 0x1, Flag_WriteSpecialFloats = 0x2, }; JSONWriter(Assimp::IOStream& out, unsigned int flags = 0u) : out(out) , first() , flags(flags) { // make sure that all formatting happens using the standard, C locale and not the user's current locale buff.imbue(std::locale("C")); } ~JSONWriter() { Flush(); } void Flush() { const std::string s = buff.str(); out.Write(s.c_str(), s.length(), 1); buff.clear(); } void PushIndent() { indent += '\t'; } void PopIndent() { indent.erase(indent.end() - 1); } void Key(const std::string& name) { AddIndentation(); Delimit(); buff << '\"' + name + "\": "; } template void Element(const Literal& name) { AddIndentation(); Delimit(); LiteralToString(buff, name) << '\n'; } template void SimpleValue(const Literal& s) { LiteralToString(buff, s) << '\n'; } void SimpleValue(const void* buffer, size_t len) { base64_encodestate s; base64_init_encodestate(&s); char* const out = new char[std::max(len * 2, static_cast(16u))]; const int n = base64_encode_block(reinterpret_cast(buffer), static_cast(len), out, &s); out[n + base64_encode_blockend(out + n, &s)] = '\0'; // base64 encoding may add newlines, but JSON strings may not contain 'real' newlines // (only escaped ones). Remove any newlines in out. for (char* cur = out; *cur; ++cur) { if (*cur == '\n') { *cur = ' '; } } buff << '\"' << out << "\"\n"; delete[] out; } void StartObj(bool is_element = false) { // if this appears as a plain array element, we need to insert a delimiter and we should also indent it if (is_element) { AddIndentation(); if (!first) { buff << ','; } } first = true; buff << "{\n"; PushIndent(); } void EndObj() { PopIndent(); AddIndentation(); first = false; buff << "}\n"; } void StartArray(bool is_element = false) { // if this appears as a plain array element, we need to insert a delimiter and we should also indent it if (is_element) { AddIndentation(); if (!first) { buff << ','; } } first = true; buff << "[\n"; PushIndent(); } void EndArray() { PopIndent(); AddIndentation(); buff << "]\n"; first = false; } void AddIndentation() { if (!(flags & Flag_DoNotIndent)) { buff << indent; } } void Delimit() { if (!first) { buff << ','; } else { buff << ' '; first = false; } } private: template std::stringstream& LiteralToString(std::stringstream& stream, const Literal& s) { stream << s; return stream; } std::stringstream& LiteralToString(std::stringstream& stream, const aiString& s) { std::string t; // escape backslashes and single quotes, both would render the JSON invalid if left as is t.reserve(s.length); for (size_t i = 0; i < s.length; ++i) { if (s.data[i] == '\\' || s.data[i] == '\'' || s.data[i] == '\"') { t.push_back('\\'); } t.push_back(s.data[i]); } stream << "\""; stream << t; stream << "\""; return stream; } std::stringstream& LiteralToString(std::stringstream& stream, float f) { if (!std::numeric_limits::is_iec559) { // on a non IEEE-754 platform, we make no assumptions about the representation or existence // of special floating-point numbers. stream << f; return stream; } // JSON does not support writing Inf/Nan // [RFC 4672: "Numeric values that cannot be represented as sequences of digits // (such as Infinity and NaN) are not permitted."] // Nevertheless, many parsers will accept the special keywords Infinity, -Infinity and NaN if (std::numeric_limits::infinity() == fabs(f)) { if (flags & Flag_WriteSpecialFloats) { stream << (f < 0 ? "\"-" : "\"") + std::string("Infinity\""); return stream; } // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; stream << "0.0"; return stream; } // f!=f is the most reliable test for NaNs that I know of else if (f != f) { if (flags & Flag_WriteSpecialFloats) { stream << "\"NaN\""; return stream; } // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; stream << "0.0"; return stream; } stream << f; return stream; } private: Assimp::IOStream& out; std::string indent, newline; std::stringstream buff; bool first; unsigned int flags; }; void Write(JSONWriter& out, const aiVector3D& ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.x); out.Element(ai.y); out.Element(ai.z); out.EndArray(); } void Write(JSONWriter& out, const aiQuaternion& ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.w); out.Element(ai.x); out.Element(ai.y); out.Element(ai.z); out.EndArray(); } void Write(JSONWriter& out, const aiColor3D& ai, bool is_elem = true) { out.StartArray(is_elem); out.Element(ai.r); out.Element(ai.g); out.Element(ai.b); out.EndArray(); } void Write(JSONWriter& out, const aiMatrix4x4& ai, bool is_elem = true) { out.StartArray(is_elem); for (unsigned int x = 0; x < 4; ++x) { for (unsigned int y = 0; y < 4; ++y) { out.Element(ai[x][y]); } } out.EndArray(); } void Write(JSONWriter& out, const aiBone& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("offsetmatrix"); Write(out, ai.mOffsetMatrix, false); out.Key("weights"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumWeights; ++i) { out.StartArray(true); out.Element(ai.mWeights[i].mVertexId); out.Element(ai.mWeights[i].mWeight); out.EndArray(); } out.EndArray(); out.EndObj(); } void Write(JSONWriter& out, const aiFace& ai, bool is_elem = true) { out.StartArray(is_elem); for (unsigned int i = 0; i < ai.mNumIndices; ++i) { out.Element(ai.mIndices[i]); } out.EndArray(); } void Write(JSONWriter& out, const aiMesh& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("materialindex"); out.SimpleValue(ai.mMaterialIndex); out.Key("primitivetypes"); out.SimpleValue(ai.mPrimitiveTypes); out.Key("vertices"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { out.Element(ai.mVertices[i].x); out.Element(ai.mVertices[i].y); out.Element(ai.mVertices[i].z); } out.EndArray(); if (ai.HasNormals()) { out.Key("normals"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { out.Element(ai.mNormals[i].x); out.Element(ai.mNormals[i].y); out.Element(ai.mNormals[i].z); } out.EndArray(); } if (ai.HasTangentsAndBitangents()) { out.Key("tangents"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { out.Element(ai.mTangents[i].x); out.Element(ai.mTangents[i].y); out.Element(ai.mTangents[i].z); } out.EndArray(); out.Key("bitangents"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { out.Element(ai.mBitangents[i].x); out.Element(ai.mBitangents[i].y); out.Element(ai.mBitangents[i].z); } out.EndArray(); } if (ai.GetNumUVChannels()) { out.Key("numuvcomponents"); out.StartArray(); for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { out.Element(ai.mNumUVComponents[n]); } out.EndArray(); out.Key("texturecoords"); out.StartArray(); for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { const unsigned int numc = ai.mNumUVComponents[n] ? ai.mNumUVComponents[n] : 2; out.StartArray(true); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { for (unsigned int c = 0; c < numc; ++c) { out.Element(ai.mTextureCoords[n][i][c]); } } out.EndArray(); } out.EndArray(); } if (ai.GetNumColorChannels()) { out.Key("colors"); out.StartArray(); for (unsigned int n = 0; n < ai.GetNumColorChannels(); ++n) { out.StartArray(true); for (unsigned int i = 0; i < ai.mNumVertices; ++i) { out.Element(ai.mColors[n][i].r); out.Element(ai.mColors[n][i].g); out.Element(ai.mColors[n][i].b); out.Element(ai.mColors[n][i].a); } out.EndArray(); } out.EndArray(); } if (ai.mNumBones) { out.Key("bones"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumBones; ++n) { Write(out, *ai.mBones[n]); } out.EndArray(); } out.Key("faces"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumFaces; ++n) { Write(out, ai.mFaces[n]); } out.EndArray(); out.EndObj(); } void Write(JSONWriter& out, const aiNode& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("transformation"); Write(out, ai.mTransformation, false); if (ai.mNumMeshes) { out.Key("meshes"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { out.Element(ai.mMeshes[n]); } out.EndArray(); } if (ai.mNumChildren) { out.Key("children"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumChildren; ++n) { Write(out, *ai.mChildren[n]); } out.EndArray(); } out.EndObj(); } void Write(JSONWriter& out, const aiMaterial& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("properties"); out.StartArray(); for (unsigned int i = 0; i < ai.mNumProperties; ++i) { const aiMaterialProperty* const prop = ai.mProperties[i]; out.StartObj(true); out.Key("key"); out.SimpleValue(prop->mKey); out.Key("semantic"); out.SimpleValue(prop->mSemantic); out.Key("index"); out.SimpleValue(prop->mIndex); out.Key("type"); out.SimpleValue(prop->mType); out.Key("value"); switch (prop->mType) { case aiPTI_Float: if (prop->mDataLength / sizeof(float) > 1) { out.StartArray(); for (unsigned int i = 0; i < prop->mDataLength / sizeof(float); ++i) { out.Element(reinterpret_cast(prop->mData)[i]); } out.EndArray(); } else { out.SimpleValue(*reinterpret_cast(prop->mData)); } break; case aiPTI_Integer: if (prop->mDataLength / sizeof(int) > 1) { out.StartArray(); for (unsigned int i = 0; i < prop->mDataLength / sizeof(int); ++i) { out.Element(reinterpret_cast(prop->mData)[i]); } out.EndArray(); } else { out.SimpleValue(*reinterpret_cast(prop->mData)); } break; case aiPTI_String: { aiString s; aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s); out.SimpleValue(s); } break; case aiPTI_Buffer: { // binary data is written as series of hex-encoded octets out.SimpleValue(prop->mData, prop->mDataLength); } break; default: assert(false); } out.EndObj(); } out.EndArray(); out.EndObj(); } void Write(JSONWriter& out, const aiTexture& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("width"); out.SimpleValue(ai.mWidth); out.Key("height"); out.SimpleValue(ai.mHeight); out.Key("formathint"); out.SimpleValue(aiString(ai.achFormatHint)); out.Key("data"); if (!ai.mHeight) { out.SimpleValue(ai.pcData, ai.mWidth); } else { out.StartArray(); for (unsigned int y = 0; y < ai.mHeight; ++y) { out.StartArray(true); for (unsigned int x = 0; x < ai.mWidth; ++x) { const aiTexel& tx = ai.pcData[y*ai.mWidth + x]; out.StartArray(true); out.Element(static_cast(tx.r)); out.Element(static_cast(tx.g)); out.Element(static_cast(tx.b)); out.Element(static_cast(tx.a)); out.EndArray(); } out.EndArray(); } out.EndArray(); } out.EndObj(); } void Write(JSONWriter& out, const aiLight& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("type"); out.SimpleValue(ai.mType); if (ai.mType == aiLightSource_SPOT || ai.mType == aiLightSource_UNDEFINED) { out.Key("angleinnercone"); out.SimpleValue(ai.mAngleInnerCone); out.Key("angleoutercone"); out.SimpleValue(ai.mAngleOuterCone); } out.Key("attenuationconstant"); out.SimpleValue(ai.mAttenuationConstant); out.Key("attenuationlinear"); out.SimpleValue(ai.mAttenuationLinear); out.Key("attenuationquadratic"); out.SimpleValue(ai.mAttenuationQuadratic); out.Key("diffusecolor"); Write(out, ai.mColorDiffuse, false); out.Key("specularcolor"); Write(out, ai.mColorSpecular, false); out.Key("ambientcolor"); Write(out, ai.mColorAmbient, false); if (ai.mType != aiLightSource_POINT) { out.Key("direction"); Write(out, ai.mDirection, false); } if (ai.mType != aiLightSource_DIRECTIONAL) { out.Key("position"); Write(out, ai.mPosition, false); } out.EndObj(); } void Write(JSONWriter& out, const aiNodeAnim& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mNodeName); out.Key("prestate"); out.SimpleValue(ai.mPreState); out.Key("poststate"); out.SimpleValue(ai.mPostState); if (ai.mNumPositionKeys) { out.Key("positionkeys"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumPositionKeys; ++n) { const aiVectorKey& pos = ai.mPositionKeys[n]; out.StartArray(true); out.Element(pos.mTime); Write(out, pos.mValue); out.EndArray(); } out.EndArray(); } if (ai.mNumRotationKeys) { out.Key("rotationkeys"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumRotationKeys; ++n) { const aiQuatKey& rot = ai.mRotationKeys[n]; out.StartArray(true); out.Element(rot.mTime); Write(out, rot.mValue); out.EndArray(); } out.EndArray(); } if (ai.mNumScalingKeys) { out.Key("scalingkeys"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumScalingKeys; ++n) { const aiVectorKey& scl = ai.mScalingKeys[n]; out.StartArray(true); out.Element(scl.mTime); Write(out, scl.mValue); out.EndArray(); } out.EndArray(); } out.EndObj(); } void Write(JSONWriter& out, const aiAnimation& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("tickspersecond"); out.SimpleValue(ai.mTicksPerSecond); out.Key("duration"); out.SimpleValue(ai.mDuration); out.Key("channels"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumChannels; ++n) { Write(out, *ai.mChannels[n]); } out.EndArray(); out.EndObj(); } void Write(JSONWriter& out, const aiCamera& ai, bool is_elem = true) { out.StartObj(is_elem); out.Key("name"); out.SimpleValue(ai.mName); out.Key("aspect"); out.SimpleValue(ai.mAspect); out.Key("clipplanefar"); out.SimpleValue(ai.mClipPlaneFar); out.Key("clipplanenear"); out.SimpleValue(ai.mClipPlaneNear); out.Key("horizontalfov"); out.SimpleValue(ai.mHorizontalFOV); out.Key("up"); Write(out, ai.mUp, false); out.Key("lookat"); Write(out, ai.mLookAt, false); out.EndObj(); } void WriteFormatInfo(JSONWriter& out) { out.StartObj(); out.Key("format"); out.SimpleValue("\"assimp2json\""); out.Key("version"); out.SimpleValue(CURRENT_FORMAT_VERSION); out.EndObj(); } void Write(JSONWriter& out, const aiScene& ai) { out.StartObj(); out.Key("__metadata__"); WriteFormatInfo(out); out.Key("rootnode"); Write(out, *ai.mRootNode, false); out.Key("flags"); out.SimpleValue(ai.mFlags); if (ai.HasMeshes()) { out.Key("meshes"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { Write(out, *ai.mMeshes[n]); } out.EndArray(); } if (ai.HasMaterials()) { out.Key("materials"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumMaterials; ++n) { Write(out, *ai.mMaterials[n]); } out.EndArray(); } if (ai.HasAnimations()) { out.Key("animations"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumAnimations; ++n) { Write(out, *ai.mAnimations[n]); } out.EndArray(); } if (ai.HasLights()) { out.Key("lights"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumLights; ++n) { Write(out, *ai.mLights[n]); } out.EndArray(); } if (ai.HasCameras()) { out.Key("cameras"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumCameras; ++n) { Write(out, *ai.mCameras[n]); } out.EndArray(); } if (ai.HasTextures()) { out.Key("textures"); out.StartArray(); for (unsigned int n = 0; n < ai.mNumTextures; ++n) { Write(out, *ai.mTextures[n]); } out.EndArray(); } out.EndObj(); } void ExportAssimp2Json(const char* file, Assimp::IOSystem* io, const aiScene* scene, const Assimp::ExportProperties*) { std::unique_ptr str(io->Open(file, "wt")); if (!str) { //throw Assimp::DeadlyExportError("could not open output file"); } // get a copy of the scene so we can modify it aiScene* scenecopy_tmp; aiCopyScene(scene, &scenecopy_tmp); try { // split meshes so they fit into a 16 bit index buffer MeshSplitter splitter; splitter.SetLimit(1 << 16); splitter.Execute(scenecopy_tmp); // XXX Flag_WriteSpecialFloats is turned on by default, right now we don't have a configuration interface for exporters JSONWriter s(*str, JSONWriter::Flag_WriteSpecialFloats); Write(s, *scenecopy_tmp); } catch (...) { aiFreeScene(scenecopy_tmp); throw; } aiFreeScene(scenecopy_tmp); } } #endif // ASSIMP_BUILD_NO_ASSJSON_EXPORTER #endif // ASSIMP_BUILD_NO_EXPORT