diff --git a/.idea/editor.xml b/.idea/editor.xml
index fde5348..bce786e 100644
--- a/.idea/editor.xml
+++ b/.idea/editor.xml
@@ -17,7 +17,7 @@
-
+
@@ -56,6 +56,7 @@
+
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 016f3be..e93e483 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -30,6 +30,9 @@ find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw)
+find_path(TINYGLTF_INCLUDE_DIRS "tiny_gltf.h")
+target_include_directories(example_glfw3 PRIVATE ${TINYGLTF_INCLUDE_DIRS})
+
if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder)
omath_setup_valgrind(example_signature_scan)
diff --git a/examples/example_glfw3.cpp b/examples/example_glfw3.cpp
index b133e9a..587d271 100644
--- a/examples/example_glfw3.cpp
+++ b/examples/example_glfw3.cpp
@@ -1,22 +1,36 @@
// main.cpp
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#define TINYGLTF_NOEXCEPTION
+#define JSON_NOEXCEPTION
+
+#include
#include
#include
+#include
#include
// --- OpenGL / windowing ---
#include // GLEW must come before GLFW
#include
+#include
// --- your math / engine stuff ---
#include "omath/3d_primitives/mesh.hpp"
+#include "omath/collision/epa_algorithm.hpp"
+#include "omath/collision/gjk_algorithm.hpp"
+#include "omath/collision/line_tracer.hpp"
+#include "omath/collision/mesh_collider.hpp"
#include "omath/engines/opengl_engine/camera.hpp"
#include "omath/engines/opengl_engine/constants.hpp"
#include "omath/engines/opengl_engine/mesh.hpp"
+#include "omath/linear_algebra/vector2.hpp"
#include "omath/linear_algebra/vector3.hpp"
using omath::Vector3;
-// ---------------- TYPE ALIASES (ADAPT TO YOUR LIB) ----------------
+// ---------------- TYPE ALIASES ----------------
// Your 4x4 matrix type
using Mat4x4 = omath::opengl_engine::Mat4X4;
@@ -24,27 +38,29 @@ using Mat4x4 = omath::opengl_engine::Mat4X4;
// Rotation angles for the Mesh
using RotationAngles = omath::opengl_engine::ViewAngles;
-// For brevity, alias the templates instantiated with your types
-using VertexType = omath::primitives::Vertex>;
-using CubeMesh = omath::opengl_engine::Mesh;
+// Vertex: pos/normal = Vector3, uv = Vector2
+using VertexType = omath::primitives::Vertex, omath::Vector2>;
+using MeshType = omath::opengl_engine::Mesh;
using MyCamera = omath::opengl_engine::Camera;
+using Idx = Vector3;
-// ---------------- SHADERS ----------------
+// ---------------- SHADERS (TEXTURED) ----------------
static const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
-layout (location = 2) in vec3 aUv;
+layout (location = 2) in vec2 aUv;
uniform mat4 uMVP;
uniform mat4 uModel;
out vec3 vNormal;
-out vec3 vUv;
+out vec2 vUv;
void main() {
- vNormal = aNormal;
+ // world-space normal (assuming no non-uniform scale)
+ vNormal = mat3(uModel) * aNormal;
vUv = aUv;
gl_Position = uMVP * uModel * vec4(aPos, 1.0);
}
@@ -53,16 +69,26 @@ void main() {
static const char* fragmentShaderSource = R"(
#version 330 core
in vec3 vNormal;
-in vec3 vUv;
+in vec2 vUv;
+
+uniform sampler2D uTexture;
out vec4 FragColor;
void main() {
- vec3 baseColor = normalize(abs(vNormal));
- FragColor = vec4(baseColor, 1.0);
+ vec3 baseColor = texture(uTexture, vUv).rgb;
+
+ // simple directional light
+ vec3 N = normalize(vNormal);
+ vec3 L = normalize(vec3(0.3, 0.6, 0.7));
+ float diff = max(dot(N, L), 0.2); // some ambient floor
+
+ FragColor = vec4(baseColor * diff, 1.0);
}
)";
+// ---------------- GL helpers ----------------
+
GLuint compileShader(GLenum type, const char* src)
{
GLuint shader = glCreateShader(type);
@@ -109,10 +135,460 @@ void framebuffer_size_callback(GLFWwindow* /*window*/, int w, int h)
glViewport(0, 0, w, h);
}
+// ---------------- tinygltf helpers ----------------
+
+static const unsigned char* get_accessor_data_ptr(const tinygltf::Model& model, const tinygltf::Accessor& accessor)
+{
+ const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
+ const tinygltf::Buffer& buffer = model.buffers[bufferView.buffer];
+
+ return buffer.data.data() + bufferView.byteOffset + accessor.byteOffset;
+}
+
+static size_t get_accessor_stride(const tinygltf::Model& model, const tinygltf::Accessor& accessor)
+{
+ const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView];
+ size_t stride = accessor.ByteStride(bufferView);
+ if (stride == 0)
+ {
+ stride = tinygltf::GetComponentSizeInBytes(accessor.componentType)
+ * tinygltf::GetNumComponentsInType(accessor.type);
+ }
+ return stride;
+}
+
+static uint32_t read_index(const unsigned char* data, int componentType)
+{
+ switch (componentType)
+ {
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
+ return static_cast(*reinterpret_cast(data));
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
+ return static_cast(*reinterpret_cast(data));
+ case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
+ return *reinterpret_cast(data);
+ default:
+ throw std::runtime_error("Unsupported index component type");
+ }
+}
+
+// ---------------- Node world transform (translation + scale) ----------------
+
+static float vec3_length(float x, float y, float z)
+{
+ return std::sqrt(x * x + y * y + z * z);
+}
+
+struct NodeWorldTransform
+{
+ Vector3 translation;
+ Vector3 scale;
+};
+
+static void compute_node_world_transform_recursive(const tinygltf::Model& model, int nodeIndex,
+ const Vector3& parentTrans, const Vector3& parentScale,
+ std::vector& outWorld)
+{
+ const tinygltf::Node& node = model.nodes[nodeIndex];
+
+ // ----- local translation -----
+ Vector3 localTrans{0.f, 0.f, 0.f};
+
+ // ----- local scale -----
+ Vector3 localScale{1.f, 1.f, 1.f};
+
+ if (node.matrix.size() == 16)
+ {
+ // glTF matrix is column-major
+ const auto& m = node.matrix;
+
+ // translation from last column
+ localTrans.x = static_cast(m[12]);
+ localTrans.y = static_cast(m[13]);
+ localTrans.z = static_cast(m[14]);
+
+ // approximate scale = length of basis vectors
+ float sx = vec3_length(static_cast(m[0]), static_cast(m[1]), static_cast(m[2]));
+ float sy = vec3_length(static_cast(m[4]), static_cast(m[5]), static_cast(m[6]));
+ float sz = vec3_length(static_cast(m[8]), static_cast(m[9]), static_cast(m[10]));
+
+ if (sx > 0.f)
+ localScale.x = sx;
+ if (sy > 0.f)
+ localScale.y = sy;
+ if (sz > 0.f)
+ localScale.z = sz;
+ }
+
+ // node.translation overrides matrix translation if present
+ if (node.translation.size() == 3)
+ {
+ localTrans.x = static_cast(node.translation[0]);
+ localTrans.y = static_cast(node.translation[1]);
+ localTrans.z = static_cast(node.translation[2]);
+ }
+
+ // node.scale overrides matrix scale if present
+ if (node.scale.size() == 3)
+ {
+ localScale.x = static_cast(node.scale[0]);
+ localScale.y = static_cast(node.scale[1]);
+ localScale.z = static_cast(node.scale[2]);
+ }
+
+ // ----- accumulate to world -----
+ Vector3 worldScale{parentScale.x * localScale.x, parentScale.y * localScale.y, parentScale.z * localScale.z};
+
+ // (ignoring scale influence on translation; good enough for simple setups)
+ Vector3 worldTrans{parentTrans.x + localTrans.x, parentTrans.y + localTrans.y, parentTrans.z + localTrans.z};
+
+ outWorld[nodeIndex] = NodeWorldTransform{worldTrans, worldScale};
+
+ for (int childIdx : node.children)
+ {
+ compute_node_world_transform_recursive(model, childIdx, worldTrans, worldScale, outWorld);
+ }
+}
+
+static std::vector compute_all_node_world_transforms(const tinygltf::Model& model)
+{
+ std::vector world(
+ model.nodes.size(), NodeWorldTransform{Vector3{0.f, 0.f, 0.f}, Vector3{1.f, 1.f, 1.f}});
+
+ if (model.nodes.empty())
+ return world;
+
+ int sceneIndex = 0;
+ if (!model.scenes.empty())
+ {
+ if (model.defaultScene >= 0 && model.defaultScene < static_cast(model.scenes.size()))
+ sceneIndex = model.defaultScene;
+ else
+ sceneIndex = 0;
+ }
+
+ if (!model.scenes.empty())
+ {
+ const tinygltf::Scene& scene = model.scenes[sceneIndex];
+ for (int rootNodeIdx : scene.nodes)
+ {
+ compute_node_world_transform_recursive(model, rootNodeIdx,
+ Vector3{0.f, 0.f, 0.f}, // parent translation
+ Vector3{1.f, 1.f, 1.f}, // parent scale
+ world);
+ }
+ }
+ else
+ {
+ // No scenes defined: treat all nodes as roots
+ for (size_t i = 0; i < model.nodes.size(); ++i)
+ {
+ compute_node_world_transform_recursive(model, static_cast(i), Vector3{0.f, 0.f, 0.f},
+ Vector3{1.f, 1.f, 1.f}, world);
+ }
+ }
+
+ return world;
+}
+
+// ---------------- Load meshes/primitives per node (origin + scale) ----------------
+
+static void load_glb_meshes(const std::string& filename, tinygltf::Model& outModel, std::vector& outMeshes,
+ std::vector& outTextureIndices)
+{
+ tinygltf::TinyGLTF loader;
+ tinygltf::Model model;
+ std::string err, warn;
+
+ bool ok = loader.LoadBinaryFromFile(&model, &err, &warn, filename);
+ if (!warn.empty())
+ std::cerr << "tinygltf warning: " << warn << std::endl;
+ if (!ok)
+ throw std::runtime_error("Failed to load GLB \"" + filename + "\": " + err);
+
+ if (model.meshes.empty())
+ throw std::runtime_error("GLB has no meshes: " + filename);
+
+ outMeshes.clear();
+ outTextureIndices.clear();
+
+ // Precompute world translation + scale for all nodes
+ std::vector nodeWorld = compute_all_node_world_transforms(model);
+
+ int primitiveIndexGlobal = 0;
+
+ // Iterate over ALL nodes that reference a mesh
+ for (size_t nodeIndex = 0; nodeIndex < model.nodes.size(); ++nodeIndex)
+ {
+ const tinygltf::Node& node = model.nodes[nodeIndex];
+ std::println("{}", node.name);
+ if (node.mesh < 0 || node.mesh >= static_cast(model.meshes.size()))
+ continue;
+
+ const tinygltf::Mesh& gltfMesh = model.meshes[node.mesh];
+ const NodeWorldTransform& nodeTf = nodeWorld[nodeIndex];
+ const Vector3& nodeOrigin = nodeTf.translation;
+ const Vector3& nodeScale = nodeTf.scale;
+ for (const tinygltf::Primitive& prim : gltfMesh.primitives)
+ {
+ if (prim.mode != TINYGLTF_MODE_TRIANGLES)
+ {
+ std::cerr << "Skipping non-triangle primitive\n";
+ continue;
+ }
+
+ // POSITION (required)
+ auto posIt = prim.attributes.find("POSITION");
+ if (posIt == prim.attributes.end())
+ {
+ std::cerr << "Primitive has no POSITION attribute, skipping\n";
+ continue;
+ }
+
+ const tinygltf::Accessor& posAccessor = model.accessors[posIt->second];
+ if (posAccessor.type != TINYGLTF_TYPE_VEC3 || posAccessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT)
+ {
+ std::cerr << "POSITION must be VEC3 float, skipping primitive\n";
+ continue;
+ }
+
+ const unsigned char* posBase = get_accessor_data_ptr(model, posAccessor);
+ size_t posStride = get_accessor_stride(model, posAccessor);
+ size_t vertexCount = posAccessor.count;
+
+ std::vector vbo(vertexCount);
+
+ // NORMAL (optional)
+ const unsigned char* nrmBase = nullptr;
+ size_t nrmStride = 0;
+ auto nrmIt = prim.attributes.find("NORMAL");
+ if (nrmIt != prim.attributes.end())
+ {
+ const tinygltf::Accessor& nrmAccessor = model.accessors[nrmIt->second];
+ if (nrmAccessor.type == TINYGLTF_TYPE_VEC3
+ && nrmAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT)
+ {
+ nrmBase = get_accessor_data_ptr(model, nrmAccessor);
+ nrmStride = get_accessor_stride(model, nrmAccessor);
+ }
+ }
+
+ // TEXCOORD_0 (optional, vec2)
+ const unsigned char* uvBase = nullptr;
+ size_t uvStride = 0;
+ auto uvIt = prim.attributes.find("TEXCOORD_0");
+ if (uvIt != prim.attributes.end())
+ {
+ const tinygltf::Accessor& uvAccessor = model.accessors[uvIt->second];
+ if (uvAccessor.type == TINYGLTF_TYPE_VEC2 && uvAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT)
+ {
+ uvBase = get_accessor_data_ptr(model, uvAccessor);
+ uvStride = get_accessor_stride(model, uvAccessor);
+ }
+ }
+
+ // Fill VBO
+ for (size_t i = 0; i < vertexCount; ++i)
+ {
+ VertexType v{};
+
+ const float* pos = reinterpret_cast(posBase + i * posStride);
+ v.position = Vector3{pos[0], pos[1], pos[2]};
+
+ if (nrmBase)
+ {
+ const float* nrm = reinterpret_cast(nrmBase + i * nrmStride);
+ v.normal = Vector3{nrm[0], nrm[1], nrm[2]};
+ }
+ else
+ {
+ v.normal = Vector3{0.f, 0.f, 1.f};
+ }
+
+ if (uvBase)
+ {
+ const float* uv = reinterpret_cast(uvBase + i * uvStride);
+ v.uv = omath::Vector2{uv[0], uv[1]};
+ }
+ else
+ {
+ v.uv = omath::Vector2{0.f, 0.f};
+ }
+
+ vbo[i] = v;
+ }
+
+ // Build triangle EBO (Vector3)
+ std::vector ebo;
+
+ if (prim.indices >= 0)
+ {
+ const tinygltf::Accessor& idxAccessor = model.accessors[prim.indices];
+ const unsigned char* idxBase = get_accessor_data_ptr(model, idxAccessor);
+ size_t idxStride = get_accessor_stride(model, idxAccessor);
+ size_t indexCount = idxAccessor.count;
+
+ if (indexCount < 3)
+ continue;
+
+ ebo.reserve(indexCount / 3);
+ for (size_t i = 0; i + 2 < indexCount; i += 3)
+ {
+ const unsigned char* p0 = idxBase + (i + 0) * idxStride;
+ const unsigned char* p1 = idxBase + (i + 1) * idxStride;
+ const unsigned char* p2 = idxBase + (i + 2) * idxStride;
+
+ uint32_t i0 = read_index(p0, idxAccessor.componentType);
+ uint32_t i1 = read_index(p1, idxAccessor.componentType);
+ uint32_t i2 = read_index(p2, idxAccessor.componentType);
+
+ ebo.emplace_back(Idx{i0, i1, i2});
+ }
+ }
+ else
+ {
+ if (vertexCount >= 3)
+ {
+ ebo.reserve(vertexCount / 3);
+ for (uint32_t i = 0; i + 2 < vertexCount; i += 3)
+ {
+ ebo.emplace_back(Idx{i, i + 1, i + 2});
+ }
+ }
+ }
+
+ if (vbo.empty() || ebo.empty())
+ {
+ std::cerr << "Primitive produced empty vbo/ebo, skipping\n";
+ continue;
+ }
+
+ // ---- Decide which texture index to use for this primitive ----
+ int textureIndex = -1;
+
+ // 1) Try material → baseColorTexture.index
+ if (prim.material >= 0 && prim.material < static_cast(model.materials.size()))
+ {
+ const tinygltf::Material& mat = model.materials[prim.material];
+ if (mat.pbrMetallicRoughness.baseColorTexture.index >= 0)
+ {
+ textureIndex = mat.pbrMetallicRoughness.baseColorTexture.index;
+ }
+ }
+
+ // 2) If that failed but there are textures, map primitive index to textures round-robin
+ if (textureIndex < 0 && !model.textures.empty())
+ {
+ textureIndex = primitiveIndexGlobal % static_cast(model.textures.size());
+ }
+
+ outTextureIndices.push_back(textureIndex);
+
+ // Create MeshType and store it, with origin & scale from node transform
+ MeshType mesh{std::move(vbo), std::move(ebo)};
+ mesh.set_origin(nodeOrigin); // origin from glTF node
+ mesh.set_scale(nodeScale); // scale from glTF node
+ mesh.set_rotation(RotationAngles{}); // keep your rotation system
+
+ outMeshes.emplace_back(std::move(mesh));
+
+ ++primitiveIndexGlobal;
+ }
+ }
+
+ if (outMeshes.empty())
+ throw std::runtime_error("No primitives with triangles were loaded from GLB: " + filename);
+
+ outModel = std::move(model);
+}
+
+// ---------------- Texture creation from glTF ----------------
+
+static GLuint create_default_white_texture()
+{
+ GLuint tex = 0;
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+
+ unsigned char white[4] = {(std::uint8_t)(rand() % 255), (std::uint8_t)(rand() % 255), (std::uint8_t)(rand() % 255),
+ 255};
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+ return tex;
+}
+
+static GLuint create_texture_from_image(const tinygltf::Image& image)
+{
+ if (image.image.empty() || image.width <= 0 || image.height <= 0)
+ {
+ std::cerr << "Image is empty or invalid, using white texture\n";
+ return create_default_white_texture();
+ }
+
+ GLenum format = GL_RGBA;
+ if (image.component == 3)
+ format = GL_RGB;
+ else if (image.component == 4)
+ format = GL_RGBA;
+ else if (image.component == 1)
+ format = GL_RED;
+
+ GLuint glTex = 0;
+ glGenTextures(1, &glTex);
+ glBindTexture(GL_TEXTURE_2D, glTex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glTexImage2D(GL_TEXTURE_2D, 0, format, image.width, image.height, 0, format, GL_UNSIGNED_BYTE, image.image.data());
+
+ glGenerateMipmap(GL_TEXTURE_2D);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ return glTex;
+}
+
+// textureIndex is an index into model.textures
+static GLuint create_texture_from_gltf(const tinygltf::Model& model, int textureIndex)
+{
+ const tinygltf::Image* image = nullptr;
+
+ if (textureIndex >= 0 && textureIndex < static_cast(model.textures.size()))
+ {
+ const tinygltf::Texture& tex = model.textures[textureIndex];
+ int imageIndex = tex.source;
+ if (imageIndex >= 0 && imageIndex < static_cast(model.images.size()))
+ {
+ image = &model.images[imageIndex];
+ }
+ }
+
+ // Fallback: if textureIndex invalid or texture had no image, use first image if available
+ if (!image && !model.images.empty())
+ {
+ image = &model.images[0];
+ }
+
+ if (!image)
+ return create_default_white_texture();
+
+ return create_texture_from_image(*image);
+}
+
// ---------------- MAIN ----------------
-int main()
+int main(int argc, char** argv)
{
+ // filename from CLI or default
+ std::string glbFile = (argc > 1) ? argv[1] : "untitled.glb";
+
// ---------- GLFW init ----------
if (!glfwInit())
{
@@ -130,7 +606,7 @@ int main()
constexpr int SCR_WIDTH = 800;
constexpr int SCR_HEIGHT = 600;
- GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "omath cube + camera (GLEW)", nullptr, nullptr);
+ GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "omath glTF multi-mesh (textured)", nullptr, nullptr);
if (!window)
{
std::cerr << "Failed to create GLFW window\n";
@@ -140,7 +616,7 @@ int main()
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
-
+ //glfwSwapInterval(0);
// ---------- GLEW init ----------
glewExperimental = GL_TRUE;
GLenum glewErr = glewInit();
@@ -154,141 +630,219 @@ int main()
// ---------- GL state ----------
glEnable(GL_DEPTH_TEST);
-
- // Face culling
glEnable(GL_CULL_FACE);
- glCullFace(GL_BACK); // cull back faces
- glFrontFace(GL_CCW); // counter-clockwise is front
+ glCullFace(GL_BACK);
+ glFrontFace(GL_CCW);
- // ---------- Build Cube Mesh (CPU side) ----------
- std::vector vbo;
- vbo.reserve(8);
+ // ---------- Load GLB meshes (CPU side) ----------
+ std::vector meshes;
+ std::vector textureIndices; // per-primitive texture index
+ tinygltf::Model gltfModel;
- Vector3 p000{-0.5f, -0.5f, -0.5f};
- Vector3 p001{-0.5f, -0.5f, 0.5f};
- Vector3 p010{-0.5f, 0.5f, -0.5f};
- Vector3 p011{-0.5f, 0.5f, 0.5f};
- Vector3 p100{0.5f, -0.5f, -0.5f};
- Vector3 p101{0.5f, -0.5f, 0.5f};
- Vector3 p110{0.5f, 0.5f, -0.5f};
- Vector3 p111{0.5f, 0.5f, 0.5f};
-
- VertexType v0{p000, Vector3{-1, -1, -1}, omath::Vector2{0, 0}};
- VertexType v1{p001, Vector3{-1, -1, 1}, omath::Vector2{0, 1}};
- VertexType v2{p010, Vector3{-1, 1, -1}, omath::Vector2{1, 0}};
- VertexType v3{p011, Vector3{-1, 1, 1}, omath::Vector2{1, 1}};
- VertexType v4{p100, Vector3{1, -1, -1}, omath::Vector2{0, 0}};
- VertexType v5{p101, Vector3{1, -1, 1}, omath::Vector2{0, 1}};
- VertexType v6{p110, Vector3{1, 1, -1}, omath::Vector2{1, 0}};
- VertexType v7{p111, Vector3{1, 1, 1}, omath::Vector2{1, 1}};
-
- vbo.push_back(v0); // 0
- vbo.push_back(v1); // 1
- vbo.push_back(v2); // 2
- vbo.push_back(v3); // 3
- vbo.push_back(v4); // 4
- vbo.push_back(v5); // 5
- vbo.push_back(v6); // 6
- vbo.push_back(v7); // 7
-
- using Idx = Vector3;
- std::vector ebo;
- ebo.reserve(12);
-
- // front (z+)
- ebo.emplace_back(1, 5, 7);
- ebo.emplace_back(1, 7, 3);
-
- // back (z-)
- ebo.emplace_back(0, 2, 6);
- ebo.emplace_back(0, 6, 4);
-
- // left (x-)
- ebo.emplace_back(0, 1, 3);
- ebo.emplace_back(0, 3, 2);
-
- // right (x+)
- ebo.emplace_back(4, 6, 7);
- ebo.emplace_back(4, 7, 5);
-
- // bottom (y-)
- ebo.emplace_back(0, 4, 5);
- ebo.emplace_back(0, 5, 1);
-
- // top (y+)
- ebo.emplace_back(2, 3, 7);
- ebo.emplace_back(2, 7, 6);
-
- CubeMesh cube{std::move(vbo), std::move(ebo)};
- cube.set_origin({0.f, 0.f, 0.f});
- cube.set_scale({2.f, 2.f, 2.f});
- cube.set_rotation(RotationAngles{});
-
- // ---------- OpenGL buffers ----------
- GLuint VAO = 0, VBO = 0, EBO_GL = 0;
- glGenVertexArrays(1, &VAO);
- glGenBuffers(1, &VBO);
- glGenBuffers(1, &EBO_GL);
-
- glBindVertexArray(VAO);
-
- // upload vertex buffer
- glBindBuffer(GL_ARRAY_BUFFER, VBO);
- glBufferData(GL_ARRAY_BUFFER, cube.m_vertex_buffer.size() * sizeof(VertexType), cube.m_vertex_buffer.data(),
- GL_STATIC_DRAW);
-
- // flatten EBO to GL indices
- std::vector flatIndices;
- flatIndices.reserve(cube.m_element_buffer_object.size() * 3);
- for (const auto& tri : cube.m_element_buffer_object)
+ try
{
- flatIndices.push_back(tri.x);
- flatIndices.push_back(tri.y);
- flatIndices.push_back(tri.z);
+ load_glb_meshes(glbFile, gltfModel, meshes, textureIndices);
+ std::cerr << "Loaded " << meshes.size() << " mesh primitives from GLB\n";
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Error loading GLB: " << e.what() << std::endl;
+ glfwTerminate();
+ return -1;
}
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_GL);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, flatIndices.size() * sizeof(GLuint), flatIndices.data(), GL_STATIC_DRAW);
+ const size_t meshCount = meshes.size();
- // vertex layout: position / normal / uv (each Vector3)
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, position));
+ // ---------- Create GL buffers per mesh ----------
+ std::vector vaos(meshCount);
+ std::vector vbos(meshCount);
+ std::vector ebos(meshCount);
+ std::vector textures(meshCount);
+ std::vector> flatIndices(meshCount);
- glEnableVertexAttribArray(1);
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, normal));
+ glGenVertexArrays(static_cast(meshCount), vaos.data());
+ glGenBuffers(static_cast(meshCount), vbos.data());
+ glGenBuffers(static_cast(meshCount), ebos.data());
- glEnableVertexAttribArray(2);
- glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, uv));
+ using Collider = omath::collision::MeshCollider;
- glBindVertexArray(0);
+ std::vector colliders;
+
+ for (const auto& mesh : meshes)
+ {
+ colliders.emplace_back(mesh);
+ }
+ using ColliderInterface = omath::collision::ColliderInterface>;
+
+ for (size_t i = 0; i < meshCount; ++i)
+ {
+ MeshType& mesh = meshes[i];
+
+ glBindVertexArray(vaos[i]);
+
+ // VBO
+ glBindBuffer(GL_ARRAY_BUFFER, vbos[i]);
+ glBufferData(GL_ARRAY_BUFFER, mesh.m_vertex_buffer.size() * sizeof(VertexType), mesh.m_vertex_buffer.data(),
+ GL_STATIC_DRAW);
+
+ // Flatten triangle EBO (Vector3) to scalar index buffer
+ flatIndices[i].reserve(mesh.m_element_buffer_object.size() * 3);
+ for (const auto& tri : mesh.m_element_buffer_object)
+ {
+ flatIndices[i].push_back(tri.x);
+ flatIndices[i].push_back(tri.y);
+ flatIndices[i].push_back(tri.z);
+ }
+
+ // EBO
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebos[i]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, flatIndices[i].size() * sizeof(GLuint), flatIndices[i].data(),
+ GL_STATIC_DRAW);
+
+ // vertex layout: position / normal / uv
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, position));
+
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, normal));
+
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, uv));
+
+ glBindVertexArray(0);
+
+ // Texture for this mesh (based on its texture index)
+ textures[i] = create_texture_from_gltf(gltfModel, textureIndices[i]);
+ }
// ---------- Camera setup ----------
omath::projection::ViewPort viewPort{static_cast(SCR_WIDTH), static_cast(SCR_HEIGHT)};
- Vector3 camPos{0.f, 0.f, 3.f};
+ Vector3 camPos{0.f, 1.0f, 6.f};
float nearPlane = 0.1f;
- float farPlane = 100.f;
- auto fov = omath::projection::FieldOfView::from_degrees(90.f);
+ float farPlane = 1000.f;
+ auto fov = omath::projection::FieldOfView::from_degrees(110.f);
MyCamera camera{camPos, {}, viewPort, fov, nearPlane, farPlane};
// ---------- Shader ----------
GLuint shaderProgram = createShaderProgram();
GLint uMvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
- GLint uModel = glGetUniformLocation(shaderProgram, "uModel");
+ GLint uModelLoc = glGetUniformLocation(shaderProgram, "uModel");
+ GLint uTexLoc = glGetUniformLocation(shaderProgram, "uTexture");
static float old_frame_time = glfwGetTime();
+ auto cam_collider = colliders.at(0);
// ---------- Main loop ----------
+ // without fallback memory allocation on heap
+
+ static std::array buf;
+ std::pmr::monotonic_buffer_resource pool_stack{buf.data(), buf.size(),
+ std::pmr::null_memory_resource()};
+
while (!glfwWindowShouldClose(window))
{
- glfwPollEvents();
-
float currentTime = glfwGetTime();
float deltaTime = currentTime - old_frame_time;
old_frame_time = currentTime;
+ glfwPollEvents();
+ auto y = camera.get_origin().y;
+ float speed = 40.f;
+ if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
+ {
+ camera.set_origin(camera.get_origin()
+ + omath::opengl_engine::forward_vector(camera.get_view_angles()) * speed * deltaTime);
+ }
+
+ if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
+ {
+ camera.set_origin(camera.get_origin()
+ - omath::opengl_engine::forward_vector(camera.get_view_angles()) * speed * deltaTime);
+ }
+ if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
+ {
+ camera.set_origin(camera.get_origin()
+ + omath::opengl_engine::right_vector(camera.get_view_angles()) * speed * deltaTime);
+ }
+ if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
+ {
+ camera.set_origin(camera.get_origin()
+ - omath::opengl_engine::right_vector(camera.get_view_angles()) * speed * deltaTime);
+ }
+ auto new_origin = camera.get_origin();
+ new_origin.y = y;
+
+ camera.set_origin(new_origin);
+ float look_speed = 60;
+ if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
+ {
+ auto view_angles = camera.get_view_angles();
+ view_angles.pitch += omath::opengl_engine::PitchAngle::from_degrees(look_speed * deltaTime);
+ camera.set_view_angles(view_angles);
+ }
+ if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
+ {
+ auto view_angles = camera.get_view_angles();
+ view_angles.pitch -= omath::opengl_engine::PitchAngle::from_degrees(look_speed * deltaTime);
+ camera.set_view_angles(view_angles);
+ }
+
+ if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS)
+ {
+ auto view_angles = camera.get_view_angles();
+ view_angles.yaw += omath::opengl_engine::YawAngle::from_degrees(look_speed * deltaTime);
+ camera.set_view_angles(view_angles);
+ }
+ if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS)
+ {
+ auto view_angles = camera.get_view_angles();
+ view_angles.yaw -= omath::opengl_engine::YawAngle::from_degrees(look_speed * deltaTime);
+ camera.set_view_angles(view_angles);
+ }
+ cam_collider.set_origin(camera.get_origin());
+
+ bool on_ground = false;
+
+ for (int b = 0; b < colliders.size(); b++)
+ {
+ auto& collider_a = cam_collider;
+ auto& collider_b = colliders.at(b);
+
+ if (&collider_a == &collider_b)
+ continue;
+ auto info = omath::collision::GjkAlgorithm::is_collide_with_simplex_info(collider_a,
+ collider_b);
+ if (!info.hit)
+ continue;
+
+ pool_stack.release();
+ auto result = omath::collision::Epa::solve(collider_a, collider_b, info.simplex, {}, pool_stack);
+
+ const auto deg = result->penetration_vector.angle_between(omath::opengl_engine::k_abs_up)->as_degrees();
+ on_ground |= deg > 150.f;
+
+ //DO NOT PUSH OBJECT AWAY, NEED TO KEEP IT INSIDE OTHER OBJECTS TO
+ //CHECK IF PLAYER STANDS ON SOMETHING
+ if (std::abs(result->penetration_vector.y) <= 0.15 && deg > 150)
+ continue;
+ collider_a.set_origin(collider_a.get_origin() - result->penetration_vector * 1.005);
+ camera.set_origin(camera.get_origin() - result->penetration_vector * 1.005);
+ }
+ if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && on_ground)
+ {
+ cam_collider.set_origin(cam_collider.get_origin() + omath::opengl_engine::k_abs_up * 5);
+ camera.set_origin(cam_collider.get_origin() + omath::opengl_engine::k_abs_up * 5);
+ on_ground = false;
+ }
+
+ if (!on_ground)
+ {
+ cam_collider.set_origin(cam_collider.get_origin() - omath::opengl_engine::k_abs_up * 5 * deltaTime);
+ camera.set_origin(cam_collider.get_origin() - omath::opengl_engine::k_abs_up * 5 * deltaTime);
+ }
int fbW = 0, fbH = 0;
glfwGetFramebufferSize(window, &fbW, &fbH);
glViewport(0, 0, fbW, fbH);
@@ -300,37 +854,45 @@ int main()
glClearColor(0.1f, 0.15f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- RotationAngles rot = cube.get_rotation_angles();
- rot.yaw += omath::opengl_engine::YawAngle ::from_degrees(40.f * deltaTime);
- rot.roll += omath::opengl_engine::RollAngle::from_degrees(40.f * deltaTime);
-
- if (rot.pitch.as_degrees() == 90.f)
- rot.pitch = omath::opengl_engine::PitchAngle::from_degrees(-90.f);
- rot.pitch += omath::opengl_engine::PitchAngle::from_degrees(40.f * deltaTime);
- cube.set_rotation(rot);
-
const Mat4x4& viewProj = camera.get_view_projection_matrix();
- const auto& model = cube.get_to_world_matrix();
-
glUseProgram(shaderProgram);
- // Send matrices to GPU
const float* mvpPtr = viewProj.raw_array().data();
- const float* modelPtr = model.raw_array().data();
-
glUniformMatrix4fv(uMvpLoc, 1, GL_FALSE, mvpPtr);
- glUniformMatrix4fv(uModel, 1, GL_FALSE, modelPtr);
- glBindVertexArray(VAO);
- glDrawElements(GL_TRIANGLES, static_cast(flatIndices.size()), GL_UNSIGNED_INT, nullptr);
+ glActiveTexture(GL_TEXTURE0);
+ glUniform1i(uTexLoc, 0);
+ // Render all meshes
+ for (size_t i = 0; i < meshCount; ++i)
+ {
+ MeshType& mesh = meshes[i];
+
+ const Mat4x4 model = mesh.get_to_world_matrix();
+ const float* modelPtr = model.raw_array().data();
+ glUniformMatrix4fv(uModelLoc, 1, GL_FALSE, modelPtr);
+
+ glBindTexture(GL_TEXTURE_2D, textures[i]);
+ glBindVertexArray(vaos[i]);
+
+ glDrawElements(GL_TRIANGLES, static_cast(flatIndices[i].size()), GL_UNSIGNED_INT, nullptr);
+ }
+
+ if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS)
+ {
+ std::println("FPS: {}", (int)(1 / deltaTime));
+ }
glfwSwapBuffers(window);
}
// ---------- Cleanup ----------
- glDeleteVertexArrays(1, &VAO);
- glDeleteBuffers(1, &VBO);
- glDeleteBuffers(1, &EBO_GL);
+ for (size_t i = 0; i < meshCount; ++i)
+ {
+ glDeleteTextures(1, &textures[i]);
+ glDeleteVertexArrays(1, &vaos[i]);
+ glDeleteBuffers(1, &vbos[i]);
+ glDeleteBuffers(1, &ebos[i]);
+ }
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
diff --git a/include/omath/collision/epa_algorithm.hpp b/include/omath/collision/epa_algorithm.hpp
index 1328dab..bdcb832 100644
--- a/include/omath/collision/epa_algorithm.hpp
+++ b/include/omath/collision/epa_algorithm.hpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -86,6 +87,7 @@ namespace omath::collision
// Converged if we can’t push the face closer than tolerance
if (p_dist - face.d <= params.tolerance)
{
+ std::println("Vectex size {}, Faces size {}",vertexes.size(), faces.size());
out.normal = face.n;
out.depth = face.d; // along unit normal
out.iterations = it + 1;
@@ -118,7 +120,6 @@ namespace omath::collision
if (faces.empty())
return std::nullopt;
-
const auto best = *std::ranges::min_element(faces, [](const auto& first, const auto& second)
{ return first.d < second.d; });
out.normal = best.n;
diff --git a/vcpkg.json b/vcpkg.json
index c4c0d42..275801e 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -30,7 +30,8 @@
"description": "Build benchmarks",
"dependencies": [
"glfw3",
- "glew"
+ "glew",
+ "tinygltf"
]
},
"imgui": {