/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */

#include "FixedShapeProcessor.h"
#include "AuxGeomDrawProcessorShared.h"

#include <AzCore/std/algorithm.h>
#include <AzCore/std/containers/array.h>

#include <Atom/RHI/Factory.h>
#include <Atom/RHI/DrawPacketBuilder.h>

#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>

#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Shader/ShaderVariant.h>
#include <Atom/RPI.Public/View.h>

namespace AZ
{
    namespace Render
    {
        namespace
        {
            static constexpr u8 PositionStreamIndex = 0;
            static constexpr u8 NormalStreamIndex = 1;

            static const char* const ShapePerspectiveTypeViewProjection = "ViewProjectionMode::ViewProjection";
            static const char* const ShapePerspectiveTypeManualOverride = "ViewProjectionMode::ManualOverride";
            AZ::Name GetAuxGeomPerspectiveTypeName(AuxGeomShapePerpectiveType shapePerspectiveType)
            {
                switch (shapePerspectiveType)
                {
                    case PerspectiveType_ViewProjection:
                        return Name(ShapePerspectiveTypeViewProjection);
                    case PerspectiveType_ManualOverride:
                        return Name(ShapePerspectiveTypeManualOverride);
                    default:
                        AZ_Assert(false, "Invalid perspective type value %d", aznumeric_cast<int>(shapePerspectiveType));
                        return Name(ShapePerspectiveTypeViewProjection);
                }
            }
        }

        bool FixedShapeProcessor::Initialize(RHI::MultiDevice::DeviceMask deviceMask, const AZ::RPI::Scene* scene)
        {
            RHI::BufferPoolDescriptor desc;
            desc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
            desc.m_bindFlags = RHI::BufferBindFlags::InputAssembly;
            desc.m_deviceMask = deviceMask;

            m_bufferPool = aznew RHI::BufferPool;
            m_bufferPool->SetName(Name("AuxGeomFixedShapeBufferPool"));
            RHI::ResultCode resultCode = m_bufferPool->Init(desc);

            if (resultCode != RHI::ResultCode::Success)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to initialize AuxGeom fixed shape buffer pool");
                return false;
            }

            SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Point], RHI::PrimitiveTopology::PointList, false);
            SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Line], RHI::PrimitiveTopology::LineList, false);
            SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Solid], RHI::PrimitiveTopology::TriangleList, false);
            SetupInputStreamLayout(m_objectStreamLayout[DrawStyle_Shaded], RHI::PrimitiveTopology::TriangleList, true);

            CreateSphereBuffersAndViews(AuxGeomShapeType::ShapeType_Sphere);
            CreateSphereBuffersAndViews(AuxGeomShapeType::ShapeType_Hemisphere);
            CreateQuadBuffersAndViews();
            CreateDiskBuffersAndViews();
            CreateConeBuffersAndViews();
            CreateCylinderBuffersAndViews(AuxGeomShapeType::ShapeType_Cylinder);
            CreateCylinderBuffersAndViews(AuxGeomShapeType::ShapeType_CylinderNoEnds);
            CreateBoxBuffersAndViews();

            // cache scene pointer for RHI::PipelineState creation.
            m_scene = scene;

            LoadShaders();

            return true;
        }

        void FixedShapeProcessor::Release()
        {
            if (m_bufferPool)
            {
                m_bufferPool.reset();
            }

            m_processSrgs.clear();
            m_drawPackets.clear();

            m_litShader = nullptr;
            m_unlitShader = nullptr;
            m_scene = nullptr;

            for (RPI::Ptr<RPI::PipelineStateForDraw>* pipelineState : m_createdPipelineStates)
            {
                pipelineState->reset();
            }
            m_createdPipelineStates.clear();
            m_needUpdatePipelineStates = false;
        }

        void FixedShapeProcessor::PrepareFrame()
        {
            if (m_needUpdatePipelineStates)
            {
                // for created pipeline state, re-set their data from scene
                for (RPI::Ptr<RPI::PipelineStateForDraw>* pipelineState : m_createdPipelineStates)
                {
                    (*pipelineState)->SetOutputFromScene(m_scene);
                    (*pipelineState)->Finalize();
                }
                m_needUpdatePipelineStates = false;
            }
        }
        
        void FixedShapeProcessor::FrameEnd()
        {
            m_processSrgs.clear();
            m_drawPackets.clear();
        }

        void FixedShapeProcessor::ProcessObjects(const AuxGeomBufferData* bufferData, const RPI::FeatureProcessor::RenderPacket& fpPacket)
        {
            AZ_PROFILE_SCOPE(AzRender, "FixedShapeProcessor: ProcessObjects");

            RHI::DrawPacketBuilder drawPacketBuilder{RHI::MultiDevice::AllDevices};

            // Draw opaque shapes with LODs. This requires a separate draw packet per shape per view that it is in (usually only one)

            // We do each draw style together to reduce state changes
            for (int drawStyle = 0; drawStyle < DrawStyle_Count; ++drawStyle)
            {
                auto drawListTag = GetShaderDataForDrawStyle(drawStyle).m_drawListTag;

                // Skip this draw style if the owner scene doesn't have this drawListTag (which means this FP won't even create the RHI PipelineState for draw)
                if (!m_scene->HasOutputForPipelineState(drawListTag))
                {
                    return;
                }

                // Draw all of the opaque shapes of this draw style
                // Possible TODO: Batch together shapes of the same type and LOD using instanced draw [ATOM-1032]
                // Note that this optimization may not be worth it for shapes because of LODs
                for (const auto& shape : bufferData->m_opaqueShapes[drawStyle])
                {
                    PipelineStateOptions pipelineStateOptions;
                    pipelineStateOptions.m_perpectiveType = (AuxGeomShapePerpectiveType)(shape.m_viewProjOverrideIndex >= 0);
                    pipelineStateOptions.m_blendMode = BlendMode_Off;
                    pipelineStateOptions.m_drawStyle = (AuxGeomDrawStyle)drawStyle;
                    pipelineStateOptions.m_depthReadType = shape.m_depthRead;
                    pipelineStateOptions.m_depthWriteType = shape.m_depthWrite;
                    pipelineStateOptions.m_faceCullMode = shape.m_faceCullMode;

                    RPI::Ptr<RPI::PipelineStateForDraw> pipelineState = GetPipelineState(pipelineStateOptions);

                    const AZ::Vector3 position = shape.m_position;
                    const AZ::Vector3 scale = shape.m_scale;

                    for (auto& view : fpPacket.m_views)
                    {
                        // If this view is ignoring packets with our draw list tag then skip this view
                        if (!view->HasDrawListTag(drawListTag))
                        {
                            continue;
                        }
                        LodIndex lodIndex = GetLodIndexForShape(shape.m_shapeType, view.get(), position, scale);
                        auto drawPacket = BuildDrawPacketForShape(
                            drawPacketBuilder, shape, drawStyle, bufferData->m_viewProjOverrides, pipelineState, lodIndex);
                        if (drawPacket)
                        {
                            m_drawPackets.emplace_back(drawPacket);
                            view->AddDrawPacket(drawPacket.get());
                        }
                    }
                }

                for (const auto& box : bufferData->m_opaqueBoxes[drawStyle])
                {
                    PipelineStateOptions pipelineStateOptions;
                    pipelineStateOptions.m_perpectiveType = (AuxGeomShapePerpectiveType)(box.m_viewProjOverrideIndex >= 0);
                    pipelineStateOptions.m_blendMode = BlendMode_Off;
                    pipelineStateOptions.m_drawStyle = (AuxGeomDrawStyle)drawStyle;
                    pipelineStateOptions.m_depthReadType = box.m_depthRead;
                    pipelineStateOptions.m_depthWriteType = box.m_depthWrite;
                    pipelineStateOptions.m_faceCullMode = box.m_faceCullMode;

                    RPI::Ptr<RPI::PipelineStateForDraw> pipelineState = GetPipelineState(pipelineStateOptions);

                    auto drawPacket =
                        BuildDrawPacketForBox(drawPacketBuilder, box, drawStyle, bufferData->m_viewProjOverrides, pipelineState);
                    if (drawPacket)
                    {
                        m_drawPackets.emplace_back(drawPacket);

                        for (auto& view : fpPacket.m_views)
                        {
                            // If this view is ignoring packets with our draw list tag then skip this view
                            if (!view->HasDrawListTag(drawListTag))
                            {
                                continue;
                            }
                            view->AddDrawPacket(drawPacket.get());
                        }
                    }
                }

            }

            // Draw all of the translucent objects (shapes and boxes) with a distance sort key per view
            // We have to create separate draw packets for each view that the AuxGeom is in (typically only one)
            // because of distance sorting
            for (int drawStyle = 0; drawStyle < DrawStyle_Count; ++drawStyle)
            {
                auto drawListTag = GetShaderDataForDrawStyle(drawStyle).m_drawListTag;

                // Skip this draw style if the owner scene doesn't have this drawListTag (which means this FP won't even create the RHI PipelineState for draw)
                if (!m_scene->HasOutputForPipelineState(drawListTag))
                {
                    return;
                }

                // Draw all the shapes of this draw style
                for (const auto& shape : bufferData->m_translucentShapes[drawStyle])
                {
                    PipelineStateOptions pipelineStateOptions;
                    pipelineStateOptions.m_perpectiveType = (AuxGeomShapePerpectiveType)(shape.m_viewProjOverrideIndex >= 0);
                    pipelineStateOptions.m_blendMode = BlendMode_Alpha;
                    pipelineStateOptions.m_drawStyle = (AuxGeomDrawStyle)drawStyle;
                    pipelineStateOptions.m_depthReadType = shape.m_depthRead;
                    pipelineStateOptions.m_depthWriteType = shape.m_depthWrite;
                    pipelineStateOptions.m_faceCullMode = shape.m_faceCullMode;

                    RPI::Ptr<RPI::PipelineStateForDraw> pipelineState = GetPipelineState(pipelineStateOptions);

                    const AZ::Vector3 position = shape.m_position;
                    const AZ::Vector3 scale = shape.m_scale;
                    for (auto& view : fpPacket.m_views)
                    {
                        // If this view is ignoring packets with our draw list tag then skip this view
                        if (!view->HasDrawListTag(drawListTag))
                        {
                            continue;
                        }
                        RHI::DrawItemSortKey sortKey = view->GetSortKeyForPosition(position);
                        LodIndex lodIndex = GetLodIndexForShape(shape.m_shapeType, view.get(), position, scale);

                        auto drawPacket = BuildDrawPacketForShape(
                            drawPacketBuilder, shape, drawStyle, bufferData->m_viewProjOverrides, pipelineState, lodIndex, sortKey);
                        if (drawPacket)
                        {
                            m_drawPackets.emplace_back(drawPacket);
                            view->AddDrawPacket(drawPacket.get());
                        }
                    }
                }

                // Draw all the boxes of this draw style
                for (const auto& box : bufferData->m_translucentBoxes[drawStyle])
                {
                    PipelineStateOptions pipelineStateOptions;
                    pipelineStateOptions.m_perpectiveType = (AuxGeomShapePerpectiveType)(box.m_viewProjOverrideIndex >= 0);
                    pipelineStateOptions.m_blendMode = BlendMode_Alpha;
                    pipelineStateOptions.m_drawStyle = (AuxGeomDrawStyle)drawStyle;
                    pipelineStateOptions.m_depthReadType = box.m_depthRead;
                    pipelineStateOptions.m_depthWriteType = box.m_depthWrite;
                    pipelineStateOptions.m_faceCullMode = box.m_faceCullMode;

                    RPI::Ptr<RPI::PipelineStateForDraw> pipelineState = GetPipelineState(pipelineStateOptions);

                    const AZ::Vector3 position = box.m_position;
                    for (auto& view : fpPacket.m_views)
                    {
                        // If this view is ignoring packets with our draw list tag then skip this view
                        if (!view->HasDrawListTag(drawListTag))
                        {
                            continue;
                        }
                        RHI::DrawItemSortKey sortKey = view->GetSortKeyForPosition(position);
                        auto drawPacket = BuildDrawPacketForBox(
                            drawPacketBuilder, box, drawStyle, bufferData->m_viewProjOverrides, pipelineState, sortKey);
                        if (drawPacket)
                        {
                            m_drawPackets.emplace_back(drawPacket);
                            view->AddDrawPacket(drawPacket.get());
                        }
                    }
                }
            }
        }

        bool FixedShapeProcessor::CreateSphereBuffersAndViews(AuxGeomShapeType sphereShapeType)
        {
            AZ_Assert(sphereShapeType == ShapeType_Sphere || sphereShapeType == ShapeType_Hemisphere,
                "Trying to create sphere buffers and views with a non-sphere shape type!");

            const uint32_t numSphereLods = 5;
            struct LodInfo
            {
                uint32_t numRings;
                uint32_t numSections;
                float    screenPercentage;
            };
            const AZStd::array<LodInfo, numSphereLods> lodInfo =
            {{
                { 25, 25, 0.1000f},
                { 21, 21, 0.0100f},
                { 17, 17, 0.0010f},
                { 13, 13, 0.0001f},
                {  9,  9, 0.0000f}
            }};

            auto& m_shape = m_shapes[sphereShapeType];
            m_shape.m_numLods = numSphereLods;

            for (uint32_t lodIndex = 0; lodIndex < numSphereLods; ++lodIndex)
            {
                MeshData meshData;
                CreateSphereMeshData(meshData, lodInfo[lodIndex].numRings, lodInfo[lodIndex].numSections, sphereShapeType);

                ObjectBuffers objectBuffers;

                if (!CreateBuffersAndViews(objectBuffers, meshData))
                {
                    m_shape.m_numLods = 0;
                    return false;
                }

                m_shape.m_lodBuffers.emplace_back(objectBuffers);
                m_shape.m_lodScreenPercentages.push_back(lodInfo[lodIndex].screenPercentage);
            }

            return true;
        }

        void FixedShapeProcessor::CreateSphereMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections, AuxGeomShapeType sphereShapeType)
        {
            const float radius = 1.0f;

            // calculate "inner" vertices
            float sectionAngle(DegToRad(360.0f / static_cast<float>(numSections)));
            float ringSlice(DegToRad(180.0f / static_cast<float>(numRings)));

            uint32_t numberOfPoles = 2;

            if (sphereShapeType == ShapeType_Hemisphere)
            {
                numberOfPoles = 1;
                numRings = (numRings + 1) / 2;
                ringSlice = DegToRad(90.0f / static_cast<float>(numRings));
            }

            // calc required number of vertices/indices/triangles to build a sphere for the given parameters
            uint32_t numVertices = (numRings - 1) * numSections + numberOfPoles;

            // setup buffers
            auto& positions = meshData.m_positions;
            positions.clear();
            positions.reserve(numVertices);

            auto& normals = meshData.m_normals;
            normals.clear();
            normals.reserve(numVertices);

            using PosType = AuxGeomPosition;
            using NormalType = AuxGeomNormal;

            // 1st pole vertex
            positions.push_back(PosType(0.0f, radius, 0.0f));
            normals.push_back(NormalType(0.0f, 1.0f, 0.0f));

            for (uint32_t ring = 1; ring < numRings - numberOfPoles + 2; ++ring)
            {
                float w(sinf(ring * ringSlice));
                for (uint32_t section = 0; section < numSections; ++section)
                {
                    float x = radius * cosf(section * sectionAngle) * w;
                    float y = radius * cosf(ring * ringSlice);
                    float z = radius * sinf(section * sectionAngle) * w;
                    Vector3 radialVector(x, y, z);
                    positions.push_back(radialVector);
                    normals.push_back(radialVector.GetNormalized());
                }
            }

            if (sphereShapeType == ShapeType_Sphere)
            {
                // 2nd vertex of pole (for end cap)
                positions.push_back(PosType(0.0f, -radius, 0.0f));
                normals.push_back(NormalType(0.0f, -1.0f, 0.0f));
            }

            // point indices
            {
                auto& indices = meshData.m_pointIndices;
                indices.clear();
                indices.reserve(positions.size());

                for (uint16_t index = 0; index < aznumeric_cast<uint16_t>(positions.size()); ++index)
                {
                    indices.push_back(index);
                }
            }

            // line indices
            {
                // NumEdges = NumRingEdges + NumSectionEdges = (numRings * numSections) + (numRings * numSections)
                const uint32_t numEdges = numRings * numSections * 2;
                const uint32_t numLineIndices = numEdges * 2;

                // build "inner" faces
                auto& indices = meshData.m_lineIndices;
                indices.clear();
                indices.reserve(numLineIndices);

                for (uint16_t ring = 0; ring < numRings - numberOfPoles + 1; ++ring)
                {
                    uint16_t firstVertOfThisRing = static_cast<uint16_t>(1 + ring * numSections);
                    for (uint16_t section = 0; section < numSections; ++section)
                    {
                        uint32_t nextSection = (section + 1) % numSections;

                        // line around ring
                        indices.push_back(firstVertOfThisRing + section);
                        indices.push_back(static_cast<uint16_t>(firstVertOfThisRing + nextSection));

                        // line around section
                        int currentVertexIndex = firstVertOfThisRing + section;
                        // max 0 will implicitly handle the top pole
                        int previousVertexIndex = AZStd::max(currentVertexIndex - (int)numSections, 0);
                        indices.push_back(static_cast<uint16_t>(currentVertexIndex));
                        indices.push_back(static_cast<uint16_t>(previousVertexIndex));
                    }
                }

                if (sphereShapeType == ShapeType_Sphere)
                {
                    // build faces for bottom pole (to connect "inner" vertices with poles)
                    uint16_t lastPoleVert = static_cast<uint16_t>((numRings - 1) * numSections + 1);
                    uint16_t firstVertOfLastRing = static_cast<uint16_t>(1 + (numRings - 2) * numSections);
                    for (uint16_t section = 0; section < numSections; ++section)
                    {
                        indices.push_back(firstVertOfLastRing + section);
                        indices.push_back(lastPoleVert);
                    }
                }
            }

            // triangle indices
            {
                // NumTriangles = NumTrianglesAtPoles + NumQuads * 2
                //              = (numSections * 2) + ((numRings - 2) * numSections * 2)
                //              = (numSections * 2) * (numRings - 2 + 1)
                const uint32_t numTriangles = (numRings - 1) * numSections * 2;
                const uint32_t numTriangleIndices = numTriangles * 3;

                // build "inner" faces
                auto& indices = meshData.m_triangleIndices;
                indices.clear();
                indices.reserve(numTriangleIndices);

                for (uint32_t ring = 0; ring < numRings - numberOfPoles; ++ring)
                {
                    uint32_t firstVertOfThisRing = 1 + ring * numSections;
                    uint32_t firstVertOfNextRing = firstVertOfThisRing + numSections;

                    for (uint32_t section = 0; section < numSections; ++section)
                    {
                        uint32_t nextSection = (section + 1) % numSections;
                        indices.push_back(static_cast<uint16_t>(firstVertOfThisRing + section));
                        indices.push_back(static_cast<uint16_t>(firstVertOfThisRing + nextSection));
                        indices.push_back(static_cast<uint16_t>(firstVertOfNextRing + nextSection));

                        indices.push_back(static_cast<uint16_t>(firstVertOfNextRing + nextSection));
                        indices.push_back(static_cast<uint16_t>(firstVertOfNextRing + section));
                        indices.push_back(static_cast<uint16_t>(firstVertOfThisRing + section));
                    }
                }

                // build faces for end caps (to connect "inner" vertices with poles)
                uint32_t firstPoleVert = 0;
                uint32_t firstVertOfFirstRing = 1 + (0) * numSections;
                for (uint32_t section = 0; section < numSections; ++section)
                {
                    uint32_t nextSection = (section + 1) % numSections;
                    indices.push_back(static_cast<uint16_t>(firstVertOfFirstRing + nextSection));
                    indices.push_back(static_cast<uint16_t>(firstVertOfFirstRing + section));
                    indices.push_back(static_cast<uint16_t>(firstPoleVert));
                }

                if (sphereShapeType == ShapeType_Sphere)
                {
                    uint32_t lastPoleVert = (numRings - 1) * numSections + 1;
                    uint32_t firstVertOfLastRing = 1 + (numRings - 2) * numSections;
                    for (uint32_t section = 0; section < numSections; ++section)
                    {
                        uint32_t nextSection = (section + 1) % numSections;
                        indices.push_back(static_cast<uint16_t>(firstVertOfLastRing + section));
                        indices.push_back(static_cast<uint16_t>(firstVertOfLastRing + nextSection));
                        indices.push_back(static_cast<uint16_t>(lastPoleVert));
                    }
                }
            }
        }

        bool FixedShapeProcessor::CreateQuadBuffersAndViews()
        {
            auto& m_shape = m_shapes[ShapeType_Quad];
            m_shape.m_numLods = 1;

            MeshData meshData;
            CreateQuadMeshData(meshData, Facing::Both);

            ObjectBuffers objectBuffers;

            if (!CreateBuffersAndViews(objectBuffers, meshData))
            {
                m_shape.m_numLods = 0;
                return false;
            }

            m_shape.m_lodBuffers.emplace_back(objectBuffers);
            m_shape.m_lodScreenPercentages.push_back(0.0f);

            return true;
        }

        void FixedShapeProcessor::CreateQuadMeshDataSide(MeshData& meshData, bool isUp, bool drawLines)
        {
            uint16_t startPos = aznumeric_cast<uint16_t>(meshData.m_positions.size());

            // Positions
            meshData.m_positions.push_back(AuxGeomPosition(-0.5f, 0.0f,  0.5f));
            meshData.m_positions.push_back(AuxGeomPosition( 0.5f, 0.0f,  0.5f));
            meshData.m_positions.push_back(AuxGeomPosition(-0.5f, 0.0f, -0.5f));
            meshData.m_positions.push_back(AuxGeomPosition( 0.5f, 0.0f, -0.5f));

            // Normals
            AuxGeomNormal normal(0.0f, isUp ? 1.0f : -1.0f, 0.0f);
            meshData.m_normals.insert(meshData.m_normals.end(), { normal, normal, normal, normal });

            // Triangles
            if (isUp)
            {
                meshData.m_triangleIndices.insert(meshData.m_triangleIndices.end(), { 1, 2, 0, 3, 2, 1 });
            }
            else
            {
                meshData.m_triangleIndices.insert(meshData.m_triangleIndices.end(), { 0, 2, 1, 1, 2, 3 });
            }

            // Update indices based on starting position of vertex.
            for (size_t index = meshData.m_triangleIndices.size(); index < meshData.m_triangleIndices.size(); ++index)
            {
                meshData.m_triangleIndices.at(index) += startPos;
            }

            // Lines
            if (drawLines)
            {
                meshData.m_lineIndices.insert(meshData.m_lineIndices.end(), { 0, 1, 1, 2, 2, 3, 3, 0 });
                meshData.m_pointIndices.insert(meshData.m_pointIndices.end(), { 0, 1, 2, 3 });
            }
        }

        void FixedShapeProcessor::CreateQuadMeshData(MeshData& meshData, Facing facing)
        {
            if (facing == Facing::Up || facing == Facing::Both)
            {
                const bool isUp = true;
                const bool drawLines = true;
                CreateQuadMeshDataSide(meshData, isUp, drawLines);
            }
            if (facing == Facing::Down || facing == Facing::Both)
            {
                const bool isUp = false;
                const bool drawLines = facing != Facing::Both;
                CreateQuadMeshDataSide(meshData, isUp, drawLines);
            }
        }

        bool FixedShapeProcessor::CreateDiskBuffersAndViews()
        {
            const uint32_t numDiskLods = 5;
            struct LodInfo
            {
                uint32_t numSections;
                float    screenPercentage;
            };
            const AZStd::array<LodInfo, numDiskLods> lodInfo =
            {{
                {38, 0.1000f},
                {22, 0.0100f},
                {14, 0.0010f},
                {10, 0.0001f},
                { 8, 0.0000f}
            }};

            auto& m_shape = m_shapes[ShapeType_Disk];
            m_shape.m_numLods = numDiskLods;

            for (uint32_t lodIndex = 0; lodIndex < numDiskLods; ++lodIndex)
            {
                MeshData meshData;
                CreateDiskMeshData(meshData, lodInfo[lodIndex].numSections, Facing::Both);

                ObjectBuffers objectBuffers;

                if (!CreateBuffersAndViews(objectBuffers, meshData))
                {
                    m_shape.m_numLods = 0;
                    return false;
                }

                m_shape.m_lodBuffers.emplace_back(objectBuffers);
                m_shape.m_lodScreenPercentages.push_back(lodInfo[lodIndex].screenPercentage);
            }

            return true;
        }

        void FixedShapeProcessor::CreateDiskMeshDataSide(MeshData& meshData, uint32_t numSections, bool isUp, float yPosition)
        {
            AuxGeomNormal normal(0.0f, isUp ? 1.0f : -1.0f, 0.0f);

            // Create center position
            uint16_t centerIndex = aznumeric_cast<uint16_t>(meshData.m_positions.size());
            uint16_t firstSection = centerIndex + 1;

            meshData.m_positions.push_back(AuxGeomPosition(0.0f, yPosition, 0.0f));
            meshData.m_normals.push_back(normal);

            // Create ring around it
            const float radius = 1.0f;
            float sectionAngle(DegToRad(360.0f / (float)numSections));
            for (uint32_t section = 0; section < numSections; ++section)
            {
                meshData.m_positions.push_back(AuxGeomPosition(radius * cosf(section * sectionAngle), yPosition, radius * sinf(section * sectionAngle)));
                meshData.m_normals.push_back(normal);
            }

            // Create point indices
            for (uint16_t index = 0; index < aznumeric_cast<uint16_t>(meshData.m_positions.size()); ++index)
            {
                meshData.m_pointIndices.push_back(index);
            }

            // Create line indices
            for (uint32_t section = 0; section < numSections; ++section)
            {
                // Line from center of disk to outer edge
                meshData.m_lineIndices.push_back(centerIndex);
                meshData.m_lineIndices.push_back(static_cast<uint16_t>(firstSection + section));

                // Line from outer edge to next edge
                meshData.m_lineIndices.push_back(static_cast<uint16_t>(firstSection + section));
                uint32_t nextSection = (section + 1) % numSections;
                meshData.m_lineIndices.push_back(static_cast<uint16_t>(firstSection + nextSection));
            }

            // Create triangle indices
            for (uint32_t section = 0; section < numSections; ++section)
            {
                uint32_t nextSection = (section + 1) % numSections;
                meshData.m_triangleIndices.push_back(centerIndex);
                if (isUp)
                {
                    meshData.m_triangleIndices.push_back(static_cast<uint16_t>(firstSection + nextSection));
                    meshData.m_triangleIndices.push_back(static_cast<uint16_t>(firstSection + section));
                }
                else
                {
                    meshData.m_triangleIndices.push_back(static_cast<uint16_t>(firstSection + section));
                    meshData.m_triangleIndices.push_back(static_cast<uint16_t>(firstSection + nextSection));
                }
            }
        }

        void FixedShapeProcessor::CreateDiskMeshData(MeshData& meshData, uint32_t numSections, Facing facing, float yPosition)
        {
            if (facing == Facing::Up || facing == Facing::Both)
            {
                CreateDiskMeshDataSide(meshData, numSections, true, yPosition);
            }
            if (facing == Facing::Down || facing == Facing::Both)
            {
                CreateDiskMeshDataSide(meshData, numSections, false, yPosition);
            }
        }

        bool FixedShapeProcessor::CreateConeBuffersAndViews()
        {
            const uint32_t numConeLods = 5;
            struct LodInfo
            {
                uint32_t numRings;
                uint32_t numSections;
                float    screenPercentage;
            };
            const AZStd::array<LodInfo, numConeLods> lodInfo =
            {{
                { 16, 38, 0.1000f},
                { 8,  22, 0.0100f},
                { 4,  14, 0.0010f},
                { 2,  10, 0.0001f},
                { 1,   8, 0.0000f}
            }};

            auto& m_shape = m_shapes[ShapeType_Cone];
            m_shape.m_numLods = numConeLods;

            for (uint32_t lodIndex = 0; lodIndex < numConeLods; ++lodIndex)
            {
                MeshData meshData;
                CreateConeMeshData(meshData, lodInfo[lodIndex].numRings, lodInfo[lodIndex].numSections);

                ObjectBuffers objectBuffers;

                if (!CreateBuffersAndViews(objectBuffers, meshData))
                {
                    m_shape.m_numLods = 0;
                    return false;
                }

                m_shape.m_lodBuffers.emplace_back(objectBuffers);
                m_shape.m_lodScreenPercentages.push_back(lodInfo[lodIndex].screenPercentage);
            }

            return true;
        }

        void FixedShapeProcessor::CreateConeMeshData(MeshData& meshData, uint32_t numRings, uint32_t numSections)
        {
            AZ_Assert(numRings >= 1, "CreateConeMeshData: at least one ring is required");

            // Because we support DrawStyle::Shaded we need normals. Creating normals for a cone that shade
            // smoothly is actually not trival. One option is to create one vertex for the point with the normal
            // point along the Y axis. This doesn't give good shading anywhere except by the bottom cap.
            // That is what we do when numRings is one.
            // One approach is to create a ring of coincident verts at the point with the correct normals. But
            // that would give non-smooth shading.
            // So we sub-divide the cone into rings, the first subdivision being halfway between the base and the point.

            const float radius = 1.0f;
            const float height = 1.0f;

            // calc required number of vertices to build a cone for the given parameters
            uint32_t numVertices = numRings * numSections + numSections + 2;

            // setup buffers
            auto& positions = meshData.m_positions;
            positions.clear();
            positions.reserve(numVertices);

            auto& normals = meshData.m_normals;
            normals.clear();
            normals.reserve(numVertices);

            // Create bottom cap with normal facing down
            CreateDiskMeshData(meshData, numSections, Facing::Down);

            // Create vertices for the sides, the sides never quite reach the point. There is a single point vertex for that
            float sectionAngle(DegToRad(360.0f / (float)numSections));
            Vector3 conePoint(0.0f, height, 0.0f);
            for (uint32_t section = 0; section < numSections; ++section)
            {
                Vector3 pointOnCapEdge(radius * cosf(section * sectionAngle), 0.0f, radius * sinf(section * sectionAngle));

                Vector3 vecAlongConeSide(conePoint - pointOnCapEdge);
                Vector3 vecAlongCapEdge = pointOnCapEdge.Cross(vecAlongConeSide);

                Vector3 normal = vecAlongConeSide.Cross(vecAlongCapEdge).GetNormalized();

                float ringDistance = 0.0f;
                float ringSpacing = height * 0.5f;
                for (uint32_t ring = 0; ring < numRings; ++ring)
                {
                    Vector3 pointOnRing = pointOnCapEdge + vecAlongConeSide * ringDistance;
                    positions.push_back(pointOnRing);
                    normals.push_back(normal);

                    ringDistance += ringSpacing;
                    ringSpacing *= 0.5f;
                }
            }

            // cone point vertex
            positions.push_back(conePoint);
            normals.push_back(AuxGeomNormal(0.0f, 1.0f, 0.0f));

            // vertex indexes for start of the cone sides and for the cone point
            uint16_t indexOfSidesStart = static_cast<uint16_t>(numSections + 1);
            uint32_t indexOfConePoint = indexOfSidesStart + numRings * numSections;

            // indices for points
            {
                auto& indices = meshData.m_pointIndices;
                for (uint16_t index = 0; index < aznumeric_cast<uint16_t>(meshData.m_positions.size()); ++index)
                {
                    indices.push_back(index);
                }
            }

            // indices for lines (we ignore the rings beyond the first (at base) when drawing lines)
            {
                auto& indices = meshData.m_lineIndices;

                // build lines between already completed cap for each section
                for (uint16_t section = 0; section < numSections; ++section)
                {
                    indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * section));
                    indices.push_back(static_cast<uint16_t>(indexOfConePoint));
                }
            }

            // indices for triangles
            {
                auto& indices = meshData.m_triangleIndices;

                // build faces
                for (uint16_t section = 0; section < numSections; ++section)
                {
                    uint16_t nextSection = (section + 1) % numSections;

                    // faces from end cap to close to point
                    for (uint32_t ring = 0; ring < numRings - 1; ++ring)
                    {
                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * nextSection + ring + 1));
                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * nextSection + ring));
                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * section + ring));

                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * section + ring));
                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * section + ring + 1));
                        indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * nextSection + ring + 1));
                    }

                    // faces for point (from last ring of verts to point)
                    indices.push_back(static_cast<uint16_t>(indexOfConePoint));
                    indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * nextSection + numRings - 1));
                    indices.push_back(static_cast<uint16_t>(indexOfSidesStart + numRings * section + numRings - 1));
                }
            }
        }

        bool FixedShapeProcessor::CreateCylinderBuffersAndViews(AuxGeomShapeType cylinderShapeType)
        {
            AZ_Assert(cylinderShapeType == ShapeType_Cylinder || cylinderShapeType == ShapeType_CylinderNoEnds,
                "Trying to create cylinder buffers and views with a non-cylinder shape type!");

            const uint32_t numCylinderLods = 5;
            struct LodInfo
            {
                uint32_t numSections;
                float    screenPercentage;
            };
            const AZStd::array<LodInfo, numCylinderLods> lodInfo =
            { {
                { 38, 0.1000f},
                { 22, 0.0100f},
                { 14, 0.0010f},
                { 10, 0.0001f},
                {  8, 0.0000f}
            } };

            auto& m_shape = m_shapes[cylinderShapeType];
            m_shape.m_numLods = numCylinderLods;

            for (uint32_t lodIndex = 0; lodIndex < numCylinderLods; ++lodIndex)
            {
                MeshData meshData;
                CreateCylinderMeshData(meshData, lodInfo[lodIndex].numSections, cylinderShapeType);

                ObjectBuffers objectBuffers;

                if (!CreateBuffersAndViews(objectBuffers, meshData))
                {
                    m_shape.m_numLods = 0;
                    return false;
                }

                m_shape.m_lodBuffers.emplace_back(objectBuffers);
                m_shape.m_lodScreenPercentages.push_back(lodInfo[lodIndex].screenPercentage);
            }

            return true;
        }

        void FixedShapeProcessor::CreateCylinderMeshData(MeshData& meshData, uint32_t numSections, AuxGeomShapeType cylinderShapeType)
        {
            const float radius = 1.0f;
            const float height = 1.0f;

            //uint16_t indexOfBottomCenter = 0;
            //uint16_t indexOfBottomStart = 1;
            //uint16_t indexOfTopCenter = numSections + 1;
            //uint16_t indexOfTopStart = numSections + 2;
            uint16_t indexOfSidesStart = static_cast<uint16_t>(2 * numSections + 2);

            if (cylinderShapeType == ShapeType_CylinderNoEnds)
            {
                // We won't draw disks at the ends of the cylinder, so no need to offset side indices
                indexOfSidesStart = 0;
            }

            // calc required number of vertices to build a cylinder for the given parameters
            uint32_t numVertices = indexOfSidesStart + 2 * numSections;

            // setup buffers
            auto& positions = meshData.m_positions;
            positions.clear();
            positions.reserve(numVertices);

            auto& normals = meshData.m_normals;
            normals.clear();
            normals.reserve(numVertices);

            float bottomHeight = -height * 0.5f;
            float topHeight = height * 0.5f;

            // Create caps
            if (cylinderShapeType == ShapeType_Cylinder)
            {
                CreateDiskMeshData(meshData, numSections, Facing::Down, bottomHeight);
                CreateDiskMeshData(meshData, numSections, Facing::Up, topHeight);
            }

            // create vertices for side (so normal points out correctly)
            float sectionAngle(DegToRad(360.0f / (float)numSections));
            for (uint32_t section = 0; section < numSections; ++section)
            {
                Vector3 bottom(radius * cosf(section * sectionAngle), bottomHeight, radius * sinf(section * sectionAngle));
                Vector3 top = bottom + Vector3(0.0f, height, 0.0f);
                Vector3 normal = bottom.GetNormalized();

                positions.push_back(bottom);
                normals.push_back(normal);

                positions.push_back(top);
                normals.push_back(normal);
            }

            // build point indices
            {
                auto& indices = meshData.m_pointIndices;
                for (uint16_t index = 0; index < aznumeric_cast<uint16_t>(positions.size()); ++index)
                {
                    indices.push_back(index);
                }
            }

            // build lines for each section between the already created caps
            {
                auto& indices = meshData.m_lineIndices;
                for (uint16_t section = 0; section < numSections; ++section)
                {
                    // line between the caps
                    indices.push_back(indexOfSidesStart + 2 * section);
                    indices.push_back(indexOfSidesStart + 2 * section + 1);
                }

                // If we're not drawing the disks at the ends of the cylinder, we still want to
                // draw a ring around the end to join the tips of lines we created just above
                if (cylinderShapeType == ShapeType_CylinderNoEnds)
                {
                    for (uint16_t section = 0; section < numSections; ++section)
                    {
                        uint16_t nextSection = (section + 1) % numSections;

                        // line around the bottom cap
                        indices.push_back(section * 2);
                        indices.push_back(nextSection * 2);

                        // line around the top cap
                        indices.push_back(section * 2 + 1);
                        indices.push_back(nextSection * 2 + 1);
                    }
                }
            }

            // indices for triangles
            {
                auto& indices = meshData.m_triangleIndices;

                // build faces for end cap
                for (uint16_t section = 0; section < numSections; ++section)
                {
                    uint16_t nextSection = (section + 1) % numSections;

                    // face from end cap to point
                    indices.push_back(indexOfSidesStart + 2 * nextSection + 1);
                    indices.push_back(indexOfSidesStart + 2 * nextSection);
                    indices.push_back(indexOfSidesStart + 2 * section);

                    indices.push_back(indexOfSidesStart + 2 * section);
                    indices.push_back(indexOfSidesStart + 2 * section + 1);
                    indices.push_back(indexOfSidesStart + 2 * nextSection + 1);
                }
            }
        }

        bool FixedShapeProcessor::CreateBoxBuffersAndViews()
        {
            MeshData meshData;
            CreateBoxMeshData(meshData);

            if (!CreateBuffersAndViews(m_boxBuffers, meshData))
            {
                return false;
            }

            return true;
        }

        void FixedShapeProcessor::CreateBoxMeshData(MeshData& meshData)
        {
            // calc required number of vertices/indices/triangles to build a sphere for the given parameters
            const uint32_t numVertices = 24;
            const uint32_t numTriangles = 12;
            const uint32_t numEdges = 12;
            const uint32_t numTriangleIndices = numTriangles * 3;
            const uint32_t numLineIndices = numEdges * 2;


            // setup vertex buffer
            auto& positions = meshData.m_positions;
            positions.clear();
            positions.reserve(numVertices);

            auto& normals = meshData.m_normals;
            normals.clear();
            normals.reserve(numVertices);

            using PosType = AuxGeomPosition;
            using NormalType = AuxGeomNormal;

            const uint32_t numVertsPerFace = 4;

            // Front face verts (looking along negative z-axis)
            positions.push_back(PosType(-0.5f, -0.5f,  0.5f));
            positions.push_back(PosType( 0.5f, -0.5f,  0.5f));
            positions.push_back(PosType( 0.5f,  0.5f,  0.5f));
            positions.push_back(PosType(-0.5f,  0.5f,  0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(0.0, 0.0f, 1.0f));
            }

            // Back Face verts
            positions.push_back(PosType(-0.5f, -0.5f, -0.5f));
            positions.push_back(PosType( 0.5f, -0.5f, -0.5f));
            positions.push_back(PosType( 0.5f,  0.5f, -0.5f));
            positions.push_back(PosType(-0.5f,  0.5f, -0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(0.0, 0.0f, -1.0f));
            }

            // Left Face verts
            positions.push_back(PosType(-0.5f, -0.5f,  0.5f));
            positions.push_back(PosType(-0.5f,  0.5f,  0.5f));
            positions.push_back(PosType(-0.5f,  0.5f, -0.5f));
            positions.push_back(PosType(-0.5f, -0.5f, -0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(-1.0, 0.0f, 0.0f));
            }

            // Right Face verts
            positions.push_back(PosType(0.5f, -0.5f,  0.5f));
            positions.push_back(PosType(0.5f,  0.5f,  0.5f));
            positions.push_back(PosType(0.5f,  0.5f, -0.5f));
            positions.push_back(PosType(0.5f, -0.5f, -0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(1.0, 0.0f, 0.0f));
            }

            // Bottom Face verts
            positions.push_back(PosType(-0.5f, -0.5f,  0.5f));
            positions.push_back(PosType( 0.5f, -0.5f,  0.5f));
            positions.push_back(PosType( 0.5f, -0.5f, -0.5f));
            positions.push_back(PosType(-0.5f, -0.5f, -0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(0.0, -1.0f, 0.0f));
            }

            // Top Face verts
            positions.push_back(PosType(-0.5f, 0.5f,  0.5f));
            positions.push_back(PosType( 0.5f, 0.5f,  0.5f));
            positions.push_back(PosType( 0.5f, 0.5f, -0.5f));
            positions.push_back(PosType(-0.5f, 0.5f, -0.5f));

            for (uint32_t vertex = 0; vertex < numVertsPerFace; ++vertex)
            {
                normals.push_back(NormalType(0.0, 1.0f, 0.0f));
            }

            // Setup point index buffer
            {
                auto& indices = meshData.m_pointIndices;
                indices.clear();
                indices.reserve(8);

                // indices - front face points
                indices.push_back(0);
                indices.push_back(1);
                indices.push_back(2);
                indices.push_back(3);

                // indices - back face points
                indices.push_back(4);
                indices.push_back(5);
                indices.push_back(6);
                indices.push_back(7);
            }

            // Setup line index buffer
            {
                auto& indices = meshData.m_lineIndices;
                indices.clear();
                indices.reserve(numLineIndices);

                // indices - front face edges
                indices.push_back(0);
                indices.push_back(1);
                indices.push_back(1);
                indices.push_back(2);
                indices.push_back(2);
                indices.push_back(3);
                indices.push_back(3);
                indices.push_back(0);

                // indices - back face edges
                indices.push_back(4);
                indices.push_back(5);
                indices.push_back(5);
                indices.push_back(6);
                indices.push_back(6);
                indices.push_back(7);
                indices.push_back(7);
                indices.push_back(4);

                // indices - side edges
                indices.push_back(0);
                indices.push_back(4);
                indices.push_back(1);
                indices.push_back(5);
                indices.push_back(2);
                indices.push_back(6);
                indices.push_back(3);
                indices.push_back(7);
            }

            // Setup triangle index buffer
            {
                auto& indices = meshData.m_triangleIndices;
                indices.clear();
                indices.reserve(numTriangleIndices);

                // indices - front face
                indices.push_back(0);
                indices.push_back(1);
                indices.push_back(2);
                indices.push_back(2);
                indices.push_back(3);
                indices.push_back(0);

                // indices - back face
                indices.push_back(5);
                indices.push_back(4);
                indices.push_back(7);
                indices.push_back(7);
                indices.push_back(6);
                indices.push_back(5);

                // indices - left face
                indices.push_back(8);
                indices.push_back(9);
                indices.push_back(10);
                indices.push_back(10);
                indices.push_back(11);
                indices.push_back(8);

                // indices - right face
                indices.push_back(14);
                indices.push_back(13);
                indices.push_back(12);
                indices.push_back(12);
                indices.push_back(15);
                indices.push_back(14);

                // indices - bottom face
                indices.push_back(18);
                indices.push_back(17);
                indices.push_back(16);
                indices.push_back(16);
                indices.push_back(19);
                indices.push_back(18);

                // indices - top face
                indices.push_back(23);
                indices.push_back(20);
                indices.push_back(21);
                indices.push_back(21);
                indices.push_back(22);
                indices.push_back(23);
            }
        }

        bool FixedShapeProcessor::CreateBuffersAndViews(ObjectBuffers& objectBuffers, const MeshData& meshData)
        {
            AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Fail;

            AZ::RHI::BufferInitRequest request;

            // setup m_pointIndexBuffer
            objectBuffers.m_pointIndexBuffer = aznew RHI::Buffer;
            const auto pointIndexDataSize = static_cast<uint32_t>(meshData.m_pointIndices.size() * sizeof(uint16_t));

            request.m_buffer = objectBuffers.m_pointIndexBuffer.get();
            request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, pointIndexDataSize };
            request.m_initialData = meshData.m_pointIndices.data();
            result = m_bufferPool->InitBuffer(request);

            if (result != AZ::RHI::ResultCode::Success)
            {
                AZ_Error( "FixedShapeProcessor", false, "Failed to initialize shape index buffer with error code: %d", result);
                return false;
            }

            // setup m_lineIndexBuffer
            objectBuffers.m_lineIndexBuffer = aznew RHI::Buffer;
            const auto lineIndexDataSize = static_cast<uint32_t>(meshData.m_lineIndices.size() * sizeof(uint16_t));

            request.m_buffer = objectBuffers.m_lineIndexBuffer.get();
            request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, lineIndexDataSize };
            request.m_initialData = meshData.m_lineIndices.data();
            result = m_bufferPool->InitBuffer(request);

            if (result != AZ::RHI::ResultCode::Success)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to initialize shape index buffer with error code: %d", result);
                return false;
            }

            // setup m_triangleIndexBuffer
            objectBuffers.m_triangleIndexBuffer = aznew RHI::Buffer;
            const auto triangleIndexDataSize = static_cast<uint32_t>(meshData.m_triangleIndices.size() * sizeof(uint16_t));
            request.m_buffer = objectBuffers.m_triangleIndexBuffer.get();
            request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, triangleIndexDataSize };
            request.m_initialData = meshData.m_triangleIndices.data();
            result = m_bufferPool->InitBuffer(request);

            if (result != AZ::RHI::ResultCode::Success)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to initialize shape index buffer with error code: %d", result);
                return false;
            }

            // setup m_positionBuffer
            objectBuffers.m_positionBuffer = aznew RHI::Buffer;
            const auto positionDataSize = static_cast<uint32_t>(meshData.m_positions.size() * sizeof(AuxGeomPosition));

            request.m_buffer = objectBuffers.m_positionBuffer.get();
            request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, positionDataSize };
            request.m_initialData = meshData.m_positions.data();
            result = m_bufferPool->InitBuffer(request);
            if (result != AZ::RHI::ResultCode::Success)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to initialize shape position buffer with error code: %d", result);
                return false;
            }

            // setup m_normalBuffer
            objectBuffers.m_normalBuffer = aznew RHI::Buffer;
            const auto normalDataSize = static_cast<uint32_t>(meshData.m_normals.size() * sizeof(AuxGeomNormal));

            request.m_buffer = objectBuffers.m_normalBuffer.get();
            request.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, normalDataSize };
            request.m_initialData = meshData.m_normals.data();
            result = m_bufferPool->InitBuffer(request);
            if (result != AZ::RHI::ResultCode::Success)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to initialize shape normal buffer with error code: %d", result);
                return false;
            }

            // Setup point index buffer view
            u32 pointIndexCount = static_cast<uint32_t>(meshData.m_pointIndices.size());
            objectBuffers.m_pointGeometryView.SetDrawArguments(RHI::DrawIndexed{ 0, pointIndexCount, 0 });
            objectBuffers.m_pointGeometryView.SetIndexBufferView(
            {
                *objectBuffers.m_pointIndexBuffer,
                0,
                static_cast<uint32_t>(pointIndexCount * sizeof(uint16_t)),
                AZ::RHI::IndexFormat::Uint16,
            });

            // Setup line index buffer view
            u32 lineIndexCount = static_cast<uint32_t>(meshData.m_lineIndices.size());
            objectBuffers.m_lineGeometryView.SetDrawArguments(RHI::DrawIndexed{ 0, lineIndexCount, 0 });
            objectBuffers.m_lineGeometryView.SetIndexBufferView(
            {
                *objectBuffers.m_lineIndexBuffer,
                0,
                static_cast<uint32_t>(lineIndexCount * sizeof(uint16_t)),
                AZ::RHI::IndexFormat::Uint16,
            });

            // Setup triangle index buffer view
            u32 triangleIndexCount = static_cast<uint32_t>(meshData.m_triangleIndices.size());
            objectBuffers.m_triangleGeometryView.SetDrawArguments(RHI::DrawIndexed{ 0, triangleIndexCount, 0 });
            objectBuffers.m_triangleGeometryView.SetIndexBufferView(
            {
                *objectBuffers.m_triangleIndexBuffer,
                0,
                static_cast<uint32_t>(triangleIndexCount * sizeof(uint16_t)),
                AZ::RHI::IndexFormat::Uint16,
            });

            // Setup vertex buffer view
            const auto positionCount = static_cast<uint32_t>(meshData.m_positions.size());
            const uint32_t positionSize = sizeof(float) * 3;

            AZ::RHI::StreamBufferView positionBufferView =
            {
                *objectBuffers.m_positionBuffer,
                0,
                positionCount * positionSize,
                positionSize,
            };

            // Setup normal buffer view
            const auto normalCount = static_cast<uint32_t>(meshData.m_normals.size());
            const uint32_t normalSize = sizeof(float) * 3;

            AZ::RHI::StreamBufferView normalBufferView =
            {
                *objectBuffers.m_normalBuffer,
                0,
                normalCount * normalSize,
                normalSize,
            };

            objectBuffers.m_pointGeometryView.AddStreamBufferView(positionBufferView);
            objectBuffers.m_lineGeometryView.AddStreamBufferView(positionBufferView);
            objectBuffers.m_triangleGeometryView.AddStreamBufferView(positionBufferView);
            objectBuffers.m_triangleGeometryView.AddStreamBufferView(normalBufferView);

            RHI::StreamBufferIndices streamIndices;
            streamIndices.AddIndex(PositionStreamIndex);

            // Validate for each draw style
            AZ::RHI::ValidateStreamBufferViews(m_objectStreamLayout[DrawStyle_Point], objectBuffers.m_pointGeometryView, streamIndices);
            AZ::RHI::ValidateStreamBufferViews(m_objectStreamLayout[DrawStyle_Line], objectBuffers.m_lineGeometryView, streamIndices);
            AZ::RHI::ValidateStreamBufferViews(m_objectStreamLayout[DrawStyle_Solid], objectBuffers.m_triangleGeometryView, streamIndices);

            streamIndices.AddIndex(NormalStreamIndex);
            AZ::RHI::ValidateStreamBufferViews(m_objectStreamLayout[DrawStyle_Shaded], objectBuffers.m_triangleGeometryView, streamIndices);

            return true;
        }

        FixedShapeProcessor::LodIndex FixedShapeProcessor::GetLodIndexForShape(AuxGeomShapeType shapeType, const AZ::RPI::View* view, const AZ::Vector3& worldPosition, const AZ::Vector3& scale)
        {
            const Shape& shape = m_shapes[shapeType];
            if (shape.m_numLods <= 1)
            {
                return 0;   // No LODs for this shape
            }

            // For LODs we really only care about the radius of the curve. i.e. a really long cylinder with a radius R
            // and a short cylinder with radius R should use same LOD if same distance from screen since the LOD is just used
            // to make the curved part look smoother. For all our curved geometries X and Z scale are the radius and Y scale is the length.
            float radius = scale.GetX();

            float screenPercentage = view->CalculateSphereAreaInClipSpace(worldPosition, radius);

            LodIndex lodIndex = shape.m_numLods - 1;

            // No need to test the last LOD since we always choose it if we get that far
            // (unless at some point we implement a test to not draw at all if below that value - but
            // that concern might be better addressed by frustum culling before this)
            for (LodIndex testIndex = 0; testIndex < shape.m_numLods - 1; ++testIndex)
            {
                if (screenPercentage >= shape.m_lodScreenPercentages[testIndex])
                {
                    lodIndex = testIndex;
                    break;
                }
            }

            return lodIndex;
        }

        void FixedShapeProcessor::SetupInputStreamLayout(RHI::InputStreamLayout& inputStreamLayout, RHI::PrimitiveTopology topology, bool includeNormals)
        {
            RHI::InputStreamLayoutBuilder layoutBuilder;

            layoutBuilder.AddBuffer()->Channel("POSITION", RHI::Format::R32G32B32_FLOAT);

            if (includeNormals)
            {
                layoutBuilder.AddBuffer()->Channel("NORMAL", RHI::Format::R32G32B32_FLOAT);
            }
            layoutBuilder.SetTopology(topology);
            inputStreamLayout = layoutBuilder.End();
        }

        void FixedShapeProcessor::FillShaderData(Data::Instance<RPI::Shader>& shader, ShaderData& shaderData)
        {
            // Get the per-object SRG and store the indices of the data we need to set per object
            shaderData.m_shaderAsset = shader->GetAsset();
            shaderData.m_supervariantIndex = shader->GetSupervariantIndex();
            shaderData.m_perObjectSrgLayout = shader->FindShaderResourceGroupLayout(Name{ "ObjectSrg" });
            if (!shaderData.m_perObjectSrgLayout)
            {
                AZ_Error("FixedShapeProcessor", false, "Failed to get shader resource group layout");
                return;
            }

            shaderData.m_drawListTag = shader->GetDrawListTag();
        }

        void FixedShapeProcessor::LoadShaders()
        {
            // load shaders for constant color and direction light
            constexpr const char* unlitObjectShaderFilePath = "Shaders/auxgeom/auxgeomobject.azshader";
            constexpr const char* litObjectShaderFilePath = "Shaders/auxgeom/auxgeomobjectlit.azshader";

            // constant color shader
            m_unlitShader = RPI::LoadCriticalShader(unlitObjectShaderFilePath);
            // direction light shader
            m_litShader = RPI::LoadCriticalShader(litObjectShaderFilePath);

            if (m_unlitShader == nullptr || m_litShader == nullptr)
            {
                return;
            }

            FillShaderData(m_unlitShader, m_perObjectShaderData[ShapeLightingStyle_ConstantColor]);
            FillShaderData(m_litShader, m_perObjectShaderData[ShapeLightingStyle_Directional]);

            // Initialize all pipeline states
            PipelineStateOptions pipelineStateOptions;
            // initialize two base pipeline state first to preserve the blend functions
            pipelineStateOptions.m_perpectiveType = PerspectiveType_ViewProjection;
            InitPipelineState(pipelineStateOptions);
            pipelineStateOptions.m_perpectiveType = PerspectiveType_ManualOverride;
            InitPipelineState(pipelineStateOptions);

            for (uint32_t perspectiveType = 0; perspectiveType < PerspectiveType_Count; perspectiveType++)
            {
                pipelineStateOptions.m_perpectiveType = (AuxGeomShapePerpectiveType)perspectiveType;
                for (uint32_t blendMode = 0; blendMode < BlendMode_Count; blendMode++)
                {
                    pipelineStateOptions.m_blendMode = (AuxGeomBlendMode)blendMode;
                    for (uint32_t drawStyle = 0; drawStyle < DrawStyle_Count; drawStyle++)
                    {
                        pipelineStateOptions.m_drawStyle = (AuxGeomDrawStyle)drawStyle;
                        for (uint32_t depthRead = 0; depthRead < DepthRead_Count; depthRead++)
                        {
                            pipelineStateOptions.m_depthReadType = (AuxGeomDepthReadType)depthRead;
                            for (uint32_t depthWrite = 0; depthWrite < DepthWrite_Count; depthWrite++)
                            {
                                pipelineStateOptions.m_depthWriteType = (AuxGeomDepthWriteType)depthWrite;
                                for (uint32_t faceCullMode = 0; faceCullMode < FaceCull_Count; faceCullMode++)
                                {
                                    pipelineStateOptions.m_faceCullMode = (AuxGeomFaceCullMode)faceCullMode;
                                    InitPipelineState(pipelineStateOptions);
                                }
                            }
                        }
                    }
                }
            }
        }

        RPI::Ptr<RPI::PipelineStateForDraw>& FixedShapeProcessor::GetPipelineState(const PipelineStateOptions& pipelineStateOptions)
        {
            // The declaration: m_pipelineStates[PerspectiveType_Count][BlendMode_Count][DrawStyle_Count][DepthRead_Count][DepthWrite_Count][FaceCull_Count];
            return m_pipelineStates[pipelineStateOptions.m_perpectiveType][pipelineStateOptions.m_blendMode][pipelineStateOptions.m_drawStyle]
                [pipelineStateOptions.m_depthReadType][pipelineStateOptions.m_depthWriteType][pipelineStateOptions.m_faceCullMode];
        }

        void FixedShapeProcessor::SetUpdatePipelineStates()
        {
            m_needUpdatePipelineStates = true;
        }

        void FixedShapeProcessor::InitPipelineState(const PipelineStateOptions& pipelineStateOptions)
        {
            // Use the the pipeline state for PipelineStateOptions with default values and input perspective type as base pipeline state. Create one if it was empty.
            PipelineStateOptions defaultOptions;
            defaultOptions.m_perpectiveType = pipelineStateOptions.m_perpectiveType;
            defaultOptions.m_drawStyle = pipelineStateOptions.m_drawStyle;
            RPI::Ptr<RPI::PipelineStateForDraw>& basePipelineState = GetPipelineState(defaultOptions);
            if (basePipelineState.get() == nullptr)
            {
                // Only DrawStyle_Shaded uses the lit shader. Others use unlit shader
                auto& shader = (pipelineStateOptions.m_drawStyle == DrawStyle_Shaded) ? m_litShader : m_unlitShader;

                basePipelineState = aznew RPI::PipelineStateForDraw;

                // shader option data for shader variant
                Name optionViewProjectionModeName = Name("o_viewProjMode");
                RPI::ShaderOptionList shaderOptionAndValues;
                shaderOptionAndValues.push_back(RPI::ShaderOption(optionViewProjectionModeName, GetAuxGeomPerspectiveTypeName(pipelineStateOptions.m_perpectiveType)));

                // initialize pipeline state with shader and shader options
                basePipelineState->Init(shader, &shaderOptionAndValues);

                m_createdPipelineStates.push_back(&basePipelineState);
            }

            RPI::Ptr<RPI::PipelineStateForDraw>& destPipelineState = GetPipelineState(pipelineStateOptions);

            // Copy from base pipeline state. Skip if it's the base pipeline state 
            if (destPipelineState.get() == nullptr)
            {
                destPipelineState = aznew RPI::PipelineStateForDraw(*basePipelineState.get());
                m_createdPipelineStates.push_back(&destPipelineState);
            }

            // blendMode
            RHI::TargetBlendState& blendState = destPipelineState->RenderStatesOverlay().m_blendState.m_targets[0];
            blendState.m_enable = pipelineStateOptions.m_blendMode == AuxGeomBlendMode::BlendMode_Alpha;
            blendState.m_blendSource = RHI::BlendFactor::AlphaSource;
            blendState.m_blendDest = RHI::BlendFactor::AlphaSourceInverse;

            // primitiveType
            destPipelineState->InputStreamLayout() = m_objectStreamLayout[pipelineStateOptions.m_drawStyle];

            // depthReadType
            // Keep the default depth comparison function and only set it when depth read is off
            // Note: since the default PipelineStateOptions::m_depthReadType is DepthRead_On, the basePipelineState keeps the comparison function read from shader variant
            if (pipelineStateOptions.m_depthReadType == AuxGeomDepthReadType::DepthRead_Off)
            {
                destPipelineState->RenderStatesOverlay().m_depthStencilState.m_depth.m_func = RHI::ComparisonFunc::Always;
            }

            // depthWriteType
            destPipelineState->RenderStatesOverlay().m_depthStencilState.m_depth.m_writeMask =
                ConvertToRHIDepthWriteMask(pipelineStateOptions.m_depthWriteType);

            // faceCullMode
            destPipelineState->RenderStatesOverlay().m_rasterState.m_cullMode =
                ConvertToRHICullMode(pipelineStateOptions.m_faceCullMode);

            // finalize
            destPipelineState->SetOutputFromScene(m_scene);
            destPipelineState->Finalize();
        }

        RHI::GeometryView* FixedShapeProcessor::GetGeometryView(ObjectBuffers& objectBuffers, int drawStyle)
        {
            switch (drawStyle)
            {
            case DrawStyle_Point:
                return &objectBuffers.m_pointGeometryView;
            case DrawStyle_Line:
                return &objectBuffers.m_lineGeometryView;
            case DrawStyle_Solid: // intentional fall through
            case DrawStyle_Shaded:
                return &objectBuffers.m_triangleGeometryView;
            default:
                AZ_Assert(false, "Unknown AuxGeom Draw Style %d.", drawStyle);
                return &objectBuffers.m_triangleGeometryView; // default to triangle since it should be drawable for any draw style
            }
        }

        RHI::GeometryView* FixedShapeProcessor::GetGeometryView(AuxGeomShapeType shapeType, int drawStyle, LodIndex lodIndex)
        {
            return GetGeometryView(m_shapes[shapeType].m_lodBuffers[lodIndex], drawStyle);
        }

        RHI::ConstPtr<RHI::DrawPacket> FixedShapeProcessor::BuildDrawPacketForShape(
            RHI::DrawPacketBuilder& drawPacketBuilder,
            const ShapeBufferEntry& shape,
            int drawStyle,
            const AZStd::vector<AZ::Matrix4x4>& viewProjOverrides,
            const RPI::Ptr<RPI::PipelineStateForDraw>& pipelineState,
            LodIndex lodIndex,
            RHI::DrawItemSortKey sortKey)
        {
            ShaderData& shaderData = m_perObjectShaderData[drawStyle==DrawStyle_Shaded?1:0];

            // Create a SRG for the shape to specify its transform and color
            // [GFX TODO] [ATOM-2333] Try to avoid doing SRG create/compile per draw. Possibly using instancing.
            auto srg = RPI::ShaderResourceGroup::Create(shaderData.m_shaderAsset, shaderData.m_supervariantIndex, shaderData.m_perObjectSrgLayout->GetName());
            if (!srg)
            {
                AZ_Warning("AuxGeom", false, "Failed to create a shader resource group for an AuxGeom draw, Ignoring the draw");
                return nullptr;
            }

            const AZ::Matrix3x4 drawMatrix = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(shape.m_rotationMatrix, shape.m_position) * AZ::Matrix3x4::CreateScale(shape.m_scale);
            if (drawStyle == DrawStyle_Shaded)
            {
                Matrix3x3 rotation = shape.m_rotationMatrix;
                rotation.MultiplyByScale(shape.m_scale.GetReciprocal());
                srg->SetConstant(shaderData.m_colorIndex, shape.m_color);
                srg->SetConstant(shaderData.m_modelToWorldIndex, drawMatrix);
                srg->SetConstant(shaderData.m_normalMatrixIndex, rotation);
            }
            else
            {
                srg->SetConstant(shaderData.m_colorIndex, shape.m_color);
                srg->SetConstant(shaderData.m_modelToWorldIndex, drawMatrix);
            }
            if (drawStyle == DrawStyle_Point)
            {
                srg->SetConstant(shaderData.m_pointSizeIndex, shape.m_pointSize);
            }
            if (shape.m_viewProjOverrideIndex >= 0)
            {
                srg->SetConstant(shaderData.m_viewProjectionOverrideIndex, viewProjOverrides[shape.m_viewProjOverrideIndex]);
            }

            pipelineState->UpdateSrgVariantFallback(srg);
            
            srg->Compile();
            m_processSrgs.push_back(srg);
            if (m_shapes[shape.m_shapeType].m_lodBuffers.size() > 0)
            {
                RHI::GeometryView* geometryView = GetGeometryView(shape.m_shapeType, drawStyle, lodIndex);
                auto& drawListTag = shaderData.m_drawListTag;

                return BuildDrawPacket(
                    drawPacketBuilder, srg, geometryView, drawListTag,
                    pipelineState->GetRHIPipelineState(), sortKey, drawStyle);
            }
            return nullptr;
        }

        RHI::GeometryView* FixedShapeProcessor::GetBoxGeometryView(int drawStyle)
        {
            return GetGeometryView(m_boxBuffers, drawStyle);
        }

        RHI::ConstPtr<RHI::DrawPacket> FixedShapeProcessor::BuildDrawPacketForBox(
            RHI::DrawPacketBuilder& drawPacketBuilder,
            const BoxBufferEntry& box,
            int drawStyle,
            const AZStd::vector<AZ::Matrix4x4>& viewProjOverrides,
            const RPI::Ptr<RPI::PipelineStateForDraw>& pipelineState,
            RHI::DrawItemSortKey sortKey)
        {
            ShaderData& shaderData = m_perObjectShaderData[drawStyle==DrawStyle_Shaded?1:0];
            // Create a SRG for the box to specify its transform and color
            auto srg = RPI::ShaderResourceGroup::Create(shaderData.m_shaderAsset, shaderData.m_supervariantIndex, shaderData.m_perObjectSrgLayout->GetName());
            if (!srg)
            {
                AZ_Warning("AuxGeom", false, "Failed to create a shader resource group for an AuxGeom draw, Ignoring the draw");
                return nullptr;
            }
            const AZ::Matrix3x4 drawMatrix = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(box.m_rotationMatrix, box.m_position) * AZ::Matrix3x4::CreateScale(box.m_scale);
            if (drawStyle == DrawStyle_Shaded)
            {
                Matrix3x3 rotation = box.m_rotationMatrix;
                rotation.MultiplyByScale(box.m_scale.GetReciprocal());
                srg->SetConstant(shaderData.m_colorIndex, box.m_color);
                srg->SetConstant(shaderData.m_modelToWorldIndex, drawMatrix);
                srg->SetConstant(shaderData.m_normalMatrixIndex, rotation);
            }
            else
            {
                srg->SetConstant(shaderData.m_colorIndex, box.m_color);
                srg->SetConstant(shaderData.m_modelToWorldIndex, drawMatrix);
            }
            if (drawStyle == DrawStyle_Point)
            {
                srg->SetConstant(shaderData.m_pointSizeIndex, box.m_pointSize);
            }
            if (box.m_viewProjOverrideIndex >= 0)
            {
                srg->SetConstant(shaderData.m_viewProjectionOverrideIndex, viewProjOverrides[box.m_viewProjOverrideIndex]);
            }
            pipelineState->UpdateSrgVariantFallback(srg);
            srg->Compile();
            m_processSrgs.push_back(srg);

            RHI::GeometryView* geometryView = GetBoxGeometryView(drawStyle);
            auto& drawListTag = shaderData.m_drawListTag;

            return BuildDrawPacket(drawPacketBuilder, srg, geometryView,
                drawListTag, pipelineState->GetRHIPipelineState(), sortKey, drawStyle);
        }

        RHI::ConstPtr<RHI::DrawPacket> FixedShapeProcessor::BuildDrawPacket(
            RHI::DrawPacketBuilder& drawPacketBuilder,
            AZ::Data::Instance<RPI::ShaderResourceGroup>& srg,
            RHI::GeometryView* geometryView,
            RHI::DrawListTag drawListTag,
            const RHI::PipelineState* pipelineState,
            RHI::DrawItemSortKey sortKey,
            int drawStyle)
        {
            drawPacketBuilder.Begin(nullptr);
            drawPacketBuilder.SetGeometryView(geometryView);
            drawPacketBuilder.AddShaderResourceGroup(srg->GetRHIShaderResourceGroup());

            RHI::StreamBufferIndices streamIndices;
            streamIndices.AddIndex(PositionStreamIndex);

            if (drawStyle == DrawStyle_Shaded)
            {
                streamIndices.AddIndex(NormalStreamIndex);
            }

            RHI::DrawPacketBuilder::DrawRequest drawRequest;
            drawRequest.m_listTag = drawListTag;
            drawRequest.m_streamIndices = streamIndices;
            drawRequest.m_pipelineState = pipelineState;
            drawRequest.m_sortKey = sortKey;
            drawPacketBuilder.AddDrawItem(drawRequest);

            return drawPacketBuilder.End();
        }
    } // namespace Render
} // namespace AZ
