#include "planner.h" #include "../gfx/gfx.h" #include "../include/SDL.h" #include "math/bezier.h" #include "math/mathutils.h" #include "motion.h" #include #include #include #include #include #include #define SELECTED state.paths.arr[state.paths.selected] #define STATE_FIELD_RESCALED state.field.w_meters, state.field.h_meters, 0, 0 static PlannerState state; static BezierPath bezier = { .arr = NULL, .cap = 51, .len = 0, .selected = -1, }; const point START_POSITIONS[6] = { [RED_ONE] = {14.5, 3.6}, [RED_TWO] = {14.5, 5.5}, [RED_THREE] = {14.5, 7.0}, [BLUE_ONE] = {2.0, 3.6}, [BLUE_TWO] = {2.0, 5.5}, [BLUE_THREE] = {2.0, 7.0}, }; static void build_bounding_box(MultiPath *path) { rect box = { state.field.startx, state.field.starty, state.field.startx, state.field.starty }; if (path->type == PATH_MOTION) { printf("motion.len: %d\n", path->motion.len); for (int i = 0; i < path->motion.len; i++) { double x = path->motion.odo_path[i].x; double y = path->motion.odo_path[i].y; if (x < box.x) box.x = x; if (y < box.y) box.y = y; if (x > box.w) box.w = x; if (y > box.h) box.h = y; } } else { for (int i = 0; i < path->bezier.len; i += 2) { point prev = path->bezier.arr[i]; for (float j = 0; j < 1.f; j += RES) { point percent_a = lerp_point(path->bezier.arr[i], path->bezier.arr[i + 1], j); point percent_b = lerp_point(path->bezier.arr[i + 1], path->bezier.arr[i + 2], j); point curve = lerp_point(percent_a, percent_b, j); if (curve.x < box.x) box.x = curve.x; if (curve.y < box.y) box.y = curve.y; if (curve.x > box.w) box.w = curve.x; if (curve.y > box.h) box.h = curve.y; prev = curve; } } } point ll = scale_to_screen(box.x, box.y, STATE_FIELD_RESCALED); point rr = scale_to_screen(box.w - box.x, box.h - box.y, STATE_FIELD_RESCALED); path->bounding_box = (rect) {ll.x, ll.y, rr.x, rr.y}; } void init_planner(StartPos start) { const size_t path_arr_size = sizeof(MotionPath) * 10; state = (PlannerState) { .field = { .startx = START_POSITIONS[start].x, // 8.25, .starty = START_POSITIONS[start].y, // 4.05, .w_meters = 16.5, .h_meters = 8.1, .xy_speed = 0.8, //4.8, .rot_speed = 0, }, .zoom_level = 1, .x_offset = 0, .y_offset = 0, .paths = { .cap = 4, .len = 0, .arr = malloc(path_arr_size), .selected = 0, }, .mode = MODE_EDIT, }; memset(state.paths.arr, 0, path_arr_size); if (state.paths.arr == NULL) { printf("could not allocate MotionPaths"); exit(1); } const size_t bezier_size = sizeof(point) * bezier.cap; bezier.arr = malloc(bezier_size); memset(bezier.arr, 0, bezier_size); bezier.arr[bezier.len++] = (point) { state.field.startx, state.field.starty }; bezier.arr[bezier.len++] = (point) { state.field.startx + 1, state.field.starty }; bezier.arr[bezier.len++] = (point) { state.field.startx + 2, state.field.starty }; state.paths.arr[state.paths.len++] = WRAP_BEZIER(bezier); state.paths.arr[state.paths.len++] = WRAP_MOTION(load_path("bezier.txt", &state.field)); for (int i = 0; i < state.paths.len; i++) build_bounding_box(&state.paths.arr[i]); } static void path_playback(bool start, MotionPath *path) { static clock_t start_time = 0; static int index = 1; if (start) { start_time = clock(); index = 1; } clock_t playback_time = clock() - start_time; while (index < path->len && path->points[index].time < playback_time) { index++; } if (index == path->len) { state.mode = MODE_EDIT; return; } double x_min = path->odo_path[index - 1].x; double x_max = path->odo_path[index ].x; double y_min = path->odo_path[index - 1].y; double y_max = path->odo_path[index ].y; double lerp_time = ((double) (path->points[index].time - path->points[index-1].time)) / ((double) playback_time); point robot_pos = { x_min + lerp_time * (x_max - x_min), y_min + lerp_time * (y_max - y_min), }; draw_robot(&state.field, robot_pos); } static void bezier_playback(bool start, BezierPath *path) { } // TODO: use BezierPath & port to bezier module static void deselect_bez_point(MultiPath *path) { if (path->type == PATH_MOTION) return; path->bezier.selected = -1; build_bounding_box(path); } static void select_path(int x, int y) { point mouse = { x, y }; for (int i = 0; i < state.paths.len; i++) { if (within_box(mouse, state.paths.arr[i].bounding_box)) { state.paths.selected = i; return; } } } static void draw_correct_path(MultiPath *path, bool bold, bool select) { if (path->type == PATH_MOTION) draw_path(&path->motion, &state.field, bold, select); else draw_bezier(&path->bezier, &state.field, bold, select); } static void draw_paths(int x, int y) { point mouse = { x, y }; for (int i = 0; i < state.paths.len && i < 2; i++) { if (state.paths.selected == i) { draw_correct_path(&state.paths.arr[i], true, true); continue; } if (within_box(mouse, state.paths.arr[i].bounding_box)) draw_correct_path(&state.paths.arr[i], true, false); else draw_correct_path(&state.paths.arr[i], false, false); } } void save_correct_format(MultiPath *path) { if (path->type == PATH_MOTION) save_path(&path->motion); else save_bezier(&path->bezier, "bezier.txt", state.field.xy_speed); } void planner_loop(SDL_Event *e) { int x, y; SDL_GetMouseState(&x, &y); while (SDL_PollEvent(e)) { switch (e->type) { case SDL_QUIT: // FIXME: crashing the program to exit is rather ungraceful. // I should make a cleanup procedure at some point. exit(0); break; case SDL_MOUSEBUTTONDOWN: if (SELECTED.type == PATH_BEZIER && select_bez_point(x, y, &SELECTED.bezier, &state.field)) break; select_path(x, y); break; case SDL_MOUSEBUTTONUP: deselect_bez_point(&SELECTED); break; case SDL_KEYDOWN: switch(e->key.keysym.scancode) { case SDL_SCANCODE_P: if (state.paths.selected >= 0 && SELECTED.type == PATH_MOTION) { path_playback(true, &SELECTED.motion); state.mode = MODE_PLAYBACK; } break; case SDL_SCANCODE_A: if (state.paths.selected >= 0 && SELECTED.type == PATH_BEZIER) { if (bezier.len >= bezier.cap) break; for (int i = 0; i < 2; i++) { SELECTED.bezier.arr[SELECTED.bezier.len++] = (point) { .x = state.field.startx, .y = state.field.starty, }; } } break; case SDL_SCANCODE_S: save_correct_format(&SELECTED); break; case SDL_SCANCODE_D: printf("(%f, %f)\n", bezier.arr[0].x, bezier.arr[0].y); break; default: break; } break; } } draw_field(&state.field); switch (state.mode) { case MODE_EDIT: if (SELECTED.type == PATH_BEZIER) move_point(x, y, &SELECTED.bezier, &state.field); draw_paths(x, y); draw_robot(&state.field, (point) { state.field.startx, state.field.starty }); break; case MODE_PLAYBACK: DRAW_PATH_SELECT(&SELECTED.motion, &state.field); path_playback(false, &SELECTED.motion); break; case MODE_RECORD: DRAW_PATH_SELECT(&SELECTED.motion, &state.field); draw_robot(&state.field, (point) { SELECTED.motion.odo_path[SELECTED.motion.len - 1].x, SELECTED.motion.odo_path[SELECTED.motion.len - 1].y, }); break; } blit_screen(); }