assimp/code/glTFImporter.cpp

1218 lines
38 KiB
C++

/*
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 ASSIMP_BUILD_NO_GLTF_IMPORTER
#include "glTFImporter.h"
#include "StreamReader.h"
#include "DefaultIOSystem.h"
#include <boost/scoped_ptr.hpp>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/ai_assert.h>
#include <assimp/DefaultLogger.hpp>
#include "glTFFileData.h"
#include "glTFUtil.h"
#define RAPIDJSON_HAS_STDSTRING 1
#include <rapidjson/rapidjson.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
using namespace rapidjson;
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;
//
// JSON Value reading helpers
//
#define GETF(VAL, OUT) { if ((VAL).IsNumber()) (OUT) = static_cast<float>((VAL).GetDouble()); }
template<class T>
struct ReadHelper { };
template<> struct ReadHelper<int> { static bool Read(Value& val, int& out) {
return val.IsInt() ? val.GetInt(), true : false;
}};
template<> struct ReadHelper<unsigned int> { static bool Read(Value& val, unsigned int& out) {
return val.IsInt() ? out = static_cast<unsigned int>(val.GetInt()), true : false;
}};
template<> struct ReadHelper<float> { static bool Read(Value& val, float& out) {
return val.IsNumber() ? out = static_cast<float>(val.GetDouble()), true : false;
}};
template<> struct ReadHelper<const char*> { static bool Read(Value& val, const char*& out) {
return val.IsString() ? out = val.GetString(), true : false;
}};
template<> struct ReadHelper<std::string> { static bool Read(Value& val, std::string& out) {
return val.IsString() ? out = val.GetString(), true : false;
}};
template<> struct ReadHelper<aiColor3D> { 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<aiVector3D> { 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<aiQuaternion> { 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<aiMatrix4x4> { 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<class T>
inline static bool Read(Value& val, T& out)
{
return ReadHelper<T>::Read(val, out);
}
template<class T>
inline static bool ReadMember(Value& obj, const char* id, T& out)
{
MemIt it = obj.FindMember(id);
if (it != obj.MemberEnd()) {
return ReadHelper<T>::Read(it->value, out);
}
return false;
}
template<class T>
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<unsigned int, unsigned int> 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<std::string, T> 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<aiMaterial*> mImpMaterials;
std::vector<aiMesh*> mImpMeshes;
std::vector<aiTexture*> mImpTextures;
Extensions mExtensions;
Ptr<Buffer> mBodyBuffer; //! Special buffer containing the body data
Ptr<Buffer> LoadBuffer(const char* id, Value& obj);
Ptr<BufferView> LoadBufferView(const char* id, Value& obj);
Ptr<Accessor> LoadAccessor(const char* id, Value& obj);
Ptr<Image> LoadImage(const char* id, Value& obj);
Ptr<Texture> 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<Ptr<Accessor>, T, &T::LoadAccessor> mAccessors;
//LazyDict<Animation*, T, &T::LoadAnimation> mAnimations;
//LazyDict<Asset*, T, &T::LoadAsset> mAssets;
LazyDict<Ptr<Buffer>, T, &T::LoadBuffer> mBuffers;
LazyDict<Ptr<BufferView>, T, &T::LoadBufferView> mBufferViews;
//LazyDict<Camera*, T, &T::LoadCamera> mCameras;
LazyDict<Ptr<Image>, T, &T::LoadImage> mImages;
LazyDict<unsigned int, T, &T::LoadMaterial> mMaterials;
LazyDict<Range, T, &T::LoadMesh> mMeshes;
LazyDict<aiNode*, T, &T::LoadNode> mNodes;
//LazyDict<Ptr<Program>, T, &T::LoadProgram> mPrograms;
//LazyDict<Ptr<Sampler>, T, &T::LoadSampler> mSamplers;
//LazyDict<Ptr<Shader>, T, &T::LoadShader> mShaders;
//LazyDict<Ptr<Skin>, T, &T::LoadSkin> mSkins;
//LazyDict<Ptr<Technique>,T, &T::LoadTechnique> mTechniques;
LazyDict<Ptr<Texture>, 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<Buffer>& 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<std::string, bool> 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<uint8_t> data;
public:
Buffer(shared_ptr<uint8_t>& 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<uint8_t> 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<Buffer> glTFReader::LoadBuffer(const char* id, Value& obj)
{
if (!obj.IsObject()) return Ptr<Buffer>();
if (mExtensions.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) {
return mBodyBuffer;
}
else {
const char* uri = TryReadMember<const char*>(obj, "uri", 0);
Buffer* b = 0;
if (IsDataURI(uri)) {
const char* comma = strchr(uri, ',');
*const_cast<char*>(comma) = '\0';
bool isBase64 = (strstr(uri, "base64") != 0);
if (isBase64) {
uint8_t* data;
std::size_t dataLen = DecodeBase64(comma + 1, data);
shared_ptr<uint8_t> dataptr(data);
b = new Buffer(dataptr, dataLen);
}
}
else if (uri) { // Local file
unsigned int byteLength = TryReadMember(obj, "byteLength", 0u);
scoped_ptr<IOStream> file(mIO.Open(uri));
b = Buffer::FromStream(*file.get(), byteLength);
}
return Ptr<Buffer>(b);
}
}
struct BufferView
{
Ptr<Buffer> buffer;
unsigned int byteOffset;
unsigned int byteLength;
BufferView() {}
BufferView(Value& obj)
{
Read(obj);
}
void Read(Value& obj)
{
if (!obj.IsObject()) return;
}
};
Ptr<BufferView> glTFReader::LoadBufferView(const char* id, Value& obj)
{
if (!obj.IsObject()) return Ptr<BufferView>();
const char* bufferId = TryReadMember<const char*>(obj, "buffer", 0);
if (!bufferId) return Ptr<BufferView>();
BufferView* bv = new BufferView();
bv->buffer = mBuffers.Get(bufferId);
bv->byteOffset = TryReadMember(obj, "byteOffset", 0u);
bv->byteLength = TryReadMember(obj, "byteLength", 0u);
return Ptr<BufferView>(bv);
}
struct Accessor
{
Ptr<BufferView> 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<class T>
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<class T>
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<unsigned int>(i);
}
};
Ptr<Accessor> glTFReader::LoadAccessor(const char* id, Value& obj)
{
if (!obj.IsObject()) return Ptr<Accessor>();
Accessor* a = new Accessor();
const char* bufferViewId = TryReadMember<const char*>(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<ComponentType>(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<Accessor>(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> 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<Accessor> 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<Image> source;
};
Ptr<Image> 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<BufferView> 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<char*>(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<char*>(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<unsigned int>(embeddedDataLen);
tex->mHeight = 0;
tex->pcData = reinterpret_cast<aiTexel*>(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<Image>(img);
}
Ptr<Texture> 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<Texture>(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<Texture> 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<unsigned int>(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<unsigned int> 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<glTFImporter>::log_prefix = "glTF: ";
static const aiImporterDesc desc = {
"glTF Importer",
"",
"",
"",
aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour
| aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
0,
0,
0,
0,
"gltf glb"
};
glTFImporter::glTFImporter()
: BaseImporter()
{
}
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 {
return &desc;
}
void glTFImporter::ReadBinaryHeader(IOStream& stream)
{
GLB_Header header;
if (stream.Read(&header, sizeof(header), 1) != 1) {
ThrowException("Unable to read the file header");
}
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<std::size_t>(header.sceneLength);
mBodyOffset = sizeof(header) + mSceneLength;
mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4
mBodyLength = header.length - mBodyOffset;
}
void glTFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) {
scoped_ptr<IOStream> 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);
}
else {
mSceneLength = stream->FileSize();
mBodyLength = 0;
}
// read the scene data
scoped_ptr<char> sceneData = new char[mSceneLength + 1];
sceneData[mSceneLength] = '\0';
if (stream->Read(sceneData, 1, mSceneLength) != mSceneLength) {
ThrowException("Could not read the file contents");
}
// parse the JSON document
Document doc;
doc.ParseInsitu(sceneData);
if (doc.HasParseError()) {
char buffer[32];
ASSIMP_itoa10(buffer, doc.GetErrorOffset());
ThrowException(std::string("JSON parse error, offset ") + buffer + ": "
+ GetParseError_En(doc.GetParseError()));
}
if (!doc.IsObject()) {
ThrowException("gltf file must be a JSON object!");
}
// Buffer instance for the current file embedded contents
shared_ptr<Buffer> bodyBuffer;
if (mBodyLength > 0) {
bodyBuffer.reset(Buffer::FromStream(*stream, mBodyLength, mBodyOffset));
}
// import the data
glTFReader reader(pScene, doc, *pIOHandler, bodyBuffer);
reader.Load();
if (pScene->mNumMeshes == 0) {
pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
}
}
#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER