diff --git a/apps/tools/twndel/state.h b/apps/tools/twndel/state.h index 2af877d..ff89ded 100644 --- a/apps/tools/twndel/state.h +++ b/apps/tools/twndel/state.h @@ -50,7 +50,10 @@ typedef struct Point { /* points have p1, p2, p3 = INVALID_POINT */ typedef struct Face { uint16_t p[4]; + /* texture origin, as point on face plane, in absolute coordinates */ + int16_t tex_x, tex_y; uint8_t texture; + uint8_t tex_scale; } Face; typedef struct Object { @@ -82,12 +85,13 @@ typedef struct State { Point points[POINT_LIMIT]; uint16_t points_sz; - Object *objects; + Object objects[OBJECT_LIMIT]; uint8_t objects_sz; /* which axes are blocked and which are not */ /* order: x, y, z */ bool axis_mask[3]; + char *current_texture; } State; diff --git a/apps/tools/twndel/tool.c b/apps/tools/twndel/tool.c index 7e172f4..fa27115 100644 --- a/apps/tools/twndel/tool.c +++ b/apps/tools/twndel/tool.c @@ -8,13 +8,16 @@ /* grid-based, bounded space (65536 / POINTS_PER_METER meters), which allows for efficient storage */ /* 65534 point limit, 16 object limit, 2048 faces per object, 32 textures per object */ /* triangles and quads only */ +/* support for billboards and flat two sided quads */ /* texture painting */ /* bones with mesh animations, snapping to grid, with no weights */ /* billboard render to specified angles */ /* 1 point light primitive lighting */ +/* live edited textures are capped at 128x128 */ /* assumptions: */ /* up is always (0,1,0) */ +/* preallocations everywhere */ static State state; static bool init; @@ -24,8 +27,6 @@ static uint8_t new_object(const char *name) { if (state.objects_sz >= OBJECT_LIMIT) return INVALID_OBJECT; state.objects_sz++; - state.objects = SDL_realloc(state.objects, state.objects_sz * sizeof (*state.objects)); - SDL_memset(&state.objects[state.objects_sz-1], 0, sizeof (Object)); state.objects[state.objects_sz-1].name = SDL_strdup(name); return state.objects_sz-1; } @@ -40,10 +41,18 @@ static uint16_t new_point(int16_t x, int16_t y, int16_t z) { } -static uint16_t push_face(uint8_t object, uint16_t p0, uint16_t p1, uint16_t p2, uint16_t p3, uint8_t texture) { +static uint16_t push_face(uint8_t object, + uint16_t p0, uint16_t p1, uint16_t p2, uint16_t p3, + uint8_t texture, uint8_t tex_scale, int16_t tex_x, int16_t tex_y) +{ Object *o = &state.objects[object]; o->faces_sz++; - o->faces[o->faces_sz-1] = (Face) {.p = {p0, p1, p2, p3}, .texture = texture}; + o->faces[o->faces_sz-1] = (Face) { + .p = {p0, p1, p2, p3}, + .texture = texture, + .tex_scale = tex_scale, + .tex_x = tex_x, .tex_y = tex_y, + }; return o->faces_sz-1; } @@ -73,6 +82,22 @@ 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) { + 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)); + log_vec3(right, "right"); + log_vec3(up, "up"); + Vec3 proj_origin = vec3_add(vec3_scale(right, origin.x), vec3_scale(up, origin.y)); + log_vec3(proj_origin, "proj_origin"); + Vec3 local = vec3_sub(proj_origin, point); + log_vec3(local, "local"); + log_float(vec3_dot(local, right) / scale, "dot_x"); + log_float(vec3_dot(local, up) / scale, "dot_y"); + return (Vec2){ vec3_dot(local, right) * scale, vec3_dot(local, up) * scale }; +} + + static void render_object(uint8_t object) { Object *o = &state.objects[object]; @@ -81,6 +106,7 @@ 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->p[3] != INVALID_POINT) { Vec3 p0 = point_to_vec3(object, f->p[0]); @@ -88,11 +114,17 @@ static void render_object(uint8_t object) { 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], + if (state.solid_display_mode) { + Vec3 n = vec3_norm(vec3_cross(vec3_sub(p1, p0), vec3_sub(p2, p0))); + Vec2 to = { f->tex_x / (float)POINTS_PER_METER, f->tex_y / (float)POINTS_PER_METER, }; + 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, p0, p1, p2, p3, - (Rect){0,0,32,32}, + (Rect){tul.x, tdr.x, tdr.x - tul.x, tdr.y - tul.y}, (Color){255,255,255,255} ); + } else { draw_line_3d(p0, p1, 1, (Color){255,255,255,255}); draw_line_3d(p1, p2, 1, (Color){255,255,255,255}); @@ -100,9 +132,19 @@ static void render_object(uint8_t object) { draw_line_3d(p3, p0, 1, (Color){255,255,255,255}); } } else { - /* triangles */ - /* TODO: */ - SDL_assert_always(false); + 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]); + + // if (state.solid_display_mode) + // draw_triangle(state.current_texture, + // p0, p1, p2, ) + // else { + // draw_line_3d(p0, p1, 1, (Color){255,255,255,255}); + // draw_line_3d(p1, p2, 1, (Color){255,255,255,255}); + // draw_line_3d(p2, p3, 1, (Color){255,255,255,255}); + // draw_line_3d(p3, p0, 1, (Color){255,255,255,255}); + // } } } } @@ -121,12 +163,12 @@ static uint8_t new_cube(Point pos, Point size) { uint16_t p7 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2); 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); + push_face(object, p2, p3, p0, p1, tex, 128, 0, 0); + push_face(object, p5, p4, p7, p6, tex, 128, 0, 0); + push_face(object, p1, p0, p4, p5, tex, 128, 0, 0); + push_face(object, p6, p7, p3, p2, tex, 128, 0, 0); + push_face(object, p2, p1, p5, p6, tex, 128, 0, 0); + push_face(object, p0, p3, p7, p4, tex, 128, 0, 0); return object; } @@ -477,6 +519,42 @@ static void draw_axes(void) { } +static void display_textures(void) { + String list = file_read("/data/assets/", ":images"); + if (!list.data) + return; + + draw_rectangle((Rect){0, 480 - 50, 640, 480}, (Color){0,0,0,126}); + + char *selected = NULL; + char *saveptr = NULL; + int count = 0; + char const *part = SDL_strtokr(list.data, "\n", &saveptr); + do { + Rect box = (Rect){(float)count * 50, 480 - 48, 48, 48}; + draw_sprite(part, + box, + (Rect){0,0,64,64}, + (Color){255,255,255,255}, + 0, false, false, true); + count++; + + if (rect_intersects(box, (Rect){ctx.mouse_position.x, ctx.mouse_position.y, 1, 1})) + selected = SDL_strdup(part); + + } while ((part = SDL_strtokr(NULL, "\n", &saveptr))); + + if (selected) { + draw_text(selected, (Vec2){0, 480 - 48 - 24}, 24, (Color){255,255,255,255}, NULL); + if (input_action_just_pressed("select")) { + if (state.current_texture) SDL_free(state.current_texture); + state.current_texture = selected; + } else + SDL_free(selected); + } +} + + void game_tick(void) { if (!init) { /* default state */ @@ -529,6 +607,9 @@ void game_tick(void) { for (uint8_t obj = 0; obj < state.objects_sz; ++obj) render_object(obj); + if (state.solid_display_mode) + display_textures(); + draw_text("twndel\x03", (Vec2){0, 2}, 32, (Color){255,255,255,200}, NULL); draw_camera( state.camera_position, @@ -548,6 +629,6 @@ void game_end(void) { SDL_free(o->textures[t]); SDL_free(o->name); } - if (state.objects_sz != 0) - SDL_free(state.objects); + if (state.current_texture) + SDL_free(state.current_texture); }