#include "pch.h"
#include "log.h"
#include "legacy_gl_mesh_dispatch.h"
#include "shape.h"
#include "app.h"
#include "renderer_gl/opengl_capabilities.h"

#include <array>
#include <cstddef>
#include <cstdint>

namespace {

[[nodiscard]] std::span<const pp::renderer::gl::OpenGlVertexAttribute> shape_vertex_attributes() noexcept
{
    static const std::array<pp::renderer::gl::OpenGlVertexAttribute, 4> attributes {
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 0U,
            .component_count = 4,
            .component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
            .normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
            .stride = static_cast<std::int32_t>(sizeof(vertex_t)),
            .offset = 0U,
        },
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 1U,
            .component_count = 2,
            .component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
            .normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
            .stride = static_cast<std::int32_t>(sizeof(vertex_t)),
            .offset = static_cast<std::uintptr_t>(offsetof(vertex_t, uvs)),
        },
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 2U,
            .component_count = 2,
            .component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
            .normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
            .stride = static_cast<std::int32_t>(sizeof(vertex_t)),
            .offset = static_cast<std::uintptr_t>(offsetof(vertex_t, uvs2)),
        },
        pp::renderer::gl::OpenGlVertexAttribute {
            .index = 3U,
            .component_count = 3,
            .component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
            .normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
            .stride = static_cast<std::int32_t>(sizeof(vertex_t)),
            .offset = static_cast<std::uintptr_t>(offsetof(vertex_t, nor)),
        },
    };
    return attributes;
}

}

bool Shape::create_buffers(GLushort * idx, GLvoid * vertices, int isize, int vsize)
{
    index_type = static_cast<GLenum>(pp::renderer::gl::index_type_for_index_size(sizeof(GLushort)));
    create_buffers_imp(idx, vertices, isize, vsize);
    return false;
}

bool Shape::create_buffers(GLuint* idx, GLvoid * vertices, int isize, int vsize)
{
    index_type = static_cast<GLenum>(pp::renderer::gl::index_type_for_index_size(sizeof(GLuint)));
    create_buffers_imp(idx, vertices, isize, vsize);
    return false;
}

bool Shape::create_buffers_imp(GLvoid* idx, GLvoid* vertices, int isize, int vsize)
{
    use_idx = true;

    bool ret = false;
    App::I->render_task([&]
    {
        destroy();

        const auto mesh = pp::legacy::gl_mesh::create_mesh_objects(
            pp::renderer::gl::OpenGlMeshUpload {
                .vertex_data = vertices,
                .vertex_byte_count = vsize,
                .index_data = idx,
                .index_byte_count = isize,
                .indexed = true,
                .attributes = shape_vertex_attributes(),
            },
            "Shape::create_buffers indexed");
        if (mesh.vertex_buffer == 0U) {
            ret = false;
            return;
        }
        buffers[0] = static_cast<GLuint>(mesh.vertex_buffer);
        buffers[1] = static_cast<GLuint>(mesh.index_buffer);
        arrays[0] = static_cast<GLuint>(mesh.vertex_arrays[0]);
        arrays[1] = static_cast<GLuint>(mesh.vertex_arrays[1]);
    });
    return ret;
}
bool Shape::create_buffers(GLvoid* vertices, int vsize)
{
    use_idx = false;

    bool ret = false;
    App::I->render_task([&]
    {
        destroy();

        const auto mesh = pp::legacy::gl_mesh::create_mesh_objects(
            pp::renderer::gl::OpenGlMeshUpload {
                .vertex_data = vertices,
                .vertex_byte_count = vsize,
                .indexed = false,
                .attributes = shape_vertex_attributes(),
            },
            "Shape::create_buffers");
        if (mesh.vertex_buffer == 0U) {
            ret = false;
            return;
        }
        buffers[0] = static_cast<GLuint>(mesh.vertex_buffer);
        buffers[1] = static_cast<GLuint>(mesh.index_buffer);
        arrays[0] = static_cast<GLuint>(mesh.vertex_arrays[0]);
        arrays[1] = static_cast<GLuint>(mesh.vertex_arrays[1]);
    });
    return ret;
}
void Shape::draw_fill() const
{
    if (count[0] == 0) return;

    const auto type = static_cast<GLenum>(pp::renderer::gl::primitive_mode_for_fill_count(count[0]));
    App::I->render_task([=]
    {
        (void)pp::legacy::gl_mesh::draw_mesh(
            pp::renderer::gl::OpenGlMeshDraw {
                .vertex_array = arrays[0],
                .mode = type,
                .count = static_cast<std::int32_t>(count[0]),
                .indexed = use_idx,
                .index_type = index_type,
                .index_offset = ioff[0],
            },
            "Shape::draw_fill");
    });
}
void Shape::draw_stroke() const
{
    if (count[0] == 0) return;

    const auto type = static_cast<GLenum>(pp::renderer::gl::primitive_mode_for_stroke_count(count[1]));
    App::I->render_task([=]
    {
        (void)pp::legacy::gl_mesh::draw_mesh(
            pp::renderer::gl::OpenGlMeshDraw {
                .vertex_array = arrays[1],
                .mode = type,
                .count = static_cast<std::int32_t>(count[1]),
                .indexed = use_idx,
                .index_type = index_type,
                .index_offset = ioff[1],
            },
            "Shape::draw_stroke");
    });
}

void Shape::destroy()
{
    if (App::I) App::I->render_task_async([b1=buffers[0],b2=buffers[1],a1=arrays[0],a2=arrays[1]]
    {
        (void)pp::legacy::gl_mesh::delete_mesh_objects({ b1, b2 }, { a1, a2 }, "Shape::destroy");
    });
    buffers[0] = buffers[1] = 0;
    arrays[0] = arrays[1] = 0;
}

Shape::~Shape()
{
    destroy();
}

bool RectShape::create(float w, float h)
{
    static GLushort idx[6 + 8] { 
        0, 1, 2, 
        0, 2, 3, 
        0, 1,
        1, 2,
        2, 3,
        3, 0,
    };
    static vertex_t vertices[4];
    vertices[0] = { { -w/2, -h/2, 0, 1 }, { 0, 0 }, { 0, 0 } }; // A
    vertices[1] = { { -w/2,  h/2, 0, 1 }, { 0, 1 }, { 0, 1 } }; // B
    vertices[2] = { {  w/2,    0, 0, 1 }, { 1, 1 }, { 1, 1 } }; // C
    vertices[3] = { {  w/2, -h/2, 0, 1 }, { 1, 0 }, { 1, 0 } }; // D
    count[0] = 6;
    count[1] = 8;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    adjust_quad_uvs(vertices[0], vertices[1], vertices[2], vertices[3]);
    return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
}

void Plane::create_impl(float w, float h, int div, GLushort *idx, vertex_t *vertices)
{
    count[0] = div * div * 6;
    count[1] = (div + 1) * 4;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    
    const float dx = w / div;
    const float dy = h / div;
    const float ox = -w * 0.5f;
    const float oy = -h * 0.5f;
    for (int y = 0; y <= div; y++)
    {
        for (int x = 0; x <= div; x++)
        {
            vertex_t v;
            v.pos.x = ox + dx * (float)x;
            v.pos.y = oy + dy * (float)y;
            v.pos.z = 0;
            v.pos.w = 1;
            v.uvs2 = v.uvs = glm::vec2(x, y) / (float)div;
            *vertices++ = v;
        }
    }
    
    // generate indices
    for (int y = 0; y < div; y++)
    {
        int i = y * (div+1);
        for (int x = 0; x < div; x++)
        {
            *idx++ = i;
            *idx++ = i + div + 1;
            *idx++ = i + div + 2;
            *idx++ = i;
            *idx++ = i + div + 2;
            *idx++ = i + 1;
            i++;
        }
    }

    // generate indices
    for (int y = 0; y <= div; y++)
    {
        int i = y * (div + 1);
        
        *idx++ = i;
        *idx++ = i + div;
        
        *idx++ = y;
        *idx++ = y + div * (div + 1);
    }
}

bool HeightmapPlane::create(float w, float h, const Image& img, float scale, float height)
{
    Image img_tmp;
    glm::u8vec4* px = (glm::u8vec4*)img.data();
    glm::vec2 new_size = glm::clamp(glm::ceil(glm::vec2(img.width, img.height) * scale), { 16, 16 }, glm::vec2(8192));
    if (scale != 1.f)
    {
        img_tmp = img.resize(new_size.x, new_size.y);
        px = (glm::u8vec4*)img_tmp.data();
    }

    int div = new_size.x - 1; // TODO: handle height also
    int idx_size = (div * div * 6) + (div * (div + 1) * 4);
    int vertices_size = (div + 1)*(div + 1);
    idx.resize(idx_size);
    vertices.resize(vertices_size);
    std::vector<int> nor_count((size_t)vertices_size, 0);

    count[0] = div * div * 6;
    count[1] = div * (div + 1) * 4;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLuint));

    auto pv = vertices.data();
    auto pi = idx.data();

    const float dx = w / div;
    const float dy = h / div;
    const float ox = -w * 0.5f;
    const float oy = -h * 0.5f;
    for (int y = 0; y <= div; y++)
    {
        for (int x = 0; x <= div; x++)
        {
            vertex_t v;
            v.pos.x = ox + dx * (float)x;
            v.pos.y = (*px++).r / 255.f * height;
            v.pos.z = oy + dy * (float)y;
            v.pos.w = 1;
            v.uvs2 = v.uvs = glm::vec2(x, y) / (float)div;
            *pv++ = v;
        }
    }

    // generate indices
    for (int y = 0; y < div; y++)
    {
        int i = y * (div + 1);
        for (int x = 0; x < div; x++)
        {
            *pi++ = i;
            *pi++ = i + div + 1;
            *pi++ = i + div + 2;
            *pi++ = i;
            *pi++ = i + div + 2;
            *pi++ = i + 1;
            auto n = glm::triangleNormal(xyz(vertices[i].pos),
                xyz(vertices[i + div + 1].pos), xyz(vertices[i + 1].pos));
            vertices[i].nor += n;
            vertices[i + 1].nor += n;
            vertices[i + div + 1].nor += n;
            vertices[i + div + 2].nor += n;
            nor_count[i]++;
            nor_count[i + 1]++;
            nor_count[i + div + 1]++;
            nor_count[i + div + 2]++;
            i++;
        }
    }

    for (int i = 0; i < vertices_size; i++)
        vertices[i].nor /= (float)nor_count[i];

    // generate indices
    for (int y = 0; y <= div; y++)
    {
        //int i = y * (div + 1);
        for (int x = 0; x <= div; x++)
        {
            if (x < div)
            {
                *pi++ = y * (div + 1) + x;
                *pi++ = y * (div + 1) + x + 1;
            }

            if (y < div)
            {
                *pi++ = y * (div + 1) + x;
                *pi++ = (y + 1) * (div + 1) + x;
            }
        }
    }

    return create_buffers(idx.data(), vertices.data(), sizeof(GLuint) * idx_size, sizeof(vertex_t) * vertices_size);
}

bool HeightmapPlane::create(float w, float h, int div)
{
    int idx_size = (div * div * 6) + (div * (div + 1) * 4);
    int vertices_size = (div + 1)*(div + 1);
    auto idx = std::make_unique<GLuint[]>(idx_size);
    auto vertices = std::make_unique<vertex_t[]>(vertices_size);

    count[0] = div * div * 6;
    count[1] = div * (div + 1) * 4;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLuint));

    auto pv = vertices.get();
    auto pi = idx.get();

    const float dx = w / div;
    const float dy = h / div;
    const float ox = -w * 0.5f;
    const float oy = -h * 0.5f;
    for (int y = 0; y <= div; y++)
    {
        for (int x = 0; x <= div; x++)
        {
            vertex_t v;
            v.pos.x = ox + dx * (float)x;
            v.pos.y = 0.f;
            v.pos.z = oy + dy * (float)y;
            v.pos.w = 1.f;
            v.uvs2 = v.uvs = glm::vec2(x, y) / (float)div;
            v.nor = { 0, 1, 0 };
            *pv++ = v;
        }
    }

    // generate indices
    for (int y = 0; y < div; y++)
    {
        int i = y * (div + 1);
        for (int x = 0; x < div; x++)
        {
            *pi++ = i;
            *pi++ = i + div + 1;
            *pi++ = i + div + 2;
            *pi++ = i;
            *pi++ = i + div + 2;
            *pi++ = i + 1;
            i++;
        }
    }

    // generate indices
    for (int y = 0; y <= div; y++)
    {
        //int i = y * (div + 1);
        for (int x = 0; x <= div; x++)
        {
            if (x < div)
            {
                *pi++ = y * (div + 1) + x;
                *pi++ = y * (div + 1) + x + 1;
            }

            if (y < div)
            {
                *pi++ = y * (div + 1) + x;
                *pi++ = (y + 1) * (div + 1) + x;
            }
        }
    }

    return create_buffers(idx.get(), vertices.get(), sizeof(GLuint) * idx_size, sizeof(vertex_t) * vertices_size);
}

void Plane::update_vertices(const glm::vec4* data, const glm::vec2* uvs, const glm::vec2* uvs2)
{
    static vertex_t vertices[4];
    
    glm::vec2 mid, hit_uv;
    segments_intersect(xy(data[0]), xy(data[2]), xy(data[1]), xy(data[3]), mid, hit_uv);
    static float d[4];
    for (int i = 0; i < 4; i++)
        d[i] = glm::distance(xy(data[i]), mid);

    vertices[0] = { data[0], { 0, 0 }, { 0, 0 } }; // A
    vertices[1] = { data[1], { 0, 1 }, { 0, 1 } }; // B
    vertices[2] = { data[2], { 1, 1 }, { 1, 1 } }; // C
    vertices[3] = { data[3], { 1, 0 }, { 1, 0 } }; // D
    
    for (int i = 0; i < 4; i++)
    {
        float q = (d[i] + d[(i + 2) % 4]) / d[(i + 2) % 4];
        if (uvs)
            vertices[i].uvs = uvs[i];
        if (uvs2)
            vertices[i].uvs2 = uvs2[i];
        vertices[i].uvs *= q;
        vertices[i].uvs2 *= q;
        vertices[i].pos.z = q;
    }

    App::I->render_task([this]
    {
        (void)pp::legacy::gl_mesh::upload_buffer_data(
            pp::renderer::gl::OpenGlBufferUpload {
                .target = pp::renderer::gl::array_buffer_target(),
                .buffer_id = buffers[0],
                .data = vertices,
                .byte_count = static_cast<std::intptr_t>(sizeof(vertices)),
                .usage = pp::renderer::gl::static_draw_buffer_usage(),
            },
            "Plane::update_vertices vertices");
        static GLushort idx[6 + 8]{
            0, 1, 2,
            0, 2, 3,
            0, 1,
            1, 2,
            2, 3,
            3, 0,
        };
        (void)pp::legacy::gl_mesh::upload_buffer_data(
            pp::renderer::gl::OpenGlBufferUpload {
                .target = pp::renderer::gl::element_array_buffer_target(),
                .buffer_id = buffers[1],
                .data = idx,
                .byte_count = static_cast<std::intptr_t>(sizeof(idx)),
                .usage = pp::renderer::gl::static_draw_buffer_usage(),
            },
            "Plane::update_vertices indices");
    });
}
void Circle::create_impl(float radius, int div, GLushort* idx, vertex_t* vertices)
{
    count[0] = div * 3;
    count[1] = div * 2;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    
    auto pidx = idx;
    auto pidx2 = idx + count[0];
    for (int i = 0; i < div; i++)
    {
        vertex_t v;
        float theta = (float)i / div * (float)M_PI * 2.f;
        glm::vec2 uv = { sinf(theta), cosf(theta) };
        v.pos = glm::vec4(uv * radius, 0, 1);
        v.uvs2 = v.uvs = uv * 0.5f + 0.5f;
        vertices[i+1] = v;
        
        *pidx++ = 0;
        *pidx++ = i+1;
        *pidx++ = ((i+1) % div) + 1;
        
        *pidx2++ = 1 + i;
        *pidx2++ = 1 + ((i+1) % div);
    }
    vertices[0].pos = { 0, 0, 0, 1 };
    vertices[0].uvs2 = vertices[0].uvs = { 0.5f, 0.5f };
}

void Circle::create_impl(float radius_out, float radius_in, int div, GLushort* idx, vertex_t* vertices, kUVMapping map)
{
    count[0] = div * (radius_in == 0.f ? 3 : 6);
    count[1] = div * 4;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    
    auto pidx = idx;
    auto pidx2 = idx + count[0];
    for (int i = 0; i < div; i++)
    {
        float theta = (float)(i%(div-1)) / (div-1) * (float)M_PI * 2.f;
        glm::vec2 uv = { sinf(theta), cosf(theta) };
        if (map == kUVMapping::Planar)
        {
            vertices[i*2].uvs2 = vertices[i*2].uvs = uv * (radius_in/radius_out) * 0.5f + 0.5f;
            vertices[i*2+1].uvs2 = vertices[i*2+1].uvs = uv * 0.5f + 0.5f;
        }
        else
        {
            vertices[i*2].uvs2 = vertices[i*2].uvs = { (float)i / div, 0.f }; // inner
            vertices[i*2+1].uvs2 = vertices[i*2+1].uvs = { (float)i / div, 1.f};// outer
        }
        vertices[i*2].pos = glm::vec4(uv * radius_in, 0, 1);
        vertices[i*2+1].pos = glm::vec4(uv * radius_out, 0, 1);
        
        *pidx++ = i*2;                   // A
        *pidx++ = i*2+1;                 // B
        *pidx++ = ((i+1)*2+1) % (div*2); // C
        
        if (radius_in != 0.f)
        {
            *pidx++ = i * 2;                 // A
            *pidx++ = ((i+1)*2+1) % (div*2); // C
            *pidx++ = ((i+1)*2) % (div*2);   // D
        }
        
        *pidx2++ = i*2;                   // A
        *pidx2++ = ((i+1)*2) % (div*2);   // D
         
        *pidx2++ = i*2+1;                 // B
        *pidx2++ = ((i+1)*2+1) % (div*2); // C
    }

    //if (radius_in != 0 && map == kUVMapping::Tube)
    //{
    //    for (int i = 0; i < div; i++)
    //    {
    //        int a = i*2;                   // A
    //        int b = i*2+1;                 // B
    //        int c = ((i+1)*2+1) % (div*2); // C
    //        int d = ((i+1)*2) % (div*2);   // D
    //        adjust_quad_uvs(vertices[a], vertices[b], vertices[c], vertices[d]);
    //    }
    //}
}

void Rounded::create_impl(float w, float h, float r, int div, GLushort* idx, GLushort* idx_tmp, vertex_t* vertices)
{
    count[0] = (10 + div * 4) * 3;
    count[1] = (4 + div * 4) * 2;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    
    auto idx2 = idx + count[0];
    
    float X[] = { -w/2, -w/2+r, w/2-r, w/2 };
    float Y[] = { -h/2, -h/2+r, h/2-r, h/2 };
    
    auto V = [&](int x, int y) -> vertex_t {
        return { glm::vec4(X[x], Y[y], 0, 1), glm::vec2(X[x]/w, Y[y]/h) + 0.5f };
    };
    *vertices++ = V(1,0);
    *vertices++ = V(2,0);
    
    *vertices++ = V(0,1);
    *vertices++ = V(1,1);
    *vertices++ = V(2,1);
    *vertices++ = V(3,1);
    
    *vertices++ = V(0,2);
    *vertices++ = V(1,2);
    *vertices++ = V(2,2);
    *vertices++ = V(3,2);

    *vertices++ = V(1,3);
    *vertices++ = V(2,3);
    
    auto Q = [&](int a, int b, int c, int d) {
        *idx++ = a;
        *idx++ = b;
        *idx++ = c;
        *idx++ = a;
        *idx++ = c;
        *idx++ = d;
    };
    Q(0, 3, 4, 1);
    Q(2, 6, 7, 3);
    Q(3, 7, 8, 4);
    Q(4, 8, 9, 5);
    Q(7,10,11, 8);
    
    auto corner = [&](int c, int a, int b, int n) {
        auto v = vertices-12;
        idx_tmp[0] = a;
        idx_tmp[div] = b;
        for (int i = 1; i < div; i++)
        {
            float t = (float)(i) / div;
            auto p = glm::normalize(glm::mix(xyz(v[a].pos)-xyz(v[c].pos), xyz(v[b].pos)-xyz(v[c].pos), t));
            v[n].pos = glm::vec4(p * r + xyz(v[c].pos), 1.0f);
            v[n].uvs2 = v[n].uvs = glm::normalize(glm::mix(v[a].uvs-v[c].uvs, v[b].uvs-v[c].uvs, t)) * glm::vec2(r/w, r/h) + v[c].uvs;
            idx_tmp[i] = n;
            n++;
        }
        for (int i = 0; i < div; i++)
        {
            *idx++ = c;
            *idx++ = *idx2++ = idx_tmp[i];
            *idx++ = *idx2++ = idx_tmp[i+1];
        }
    };
    corner(3, 0, 2, 12 + (div-1)*0);
    corner(7, 6,10, 12 + (div-1)*1);
    corner(8,11, 9, 12 + (div-1)*2);
    corner(4, 5, 1, 12 + (div-1)*3);
    
    *idx2++ = 0;
    *idx2++ = 1;
    *idx2++ = 5;
    *idx2++ = 9;
    *idx2++ = 11;
    *idx2++ = 10;
    *idx2++ = 6;
    *idx2++ = 2;
}

void Slice9::create_impl(float w, float h, float r, float tr, GLushort *idx, vertex_t *vertices)
{
    count[0] = 3 * 3 * 6;
    count[1] = 4 * 2;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)(count[0] * sizeof(GLushort));
    
    float X[] = { -w/2, -w/2+r, w/2-r, w/2 };
    float Y[] = { -h/2, -h/2+r, h/2-r, h/2 };
    float T[] = { 0, tr, 1-tr, 1 };
    
    auto V = [&](int x, int y) -> vertex_t {
        return { glm::vec4(X[x], Y[y], 0, 1), glm::vec2(T[x], T[y]) };
    };
    
    for (int y = 0; y < 4; y++)
        for (int x = 0; x < 4; x++)
            *vertices++ = V(x,y);

    for (int y = 0; y < 3; y++)
    {
        int i = y * (3+1);
        for (int x = 0; x < 3; x++)
        {
            *idx++ = i;
            *idx++ = i + 3 + 1;
            *idx++ = i + 3 + 2;
            *idx++ = i;
            *idx++ = i + 3 + 2;
            *idx++ = i + 1;
            i++;
        }
    }
    // outline indices
    *idx++ = 0;  // A
    *idx++ = 3;  // B
    *idx++ = 3;  // B
    *idx++ = 15; // C
    *idx++ = 15; // C
    *idx++ = 12; // D
    *idx++ = 12; // D
    *idx++ = 0;  // A
}
void Sphere::create_impl(int rings, int sectors, float radius, 
    float lat_start, float lat_end, float lon_start, float lon_end, 
    GLushort *idx, vertex_t *vertices)
{
    count[0] = rings * sectors * 6;
    count[1] = 0;
    ioff[0] = (GLvoid*)0;
    ioff[1] = (GLvoid*)0;
    lat_start += M_PI_2;
    lat_end += M_PI_2;
    lon_start -= M_PI_2;
    lon_end -= M_PI_2;
    float lat_size = (lat_end - lat_start);
    float lon_size = (lon_end - lon_start);
    float const R = 1.f / (float)(rings-1);
    float const S = 1.f / (float)(sectors-1);
    int r, s;
    
    auto v = vertices;
    for(r = 0; r < rings; r++) for(s = 0; s < sectors; s++) {
        float lat = lat_start + r * lat_size * R;
        float lon = lon_start + s * lon_size * S;
        float const y = (float)sin(lat - M_PI_2);
        float const x = (float)cos(lon) * (float)sin(lat);
        float const z = (float)sin(lon) * (float)sin(lat);
        
        *v++ = { glm::vec4(x, y, z, 1) * radius, glm::vec2(s*S, r*R) };
    }
    
    auto i = idx;
    for(r = 0; r < rings-1; r++) for(s = 0; s < sectors-1; s++) {
        *i++ = r * sectors + s;
        *i++ = r * sectors + (s+1);
        *i++ = (r+1) * sectors + (s+1);
        
        *i++ = r * sectors + s;
        *i++ = (r+1) * sectors + (s+1);
        *i++ = (r+1) * sectors + s;
    }
}
void LineSegment::update_vertices(const glm::vec4 data[2])
{
    App::I->render_task([&]
    {
        static vertex_t vertices[2];
        vertices[0] = { data[0], { 0, 0 } }; // A
        vertices[1] = { data[1], { 0, 1 } }; // B
        (void)pp::legacy::gl_mesh::upload_buffer_data(
            pp::renderer::gl::OpenGlBufferUpload {
                .target = pp::renderer::gl::array_buffer_target(),
                .buffer_id = buffers[0],
                .data = vertices,
                .byte_count = static_cast<std::intptr_t>(sizeof(vertices)),
                .usage = pp::renderer::gl::static_draw_buffer_usage(),
            },
            "LineSegment::update_vertices");
    });
}
void DynamicShape::update_vertices(vertex_t* vertices, int vcount)
{
    App::I->render_task([&]
    {
        count[0] = vcount;
        count[1] = vcount;
        (void)pp::legacy::gl_mesh::upload_buffer_data(
            pp::renderer::gl::OpenGlBufferUpload {
                .target = pp::renderer::gl::array_buffer_target(),
                .buffer_id = buffers[0],
                .data = vertices,
                .byte_count = static_cast<std::intptr_t>(sizeof(vertex_t) * vcount),
                .usage = pp::renderer::gl::static_draw_buffer_usage(),
            },
            "DynamicShape::update_vertices");
    });
}
