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

#include "qquick3dpointlight_p.h"
#include "qquick3dobject_p.h"

#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>

#include "qquick3dnode_p_p.h"

QT_BEGIN_NAMESPACE

/*!
    \qmltype PointLight
    \inherits Light
    \inqmlmodule QtQuick3D
    \brief Defines a point light in the scene.

    The point light can be described as a sphere, emitting light with equal strength in all
    directions from the center of the light. This is similar to the way a light bulb emits light.

    Rotating or scaling a point light does not have any effect. Moving a point light will change
    the position from where the light is emitted.

    By default, a point light intensity diminishes according to inverse-square-law. However, the fade-off
    (and range) can be controlled with the \l {constantFade}, \l {linearFade}, and
    \l quadraticFade properties. Light attenuation is calculated using the formula:
    \l {constantFade} + \c distance * (\l {linearFade} * 0.01) + \c distance^2 * (\l {quadraticFade} * 0.0001)

    \section2 A simple example: shading a sphere in three different ways

    Take a scene containing a sphere in front of a scaled up rectangle in the
    background. The default base color of the PrincipledMaterial of the
    rectangle is white.

    Without any lights and disabling light-related shading for the two meshes,
    we get the following:

    \qml
    import QtQuick
    import QtQuick3D
    View3D {
        anchors.fill: parent

        PerspectiveCamera { z: 600 }

        Model {
            source: "#Rectangle"
            scale: Qt.vector3d(10, 10, 10)
            z: -100
            materials: PrincipledMaterial {
                lighting: PrincipledMaterial.NoLighting
            }
        }

        Model {
            source: "#Sphere"
            scale: Qt.vector3d(2, 2, 2)
            materials: PrincipledMaterial {
                lighting: PrincipledMaterial.NoLighting
                baseColor: "#40c060"
                roughness: 0.1
            }
        }
    }
    \endqml

    \image pointlight-1.png
           {Green circle with white background}

    Adding a directional light, emitting down the Z axis by default, leads to the following:

    \qml
    import QtQuick
    import QtQuick3D
    View3D {
        anchors.fill: parent

        PerspectiveCamera { z: 600 }

        DirectionalLight { }

        Model {
            source: "#Rectangle"
            scale: Qt.vector3d(10, 10, 10)
            z: -100
            materials: PrincipledMaterial { }
        }

        Model {
            source: "#Sphere"
            scale: Qt.vector3d(2, 2, 2)
            materials: PrincipledMaterial {
                baseColor: "#40c060"
                roughness: 0.1
            }
        }
    }
    \endqml

    \image pointlight-2.png
           {Green circle with black edge}

    What if we now replace DirectionalLight with:

    \qml
        PointLight {
            z: 300
        }
    \endqml

    The white colored PointLight here is moved on the Z axis so that it is
    halfway between the camera and the center of the scene. Unlike
    DirectionalLight, the rotation of the PointLight does not matter, whereas
    its position is significant. The diminishing intensity is visible here,
    especially on the rectangle mesh in the background.

    \image pointlight-3.png
           {Green gradient circle with black edge and dark gray gradient
           background showing point light attenuation}

    For more usage examples, see \l{Qt Quick 3D - Lights Example}.

    \sa DirectionalLight, SpotLight, {Shadow Mapping}
*/

/*!
    \qmlproperty real PointLight::constantFade

    This property is constant factor of the attenuation term of the light.
    The default value is 1.0.
 */

/*!
    \qmlproperty real PointLight::linearFade

    This property increases the rate at which the lighting effect dims the light
    in proportion to the distance to the light. The default value is \c 0.0, meaning the light doesn't
    have linear fade. The value used here is multiplied by \c 0.01 before being used to
    calculate light attenuation.
*/

/*!
    \qmlproperty real PointLight::quadraticFade

    This property increases the rate at which the lighting effect dims the light
    in proportion to the inverse square law. The default value is 1.0 meaning the point light
    fade exactly follows the inverse square law i.e. when distance to an object doubles the
    light intensity decreases to 1/4th. The value used here is multiplied by \c 0.0001 before
    being used to calculate light attenuation.
*/

QQuick3DPointLight::QQuick3DPointLight(QQuick3DNode *parent)
    : QQuick3DAbstractLight(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::PointLight)), parent) {}

float QQuick3DPointLight::constantFade() const
{
    return m_constantFade;
}

float QQuick3DPointLight::linearFade() const
{
    return m_linearFade;
}

float QQuick3DPointLight::quadraticFade() const
{
    return m_quadraticFade;
}

void QQuick3DPointLight::setConstantFade(float constantFade)
{
    if (qFuzzyCompare(m_constantFade, constantFade))
        return;

    m_constantFade = constantFade;
    m_dirtyFlags.setFlag(DirtyFlag::FadeDirty);
    emit constantFadeChanged();
    update();
}

void QQuick3DPointLight::setLinearFade(float linearFade)
{
    if (qFuzzyCompare(m_linearFade, linearFade))
        return;

    m_linearFade = linearFade;
    m_dirtyFlags.setFlag(DirtyFlag::FadeDirty);
    emit linearFadeChanged();
    update();
}

void QQuick3DPointLight::setQuadraticFade(float quadraticFade)
{
    if (qFuzzyCompare(m_quadraticFade, quadraticFade))
        return;

    m_quadraticFade = quadraticFade;
    m_dirtyFlags.setFlag(DirtyFlag::FadeDirty);
    emit quadraticFadeChanged();
    update();
}

QSSGRenderGraphObject *QQuick3DPointLight::updateSpatialNode(QSSGRenderGraphObject *node)
{
    if (!node) {
        markAllDirty();
        node = new QSSGRenderLight(QSSGRenderLight::Type::PointLight);
    }

    QQuick3DAbstractLight::updateSpatialNode(node); // Marks the light node dirty if m_dirtyFlags != 0

    QSSGRenderLight *light = static_cast<QSSGRenderLight *>(node);

    if (m_dirtyFlags.testFlag(DirtyFlag::FadeDirty)) {
        m_dirtyFlags.setFlag(DirtyFlag::FadeDirty, false);
        light->m_constantFade = m_constantFade;
        light->m_linearFade = m_linearFade;
        light->m_quadraticFade = m_quadraticFade;
    }

    return node;
}

QT_END_NAMESPACE
