mirror of
https://github.com/Team4388/auto_planner.git
synced 2026-06-09 00:38:06 -06:00
317 lines
7.6 KiB
C
317 lines
7.6 KiB
C
#include "planner.h"
|
|
#include "../gfx/gfx.h"
|
|
#include "../include/SDL.h"
|
|
#include "math/bezier.h"
|
|
#include "math/mathutils.h"
|
|
#include "motion.h"
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#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();
|
|
}
|