From 920fe2e74b62142ae899920b15068bb452361715 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sun, 31 Jul 2022 13:40:30 +0100
Subject: [PATCH] fix error in config import

---
 demo/data/redbook/debug_frag.glsl             | 16 ++++
 demo/data/redbook/debug_vert.glsl             | 24 +++++
 demo/data/redbook/lighting_frag.glsl          |  2 -
 demo/data/redbook/lighting_vert.glsl          | 11 ++-
 demo/data/rollball.yml                        | 10 +-
 demo/data/topdown.yml                         | 10 +-
 demo/demo/topdown.cpp                         |  2 +-
 fggl/gfx/ogl/renderer.cpp                     |  2 +
 fggl/gfx/ogl4/models.cpp                      | 39 +++-----
 fggl/gfx/ogl4/module.cpp                      | 14 ++-
 fggl/platform/linux/paths.cpp                 |  4 +-
 include/fggl/debug/impl/logging_std20.hpp     | 30 +++---
 include/fggl/entity/loader/serialise.hpp      | 42 +++++++-
 include/fggl/modules/module.hpp               |  1 +
 integrations/bullet/CMakeLists.txt            |  1 +
 .../include/fggl/phys/bullet/bullet.hpp       | 42 +-------
 .../include/fggl/phys/bullet/module.hpp       | 51 ++++++++++
 integrations/bullet/src/module.cpp            | 95 +++++++++++++++++++
 18 files changed, 286 insertions(+), 110 deletions(-)
 create mode 100644 demo/data/redbook/debug_frag.glsl
 create mode 100644 demo/data/redbook/debug_vert.glsl
 create mode 100644 integrations/bullet/include/fggl/phys/bullet/module.hpp
 create mode 100644 integrations/bullet/src/module.cpp

diff --git a/demo/data/redbook/debug_frag.glsl b/demo/data/redbook/debug_frag.glsl
new file mode 100644
index 0000000..04ed267
--- /dev/null
+++ b/demo/data/redbook/debug_frag.glsl
@@ -0,0 +1,16 @@
+/**
+ * OpenGL RedBook Shader.
+ * Examples 7.8, 7.9 and 7.10.
+ */
+#version 330 core
+
+in vec4 Position;
+in vec3 Normal;
+in vec4 Colour;
+
+out vec4 FragColour;
+
+void main() {
+    vec3 normalScale = 0.5 + (Normal / 2);
+    FragColour = vec4(normalScale, 1);
+}
\ No newline at end of file
diff --git a/demo/data/redbook/debug_vert.glsl b/demo/data/redbook/debug_vert.glsl
new file mode 100644
index 0000000..02d3cd9
--- /dev/null
+++ b/demo/data/redbook/debug_vert.glsl
@@ -0,0 +1,24 @@
+/**
+ * OpenGL RedBook Shader.
+ * Example 7.8
+ */
+#version 330 core
+
+layout (location = 0) in vec3 VertexPosition;
+layout (location = 1) in vec3 VertexNormal;
+layout (location = 2) in vec3 VertexColour;
+
+uniform mat4 MVPMatrix;
+uniform mat4 MVMatrix;
+uniform mat3 NormalMatrix;
+
+out vec4 Position;
+out vec3 Normal;
+out vec4 Colour;
+
+void main() {
+    Colour = vec4(0.5, 0.5, 0.5, 1.0f);
+    Normal = NormalMatrix * VertexNormal;
+    Position = MVMatrix * vec4(VertexPosition, 1);
+    gl_Position = MVPMatrix * vec4(VertexPosition, 1);
+}
\ No newline at end of file
diff --git a/demo/data/redbook/lighting_frag.glsl b/demo/data/redbook/lighting_frag.glsl
index 78242e7..cd417d9 100644
--- a/demo/data/redbook/lighting_frag.glsl
+++ b/demo/data/redbook/lighting_frag.glsl
@@ -86,8 +86,6 @@ void main() {
         }
 
         float diffuse = max(0.0, dot(Normal, lightDirection));
-
-
         if (diffuse == 0.0)
             specular = 0.0;
         else
diff --git a/demo/data/redbook/lighting_vert.glsl b/demo/data/redbook/lighting_vert.glsl
index 435d901..9085329 100644
--- a/demo/data/redbook/lighting_vert.glsl
+++ b/demo/data/redbook/lighting_vert.glsl
@@ -8,9 +8,9 @@ uniform mat4 MVPMatrix;
 uniform mat4 MVMatrix;
 uniform mat3 NormalMatrix;
 
-in vec4 VertexPosition;
-in vec3 VertexNormal;
-in vec4 VertexColour;
+layout (location = 0) in vec3 VertexPosition;
+layout (location = 1) in vec3 VertexNormal;
+layout (location = 2) in vec3 VertexColour;
 
 out vec4 Position;
 out vec3 Normal;
@@ -20,7 +20,8 @@ out int matIndex;
 void main() {
     Colour = vec4(1.0, 1.0, 1.0, 1.0f);
     Normal = NormalMatrix * VertexNormal;
-    Position = MVMatrix * VertexPosition;
-    gl_Position = MVPMatrix * VertexPosition;
+    Position = MVMatrix * vec4(VertexPosition, 1.0);
     matIndex = 0;
+
+    gl_Position = MVPMatrix * vec4(VertexPosition, 1.0);
 }
\ No newline at end of file
diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
index d31c966..61598f8 100644
--- a/demo/data/rollball.yml
+++ b/demo/data/rollball.yml
@@ -4,7 +4,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: redbook/lighting
+        pipeline: redbook/debug
         shape:
           type: box
           scale: [1.0, 5.0, 41]
@@ -23,7 +23,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: redbook/lighting
+        pipeline: redbook/debug
         shape:
           type: box
           scale: [39, 5, 1]
@@ -41,7 +41,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: redbook/lighting
+        pipeline: redbook/debug
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
@@ -59,7 +59,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: redbook/lighting
+        pipeline: redbook/debug
         shape:
           type: sphere
       gfx::material:
@@ -75,7 +75,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: redbook/lighting
+        pipeline: redbook/debug
         shape:
           type: box
       gfx::material:
diff --git a/demo/data/topdown.yml b/demo/data/topdown.yml
index d6c6658..0b1c8c5 100644
--- a/demo/data/topdown.yml
+++ b/demo/data/topdown.yml
@@ -4,7 +4,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: phong
+        pipeline: redbook/debug
         shape:
           type: box
           scale: [1.0, 5.0, 41]
@@ -23,7 +23,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: phong
+        pipeline: redbook/debug
         shape:
           type: box
           scale: [39, 5, 1]
@@ -41,7 +41,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: phong
+        pipeline: redbook/debug
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
@@ -59,7 +59,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: phong
+        pipeline: redbook/lighting
         shape:
           type: sphere
       gfx::material:
@@ -75,7 +75,7 @@ prefabs:
     components:
       Transform:
       StaticMesh:
-        pipeline: phong
+        pipeline: redbook/lighting
         shape:
           type: box
       gfx::material:
diff --git a/demo/demo/topdown.cpp b/demo/demo/topdown.cpp
index 4316c2a..fb313b6 100644
--- a/demo/demo/topdown.cpp
+++ b/demo/demo/topdown.cpp
@@ -154,7 +154,7 @@ void TopDown::pick_object() {
 	if ( hit != fggl::entity::INVALID) {
 		//fggl::debug::log("hit: {}", hit);
 	} else {
-		fggl::debug::log("no hit");
+		fggl::debug::info("no hit");
 	}
 }
 
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index 722a232..8f38d34 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -168,8 +168,10 @@ namespace fggl::gfx {
 			m_canvasPipeline = m_cache->get(ogl4::FALLBACK_CANVAS_PIPELINE);
 		}
 
+		// FIXME this should not be hard-coded, it should be part of the scene loading process
 		m_cache->load(ShaderFromName("phong"));
 		m_cache->load(ShaderFromName("redbook/lighting"));
+		m_cache->load(ShaderFromName("redbook/debug"));
 
 		// rendering helpers
 		m_canvasRenderer = std::make_unique<ogl4::CanvasRenderer>(fonts);
diff --git a/fggl/gfx/ogl4/models.cpp b/fggl/gfx/ogl4/models.cpp
index 5a99963..c00e08f 100644
--- a/fggl/gfx/ogl4/models.cpp
+++ b/fggl/gfx/ogl4/models.cpp
@@ -36,9 +36,9 @@ namespace fggl::gfx::ogl4 {
 		auto normalAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, normal));
 		auto colAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, colour));
 
-		vao->setAttribute( *buff.get(), 0, posAttr );
-		vao->setAttribute( *buff.get(), 1, normalAttr );
-		vao->setAttribute( *buff.get(), 3, colAttr );
+		vao->setAttribute( *buff, 0, posAttr );
+		vao->setAttribute( *buff, 1, normalAttr );
+		vao->setAttribute( *buff, 2, colAttr );
 		return buff;
 	}
 
@@ -118,14 +118,14 @@ namespace fggl::gfx::ogl4 {
 		auto cameras = world.find<gfx::Camera>();
 
 		// if there are no cameras, we can't do anything...
-		if ( cameras.size() == 0) {
+		if ( cameras.empty() ) {
 			spdlog::warn("asked to render static models, but there were no cameras");
 			return;
 		}
 
 		// perform a rendering pass for each camera (will usually only be one...)
-		for ( auto& cameraEnt : cameras ){
-			//TODO should be cliping this to only visible objects
+		for ( const auto& cameraEnt : cameras ){
+			//TODO should be clipping this to only visible objects
 
 			// enable required OpenGL state
 			glEnable( GL_CULL_FACE );
@@ -149,7 +149,7 @@ namespace fggl::gfx::ogl4 {
 			for ( const auto& entity : renderables ){
 
 				// ensure that the model pipeline actually exists...
-				const StaticModel& model = world.get<StaticModel>(entity);
+				const auto& model = world.get<StaticModel>(entity);
 				if ( model.pipeline == nullptr ) {
 					spdlog::warn("shader was null, aborting render");
 					continue;
@@ -168,15 +168,11 @@ namespace fggl::gfx::ogl4 {
 
 				// set model transform
 				const auto& transform = world.get<math::Transform>(entity);
-				if (shader->hasUniform("model")) {
-					shader->setUniformMtx(shader->uniform("model"), transform.model());
-				} else {
-					shader->setUniformMtx(shader->uniform("MVPMatrix"), projectionMatrix * viewMatrix * transform.model());
-					shader->setUniformMtx(shader->uniform("MVMatrix"), viewMatrix * transform.model());
-
-					auto normalMatrix = glm::mat3(glm::transpose(inverse(transform.model())));
-					shader->setUniformMtx(shader->uniform("NormalMatrix"), normalMatrix);
-				}
+				shader->setUniformMtx(shader->uniform("MVPMatrix"),  projectionMatrix * viewMatrix * transform.model() );
+				shader->setUniformMtx(shader->uniform("MVMatrix"), viewMatrix * transform.model());
+
+				auto normalMatrix = glm::mat3(glm::transpose(inverse(transform.model())));
+				shader->setUniformMtx(shader->uniform("NormalMatrix"), normalMatrix);
 
 				// setup lighting mode
 				if ( shader->hasUniform("lights[0].isEnabled") ) {
@@ -215,12 +211,7 @@ namespace fggl::gfx::ogl4 {
 				// material detection with fallback
 				const auto& material = world.get<PhongMaterial>(entity);
 
-				if ( shader->hasUniform("material.ambient") ) {
-					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);
-				} else {
+				if ( shader->hasUniform("materials[0].ambient") ) {
 					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);
@@ -236,12 +227,12 @@ namespace fggl::gfx::ogl4 {
 				vao->bind();
 
 				model.vertexData->bind();
-
 				if ( model.restartIndex != NO_RESTART_IDX) {
 					glEnable(GL_PRIMITIVE_RESTART);
 					glPrimitiveRestartIndex(model.restartIndex);
 				}
-				auto elements = model.elements.get();
+
+				auto* elements = model.elements.get();
 				vao->drawElements( *elements, model.drawType, model.elementCount);
 				if ( model.restartIndex != NO_RESTART_IDX) {
 					glDisable(GL_PRIMITIVE_RESTART);
diff --git a/fggl/gfx/ogl4/module.cpp b/fggl/gfx/ogl4/module.cpp
index 4f44bf3..d0c2b33 100644
--- a/fggl/gfx/ogl4/module.cpp
+++ b/fggl/gfx/ogl4/module.cpp
@@ -31,15 +31,13 @@ namespace fggl::gfx {
 
 	static void process_shape(const YAML::Node& node, data::Mesh& mesh) {
 		auto transform = data::OFFSET_NONE;
-		if ( node["offset"] ) {
-			auto offset = node["offset"].as<math::vec3>();
-			transform = glm::translate(transform, offset);
-		}
 
-		if ( node["scale"] ) {
-			auto offset = node["scale"].as<math::vec3>();
-			transform = glm::scale(transform, offset);
-		}
+		auto offset = node["offset"].as<math::vec3>(math::VEC3_ZERO);
+		transform = glm::translate(transform, offset);
+
+		auto scale = node["scale"].as<math::vec3>(math::VEC3_ONES);
+		transform = glm::scale(transform, scale);
+		debug::debug("scale: {}, {}, {}", scale.x, scale.y, scale.z);
 
 		// now the shape itself
 		auto type = node["type"].as<std::string>();
diff --git a/fggl/platform/linux/paths.cpp b/fggl/platform/linux/paths.cpp
index d53cc7b..b1aba8c 100644
--- a/fggl/platform/linux/paths.cpp
+++ b/fggl/platform/linux/paths.cpp
@@ -83,7 +83,7 @@ namespace fggl::platform {
 		// check system paths
 		for ( const auto& path : paths.dataDirs ) {
 			auto fullPath = path / relPath;
-			debug::log(debug::Level::debug, "Checking data path: {}, {}", fullPath.c_str(), std::filesystem::exists(fullPath));
+			debug::trace("Checking data path: {}, exists: {}", fullPath.c_str(), std::filesystem::exists(fullPath));
 			if ( std::filesystem::exists(fullPath) ) {
 				return fullPath;
 			}
@@ -92,7 +92,7 @@ namespace fggl::platform {
 		// if debug mode, try CWD as well.
 		auto debugPath = std::filesystem::current_path() / "data" / relPath;
 		if ( std::filesystem::exists(debugPath) ) {
-			debug::log(debug::Level::debug, "Checking debug path: {}, {}", debugPath.c_str(), std::filesystem::exists(debugPath));
+			debug::trace("Checking debug path: {}, exists: {}", debugPath.c_str(), std::filesystem::exists(debugPath));
 			return debugPath;
 		}
 
diff --git a/include/fggl/debug/impl/logging_std20.hpp b/include/fggl/debug/impl/logging_std20.hpp
index 81e55bd..afc2a74 100644
--- a/include/fggl/debug/impl/logging_std20.hpp
+++ b/include/fggl/debug/impl/logging_std20.hpp
@@ -80,34 +80,36 @@ namespace fggl::debug {
 		fmt::print(CERR_FMT, level_to_string(level), fmtStr);
 	}
 
-	template<typename ...T>
-	void error(FmtType fmt, T &&...args) {
-		log( Level::error, fmt, args...);
-	}
+	// inlined, pre-set level versions of the log function above
 
 	template<typename ...T>
-	void warning(FmtType fmt, T &&...args) {
-		log( Level::warning, fmt, args...);
+	inline void error(FmtType fmt, T &&...args) {
+		auto fmtStr = fmt::format(fmt::runtime(fmt), args...);
+		fmt::print(CERR_FMT, level_to_string(Level::error), fmtStr);
 	}
 
 	template<typename ...T>
-	void info(FmtType fmt, T &&...args) {
-		log( Level::info, fmt, args... );
+	inline void warning(FmtType fmt, T &&...args) {
+		auto fmtStr = fmt::format(fmt::runtime(fmt), args...);
+		fmt::print(CERR_FMT, level_to_string(Level::warning), fmtStr);
 	}
 
 	template<typename ...T>
-	void log(FmtType fmt, T &&...args) {
-		log( Level::info, fmt, args...);
+	inline void info(FmtType fmt, T &&...args) {
+		auto fmtStr = fmt::format(fmt::runtime(fmt), args...);
+		fmt::print(CERR_FMT, level_to_string(Level::info), fmtStr);
 	}
 
 	template<typename ...T>
-	void debug(FmtType fmt, T &&...args) {
-		log( Level::debug, fmt, args...);
+	inline void debug(FmtType fmt, T &&...args) {
+		auto fmtStr = fmt::format(fmt::runtime(fmt), args...);
+		fmt::print(CERR_FMT, level_to_string(Level::debug), fmtStr);
 	}
 
 	template<typename ...T>
-	void trace(FmtType fmt, T &&...args) {
-		log( Level::trace, fmt, args...);
+	inline void trace(FmtType fmt, T &&...args) {
+		auto fmtStr = fmt::format(fmt::runtime(fmt), args...);
+		fmt::print(CERR_FMT, level_to_string(Level::trace), fmtStr);
 	}
 
 }
diff --git a/include/fggl/entity/loader/serialise.hpp b/include/fggl/entity/loader/serialise.hpp
index 6650351..85783c4 100644
--- a/include/fggl/entity/loader/serialise.hpp
+++ b/include/fggl/entity/loader/serialise.hpp
@@ -23,6 +23,7 @@
 
 #include "fggl/math/types.hpp"
 #include "fggl/data/model.hpp"
+#include "fggl/phys/types.hpp"
 
 namespace YAML {
 
@@ -42,8 +43,8 @@ namespace YAML {
 			}
 
 			rhs.x = node[0].as<float>();
-			rhs.y = node[0].as<float>();
-			rhs.z = node[0].as<float>();
+			rhs.y = node[1].as<float>();
+			rhs.z = node[2].as<float>();
 			return true;
 		}
 	};
@@ -63,7 +64,7 @@ namespace YAML {
 			}
 
 			rhs.x = node[0].as<float>();
-			rhs.y = node[0].as<float>();
+			rhs.y = node[1].as<float>();
 			return true;
 		}
 	};
@@ -92,6 +93,41 @@ namespace YAML {
 		}
 	};
 
+	template<>
+	struct convert<fggl::phys::BodyType> {
+		static Node encode(const fggl::phys::BodyType& rhs) {
+			Node node;
+			if ( rhs == fggl::phys::BodyType::STATIC ) {
+				node = "static";
+			} else if ( rhs == fggl::phys::BodyType::DYNAMIC ) {
+				node = "dynamic";
+			} else if ( rhs == fggl::phys::BodyType::KINEMATIC ) {
+				node = "kinematic";
+			}
+			return node;
+		}
+
+		static bool decode(const Node& node, fggl::phys::BodyType& rhs) {
+			auto strVal = node.as<std::string>();
+			if ( strVal == "static" ) {
+				rhs = fggl::phys::BodyType::STATIC;
+				return true;
+			}
+
+			if ( strVal == "dynamic" ) {
+				rhs = fggl::phys::BodyType::DYNAMIC;
+				return true;
+			}
+
+			if ( strVal == "kinematic" ) {
+				rhs = fggl::phys::BodyType::KINEMATIC;
+				return true;
+			}
+
+			return false;
+		}
+	};
+
 	template<>
 	struct convert<fggl::util::GUID> {
 		static Node encode(const fggl::util::GUID& rhs) {
diff --git a/include/fggl/modules/module.hpp b/include/fggl/modules/module.hpp
index 5ff0317..6fac536 100644
--- a/include/fggl/modules/module.hpp
+++ b/include/fggl/modules/module.hpp
@@ -24,6 +24,7 @@
 #include <functional>
 #include <map>
 #include <memory>
+
 #include "fggl/util/safety.hpp"
 
 namespace fggl::modules {
diff --git a/integrations/bullet/CMakeLists.txt b/integrations/bullet/CMakeLists.txt
index 30b2f8f..f128ed8 100644
--- a/integrations/bullet/CMakeLists.txt
+++ b/integrations/bullet/CMakeLists.txt
@@ -32,6 +32,7 @@ else()
     # bullet cpp files
     target_sources( fggl
         PRIVATE
+            src/module.cpp
             src/simulation.cpp
             src/phys_draw.cpp
     )
diff --git a/integrations/bullet/include/fggl/phys/bullet/bullet.hpp b/integrations/bullet/include/fggl/phys/bullet/bullet.hpp
index a65250a..b0d2ba0 100644
--- a/integrations/bullet/include/fggl/phys/bullet/bullet.hpp
+++ b/integrations/bullet/include/fggl/phys/bullet/bullet.hpp
@@ -37,46 +37,6 @@
 
 #include "fggl/phys/types.hpp"
 #include "fggl/phys/bullet/types.hpp"
-
-namespace fggl::phys::bullet {
-
-	/**
-	 * Bullet integration module.
-	 *
-	 * This provides the ability for FGGL to use bullet as a physics backened. Bullet is mature, stable physics
-	 * engine which makes it suitable for most use cases. For use with FGGL there is a reasonable amount of copying
-	 * back and forth to keep the two states in sync.
-	 */
-	/*struct BulletModule : ecs3::Module {
-		public:
-			BulletModule() = default;
-
-			[[nodiscard]]
-			std::string name() const override {
-				return "phys::Bullet3";
-			}
-
-			void onLoad(ecs3::ModuleManager&, ecs3::TypeRegistry &types) override {
-				// dependencies
-				types.make<phys::CollisionCallbacks>();
-				types.make<phys::CollisionCache>();
-
-				types.make<phys::RigidBody>();
-				types.make<phys::Dynamics>();
-
-				// my types
-				types.make<phys::bullet::BulletBody>();
-			}
-
-	};*/
-
-} // namespace fggl::phys::bullet
-
-namespace fggl::phys {
-
-	// allows using fggl::phys::Bullet3 as the module name
-	//using Bullet3 = bullet::BulletModule;
-
-} // namespace fggl::phys
+#include "fggl/phys/bullet/module.hpp"
 
 #endif //FGGL_PHYS_BULLET_BULLET_HPP
diff --git a/integrations/bullet/include/fggl/phys/bullet/module.hpp b/integrations/bullet/include/fggl/phys/bullet/module.hpp
new file mode 100644
index 0000000..7ef91f6
--- /dev/null
+++ b/integrations/bullet/include/fggl/phys/bullet/module.hpp
@@ -0,0 +1,51 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 31/07/22.
+//
+
+#ifndef FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
+#define FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
+
+#include "fggl/modules/module.hpp"
+#include "fggl/entity/module.hpp"
+
+#include "fggl/phys/types.hpp"
+#include "fggl/phys/bullet/types.hpp"
+
+namespace fggl::phys::bullet {
+
+	constexpr modules::ModuleService BIND_BULLET = modules::make_service("bind_bullet");
+	constexpr util::GUID CONFIG_PHYS_BODY = util::make_guid("phys::body");
+
+	struct Bullet {
+		constexpr static const char* name = "fggl::phys::bullet";
+		constexpr static const std::array<modules::ModuleService, 1> provides = {
+			BIND_BULLET
+		};
+		constexpr static const std::array<modules::ModuleService, 1> depends = {
+			entity::EntityFactory::service
+		};
+		static const modules::ServiceFactory factory;
+	};
+
+} // namespace fggl::phys::bullet
+
+namespace fggl::phys {
+	using Bullet3 = bullet::Bullet;
+
+} // namespace fggl::phys
+
+#endif //FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
diff --git a/integrations/bullet/src/module.cpp b/integrations/bullet/src/module.cpp
new file mode 100644
index 0000000..da7d6d4
--- /dev/null
+++ b/integrations/bullet/src/module.cpp
@@ -0,0 +1,95 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 31/07/22.
+//
+
+#include "fggl/phys/bullet/module.hpp"
+#include "fggl/phys/bullet/motion.hpp"
+
+namespace fggl::phys::bullet {
+
+	constexpr float UNIT_EXTENT = 0.5F;
+
+	btCollisionShape* create_bullet_shape(const YAML::Node& node) {
+		auto type = node["type"].as<std::string>("MISSING");
+		if ( type == "sphere" ) {
+			auto radius = node["radius"].as<float>(UNIT_EXTENT);
+			return new btSphereShape(radius);
+		} else if ( type == "box" ) {
+			auto extents = node["extents"].as<math::vec3>(math::vec3(UNIT_EXTENT));
+			return new btBoxShape(btVector3(extents.x, extents.y, extents.z));
+		} else {
+			debug::warning("unknown shape type: {}, assuming unit sphere", type);
+			return new btSphereShape(0.5F);
+		}
+	}
+
+	void setup_body(BodyType type, btRigidBody& body) {
+		if ( type != BodyType::STATIC ) {
+			body.setRestitution(0.5F);
+			body.setRollingFriction(0.0F);
+
+			// kinematic bodies cannot sleep
+			if (type == BodyType::KINEMATIC) {
+				auto flags = (body.getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
+				body.setCollisionFlags(flags);
+				body.setActivationState(DISABLE_DEACTIVATION);
+			}
+		}
+	}
+
+	void add_bt_body(const entity::ComponentSpec& spec, entity::EntityManager& manager, const entity::EntityID& id) {
+		auto& body = manager.add<BulletBody>(id);
+		body.motion = new FgglMotionState(&manager, id);
+
+		auto type = spec.get<BodyType>("type", BodyType::DYNAMIC);
+		auto mass = 0.0F;
+
+		// non-static objects can have mass
+		if ( type != BodyType::STATIC ) {
+			mass = spec.get<float>("mass", 1.0F);
+		}
+
+		// calculate inertia
+		btCollisionShape* shape = create_bullet_shape(spec.config["shape"]);
+		btVector3 inertia(0, 0, 0);
+		if ( type != BodyType::STATIC ) {
+			shape->calculateLocalInertia(mass, inertia);
+		}
+
+		// construction information for the simulation
+		btRigidBody::btRigidBodyConstructionInfo constructionInfo {
+			mass,
+			body.motion,
+			shape,
+			inertia
+		};
+		body.body = new btRigidBody(constructionInfo);
+		setup_body(type, *body.body);
+
+		// add the body to the simulation
+	}
+
+	bool bullet_factory(modules::ModuleService service, modules::Services& services) {
+		if ( service == BIND_BULLET ) {
+			auto* entityFactory = services.get<entity::EntityFactory>();
+			entityFactory->bind(CONFIG_PHYS_BODY, add_bt_body);
+		}
+		return false;
+	}
+	const modules::ServiceFactory Bullet::factory = bullet_factory;
+
+} // namespace fggl::phys::bullet
\ No newline at end of file
-- 
GitLab