diff --git a/README.md b/README.md index e3da030..4ed73f2 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,15 @@ The following list of actions are supported: - `set_color_a `: The same as `set_color_r`, but for the alpha channel. + - `push_position 0.0`: Pushes the turtle's current position onto a stack of + positions, so that it can be restored later. The "0.0" is ignored, but + required to simplify internal config-parsing logic. + + - `pop_position 0.0`: Restores the turtle's current position from whichever + position is on top of the stack. Removes the position on top of the stack. + It is an error to encounter this instruction if the stack is already empty. + Once again, the "0.0" argument is ignored, but required in the config. + Prior to taking any actions, the turtle is initialized at position (0, 0, 0), facing in the positive-X direction, with its pitch and roll set so that its "back" is facing upwards; in the positive-Y direction. diff --git a/l_system_3d.c b/l_system_3d.c index 5115fb4..083737b 100644 --- a/l_system_3d.c +++ b/l_system_3d.c @@ -186,14 +186,13 @@ static int DecreaseIterations(ApplicationState *s) { return 1; } target_iterations = s->l_system_iterations - 1; - // TODO: DecreaseIterations somestimes seems to segfault. Probably happens - // around here. free(s->l_system_string); s->l_system_string = (uint8_t *) strdup(s->config->init); if (!s->l_system_string) { printf("Failed copying the initial L-system string.\n"); return 0; } + s->l_system_length = strlen(s->config->init); s->l_system_iterations = 0; for (i = 0; i < target_iterations; i++) { if (!IncreaseIterations(s)) return 0; diff --git a/parse_config.c b/parse_config.c index 183227a..8c7d8f6 100644 --- a/parse_config.c +++ b/parse_config.c @@ -291,6 +291,8 @@ static int ParseActionRules(LSystemConfig *config) { "set_color_g", "set_color_b", "set_color_a", + "push_position", + "pop_position", }; TurtleInstruction action_fns[] = { MoveTurtleForward, @@ -303,6 +305,8 @@ static int ParseActionRules(LSystemConfig *config) { SetTurtleGreen, SetTurtleBlue, SetTurtleAlpha, + PushTurtlePosition, + PopTurtlePosition, }; char *current_line = NULL; uint8_t current_char = 0; diff --git a/shared_uniforms.glsl b/shared_uniforms.glsl index 3b7474e..a7ccdfa 100644 --- a/shared_uniforms.glsl +++ b/shared_uniforms.glsl @@ -1,4 +1,3 @@ -// There are no shared uniforms so far. This file will contain them later. layout(std140) uniform SharedUniforms { mat4 projection; mat4 view; diff --git a/turtle_3d.c b/turtle_3d.c index 09c485a..bca14cd 100644 --- a/turtle_3d.c +++ b/turtle_3d.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,12 +9,32 @@ // The number of vertices the turtle initially allocates space for. #define INITIAL_TURTLE_CAPACITY (1024) +// The initial capacity of the turtle's position stack. +#define INITIAL_STACK_CAPACITY (32) + // The minimum spacing (squared) between two subsequent positions for them to // be considered identical. #define MIN_POSITION_DIST (1.0e-6) #define PI (3.1415926536) +// Initializes the given stack of turtle positions. Returns 0 on error. +static int InitializePositionStack(PositionStack *s) { + s->size = 0; + s->buffer = (TurtlePosition *) calloc(INITIAL_STACK_CAPACITY, + sizeof(TurtlePosition)); + if (!s->buffer) return 0; + s->capacity = INITIAL_STACK_CAPACITY; + return 1; +} + +// Cleans up the stack of positions. Doesn't free s itself; just the buffer it +// wraps. +static void FreePositionStack(PositionStack *s) { + free(s->buffer); + memset(s, 0, sizeof(*s)); +} + Turtle3D* CreateTurtle3D(void) { Turtle3D *to_return = NULL; to_return = (Turtle3D *) calloc(1, sizeof(*to_return)); @@ -28,6 +49,12 @@ Turtle3D* CreateTurtle3D(void) { free(to_return); return NULL; } + if (!InitializePositionStack(&(to_return->position_stack))) { + printf("Failed initializing stack of turtle positions.\n"); + free(to_return->vertices); + free(to_return); + return NULL; + } to_return->vertex_capacity = INITIAL_TURTLE_CAPACITY; ResetTurtle3D(to_return); return to_return; @@ -37,13 +64,13 @@ void ResetTurtle3D(Turtle3D *t) { // Opaque white color glm_vec4_one(t->color); // Start at the origin - glm_vec3_zero(t->position); - glm_vec3_zero(t->prev_position); + glm_vec3_zero(t->p.position); + glm_vec3_zero(t->p.prev_position); // Face in positive X direction, with up towards positive Y. - glm_vec3_zero(t->forward); - t->forward[0] = 1; - glm_vec3_zero(t->up); - t->up[1] = 1; + glm_vec3_zero(t->p.forward); + t->p.forward[0] = 1; + glm_vec3_zero(t->p.up); + t->p.up[1] = 1; // The bounding cube is empty and ill-defined to begin with. glm_vec3_zero(t->min_bounds); glm_vec3_zero(t->max_bounds); @@ -51,11 +78,14 @@ void ResetTurtle3D(Turtle3D *t) { // turtle to draw another iteration without needing to clear all the // vertices. t->vertex_count = 0; + // Clear the stack. As with the vertex array, don't free it, though. + t->position_stack.size = 0; } void DestroyTurtle3D(Turtle3D *t) { if (!t) return; free(t->vertices); + FreePositionStack(&(t->position_stack)); memset(t, 0, sizeof(*t)); free(t); } @@ -105,7 +135,7 @@ int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset) { // 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) { +static int IncreaseCapacityIfNeeded(Turtle3D *t) { void *new_buffer = NULL; uint32_t new_capacity; uint32_t required_capacity = t->vertex_count + 2; @@ -121,7 +151,7 @@ static int IncreateCapacityIfNeeded(Turtle3D *t) { } new_buffer = realloc(t->vertices, new_capacity * sizeof(MeshVertex)); if (!new_buffer) { - printf("Unable to increate number of vertices: out of memory.\n"); + printf("Unable to increase number of vertices: out of memory.\n"); return 0; } t->vertices = (MeshVertex *) new_buffer; @@ -134,34 +164,34 @@ static int IncreateCapacityIfNeeded(Turtle3D *t) { static int AppendSegment(Turtle3D *t) { MeshVertex *v = NULL; vec3 direction; - if (!IncreateCapacityIfNeeded(t)) return 0; + if (!IncreaseCapacityIfNeeded(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); + glm_vec3_sub(t->p.position, t->p.prev_position, direction); if (glm_vec3_norm2(direction) < MIN_POSITION_DIST) { - glm_vec3_copy(t->forward, direction); + glm_vec3_copy(t->p.forward, direction); } else { glm_vec3_normalize(direction); } v = t->vertices + t->vertex_count; - glm_vec3_copy(t->prev_position, v->location); + glm_vec3_copy(t->p.prev_position, v->location); glm_vec3_copy(direction, v->forward); - glm_vec3_copy(t->up, v->up); + glm_vec3_copy(t->p.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); + glm_vec3_copy(t->p.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); + float *p = t->p.position; + glm_vec3_copy(t->p.position, p); if (p[0] > t->max_bounds[0]) { t->max_bounds[0] = p[0]; } @@ -184,9 +214,9 @@ static void UpdateBounds(Turtle3D *t) { int MoveTurtleForwardNoDraw(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); + glm_vec3_copy(t->p.position, t->p.prev_position); + glm_vec3_scale(t->p.forward, distance, change); + glm_vec3_add(t->p.position, change, t->p.position); UpdateBounds(t); return 1; } @@ -201,20 +231,20 @@ static float ToRadians(float degrees) { } int RotateTurtle(Turtle3D *t, float angle) { - glm_vec3_rotate(t->forward, ToRadians(angle), t->up); + glm_vec3_rotate(t->p.forward, ToRadians(angle), t->p.up); return 1; } int PitchTurtle(Turtle3D *t, float angle) { vec3 right; - glm_vec3_cross(t->forward, t->up, right); - glm_vec3_rotate(t->up, ToRadians(angle), right); - glm_vec3_rotate(t->forward, ToRadians(angle), right); + glm_vec3_cross(t->p.forward, t->p.up, right); + glm_vec3_rotate(t->p.up, ToRadians(angle), right); + glm_vec3_rotate(t->p.forward, ToRadians(angle), right); return 1; } int RollTurtle(Turtle3D *t, float angle) { - glm_vec3_rotate(t->up, ToRadians(angle), t->forward); + glm_vec3_rotate(t->p.up, ToRadians(angle), t->p.forward); return 1; } @@ -243,3 +273,39 @@ int SetTurtleAlpha(Turtle3D *t, float alpha) { t->color[3] = ClampColor(alpha); return 1; } + +int PushTurtlePosition(Turtle3D *t, float ignored) { + PositionStack *s = &(t->position_stack); + TurtlePosition *new_buf = NULL; + uint32_t new_cap = 0; + // Expand capacity if necessary. + if (s->size >= s->capacity) { + new_cap = s->capacity * 2; + if (new_cap < s->capacity) { + printf("Turtle position stack overflow.\n"); + return 0; + } + new_buf = (TurtlePosition *) realloc(s->buffer, new_cap * + sizeof(TurtlePosition)); + if (!new_buf) { + printf("Failed expanding position stack.\n"); + return 0; + } + s->buffer = new_buf; + s->capacity = new_cap; + } + s->buffer[s->size] = t->p; + s->size++; + return 1; +} + +int PopTurtlePosition(Turtle3D *t, float ignored) { + PositionStack *s = &(t->position_stack); + if (s->size == 0) { + printf("Turtle position stack is empty.\n"); + return 0; + } + t->p = s->buffer[s->size - 1]; + s->size--; + return 1; +} diff --git a/turtle_3d.h b/turtle_3d.h index f870c07..21edddd 100644 --- a/turtle_3d.h +++ b/turtle_3d.h @@ -1,22 +1,38 @@ #ifndef TURTLE_3D_H #define TURTLE_3D_H #include +#include #include "l_system_mesh.h" -// Holds the state of a 3D "turtle" that follows instructions relative to -// itself in 3D space. +// Holds the turtle's position and orientation information. 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; +} TurtlePosition; + +// Holds a stack of turtle positions. +typedef struct { + // The number of positions currently in the stack. + uint32_t size; + // The max size of the buffer before it needs to be expanded. + uint32_t capacity; + // The actual buffer being used as a stack. + TurtlePosition *buffer; +} PositionStack; + +// Holds the state of a 3D "turtle" that follows instructions relative to +// itself in 3D space. +typedef struct { + TurtlePosition p; + // The current with which the turtle draws lines. + vec4 color; // 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. @@ -31,11 +47,9 @@ typedef struct { MeshVertex *vertices; uint32_t vertex_count; uint32_t vertex_capacity; + PositionStack position_stack; } Turtle3D; -// TODO: add push and pop instructions that keep the turtle's position, -// previous position, orientation, and color on a stack. - // 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. @@ -84,5 +98,14 @@ int SetTurtleGreen(Turtle3D *t, float green); int SetTurtleBlue(Turtle3D *t, float blue); int SetTurtleAlpha(Turtle3D *t, float alpha); +// Saves the turtle's position on a stack. Returns 0 on error. The argument is +// ignored. +int PushTurtlePosition(Turtle3D *t, float ignored); + +// Pops the turtle's position off the stack of positions. Returns 0 on error. +// It's an error if this instruction is issued when the stack is empty. The +// argument is ignored. +int PopTurtlePosition(Turtle3D *t, float ignored); + #endif // TURTLE_3D_H