// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "gridgeometry_p.h"
#include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h>

/*!
    \qmltype GridGeometry
    \inqmlmodule QtQuick3D.Helpers
    \inherits Geometry
    \brief A custom geometry provider for rendering grids.

    This helper implements grid geometry, which allows showing a grid in a scene.

    For example, the following snippet would display a grid with 19 cells in
    both directions in a scene that has one light. Without further
    transformations, the grid is facing the camera by default.

    \image gridgeometry.jpg

    \badcode
        View3D {
            anchors.fill: parent
            camera: camera

            PerspectiveCamera {
                id: camera
                position: Qt.vector3d(0, 0, 600)
            }

            DirectionalLight {
                position: Qt.vector3d(-500, 500, -100)
                color: Qt.rgba(0.4, 0.2, 0.6, 1.0)
                ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
            }

            Model {
                scale: Qt.vector3d(100, 100, 100)
                geometry: GridGeometry {
                    horizontalLines: 20
                    verticalLines: 20
                }
                materials: [ DefaultMaterial { } ]
            }
        }
    \endcode

    \sa {Qt Quick 3D - Custom Geometry Example}, Model
*/

/*! \qmlproperty int GridGeometry::horizontalLines
    Specifies the number of horizontal lines in a grid. The default value is 1000.
*/
/*! \qmlproperty int GridGeometry::verticalLines
    Specifies the number of vertical lines in a grid. The default value is 1000.
*/
/*! \qmlproperty real GridGeometry::horizontalStep
    Specifies the spacing between horizontal lines. The default value is 0.1.
*/
/*! \qmlproperty real GridGeometry::verticalStep
    Specifies the spacing between vertical lines. The default value is 0.1.
*/

GridGeometry::GridGeometry()
    : QQuick3DGeometry()
{
    updateData();
}

GridGeometry::~GridGeometry()
{

}

int GridGeometry::horizontalLines() const
{
    return m_horLines;
}

int GridGeometry::verticalLines() const
{
    return m_vertLines;
}

float GridGeometry::horizontalStep() const
{
    return m_horStep;
}

float GridGeometry::verticalStep() const
{
    return m_vertStep;
}

void GridGeometry::setHorizontalLines(int count)
{
    count = qMax(count, 1);
    if (m_horLines == count)
        return;
    m_horLines = qMax(count, 1);
    emit horizontalLinesChanged();
    updateData();
    update();
}

void GridGeometry::setVerticalLines(int count)
{
    count = qMax(count, 1);
    if (m_vertLines == count)
        return;
    m_vertLines = qMax(count, 1);
    emit verticalLinesChanged();
    updateData();
    update();
}

void GridGeometry::setHorizontalStep(float step)
{
    step = qMax(step, 0.0f);
    if (qFuzzyCompare(m_horStep, step))
        return;
    m_horStep = step;
    emit horizontalStepChanged();
    updateData();
    update();
}

void GridGeometry::setVerticalStep(float step)
{
    step = qMax(step, 0.0f);
    if (qFuzzyCompare(m_vertStep, step))
        return;
    m_vertStep = step;
    emit verticalStepChanged();
    updateData();
    update();
}

static void fillVertexData(QByteArray &vertexData, int horLines, float horStep,
                           int vertLines, float vertStep)
{
    const int size = (horLines + vertLines) * sizeof(float) * 8 * 2;
    vertexData.resize(size);
    float *dataPtr = reinterpret_cast<float *>(vertexData.data());

    float y0 = -float(horLines - 1) * horStep * .5f;
    float x0 = -float(vertLines - 1) * vertStep * .5f;
    float y1 = -y0;
    float x1 = -x0;

    for (int i = 0; i < horLines; ++i) {
        // start position
        dataPtr[0] = x0;
        dataPtr[1] = y0 + i * horStep;
        dataPtr[2] = .0f;
        dataPtr[3] = 1.f;

        dataPtr[4] = 0;
        dataPtr[5] = 0;
        dataPtr[6] = 1.f;
        dataPtr[7] = 0;

        dataPtr += 8;

        // end position
        dataPtr[0] = x1;
        dataPtr[1] = y0 + i * horStep;
        dataPtr[2] = .0f;
        dataPtr[3] = 1.f;

        dataPtr[4] = 0;
        dataPtr[5] = 0;
        dataPtr[6] = 1.f;
        dataPtr[7] = 0;

        dataPtr += 8;
    }

    for (int i = 0; i < vertLines; ++i) {
        // start position
        dataPtr[0] = x0 + i * vertStep;
        dataPtr[1] = y0;
        dataPtr[2] = .0f;
        dataPtr[3] = 1.f;

        dataPtr[4] = 0;
        dataPtr[5] = 0;
        dataPtr[6] = 1.f;
        dataPtr[7] = 0;

        dataPtr += 8;

        // end position
        dataPtr[0] = x0 + i * vertStep;
        dataPtr[1] = y1;
        dataPtr[2] = .0f;
        dataPtr[3] = 1.f;

        dataPtr[4] = 0;
        dataPtr[5] = 0;
        dataPtr[6] = 1.f;
        dataPtr[7] = 0;

        dataPtr += 8;
    }
}

void GridGeometry::updateData()
{
    QByteArray vertexData;
    fillVertexData(vertexData, m_horLines, m_horStep, m_vertLines, m_vertStep);
    clear();
    addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
                 QQuick3DGeometry::Attribute::ComponentType::F32Type);
    addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
                 QQuick3DGeometry::Attribute::ComponentType::F32Type);
    setStride(32);
    setVertexData(vertexData);
    setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
    setBounds(QVector3D(float(-m_vertLines / 2), float(-m_horLines / 2), 0.0f) * m_horStep,
              QVector3D(float(m_vertLines / 2), float(m_horLines / 2), 0.0f) * m_vertStep);
}
