Meshroom/meshroom/ui/qml/Viewer3D/Viewer3D.qml
2024-11-26 15:30:09 +01:00

345 lines
12 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Scene3D 2.6
import Qt3D.Core 2.6
import Qt3D.Render 2.6
import Qt3D.Extras 2.15
import Qt3D.Input 2.6 as Qt3DInput // to avoid clash with Controls2 Action
import Controls 1.0
import MaterialIcons 2.2
import Utils 1.0
FocusScope {
id: root
property int renderMode: 2
readonly property alias library: mediaLibrary
readonly property alias mainCamera: mainCamera
readonly property vector3d defaultCamPosition: Qt.vector3d(12.0, 10.0, -12.0)
readonly property vector3d defaultCamUpVector: Qt.vector3d(-0.358979, 0.861550, 0.358979) // should be accurate, consistent with camera view center
readonly property vector3d defaultCamViewCenter: Qt.vector3d(0.0, 0.0, 0.0)
readonly property var viewpoint: _reconstruction ? _reconstruction.selectedViewpoint : null
readonly property bool doSyncViewpointCamera: Viewer3DSettings.syncViewpointCamera && (viewpoint && viewpoint.isReconstructed)
// Functions
function resetCameraPosition() {
mainCamera.position = defaultCamPosition
mainCamera.upVector = defaultCamUpVector
mainCamera.viewCenter = defaultCamViewCenter
}
function load(filepath, label = undefined) {
mediaLibrary.load(filepath, label)
}
/// View 'attribute' in the 3D Viewer. Media will be loaded if needed.
/// Returns whether the attribute can be visualized (matching type and extension).
function view(attribute) {
if (attribute.desc.type === "File"
&& Viewer3DSettings.supportedExtensions.indexOf(Filepath.extension(attribute.value)) > - 1) {
mediaLibrary.view(attribute)
return true
}
return false
}
/// Solo (i.e display only) the given attribute.
function solo(attribute) {
mediaLibrary.solo(mediaLibrary.find(attribute))
}
function clear() {
mediaLibrary.clear()
}
SystemPalette { id: activePalette }
Scene3D {
id: scene3D
anchors.fill: parent
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio // vs. UserAspectRatio
hoverEnabled: true // If true, will trigger positionChanged events in attached MouseHandler
aspects: ["logic", "input"]
focus: true
// We cannot use directly an ExifOrientedViewer since this component is not a Loader
// so we redefine the transform using the ExifOrientation utility functions
property var orientationTag: (doSyncViewpointCamera && root.viewpoint) ? root.viewpoint.orientation.toString() : "1"
transform: [
Rotation {
angle: ExifOrientation.rotation(scene3D.orientationTag)
origin.x: scene3D.width * 0.5
origin.y: scene3D.height * 0.5
},
Scale {
xScale: ExifOrientation.xscale(scene3D.orientationTag)
origin.x: scene3D.width * 0.5
origin.y: scene3D.height * 0.5
}
]
Keys.onPressed: function(event) {
if (event.key === Qt.Key_F) {
resetCameraPosition()
} else if (Qt.Key_1 <= event.key && event.key < Qt.Key_1 + Viewer3DSettings.renderModes.length) {
Viewer3DSettings.renderMode = event.key - Qt.Key_1
} else {
event.accepted = false
}
}
Entity {
id: rootEntity
Camera {
id: mainCamera
projectionType: CameraLens.PerspectiveProjection
enabled: cameraSelector.camera == mainCamera
fieldOfView: 45
nearPlane : 0.01
farPlane : 10000.0
position: defaultCamPosition
upVector: defaultCamUpVector
viewCenter: defaultCamViewCenter
aspectRatio: width/height
}
ViewpointCamera {
id: viewpointCamera
enabled: cameraSelector.camera === camera
viewpoint: root.viewpoint
camera.aspectRatio: width/height
}
Entity {
components: [
DirectionalLight{
color: "white"
worldDirection: Transformations3DHelper.getRotatedCameraViewVector(cameraSelector.camera.viewVector, cameraSelector.camera.upVector, directionalLightPane.lightPitchValue, directionalLightPane.lightYawValue).normalized()
}
]
}
TrackballGizmo {
beamRadius: 4.0/root.height
alpha: cameraController.moving ? 1.0 : 0.7
enabled: Viewer3DSettings.displayGizmo && cameraSelector.camera == mainCamera
xColor: Colors.red
yColor: Colors.green
zColor: Colors.blue
centerColor: Colors.sysPalette.highlight
transform: Transform {
translation: mainCamera.viewCenter
scale: 0.15 * mainCamera.viewCenter.minus(mainCamera.position).length()
}
}
DefaultCameraController {
id: cameraController
enabled: cameraSelector.camera == mainCamera
windowSize {
width: root.width
height: root.height
}
rotationSpeed: 16
trackballSize: 0.9
camera: mainCamera
focus: scene3D.activeFocus
onMousePressed: function(mouse) {
scene3D.forceActiveFocus()
}
onMouseReleased: function(mouse, moved) {
if (moving)
return
if (!moved && mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
}
components: [
RenderSettings {
pickingSettings.pickMethod: PickingSettings.PrimitivePicking // Enables point/edge/triangle picking
pickingSettings.pickResultMode: PickingSettings.NearestPick
renderPolicy: RenderSettings.Always
activeFrameGraph: RenderSurfaceSelector {
// Use the whole viewport
Viewport {
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
CameraSelector {
id: cameraSelector
camera: doSyncViewpointCamera ? viewpointCamera.camera : mainCamera
FrustumCulling {
ClearBuffers {
clearColor: "transparent"
buffers : ClearBuffers.ColorDepthBuffer
RenderStateSet {
renderStates: [
DepthTest { depthFunction: DepthTest.Less }
]
}
}
LayerFilter {
filterMode: LayerFilter.DiscardAnyMatchingLayers
layers: Layer {id: drawOnFront}
}
LayerFilter {
filterMode: LayerFilter.AcceptAnyMatchingLayers
layers: [drawOnFront]
RenderStateSet {
renderStates: DepthTest { depthFunction: DepthTest.GreaterOrEqual }
}
}
}
}
}
}
},
Qt3DInput.InputSettings { }
]
MediaLibrary {
id: mediaLibrary
renderMode: Viewer3DSettings.renderMode
// Picking to set focus point (camera view center)
// Only activate it when the 'Control' key is pressed
pickingEnabled: cameraController.pickingActive
camera: cameraSelector.camera
// Used for TransformGizmo in BoundingBox
sceneCameraController: cameraController
frontLayerComponent: drawOnFront
window: root
components: [
Transform {
id: transform
}
]
onClicked: function(pick) {
if (pick.button === Qt.LeftButton) {
mainCamera.viewCenter = pick.worldIntersection
}
}
}
Locator3D { enabled: Viewer3DSettings.displayOrigin }
Grid3D { enabled: Viewer3DSettings.displayGrid }
}
}
// 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.pixelAspectRatio / 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 {
anchors.fill: parent
visible: mediaLibrary.loading
color: Qt.darker(Colors.sysPalette.mid, 1.2)
opacity: 0.6
BusyIndicator {
anchors.centerIn: parent
running: parent.visible
}
}
FloatingPane {
visible: Viewer3DSettings.renderMode == 3
anchors.bottom: renderModesPanel.top
GridLayout {
columns: 2
rowSpacing: 0
RadioButton {
text: "SHL File"
autoExclusive: true
checked: true
}
TextField {
text: Viewer3DSettings.shlFile
selectByMouse: true
Layout.minimumWidth: 300
onEditingFinished: Viewer3DSettings.shlFile = text
}
RadioButton {
Layout.columnSpan: 2
autoExclusive: true
text: "Normals"
onCheckedChanged: Viewer3DSettings.displayNormals = checked
}
}
}
// Rendering modes
FloatingPane {
id: renderModesPanel
anchors.bottom: parent.bottom
padding: 4
Row {
Repeater {
model: Viewer3DSettings.renderModes
delegate: MaterialToolButton {
text: modelData["icon"]
ToolTip.text: modelData["name"] + " (" + (index+1) + ")"
font.pointSize: 11
onClicked: Viewer3DSettings.renderMode = index
checked: Viewer3DSettings.renderMode === index
checkable: !checked // Hack to disabled check on toggle
}
}
}
}
// Directional light controller
DirectionalLightPane {
id: directionalLightPane
anchors {
bottom: parent.bottom
right: parent.right
margins: 2
}
visible: Viewer3DSettings.displayLightController
}
// Menu
Menu {
id: contextMenu
MenuItem {
text: "Fit All"
onTriggered: mainCamera.viewAll()
}
MenuItem {
text: "Reset View"
onTriggered: resetCameraPosition()
}
}
}