diff --git a/Makefile b/Makefile index bf88c31..cd5d735 100644 --- a/Makefile +++ b/Makefile @@ -17,12 +17,17 @@ l_system_mesh.o: l_system_mesh.c l_system_mesh.h utilities.h 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 +parse_config.o: parse_config.c parse_config.h turtle_3d.h + gcc $(CFLAGS) -c -o parse_config.o parse_config.c + +l_system_3d: l_system_3d.c l_system_mesh.o turtle_3d.o utilities.o \ + parse_config.o gcc $(CFLAGS) -o l_system_3d l_system_3d.c \ glad/src/glad.c \ utilities.o \ l_system_mesh.o \ turtle_3d.o \ + parse_config.o \ -I glad/include \ -I cglm/include \ $(GLFW_CFLAGS) diff --git a/README.md b/README.md index 214910c..a3c6023 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,116 @@ I will write a README when the project is done. +Configuring the L-System +======================== + +The system reads its configuration from the "config.txt" file. The file +must contain only ASCII characters. It starts with the L-system definition +(replacement rules). Following a line containing the keyword `actions`, the +file switches to defining the drawing actions the "turtle" takes when +encountering each character. (The `actions` line must occur exactly once in the +config.) + +If you want a longer example, the default "config.txt" contains instructions +for drawing the dragon curve, and is shown below: +``` +init F +F F+G +G F-G + +actions +F +move_forward 1.0 +G +move_forward 1.0 +- +rotate -90.0 ++ +rotate 90.0 +``` + +Any lines starting with `#` in the config file are ignored. So, in addition to +L-system characters being limited to ASCII, non-printing, whitespace +characters, and the `#` character are not allowed in replacement rules. Note +that the `#` must be the first non-whitespace character in the line. + +Replacement Rules +----------------- + +The first part of the config defines the replacement rules. This section of +the file ends when the line containing the word `actions` is encountered. +Exactly one line in this part of the file must give the initial contents of the +L-system string: + + - `init ` + +All other non-blank and non-comment lines in this section take the following +format: + + - ` ` + +The character is separated from the string of replacements by one or more tab +or space characters. + +Characters with no defined replacement rules are left as-is during L-system +iteration. Any line containing a character with no following replacement text +indicates that the character is to be deleted when encountered (i.e. replaced +with a blank string). + +Each character can have at most one replacement rule. + +Action Rules +------------ + +Following the `actions` line, the config switches to defining what each +character means for drawing the graphics. Actions for a character are defined +as follows: + +``` + + + +... + +``` + +In other words, the character must first appear on a line by itself. The +following lines must contain the arbitrary number of actions to be taken when +the character is processed. The list of actions ends when either the end of the +file is reached or another line containing a single character is reached. (Note +that none of the actions are a single character long, so there is no ambiguity +between lines containing a single character and lines containing an action.) + +Each character is allowed at most one associated sequence of actions. Any +character that does not appear in the actions section prompts no drawing +actions when rendering the L-system. (This can also be achieved by including +a character in the actions section, but not specifying any actions after it.) + +Available Actions +----------------- + +The following list of actions are supported: + + - `move_forward `: Moves the turtle forward, drawing a line, the + given distance. The distance is a floating-point number of units. Units are + not particularly relevant, because the resulting 3D rendering will be scaled + to fit in the viewport regardless. + + - `rotate `: Rotates the turtle to its left by the given + floating-point number of degrees. + + - `yaw `: The same as the `rotate` action. + + - `pitch `: Rotates the turtle in the upwards direction by the given + floating-point number of degrees. + + - `roll `: Rotates the turtle by the given number of degrees about + its forward-facing axis. + + - `move_forward_nodraw `: Like `move_forward`, but does not draw a + line segment. + +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/build_windows.bat b/build_windows.bat index 5526cc8..460ee43 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -2,6 +2,7 @@ gcc -Wall -Werror -O3 -o l_system_3d ^ l_system_3d.c ^ l_system_mesh.c ^ turtle_3d.c ^ + parse_config.c ^ utilities.c ^ glad\src\glad.c ^ -I cglm\include ^ diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..5827338 --- /dev/null +++ b/config.txt @@ -0,0 +1,20 @@ +# Draw a dragon curve. +init F +F F+G +G F-G + +actions + +F +move_forward 1.0 + +G +move_forward 1.0 + +- +rotate -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 c64b04a..cda6188 100644 --- a/l_system_3d.c +++ b/l_system_3d.c @@ -6,13 +6,13 @@ #include #include #include "l_system_mesh.h" +#include "parse_config.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; @@ -29,6 +29,7 @@ void FreeApplicationState(ApplicationState *s) { if (!s) return; if (s->mesh) DestroyLSystemMesh(s->mesh); if (s->turtle) DestroyTurtle3D(s->turtle); + if (s->config) DestroyLSystemConfig(s->config); glDeleteBuffers(1, &(s->ubo)); if (s->window) glfwDestroyWindow(s->window); memset(s, 0, sizeof(*s)); @@ -85,7 +86,7 @@ static int SetupUniformBuffer(ApplicationState *s) { } static int Rotate90(Turtle3D *t) { - return RotateTurtle(t, PI * 0.5); + return RotateTurtle(t, 90); } // This generates the vertices for the L-system, and updates the mesh. Returns @@ -108,7 +109,7 @@ static int GenerateVertices(ApplicationState *s) { t->color[0] = 1; t->color[1] = 1; if (!MoveTurtleForward(t, 1.0)) return 0; - if (!PitchTurtle(t, PI * 0.5)) 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; @@ -219,6 +220,19 @@ int main(int argc, char **argv) { to_return = 1; goto cleanup; } + s->config = LoadLSystemConfig("./config.txt"); + if (!s->config) { + printf("Error parsing ./config.txt.\n"); + to_return = 1; + 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. if (!GenerateVertices(s)) { printf("Failed generating vertices.\n"); to_return = 1; diff --git a/l_system_3d.h b/l_system_3d.h index 3e84b77..7dbabb8 100644 --- a/l_system_3d.h +++ b/l_system_3d.h @@ -2,6 +2,7 @@ #include #include #include "l_system_mesh.h" +#include "parse_config.h" #include "turtle_3d.h" // Uniforms shared with all shaders. Must match the layout in @@ -19,6 +20,7 @@ typedef struct { int window_height; float aspect_ratio; LSystemMesh *mesh; + LSystemConfig *config; Turtle3D *turtle; GLuint ubo; SharedUniforms shared_uniforms; diff --git a/parse_config.c b/parse_config.c new file mode 100644 index 0000000..8096fb4 --- /dev/null +++ b/parse_config.c @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include "parse_config.h" +#include "utilities.h" + +// Frees the resources associated with the given file. After calling this, the +// pointer is no longer valid. +void FreeConfigFile(ConfigFile *f) { + free(f->file_content); + free(f->lines); + memset(f, 0, sizeof(*f)); + free(f); +} + +// Returns nonzero if c is a tab, space, or carriage return. +static int IsWhitespace(char c) { + return (c == ' ') || (c == '\t') || (c == '\r'); +} + +// Returns a pointer to the first char in s that is non-whitespace. Only skips +// tabs and spaces; not newlines. +static char* SkipWhitespace(char *s) { + while (IsWhitespace(*s)) s++; + return s; +} + +// Replaces any whitespace characters starting at s, and moving backwards. +// Stops either when a non-whitespace character is encountered, or the limit +// pointer is reached. Will not channge the char at the limit pointer. (Used to +// strip trailing spaces.) +static void ReplaceWhitespaceBackwards(char *s, char *limit) { + while ((s != limit) && IsWhitespace(*s)) { + *s = 0; + s--; + } +} + +// Reads the file at the given path, and splits it into lines. Returns NULL on +// error, including if the file contains non-ASCII characters. +static ConfigFile *LoadConfigFile(const char *path) { + ConfigFile *f = NULL; + char *current = NULL; + uint32_t line_index = 0; + f = (ConfigFile *) calloc(1, sizeof(*f)); + if (!f) { + printf("Error allocating internal config-file struct.\n"); + return NULL; + } + f->file_content = ReadFullFile(path); + if (!f->file_content) { + printf("Failed reading config file %s.\n", path); + free(f); + return NULL; + } + if (strlen(f->file_content) == 0) { + printf("The config file %s was empty.\n", path); + FreeConfigFile(f); + return NULL; + } + // Split the file into lines. Start by counting the lines. + current = f->file_content; + while (*current != 0) { + if (*current == '\n') f->line_count++; + if (*current >= 127) { + printf("The config file %s contains a non-ASCII character 0x%x.\n", path, + (unsigned int) *current); + FreeConfigFile(f); + return NULL; + } + current++; + } + // We always end the file with a "line", which will be blank if the file ends + // with a newline. It keeps it simpler to have at least one line. + f->line_count++; + f->lines = (char **) calloc(f->line_count, sizeof(char *)); + if (!f->lines) { + printf("Failed allocating list of file lines.\n"); + FreeConfigFile(f); + return NULL; + } + // Next, set the pointers to the start of each line. + current = f->file_content; + f->lines[0] = current; + line_index = 1; + while (*current != 0) { + if (*current == '\n') { + // Replace newlines with null chars to terminate the line strings. + *current = 0; + // Strip the trailing whitespace for the previous line. + ReplaceWhitespaceBackwards(current - 1, f->lines[line_index - 1] - 1); + // Skip leading whitespace on this line. + f->lines[line_index] = SkipWhitespace(current + 1); + line_index++; + } + current++; + } + return f; +} + +// Gets the next line in the config file. Returns NULL if no more unread lines +// remain. +static char* GetNextLine(ConfigFile *f) { + char *to_return = NULL; + if (f->current_line >= f->line_count) return NULL; + to_return = f->lines[f->current_line]; + f->current_line++; + return to_return; +} + +// Like GetNextLine, but skips blank or comment lines. +static char* GetNextNonBlankLine(ConfigFile *f) { + char *line = GetNextLine(f); + while (1) { + if (line == NULL) return NULL; + if (strlen(line) == 0) { + line = GetNextLine(f); + continue; + } + if (line[0] == '#') { + line = GetNextLine(f); + continue; + } + return line; + } + // Unreachable + return NULL; +} + +void DestroyLSystemConfig(LSystemConfig *c) { + if (c->f) FreeConfigFile(c->f); + memset(c, 0, sizeof(*c)); + free(c); +} + +// Returns nonzero if c is a non-whitespace, printable, ASCII character that +// isn't a '#'. +static int IsValidLSystemChar(char c) { + // All non-printable and whitespace ascii characters fall at or below ' ' + return (c > ' ') && (c < 127) && (c != '#'); +} + +// If s starts with the given token (followed by whitespace), this returns a +// pointer to the first character past the token and the following whitespace. +// Note that this will return an empty string (pointer to a null char) if the +// token takes up the entirety of s. Returns NULL if s doesn't start with the +// token, or if the token isn't followed by whitespace. +static char* ConsumeToken(const char *token, char *s) { + int token_length = strlen(token); + if (token_length == 0) return s; + if (!s) return NULL; + s = SkipWhitespace(s); + // Return now if s doesn't start with the token. + if (strstr(s, token) != s) return NULL; + s += token_length; + // Make sure the char after the token is whitespace or NULL. + if (*s && !IsWhitespace(*s)) return NULL; + s = SkipWhitespace(s); + return s; +} + +// Consumes input lines until the "actions" line is encountered. (If this +// returns successfully, input will be at the first line past "actions".) +// Returns 0 on error. Fills in c->replacements and c->init. +static int ParseReplacementRules(LSystemConfig *config) { + char *current_line = NULL; + char *replacement = NULL; + char *tmp = NULL; + uint8_t c; + int init_found = 0; + while (1) { + replacement = NULL; + current_line = GetNextNonBlankLine(config->f); + if (!current_line) { + printf("The config file didn't contain an \"actions\" line.\n"); + return 0; + } + // Check if we're on the "init" line. + tmp = ConsumeToken("init", current_line); + if (tmp) { + if (init_found) { + printf("The config file contains a duplicate \"init\" on line %d.\n", + config->f->current_line); + return 0; + } + if (strlen(tmp) <= 0) { + printf("The config file's \"init\" string is empty.\n"); + return 0; + } + init_found = 1; + config->init = tmp; + continue; + } + // Check if we're on the "actions" line. + tmp = ConsumeToken("actions", current_line); + if (tmp) { + if (strlen(tmp) > 0) { + printf("The config contains extra text on the \"actions\" line.\n"); + return 0; + } + break; + } + // Finally, parse a replacement rule. + c = current_line[0]; + if (!IsValidLSystemChar(c)) { + printf("Line %d of the config: invalid char to replace (0x%x).\n", + (int) config->f->current_line, (unsigned int) c); + return 0; + } + if (config->replacements[c].used) { + printf("Replacement rule for %c redefined on line %d of the config.\n", + c, (int) config->f->current_line); + return 0; + } + replacement = SkipWhitespace(current_line + 1); + config->replacements[c].used = 1; + config->replacements[c].length = strlen(replacement); + config->replacements[c].replacement = replacement; + } + if (!init_found) { + printf("The config file didn't contain an \"init\" line.\n"); + return 0; + } + return 1; +} + +// Attempts to parse a float arg from string s. The arg must be preceded only +// by whitespace, and followed only by whitespace and/or a null char. Returns 0 +// if a float can't be parsed in such a manner. Otherwise, sets *arg to the +// float and returns 1. +static int ParseFloatArg(char *s, float *arg) { + char *end = s; + float result; + *arg = 0; + s = SkipWhitespace(s); + result = strtof(s, &end); + if (s == end) { + printf("Failed parsing floating-point number.\n"); + return 0; + } + end = SkipWhitespace(end); + if (*end != 0) { + printf("Got extra text following a number.\n"); + return 0; + } + *arg = result; + return 1; +} + +// Attempts to parse an action starting with the given token on the given line. +// Returns 0 if the line doesn't start with the token, -1 if the error is +// fatal, or 1 if the action was parsed and added OK. +static int TryParseAction(LSystemConfig *config, const char *token, + char *line, uint8_t c, TurtleInstruction n) { + char *next = NULL; + ActionRule *a = NULL; + float arg; + next = ConsumeToken(token, line); + // The line just didn't start with the token; not a fatal error. + if (!next) return 0; + if (!ParseFloatArg(next, &arg)) { + printf("Failed parsing arg for \"%s\" on line %d of the config.\n", + token, config->f->current_line); + return -1; + } + a = config->actions + c; + if (a->length >= MAX_ACTIONS_PER_CHAR) { + printf("Too many actions defined for char %c. The limit is %d.\n", + c, MAX_ACTIONS_PER_CHAR); + return -1; + } + a->instructions[a->length] = n; + a->args[a->length] = arg; + a->length++; + return 1; +} + +// 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 *current_line = NULL; + uint8_t current_char = 0; + int result; + while (1) { + current_line = GetNextNonBlankLine(config->f); + if (!current_line) break; + if (strlen(current_line) == 1) { + // We're switching chars. + current_char = current_line[0]; + if (!IsValidLSystemChar(current_char)) { + printf("Got invalid char on line %d of the config.\n", + config->f->current_line); + return 0; + } + if (config->actions[current_char].length != 0) { + printf("Actions for char %c redefined starting on line %d.\n", + current_char, config->f->current_line); + return 0; + } + continue; + } + if (current_char == 0) { + printf("Got non-char in the config file's actions sections before any " + "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; + } + return 1; +} + +LSystemConfig* LoadLSystemConfig(const char *path) { + LSystemConfig *to_return = NULL; + to_return = (LSystemConfig *) calloc(1, sizeof(*to_return)); + if (!to_return) { + printf("Failed allocating L-system config data.\n"); + return NULL; + } + to_return->f = LoadConfigFile(path); + if (!to_return->f) { + DestroyLSystemConfig(to_return); + return NULL; + } + if (!ParseReplacementRules(to_return)) { + DestroyLSystemConfig(to_return); + return NULL; + } + if (!ParseActionRules(to_return)) { + DestroyLSystemConfig(to_return); + return NULL; + } + return to_return; +} diff --git a/parse_config.h b/parse_config.h new file mode 100644 index 0000000..d5cffee --- /dev/null +++ b/parse_config.h @@ -0,0 +1,65 @@ +#ifndef PARSE_CONFIG_H +#define PARSE_CONFIG_H +#include +#include "turtle_3d.h" + +// An arbitrary limit on the number of actions that can be taken by a single +// character. Can probably be increased without much consequence, but my +// assumption is that most L-system definitions won't have huge lists of +// actions for a single character. +#define MAX_ACTIONS_PER_CHAR (32) + +// A struct to help read the config file line-by-line. Not intended for +// external use. +typedef struct { + char *file_content; + char **lines; + uint32_t line_count; + uint32_t current_line; +} ConfigFile; + +void FreeConfigFile(ConfigFile *f); + +// Keeps track of the contents with which we replace a single character. +typedef struct { + // If this is nonzero, the replacement rule is actually used. This + // differentiates replacements with a blank string (deletions) from + // characters that should be left as-is. + int used; + // The number of characters with which the char is replaced. + int length; + // The actual replacement chars. + const char *replacement; +} ReplacementRule; + +// Keeps track of the actions taken when processing a character in a string. +typedef struct { + // The number of actions to carry out. + int length; + // The functions used to move the turtle. + TurtleInstruction instructions[MAX_ACTIONS_PER_CHAR]; + // The arguments to pass to each corresponding function. + float args[MAX_ACTIONS_PER_CHAR]; +} ActionRule; + +// Tracks the rules for the L-system generation and drawing. +typedef struct { + ConfigFile *f; + // The initial string in the L-system. + const char *init; + // The replacement rules for each char in the ascii range. + ReplacementRule replacements[128]; + // The action rules for each char in the ascii range. + ActionRule actions[128]; +} LSystemConfig; + +// Loads and parses the config file at the given path, returning an +// LSystemConfig struct. Returns NULL if any error occurs. +LSystemConfig* LoadLSystemConfig(const char *path); + +// Any resources associated with the given config. The pointer becomes invalid +// after this function is called. +void DestroyLSystemConfig(LSystemConfig *c); + +#endif // PARSE_CONFIG_H + diff --git a/turtle_3d.c b/turtle_3d.c index 8b9f2f8..c3dd3ca 100644 --- a/turtle_3d.c +++ b/turtle_3d.c @@ -12,6 +12,8 @@ // be considered identical. #define MIN_POSITION_DIST (1.0e-6) +#define PI (3.1415926536) + Turtle3D* CreateTurtle3D(void) { Turtle3D *to_return = NULL; to_return = (Turtle3D *) calloc(1, sizeof(*to_return)); @@ -19,10 +21,6 @@ Turtle3D* CreateTurtle3D(void) { 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) { @@ -31,9 +29,30 @@ Turtle3D* CreateTurtle3D(void) { return NULL; } to_return->vertex_capacity = INITIAL_TURTLE_CAPACITY; + ResetTurtle3D(to_return); return to_return; } +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); + // 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; + // The bounding cube is empty and ill-defined to begin with. + glm_vec3_zero(t->min_bounds); + glm_vec3_zero(t->max_bounds); + // We don't reallocate the array or zero it out here so we can reuse the + // turtle to draw another iteration without needing to clear all the + // vertices. + t->vertex_count = 0; +} + void DestroyTurtle3D(Turtle3D *t) { if (!t) return; free(t->vertices); @@ -121,24 +140,38 @@ static void UpdateBounds(Turtle3D *t) { } } -int MoveTurtleForward(Turtle3D *t, float distance) { +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); UpdateBounds(t); + return 1; +} + +int MoveTurtleForward(Turtle3D *t, float distance) { + if (!MoveTurtleForwardNoDraw(t, distance)) return 0; return AppendSegment(t); } +static float ToRadians(float degrees) { + return degrees * (PI / 180.0); +} + int RotateTurtle(Turtle3D *t, float angle) { - glm_vec3_rotate(t->forward, angle, t->up); + glm_vec3_rotate(t->forward, ToRadians(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); + glm_vec3_rotate(t->up, ToRadians(angle), right); + glm_vec3_rotate(t->forward, ToRadians(angle), right); + return 1; +} + +int RollTurtle(Turtle3D *t, float angle) { + glm_vec3_rotate(t->up, ToRadians(angle), t->forward); return 1; } diff --git a/turtle_3d.h b/turtle_3d.h index 7c8343a..87a8229 100644 --- a/turtle_3d.h +++ b/turtle_3d.h @@ -33,26 +33,45 @@ typedef struct { uint32_t vertex_capacity; } 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. Turtle3D* CreateTurtle3D(void); +// Resets the turtle to its original position (0, 0, 0) and orientation, facing +// right. Clears the list of all generated vertices. +void ResetTurtle3D(Turtle3D *t); + // 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. +// 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. +typedef int (*TurtleInstruction)(Turtle3D *t, float param); + +// Instrcts the turtle to move forward by the given distance, drawing a +// segment. 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. +// Moves the turtle forward by the given distance, but does not draw a segment. +int MoveTurtleForwardNoDraw(Turtle3D *t, float distance); + +// Instructs the turtle to rotate about its upward axis by the given angle. +// Does not change the turtle's position. The angle is in degrees. 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. +// Instructs the turtle to rotate about its right-facing axis by the given +// angle. Does not change the turtle's position. The angle is in degrees. int PitchTurtle(Turtle3D *t, float angle); -// TODO: Implement functions for rotating the turtle. Use rotation matrices? +// Instructs the turtle to rotate about its forward-facing axis by the given +// angle. Does not change the turtle's position. The angle is in degrees. +int RollTurtle(Turtle3D *t, float angle); #endif // TURTLE_3D_H +