/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquick3dprincipledmaterial_p.h"
#include "qquick3dobject_p.h"

#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype PrincipledMaterial
    \inherits Material
    \inqmlmodule QtQuick3D
    \brief Lets you define a material for 3D items.

    Before a Model can be rendered in a scene, it must have at least one
    material to define how the mesh is shaded. The PrincipledMaterial aims to be
    easy to use and with as few parameters as possible. In addition to having few
    parameters, all input values are strictly normalized between 0 and 1.
    Even if you define a PrincipledMaterial with no properties set, a valid mesh will be rendered,
    because the mesh defines some sensible defaults.

    As you change the properties of the PrincipledMaterial, behind the scenes
    new shaders are generated, and the property values are bound. The
    complexity of a shader depends on a combination of the properties that
    are set on it, and the context of the scene itself.
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::lighting

    This property defines which lighting method is used when generating this
    material.

    The default value is \c PrincipledMaterial.FragmentLighting

    When using \c PrincipledMaterial.FragmentLighting, diffuse and specular lighting is
    calculated for each rendered pixel. Certain effects (such as a Fresnel or normal map) require
    \c PrincipledMaterial.FragmentLighting to work.

    When using \c PrincipledMaterial.NoLighting no lighting is calculated. This
    mode is (predictably) very fast, and is quite effective when image maps are
    used that you do not need to be shaded by lighting.

    \value PrincipledMaterial.NoLighting
    \value PrincipledMaterial.FragmentLighting
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::blendMode

    This property determines how the colors of the model rendered blends with
    those behind it.

    \value PrincipledMaterial.SourceOver
        Default blend mode. Opaque objects occlude objects behind them.
    \value PrincipledMaterial.Screen
        Colors are blended using an inverted multiply, producing a lighter result. This blend mode
        is order-independent; if you are using semi-opaque objects and experiencing 'popping'
        as faces or models sort differently, using Screen blending is one way to produce results
        without popping.
    \value PrincipledMaterial.Multiply
        Colors are blended using a multiply, producing a darker result. This blend mode is also
        order-independent.
    \value PrincipledMaterial.Overlay
        A mix of Multiply and Screen modes, producing a result with higher contrast.
    \value PrincipledMaterial.ColorBurn
        Colors are blended by inverted division where the result also is inverted, producing
        a darker result. Darker than Multiply.
    \value PrincipledMaterial.ColorDodge
        Colors are blended by inverted division, producing a lighter result. Lighter than Screen.
*/

/*!
    \qmlproperty color PrincipledMaterial::baseColor

    This property sets the base color for the material. Depending on the type
    of material specified (metal or dielectric) the diffuse and specular channels will be
    set appropriately. For example, a dielectric material will have a diffuse color equal to
    the base color, while it's specular color, depending on the specular amount, will have a
    bright specular color. For metals the diffuse and specular channels will be mixed from
    the base color and have a dark diffuse channel and a specular channel close to the base color.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::baseColorMap

    This property defines the texture used to set the base color of the material.

    \sa baseColor
*/

/*!
    \qmlproperty Texture PrincipledMaterial::metalness

    The metalness property defines the \e metalness of the the material. The value
    is normalized, where 0.0 means the material is a \e dielectric (non-metallic) material and
    a value of 1.0 means the material is a metal.

    \note In principle, materials are either dielectrics with a metalness of 0, or metals with a
    metalness of 1. Metalness values between 0 and 1 are still allowed and will give a material that
    is a blend between the different models.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::metalnessMap

    This property sets a Texture to be used to set the metalness amount for the
    different parts of the material.
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::metalnessChannel

    This property defines the texture channel used to read the metalness value from metalnessMap.
    The default value is \c Material.B.

    \value Material.R Read value from texture R channel.
    \value Material.G Read value from texture G channel.
    \value Material.B Read value from texture B channel.
    \value Material.A Read value from texture A channel.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::emissiveMap

    This property sets a Texture to be used to set the emissive factor for
    different parts of the material. Using a grayscale image will not affect the
    color of the result, while using a color image will produce glowing regions
    with the color affected by the emissive map.
*/

/*!
    \qmlproperty color PrincipledMaterial::emissiveColor

    This property determines the color of self-illumination for this material.
    If an emissive map is set, this property is used as a factor for the RGB channels
    of the texture.

    \note In a scene with black ambient lighting a material with a emissive factor of 0 will
    appear black wherever the light does not shine on it; turning the emissive
    factor to 1 will cause the material to appear as its diffuse color instead.

    \note When you want a material to not be affected by lighting, instead of
    using 100% emissiveFactor consider setting the lightingMode to
    \c PrincipledMaterial.NoLighting for a performance benefit.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::specularReflectionMap

    This property sets a Texture used for specular highlights on the material.
    By default the Texture is applied using environmental mapping (not UV
    mapping): as you rotate the model the map will appear as though it is
    reflecting from the environment. Specular Reflection maps are an easy way to
    add a high-quality look with relatively low cost.

    \note Using a Light Probe in your \l SceneEnvironment for image-based lighting
    will automatically use that image as the specular reflection.

    \note Crisp images cause your material to look very glossy; the more you
    blur your image the softer your material will appear.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::specularMap

    The property defines a RGB Texture to modulate the amount and the color of
    specularity across the surface of the material. These values are multiplied
    by the specularAmount.

    \note The specular map will be ignored unless the material is dielectric.
*/

/*!
    \qmlproperty real PrincipledMaterial::specularTint

    This property defines how much of the base color contributes to the specular reflections.

    \note This property does only apply to dielectric materials.
*/

/*!
    \qmlproperty real PrincipledMaterial::indexOfRefraction

    This property controls how fast light travels through the material.
*/

/*!
    \qmlproperty real PrincipledMaterial::specularAmount

    This property controls the strength of specularity (highlights and
    reflections).

    \note For non-dielectrics (metals) this property has no effect.

    \note This property does not affect the specularReflectionMap, but does affect the amount of
    reflections from a scenes SceneEnvironment::lightProbe.

    \note Unless your mesh is high resolution, you may need to use
    \c PrincipledMaterial.FragmentLighting to get good specular highlights from scene
    lights.
*/

/*!
    \qmlproperty real PrincipledMaterial::roughness

    This property controls the size of the specular highlight generated from
    lights, and the clarity of reflections in general. Larger values increase
    the roughness, softening specular highlights and blurring reflections.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::roughnessMap

    This property defines a Texture to control the specular roughness of the
    material.
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::roughnessChannel

    This property defines the texture channel used to read the roughness value from roughnessMap.
    The default value is \c Material.G.

    \value Material.R Read value from texture R channel.
    \value Material.G Read value from texture G channel.
    \value Material.B Read value from texture B channel.
    \value Material.A Read value from texture A channel.
*/

/*!
    \qmlproperty real PrincipledMaterial::opacity

    This property drops the opacity of just this material, separate from the
    model.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::opacityMap

    This property defines a Texture used to control the opacity differently for
    different parts of the material.
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::opacityChannel

    This property defines the texture channel used to read the opacity value from opacityMap.
    The default value is \c Material.A.

    \value Material.R Read value from texture R channel.
    \value Material.G Read value from texture G channel.
    \value Material.B Read value from texture B channel.
    \value Material.A Read value from texture A channel.
*/

/*!
    \qmlproperty Texture PrincipledMaterial::normalMap

    This property defines an RGB image used to simulate fine geometry
    displacement across the surface of the material. The RGB channels indicate
    XYZ normal deviations.

    \note Normal maps will not affect the silhouette of a model.
*/

/*!
    \qmlproperty real PrincipledMaterial::normalStrength

    This property controls the amount of simulated displacement for the normalMap.
*/

/*!
    \qmlproperty real PrincipledMaterial::occlusionAmount

    This property contains the factor used to modify the values from the \l occlusionMap texture.
    The value should be between 0.0 to 1.0. The default is 1.0
*/

/*!
    \qmlproperty Texture PrincipledMaterial::occlusionMap

    This property defines a texture used to determine how much indirect light the different areas of the
    material should receive. Values are expected to be linear from 0.0 to 1.0, where 0.0 means no indirect lighting
    and 1.0 means the effect of the indirect lighting is left unchanged.

    \sa occlusionAmount
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::occlusionChannel

    This property defines the texture channel used to read the occlusion value from occlusionMap.
    The default value is \c Material.R.

    \value Material.R Read value from texture R channel.
    \value Material.G Read value from texture G channel.
    \value Material.B Read value from texture B channel.
    \value Material.A Read value from texture A channel.
*/

/*!
    \qmlproperty enumeration PrincipledMaterial::alphaMode

    This property sets the mode for how the alpha channel of material color is used.

    \value PrincipledMaterial.Opaque The alpha channel is ignored and the output is rendered
            fully opaque. This is the default.
    \value PrincipledMaterial.Mask The output is either fully transparent of fully opaque depending
            on the alpha value and the specified \l alphaCutoff value.
    \value PrincipledMaterial.Blend The output is blended with the background.

    \note These modes only consider the alpha channel of the material's
    \l {baseColor} {color} or \l {baseColorMap}{color map}.
    The general \l opacity of the material does therefore not affect
    how the \c alphaMode or \c alphaCutoff is interpreted.
*/

/*!
    \qmlproperty real PrincipledMaterial::alphaCutoff

    The alphaCutoff property can be used to specify the cutoff value when using the
    \l {alphaMode} {Mask alphaMode}. Alpha values below the threshold will be rendered
    fully transparent, everything else will be fully opaque. The default value is 0.5

    \sa alphaMode
*/

inline static float ensureNormalized(float val) { return qBound(0.0f, val, 1.0f); }

QQuick3DPrincipledMaterial::QQuick3DPrincipledMaterial(QQuick3DObject *parent)
    : QQuick3DMaterial(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::PrincipledMaterial)), parent)
{}

QQuick3DPrincipledMaterial::~QQuick3DPrincipledMaterial()
{
    for (auto cit = m_connections.cbegin(), end = m_connections.cend(); cit != end; ++cit)
        disconnect(*cit);
}

QQuick3DPrincipledMaterial::Lighting QQuick3DPrincipledMaterial::lighting() const
{
    return m_lighting;
}

QQuick3DPrincipledMaterial::BlendMode QQuick3DPrincipledMaterial::blendMode() const
{
    return m_blendMode;
}

QColor QQuick3DPrincipledMaterial::baseColor() const
{
    return m_baseColor;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::baseColorMap() const
{
    return m_baseColorMap;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::emissiveMap() const
{
    return m_emissiveMap;
}

QColor QQuick3DPrincipledMaterial::emissiveColor() const
{
    return m_emissiveColor;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::specularReflectionMap() const
{
    return m_specularReflectionMap;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::specularMap() const
{
    return m_specularMap;
}

float QQuick3DPrincipledMaterial::specularTint() const
{
    return m_specularTint;
}

float QQuick3DPrincipledMaterial::indexOfRefraction() const
{
    return m_indexOfRefraction;
}

float QQuick3DPrincipledMaterial::specularAmount() const
{
    return m_specularAmount;
}

float QQuick3DPrincipledMaterial::roughness() const
{
    return m_roughness;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::roughnessMap() const
{
    return m_roughnessMap;
}

float QQuick3DPrincipledMaterial::opacity() const
{
    return m_opacity;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::opacityMap() const
{
    return m_opacityMap;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::normalMap() const
{
    return m_normalMap;
}

float QQuick3DPrincipledMaterial::metalness() const
{
    return m_metalnessAmount;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::metalnessMap() const
{
    return m_metalnessMap;
}

float QQuick3DPrincipledMaterial::normalStrength() const
{
    return m_normalStrength;
}

QQuick3DTexture *QQuick3DPrincipledMaterial::occlusionMap() const
{
    return m_occlusionMap;
}

float QQuick3DPrincipledMaterial::occlusionAmount() const
{
    return m_occlusionAmount;
}

QQuick3DPrincipledMaterial::AlphaMode QQuick3DPrincipledMaterial::alphaMode() const
{
    return m_alphaMode;
}

float QQuick3DPrincipledMaterial::alphaCutoff() const
{
    return m_alphaCutoff;
}

QQuick3DMaterial::TextureChannelMapping QQuick3DPrincipledMaterial::roughnessChannel() const
{
    return m_roughnessChannel;
}

QQuick3DMaterial::TextureChannelMapping QQuick3DPrincipledMaterial::opacityChannel() const
{
    return m_opacityChannel;
}

QQuick3DMaterial::TextureChannelMapping QQuick3DPrincipledMaterial::metalnessChannel() const
{
    return m_metalnessChannel;
}

QQuick3DMaterial::TextureChannelMapping QQuick3DPrincipledMaterial::occlusionChannel() const
{
    return m_occlusionChannel;
}

void QQuick3DPrincipledMaterial::markAllDirty()
{
    m_dirtyAttributes = 0xffffffff;
    QQuick3DMaterial::markAllDirty();
}

void QQuick3DPrincipledMaterial::setLighting(QQuick3DPrincipledMaterial::Lighting lighting)
{
    if (m_lighting == lighting)
        return;

    m_lighting = lighting;
    emit lightingChanged(m_lighting);
    markDirty(LightingModeDirty);
}

void QQuick3DPrincipledMaterial::setBlendMode(QQuick3DPrincipledMaterial::BlendMode blendMode)
{
    if (m_blendMode == blendMode)
        return;

    m_blendMode = blendMode;
    emit blendModeChanged(m_blendMode);
    markDirty(BlendModeDirty);
}

void QQuick3DPrincipledMaterial::setBaseColor(QColor diffuseColor)
{
    if (m_baseColor == diffuseColor)
        return;

    m_baseColor = diffuseColor;
    emit baseColorChanged(m_baseColor);
    markDirty(BaseColorDirty);
}

void QQuick3DPrincipledMaterial::setBaseColorMap(QQuick3DTexture *baseColorMap)
{
    if (m_baseColorMap == baseColorMap)
        return;

    updatePropertyListener(baseColorMap, m_baseColorMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("baseColorMap"), m_connections, [this](QQuick3DObject *n) {
        setBaseColorMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_baseColorMap = baseColorMap;
    emit baseColorMapChanged(m_baseColorMap);
    markDirty(BaseColorDirty);
}

void QQuick3DPrincipledMaterial::setEmissiveMap(QQuick3DTexture *emissiveMap)
{
    if (m_emissiveMap == emissiveMap)
        return;

    updatePropertyListener(emissiveMap, m_emissiveMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("emissiveMap"), m_connections, [this](QQuick3DObject *n) {
        setEmissiveMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_emissiveMap = emissiveMap;
    emit emissiveMapChanged(m_emissiveMap);
    markDirty(EmissiveDirty);
}

void QQuick3DPrincipledMaterial::setEmissiveColor(QColor emissiveColor)
{
    if (m_emissiveColor == emissiveColor)
        return;

    m_emissiveColor = emissiveColor;
    emit emissiveColorChanged(m_emissiveColor);
    markDirty(EmissiveDirty);
}

void QQuick3DPrincipledMaterial::setSpecularReflectionMap(QQuick3DTexture *specularReflectionMap)
{
    if (m_specularReflectionMap == specularReflectionMap)
        return;

    updatePropertyListener(specularReflectionMap, m_specularReflectionMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("specularReflectionMap"), m_connections, [this](QQuick3DObject *n) {
        setSpecularReflectionMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_specularReflectionMap = specularReflectionMap;
    emit specularReflectionMapChanged(m_specularReflectionMap);
    markDirty(SpecularDirty);
}

void QQuick3DPrincipledMaterial::setSpecularMap(QQuick3DTexture *specularMap)
{
    if (m_specularMap == specularMap)
        return;

    updatePropertyListener(specularMap, m_specularMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("specularMap"), m_connections, [this](QQuick3DObject *n) {
        setSpecularMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_specularMap = specularMap;
    emit specularMapChanged(m_specularMap);
    markDirty(SpecularDirty);
}

void QQuick3DPrincipledMaterial::setSpecularTint(float specularTint)
{
    specularTint = ensureNormalized(specularTint);
    if (qFuzzyCompare(m_specularTint, specularTint))
        return;

    m_specularTint = specularTint;
    emit specularTintChanged(m_specularTint);
    markDirty(SpecularDirty);
}

void QQuick3DPrincipledMaterial::setIndexOfRefraction(float indexOfRefraction)
{
    if (qFuzzyCompare(m_indexOfRefraction, indexOfRefraction))
        return;

    m_indexOfRefraction = indexOfRefraction;
    emit indexOfRefractionChanged(m_indexOfRefraction);
    markDirty(IorDirty);
}

void QQuick3DPrincipledMaterial::setSpecularAmount(float specularAmount)
{
    specularAmount = ensureNormalized(specularAmount);
    if (qFuzzyCompare(m_specularAmount, specularAmount))
        return;

    m_specularAmount = specularAmount;
    emit specularAmountChanged(m_specularAmount);
    markDirty(SpecularDirty);
}

void QQuick3DPrincipledMaterial::setRoughness(float roughness)
{
    roughness = ensureNormalized(roughness);
    if (qFuzzyCompare(m_roughness, roughness))
        return;

    m_roughness = roughness;
    emit roughnessChanged(m_roughness);
    markDirty(RoughnessDirty);
}

void QQuick3DPrincipledMaterial::setRoughnessMap(QQuick3DTexture *roughnessMap)
{
    if (m_roughnessMap == roughnessMap)
        return;

    updatePropertyListener(roughnessMap, m_roughnessMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("roughnessMap"), m_connections, [this](QQuick3DObject *n) {
        setRoughnessMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_roughnessMap = roughnessMap;
    emit roughnessMapChanged(m_roughnessMap);
    markDirty(RoughnessDirty);
}

void QQuick3DPrincipledMaterial::setOpacity(float opacity)
{
    opacity = ensureNormalized(opacity);
    if (qFuzzyCompare(m_opacity, opacity))
        return;

    m_opacity = opacity;
    emit opacityChanged(m_opacity);
    markDirty(OpacityDirty);
}

void QQuick3DPrincipledMaterial::setOpacityMap(QQuick3DTexture *opacityMap)
{
    if (m_opacityMap == opacityMap)
        return;

    updatePropertyListener(opacityMap, m_opacityMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("opacityMap"), m_connections, [this](QQuick3DObject *n) {
        setOpacityMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_opacityMap = opacityMap;
    emit opacityMapChanged(m_opacityMap);
    markDirty(OpacityDirty);
}

void QQuick3DPrincipledMaterial::setNormalMap(QQuick3DTexture *normalMap)
{
    if (m_normalMap == normalMap)
        return;

    updatePropertyListener(normalMap, m_normalMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("normalMap"), m_connections, [this](QQuick3DObject *n) {
        setNormalMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_normalMap = normalMap;
    emit normalMapChanged(m_normalMap);
    markDirty(NormalDirty);
}

void QQuick3DPrincipledMaterial::setMetalness(float metalnessAmount)
{
    metalnessAmount = ensureNormalized(metalnessAmount);
    if (qFuzzyCompare(m_metalnessAmount, metalnessAmount))
        return;

    m_metalnessAmount = metalnessAmount;
    emit metalnessChanged(m_metalnessAmount);
    markDirty(MetalnessDirty);
}

void QQuick3DPrincipledMaterial::setMetalnessMap(QQuick3DTexture *metallicMap)
{
    if (m_metalnessMap == metallicMap)
        return;

    updatePropertyListener(metallicMap, m_metalnessMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("metalnessMap"), m_connections, [this](QQuick3DObject *n) {
        setMetalnessMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_metalnessMap = metallicMap;
    emit metalnessMapChanged(m_metalnessMap);
    markDirty(MetalnessDirty);
}

void QQuick3DPrincipledMaterial::setNormalStrength(float factor)
{
    factor = ensureNormalized(factor);
    if (qFuzzyCompare(m_normalStrength, factor))
        return;

    m_normalStrength = factor;
    emit normalStrengthChanged(m_normalStrength);
    markDirty(NormalDirty);
}

void QQuick3DPrincipledMaterial::setOcclusionMap(QQuick3DTexture *occlusionMap)
{
    if (m_occlusionMap == occlusionMap)
        return;

    updatePropertyListener(occlusionMap, m_occlusionMap, QQuick3DObjectPrivate::get(this)->sceneManager, QByteArrayLiteral("occlusionMap"), m_connections, [this](QQuick3DObject *n) {
        setOcclusionMap(qobject_cast<QQuick3DTexture *>(n));
    });

    m_occlusionMap = occlusionMap;
    emit occlusionMapChanged(m_occlusionMap);
    markDirty(OcclusionDirty);
}

void QQuick3DPrincipledMaterial::setOcclusionAmount(float occlusionAmount)
{
    if (qFuzzyCompare(m_occlusionAmount, occlusionAmount))
        return;

    m_occlusionAmount = occlusionAmount;
    emit occlusionAmountChanged(m_occlusionAmount);
    markDirty(OcclusionDirty);
}

void QQuick3DPrincipledMaterial::setAlphaMode(QQuick3DPrincipledMaterial::AlphaMode alphaMode)
{
    if (m_alphaMode == alphaMode)
        return;

    m_alphaMode = alphaMode;
    emit alphaModeChanged(m_alphaMode);
    markDirty(AlphaModeDirty);
}

void QQuick3DPrincipledMaterial::setAlphaCutoff(float alphaCutoff)
{
    if (qFuzzyCompare(m_alphaCutoff, alphaCutoff))
        return;

    m_alphaCutoff = alphaCutoff;
    emit alphaCutoffChanged(m_alphaCutoff);
    markDirty(AlphaModeDirty);
}

void QQuick3DPrincipledMaterial::setMetalnessChannel(TextureChannelMapping channel)
{
    if (m_metalnessChannel == channel)
        return;

    m_metalnessChannel = channel;
    emit metalnessChannelChanged(channel);
    markDirty(MetalnessDirty);
}

void QQuick3DPrincipledMaterial::setRoughnessChannel(TextureChannelMapping channel)
{
    if (m_roughnessChannel == channel)
        return;

    m_roughnessChannel = channel;
    emit roughnessChannelChanged(channel);
    markDirty(RoughnessDirty);
}

void QQuick3DPrincipledMaterial::setOpacityChannel(TextureChannelMapping channel)
{
    if (m_opacityChannel == channel)
        return;

    m_opacityChannel = channel;
    emit opacityChannelChanged(channel);
    markDirty(OpacityDirty);
}

void QQuick3DPrincipledMaterial::setOcclusionChannel(TextureChannelMapping channel)
{
    if (m_occlusionChannel == channel)
        return;

    m_occlusionChannel = channel;
    emit occlusionChannelChanged(channel);
    markDirty(OcclusionDirty);
}

QSSGRenderGraphObject *QQuick3DPrincipledMaterial::updateSpatialNode(QSSGRenderGraphObject *node)
{
    static const auto colorToVec3 = [](const QColor &c) {
        return QVector3D{float(c.redF()), float(c.greenF()), float(c.blueF())};
    };

    static const auto channelMapping = [](TextureChannelMapping mapping) {
        return QSSGRenderDefaultMaterial::TextureChannelMapping(mapping);
    };

    if (!node) {
        markAllDirty();
        node = new QSSGRenderDefaultMaterial(QSSGRenderGraphObject::Type::PrincipledMaterial);
    }

    // Set common material properties
    QQuick3DMaterial::updateSpatialNode(node);

    QSSGRenderDefaultMaterial *material = static_cast<QSSGRenderDefaultMaterial *>(node);

    if (m_dirtyAttributes & LightingModeDirty)
        material->lighting = QSSGRenderDefaultMaterial::MaterialLighting(m_lighting);

    if (m_dirtyAttributes & BlendModeDirty)
        material->blendMode = QSSGRenderDefaultMaterial::MaterialBlendMode(m_blendMode);

    if (m_dirtyAttributes & BaseColorDirty) {
        if (!m_baseColorMap)
            material->colorMap = nullptr;
        else
            material->colorMap = m_baseColorMap->getRenderImage();

        material->color = QVector4D{colorToVec3(m_baseColor), float(m_baseColor.alphaF())};
    }

    if (m_dirtyAttributes & EmissiveDirty) {
        if (!m_emissiveMap)
            material->emissiveMap = nullptr;
        else
            material->emissiveMap = m_emissiveMap->getRenderImage();

        material->emissiveColor = colorToVec3(m_emissiveColor);
    }

    material->fresnelPower = 5.0f;

    if (m_dirtyAttributes & IorDirty)
        material->ior = m_indexOfRefraction;

    if (m_dirtyAttributes & RoughnessDirty) {
        if (!m_roughnessMap)
            material->roughnessMap = nullptr;
        else
            material->roughnessMap = m_roughnessMap->getRenderImage();

        material->specularRoughness = m_roughness;
        material->roughnessChannel = channelMapping(m_roughnessChannel);
    }

    if (m_dirtyAttributes & MetalnessDirty) {
        if (!m_metalnessMap)
            material->metalnessMap = nullptr;
        else
            material->metalnessMap = m_metalnessMap->getRenderImage();

        material->metalnessAmount = m_metalnessAmount;
        material->metalnessChannel = channelMapping(m_metalnessChannel);

        // Update specular values if needed.
        if (!material->isMetalnessEnabled()) {
            m_dirtyAttributes |= SpecularDirty;
        } else {
            material->specularAmount = m_specularAmount;
            material->specularTint = colorToVec3(Qt::white);
        }
    }

    // This test here is intentional, as we want to make sure we match the backend!
    const bool isDielectric = !material->isMetalnessEnabled();
    if (isDielectric && m_dirtyAttributes & SpecularDirty) {
        if (!m_specularReflectionMap)
            material->specularReflection = nullptr;
        else
            material->specularReflection = m_specularReflectionMap->getRenderImage();

        if (!m_specularMap) {
            material->specularMap = nullptr;
        } else {
            material->specularMap = m_specularMap->getRenderImage();
        }

        material->specularAmount = m_specularAmount;
        material->specularTint = QVector3D(m_specularTint, m_specularTint, m_specularTint);
    }

    if (m_dirtyAttributes & OpacityDirty) {
        material->opacity = m_opacity;
        if (!m_opacityMap)
            material->opacityMap = nullptr;
        else
            material->opacityMap = m_opacityMap->getRenderImage();

        material->opacity = m_opacity;
        material->opacityChannel = channelMapping(m_opacityChannel);
    }

    if (m_dirtyAttributes & NormalDirty) {
        if (!m_normalMap)
            material->normalMap = nullptr;
        else
            material->normalMap = m_normalMap->getRenderImage();

        material->bumpAmount = m_normalStrength;
    }

    if (m_dirtyAttributes & OcclusionDirty) {
        if (!m_occlusionMap)
            material->occlusionMap = nullptr;
        else
            material->occlusionMap = m_occlusionMap->getRenderImage();
        material->occlusionAmount = m_occlusionAmount;
        material->occlusionChannel = channelMapping(m_occlusionChannel);
    }

    if (m_dirtyAttributes & AlphaModeDirty) {
        material->alphaMode = QSSGRenderDefaultMaterial::MaterialAlphaMode(m_alphaMode);
        material->alphaCutoff = m_alphaCutoff;
    }

    m_dirtyAttributes = 0;

    return node;
}

void QQuick3DPrincipledMaterial::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
{
    if (change == QQuick3DObject::ItemSceneChange)
        updateSceneManager(value.sceneManager);
}

void QQuick3DPrincipledMaterial::updateSceneManager(const QSharedPointer<QQuick3DSceneManager> &window)
{
    // Check all the resource value's windows, and update as necessary
    if (window) {
        QQuick3DObjectPrivate::refSceneManager(m_baseColorMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_emissiveMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_specularReflectionMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_specularMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_roughnessMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_opacityMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_normalMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_metalnessMap, window);
        QQuick3DObjectPrivate::refSceneManager(m_occlusionMap, window);
    } else {
        QQuick3DObjectPrivate::derefSceneManager(m_baseColorMap);
        QQuick3DObjectPrivate::derefSceneManager(m_emissiveMap);
        QQuick3DObjectPrivate::derefSceneManager(m_specularReflectionMap);
        QQuick3DObjectPrivate::derefSceneManager(m_specularMap);
        QQuick3DObjectPrivate::derefSceneManager(m_roughnessMap);
        QQuick3DObjectPrivate::derefSceneManager(m_opacityMap);
        QQuick3DObjectPrivate::derefSceneManager(m_normalMap);
        QQuick3DObjectPrivate::derefSceneManager(m_metalnessMap);
        QQuick3DObjectPrivate::derefSceneManager(m_occlusionMap);
    }
}

void QQuick3DPrincipledMaterial::markDirty(QQuick3DPrincipledMaterial::DirtyType type)
{
    if (!(m_dirtyAttributes & quint32(type))) {
        m_dirtyAttributes |= quint32(type);
        update();
    }
}

QT_END_NAMESPACE
