From 0e075ec334d55c5d650469b1d813ade2819678a4 Mon Sep 17 00:00:00 2001
From: veclavtalica <veclavtalica@tutamail.com>
Date: Tue, 11 Mar 2025 04:58:44 +0300
Subject: [PATCH] /apps/tools/twndel: texture painting, face selection

---
 apps/tools/twndel/state.h |   9 ++-
 apps/tools/twndel/tool.c  | 146 +++++++++++++++++++++++++++++++++-----
 2 files changed, 138 insertions(+), 17 deletions(-)

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;
         }