forked from qt-creator/qt-creator
Active scene change involves deleting and recreating the View3D instance of the 3D editor that imports the actual shown scene. In Qt 5 it was necessary to mark the deleted View3D instance as having no content, so it wouldn't receive any more updates. This hack no longer appears to be necessary and causes this crash instead, so it was removed. Fixes: QDS-4786 Change-Id: I848ac78dc2f951ba7e1bf4d3d6aa4244b1443309 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
883 lines
34 KiB
QML
883 lines
34 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 6.0
|
|
import QtQuick3D 6.0
|
|
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 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);
|
|
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;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function updateActiveScene()
|
|
{
|
|
if (editView) {
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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 ("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, "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;});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
_generalHelper.registerGizmoTarget(obj);
|
|
return;
|
|
}
|
|
|
|
// No free gizmos available, create a new one
|
|
var gizmoComponent = Qt.createComponent("LightIconGizmo.qml");
|
|
if (gizmoComponent.status === Component.Ready) {
|
|
_generalHelper.registerGizmoTarget(obj);
|
|
var gizmo = gizmoComponent.createObject(overlayView,
|
|
{"view3D": overlayView, "targetNode": obj,
|
|
"selectedNodes": selectedNodes, "scene": scene,
|
|
"activeScene": activeScene,
|
|
"locked": _generalHelper.isLocked(obj),
|
|
"hidden": _generalHelper.isHidden(obj)});
|
|
lightIconGizmos[lightIconGizmos.length] = gizmo;
|
|
gizmo.clicked.connect(handleObjectClicked);
|
|
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
|
|
gizmo.activeScene = Qt.binding(function() {return activeScene;});
|
|
}
|
|
}
|
|
|
|
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);
|
|
_generalHelper.registerGizmoTarget(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) {
|
|
_generalHelper.registerGizmoTarget(obj);
|
|
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)});
|
|
|
|
cameraGizmos[cameraGizmos.length] = gizmo;
|
|
gizmo.clicked.connect(handleObjectClicked);
|
|
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
|
|
gizmo.activeScene = Qt.binding(function() {return activeScene;});
|
|
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;
|
|
_generalHelper.unregisterGizmoTarget(obj);
|
|
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;
|
|
_generalHelper.unregisterGizmoTarget(obj);
|
|
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) {
|
|
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
|
|
}
|
|
}
|
|
}
|