From 0e3c14eed0a16ca4c0d8bac44f71b226b2d3e96c Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sat, 26 Nov 2022 10:46:13 +0000
Subject: [PATCH] update model loading support to work with some humble-bundle
 assets

---
 demo/data/redbook/debug_frag.glsl      |  16 ++-
 demo/demo/models/viewer.cpp            |  43 +++++---
 demo/include/models/viewer.hpp         |   7 +-
 fggl/data/assimp/module.cpp            | 141 +++++++++++++++----------
 fggl/gfx/ogl4/meshes.cpp               |  83 ++++++++++-----
 include/fggl/assets/loader.hpp         |   2 +-
 include/fggl/assets/packed/adapter.hpp |  17 +--
 include/fggl/assets/types.hpp          |   8 ++
 include/fggl/data/assimp/module.hpp    |   9 +-
 include/fggl/gfx/ogl4/meshes.hpp       |   3 +
 10 files changed, 216 insertions(+), 113 deletions(-)

diff --git a/demo/data/redbook/debug_frag.glsl b/demo/data/redbook/debug_frag.glsl
index 050be21..8eda54c 100644
--- a/demo/data/redbook/debug_frag.glsl
+++ b/demo/data/redbook/debug_frag.glsl
@@ -35,7 +35,15 @@ struct DirectionalLight {
     vec3 specular;
 };
 
+struct Material {
+    vec3 emission;
+    vec3 ambient;
+    vec3 diffuse;
+    vec3 specular;
+};
+
 uniform DirectionalLight light;
+uniform Material material;
 
 const int hasPos = 0;
 
@@ -68,11 +76,11 @@ vec4 calcDirLight(DirectionalLight light, vec3 Normal, vec3 viewDir, vec4 specPx
 void main() {
     vec3 viewDir = normalize(-Position);
 
-    vec4 diffPx = vec4(1, 1, 1, 1);
-    vec4 specPx = vec4(1, 1, 1, 1);
+    vec4 diffPx = vec4(material.diffuse, 1);
+    vec4 specPx = vec4(material.specular, 1);
     if ( hasPos != 1) {
-        diffPx = texture(diffuseTexture, TexPos);
-        specPx = texture(specularTexture, TexPos);
+        diffPx *= texture(diffuseTexture, TexPos);
+        specPx *= texture(specularTexture, TexPos);
     }
 
     FragColour = vec4(Colour, 1);
diff --git a/demo/demo/models/viewer.cpp b/demo/demo/models/viewer.cpp
index 277cbae..17ec4ee 100644
--- a/demo/demo/models/viewer.cpp
+++ b/demo/demo/models/viewer.cpp
@@ -32,13 +32,12 @@
 
 namespace demo {
 
-	static fggl::entity::EntityID build_model(fggl::entity::EntityManager& manager, fggl::assets::AssetManager *assets){
+	static fggl::entity::EntityID build_model(fggl::entity::EntityManager& manager, fggl::assets::AssetManager *assets, fggl::assets::AssetID assetRef){
 		auto model = manager.create();
 
 		manager.add<fggl::math::Transform>(model);
 		auto& mesh = manager.add<fggl::mesh::StaticMultiMesh3D>(model);
 
-		auto assetRef = fggl::assets::make_asset_id_rt("viewer", "backpack/backpack.obj" );
 		auto* meshData = assets->get<fggl::mesh::MultiMesh3D>( assetRef );
 		if ( meshData == nullptr ) {
 			fggl::debug::warning("loading model did not work!");
@@ -110,23 +109,22 @@ namespace demo {
 	void Viewer::activate() {
 		Game::activate();
 
-		// force load required data
-		auto *loader = owner().service<fggl::assets::Loader>();
-		auto *manager = owner().service<fggl::assets::AssetManager>();
-
-		auto backPackRef = fggl::assets::make_asset_id_rt("viewer", "backpack/backpack.obj");
-		loader->loadChain(backPackRef, manager);
+		// setup the assets we can select between
+		// TODO some form of introspection to automatically find declared/discovered assets of a given type
+		m_assets.clear();
+		m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "backpack/backpack.obj") );
+		m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "lowpoly_scifi/wallDoor_double.FBX") );
+		m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "lowpoly_scifi/wallDoor_double_end.FBX") );
+		m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "newell_teaset/teapot.obj") );
+		m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "humansanimatedpack/Paladin/Paladin.fbx") );
 
 		// create camera
 		setup_camera(world());
 		setup_lighting(world());
 
 		// setup model
-		m_model = build_model(world(), manager);
-
-		// asset loader
-
-		//loader->
+		m_model = fggl::entity::INVALID;
+		cycleAsset(0);
 	}
 
 	void Viewer::deactivate() {
@@ -140,6 +138,25 @@ namespace demo {
 		if ( input().keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F2)) ) {
 			m_debug = !m_debug;
 		}
+
+		if ( input().keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F3)) ) {
+			// trigger the asset cycle
+			m_lastAsset = (m_lastAsset + 1) % m_assets.size();
+			cycleAsset(m_lastAsset);
+		}
+	}
+
+	void Viewer::cycleAsset(uint64_t idx) {
+		auto *loader = owner().service<fggl::assets::Loader>();
+		auto *manager = owner().service<fggl::assets::AssetManager>();
+
+		auto nextAsset = m_assets[ m_lastAsset ];
+		loader->loadChain(nextAsset, manager);
+
+		if ( m_model != fggl::entity::INVALID) {
+			world().destroy(m_model);
+		}
+		m_model = build_model(world(), manager, nextAsset);
 	}
 
 	void Viewer::render(fggl::gfx::Graphics &gfx) {
diff --git a/demo/include/models/viewer.hpp b/demo/include/models/viewer.hpp
index 242cd35..0ab0115 100644
--- a/demo/include/models/viewer.hpp
+++ b/demo/include/models/viewer.hpp
@@ -20,6 +20,7 @@
 #define FGGL_DEMO_INCLUDE_MODELS_VIEWER_HPP
 
 #include "fggl/scenes/game.hpp"
+#include "fggl/assets/types.hpp"
 
 namespace demo {
 
@@ -35,9 +36,13 @@ namespace demo {
 			void render(fggl::gfx::Graphics& gfx) override;
 
 		private:
-			fggl::entity::EntityID m_model;
+			fggl::entity::EntityID m_model = fggl::entity::INVALID;
+			std::vector< fggl::assets::AssetID> m_assets;
+			uint64_t m_lastAsset = 0;
 			bool m_debug = false;
 
+			void cycleAsset(uint64_t asset);
+
 	};
 }
 
diff --git a/fggl/data/assimp/module.cpp b/fggl/data/assimp/module.cpp
index b8d3707..97f0c6d 100644
--- a/fggl/data/assimp/module.cpp
+++ b/fggl/data/assimp/module.cpp
@@ -27,6 +27,8 @@
 #include <assimp/scene.h>
 #include <assimp/postprocess.h>
 
+#include <filesystem>
+
 namespace fggl::data::models {
 
 	constexpr math::vec3 convert(aiVector3D& vec) {
@@ -41,6 +43,10 @@ namespace fggl::data::models {
 		return {vec.x, vec.y};
 	}
 
+	constexpr math::vec3 convert(aiColor3D& col) {
+		return {col.r, col.g, col.b};
+	}
+
 	static void process_mesh(mesh::Mesh3D& mesh, aiMesh* assimpMesh, const aiScene* scene, const std::vector<assets::AssetID>& assets) {
 		assert( assimpMesh != nullptr );
 		assert( scene != nullptr );
@@ -91,60 +97,82 @@ namespace fggl::data::models {
 		}
 	}
 
+	struct AssetStuff {
+		assets::Loader* loader;
+		assets::AssetManager* manager;
 
-	static void process_material(const assets::AssetID& guid, aiMaterial* assimpMat, assets::Loader* loader, assets::AssetManager* manager, const std::filesystem::path& prefix, const std::string& pack) {
-		mesh::Material* material = new mesh::Material();
-
-		// for each material, calculate what it's name would be then request it
-		for ( unsigned int i = 0U ; i < assimpMat->GetTextureCount(aiTextureType_DIFFUSE); ++i ) {
-			aiString texName;
-			assimpMat->GetTexture( aiTextureType_DIFFUSE, i, &texName );
-
-			auto texID = assets::make_asset_id_rt(pack, prefix / texName.C_Str() );
-			if ( !manager->has(texID) ) {
-				debug::info("triggered JIT upload for {}, use the chain loader", texID);
-				loader->load(texID, manager);
+		inline void checkLoaded(assets::AssetID asset) {
+			if (!manager->has(asset)) {
+				debug::info("triggered JIT upload for {}, use the chain loader", asset);
+				loader->load(asset);
 			}
+		}
 
-			material->diffuseTextures.push_back(texID);
+		inline bool isLoaded(assets::AssetID asset) const {
+			return manager->has(asset);
 		}
 
-		for ( unsigned int i = 0U ; i < assimpMat->GetTextureCount(aiTextureType_NORMALS); ++i ) {
-			aiString texName;
-			assimpMat->GetTexture( aiTextureType_NORMALS, i, &texName );
+		inline void set(assets::AssetID asset, auto* assetPtr) {
+			assert(assetPtr != nullptr);
+			manager->set(asset, assetPtr);
+		}
 
-			auto texID = assets::make_asset_id_rt(pack, prefix / texName.C_Str() );
-			if ( !manager->has(texID) ) {
-				debug::info("triggered JIT upload for {}, use the chain loader", texID);
-				loader->load(texID, manager);
-			}
+	};
 
-			material->normalTextures.push_back(texID);
-		}
+	static std::vector<assets::AssetID> process_texture( const aiTextureType type, const aiMaterial* assimpMat, AssetStuff& stuff, const assets::LoaderContext& config) {
+		std::vector<assets::AssetID> matRefs;
+		matRefs.reserve( assimpMat->GetTextureCount(type) );
 
-		for ( unsigned int i = 0U ; i < assimpMat->GetTextureCount(aiTextureType_SPECULAR); ++i ) {
+		// iterate through things
+		for ( auto i = 0U ; i < assimpMat->GetTextureCount(type); ++i ) {
 			aiString texName;
-			assimpMat->GetTexture( aiTextureType_SPECULAR, i, &texName );
+			assimpMat->GetTexture( type, i, &texName );
+			const auto texID = config.makeRef(texName.C_Str());
 
-			auto texID = assets::make_asset_id_rt(pack, prefix / texName.C_Str() );
-			if ( !manager->has(texID) ) {
-				debug::info("triggered JIT upload for {}, use the chain loader", texID);
-				loader->load(texID, manager);
-			}
+			// trigger asset loading
+			stuff.checkLoaded( texID );
 
-			material->specularTextures.push_back(texID);
+			// assets
+			matRefs.push_back( texID );
 		}
+		return matRefs;
+	}
+
+	static math::vec3 process_mat_colour(const aiMaterial* mat, const char* name, int a1, int a2) {
+		aiColor3D col{0.0F, 0.0F, 0.0F};
+		mat->Get( name, a1, a2, col );
+		debug::info("read colour: {}, {}, {}, {}", name, col.r, col.g, col.b);
 
-		manager->set( guid, material );
+		return convert(col);
 	}
 
+	static void process_material(const assets::AssetID& guid, aiMaterial* assimpMat, AssetStuff stuff, const assets::LoaderContext& config) {
+		mesh::Material* material = new mesh::Material();
+
+		debug::info("processing: {}", guid);
+
+		// for each material, calculate what it's name would be then request it
+		material->diffuseTextures = process_texture( aiTextureType_DIFFUSE, assimpMat, stuff, config );
+		material->normalTextures = process_texture( aiTextureType_NORMALS, assimpMat, stuff, config );
+		material->specularTextures = process_texture( aiTextureType_SPECULAR, assimpMat, stuff, config );
+
+		material->ambient = process_mat_colour( assimpMat, AI_MATKEY_COLOR_AMBIENT );
+		material->diffuse = process_mat_colour( assimpMat, AI_MATKEY_COLOR_DIFFUSE );
+		material->specular = process_mat_colour( assimpMat, AI_MATKEY_COLOR_SPECULAR );
+
+		stuff.set( guid, material );
+	}
 
 	assets::AssetRefRaw load_assimp_model(assets::Loader* loader, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr) {
 //		auto *filePath = std::get<assets::AssetPath *>(data);
-		assets::AssetManager* manager = (assets::AssetManager*)userPtr;
 		auto filePath = data.assetPath;
 
-		if ( manager->has(guid) ) {
+		AssetStuff stuff {
+			.loader = loader,
+			.manager = (assets::AssetManager*)userPtr
+		};
+
+		if ( stuff.isLoaded(guid) ) {
 			// asset in DB, what do you want me to do?
 			return nullptr;
 		}
@@ -153,8 +181,8 @@ namespace fggl::data::models {
 		Assimp::Importer importer;
 
 		// import the scene from disk
-		const aiScene *scene = importer.ReadFile(filePath.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs);
-		if ( !scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode ) {
+		const aiScene *scene = importer.ReadFile(filePath.c_str(), aiProcessPreset_TargetRealtime_Fast | aiProcess_FlipUVs);
+		if ( scene == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0 || scene->mRootNode == nullptr ) {
 			debug::warning("unable to load required model asset");
 			return nullptr;
 		}
@@ -164,14 +192,10 @@ namespace fggl::data::models {
 		// calculate the mapping from material => asset mappings
 		auto parentRel = std::filesystem::relative( filePath.parent_path(), filePath.parent_path().parent_path() );
 		std::vector<assets::AssetID> matAssets;
-		for ( uint64_t idx = 0; idx < scene->mNumMaterials; ++idx ) {
-
-			// FIXME to calculate this properly we need to know the pack name, root and relative path
+		for ( auto idx = 0U; idx < scene->mNumMaterials; ++idx ) {
 			aiString matName = scene->mMaterials[idx]->GetName();
 			auto matRel = parentRel / matName.C_Str();
-			auto matIDMinusPack = assets::make_asset_id_rt( data.pack, matRel);
-
-			matAssets.push_back(matIDMinusPack);
+			matAssets.push_back( assets::make_asset_id_rt( data.pack, matRel ) );
 		}
 
 		// now we can try importing the mesh data
@@ -179,15 +203,12 @@ namespace fggl::data::models {
 		process_node( *packedMesh, scene->mRootNode, scene, matAssets);
 
 		// now we try importing the materials
-		for (auto idx = 0u; idx < scene->mNumMaterials; ++idx ) {
-			auto *mat = scene->mMaterials[idx];
-			auto matID = matAssets[idx];
-
-			process_material(matID, mat, loader, manager, parentRel, data.pack);
+		for (auto idx = 0U; idx < scene->mNumMaterials; ++idx ) {
+			process_material( matAssets[idx], scene->mMaterials[idx], stuff, data );
 		}
 
 		// FIXME asset loading system needs rework, this is bonkers.
-		manager->set(guid, packedMesh);
+		stuff.set(guid, packedMesh);
 		return nullptr;
 	}
 
@@ -200,10 +221,10 @@ namespace fggl::data::models {
 		}
 
 		auto filePath = data.assetPath;
-		stbi_set_flip_vertically_on_load(true);
+		stbi_set_flip_vertically_on_load(1);
 
 		//load the texture data into memory
-		data::Texture2D* image = new data::Texture2D();
+		auto* image = new data::Texture2D();
 		image->data = stbi_load(filePath.c_str(), &image->size.x, &image->size.y, &image->channels, 3);
 
 		if ( image->data == nullptr ) {
@@ -217,11 +238,11 @@ namespace fggl::data::models {
 		return nullptr;
 	}
 
-	bool load_tex_stb(std::filesystem::path filePath, assets::MemoryBlock& block) {
-		stbi_set_flip_vertically_on_load(true);
+	bool load_tex_stb(const std::filesystem::path& filePath, assets::MemoryBlock& block) {
+		stbi_set_flip_vertically_on_load(1);
 
 		//load the texture data into memory
-		data::Texture2D* image = new data::Texture2D();
+		auto* image = new data::Texture2D();
 		image->data = stbi_load(filePath.c_str(), &image->size.x, &image->size.y, &image->channels, 3);
 
 		if ( image->data == nullptr ) {
@@ -229,7 +250,6 @@ namespace fggl::data::models {
 			delete image;
 			return false;
 		} else {
-			//manager->set(guid, image);
 			// TODO pass metadata to loader in a sensible way
 			block.size = image->channels * image->size.x * image->size.y;
 			block.data = (std::byte*)image->data;
@@ -238,7 +258,7 @@ namespace fggl::data::models {
 		}
 	}
 
-	assets::AssetTypeID is_tex_stb(std::filesystem::path filePath) {
+	assets::AssetTypeID is_tex_stb(const std::filesystem::path& filePath) {
 		// detect jpgs
 		if ( filePath.extension() == ".jpg" || filePath.extension() == ".jpeg" ) {
 			return TEXTURE_RGBA;
@@ -252,15 +272,18 @@ namespace fggl::data::models {
 		return assets::INVALID_ASSET_TYPE;
 	}
 
-	assets::AssetTypeID is_model_assimp(std::filesystem::path filePath) {
-		if ( filePath.extension() == ".obj" ){
+	assets::AssetTypeID is_model_assimp(const std::filesystem::path& filePath) {
+		auto ext = filePath.extension().string();
+		std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
+
+		if ( ext == ".obj" || ext == ".fbx" ) {
 			return MODEL_MULTI3D;
 		}
 
 		return assets::INVALID_ASSET_TYPE;
 	}
 
-	bool extract_requirements(const std::string& packName, std::filesystem::path packRoot, assets::ResourceRecord& rr) {
+	bool extract_requirements(const std::string& packName, const std::filesystem::path& packRoot, assets::ResourceRecord& rr) {
 		Assimp::Importer importer;
 		const aiScene *scene = importer.ReadFile( rr.m_path, aiProcess_Triangulate | aiProcess_FlipUVs);
 		if ( !scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode ) {
@@ -303,7 +326,9 @@ namespace fggl::data::models {
 			checkin->setLoader( MIME_PNG, load_tex_stb, is_tex_stb );
 
 			checkin->setLoader( MIME_OBJ, assets::NEEDS_CHECKIN, is_model_assimp );
+			checkin->setLoader( MIME_FBX, assets::NEEDS_CHECKIN, is_model_assimp );
 			checkin->setProcessor( MIME_OBJ, extract_requirements);
+			checkin->setProcessor( MIME_FBX, extract_requirements );
 			return false;
 		}
 	}
diff --git a/fggl/gfx/ogl4/meshes.cpp b/fggl/gfx/ogl4/meshes.cpp
index 34f757b..2d0e164 100644
--- a/fggl/gfx/ogl4/meshes.cpp
+++ b/fggl/gfx/ogl4/meshes.cpp
@@ -50,24 +50,54 @@ namespace fggl::gfx::ogl4 {
 		return elementBuffer;
 	}
 
+	void setup_material(const std::shared_ptr<ogl::Shader>& shader, const PhongMaterial* material) {
+		if ( !shader->hasUniform("material.ambient") ) {
+			return;
+		}
+
+		// setup material block
+		shader->setUniformF(shader->uniform("material.emission"), material->emission);
+		shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
+		shader->setUniformF(shader->uniform("material.diffuse"), material->diffuse);
+		shader->setUniformF(shader->uniform("material.specular"), material->specular);
+		//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
+	}
+
+	void setup_material(const std::shared_ptr<ogl::Shader>& shader, const Material* material) {
+		if ( shader->hasUniform("material.diffuse") ) {
+			// setup material block
+//			shader->setUniformF(shader->uniform("material.emission"), material->emission);
+//			shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
+			shader->setUniformF(shader->uniform("material.diffuse"), material->m_diffCol);
+			shader->setUniformF(shader->uniform("material.specular"), material->m_specCol);
+		}
+
+
+		// setup diffuse texture
+		if (material->m_diffuse != nullptr) {
+			material->m_diffuse->bind(0);
+			if (shader->hasUniform("diffuseTexture")) {
+				shader->setUniformI(shader->uniform("diffuseTexture"), 0);
+			}
+		}
+
+		// setup specular texture
+		if (material->m_specular != nullptr) {
+			material->m_specular->bind(1);
+			if (shader->hasUniform("specularTexture")) {
+				shader->setUniformI(shader->uniform("specularTexture"), 1);
+			}
+		}
+		//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
+	}
+
+
 	void MeshData::draw(std::shared_ptr<ogl::Shader> shader) const {
 		vao->bind();
 		vertexData->bind();
 
 		if ( material != nullptr ) {
-			if (material->m_diffuse != nullptr) {
-				material->m_diffuse->bind(0);
-				if (shader->hasUniform("diffuseTexture")) {
-					shader->setUniformI(shader->uniform("diffuseTexture"), 0);
-				}
-			}
-
-			if (material->m_specular != nullptr) {
-				material->m_specular->bind(1);
-				if (shader->hasUniform("specularTexture")) {
-					shader->setUniformI(shader->uniform("specularTexture"), 1);
-				}
-			}
+			setup_material(shader, material);
 		} else {
 			debug::info("no material is active, cannot bind textures!");
 		}
@@ -90,19 +120,6 @@ namespace fggl::gfx::ogl4 {
 		}
 	}
 
-	void setup_material(const std::shared_ptr<ogl::Shader>& shader, const PhongMaterial* material) {
-		if ( !shader->hasUniform("materials[0].ambient") ) {
-			return;
-		}
-
-		// setup material block
-		shader->setUniformF(shader->uniform("materials[0].emission"), material->emission);
-		shader->setUniformF(shader->uniform("materials[0].ambient"), material->ambient);
-		shader->setUniformF(shader->uniform("materials[0].diffuse"), material->diffuse);
-		shader->setUniformF(shader->uniform("materials[0].specular"), material->specular);
-		shader->setUniformF(shader->uniform("materials[0].shininess"), material->shininess);
-	}
-
 	void setup_lighting(const std::shared_ptr<ogl::Shader>& shader, const DirectionalLight* light) {
 		assert( light != nullptr );
 		if ( !shader->hasUniform("light.direction") ) {
@@ -208,14 +225,28 @@ namespace fggl::gfx::ogl4 {
 		}
 
 		material = new Material();
+		material->m_diffCol = meshMaterial->diffuse;
+		material->m_specCol = meshMaterial->specular;
+
+		// do we have a diffuse texture?
 		if ( !meshMaterial->diffuseTextures.empty() ) {
 			material->m_diffuse = upload_texture(meshMaterial->getPrimaryDiffuse(), manager);
+		} else {
+			material->m_diffuse = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
 		}
+
+		// do we have a normal Texture?
 		if ( !meshMaterial->normalTextures.empty() ) {
 			material->m_normals = upload_texture(meshMaterial->getPrimaryNormals(), manager);
+		} else {
+			material->m_normals = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
 		}
+
+		// do we have a specular texture?
 		if ( !meshMaterial->specularTextures.empty() ) {
 			material->m_specular = upload_texture(meshMaterial->getPrimarySpecular(), manager);
+		} else {
+			material->m_specular = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
 		}
 
 		return manager->set( uploadedMat, material);
diff --git a/include/fggl/assets/loader.hpp b/include/fggl/assets/loader.hpp
index 2ccbfd2..64af0e4 100644
--- a/include/fggl/assets/loader.hpp
+++ b/include/fggl/assets/loader.hpp
@@ -141,7 +141,7 @@ namespace fggl::assets {
 				// perform loading
 				LoaderContext ctx {
 					.pack = pack,
-					.packRoot = "",
+					.packRoot = m_checkin->getPackPath(pack),
 					.assetPath = path
 				};
 
diff --git a/include/fggl/assets/packed/adapter.hpp b/include/fggl/assets/packed/adapter.hpp
index 17fb107..022eee6 100644
--- a/include/fggl/assets/packed/adapter.hpp
+++ b/include/fggl/assets/packed/adapter.hpp
@@ -50,7 +50,7 @@ namespace fggl::assets {
 	};
 
 	[[maybe_unused]]
-	inline bool NEEDS_CHECKIN(std::filesystem::path, MemoryBlock) {
+	inline bool NEEDS_CHECKIN(const std::filesystem::path&, MemoryBlock) {
 		debug::error("attempted to load asset which does not have a valid checkin yet");
 		return false;
 	}
@@ -66,9 +66,9 @@ namespace fggl::assets {
 		public:
 			constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::checkin::debug");
 
-			using FilePredicate = std::function<AssetTypeID(std::filesystem::path)>;
-			using FileLoader = std::function<bool(std::filesystem::path, MemoryBlock& block)>;
-			using AssetMetadata = std::function<bool(const std::string& pack, std::filesystem::path packRoot, ResourceRecord&)>;
+			using FilePredicate = std::function<AssetTypeID(const std::filesystem::path&)>;
+			using FileLoader = std::function<bool(const std::filesystem::path&, MemoryBlock& block)>;
+			using AssetMetadata = std::function<bool(const std::string& pack, const std::filesystem::path& packRoot, ResourceRecord&)>;
 
 			CheckinAdapted(data::Storage* storage, RawCheckin* checkSvc) : m_storage(storage), m_checkin(checkSvc) {};
 
@@ -91,7 +91,7 @@ namespace fggl::assets {
 			}
 
 			inline std::filesystem::path getPath(AssetID asset) const {
-				auto& file = m_files.at(asset);
+				const auto& file = m_files.at(asset);
 				return file.m_path;
 			}
 
@@ -137,7 +137,7 @@ namespace fggl::assets {
 				m_packs[ packDir.filename() ].assets = packFiles;
 			}
 
-			inline void setLoader(ResourceType type, FileLoader loader, FilePredicate predicate = nullptr) {
+			inline void setLoader(ResourceType type, const CheckinAdapted::FileLoader& loader, CheckinAdapted::FilePredicate predicate = nullptr) {
 				assert( loader != nullptr );
 				m_loaders[type] = loader;
 
@@ -154,6 +154,11 @@ namespace fggl::assets {
 				return m_files.at(asset);
 			}
 
+			inline std::filesystem::path getPackPath(const std::string& name) const {
+				auto& info = m_packs.at(name);
+				return info.rootDir;
+			}
+
 		private:
 			data::Storage* m_storage;
 			RawCheckin* m_checkin;
diff --git a/include/fggl/assets/types.hpp b/include/fggl/assets/types.hpp
index 8f507b4..5ab2359 100644
--- a/include/fggl/assets/types.hpp
+++ b/include/fggl/assets/types.hpp
@@ -73,6 +73,14 @@ namespace fggl::assets {
 		std::string pack;
 		std::filesystem::path packRoot;
 		std::filesystem::path assetPath;
+
+		inline std::filesystem::path relParent() const {
+			return std::filesystem::relative( assetPath, packRoot ).parent_path();
+		}
+
+		inline assets::AssetID makeRef(const char* name) const {
+			return assets::make_asset_id_rt(pack, relParent() / name );
+		}
 	};
 
 	class Loader;
diff --git a/include/fggl/data/assimp/module.hpp b/include/fggl/data/assimp/module.hpp
index 3a7f71f..0a4001d 100644
--- a/include/fggl/data/assimp/module.hpp
+++ b/include/fggl/data/assimp/module.hpp
@@ -32,6 +32,7 @@ namespace fggl::data::models {
 	constexpr auto MIME_JPG = assets::from_mime("image/jpeg");
 	constexpr auto MIME_PNG = assets::from_mime("image/png");
 	constexpr auto MIME_OBJ = assets::from_mime("model/obj");
+	constexpr auto MIME_FBX = assets::from_mime("model/fbx");
 
 	constexpr auto MODEL_MULTI3D = assets::make_asset_type("model/multi3D");
 	constexpr auto TEXTURE_RGBA = assets::make_asset_type("texture/rgba");
@@ -41,12 +42,12 @@ namespace fggl::data::models {
 	assets::AssetRefRaw load_assimp_texture(assets::Loader* loader, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr);
 
 	// new style loaders (textures)
-	bool load_tex_stb(std::filesystem::path filePath, assets::MemoryBlock& block);
-	static assets::AssetTypeID is_tex_stb(std::filesystem::path filePath);
+	bool load_tex_stb(const std::filesystem::path& filePath, assets::MemoryBlock& block);
+	static assets::AssetTypeID is_tex_stb(const std::filesystem::path& filePath);
 
 	// new style loaders (models)
-	assets::AssetTypeID is_model_assimp(std::filesystem::path filePath);
-	bool extract_requirements(const std::string& packName, std::filesystem::path packRoot, assets::ResourceRecord& rr);
+	assets::AssetTypeID is_model_assimp(const std::filesystem::path& filePath);
+	bool extract_requirements(const std::string& packName, const std::filesystem::path& packRoot, assets::ResourceRecord& rr);
 
 	struct AssimpModule {
 		constexpr static const char *name = "fggl::data::Assimp";
diff --git a/include/fggl/gfx/ogl4/meshes.hpp b/include/fggl/gfx/ogl4/meshes.hpp
index 09a97a5..150a891 100644
--- a/include/fggl/gfx/ogl4/meshes.hpp
+++ b/include/fggl/gfx/ogl4/meshes.hpp
@@ -37,6 +37,9 @@ namespace fggl::gfx::ogl4 {
 	};
 
 	struct Material {
+		math::vec3 m_diffCol{1.0F, 1.0F, 1.0F};
+		math::vec3 m_specCol{1.0F, 1.0F, 1.0F};
+
 		ogl::Texture* m_diffuse;
 		ogl::Texture* m_normals;
 		ogl::Texture* m_specular;
-- 
GitLab