mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 10:17:27 +02:00
330 lines
11 KiB
QML
330 lines
11 KiB
QML
import QtQuick 2.15
|
|
import Utils 1.0
|
|
|
|
import AliceVision 1.0 as AliceVision
|
|
|
|
/**
|
|
* PanoramaViwer displays a list of Float Images
|
|
* Requires QtAliceVision plugin.
|
|
*/
|
|
|
|
AliceVision.PanoramaViewer {
|
|
id: root
|
|
|
|
width: 3000
|
|
height: 1500
|
|
|
|
visible: (status === Image.Ready)
|
|
|
|
// paintedWidth / paintedHeight / status for compatibility with standard Image
|
|
property int paintedWidth: sourceSize.width
|
|
property int paintedHeight: sourceSize.height
|
|
property var status: {
|
|
if (readyToLoad === Image.Ready) {
|
|
return Image.Ready
|
|
} else {
|
|
return Image.Null
|
|
}
|
|
}
|
|
|
|
property int readyToLoad: Image.Null
|
|
|
|
property int subdivisionsPano: 12
|
|
|
|
property bool isEditable: true
|
|
|
|
property bool isHighlightable: true
|
|
|
|
property bool displayGridPano: true
|
|
|
|
property int mouseMultiplier: 1
|
|
|
|
property bool cropFisheyePano: false
|
|
|
|
property int idSelected : -1
|
|
|
|
onIsHighlightableChanged: {
|
|
for (var i = 0; i < repeater.model; ++i) {
|
|
repeater.itemAt(i).item.onChangedHighlightState(isHighlightable)
|
|
}
|
|
}
|
|
|
|
property alias containsMouse: mouseAreaPano.containsMouse
|
|
|
|
property bool isRotating: false
|
|
property double lastX : 0
|
|
property double lastY: 0
|
|
|
|
property double xStart : 0
|
|
property double yStart : 0
|
|
|
|
property double previous_yaw: 0;
|
|
property double previous_pitch: 0;
|
|
property double previous_roll: 0;
|
|
|
|
property double yaw: 0;
|
|
property double pitch: 0;
|
|
property double roll: 0;
|
|
|
|
property var activeNode: _reconstruction.activeNodes.get('SfMTransform').node
|
|
|
|
// Yaw and Pitch in Degrees from SfMTransform node sliders
|
|
property double yawNode: activeNode ? activeNode.attribute("manualTransform.manualRotation.y").value : 0
|
|
property double pitchNode: activeNode ? activeNode.attribute("manualTransform.manualRotation.x").value : 0
|
|
property double rollNode: activeNode ? activeNode.attribute("manualTransform.manualRotation.z").value : 0
|
|
|
|
//Convert angle functions
|
|
function toDegrees(radians) {
|
|
return radians * (180 / Math.PI)
|
|
}
|
|
|
|
function toRadians(degrees) {
|
|
return degrees * (Math.PI / 180)
|
|
}
|
|
|
|
function fmod(a,b) { return Number((a - (Math.floor(a / b) * b)).toPrecision(8)) }
|
|
|
|
// Limit angle between -180 and 180
|
|
function limitAngle(angle) {
|
|
if (angle > 180) angle = -180.0 + (angle - 180.0)
|
|
if (angle < -180) angle = 180.0 - (Math.abs(angle) - 180)
|
|
return angle
|
|
}
|
|
|
|
function limitPitch(angle) {
|
|
return (angle > 180 || angle < -180) ? root.pitch : angle
|
|
}
|
|
|
|
onYawNodeChanged: {
|
|
root.yaw = yawNode
|
|
}
|
|
onPitchNodeChanged: {
|
|
root.pitch = pitchNode
|
|
}
|
|
onRollNodeChanged: {
|
|
root.roll = rollNode
|
|
}
|
|
|
|
Item {
|
|
id: containerPanorama
|
|
z: 10
|
|
Rectangle {
|
|
width: 3000
|
|
height: 1500
|
|
color: "transparent"
|
|
MouseArea {
|
|
id: mouseAreaPano
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
enabled: allImagesLoaded
|
|
cursorShape: {
|
|
if (isEditable)
|
|
isRotating ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
|
}
|
|
onPositionChanged: {
|
|
// Send Mouse Coordinates to Float Images Viewers
|
|
idSelected = -1
|
|
for (var i = 0; i < repeater.model && isHighlightable; ++i) {
|
|
var highlight = repeater.itemAt(i).item.getMouseCoordinates(mouse.x, mouse.y)
|
|
repeater.itemAt(i).z = highlight ? 2 : 0
|
|
if (highlight) {
|
|
idSelected = root.msfmData.viewsIds[i]
|
|
}
|
|
}
|
|
|
|
// Rotate Panorama
|
|
if (isRotating && isEditable) {
|
|
var nx = Math.min(width - 1, mouse.x)
|
|
var ny = Math.min(height - 1, mouse.y)
|
|
|
|
var xoffset = nx - lastX;
|
|
var yoffset = ny - lastY;
|
|
|
|
if (xoffset != 0 || yoffset !=0) {
|
|
var latitude_start = (yStart / height) * Math.PI - (Math.PI / 2);
|
|
var longitude_start = ((xStart / width) * 2 * Math.PI) - Math.PI;
|
|
var latitude_end = (ny / height) * Math.PI - ( Math.PI / 2);
|
|
var longitude_end = ((nx / width) * 2 * Math.PI) - Math.PI;
|
|
|
|
var start_pt = Qt.vector2d(latitude_start, longitude_start)
|
|
var end_pt = Qt.vector2d(latitude_end, longitude_end)
|
|
|
|
var previous_euler = Qt.vector3d(previous_yaw, previous_pitch, previous_roll)
|
|
var result
|
|
|
|
if (mouse.modifiers & Qt.ControlModifier) {
|
|
result = Transformations3DHelper.updatePanoramaInPlane(previous_euler, start_pt, end_pt)
|
|
root.pitch = result.x
|
|
root.yaw = result.y
|
|
root.roll = result.z
|
|
} else {
|
|
result = Transformations3DHelper.updatePanorama(previous_euler, start_pt, end_pt)
|
|
root.pitch = result.x
|
|
root.yaw = result.y
|
|
root.roll = result.z
|
|
}
|
|
}
|
|
|
|
_reconstruction.setAttribute(activeNode.attribute("manualTransform.manualRotation.x"), Math.round(root.pitch))
|
|
_reconstruction.setAttribute(activeNode.attribute("manualTransform.manualRotation.y"), Math.round(root.yaw))
|
|
_reconstruction.setAttribute(activeNode.attribute("manualTransform.manualRotation.z"), Math.round(root.roll))
|
|
}
|
|
}
|
|
|
|
onPressed:{
|
|
_reconstruction.beginModification("Panorama Manual Rotation")
|
|
isRotating = true
|
|
lastX = mouse.x
|
|
lastY = mouse.y
|
|
|
|
xStart = mouse.x
|
|
yStart = mouse.y
|
|
|
|
previous_yaw = yaw
|
|
previous_pitch = pitch
|
|
previous_roll = roll
|
|
}
|
|
|
|
onReleased: {
|
|
_reconstruction.endModification()
|
|
isRotating = false
|
|
lastX = 0
|
|
lastY = 0
|
|
|
|
// Select the image in the image gallery if clicked
|
|
if (xStart == mouse.x && yStart == mouse.y && idSelected != -1) {
|
|
_reconstruction.selectedViewId = idSelected
|
|
}
|
|
}
|
|
}
|
|
|
|
// Grid Panorama Viewer
|
|
Canvas {
|
|
id: gridPano
|
|
visible: displayGridPano
|
|
anchors.fill : parent
|
|
property int wgrid: 40
|
|
onPaint: {
|
|
var ctx = getContext("2d")
|
|
ctx.lineWidth = 1.0
|
|
ctx.shadowBlur = 0
|
|
ctx.strokeStyle = "grey"
|
|
var nrows = height / wgrid
|
|
for (var i = 0; i < nrows + 1; ++i) {
|
|
ctx.moveTo(0, wgrid * i)
|
|
ctx.lineTo(width, wgrid * i)
|
|
}
|
|
|
|
var ncols = width / wgrid
|
|
for (var j = 0; j < ncols + 1; ++j) {
|
|
ctx.moveTo(wgrid * j, 0)
|
|
ctx.lineTo(wgrid * j, height)
|
|
}
|
|
|
|
ctx.closePath()
|
|
ctx.stroke()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
property int imagesLoaded: 0
|
|
property bool allImagesLoaded: false
|
|
|
|
function loadRepeaterImages(index) {
|
|
if (index < repeater.model)
|
|
repeater.itemAt(index).loadItem()
|
|
else
|
|
allImagesLoaded = true
|
|
}
|
|
|
|
Item {
|
|
id: panoImages
|
|
width: root.width
|
|
height: root.height
|
|
|
|
Component {
|
|
id: imgPano
|
|
Loader {
|
|
id: floatOneLoader
|
|
active: root.readyToLoad
|
|
visible: (floatOneLoader.status === Loader.Ready)
|
|
z: 0
|
|
property bool imageLoaded: false
|
|
property bool loading: false
|
|
|
|
onImageLoadedChanged: {
|
|
imagesLoaded++
|
|
loadRepeaterImages(imagesLoaded)
|
|
}
|
|
|
|
function loadItem() {
|
|
if (!active)
|
|
return
|
|
|
|
if (loading) {
|
|
loadRepeaterImages(index + 1)
|
|
return
|
|
}
|
|
|
|
loading = true
|
|
|
|
var idViewItem = msfmData.viewsIds[index]
|
|
var sourceItem = Filepath.stringToUrl(msfmData.getUrlFromViewId(idViewItem))
|
|
|
|
setSource("FloatImage.qml", {
|
|
'surface.viewerType': AliceVision.Surface.EViewerType.PANORAMA,
|
|
'viewerTypeString': 'panorama',
|
|
'surface.subdivisions': Qt.binding(function() { return subdivisionsPano }),
|
|
'cropFisheye' : Qt.binding(function(){ return cropFisheyePano }),
|
|
'surface.pitch': Qt.binding(function() { return root.pitch }),
|
|
'surface.yaw': Qt.binding(function() { return root.yaw }),
|
|
'surface.roll': Qt.binding(function() { return root.roll }),
|
|
'idView': Qt.binding(function() { return idViewItem }),
|
|
'gamma': Qt.binding(function() { return hdrImageToolbar.gammaValue }),
|
|
'gain': Qt.binding(function() { return hdrImageToolbar.gainValue }),
|
|
'channelModeString': Qt.binding(function() { return hdrImageToolbar.channelModeValue }),
|
|
'downscaleLevel' : Qt.binding(function() { return downscale }),
|
|
'source': Qt.binding(function() { return sourceItem }),
|
|
'surface.msfmData': Qt.binding(function() { return root.msfmData }),
|
|
'canBeHovered': true,
|
|
'useSequence': false
|
|
})
|
|
imageLoaded = Qt.binding(function() { return repeater.itemAt(index).item.status === Image.Ready ? true : false })
|
|
}
|
|
|
|
}
|
|
}
|
|
Repeater {
|
|
id: repeater
|
|
model: 0
|
|
delegate: imgPano
|
|
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onDownscaleReady() {
|
|
root.imagesLoaded = 0
|
|
|
|
// Retrieve downscale value from C++
|
|
panoramaViewerToolbar.updateDownscaleValue(root.downscale)
|
|
|
|
//Changing the repeater model (number of elements)
|
|
panoImages.updateRepeater()
|
|
|
|
root.readyToLoad = Image.Ready
|
|
|
|
// Load images two by two
|
|
loadRepeaterImages(0)
|
|
loadRepeaterImages(1)
|
|
}
|
|
}
|
|
|
|
function updateRepeater() {
|
|
if (repeater.model !== root.msfmData.viewsIds.length) {
|
|
repeater.model = 0
|
|
}
|
|
repeater.model = root.msfmData.viewsIds.length
|
|
}
|
|
}
|
|
}
|