#include "crPrimitiveGeometry.h"

#include <SDL3/SDL_stdinc.h>

#include "crMath.h"

// unit box (half extent 1) — the instance scale carries the real half extents;
// 4 verts per face for hard-edged normals, CCW seen from outside
static const float BOX_VERTS[] =
{
    // pos                 normal
     1.0f, -1.0f, -1.0f,    1.0f,  0.0f,  0.0f,
     1.0f,  1.0f, -1.0f,    1.0f,  0.0f,  0.0f,
     1.0f,  1.0f,  1.0f,    1.0f,  0.0f,  0.0f,
     1.0f, -1.0f,  1.0f,    1.0f,  0.0f,  0.0f,

    -1.0f, -1.0f, -1.0f,   -1.0f,  0.0f,  0.0f,
    -1.0f, -1.0f,  1.0f,   -1.0f,  0.0f,  0.0f,
    -1.0f,  1.0f,  1.0f,   -1.0f,  0.0f,  0.0f,
    -1.0f,  1.0f, -1.0f,   -1.0f,  0.0f,  0.0f,

    -1.0f,  1.0f, -1.0f,    0.0f,  1.0f,  0.0f,
    -1.0f,  1.0f,  1.0f,    0.0f,  1.0f,  0.0f,
     1.0f,  1.0f,  1.0f,    0.0f,  1.0f,  0.0f,
     1.0f,  1.0f, -1.0f,    0.0f,  1.0f,  0.0f,

    -1.0f, -1.0f, -1.0f,    0.0f, -1.0f,  0.0f,
     1.0f, -1.0f, -1.0f,    0.0f, -1.0f,  0.0f,
     1.0f, -1.0f,  1.0f,    0.0f, -1.0f,  0.0f,
    -1.0f, -1.0f,  1.0f,    0.0f, -1.0f,  0.0f,

    -1.0f, -1.0f,  1.0f,    0.0f,  0.0f,  1.0f,
     1.0f, -1.0f,  1.0f,    0.0f,  0.0f,  1.0f,
     1.0f,  1.0f,  1.0f,    0.0f,  0.0f,  1.0f,
    -1.0f,  1.0f,  1.0f,    0.0f,  0.0f,  1.0f,

    -1.0f, -1.0f, -1.0f,    0.0f,  0.0f, -1.0f,
    -1.0f,  1.0f, -1.0f,    0.0f,  0.0f, -1.0f,
     1.0f,  1.0f, -1.0f,    0.0f,  0.0f, -1.0f,
     1.0f, -1.0f, -1.0f,    0.0f,  0.0f, -1.0f,
};
static const uint16_t BOX_INDICES[] =
{
     0,  1,  2,   0,  2,  3,
     4,  5,  6,   4,  6,  7,
     8,  9, 10,   8, 10, 11,
    12, 13, 14,  12, 14, 15,
    16, 17, 18,  16, 18, 19,
    20, 21, 22,  20, 22, 23,
};

/*static*/ void crPrimitiveGeometry::BuildBox( crArray<float>* verts, crArray<uint16_t>* indices )
{
    const int32_t floatCount = static_cast<int32_t>( SDL_arraysize( BOX_VERTS ) );
    const int32_t indexCount = static_cast<int32_t>( SDL_arraysize( BOX_INDICES ) );

    verts->Reserve( floatCount );
    indices->Reserve( indexCount );

    for( int32_t i = 0; i < floatCount; ++i )
        verts->Add( BOX_VERTS[ i ] );
    for( int32_t i = 0; i < indexCount; ++i )
        indices->Add( BOX_INDICES[ i ] );
}
// unit UV sphere at an explicit tessellation — SPHERE and SPHERE_SHELL pass very different counts
static void BuildUvSphere( crArray<float>* verts, crArray<uint16_t>* indices, int32_t segments, int32_t rings )
{
    // rings from the south pole — the position doubles as the normal
    verts->Reserve( ( rings + 1 ) * ( segments + 1 ) * 6 );
    indices->Reserve( rings * segments * 6 );

    for( int32_t r = 0; r <= rings; ++r )
    {
        const float  phi = ( ( static_cast<float>( r ) / static_cast<float>( rings ) ) - 0.5f ) * crMath::PI;
        const float2 cp  = crMath::CosSin( phi );

        for( int32_t s = 0; s <= segments; ++s )
        {
            const float  theta = ( static_cast<float>( s ) / static_cast<float>( segments ) ) * ( 2.0f * crMath::PI );
            const float2 ct    = crMath::CosSin( theta );

            const float p[ 3 ] = { cp.x * ct.x, cp.y, cp.x * ct.y };
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( p[ k ] );
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( p[ k ] );
        }
    }

    for( int32_t r = 0; r < rings; ++r )
    {
        for( int32_t s = 0; s < segments; ++s )
        {
            const uint16_t i0 = static_cast<uint16_t>( ( r * ( segments + 1 ) ) + s );
            const uint16_t i1 = static_cast<uint16_t>( i0 + segments + 1 );

            indices->Add( i0 );
            indices->Add( i1 );
            indices->Add( static_cast<uint16_t>( i1 + 1 ) );
            indices->Add( i0 );
            indices->Add( static_cast<uint16_t>( i1 + 1 ) );
            indices->Add( static_cast<uint16_t>( i0 + 1 ) );
        }
    }
}

/*static*/ void crPrimitiveGeometry::BuildSphere( crArray<float>* verts, crArray<uint16_t>* indices )
{
    BuildUvSphere( verts, indices, SPHERE_SEGMENTS, SPHERE_RINGS );
}
/*static*/ void crPrimitiveGeometry::BuildSphereShell( crArray<float>* verts, crArray<uint16_t>* indices )
{
    BuildUvSphere( verts, indices, SHELL_SEGMENTS, SHELL_RINGS );

    // the inward half needs its own vertices, not just reversed indices, because the normal has to
    // flip too — shared normals would make every view-dependent term (fresnel) read backwards inside
    const int32_t vertFloats = verts->Size();
    const int32_t indexCount = indices->Size();
    const uint16_t base      = static_cast<uint16_t>( vertFloats / 6 );

    verts->Reserve( vertFloats * 2 );
    indices->Reserve( indexCount * 2 );

    for( int32_t i = 0; i < vertFloats; i += 6 )
    {// read out before appending — Add takes a reference, and a grow would strand it mid-call
        const float px = verts->At( i );
        const float py = verts->At( i + 1 );
        const float pz = verts->At( i + 2 );
        const float nx = verts->At( i + 3 );
        const float ny = verts->At( i + 4 );
        const float nz = verts->At( i + 5 );

        verts->Add( px );
        verts->Add( py );
        verts->Add( pz );
        verts->Add( -nx );
        verts->Add( -ny );
        verts->Add( -nz );
    }

    for( int32_t i = 0; i < indexCount; i += 3 )
    {// reversed winding, so these faces are the front ones when seen from inside
        const uint16_t a = indices->At( i );
        const uint16_t b = indices->At( i + 1 );
        const uint16_t c = indices->At( i + 2 );

        indices->Add( static_cast<uint16_t>( base + a ) );
        indices->Add( static_cast<uint16_t>( base + c ) );
        indices->Add( static_cast<uint16_t>( base + b ) );
    }
}
/*static*/ void crPrimitiveGeometry::BuildOctahedron( crArray<float>* verts, crArray<uint16_t>* indices )
{
    // one hard-normal triangle per octant, CCW seen from outside
    verts->Reserve( 8 * 3 * 6 );
    indices->Reserve( 8 * 3 );

    const float invSqrt3 = 0.5773503f;

    for( int32_t oct = 0; oct < 8; ++oct )
    {
        const float sx = ( ( oct & 1 ) != 0 ) ? -1.0f : 1.0f;
        const float sy = ( ( oct & 2 ) != 0 ) ? -1.0f : 1.0f;
        const float sz = ( ( oct & 4 ) != 0 ) ? -1.0f : 1.0f;

        float corners[ 3 ][ 3 ] =
        {
            { sx, 0.0f, 0.0f },
            { 0.0f, sy, 0.0f },
            { 0.0f, 0.0f, sz },
        };
        if( ( sx * sy * sz ) < 0.0f )
        {// mirrored octant — swap two corners to keep the winding outward
            for( int32_t k = 0; k < 3; ++k )
            {
                const float tmp    = corners[ 1 ][ k ];
                corners[ 1 ][ k ]  = corners[ 2 ][ k ];
                corners[ 2 ][ k ]  = tmp;
            }
        }

        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( corners[ v ][ k ] );
            verts->Add( sx * invSqrt3 );
            verts->Add( sy * invSqrt3 );
            verts->Add( sz * invSqrt3 );

            indices->Add( static_cast<uint16_t>( ( oct * 3 ) + v ) );
        }
    }
}
/*static*/ void crPrimitiveGeometry::BuildIcosahedron( crArray<float>* verts, crArray<uint16_t>* indices )
{
    // hard faceted, one flat normal per face (normal = normalized centroid)
    constexpr float PHI = 1.6180339f;

    static const float ICO_VERTS[ 12 ][ 3 ] =
    {
        { -1.0f,  PHI,  0.0f }, {  1.0f,  PHI,  0.0f }, { -1.0f, -PHI,  0.0f }, {  1.0f, -PHI,  0.0f },
        {  0.0f, -1.0f,  PHI }, {  0.0f,  1.0f,  PHI }, {  0.0f, -1.0f, -PHI }, {  0.0f,  1.0f, -PHI },
        {  PHI,  0.0f, -1.0f }, {  PHI,  0.0f,  1.0f }, { -PHI,  0.0f, -1.0f }, { -PHI,  0.0f,  1.0f },
    };
    static const uint16_t ICO_FACES[ 20 ][ 3 ] =
    {
        { 0, 11, 5 }, { 0, 5, 1 }, { 0, 1, 7 }, { 0, 7, 10 }, { 0, 10, 11 },
        { 1, 5, 9 }, { 5, 11, 4 }, { 11, 10, 2 }, { 10, 7, 6 }, { 7, 1, 8 },
        { 3, 9, 4 }, { 3, 4, 2 }, { 3, 2, 6 }, { 3, 6, 8 }, { 3, 8, 9 },
        { 4, 9, 5 }, { 2, 4, 11 }, { 6, 2, 10 }, { 8, 6, 7 }, { 9, 8, 1 },
    };

    const float invCircum = 1.0f / crMath::Sqrt( 1.0f + ( PHI * PHI ) );   // unit circumradius

    verts->Reserve( 20 * 3 * 6 );
    indices->Reserve( 20 * 3 );

    for( int32_t f = 0; f < 20; ++f )
    {
        float centroid[ 3 ] = { 0.0f, 0.0f, 0.0f };
        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                centroid[ k ] += ICO_VERTS[ ICO_FACES[ f ][ v ] ][ k ];
        }
        const float centroidLen = crMath::Sqrt( ( centroid[ 0 ] * centroid[ 0 ] ) + ( centroid[ 1 ] * centroid[ 1 ] ) + ( centroid[ 2 ] * centroid[ 2 ] ) );

        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( ICO_VERTS[ ICO_FACES[ f ][ v ] ][ k ] * invCircum );
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( centroid[ k ] / centroidLen );

            indices->Add( static_cast<uint16_t>( ( f * 3 ) + v ) );
        }
    }
}
/*static*/ void crPrimitiveGeometry::BuildBeam( crArray<float>* verts, crArray<uint16_t>* indices )
{
    // a box subdivided along X so per-vertex warp shows traveling waves; hard face normals
    // side faces: ( normal, cross axis 1 = y / 2 = z, cross start -> end ) — orders keep CCW outward
    struct BeamFace
    {
        float   normal[ 3 ];
        int32_t crossAxis;
        float   crossStart;
        float   crossEnd;
    };
    static const BeamFace FACES[ 4 ] =
    {
        { { 0.0f,  1.0f,  0.0f }, 2,  1.0f, -1.0f },
        { { 0.0f, -1.0f,  0.0f }, 2, -1.0f,  1.0f },
        { { 0.0f,  0.0f,  1.0f }, 1, -1.0f,  1.0f },
        { { 0.0f,  0.0f, -1.0f }, 1,  1.0f, -1.0f },
    };

    verts->Reserve( ( 4 * ( BEAM_SEGMENTS + 1 ) * 2 + 8 ) * 6 );
    indices->Reserve( ( 4 * BEAM_SEGMENTS * 6 ) + 12 );

    for( int32_t f = 0; f < 4; ++f )
    {
        const BeamFace& face = FACES[ f ];
        const uint16_t  base = static_cast<uint16_t>( verts->Size() / 6 );

        for( int32_t c = 0; c <= BEAM_SEGMENTS; ++c )
        {
            const float x = -1.0f + ( ( static_cast<float>( c ) / static_cast<float>( BEAM_SEGMENTS ) ) * 2.0f );

            for( int32_t row = 0; row < 2; ++row )
            {
                const float cross = ( row == 0 ) ? face.crossStart : face.crossEnd;

                float p[ 3 ] = { x, face.normal[ 1 ], face.normal[ 2 ] };
                p[ face.crossAxis ] = cross;

                for( int32_t k = 0; k < 3; ++k )
                    verts->Add( p[ k ] );
                for( int32_t k = 0; k < 3; ++k )
                    verts->Add( face.normal[ k ] );
            }
        }

        for( int32_t c = 0; c < BEAM_SEGMENTS; ++c )
        {
            const uint16_t a = static_cast<uint16_t>( base + ( c * 2 ) );         // (c, start)
            const uint16_t b = static_cast<uint16_t>( base + ( c * 2 ) + 2 );     // (c+1, start)
            const uint16_t d = static_cast<uint16_t>( base + ( c * 2 ) + 3 );     // (c+1, end)
            const uint16_t e = static_cast<uint16_t>( base + ( c * 2 ) + 1 );     // (c, end)

            indices->Add( a );
            indices->Add( b );
            indices->Add( d );
            indices->Add( a );
            indices->Add( d );
            indices->Add( e );
        }
    }

    {// end caps
        static const float CAPS[ 2 ][ 4 ][ 3 ] =
        {
            { {  1.0f, -1.0f, -1.0f }, {  1.0f,  1.0f, -1.0f }, {  1.0f,  1.0f,  1.0f }, {  1.0f, -1.0f,  1.0f } },
            { { -1.0f, -1.0f, -1.0f }, { -1.0f, -1.0f,  1.0f }, { -1.0f,  1.0f,  1.0f }, { -1.0f,  1.0f, -1.0f } },
        };

        for( int32_t s = 0; s < 2; ++s )
        {
            const float    nx   = ( s == 0 ) ? 1.0f : -1.0f;
            const uint16_t base = static_cast<uint16_t>( verts->Size() / 6 );

            for( int32_t v = 0; v < 4; ++v )
            {
                for( int32_t k = 0; k < 3; ++k )
                    verts->Add( CAPS[ s ][ v ][ k ] );
                verts->Add( nx );
                verts->Add( 0.0f );
                verts->Add( 0.0f );
            }

            indices->Add( base );
            indices->Add( static_cast<uint16_t>( base + 1 ) );
            indices->Add( static_cast<uint16_t>( base + 2 ) );
            indices->Add( base );
            indices->Add( static_cast<uint16_t>( base + 2 ) );
            indices->Add( static_cast<uint16_t>( base + 3 ) );
        }
    }
}
/*static*/ void crPrimitiveGeometry::BuildRing( crArray<float>* verts, crArray<uint16_t>* indices, float inner )
{
    // both faces emitted so back-face culling keeps it two-sided
    verts->Reserve( ( RING_SEGMENTS + 1 ) * 2 * 2 * 6 );
    indices->Reserve( RING_SEGMENTS * 6 * 2 );

    for( int32_t side = 0; side < 2; ++side )
    {
        const float nz = ( side == 0 ) ? 1.0f : -1.0f;

        for( int32_t s = 0; s <= RING_SEGMENTS; ++s )
        {
            const float  theta = ( static_cast<float>( s ) / static_cast<float>( RING_SEGMENTS ) ) * ( 2.0f * crMath::PI );
            const float2 ct    = crMath::CosSin( theta );

            const float ringVerts[ 2 ][ 3 ] =
            {
                { ct.x, ct.y, 0.0f },                       // outer
                { ct.x * inner, ct.y * inner, 0.0f },       // inner
            };
            for( int32_t v = 0; v < 2; ++v )
            {
                for( int32_t k = 0; k < 3; ++k )
                    verts->Add( ringVerts[ v ][ k ] );
                verts->Add( 0.0f );
                verts->Add( 0.0f );
                verts->Add( nz );
            }
        }

        const uint16_t base = static_cast<uint16_t>( side * ( ( RING_SEGMENTS + 1 ) * 2 ) );
        for( int32_t s = 0; s < RING_SEGMENTS; ++s )
        {
            const uint16_t o0 = static_cast<uint16_t>( base + ( s * 2 ) );
            const uint16_t i0 = static_cast<uint16_t>( o0 + 1 );
            const uint16_t o1 = static_cast<uint16_t>( o0 + 2 );
            const uint16_t i1 = static_cast<uint16_t>( o0 + 3 );

            if( side == 0 )
            {// top (+Z): CCW seen from above
                indices->Add( o0 );
                indices->Add( o1 );
                indices->Add( i1 );
                indices->Add( o0 );
                indices->Add( i1 );
                indices->Add( i0 );
            }
            else
            {// bottom (-Z): reversed
                indices->Add( o0 );
                indices->Add( i1 );
                indices->Add( o1 );
                indices->Add( o0 );
                indices->Add( i0 );
                indices->Add( i1 );
            }
        }
    }
}
/*static*/ void crPrimitiveGeometry::BuildTetrahedron( crArray<float>* verts, crArray<uint16_t>* indices )
{
    // one hard-normal triangle per face, CCW seen from outside, unit circumradius
    static const float TET_VERTS[ 4 ][ 3 ] =
    {
        {  1.0f,  1.0f,  1.0f },
        {  1.0f, -1.0f, -1.0f },
        { -1.0f,  1.0f, -1.0f },
        { -1.0f, -1.0f,  1.0f },
    };
    static const uint16_t TET_FACES[ 4 ][ 3 ] =
    {
        { 0, 1, 2 }, { 0, 3, 1 }, { 0, 2, 3 }, { 1, 3, 2 },
    };

    const float invSqrt3 = 0.5773503f;

    verts->Reserve( 4 * 3 * 6 );
    indices->Reserve( 4 * 3 );

    for( int32_t f = 0; f < 4; ++f )
    {
        float centroid[ 3 ] = { 0.0f, 0.0f, 0.0f };
        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                centroid[ k ] += TET_VERTS[ TET_FACES[ f ][ v ] ][ k ];
        }
        const float centroidLen = crMath::Sqrt( ( centroid[ 0 ] * centroid[ 0 ] ) + ( centroid[ 1 ] * centroid[ 1 ] ) + ( centroid[ 2 ] * centroid[ 2 ] ) );

        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( TET_VERTS[ TET_FACES[ f ][ v ] ][ k ] * invSqrt3 );
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( centroid[ k ] / centroidLen );

            indices->Add( static_cast<uint16_t>( ( f * 3 ) + v ) );
        }
    }
}
// shared chip generator, gem-cut: the top is a fan of sloped facets rising to an apex, so a chip
// that only ever yaws still shifts light across its faces — a flat top under a top-down camera
// reads as a decal. rim sits at z 0, apex at +1, flat (never seen) bottom at -1; per-facet hard
// normals. the outline must be CCW seen from +Z and star-shaped about the origin
static void BuildChip( crArray<float>* verts, crArray<uint16_t>* indices, const float2* outline, int32_t count )
{
    verts->Reserve( ( count * 3 * 6 ) + ( ( count + 1 ) * 6 ) + ( count * 4 * 6 ) );
    indices->Reserve( ( count * 3 * 2 ) + ( count * 6 ) );

    for( int32_t i = 0; i < count; ++i )
    {// crown facets — apex + one outline edge each
        const float2 a = outline[ i ];
        const float2 b = outline[ ( i + 1 ) % count ];

        // cross( a - apex, b - apex ), apex = (0,0,1) — outward and up for a CCW outline
        float nx = ( ( a.y * -1.0f ) - ( -1.0f * b.y ) );
        float ny = ( ( -1.0f * b.x ) - ( a.x * -1.0f ) );
        float nz = ( ( a.x * b.y ) - ( a.y * b.x ) );
        const float len = crMath::Sqrt( ( nx * nx ) + ( ny * ny ) + ( nz * nz ) );
        nx /= len;
        ny /= len;
        nz /= len;

        const uint16_t base = static_cast<uint16_t>( ( verts->Size() / 6 ) );

        const float facet[ 3 ][ 3 ] =
        {
            { 0.0f, 0.0f, 1.0f },
            { a.x,  a.y,  0.0f },
            { b.x,  b.y,  0.0f },
        };
        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( facet[ v ][ k ] );
            verts->Add( nx );
            verts->Add( ny );
            verts->Add( nz );

            indices->Add( static_cast<uint16_t>( base + v ) );
        }
    }

    {// bottom — flat fan, faces away from the camera
        const uint16_t base = static_cast<uint16_t>( ( verts->Size() / 6 ) );

        verts->Add( 0.0f );
        verts->Add( 0.0f );
        verts->Add( -1.0f );
        verts->Add( 0.0f );
        verts->Add( 0.0f );
        verts->Add( -1.0f );

        for( int32_t i = 0; i < count; ++i )
        {
            verts->Add( outline[ i ].x );
            verts->Add( outline[ i ].y );
            verts->Add( -1.0f );
            verts->Add( 0.0f );
            verts->Add( 0.0f );
            verts->Add( -1.0f );
        }

        for( int32_t i = 0; i < count; ++i )
        {
            const uint16_t a = static_cast<uint16_t>( base + 1 + i );
            const uint16_t b = static_cast<uint16_t>( base + 1 + ( ( i + 1 ) % count ) );

            indices->Add( base );
            indices->Add( b );
            indices->Add( a );
        }
    }

    for( int32_t i = 0; i < count; ++i )
    {// rim walls, z 0 down to -1 — outward normal is the edge direction's right-hand perpendicular
        const float2 a = outline[ i ];
        const float2 b = outline[ ( i + 1 ) % count ];

        const float ex = ( b.x - a.x );
        const float ey = ( b.y - a.y );
        const float len = crMath::Sqrt( ( ex * ex ) + ( ey * ey ) );
        const float nx  = (  ey / len );
        const float ny  = ( -ex / len );

        const uint16_t base = static_cast<uint16_t>( ( verts->Size() / 6 ) );

        const float wall[ 4 ][ 3 ] =
        {
            { a.x, a.y,  0.0f },
            { b.x, b.y,  0.0f },
            { b.x, b.y, -1.0f },
            { a.x, a.y, -1.0f },
        };
        for( int32_t v = 0; v < 4; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( wall[ v ][ k ] );
            verts->Add( nx );
            verts->Add( ny );
            verts->Add( 0.0f );
        }

        indices->Add( base );
        indices->Add( static_cast<uint16_t>( base + 3 ) );
        indices->Add( static_cast<uint16_t>( base + 2 ) );
        indices->Add( base );
        indices->Add( static_cast<uint16_t>( base + 2 ) );
        indices->Add( static_cast<uint16_t>( base + 1 ) );
    }
}
/*static*/ void crPrimitiveGeometry::BuildArrow( crArray<float>* verts, crArray<uint16_t>* indices )
{
    static const float2 OUTLINE[ 3 ] =
    {
        {  1.0f,  0.0f },
        { -0.7f,  0.65f },
        { -0.7f, -0.65f },
    };
    BuildChip( verts, indices, OUTLINE, 3 );
}
/*static*/ void crPrimitiveGeometry::BuildHexagon( crArray<float>* verts, crArray<uint16_t>* indices )
{
    static const float2 OUTLINE[ 6 ] =
    {
        {  1.0f,  0.0f },
        {  0.5f,  0.8660254f },
        { -0.5f,  0.8660254f },
        { -1.0f,  0.0f },
        { -0.5f, -0.8660254f },
        {  0.5f, -0.8660254f },
    };
    BuildChip( verts, indices, OUTLINE, 6 );
}
/*static*/ void crPrimitiveGeometry::BuildStar( crArray<float>* verts, crArray<uint16_t>* indices )
{
    // outer points at the axes (+X stretched to the unit radius), waist points on the diagonals
    static const float2 OUTLINE[ 8 ] =
    {
        {  1.0f,   0.0f },
        {  0.184f, 0.184f },
        {  0.0f,   0.62f },
        { -0.184f, 0.184f },
        { -0.62f,  0.0f },
        { -0.184f, -0.184f },
        {  0.0f,  -0.62f },
        {  0.184f, -0.184f },
    };
    BuildChip( verts, indices, OUTLINE, 8 );
}
/*static*/ void crPrimitiveGeometry::BuildSpike( crArray<float>* verts, crArray<uint16_t>* indices )
{
    constexpr int32_t SIDES = 5;

    float2 ring[ SIDES ];
    for( int32_t i = 0; i < SIDES; ++i )
        ring[ i ] = crMath::CosSin( ( static_cast<float>( i ) / static_cast<float>( SIDES ) ) * ( 2.0f * crMath::PI ) );

    verts->Reserve( ( SIDES * 3 * 6 ) + ( ( SIDES + 1 ) * 6 ) );
    indices->Reserve( ( SIDES * 3 ) + ( SIDES * 3 ) );

    for( int32_t i = 0; i < SIDES; ++i )
    {// side facets — apex plus one base edge, flat normal each
        const float2 a = ring[ i ];
        const float2 b = ring[ ( i + 1 ) % SIDES ];

        const float facet[ 3 ][ 3 ] =
        {
            {  1.0f, 0.0f, 0.0f },
            { -1.0f, a.x,  a.y  },
            { -1.0f, b.x,  b.y  },
        };

        const float3 e0 = float3( facet[ 1 ][ 0 ] - facet[ 0 ][ 0 ], facet[ 1 ][ 1 ] - facet[ 0 ][ 1 ], facet[ 1 ][ 2 ] - facet[ 0 ][ 2 ] );
        const float3 e1 = float3( facet[ 2 ][ 0 ] - facet[ 0 ][ 0 ], facet[ 2 ][ 1 ] - facet[ 0 ][ 1 ], facet[ 2 ][ 2 ] - facet[ 0 ][ 2 ] );
        const float3 n  = crMath::Normalize3( crMath::Cross3( e0, e1 ) );

        const uint16_t base = static_cast<uint16_t>( verts->Size() / 6 );
        for( int32_t v = 0; v < 3; ++v )
        {
            for( int32_t k = 0; k < 3; ++k )
                verts->Add( facet[ v ][ k ] );
            verts->Add( n.x );
            verts->Add( n.y );
            verts->Add( n.z );

            indices->Add( static_cast<uint16_t>( base + v ) );
        }
    }

    {// base cap — fan wound to face -X
        const uint16_t base = static_cast<uint16_t>( verts->Size() / 6 );

        verts->Add( -1.0f );
        verts->Add( 0.0f );
        verts->Add( 0.0f );
        verts->Add( -1.0f );
        verts->Add( 0.0f );
        verts->Add( 0.0f );

        for( int32_t i = 0; i < SIDES; ++i )
        {
            verts->Add( -1.0f );
            verts->Add( ring[ i ].x );
            verts->Add( ring[ i ].y );
            verts->Add( -1.0f );
            verts->Add( 0.0f );
            verts->Add( 0.0f );
        }

        for( int32_t i = 0; i < SIDES; ++i )
        {
            indices->Add( base );
            indices->Add( static_cast<uint16_t>( base + 1 + ( ( i + 1 ) % SIDES ) ) );
            indices->Add( static_cast<uint16_t>( base + 1 + i ) );
        }
    }
}
/*static*/ void crPrimitiveGeometry::BuildDiamond( crArray<float>* verts, crArray<uint16_t>* indices )
{
    constexpr int32_t SIDES = 5;

    float2 ring[ SIDES ];
    for( int32_t i = 0; i < SIDES; ++i )
        ring[ i ] = crMath::CosSin( ( static_cast<float>( i ) / static_cast<float>( SIDES ) ) * ( 2.0f * crMath::PI ) );

    verts->Reserve( SIDES * 2 * 3 * 6 );
    indices->Reserve( SIDES * 2 * 3 );

    for( int32_t half = 0; half < 2; ++half )
    {// +X cone, then -X cone off the same waist ring — the two windings are mirrored
        const float apexX = ( half == 0 ) ? 1.0f : -1.0f;

        for( int32_t i = 0; i < SIDES; ++i )
        {
            const float2 a = ring[ ( half == 0 ) ? i : ( ( i + 1 ) % SIDES ) ];
            const float2 b = ring[ ( half == 0 ) ? ( ( i + 1 ) % SIDES ) : i ];

            const float facet[ 3 ][ 3 ] =
            {
                { apexX, 0.0f, 0.0f },
                {  0.0f, a.x,  a.y  },
                {  0.0f, b.x,  b.y  },
            };

            const float3 e0 = float3( facet[ 1 ][ 0 ] - facet[ 0 ][ 0 ], facet[ 1 ][ 1 ] - facet[ 0 ][ 1 ], facet[ 1 ][ 2 ] - facet[ 0 ][ 2 ] );
            const float3 e1 = float3( facet[ 2 ][ 0 ] - facet[ 0 ][ 0 ], facet[ 2 ][ 1 ] - facet[ 0 ][ 1 ], facet[ 2 ][ 2 ] - facet[ 0 ][ 2 ] );
            const float3 n  = crMath::Normalize3( crMath::Cross3( e0, e1 ) );

            const uint16_t base = static_cast<uint16_t>( verts->Size() / 6 );
            for( int32_t v = 0; v < 3; ++v )
            {
                for( int32_t k = 0; k < 3; ++k )
                    verts->Add( facet[ v ][ k ] );
                verts->Add( n.x );
                verts->Add( n.y );
                verts->Add( n.z );

                indices->Add( static_cast<uint16_t>( base + v ) );
            }
        }
    }
}
