//-------------------------------------------
bool ofxAssimpModelLoader::loadModel(ofBuffer & buffer, bool optimize, const char * extension){
	normalizeFactor = ofGetWidth() / 2.0;

	// only ever give us triangles.
	aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
	aiSetImportPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, true);

	// aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
	unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_Triangulate | aiProcess_FlipUVs;
	if(optimize) flags |=  aiProcess_ImproveCacheLocality | aiProcess_OptimizeGraph |
			aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices |
			aiProcess_RemoveRedundantMaterials;

	if(scene){
		clear();
	}
	scene = aiImportFileFromMemory(buffer.getBinaryBuffer(), buffer.size(), flags, extension);
	if(scene){
		calculateDimensions();
		loadGLResources();

		if(getAnimationCount())
			ofLog(OF_LOG_VERBOSE, "scene has animations");
		else {
			ofLog(OF_LOG_VERBOSE, "no animations");

		}
		return true;
	}else{
		ofLog(OF_LOG_ERROR,string("ofxAssimpModelLoader: ") + aiGetErrorString());
		return false;
	}

}
Esempio n. 2
0
int main(int argc, const char **argv)
{
	if(argc == 1)
	{
		const char *params[] = {"-p", "win32", "../../../Raw/Level/level.dae", "./"};
		parseArgs(4, params);
	}
	else
		parseArgs(argc, argv);

	printf("Outputting in %s format\n",
		g_Endianness == LittleEndian ? "LittleEndian" : "BigEndian");

	aiSetImportPropertyInteger(
		AI_CONFIG_PP_RVC_FLAGS, 
		aiComponent_TANGENTS_AND_BITANGENTS | 
		aiComponent_COLORS
	);

	// Remove degenerates; if we don't do this, tangents & bitangents
	// can't be calculated for meshes with degenerate tris
	aiSetImportPropertyInteger(
		AI_CONFIG_PP_FD_REMOVE,
		true
	);

	const aiScene* scene = aiImportFile(
		filename.c_str(), 
		aiProcessPreset_TargetRealtime_MaxQuality | 
		aiProcess_GenNormals | 
		aiProcess_RemoveComponent
	); 

	if(!scene)
		printf("Failed to load %s\n", filename.c_str());
	else
	{
		makeDirectories();

		cJSON *root = cJSON_CreateObject();
		cJSON *list = cJSON_CreateArray();
		cJSON_AddItemToObject(root, "scene", list);

		aiMatrix4x4 identity;

		printf("Loaded %s\n", filename.c_str());
		processSceneRecursive(scene, list, identity, scene->mRootNode);
		printf("Done\n");

		outputFinalStep();

		writeJsonToFile(root, outdir + "/scene.scene");
	}

	aiReleaseImport(scene);
	getchar();

	return 0;
}
//------------------------------------------
void ofxAssimpModelLoader::loadModel(string modelName){
	
    
    // if we have a model loaded, unload the f****r.
    if(scene != NULL)
    {
        aiReleaseImport(scene);
        scene = NULL;
        
        deleteGLResources();   
    }
    
    
    // Load our new path.
    string filepath = ofToDataPath(modelName);

    ofLog(OF_LOG_VERBOSE, "loading model %s", filepath.c_str());
    
    // only ever give us triangles.
    aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
    aiSetImportPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, true);
    
    // aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
    scene = (aiScene*) aiImportFile(filepath.c_str(), aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_OptimizeGraph | aiProcess_Triangulate | aiProcess_FlipUVs | 0 );
    
    if(scene){        
        ofLog(OF_LOG_VERBOSE, "initted scene with %i meshes & %i animations", scene->mNumMeshes, scene->mNumAnimations);

        getBoundingBoxWithMinVector(&scene_min, &scene_max);
        scene_center.x = (scene_min.x + scene_max.x) / 2.0f;
        scene_center.y = (scene_min.y + scene_max.y) / 2.0f;
        scene_center.z = (scene_min.z + scene_max.z) / 2.0f;
        
        // optional normalized scaling
        normalizedScale = scene_max.x-scene_min.x;
        normalizedScale = aisgl_max(scene_max.y - scene_min.y,normalizedScale);
        normalizedScale = aisgl_max(scene_max.z - scene_min.z,normalizedScale);
        normalizedScale = 1.f / normalizedScale;
        normalizedScale *= ofGetWidth() / 2.0;
        
        glPushAttrib(GL_ALL_ATTRIB_BITS);
        glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
        
        loadGLResources();
        
        glPopClientAttrib();
        glPopAttrib();

        if(getAnimationCount())
            ofLog(OF_LOG_VERBOSE, "scene has animations");
        else {
            ofLog(OF_LOG_VERBOSE, "no animations");
            
        }
    }
}
Esempio n. 4
0
//------------------------------------------
bool ofxAssimpModelLoader::loadModel(string modelName, unsigned extraFlags /* = 0 */){


    // if we have a model loaded, unload the f****r.
    if(scene != NULL){
        clear();
    }

    // Load our new path.
    filepath = modelName;
    string filepath = ofToDataPath(modelName);

	//theo added - so we can have models and their textures in sub folders
	modelFolder = ofFilePath::getEnclosingDirectory(filepath);

    ofLog(OF_LOG_VERBOSE, "loading model %s", filepath.c_str());
    ofLog(OF_LOG_VERBOSE, "loading from folder %s", modelFolder.c_str());

    // only ever give us triangles.
    aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
    aiSetImportPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, true);

    // aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
    unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_Triangulate | aiProcess_FlipUVs;
	flags |= extraFlags;
	/*
    if(optimize) flags |=  aiProcess_ImproveCacheLocality | aiProcess_OptimizeGraph |
			aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices |
			aiProcess_RemoveRedundantMaterials;
	*/

    scene = aiImportFile(filepath.c_str(), flags);

    if(scene){
        calculateDimensions();
        loadGLResources();

        if(getAnimationCount())
            ofLog(OF_LOG_VERBOSE, "scene has animations");
        else {
            ofLog(OF_LOG_VERBOSE, "no animations");

        }
		collectNodeTransforms(scene, scene->mRootNode);
		collectBoneTransforms();

        return true;
    }else{
    	ofLog(OF_LOG_ERROR,string("ofxAssimpModelLoader: ") + aiGetErrorString());
    	return false;
    }
}
unsigned int ofxAssimpModelLoader::initImportProperties(bool optimize) {    
    store.reset(aiCreatePropertyStore(), aiReleasePropertyStore);
    
    // only ever give us triangles.
    aiSetImportPropertyInteger(store.get(), AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
    aiSetImportPropertyInteger(store.get(), AI_CONFIG_PP_PTV_NORMALIZE, true);
    
    // aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
    unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_Triangulate | aiProcess_FlipUVs;
    if(optimize) flags |=  aiProcess_ImproveCacheLocality | aiProcess_OptimizeGraph |
        aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices |
        aiProcess_RemoveRedundantMaterials;
    
    return flags;
}
Esempio n. 6
0
const aiScene* MeshShape::loadMesh(const std::string& _fileName) {
  aiPropertyStore* propertyStore = aiCreatePropertyStore();
  // remove points and lines
  aiSetImportPropertyInteger(propertyStore,
                             AI_CONFIG_PP_SBP_REMOVE,
                             aiPrimitiveType_POINT | aiPrimitiveType_LINE);
  const aiScene* scene =
      aiImportFileExWithProperties(_fileName.c_str(),
                                   aiProcess_GenNormals             |
                                   aiProcess_Triangulate            |
                                   aiProcess_JoinIdenticalVertices  |
                                   aiProcess_SortByPType            |
                                   aiProcess_OptimizeMeshes,
                                   NULL, propertyStore);
  aiReleasePropertyStore(propertyStore);

  // Assimp rotates collada files such that the up-axis (specified in the
  // collada file) aligns with assimp's y-axis. Here we are reverting this
  // rotation. We are only catching files with the .dae file ending here. We
  // might miss files with an .xml file ending, which would need to be looked
  // into to figure out whether they are collada files.
  if (_fileName.length() >= 4
     && _fileName.substr(_fileName.length() - 4, 4) == ".dae") {
    scene->mRootNode->mTransformation = aiMatrix4x4();
  }
  scene = aiApplyPostProcessing(scene, aiProcess_PreTransformVertices);

  return scene;
}
//-------------------------------------------
bool ofxAssimpModelLoader::loadModel(ofBuffer & buffer, bool optimize, const char * extension){
	normalizeFactor = ofGetWidth() / 2.0;
    
    if(scene != NULL){
        clear();
    }
    
	// only ever give us triangles.
	aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
	aiSetImportPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, true);

	// aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
	unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_Triangulate | aiProcess_FlipUVs;
	if(optimize) flags |=  aiProcess_ImproveCacheLocality | aiProcess_OptimizeGraph |
			aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices |
			aiProcess_RemoveRedundantMaterials;

	scene = aiImportFileFromMemory(buffer.getBinaryBuffer(), buffer.size(), flags, extension);
    
	if(scene){
		calculateDimensions();
		loadGLResources();
        update();

		if(getAnimationCount())
			ofLogVerbose("ofxAssimpModelLoader") << "loadModel(): scene has " << getAnimationCount() << "animations";
		else {
			ofLogVerbose("ofxAssimpModelLoader") << "loadMode(): no animations";
		}

		ofAddListener(ofEvents().exit,this,&ofxAssimpModelLoader::onAppExit);


		return true;
	}else{
		ofLogError("ofxAssimpModelLoader") << "loadModel(): " + (string) aiGetErrorString();
		clear();
		return false;
	}
}
Esempio n. 8
0
 const aiScene* ShapeMesh::loadMesh(const string& fileName) {
     aiPropertyStore* propertyStore = aiCreatePropertyStore();
     aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE ); // remove points and lines
     const aiScene* scene = aiImportFileExWithProperties(fileName.c_str(), aiProcess_GenNormals             |
                                                                           aiProcess_Triangulate            |
                                                                           aiProcess_JoinIdenticalVertices  |
                                                                           aiProcess_SortByPType            |
                                                                           aiProcess_PreTransformVertices   |
                                                                           aiProcess_OptimizeMeshes,
                                                         NULL, propertyStore);
     aiReleasePropertyStore(propertyStore);
     return scene;
 }
void NCAssimpModel::setup(string pathtomodel){

    string filepath = ofToDataPath(pathtomodel);
    bool optimize =false;

    // only ever give us triangles.
    aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
    aiSetImportPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, true);
    
    // aiProcess_FlipUVs is for VAR code. Not needed otherwise. Not sure why.
    unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_OptimizeGraph | aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType;
    
    if(optimize) flags |=  aiProcess_ImproveCacheLocality | aiProcess_OptimizeGraph |
        aiProcess_OptimizeMeshes | aiProcess_JoinIdenticalVertices |
        aiProcess_RemoveRedundantMaterials;
    
    if (scene) {
        for(int i = 0; i < 100; i++) {
            jointlabels[i] = "";
        }
        bones.clear();
        delete scene;
    }
    
    scene = aiImportFile(filepath.c_str(), flags);
    
    if(scene){
        meshes.clear();
        initBones();
        
        for (int i=0;i<scene->mNumMeshes;i++) {
            NCAIMesh mesh;
            mesh.setup(scene->mMeshes[i], scene);
            meshes.push_back(mesh);
        }
    }
   
}
Esempio n. 10
0
bool opMeshBaker::LoadAndBake(const char* input_filename, const char* output_filename) {

  aiSetImportPropertyFloat("AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE", 80.0f);
  aiSetImportPropertyInteger("AI_CONFIG_PP_RVC_FLAGS", aiComponent_NORMALS); // throw away normals (generate them)

  /* generate a mesh from the input file:
    Calculate tangents and bitangents
    Triangulate
    Weld vertices together
    Sort by primitive type
    Generate smooth normals
    Fix infacing normals
    Generate UV coords
    optimize the mesh
  */
  const aiScene* scene = aiImportFile(input_filename, 
    aiProcess_CalcTangentSpace | 
    aiProcess_Triangulate      |
    aiProcess_JoinIdenticalVertices  |
    aiProcess_SortByPType |
    aiProcess_GenSmoothNormals |
    aiProcess_FixInfacingNormals |
    aiProcess_GenUVCoords |
    aiProcess_ConvertToLeftHanded |
    aiProcess_OptimizeMeshes);

  if(scene == NULL) {
    palPrintf("Could not open understand input file: %s\n", input_filename);
    return false;
  }

  palPrintf("Baking %s to %s\n", input_filename, output_filename);

  // we only support 1:1 mapping between input file meshes and output file meshes
  palAssert(scene->mNumMeshes == 1);

  aiMesh* mesh = scene->mMeshes[0];

  // we expect faces
  palAssert(mesh->HasFaces() == true);
  
  // we expect vertex positions
  palAssert(mesh->HasPositions() == true);
  //printf("Has vertex positions.\n");
  // we expect normals
  palAssert(mesh->HasNormals() == true);
  //printf("Has vertex normal.\n");
  // we only support triangular meshes
  palAssert(mesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE);

  palFile baked;
  int r;
  r = baked.OpenForWritingTruncate(output_filename);
  if (r != 0) {
    palPrintf("Could not open output file: %s\n", output_filename);
    return false;
  }

  palArray<opBakedMeshArrayHeader> mesh_arrays;
  mesh_arrays.SetAllocator(g_DefaultHeapAllocator);
  palArray<void*> mesh_arrays_data;
  mesh_arrays_data.SetAllocator(g_DefaultHeapAllocator);
  palArray<opBakedMeshAttributeHeader> mesh_attributes;
  mesh_attributes.SetAllocator(g_DefaultHeapAllocator);

  opBakedMeshArrayHeader mesh_array;

  // index array
  mesh_array.array_type = OP_BAKED_MESH_INDEX_ARRAY;

  // pack index data tightly
  uint32_t num_indices = mesh->mNumFaces * 3;
  void* index_array_data = NULL;
  if (num_indices < 0xffff) {
    mesh_array.index_type = kOpRenderDeviceFormatUintR16;
    mesh_array.length_in_bytes = num_indices * sizeof(uint16_t);
    uint16_t* indices = (uint16_t*)g_DefaultHeapAllocator->Allocate(mesh_array.length_in_bytes);
    palAssert(indices);
    index_array_data = indices;
    for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
      *indices = mesh->mFaces[i].mIndices[0];
      indices++;
      *indices = mesh->mFaces[i].mIndices[1];
      indices++;
      *indices = mesh->mFaces[i].mIndices[2];
      indices++;
    }
  } else {
    mesh_array.index_type = kOpRenderDeviceFormatUintR32;
    mesh_array.length_in_bytes = num_indices * sizeof(uint32_t);
    uint32_t* indices = (uint32_t*)g_DefaultHeapAllocator->Allocate(mesh_array.length_in_bytes);
    palAssert(indices);
    index_array_data = indices;
    for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
      *indices = mesh->mFaces[i].mIndices[0];
      indices++;
      *indices = mesh->mFaces[i].mIndices[1];
      indices++;
      *indices = mesh->mFaces[i].mIndices[2];
      indices++;
    }
  }
  printf("Indexed triangle mesh: %d triangles (%d bytes in index data)\n", num_indices/3, mesh_array.length_in_bytes);

  mesh_arrays_data.push_back(index_array_data);
  mesh_arrays.push_back(mesh_array);

  // pack into single interleaved array
  {
    uint32_t offset = 0;

    opBakedMeshAttributeHeader mesh_component;
    
    // position
    mesh_component.attribute_type = OP_BAKED_MESH_ATTRIBUTE_VERTICES;
    mesh_component.element_type = kOpRenderDeviceFormatFloat32x3;
    mesh_component.offset_in_bytes = 0;
    mesh_component.array_index = 1;

    mesh_attributes.push_back(mesh_component);

    offset += 3 * sizeof(float);

    // normals
    mesh_component.attribute_type = OP_BAKED_MESH_ATTRIBUTE_NORMALS;
    mesh_component.element_type = kOpRenderDeviceFormatFloat32x3;
    mesh_component.offset_in_bytes = offset;
    mesh_component.array_index = 1;

    mesh_attributes.push_back(mesh_component);

    offset += 3 * sizeof(float);

    if (mesh->HasTangentsAndBitangents()) {
      // tangents
      mesh_component.attribute_type = OP_BAKED_MESH_ATTRIBUTE_TANGENTS;
      mesh_component.element_type = kOpRenderDeviceFormatFloat32x3;
      mesh_component.offset_in_bytes = offset;
      mesh_component.array_index = 1;

      mesh_attributes.push_back(mesh_component);

      offset += 3 * sizeof(float);

      // bitangents
      mesh_component.attribute_type = OP_BAKED_MESH_ATTRIBUTE_BITANGENTS;
      mesh_component.element_type = kOpRenderDeviceFormatFloat32x3;
      mesh_component.offset_in_bytes = offset;
      mesh_component.array_index = 1;

      mesh_attributes.push_back(mesh_component);

      offset += 3 * sizeof(float);

      printf("Has vertex tangents and bitangents.\n");
    }
    
    // texture coordinates 0..3
    for (unsigned int i = 0; i < mesh->GetNumUVChannels(); i++) {
      mesh_component.attribute_type = (opBakedMeshAttributeType)(OP_BAKED_MESH_ATTRIBUTE_TEXCOORDS0 + i);
      mesh_component.element_type = TextureElementType(mesh->mNumUVComponents[i]);
      mesh_component.offset_in_bytes = offset;
      mesh_component.array_index = 1;
      printf("Has UV channel %d (%d)\n", i, mesh->mNumUVComponents[i]);
      mesh_attributes.push_back(mesh_component);
      offset += mesh->mNumUVComponents[i] * sizeof(float);
    }

    // fill in the stride between vertices
    for (int i = 0; i < mesh_attributes.GetSize(); i++) {
      mesh_attributes[i].stride_in_bytes = offset;
    }

    mesh_array.array_type = OP_BAKED_MESH_VERTEX_ARRAY;
    mesh_array.length_in_bytes = mesh->mNumVertices * offset;

    printf("%d vertices (%d bytes in vertex data)\n", mesh->mNumVertices, mesh_array.length_in_bytes);

    void* vertex_array_data = g_DefaultHeapAllocator->Allocate(mesh_array.length_in_bytes);
    float* vertex_array_data_f = (float*)vertex_array_data;
    for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
      // positions
      *vertex_array_data_f = mesh->mVertices[i].x;
      vertex_array_data_f++;
      *vertex_array_data_f = mesh->mVertices[i].y;
      vertex_array_data_f++;
      *vertex_array_data_f = mesh->mVertices[i].z;
      vertex_array_data_f++;
      // normals
      *vertex_array_data_f = mesh->mNormals[i].x;
      vertex_array_data_f++;
      *vertex_array_data_f = mesh->mNormals[i].y;
      vertex_array_data_f++;
      *vertex_array_data_f = mesh->mNormals[i].z;
      vertex_array_data_f++;

      if (mesh->HasTangentsAndBitangents()) {
        // tangents
        *vertex_array_data_f = mesh->mTangents[i].x;
        vertex_array_data_f++;
        *vertex_array_data_f = mesh->mTangents[i].y;
        vertex_array_data_f++;
        *vertex_array_data_f = mesh->mTangents[i].z;
        vertex_array_data_f++;
        // bitangents
        *vertex_array_data_f = mesh->mBitangents[i].x;
        vertex_array_data_f++;
        *vertex_array_data_f = mesh->mBitangents[i].y;
        vertex_array_data_f++;
        *vertex_array_data_f = mesh->mBitangents[i].z;
        vertex_array_data_f++;
      }
      
      // texture channels
      for (unsigned int tc = 0; tc < mesh->GetNumUVChannels(); tc++) {
        // texture coordinates
        for (unsigned int st = 0; st < mesh->mNumUVComponents[tc]; st++) {
          //printf("tex[%d] = %f\n", st, mesh->mTextureCoords[tc][i][st]);
          *vertex_array_data_f = mesh->mTextureCoords[tc][i][st];
          vertex_array_data_f++;
        }
      }
    }

    // push data back into arrays
    mesh_arrays_data.push_back(vertex_array_data);
    mesh_arrays.push_back(mesh_array);
  }

  // write data to disk
  opBakedMeshHeader header;
  header.magic = OP_BAKED_MESH_FILE_MAGIC;
  header.attribute_count = mesh_attributes.GetSize();
  header.attribute_offset = sizeof(header);
  header.array_count = mesh_arrays.GetSize();
  header.array_offset = sizeof(header) + sizeof(opBakedMeshAttributeHeader) * mesh_attributes.GetSize();
  baked.Write(&header, sizeof(header));
  for (int i = 0; i < mesh_attributes.GetSize();i++) {
    opBakedMeshAttributeHeader& component_attribute = mesh_attributes[i];
    baked.Write(&component_attribute, sizeof(component_attribute));
  }

  uint32_t array_offset = sizeof(header);
  array_offset += sizeof(opBakedMeshAttributeHeader) * mesh_attributes.GetSize();
  array_offset += sizeof(opBakedMeshArrayHeader) * mesh_arrays.GetSize();

  for (int i = 0; i < mesh_arrays.GetSize(); i++) {
    opBakedMeshArrayHeader& array_header = mesh_arrays[i];
    array_header.offset_in_bytes = array_offset;
    baked.Write(&array_header, sizeof(array_header));
    array_offset += array_header.length_in_bytes;
  }

  for (int i = 0; i < mesh_arrays.GetSize(); i++) {
    baked.Write(mesh_arrays_data[i], mesh_arrays[i].length_in_bytes);
    g_DefaultHeapAllocator->Deallocate(mesh_arrays_data[i]);
  }

  baked.Close();

  aiReleaseImport(scene);

  return true;
}
Esempio n. 11
0
const aiScene* MeshShape::loadMesh(
    const std::string& _uri, const common::ResourceRetrieverPtr& _retriever)
{
    // Remove points and lines from the import.
    aiPropertyStore* propertyStore = aiCreatePropertyStore();
    aiSetImportPropertyInteger(propertyStore,
                               AI_CONFIG_PP_SBP_REMOVE,
                               aiPrimitiveType_POINT
                               | aiPrimitiveType_LINE
                              );

    // Wrap ResourceRetriever in an IOSystem from Assimp's C++ API.  Then wrap
    // the IOSystem in an aiFileIO from Assimp's C API. Yes, this API is
    // completely ridiculous...
    AssimpInputResourceRetrieverAdaptor systemIO(_retriever);
    aiFileIO fileIO = createFileIO(&systemIO);

    // Import the file.
    const aiScene* scene = aiImportFileExWithProperties(
                               _uri.c_str(),
                               aiProcess_GenNormals
                               | aiProcess_Triangulate
                               | aiProcess_JoinIdenticalVertices
                               | aiProcess_SortByPType
                               | aiProcess_OptimizeMeshes,
                               &fileIO,
                               propertyStore
                           );

    // If succeeded, store the importer in the scene to keep it alive. This is
    // necessary because the importer owns the memory that it allocates.
    if(!scene)
    {
        dtwarn << "[MeshShape::loadMesh] Failed loading mesh '" << _uri << "'.\n";
        return nullptr;
    }

    // Assimp rotates collada files such that the up-axis (specified in the
    // collada file) aligns with assimp's y-axis. Here we are reverting this
    // rotation. We are only catching files with the .dae file ending here. We
    // might miss files with an .xml file ending, which would need to be looked
    // into to figure out whether they are collada files.
    std::string extension;
    const size_t extensionIndex = _uri.find_last_of('.');
    if(extensionIndex != std::string::npos)
        extension = _uri.substr(extensionIndex);

    std::transform(std::begin(extension), std::end(extension),
                   std::begin(extension), ::tolower);

    if(extension == ".dae" || extension == ".zae")
        scene->mRootNode->mTransformation = aiMatrix4x4();

    // Finally, pre-transform the vertices. We can't do this as part of the
    // import process, because we may have changed mTransformation above.
    scene = aiApplyPostProcessing(scene, aiProcess_PreTransformVertices);
    if(!scene)
        dtwarn << "[MeshShape::loadMesh] Failed pre-transforming vertices.\n";

    return scene;
}