mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-04 01:08:26 +02:00
[ui] Viewer3D: add "Sync with Image Selection" camera mode
* Inspector * changed "SETTINGS" to "DISPLAY" * new "CAMERA" section: activate camera synchronization + control image overlay * ImageOverlay: new component to display (undistorted) image on top of the 3D view * ViewpointCamera: new component that sets up a Camera based on a Viewpoint internal parameter * Viewer3D: additional ViewpointCamera to perform synchronization with image selection * Viewer3DSettings: new properties related to camera synchronization mode
This commit is contained in:
parent
774675bd65
commit
8dce4fc72f
5 changed files with 280 additions and 35 deletions
99
meshroom/ui/qml/Viewer3D/ImageOverlay.qml
Normal file
99
meshroom/ui/qml/Viewer3D/ImageOverlay.qml
Normal file
|
@ -0,0 +1,99 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
/**
|
||||
* ImageOverlay enables to display a Viewpoint image on top of a 3D View.
|
||||
* It takes the principal point correction into account and handle image ratio to
|
||||
* correclty fit or crop according to original image ratio and parent Item ratio.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
/// The url of the image to display
|
||||
property alias source: image.source
|
||||
/// Source image ratio
|
||||
property real imageRatio: 1.0
|
||||
/// Principal Point correction as UV coordinates offset
|
||||
property alias uvCenterOffset: shader.uvCenterOffset
|
||||
/// Whether to display the frame around the image
|
||||
property bool showFrame
|
||||
/// Opacity of the image
|
||||
property alias imageOpacity: shader.opacity
|
||||
|
||||
implicitWidth: 300
|
||||
implicitHeight: 300
|
||||
|
||||
// Display frame
|
||||
RowLayout {
|
||||
id: frameBG
|
||||
spacing: 1
|
||||
anchors.fill: parent
|
||||
visible: root.showFrame && image.status === Image.Ready
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
Item {
|
||||
Layout.preferredHeight: image.paintedHeight
|
||||
Layout.preferredWidth: image.paintedWidth
|
||||
}
|
||||
Rectangle {
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
asynchronous: true
|
||||
smooth: false
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
// Preserver aspect fit while display ratio is aligned with image ratio, crop otherwise
|
||||
fillMode: width/height >= imageRatio ? Image.PreserveAspectFit : Image.PreserveAspectCrop
|
||||
autoTransform: true
|
||||
}
|
||||
|
||||
|
||||
// Custom shader for displaying undistorted images
|
||||
// with principal point correction
|
||||
ShaderEffect {
|
||||
id: shader
|
||||
anchors.centerIn: parent
|
||||
visible: image.status === Image.Ready
|
||||
width: image.paintedWidth
|
||||
height: image.paintedHeight
|
||||
property variant src: image
|
||||
property variant uvCenterOffset
|
||||
|
||||
vertexShader: "
|
||||
#version 330 core
|
||||
uniform highp mat4 qt_Matrix;
|
||||
attribute highp vec4 qt_Vertex;
|
||||
attribute highp vec2 qt_MultiTexCoord0;
|
||||
out highp vec2 coord;
|
||||
void main() {
|
||||
coord = qt_MultiTexCoord0;
|
||||
gl_Position = qt_Matrix * qt_Vertex;
|
||||
}"
|
||||
fragmentShader: "
|
||||
#version 330 core
|
||||
in highp vec2 coord;
|
||||
uniform sampler2D src;
|
||||
uniform lowp vec2 uvCenterOffset;
|
||||
uniform lowp float qt_Opacity;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
vec2 xy = coord + uvCenterOffset;
|
||||
fragColor = texture2D(src, xy);
|
||||
fragColor.rgb *= qt_Opacity;
|
||||
fragColor.a = qt_Opacity;
|
||||
// remove undistortion black pixels
|
||||
fragColor.a *= step(0.001, fragColor.r + fragColor.g + fragColor.b);
|
||||
}"
|
||||
}
|
||||
}
|
|
@ -32,15 +32,37 @@ FloatingPane {
|
|||
|
||||
Group {
|
||||
Layout.fillWidth: true
|
||||
title: "SETTINGS"
|
||||
title: "DISPLAY"
|
||||
|
||||
GridLayout {
|
||||
width: parent.width
|
||||
columns: 2
|
||||
columnSpacing: 6
|
||||
rowSpacing: 3
|
||||
|
||||
MaterialLabel { font.family: MaterialIcons.fontFamily; text: MaterialIcons.grain; padding: 2 }
|
||||
Flow {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
spacing: 1
|
||||
MaterialToolButton {
|
||||
text: MaterialIcons.grid_on
|
||||
ToolTip.text: "Display Grid"
|
||||
checked: Viewer3DSettings.displayGrid
|
||||
onClicked: Viewer3DSettings.displayGrid = !Viewer3DSettings.displayGrid
|
||||
}
|
||||
MaterialToolButton {
|
||||
text: MaterialIcons.adjust
|
||||
checked: Viewer3DSettings.displayGizmo
|
||||
ToolTip.text: "Display Trackball"
|
||||
onClicked: Viewer3DSettings.displayGizmo = !Viewer3DSettings.displayGizmo
|
||||
}
|
||||
MaterialToolButton {
|
||||
text: MaterialIcons.call_merge
|
||||
ToolTip.text: "Display Origin"
|
||||
checked: Viewer3DSettings.displayOrigin
|
||||
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
|
||||
}
|
||||
}
|
||||
MaterialLabel { text: MaterialIcons.grain; padding: 2 }
|
||||
RowLayout {
|
||||
Slider {
|
||||
Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.1
|
||||
|
@ -60,7 +82,7 @@ FloatingPane {
|
|||
}
|
||||
|
||||
}
|
||||
MaterialLabel { font.family: MaterialIcons.fontFamily; text: MaterialIcons.videocam; padding: 2 }
|
||||
MaterialLabel { text: MaterialIcons.videocam; padding: 2 }
|
||||
Slider {
|
||||
value: Viewer3DSettings.cameraScale
|
||||
from: 0
|
||||
|
@ -73,27 +95,60 @@ FloatingPane {
|
|||
ToolTip.visible: hovered || pressed
|
||||
ToolTip.delay: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Group {
|
||||
Layout.fillWidth: true
|
||||
title: "CAMERA"
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
||||
// Image/Camera synchronization
|
||||
Flow {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
CheckBox {
|
||||
text: "Grid"
|
||||
padding: 2
|
||||
checked: Viewer3DSettings.displayGrid
|
||||
onClicked: Viewer3DSettings.displayGrid = !Viewer3DSettings.displayGrid
|
||||
// Synchronization
|
||||
MaterialToolButton {
|
||||
id: syncViewpointCamera
|
||||
enabled: _reconstruction.sfmReport
|
||||
text: MaterialIcons.linked_camera
|
||||
ToolTip.text: "Sync with Image Selection"
|
||||
checked: enabled && Viewer3DSettings.syncViewpointCamera
|
||||
onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
|
||||
}
|
||||
CheckBox {
|
||||
text: "Gizmo"
|
||||
padding: 2
|
||||
checked: Viewer3DSettings.displayGizmo
|
||||
onClicked: Viewer3DSettings.displayGizmo = !Viewer3DSettings.displayGizmo
|
||||
// Image Overlay controls
|
||||
RowLayout {
|
||||
visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
|
||||
spacing: 2
|
||||
// Activation
|
||||
MaterialToolButton {
|
||||
text: MaterialIcons.image
|
||||
ToolTip.text: "Image Overlay"
|
||||
checked: Viewer3DSettings.viewpointImageOverlay
|
||||
onClicked: Viewer3DSettings.viewpointImageOverlay = !Viewer3DSettings.viewpointImageOverlay
|
||||
}
|
||||
// Opacity
|
||||
Slider {
|
||||
visible: Viewer3DSettings.showViewpointImageOverlay
|
||||
implicitWidth: 60
|
||||
from: 0
|
||||
to: 100
|
||||
value: Viewer3DSettings.viewpointImageOverlayOpacity * 100
|
||||
onValueChanged: Viewer3DSettings.viewpointImageOverlayOpacity = value / 100
|
||||
ToolTip.text: "Image Opacity: " + Viewer3DSettings.viewpointImageOverlayOpacity.toFixed(2)
|
||||
ToolTip.visible: hovered || pressed
|
||||
ToolTip.delay: 100
|
||||
}
|
||||
}
|
||||
CheckBox {
|
||||
text: "Origin"
|
||||
padding: 2
|
||||
checked: Viewer3DSettings.displayOrigin
|
||||
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
|
||||
// Image Frame control
|
||||
MaterialToolButton {
|
||||
visible: syncViewpointCamera.enabled && Viewer3DSettings.showViewpointImageOverlay
|
||||
enabled: Viewer3DSettings.syncViewpointCamera
|
||||
text: MaterialIcons.crop_free
|
||||
ToolTip.text: "Frame Overlay"
|
||||
checked: Viewer3DSettings.viewpointImageFrame
|
||||
onClicked: Viewer3DSettings.viewpointImageFrame = !Viewer3DSettings.viewpointImageFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +169,6 @@ FloatingPane {
|
|||
implicitHeight: parent.height
|
||||
checkable: true
|
||||
checked: true
|
||||
padding: 0
|
||||
}
|
||||
|
||||
ListView {
|
||||
|
@ -300,21 +354,13 @@ FloatingPane {
|
|||
}
|
||||
}
|
||||
|
||||
// Media unavailability indicator
|
||||
MaterialToolButton {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
enabled: false
|
||||
visible: !model.valid
|
||||
text: MaterialIcons.no_sim
|
||||
font.pointSize: 10
|
||||
}
|
||||
|
||||
// Remove media from library button
|
||||
MaterialToolButton {
|
||||
id: removeButton
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillHeight: true
|
||||
|
||||
visible: !loading
|
||||
visible: hovered || mouseArea.containsMouse && !loading
|
||||
text: MaterialIcons.clear
|
||||
font.pointSize: 10
|
||||
ToolTip.text: "Remove"
|
||||
|
|
|
@ -6,7 +6,7 @@ import QtQml.Models 2.2
|
|||
import QtQuick.Scene3D 2.0
|
||||
import Qt3D.Core 2.1
|
||||
import Qt3D.Render 2.1
|
||||
import Qt3D.Extras 2.1
|
||||
import Qt3D.Extras 2.10
|
||||
import Qt3D.Input 2.1 as Qt3DInput // to avoid clash with Controls2 Action
|
||||
|
||||
import MaterialIcons 2.2
|
||||
|
@ -25,6 +25,9 @@ FocusScope {
|
|||
readonly property vector3d defaultCamUpVector: Qt.vector3d(0.0, 1.0, 0.0)
|
||||
readonly property vector3d defaultCamViewCenter: Qt.vector3d(0.0, 0.0, 0.0)
|
||||
|
||||
readonly property var viewpoint: _reconstruction.selectedViewpoint
|
||||
readonly property bool doSyncViewpointCamera: Viewer3DSettings.syncViewpointCamera && (viewpoint && viewpoint.isReconstructed)
|
||||
|
||||
// functions
|
||||
function resetCameraPosition() {
|
||||
mainCamera.position = defaultCamPosition;
|
||||
|
@ -88,6 +91,7 @@ FocusScope {
|
|||
Camera {
|
||||
id: mainCamera
|
||||
projectionType: CameraLens.PerspectiveProjection
|
||||
enabled: cameraSelector.camera == mainCamera
|
||||
fieldOfView: 45
|
||||
nearPlane : 0.01
|
||||
farPlane : 10000.0
|
||||
|
@ -113,10 +117,17 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
ViewpointCamera {
|
||||
id: viewpointCamera
|
||||
enabled: cameraSelector.camera === camera
|
||||
viewpoint: root.viewpoint
|
||||
camera.aspectRatio: width/height
|
||||
}
|
||||
|
||||
TrackballGizmo {
|
||||
beamRadius: 4.0/root.height
|
||||
alpha: cameraController.moving ? 1.0 : 0.7
|
||||
enabled: Viewer3DSettings.displayGizmo
|
||||
enabled: Viewer3DSettings.displayGizmo && cameraSelector.camera == mainCamera
|
||||
xColor: Colors.red
|
||||
yColor: Colors.green
|
||||
zColor: Colors.blue
|
||||
|
@ -129,6 +140,8 @@ FocusScope {
|
|||
|
||||
DefaultCameraController {
|
||||
id: cameraController
|
||||
enabled: cameraSelector.camera == mainCamera
|
||||
|
||||
windowSize {
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
@ -178,7 +191,7 @@ FocusScope {
|
|||
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
|
||||
CameraSelector {
|
||||
id: cameraSelector
|
||||
camera: mainCamera
|
||||
camera: doSyncViewpointCamera ? viewpointCamera.camera : mainCamera
|
||||
FrustumCulling {
|
||||
ClearBuffers {
|
||||
clearColor: "transparent"
|
||||
|
@ -228,6 +241,24 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
// Image overlay when navigating reconstructed cameras
|
||||
Loader {
|
||||
id: imageOverlayLoader
|
||||
anchors.fill: parent
|
||||
|
||||
active: doSyncViewpointCamera
|
||||
visible: Viewer3DSettings.showViewpointImageOverlay
|
||||
|
||||
sourceComponent: ImageOverlay {
|
||||
id: imageOverlay
|
||||
source: root.viewpoint.undistortedImageSource
|
||||
imageRatio: root.viewpoint.orientedImageSize.width / root.viewpoint.orientedImageSize.height
|
||||
uvCenterOffset: root.viewpoint.uvCenterOffset
|
||||
showFrame: Viewer3DSettings.showViewpointImageFrame
|
||||
imageOpacity: Viewer3DSettings.viewpointImageOverlayOpacity
|
||||
}
|
||||
}
|
||||
|
||||
// Media loading overlay
|
||||
// (Scene3D is frozen while a media is being loaded)
|
||||
Rectangle {
|
||||
|
|
|
@ -24,7 +24,7 @@ Item {
|
|||
// Available render modes
|
||||
readonly property var renderModes: [ // Can't use ListModel because of MaterialIcons expressions
|
||||
{"name": "Solid", "icon": MaterialIcons.crop_din },
|
||||
{"name": "Wireframe", "icon": MaterialIcons.grid_on },
|
||||
{"name": "Wireframe", "icon": MaterialIcons.details },
|
||||
{"name": "Textured", "icon": MaterialIcons.texture },
|
||||
]
|
||||
// Current render mode
|
||||
|
@ -39,4 +39,11 @@ Item {
|
|||
property bool displayGrid: true
|
||||
property bool displayGizmo: true
|
||||
property bool displayOrigin: false
|
||||
// Camera
|
||||
property bool syncViewpointCamera: false
|
||||
property bool viewpointImageOverlay: true
|
||||
property real viewpointImageOverlayOpacity: 0.5
|
||||
readonly property bool showViewpointImageOverlay: syncViewpointCamera && viewpointImageOverlay
|
||||
property bool viewpointImageFrame: false
|
||||
readonly property bool showViewpointImageFrame: syncViewpointCamera && viewpointImageFrame
|
||||
}
|
||||
|
|
62
meshroom/ui/qml/Viewer3D/ViewpointCamera.qml
Normal file
62
meshroom/ui/qml/Viewer3D/ViewpointCamera.qml
Normal file
|
@ -0,0 +1,62 @@
|
|||
import QtQuick 2.12
|
||||
import Qt3D.Core 2.12
|
||||
import Qt3D.Render 2.12
|
||||
|
||||
|
||||
/**
|
||||
* ViewpointCamera sets up a Camera to match a Viewpoint's internal parameters.
|
||||
*/
|
||||
Entity {
|
||||
id: root
|
||||
|
||||
property variant viewpoint
|
||||
|
||||
property Camera camera: Camera {
|
||||
|
||||
nearPlane : 0.1
|
||||
farPlane : 10000.0
|
||||
viewCenter: Qt.vector3d(0.0, 0.0, -1.0)
|
||||
|
||||
// Scene light, attached to the camera
|
||||
Entity {
|
||||
components: [
|
||||
PointLight {
|
||||
color: "white"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
components: [
|
||||
Transform {
|
||||
id: transform
|
||||
|
||||
Behavior on rotation {
|
||||
PropertyAnimation { duration: 200}
|
||||
}
|
||||
Behavior on translation {
|
||||
Vector3dAnimation { duration: 200}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
StateGroup {
|
||||
states: [
|
||||
State {
|
||||
name: "valid"
|
||||
when: root.viewpoint !== null
|
||||
PropertyChanges {
|
||||
target: camera
|
||||
fieldOfView: root.viewpoint.fieldOfView
|
||||
upVector: root.viewpoint.upVector
|
||||
}
|
||||
PropertyChanges {
|
||||
target: transform
|
||||
rotation: root.viewpoint.rotation
|
||||
translation: root.viewpoint.translation
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue