void OptimizeMesh()

in MiniEngine/Model/MeshConvert.cpp [73:440]


void OptimizeMesh( Renderer::Primitive& outPrim, const glTF::Primitive& inPrim, const Math::Matrix4& localToObject )
{
    ASSERT(inPrim.attributes[0] != nullptr, "Must have POSITION");
    uint32_t vertexCount = inPrim.attributes[0]->count;

    void* indices = nullptr;
    uint32_t indexCount;
    bool b32BitIndices;
    uint32_t maxIndex = inPrim.maxIndex;

    if (inPrim.indices == nullptr)
    {
        ASSERT(inPrim.mode == 4, "Impossible primitive topology when lacking indices");

        indexCount = vertexCount * 3;
        maxIndex = indexCount - 1;
        if (indexCount > 0xFFFF)
        {
            b32BitIndices = true;
            outPrim.IB = std::make_shared<std::vector<byte>>(4 * indexCount);
            indices = outPrim.IB->data();
            uint32_t* tmp = (uint32_t*)indices;
            for (uint32_t i = 0; i < indexCount; ++i)
                tmp[i] = i;
        }
        else
        {
            b32BitIndices = false;
            outPrim.IB = std::make_shared<std::vector<byte>>(2 * indexCount);
            indices = outPrim.IB->data();
            uint16_t* tmp = (uint16_t*)indices;
            for (uint16_t i = 0; i < indexCount; ++i)
                tmp[i] = i;
        }
    }
    else
    {
        switch (inPrim.mode)
        {
        default:
        case 0: // POINT LIST
        case 1: // LINE LIST
        case 2: // LINE LOOP
        case 3: // LINE STRIP
            Utility::Printf("Found unsupported primitive topology\n");
            return;
        case 4: // TRIANGLE LIST
            break;
        case 5: // TODO: Convert TRIANGLE STRIP
        case 6: // TODO: Convert TRIANGLE FAN
            Utility::Printf("Found an index buffer that needs to be converted to a triangle list\n");
            return;
        }

        indices = inPrim.indices->dataPtr;
        indexCount = inPrim.indices->count;
        if (maxIndex == 0)
        {
            if (inPrim.indices->componentType == Accessor::kUnsignedInt)
            {
                uint32_t* ib = (uint32_t*)inPrim.indices->dataPtr;
                for (uint32_t k = 0; k < indexCount; ++k)
                    maxIndex = std::max(ib[k], maxIndex);
            }
            else
            {
                uint16_t* ib = (uint16_t*)inPrim.indices->dataPtr;
                for (uint32_t k = 0; k < indexCount; ++k)
                    maxIndex = std::max<uint32_t>(ib[k], maxIndex);
            }
        }
        b32BitIndices = maxIndex > 0xFFFF;
        uint32_t indexSize = b32BitIndices ? 4 : 2;
        outPrim.IB = std::make_shared<std::vector<byte>>(indexSize * indexCount);
        if (b32BitIndices)
        {
            ASSERT(inPrim.indices->componentType == Accessor::kUnsignedInt);
            OptimizeFaces((uint32_t*)inPrim.indices->dataPtr, inPrim.indices->count, (uint32_t*)outPrim.IB->data(), 64);
        }
        else if (inPrim.indices->componentType == Accessor::kUnsignedShort)
        {
            OptimizeFaces((uint16_t*)inPrim.indices->dataPtr, inPrim.indices->count, (uint16_t*)outPrim.IB->data(), 64);
        }
        else
        {
            OptimizeFaces((uint32_t*)inPrim.indices->dataPtr, inPrim.indices->count, (uint16_t*)outPrim.IB->data(), 64);
        }
        indices = outPrim.IB->data();
    }

    ASSERT(maxIndex > 0);

    const bool HasNormals = inPrim.attributes[glTF::Primitive::kNormal] != nullptr;
    const bool HasTangents = inPrim.attributes[glTF::Primitive::kTangent] != nullptr;
    const bool HasUV0 = inPrim.attributes[glTF::Primitive::kTexcoord0] != nullptr;
    const bool HasUV1 = inPrim.attributes[glTF::Primitive::kTexcoord1] != nullptr;
    const bool HasJoints = inPrim.attributes[glTF::Primitive::kJoints0] != nullptr;
    const bool HasWeights = inPrim.attributes[glTF::Primitive::kWeights0] != nullptr;
    const bool HasSkin = HasJoints && HasWeights;
    
    std::vector<D3D12_INPUT_ELEMENT_DESC> InputElements;
    InputElements.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, glTF::Primitive::kPosition});
    if (HasNormals)
    {
        InputElements.push_back({"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, glTF::Primitive::kNormal });
    }
    if (HasTangents)
    {
        InputElements.push_back({"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,  glTF::Primitive::kTangent });
    }
    if (HasUV0)
    {
        InputElements.push_back({ "TEXCOORD", 0,
            AccessorFormat(*inPrim.attributes[glTF::Primitive::kTexcoord0]),
            glTF::Primitive::kTexcoord0 });
    }
    if (HasUV1)
    {
        InputElements.push_back({ "TEXCOORD", 1,
            AccessorFormat(*inPrim.attributes[glTF::Primitive::kTexcoord1]),
            glTF::Primitive::kTexcoord1 });
    }
    if (HasSkin)
    {
        InputElements.push_back({ "BLENDINDICES", 0,
            JointIndexFormat(*inPrim.attributes[glTF::Primitive::kJoints0]),
            glTF::Primitive::kJoints0 });
        InputElements.push_back({ "BLENDWEIGHT", 0,
            AccessorFormat(*inPrim.attributes[glTF::Primitive::kWeights0]), 
            glTF::Primitive::kWeights0 });
    }

    VBReader vbr;
    vbr.Initialize({InputElements.data(), (uint32_t)InputElements.size()});

    for (uint32_t i = 0; i < Primitive::kNumAttribs; ++i)
    {
        Accessor* attrib = inPrim.attributes[i];
        if (attrib)
            vbr.AddStream(attrib->dataPtr, vertexCount, i, attrib->stride);
    }

    const glTF::Material& material = *inPrim.material;

    std::unique_ptr<XMFLOAT3[]> position;
    std::unique_ptr<XMFLOAT3[]> normal;
    std::unique_ptr<XMFLOAT4[]> tangent;
    std::unique_ptr<XMFLOAT2[]> texcoord0;
    std::unique_ptr<XMFLOAT2[]> texcoord1;
    std::unique_ptr<XMFLOAT4[]> joints;
    std::unique_ptr<XMFLOAT4[]> weights;
    position.reset(new XMFLOAT3[vertexCount]);
    normal.reset(new XMFLOAT3[vertexCount]);

    ASSERT_SUCCEEDED(vbr.Read(position.get(), "POSITION", 0, vertexCount));
    {
        // Local space bounds
        Vector3 sphereCenterLS = (Vector3(*(XMFLOAT3*)inPrim.minPos) + Vector3(*(XMFLOAT3*)inPrim.maxPos)) * 0.5f;
        Scalar maxRadiusLSSq(kZero);

        // Object space bounds
        // (This would be expressed better with an AffineTransform * Vector3)
        Vector3 sphereCenterOS = Vector3(localToObject * Vector4(sphereCenterLS));
        Scalar maxRadiusOSSq(kZero);

        outPrim.m_BBoxLS = AxisAlignedBox(kZero);
        outPrim.m_BBoxOS = AxisAlignedBox(kZero);

        for (uint32_t v = 0; v < vertexCount/*maxIndex*/; ++v)
        {
            Vector3 positionLS = Vector3(position[v]);
            maxRadiusLSSq = Max(maxRadiusLSSq, LengthSquare(sphereCenterLS - positionLS));

            outPrim.m_BBoxLS.AddPoint(positionLS);

            Vector3 positionOS = Vector3(localToObject * Vector4(positionLS));
            maxRadiusOSSq = Max(maxRadiusOSSq, LengthSquare(sphereCenterOS - positionOS));

            outPrim.m_BBoxOS.AddPoint(positionOS);
        }

        outPrim.m_BoundsLS = Math::BoundingSphere(sphereCenterLS, Sqrt(maxRadiusLSSq));
        outPrim.m_BoundsOS = Math::BoundingSphere(sphereCenterOS, Sqrt(maxRadiusOSSq));
        ASSERT(outPrim.m_BoundsOS.GetRadius() > 0.0f);
    }

    if (HasNormals)
    {
        ASSERT_SUCCEEDED(vbr.Read(normal.get(), "NORMAL", 0, vertexCount));
    }
    else
    {
        const size_t faceCount = indexCount / 3;

        if (b32BitIndices)
            ComputeNormals((const uint32_t*)indices, faceCount, position.get(), vertexCount, CNORM_DEFAULT, normal.get());
        else
            ComputeNormals((const uint16_t*)indices, faceCount, position.get(), vertexCount, CNORM_DEFAULT, normal.get());
    }

    if (HasUV0)
    {
        texcoord0.reset(new XMFLOAT2[vertexCount]);
        ASSERT_SUCCEEDED(vbr.Read(texcoord0.get(), "TEXCOORD", 0, vertexCount));
    }

    if (HasUV1)
    {
        texcoord1.reset(new XMFLOAT2[vertexCount]);
        ASSERT_SUCCEEDED(vbr.Read(texcoord1.get(), "TEXCOORD", 1, vertexCount));
    }

    if (HasTangents)
    {
        tangent.reset(new XMFLOAT4[vertexCount]);
        ASSERT_SUCCEEDED(vbr.Read(tangent.get(), "TANGENT", 0, vertexCount));
    }
    else
    {
        ASSERT(maxIndex < vertexCount);
        ASSERT(indexCount % 3 == 0);

        HRESULT hr = S_OK;

        if (HasUV0 && material.normalUV == 0)
        {
            tangent.reset(new XMFLOAT4[vertexCount]);
            if (b32BitIndices)
            {
                hr = ComputeTangentFrame((uint32_t*)indices, indexCount / 3, position.get(), normal.get(), texcoord0.get(),
                    vertexCount, tangent.get());
            }
            else
            {
                hr = ComputeTangentFrame((uint16_t*)indices, indexCount / 3, position.get(), normal.get(), texcoord0.get(),
                    vertexCount, tangent.get());
            }
        }
        else if (HasUV1 && material.normalUV == 1)
        {
            tangent.reset(new XMFLOAT4[vertexCount]);
            if (b32BitIndices)
            {
                hr = ComputeTangentFrame((uint32_t*)indices, indexCount / 3, position.get(), normal.get(), texcoord1.get(),
                    vertexCount, tangent.get());
            }
            else
            {
                hr = ComputeTangentFrame((uint16_t*)indices, indexCount / 3, position.get(), normal.get(), texcoord1.get(),
                    vertexCount, tangent.get());
            }
        }

        ASSERT_SUCCEEDED(hr, "Error generating a tangent frame");
    }

    if (HasSkin)
    {
        joints.reset(new XMFLOAT4[vertexCount]);
        weights.reset(new XMFLOAT4[vertexCount]);
        ASSERT_SUCCEEDED(vbr.Read(joints.get(), "BLENDINDICES", 0, vertexCount));
        ASSERT_SUCCEEDED(vbr.Read(weights.get(), "BLENDWEIGHT", 0, vertexCount));
    }

    // Use VBWriter to generate a new, interleaved and compressed vertex buffer
    std::vector<D3D12_INPUT_ELEMENT_DESC> OutputElements;

    outPrim.psoFlags = PSOFlags::kHasPosition | PSOFlags::kHasNormal;
    OutputElements.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT});
    OutputElements.push_back({"NORMAL", 0, DXGI_FORMAT_R10G10B10A2_UNORM, 0, D3D12_APPEND_ALIGNED_ELEMENT});
    if (tangent.get())
    {
        OutputElements.push_back({"TANGENT", 0, DXGI_FORMAT_R10G10B10A2_UNORM, 0, D3D12_APPEND_ALIGNED_ELEMENT});
        outPrim.psoFlags |= PSOFlags::kHasTangent;
    }
    if (texcoord0.get())
    {
        OutputElements.push_back({"TEXCOORD", 0, DXGI_FORMAT_R16G16_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT});
        outPrim.psoFlags |= PSOFlags::kHasUV0;
    }
    if (texcoord1.get())
    {
        OutputElements.push_back({"TEXCOORD", 1, DXGI_FORMAT_R16G16_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT});
        outPrim.psoFlags |= PSOFlags::kHasUV1;
    }
    if (HasSkin)
    {
        OutputElements.push_back({ "BLENDINDICES", 0, DXGI_FORMAT_R16G16B16A16_UINT, 0, D3D12_APPEND_ALIGNED_ELEMENT });
        OutputElements.push_back({ "BLENDWEIGHT", 0, DXGI_FORMAT_R16G16B16A16_UNORM, 0, D3D12_APPEND_ALIGNED_ELEMENT });
        outPrim.psoFlags |= PSOFlags::kHasSkin;
    }
    if (material.alphaBlend)
        outPrim.psoFlags |= PSOFlags::kAlphaBlend;
    if (material.alphaTest)
        outPrim.psoFlags |= PSOFlags::kAlphaTest;
    if (material.twoSided)
        outPrim.psoFlags |= PSOFlags::kTwoSided;

    D3D12_INPUT_LAYOUT_DESC layout = {OutputElements.data(), (uint32_t)OutputElements.size()};

    VBWriter vbw;
    vbw.Initialize(layout);

    uint32_t offsets[10];
    uint32_t strides[D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
    ComputeInputLayout(layout, offsets, strides);
    uint32_t stride = strides[0];

    outPrim.VB = std::make_shared<std::vector<byte>>(stride * vertexCount);
    ASSERT_SUCCEEDED(vbw.AddStream(outPrim.VB->data(), vertexCount, 0, stride));

    vbw.Write( position.get(), "POSITION", 0, vertexCount );
    vbw.Write( normal.get(), "NORMAL", 0, vertexCount, true );
    if (tangent.get())
        vbw.Write( tangent.get(), "TANGENT", 0, vertexCount, true );
    if (texcoord0.get())
        vbw.Write( texcoord0.get(), "TEXCOORD", 0, vertexCount );
    if (texcoord1.get())
        vbw.Write( texcoord1.get(), "TEXCOORD", 1, vertexCount );
    if (HasSkin)
    {
        vbw.Write(joints.get(), "BLENDINDICES", 0, vertexCount);
        vbw.Write(weights.get(), "BLENDWEIGHT", 0, vertexCount);
    }

    // Now write a VB for positions only (or positions and UV when alpha testing)
    uint32_t depthStride = 12;
    std::vector<D3D12_INPUT_ELEMENT_DESC> DepthElements;
    DepthElements.push_back({"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT});
    if (material.alphaTest)
    {
        depthStride += 4;
        DepthElements.push_back({"TEXCOORD", 0, DXGI_FORMAT_R16G16_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT});
    }
    if (HasSkin)
    {
        depthStride += 16;
        DepthElements.push_back({ "BLENDINDICES", 0, DXGI_FORMAT_R16G16B16A16_UINT, 0, D3D12_APPEND_ALIGNED_ELEMENT });
        DepthElements.push_back({ "BLENDWEIGHT", 0, DXGI_FORMAT_R16G16B16A16_UNORM, 0, D3D12_APPEND_ALIGNED_ELEMENT });
    }

    VBWriter dvbw;
    dvbw.Initialize({DepthElements.data(), (uint32_t)DepthElements.size()});

    outPrim.DepthVB = std::make_shared<std::vector<byte>>(depthStride * vertexCount);
    ASSERT_SUCCEEDED(dvbw.AddStream(outPrim.DepthVB->data(), vertexCount, 0, depthStride));

    dvbw.Write( position.get(), "POSITION", 0, vertexCount );
    if (material.alphaTest)
    {
        dvbw.Write(material.baseColorUV ? texcoord1.get() : texcoord0.get(), "TEXCOORD", 0, vertexCount);
    }
    if (HasSkin)
    {
        dvbw.Write(joints.get(), "BLENDINDICES", 0, vertexCount);
        dvbw.Write(weights.get(), "BLENDWEIGHT", 0, vertexCount);
    }

    ASSERT(material.index < 0x8000, "Only 15-bit material indices allowed");

    outPrim.vertexStride = (uint16_t)stride;
    outPrim.index32 = b32BitIndices ? 1 : 0;
    outPrim.materialIdx = material.index;

    outPrim.primCount = indexCount;

    // TODO:  Generate optimized depth-only streams
}