#ifndef prototype1_src_l2_anne_r4_h
#define prototype1_src_l2_anne_r4_h

#include "../alice/assets.h"
#include "../../../gen/l1/pixel_masses.h"
#include "../marie/rasterization.h"
#include "../marie/texture_processing.h"
#include <math.h>

/* generating my cool textures2 */

#include "../../../gen/l1/VecAndSpan_vec2.h"
// todo: move to marie
void TextureDataR8_pixel_maxing(TextureDataR8* self, S32 x, S32 y, U8 val) {
    if (x < 0 || y < 0 || (size_t)x >= self->width)
        return;
    size_t p = (size_t)x + (size_t)y * self->width;
    if (p >= self->pixels.len)
        return;
    U8 b = *TextureDataR8_at(self, x, y);
    *TextureDataR8_mat(self, x, y) = MAX_U8(b, val);
}

// todo: move to marie
U8 a_small_cute_gradient(float r_cut, float r_decay, float dist) {
    return dist > r_cut ? 0 : (dist < r_decay) ? 255 : (U8)roundf( 255.f * (r_cut - dist) / (r_cut - r_decay) );
}

// todo: move it to marie
void TextureDataR8_draw_spot_maxing(TextureDataR8* self, vec2 v, float r_cut, float r_decay) {
    S32 sx = (S32)roundf(v.x - .5f);
    S32 sy = (S32)roundf(v.y - .5f);
    S32 k = (S32)ceilf(r_cut);
    for (S32 i = sx-k; i <= sx+k; i++) {
        for (S32 j = sy-k; j <= sy+k; j++) {
            float dx = 0.5f + (float)i - v.x;
            float dy = 0.5f + (float)j - v.y;
            float dist = sqrtf(dx*dx + dy*dy);
            TextureDataR8_pixel_maxing(self, i, j, a_small_cute_gradient(r_cut, r_decay, dist));
        }
    }
}


// todo: move to marie, not used here
vec2 perimeter_line_get_thorn_on_vertex(Spanvec2 P, size_t i, float thickness) {
    assert(P.len >= 3 && i < P.len);
    vec2 A = *Spanvec2_at(P, i ? i - 1 : P.len - 1);
    vec2 B = *Spanvec2_at(P, i);
    vec2 C = *Spanvec2_at(P, i == P.len - 1 ? 0 : i + 1);
    vec2 b = vec2_normalize(vec2_minus_vec2(A, B));
    vec2 f = vec2_minus_vec2(C, B);
    float cr = b.x * f.y - b.y * f.x;
    float dt = b.x * f.x + b.y * f.y;
    float a = M_PIf + atan2f(cr, dt);
    float t = M_PI_2f + a / 2;
    return vec2_mul_scal(mat2_mul_vec2(marie_2d_rot_mat2(t), b), thickness / sinf(t));
}

// todo: move to marie
/* It is assumed that A != B */
float distance_to_segment(vec2 A, vec2 B, vec2 P) {
    vec2 seg = vec2_minus_vec2(B, A);
    vec2 tg = vec2_minus_vec2(P, A);
    float t = vec2_dot(seg, tg) / vec2_dot(seg, seg);
    float c = marie_clamp_float(t, 0, 1);
    vec2 closest = vec2_add_vec2(A, vec2_mul_scal(seg, c));
    float len = vec2_length(vec2_minus_vec2(P, closest));
    return len;
}

// todo: move to marie
typedef struct {
    TextureDataR8* texture;
    vec2 A;
    vec2 B;
    float r_cut;
    float r_decay;
} TextureDataR8_draw_perimeter_maxing_H_DrawGuest;

// todo: move to marie
void TextureDataR8_draw_perimeter_maxing_h_draw_guest(void* ug, S32 x, S32 y, vec4 attr) {
    TextureDataR8_draw_perimeter_maxing_H_DrawGuest* g = ug;
    if (TextureDataR8_is_inside(g->texture, x, y)) {
        vec2 P = {attr.x, attr.y};
        U8 clr = a_small_cute_gradient(g->r_cut, g->r_decay, distance_to_segment(g->A, g->B, P));
        TextureDataR8_pixel_maxing(g->texture, x, y, clr);
    }
}

// todo: move to marie
void TextureDataR8_draw_perimeter_maxing_h_draw_triangle(
    TextureDataR8_draw_perimeter_maxing_H_DrawGuest* aboba, vec2 a, vec2 b, vec2 c
    ) {
    marie_rasterize_triangle_with_attr((MariePlaneVertAttr){a, {a.x, a.y, 0, 0}},
            (MariePlaneVertAttr){b, {b.x, b.y, 0, 0}}, (MariePlaneVertAttr){c, {c.x, c.y, 0, 0}},
            (FnMarieRasterizerCallback){TextureDataR8_draw_perimeter_maxing_h_draw_guest, aboba});
}


// todo: move to marie
/* It is assumed that P[i] != P[i + 1] foreach i from 0 to P.len - 1 */
void TextureDataR8_draw_perimeter_maxing(TextureDataR8* self, Spanvec2 P) {
    float r_cut = 5;
    float r_decay = 2;
    for (size_t i = 0; i < P.len; i++) {
        size_t ia = i == P.len - 1 ? 0 : i + 1;
        vec2 A = *Spanvec2_at(P, i);
        vec2 B = *Spanvec2_at(P, ia);
        vec2 hA = perimeter_line_get_thorn_on_vertex(P, i, r_cut);
        vec2 hB = perimeter_line_get_thorn_on_vertex(P, ia, r_cut);
        vec2 q0 = vec2_add_vec2(A, hA);
        vec2 q1 = vec2_add_vec2(B, hB);
        vec2 q2 = vec2_minus_vec2(B, hB);
        vec2 q3 = vec2_minus_vec2(A, hA);
        TextureDataR8_draw_perimeter_maxing_H_DrawGuest aboba = { self, A, B, r_cut, r_decay };
        TextureDataR8_draw_perimeter_maxing_h_draw_triangle(&aboba, q0, q1, q2);
        TextureDataR8_draw_perimeter_maxing_h_draw_triangle(&aboba, q0, q2, q3);
    }
}

typedef struct {
    vec2 bl;
    vec2 tr;
    float height;
    float brd;
} Wimbzle;

#include "../../../gen/l1/eve/r0/VecWimbzle.h"

typedef struct {
    vec2 center;
    float rad;
    float hc;
} Nibzle;

#include "../../../gen/l1/eve/r0/VecNibzle.h"

typedef struct {
    VecWimbzle wimbzles;
    VecNibzle nibzles;
} Bublazhuzhka;

Bublazhuzhka Bublazhuzhka_new() {
    return (Bublazhuzhka){.wimbzles = VecWimbzle_new(), .nibzles = VecNibzle_new()};
}

void Bublazhuzhka_drop(Bublazhuzhka self) {
    VecWimbzle_drop(self.wimbzles);
    VecNibzle_drop(self.nibzles);
}

void Bublazhuzhka_TextureDataR8_draw_maxing(const Bublazhuzhka* self, TextureDataR8* canvas, mat3x2 trop) {
    for (size_t i = 0; i < self->wimbzles.len; i++) {
        Wimbzle rect = *VecWimbzle_at(&self->wimbzles, i);
        vec2 B = {rect.tr.x + rect.brd, rect.tr.y + rect.brd};
        vec2 C = {rect.bl.x - rect.brd, rect.bl.y - rect.brd};
        vec2 A = {C.x, B.y};
        vec2 D = {B.x, C.y};
        vec2 F = rect.tr;
        vec2 G = rect.bl;
        vec2 E = {G.x, F.y};
        vec2 H = {F.x, G.y};

        vec2 p1[4] = {mat3x2_mul_vec3(trop, vec2_and_one(A)), mat3x2_mul_vec3(trop, vec2_and_one(B)),
            mat3x2_mul_vec3(trop, vec2_and_one(D)), mat3x2_mul_vec3(trop, vec2_and_one(C))};
        TextureDataR8_draw_perimeter_maxing(canvas, (Spanvec2){.data = p1, ARRAY_SIZE(p1)});
        vec2 p2[4] = {mat3x2_mul_vec3(trop, vec2_and_one(E)), mat3x2_mul_vec3(trop, vec2_and_one(F)),
            mat3x2_mul_vec3(trop, vec2_and_one(H)), mat3x2_mul_vec3(trop, vec2_and_one(G))};
        TextureDataR8_draw_perimeter_maxing(canvas, (Spanvec2){.data = p2, ARRAY_SIZE(p2)});
    }
    for (size_t i = 0; i < self->nibzles.len; i++) {
        Nibzle sphere = *VecNibzle_at(&self->nibzles, i);
        Vecvec2 p = Vecvec2_new_zeroinit(13);
        for (int j = 0; j < 13; j++) {
            float a = (float)j * 2 * M_PIf / 13;
            *Vecvec2_mat(&p, j) = mat3x2_mul_vec3(trop, vec2_and_one(vec2_add_vec2(sphere.center,
                vec2_mul_scal(marie_trigonom_circle(a), sphere.rad))));
        }
        TextureDataR8_draw_perimeter_maxing(canvas, Vecvec2_to_span(&p));
        Vecvec2_drop(p);
        TextureDataR8_draw_spot_maxing(canvas, mat3x2_mul_vec3(trop, vec2_and_one(sphere.center)), 3, 1);
    }
}

Bublazhuzhka fill_rectangle_with_crap(float w, float h) {
    S32 k = MAX_S32(0, (S32)floorf((w/h + 0.1f) / 0.7f));
    VecWimbzle wimbzles = VecWimbzle_new_zeroinit(k);
    VecNibzle nibzles = VecNibzle_new_zeroinit(2 * k);
    float start = k != 1 ? h * 0.2f : ((w - 0.2f * h) / 2);
    float d = k > 1 ? ((w - h * 0.4f - h * 0.2f * (float)k) / (float)(k - 1)) : 0;
    for (S32 i = 0; i < k; i++) {
        float x = start + (d + h * 0.2f) * (float)i;
        *VecWimbzle_mat(&wimbzles, i) = (Wimbzle){.bl = {x + 0.02f * h, 0.27f * h}, .tr = {x + 0.18f * h, h * 0.73f}, .height = h * 0.03f, .brd = h * 0.03f};
        /* hc is a height coefficient*/
        *VecNibzle_mat(&nibzles, 2 * i) = (Nibzle){.center = {x + 0.10f * h, 0.11f * h}, .rad = h * 0.05f, .hc = 0.75f};
        *VecNibzle_mat(&nibzles, 2 * i + 1) = (Nibzle){.center = {x + 0.10f * h, 0.89f * h}, .rad = h * 0.05f, .hc = 0.75f};
    }
    return (Bublazhuzhka){.wimbzles = wimbzles, .nibzles = nibzles};
}

vec2 Bublazhuzhka_get_derivative(const Bublazhuzhka* self, vec2 v) {
    vec2 sum = { 0 };
    for (size_t i = 0; i < self->wimbzles.len; i++) {
        Wimbzle rect = *VecWimbzle_at(&self->wimbzles, i);
        vec2 B = {rect.tr.x + rect.brd, rect.tr.y + rect.brd};
        vec2 C = {rect.bl.x - rect.brd, rect.bl.y - rect.brd};
        vec2 A = {C.x, B.y};
        vec2 D = {B.x, C.y};
        vec2 F = rect.tr;
        vec2 G = rect.bl;
        vec2 E = {G.x, F.y};
        vec2 H = {F.x, G.y};
        float slp = rect.height / rect.brd;
        if (A.x < v.x && v.x < E.x && marie_surface(E, A, v) > 0 && marie_surface(C, G, v) > 0) {
            sum.x += slp;
        } else if (F.x < v.x && v.x < B.x && marie_surface(B, F, v) > 0 && marie_surface(H, D, v) > 0) {
            sum.x -= slp;
        } else if (C.y < v.y && v.y < G.y && marie_surface(G, C, v) > 0 && marie_surface(D, H, v) > 0) {
            sum.y += slp;
        } else if (F.y < v.y && v.y < B.y && marie_surface(A, E, v) > 0 && marie_surface(F, B, v) > 0) {
            sum.y -= slp;
        }
    }
    for (size_t i = 0; i < self->nibzles.len; i++) {
        Nibzle sphere = *VecNibzle_at(&self->nibzles, i);
        float sq_h = pow2f(sphere.rad) - pow2f(v.x - sphere.center.x) - pow2f(v.y - sphere.center.y);
        if (sq_h <= 0)
            continue;
        float w = sphere.hc / sqrtf(sq_h);
        sum = vec2_add_vec2(sum, vec2_mul_scal(vec2_minus_vec2(sphere.center, v), w));
    }
    return sum;
}

cvec3 Bublazhuzhka_get_color(const Bublazhuzhka* self, vec2 v) {
    float flats = 0;
    for (size_t i = 0; i < self->wimbzles.len; i++) {
        Wimbzle rect = *VecWimbzle_at(&self->wimbzles, i);
        vec2 B = {rect.tr.x + rect.brd, rect.tr.y + rect.brd};
        vec2 C = {rect.bl.x - rect.brd, rect.bl.y - rect.brd};
        vec2 A = {C.x, B.y};
        vec2 D = {B.x, C.y};
        vec2 F = rect.tr;
        vec2 G = rect.bl;
        vec2 E = {G.x, F.y};
        vec2 H = {F.x, G.y};
        float slp = rect.height / rect.brd;
        if (A.x < v.x && v.x < E.x && marie_surface(E, A, v) > 0 && marie_surface(C, G, v) > 0) {
            return (cvec3){100, 30, 40};
        } else if (F.x < v.x && v.x < B.x && marie_surface(B, F, v) > 0 && marie_surface(H, D, v) > 0) {
            return (cvec3){70, 60, 45};
        } else if (C.y < v.y && v.y < G.y && marie_surface(G, C, v) > 0 && marie_surface(D, H, v) > 0) {
            return (cvec3){10, 100, 70};
        } else if (F.y < v.y && v.y < B.y && marie_surface(A, E, v) > 0 && marie_surface(F, B, v) > 0) {
            return (cvec3){25, 50, 110};
        } else if (E.x <= v.x && v.x <= F.x && G.y <= v.y && v.y <= F.y) {
            flats += rect.height;
        }
    }
    for (size_t i = 0; i < self->nibzles.len; i++) {
        Nibzle sphere = *VecNibzle_at(&self->nibzles, i);
        float sq_h = pow2f(sphere.rad) - pow2f(v.x - sphere.center.x) - pow2f(v.y - sphere.center.y);
        if (sq_h >= 0)
            return (cvec3){120, 120, 120};
    }
    U8 p = (U8)roundf((1.f - expf(flats)) * 60);
    return (cvec3){121 - p * 2, 30 + p, 65 - p};
}

cvec4 compress_normal_vec_into_norm_texel(vec3 n) {
    return (cvec4){(U32)roundf(255 * (n.x + 1) / 2), (U32)roundf(255 * (n.y + 1) / 2), (U32)roundf(255 * (n.z + 1) / 2), 255};
}


typedef struct {
    /* (guest, param) -> normal vector in model space */
    vec3 (*fn)(void*, vec2);
    void* guest;
} FnNormalVectorGenCallback;

typedef struct {
    TextureDataR8G8B8A8* tex;
    FnNormalVectorGenCallback my_client;
} draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest;

void draw_polygon_on_normal_texture_smooth_param_surf_h_draw_cb(void* ug, S32 x, S32 y, vec4 attr) {
    draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest* g = ug;
    vec3 normal = g->my_client.fn(g->my_client.guest, (vec2){attr.x, attr.y});
    *TextureDataR8G8B8A8_mat(g->tex, x, y) = compress_normal_vec_into_norm_texel(normal);
}

void draw_polygon_on_normal_texture_smooth_param_surf(
    TextureDataR8G8B8A8* tex, vec2 pa, vec2 pb, vec2 pc, mat3x2 trop, FnNormalVectorGenCallback cb
    ) {
    draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest aboba = {.tex = tex, .my_client = cb};
    // todo: generate rasterization function (with different precision + different attributes)
    marie_rasterize_triangle_with_attr(
        (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pa)), .attr = {pa.x, pa.y, 0, 0} },
        (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pb)), .attr = {pb.x, pb.y, 0, 0} },
        (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pc)), .attr = {pc.x, pc.y, 0, 0} },
        (FnMarieRasterizerCallback){draw_polygon_on_normal_texture_smooth_param_surf_h_draw_cb, (void*)&aboba});
}


typedef struct {
    /* (guest, param) -> normal vector in model space */
    vec3 (*fn)(void*, vec3);
    void* guest;
} FnNormalVectorGenExaggParamCallback;

typedef struct {
    TextureDataR8G8B8A8* tex;
    FnNormalVectorGenExaggParamCallback my_client;
    mat3 BNT_trans;
} draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest;

void draw_polygon_on_normal_texture_exaggerated_param_surf_draw_cb(void* ug, S32 x, S32 y, vec4 attr) {
    draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest* g = ug;
    vec3 normal = g->my_client.fn(g->my_client.guest, (vec3){attr.x, attr.y, attr.z});
    vec3 tang_normal = mat3_mul_vec3(g->BNT_trans, normal);
    *TextureDataR8G8B8A8_mat(g->tex, x, y) = compress_normal_vec_into_norm_texel(tang_normal);
}

/* We can't derive texture coordinates from parameter space coordinates, you have to do it yourself.
 * Also, we have to convert normal vector in world space to normal space in tangent space.
 * You specify an orthogonal basis of tangent space of that triangle: BNT - { tangent_U, normal vector tangent_ } */
void draw_polygon_on_normal_texture_nat_cords_exaggerated_param_surf(
    TextureDataR8G8B8A8* tex, vec2 ta, vec2 tb, vec2 tc, vec3 pa, vec3 pb, vec3 pc, FnNormalVectorGenExaggParamCallback cb,
    mat3 BNT
    ) {
    draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest aboba = {.tex = tex, .my_client = cb,
        .BNT_trans = mat3_transpose(BNT)};
    marie_rasterize_triangle_with_attr(
    (MariePlaneVertAttr){.pos = ta, .attr = {pa.x, pa.y, pa.z, 0} },
    (MariePlaneVertAttr){.pos = tb, .attr = {pb.x, pb.y, pb.z, 0} },
    (MariePlaneVertAttr){.pos = tc, .attr = {pc.x, pc.y, pc.z, 0} },
    (FnMarieRasterizerCallback){draw_polygon_on_normal_texture_exaggerated_param_surf_draw_cb, (void*)&aboba});
}
// todo: add a version for that function with non-native coordinate system (on vertex) (like I did with absolutely flat surface)
// todo: also, maybe, add a function to derive BNT and do cool stuff with trop mat3x2


typedef struct {
    TextureDataR8G8B8A8* tex;
} draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest;

void draw_polygon_on_normal_texture_absolutely_flat_h_draw_cb(void* ug, S32 x, S32 y, vec4 attr) {
    draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest* g = ug;
    *TextureDataR8G8B8A8_mat(g->tex, x, y) = compress_normal_vec_into_norm_texel((vec3){0, 1, 0});
}

void draw_polygon_on_normal_texture_nat_cords_absolutely_flat(TextureDataR8G8B8A8* tex,
    vec2 ta, vec2 tb, vec2 tc
    ) {
    draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest aboba = {tex};
    marie_rasterize_triangle_with_attr((MariePlaneVertAttr){.pos = ta}, (MariePlaneVertAttr){.pos = tb},
        (MariePlaneVertAttr){.pos = tc}, (FnMarieRasterizerCallback){
            .fn = draw_polygon_on_normal_texture_absolutely_flat_h_draw_cb, .guest = (void*)&aboba});
}

// todo: replace it with a "color everything in one color" function
void draw_polygon_on_normal_texture_absolutely_flat(TextureDataR8G8B8A8* tex,
                                                    vec2 pa, vec2 pb, vec2 pc, mat3x2 trop
) {
    draw_polygon_on_normal_texture_nat_cords_absolutely_flat(tex, mat3x2_mul_vec3(trop, vec2_and_one(pa)),
        mat3x2_mul_vec3(trop, vec2_and_one(pb)), mat3x2_mul_vec3(trop, vec2_and_one(pc)));
}


typedef struct {
    /* (guest, param) -> height gradient */
    vec2 (*fn)(void*, vec2);
    void* guest;
} FnHeightMapGradFlatSurfCallback;

typedef struct {
    FnHeightMapGradFlatSurfCallback my_client;
} draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest;

vec3 draw_polygon_on_normal_texture_flat_param_surf_h_draw_cb(void* ug, vec2 p) {
    draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest* g = ug;
    vec2 grad = g->my_client.fn(g->my_client.guest, p);
    return marie_normal_from_tang_space_gradient(grad.x, grad.y);  // todo: remove this cluster, while leaving only this function where it's nee
}

/* The simplest case of normal texture generation: for a smooth flat surface of a polygon */
void draw_polygon_on_normal_texture_flat_param_surf(TextureDataR8G8B8A8* tex, vec2 pa, vec2 pb, vec2 pc, mat3x2 trop,
    FnHeightMapGradFlatSurfCallback height_map_cb
    ) {
    draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest aboba = {height_map_cb};
    draw_polygon_on_normal_texture_smooth_param_surf(tex, pa, pb, pc, trop, (FnNormalVectorGenCallback){
        .fn = draw_polygon_on_normal_texture_flat_param_surf_h_draw_cb, .guest = (void*)&aboba});
}


typedef struct {
    TextureDataR8G8B8A8* self;
    const Bublazhuzhka* bublazhuzhka;
} TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka_H_DrawGuest;

void TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka_h_draw_guest(void* ug, S32 x, S32 y, vec4 attr) {
    TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka_H_DrawGuest *g = ug;
    cvec3 clr = Bublazhuzhka_get_color(g->bublazhuzhka, (vec2){attr.x, attr.y});
    if (TextureDataR8G8B8A8_is_inside(g->self, x, y))
        *TextureDataR8G8B8A8_mat(g->self, x, y) = (cvec4){clr.x, clr.y, clr.z, 255};
}

/* Not natural coordinates. Needs translation operator to convert
 * from parameter space*/
void TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka(
    TextureDataR8G8B8A8* self, const Bublazhuzhka* bublazhuzhka, MarieTriangle param_triangle, mat3x2 trop
    ) {
    TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka_H_DrawGuest aboba =
        {self, bublazhuzhka};
    MarieTriangleAttr natural = MarieTriangle_goto_nat_cords_pres_par(param_triangle, trop);
    // todo: continue from here
    marie_rasterize_triangle_with_attr(
        natural.v0, natural.v1, natural.v2,
    (FnMarieRasterizerCallback){.fn = TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka_h_draw_guest, .guest = &aboba});
}



// todo: rewrite this crrp (again)
TextureDataR8G8B8A8 generate_tex_template_for_one_fourth_of_a_cylinder(float s_resol, float w, float r, U32 k) {
    assert(k >= 1);
    const float a = M_PI_2f / (float)k;
    const float l = 2 * r * sinf(M_PI_4f / (float)k);
    size_t width_pix = (size_t)ceilf(s_resol * (2 * r + w));
    size_t height_pix = (size_t)ceilf(s_resol * (2 * r + (float)k * l));
    vec2 cord_resol = {(float)width_pix / (2 * r + w), (float)height_pix / (2 * r + (float)k * l)};
    const vec2 v0tex = {r, r};
    const vec2 v1tex = {r + w, r};
    const vec2 v4tex = {r, 0};
    const vec2 v5tex = {r + w, 0};
    TextureDataR8G8B8A8 res = TextureDataR8G8B8A8_new(width_pix, height_pix);
    mat3x2 cord_resol_trop = (mat3x2){.x.x = cord_resol.x, .y.y = cord_resol.y};

    vec3 color_1 = (vec3){0.3f, 0.5f, 0.1f};
    TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, color_1, (MarieTriangle){v0tex, v4tex, v5tex}, cord_resol_trop);
    TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, color_1, (MarieTriangle){v0tex, v5tex, v1tex}, cord_resol_trop);
    vec3 color_2 = (vec3){0.1f, 0.2f, 0.8f};
    vec3 color_3 = (vec3){0.2f, 0.3f, 0.9f};
    vec3 color_4 = (vec3){0.1f, 0.5f, 0.7f};
    vec3 color_5 = (vec3){0.7f, 0.05f, 0.2f};
    for (size_t i = 1; i <= k; i++) {
        vec2 A = (vec2){r - r * sinf(a * (float)i), r + r * cosf(a * (float)i)};
        vec2 B = (vec2){r - r * sinf(a * (float)(i-1)), r + r * cosf(a * (float)(i-1))};
        TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, color_2, (MarieTriangle){v0tex, B, A}, cord_resol_trop);
    }
    for (size_t i = 1; i <= k; i++) {
        vec2 A = (vec2){r + w + r * sinf(a * (float)i), r + r * cosf(a * (float)i)};
        vec2 B = (vec2){r + w + r * sinf(a * (float)(i-1)), r + r * cosf(a * (float)(i-1))};
        TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, color_3, (MarieTriangle){v1tex, A, B}, cord_resol_trop);
    }
    for (size_t i = 1; i <= k; i++) {
        vec2 A = (vec2){r, 2 * r + (float)(i) * l};
        vec2 B = (vec2){r, 2 * r + (float)(i-1) * l};
        vec2 C = (vec2){r + w, 2 * r + (float)(i-1) * l};
        vec2 D = (vec2){r + w, 2 * r + (float)(i) * l};
        vec3 c = i % 2 ? color_4 : color_5;
        // todo: replace this crrp with something more normal
        TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, c, (MarieTriangle){A, B, C}, cord_resol_trop);
        TextureDataR8G8B8A8_draw_parametrized_triangle_of_one_color(&res, c, (MarieTriangle){A, C, D}, cord_resol_trop);
    }

    Bublazhuzhka crap_on_back_side = fill_rectangle_with_crap(w, r);
    mat3x2 back_side_trop = (mat3x2){.x.x = cord_resol.x, .y.y = cord_resol.y, .z = vec2_mul_vec2((vec2){r, r}, cord_resol)};
    TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka(&res, &crap_on_back_side, (MarieTriangle){{0, r}, {0, 0}, {w, 0}}, back_side_trop);
    TextureDataR8G8B8A8_draw_triang_part_bublazhuzhka(&res, &crap_on_back_side, (MarieTriangle){{0, r}, {w, 0}, {w, r}}, back_side_trop);
    Bublazhuzhka_drop(crap_on_back_side);
    return res;
}

/* Use it as a callback in normal map drawing functions that work with smooth (smooth / flat / cylindrical)
 * height maps. Guest pointer is of type Bublazhuzhka* */
vec2 height_map_cb_that_uses_bublazhuzhka(void* ug, vec2 v) {
    Bublazhuzhka* bzh = ug;
    return Bublazhuzhka_get_derivative(bzh, v);
}

// todo: rewrite this crrp and merge it with other one-fourth-of-a-cylinder generiting functions
TextureDataR8G8B8A8 generate_normal_tex_for_one_fourth_of_a_cylinder(float s_resol, float w, float r, U32 k) {
    assert(k >= 1);
    const float a = M_PI_2f / (float)k;
    const float l = 2 * r * sinf(M_PI_4f / (float)k);
    size_t width_pix = (size_t)ceilf(s_resol * (2 * r + w));
    size_t height_pix = (size_t)ceilf(s_resol * (2 * r + (float)k * l));
    vec2 cord_resol = {(float)width_pix / (2 * r + w), (float)height_pix / (2 * r + (float)k * l)};
    const vec2 v0tex = {r, r};
    const vec2 v1tex = {r + w, r};
    // const vec2 v2tex = {r, 2 * r};
    // const vec2 v3tex = {r + w, 2 * r};
    const vec2 v4tex = {r, 0};
    const vec2 v5tex = {r + w, 0};
    TextureDataR8G8B8A8 res = TextureDataR8G8B8A8_new(width_pix, height_pix);

    Bublazhuzhka crap_on_the_back_side = fill_rectangle_with_crap(w, r);
    mat3x2 trop_back_side = {.x.x = cord_resol.x, .y.y = cord_resol.y, .z = vec2_mul_vec2((vec2){r, r}, cord_resol)};
    draw_polygon_on_normal_texture_flat_param_surf(&res, (vec2){0, 0}, (vec2){w, 0}, (vec2){w, r}, trop_back_side,
        (FnHeightMapGradFlatSurfCallback){.fn = height_map_cb_that_uses_bublazhuzhka, .guest = &crap_on_the_back_side});
    draw_polygon_on_normal_texture_flat_param_surf(&res, (vec2){0, 0}, (vec2){0, r}, (vec2){w, r}, trop_back_side,
        (FnHeightMapGradFlatSurfCallback){.fn = height_map_cb_that_uses_bublazhuzhka, .guest = &crap_on_the_back_side});
    Bublazhuzhka_drop(crap_on_the_back_side);

    mat3x2 str = {.x.x = cord_resol.x, .y.y = cord_resol.y};
    draw_polygon_on_normal_texture_absolutely_flat(&res, v0tex, v1tex, v4tex, str);
    draw_polygon_on_normal_texture_absolutely_flat(&res, v1tex, v4tex, v5tex, str);
    for (size_t i = 0; i < k; i++) {
        vec2 A = {r - sinf((float)i * a) * r, r + cosf((float)i * a) * r};
        vec2 B = {r - sinf((float)(i + 1) * a) * r, r + cosf((float)(i + 1) * a) * r};
        draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, (vec2){r, r}, str);
    }
    for (size_t i = 0; i < k; i++) {
        vec2 A = {r + w + sinf((float)i * a) * r, r + cosf((float)i * a) * r};
        vec2 B = {r + w + sinf((float)(i + 1) * a) * r, r + cosf((float)(i + 1) * a) * r};
        draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, (vec2){r + w, r}, str);
    }
    for (size_t i = 0; i < k; i++) {
        vec2 A = {r, 2 * r + (float)i * l};
        vec2 B = {r + w, 2 * r + (float)i * l};
        vec2 C = {r, 2 * r + (float)i * l + l};
        vec2 D = {r + w, 2 * r + (float)i * l + l};
        draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, C, str);
        draw_polygon_on_normal_texture_absolutely_flat(&res, D, B, C, str);
    }
    return res;
}



GenericMeshTopology generate_one_fourth_of_a_cylinder(float w, float r, U32 k) {
    assert(k >= 1);
    const float a = M_PI_2f / (float)k;
    const float l = 2 * r * sinf(M_PI_4f / (float)k);
    float tex_width = 2 * r + w;
    float tex_height = 2 * r + (float)k * l;

    const vec2 v0tex = {r / tex_width, r / tex_height};
    const vec2 v1tex = {(r + w) / tex_width, r / tex_height};
    const vec2 v2tex = {r / tex_width, 2 * r / tex_height};
    const vec2 v3tex = {(r + w) / tex_width, 2 * r / tex_height};
    VecGenericMeshVertexInc vertices = VecGenericMeshVertexInc_new_reserved(8 + 4 * k + (k + 2) * 2);
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {0, 0, 0}, .tex = v0tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {w, 0, 0}, .tex = v1tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {0, r, 0}, .tex = v2tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {w, r, 0}, .tex = v3tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {0, 0, 0}, .tex = v0tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {w, 0, 0}, .tex = v1tex});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {0, 0, -r}, .tex = {r / tex_width, 0}});
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {w, 0, -r}, .tex = {(r + w) / tex_width, 0}});

    for (U32 i = 0; i < k; i++) {
        for (int j = 0; j < 2; j++) {
            VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
                .pos = {0, cosf(a * (float)(i + j)) * r, -sinf(a * (float)(i + j)) * r},
                .tex = {v2tex.x, v2tex.y + (float)(i + j) * l / tex_height}
            });
            VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
                .pos = {w, cosf(a * (float)(i + j)) * r, -sinf(a * (float)(i + j)) * r},
                .tex = {v3tex.x, v3tex.y + (float)(i + j) * l / tex_height}
            });
        }
    }
    assert(vertices.len == 8 + 4 * k);

    for (U32 i = 0; i <= k; i++) {
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {0, cosf(a * (float)i) * r, -sinf(a * (float)i) * r},
            .tex = (vec2){ (r - r *sinf(a * (float)i)) / tex_width, (r + r * cosf(a * (float)i)) / tex_height},
        });
    }
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {0, 0, 0}, .tex = v0tex});
    for (U32 i = 0; i <= k; i++) {
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {w, cosf(a * (float)i) * r, -sinf(a * (float)i) * r},
            .tex = (vec2){ (r + w + r * sinf(a * (float)i)) / tex_width, (r + r * cosf(a * (float)i)) / tex_height},
        });
    }
    VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){.pos = {w, 0, 0}, .tex = v1tex});
    assert(vertices.len == 8 + 4 * k + (k + 2) * 2);

    VecU32 indexes = VecU32_new_reserved(3*(4+2*k+2*k));
    U32 _span_0[] = {7, 5, 4, 7, 4, 6,   1, 3, 0, 3, 2, 0};
    VecU32_append_span(&indexes, (SpanU32){.data = _span_0, .len = ARRAY_SIZE(_span_0)});
    for (U32 i = 0; i < k; i++) {
        U32 _span_1[] = {
            8 + 4 * k + k + 1,      8 + 4 * k + i,                8 + 4 * k + i + 1,
            8 + 4 * k + 2 * k + 3,  8 + 4 * k + (k + 2) + i + 1,  8 + 4 * k + (k + 2) + i,
            8 + 4 * i + 0,          8 + 4 * i + 1,                8 + 4 * i + 3,
            8 + 4 * i + 0,          8 + 4 * i + 3,                8 + 4 * i + 2,
        };
        VecU32_append_span(&indexes, (SpanU32){.data = _span_1, .len = ARRAY_SIZE(_span_1)});
    }
    return (GenericMeshTopology){.vertices = vertices, .indexes = indexes};
}

U32 quad_to_triangles_conv_arr[6] = {0, 1, 2, 0, 2, 3};

ShinyMeshTopology generate_shiny_cube(float r) {
    ShinyMeshVertexInc vert[24] = {
        {{+r, +r, +r}},
        {{+r, -r, +r}},
        {{+r, -r, -r}},
        {{+r, +r, -r}},

        {{-r, -r, -r}},
        {{-r, -r, +r}},
        {{-r, +r, +r}},
        {{-r, +r, -r}},

        {{+r, +r, +r}},
        {{+r, +r, -r}},
        {{-r, +r, -r}},
        {{-r, +r, +r}},

        {{-r, -r, -r}},
        {{+r, -r, -r}},
        {{+r, -r, +r}},
        {{-r, -r, +r}},

        {{+r, +r, +r}},
        {{-r, +r, +r}},
        {{-r, -r, +r}},
        {{+r, -r, +r}},

        {{-r, -r, -r}},
        {{-r, +r, -r}},
        {{+r, +r, -r}},
        {{+r, -r, -r}},
    };
    VecShinyMeshVertexInc vertices_vec = VecShinyMeshVertexInc_from_span(
        (SpanShinyMeshVertexInc){ .data = vert, .len = ARRAY_SIZE(vert) });
    VecU32 indexes_vec = VecU32_new_reserved(36);
    for (U32 f = 0; f < 6; f++) {
        for (U32 j = 0; j < 6; j++)
            VecU32_append(&indexes_vec, f * 4 + quad_to_triangles_conv_arr[j]);
    }
    return (ShinyMeshTopology){ .vertices = vertices_vec, .indexes = indexes_vec};
}

typedef struct {
    int face;
    /* There is a counterclockwise one-to-one correspondence between vertexes of face and edges of face */
    int vert_on_it;
} CubeVertOfFace;

CubeVertOfFace CubeVertOfFace_jump(CubeVertOfFace vert) {
    const CubeVertOfFace cube_detour_get_neighbour_face[6][4] = {
        {{4, 3}, {3, 1}, {5, 2}, {2, 0}},
        {{3, 3}, {4, 1}, {2, 2}, {5, 0}},
        {{0, 3}, {5, 1}, {1, 2}, {4, 0}},
        {{5, 3}, {0, 1}, {4, 2}, {1, 0}},
        {{2, 3}, {1, 1}, {3, 2}, {0, 0}},
        {{1, 3}, {2, 1}, {0, 2}, {3, 0}}
    };
    return cube_detour_get_neighbour_face[vert.face][vert.vert_on_it];
}

U32 CubeVertOfFace_to_vid(CubeVertOfFace vert) {
    return 4 * vert.face + vert.vert_on_it;
}

CubeVertOfFace CubeVertOfFace_next(CubeVertOfFace vert) {
    return (CubeVertOfFace){vert.face, (vert.vert_on_it + 1) % 4};
}

#include "../../../gen/l1/margaret/png_pixel_masses.h"
#include "../marie/texture_processing.h"
#include "../../l1/system/fsmanip.h"
#include "../alice/model_file.h"

/* Situation: we generated vertices array of generic mesh, we filled .tex attribute (scaled pixel pos)
 * Now I want the normally scaled stuff back */
MarieTriangle restore_triangle_from_mesh_topology(const VecGenericMeshVertexInc* vertices,
    U32 texture_width, U32 texture_height, U32 vi1, U32 vi2, U32 vi3){
    vec2 tex1 = VecGenericMeshVertexInc_at(vertices, vi1)->tex;
    vec2 tex2 = VecGenericMeshVertexInc_at(vertices, vi2)->tex;
    vec2 tex3 = VecGenericMeshVertexInc_at(vertices, vi3)->tex;
    return (MarieTriangle){
        .v0 = {tex1.x * (float)texture_width, tex1.y * (float)texture_height},
        .v1 = {tex2.x * (float)texture_width, tex2.y * (float)texture_height},
        .v2 = {tex3.x * (float)texture_width, tex3.y * (float)texture_height},
    };
}

void r4_generate_flat_normal_map(VecU8 save_path){
    TextureDataR8G8B8A8 normal = TextureDataR8G8B8A8_new(1, 1);
    *TextureDataR8G8B8A8_mat(&normal, 0, 0) = compress_normal_vec_into_norm_texel((vec3){0, 1, 0});
    TextureDataR8G8B8A8_write_to_png_nofail(&normal, VecU8_to_span(&save_path));
    VecU8_drop(save_path);
    TextureDataR8G8B8A8_drop(normal);
}

/* r is radius, w is length of cylinder. Will write everything into files for us */
void r4_asset_gen_generic_mesh_cylinder(float s_resol, float r, float w, U32 k,
    VecU8 path_to_mesh, VecU8 path_to_template_tex, VecU8 path_to_normal_tex){
    assert(k >= 3);
    VecGenericMeshVertexInc vertices = VecGenericMeshVertexInc_new_reserved(6 * k);
    VecU32 indexes = VecU32_new_reserved(3 * (2 * (k - 2) + 2 * k));

    U32 uber_square_takes_px = (U32)ceilf(2 * r * s_resol);
    float r_px = (float)uber_square_takes_px / 2;

    vec2 ubs_front_center = (vec2){r_px, r_px};

    U32 back_ubs_x = uber_square_takes_px + 1;
    vec2 ubs_back_center = (vec2){(float)back_ubs_x + r_px, r_px};

    U32 belt_y = uber_square_takes_px + 1;

    /* l is a length of a side. I am 100 % that there is a simpler formula for that */
    float l = r * sqrtf(2 - 2 * cosf(2 * M_PIf / (float)k));
    U32 belt_takes_px = (U32)ceilf(l * (float)k * s_resol);
    float l_px = (float)belt_takes_px / (float)k;

    U32 cyl_length_takes_px = (U32)ceilf(w * s_resol);
    U32 texture_width = MAX_U32(back_ubs_x + uber_square_takes_px, belt_takes_px);
    U32 texture_height = belt_y + cyl_length_takes_px;

    for (U32 i = 0; i < k; i++) {
        float angle = (float)i * 2 * M_PIf / (float)k;
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(angle), r * sinf(angle), w / 2},
            .tex = {
                (ubs_front_center.x + cosf(angle) * r_px) / (float)texture_width,
                (ubs_front_center.y - sinf(angle) * r_px) / (float)texture_height},
        });
    }
    for (U32 i = 0; i < k; i++) {
        float angle = (float)i * 2 * M_PIf / (float)k;
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(angle), r * sinf(angle), -w / 2},
            .tex = {
                (ubs_back_center.x - cosf(angle) * r_px) / (float)texture_width,
                (ubs_back_center.y - sinf(angle) * r_px) / (float)texture_height},
        });
    }
    for (U32 i = 0; i < k; i++) {
        float angle = (float)i * 2 * M_PIf / (float)k;
        float anext = (float)(i + 1) * 2 * M_PIf / (float)k;

        vec2 tex0 = {l_px * (float)i / (float)texture_width, (float)belt_y / (float)texture_height};
        vec2 tex4 = {l_px * (float)(i + 1) / (float)texture_width,
            (float)(belt_y + cyl_length_takes_px) / (float)texture_height};
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(angle), r * sinf(angle), w / 2},
            .tex = tex0
        });
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(anext), r * sinf(anext), w / 2},
            .tex = (vec2){tex4.x, tex0.y}
        });
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(angle), r * sinf(angle), -w / 2},
            .tex = (vec2){tex0.x, tex4.y}
        });
        VecGenericMeshVertexInc_append(&vertices, (GenericMeshVertexInc){
            .pos = {r * cosf(anext), r * sinf(anext), -w / 2},
            .tex = tex4
        });
    }
    TextureDataR8G8B8A8 template = TextureDataR8G8B8A8_new(texture_width, texture_height);

    for (U32 i = 1; i + 1 < k; i++) {
        U32 vis = i;
        U32 vin = i + 1;
        U32 vic = 0;
        VecU32_append_span(&indexes, (SpanU32){.data = (U32[]){vis, vin, vic}, .len = 3});
        MarieTriangle tex_trig = restore_triangle_from_mesh_topology(&vertices, texture_width, texture_height,
            vis, vin, vic);
        TextureDataR8G8B8A8_draw_triangle_of_one_color(&template, (vec3){0.5f, 0.9f, 0.5f}, tex_trig);
    }
    for (U32 i = 1; i + 1 < k; i++) {
        U32 vis = k + i;
        U32 vin = k + i + 1;
        U32 vic = k;
        VecU32_append_span(&indexes, (SpanU32){.data = (U32[]){vin, vis, vic}, .len = 3});
        MarieTriangle tex_trig = restore_triangle_from_mesh_topology(&vertices, texture_width, texture_height,
            vis, vin, vic);
        TextureDataR8G8B8A8_draw_triangle_of_one_color(&template, (vec3){0.2f, 0.1f, 0.9f}, tex_trig);
    }
    for (U32 i = 0; i < k; i++) {
        vec3 color = (i % 3 == 0) ? (vec3){0.8f, 0.8f, 0} :
            ((i % 3 == 1) ? (vec3){0.6f, 0.2f, 0.7f} : (vec3){0.3f, 0, 0.95f});
        U32 v0 = 2 * k + 4 * i;
        VecU32_append_span(&indexes, (SpanU32){.data = (U32[]){v0 + 1, v0, v0 + 2, v0 + 1, v0 + 2, v0 + 3}, .len = 6});
        MarieTriangle tex_trig_1 = restore_triangle_from_mesh_topology(&vertices, texture_width, texture_height,
            v0 + 1, v0, v0 + 2);
        TextureDataR8G8B8A8_draw_triangle_of_one_color(&template, color, tex_trig_1);
        MarieTriangle tex_trig_2 = restore_triangle_from_mesh_topology(&vertices, texture_width, texture_height,
            v0 + 1, v0 + 2, v0 + 3);
        TextureDataR8G8B8A8_draw_triangle_of_one_color(&template, color, tex_trig_2);
    }

    alice_write_generic_mesh_to_file((GenericMeshTopology){.vertices = vertices, .indexes = indexes}, path_to_mesh);
    TextureDataR8G8B8A8_write_to_png_nofail(&template, VecU8_to_span(&path_to_template_tex));
    VecU8_drop(path_to_template_tex);
    TextureDataR8G8B8A8_drop(template);

    /* Right now it's just a pixel... */
    r4_generate_flat_normal_map(path_to_normal_tex);
}

void r4_asset_gen_generic_mesh_quad(float width, float length, VecU8 path_to_save){
    VecGenericMeshVertexInc vert = VecGenericMeshVertexInc_from_span((SpanGenericMeshVertexInc){
        .data = (GenericMeshVertexInc[]){
            {.pos = {0, 0, 0}, .tex = {0, 0}},
            {.pos = {width, 0, 0}, .tex = {1, 0}},
            {.pos = {0, 0, length}, .tex = {0, 1}},
            {.pos = {width, 0, length}, .tex = {1, 1}}
        }, .len = 4});
    VecU32 indexes = VecU32_from_span((SpanU32){.data = (U32[]){1, 0, 2, 1, 2, 3}, .len = 6});
    alice_write_generic_mesh_to_file((GenericMeshTopology){.vertices = vert, .indexes = indexes}, path_to_save);
}

/* a is r at bottom, b is r on top. y is in [0, height]. Shape is symmetrical from Oy */
ShinyMeshTopology generate_shiny_lamp(float height, float a, float b){
    ShinyMeshVertexInc vert[24] = {
        {{+b, height, +b}},
        {{+b, 0, +b}},
        {{+b, 0, -b}},
        {{+b, height, -b}},

        {{-b, 0, -b}},
        {{-b, 0, +b}},
        {{-b, height, +b}},
        {{-b, height, -b}},

        {{+b, height, +b}},
        {{+b, height, -b}},
        {{-b, height, -b}},
        {{-b, height, +b}},

        {{-b, 0, -b}},
        {{+b, 0, -b}},
        {{+b, 0, +b}},
        {{-b, 0, +b}},

        {{+b, height, +b}},
        {{-b, height, +b}},
        {{-b, 0, +b}},
        {{+b, 0, +b}},

        {{-b, 0, -b}},
        {{-b, height, -b}},
        {{+b, height, -b}},
        {{+b, 0, -b}},
    };
    VecShinyMeshVertexInc vertices = VecShinyMeshVertexInc_from_span(
        (SpanShinyMeshVertexInc){ .data = vert, .len = ARRAY_SIZE(vert) });
    VecU32 indexes = VecU32_new_reserved(36);
    for (U32 f = 0; f < 6; f++) {
        for (U32 j = 0; j < 6; j++)
            VecU32_append(&indexes, f * 4 + quad_to_triangles_conv_arr[j]);
    }
    return (ShinyMeshTopology){ .vertices = vertices, .indexes = indexes};
}

void generate_one_forth_of_a_cylinder_with_bublazhuzhka(U64 w, U64 r, U64 k) {
    {
        TextureDataR8G8B8A8 tex = generate_tex_template_for_one_fourth_of_a_cylinder(120, (float)w, (float)r, k);
        TextureDataR8G8B8A8 fixed_tex = TextureDataR8G8B8A8_expand_nontransparent_1px(&tex);
        VecU8 name = VecU8_fmt("l2/textures/log_%u_%u_%u_TEMPLATE.png", w, r, k);
        TextureDataR8G8B8A8_write_to_png_nofail(&fixed_tex, VecU8_to_span(&name));
        VecU8_drop(name);
        TextureDataR8G8B8A8_drop(fixed_tex);
        TextureDataR8G8B8A8_drop(tex);
    }
    {
        TextureDataR8G8B8A8 tex = generate_normal_tex_for_one_fourth_of_a_cylinder(120, (float)w, (float)r, k);
        TextureDataR8G8B8A8 fixed_tex = TextureDataR8G8B8A8_expand_nontransparent_1px(&tex);
        VecU8 name = VecU8_fmt("l2/textures/log_%u_%u_%u_NORMAL.png", w, r, k);
        TextureDataR8G8B8A8_write_to_png_nofail(&fixed_tex, VecU8_to_span(&name));
        VecU8_drop(name);
        TextureDataR8G8B8A8_drop(fixed_tex);
        TextureDataR8G8B8A8_drop(tex);
    }
    GenericMeshTopology top = generate_one_fourth_of_a_cylinder((float)w, (float)r, k);
    alice_write_generic_mesh_to_file(top, VecU8_fmt("l2/models/log_%u_%u_%u.AliceGenericMesh", w, r, k));
}

/* We are on l2 */
int gen_assets_for_r4() {
    mkdir_nofail("l2/models");
    mkdir_nofail("l2/textures");
    mkdir_nofail("l2/textures/r4");
    generate_one_forth_of_a_cylinder_with_bublazhuzhka(10, 2, 6);
    alice_write_shiny_mesh_to_file(generate_shiny_cube(0.3f), vcstr("l2/models/cube.AliceShinyMesh"));
    alice_write_shiny_mesh_to_file(generate_shiny_lamp(0.3f, 0.13f, 0.19f), vcstr("l2/models/lamp.AliceShinyMesh"));
    r4_asset_gen_generic_mesh_quad(10, 10, vcstr("l2/models/quad.AliceGenericMesh"));
    r4_asset_gen_generic_mesh_cylinder(200, 0.4f, 0.17f, 30, vcstr("l2/models/puck.AliceGenericMesh"),
        vcstr("l2/textures/puck_TEMPLATE.png"), vcstr("l2/textures/puck_NORMAL.png"));
    r4_asset_gen_generic_mesh_cylinder(100, 0.04f, 1.5f, 4, vcstr("l2/models/stick.AliceGenericMesh"),
        vcstr("l2/textures/stick_TEMPLATE.png"), vcstr("l2/textures/stick_NORMAL.png"));
    r4_generate_flat_normal_map(vcstr("l2/textures/flat_NORMAL.png"));
    return 0;
}


#endif