Skip to content

Commit

Permalink
Add initial "turtle graphics"
Browse files Browse the repository at this point in the history
 - Added a "turtle" capaple of drawing lines in 3D space. Currently, it
   draws a simple test square, but the next commit should have it
   following "real" instructions from an L-system.

 - Added a Makefile for building on Linux. Works on my machine.

 - Switched from using a mix of names (down/orientation/etc) for vectors
   indicating segment's direction, etc. Vertices now have a "forward"
   vector and an "up" vector.
  • Loading branch information
Nathan O authored and Nathan O committed Feb 7, 2022
1 parent 2c5d93c commit b7e2489
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 23 deletions.
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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

1 change: 1 addition & 0 deletions build_windows.bat
Original file line number Diff line number Diff line change
@@ -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 ^
Expand Down
48 changes: 36 additions & 12 deletions l_system_3d.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#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;
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions l_system_3d.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <GLFW/glfw3.h>
#include <cglm/cglm.h>
#include <glad/glad.h>
#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.
Expand All @@ -17,6 +19,7 @@ typedef struct {
int window_height;
float aspect_ratio;
LSystemMesh *mesh;
Turtle3D *turtle;
GLuint ubo;
SharedUniforms shared_uniforms;
} ApplicationState;
Expand Down
21 changes: 19 additions & 2 deletions l_system_mesh.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
15 changes: 9 additions & 6 deletions l_system_mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions mesh_vertex_shader.vert
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
144 changes: 144 additions & 0 deletions turtle_3d.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <cglm/cglm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
Loading

0 comments on commit b7e2489

Please sign in to comment.