diff --git a/apps/tools/twndel/data/bong.ogg b/apps/tools/twndel/data/bong.ogg new file mode 100644 index 0000000..491187a --- /dev/null +++ b/apps/tools/twndel/data/bong.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d21d0f0b782445db579d11e2506b24cd1ac9d664ee33aeaf807761aa7b6fd710 +size 4848 diff --git a/apps/tools/twndel/data/click.wav b/apps/tools/twndel/data/click.wav new file mode 100644 index 0000000..6dcf290 Binary files /dev/null and b/apps/tools/twndel/data/click.wav differ diff --git a/apps/tools/twndel/data/drop.ogg b/apps/tools/twndel/data/drop.ogg new file mode 100644 index 0000000..1293c6e --- /dev/null +++ b/apps/tools/twndel/data/drop.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac4d1cef7e936965cbf795852ca2020300b9e2ba7daa59f2bf4f1f7bf416218 +size 6411 diff --git a/apps/tools/twndel/data/point.png b/apps/tools/twndel/data/point.png index ceb77a6..faaa984 100644 --- a/apps/tools/twndel/data/point.png +++ b/apps/tools/twndel/data/point.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a94d5677f37025bb62bd91709e8f3066ff2ec9dbcf194089a62f1a06e275c8a -size 1217 +oid sha256:7ed596a24e5052a0f46d8f5f1dcaa1568b64e54317d96b5155ee1194abbcd1fd +size 1039 diff --git a/apps/tools/twndel/data/pop.wav b/apps/tools/twndel/data/pop.wav new file mode 100644 index 0000000..1129be4 Binary files /dev/null and b/apps/tools/twndel/data/pop.wav differ diff --git a/apps/tools/twndel/data/rip.wav b/apps/tools/twndel/data/rip.wav new file mode 100644 index 0000000..b9a7281 Binary files /dev/null and b/apps/tools/twndel/data/rip.wav differ diff --git a/apps/tools/twndel/data/selectin.ogg b/apps/tools/twndel/data/selectin.ogg new file mode 100644 index 0000000..7179aae --- /dev/null +++ b/apps/tools/twndel/data/selectin.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a612fd2f513d52a296906b41071c990f75defe56be3bee60d2d4e2423b51b8b +size 6901 diff --git a/apps/tools/twndel/data/selectout.ogg b/apps/tools/twndel/data/selectout.ogg new file mode 100644 index 0000000..63d3aad --- /dev/null +++ b/apps/tools/twndel/data/selectout.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad09146e4ea33b931b2f5dfb4051a4f1fe4a36f1a48c42c5e9269c292ae21214 +size 6767 diff --git a/apps/tools/twndel/data/snip.wav b/apps/tools/twndel/data/snip.wav new file mode 100644 index 0000000..6fa6450 Binary files /dev/null and b/apps/tools/twndel/data/snip.wav differ diff --git a/apps/tools/twndel/data/spooncap.wav b/apps/tools/twndel/data/spooncap.wav new file mode 100644 index 0000000..20de91f Binary files /dev/null and b/apps/tools/twndel/data/spooncap.wav differ diff --git a/apps/tools/twndel/data/switch.ogg b/apps/tools/twndel/data/switch.ogg new file mode 100644 index 0000000..eef91ea --- /dev/null +++ b/apps/tools/twndel/data/switch.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eeaf60c2abc7e1b715e2b032401bcbf6b3753b77f96b238f861a034a75515967 +size 7921 diff --git a/apps/tools/twndel/data/x.png b/apps/tools/twndel/data/x.png new file mode 100644 index 0000000..53a8c1a --- /dev/null +++ b/apps/tools/twndel/data/x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb4b6b05f3d511748b81d0ecf13535acb48e34bff0cd26f295086ad6cb3c73ab +size 1111 diff --git a/apps/tools/twndel/data/y.png b/apps/tools/twndel/data/y.png new file mode 100644 index 0000000..0bf6c03 --- /dev/null +++ b/apps/tools/twndel/data/y.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:024a6a6a5b52bded93dbc8d204f80e64ffb5c0714d4fcc80ee0d8d15fd9ab563 +size 1159 diff --git a/apps/tools/twndel/data/z.png b/apps/tools/twndel/data/z.png new file mode 100644 index 0000000..923965c --- /dev/null +++ b/apps/tools/twndel/data/z.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7157f0dc6743ee61fcdce291c83392fe3cdfa350d4abba4e87985ea1e9ea9ddc +size 1082 diff --git a/apps/tools/twndel/state.h b/apps/tools/twndel/state.h index fa9a745..09a776e 100644 --- a/apps/tools/twndel/state.h +++ b/apps/tools/twndel/state.h @@ -18,6 +18,8 @@ #define INVALID_OBJECT (OBJECT_LIMIT+1) #define INVALID_TEXTURE (TEXTURE_LIMIT+1) +#define CAMERA_ROTATION_SPEED 0.04f +#define SELECTION_SPHERE_RADIUS 32 typedef struct Operation { enum { @@ -40,7 +42,7 @@ typedef struct Point { /* lines have p2, p3 = INVALID_POINT */ /* points have p1, p2, p3 = INVALID_POINT */ typedef struct Face { - uint16_t p0, p1, p2, p3; + uint16_t p[4]; uint8_t texture; } Face; diff --git a/apps/tools/twndel/tool.c b/apps/tools/twndel/tool.c index f2c2ab7..7da5a02 100644 --- a/apps/tools/twndel/tool.c +++ b/apps/tools/twndel/tool.c @@ -54,6 +54,14 @@ static uint8_t push_texture(uint8_t object, char *texture) { } +static inline Vec3 point_to_vec3(uint8_t object, uint16_t point) { + Object *o = &state.objects[object]; + return (Vec3){ (o->position.x + state.points[point].x) / (float)POINTS_PER_METER, + (o->position.y + state.points[point].y) / (float)POINTS_PER_METER, + (o->position.z + state.points[point].z) / (float)POINTS_PER_METER }; +} + + static void render_object(uint8_t object) { Object *o = &state.objects[object]; @@ -63,24 +71,16 @@ static void render_object(uint8_t object) { for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { Face *f = &o->faces[fi]; /* quads */ - if (f->p3 != INVALID_POINT) { - Vec3 p0 = (Vec3){(float)POINTS_PER_METER / (o->position.x + state.points[f->p0].x), - (float)POINTS_PER_METER / (o->position.y + state.points[f->p0].y), - (float)POINTS_PER_METER / (o->position.z + state.points[f->p0].z) }; - Vec3 p1 = (Vec3){(float)POINTS_PER_METER / (o->position.x + state.points[f->p1].x), - (float)POINTS_PER_METER / (o->position.y + state.points[f->p1].y), - (float)POINTS_PER_METER / (o->position.z + state.points[f->p1].z) }; - Vec3 p2 = (Vec3){(float)POINTS_PER_METER / (o->position.x + state.points[f->p2].x), - (float)POINTS_PER_METER / (o->position.y + state.points[f->p2].y), - (float)POINTS_PER_METER / (o->position.z + state.points[f->p2].z) }; - Vec3 p3 = (Vec3){(float)POINTS_PER_METER / (o->position.x + state.points[f->p3].x), - (float)POINTS_PER_METER / (o->position.y + state.points[f->p3].y), - (float)POINTS_PER_METER / (o->position.z + state.points[f->p3].z) }; + if (f->p[3] != INVALID_POINT) { + Vec3 p0 = point_to_vec3(object, f->p[0]); + Vec3 p1 = point_to_vec3(object, f->p[1]); + Vec3 p2 = point_to_vec3(object, f->p[2]); + Vec3 p3 = point_to_vec3(object, f->p[3]); if (state.solid_display_mode) draw_quad(o->textures[f->texture], p0, p1, p2, p3, - (Rect){0}, + (Rect){0,0,32,32}, (Color){255,255,255,255} ); else { draw_line_3d(p0, p1, 1, (Color){255,255,255,255}); @@ -109,36 +109,169 @@ static uint8_t new_cube(Point pos, Point size) { uint16_t p6 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2); uint16_t p7 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2); - uint8_t txt = push_texture(object, "/data/default.png"); - push_face(object, p0, p1, p2, p3, txt); - push_face(object, p7, p6, p5, p4, txt); - push_face(object, p4, p5, p1, p0, txt); - push_face(object, p6, p7, p3, p2, txt); - push_face(object, p2, p1, p5, p6, txt); - push_face(object, p0, p3, p7, p4, txt); + uint8_t tex = push_texture(object, "/data/placeholder.png"); + push_face(object, p0, p1, p2, p3, tex); + push_face(object, p7, p6, p5, p4, tex); + push_face(object, p4, p5, p1, p0, tex); + push_face(object, p6, p7, p3, p2, tex); + push_face(object, p2, p1, p5, p6, tex); + push_face(object, p0, p3, p7, p4, tex); return object; } +static void process_camera_movement(void) { + input_action("camera_rotate_left", "A"); + input_action("camera_rotate_right", "D"); + input_action("camera_rotate_up", "W"); + input_action("camera_rotate_down", "S"); + + float horizontal_rotation = 0; + float vertical_rotation = 0; + + if (input_action_pressed("camera_rotate_left")) + horizontal_rotation -= CAMERA_ROTATION_SPEED; + if (input_action_pressed("camera_rotate_right")) + horizontal_rotation += CAMERA_ROTATION_SPEED; + if (input_action_pressed("camera_rotate_up")) + vertical_rotation -= CAMERA_ROTATION_SPEED; + if (input_action_pressed("camera_rotate_down")) + vertical_rotation += CAMERA_ROTATION_SPEED; + + Vec3 front = vec3_cross(state.camera_direction, (Vec3){0,1,0}); + + state.camera_position = vec3_rotate(state.camera_position, horizontal_rotation, (Vec3){0,1,0}); + state.camera_direction = vec3_rotate(state.camera_direction, horizontal_rotation, (Vec3){0,1,0}); + + Vec3 new_rot = vec3_rotate(state.camera_direction, vertical_rotation, front); + + /* only apply if it's in limits */ + if (fabsf(vec3_dot(new_rot, (Vec3){0,-1,0})) <= 0.99f) { + state.camera_position = vec3_rotate(state.camera_position, vertical_rotation, front); + state.camera_direction = new_rot; + } +} + + +static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) { + DrawCameraUnprojectResult pos_and_ray = draw_camera_unproject( + ctx.mouse_position, + state.camera_position, + state.camera_direction, + (Vec3){0, 1, 0}, + state.camera_is_orthographic ? 0 : CAMERA_FOV, + state.camera_zoom, + 100 ); + + /* step over every selectable object and find points closest to the view ray */ + /* by constructing triangles and finding their height, from perpendicular */ + uint16_t closest_point = INVALID_POINT; + uint8_t closest_obj = INVALID_OBJECT; + float closest_distance = INFINITY; + for (uint8_t obj = 0; obj < state.objects_sz; ++obj) { + Object *o = &state.objects[obj]; + + if (o->is_invisible) + continue; + + /* TODO: is it possible to skip repeated points? does it matter? */ + /* as we limit the point could we could actually have bool array preallocated for this */ + for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { + Face *f = &o->faces[fi]; + for (uint16_t pi = 0; pi < 4; ++pi) { + Vec3 p = point_to_vec3(obj, f->p[pi]); + Vec3 d = vec3_sub(pos_and_ray.position, p); + Vec3 b = vec3_cross(d, pos_and_ray.direction); + float ray_dist = vec3_length(b); + if (ray_dist > ((float)SELECTION_SPHERE_RADIUS / POINTS_PER_METER)) + continue; + Vec3 i = vec3_add(p, b); + float dist = vec3_length(i); + if (dist < closest_distance) { + closest_distance = dist; + closest_obj = obj; + closest_point = f->p[pi]; + } + } + } + } + + if (closest_point == INVALID_POINT) + return false; + + if (object_result) + *object_result = closest_obj; + if (point_result) + *point_result = closest_point; + + return true; +} + + void game_tick(void) { if (!init) { /* default state */ new_cube((Point){0,0,0}, (Point){POINTS_PER_METER,POINTS_PER_METER,POINTS_PER_METER}); - state.camera_position = (Vec3){10,5,10}; - state.camera_direction = vec3_norm(((Vec3){-10,-5,-10})); + state.camera_position = (Vec3){2,1,2}; + state.camera_direction = vec3_norm(((Vec3){-2,-1,-2})); + state.camera_zoom = 1.0f; init = true; } - input_action("toggle_display_mode", "C"); + input_action("toggle_display_mode", "Q"); + input_action("toggle_projection", "E"); - if (input_action_just_pressed("toggle_display_mode")) + if (input_action_just_pressed("toggle_display_mode")) { + audio_play("/data/click.wav", NULL, false, 0.7f, 0.0f); state.solid_display_mode = !state.solid_display_mode; + } + + if (input_action_just_pressed("toggle_projection")) { + audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f); + state.camera_is_orthographic = !state.camera_is_orthographic; + } + + process_camera_movement(); + + uint16_t point_select; uint8_t obj_select; + if (find_closest_point(&obj_select, &point_select)) + draw_billboard("/data/point.png", + point_to_vec3(obj_select, point_select), + (Vec2){0.05f, 0.05f}, + (Rect){0,0,16,16}, + (Color){255,255,255,255}, + false); + + /* axis helpers */ + /* idea: black out inactives when dragging points */ + /* idea: double selection of axes for diagonal edits */ + draw_line_3d((Vec3){0}, (Vec3){0,0,256}, 1, (Color){255,0,0,125}); + draw_billboard("/data/x.png", + (Vec3){0,0,2}, + (Vec2){0.1f, 0.1f}, + (Rect){0}, + (Color){255,0,0,255}, + false); + draw_line_3d((Vec3){0}, (Vec3){256,0,0}, 1, (Color){0,0,255,125}); + draw_billboard("/data/y.png", + (Vec3){2,0,0}, + (Vec2){0.1f, 0.1f}, + (Rect){0}, + (Color){0,0,255,255}, + false); + draw_line_3d((Vec3){0}, (Vec3){0,256,0}, 1, (Color){75,125,25,125}); + draw_billboard("/data/z.png", + (Vec3){0,1.5f,0}, + (Vec2){0.1f, 0.1f}, + (Rect){0}, + (Color){75,125,25,255}, + false); for (uint8_t obj = 0; obj < state.objects_sz; ++obj) render_object(obj); - draw_text("twndel\x03", (Vec2){0, 0}, 32, (Color){255,255,255,255}, NULL); + draw_text("twndel\x03", (Vec2){0, 2}, 32, (Color){255,255,255,200}, NULL); draw_camera( state.camera_position, state.camera_direction,