forked from qt-creator/qt-creator
If a model defined inside the View3D component is picked on 3D editor, the parent View3D is selected instead as there is no instance for the model itself. This is similar to how Node based component picking works. Fixes: QDS-6934 Change-Id: I4f273972da8cb1c55f03cab323dd9804a5d10def Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
943 lines
37 KiB
QML
943 lines
37 KiB
QML
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
import QtQuick 2.12
|
|
import QtQuick3D 1.15
|
|
import MouseArea3D 1.0
|
|
|
|
Item {
|
|
id: viewRoot
|
|
width: 1024
|
|
height: 768
|
|
visible: true
|
|
|
|
property Node activeScene: null
|
|
property View3D editView: null
|
|
property string sceneId
|
|
|
|
property bool showEditLight: false
|
|
property bool showGrid: true
|
|
property bool showSelectionBox: true
|
|
property bool showIconGizmo: true
|
|
property bool showCameraFrustum: false
|
|
property bool usePerspective: true
|
|
property bool globalOrientation: false
|
|
property alias contentItem: contentItem
|
|
|
|
enum SelectionMode { Item, Group }
|
|
enum TransformMode { Move, Rotate, Scale }
|
|
|
|
property int selectionMode: EditView3D.SelectionMode.Item
|
|
property int transformMode: EditView3D.TransformMode.Move
|
|
|
|
property Node selectedNode: null // This is multiSelectionNode in multi-selection case
|
|
property var selectedNodes: [] // All selected nodes
|
|
|
|
property var lightIconGizmos: []
|
|
property var cameraGizmos: []
|
|
property var selectionBoxes: []
|
|
property rect viewPortRect: Qt.rect(0, 0, 1000, 1000)
|
|
|
|
property bool shuttingDown: false
|
|
|
|
property real fps: 0
|
|
|
|
signal selectionChanged(var selectedNodes)
|
|
signal commitObjectProperty(var objects, var propNames)
|
|
signal changeObjectProperty(var objects, var propNames)
|
|
signal notifyActiveSceneChange()
|
|
|
|
onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective)
|
|
onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight)
|
|
onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation)
|
|
onShowGridChanged: _generalHelper.storeToolState(sceneId, "showGrid", showGrid);
|
|
onShowSelectionBoxChanged: _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox);
|
|
onShowIconGizmoChanged: _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo);
|
|
onShowCameraFrustumChanged: _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum);
|
|
onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode);
|
|
onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode);
|
|
|
|
onActiveSceneChanged: updateActiveScene()
|
|
|
|
function aboutToShutDown()
|
|
{
|
|
shuttingDown = true;
|
|
}
|
|
|
|
function createEditView()
|
|
{
|
|
var component = Qt.createComponent("SceneView3D.qml");
|
|
if (component.status === Component.Ready) {
|
|
editView = component.createObject(viewRect,
|
|
{"usePerspective": usePerspective,
|
|
"showSceneLight": showEditLight,
|
|
"showGrid": showGrid,
|
|
"importScene": activeScene,
|
|
"cameraZoomFactor": cameraControl._zoomFactor,
|
|
"z": 1});
|
|
editView.usePerspective = Qt.binding(function() {return usePerspective;});
|
|
editView.showSceneLight = Qt.binding(function() {return showEditLight;});
|
|
editView.showGrid = Qt.binding(function() {return showGrid;});
|
|
editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;});
|
|
|
|
selectionBoxes.length = 0;
|
|
cameraControl.forceActiveFocus();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function updateActiveScene()
|
|
{
|
|
if (editView) {
|
|
// Destroy is async, so make sure we don't get any more updates for the old editView
|
|
_generalHelper.enableItemUpdate(editView, false);
|
|
editView.visible = false;
|
|
editView.destroy();
|
|
}
|
|
|
|
// importScene cannot be updated after initial set, so we need to reconstruct entire View3D
|
|
if (createEditView()) {
|
|
if (activeScene) {
|
|
var toolStates = _generalHelper.getToolStates(sceneId);
|
|
if (Object.keys(toolStates).length > 0) {
|
|
updateToolStates(toolStates, true);
|
|
} else {
|
|
// Don't inherit the edit light state from the previous scene, but rather
|
|
// turn the edit light on for scenes that do not have any scene
|
|
// lights, and turn it off for scenes that have.
|
|
var hasSceneLight = false;
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (lightIconGizmos[i].scene === activeScene) {
|
|
hasSceneLight = true;
|
|
break;
|
|
}
|
|
}
|
|
showEditLight = !hasSceneLight;
|
|
storeCurrentToolStates();
|
|
}
|
|
} else {
|
|
// When active scene is deleted, this function gets called by object deletion
|
|
// handlers without going through setActiveScene, so make sure sceneId is cleared.
|
|
// This is skipped during application shutdown, as calling QQuickText::setText()
|
|
// during application shutdown can crash the application.
|
|
if (!shuttingDown) {
|
|
sceneId = "";
|
|
storeCurrentToolStates();
|
|
}
|
|
}
|
|
|
|
notifyActiveSceneChange();
|
|
}
|
|
}
|
|
|
|
function setActiveScene(newScene, newSceneId)
|
|
{
|
|
var needExplicitUpdate = !activeScene && !newScene;
|
|
|
|
sceneId = newSceneId;
|
|
activeScene = newScene;
|
|
|
|
if (needExplicitUpdate)
|
|
updateActiveScene();
|
|
}
|
|
|
|
// Disables edit view update if scene doesn't match current activeScene.
|
|
// If it matches, updates are enabled.
|
|
function enableEditViewUpdate(scene)
|
|
{
|
|
if (editView)
|
|
_generalHelper.enableItemUpdate(editView, (scene && scene === activeScene));
|
|
}
|
|
|
|
function handleActiveSceneIdChange(newId)
|
|
{
|
|
if (sceneId !== newId) {
|
|
sceneId = newId;
|
|
storeCurrentToolStates();
|
|
}
|
|
}
|
|
|
|
function fitToView()
|
|
{
|
|
if (editView) {
|
|
var boxModels = [];
|
|
if (selectedNodes.length > 1) {
|
|
for (var i = 0; i < selectedNodes.length; ++i) {
|
|
if (selectionBoxes.length > i)
|
|
boxModels.push(selectionBoxes[i].model)
|
|
}
|
|
} else if (selectedNodes.length > 0 && selectionBoxes.length > 0) {
|
|
boxModels.push(selectionBoxes[0].model);
|
|
}
|
|
cameraControl.focusObject(boxModels, editView.camera.eulerRotation, true, false);
|
|
}
|
|
}
|
|
|
|
function alignCamerasToView()
|
|
{
|
|
if (editView) {
|
|
cameraControl.alignCameras(selectedNodes);
|
|
var propertyNames = ["position", "eulerRotation"];
|
|
viewRoot.changeObjectProperty(selectedNodes, propertyNames);
|
|
viewRoot.commitObjectProperty(selectedNodes, propertyNames);
|
|
}
|
|
}
|
|
|
|
function alignViewToCamera()
|
|
{
|
|
if (editView)
|
|
cameraControl.alignView(selectedNodes);
|
|
}
|
|
|
|
// If resetToDefault is true, tool states not specifically set to anything will be reset to
|
|
// their default state.
|
|
function updateToolStates(toolStates, resetToDefault)
|
|
{
|
|
if ("showEditLight" in toolStates)
|
|
showEditLight = toolStates.showEditLight;
|
|
else if (resetToDefault)
|
|
showEditLight = false;
|
|
|
|
if ("showGrid" in toolStates)
|
|
showGrid = toolStates.showGrid;
|
|
else if (resetToDefault)
|
|
showGrid = true;
|
|
|
|
if ("showSelectionBox" in toolStates)
|
|
showSelectionBox = toolStates.showSelectionBox;
|
|
else if (resetToDefault)
|
|
showSelectionBox = true;
|
|
|
|
if ("showIconGizmo" in toolStates)
|
|
showIconGizmo = toolStates.showIconGizmo;
|
|
else if (resetToDefault)
|
|
showIconGizmo = true;
|
|
|
|
if ("showCameraFrustum" in toolStates)
|
|
showCameraFrustum = toolStates.showCameraFrustum;
|
|
else if (resetToDefault)
|
|
showCameraFrustum = false;
|
|
|
|
if ("usePerspective" in toolStates)
|
|
usePerspective = toolStates.usePerspective;
|
|
else if (resetToDefault)
|
|
usePerspective = true;
|
|
|
|
if ("globalOrientation" in toolStates)
|
|
globalOrientation = toolStates.globalOrientation;
|
|
else if (resetToDefault)
|
|
globalOrientation = false;
|
|
|
|
if ("selectionMode" in toolStates)
|
|
selectionMode = toolStates.selectionMode;
|
|
else if (resetToDefault)
|
|
selectionMode = EditView3D.SelectionMode.Item;
|
|
|
|
if ("transformMode" in toolStates)
|
|
transformMode = toolStates.transformMode;
|
|
else if (resetToDefault)
|
|
transformMode = EditView3D.TransformMode.Move;
|
|
|
|
if ("editCamState" in toolStates)
|
|
cameraControl.restoreCameraState(toolStates.editCamState);
|
|
else if (resetToDefault)
|
|
cameraControl.restoreDefaultState();
|
|
}
|
|
|
|
function storeCurrentToolStates()
|
|
{
|
|
_generalHelper.storeToolState(sceneId, "showEditLight", showEditLight)
|
|
_generalHelper.storeToolState(sceneId, "showGrid", showGrid)
|
|
_generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox)
|
|
_generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo)
|
|
_generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum)
|
|
_generalHelper.storeToolState(sceneId, "usePerspective", usePerspective)
|
|
_generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation)
|
|
_generalHelper.storeToolState(sceneId, "selectionMode", selectionMode);
|
|
_generalHelper.storeToolState(sceneId, "transformMode", transformMode);
|
|
|
|
cameraControl.storeCameraState(0);
|
|
}
|
|
|
|
function ensureSelectionBoxes(count)
|
|
{
|
|
var needMore = count - selectionBoxes.length
|
|
if (needMore > 0) {
|
|
var component = Qt.createComponent("SelectionBox.qml");
|
|
if (component.status === Component.Ready) {
|
|
for (var i = 0; i < needMore; ++i) {
|
|
var geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry");
|
|
var boxParent = null;
|
|
if (editView)
|
|
boxParent = editView.sceneHelpers;
|
|
var box = component.createObject(boxParent, {"view3D": editView,
|
|
"geometryName": geometryName});
|
|
selectionBoxes[selectionBoxes.length] = box;
|
|
box.view3D = Qt.binding(function() {return editView;});
|
|
box.visible = Qt.binding(function() {return showSelectionBox;});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function selectObjects(objects)
|
|
{
|
|
// Create selection boxes as necessary. One more box than is actually needed is created, so
|
|
// that we always have a previously created box to use for new selection.
|
|
// This fixes an occasional visual glitch when creating a new box.
|
|
ensureSelectionBoxes(objects.length + 1)
|
|
|
|
var i;
|
|
for (i = 0; i < objects.length; ++i)
|
|
selectionBoxes[i].targetNode = objects[i];
|
|
for (i = objects.length; i < selectionBoxes.length; ++i)
|
|
selectionBoxes[i].targetNode = null;
|
|
|
|
selectedNodes = objects;
|
|
if (objects.length === 0) {
|
|
selectedNode = null;
|
|
} else if (objects.length > 1) {
|
|
selectedNode = multiSelectionNode;
|
|
_generalHelper.setMultiSelectionTargets(multiSelectionNode, objects);
|
|
} else {
|
|
selectedNode = objects[0];
|
|
}
|
|
}
|
|
|
|
function handleObjectClicked(object, multi)
|
|
{
|
|
if (object instanceof View3D) {
|
|
// View3D can be the resolved pick target in case the 3D editor is showing content
|
|
// of a component that has View3D as root. In that case locking is resolved on C++ side
|
|
// and we ignore multiselection.
|
|
selectObjects([]);
|
|
selectionChanged([object]);
|
|
return;
|
|
}
|
|
|
|
var clickedObject;
|
|
|
|
// Click on locked object is treated same as click on empty space
|
|
if (!_generalHelper.isLocked(object))
|
|
clickedObject = object;
|
|
|
|
if (selectionMode === EditView3D.SelectionMode.Group) {
|
|
while (clickedObject && clickedObject !== activeScene
|
|
&& (activeScene instanceof Model || clickedObject.parent !== activeScene)) {
|
|
clickedObject = clickedObject.parent;
|
|
}
|
|
}
|
|
// Object selection logic:
|
|
// Regular click: Clear any multiselection, single-selects the clicked object
|
|
// Ctrl-click: No objects selected: Act as single select
|
|
// One or more objects selected: Multiselect
|
|
// Null object always clears entire selection
|
|
var newSelection = [];
|
|
if (clickedObject) {
|
|
if (multi && selectedNodes.length > 0) {
|
|
var deselect = false;
|
|
for (var i = 0; i < selectedNodes.length; ++i) {
|
|
// Multiselecting already selected object clears that object from selection
|
|
if (selectedNodes[i] !== clickedObject)
|
|
newSelection[newSelection.length] = selectedNodes[i];
|
|
else
|
|
deselect = true;
|
|
}
|
|
if (!deselect)
|
|
newSelection[newSelection.length] = clickedObject;
|
|
} else {
|
|
newSelection[0] = clickedObject;
|
|
}
|
|
}
|
|
selectObjects(newSelection);
|
|
selectionChanged(newSelection);
|
|
}
|
|
|
|
function addLightGizmo(scene, obj)
|
|
{
|
|
// Insert into first available gizmo if we don't already have gizmo for this object
|
|
var slotFound = -1;
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (!lightIconGizmos[i].targetNode) {
|
|
slotFound = i;
|
|
} else if (lightIconGizmos[i].targetNode === obj) {
|
|
lightIconGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (slotFound !== -1) {
|
|
lightIconGizmos[slotFound].scene = scene;
|
|
lightIconGizmos[slotFound].targetNode = obj;
|
|
lightIconGizmos[slotFound].locked = _generalHelper.isLocked(obj);
|
|
lightIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj);
|
|
return;
|
|
}
|
|
|
|
// No free gizmos available, create a new one
|
|
var gizmoComponent = Qt.createComponent("LightIconGizmo.qml");
|
|
if (gizmoComponent.status === Component.Ready) {
|
|
var gizmo = gizmoComponent.createObject(overlayView,
|
|
{"view3D": overlayView, "targetNode": obj,
|
|
"selectedNodes": selectedNodes, "scene": scene,
|
|
"activeScene": activeScene,
|
|
"locked": _generalHelper.isLocked(obj),
|
|
"hidden": _generalHelper.isHidden(obj),
|
|
"globalShow": showIconGizmo});
|
|
lightIconGizmos[lightIconGizmos.length] = gizmo;
|
|
gizmo.clicked.connect(handleObjectClicked);
|
|
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
|
|
gizmo.activeScene = Qt.binding(function() {return activeScene;});
|
|
gizmo.globalShow = Qt.binding(function() {return showIconGizmo;});
|
|
}
|
|
}
|
|
|
|
function addCameraGizmo(scene, obj)
|
|
{
|
|
// Insert into first available gizmo if we don't already have gizmo for this object
|
|
var slotFound = -1;
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (!cameraGizmos[i].targetNode) {
|
|
slotFound = i;
|
|
} else if (cameraGizmos[i].targetNode === obj) {
|
|
cameraGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (slotFound !== -1) {
|
|
cameraGizmos[slotFound].scene = scene;
|
|
cameraGizmos[slotFound].targetNode = obj;
|
|
cameraGizmos[slotFound].locked = _generalHelper.isLocked(obj);
|
|
cameraGizmos[slotFound].hidden = _generalHelper.isHidden(obj);
|
|
return;
|
|
}
|
|
|
|
// No free gizmos available, create a new one
|
|
var gizmoComponent = Qt.createComponent("CameraGizmo.qml");
|
|
var frustumComponent = Qt.createComponent("CameraFrustum.qml");
|
|
if (gizmoComponent.status === Component.Ready && frustumComponent.status === Component.Ready) {
|
|
var geometryName = _generalHelper.generateUniqueName("CameraGeometry");
|
|
var frustum = frustumComponent.createObject(
|
|
overlayScene,
|
|
{"geometryName": geometryName, "viewPortRect": viewPortRect});
|
|
var gizmo = gizmoComponent.createObject(
|
|
overlayView,
|
|
{"view3D": overlayView, "targetNode": obj,
|
|
"selectedNodes": selectedNodes, "scene": scene, "activeScene": activeScene,
|
|
"locked": _generalHelper.isLocked(obj), "hidden": _generalHelper.isHidden(obj),
|
|
"globalShow": showIconGizmo, "globalShowFrustum": showCameraFrustum});
|
|
|
|
cameraGizmos[cameraGizmos.length] = gizmo;
|
|
gizmo.clicked.connect(handleObjectClicked);
|
|
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
|
|
gizmo.activeScene = Qt.binding(function() {return activeScene;});
|
|
gizmo.globalShow = Qt.binding(function() {return showIconGizmo;});
|
|
gizmo.globalShowFrustum = Qt.binding(function() {return showCameraFrustum;});
|
|
frustum.viewPortRect = Qt.binding(function() {return viewPortRect;});
|
|
gizmo.connectFrustum(frustum);
|
|
}
|
|
}
|
|
|
|
function releaseLightGizmo(obj)
|
|
{
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (lightIconGizmos[i].targetNode === obj) {
|
|
lightIconGizmos[i].scene = null;
|
|
lightIconGizmos[i].targetNode = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function releaseCameraGizmo(obj)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === obj) {
|
|
cameraGizmos[i].scene = null;
|
|
cameraGizmos[i].targetNode = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateLightGizmoScene(scene, obj)
|
|
{
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (lightIconGizmos[i].targetNode === obj) {
|
|
lightIconGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateCameraGizmoScene(scene, obj)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === obj) {
|
|
cameraGizmos[i].scene = scene;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
createEditView();
|
|
selectObjects([]);
|
|
// Work-around the fact that the projection matrix for the camera is not calculated until
|
|
// the first frame is rendered, so any initial calls to mapFrom3DScene() will fail.
|
|
_generalHelper.requestOverlayUpdate();
|
|
}
|
|
|
|
onWidthChanged: _generalHelper.requestOverlayUpdate()
|
|
onHeightChanged: _generalHelper.requestOverlayUpdate()
|
|
|
|
Connections {
|
|
target: _generalHelper
|
|
function onLockedStateChanged(node)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === node) {
|
|
cameraGizmos[i].locked = _generalHelper.isLocked(node);
|
|
return;
|
|
}
|
|
}
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (lightIconGizmos[i].targetNode === node) {
|
|
lightIconGizmos[i].locked = _generalHelper.isLocked(node);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
function onHiddenStateChanged(node)
|
|
{
|
|
for (var i = 0; i < cameraGizmos.length; ++i) {
|
|
if (cameraGizmos[i].targetNode === node) {
|
|
cameraGizmos[i].hidden = _generalHelper.isHidden(node);
|
|
return;
|
|
}
|
|
}
|
|
for (var i = 0; i < lightIconGizmos.length; ++i) {
|
|
if (lightIconGizmos[i].targetNode === node) {
|
|
lightIconGizmos[i].hidden = _generalHelper.isHidden(node);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Node {
|
|
id: overlayScene
|
|
|
|
PerspectiveCamera {
|
|
id: overlayPerspectiveCamera
|
|
clipFar: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipFar : 1000
|
|
clipNear: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipNear : 1
|
|
position: viewRoot.editView ? viewRoot.editView.perspectiveCamera.position : Qt.vector3d(0, 0, 0)
|
|
rotation: viewRoot.editView ? viewRoot.editView.perspectiveCamera.rotation : Qt.quaternion(1, 0, 0, 0)
|
|
}
|
|
|
|
OrthographicCamera {
|
|
id: overlayOrthoCamera
|
|
clipFar: viewRoot.editView ? viewRoot.editView.orthoCamera.clipFar : 1000
|
|
clipNear: viewRoot.editView ? viewRoot.editView.orthoCamera.clipNear : 1
|
|
position: viewRoot.editView ? viewRoot.editView.orthoCamera.position : Qt.vector3d(0, 0, 0)
|
|
rotation: viewRoot.editView ? viewRoot.editView.orthoCamera.rotation : Qt.quaternion(1, 0, 0, 0)
|
|
scale: viewRoot.editView ? viewRoot.editView.orthoCamera.scale : Qt.vector3d(0, 0, 0)
|
|
}
|
|
|
|
MouseArea3D {
|
|
id: gizmoDragHelper
|
|
view3D: overlayView
|
|
}
|
|
|
|
Node {
|
|
id: multiSelectionNode
|
|
objectName: "multiSelectionNode"
|
|
}
|
|
|
|
MoveGizmo {
|
|
id: moveGizmo
|
|
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
|
|
highlightOnHover: true
|
|
targetNode: viewRoot.selectedNode
|
|
globalOrientation: viewRoot.globalOrientation
|
|
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move
|
|
view3D: overlayView
|
|
dragHelper: gizmoDragHelper
|
|
property var propertyNames: ["position"]
|
|
|
|
onPositionCommit: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames);
|
|
else
|
|
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
onPositionMove: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames);
|
|
else
|
|
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
}
|
|
|
|
ScaleGizmo {
|
|
id: scaleGizmo
|
|
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
|
|
highlightOnHover: true
|
|
targetNode: viewRoot.selectedNode
|
|
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale
|
|
view3D: overlayView
|
|
dragHelper: gizmoDragHelper
|
|
property var propertyNames: ["scale"]
|
|
property var propertyNamesMulti: ["position", "scale"]
|
|
|
|
onScaleCommit: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
|
|
else
|
|
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
onScaleChange: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
|
|
else
|
|
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
}
|
|
|
|
RotateGizmo {
|
|
id: rotateGizmo
|
|
scale: autoScale.getScale(Qt.vector3d(7, 7, 7))
|
|
highlightOnHover: true
|
|
targetNode: viewRoot.selectedNode
|
|
globalOrientation: viewRoot.globalOrientation
|
|
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate
|
|
view3D: overlayView
|
|
dragHelper: gizmoDragHelper
|
|
property var propertyNames: ["eulerRotation"]
|
|
property var propertyNamesMulti: ["position", "eulerRotation"]
|
|
|
|
onRotateCommit: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
|
|
else
|
|
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
onRotateChange: {
|
|
if (targetNode == multiSelectionNode)
|
|
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
|
|
else
|
|
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
|
|
}
|
|
}
|
|
|
|
LightGizmo {
|
|
id: lightGizmo
|
|
targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null
|
|
view3D: overlayView
|
|
dragHelper: gizmoDragHelper
|
|
|
|
onPropertyValueCommit: (propName)=> {
|
|
viewRoot.commitObjectProperty([targetNode], [propName]);
|
|
}
|
|
onPropertyValueChange: (propName)=> {
|
|
viewRoot.changeObjectProperty([targetNode], [propName]);
|
|
}
|
|
}
|
|
|
|
AutoScaleHelper {
|
|
id: autoScale
|
|
view3D: overlayView
|
|
position: moveGizmo.scenePosition
|
|
}
|
|
|
|
AutoScaleHelper {
|
|
id: pivotAutoScale
|
|
view3D: overlayView
|
|
position: pivotLine.startPos
|
|
}
|
|
|
|
Line3D {
|
|
id: pivotLine
|
|
visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode
|
|
name: "3D Edit View Pivot Line"
|
|
color: "#ddd600"
|
|
|
|
startPos: viewRoot.selectedNode ? viewRoot.selectedNode.scenePosition
|
|
: Qt.vector3d(0, 0, 0)
|
|
Connections {
|
|
target: viewRoot
|
|
function onSelectedNodeChanged()
|
|
{
|
|
pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode);
|
|
}
|
|
}
|
|
Connections {
|
|
target: viewRoot.selectedNode
|
|
function onSceneTransformChanged()
|
|
{
|
|
pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode);
|
|
}
|
|
}
|
|
|
|
Model {
|
|
id: pivotCap
|
|
source: "#Sphere"
|
|
scale: pivotAutoScale.getScale(Qt.vector3d(0.03, 0.03, 0.03))
|
|
position: pivotLine.startPos
|
|
materials: [
|
|
DefaultMaterial {
|
|
id: lineMat
|
|
lighting: DefaultMaterial.NoLighting
|
|
cullMode: Material.NoCulling
|
|
diffuseColor: pivotLine.color
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: contentItem
|
|
anchors.fill: parent
|
|
|
|
Rectangle {
|
|
id: viewRect
|
|
anchors.fill: parent
|
|
|
|
gradient: Gradient {
|
|
GradientStop { position: 1.0; color: "#222222" }
|
|
GradientStop { position: 0.0; color: "#999999" }
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
hoverEnabled: false
|
|
|
|
property MouseArea3D freeDraggerArea
|
|
property point pressPoint
|
|
property bool initialMoveBlock: false
|
|
|
|
onPressed: (mouse)=> {
|
|
if (viewRoot.editView) {
|
|
var pickResult = viewRoot.editView.pick(mouse.x, mouse.y);
|
|
handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit),
|
|
mouse.modifiers & Qt.ControlModifier);
|
|
|
|
if (pickResult.objectHit && pickResult.objectHit instanceof Node) {
|
|
if (transformMode === EditView3D.TransformMode.Move)
|
|
freeDraggerArea = moveGizmo.freeDraggerArea;
|
|
else if (transformMode === EditView3D.TransformMode.Rotate)
|
|
freeDraggerArea = rotateGizmo.freeDraggerArea;
|
|
else if (transformMode === EditView3D.TransformMode.Scale)
|
|
freeDraggerArea = scaleGizmo.freeDraggerArea;
|
|
pressPoint.x = mouse.x;
|
|
pressPoint.y = mouse.y;
|
|
initialMoveBlock = true;
|
|
} else {
|
|
mouse.accepted = false;
|
|
}
|
|
}
|
|
}
|
|
onPositionChanged: (mouse)=> {
|
|
if (freeDraggerArea) {
|
|
if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) {
|
|
// Don't force press event at actual press, as that puts the gizmo
|
|
// in free-dragging state, which is bad UX if drag is not actually done
|
|
freeDraggerArea.forcePressEvent(pressPoint.x, pressPoint.y);
|
|
freeDraggerArea.forceMoveEvent(mouse.x, mouse.y);
|
|
initialMoveBlock = false;
|
|
} else {
|
|
freeDraggerArea.forceMoveEvent(mouse.x, mouse.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleRelease(mouse)
|
|
{
|
|
if (freeDraggerArea) {
|
|
if (initialMoveBlock)
|
|
freeDraggerArea.forceReleaseEvent(pressPoint.x, pressPoint.y);
|
|
else
|
|
freeDraggerArea.forceReleaseEvent(mouse.x, mouse.y);
|
|
freeDraggerArea = null;
|
|
}
|
|
}
|
|
|
|
onReleased: (mouse)=> {
|
|
handleRelease(mouse);
|
|
}
|
|
onCanceled: (mouse)=> {
|
|
handleRelease(mouse);
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
}
|
|
|
|
View3D {
|
|
id: overlayView
|
|
anchors.fill: parent
|
|
camera: viewRoot.usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera
|
|
importScene: overlayScene
|
|
z: 2
|
|
}
|
|
|
|
Overlay2D {
|
|
id: gizmoLabel
|
|
targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo
|
|
targetView: overlayView
|
|
visible: targetNode.dragging
|
|
z: 3
|
|
|
|
Rectangle {
|
|
color: "white"
|
|
x: -width / 2
|
|
y: -height - 8
|
|
width: gizmoLabelText.width + 4
|
|
height: gizmoLabelText.height + 4
|
|
border.width: 1
|
|
Text {
|
|
id: gizmoLabelText
|
|
text: {
|
|
// This is skipped during application shutdown, as calling QQuickText::setText()
|
|
// during application shutdown can crash the application.
|
|
if (shuttingDown)
|
|
return text;
|
|
var l = Qt.locale();
|
|
var targetProperty;
|
|
if (viewRoot.selectedNode) {
|
|
if (gizmoLabel.targetNode === moveGizmo)
|
|
targetProperty = viewRoot.selectedNode.position;
|
|
else
|
|
targetProperty = viewRoot.selectedNode.scale;
|
|
return qsTr("x:") + Number(targetProperty.x).toLocaleString(l, 'f', 1)
|
|
+ qsTr(" y:") + Number(targetProperty.y).toLocaleString(l, 'f', 1)
|
|
+ qsTr(" z:") + Number(targetProperty.z).toLocaleString(l, 'f', 1);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: rotateGizmoLabel
|
|
color: "white"
|
|
x: rotateGizmo.currentMousePos.x - (10 + width)
|
|
y: rotateGizmo.currentMousePos.y - (10 + height)
|
|
width: rotateGizmoLabelText.width + 4
|
|
height: rotateGizmoLabelText.height + 4
|
|
border.width: 1
|
|
visible: rotateGizmo.dragging
|
|
parent: rotateGizmo.view3D
|
|
z: 3
|
|
|
|
Text {
|
|
id: rotateGizmoLabelText
|
|
text: {
|
|
// This is skipped during application shutdown, as calling QQuickText::setText()
|
|
// during application shutdown can crash the application.
|
|
if (shuttingDown)
|
|
return text;
|
|
var l = Qt.locale();
|
|
if (rotateGizmo.targetNode) {
|
|
var degrees = rotateGizmo.currentAngle * (180 / Math.PI);
|
|
return Number(degrees).toLocaleString(l, 'f', 1);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: lightGizmoLabel
|
|
color: "white"
|
|
x: lightGizmo.currentMousePos.x - (10 + width)
|
|
y: lightGizmo.currentMousePos.y - (10 + height)
|
|
width: lightGizmoLabelText.width + 4
|
|
height: lightGizmoLabelText.height + 4
|
|
border.width: 1
|
|
visible: lightGizmo.dragging
|
|
parent: lightGizmo.view3D
|
|
z: 3
|
|
|
|
Text {
|
|
id: lightGizmoLabelText
|
|
text: lightGizmo.currentLabel
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
|
|
EditCameraController {
|
|
id: cameraControl
|
|
camera: viewRoot.editView ? viewRoot.editView.camera : null
|
|
anchors.fill: parent
|
|
view3d: viewRoot.editView
|
|
sceneId: viewRoot.sceneId
|
|
}
|
|
}
|
|
|
|
AxisHelper {
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
width: 100
|
|
height: width
|
|
editCameraCtrl: cameraControl
|
|
selectedNode: viewRoot.selectedNodes.length === 1 ? viewRoot.selectionBoxes[0].model
|
|
: viewRoot.selectedNode
|
|
}
|
|
|
|
Text {
|
|
id: sceneLabel
|
|
text: viewRoot.sceneId
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.margins: 4
|
|
font.pixelSize: 14
|
|
color: "white"
|
|
}
|
|
|
|
Text {
|
|
id: fpsLabel
|
|
text: viewRoot.fps
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.margins: 4
|
|
font.pixelSize: 12
|
|
color: "white"
|
|
visible: viewRoot.fps > 0
|
|
}
|
|
}
|
|
}
|