Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Add and improve multiple UI tools for Photometric stereo #2444

Merged
merged 7 commits into from
Nov 14, 2024
Merged
18 changes: 17 additions & 1 deletion meshroom/ui/components/scene3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,23 @@ def convertRotationFromCV2GL(self, rotation):
U = M * quaternion * M

return U.toEulerAngles()


@Slot(QVector3D, QVector3D, float, float, result=QVector3D)
def getRotatedCameraViewVector(self, camereViewVector, cameraUpVector, pitch, yaw):
""" Compute the rotated camera view vector with given pitch and yaw (in degrees).
Args:
camereViewVector (QVector3D): Camera view vector, the displacement from the camera position to its target
cameraUpVector (QVector3D): Camera up vector, the direction the top of the camera is facing
pitch (float): Rotation pitch (in degrees)
yaw (float): Rotation yaw (in degrees)
Returns:
QVector3D: rotated camera view vector
"""
cameraSideVector = QVector3D.crossProduct(camereViewVector, cameraUpVector)
yawRot = QQuaternion.fromAxisAndAngle(cameraUpVector, yaw)
pitchRot = QQuaternion.fromAxisAndAngle(cameraSideVector, pitch)
return (yawRot * pitchRot).rotatedVector(camereViewVector)

@Slot(QVector3D, QMatrix4x4, Qt3DRender.QCamera, QSize, result=float)
def computeScaleUnitFromModelMatrix(self, axis, modelMat, camera, windowSize):
""" Compute the length of the screen projected vector axis unit transformed by the model matrix.
Expand Down
261 changes: 261 additions & 0 deletions meshroom/ui/qml/Controls/DirectionalLightPane.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects // for OpacityMask & Glow
import MaterialIcons 2.2
import Utils 1.0

/**
* Directional Light Pane
*
* @biref A small pane to control a directional light with a 2d ball controller.
*
* @param lightYawValue - directional light yaw (degrees)
* @param lightPitchValue - directional light pitch (degrees)
*/

FloatingPane {
id: root

// yaw and pitch properties
property double lightYawValue: 0
property double lightPitchValue: 0

// 2d controller display size properties
readonly property real controllerSize: 80
readonly property real controllerRadius: controllerSize * 0.5

function reset() {
lightYawValue = 0;
lightPitchValue = 0;
}

// update 2d controller if yaw value changed
onLightYawValueChanged: { lightBallController.update() }

// update 2d controller if pitch value changed
onLightPitchValueChanged: { lightBallController.update() }

anchors.margins: 0
padding: 5
radius: 0

ColumnLayout {
anchors.fill: parent
spacing: 5

// header
RowLayout {
// pane title
Label {
text: "Directional Light"
font.bold: true
Layout.fillWidth: true
}

// minimize or maximize button
MaterialToolButton {
id: bodyButton
text: lightPaneBody.visible ? MaterialIcons.arrow_drop_down : MaterialIcons.arrow_drop_up
font.pointSize: 10
ToolTip.text: lightPaneBody.visible ? "Minimize" : "Maximize"
onClicked: { lightPaneBody.visible = !lightPaneBody.visible }
}

// reset button
MaterialToolButton {
id: resetButton
text: MaterialIcons.refresh
font.pointSize: 10
ToolTip.text: "Reset"
onClicked: reset()
}
}

// body
RowLayout {
id: lightPaneBody
spacing: 10

// light parameters
GridLayout {
columns: 3
rowSpacing: 2
columnSpacing: 8
Layout.alignment: Qt.AlignBottom

// light yaw
Label {
text: "Yaw"
}
TextField {
id: lightYawTF
text: lightYawValue.toFixed(2)
selectByMouse: true
horizontalAlignment: TextInput.AlignRight
validator: doubleDegreeValidator
onEditingFinished: { lightYawValue = lightYawTF.text }
ToolTip.text: "Light yaw (degrees)."
ToolTip.visible: hovered
Layout.preferredWidth: textMetricsDegreeValue.width
}
Label {
text: "°"
}

// light pitch
Label {
text: "Pitch"
}
TextField {
id: lightPitchTF
text: lightPitchValue.toFixed(2)
selectByMouse: true
horizontalAlignment: TextInput.AlignRight
validator: doubleDegreeValidator
onEditingFinished: { lightPitchValue = lightPitchTF.text }
ToolTip.text: "Light pitch (degrees)."
ToolTip.visible: hovered
Layout.preferredWidth: textMetricsDegreeValue.width
}
Label {
text: "°"
}
}

// directional light ball controller
Rectangle {
id: lightBallController
anchors.margins: 0
width: controllerSize
height: controllerSize
radius: 180 // circle
color: "#FF000000"
Layout.rightMargin: 5
Layout.leftMargin: 5
Layout.bottomMargin: 5

function update() {
// get point from light yaw and pitch
var y = (lightPitchValue / 90 * controllerRadius)
var xMax = Math.sqrt(controllerRadius * controllerRadius - y * y) // get sphere maximum x coordinate
var x = (lightYawValue / 90 * xMax)

// get angle and distance
var angleRad = Math.atan2(y, x)
var distance = Math.sqrt(x * x + y * y)

// avoid controller overflow
if(distance > controllerRadius)
{
x = controllerRadius * Math.cos(angleRad)
y = controllerRadius * Math.sin(angleRad)
}

// compute distance function for light gradient emulation
var distanceRatio = Math.min(distance, controllerRadius) / controllerRadius
var distanceFunct = distanceRatio * distanceRatio * 0.3

// update light point
lightPoint.x = lightPoint.startOffset + x
lightPoint.y = lightPoint.startOffset + y

// update light gradient
lightGradient.angle = angleRad * (180 / Math.PI) // radians to degrees
lightGradient.horizontalRadius = controllerSize * (1 - distanceFunct)
lightGradient.verticalRadius = controllerSize * (1 + distanceFunct)
lightGradient.horizontalOffset = x * (1 - distanceFunct)
lightGradient.verticalOffset = y * (1 - distanceFunct)
}

RadialGradient {
id: lightGradient
anchors.centerIn: parent
width: controllerSize
height: width
horizontalRadius: controllerSize
verticalRadius: controllerSize
angle: 0
gradient: Gradient {
GradientStop { position: 0.00; color: "#FFFFFFFF" }
GradientStop { position: 0.10; color: "#FFAAAAAA" }
GradientStop { position: 0.50; color: "#FF0C0C0C" }
}
layer.enabled: true
layer.effect: OpacityMask {
id: mask
maskSource: Rectangle {
height: lightGradient.height
width: lightGradient.width
radius: 180 // circle
}
}
}

Rectangle {
id: lightPoint

property double startOffset : (parent.width - width) * 0.5

x: startOffset
y: startOffset
width: controllerRadius / 6
height: width
radius: 180 // circle
color: "white"
}

Glow {
anchors.fill: lightPoint
radius: controllerRadius / 5
samples: 17
color: "white"
source: lightPoint
}

MouseArea {
id: lightMouseArea
anchors.centerIn: parent
anchors.fill: parent

onPositionChanged: {
// get coordinates from center
var x = mouseX - controllerRadius
var y = mouseY - controllerRadius

// get distance to center
var distance = Math.sqrt(x * x + y * y)

// avoid controller overflow
if(distance > controllerRadius)
{
var angleRad = Math.atan2(y, x)
x = controllerRadius * Math.cos(angleRad)
y = controllerRadius * Math.sin(angleRad)
}

// get sphere maximum x coordinate
var xMax = Math.sqrt(controllerRadius * controllerRadius - y * y)

// update light yaw and pitch
lightYawValue = (xMax > 0) ? ((x / xMax) * 90) : 0 // between -90 and 90 degrees
lightPitchValue = (y / controllerRadius) * 90 // between -90 and 90 degrees
}
}
}
}
}

DoubleValidator {
id: doubleDegreeValidator
locale: 'C' // use '.' decimal separator disregarding of the system locale
bottom: -90.0
top: 90.0
}

TextMetrics {
id: textMetricsDegreeValue
font: lightYawTF.font
text: "12.3456"
}
}
3 changes: 2 additions & 1 deletion meshroom/ui/qml/Controls/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ ExifOrientedViewer 1.0 ExifOrientedViewer.qml
FilterComboBox 1.0 FilterComboBox.qml
IntSelector 1.0 IntSelector.qml
MScrollBar 1.0 MScrollBar.qml
MSplitView 1.0 MSplitView.qml
MSplitView 1.0 MSplitView.qml
DirectionalLightPane 1.0 DirectionalLightPane.qml
48 changes: 48 additions & 0 deletions meshroom/ui/qml/Viewer/PhongImageViewer.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import QtQuick
import Utils 1.0

import AliceVision 1.0 as AliceVision

/**
* PhongImageViewer displays an Image (albedo + normal) with a given light direction.
* Shading is done using Blinn-Phong reflection model, material and light direction parameters available.
* Accept HdrImageToolbar controls (gamma / offset / channel).
*
* <!> Requires QtAliceVision plugin.
*/

AliceVision.PhongImageViewer {
id: root

width: sourceSize.width
height: sourceSize.height
visible: true

// paintedWidth / paintedHeight / imageStatus for compatibility with standard Image
property int paintedWidth: sourceSize.width
property int paintedHeight: sourceSize.height
property var imageStatus: {
if (root.status === AliceVision.PhongImageViewer.EStatus.LOADING) {
return Image.Loading
} else if (root.status === AliceVision.PhongImageViewer.EStatus.LOADING_ERROR ||
root.status === AliceVision.PhongImageViewer.EStatus.MISSING_FILE) {
return Image.Error
} else if ((root.sourcePath === "") || (root.sourceSize.height <= 0) || (root.sourceSize.width <= 0)) {
return Image.Null
}

return Image.Ready
}

property string channelModeString : "rgba"
channelMode: {
switch (channelModeString) {
case "rgb": return AliceVision.PhongImageViewer.EChannelMode.RGB
case "r": return AliceVision.PhongImageViewer.EChannelMode.R
case "g": return AliceVision.PhongImageViewer.EChannelMode.G
case "b": return AliceVision.PhongImageViewer.EChannelMode.B
case "a": return AliceVision.PhongImageViewer.EChannelMode.A
default: return AliceVision.PhongImageViewer.EChannelMode.RGBA
}
}
}
Loading
Loading