Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
++
++ import QtQuick
++ import QtQuick.Controls
++ import QtQuick.Layouts
++ import QtQuick3D
++
++ ScrollView {
++ id: rootView
++ required property PrincipledMaterial targetMaterial
++ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
++ width: availableWidth
++ property bool specularGlossyMode: false
++
++ ColumnLayout {
++ width: rootView.availableWidth
++
++ MarkdownLabel {
++ text: `# Iridescence
++ Iridescence is what gives a soap bubble, an oil slick and a beetle shell their
++ shifting colors. A film only a few hundred nanometers thick sits over the
++ surface, and light reflecting off its front face interferes with light that
++ went through and reflected off the back. Some wavelengths cancel out and others
++ reinforce, so a white light comes back colored, and the color changes as the
++ surface curves away from you.
++
++ Iridescence is only available on PrincipledMaterial.${rootView.specularGlossyMode ? "\n\n**The scene is currently showing the SpecularGlossyMaterial, so switch back to the PrincipledMaterial in the Basics tab to see these controls take effect.**" : ""}
++
++ Like anisotropy, this is not an extra layer of reflection. It replaces the
++ Fresnel response of the specular reflection the material already has, which
++ means it is most visible where that reflection is strongest. Turning Metalness
++ up in the Basics tab makes the effect far more dramatic; over a dielectric it
++ is deliberately subtle, because a real soap film only reflects a few percent of
++ the light that hits it.
++
++ ## Iridescence Factor
++ This blends between the material without and with the film. At \`0.0\`, the
++ default, there is no film and nothing changes.
++ `
++ }
++
++ RowLayout {
++ Label {
++ text: "Iridescence Factor (" + rootView.targetMaterial.iridescenceFactor.toFixed(2) + ")"
++ Layout.fillWidth: true
++ }
++ Slider {
++ from: 0
++ to: 1
++ value: rootView.targetMaterial.iridescenceFactor
++ onValueChanged: rootView.targetMaterial.iridescenceFactor = value
++ }
++ }
++
++ MarkdownLabel {
++ text: `### Iridescence Map
++ The Iridescence Map is a single channel texture multiplied against the
++ Iridescence Factor, so it can fade the film in and out across the surface.
++ `
++ }
++
++ ComboBox {
++ id: iridescenceChannelComboBox
++ textRole: "text"
++ valueRole: "value"
++ implicitContentWidthPolicy: ComboBox.WidestText
++ onActivated: rootView.targetMaterial.iridescenceChannel = currentValue
++ Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.iridescenceChannel)
++ model: [
++ { value: PrincipledMaterial.R, text: "Red Channel"},
++ { value: PrincipledMaterial.G, text: "Green Channel"},
++ { value: PrincipledMaterial.B, text: "Blue Channel"},
++ { value: PrincipledMaterial.A, text: "Alpha Channel"}
++ ]
++ }
++
++ TextureSourceControl {
++ defaultTexture: "maps/noise.png"
++ defaultClearColor: "black"
++ onTargetTextureChanged: {
++ rootView.targetMaterial.iridescenceMap = targetTexture
++ }
++ }
++
++ MarkdownLabel {
++ text: `## Index Of Refraction
++ This is the index of refraction of the film itself, not of the material under
++ it. Together with the thickness it decides which colors come back. The default
++ of \`1.3\` is about that of a soap film; raising it strengthens the effect and
++ shifts the palette.
++ `
++ }
++
++ RowLayout {
++ Label {
++ text: "Iridescence IOR (" + rootView.targetMaterial.iridescenceIndexOfRefraction.toFixed(2) + ")"
++ Layout.fillWidth: true
++ }
++ Slider {
++ from: 1
++ to: 3
++ value: rootView.targetMaterial.iridescenceIndexOfRefraction
++ onValueChanged: rootView.targetMaterial.iridescenceIndexOfRefraction = value
++ }
++ }
++
++ MarkdownLabel {
++ text: `## Thickness
++ Thickness is measured in nanometers, and it is what picks the hue. Sweeping it
++ across the range of visible light, roughly \`200\` to \`800\` nanometers, walks
++ through the whole sequence of colors the film can produce, then begins to
++ repeat as higher interference orders take over.
++
++ With no Thickness Map the film is uniformly Thickness Maximum thick, and the
++ minimum has no effect. Add a map and the two values become the ends of the
++ range that the map interpolates between.
++ `
++ }
++
++ RowLayout {
++ Label {
++ text: "Thickness Minimum (" + rootView.targetMaterial.iridescenceThicknessMinimum.toFixed(0) + " nm)"
++ Layout.fillWidth: true
++ }
++ Slider {
++ from: 0
++ to: 1200
++ value: rootView.targetMaterial.iridescenceThicknessMinimum
++ onValueChanged: rootView.targetMaterial.iridescenceThicknessMinimum = value
++ }
++ }
++
++ RowLayout {
++ Label {
++ text: "Thickness Maximum (" + rootView.targetMaterial.iridescenceThicknessMaximum.toFixed(0) + " nm)"
++ Layout.fillWidth: true
++ }
++ Slider {
++ from: 0
++ to: 1200
++ value: rootView.targetMaterial.iridescenceThicknessMaximum
++ onValueChanged: rootView.targetMaterial.iridescenceThicknessMaximum = value
++ }
++ }
++
++ MarkdownLabel {
++ text: `### Thickness Map
++ A single channel texture interpolating the film thickness between the minimum
++ and the maximum. The glTF extension packs thickness into the green channel,
++ which is why that is the default here.
++ `
++ }
++
++ ComboBox {
++ id: thicknessChannelComboBox
++ textRole: "text"
++ valueRole: "value"
++ implicitContentWidthPolicy: ComboBox.WidestText
++ onActivated: rootView.targetMaterial.iridescenceThicknessChannel = currentValue
++ Component.onCompleted: currentIndex = indexOfValue(rootView.targetMaterial.iridescenceThicknessChannel)
++ model: [
++ { value: PrincipledMaterial.R, text: "Red Channel"},
++ { value: PrincipledMaterial.G, text: "Green Channel"},
++ { value: PrincipledMaterial.B, text: "Blue Channel"},
++ { value: PrincipledMaterial.A, text: "Alpha Channel"}
++ ]
++ }
++
++ TextureSourceControl {
++ defaultTexture: "maps/noise.png"
++ defaultClearColor: "black"
++ onTargetTextureChanged: {
++ rootView.targetMaterial.iridescenceThicknessMap = targetTexture
++ }
++ }
++ }
++ }