[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:
Yann Lanthony 2019-09-10 19:33:55 +02:00
parent 774675bd65
commit 8dce4fc72f
No known key found for this signature in database
GPG key ID: 519FAE6DF7A70642
5 changed files with 280 additions and 35 deletions

View 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);
}"
}
}

View file

@ -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"

View file

@ -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 {

View file

@ -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
}

View 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
}
}
]
}
}