From bdabd04388272b6dfd3876c5a303964a2193f81a Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Tue, 11 Mar 2025 07:02:52 +0300 Subject: [PATCH] /apps/tools/twndel: reversable triangulation, fix to point finding with triangles --- apps/tools/twndel/state.h | 9 +++ apps/tools/twndel/tool.c | 121 ++++++++++++++++++++++++++++---------- 2 files changed, 100 insertions(+), 30 deletions(-) diff --git a/apps/tools/twndel/state.h b/apps/tools/twndel/state.h index 4379d99..fe2efd8 100644 --- a/apps/tools/twndel/state.h +++ b/apps/tools/twndel/state.h @@ -30,6 +30,7 @@ typedef struct Operation { enum { OPERATION_MOVE_POINT, OPERATION_SET_TEXTURE, + OPERATION_TRIANGULATE, } kind; union { struct { @@ -45,7 +46,15 @@ typedef struct Operation { int16_t delta_texture; uint8_t object; } set_texture; + + struct { + uint16_t old_face; + uint16_t new_face; + uint8_t object; + } triangulate; } data; + + bool chained; } Operation; typedef struct Point { diff --git a/apps/tools/twndel/tool.c b/apps/tools/twndel/tool.c index 9bcc8d8..88719d9 100644 --- a/apps/tools/twndel/tool.c +++ b/apps/tools/twndel/tool.c @@ -71,6 +71,17 @@ static uint8_t push_texture(uint8_t object, char *texture) { } +/* TODO: use tombstones instead? it would be easier to maintain, by a lot */ +/* note: make sure nothing depends on none */ +static void pop_face(uint8_t object, uint16_t face) { + Object *o = &state.objects[object]; + if (face != o->faces_sz-1) + o->faces[face] = o->faces[o->faces_sz-1]; + o->faces_sz--; +} + + + static void push_operation(Operation operation, bool active) { state.op_stack_ptr++; uint8_t op = state.op_stack_ptr % UNDO_STACK_SIZE; @@ -79,6 +90,15 @@ static void push_operation(Operation operation, bool active) { } +static void extend_operation(Operation operation) { + uint8_t op = state.op_stack_ptr % UNDO_STACK_SIZE; + Operation ext = state.op_stack[op]; + ext.chained = true; + state.op_stack[op] = operation; + push_operation(ext, state.op_active); +} + + 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, @@ -88,6 +108,7 @@ static inline Vec3 point_to_vec3(uint8_t object, uint16_t point) { static inline Vec2 project_texture_coordinate(Vec2 origin, Vec3 plane, Vec3 point, float scale) { + /* TODO: should be a better way */ Vec3 right = vec3_norm(vec3_cross(plane, (Vec3){0,1,0})); if (isnanf(right.x)) right = vec3_norm(vec3_cross(plane, (Vec3){1,0,0})); Vec3 up = vec3_norm(vec3_cross(plane, right)); @@ -217,8 +238,8 @@ static void process_camera_rotation(void) { static void process_camera_translation(void) { - Vec3 right = vec3_norm(vec3_cross(state.camera_direction, (Vec3){0,1,0})); - Vec3 up = vec3_norm(vec3_cross(state.camera_direction, right)); + Vec3 right = vec3_cross(state.camera_direction, (Vec3){0,1,0}); + Vec3 up = vec3_cross(state.camera_direction, right); Vec3 was = state.camera_position; if (input_action_pressed("camera_rotate_left")) @@ -286,6 +307,7 @@ static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) { 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]; @@ -297,6 +319,7 @@ static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) { for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { Face *f = &o->faces[fi]; for (uint16_t pi = 0; pi < 4; ++pi) { + if (f->p[pi] == INVALID_POINT) break; 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); @@ -415,6 +438,7 @@ static bool find_closest_face(uint8_t* object_result, uint16_t *face_result) { 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; + for (int l = -lines_per_side; l <= lines_per_side; ++l) { if (!state.axis_mask[0]) { Vec3 c = vec3_add(p, vec3_scale((Vec3){1,0,0}, step * (float)l)); @@ -424,6 +448,7 @@ static void show_snap_lines(Vec3 p) { 1, SNAP_LINES_COLOR); } + if (!state.axis_mask[1]) { Vec3 axis = fabsf(vec3_dot(state.camera_direction, (Vec3){0,0,1})) >= 0.5f ? (Vec3){1,0,0} : (Vec3){0,0,1}; Vec3 c = vec3_add(p, vec3_scale((Vec3){0,1,0}, step * (float)l)); @@ -433,6 +458,7 @@ static void show_snap_lines(Vec3 p) { 1, SNAP_LINES_COLOR); } + if (!state.axis_mask[2]) { Vec3 c = vec3_add(p, vec3_scale((Vec3){0,0,1}, step * (float)l)); draw_line_3d( @@ -531,9 +557,21 @@ static void reverse_operation_set_texture(Operation *op) { } +static void reverse_triangulation(Operation *op) { + SDL_assert(op->kind == OPERATION_TRIANGULATE); + Object *o = &state.objects[op->data.set_texture.object]; + Face *fn = &o->faces[op->data.triangulate.new_face]; + Face *fo = &o->faces[op->data.triangulate.old_face]; + fo->p[3] = fo->p[2]; + fo->p[2] = fn->p[1]; + pop_face(op->data.set_texture.object, op->data.triangulate.new_face); +} + + /* TODO: reverse of this */ static void try_subdividing_from_moving(uint8_t object, uint16_t point) { Object *o = &state.objects[object]; + bool not_first = false; for (uint16_t fi = 0; fi < o->faces_sz; ++fi) { Face *f = &o->faces[fi]; @@ -546,44 +584,66 @@ static void try_subdividing_from_moving(uint8_t object, uint16_t point) { new0.p[2] = f->p[(pi + 3) % 4]; new0.p[3] = INVALID_POINT; - push_face(object, - f->p[(pi + 1) % 4], - f->p[(pi + 2) % 4], - f->p[(pi + 3) % 4], - INVALID_POINT, - f->texture, - f->tex_scale, - f->tex_x, - f->tex_y); + uint16_t newf = push_face( + object, + f->p[(pi + 1) % 4], + f->p[(pi + 2) % 4], + f->p[(pi + 3) % 4], + INVALID_POINT, + f->texture, + f->tex_scale, + f->tex_x, + f->tex_y); + *f = new0; + + extend_operation((Operation){ + .kind = OPERATION_TRIANGULATE, + .data = { .triangulate = { .new_face = newf, .old_face = fi, .object = object} }, + .chained = not_first, + }); + + not_first = true; } } } } -static void process_operations(void) { - if (input_action_just_pressed("undo")) { - /* TODO: checks and defined limit */ - Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE]; - state.op_stack_ptr--; +static void process_undo(void) { + /* TODO: checks and defined limit */ + Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE]; + state.op_stack_ptr--; - switch (op->kind) { - case OPERATION_MOVE_POINT: { - reverse_operation_move_point(op); - break; - } - - case OPERATION_SET_TEXTURE: { - reverse_operation_set_texture(op); - break; - } - - default: - (void)0; - } + switch (op->kind) { + case OPERATION_MOVE_POINT: { + reverse_operation_move_point(op); + break; } + case OPERATION_SET_TEXTURE: { + reverse_operation_set_texture(op); + break; + } + + case OPERATION_TRIANGULATE: { + reverse_triangulation(op); + break; + } + + default: + (void)0; + } + + /* pop another if they're chained together */ + if (op->chained) process_undo(); +} + + +static void process_operations(void) { + if (input_action_just_pressed("undo")) + process_undo(); + if (!state.op_active) { /* point dragging */ if (!state.solid_display_mode) { @@ -640,6 +700,7 @@ static void process_operations(void) { } case OPERATION_SET_TEXTURE: + case OPERATION_TRIANGULATE: default: (void)0; }