diff --git a/paint/sources/trait.c b/paint/sources/trait.c new file mode 100644 index 00000000..1028983c --- /dev/null +++ b/paint/sources/trait.c @@ -0,0 +1,53 @@ + +#include "global.h" + +typedef void (*trait_init_t)(char *object); +typedef void (*trait_run_t)(); +typedef void (*trait_stop_t)(); + +typedef struct trait { + char *name; + trait_init_t init; + trait_run_t run; + trait_stop_t stop; +} trait_t; + +static trait_t traits[] = { + {"third_person_controller", trait_third_person_controller_init, trait_third_person_controller_run, trait_third_person_controller_stop}, + {"point_and_click_controller", trait_point_and_click_controller_init, trait_point_and_click_controller_run, trait_point_and_click_controller_stop}, +}; + +#define TRAIT_COUNT (i32)(sizeof(traits) / sizeof(traits[0])) + +static bool trait_attached[TRAIT_COUNT]; + +void script_add_trait(char *object, char *trait) { + for (i32 i = 0; i < TRAIT_COUNT; ++i) { + if (!string_equals(traits[i].name, trait)) { + continue; + } + if (trait_attached[i]) { + return; + } + trait_attached[i] = true; + traits[i].init(object); + return; + } + // console_error(string("Unknown trait: %s", trait)); +} + +void trait_update() { + for (i32 i = 0; i < TRAIT_COUNT; ++i) { + if (trait_attached[i]) { + traits[i].run(); + } + } +} + +void trait_stop() { + for (i32 i = 0; i < TRAIT_COUNT; ++i) { + if (trait_attached[i] && traits[i].stop != NULL) { + traits[i].stop(); + } + } +} diff --git a/paint/sources/traits/trait_point_and_click_controller.c b/paint/sources/traits/trait_point_and_click_controller.c new file mode 100644 index 00000000..56954b43 --- /dev/null +++ b/paint/sources/traits/trait_point_and_click_controller.c @@ -0,0 +1,410 @@ + +#include "../global.h" + +#define PAC_SPEED 1.0 +#define PAC_ARRIVE 0.1 +#define PAC_CLEARANCE 0.15 +#define PAC_TERRAIN "Terrain" +#define PAC_MAX_RECTS 32 +#define PAC_MAX_NODES (PAC_MAX_RECTS * 4 + 2) +#define PAC_EPS 0.001 +#define PAC_FAR 1.0e30 + +static char *trait_point_and_click_controller_object = NULL; +static quat_t trait_point_and_click_controller_rot = {0.0, 0.0, 0.0, 1.0}; +static bool trait_point_and_click_controller_moving = false; + +typedef struct { + f32 min_x; + f32 min_y; + f32 max_x; + f32 max_y; +} pac_rect_t; + +static pac_rect_t pac_rects[PAC_MAX_RECTS]; +static i32 pac_rect_count = 0; +static vec4_t pac_nodes[PAC_MAX_NODES]; +static i32 pac_node_count = 0; +static bool pac_visible[PAC_MAX_NODES][PAC_MAX_NODES]; +static vec4_t pac_path[PAC_MAX_NODES]; +static i32 pac_path_count = 0; +static i32 pac_path_index = 0; + +void trait_point_and_click_controller_init(char *object) { + gc_unroot(trait_point_and_click_controller_object); + trait_point_and_click_controller_object = string_copy(object); + gc_root(trait_point_and_click_controller_object); + trait_point_and_click_controller_moving = false; + trait_point_and_click_controller_rot = (quat_t){0.0, 0.0, 0.0, 1.0}; + pac_path_count = 0; + pac_path_index = 0; +} + +static f32 pac_dist(vec4_t a, vec4_t b) { + f32 dx = a.x - b.x; + f32 dy = a.y - b.y; + return math_sqrt(dx * dx + dy * dy); +} + +static bool pac_inside(pac_rect_t *r, vec4_t p) { + return p.x > r->min_x && p.x < r->max_x && p.y > r->min_y && p.y < r->max_y; +} + +static void pac_gather_rects(asim_body_t *self) { + pac_rect_count = 0; + f32 grow = math_sqrt(self->dimx * self->dimx + self->dimy * self->dimy) / 2.0 + PAC_CLEARANCE; + + mesh_object_t_array_t *objects = g_project->_->paint_objects; + for (i32 i = 0; i < objects->length && pac_rect_count < PAC_MAX_RECTS; ++i) { + object_t *o = objects->buffer[i]->base; + if (o->_ == NULL || string_equals(o->name, PAC_TERRAIN)) { + continue; + } + asim_body_t *body = o->_->body; + if (body == NULL || body == self || body->shape != ASIM_SHAPE_BOX || body->mass > 0.0) { + continue; + } + + quat_t rot = o->transform->rot; + vec4_t center = vec4_add(o->transform->loc, vec4_apply_quat(body->offset, rot)); + vec4_t half = (vec4_t){body->dimx / 2.0, body->dimy / 2.0, body->dimz / 2.0, 0.0}; + + pac_rect_t r = {PAC_FAR, PAC_FAR, -PAC_FAR, -PAC_FAR}; + for (i32 c = 0; c < 8; ++c) { + vec4_t corner = (vec4_t){(c & 1) ? half.x : -half.x, (c & 2) ? half.y : -half.y, (c & 4) ? half.z : -half.z, 0.0}; + corner = vec4_apply_quat(corner, rot); + r.min_x = math_min(r.min_x, center.x + corner.x); + r.min_y = math_min(r.min_y, center.y + corner.y); + r.max_x = math_max(r.max_x, center.x + corner.x); + r.max_y = math_max(r.max_y, center.y + corner.y); + } + + r.min_x -= grow; + r.min_y -= grow; + r.max_x += grow; + r.max_y += grow; + pac_rects[pac_rect_count++] = r; + } +} + +static vec4_t pac_push_out(vec4_t p) { + for (i32 pass = 0; pass < 4; ++pass) { + bool moved = false; + for (i32 i = 0; i < pac_rect_count; ++i) { + pac_rect_t *r = &pac_rects[i]; + if (!pac_inside(r, p)) { + continue; + } + + f32 left = p.x - r->min_x; + f32 right = r->max_x - p.x; + f32 down = p.y - r->min_y; + f32 up = r->max_y - p.y; + f32 best = math_min(math_min(left, right), math_min(down, up)); + if (best == left) { + p.x = r->min_x - PAC_EPS; + } + else if (best == right) { + p.x = r->max_x + PAC_EPS; + } + else if (best == down) { + p.y = r->min_y - PAC_EPS; + } + else { + p.y = r->max_y + PAC_EPS; + } + moved = true; + } + if (!moved) { + break; + } + } + return p; +} + +static bool pac_clip_slab(f32 origin, f32 dir, f32 lo, f32 hi, f32 *t0, f32 *t1) { + if (math_abs(dir) < 1e-6) { + return origin >= lo && origin <= hi; + } + + f32 ta = (lo - origin) / dir; + f32 tb = (hi - origin) / dir; + if (ta > tb) { + f32 tmp = ta; + ta = tb; + tb = tmp; + } + if (ta > *t0) { + *t0 = ta; + } + if (tb < *t1) { + *t1 = tb; + } + return *t0 <= *t1; +} + +static bool pac_crosses(vec4_t a, vec4_t b, pac_rect_t *r) { + f32 min_x = r->min_x + PAC_EPS; + f32 max_x = r->max_x - PAC_EPS; + f32 min_y = r->min_y + PAC_EPS; + f32 max_y = r->max_y - PAC_EPS; + if (min_x >= max_x || min_y >= max_y) { + return false; + } + + f32 t0 = 0.0; + f32 t1 = 1.0; + return pac_clip_slab(a.x, b.x - a.x, min_x, max_x, &t0, &t1) && pac_clip_slab(a.y, b.y - a.y, min_y, max_y, &t0, &t1); +} + +static bool pac_clear(vec4_t a, vec4_t b) { + for (i32 i = 0; i < pac_rect_count; ++i) { + if (pac_crosses(a, b, &pac_rects[i])) { + return false; + } + } + return true; +} + +static void pac_build_nodes(vec4_t start, vec4_t goal) { + pac_nodes[0] = start; + pac_nodes[1] = goal; + pac_node_count = 2; + + for (i32 i = 0; i < pac_rect_count; ++i) { + pac_rect_t *r = &pac_rects[i]; + vec4_t corners[4] = {{r->min_x, r->min_y, 0.0, 1.0}, {r->max_x, r->min_y, 0.0, 1.0}, {r->max_x, r->max_y, 0.0, 1.0}, {r->min_x, r->max_y, 0.0, 1.0}}; + + for (i32 c = 0; c < 4; ++c) { + bool standable = true; + for (i32 j = 0; j < pac_rect_count && standable; ++j) { + standable = !pac_inside(&pac_rects[j], corners[c]); + } + if (standable) { + pac_nodes[pac_node_count++] = corners[c]; + } + } + } +} + +static void pac_build_visibility() { + for (i32 i = 0; i < pac_node_count; ++i) { + pac_visible[i][i] = false; + for (i32 j = i + 1; j < pac_node_count; ++j) { + bool clear = pac_clear(pac_nodes[i], pac_nodes[j]); + pac_visible[i][j] = clear; + pac_visible[j][i] = clear; + } + } +} + +static bool pac_search() { + f32 dist[PAC_MAX_NODES]; + i32 prev[PAC_MAX_NODES]; + bool done[PAC_MAX_NODES]; + + for (i32 i = 0; i < pac_node_count; ++i) { + dist[i] = PAC_FAR; + prev[i] = -1; + done[i] = false; + } + dist[0] = 0.0; + + for (;;) { + i32 u = -1; + for (i32 i = 0; i < pac_node_count; ++i) { + if (!done[i] && dist[i] < PAC_FAR && (u == -1 || dist[i] < dist[u])) { + u = i; + } + } + if (u == -1 || u == 1) { + break; + } + + done[u] = true; + for (i32 v = 0; v < pac_node_count; ++v) { + if (done[v] || !pac_visible[u][v]) { + continue; + } + f32 d = dist[u] + pac_dist(pac_nodes[u], pac_nodes[v]); + if (d < dist[v]) { + dist[v] = d; + prev[v] = u; + } + } + } + + if (dist[1] >= PAC_FAR) { + return false; + } + + i32 chain[PAC_MAX_NODES]; + i32 count = 0; + for (i32 n = 1; n != -1; n = prev[n]) { + chain[count++] = n; + } + + pac_path_count = 0; + for (i32 i = count - 2; i >= 0; --i) { + pac_path[pac_path_count++] = pac_nodes[chain[i]]; + } + pac_path_index = 0; + return true; +} + +static bool pac_plan(asim_body_t *body, vec4_t goal) { + vec4_t pos; + asim_body_get_pos(body->_body, &pos); + + pac_gather_rects(body); + vec4_t start = pac_push_out((vec4_t){pos.x, pos.y, 0.0, 1.0}); + goal = pac_push_out((vec4_t){goal.x, goal.y, 0.0, 1.0}); + + if (pac_clear(start, goal)) { + pac_path[0] = goal; + pac_path_count = 1; + pac_path_index = 0; + return true; + } + + pac_build_nodes(start, goal); + pac_build_visibility(); + return pac_search(); +} + +static quat_t trait_point_and_click_controller_facing(vec4_t dir) { + return quat_from_axis_angle(vec4_z_axis(), math_atan2(-dir.x, dir.y) + math_pi()); +} + +static object_t *trait_point_and_click_controller_run_object(object_t *o) { + return script_get_object(string("%s_run", o->name)); +} + +static void trait_point_and_click_controller_set_moving(object_t *o, bool moving) { + if (moving == trait_point_and_click_controller_moving) { + return; + } + object_t *run = trait_point_and_click_controller_run_object(o); + if (run == NULL) { + return; + } + trait_point_and_click_controller_moving = moving; + o->visible = !moving; + run->visible = moving; +} + +static void trait_point_and_click_controller_place(asim_body_t *body) { + if (!trait_point_and_click_controller_moving) { + return; + } + object_t *run = trait_point_and_click_controller_run_object(body->obj); + if (run == NULL) { + return; + } + + vec4_t pos; + asim_body_get_pos(body->_body, &pos); + quat_t rot = trait_point_and_click_controller_rot; + vec4_t off = vec4_apply_quat(body->offset, rot); + + transform_t *t = run->transform; + t->loc = (vec4_t){pos.x - off.x, pos.y - off.y, pos.z - off.z, 1.0}; + t->rot = rot; + transform_build_matrix(t); +} + +static void trait_point_and_click_controller_terrain() { + object_t *o = script_get_object(PAC_TERRAIN); + if (o == NULL || o->_ == NULL) { + return; + } + asim_body_t *body = o->_->body; + if (body == NULL || body->shape != ASIM_SHAPE_TERRAIN) { + script_physics_set_shape(o, ASIM_SHAPE_TERRAIN); + } +} + +static bool trait_point_and_click_controller_pick(vec4_t *target) { + f32 mx = mouse_view_x(); + f32 my = mouse_view_y(); + if (mx < 0.0 || my < 0.0 || mx > sys_w() || my > sys_h()) { + return false; + } + + trait_point_and_click_controller_terrain(); + ray_t *ray = raycast_get_ray(mx, my, scene_camera); + return asim_terrain_raycast(ray->origin, ray->dir, target); +} + +static bool trait_point_and_click_controller_move(asim_body_t *body) { + vec4_t vel; + asim_body_get_velocity(body->_body, &vel); + vec4_t pos; + asim_body_get_pos(body->_body, &pos); + + while (pac_path_index < pac_path_count && pac_dist(pac_path[pac_path_index], pos) <= PAC_ARRIVE) { + pac_path_index++; + } + if (pac_path_index >= pac_path_count) { + asim_body_set_velocity(body->_body, 0.0, 0.0, vel.z); + return false; + } + + vec4_t target = pac_path[pac_path_index]; + f32 dist = pac_dist(target, pos); + vec4_t dir = (vec4_t){(target.x - pos.x) / dist, (target.y - pos.y) / dist, 0.0, 0.0}; + + f32 speed = PAC_SPEED; + f32 delta = sys_delta(); + if (pac_path_index == pac_path_count - 1 && delta > 0.0 && dist / delta < speed) { + speed = dist / delta; + } + asim_body_set_velocity(body->_body, dir.x * speed, dir.y * speed, vel.z); + + trait_point_and_click_controller_rot = trait_point_and_click_controller_facing(dir); + asim_body_set_rotation(body, trait_point_and_click_controller_rot); + return true; +} + +static asim_body_t *trait_point_and_click_controller_body(object_t *o) { + asim_body_t *body = o->_->body; + if (body != NULL && body->shape == ASIM_SHAPE_BOX && body->mass > 0.0) { + return body; + } + script_physics_set_shape(o, ASIM_SHAPE_BOX); + return o->_->body; +} + +void trait_point_and_click_controller_run() { + object_t *o = script_get_object(trait_point_and_click_controller_object); + if (o == NULL) { + return; + } + + asim_body_t *body = trait_point_and_click_controller_body(o); + if (body == NULL) { + return; + } + + vec4_t target; + if (mouse_started("left") && trait_point_and_click_controller_pick(&target) && !pac_plan(body, target)) { + pac_path_count = 0; + pac_path_index = 0; + } + + bool moving = trait_point_and_click_controller_move(body); + trait_point_and_click_controller_set_moving(o, moving); + trait_point_and_click_controller_place(body); + if (moving) { + g_context->ddirty = 2; + } +} + +void trait_point_and_click_controller_stop() { + pac_path_count = 0; + pac_path_index = 0; + object_t *o = script_get_object(trait_point_and_click_controller_object); + if (o != NULL) { + trait_point_and_click_controller_set_moving(o, false); + } +} diff --git a/paint/sources/traits/trait_third_person_controller.c b/paint/sources/traits/trait_third_person_controller.c new file mode 100644 index 00000000..ed197ce0 --- /dev/null +++ b/paint/sources/traits/trait_third_person_controller.c @@ -0,0 +1,185 @@ + +#include "../global.h" + +#define TPC_SENSITIVITY 0.0025 +#define TPC_PITCH_MIN (-1.3) +#define TPC_PITCH_MAX 0.6 +#define TPC_SPEED 2.0 +#define TPC_CAMERA_DIST 3.5 +#define TPC_CAMERA_FOCUS 0.45 + +static char *trait_third_person_controller_object = NULL; +static f32 trait_third_person_controller_yaw = 0.0; +static f32 trait_third_person_controller_pitch = 0.0; +static bool trait_third_person_controller_moving = false; + +void trait_third_person_controller_init(char *object) { + gc_unroot(trait_third_person_controller_object); + trait_third_person_controller_object = string_copy(object); + gc_root(trait_third_person_controller_object); + trait_third_person_controller_moving = false; +} + +static quat_t trait_third_person_controller_facing() { + return quat_from_axis_angle(vec4_z_axis(), trait_third_person_controller_yaw + math_pi()); +} + +static vec4_t trait_third_person_controller_look() { + f32 cp = math_cos(trait_third_person_controller_pitch); + return (vec4_t){-math_sin(trait_third_person_controller_yaw) * cp, math_cos(trait_third_person_controller_yaw) * cp, + math_sin(trait_third_person_controller_pitch), 0.0}; +} + +static void trait_third_person_controller_grab() { + vec4_t look = camera_object_look_world(scene_camera); + trait_third_person_controller_yaw = math_atan2(-look.x, look.y); + trait_third_person_controller_pitch = math_asin(math_max(math_min(look.z, 1.0), -1.0)); + trait_third_person_controller_pitch = math_max(math_min(trait_third_person_controller_pitch, TPC_PITCH_MAX), TPC_PITCH_MIN); + iron_mouse_lock(); +} + +static void trait_third_person_controller_rotate() { + if (mouse_movement_x == 0 && mouse_movement_y == 0) { + return; + } + trait_third_person_controller_yaw -= mouse_movement_x * TPC_SENSITIVITY; + trait_third_person_controller_pitch -= mouse_movement_y * TPC_SENSITIVITY; + trait_third_person_controller_pitch = math_max(math_min(trait_third_person_controller_pitch, TPC_PITCH_MAX), TPC_PITCH_MIN); +} + +static object_t *trait_third_person_controller_run_object(object_t *o) { + return script_get_object(string("%s_run", o->name)); +} + +static void trait_third_person_controller_set_moving(object_t *o, bool moving) { + if (moving == trait_third_person_controller_moving) { + return; + } + object_t *run = trait_third_person_controller_run_object(o); + if (run == NULL) { + return; + } + trait_third_person_controller_moving = moving; + o->visible = !moving; + run->visible = moving; +} + +static void trait_third_person_controller_place(asim_body_t *body) { + if (!trait_third_person_controller_moving) { + return; + } + object_t *run = trait_third_person_controller_run_object(body->obj); + if (run == NULL) { + return; + } + + vec4_t pos; + asim_body_get_pos(body->_body, &pos); + quat_t rot = trait_third_person_controller_facing(); + vec4_t off = vec4_apply_quat(body->offset, rot); + + transform_t *t = run->transform; + t->loc = (vec4_t){pos.x - off.x, pos.y - off.y, pos.z - off.z, 1.0}; + t->rot = rot; + transform_build_matrix(t); +} + +static bool trait_third_person_controller_move(asim_body_t *body) { + f32 s = math_sin(trait_third_person_controller_yaw); + f32 c = math_cos(trait_third_person_controller_yaw); + vec4_t forward = (vec4_t){-s, c, 0.0, 0.0}; + vec4_t right = (vec4_t){c, s, 0.0, 0.0}; + vec4_t dir = (vec4_t){0.0, 0.0, 0.0, 0.0}; + + if (keyboard_down("w")) { + dir = vec4_add(dir, forward); + } + if (keyboard_down("s")) { + dir = vec4_sub(dir, forward); + } + if (keyboard_down("d")) { + dir = vec4_add(dir, right); + } + if (keyboard_down("a")) { + dir = vec4_sub(dir, right); + } + + vec4_t vel; + asim_body_get_velocity(body->_body, &vel); + bool moving = dir.x != 0.0 || dir.y != 0.0; + if (moving) { + dir = vec4_norm(dir); + asim_body_set_velocity(body->_body, dir.x * TPC_SPEED, dir.y * TPC_SPEED, vel.z); + } + else { + asim_body_set_velocity(body->_body, 0.0, 0.0, vel.z); + } + + asim_body_set_rotation(body, trait_third_person_controller_facing()); + return moving; +} + +static void trait_third_person_controller_follow(asim_body_t *body) { + vec4_t pos; + asim_body_get_pos(body->_body, &pos); + + f32 size = math_sqrt(body->dimx * body->dimx + body->dimy * body->dimy + body->dimz * body->dimz); + f32 dist = (size > 0.0 ? size : 1.0) * TPC_CAMERA_DIST; + vec4_t look = trait_third_person_controller_look(); + vec4_t focus = (vec4_t){pos.x, pos.y, pos.z + body->dimz * TPC_CAMERA_FOCUS, 1.0}; + + transform_t *ct = scene_camera->base->transform; + ct->loc = (vec4_t){focus.x - look.x * dist, focus.y - look.y * dist, focus.z - look.z * dist, 1.0}; + ct->rot = quat_mult(quat_from_axis_angle(vec4_z_axis(), trait_third_person_controller_yaw), + quat_from_axis_angle(vec4_x_axis(), math_pi() / 2.0 + trait_third_person_controller_pitch)); + camera_object_build_mat(scene_camera); + g_context->ddirty = 2; +} + +static asim_body_t *trait_third_person_controller_body(object_t *o) { + asim_body_t *body = o->_->body; + if (body != NULL && body->shape == ASIM_SHAPE_BOX && body->mass > 0.0) { + return body; + } + script_physics_set_shape(o, ASIM_SHAPE_BOX); + return o->_->body; +} + +void trait_third_person_controller_run() { + object_t *o = script_get_object(trait_third_person_controller_object); + if (o == NULL) { + return; + } + + asim_body_t *body = trait_third_person_controller_body(o); + if (body == NULL) { + return; + } + + bool control = iron_mouse_is_locked(); + if (control && keyboard_started("escape")) { + iron_mouse_unlock(); + control = false; + } + else if (!control && mouse_started("left")) { + trait_third_person_controller_grab(); + control = iron_mouse_is_locked(); + } + + if (!control) { + trait_third_person_controller_set_moving(o, false); + return; + } + + trait_third_person_controller_rotate(); + trait_third_person_controller_set_moving(o, trait_third_person_controller_move(body)); + trait_third_person_controller_place(body); + trait_third_person_controller_follow(body); +} + +void trait_third_person_controller_stop() { + object_t *o = script_get_object(trait_third_person_controller_object); + if (o != NULL) { + trait_third_person_controller_set_moving(o, false); + } +}