[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 { Group {
Layout.fillWidth: true Layout.fillWidth: true
title: "SETTINGS" title: "DISPLAY"
GridLayout { GridLayout {
width: parent.width width: parent.width
columns: 2 columns: 2
columnSpacing: 6 columnSpacing: 6
rowSpacing: 3 rowSpacing: 3
Flow {
MaterialLabel { font.family: MaterialIcons.fontFamily; text: MaterialIcons.grain; padding: 2 } 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 { RowLayout {
Slider { Slider {
Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.1 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 { Slider {
value: Viewer3DSettings.cameraScale value: Viewer3DSettings.cameraScale
from: 0 from: 0
@ -73,27 +95,60 @@ FloatingPane {
ToolTip.visible: hovered || pressed ToolTip.visible: hovered || pressed
ToolTip.delay: 150 ToolTip.delay: 150
} }
}
}
Group {
Layout.fillWidth: true
title: "CAMERA"
ColumnLayout {
width: parent.width
// Image/Camera synchronization
Flow { Flow {
Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
spacing: 2 spacing: 2
CheckBox { // Synchronization
text: "Grid" MaterialToolButton {
padding: 2 id: syncViewpointCamera
checked: Viewer3DSettings.displayGrid enabled: _reconstruction.sfmReport
onClicked: Viewer3DSettings.displayGrid = !Viewer3DSettings.displayGrid text: MaterialIcons.linked_camera
ToolTip.text: "Sync with Image Selection"
checked: enabled && Viewer3DSettings.syncViewpointCamera
onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
} }
CheckBox { // Image Overlay controls
text: "Gizmo" RowLayout {
padding: 2 visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
checked: Viewer3DSettings.displayGizmo spacing: 2
onClicked: Viewer3DSettings.displayGizmo = !Viewer3DSettings.displayGizmo // 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 { // Image Frame control
text: "Origin" MaterialToolButton {
padding: 2 visible: syncViewpointCamera.enabled && Viewer3DSettings.showViewpointImageOverlay
checked: Viewer3DSettings.displayOrigin enabled: Viewer3DSettings.syncViewpointCamera
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin text: MaterialIcons.crop_free
ToolTip.text: "Frame Overlay"
checked: Viewer3DSettings.viewpointImageFrame
onClicked: Viewer3DSettings.viewpointImageFrame = !Viewer3DSettings.viewpointImageFrame
} }
} }
} }
@ -114,7 +169,6 @@ FloatingPane {
implicitHeight: parent.height implicitHeight: parent.height
checkable: true checkable: true
checked: true checked: true
padding: 0
} }
ListView { 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 // Remove media from library button
MaterialToolButton { MaterialToolButton {
id: removeButton id: removeButton
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
visible: !loading visible: hovered || mouseArea.containsMouse && !loading
text: MaterialIcons.clear text: MaterialIcons.clear
font.pointSize: 10 font.pointSize: 10
ToolTip.text: "Remove" ToolTip.text: "Remove"

View file

@ -6,7 +6,7 @@ import QtQml.Models 2.2
import QtQuick.Scene3D 2.0 import QtQuick.Scene3D 2.0
import Qt3D.Core 2.1 import Qt3D.Core 2.1
import Qt3D.Render 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 Qt3D.Input 2.1 as Qt3DInput // to avoid clash with Controls2 Action
import MaterialIcons 2.2 import MaterialIcons 2.2
@ -25,6 +25,9 @@ FocusScope {
readonly property vector3d defaultCamUpVector: Qt.vector3d(0.0, 1.0, 0.0) 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 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 // functions
function resetCameraPosition() { function resetCameraPosition() {
mainCamera.position = defaultCamPosition; mainCamera.position = defaultCamPosition;
@ -88,6 +91,7 @@ FocusScope {
Camera { Camera {
id: mainCamera id: mainCamera
projectionType: CameraLens.PerspectiveProjection projectionType: CameraLens.PerspectiveProjection
enabled: cameraSelector.camera == mainCamera
fieldOfView: 45 fieldOfView: 45
nearPlane : 0.01 nearPlane : 0.01
farPlane : 10000.0 farPlane : 10000.0
@ -113,10 +117,17 @@ FocusScope {
} }
} }
ViewpointCamera {
id: viewpointCamera
enabled: cameraSelector.camera === camera
viewpoint: root.viewpoint
camera.aspectRatio: width/height
}
TrackballGizmo { TrackballGizmo {
beamRadius: 4.0/root.height beamRadius: 4.0/root.height
alpha: cameraController.moving ? 1.0 : 0.7 alpha: cameraController.moving ? 1.0 : 0.7
enabled: Viewer3DSettings.displayGizmo enabled: Viewer3DSettings.displayGizmo && cameraSelector.camera == mainCamera
xColor: Colors.red xColor: Colors.red
yColor: Colors.green yColor: Colors.green
zColor: Colors.blue zColor: Colors.blue
@ -129,6 +140,8 @@ FocusScope {
DefaultCameraController { DefaultCameraController {
id: cameraController id: cameraController
enabled: cameraSelector.camera == mainCamera
windowSize { windowSize {
width: root.width width: root.width
height: root.height height: root.height
@ -178,7 +191,7 @@ FocusScope {
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0) normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
CameraSelector { CameraSelector {
id: cameraSelector id: cameraSelector
camera: mainCamera camera: doSyncViewpointCamera ? viewpointCamera.camera : mainCamera
FrustumCulling { FrustumCulling {
ClearBuffers { ClearBuffers {
clearColor: "transparent" 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 // Media loading overlay
// (Scene3D is frozen while a media is being loaded) // (Scene3D is frozen while a media is being loaded)
Rectangle { Rectangle {

View file

@ -24,7 +24,7 @@ Item {
// Available render modes // Available render modes
readonly property var renderModes: [ // Can't use ListModel because of MaterialIcons expressions readonly property var renderModes: [ // Can't use ListModel because of MaterialIcons expressions
{"name": "Solid", "icon": MaterialIcons.crop_din }, {"name": "Solid", "icon": MaterialIcons.crop_din },
{"name": "Wireframe", "icon": MaterialIcons.grid_on }, {"name": "Wireframe", "icon": MaterialIcons.details },
{"name": "Textured", "icon": MaterialIcons.texture }, {"name": "Textured", "icon": MaterialIcons.texture },
] ]
// Current render mode // Current render mode
@ -39,4 +39,11 @@ Item {
property bool displayGrid: true property bool displayGrid: true
property bool displayGizmo: true property bool displayGizmo: true
property bool displayOrigin: false 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
}
}
]
}
}