SRCDIR ?= src
CPPFLAGS ::= -iquotesrc/ $(CPPFLAGS)
-CFLAGS ::= -g -std=c99 -Wall -Wextra -Wpedantic -Werror $(CFLAGS)
+CFLAGS ::= -g -std=c99 -Wall -Wextra -Wpedantic -Werror \
+ -Wno-error=unused-function -Wno-error=unused-parameter $(CFLAGS)
LDFLAGS ::= $(LDFLAGS)
-LDLIBS ::= -L/usr/x86_64-w64-mingw32/lib -lopengl32 -lglew32 -lfreeglut -lassimp $(LDLIBS)
+#LDLIBS ::= -L/usr/x86_64-w64-mingw32/lib -lopengl32 -lglew32 -lfreeglut -lassimp $(LDLIBS)
+LDLIBS ::= -lm -lopengl32 -lglew32 -lglfw -lassimp $(LDLIBS)
# ######
# Paths
sources ::= main.c \
engine/asset.c \
+ engine/engine.c \
engine/geometry.c \
+ engine/input.c \
engine/logger.c \
engine/performance.c \
engine/render.c \
engine/scene.c \
+ engine/string.c \
engine/tga.c \
engine/ui.c \
+ game/game.c \
+ game/input.c \
game/level.c \
game/player.c
--- /dev/null
+#ifndef ENGINE_PRELUDE_H_
+#define ENGINE_PRELUDE_H_
+
+#ifdef __GNUC__
+
+# define UNUSED __attribute__((unused))
+
+#else // __GNUC__
+
+# define UNUSED
+
+#endif // __GNUC__
+
+#endif // ENGINE_PRELUDE_H_
#include "asset.h"
+#include <math.h>
#include <stdlib.h>
#include <assimp/cimport.h>
+//#include <assimp/metadata.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include "logger.h"
+#include "string.h"
#include "tga.h"
+#define IMPORT_DEBUG_ 0
+
+static const float smoothingThresholdAngle = TAU / 14.0f;
+
static const struct aiScene* importScene(const char* path);
-static Vector3D convertAiVector3D(struct aiVector3D vect);
+static Vector triangleNormal(Vector v1, Vector v2, Vector v3);
+static Vector convertAiVector3D(struct aiVector3D vect);
static const char* replaceFileExtension(const struct aiString path, const char* ext);
+#if IMPORT_DEBUG_
+static void printMetadata(const struct aiMetadata* meta) {
+ if (meta) {
+ for (size_t i = 0; i < meta->mNumProperties; ++i) {
+ String key = stringFromAiString(meta->mKeys[i]);
+ const struct aiMetadataEntry value = meta->mValues[i];
+ switch (value.mType) {
+ case AI_BOOL:
+ logDebug("\"%s\": (bool) %d", key.cstr, *((int*) value.mData));
+ break;
+ case AI_INT32:
+ logDebug("\"%s\": (int32) %d", key.cstr, *((int32_t*) value.mData));
+ break;
+ case AI_UINT64:
+ logDebug("\"%s\": (uint64) %llu", key.cstr, *((uint64_t*) value.mData));
+ break;
+ case AI_FLOAT:
+ logDebug("\"%s\": (float) %f", key.cstr, *((float*) value.mData));
+ break;
+ case AI_DOUBLE:
+ logDebug("\"%s\": (double) %f", key.cstr, *((double*) value.mData));
+ break;
+ case AI_AISTRING: {
+ String str = stringFromAiString(*((struct aiString*) value.mData));
+ logDebug("\"%s\": (string) %s", key.cstr, str.cstr);
+ dropString(str);
+ break; }
+ case AI_AIVECTOR3D: {
+ struct aiVector3D vec = *((struct aiVector3D*) value.mData);
+ logDebug("\"%s\": (vector3d) { %f, %f, %f }", key.cstr, vec.x, vec.y, vec.z);
+ break; }
+ case AI_META_MAX:
+ default:
+ logDebug("\"%s\": (unrecognized type)", key.cstr);
+ break;
+ }
+ dropString(key);
+ }
+ }
+}
+
+void printAiNodeMetadata(const struct aiNode* node) {
+ if (!node) {
+ return;
+ }
+
+ String name = stringFromAiString(node->mName);
+ logDebug("Metadata from node \"%s\": %p", name.cstr, node->mMetaData);
+ printMetadata(node->mMetaData);
+
+ for (size_t i = 0; i < node->mNumChildren; ++i) {
+ printAiNodeMetadata(node->mChildren[i]);
+ }
+
+ dropString(name);
+}
+#endif // IMPORT_DEBUG_
+
const Solid* importSolid(const char* path) {
const struct aiScene* scene = importScene(path);
if (scene == NULL) {
logError("Failed to import solid from %s", path);
return NULL;
}
-
+
+#if IMPORT_DEBUG_
+ const struct aiMetadata* meta = scene->mMetaData;
+ logDebug("Metadata from %s: %p", path, meta);
+ printMetadata(meta);
+ printAiNodeMetadata(scene->mRootNode);
+#endif // IMPORT_DEBUG_
+
const unsigned int numMeshes = scene->mNumMeshes;
const unsigned int numMaterials = scene->mNumMaterials;
// TODO Consider assets with some arrays empty, and prevent zero mallocs
-
+
Solid* solid = malloc(sizeof(Solid));
solid->numMeshes = numMeshes;
solid->meshes = malloc(numMeshes * sizeof(Mesh));
solid->numMaterials = numMaterials;
solid->materials = malloc(numMaterials * sizeof(Material));
-
+
for (unsigned int meshIndex = 0; meshIndex < numMeshes; ++meshIndex) {
const struct aiMesh* aiMesh = scene->mMeshes[meshIndex];
const unsigned int numVertices = aiMesh->mNumVertices;
const unsigned int numFaces = aiMesh->mNumFaces;
-
+
Mesh mesh = { .numVertices = numVertices,
- .vertices = malloc(numVertices * sizeof(Vector3D)),
+ .vertices = malloc(numVertices * sizeof(Vector)),
.normals = NULL,
.textureCoords = NULL,
.numFaces = numFaces,
.faces = malloc(numFaces * sizeof(Face)),
.materialIndex = aiMesh->mMaterialIndex };
-
+
for (unsigned int vertIndex = 0; vertIndex < numVertices; ++vertIndex) {
mesh.vertices[vertIndex] = convertAiVector3D(
aiMesh->mVertices[vertIndex]);
}
-
+
if (aiMesh->mNormals != NULL) {
- mesh.normals = malloc(numVertices * sizeof(Vector3D));
+ mesh.normals = malloc(numVertices * sizeof(Vector));
for (unsigned int normIndex = 0; normIndex < numVertices; ++normIndex) {
mesh.normals[normIndex] = convertAiVector3D(
aiMesh->mNormals[normIndex]);
}
}
-
- mesh.textureCoords = malloc(numVertices * sizeof(Vector3D));
+
+ mesh.textureCoords = malloc(numVertices * sizeof(Vector));
for (unsigned int texcIndex = 0; texcIndex < numVertices; ++texcIndex) {
mesh.textureCoords[texcIndex] = convertAiVector3D(
aiMesh->mTextureCoords[0][texcIndex]);
}
-
+
for (unsigned int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
const struct aiFace aiFace = aiMesh->mFaces[faceIndex];
const unsigned int numIndices = aiFace.mNumIndices;
-
+
Face face = { .numIndices = numIndices,
- .indices = malloc(numIndices * sizeof(size_t)) };
-
+ .indices = malloc(numIndices * sizeof(size_t)),
+ .normals = malloc(numIndices * sizeof(Vector)) };
+
for (unsigned int i = 0; i < numIndices; ++i) {
face.indices[i] = aiFace.mIndices[i];
}
-
+
+ if (numIndices == 3) {
+ Vector normal = triangleNormal(mesh.vertices[face.indices[0]],
+ mesh.vertices[face.indices[1]],
+ mesh.vertices[face.indices[2]]);
+ for (size_t i = 0; i < numIndices; ++i) {
+ face.normals[i] = normal;
+ }
+ }
+ else {
+ if (mesh.normals) {
+ for (size_t i = 0; i < numIndices; ++i) {
+ face.normals[i] = mesh.normals[face.indices[i]];
+ }
+ }
+ else {
+ free(face.normals);
+ face.normals = NULL;
+ }
+ }
+
mesh.faces[faceIndex] = face;
}
-
+
+ float smoothingThreshold = cosf(smoothingThresholdAngle);
+ Face* smoothedFaces = malloc(mesh.numFaces * sizeof(Face));
+ for (size_t faceIndex = 0; faceIndex < mesh.numFaces; ++faceIndex) {
+ Face face = mesh.faces[faceIndex];
+
+ if (face.normals) {
+ face.normals = memcpy(malloc(face.numIndices * sizeof(Vector)),
+ face.normals,
+ face.numIndices * sizeof(Vector));
+
+ for (size_t indexIndex = 0; indexIndex < face.numIndices; ++indexIndex) {
+ Vector smoothedNormal = face.normals[indexIndex];
+
+ for (size_t i = 0; i < mesh.numFaces; ++i) {
+ if (i == faceIndex || !mesh.faces[i].normals) {
+ continue;
+ }
+
+ for (size_t k = 0; k < mesh.faces[i].numIndices; ++k) {
+ if (mesh.faces[i].indices[k] == face.indices[indexIndex]
+ && dotProduct(face.normals[indexIndex],
+ mesh.faces[i].normals[k]) >= smoothingThreshold) {
+ smoothedNormal = addVectors(smoothedNormal, mesh.faces[i].normals[k]);
+ }
+ }
+ }
+
+ face.normals[indexIndex] = normalized(smoothedNormal);
+ }
+ }
+ smoothedFaces[faceIndex] = face;
+ }
+ // TODO Actually clean up the stuff inside
+ free(mesh.faces);
+ mesh.faces = smoothedFaces;
+
solid->meshes[meshIndex] = mesh;
}
-
+
GLuint* textureIds = malloc(numMaterials * sizeof(GLuint));
glGenTextures(numMaterials, textureIds);
-
+
for (unsigned int matIndex = 0; matIndex < numMaterials; ++matIndex) {
Material material = { .textureId = textureIds[matIndex] };
-
+
+#if IMPORT_DEBUG_
+ const struct aiMaterialProperty* prop;
+ aiGetMaterialProperty(scene->mMaterials[matIndex],
+ AI_MATKEY_SHADING_MODEL,
+ &prop);
+
+ String key = stringFromAiString(prop->mKey);
+
+ logDebug("Material property \"%s\": Shading model: (length %u) %d",
+ key.cstr,
+ prop->mDataLength,
+ *((int32_t*) prop->mData));
+
+ dropString(key);
+#endif // IMPORT_DEBUG_
+
glBindTexture(GL_TEXTURE_2D, material.textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-
+
struct aiString originalTexturePath;
if (aiGetMaterialTexture(scene->mMaterials[matIndex],
aiTextureType_DIFFUSE,
char* texturePath = malloc(strlen("assets/") + textureFilenameLength + 1);
strcpy(texturePath, "assets/");
strncat(texturePath, textureFilename, textureFilenameLength);
-
+
TgaImage* textureImage = readTga(texturePath);
if (textureImage == NULL) {
logError("Importing solid from %s: Cannot read texture file %s", path, texturePath);
free(textureImage);
}
}
-
+
solid->materials[matIndex] = material;
}
glBindTexture(GL_TEXTURE_2D, 0);
-
+
aiReleaseImport(scene);
return solid;
}
static const struct aiScene* importScene(const char* path) {
- const struct aiScene* scene = aiImportFile(path, aiProcess_PreTransformVertices);
+ const struct aiScene* scene = aiImportFile(path, aiProcess_JoinIdenticalVertices
+ | aiProcess_PreTransformVertices
+ | aiProcess_ValidateDataStructure);
if (scene != NULL && scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) {
logError("Incomplete scene imported from %s", path);
aiReleaseImport(scene);
return scene;
}
-static Vector3D convertAiVector3D(struct aiVector3D vect) {
- return (Vector3D) { .x = vect.x,
- .y = vect.y,
- .z = vect.z };
+static Vector triangleNormal(Vector v1, Vector v2, Vector v3) {
+ return normalized(crossProduct(subtractVectors(v2, v1), subtractVectors(v3, v1)));
+}
+
+static Vector convertAiVector3D(struct aiVector3D vect) {
+ return (Vector) { .x = vect.x,
+ .y = vect.y,
+ .z = vect.z };
}
/**
*/
static const char* replaceFileExtension(const struct aiString path, const char* ext) {
size_t lengthToCopy = path.length;
-
+
char* lastDotSubstr = strrchr(path.data, '.');
if (lastDotSubstr != NULL) {
if (strpbrk(lastDotSubstr, "\\/") == NULL) {
lengthToCopy = lastDotSubstr - path.data;
}
}
-
+
size_t extLength = strlen(ext) + 1;
char* newPath = malloc(lengthToCopy + extLength);
strncpy(newPath, path.data, lengthToCopy);
strncpy(newPath + lengthToCopy, ext, extLength);
-
+
return newPath;
}
-#ifndef ASSET_H_
-#define ASSET_H_
+#ifndef ENGINE_ASSET_H_
+#define ENGINE_ASSET_H_
#include <stddef.h>
#include <GL/gl.h>
struct Mesh {
size_t numVertices;
- Vector3D* vertices;
- Vector3D* normals;
- Vector3D* textureCoords;
+ Vector* vertices;
+ Vector* normals;
+ Vector* textureCoords;
size_t numFaces;
Face* faces;
size_t materialIndex;
struct Face {
size_t numIndices;
size_t* indices;
+ Vector* normals;
};
struct Material {
const Solid* importSolid(const char* path);
-#endif // ASSET_H_
+#endif // ENGINE_ASSET_H_
--- /dev/null
+#include "engine.h"
+
+#include <stdlib.h>
+#include <assimp/version.h>
+//#include <GL/glxew.h>
+#include <GLFW/glfw3.h>
+
+#include "_prelude.h"
+#include "input.h"
+#include "logger.h"
+#include "performance.h"
+#include "render.h"
+#include "ui.h"
+
+// static const int EXIT_OK = 0;
+static const int EXIT_LIB_FAIL = 1;
+static const int EXIT_CTX_FAIL = 2;
+
+static GLFWwindow* window;
+
+static void onGlfwError(int error, const char* description);
+
+
+
+void init(EngineConfig config) {
+ if (window) {
+ logError("init called more than once");
+ return;
+ }
+
+ logInfo("Assimp %u.%u", aiGetVersionMajor(), aiGetVersionMinor());
+ logInfo("GLEW %s", (const char*) glewGetString(GLEW_VERSION));
+ logInfo("GLFW %s", glfwGetVersionString());
+
+ glfwSetErrorCallback(onGlfwError);
+
+ if (!glfwInit()) {
+ logError("GLFW init failed");
+ exit(EXIT_LIB_FAIL);
+ }
+ // glutInitContextVersion(4,5); TODO establish correct context
+ // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+ // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
+
+ window = glfwCreateWindow(config.windowWidth,
+ config.windowHeight,
+ config.windowTitle.cstr,
+ NULL,
+ NULL);
+
+ if (!window) {
+ logError("Window or context creation failed");
+ glfwTerminate();
+ exit(EXIT_CTX_FAIL);
+ }
+
+ glfwMakeContextCurrent(window);
+
+ logInfo("OpenGL %s", (const char*) glGetString(GL_VERSION));
+// logInfo("GLSL %s", (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
+ logInfo("Renderer: %s", (const char*) glGetString(GL_RENDERER));
+/*
+ GLenum glewInitStatus = glewInit();
+ if (glewInitStatus != GLEW_OK) {
+ logError("GLEW init failed: %s", (const char*) glewGetErrorString(glewInitStatus));
+ exit(EXIT_LIB_FAIL);
+ }
+*/
+ logInfo("Setting swap interval: %d", config.swapInterval);
+ glfwSwapInterval(config.swapInterval);
+
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+ resizeStage(window, width, height);
+
+ glfwSetFramebufferSizeCallback(window, resizeStage);
+ glfwSetKeyCallback(window, onKeyboardEvent);
+
+ initRender();
+ //initPerformanceMetering();
+}
+
+void run(void (*updateFn) (float)) {
+ if (!updateFn) {
+ logError("No update function provided");
+ return;
+ }
+
+ float lastTime = glfwGetTime();
+ float delta = 0.0f;
+
+ while (!glfwWindowShouldClose(window)) {
+ float time = glfwGetTime();
+ delta = time - lastTime;
+ lastTime = time;
+
+ updateFn(delta);
+
+ renderFrame(window);
+ glfwPollEvents();
+ }
+}
+
+void terminate() {
+ glfwTerminate();
+}
+
+EngineConfig defaultConfig() {
+ return (EngineConfig) { .windowWidth = 800,
+ .windowHeight = 600,
+ .windowTitle = newString(NULL),
+ .swapInterval = 1 };
+}
+
+static void onGlfwError(int error UNUSED, const char* description) {
+ logError("GLFW error: %s", description);
+}
--- /dev/null
+#ifndef ENGINE_ENGINE_H_
+#define ENGINE_ENGINE_H_
+
+#include "string.h"
+
+typedef struct EngineConfig EngineConfig;
+
+struct EngineConfig {
+ int windowWidth;
+ int windowHeight;
+ String windowTitle;
+ int swapInterval;
+};
+
+void init(EngineConfig);
+void run(void (*updateFn) (float));
+void terminate();
+
+EngineConfig defaultConfig();
+
+#endif // ENGINE_ENGINE_H_
#include "geometry.h"
+#include <math.h>
#include <stddef.h>
Transform identity() {
Transform multiply(Transform t1, Transform t2) {
GLfloat* a = (GLfloat*) &t1;
GLfloat* b = (GLfloat*) &t2;
+ Transform result;
+ GLfloat* c = (GLfloat*) &result;
for (size_t row = 0; row < 4; ++row) {
for (size_t col = 0; col < 4; ++col) {
- b[(row * 4) + col] =
+ c[(row * 4) + col] =
a[(row * 4) + 0] * b[(0 * 4) + col]
+ a[(row * 4) + 1] * b[(1 * 4) + col]
+ a[(row * 4) + 2] * b[(2 * 4) + col]
+ a[(row * 4) + 3] * b[(3 * 4) + col];
}
}
- return t2;
+ return result;
}
-void translate(Transform* transform, Vector3D vec) {
+void translate(Transform* transform, Vector vec) {
*transform = multiply(
(Transform) { .a1 = 1.0f, .a2 = 0.0f, .a3 = 0.0f, .a4 = vec.x,
.b1 = 0.0f, .b2 = 1.0f, .b3 = 0.0f, .b4 = vec.y,
*transform);
}
-Vector3D translationOf(Transform transform) {
- return (Vector3D) { transform.a4, transform.b4, transform.c4 };
+void rotate(Transform* transform, Vector axis, float angle) {
+ axis = normalized(axis);
+ float l = axis.x;
+ float m = axis.y;
+ float n = axis.z;
+ float sinA = sinf(angle);
+ float cosA = cosf(angle);
+ float omcA = 1 - cosA;
+
+ *transform = multiply(
+ (Transform) { l*l*omcA + cosA, m*l*omcA - n*sinA, n*l*omcA + m*sinA, 0.0f,
+ l*m*omcA + n*sinA, m*m*omcA + cosA, n*m*omcA - l*sinA, 0.0f,
+ l*n*omcA - m*sinA, m*n*omcA + l*sinA, n*n*omcA + cosA, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f },
+ *transform);
+}
+
+Vector addVectors(Vector v1, Vector v2){
+ return (Vector) { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
+}
+
+Vector subtractVectors(Vector v1, Vector v2) {
+ return (Vector) { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
+}
+
+Vector crossProduct(Vector v1, Vector v2) {
+ return (Vector) { .x = (v1.y * v2.z) - (v1.z * v2.y),
+ .y = (v1.z * v2.x) - (v1.x * v2.z),
+ .z = (v1.x * v2.y) - (v1.y * v2.x) };
+}
+
+float dotProduct(Vector v1, Vector v2) {
+ return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
+}
+
+Vector scaleVector(Vector vec, float scale) {
+ return (Vector) { vec.x * scale,
+ vec.y * scale,
+ vec.z * scale };
+}
+
+Vector growVectorNoFlip(Vector vec, float amount) {
+ float mag = magnitude(vec);
+ float factor = (mag + amount) / mag;
+ if (factor < 0.0f) {
+ factor = 0.0f;
+ }
+ return scaleVector(vec, factor);
+}
+
+Vector clampMagnitude(Vector vec, float maxMagnitude) {
+ float m = magnitude(vec);
+ if (m > maxMagnitude) {
+ vec = scaleVector(vec, maxMagnitude / m);
+ }
+ return vec;
+}
+
+float magnitude(Vector vec) {
+ return sqrtf(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
+}
+
+Vector applyTransform(Transform transform, Vector vec) {
+ GLfloat* a = (GLfloat*) &transform;
+ GLfloat b[4] = { vec.x, vec.y, vec.z, 1.0f };
+ GLfloat c[4];
+
+ for (size_t row = 0; row < 4; ++row) {
+ c[row] =
+ a[(row * 4) + 0] * b[0]
+ + a[(row * 4) + 1] * b[1]
+ + a[(row * 4) + 2] * b[2]
+ + a[(row * 4) + 3] * b[3];
+ }
+ return (Vector) { c[0], c[1], c[2] };
+}
+
+Vector translationOf(Transform transform) {
+ return (Vector) { transform.a4, transform.b4, transform.c4 };
+}
+
+Vector normalized(Vector vec) {
+ float m = magnitude(vec);
+ return (Vector) { vec.x / m, vec.y / m, vec.z / m };
}
-#ifndef GEOMETRY_H_
-#define GEOMETRY_H_
+#ifndef ENGINE_GEOMETRY_H_
+#define ENGINE_GEOMETRY_H_
#include <GL/gl.h>
-typedef struct Vector3D Vector3D;
+typedef struct Vector Vector;
typedef struct Transform Transform;
-struct Vector3D {
+struct Vector {
float x;
float y;
float z;
GLfloat d1, d2, d3, d4;
};
+static const float TAU = 6.28318530718f;
+
Transform identity();
Transform multiply(Transform t1, Transform t2);
-void translate(Transform* transform, Vector3D vec);
-Vector3D translationOf(Transform transform);
+void translate(Transform* transform, Vector vec);
+void rotate(Transform* transform, Vector axis, float angle);
+Vector addVectors(Vector v1, Vector v2);
+Vector subtractVectors(Vector v1, Vector v2);
+Vector crossProduct(Vector v1, Vector v2);
+float dotProduct(Vector v1, Vector v2);
+Vector scaleVector(Vector vec, float scale);
+Vector growVectorNoFlip(Vector vec, float amount);
+Vector clampMagnitude(Vector vec, float maxMagnitude);
+float magnitude(Vector vec);
+Vector applyTransform(Transform transform, Vector vec);
+Vector translationOf(Transform transform);
+Vector normalized(Vector vec);
-#endif // GEOMETRY_H_
+#endif // ENGINE_GEOMETRY_H_
--- /dev/null
+#include "input.h"
+
+#include <stdbool.h>
+
+#include "_prelude.h"
+#include "render.h"
+
+static void (*keyboardInputCallback) (int, int, int, int);
+
+
+
+void onKeyboardEvent(GLFWwindow* window UNUSED, int key, int scancode, int action, int mods) {
+ if (!(mods & GLFW_MOD_CONTROL)) {
+ if (keyboardInputCallback) {
+ keyboardInputCallback(key, scancode, action, mods);
+ }
+ return;
+ }
+
+ switch (key) {
+ case GLFW_KEY_1:
+ if (action == GLFW_PRESS) {
+ debugScene = !debugScene;
+ }
+ break;
+ case GLFW_KEY_2:
+ if (action == GLFW_PRESS) {
+ debugRender = !debugRender;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void setKeyboardInputCallback(void (*callback) (int, int, int, int)) {
+ keyboardInputCallback = callback;
+}
--- /dev/null
+#ifndef ENGINE_INPUT_H
+#define ENGINE_INPUT_H
+
+#include <GLFW/glfw3.h>
+
+void onKeyboardEvent(GLFWwindow* window, int key, int scancode, int action, int mods);
+void setKeyboardInputCallback(void (*) (int, int, int, int));
+
+#endif // ENGINE_INPUT_H
if (msgLevel > logLevel) {
return;
}
-
+
const char* msgLevelString;
switch (msgLevel) {
case LOGLEVEL_ERROR:
msgLevelString = "(invalid message level) ";
break;
}
-
+
va_list args;
va_start(args, message);
-
+
fprintf(stderr, "%s %s:: ", func, msgLevelString);
vfprintf(stderr, message, args);
fputc('\n', stderr);
-
+
va_end(args);
}
-#ifndef LOGGER_H_
-#define LOGGER_H_
+#ifndef ENGINE_LOGGER_H_
+#define ENGINE_LOGGER_H_
-typedef enum {
+enum LogLevel {
LOGLEVEL_ERROR,
LOGLEVEL_WARNING,
LOGLEVEL_INFO,
LOGLEVEL_DEBUG
-} LogLevel;
+};
+
+typedef enum LogLevel LogLevel;
extern LogLevel logLevel;
void logMessage(LogLevel msgLevel, const char* func, const char* message, ...);
-#endif // LOGGER_H_
+#endif // ENGINE_LOGGER_H_
if (meteringEnabled) {
++frames;
Timepoint now;
-
+
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
logWarning("Clock read failed, stopping performance metering");
meteringEnabled = false;
return;
}
-
+
time_t fullSeconds = now.tv_sec - lastDisplayTime.tv_sec;
if (now.tv_nsec < lastDisplayTime.tv_nsec) --fullSeconds;
-
+
if (fullSeconds > 0) {
float seconds = (now.tv_nsec - lastDisplayTime.tv_nsec) / 1000000000.0f;
seconds += (float) (now.tv_sec - lastDisplayTime.tv_sec);
-#ifndef PERFORMANCE_H_
-#define PERFORMANCE_H_
+#ifndef ENGINE_PERFORMANCE_H_
+#define ENGINE_PERFORMANCE_H_
void initPerformanceMetering();
void frameRendered();
-#endif // PERFORMANCE_H_
+#endif // ENGINE_PERFORMANCE_H_
#include "render.h"
-#include <stdbool.h>
-#include <GL/freeglut_std.h>
-#define GL_GLEXT_PROTOTYPES
-#include <GL/glext.h>
-#undef GL_GLEXT_PROTOTYPES
-
#include "geometry.h"
#include "performance.h"
+#include "game/player.h"
+
float viewportAspectRatio = 1.0f;
const Scene* cameraAnchor;
+bool debugScene = false;
+bool debugRender = false;
static const float AXIS_RADIUS = 5.0f;
void initRender() {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-
+
GLfloat light0_ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
GLfloat light0_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light0_specular[] = {0.96f, 0.98f, 1.0f, 1.0f};
GLfloat light0_position[] = {5.0f, 10.0f, 5.0f, 0.0f}; // (w == 0.0f) == directional
-
+
glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
-
+
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0f);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05f);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.005f);
+
+ //glShadeModel(GL_FLAT);
}
-void renderFrame() {
+void renderFrame(GLFWwindow* window) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_NORMALIZE);
renderScene(currentScene, identity());
glFlush();
- glutSwapBuffers();
+ glfwSwapBuffers(window);
frameRendered();
- glutPostRedisplay();
}
static void renderScene(const Scene* scene, const Transform baseTransform) {
glLoadMatrixf((const GLfloat*) &transform);
glDisable(GL_LIGHTING);
- drawAxes();
+
+ if (debugScene || scene == playerProjectedMovement) {
+ drawAxes();
+ }
+
glEnable(GL_LIGHTING);
if (scene->solid) {
static void moveCameraTo(const Scene* anchor) {
glMatrixMode(GL_PROJECTION);
- Vector3D pos = translationOf(worldTransform(anchor));
+ Vector pos = translationOf(worldTransform(anchor));
glTranslatef(-pos.x, -pos.y, -pos.z);
}
glEnd();
}
+static GLfloat absolute(GLfloat a) {
+ return a < 0 ? -a : a;
+}
+
static void drawSolid(const Solid* solid) {
if (solid == NULL) {
return;
}
-
+
glMatrixMode(GL_MODELVIEW);
glColor3f(0.5f, 1.0f, 0.0f);
-
+
for (size_t meshIndex = 0; meshIndex < solid->numMeshes; ++meshIndex) {
const Mesh mesh = solid->meshes[meshIndex];
glBindTexture(GL_TEXTURE_2D,
solid->materials[mesh.materialIndex].textureId);
- bool hasNormals = mesh.normals != NULL;
- bool hasTextureCoords = mesh.textureCoords != NULL;
-
+
for (size_t faceIndex = 0; faceIndex < mesh.numFaces; ++faceIndex) {
const Face face = mesh.faces[faceIndex];
-
+
+ if (debugRender && face.normals) {
+ glDisable(GL_LIGHTING);
+ glDisable(GL_TEXTURE_2D);
+ glBegin(GL_LINES);
+ for (size_t i = 0; i < face.numIndices; ++i) {
+ size_t vertIndex = face.indices[i];
+ Vector vertex = mesh.vertices[vertIndex];
+ Vector normal = face.normals[i];
+ glColor3f(absolute(normal.x), absolute(normal.y), absolute(normal.z));
+ glVertex3f(vertex.x, vertex.y, vertex.z);
+ glVertex3f(vertex.x + normal.x, vertex.y + normal.y, vertex.z + normal.z);
+ }
+ glEnd();
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_LIGHTING);
+ }
+
GLenum faceMode;
switch (face.numIndices) {
case 1: faceMode = GL_POINTS; break;
case 3: faceMode = GL_TRIANGLES; break;
default: faceMode = GL_POLYGON; break;
}
-
+
glBegin(faceMode);
-
+
for (size_t i = 0; i < face.numIndices; ++i) {
size_t vertIndex = face.indices[i];
- if (hasNormals) {
- if (hasTextureCoords) {
- Vector3D coords = mesh.textureCoords[vertIndex];
+ if (face.normals) {
+ if (mesh.textureCoords) {
+ Vector coords = mesh.textureCoords[vertIndex];
glTexCoord2f(coords.x, coords.y);
}
- Vector3D normal = mesh.normals[vertIndex];
+ Vector normal = face.normals[i];
glNormal3f(normal.x, normal.y, normal.z);
}
- Vector3D vertex = mesh.vertices[vertIndex];
+
+ Vector vertex = mesh.vertices[vertIndex];
glVertex3f(vertex.x, vertex.y, vertex.z);
}
-
+
glEnd();
}
}
-#ifndef RENDER_H_
-#define RENDER_H_
+#ifndef ENGINE_RENDER_H_
+#define ENGINE_RENDER_H_
+
+#include <stdbool.h>
+#include <GLFW/glfw3.h>
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glext.h>
+#undef GL_GLEXT_PROTOTYPES
#include "scene.h"
extern float viewportAspectRatio;
extern const Scene* cameraAnchor;
+extern bool debugScene;
+extern bool debugRender;
void initRender();
-void renderFrame();
+void renderFrame(GLFWwindow* window);
-#endif // RENDER_H_
+#endif // ENGINE_RENDER_H_
-#ifndef SCENE_H_
-#define SCENE_H_
+#ifndef ENGINE_SCENE_H_
+#define ENGINE_SCENE_H_
#include "asset.h"
void reparentScene(Scene* scene, Scene* newParent);
Transform worldTransform(const Scene* scene);
-#endif // SCENE_H_
+#endif // ENGINE_SCENE_H_
--- /dev/null
+#include "string.h"
+
+#include <stdlib.h>
+
+String newString(const char* s) {
+ size_t len = 0u;
+ char* cstr;
+ if (s) {
+ len = strlen(s);
+ cstr = memcpy(malloc((len + 1) * sizeof(char)),
+ s,
+ len * sizeof(char));
+ }
+ else {
+ cstr = malloc(1 * sizeof(char));
+ }
+ cstr[len] = '\0';
+ return (String) { .length = len, .cstr = cstr };
+}
+
+String stringFromAiString(const struct aiString aistr) {
+ char* cstr = memcpy(malloc((aistr.length + 1) * sizeof(char)),
+ aistr.data,
+ aistr.length * sizeof(char));
+ cstr[aistr.length] = '\0';
+ return (String) { .length = aistr.length, .cstr = cstr };
+}
+
+void dropString(String str) {
+ free(str.cstr);
+ str.length = 0u;
+ str.cstr = NULL;
+}
--- /dev/null
+#ifndef ENGINE_STRING_H_
+#define ENGINE_STRING_H_
+
+#include <assimp/types.h>
+
+typedef struct String String;
+
+struct String {
+ size_t length;
+ char* cstr;
+};
+
+String newString(const char* s);
+String stringFromAiString(const struct aiString aistr);
+void dropString(String str);
+
+#endif // ENGINE_STRING_H_
if (tgaFile == NULL) {
return NULL;
}
-
+
TgaHeader header;
-
+
if (fread(&header, sizeof(TgaHeader), 1, tgaFile) != 1) {
fclose(tgaFile);
return NULL;
}
-
+
GLenum imageFormat;
GLint imageComponents;
-
+
switch (header.imageBpp) {
case 32:
imageFormat = GL_BGRA;
fclose(tgaFile);
return NULL;
}
-
+
unsigned long imageSize = header.imageWidth * header.imageHeight * (header.imageBpp >> 3);
-
+
GLbyte* bytes = malloc(imageSize * sizeof(GLbyte));
if (bytes == NULL) {
fclose(tgaFile);
return NULL;
}
-
+
if (fread(bytes, imageSize, 1, tgaFile) != 1) {
free(bytes);
fclose(tgaFile);
return NULL;
}
-
+
fclose(tgaFile);
-
+
TgaImage* image = malloc(sizeof(TgaImage));
if (image == NULL) {
return NULL;
}
-
+
(*image).header = header;
(*image).imageFormat = imageFormat;
(*image).imageComponents = imageComponents;
(*image).bytes = bytes;
-
+
return image;
}
-#ifndef TGA_H_
-#define TGA_H_
+#ifndef ENGINE_TGA_H_
+#define ENGINE_TGA_H_
#include <GL/gl.h>
+typedef struct TgaHeader TgaHeader;
+typedef struct TgaImage TgaImage;
+
#pragma pack(push, 1)
-typedef struct {
+struct TgaHeader {
GLubyte idLength;
GLbyte colorMapType;
GLbyte imageType;
GLushort imageHeight;
GLubyte imageBpp;
GLbyte imageDescriptor;
-} TgaHeader;
+};
#pragma pack(pop)
-typedef struct {
+struct TgaImage {
TgaHeader header;
GLenum imageFormat;
GLint imageComponents;
GLbyte* bytes;
-} TgaImage;
+};
TgaImage* readTga(const char* path);
-#endif // TGA_H_
+#endif // ENGINE_TGA_H_
#include "ui.h"
+#include "_prelude.h"
#include "render.h"
-void resizeStage(GLsizei width, GLsizei height) {
+void resizeStage(GLFWwindow* window UNUSED, int width, int height) {
if (height == 0)
height = 1;
-
+
glViewport(0, 0, width, height);
-
+
viewportAspectRatio = (float) width / (float) height;
}
-#ifndef UI_H_
-#define UI_H_
+#ifndef ENGINE_UI_H_
+#define ENGINE_UI_H_
#include <GL/gl.h>
-void resizeStage(GLsizei width, GLsizei height);
+typedef struct GLFWwindow GLFWwindow;
-#endif // UI_H_
+void resizeStage(GLFWwindow* window, int width, int height);
+
+#endif // ENGINE_UI_H_
--- /dev/null
+#include "game.h"
+
+#include "engine/input.h"
+
+#include "input.h"
+#include "level.h"
+#include "player.h"
+
+void initGame() {
+ initLevel();
+ initPlayer();
+ startLevel();
+
+ setKeyboardInputCallback(keyboardInput);
+}
+
+void update(float delta) {
+ updatePlayer(delta);
+}
--- /dev/null
+#ifndef GAME_H_
+#define GAME_H_
+
+void initGame();
+void update(float delta);
+
+#endif // GAME_H_
--- /dev/null
+#include "input.h"
+
+#include <GLFW/glfw3.h>
+
+#include "engine/_prelude.h"
+
+#include "player.h"
+
+void keyboardInput(int key, int scancode UNUSED, int action, int mods UNUSED) {
+ switch (key) {
+ case GLFW_KEY_W:
+ if (action == GLFW_PRESS) {
+ startMovement(DIRECTION_UP);
+ }
+ else if (action == GLFW_RELEASE) {
+ stopMovement(DIRECTION_UP);
+ }
+ break;
+ case GLFW_KEY_S:
+ if (action == GLFW_PRESS) {
+ startMovement(DIRECTION_DOWN);
+ }
+ else if (action == GLFW_RELEASE) {
+ stopMovement(DIRECTION_DOWN);
+ }
+ break;
+ case GLFW_KEY_A:
+ if (action == GLFW_PRESS) {
+ startMovement(DIRECTION_LEFT);
+ }
+ else if (action == GLFW_RELEASE) {
+ stopMovement(DIRECTION_LEFT);
+ }
+ break;
+ case GLFW_KEY_D:
+ if (action == GLFW_PRESS) {
+ startMovement(DIRECTION_RIGHT);
+ }
+ else if (action == GLFW_RELEASE) {
+ stopMovement(DIRECTION_RIGHT);
+ }
+ break;
+ default:
+ break;
+ }
+}
--- /dev/null
+#ifndef INPUT_H_
+#define INPUT_H_
+
+void keyboardInput(int key, int scancode, int action, int mods);
+
+#endif // INPUT_H_
#include "engine/logger.h"
#include "engine/scene.h"
+#include "engine/tga.h"
#include "player.h"
static Block blockEmpty = { .type = BLOCKTYPE_SPACE,
.solid = NULL };
-static Block blockWall01 = { .type = BLOCKTYPE_OBSTACLE,
+static Block blockWall01 = { .type = BLOCKTYPE_OBSTACLE_X | BLOCKTYPE_OBSTACLE_Z,
.solid = NULL };
static Transform playerSpawnTransform;
+static void buildLevelFromImage(const TgaImage* image);
+
void initLevel() {
playerSpawnTransform = identity();
- translate(&playerSpawnTransform, (Vector3D) { .x = -BLOCKGRID_CELL_SIZE,
- .y = 0.0f,
- .z = -BLOCKGRID_CELL_SIZE });
+ translate(&playerSpawnTransform, (Vector) { -BLOCKGRID_CELL_SIZE,
+ 0.0f,
+ -BLOCKGRID_CELL_SIZE });
blockWall01.solid = importSolid("assets/wall01.3ds");
for (size_t z = 0; z < levelGrid.depth; ++z) {
for (size_t x = 0; x < levelGrid.width; ++x) {
Scene* blockScene = newScene();
- translate(&blockScene->transform, (Vector3D) { .x = x * BLOCKGRID_CELL_SIZE,
- .y = 0.0f,
- .z = z * BLOCKGRID_CELL_SIZE });
+ translate(&blockScene->transform, (Vector) {
+ (x * BLOCKGRID_CELL_SIZE) + (BLOCKGRID_CELL_SIZE * 0.5f),
+ 0.0f,
+ (z * BLOCKGRID_CELL_SIZE) + (BLOCKGRID_CELL_SIZE * 0.5f) });
blockScene->solid = getBlockFromGrid(levelGrid, x, z)->solid;
insertChildScene(levelScene, blockScene);
}
spawnPlayer(playerSpawnTransform);
}
-void buildLevelFromImage(TgaImage* image) {
+static void buildLevelFromImage(const TgaImage* image) {
if (image == NULL) {
logError("Null image received, cannot build level");
return;
}
-
+
if (image->header.imageBpp != 32) {
logError("Invalid level image format (%d bpp)", image->header.imageBpp);
return;
}
-
+
BlockGrid newGrid = { .width = image->header.imageWidth,
.depth = image->header.imageHeight,
.blocks = malloc(image->header.imageWidth
* image->header.imageHeight
* sizeof(Block*)) };
-
+
for (size_t row = 0; row < newGrid.depth; ++row) {
for (size_t x = 0; x < newGrid.width; ++x) {
// Flip the image vertically due to (0, 0) being bottom left
size_t z = newGrid.depth - row - 1;
-
+
uint32_t pixelColorARGB = ((uint32_t*) image->bytes)[(row * newGrid.width) + x];
Block* block;
switch (pixelColorARGB) {
case 0xFF00FFFF:
block = &blockEmpty;
playerSpawnTransform = identity();
- translate(&playerSpawnTransform, (Vector3D) { .x = x * BLOCKGRID_CELL_SIZE,
- .y = 0.0f,
- .z = z * BLOCKGRID_CELL_SIZE });
+ translate(&playerSpawnTransform, (Vector) {
+ (x * BLOCKGRID_CELL_SIZE) + (BLOCKGRID_CELL_SIZE * 0.5f),
+ 0.0f,
+ (z * BLOCKGRID_CELL_SIZE) + (BLOCKGRID_CELL_SIZE * 0.5f) });
break;
default:
block = &blockEmpty;
setBlockInGrid(newGrid, x, z, block);
}
}
-
+
levelGrid = newGrid;
}
+
+static inline size_t nonNegative(long n) {
+ return n < 0 ? 0u : n;
+}
+
+GridLocation gridLocationFromPosition(Vector pos) {
+ Vector scaledPos = scaleVector(pos, 1.0f / BLOCKGRID_CELL_SIZE);
+ return (GridLocation) { .x = nonNegative(scaledPos.x),
+ .z = nonNegative(scaledPos.z) };
+}
+
+Obstacle getObstacles(GridLocation loc) {
+ Obstacle result = OBSTACLE_NONE;
+ if (loc.x == 0) {
+ result |= OBSTACLE_XN | OBSTACLE_XN_ZP | OBSTACLE_XN_ZN;
+ }
+ if (loc.x >= levelGrid.width - 1) {
+ result |= OBSTACLE_XP | OBSTACLE_XP_ZP | OBSTACLE_XP_ZN;
+ }
+ if (loc.z == 0) {
+ result |= OBSTACLE_ZN | OBSTACLE_XP_ZN | OBSTACLE_XN_ZN;
+ }
+ if (loc.z >= levelGrid.depth - 1) {
+ result |= OBSTACLE_ZP | OBSTACLE_XP_ZP | OBSTACLE_XN_ZP;
+ }
+ if (!(result & OBSTACLE_XP)
+ && getBlockFromGrid(levelGrid, loc.x + 1, loc.z)->type & BLOCKTYPE_OBSTACLE_X) {
+ result |= OBSTACLE_XP;
+ }
+ if (!(result & OBSTACLE_XN)
+ && getBlockFromGrid(levelGrid, loc.x - 1, loc.z)->type & BLOCKTYPE_OBSTACLE_X) {
+ result |= OBSTACLE_XN;
+ }
+ if (!(result & OBSTACLE_ZP)
+ && getBlockFromGrid(levelGrid, loc.x, loc.z + 1)->type & BLOCKTYPE_OBSTACLE_Z) {
+ result |= OBSTACLE_ZP;
+ }
+ if (!(result & OBSTACLE_ZN)
+ && getBlockFromGrid(levelGrid, loc.x, loc.z - 1)->type & BLOCKTYPE_OBSTACLE_Z) {
+ result |= OBSTACLE_ZN;
+ }
+ if (!(result & OBSTACLE_XP_ZP)
+ && getBlockFromGrid(levelGrid, loc.x + 1, loc.z + 1)->type
+ & (BLOCKTYPE_OBSTACLE_X | BLOCKTYPE_OBSTACLE_Z)) {
+ result |= OBSTACLE_XP_ZP;
+ }
+ if (!(result & OBSTACLE_XP_ZN)
+ && getBlockFromGrid(levelGrid, loc.x + 1, loc.z - 1)->type
+ & (BLOCKTYPE_OBSTACLE_X | BLOCKTYPE_OBSTACLE_Z)) {
+ result |= OBSTACLE_XP_ZN;
+ }
+ if (!(result & OBSTACLE_XN_ZP)
+ && getBlockFromGrid(levelGrid, loc.x - 1, loc.z + 1)->type
+ & (BLOCKTYPE_OBSTACLE_X | BLOCKTYPE_OBSTACLE_Z)) {
+ result |= OBSTACLE_XN_ZP;
+ }
+ if (!(result & OBSTACLE_XN_ZN)
+ && getBlockFromGrid(levelGrid, loc.x - 1, loc.z - 1)->type
+ & (BLOCKTYPE_OBSTACLE_X | BLOCKTYPE_OBSTACLE_Z)) {
+ result |= OBSTACLE_XN_ZN;
+ }
+ return result;
+}
#include <stdint.h>
#include "engine/asset.h"
-#include "engine/tga.h"
-typedef enum {
- BLOCKTYPE_SPACE,
- BLOCKTYPE_OBSTACLE_X,
- BLOCKTYPE_OBSTACLE_Z,
- BLOCKTYPE_OBSTACLE
-} BlockType;
+enum BlockType {
+ BLOCKTYPE_SPACE = 0,
+ BLOCKTYPE_OBSTACLE_X = 1 << 0,
+ BLOCKTYPE_OBSTACLE_Z = 1 << 1
+};
-typedef struct {
- const BlockType type;
+enum Obstacle {
+ OBSTACLE_NONE = 0,
+ OBSTACLE_XP = 1 << 0,
+ OBSTACLE_XN = 1 << 1,
+ OBSTACLE_ZP = 1 << 2,
+ OBSTACLE_ZN = 1 << 3,
+ OBSTACLE_XP_ZP = 1 << 4,
+ OBSTACLE_XP_ZN = 1 << 5,
+ OBSTACLE_XN_ZP = 1 << 6,
+ OBSTACLE_XN_ZN = 1 << 7
+};
+
+typedef enum BlockType BlockType;
+typedef enum Obstacle Obstacle;
+typedef struct Block Block;
+typedef struct BlockGrid BlockGrid;
+typedef struct GridLocation GridLocation;
+
+struct Block {
+ BlockType type;
const Solid* solid;
-} Block;
+};
-typedef struct {
+struct BlockGrid {
size_t width;
size_t depth;
Block** blocks;
-} BlockGrid;
+};
+
+struct GridLocation {
+ size_t x;
+ size_t z;
+};
-#define BLOCKGRID_CELL_SIZE 2.5f
+static const float BLOCKGRID_CELL_SIZE = 2.5f;
extern BlockGrid levelGrid;
void initLevel();
void startLevel();
-void buildLevelFromImage(TgaImage* image);
+GridLocation gridLocationFromPosition(Vector pos);
+Obstacle getObstacles(GridLocation loc);
static inline Block* getBlockFromGrid(BlockGrid grid, size_t x, size_t z) {
return grid.blocks[(z * grid.width) + x];
grid.blocks[(z * grid.width) + x] = block;
}
+static inline float cellBoundaryCoord(size_t cellIndex) {
+ return cellIndex * BLOCKGRID_CELL_SIZE;
+}
+
#endif // LEVEL_H_
#include "engine/asset.h"
#include "engine/render.h"
+#include "level.h"
+
+static const float movementSpeed = 0.5f;
+static const float collisionRadius = 0.5f;
+
Scene* playerCharacter;
+Scene* playerProjectedMovement;
+static Transform screenToWorldMovementTransform;
+static Vector worldMovementUp;
+static Vector worldMovementDown;
+static Vector worldMovementLeft;
+static Vector worldMovementRight;
+static Direction movementDirection;
+
+static void movePlayer(Vector direction, float delta);
+static Vector worldMovementDirection(float x, float y);
void initPlayer() {
+ screenToWorldMovementTransform = identity();
+ rotate(&screenToWorldMovementTransform, (Vector) { 0.0f, 1.0f, 0.0f }, - TAU / 8.0f);
+
+ worldMovementUp = worldMovementDirection(0.0f, 1.0f);
+ worldMovementDown = worldMovementDirection(0.0f, -1.0f);
+ worldMovementLeft = worldMovementDirection(-1.0f, 0.0f);
+ worldMovementRight = worldMovementDirection(1.0f, 0.0f);
+
playerCharacter = newScene();
cameraAnchor = playerCharacter;
playerCharacter->solid = importSolid("assets/playercharacter.3ds");
+
+ playerProjectedMovement = newScene();
}
void spawnPlayer(Transform transform) {
playerCharacter->transform = transform;
reparentScene(playerCharacter, currentScene);
+ reparentScene(playerProjectedMovement, currentScene);
+}
+
+void updatePlayer(float delta) {
+ Vector direction = { 0.0f, 0.0f, 0.0f };
+ if (movementDirection & DIRECTION_UP) {
+ direction = addVectors(direction, worldMovementUp);
+ }
+ if (movementDirection & DIRECTION_DOWN) {
+ direction = addVectors(direction, worldMovementDown);
+ }
+ if (movementDirection & DIRECTION_LEFT) {
+ direction = addVectors(direction, worldMovementLeft);
+ }
+ if (movementDirection & DIRECTION_RIGHT) {
+ direction = addVectors(direction, worldMovementRight);
+ }
+ movePlayer(direction, delta);
+}
+
+void startMovement(Direction direction) {
+ movementDirection |= direction;
+}
+
+void stopMovement(Direction direction) {
+ movementDirection &= ~direction;
+}
+
+static void movePlayer(Vector direction, float delta) {
+ direction = clampMagnitude(direction, 1.0f);
+ Vector displacement = scaleVector(direction, delta * movementSpeed);
+
+{
+Vector displacement = scaleVector(direction, 0.006944f * movementSpeed * 1000.0f);
+
+ playerProjectedMovement->transform = playerCharacter->transform;
+
+ Vector initialPosition = translationOf(playerCharacter->transform);
+ Vector position = initialPosition;
+
+ GridLocation location = gridLocationFromPosition(position);
+ Obstacle obstacle = getObstacles(location);
+
+ // Eliminate redundant corner checks
+ if (obstacle & OBSTACLE_XP) {
+ obstacle &= ~(OBSTACLE_XP_ZP | OBSTACLE_XP_ZN);
+ }
+ if (obstacle & OBSTACLE_XN) {
+ obstacle &= ~(OBSTACLE_XN_ZP | OBSTACLE_XN_ZN);
+ }
+ if (obstacle & OBSTACLE_ZP) {
+ obstacle &= ~(OBSTACLE_XP_ZP | OBSTACLE_XN_ZP);
+ }
+ if (obstacle & OBSTACLE_ZN) {
+ obstacle &= ~(OBSTACLE_XP_ZN | OBSTACLE_XN_ZN);
+ }
+
+ float edgeXp = cellBoundaryCoord(location.x + 1);
+ float edgeXn = cellBoundaryCoord(location.x);
+ float edgeZp = cellBoundaryCoord(location.z + 1);
+ float edgeZn = cellBoundaryCoord(location.z);
+ float distanceXp = edgeXp - position.x;
+ if (obstacle & OBSTACLE_XP) distanceXp -= collisionRadius;
+ float distanceXn = edgeXn - position.x;
+ if (obstacle & OBSTACLE_XN) distanceXn += collisionRadius;
+ float distanceZp = edgeZp - position.z;
+ if (obstacle & OBSTACLE_ZP) distanceZp -= collisionRadius;
+ float distanceZn = edgeZn - position.z;
+ if (obstacle & OBSTACLE_ZN) distanceZn += collisionRadius;
+
+ // Check all edges for intersecting already
+ if (distanceXp < 0.0f) {
+ position.x += distanceXp;
+ displacement = growVectorNoFlip(displacement, distanceXp);
+ distanceXp = 0.0f;
+ }
+ if (distanceXn > 0.0f) {
+ position.x += distanceXn;
+ displacement = growVectorNoFlip(displacement, - distanceXn);
+ distanceXn = 0.0f;
+ }
+ if (distanceZp < 0.0f) {
+ position.z += distanceZp;
+ displacement = growVectorNoFlip(displacement, distanceZp);
+ distanceZp = 0.0f;
+ }
+ if (distanceZn > 0.0f) {
+ position.z += distanceZn;
+ displacement = growVectorNoFlip(displacement, - distanceZn);
+ distanceZn = 0.0f;
+ }
+
+ // Calculate direct movement limits
+ float dx = displacement.x;
+ float dz = displacement.z;
+ if (dx > distanceXp) {
+ dz *= distanceXp / dx;
+ dx = distanceXp;
+ // Might need a flag that we've reached a limit in X+?
+ }
+ if (dx < distanceXn) {
+ dz *= distanceXn / dx;
+ dx = distanceXn;
+ // ?
+ }
+ if (dz > distanceZp) {
+ dx *= distanceZp / dz;
+ dz = distanceZp;
+ // ?
+ }
+ if (dz < distanceZn) {
+ dx *= distanceZn / dz;
+ dz = distanceZn;
+ // ?
+ }
+ // This is how far we can move until we collide or leave the grid cell
+ position = addVectors(position, (Vector) { dx, 0.0f, dz });
+ translate(&playerProjectedMovement->transform, subtractVectors(position, initialPosition));
+}
+
+
+ translate(&playerCharacter->transform, displacement);
+}
+
+static Vector worldMovementDirection(float x, float y) {
+ Vector direction = (Vector) { x, 0.0f, -y };
+ direction = normalized(
+ applyTransform(screenToWorldMovementTransform, direction));
+ return direction;
}
#include "engine/scene.h"
+enum Direction {
+ DIRECTION_UP = 1 << 0,
+ DIRECTION_DOWN = 1 << 1,
+ DIRECTION_LEFT = 1 << 2,
+ DIRECTION_RIGHT = 1 << 3,
+};
+
+typedef enum Direction Direction;
+
extern Scene* playerCharacter;
+extern Scene* playerProjectedMovement;
void initPlayer();
void spawnPlayer(Transform transform);
+void updatePlayer(float delta);
+void startMovement(Direction direction);
+void stopMovement(Direction direction);
#endif // PLAYER_H_
-//#include <GL/glxew.h>
-#include <GL/freeglut_std.h>
+#include "engine/engine.h"
-#include "engine/logger.h"
-#include "engine/performance.h"
-#include "engine/render.h"
-#include "engine/ui.h"
+#include "game/game.h"
-#include "game/level.h"
-#include "game/player.h"
+int main(/*int argc, char** argv*/) {
+ EngineConfig cfg = { .windowWidth = 1280,
+ .windowHeight = 720,
+ .windowTitle = newString("shadowclad"),
+ .swapInterval = 1 };
-int main(int argc, char** argv) {
- glutInit(&argc, argv);
- // glutInitContextVersion(4,5); TODO establish correct context
-
- glutInitWindowSize(1280, 720);
-
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
- glutCreateWindow("shadowclad");
-
- logInfo("OpenGL %s", (const char*) glGetString(GL_VERSION));
- //logInfo("GLSL %s", (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
- logInfo("%s", (const char*) glGetString(GL_RENDERER));
- /*
- GLenum glewInitStatus = glewInit();
- if (glewInitStatus != GLEW_OK) {
- logError("GLEW init failed: %s", (const char*) glewGetErrorString(glewInitStatus));
- return 1;
- }
- logInfo("GLEW %s", (const char*) glewGetString(GLEW_VERSION));
-
- if (GLXEW_EXT_swap_control) {
- Display* display = glXGetCurrentDisplay();
- GLXDrawable drawable = glXGetCurrentDrawable();
- if (drawable) {
- glXSwapIntervalEXT(display, drawable, 1);
- }
- else {
- logWarning("Drawable is not here ¯\\_(ツ)_/¯");
- logWarning("Could not enable vsync (GLX_EXT_swap_control)");
- }
- }
- else if (GLXEW_MESA_swap_control) {
- glXSwapIntervalMESA(1);
- logDebug("Vsync enabled with GLX_MESA_swap_control, swap interval %d", glXGetSwapIntervalMESA());
- }
- else {
- logWarning("Could not enable vsync (extensions not supported)");
- }
- */
- glutDisplayFunc(renderFrame);
- glutReshapeFunc(resizeStage);
- //glutKeyboardFunc(key_pressed);
- //glutMouseFunc(mouse_button_event);
- //glutMotionFunc(mouse_motion_event);
-
- initRender();
- //initPerformanceMetering();
- initLevel();
- initPlayer();
- startLevel();
-
- glutMainLoop();
+ // Engine startup
+ init(cfg);
+
+ initGame();
+
+ // Main update loop
+ run(update);
+
+ // Shutdown
+ terminate();
return 0;
}