diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bf88c31 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +.PHONY: all clean + +GLFW_DIR ?= /storage/other/glfw/install +GLFW_CFLAGS := -L$(GLFW_DIR)/lib -lglfw3 -ldl -lm -lpthread +INCLUDE_DIRS := -I $(GLFW_DIR)/include -I glad/include -I cglm/include +CFLAGS := $(INCLUDE_DIRS) -g -Wall -Werror -O3 + +all: l_system_3d + +utilities.o: utilities.c utilities.h + gcc $(CFLAGS) -c -o utilities.o utilities.c -I glad/include + +l_system_mesh.o: l_system_mesh.c l_system_mesh.h utilities.h + gcc $(CFLAGS) -c -o l_system_mesh.o l_system_mesh.c -I glad/include \ + -I cglm/include + +turtle_3d.o: turtle_3d.c turtle_3d.h + gcc $(CFLAGS) -c -o turtle_3d.o turtle_3d.c -I cglm/include + +l_system_3d: l_system_3d.c l_system_mesh.o turtle_3d.o utilities.o + gcc $(CFLAGS) -o l_system_3d l_system_3d.c \ + glad/src/glad.c \ + utilities.o \ + l_system_mesh.o \ + turtle_3d.o \ + -I glad/include \ + -I cglm/include \ + $(GLFW_CFLAGS) + +clean: + rm -f *.o + rm -f l_system_3d + diff --git a/build_windows.bat b/build_windows.bat index b71e238..5526cc8 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -1,6 +1,7 @@ gcc -Wall -Werror -O3 -o l_system_3d ^ l_system_3d.c ^ l_system_mesh.c ^ + turtle_3d.c ^ utilities.c ^ glad\src\glad.c ^ -I cglm\include ^ diff --git a/l_system_3d.c b/l_system_3d.c index fe624e2..c64b04a 100644 --- a/l_system_3d.c +++ b/l_system_3d.c @@ -6,11 +6,13 @@ #include #include #include "l_system_mesh.h" +#include "turtle_3d.h" #include "utilities.h" #include "l_system_3d.h" #define DEFAULT_WINDOW_WIDTH (800) #define DEFAULT_WINDOW_HEIGHT (600) +#define PI (3.1415926536) ApplicationState* AllocateApplicationState(void) { ApplicationState *to_return = NULL; @@ -26,6 +28,7 @@ ApplicationState* AllocateApplicationState(void) { void FreeApplicationState(ApplicationState *s) { if (!s) return; if (s->mesh) DestroyLSystemMesh(s->mesh); + if (s->turtle) DestroyTurtle3D(s->turtle); glDeleteBuffers(1, &(s->ubo)); if (s->window) glfwDestroyWindow(s->window); memset(s, 0, sizeof(*s)); @@ -81,21 +84,35 @@ static int SetupUniformBuffer(ApplicationState *s) { return CheckGLErrors(); } +static int Rotate90(Turtle3D *t) { + return RotateTurtle(t, PI * 0.5); +} + // This generates the vertices for the L-system, and updates the mesh. Returns // 0 on error. static int GenerateVertices(ApplicationState *s) { - // TODO: Actually store the vertices in s. - MeshVertex tmp_verts[] = { - {{0, 0, 0}, 0, {1, 1, 1}, 0, {0, 0, 0}, 0, {1, 0, 0, 1}}, - {{1, 1, 1}, 0, {0, 0, 0}, 0, {0, 0, 0}, 0, {1, 0, 0, 1}}, - // Green line from 0, 0, 0 -> 0, 1, 1 - {{0, 0, 0}, 0, {0, 1, 1}, 0, {0, 0, 0}, 0, {0, 1, 0, 1}}, - {{0, 1, 1}, 0, {0, 0, 0}, 0, {0, 0, 0}, 0, {0, 1, 0, 1}}, - // Blue line from 0, 0, 0, -> 1, 1, 0 - {{0, 0, 0}, 0, {1, 1, 0}, 0, {0, 0, 0}, 0, {0, 0, 1, 1}}, - {{1, 1, 0}, 0, {0, 0, 0}, 0, {0, 0, 0}, 0, {0, 0, 1, 1}}, - }; - if (!SetMeshVertices(s->mesh, tmp_verts, 6)) { + Turtle3D *t = s->turtle; + t->color[0] = 1; + t->color[1] = 0; + t->color[2] = 0; + if (!MoveTurtleForward(t, 1.0)) return 0; + if (!Rotate90(t)) return 0; + t->color[0] = 0; + t->color[1] = 1; + if (!MoveTurtleForward(t, 1.0)) return 0; + if (!Rotate90(t)) return 0; + t->color[1] = 0; + t->color[2] = 1; + if (!MoveTurtleForward(t, 1.0)) return 0; + if (!Rotate90(t)) return 0; + t->color[0] = 1; + t->color[1] = 1; + if (!MoveTurtleForward(t, 1.0)) return 0; + if (!PitchTurtle(t, PI * 0.5)) return 0; + t->color[0] = 0.7; + t->color[1] = 0.1; + if (!MoveTurtleForward(t, 0.5)) return 0; + if (!SetMeshVertices(s->mesh, t->vertices, t->vertex_count)) { printf("Failed setting vertices.\n"); return 0; } @@ -120,6 +137,7 @@ static void UpdateCamera(ApplicationState *s) { up[1] = 1.0; tmp = glfwGetTime() / 4.0; position[0] = sin(tmp) * 4.0; + position[1] = 3.0; position[2] = cos(tmp) * 4.0; glm_lookat(position, target, up, s->shared_uniforms.view); glm_vec4(position, 0, s->shared_uniforms.camera_position); @@ -195,6 +213,12 @@ int main(int argc, char **argv) { goto cleanup; } + s->turtle = CreateTurtle3D(); + if (!s->turtle) { + printf("Failed creating the \"turtle\" for drawing.\n"); + to_return = 1; + goto cleanup; + } if (!GenerateVertices(s)) { printf("Failed generating vertices.\n"); to_return = 1; diff --git a/l_system_3d.h b/l_system_3d.h index a7b9fdd..3e84b77 100644 --- a/l_system_3d.h +++ b/l_system_3d.h @@ -1,6 +1,8 @@ #include #include #include +#include "l_system_mesh.h" +#include "turtle_3d.h" // Uniforms shared with all shaders. Must match the layout in // shared_uniforms.glsl, and every field must be padded to four floats. @@ -17,6 +19,7 @@ typedef struct { int window_height; float aspect_ratio; LSystemMesh *mesh; + Turtle3D *turtle; GLuint ubo; SharedUniforms shared_uniforms; } ApplicationState; diff --git a/l_system_mesh.c b/l_system_mesh.c index f91d0e1..268c6ac 100644 --- a/l_system_mesh.c +++ b/l_system_mesh.c @@ -7,6 +7,23 @@ #include "utilities.h" #include "l_system_mesh.h" +static void DebugPrintVec3(float *v) { + printf("(%.03f %.03f %.03f)", v[0], v[1], v[2]); +} + +void DebugPrintVertex(MeshVertex *v) { + printf("Mesh vertex: "); + printf("position "); + DebugPrintVec3(v->location); + printf(", forward "); + DebugPrintVec3(v->forward); + printf(", up "); + DebugPrintVec3(v->up); + printf(", color "); + DebugPrintVec3(v->color); + printf(".\n"); +} + // Loads and compiles a shader from the given file path. Returns 0 on error. static GLuint LoadShader(const char *path, GLenum shader_type) { GLuint to_return = 0; @@ -152,10 +169,10 @@ LSystemMesh* CreateLSystemMesh(void) { (void *) offsetof(MeshVertex, location)); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), - (void *) offsetof(MeshVertex, direction)); + (void *) offsetof(MeshVertex, forward)); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), - (void *) offsetof(MeshVertex, orientation)); + (void *) offsetof(MeshVertex, up)); glEnableVertexAttribArray(2); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), (void *) offsetof(MeshVertex, color)); diff --git a/l_system_mesh.h b/l_system_mesh.h index 41c4f64..76b0c98 100644 --- a/l_system_mesh.h +++ b/l_system_mesh.h @@ -10,18 +10,18 @@ // Defines a single vertex within the mesh. typedef struct { - float location[3]; + vec3 location; float pad1; // Points in the direction of the next vertex. May be 0 if this is at the // end of a sequence of lines. - float direction[3]; + vec3 forward; float pad2; - // Points "downward"; must be orthogonal to the direction. Basically just - // gives a consistent way to orient geometry. - float orientation[3]; + // Points "upward"; must be orthogonal to the direction and normalized. + // Basically just gives a consistent way to orient geometry. + vec3 up; float pad3; // The color of the vertex. - float color[4]; + vec4 color; } MeshVertex; // Holds information about a full mesh to render. @@ -41,6 +41,9 @@ typedef struct { GLint normal_uniform_index; } LSystemMesh; +// Prints the vertex's info to stdout. +void DebugPrintVertex(MeshVertex *v); + // Allocates and initializes a new L-system mesh. Returns NULL on error. The // mesh is initially empty. Destroy the returned mesh using DestroyLSystemMesh // when it's no longer needed. diff --git a/mesh_vertex_shader.vert b/mesh_vertex_shader.vert index a336e58..30fb3d7 100644 --- a/mesh_vertex_shader.vert +++ b/mesh_vertex_shader.vert @@ -1,7 +1,7 @@ #version 330 core layout (location = 0) in vec3 position_in; -layout (location = 1) in vec3 direction_in; -layout (location = 2) in vec3 orientation_in; +layout (location = 1) in vec3 forward_in; +layout (location = 2) in vec3 up_in; layout (location = 3) in vec4 color_in; uniform mat4 model; diff --git a/turtle_3d.c b/turtle_3d.c new file mode 100644 index 0000000..8b9f2f8 --- /dev/null +++ b/turtle_3d.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include "l_system_mesh.h" +#include "turtle_3d.h" + +// The number of vertices the turtle initially allocates space for. +#define INITIAL_TURTLE_CAPACITY (1024) + +// The minimum spacing (squared) between two subsequent positions for them to +// be considered identical. +#define MIN_POSITION_DIST (1.0e-6) + +Turtle3D* CreateTurtle3D(void) { + Turtle3D *to_return = NULL; + to_return = (Turtle3D *) calloc(1, sizeof(*to_return)); + if (!to_return) { + printf("Failed allocating Turtle3D struct.\n"); + return NULL; + } + // Start out as opaque white + glm_vec4_one(to_return->color); + to_return->forward[0] = 1.0; + to_return->up[1] = 1.0; + to_return->vertices = (MeshVertex *) calloc(INITIAL_TURTLE_CAPACITY, + sizeof(MeshVertex)); + if (!to_return->vertices) { + printf("Failed allocating the turtle's vertex array.\n"); + free(to_return); + return NULL; + } + to_return->vertex_capacity = INITIAL_TURTLE_CAPACITY; + return to_return; +} + +void DestroyTurtle3D(Turtle3D *t) { + if (!t) return; + free(t->vertices); + memset(t, 0, sizeof(*t)); + free(t); +} + +// Checks if the internal array has space for two more vertices (another line +// segment). If not, this attempts to reallocate the turtle's internal array of +// vertices, doubling its capacity. Returns 0 on error. +static int IncreateCapacityIfNeeded(Turtle3D *t) { + void *new_buffer = NULL; + uint32_t new_capacity; + uint32_t required_capacity = t->vertex_count + 2; + if (required_capacity < t->vertex_count) { + printf("Vertex capacity overflow: too many vertices.\n"); + return 0; + } + if (required_capacity <= t->vertex_capacity) return 1; + new_capacity = t->vertex_capacity * 2; + if (new_capacity < t->vertex_capacity) { + printf("Vertex capacity overflow: too many vertices.\n"); + return 0; + } + new_buffer = realloc(t->vertices, new_capacity * sizeof(MeshVertex)); + if (!new_buffer) { + printf("Unable to increate number of vertices: out of memory.\n"); + return 0; + } + t->vertices = (MeshVertex *) new_buffer; + t->vertex_capacity = new_capacity; + return 1; +} + +// Appends a line segment from the turtle's previous position to its current +// position to the turtle's path. +static int AppendSegment(Turtle3D *t) { + MeshVertex *v = NULL; + vec3 direction; + if (!IncreateCapacityIfNeeded(t)) return 0; + // If the two positions are too close together, then we'll just set the + // direction vector to the turtle's current direction. Otherwise, the + // direction in the line segment points from the previous point to the + // current position. + glm_vec3_sub(t->position, t->prev_position, direction); + if (glm_vec3_norm2(direction) < MIN_POSITION_DIST) { + glm_vec3_copy(t->forward, direction); + } else { + glm_vec3_normalize(direction); + } + v = t->vertices + t->vertex_count; + glm_vec3_copy(t->prev_position, v->location); + glm_vec3_copy(direction, v->forward); + glm_vec3_copy(t->up, v->up); + glm_vec4_copy(t->color, v->color); + // The only difference between the two vertices in the line segment is the + // position. + *(v + 1) = *v; + glm_vec3_copy(t->position, (v + 1)->location); + t->vertex_count += 2; + return 1; +} + +// Updates min_bounds and max_bounds to contain the turtle's current position. +static void UpdateBounds(Turtle3D *t) { + float *p = t->position; + glm_vec3_copy(t->position, p); + if (p[0] > t->max_bounds[0]) { + t->max_bounds[0] = p[0]; + } + if (p[0] < t->min_bounds[0]) { + t->min_bounds[0] = p[0]; + } + if (p[1] > t->max_bounds[1]) { + t->max_bounds[1] = p[1]; + } + if (p[1] < t->min_bounds[1]) { + t->min_bounds[1] = p[1]; + } + if (p[2] > t->max_bounds[2]) { + t->max_bounds[2] = p[2]; + } + if (p[2] < t->min_bounds[2]) { + t->min_bounds[2] = p[2]; + } +} + +int MoveTurtleForward(Turtle3D *t, float distance) { + vec3 change; + glm_vec3_copy(t->position, t->prev_position); + glm_vec3_scale(t->forward, distance, change); + glm_vec3_add(t->position, change, t->position); + UpdateBounds(t); + return AppendSegment(t); +} + +int RotateTurtle(Turtle3D *t, float angle) { + glm_vec3_rotate(t->forward, angle, t->up); + return 1; +} + +int PitchTurtle(Turtle3D *t, float angle) { + vec3 right; + glm_vec3_cross(t->forward, t->up, right); + glm_vec3_rotate(t->up, angle, right); + glm_vec3_rotate(t->forward, angle, right); + return 1; +} diff --git a/turtle_3d.h b/turtle_3d.h new file mode 100644 index 0000000..7c8343a --- /dev/null +++ b/turtle_3d.h @@ -0,0 +1,58 @@ +#ifndef TURTLE_3D_H +#define TURTLE_3D_H +#include +#include "l_system_mesh.h" + +// Holds the state of a 3D "turtle" that follows instructions relative to +// itself in 3D space. +typedef struct { + // The turtle's current location. + vec3 position; + // The turtle's previous location. Can be arbitrary if there is no previous + // position. + vec3 prev_position; + // The current with which the turtle draws lines. + vec4 color; + // This determines the turtle's current orientation. Must always be + // normalized and orthogonal. + vec3 forward; + vec3 up; + + // Defines a cube bounding the turtle's entire path so far. This *must* be + // well-formed, i.e. each min component must be less than each max component. + // The one exception to this is if no vertices have been output yet. Not + // intended to be modified directly, instead it is modified by the + // drawing functions. + vec3 min_bounds; + vec3 max_bounds; + + // The list of vertices generated by the turtle. Not intended to be modified + // directly. (Instead use AppendSegment within drawing functions.) + MeshVertex *vertices; + uint32_t vertex_count; + uint32_t vertex_capacity; +} Turtle3D; + +// Allocates a new turtle, at position 0, 0, 0, with no vertices. Returns NULL +// on error. The turtle starts out facing right, with up in the positive Y +// direction. +Turtle3D* CreateTurtle3D(void); + +// Destroys the given turtle, freeing any resources and vertices. The given +// pointer is no longer valid after this returns. +void DestroyTurtle3D(Turtle3D *t); + +// Moves the turtle forward by the given distance. Returns 0 on error. +int MoveTurtleForward(Turtle3D *t, float distance); + +// Rotates the turtle about the upward axis by the given angle. Returns 0 on +// error. Does not change the turtle's position; only its orientation. +int RotateTurtle(Turtle3D *t, float angle); + +// Rotates the turtle about its right-facing axis by the given angle. Returns +// 0 on error. Does not change the turtle's position; only its orientation. +int PitchTurtle(Turtle3D *t, float angle); + +// TODO: Implement functions for rotating the turtle. Use rotation matrices? + +#endif // TURTLE_3D_H diff --git a/utilities.c b/utilities.c index 22fa4f0..a0aeccd 100644 --- a/utilities.c +++ b/utilities.c @@ -143,7 +143,7 @@ char* StringReplace(const char *input, const char *match, const char *r) { input_pos = strstr(input_pos, match); } - // Finally, copy any remaining input past the final output. + // Finally, copy any remaining input past the final match. unchanged_length = original_length - (((uintptr_t) prev_input_pos) - ((uintptr_t) input)); memcpy(output_pos, prev_input_pos, unchanged_length);