diff --git a/apps/tools/twndel/state.h b/apps/tools/twndel/state.h index abcb362..4379d99 100644 --- a/apps/tools/twndel/state.h +++ b/apps/tools/twndel/state.h @@ -29,6 +29,7 @@ typedef struct Operation { enum { OPERATION_MOVE_POINT, + OPERATION_SET_TEXTURE, } kind; union { struct { @@ -38,6 +39,12 @@ typedef struct Operation { int16_t delta_z; uint8_t object; } move_point; + + struct { + uint16_t face; + int16_t delta_texture; + uint8_t object; + } set_texture; } data; } Operation; @@ -61,7 +68,7 @@ typedef struct Object { char *name; bool is_invisible; Point position; - char *textures[TEXTURE_LIMIT]; + char *textures[TEXTURE_LIMIT + 1]; uint8_t textures_sz; Point rotation; Face faces[FACE_LIMIT]; diff --git a/apps/tools/twndel/tool.c b/apps/tools/twndel/tool.c index 00d3a80..9bcc8d8 100644 --- a/apps/tools/twndel/tool.c +++ b/apps/tools/twndel/tool.c @@ -58,8 +58,13 @@ static uint16_t push_face(uint8_t object, static uint8_t push_texture(uint8_t object, char *texture) { - /* TODO: search and combine if it already exists */ Object *o = &state.objects[object]; + + /* check whether it's already here */ + for (uint8_t i = 0; i < o->textures_sz; ++i) + if (SDL_strcmp(o->textures[i], texture) == 0) + return i; + o->textures_sz++; o->textures[o->textures_sz-1] = SDL_strdup(texture); return o->textures_sz-1; @@ -114,7 +119,7 @@ static void render_object(uint8_t object) { Vec2 tul = project_texture_coordinate(to, n, p0, (float)f->tex_scale); Vec2 tdr = project_texture_coordinate(to, n, p2, (float)f->tex_scale); - draw_quad(state.current_texture, + draw_quad(o->textures[f->texture], p0, p1, p2, p3, (Rect){tul.x, tdr.x, tdr.x - tul.x, tdr.y - tul.y}, (Color){255,255,255,255} ); @@ -138,7 +143,7 @@ static void render_object(uint8_t object) { Vec2 tdr = project_texture_coordinate(to, n, p2, (float)f->tex_scale); if (state.solid_display_mode) - draw_triangle(state.current_texture, + draw_triangle(o->textures[f->texture], p0, p1, p2, tul, tdl, tdr, (Color){255,255,255,255}, @@ -337,6 +342,76 @@ static bool vector_plane_intersection(Vec3 o, Vec3 v, Vec3 p, Vec3 n, Vec3 *out) } +static bool find_closest_face(uint8_t* object_result, uint16_t *face_result) { + DrawCameraUnprojectResult cam = unproject_point(ctx.mouse_position); + + uint8_t closest_obj = INVALID_OBJECT; + uint16_t closest_face = INVALID_FACE; + float closest_distance = INFINITY; + + for (uint8_t oi = 0; oi < state.objects_sz; ++oi) { + Object *o = &state.objects[oi]; + for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { + Face *f = &o->faces[fi]; + + if (f->p[1] == INVALID_POINT) continue; + + Vec3 p0 = point_to_vec3(oi, f->p[0]); + Vec3 p1 = point_to_vec3(oi, f->p[1]); + Vec3 p2 = point_to_vec3(oi, f->p[2]); + + Vec3 n = vec3_cross(vec3_sub(p1, p0), vec3_sub(p2, p0)); + + /* culling */ + if (vec3_dot(state.camera_direction, n) > 0) continue; + + Vec3 i; + if (!vector_plane_intersection(cam.position, cam.direction, p0, n, &i)) + continue; + + float dist = vec3_length(vec3_sub(p0, i)); + if (dist >= closest_distance) continue; + + /* left normals are used to determine whether point lies to the left for all forming lines */ + Vec3 ln0 = vec3_cross(n, vec3_sub(p1, p0)); + if (vec3_dot(ln0, vec3_sub(p0, i)) > 0) continue; + + Vec3 ln1 = vec3_cross(n, vec3_sub(p2, p1)); + if (vec3_dot(ln1, vec3_sub(p1, i)) > 0) continue; + + if (f->p[3] == INVALID_POINT) { + /* triangle */ + Vec3 ln2 = vec3_cross(n, vec3_sub(p0, p2)); + if (vec3_dot(ln2, vec3_sub(p2, i)) > 0) continue; + + } else { + /* quad */ + Vec3 p3 = point_to_vec3(oi, f->p[3]); + Vec3 ln2 = vec3_cross(n, vec3_sub(p3, p2)); + if (vec3_dot(ln2, vec3_sub(p2, i)) > 0) continue; + + Vec3 ln3 = vec3_cross(n, vec3_sub(p0, p3)); + if (vec3_dot(ln3, vec3_sub(p3, i)) > 0) continue; + } + + closest_distance = dist; + closest_face = fi; + closest_obj = oi; + } + } + + if (closest_face == INVALID_FACE) + return false; + + if (object_result) + *object_result = closest_obj; + if (face_result) + *face_result = closest_face; + + return true; +} + + static void show_snap_lines(Vec3 p) { float step = 1.0f / ((float)POINTS_PER_METER / state.grid_snap_granularity); int const lines_per_side = (SNAP_LINES_SHOW - 1) / 2; @@ -448,6 +523,15 @@ static void reverse_operation_move_point(Operation *op) { } +static void reverse_operation_set_texture(Operation *op) { + SDL_assert(op->kind == OPERATION_SET_TEXTURE); + Object *o = &state.objects[op->data.set_texture.object]; + o->faces[op->data.set_texture.face].texture += op->data.set_texture.delta_texture; + audio_play("/data/drop.ogg", NULL, false, 0.4f, 0.0f); +} + + +/* TODO: reverse of this */ static void try_subdividing_from_moving(uint8_t object, uint16_t point) { Object *o = &state.objects[object]; @@ -487,7 +571,11 @@ static void process_operations(void) { switch (op->kind) { case OPERATION_MOVE_POINT: { reverse_operation_move_point(op); + break; + } + case OPERATION_SET_TEXTURE: { + reverse_operation_set_texture(op); break; } @@ -497,20 +585,45 @@ static void process_operations(void) { } if (!state.op_active) { - 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); + /* point dragging */ + if (!state.solid_display_mode) { + 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); - if (input_action_just_pressed("select")) - push_operation((Operation){ - .kind = OPERATION_MOVE_POINT, - .data = { .move_point = { .point = point_select, .object = obj_select } }, - }, true ); + if (input_action_just_pressed("select")) + push_operation((Operation){ + .kind = OPERATION_MOVE_POINT, + .data = { .move_point = { .point = point_select, .object = obj_select } }, + }, true ); + } + + /* texture setting */ + } else if (input_action_pressed("select")) { + uint8_t obj_select; uint16_t face_select; + if (find_closest_face(&obj_select, &face_select)) { + uint8_t new_tex = push_texture(obj_select, state.current_texture); + uint8_t cur_tex = state.objects[obj_select].faces[face_select].texture; + + if (new_tex != cur_tex) { + state.objects[obj_select].faces[face_select].texture = new_tex; + audio_play("/data/bong.ogg", NULL, false, 0.5f, 0.0f); + + push_operation((Operation){ + .kind = OPERATION_SET_TEXTURE, + .data = { .set_texture = { + .face = face_select, + .object = obj_select, + .delta_texture = cur_tex - new_tex, + }}, + }, false ); + } + } } } @@ -526,6 +639,7 @@ static void process_operations(void) { break; } + case OPERATION_SET_TEXTURE: default: (void)0; }