diff --git a/README.md b/README.md index a3c6023..3801e14 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,15 @@ The following list of actions are supported: - `move_forward_nodraw `: Like `move_forward`, but does not draw a line segment. + - `set_color_r `: Sets the turtle's red color channel to the given + value. The value will be clamped to be between 0.0 and 1.0. + + - `set_color_g `: The same as `set_color_r`, but for the green channel. + + - `set_color_b `: The same as `set_color_r`, but for the blue channel. + + - `set_color_a `: The same as `set_color_r`, but for the alpha channel. + 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/config.txt b/config.txt index 5827338..d356289 100644 --- a/config.txt +++ b/config.txt @@ -1,10 +1,19 @@ # Draw a dragon curve. -init F +init PFRF F F+G G F-G actions +# Make the initial portion purple. +P +set_color_r 0.7 +set_color_g 0.2 +set_color_b 1.0 + +B +set_turtle_B + F move_forward 1.0 @@ -14,6 +23,13 @@ move_forward 1.0 - rotate -90.0 +# Make the rotated portion green. +R +set_color_r 0.2 +set_color_g 0.9 +set_color_b 0.2 +pitch 90.0 + + # Comments can go anywhere, so long as the '#' is at the start of the line. rotate 90.0 diff --git a/l_system_3d.c b/l_system_3d.c index cda6188..7f9f509 100644 --- a/l_system_3d.c +++ b/l_system_3d.c @@ -1,6 +1,7 @@ #include -#include +#include #include +#include #include #include @@ -85,44 +86,148 @@ static int SetupUniformBuffer(ApplicationState *s) { return CheckGLErrors(); } -static int Rotate90(Turtle3D *t) { - return RotateTurtle(t, 90); -} - // This generates the vertices for the L-system, and updates the mesh. Returns // 0 on error. static int GenerateVertices(ApplicationState *s) { 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, 90)) return 0; - t->color[0] = 0.7; - t->color[1] = 0.1; - if (!MoveTurtleForward(t, 0.5)) return 0; + ActionRule *r = NULL; + int result; + uint32_t char_index, inst_index; + uint8_t c; + ResetTurtle3D(t); + for (char_index = 0; char_index < s->l_system_length; char_index++) { + c = s->l_system_string[char_index]; + r = s->config->actions + c; + for (inst_index = 0; inst_index < r->length; inst_index++) { + result = r->instructions[inst_index](t, r->args[inst_index]); + if (!result) { + printf("Failed running instruction %d for char %c.\n", (int) inst_index, + (char) c); + return 0; + } + } + } + if (!SetMeshVertices(s->mesh, t->vertices, t->vertex_count)) { printf("Failed setting vertices.\n"); return 0; } + if (!SetTransformInfo(s->turtle, s->mesh->model, s->mesh->normal, + s->mesh->location_offset)) { + printf("Failed getting transform matrices.\n"); + return 0; + } + return 1; +} + +static float ToMB(uint64_t bytes) { + float tmp = (float) bytes; + return tmp / (1024.0 * 1024.0); +} + +// Iterates the L-system exactly once. Returns 0 on error. Does not update the +// mesh. +static int IncreaseIterations(ApplicationState *s) { + uint32_t new_length = 0; + ReplacementRule *r = NULL; + uint8_t *new_buffer = NULL; + uint8_t *dst = NULL; + uint8_t *loc = s->l_system_string; + uint8_t c; + // First iterate over the string to pre-calcuate the size of the buffer we'll + // need. + while (*loc) { + c = *loc; + loc++; + r = s->config->replacements + c; + if (!r->used) { + new_length++; + continue; + } + new_length += r->length; + } + // +1 to ensure a null terminator. + new_buffer = (uint8_t *) calloc(1, new_length + 1); + if (!new_buffer) { + printf("Failed allocating new %f MB L-system string.\n", ToMB(new_length)); + return 0; + } + // Iterate over the string again, this time populating the new buffer. + dst = new_buffer; + loc = s->l_system_string; + while (*loc) { + c = *loc; + loc++; + r = s->config->replacements + c; + if (!r->used) { + // Keep the same char if no replacement was defined. + *dst = c; + dst++; + continue; + } + memcpy(dst, r->replacement, r->length); + dst += r->length; + continue; + } + free(s->l_system_string); + s->l_system_string = new_buffer; + s->l_system_length = new_length; + s->l_system_iterations++; + return 1; +} + +// Reduces the L-system iterations by one. Unfortunately, this is implemented +// by recomputing the entire thing. Does nothing if we're already at 0 +// iterations. +static int DecreaseIterations(ApplicationState *s) { + uint32_t target_iterations, i; + if (s->l_system_iterations == 0) { + printf("Can't decrease iterations. Already at 0 iterations.\n"); + 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_iterations = 0; + for (i = 0; i < target_iterations; i++) { + if (!IncreaseIterations(s)) return 0; + } return 1; } static int ProcessInputs(ApplicationState *s) { + int pressed; if (glfwGetKey(s->window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(s->window, 1); + return 1; + } + // s->key_pressed_tmp is used to prevent counting one press multiple times, + // and to prevent up and down from being pressed together. + pressed = glfwGetKey(s->window, GLFW_KEY_UP) == GLFW_PRESS; + if (!s->key_pressed_tmp && pressed) { + // Nothing pressed -> up pressed + s->key_pressed_tmp = GLFW_KEY_UP; + if (!(IncreaseIterations(s) && GenerateVertices(s))) return 0; + printf("New L-system length: %.02f MB.\n", ToMB(s->l_system_length)); + } else if ((s->key_pressed_tmp == GLFW_KEY_UP) && !pressed) { + // Up pressed -> up released + s->key_pressed_tmp = 0; + } + pressed = glfwGetKey(s->window, GLFW_KEY_DOWN) == GLFW_PRESS; + if (!s->key_pressed_tmp && pressed) { + // Nothing pressed -> down pressed + s->key_pressed_tmp = GLFW_KEY_DOWN; + if (!(DecreaseIterations(s) && GenerateVertices(s))) return 0; + printf("New L-system length: %.02f MB.\n", ToMB(s->l_system_length)); + } else if ((s->key_pressed_tmp == GLFW_KEY_DOWN) && !pressed) { + // Down pressed -> down released + s->key_pressed_tmp = 0; } return 1; } @@ -227,12 +332,13 @@ int main(int argc, char **argv) { goto cleanup; } printf("Config loaded OK!\n"); - // TODO: Create the actual L-system - // - Parse the config file - // - If the config file is faulty, then display some basic test model, to - // avoid closing the program if someone mistakenly reloads a bad config. - // - Iterate the string. Create an LSystem type, probably. - // - Make the turtle follow the rules. + s->l_system_string = (uint8_t *) strdup(s->config->init); + if (!s->l_system_string) { + printf("Error initializing L-system string.\n"); + to_return = 1; + goto cleanup; + } + // TODO: Display a test model if the config is faulty. if (!GenerateVertices(s)) { printf("Failed generating vertices.\n"); to_return = 1; diff --git a/l_system_3d.h b/l_system_3d.h index 7dbabb8..e3aa13d 100644 --- a/l_system_3d.h +++ b/l_system_3d.h @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,6 +25,10 @@ typedef struct { Turtle3D *turtle; GLuint ubo; SharedUniforms shared_uniforms; + int key_pressed_tmp; + uint32_t l_system_iterations; + uint32_t l_system_length; + uint8_t *l_system_string; } ApplicationState; // Allocates an ApplicationState struct and initializes its values to 0. diff --git a/l_system_mesh.c b/l_system_mesh.c index 268c6ac..2e9d0a8 100644 --- a/l_system_mesh.c +++ b/l_system_mesh.c @@ -140,6 +140,10 @@ static int SetupShaderProgram(LSystemMesh *m) { m->shader_program = p; if (!UniformIndex(p, "model", &(m->model_uniform_index))) return 0; if (!UniformIndex(p, "normal", &(m->normal_uniform_index))) return 0; + if (!UniformIndex(p, "location_offset", + &(m->location_offset_uniform_index))) { + return 0; + } block_index = glGetUniformBlockIndex(p, "SharedUniforms"); if (block_index == GL_INVALID_INDEX) { printf("Failed getting index of shared uniform block.\n"); @@ -215,6 +219,8 @@ int DrawMesh(LSystemMesh *m) { glUniformMatrix4fv(m->model_uniform_index, 1, GL_FALSE, (float *) m->model); glUniformMatrix3fv(m->normal_uniform_index, 1, GL_FALSE, (float *) m->normal); + glUniform3fv(m->location_offset_uniform_index, 1, + (float *) m->location_offset); glDrawArrays(GL_LINES, 0, m->vertex_count); return CheckGLErrors(); } diff --git a/l_system_mesh.h b/l_system_mesh.h index 76b0c98..0388605 100644 --- a/l_system_mesh.h +++ b/l_system_mesh.h @@ -33,12 +33,13 @@ typedef struct { GLuint vao; GLuint vbo; // The model and normal matrices used when drawing this mesh. - // TODO: Try to just make these identity matrices? Or perhaps use these to - // scale the model to fit in a 1x1x1 cube and sit on the floor? mat4 model; mat3 normal; + // The offset to add to every vertex's location to center the mesh. + vec3 location_offset; GLint model_uniform_index; GLint normal_uniform_index; + GLint location_offset_uniform_index; } LSystemMesh; // Prints the vertex's info to stdout. diff --git a/mesh_vertex_shader.vert b/mesh_vertex_shader.vert index 30fb3d7..cce296d 100644 --- a/mesh_vertex_shader.vert +++ b/mesh_vertex_shader.vert @@ -7,6 +7,9 @@ layout (location = 3) in vec4 color_in; uniform mat4 model; uniform mat3 normal; +// Added to the location of each vertex to center the overall mesh on 0,0,0. +uniform vec3 location_offset; + out VS_OUT { vec3 frag_position; vec4 color; @@ -17,10 +20,11 @@ out VS_OUT { void main() { gl_Position = shared_uniforms.projection * shared_uniforms.view * model * - vec4(position_in, 1.0); + vec4(position_in + location_offset, 1.0); vs_out.color = color_in; // Prevent optimizing out the normal uniform. + // TODO (eventually): Remove once we actually use the normal uniform. if (normal[0].x < -1) { vs_out.color[1] = 1; } diff --git a/parse_config.c b/parse_config.c index 8096fb4..183227a 100644 --- a/parse_config.c +++ b/parse_config.c @@ -280,9 +280,34 @@ static int TryParseAction(LSystemConfig *config, const char *token, // Parses the action rules from the config file. Expects to be on the line // immediately following the line containing "actions". Returns 0 on error. static int ParseActionRules(LSystemConfig *config) { + char *action_names[] = { + "move_forward", + "move_forward_nodraw", + "rotate", + "yaw", + "pitch", + "roll", + "set_color_r", + "set_color_g", + "set_color_b", + "set_color_a", + }; + TurtleInstruction action_fns[] = { + MoveTurtleForward, + MoveTurtleForwardNoDraw, + RotateTurtle, + RotateTurtle, + PitchTurtle, + RollTurtle, + SetTurtleRed, + SetTurtleGreen, + SetTurtleBlue, + SetTurtleAlpha, + }; char *current_line = NULL; uint8_t current_char = 0; - int result; + int possible_action_count = sizeof(action_fns) / sizeof(TurtleInstruction); + int result, i; while (1) { current_line = GetNextNonBlankLine(config->f); if (!current_line) break; @@ -306,36 +331,17 @@ static int ParseActionRules(LSystemConfig *config) { "char was specified (line %d).\n", config->f->current_line); return 0; } - // We're not looking at a char so we must be looking at a rule. - result = TryParseAction(config, "move_forward", current_line, current_char, - MoveTurtleForward); - if (result < 0) return 0; - if (result > 0) continue; - result = TryParseAction(config, "rotate", current_line, current_char, - RotateTurtle); - if (result < 0) return 0; - if (result > 0) continue; - result = TryParseAction(config, "yaw", current_line, current_char, - RotateTurtle); - if (result < 0) return 0; - if (result > 0) continue; - result = TryParseAction(config, "pitch", current_line, current_char, - PitchTurtle); - if (result < 0) return 0; - if (result > 0) continue; - result = TryParseAction(config, "roll", current_line, current_char, - RollTurtle); - if (result < 0) return 0; - if (result > 0) continue; - result = TryParseAction(config, "move_forward_nodraw", current_line, - current_char, MoveTurtleForwardNoDraw); - if (result < 0) return 0; - if (result > 0) continue; - - // We've checked for all supported actions at this point. - printf("Got invalid action at line %d of the config.\n", - config->f->current_line); - return 0; + // We're not looking at a char so we must be looking at an instruction. + for (i = 0; i < possible_action_count; i++) { + result = TryParseAction(config, action_names[i], current_line, + current_char, action_fns[i]); + if (result < 0) return 0; + if (result == 0) continue; + if (result > 0) break; + printf("Got invalid action at line %d of the config.\n", + config->f->current_line); + return 0; + } } return 1; } diff --git a/turtle_3d.c b/turtle_3d.c index c3dd3ca..09c485a 100644 --- a/turtle_3d.c +++ b/turtle_3d.c @@ -60,6 +60,48 @@ void DestroyTurtle3D(Turtle3D *t) { free(t); } +static float Max3(float a, float b, float c) { + if (a > b) { + if (a >= c) return a; + return c; + } + if (b >= c) return b; + return c; +} + +static void ModelToNormalMatrix(mat4 model, mat3 normal) { + mat4 dst; + glm_mat4_inv(model, dst); + glm_mat4_transpose(dst); + glm_mat4_pick3(dst, normal); +} + +int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset) { + float dx, dy, dz, max_axis; + dx = t->max_bounds[0] - t->min_bounds[0]; + dy = t->max_bounds[1] - t->min_bounds[1]; + dz = t->max_bounds[2] - t->min_bounds[2]; + if ((dx < 0) || (dy < 0) || (dz < 0)) { + printf("Mesh bounds (deltas %f, %f, %f) not well-formed.\n", dx, dy, dz); + return 0; + } + // Center the model and make it at most 2 units wide in any axis. Start by + // computing the amount to add to each vertex to center the mesh. + loc_offset[0] = -(dx / 2) - t->min_bounds[0]; + loc_offset[1] = -(dy / 2) - t->min_bounds[1]; + loc_offset[2] = -(dz / 2) - t->min_bounds[2]; + max_axis = Max3(dx, dy, dz); + glm_mat4_identity(model); + if (max_axis > 0) { + glm_scale_uni(model, 2.0 / max_axis); + } + ModelToNormalMatrix(model, normal); + // TODO (eventually): If I get less stupid, combine the loc_offset + // translation into the model matrix. This should obviously be possible, but + // I just am not good enough to know how. + return 1; +} + // 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. @@ -175,3 +217,29 @@ int RollTurtle(Turtle3D *t, float angle) { glm_vec3_rotate(t->up, ToRadians(angle), t->forward); return 1; } + +static float ClampColor(float c) { + if (c <= 0.0) return 0; + if (c >= 1.0) return 1.0; + return c; +} + +int SetTurtleRed(Turtle3D *t, float red) { + t->color[0] = ClampColor(red); + return 1; +} + +int SetTurtleGreen(Turtle3D *t, float green) { + t->color[1] = ClampColor(green); + return 1; +} + +int SetTurtleBlue(Turtle3D *t, float blue) { + t->color[2] = ClampColor(blue); + return 1; +} + +int SetTurtleAlpha(Turtle3D *t, float alpha) { + t->color[3] = ClampColor(alpha); + return 1; +} diff --git a/turtle_3d.h b/turtle_3d.h index 87a8229..f870c07 100644 --- a/turtle_3d.h +++ b/turtle_3d.h @@ -49,6 +49,11 @@ void ResetTurtle3D(Turtle3D *t); // pointer is no longer valid after this returns. void DestroyTurtle3D(Turtle3D *t); +// Sets the model and normal matrices for the turtle's vertices, and the +// location offset vector (to be added to each vertex location) that will +// center the mesh on 0, 0, 0. Returns 0 on error. +int SetTransformInfo(Turtle3D *t, mat4 model, mat3 normal, vec3 loc_offset); + // The type used by all turtle-movement instructions. Some instructions may not // use the floating-point parameter. All instructions must return 0 on error // or nonzero on success. @@ -73,5 +78,11 @@ int PitchTurtle(Turtle3D *t, float angle); // angle. Does not change the turtle's position. The angle is in degrees. int RollTurtle(Turtle3D *t, float angle); +// Set the corresponding channels in the turtle's current output color. +int SetTurtleRed(Turtle3D *t, float red); +int SetTurtleGreen(Turtle3D *t, float green); +int SetTurtleBlue(Turtle3D *t, float blue); +int SetTurtleAlpha(Turtle3D *t, float alpha); + #endif // TURTLE_3D_H