diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c47b54b..d3af58d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,48 +5,80 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
-find_package(OpenGL REQUIRED)
-find_package(glfw3 3.3 QUIET)
-
-if(NOT glfw3_FOUND)
- message(STATUS "glfw3 not found via find_package")
- if(WIN32)
- set(GLFW_LIB "${CMAKE_CURRENT_SOURCE_DIR}/vendor/glfw/libglfw3.a")
- else()
- message(FATAL_ERROR "GLFW not found")
- endif()
-endif()
-
-add_executable(Engine2026
+# ------------------------------------------------------------------------------
+# 1. Source and Header Files
+# ------------------------------------------------------------------------------
+set(SOURCES
main.cpp
+ vendor/glad/glad.c
+ vendor/stb/stb_image.cpp
+)
+
+set(HEADERS
engine/shader.h
engine/texture.h
engine/mesh.h
- vendor/glad/glad.c
engine/camera.h
+ game/game.cpp
+ game/game.h
)
-target_include_directories(Engine2026 PRIVATE
+# Create the executable
+add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
+
+# ------------------------------------------------------------------------------
+# 2. Include Directories
+# ------------------------------------------------------------------------------
+target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/vendor
+ ${CMAKE_CURRENT_SOURCE_DIR}/vendor/assimp/include
)
-if(glfw3_FOUND)
- target_link_libraries(Engine2026 PRIVATE glfw OpenGL::GL)
-else()
- target_link_libraries(Engine2026 PRIVATE
- ${GLFW_LIB}
- opengl32
- gdi32
- user32
- )
-endif()
+# ------------------------------------------------------------------------------
+# 3. Dependencies & Linking
+# ------------------------------------------------------------------------------
-# Copy assets to build dir
-add_custom_command(
- TARGET Engine2026 POST_BUILD
+# -- OpenGL --
+find_package(OpenGL REQUIRED)
+target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL)
+
+# -- GLFW (Built from source) --
+# Turn off extra GLFW targets to speed up your compile times
+set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
+set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
+
+# Add the GLFW source directory.
+# NOTE: This automatically handles all underlying OS dependencies (gdi32, X11, Cocoa, etc.)
+add_subdirectory(vendor/glfw)
+
+# Link the GLFW target
+target_link_libraries(${PROJECT_NAME} PRIVATE glfw)
+
+# -- Assimp (Built from source) --
+# Turn off extra Assimp targets to speed up compile times
+set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE)
+
+# Add the Assimp source directory
+add_subdirectory(vendor/assimp)
+# Link the GLFW and Assimp targets
+target_link_libraries(${PROJECT_NAME} PRIVATE glfw assimp)
+
+# ------------------------------------------------------------------------------
+# 4. Asset Synchronization (Runs Every Single Compile)
+# ------------------------------------------------------------------------------
+
+add_custom_target(refresh_assets
+ # Step 1: Wipe the assets folder using a static configuration path
+ COMMAND ${CMAKE_COMMAND} -E rm -rf "${CMAKE_CURRENT_BINARY_DIR}/assets"
+ # Step 2: Copy them fresh
COMMAND ${CMAKE_COMMAND} -E copy_directory
- ${CMAKE_CURRENT_SOURCE_DIR}/assets
- $/assets
- COMMENT "Copying assets to output directory"
-)
\ No newline at end of file
+ "${CMAKE_CURRENT_SOURCE_DIR}/assets"
+ "${CMAKE_CURRENT_BINARY_DIR}/assets"
+ COMMENT "Force-syncing assets to build directory..."
+)
+
+# This now safely runs BEFORE the executable compiles, without the dependency loop
+add_dependencies(${PROJECT_NAME} refresh_assets)
\ No newline at end of file
diff --git a/assets/shaders/basicFragment.frag b/assets/shaders/basicFragment.frag
index b64ba4e..f33c7ae 100644
--- a/assets/shaders/basicFragment.frag
+++ b/assets/shaders/basicFragment.frag
@@ -1,56 +1,204 @@
#version 330 core
out vec4 FragColor;
+
+struct Material {
+ sampler2D diffuse;
+ sampler2D specular;
+ float shininess;
+};
+
+struct DirectionalLight {
+ vec3 direction;
+
+ vec3 ambient;
+ vec3 diffuse;
+ vec3 specular;
+};
+struct PointLight {
+ bool disabled;
+
+ vec3 position;
+
+ float constant;
+ float linear;
+ float quadratic;
+
+ vec3 ambient;
+ vec3 diffuse;
+ vec3 specular;
+};
+struct SpotLight {
+ bool disabled;
+
+ vec3 position;
+ vec3 direction;
+ float cutOff;
+ float outerCutOff;
+
+ float constant;
+ float linear;
+ float quadratic;
+
+ vec3 ambient;
+ vec3 diffuse;
+ vec3 specular;
+};
+
+struct Decal {
+ sampler2D tex;
+ float opacity;
+ vec4 uvCoords;
+};
+
in vec3 FragPos;
in vec3 Normal;
-in vec2 TexCoord;
+in vec2 TexCoords;
-uniform sampler2D ourTexture;
-uniform sampler2D decal;
+#define MAX_POINT_LIGHTS 4
+#define MAX_SPOT_LIGHTS 4
+#define MAX_DECALS 4
+
+// Texture & Decals
+uniform Material material;
+uniform sampler2D mainTexture;
+uniform Decal decals[MAX_DECALS];
+uniform int numberOfDecals;
-uniform vec3 lightPos;
-uniform vec3 lightColor;
uniform vec3 viewPos;
-vec4 calcTextures(){
- vec4 tex1 = texture(ourTexture, TexCoord);
- vec4 tex2 = texture(decal, TexCoord);
- vec4 textures = mix(tex1, tex2, 0.2f);
+// Lights
+uniform DirectionalLight dirLight;
- return textures;
-}
+uniform PointLight pointLights[MAX_POINT_LIGHTS];
+uniform int numberOfPointLights;
-vec4 calcDirectLight(norm, viewDir){
- // Ambient Lighting
- float ambientStrength = 0.1;
- vec3 ambient = ambientStrength * lightColor;
+uniform SpotLight spotLights[MAX_SPOT_LIGHTS];
+uniform int numberOfSpotLights;
- // Diffuse Lighting
- vec3 lightDir = normalize(lightPos - FragPos);
-
- float diff = max(dot(norm, lightDir), 0.0);
- vec3 diffuse = diff * lightColor;
-
- // Specular Lighting
- float specularStrength = 0.5;
-
- vec3 reflectDir = reflect(-lightDir, norm);
-
- float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
- vec3 specular = specularStrength * spec * lightColor;
-
- vec4 result = vec4(ambient.rgb + diffuse.rgb + specular.rgb, 1.0f);
-
- return result;
-}
+// func declarations
+vec3 calcTextures();
+vec4 calcDirectLight(DirectionalLight light, vec3 normal, vec3 viewDir, vec3 albedo);
+vec4 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir, vec3 albedo);
+vec4 calcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir, vec3 albedo);
void main()
{
+ vec4 result = vec4(0.0f);
+
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
- vec4 result = calcTextures();
- result += calcDirectLight(norm, viewDir);
+ vec3 albedo = calcTextures();
+ albedo = vec3(1,1,1);
+
+ //result += calcDirectLight(dirLight, norm, viewDir, albedo);
+
+ for(int i = 0; i < numberOfPointLights; i++){
+ if (pointLights[i].disabled) continue;
+ result += calcPointLight(pointLights[i], norm, FragPos, viewDir, albedo);
+ }
+
+ for(int i = 0; i < numberOfSpotLights; i++){
+ if (spotLights[i].disabled) continue;
+ result += calcSpotLight(spotLights[i], norm, FragPos, viewDir, albedo);
+ }
FragColor = result;
}
+
+
+vec3 calcTextures(){
+ vec4 textureAsVec = texture(mainTexture, TexCoords);
+
+ vec4 decalMix;
+ for(int i = 0; i < numberOfDecals; i++){
+ // We invert the 2nd one to make uv scaling easier
+ vec2 decalCoords = TexCoords * (1.0 / decals[i].uvCoords.xy) + decals[i].uvCoords.zw;;
+
+ vec4 decal = texture(decals[i].tex, decalCoords);
+
+ decalMix += mix(decalMix, decal, 0.2f);
+ }
+
+ vec4 finalMix = mix(textureAsVec, decalMix, 0.2f);
+
+ return vec3(finalMix);
+}
+
+// calculates the color when using a directional light.
+vec4 calcDirectLight(DirectionalLight light, vec3 norm, vec3 viewDir, vec3 albedo)
+{
+ vec3 lightDir = normalize(-light.direction);
+
+ // diffuse shading
+ float diff = max(dot(norm, lightDir), 0.0);
+
+ // specular shading
+ vec3 reflectDir = reflect(-lightDir, norm);
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
+
+ // combine results
+ vec3 ambient = light.ambient * albedo;
+ vec3 diffuse = light.diffuse * diff * albedo;
+ vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
+
+ return vec4(ambient + diffuse + specular, 1.0f);
+}
+
+vec4 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir, vec3 albedo)
+{
+ vec3 lightDir = normalize(light.position - fragPos);
+
+ // diffuse shading
+ float diff = max(dot(normal, lightDir), 0.0);
+
+ // specular shading
+ vec3 reflectDir = reflect(-lightDir, normal);
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
+
+ // attenuation
+ float distance = length(light.position - fragPos);
+ float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
+
+ // combine results
+ vec3 ambient = light.ambient * albedo;
+ vec3 diffuse = light.diffuse * diff * albedo;
+ vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
+ ambient *= attenuation;
+ diffuse *= attenuation;
+ specular *= attenuation;
+
+ return vec4(ambient + diffuse + specular, 1.0f);
+}
+
+vec4 calcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir, vec3 albedo)
+{
+ vec3 lightDir = normalize(light.position - fragPos);
+
+ // diffuse shading
+ float diff = max(dot(normal, lightDir), 0.0);
+
+ // specular shading
+ vec3 reflectDir = reflect(-lightDir, normal);
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
+
+ // attenuation
+ float distance = length(light.position - fragPos);
+ float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
+
+ // spotlight intensity
+ float theta = dot(lightDir, normalize(-light.direction));
+ float epsilon = light.cutOff - light.outerCutOff;
+ float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
+
+ // combine results
+ vec3 ambient = light.ambient * albedo;
+ vec3 diffuse = light.diffuse * diff * albedo;
+ vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
+ ambient *= attenuation * intensity;
+ diffuse *= attenuation * intensity;
+ specular *= attenuation * intensity;
+
+ return vec4(ambient + diffuse + specular, 1.0f);
+}
\ No newline at end of file
diff --git a/assets/shaders/basicVertex.vert b/assets/shaders/basicVertex.vert
index 887b27f..1c82e0e 100644
--- a/assets/shaders/basicVertex.vert
+++ b/assets/shaders/basicVertex.vert
@@ -5,7 +5,7 @@ layout (location = 2) in vec2 aTexCoord;
out vec3 FragPos;
out vec3 Normal;
-out vec2 TexCoord;
+out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
@@ -13,9 +13,9 @@ uniform mat4 projection;
void main()
{
- FragPos = vec3(model * vec4(aPos, 1.0));
+ FragPos = vec3(model * vec4(aPos, 1.0f));
Normal = mat3(transpose(inverse(model))) * aNormal;
- TexCoord = aTexCoord;
+ TexCoords = aTexCoord;
gl_Position = projection * view * model * vec4(aPos, 1.0f);
-}
+}
\ No newline at end of file
diff --git a/engine/blend_importer/export_embedded_fbx.py b/engine/blend_importer/export_embedded_fbx.py
new file mode 100644
index 0000000..82a0769
--- /dev/null
+++ b/engine/blend_importer/export_embedded_fbx.py
@@ -0,0 +1,4 @@
+import bpy
+import sys
+
+bpy.ops.export_scene.fbx(filepath=sys.argv[-1], path_mode='COPY', embed_textures=True)
\ No newline at end of file
diff --git a/engine/camera.h b/engine/camera.h
index ce01cd1..c9586f1 100644
--- a/engine/camera.h
+++ b/engine/camera.h
@@ -11,13 +11,14 @@ enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
- RIGHT
+ RIGHT,
+ SPRINT
};
// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
-const float SPEED = 2.5f;
+const float SPEED = 5.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;
@@ -27,6 +28,7 @@ public:
glm::vec3 position, front, up, right, worldUp;
float yaw, pitch;
float movementSpeed, mouseSensitivity, zoom;
+ bool sprinting = false;
// constructor with vectors
Camera(glm::vec3 newPosition = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float newYaw = YAW, float newPitch = PITCH) : front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM)
@@ -57,6 +59,9 @@ public:
void processKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = movementSpeed * deltaTime;
+ if (sprinting)
+ velocity *= 8;
+
if (direction == FORWARD)
position += front * velocity;
if (direction == BACKWARD)
@@ -65,6 +70,8 @@ public:
position -= right * velocity;
if (direction == RIGHT)
position += right * velocity;
+ if (direction == SPRINT)
+ sprinting = !sprinting;
}
// processes input received from a mouse input system. Expects the offset value in both the x and y direction.
@@ -100,6 +107,14 @@ public:
zoom = 45.0f;
}
+ void Update(Shader shader, int screenWidth, int screenHeight)
+ {
+ glm::mat4 projection = glm::perspective(glm::radians(zoom), (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
+
+ shader.setMat4("projection", projection);
+ shader.setMat4("view", GetViewMatrix());
+ shader.setVec3("viewPos", position);
+ }
private:
// calculates the front vector from the Camera's (updated) Euler Angles
diff --git a/engine/mesh.h b/engine/mesh.h
index acf1a11..e121767 100644
--- a/engine/mesh.h
+++ b/engine/mesh.h
@@ -1,63 +1,147 @@
-#include
+#ifndef ENGINE2026_MESH_H
+#define ENGINE2026_MESH_H
+
+#include // holds all OpenGL type declarations
+
+#include
+#include
+
+#include
+
+#include
#include
-#include
+using namespace std;
+
+#define MAX_BONE_INFLUENCE 4
struct Vertex {
- float x, y, z; // position
- float nX, nY, zY; // normal
- float u, v; // texcoord
+ // position
+ glm::vec3 Position;
+ // normal
+ glm::vec3 Normal;
+ // texCoords
+ glm::vec2 TexCoords;
+ // tangent
+ glm::vec3 Tangent;
+ // bitangent
+ glm::vec3 Bitangent;
+ //bone indexes which will influence this vertex
+ int m_BoneIDs[MAX_BONE_INFLUENCE];
+ //weights from each bone
+ float m_Weights[MAX_BONE_INFLUENCE];
};
-class Mesh
-{
+struct Texture {
+ unsigned int id;
+ string type;
+ string path;
+};
+
+class Mesh {
public:
- unsigned int VBO, VAO, EBO;
- std::size_t indexCount = 0;
+ // mesh Data
+ vector vertices;
+ vector indices;
+ vector textures;
+ unsigned int VAO;
- Mesh(const std::vector& vertices, const std::vector& indices) : indexCount(indices.size())
- {
- if (vertices.empty() || indices.empty()) {
- throw std::runtime_error("Mesh created with empty vertices or indices");
- }
-
- try
- {
- glGenVertexArrays(1, &VAO);
- glGenBuffers(1, &VBO);
- glGenBuffers(1, &EBO);
+ // constructor
+ Mesh(vector vertices, vector indices, vector textures)
+ {
+ this->vertices = vertices;
+ this->indices = indices;
+ this->textures = textures;
- glBindVertexArray(VAO);
+ // now that we have all the required data, set the vertex buffers and its attribute pointers.
+ setupMesh();
+ }
- glBindBuffer(GL_ARRAY_BUFFER, VBO);
- glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
+ // render the mesh
+ void Draw(Shader &shader)
+ {
+ // bind appropriate textures
+ unsigned int diffuseNr = 1;
+ unsigned int specularNr = 1;
+ unsigned int normalNr = 1;
+ unsigned int heightNr = 1;
+ for(unsigned int i = 0; i < textures.size(); i++)
+ {
+ glActiveTexture(GL_TEXTURE0 + i); // active proper texture unit before binding
+ // retrieve texture number (the N in diffuse_textureN)
+ string number;
+ string name = textures[i].type;
+ if(name == "texture_diffuse")
+ number = std::to_string(diffuseNr++);
+ else if(name == "texture_specular")
+ number = std::to_string(specularNr++); // transfer unsigned int to string
+ else if(name == "texture_normal")
+ number = std::to_string(normalNr++); // transfer unsigned int to string
+ else if(name == "texture_height")
+ number = std::to_string(heightNr++); // transfer unsigned int to string
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
+ // now set the sampler to the correct texture unit
+ glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i);
+ // and finally bind the texture
+ glBindTexture(GL_TEXTURE_2D, textures[i].id);
+ }
- // Position
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, x));
- glEnableVertexAttribArray(0);
+ // draw mesh
+ glBindVertexArray(VAO);
+ glDrawElements(GL_TRIANGLES, static_cast(indices.size()), GL_UNSIGNED_INT, 0);
+ glBindVertexArray(0);
- // Normals
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, nX));
- glEnableVertexAttribArray(1);
+ // always good practice to set everything back to defaults once configured.
+ glActiveTexture(GL_TEXTURE0);
+ }
- // Texcoords
- glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, u));
- glEnableVertexAttribArray(2);
- }
- catch (...)
- {
- std::cout << "Failed to load mesh" << std::endl;
- }
- }
+private:
+ // render data
+ unsigned int VBO, EBO;
- void Draw() const
- {
- //glDrawArrays(GL_TRIANGLES, 0, 36);
- glBindVertexArray(VAO);
- glDrawElements(GL_TRIANGLES, static_cast(indexCount),
- GL_UNSIGNED_INT, 0);
- }
+ // initializes all the buffer objects/arrays
+ void setupMesh()
+ {
+ // create buffers/arrays
+ glGenVertexArrays(1, &VAO);
+ glGenBuffers(1, &VBO);
+ glGenBuffers(1, &EBO);
+
+ glBindVertexArray(VAO);
+ // load data into vertex buffers
+ glBindBuffer(GL_ARRAY_BUFFER, VBO);
+ // A great thing about structs is that their memory layout is sequential for all its items.
+ // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
+ // again translates to 3/2 floats which translates to a byte array.
+ glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
+
+ // set the vertex attribute pointers
+ // vertex Positions
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
+ // vertex normals
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
+ // vertex texture coords
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
+ // vertex tangent
+ glEnableVertexAttribArray(3);
+ glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent));
+ // vertex bitangent
+ glEnableVertexAttribArray(4);
+ glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent));
+ // ids
+ glEnableVertexAttribArray(5);
+ glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, m_BoneIDs));
+
+ // weights
+ glEnableVertexAttribArray(6);
+ glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, m_Weights));
+ glBindVertexArray(0);
+ }
};
+#endif //ENGINE2026_MESH_H
diff --git a/engine/model.h b/engine/model.h
new file mode 100644
index 0000000..aaeb8e8
--- /dev/null
+++ b/engine/model.h
@@ -0,0 +1,260 @@
+
+#ifndef ENGINE2026_MODEL_H
+#define ENGINE2026_MODEL_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include