From 49047a5e037eef4023fcc2f555f6c699a67e6d6e Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Mon, 19 Jul 2021 22:16:05 +0200
Subject: [PATCH] [#79] Asset-Loader refactoring mostly done

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 .../include/vkcv/asset/asset_loader.hpp       |  204 +--
 .../src/vkcv/asset/asset_loader.cpp           | 1422 ++++++++---------
 2 files changed, 776 insertions(+), 850 deletions(-)

diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 04f0b39b..88021075 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -49,14 +49,24 @@ namespace vkcv::asset {
  * (TRIANGLES) if no mode is given in the file.
  */
 enum class PrimitiveMode : uint8_t {
-	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
-	TRIANGLEFAN
+	POINTS = 0,
+	LINES = 1,
+	LINELOOP = 2,
+	LINESTRIP = 3,
+	TRIANGLES = 4,
+	TRIANGLESTRIP = 5,
+	TRIANGLEFAN = 6
 };
 
 /**
  * The indices in the index buffer can be of different bit width.
  */
-enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
+enum class IndexType : uint8_t {
+	UNDEFINED=0,
+	UINT8=1,
+	UINT16=2,
+	UINT32=3
+};
 
 /**
  * This struct defines a sampler for a texture object. All values here can
@@ -67,25 +77,29 @@ enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
  * Since glTF does not specify border sampling for more than two dimensions,
  * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT.
  */
-typedef struct {
+struct Sampler {
 	int minFilter, magFilter;
 	int mipmapMode;
 	float minLOD, maxLOD;
 	int addressModeU, addressModeV, addressModeW;
-} Sampler;
+};
 
 /**
  * This struct describes a loaded texture.
  * Note that textures are currently always loaded with 4 channels as RGBA, even
  * if the image has just RGB or is grayscale.
  */
-typedef struct {
-	int uri;		// index into the URIs array of the Scene
-	int sampler;		// index into the sampler array of the Scene
-	uint8_t channels;	// number of channels
-	uint16_t w, h;		// width and height of the texture
-	std::vector<uint8_t> data;	// binary data of the decoded texture
-} Texture;
+struct Texture {
+	std::filesystem::path path; // file path of the encoded texture data
+	int sampler;				// index into the sampler array of the Scene
+	
+	union { int width; int w; };
+	union { int height; int h; };
+	int channels;
+	
+	// binary data of the decoded texture
+	std::vector<char*> data;
+};
 
 /**
  * Flags for the bit-mask in the Material struct. To check if a material has a
@@ -93,7 +107,11 @@ typedef struct {
  * the material struct and the enum.
  */
 enum class PBRTextureTarget {
-	baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
+	baseColor=1,
+	metalRough=2,
+	normal=4,
+	occlusion=8,
+	emissive=16
 };
 
 /**
@@ -112,10 +130,12 @@ enum class PBRTextureTarget {
  * The asset loader module only supports the PBR-MetallicRoughness model for
  * materials.
  */
-typedef struct {
+struct Material {
 	uint16_t textureMask;	// bit mask with active texture targets
+	
 	// Indices into the Scene.textures vector
 	int baseColor, metalRough, normal, occlusion, emissive;
+	
 	// Scaling factors for each texture target
 	struct { float r, g, b, a; } baseColorFactor;
 	float metallicFactor, roughnessFactor;
@@ -128,12 +148,13 @@ typedef struct {
 	 * struct, its bit is set in the textureMask. You can use this function
 	 * to check that:
 	 * if (myMaterial.hasTexture(baseColor)) {...}
+	 *
 	 * @param t The target to query for
 	 * @return Boolean to signal whether the texture target is active in
 	 * the material.
 	 */
-	bool hasTexture(const PBRTextureTarget t) const;
-} Material;
+	bool hasTexture(PBRTextureTarget target) const;
+};
 
 /* With these enums, 0 is reserved to signal uninitialized or invalid data. */
 enum class PrimitiveType : uint32_t {
@@ -157,21 +178,28 @@ enum class PrimitiveType : uint32_t {
  * avoid exposing that dependency, thus it is re-defined here.
  */
 enum class ComponentType : uint16_t {
-    NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123,
-    UINT32 = 5125, FLOAT32 = 5126
+    NONE = 0,
+    INT8 = 5120,
+    UINT8 = 5121,
+    INT16 = 5122,
+    UINT16 = 5123,
+    UINT32 = 5125,
+    FLOAT32 = 5126
 };
 
 /**
  * This struct describes one vertex attribute of a vertex buffer.
  */
-typedef struct {
+struct VertexAttribute {
     PrimitiveType type;			// POSITION, NORMAL, ...
+    
     uint32_t offset;			// offset in bytes
     uint32_t length;			// length of ... in bytes
     uint32_t stride;			// stride in bytes
+    
     ComponentType componentType;	// eg. 5126 for float
     uint8_t  componentCount;		// eg. 3 for vec3
-} VertexAttribute;
+};
 
 /**
  * This struct represents one (possibly the only) part of a mesh. There is
@@ -180,100 +208,96 @@ typedef struct {
  * by indexBuffer.data being empty. Each vertex buffer can have one or more
  * vertex attributes.
  */
-typedef struct {
+struct VertexGroup {
 	enum PrimitiveMode mode;	// draw as points, lines or triangle?
-	size_t numIndices, numVertices;
+	size_t numIndices;
+	size_t numVertices;
+	
 	struct {
-		int uri;		// index into the URIs array of Scene
 		enum IndexType type;	// data type of the indices
 		std::vector<uint8_t> data; // binary data of the index buffer
 	} indexBuffer;
+	
 	struct {
-		int uri;		// index into the URIs array of Scene
 		std::vector<uint8_t> data; // binary data of the vertex buffer
 		std::vector<VertexAttribute> attributes; // description of one
 	} vertexBuffer;
+	
 	struct { float x, y, z; } min;	// bounding box lower left
 	struct { float x, y, z; } max;	// bounding box upper right
+	
 	int materialIndex;		// index to one of the materials
-} VertexGroup;
+};
 
 /**
  * This struct represents a single mesh as it was loaded from a glTF file. It
  * consists of at least one VertexGroup, which then references other resources
  * such as Materials.
  */
-typedef struct {
+struct Mesh {
 	std::string name;
 	std::array<float, 16> modelMatrix;
 	std::vector<int> vertexGroups;
-} Mesh;
-
-/**
- * The scene struct is simply a collection of objects in the scene as well as
- * the resources used by those objects.
- * Note that parent-child relations are not yet possible.
- */
-typedef struct {
-	std::vector<Mesh> meshes;
-	std::vector<VertexGroup> vertexGroups;
-	std::vector<Material> materials;
-	std::vector<Texture> textures;
-	std::vector<Sampler> samplers;
-	std::vector<std::string> uris;
-} Scene;
-
-/**
- * Load every mesh from the glTF file, as well as materials, textures and other
- * associated objects.
- *
- * @param path	must be the path to a glTF- or glb-file.
- * @param scene is a reference to a Scene struct that will be filled with the
- * 	content of the glTF file being loaded.
- */
-int loadScene(const std::filesystem::path &path, Scene &scene);
+};
 
-/**
- * Parse the given glTF file and create a shallow description of the content.
- * Only the meta-data of the objects in the scene is loaded, not the binary
- * content. The rationale is to provide a means of probing the content of a
- * glTF file without the costly process of loading and decoding large amounts
- * of data. The returned Scene struct can be used to search for specific meshes
- * in the scene, that can then be loaded on their own using the loadMesh()
- * function. Note that the Scene struct received as output argument will be
- * overwritten by this function.
- * After this function completes, the returned Scene struct is completely
- * initialized and all information is final, except for the missing binary
- * data. This means that indices to vectors will remain valid even when the
- * shallow scene struct is filled with data by loadMesh().
- *
- * @param path	must be the path to a glTF- or glb-file.
- * @param scene	is a reference to a Scene struct that will be filled with the
- * 	meta-data of all objects described in the glTF file.
- */
-int probeScene(const std::filesystem::path &path, Scene &scene);
+	/**
+	 * The scene struct is simply a collection of objects in the scene as well as
+	 * the resources used by those objects.
+	 * Note that parent-child relations are not yet possible.
+	 */
+	struct Scene {
+		std::vector<Mesh> meshes;
+		std::vector<VertexGroup> vertexGroups;
+		std::vector<Material> materials;
+		std::vector<Texture> textures;
+		std::vector<Sampler> samplers;
+		std::vector<std::string> uris;
+	};
 
-/**
- * This function loads a single mesh from the given file and adds it to the
- * given scene. The scene must already be initialized (via probeScene()).
- * The mesh_index refers to the Scenes meshes array and identifies the mesh to
- * load. To find the mesh you want, iterate over the probed scene and check the
- * meshes details (eg. name).
- * Besides the mesh, this function will also add any associated data to the
- * Scene struct such as Materials and Textures required by the Mesh.
- *
- * @param path	must be the path to a glTF- or glb-file.
- * @param scene	is the scene struct to which the results will be written.
- */
-int loadMesh(Scene &scene, int mesh_index);
+	/**
+	 * Parse the given glTF file and create a shallow description of the content.
+	 * Only the meta-data of the objects in the scene is loaded, not the binary
+	 * content. The rationale is to provide a means of probing the content of a
+	 * glTF file without the costly process of loading and decoding large amounts
+	 * of data. The returned Scene struct can be used to search for specific meshes
+	 * in the scene, that can then be loaded on their own using the loadMesh()
+	 * function. Note that the Scene struct received as output argument will be
+	 * overwritten by this function.
+	 * After this function completes, the returned Scene struct is completely
+	 * initialized and all information is final, except for the missing binary
+	 * data. This means that indices to vectors will remain valid even when the
+	 * shallow scene struct is filled with data by loadMesh().
+	 *
+	 * @param path	must be the path to a glTF- or glb-file.
+	 * @param scene	is a reference to a Scene struct that will be filled with the
+	 * 	meta-data of all objects described in the glTF file.
+	 */
+	int probeScene(const std::filesystem::path &path, Scene &scene);
 
-struct TextureData {
-    int width;
-    int height;
-    int componentCount;
-    std::vector<char*> data;
-};
+	/**
+	 * This function loads a single mesh from the given file and adds it to the
+	 * given scene. The scene must already be initialized (via probeScene()).
+	 * The mesh_index refers to the Scenes meshes array and identifies the mesh to
+	 * load. To find the mesh you want, iterate over the probed scene and check the
+	 * meshes details (eg. name).
+	 * Besides the mesh, this function will also add any associated data to the
+	 * Scene struct such as Materials and Textures required by the Mesh.
+	 *
+	 * @param path	must be the path to a glTF- or glb-file.
+	 * @param scene	is the scene struct to which the results will be written.
+	 */
+	int loadMesh(Scene &scene, int mesh_index);
+	
+	/**
+	 * Load every mesh from the glTF file, as well as materials, textures and other
+	 * associated objects.
+	 *
+	 * @param path	must be the path to a glTF- or glb-file.
+	 * @param scene is a reference to a Scene struct that will be filled with the
+	 * 	content of the glTF file being loaded.
+	 */
+	int loadScene(const std::filesystem::path &path, Scene &scene);
 
-TextureData loadTexture(const std::filesystem::path& path);
+	Texture loadTexture(const std::filesystem::path& path);
 
 }
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index f07d4ba7..07b1c543 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -12,823 +12,725 @@
 
 namespace vkcv::asset {
 
-/**
- * This function unrolls nested exceptions via recursion and prints them
- * @param e	The exception being thrown
- * @param path	The path to the file that was responsible for the exception
- */
-void recurseExceptionPrint(const std::exception& e, const std::string &path)
-{
-	vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what());
-	try {
-		std::rethrow_if_nested(e);
-	} catch (const std::exception& nested) {
-		recurseExceptionPrint(nested, path);
-	}
-}
-
-/**
- * Computes the component count for an accessor type of the fx-gltf library.
- * @param type	The accessor type
- * @return	An unsigned integer count
- */
-uint8_t getCompCount(const fx::gltf::Accessor::Type type) {
-	switch (type) {
-	case fx::gltf::Accessor::Type::Scalar :
-		return 1;
-	case fx::gltf::Accessor::Type::Vec2 :
-		return 2;
-	case fx::gltf::Accessor::Type::Vec3 :
-		return 3;
-	case fx::gltf::Accessor::Type::Vec4 :
-	case fx::gltf::Accessor::Type::Mat2 :
-		return 4;
-	case fx::gltf::Accessor::Type::Mat3 :
-		return 9;
-	case fx::gltf::Accessor::Type::Mat4 :
-		return 16;
-	case fx::gltf::Accessor::Type::None :
-	default:
-		return ASSET_ERROR;
+	/**
+	 * This function unrolls nested exceptions via recursion and prints them
+	 * @param e	The exception being thrown
+	 * @param path	The path to the file that was responsible for the exception
+	 */
+	static void recurseExceptionPrint(const std::exception& e, const std::string &path) {
+		vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what());
+		
+		try {
+			std::rethrow_if_nested(e);
+		} catch (const std::exception& nested) {
+			recurseExceptionPrint(nested, path);
+		}
 	}
-}
 
-/**
- * Translate the component type used in the index accessor of fx-gltf to our
- * enum for index type. The reason we have defined an incompatible enum that
- * needs translation is that only a subset of component types is valid for
- * indices and we want to catch these incompatibilities here.
- * @param t	The component type
- * @return 	The vkcv::IndexType enum representation
- */
-enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type)
-{
-	switch (type) {
-	case fx::gltf::Accessor::ComponentType::UnsignedByte:
-		return IndexType::UINT8;
-	case fx::gltf::Accessor::ComponentType::UnsignedShort:
-		return IndexType::UINT16;
-	case fx::gltf::Accessor::ComponentType::UnsignedInt:
-		return IndexType::UINT32;
-	default:
-		vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type));
-		return IndexType::UNDEFINED;
+	/**
+	 * Returns the component count for an accessor type of the fx-gltf library.
+	 * @param type	The accessor type
+	 * @return	An unsigned integer count
+	 */
+	static uint8_t getComponentCount(const fx::gltf::Accessor::Type type) {
+		switch (type) {
+		case fx::gltf::Accessor::Type::Scalar:
+			return 1;
+		case fx::gltf::Accessor::Type::Vec2:
+			return 2;
+		case fx::gltf::Accessor::Type::Vec3:
+			return 3;
+		case fx::gltf::Accessor::Type::Vec4:
+		case fx::gltf::Accessor::Type::Mat2:
+			return 4;
+		case fx::gltf::Accessor::Type::Mat3:
+			return 9;
+		case fx::gltf::Accessor::Type::Mat4:
+			return 16;
+		case fx::gltf::Accessor::Type::None:
+		default:
+			return 0;
+		}
 	}
-}
 
-
-int loadTexture(const Scene &scene, Texture &tex)
-{
-	if (tex.uri < 0 || tex.uri >= scene.uris.size()) {
-		vkcv_log(LogLevel::ERROR, "URI index out of range: %d", tex.uri);
-	}
-	const std::string &uri = scene.uris[tex.uri];
-	int w, h, ch;
-	uint8_t *data = NULL;
-	if (!(data = stbi_load(uri.c_str(), &w, &h, &ch, 4))) {
-		vkcv_log(LogLevel::ERROR, "Failed to load image data from %s",
-				uri.c_str());
-		tex.w = tex.h = tex.channels = 0;
-		return ASSET_ERROR;
+	/**
+	 * Translate the component type used in the index accessor of fx-gltf to our
+	 * enum for index type. The reason we have defined an incompatible enum that
+	 * needs translation is that only a subset of component types is valid for
+	 * indices and we want to catch these incompatibilities here.
+	 * @param t	The component type
+	 * @return 	The vkcv::IndexType enum representation
+	 */
+	enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) {
+		switch (type) {
+		case fx::gltf::Accessor::ComponentType::UnsignedByte:
+			return IndexType::UINT8;
+		case fx::gltf::Accessor::ComponentType::UnsignedShort:
+			return IndexType::UINT16;
+		case fx::gltf::Accessor::ComponentType::UnsignedInt:
+			return IndexType::UINT32;
+		default:
+			vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type));
+			return IndexType::UNDEFINED;
+		}
 	}
-	tex.channels = 4;
-	tex.w = static_cast<uint16_t>(w);
-	tex.h = static_cast<uint16_t>(h);
-
-	tex.data.resize(tex.w * tex.h * tex.channels);
-	memcpy(tex.data.data(), data, tex.data.size());
-	free(data);
-
-	return ASSET_SUCCESS;
-}
-
 
-/**
- * This function loads all the textures of a Scene described by the texture-
- * and image- array of an fx::gltf::Document.
- * @param tex_src The array of textures from a fx::gltf::Document
- * @param img_src The array of images from a fx::gltf::Document
- * @param buf_src The Array of buffers from a fx::gltf::Document
- * @param bV_src The Array of bufferViews from a fx::gltf::Document
- * @param dir	  The path of directory in which the glTF file is located
- * @param dst	  The array from the vkcv::Scene to write the textures to
- * @return	  ASSET_ERROR if at least one texture could not be constructed
- * 		  properly, otherwise ASSET_SUCCESS
- */
-int createTextures(const std::vector<fx::gltf::Texture>& tex_src,
-	const std::vector<fx::gltf::Image>& img_src,
-	const std::vector<fx::gltf::Buffer>& buf_src,
-	const std::vector<fx::gltf::BufferView>& bV_src,
-	const std::string& dir, std::vector<std::string> &uris,
-	std::vector<Texture>& dst)
-{
-	std::unordered_map<std::string,int> known_uris;
-	for (int i = 0; i < uris.size(); i++) known_uris[uris[i]] = i;
-
-	dst.clear();
-	dst.reserve(tex_src.size());
-	for (const auto& tex : tex_src) {
-		std::string uri = dir + "/" + img_src[tex.source].uri;
-		int w, h, c;
-		uint8_t* data;
-		int uri_index = -1;
-		if (!uri.empty()) {
-			data = stbi_load(uri.c_str(), &w, &h, &c, 4);
-			if (!data) {
-				vkcv_log(LogLevel::ERROR, "Failed to load image data from %s",
-					uri.c_str());
-				return ASSET_ERROR;
-			}
-			if (known_uris.count(uri)) {
-				uri_index = known_uris[uri];
+	/**
+	 * This function fills the array of vertex attributes of a VertexGroup (usually
+	 * part of a vkcv::asset::Mesh) object based on the description of attributes
+	 * for a fx::gltf::Primitive.
+	 *
+	 * @param src	The description of attribute objects from the fx-gltf library
+	 * @param gltf	The main glTF document
+	 * @param dst	The array of vertex attributes stored in an asset::Mesh object
+	 * @return	ASSET_ERROR when at least one VertexAttribute could not be
+	 * 		constructed properly, otherwise ASSET_SUCCESS
+	 */
+	static int loadVertexAttributes(const fx::gltf::Attributes &src,
+									const std::vector<fx::gltf::Accessor> &accessors,
+									const std::vector<fx::gltf::BufferView> &bufferViews,
+									std::vector<VertexAttribute> &dst) {
+		for (const auto &attrib : src) {
+			VertexAttribute att;
+			
+			if (attrib.first == "POSITION") {
+				att.type = PrimitiveType::POSITION;
+			} else if (attrib.first == "NORMAL") {
+				att.type = PrimitiveType::NORMAL;
+			} else if (attrib.first == "TANGENT") {
+				att.type = PrimitiveType::TANGENT;
+			} else if (attrib.first == "TEXCOORD_0") {
+				att.type = PrimitiveType::TEXCOORD_0;
+			} else if (attrib.first == "TEXCOORD_1") {
+				att.type = PrimitiveType::TEXCOORD_1;
+			} else if (attrib.first == "COLOR_0") {
+				att.type = PrimitiveType::COLOR_0;
+			} else if (attrib.first == "COLOR_1") {
+				att.type = PrimitiveType::COLOR_1;
+			} else if (attrib.first == "JOINTS_0") {
+				att.type = PrimitiveType::JOINTS_0;
+			} else if (attrib.first == "WEIGHTS_0") {
+				att.type = PrimitiveType::WEIGHTS_0;
 			} else {
-				uri_index = uris.size();
-				uris.push_back(uri);
+				att.type = PrimitiveType::UNDEFINED;
+			}
+			
+			if (att.type != PrimitiveType::UNDEFINED) {
+				const fx::gltf::Accessor &accessor = accessors[attrib.second];
+				const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView];
+				
+				att.offset = buf.byteOffset;
+				att.length = buf.byteLength;
+				att.stride = buf.byteStride;
+				att.componentType = static_cast<ComponentType>(accessor.componentType);
+				att.componentCount = getComponentCount(accessor.type);
 			}
-		} else {
-			const fx::gltf::BufferView bufferView = bV_src[img_src[tex.source].bufferView];
-			data = stbi_load_from_memory(
-					&buf_src[bufferView.buffer].data[bufferView.byteOffset],
-					static_cast<int>(bufferView.byteLength),
-					&w, &h, &c, 4
-			);
 			
-			if (!data) {
-				vkcv_log(LogLevel::ERROR, "Failed to load image data from Buffer %s",
-					buf_src[bufferView.buffer].name.c_str());
+			if ((att.type == PrimitiveType::UNDEFINED) ||
+				(att.componentCount == 0)) {
 				return ASSET_ERROR;
 			}
+			
+			dst.push_back(att);
 		}
-		c = 4;
-		const size_t nbytes = w * h * c;
-		std::vector<uint8_t> imgdata;
-		imgdata.resize(nbytes);
-		if (!memcpy(imgdata.data(), data, nbytes)) {
-			vkcv_log(LogLevel::ERROR, "Failed to copy texture data");
-			free(data);
-			return ASSET_ERROR;
-		}
-		free(data);
-
-		if (uri_index < 0) vkcv_log(LogLevel::WARNING, "Texture is missing a URI.");
-		dst.push_back({
-			uri_index,
-			tex.sampler,
-			static_cast<uint8_t>(c),
-			static_cast<uint16_t>(w), static_cast<uint16_t>(h),
-			imgdata
-		});
+		
+		return ASSET_SUCCESS;
 	}
-	return ASSET_SUCCESS;
-}
 
-/**
- * This function fills the array of vertex attributes of a VertexGroup (usually
- * part of a vkcv::asset::Mesh) object based on the description of attributes
- * for a fx::gltf::Primitive.
- * @param src	The description of attribute objects from the fx-gltf library
- * @param gltf	The main glTF document
- * @param dst	The array of vertex attributes stored in an asset::Mesh object
- * @return	ASSET_ERROR when at least one VertexAttribute could not be
- * 		constructed properly, otherwise ASSET_SUCCESS
- */
-int createVertexAttributes(const fx::gltf::Attributes &src,
-		const std::vector<fx::gltf::Accessor> &accessors,
-		const std::vector<fx::gltf::BufferView> &bufferViews,
-		std::vector<VertexAttribute> &dst)
-{
-	dst.clear();
-	dst.reserve(src.size());
-	for (const auto &attrib : src) {
-		const fx::gltf::Accessor &accessor = accessors[attrib.second];
-
-		dst.push_back({});
-		VertexAttribute &att = dst.back();
-		if (attrib.first == "POSITION") {
-			att.type = PrimitiveType::POSITION;
-		} else if (attrib.first == "NORMAL") {
-			att.type = PrimitiveType::NORMAL;
-		} else if (attrib.first == "TANGENT") {
-			att.type = PrimitiveType::TANGENT;
-		} else if (attrib.first == "TEXCOORD_0") {
-			att.type = PrimitiveType::TEXCOORD_0;
-		} else if (attrib.first == "TEXCOORD_1") {
-			att.type = PrimitiveType::TEXCOORD_1;
-		} else if (attrib.first == "COLOR_0") {
-			att.type = PrimitiveType::COLOR_0;
-		} else if (attrib.first == "COLOR_1") {
-			att.type = PrimitiveType::COLOR_1;
-		} else if (attrib.first == "JOINTS_0") {
-			att.type = PrimitiveType::JOINTS_0;
-		} else if (attrib.first == "WEIGHTS_0") {
-			att.type = PrimitiveType::WEIGHTS_0;
-		} else {
-			att.type = PrimitiveType::UNDEFINED;
-			return ASSET_ERROR;
-		}
-		const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView];
-		att.offset = buf.byteOffset;
-		att.length = buf.byteLength;
-		att.stride = buf.byteStride;
-		att.componentType = static_cast<ComponentType>(accessor.componentType);
-		att.componentCount = getCompCount(accessor.type);
+	/**
+	 * This function calculates the modelMatrix out of the data given in the gltf file.
+	 * It also checks, whether a modelMatrix was given.
+	 *
+	 * @param translation possible translation vector (default 0,0,0)
+	 * @param scale possible scale vector (default 1,1,1)
+	 * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
+	 * @param matrix possible modelmatrix (default identity)
+	 * @return model Matrix as an array of floats
+	 */
+	static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation,
+													  const std::array<float, 3>& scale,
+													  const std::array<float, 4>& rotation,
+													  const std::array<float, 16>& matrix){
+		std::array<float, 16> modelMatrix = {
+				1,0,0,0,
+				0,1,0,0,
+				0,0,1,0,
+				0,0,0,1
+		};
 		
-		if (att.componentCount == ASSET_ERROR) {
-			return ASSET_ERROR;
+		if (matrix != modelMatrix){
+			return matrix;
+		} else {
+			// translation
+			modelMatrix[3] = translation[0];
+			modelMatrix[7] = translation[1];
+			modelMatrix[11] = translation[2];
+			
+			// rotation and scale
+			auto a = rotation[0];
+			auto q1 = rotation[1];
+			auto q2 = rotation[2];
+			auto q3 = rotation[3];
+	
+			modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
+			modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
+			modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
+	
+			modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
+			modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
+			modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
+	
+			modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
+			modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
+			modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
+	
+			// flip y, because GLTF uses y up, but vulkan -y up
+			modelMatrix[5] *= -1;
+	
+			return modelMatrix;
 		}
 	}
-	return ASSET_SUCCESS;
-}
-
-/**
- * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
- * @param translation possible translation vector (default 0,0,0)
- * @param scale possible scale vector (default 1,1,1)
- * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
- * @param matrix possible modelmatrix (default identity)
- * @return model Matrix as an array of floats
- */
-std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
-    std::array<float, 16> modelMatrix = {1,0,0,0,
-                                         0,1,0,0,
-                                         0,0,1,0,
-                                         0,0,0,1};
-    if (matrix != modelMatrix){
-        return matrix;
-    } else {
-        // translation
-        modelMatrix[3] = translation[0];
-        modelMatrix[7] = translation[1];
-        modelMatrix[11] = translation[2];
-        // rotation and scale
-        auto a = rotation[0];
-        auto q1 = rotation[1];
-        auto q2 = rotation[2];
-        auto q3 = rotation[3];
-
-        modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
-        modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
-        modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
-
-        modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
-        modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
-        modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
-
-        modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
-        modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
-        modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
-
-        // flip y, because GLTF uses y up, but vulkan -y up
-        modelMatrix[5] *= -1;
-
-        return modelMatrix;
-    }
-
-}
-
-bool Material::hasTexture(const PBRTextureTarget t) const
-{
-	return textureMask & bitflag(t);
-}
-
- /**
-  * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart.
-  * @param mode: wrapping mode of a sampler given as fx-gltf-enum
-  * @return int vulkan-enum representing the same wrapping mode
-  */
-int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode)
-{
-	switch (mode) {
-	case fx::gltf::Sampler::WrappingMode::ClampToEdge:
-		return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
-	case fx::gltf::Sampler::WrappingMode::MirroredRepeat:
-		return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
-	case fx::gltf::Sampler::WrappingMode::Repeat:
-	default:
-		return VK_SAMPLER_ADDRESS_MODE_REPEAT;
-	}
-}
 
-/**
- * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf.
- * The following are details about the glTF/OpenGL to Vulkan translation.
- * magFilter (VkFilter?):
- * 	GL_NEAREST -> VK_FILTER_NEAREST
- * 	GL_LINEAR -> VK_FILTER_LINEAR
- * minFilter (VkFilter?):
- * mipmapMode (VkSamplerMipmapMode?):
- * Vulkans minFilter and mipmapMode combined correspond to OpenGLs
- * GL_minFilter_MIPMAP_mipmapMode:
- * 	GL_NEAREST_MIPMAP_NEAREST:
- * 		minFilter=VK_FILTER_NEAREST
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
- * 	GL_LINEAR_MIPMAP_NEAREST:
- * 		minFilter=VK_FILTER_LINEAR
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
- * 	GL_NEAREST_MIPMAP_LINEAR:
- * 		minFilter=VK_FILTER_NEAREST
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
- * 	GL_LINEAR_MIPMAP_LINEAR:
- * 		minFilter=VK_FILTER_LINEAR
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
- * The modes of GL_LINEAR and GL_NEAREST have to be emulated using
- * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD:
- * 	GL_LINEAR:
- * 		minFilter=VK_FILTER_LINEAR
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
- * 		minLOD=0, maxLOD=0.25
- * 	GL_NEAREST:
- * 		minFilter=VK_FILTER_NEAREST
- * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
- * 		minLOD=0, maxLOD=0.25
- * Setting maxLOD=0 causes magnification to always be performed (using
- * the defined magFilter), this may be valid if the min- and magFilter
- * are equal, otherwise it won't be the expected behaviour from OpenGL
- * and glTF; instead using maxLod=0.25 allows the minFilter to be
- * performed while still always rounding to the base level.
- * With other modes, minLOD and maxLOD default to:
- * 	minLOD=0
- * 	maxLOD=VK_LOD_CLAMP_NONE
- * wrapping:
- * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while
- * Vulkan has addressModeU, addressModeV, addressModeW with values
- * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE,
- * 			    CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE}
- * Translation from glTF to Vulkan is straight forward for the 3 existing
- * modes, default is repeat, the other modes aren't available.
- */
-int translateSampler(const fx::gltf::Sampler &src, vkcv::asset::Sampler &dst)
-{
-	dst.minLOD = 0;
-	dst.maxLOD = VK_LOD_CLAMP_NONE;
-
-	switch (src.minFilter) {
-	case fx::gltf::Sampler::MinFilter::None:
-	case fx::gltf::Sampler::MinFilter::Nearest:
-		dst.minFilter = VK_FILTER_NEAREST;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
-		dst.maxLOD = 0.25;
-		break;
-	case fx::gltf::Sampler::MinFilter::Linear:
-		dst.minFilter = VK_FILTER_LINEAR;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
-		dst.maxLOD = 0.25;
-		break;
-	case fx::gltf::Sampler::MinFilter::NearestMipMapNearest:
-		dst.minFilter = VK_FILTER_NEAREST;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
-		break;
-	case fx::gltf::Sampler::MinFilter::LinearMipMapNearest:
-		dst.minFilter = VK_FILTER_LINEAR;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
-		break;
-	case fx::gltf::Sampler::MinFilter::NearestMipMapLinear:
-		dst.minFilter = VK_FILTER_NEAREST;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
-		break;
-	case fx::gltf::Sampler::MinFilter::LinearMipMapLinear:
-		dst.minFilter = VK_FILTER_LINEAR;
-		dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
-		break;
-	default:
-		break;
+	bool Material::hasTexture(const PBRTextureTarget target) const {
+		return textureMask & bitflag(target);
 	}
 
-	switch (src.magFilter) {
-	case fx::gltf::Sampler::MagFilter::None:
-	case fx::gltf::Sampler::MagFilter::Nearest:
-		dst.magFilter = VK_FILTER_NEAREST;
-		break;
-	case fx::gltf::Sampler::MagFilter::Linear:
-		dst.magFilter = VK_FILTER_LINEAR;
-		break;
-	default:
-		break;
+	/**
+	 * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart.
+	 * @param mode: wrapping mode of a sampler given as fx-gltf-enum
+	 * @return int vulkan-enum representing the same wrapping mode
+	 */
+	static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) {
+		switch (mode) {
+		case fx::gltf::Sampler::WrappingMode::ClampToEdge:
+			return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		case fx::gltf::Sampler::WrappingMode::MirroredRepeat:
+			return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		case fx::gltf::Sampler::WrappingMode::Repeat:
+		default:
+			return VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		}
 	}
 
-	dst.addressModeU = translateSamplerMode(src.wrapS);
-	dst.addressModeV = translateSamplerMode(src.wrapT);
-	// There is no information about wrapping for a third axis in glTF and
-	// we have to hardcode this value.
-	dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
-
-	return ASSET_SUCCESS;
-}
-
-/**
- * TODO document
- */
-int createVertexGroups(fx::gltf::Mesh const& objectMesh,
-	fx::gltf::Document &sceneObjects, 
-	std::vector<VertexGroup> &vertexGroups,
-	std::vector<int> &vertexGroupsIndices,
-	std::vector<std::string> &uris,
-	int &groupCount, bool probe) {
-
-	const size_t numVertexGroups = objectMesh.primitives.size();
-	vertexGroups.reserve(numVertexGroups);
-
-	std::unordered_map<std::string,int> known_uris;
-	for (int i = 0; i < uris.size(); i++) known_uris[uris[i]] = i;
-
-	for (const auto & objectPrimitive : objectMesh.primitives) {
-			std::vector<VertexAttribute> vertexAttributes;
-		vertexAttributes.reserve(objectPrimitive.attributes.size());
-
-		if (createVertexAttributes(objectPrimitive.attributes,
-			sceneObjects.accessors,
-			sceneObjects.bufferViews,
-			vertexAttributes)
-			!= ASSET_SUCCESS) {
-			vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes");
-			return ASSET_ERROR;
+	/**
+	 * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf.
+	 * The following are details about the glTF/OpenGL to Vulkan translation.
+	 * magFilter (VkFilter?):
+	 * 	GL_NEAREST -> VK_FILTER_NEAREST
+	 * 	GL_LINEAR -> VK_FILTER_LINEAR
+	 * minFilter (VkFilter?):
+	 * mipmapMode (VkSamplerMipmapMode?):
+	 * Vulkans minFilter and mipmapMode combined correspond to OpenGLs
+	 * GL_minFilter_MIPMAP_mipmapMode:
+	 * 	GL_NEAREST_MIPMAP_NEAREST:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 	GL_LINEAR_MIPMAP_NEAREST:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 	GL_NEAREST_MIPMAP_LINEAR:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
+	 * 	GL_LINEAR_MIPMAP_LINEAR:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
+	 * The modes of GL_LINEAR and GL_NEAREST have to be emulated using
+	 * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD:
+	 * 	GL_LINEAR:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 		minLOD=0, maxLOD=0.25
+	 * 	GL_NEAREST:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 		minLOD=0, maxLOD=0.25
+	 * Setting maxLOD=0 causes magnification to always be performed (using
+	 * the defined magFilter), this may be valid if the min- and magFilter
+	 * are equal, otherwise it won't be the expected behaviour from OpenGL
+	 * and glTF; instead using maxLod=0.25 allows the minFilter to be
+	 * performed while still always rounding to the base level.
+	 * With other modes, minLOD and maxLOD default to:
+	 * 	minLOD=0
+	 * 	maxLOD=VK_LOD_CLAMP_NONE
+	 * wrapping:
+	 * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while
+	 * Vulkan has addressModeU, addressModeV, addressModeW with values
+	 * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE,
+	 * 			    CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE}
+	 * Translation from glTF to Vulkan is straight forward for the 3 existing
+	 * modes, default is repeat, the other modes aren't available.
+	 */
+	static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) {
+		Sampler dst;
+		
+		dst.minLOD = 0;
+		dst.maxLOD = VK_LOD_CLAMP_NONE;
+	
+		switch (src.minFilter) {
+		case fx::gltf::Sampler::MinFilter::None:
+		case fx::gltf::Sampler::MinFilter::Nearest:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			dst.maxLOD = 0.25;
+			break;
+		case fx::gltf::Sampler::MinFilter::Linear:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			dst.maxLOD = 0.25;
+			break;
+		case fx::gltf::Sampler::MinFilter::NearestMipMapNearest:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			break;
+		case fx::gltf::Sampler::MinFilter::LinearMipMapNearest:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			break;
+		case fx::gltf::Sampler::MinFilter::NearestMipMapLinear:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			break;
+		case fx::gltf::Sampler::MinFilter::LinearMipMapLinear:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			break;
+		default:
+			break;
 		}
-		// The accessor for the position attribute is used for
-		// 1) getting the vertex buffer view which is only needed to get
-		//    the vertex buffer
-		// 2) getting the vertex count for the VertexGroup
-		// 3) getting the min/max of the bounding box for the VertexGroup
-		fx::gltf::Accessor posAccessor;
-		for (auto const& attrib : objectPrimitive.attributes) {
-			if (attrib.first == "POSITION") {
-				posAccessor = sceneObjects.accessors[attrib.second];
-				break;
-			}
+	
+		switch (src.magFilter) {
+		case fx::gltf::Sampler::MagFilter::None:
+		case fx::gltf::Sampler::MagFilter::Nearest:
+			dst.magFilter = VK_FILTER_NEAREST;
+			break;
+		case fx::gltf::Sampler::MagFilter::Linear:
+			dst.magFilter = VK_FILTER_LINEAR;
+			break;
+		default:
+			break;
 		}
+	
+		dst.addressModeU = translateSamplerMode(src.wrapS);
+		dst.addressModeV = translateSamplerMode(src.wrapT);
+		
+		// There is no information about wrapping for a third axis in glTF and
+		// we have to hardcode this value.
+		dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		
+		return dst;
+	}
 
-		IndexType indexType;
-		std::vector<uint8_t> indexBufferData = {};
-		const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
-		int indexBufferURI;
-		if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf
-			const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
-			const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
-			if (known_uris.count(indexBuffer.uri)) {
-				indexBufferURI = known_uris[indexBuffer.uri];
-			} else {
-				indexBufferURI = uris.size();
-				uris.push_back(indexBuffer.uri);
+	/**
+	 * TODO document
+	 */
+	static int loadVertexGroups(const fx::gltf::Mesh &objectMesh,
+								const fx::gltf::Document &sceneObjects,
+								Scene &scene, Mesh &mesh) {
+		mesh.vertexGroups.reserve(objectMesh.primitives.size());
+	
+		for (const auto & objectPrimitive : objectMesh.primitives) {
+			VertexGroup vertexGroup;
+			
+			vertexGroup.vertexBuffer.attributes.reserve(
+					objectPrimitive.attributes.size()
+			);
+	
+			if (ASSET_SUCCESS != loadVertexAttributes(
+					objectPrimitive.attributes,
+					sceneObjects.accessors,
+					sceneObjects.bufferViews,
+					vertexGroup.vertexBuffer.attributes)) {
+				vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'",
+						 mesh.name.c_str());
+				return ASSET_ERROR;
 			}
-
-			indexBufferData.resize(indexBufferView.byteLength);
-			if (!probe) {
-				const size_t off = indexBufferView.byteOffset;
-				const void* const ptr = ((char*)indexBuffer.data.data()) + off;
-				if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
-					vkcv_log(LogLevel::ERROR, "Copying index buffer data");
-					return ASSET_ERROR;
+			
+			// The accessor for the position attribute is used for
+			// 1) getting the vertex buffer view which is only needed to get
+			//    the vertex buffer
+			// 2) getting the vertex count for the VertexGroup
+			// 3) getting the min/max of the bounding box for the VertexGroup
+			fx::gltf::Accessor posAccessor;
+			bool noPosition = true;
+			
+			for (auto const& attrib : objectPrimitive.attributes) {
+				if (attrib.first == "POSITION") {
+					posAccessor = sceneObjects.accessors[attrib.second];
+					noPosition = false;
+					break;
 				}
 			}
-		}
-		indexType = getIndexType(indexAccessor.componentType);
-		if (indexType == IndexType::UNDEFINED) {
-			vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported.");
-			return ASSET_ERROR;
-		}
-
-		const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
-		const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
-		int vertexBufferURI;
-		if (known_uris.count(vertexBuffer.uri)) {
-			vertexBufferURI = known_uris[vertexBuffer.uri];
-		} else {
-			vertexBufferURI = uris.size();
-			uris.push_back(vertexBuffer.uri);
-		}
-
-		// only copy relevant part of vertex data
-		uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
-		uint32_t relevantBufferEnd = 0;
-		for (const auto& attribute : vertexAttributes) {
-			relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
-			const uint32_t attributeEnd = attribute.offset + attribute.length;
-			relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd);
-		}
-		const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
-
-		std::vector<uint8_t> vertexBufferData;
-		if (!probe) {
-			vertexBufferData.resize(relevantBufferSize);
-			{
-				const void* const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
-				if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
-					vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
-					return ASSET_ERROR;
-				}
+			
+			if (noPosition) {
+				vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'",
+						 mesh.name.c_str());
+				return ASSET_ERROR;
 			}
-
+	
+			const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
+			
+			int indexBufferURI;
+			if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf
+				const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
+				const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
+				
+				// Because the buffers are already preloaded into the memory by the gltf-library,
+				// it makes no sense to later them later on manually again into memory.
+				vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength);
+				memcpy(vertexGroup.indexBuffer.data.data(),
+					   indexBuffer.data.data() + indexBufferView.byteOffset,
+					   indexBufferView.byteLength);
+			} else {
+				indexBufferURI = -1;
+			}
+			
+			vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType);
+			
+			if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) {
+				vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported.");
+				return ASSET_ERROR;
+			}
+	
+			const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
+			const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
+			
+			// only copy relevant part of vertex data
+			uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
+			uint32_t relevantBufferEnd = 0;
+			
+			for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+				relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
+				relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length);
+			}
+			
+			const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
+			
+			vertexGroup.vertexBuffer.data.resize(relevantBufferSize);
+			memcpy(vertexGroup.vertexBuffer.data.data(),
+				   vertexBuffer.data.data() + relevantBufferOffset,
+				   relevantBufferSize);
+			
 			// make vertex attributes relative to copied section
-			for (auto& attribute : vertexAttributes) {
+			for (auto& attribute : vertexGroup.vertexBuffer.attributes) {
 				attribute.offset -= relevantBufferOffset;
 			}
+			
+			vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode);
+			vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count;
+			vertexGroup.numVertices = posAccessor.count;
+			
+			memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min));
+			memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max));
+			
+			vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material);
+			
+			mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size()));
+			scene.vertexGroups.push_back(vertexGroup);
 		}
 		
-		vertexGroups.push_back({
-			static_cast<PrimitiveMode>(objectPrimitive.mode),
-			sceneObjects.accessors[objectPrimitive.indices].count,
-			posAccessor.count,
-			{indexBufferURI, indexType, indexBufferData},
-			{vertexBufferURI, vertexBufferData, vertexAttributes},
-			{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
-			{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
-			static_cast<uint8_t>(objectPrimitive.material)
-			});
-
-		vertexGroupsIndices.push_back(groupCount);
-		groupCount++;
-	}
-	return ASSET_SUCCESS;
-}
-
-/**
- * TODO document
- */
-void generateTextureMask(fx::gltf::Material &material, uint16_t &textureMask) {
-	if (material.pbrMetallicRoughness.baseColorTexture.index > -1) {
-		textureMask |= bitflag(asset::PBRTextureTarget::baseColor);
+		return ASSET_SUCCESS;
 	}
-	if (material.pbrMetallicRoughness.metallicRoughnessTexture.index > -1) {
-		textureMask |= bitflag(asset::PBRTextureTarget::metalRough);
-	}
-	if (material.normalTexture.index > -1) {
-		textureMask |= bitflag(asset::PBRTextureTarget::normal);
-	}
-	if (material.occlusionTexture.index > -1) {
-		textureMask |= bitflag(asset::PBRTextureTarget::occlusion);
-	}
-	if (material.emissiveTexture.index > -1) {
-		textureMask |= bitflag(asset::PBRTextureTarget::emissive);
-	}
-}
-
-/**
- * TODO document
- */
-int createMaterial(fx::gltf::Document &sceneObjects, std::vector<Material> &materials) {
-	if (!sceneObjects.materials.empty()) {
-		materials.reserve(sceneObjects.materials.size());
 
-		for (auto material : sceneObjects.materials) {
-				uint16_t textureMask = 0;
-			generateTextureMask(material, textureMask);
-			materials.push_back({
-			textureMask,	
-			material.pbrMetallicRoughness.baseColorTexture.index,
-			material.pbrMetallicRoughness.metallicRoughnessTexture.index,
-			material.normalTexture.index,
-			material.occlusionTexture.index,
-			material.emissiveTexture.index,
-			{
-				material.pbrMetallicRoughness.baseColorFactor[0],
-				material.pbrMetallicRoughness.baseColorFactor[1],
-				material.pbrMetallicRoughness.baseColorFactor[2],
-				material.pbrMetallicRoughness.baseColorFactor[3]
-			},
-			material.pbrMetallicRoughness.metallicFactor,
-			material.pbrMetallicRoughness.roughnessFactor,
-			material.normalTexture.scale,
-			material.occlusionTexture.strength,
-			{
-				material.emissiveFactor[0],
-				material.emissiveFactor[1],
-				material.emissiveFactor[2]
-			}
-			});
+	/**
+	 * TODO document
+	 */
+	static uint16_t generateTextureMask(fx::gltf::Material &material) {
+		uint16_t textureMask = 0;
+		
+		if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::baseColor);
 		}
-	} else {
-		return ASSET_ERROR;
-	}
-	return ASSET_SUCCESS;
-}
-
-int loadScene(const std::filesystem::path &path, Scene &scene){
-    fx::gltf::Document sceneObjects;
-
-	try {
-		if ( path.extension() == ".glb") {
-			sceneObjects = fx::gltf::LoadFromBinary(path.string());
+		if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::metalRough);
 		}
-		else {
-			sceneObjects = fx::gltf::LoadFromText(path.string());
+		if (material.normalTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::normal);
 		}
-	} catch (const std::system_error& err) {
-		recurseExceptionPrint(err, path.string());
-		return ASSET_ERROR;
-	} catch (const std::exception& e) {
-		recurseExceptionPrint(e, path.string());
-		return ASSET_ERROR;
-	}
-	auto dir = path.parent_path().string();
-
-    // file has to contain at least one mesh
-    if (sceneObjects.meshes.empty()) {
-    	return ASSET_ERROR;
-    }
-
-    std::vector<Material> materials;
-    std::vector<Texture> textures;
-    std::vector<Sampler> samplers;
-    std::vector<Mesh> meshes;
-    std::vector<VertexGroup> vertexGroups;
-    std::vector<std::string> uris;
-	int groupCount = 0;
-
-    for (size_t i = 0; i < sceneObjects.meshes.size(); i++){
-        std::vector<int> vertexGroupsIndices;
-        fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
-
-		if (createVertexGroups(objectMesh, sceneObjects, vertexGroups,
-					vertexGroupsIndices, uris, groupCount,
-					false) != ASSET_SUCCESS) {
-			vkcv_log(LogLevel::ERROR, "Failed to get Vertex Groups!");
-			return ASSET_ERROR;
+		if (material.occlusionTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::occlusion);
 		}
-
-		Mesh mesh = {};
-        mesh.name = sceneObjects.meshes[i].name;
-        mesh.vertexGroups = vertexGroupsIndices;
-
-        meshes.push_back(mesh);
-    }
-
-    // This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights
-    for (auto & node : sceneObjects.nodes) {
-		if (node.mesh > -1) {
-			meshes[node.mesh].modelMatrix = computeModelMatrix(
-					node.translation,
-					node.scale,
-					node.rotation,
-					node.matrix
-			);
+		if (material.emissiveTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::emissive);
 		}
+		
+		return textureMask;
 	}
 
-    if (createTextures(sceneObjects.textures, sceneObjects.images,sceneObjects.buffers,sceneObjects.bufferViews, dir, uris, textures) != ASSET_SUCCESS) {
-	    size_t missing = sceneObjects.textures.size() - textures.size();
-	    vkcv_log(LogLevel::ERROR, "Failed to get %lu textures from glTF source '%s'",
-			    missing, path.c_str());
-    }
-
-
-	if (createMaterial(sceneObjects, materials) != ASSET_SUCCESS) {
-		vkcv_log(LogLevel::ERROR, "Failed to get Materials!");
-		return ASSET_ERROR;
-	}
-
-    samplers.reserve(sceneObjects.samplers.size());
-    for (const auto &it : sceneObjects.samplers) {
-	    samplers.push_back({});
-	    auto &sampler = samplers.back();
-	    if (translateSampler(it, sampler) != ASSET_SUCCESS) {
+	int probeScene(const std::filesystem::path& path, Scene& scene) {
+		fx::gltf::Document sceneObjects;
+	
+		try {
+			if (path.extension() == ".glb") {
+				sceneObjects = fx::gltf::LoadFromBinary(path.string());
+			} else {
+				sceneObjects = fx::gltf::LoadFromText(path.string());
+			}
+		} catch (const std::system_error& err) {
+			recurseExceptionPrint(err, path.string());
+			return ASSET_ERROR;
+		} catch (const std::exception& e) {
+			recurseExceptionPrint(e, path.string());
 			return ASSET_ERROR;
 		}
-    }
-
-    scene = {
-            meshes,
-            vertexGroups,
-            materials,
-            textures,
-            samplers
-    };
-
-    return ASSET_SUCCESS;
-}
-
-TextureData loadTexture(const std::filesystem::path& path) {
-    TextureData texture;
-    
-    uint8_t* data = stbi_load(path.string().c_str(), &texture.width, &texture.height, &texture.componentCount, 4);
-    
-    if (!data) {
-		vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", path.c_str());
-    	
-    	texture.width = 0;
-    	texture.height = 0;
-    	texture.componentCount = 0;
-    	return texture;
-    }
-    
-    texture.data.resize(texture.width * texture.height * 4);
-    memcpy(texture.data.data(), data, texture.data.size());
-    return texture;
-}
-
-int probeScene(const std::filesystem::path& path, Scene& scene) {
-	fx::gltf::Document sceneObjects;
-
-	try {
-		if (path.extension() == ".glb") {
-			sceneObjects = fx::gltf::LoadFromBinary(path.string());
+		
+		const auto directory = path.parent_path();
+		
+		scene.meshes.clear();
+		scene.vertexGroups.clear();
+		scene.materials.clear();
+		scene.textures.clear();
+		scene.samplers.clear();
+	
+		// file has to contain at least one mesh
+		if (sceneObjects.meshes.empty()) {
+			vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str());
+			return ASSET_ERROR;
+		} else {
+			scene.meshes.reserve(sceneObjects.meshes.size());
+			
+			for (size_t i = 0; i < sceneObjects.meshes.size(); i++) {
+				Mesh mesh;
+				mesh.name = sceneObjects.meshes[i].name;
+				
+				if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) {
+					vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)",
+							 mesh.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				}
+				
+				scene.meshes.push_back(mesh);
+			}
+			
+			// This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights
+			for (const auto& node : sceneObjects.nodes) {
+				if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) {
+					scene.meshes[node.mesh].modelMatrix = calculateModelMatrix(
+							node.translation,
+							node.scale,
+							node.rotation,
+							node.matrix
+					);
+				}
+			}
 		}
-		else {
-			sceneObjects = fx::gltf::LoadFromText(path.string());
+		
+		if (sceneObjects.samplers.empty()) {
+			vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str());
+		} else {
+			scene.samplers.reserve(sceneObjects.samplers.size());
+			
+			for (const auto &samplerObject : sceneObjects.samplers) {
+				scene.samplers.push_back(loadSampler(samplerObject));
+			}
 		}
-	}
-	catch (const std::system_error& err) {
-		recurseExceptionPrint(err, path.string());
-		return ASSET_ERROR;
-	}
-	catch (const std::exception& e) {
-		recurseExceptionPrint(e, path.string());
-		return ASSET_ERROR;
-	}
-	auto dir = path.parent_path().string();
-
-	// file has to contain at least one mesh
-	if (sceneObjects.meshes.empty()) {
-		return ASSET_ERROR;
-	}
-
-	std::vector<Material> materials;
-	std::vector<Texture> textures;
-	std::vector<Sampler> samplers;
-	std::vector<Mesh> meshes;
-	std::vector<VertexGroup> vertexGroups;
-	std::vector<std::string> uris;
-	int groupCount = 0;
-
-	std::set<std::string> names;
-	for (size_t i = 0; i < sceneObjects.meshes.size(); i++) {
-		std::vector<int> vertexGroupsIndices;
-		fx::gltf::Mesh const& objectMesh = sceneObjects.meshes[i];
-
-		if (createVertexGroups(objectMesh, sceneObjects, vertexGroups,
-					vertexGroupsIndices, uris, groupCount,
-					true) != ASSET_SUCCESS) {
-			vkcv_log(LogLevel::ERROR, "Failed to get Vertex Groups!");
-			return ASSET_ERROR;
+		
+		if (sceneObjects.textures.empty()) {
+			vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str());
+		} else {
+			scene.textures.reserve(sceneObjects.textures.size());
+			
+			for (const auto& textureObject : sceneObjects.textures) {
+				if ((textureObject.sampler < 0) ||
+					(static_cast<size_t>(textureObject.sampler) >= scene.samplers.size())) {
+					vkcv_log(LogLevel::WARNING, "Sampler of texture '%s' missing (%s)",
+							 textureObject.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				}
+				
+				Texture texture;
+				texture.sampler = textureObject.sampler;
+				
+				if ((textureObject.source < 0) ||
+					(static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) {
+					vkcv_log(LogLevel::WARNING, "Failed to load texture '%s' (%s)",
+							 textureObject.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				}
+				
+				const auto& image = sceneObjects.images[textureObject.source];
+				
+				if (image.uri.empty()) {
+					const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView];
+					
+					texture.path.clear();
+					texture.data.resize(bufferView.byteLength);
+					memcpy(texture.data.data(),
+						   sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset,
+						   bufferView.byteLength);
+				} else {
+					texture.path = directory / image.uri;
+				}
+				
+				scene.textures.push_back(texture);
+			}
 		}
-
-		Mesh mesh = {};
-		mesh.name = sceneObjects.meshes[i].name;
-		mesh.vertexGroups = vertexGroupsIndices;
-
-		meshes.push_back(mesh);
-		if (names.count(mesh.name)) {
-			vkcv_log(LogLevel::WARNING, "More than one mesh with name %s",
-					mesh.name.c_str());
+		
+		if (sceneObjects.materials.empty()) {
+			vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str());
 		} else {
-			names.insert(mesh.name);
+			scene.materials.reserve(sceneObjects.materials.size());
+			
+			for (auto material : sceneObjects.materials) {
+				scene.materials.push_back({
+						generateTextureMask(material),
+						material.pbrMetallicRoughness.baseColorTexture.index,
+						material.pbrMetallicRoughness.metallicRoughnessTexture.index,
+						material.normalTexture.index,
+						material.occlusionTexture.index,
+						material.emissiveTexture.index,
+						{
+								material.pbrMetallicRoughness.baseColorFactor[0],
+								material.pbrMetallicRoughness.baseColorFactor[1],
+								material.pbrMetallicRoughness.baseColorFactor[2],
+								material.pbrMetallicRoughness.baseColorFactor[3]
+						},
+						material.pbrMetallicRoughness.metallicFactor,
+						material.pbrMetallicRoughness.roughnessFactor,
+						material.normalTexture.scale,
+						material.occlusionTexture.strength,
+						{
+								material.emissiveFactor[0],
+								material.emissiveFactor[1],
+								material.emissiveFactor[2]
+						}
+				});
+			}
 		}
+	
+		return ASSET_SUCCESS;
 	}
-
-	// This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights
-	for (auto& node : sceneObjects.nodes) {
-		if (node.mesh > -1) {
-			meshes[node.mesh].modelMatrix = computeModelMatrix(
-				node.translation,
-				node.scale,
-				node.rotation,
-				node.matrix
+	
+	static int loadTextureData(Texture& texture) {
+		if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) &&
+			(!texture.data.empty())) {
+			return ASSET_SUCCESS; // Texture data was loaded already!
+		}
+		
+		uint8_t* data;
+		
+		if (texture.path.empty()) {
+			data = stbi_load_from_memory(
+					reinterpret_cast<uint8_t*>(texture.data.data()),
+					static_cast<int>(texture.data.size()),
+					&texture.width,
+					&texture.height,
+					&texture.channels, 4
+			);
+		} else {
+			data = stbi_load(
+					texture.path.c_str(),
+					&texture.width,
+					&texture.height,
+					&texture.channels, 4
 			);
 		}
+		
+		if (!data) {
+			vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'",
+					 texture.path.c_str());
+			
+			texture.width = 0;
+			texture.height = 0;
+			texture.channels = 0;
+			return ASSET_ERROR;
+		}
+		
+		texture.data.resize(texture.width * texture.height * 4);
+		memcpy(texture.data.data(), data, texture.data.size());
+		stbi_image_free(data);
+	
+		return ASSET_SUCCESS;
 	}
 
-	/*if (createTextures(sceneObjects.textures, sceneObjects.images, sceneObjects.buffers, sceneObjects.bufferViews, dir, uris, textures) != ASSET_SUCCESS) {
-		size_t missing = sceneObjects.textures.size() - textures.size();
-		vkcv_log(LogLevel::ERROR, "Failed to get %lu textures from glTF source '%s'",
-			missing, path.c_str());
-	}*/
-
-
-	if (createMaterial(sceneObjects, materials) != ASSET_SUCCESS) {
-		vkcv_log(LogLevel::ERROR, "Failed to get Materials!");
-		return ASSET_ERROR;
-	}
-
-	/*samplers.reserve(sceneObjects.samplers.size());
-	for (const auto& it : sceneObjects.samplers) {
-		samplers.push_back({});
-		auto& sampler = samplers.back();
-		if (translateSampler(it, sampler) != ASSET_SUCCESS) {
+	int loadMesh(Scene &scene, int index) {
+		if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) {
+			vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index);
 			return ASSET_ERROR;
 		}
-	}*/
-
-	scene = {
-			meshes,
-			vertexGroups,
-			materials,
-			textures,
-			samplers
-	};
-
-	return ASSET_SUCCESS;
-}
-
-
-int loadMesh(Scene &scene, int idx)
-{
-	if (idx < 0 || (size_t)idx >= scene.meshes.size()) {
-		vkcv_log(LogLevel::ERROR, "mesh index out of range: %d", idx);
-		return ASSET_ERROR;
+		
+		const Mesh &mesh = scene.meshes[index];
+		
+		for (const auto& vg : mesh.vertexGroups) {
+			const VertexGroup &vertexGroup = scene.vertexGroups[vg];
+			const Material& material = scene.materials[vertexGroup.materialIndex];
+			
+			if (material.hasTexture(PBRTextureTarget::baseColor)) {
+				const int result = loadTextureData(scene.textures[material.baseColor]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::metalRough)) {
+				const int result = loadTextureData(scene.textures[material.metalRough]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::normal)) {
+				const int result = loadTextureData(scene.textures[material.normal]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::occlusion)) {
+				const int result = loadTextureData(scene.textures[material.occlusion]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::emissive)) {
+				const int result = loadTextureData(scene.textures[material.emissive]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+		}
+	
+		return ASSET_SUCCESS;
+	}
+	
+	int loadScene(const std::filesystem::path &path, Scene &scene) {
+		int result = probeScene(path, scene);
+		
+		if (result != ASSET_SUCCESS) {
+			vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'",
+					 path.c_str());
+			return result;
+		}
+		
+		for (size_t i = 0; i < scene.meshes.size(); i++) {
+			result = loadMesh(scene, static_cast<int>(i));
+			
+			if (result != ASSET_SUCCESS) {
+				vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'",
+						 static_cast<int>(i), path.c_str());
+				return result;
+			}
+		}
+		
+		return ASSET_SUCCESS;
+	}
+	
+	Texture loadTexture(const std::filesystem::path& path) {
+		Texture texture;
+		texture.path = path;
+		texture.sampler = -1;
+		loadTextureData(texture);
+		return texture;
 	}
-	Mesh &mesh = scene.meshes[idx];
-
-	// TODO go through all vertex groups of the mesh and load the
-	// associated materials and index/vertex buffers
-	return ASSET_SUCCESS;
-}
 
 }
-- 
GitLab