2
0
Эх сурвалжийг харах

Editor camera improved:
1. Camera now has a virtual look-at point. Camera can now rotate around the look-at point or as well as itself. No focus will be lost during camera operation (issue #2039). New object can place at this look-at point(view center).
2. Better camera close-look - adjust pos/zoom of camera and make view frustum fit the calculated bounding box of selected nodes and components.
3. Double click component in Hierarchy Window can also look close to the node.
4. Camera smooth interpolation - pos/rot/zoom smooth interpolating for close-look, top/front/side view switching.
5. Removed MouseWheelCameraPosition to make camera less confusing.
6. Key/Mouse changed - Added F key(Standard Hotkey, many popular tools use this key) and NumPad Period(Blender Hotkey) for camera close-look. Moving middle mouse button for orbiting around look-at and right button movement for orbiting around camera itself (For standard Hotkey).

SuperWangKai 8 жил өмнө
parent
commit
0e88ed738e

+ 0 - 4
bin/Data/Scripts/Editor.as

@@ -205,7 +205,6 @@ void LoadConfig()
         if (cameraElem.HasAttribute("fov")) viewFov = cameraElem.GetFloat("fov");
         if (cameraElem.HasAttribute("speed")) cameraBaseSpeed = cameraElem.GetFloat("speed");
         if (cameraElem.HasAttribute("limitrotation")) limitRotation = cameraElem.GetBool("limitrotation");
-        if (cameraElem.HasAttribute("mousewheelcameraposition")) mouseWheelCameraPosition = cameraElem.GetBool("mousewheelcameraposition");
         if (cameraElem.HasAttribute("viewportmode")) viewportMode = cameraElem.GetUInt("viewportmode");
         if (cameraElem.HasAttribute("mouseorbitmode")) mouseOrbitMode = cameraElem.GetInt("mouseorbitmode");
         if (cameraElem.HasAttribute("mmbpan")) mmbPanMode = cameraElem.GetBool("mmbpan");
@@ -217,7 +216,6 @@ void LoadConfig()
         if (objectElem.HasAttribute("cameraflymode")) cameraFlyMode = objectElem.GetBool("cameraflymode");
         if (objectElem.HasAttribute("hotkeymode")) hotKeyMode = objectElem.GetInt("hotkeymode");
         if (objectElem.HasAttribute("newnodemode")) newNodeMode = objectElem.GetInt("newnodemode");
-        if (objectElem.HasAttribute("newnodedistance")) newNodeDistance = objectElem.GetFloat("newnodedistance");
         if (objectElem.HasAttribute("movestep")) moveStep = objectElem.GetFloat("movestep");
         if (objectElem.HasAttribute("rotatestep")) rotateStep = objectElem.GetFloat("rotatestep");
         if (objectElem.HasAttribute("scalestep")) scaleStep = objectElem.GetFloat("scalestep");
@@ -359,7 +357,6 @@ void SaveConfig()
     cameraElem.SetFloat("fov", viewFov);
     cameraElem.SetFloat("speed", cameraBaseSpeed);
     cameraElem.SetBool("limitrotation", limitRotation);
-    cameraElem.SetBool("mousewheelcameraposition", mouseWheelCameraPosition);
     cameraElem.SetUInt("viewportmode", viewportMode);
     cameraElem.SetInt("mouseorbitmode", mouseOrbitMode);
     cameraElem.SetBool("mmbpan", mmbPanMode);
@@ -367,7 +364,6 @@ void SaveConfig()
     objectElem.SetBool("cameraflymode", cameraFlyMode);
     objectElem.SetInt("hotkeymode", hotKeyMode);
     objectElem.SetInt("newnodemode", newNodeMode);
-    objectElem.SetFloat("newnodedistance", newNodeDistance);
     objectElem.SetFloat("movestep", moveStep);
     objectElem.SetFloat("rotatestep", rotateStep);
     objectElem.SetFloat("scalestep", scaleStep);

+ 10 - 1
bin/Data/Scripts/Editor/EditorHierarchyWindow.as

@@ -820,7 +820,16 @@ void HandleHierarchyListDoubleClick(StringHash eventType, VariantMap& eventData)
     if (type == ITEM_NODE)
     {
         Node@ node = editorScene.GetNode(item.vars[NODE_ID_VAR].GetUInt());
-        LocateNode(node);
+        Array<Node@> nodes;
+        nodes.Push(node);
+        LocateNodes(nodes);
+    }
+    else if (type == ITEM_COMPONENT)
+    {
+        Component@ component = editorScene.GetComponent(item.vars[COMPONENT_ID_VAR].GetUInt());
+        Array<Component@> components;
+        components.Push(component);
+        LocateComponents(components);
     }
 
     bool isExpanded = hierarchyList.IsExpanded(hierarchyList.selection);

+ 3 - 1
bin/Data/Scripts/Editor/EditorScene.as

@@ -911,7 +911,9 @@ bool SceneSmartDuplicateNode()
 
 bool ViewCloser()
 {
-    return (viewCloser = true);
+    LocateNodesAndComponents(selectedNodes, selectedComponents);
+    
+    return true;
 }
 
 

+ 0 - 22
bin/Data/Scripts/Editor/EditorSettings.as

@@ -37,8 +37,6 @@ void UpdateEditorSettingsDialog()
     CheckBox@ limitRotationToggle = settingsDialog.GetChild("LimitRotationToggle", true);
     limitRotationToggle.checked = limitRotation;
 
-    CheckBox@ mouseWheelCameraPositionToggle = settingsDialog.GetChild("MouseWheelCameraPositionToggle", true);
-    mouseWheelCameraPositionToggle.checked = mouseWheelCameraPosition;
 
     DropDownList@ mouseOrbitEdit = settingsDialog.GetChild("MouseOrbitEdit", true);
     mouseOrbitEdit.selection = mouseOrbitMode;
@@ -52,9 +50,6 @@ void UpdateEditorSettingsDialog()
     DropDownList@ newNodeModeEdit = settingsDialog.GetChild("NewNodeModeEdit", true);
     newNodeModeEdit.selection = newNodeMode;
 
-    LineEdit@ distanceEdit = settingsDialog.GetChild("DistanceEdit", true);
-    distanceEdit.text = String(newNodeDistance);
-
     LineEdit@ moveStepEdit = settingsDialog.GetChild("MoveStepEdit", true);
     moveStepEdit.text = String(moveStep);
     CheckBox@ moveSnapToggle = settingsDialog.GetChild("MoveSnapToggle", true);
@@ -139,13 +134,10 @@ void UpdateEditorSettingsDialog()
         SubscribeToEvent(speedEdit, "TextChanged", "EditCameraSpeed");
         SubscribeToEvent(speedEdit, "TextFinished", "EditCameraSpeed");
         SubscribeToEvent(limitRotationToggle, "Toggled", "EditLimitRotation");
-        SubscribeToEvent(mouseWheelCameraPositionToggle, "Toggled", "EditMouseWheelCameraPosition");
         SubscribeToEvent(middleMousePanToggle, "Toggled", "EditMiddleMousePan");
         SubscribeToEvent(mouseOrbitEdit, "ItemSelected", "EditMouseOrbitMode");
         SubscribeToEvent(hotKeysModeEdit, "ItemSelected", "EditHotKeyMode");
         SubscribeToEvent(newNodeModeEdit, "ItemSelected", "EditNewNodeMode");
-        SubscribeToEvent(distanceEdit, "TextChanged", "EditNewNodeDistance");
-        SubscribeToEvent(distanceEdit, "TextFinished", "EditNewNodeDistance");
         SubscribeToEvent(moveStepEdit, "TextChanged", "EditMoveStep");
         SubscribeToEvent(moveStepEdit, "TextFinished", "EditMoveStep");
         SubscribeToEvent(rotateStepEdit, "TextChanged", "EditRotateStep");
@@ -250,12 +242,6 @@ void EditLimitRotation(StringHash eventType, VariantMap& eventData)
     limitRotation = edit.checked;
 }
 
-void EditMouseWheelCameraPosition(StringHash eventType, VariantMap& eventData)
-{
-    CheckBox@ edit = eventData["Element"].GetPtr();
-    mouseWheelCameraPosition = edit.checked;
-}
-
 void EditMouseOrbitMode(StringHash eventType, VariantMap& eventData)
 {
     DropDownList@ edit = eventData["Element"].GetPtr();
@@ -281,14 +267,6 @@ void EditNewNodeMode(StringHash eventType, VariantMap& eventData)
     newNodeMode = edit.selection;
 }
 
-void EditNewNodeDistance(StringHash eventType, VariantMap& eventData)
-{
-    LineEdit@ edit = eventData["Element"].GetPtr();
-    newNodeDistance = Max(edit.text.ToFloat(), 0.0);
-    if (eventType == StringHash("TextFinished"))
-        edit.text = String(newNodeDistance);
-}
-
 void EditMoveStep(StringHash eventType, VariantMap& eventData)
 {
     LineEdit@ edit = eventData["Element"].GetPtr();

+ 83 - 93
bin/Data/Scripts/Editor/EditorUI.as

@@ -428,7 +428,11 @@ void CreateMenuBar()
         //else if (hotKeyMode == HOT_KEYS_MODE_BLENDER)
         //    popup.AddChild(CreateMenuItem("Toggle update", @ToggleSceneUpdate, KEY_P, QUAL_CTRL));
 
-        if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+        if (hotKeyMode == HOTKEYS_MODE_STANDARD)
+        {
+            popup.AddChild(CreateMenuItem("View closer", @ViewCloser, KEY_F));
+        }
+        else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
         {
              popup.AddChild(CreateMenuItem("Move to layer", @ShowLayerMover, KEY_M));
              popup.AddChild(CreateMenuItem("Smart Duplicate", @SceneSmartDuplicateNode, KEY_D, QUAL_ALT));
@@ -1014,6 +1018,8 @@ Text@ CreateAccelKeyText(int accelKey, int accelQual)
         text = "F12";
     else if (accelKey == SHOW_POPUP_INDICATOR)
         text = ">";
+    else if (accelKey == KEY_KP_PERIOD)
+        text = "NumPad .";
     else
         text.AppendUTF8(accelKey);
     if (accelQual & QUAL_ALT > 0)
@@ -1346,44 +1352,51 @@ void HandleHotKeysBlender( VariantMap& eventData)
         screenshot.SavePNG(screenshotDir + "/Screenshot_" +
                 time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png");
     }
-    else if (key == KEY_KP_1 && ui.focusElement is null) // Front view
+    // In Blender, HOME key is for locating the selected objects by pan and
+    // the PERIOD key of keypad is for moving the camera to focus the selected.
+    // Here we ignore the difference.
+    else if ((key == KEY_HOME || key == KEY_KP_PERIOD) && ui.focusElement is null)
     {
-        Vector3 center = Vector3(0,0,0);
         if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
-
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(0.0, 0.0, pos.length * viewDirection);
-        cameraNode.direction = Vector3(0, 0, viewDirection);
-        ReacquireCameraYawPitch();
+        {
+            LocateNodesAndComponents(selectedNodes, selectedComponents);
+        }
     }
-
-    else if (key == KEY_KP_3 && ui.focusElement is null) // Side view
+    else if (key == KEY_KP_1 && ui.focusElement is null) // Front view
     {
-        Vector3 center = Vector3(0,0,0);
-        if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
+        cameraSmoothInterpolate.Finish();
+
+        Vector3 pos = -Vector3(0.0, 0.0, cameraNode.position.length * viewDirection);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, 0, viewDirection));
 
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(pos.length * -viewDirection, 0.0, 0.0);
-        cameraNode.direction = Vector3(-viewDirection, 0, 0);
-        ReacquireCameraYawPitch();
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
     }
+    else if ((key == KEY_KP_3 || key == KEY_KP_9) && ui.focusElement is null) // Side view
+    {
+        cameraSmoothInterpolate.Finish();
+
+        Vector3 pos = -Vector3(cameraNode.position.length * -viewDirection, 0.0, 0.0);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(-viewDirection, 0, 0));
 
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
+    }
     else if (key == KEY_KP_7 && ui.focusElement is null) // Top view
     {
-        Vector3 center = Vector3(0,0,0);
-        if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
+        cameraSmoothInterpolate.Finish();
+
+        Vector3 pos = -Vector3(0.0, cameraNode.position.length * -viewDirection, 0.0);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, -viewDirection, 0));
 
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(0.0, pos.length * -viewDirection, 0.0);
-        cameraNode.direction = Vector3(0, -viewDirection, 0);
-        ReacquireCameraYawPitch();
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
     }
     else if (key == KEY_KP_5 && ui.focusElement is null)
     {
-        activeViewport.camera.zoom = 1;
         activeViewport.ToggleOrthographic();
     }
     else if (key == '4' && ui.focusElement is null)
@@ -1434,53 +1447,26 @@ void HandleHotKeysBlender( VariantMap& eventData)
                 SceneResetRotation();
             else if (key == KEY_S)
                 SceneResetScale();
-            else if (key == KEY_F)
-            {
-                 Vector3 center = Vector3(0,0,0);
-
-                 if (selectedNodes.length > 0)
-                    center = SelectedNodesCenterPoint();
-
-                 cameraNode.LookAt(center);
-                 ReacquireCameraYawPitch();
-            }
          }
          else if (eventData["Qualifiers"].GetInt() != QUAL_CTRL) // set transformations
          {
-                if (key == KEY_G)
-                {
-                    editMode = EDIT_MOVE;
-                    axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
-
-                }
-                else if (key == KEY_R)
-                {
-                    editMode = EDIT_ROTATE;
-                    axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
-
-                }
-                else if (key == KEY_S)
-                {
-                    editMode = EDIT_SCALE;
-                    axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
-                }
-                else if (key == KEY_F)
-                {
-                    if (camera.orthographic)
-                    {
-                        viewCloser = true;
-                    }
-                    else
-                    {
-                        Vector3 center = Vector3(0,0,0);
+            if (key == KEY_G)
+            {
+                editMode = EDIT_MOVE;
+                axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
 
-                        if (selectedNodes.length > 0)
-                            center = SelectedNodesCenterPoint();
+            }
+            else if (key == KEY_R)
+            {
+                editMode = EDIT_ROTATE;
+                axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
 
-                        cameraNode.LookAt(center);
-                        ReacquireCameraYawPitch();
-                    }
-                }
+            }
+            else if (key == KEY_S)
+            {
+                editMode = EDIT_SCALE;
+                axisMode = AxisMode(axisMode ^ AXIS_LOCAL);
+            }
          }
     }
 
@@ -1539,42 +1525,46 @@ void HandleHotKeysStandard(VariantMap& eventData)
         screenshot.SavePNG(screenshotDir + "/Screenshot_" +
                 time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png");
     }
-    else if (key == KEY_KP_1 && ui.focusElement is null) // Front view
+    else if ((key == KEY_HOME || key == KEY_F) && ui.focusElement is null)
     {
-        Vector3 center = Vector3(0,0,0);
         if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
-
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(0.0, 0.0, pos.length * viewDirection);
-        cameraNode.direction = Vector3(0, 0, viewDirection);
-        ReacquireCameraYawPitch();
+        {
+            LocateNodesAndComponents(selectedNodes, selectedComponents);
+        }
     }
-
-    else if (key == KEY_KP_3 && ui.focusElement is null) // Side view
+    else if (key == KEY_KP_1 && ui.focusElement is null) // Front view
     {
-        Vector3 center = Vector3(0,0,0);
-        if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
+        cameraSmoothInterpolate.Finish();
 
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(pos.length * -viewDirection, 0.0, 0.0);
-        cameraNode.direction = Vector3(-viewDirection, 0, 0);
-        ReacquireCameraYawPitch();
+        Vector3 pos = -Vector3(0.0, 0.0, cameraNode.position.length * viewDirection);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, 0, viewDirection));
+
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
     }
+    else if ((key == KEY_KP_3 || key == KEY_KP_9) && ui.focusElement is null) // Side view
+    {
+        cameraSmoothInterpolate.Finish();
 
+        Vector3 pos = -Vector3(cameraNode.position.length * -viewDirection, 0.0, 0.0);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(-viewDirection, 0, 0));
+
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
+    }
     else if (key == KEY_KP_7 && ui.focusElement is null) // Top view
     {
-        Vector3 center = Vector3(0,0,0);
-        if (selectedNodes.length > 0 || selectedComponents.length > 0)
-            center = SelectedNodesCenterPoint();
+        cameraSmoothInterpolate.Finish();
 
-        Vector3 pos = cameraNode.worldPosition - center;
-        cameraNode.worldPosition = center - Vector3(0.0, pos.length * -viewDirection, 0.0);
-        cameraNode.direction = Vector3(0, -viewDirection, 0);
-        ReacquireCameraYawPitch();
-    }
+        Vector3 pos = -Vector3(0.0, cameraNode.position.length * -viewDirection, 0.0);
+        Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, -viewDirection, 0));
 
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos);
+        cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot);
+        cameraSmoothInterpolate.Start(0.5f);
+    }
     else if (key == KEY_KP_5 && ui.focusElement is null)
     {
         activeViewport.ToggleOrthographic();

+ 646 - 184
bin/Data/Scripts/Editor/EditorView.as

@@ -1,9 +1,13 @@
 // Urho3D editor view & camera functions
 
 WeakHandle previewCamera;
+
+Node@ cameraLookAtNode;
 Node@ cameraNode;
 Camera@ camera;
 
+float orthoCameraZoom = 1.0f;
+
 Node@ gridNode;
 CustomGeometry@ grid;
 
@@ -19,7 +23,6 @@ RenderPath@ renderPath; // Renderpath to use on all views
 String renderPathName;
 bool gammaCorrection = false;
 bool HDR = false;
-bool mouseWheelCameraPosition = false;
 bool contextMenuActionWaitFrame = false;
 bool cameraFlyMode = true;
 int hotKeyMode = 0; // used for checking that kind of style manipulation user are prefer (see HotKeysMode)
@@ -27,7 +30,6 @@ Vector3 lastSelectedNodesCenterPoint = Vector3(0,0,0); // for Blender mode to av
 WeakHandle lastSelectedNode = null;
 WeakHandle lastSelectedDrawable = null;
 WeakHandle lastSelectedComponent = null;
-bool viewCloser = false;
 Component@ coloringComponent = null;
 String coloringTypeName;
 String coloringPropertyName;
@@ -104,6 +106,7 @@ class ViewportContext
     float cameraYaw = 0;
     float cameraPitch = 0;
     Camera@ camera;
+    Node@ cameraLookAtNode;
     Node@ cameraNode;
     SoundListener@ soundListener;
     Viewport@ viewport;
@@ -128,7 +131,10 @@ class ViewportContext
     ViewportContext(IntRect viewRect, uint index_, uint viewportId_)
     {
         cameraNode = Node();
+        cameraLookAtNode = Node();
+        cameraLookAtNode.AddChild(cameraNode);        
         camera = cameraNode.CreateComponent("Camera");
+        orthoCameraZoom = camera.zoom;
         camera.fillMode = fillMode;
         soundListener = cameraNode.CreateComponent("SoundListener");
         viewport = Viewport(editorScene, camera, viewRect, renderPath);
@@ -139,6 +145,11 @@ class ViewportContext
 
     void ResetCamera()
     {
+        cameraSmoothInterpolate.Stop();
+
+        cameraLookAtNode.position = Vector3(0, 0, 0);
+        cameraLookAtNode.rotation = Quaternion();
+
         cameraNode.position = Vector3(0, 5, -10);
         // Look at the origin so user can see the scene.
         cameraNode.rotation = Quaternion(Vector3(0, 0, 1), -cameraNode.position);
@@ -245,11 +256,22 @@ class ViewportContext
     void SetOrthographic(bool orthographic)
     {
         camera.orthographic = orthographic;
+        if (camera.orthographic)
+            camera.zoom = orthoCameraZoom;
+        else
+            camera.zoom = 1.0f;
+            
         UpdateSettingsUI();
     }
 
     void Update(float timeStep)
     {
+        // Update camera smooth move
+        if (cameraSmoothInterpolate.IsRunning())
+        {
+            cameraSmoothInterpolate.Update(timeStep);
+        }
+
         Vector3 cameraPos = cameraNode.position;
         String xText(cameraPos.x);
         String yText(cameraPos.y);
@@ -365,7 +387,8 @@ float viewNearClip = 0.1;
 float viewFarClip = 1000.0;
 float viewFov = 45.0;
 
-float cameraBaseSpeed = 10;
+
+float cameraBaseSpeed = 3;
 float cameraBaseRotationSpeed = 0.2;
 float cameraShiftSpeedMultiplier = 5;
 float moveStep = 0.5;
@@ -395,13 +418,12 @@ bool mmbPanMode = false;
 
 enum NewNodeMode
 {
-    NEW_NODE_CAMERA_DIST = 0,
+    NEW_NODE_CAMERA_LOOKAT = 0,
     NEW_NODE_IN_CENTER,
     NEW_NODE_RAYCAST
 }
 
-float newNodeDistance = 20;
-int newNodeMode = NEW_NODE_CAMERA_DIST;
+int newNodeMode = NEW_NODE_CAMERA_LOOKAT;
 
 bool showGrid = true;
 bool grid2DMode = false;
@@ -447,6 +469,169 @@ Array<String> fillModeText = {
     "Point"
 };
 
+// This class provides smooth translation/rotation/zoom interpolation for the editor camera
+class CameraSmoothInterpolate
+{
+    Vector3 lookAtNodeBeginPos;
+    Vector3 cameraNodeBeginPos;
+    
+    Vector3 lookAtNodeEndPos;
+    Vector3 cameraNodeEndPos;
+
+    Quaternion cameraNodeBeginRot;
+    Quaternion cameraNodeEndRot;
+
+    float cameraBeginZoom;
+    float cameraEndZoom;
+    
+    bool isRunning = false;
+    float duration = 0.0f;
+    float elapsedTime = 0.0f;
+
+    bool interpLookAtNodePos = false;
+    bool interpCameraNodePos = false;
+    bool interpCameraRot = false;
+    bool interpCameraZoom = false;
+
+    CameraSmoothInterpolate()
+    {
+    }
+
+    void SetLookAtNodePosition(Vector3 lookAtBeginPos, Vector3 lookAtEndPos)
+    {
+        lookAtNodeBeginPos = lookAtBeginPos;
+        lookAtNodeEndPos = lookAtEndPos;
+        interpLookAtNodePos = true;
+    }
+
+    void SetCameraNodePosition(Vector3 cameraBeginPos, Vector3 cameraEndPos)
+    {
+        cameraNodeBeginPos = cameraBeginPos;
+        cameraNodeEndPos = cameraEndPos;
+        interpCameraNodePos = true;
+    }
+
+    void SetCameraNodeRotation(Quaternion cameraBeginRot, Quaternion cameraEndRot)
+    {
+        cameraNodeBeginRot = cameraBeginRot;
+        cameraNodeEndRot = cameraEndRot;
+        interpCameraRot = true;
+    }
+
+    void SetCameraZoom(float beginZoom, float endZoom)
+    {
+        cameraBeginZoom = beginZoom;
+        cameraEndZoom = endZoom;
+        interpCameraZoom = true;
+    }
+
+    void Start(float duration_)
+    {
+        if (cameraLookAtNode is null || cameraNode is null || camera is null)
+            return;
+
+        duration = duration_;
+        elapsedTime = 0.0f;
+        isRunning = true;
+    }
+
+    void Stop()
+    {
+        interpLookAtNodePos = false;
+        interpCameraNodePos = false;
+        interpCameraRot = false;
+        interpCameraZoom = false;
+
+        isRunning = false;
+    }
+
+    void Finish()
+    {
+        if (!isRunning)
+            return;
+
+        if (cameraLookAtNode is null || cameraNode is null || camera is null)
+            return;
+
+        if (interpLookAtNodePos)
+            cameraLookAtNode.worldPosition = lookAtNodeEndPos;
+        
+        if (interpCameraNodePos)
+            cameraNode.position = cameraNodeEndPos;
+
+        if (interpCameraRot)
+        {
+            cameraNode.rotation = cameraNodeEndRot;
+            ReacquireCameraYawPitch();
+        }
+
+        if (interpCameraZoom)
+        {
+            orthoCameraZoom = cameraEndZoom;
+            camera.zoom = cameraEndZoom;
+        }
+
+        interpLookAtNodePos = false;
+        interpCameraNodePos = false;
+        interpCameraRot = false;
+        interpCameraZoom = false;
+
+        isRunning = false;
+    }
+
+    bool IsRunning() const
+    {
+        return isRunning;
+    }
+
+    // Cubic easing out
+    // http://robertpenner.com/easing/
+    float EaseOut(float t, float b , float c, float d) 
+    {
+        return c * ((t = t / d - 1) * t * t + 1) + b;
+    }
+
+    void Update(float timeStep)
+    {
+        if (!isRunning)
+            return;
+
+        if (cameraLookAtNode is null || cameraNode is null || camera is null)
+            return;
+
+        elapsedTime += timeStep;
+    
+        if (elapsedTime <= duration)
+        {
+            float factor = EaseOut(elapsedTime, 0.0f, 1.0f, duration);
+
+            if (interpLookAtNodePos)
+                cameraLookAtNode.worldPosition = lookAtNodeBeginPos + (lookAtNodeEndPos - lookAtNodeBeginPos) * factor;
+            
+            if (interpCameraNodePos)
+                cameraNode.position = cameraNodeBeginPos + (cameraNodeEndPos - cameraNodeBeginPos) * factor;
+
+            if (interpCameraRot)
+            {
+                cameraNode.rotation = cameraNodeBeginRot.Slerp(cameraNodeEndRot, factor);
+                ReacquireCameraYawPitch();
+            }
+
+            if (interpCameraZoom)
+            {
+                orthoCameraZoom = cameraBeginZoom + (cameraEndZoom - cameraBeginZoom) * factor;
+                camera.zoom = orthoCameraZoom;
+            }
+        }
+        else
+        {
+            Finish();
+        }
+    }
+}
+
+CameraSmoothInterpolate cameraSmoothInterpolate;
+
 void SetRenderPath(const String&in newRenderPathName)
 {
     renderPath = null;
@@ -644,10 +829,15 @@ void SetFillMode(FillMode fillMode_)
 void SetViewportMode(uint mode = VIEWPORT_SINGLE)
 {
     // Remember old viewport positions
+    Array<Vector3> cameralookAtPositions;
+    Array<Quaternion> cameraLookAtRotations;
     Array<Vector3> cameraPositions;
     Array<Quaternion> cameraRotations;
     for (uint i = 0; i < viewports.length; ++i)
     {
+        cameralookAtPositions.Push(viewports[i].cameraLookAtNode.position);
+        cameraLookAtRotations.Push(viewports[i].cameraLookAtNode.rotation);
+
         cameraPositions.Push(viewports[i].cameraNode.position);
         cameraRotations.Push(viewports[i].cameraNode.rotation);
     }
@@ -798,6 +988,10 @@ void SetViewportMode(uint mode = VIEWPORT_SINGLE)
             uint src = i;
             if (src >= cameraPositions.length)
                 src = cameraPositions.length - 1;
+
+            viewports[i].cameraLookAtNode.position = cameralookAtPositions[src];
+            viewports[i].cameraLookAtNode.rotation = cameraLookAtRotations[src];
+
             viewports[i].cameraNode.position = cameraPositions[src];
             viewports[i].cameraNode.rotation = cameraRotations[src];
         }
@@ -1088,6 +1282,7 @@ void SetViewportCursor()
 void SetActiveViewport(ViewportContext@ context)
 {
     // Sets the global variables to the current context
+    @cameraLookAtNode = context.cameraLookAtNode;
     @cameraNode = context.cameraNode;
     @camera = context.camera;
     @audio.listener = context.soundListener;
@@ -1331,7 +1526,195 @@ void ReleaseMouseLock()
     }
 }
 
-void UpdateView(float timeStep)
+void CameraPan(Vector3 trans)
+{
+    cameraLookAtNode.Translate(trans);
+}
+
+void CameraMoveForward(Vector3 trans)
+{
+    cameraNode.Translate(trans, TS_PARENT);
+}
+
+void CameraRotateAroundLookAt(Quaternion rot)
+{
+    cameraNode.rotation = rot;
+
+    Vector3 dir = cameraNode.direction;
+    dir.Normalize();
+
+    float dist = cameraNode.position.length;
+
+    cameraNode.position = -dir * dist;
+}
+
+void CameraRotateAroundCenter(Quaternion rot)
+{
+    cameraNode.rotation = rot;
+    
+    Vector3 oldPos = cameraNode.worldPosition;
+
+    Vector3 dir = cameraNode.worldDirection;
+    dir.Normalize();
+
+    float dist = cameraNode.position.length;
+
+    cameraLookAtNode.worldPosition = cameraNode.worldPosition + dir * dist;
+    cameraNode.worldPosition = oldPos;
+}
+
+void HandleStandardUserInput(float timeStep)
+{
+    // Speedup camera move if Shift key is down
+    float speedMultiplier = 1.0;
+    if (input.keyDown[KEY_LSHIFT])
+        speedMultiplier = cameraShiftSpeedMultiplier;
+
+    // Handle FPS mode
+    if (!input.keyDown[KEY_LCTRL] && !input.keyDown[KEY_LALT])
+    {
+        if (input.keyDown[KEY_W] || input.keyDown[KEY_UP])
+        {
+            Vector3 dir = cameraNode.direction;
+            dir.Normalize();
+
+            CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+        if (input.keyDown[KEY_S] || input.keyDown[KEY_DOWN])
+        {
+            Vector3 dir = cameraNode.direction;
+            dir.Normalize();
+
+            CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+        if (input.keyDown[KEY_A] || input.keyDown[KEY_LEFT])
+        {
+            Vector3 dir = cameraNode.right;
+            dir.Normalize();
+
+            CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+        if (input.keyDown[KEY_D] || input.keyDown[KEY_RIGHT])
+        {
+            Vector3 dir = cameraNode.right;
+            dir.Normalize();
+
+            CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+        if (input.keyDown[KEY_E] || input.keyDown[KEY_PAGEUP])
+        {
+            Vector3 dir = cameraNode.up;
+            dir.Normalize();
+
+            CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+        if (input.keyDown[KEY_Q] || input.keyDown[KEY_PAGEDOWN])
+        {
+            Vector3 dir = cameraNode.up;
+            dir.Normalize();
+
+            CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
+            FadeUI();
+        }
+    }
+
+    // Zoom in/out
+    if (input.mouseMoveWheel != 0 && ui.GetElementAt(ui.cursor.position) is null)
+    {
+        float distance = cameraNode.position.length;
+        float ratio = distance / 40.0f;
+        float factor = ratio < 1.0f ? ratio : 1.0f;
+
+        if (!camera.orthographic)
+        {
+            Vector3 dir = cameraNode.direction;
+            dir.Normalize();
+            dir *= input.mouseMoveWheel * 40 * timeStep * cameraBaseSpeed * speedMultiplier * factor;
+
+            CameraMoveForward(dir);
+        }
+        else
+        {
+            float zoom = camera.zoom + input.mouseMoveWheel * speedMultiplier * factor;
+            camera.zoom = Clamp(zoom, .1, 30);
+        }
+    }
+
+
+    // Rotate/orbit/pan camera
+    bool changeCamViewButton = false;
+    
+    changeCamViewButton = input.mouseButtonDown[MOUSEB_RIGHT] || input.mouseButtonDown[MOUSEB_MIDDLE];
+
+    if (changeCamViewButton)
+    {
+        SetMouseLock();
+
+        IntVector2 mouseMove = input.mouseMove;
+        if (mouseMove.x != 0 || mouseMove.y != 0)
+        {
+            bool panTheCamera = false;
+
+            if (input.mouseButtonDown[MOUSEB_MIDDLE])
+            {
+                if (mmbPanMode)
+                    panTheCamera = !input.keyDown[KEY_LSHIFT];
+                else
+                    panTheCamera = input.keyDown[KEY_LSHIFT];
+            }
+
+            // Pan the camera
+            if (panTheCamera)
+            {
+                Vector3 right = -cameraNode.worldRight;
+                right.Normalize();
+                right *= mouseMove.x;
+                Vector3 up = cameraNode.worldUp;
+                up.Normalize();
+                up *= mouseMove.y;
+
+                Vector3 trans = (right + up) * timeStep * cameraBaseSpeed * 0.5;
+
+                CameraPan(trans);
+            }
+            else // Rotate the camera
+            {
+                activeViewport.cameraYaw += mouseMove.x * cameraBaseRotationSpeed;
+                activeViewport.cameraPitch += mouseMove.y * cameraBaseRotationSpeed;
+
+                if (limitRotation)
+                    activeViewport.cameraPitch = Clamp(activeViewport.cameraPitch, -90.0, 90.0);
+
+                Quaternion rot = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0);
+                
+                if (input.mouseButtonDown[MOUSEB_MIDDLE]) // Rotate around the camera center
+                {
+                    CameraRotateAroundLookAt(rot);
+                    
+                    orbiting = true;
+                }
+                else // Rotate around the look-at
+                {
+                    CameraRotateAroundCenter(rot);
+                                            
+                    orbiting = true;
+                }
+            }
+        }
+    }
+    else
+        ReleaseMouseLock();
+
+    if (orbiting && !input.mouseButtonDown[MOUSEB_MIDDLE])
+        orbiting = false;
+}
+
+void HandleBlenderUserInput(float timeStep)
 {
     if (ui.HasModalElement() || ui.focusElement !is null)
     {
@@ -1340,51 +1723,67 @@ void UpdateView(float timeStep)
     }
 
     // Check for camara fly mode
-    if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+    if (input.keyDown[KEY_LSHIFT] && input.keyPress[KEY_F])
     {
-        if (input.keyDown[KEY_LSHIFT] && input.keyPress[KEY_F])
-        {
-            cameraFlyMode = !cameraFlyMode;
-        }
+        cameraFlyMode = !cameraFlyMode;
     }
 
-    // Move camera
+    // Speedup camera move if Shift key is down
     float speedMultiplier = 1.0;
     if (input.keyDown[KEY_LSHIFT])
         speedMultiplier = cameraShiftSpeedMultiplier;
 
+    // Handle FPS mode
     if (!input.keyDown[KEY_LCTRL] && !input.keyDown[KEY_LALT])
     {
-        if (hotKeyMode == HOTKEYS_MODE_STANDARD || (hotKeyMode == HOTKEYS_MODE_BLENDER && cameraFlyMode && !input.keyDown[KEY_LSHIFT]))
+        if (cameraFlyMode /*&& !input.keyDown[KEY_LSHIFT]*/)
         {
             if (input.keyDown[KEY_W] || input.keyDown[KEY_UP])
             {
-                cameraNode.Translate(Vector3(0, 0, cameraBaseSpeed) * timeStep * speedMultiplier);
+                Vector3 dir = cameraNode.direction;
+                dir.Normalize();
+
+                CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
             if (input.keyDown[KEY_S] || input.keyDown[KEY_DOWN])
             {
-                cameraNode.Translate(Vector3(0, 0, -cameraBaseSpeed) * timeStep * speedMultiplier);
+                Vector3 dir = cameraNode.direction;
+                dir.Normalize();
+
+                CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
             if (input.keyDown[KEY_A] || input.keyDown[KEY_LEFT])
             {
-                cameraNode.Translate(Vector3(-cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
+                Vector3 dir = cameraNode.right;
+                dir.Normalize();
+
+                CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
             if (input.keyDown[KEY_D] || input.keyDown[KEY_RIGHT])
             {
-                cameraNode.Translate(Vector3(cameraBaseSpeed, 0, 0) * timeStep * speedMultiplier);
+                Vector3 dir = cameraNode.right;
+                dir.Normalize();
+
+                CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
             if (input.keyDown[KEY_E] || input.keyDown[KEY_PAGEUP])
             {
-                cameraNode.Translate(Vector3(0, cameraBaseSpeed, 0) * timeStep * speedMultiplier, TS_WORLD);
+                Vector3 dir = cameraNode.up;
+                dir.Normalize();
+
+                CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
             if (input.keyDown[KEY_Q] || input.keyDown[KEY_PAGEDOWN])
             {
-                cameraNode.Translate(Vector3(0, -cameraBaseSpeed, 0) * timeStep * speedMultiplier, TS_WORLD);
+                Vector3 dir = cameraNode.up;
+                dir.Normalize();
+
+                CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier);
                 FadeUI();
             }
         }
@@ -1392,85 +1791,64 @@ void UpdateView(float timeStep)
 
     if (input.mouseMoveWheel != 0 && ui.GetElementAt(ui.cursor.position) is null)
     {
-        if (hotKeyMode == HOTKEYS_MODE_STANDARD)
+        if (!camera.orthographic)
         {
-            if (mouseWheelCameraPosition)
+            if (input.keyDown[KEY_LSHIFT])
             {
-                cameraNode.Translate(Vector3(0, 0, -cameraBaseSpeed) * -input.mouseMoveWheel*20 * timeStep *
-                speedMultiplier);
+                Vector3 dir = cameraNode.up;
+                dir.Normalize();
+
+                CameraPan(dir * input.mouseMoveWheel * 5 * timeStep * cameraBaseSpeed * speedMultiplier);
             }
-            else
+            else if (input.keyDown[KEY_LCTRL])
             {
-                float zoom = camera.zoom + -input.mouseMoveWheel *.1 * speedMultiplier;
-                camera.zoom = Clamp(zoom, .1, 30);
+                Vector3 dir = cameraNode.right;
+                dir.Normalize();
+
+                CameraPan(dir * input.mouseMoveWheel * 5 * timeStep * cameraBaseSpeed * speedMultiplier);
+            }
+            else // Zoom in/out
+            {
+                float distance = cameraNode.position.length;
+                float ratio = distance / 40.0f;
+                float factor = ratio < 1.0f ? ratio : 1.0f;
+                
+                Vector3 dir = cameraNode.direction;
+                dir.Normalize();
+                dir *= input.mouseMoveWheel * 40 * timeStep * cameraBaseSpeed * speedMultiplier * factor;
+
+                CameraMoveForward(dir);
             }
         }
-        else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+        else
         {
-            if (mouseWheelCameraPosition && !camera.orthographic)
+            if (input.keyDown[KEY_LSHIFT])
             {
-                if (input.keyDown[KEY_LSHIFT])
-                    cameraNode.Translate(Vector3(0, -cameraBaseSpeed, 0) * -input.mouseMoveWheel*20* timeStep * speedMultiplier);
-                else if (input.keyDown[KEY_LCTRL])
-                    cameraNode.Translate(Vector3(-cameraBaseSpeed,0, 0) * -input.mouseMoveWheel*20 * timeStep * speedMultiplier);
-                else
-                {
-                    Vector3 center = SelectedNodesCenterPoint();
-                    float distance = (cameraNode.worldPosition - center).length;
-                    float ratio = distance / 40.0f;
-                    float factor = ratio < 1.0f ? ratio : 1.0f;
-                    cameraNode.Translate(Vector3(0, 0, -cameraBaseSpeed) * -input.mouseMoveWheel*40*factor*timeStep*speedMultiplier);
-                }
+                Vector3 dir = cameraNode.up;
+                dir.Normalize();
+
+                CameraPan(dir * input.mouseMoveWheel * timeStep * cameraBaseSpeed * speedMultiplier * 4.0f);
+            }
+            else if (input.keyDown[KEY_LCTRL])
+            {
+                Vector3 dir = cameraNode.right;
+                dir.Normalize();
+
+                CameraPan(dir * input.mouseMoveWheel * timeStep * cameraBaseSpeed * speedMultiplier * 4.0f);
             }
             else
             {
-                if (input.keyDown[KEY_LSHIFT])
-                {
-                    cameraNode.Translate(Vector3(0, -cameraBaseSpeed, 0) * -input.mouseMoveWheel*20* timeStep * speedMultiplier);
-                }
-                else if (input.keyDown[KEY_LCTRL])
-                {
-                    cameraNode.Translate(Vector3(-cameraBaseSpeed,0, 0) * -input.mouseMoveWheel*20 * timeStep * speedMultiplier);
-                }
-                else
-                {
-                    if (input.qualifierDown[QUAL_ALT])
-                    {
-                        float zoom = camera.zoom + -input.mouseMoveWheel *.1 * speedMultiplier;
-                        camera.zoom = Clamp(zoom, .1, 30);
-                    }
-                    else
-                    {
-                        cameraNode.Translate(Vector3(0, 0, -cameraBaseSpeed) * -input.mouseMoveWheel*20 * timeStep * speedMultiplier);
-                    }
-                }
+                float zoom = camera.zoom + input.mouseMoveWheel * speedMultiplier * 0.5f;
+                camera.zoom = Clamp(zoom, .1, 30);
             }
         }
     }
 
-    if (input.keyDown[KEY_HOME])
-    {
-        if (selectedNodes.length > 0 || selectedComponents.length > 0)
-        {
-            Quaternion q = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0);
-            Vector3 centerPoint = SelectedNodesCenterPoint();
-            Vector3 d = cameraNode.worldPosition - centerPoint;
-            cameraNode.worldPosition = centerPoint - q * Vector3(0.0, 0.0,10);
-        }
-    }
-
     // Rotate/orbit/pan camera
-    bool changeCamViewButton = false;
-
-    if (hotKeyMode == HOTKEYS_MODE_STANDARD)
-        changeCamViewButton = input.mouseButtonDown[MOUSEB_RIGHT] || input.mouseButtonDown[MOUSEB_MIDDLE];
-    else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
-    {
-        changeCamViewButton = input.mouseButtonDown[MOUSEB_MIDDLE] || cameraFlyMode;
+    bool changeCamViewButton = input.mouseButtonDown[MOUSEB_MIDDLE] || cameraFlyMode;
 
-        if (input.mouseButtonPress[MOUSEB_RIGHT] || input.keyDown[KEY_ESCAPE])
-            cameraFlyMode = false;
-    }
+    if (input.mouseButtonPress[MOUSEB_RIGHT] || input.keyDown[KEY_ESCAPE])
+        cameraFlyMode = false;
 
     if (changeCamViewButton)
     {
@@ -1481,24 +1859,22 @@ void UpdateView(float timeStep)
         {
             bool panTheCamera = false;
 
-            if (hotKeyMode == HOTKEYS_MODE_STANDARD)
-            {
-                if (input.mouseButtonDown[MOUSEB_MIDDLE])
-                {
-                    if (mmbPanMode)
-                        panTheCamera = !input.keyDown[KEY_LSHIFT];
-                    else
-                        panTheCamera = input.keyDown[KEY_LSHIFT];
-                }
-            }
-            else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
-            {
-                if (!cameraFlyMode)
-                    panTheCamera = input.keyDown[KEY_LSHIFT];
-            }
+            if (!cameraFlyMode)
+                panTheCamera = input.keyDown[KEY_LSHIFT];
 
             if (panTheCamera)
-                cameraNode.Translate(Vector3(-mouseMove.x, mouseMove.y, 0) * timeStep * cameraBaseSpeed * 0.5);
+            {
+                Vector3 right = -cameraNode.worldRight;
+                right.Normalize();
+                right *= mouseMove.x;
+                Vector3 up = cameraNode.worldUp;
+                up.Normalize();
+                up *= mouseMove.y;
+
+                Vector3 trans = (right + up) * timeStep * cameraBaseSpeed * 0.5;
+
+                CameraPan(trans);
+            }
             else
             {
                 activeViewport.cameraYaw += mouseMove.x * cameraBaseRotationSpeed;
@@ -1507,34 +1883,17 @@ void UpdateView(float timeStep)
                 if (limitRotation)
                     activeViewport.cameraPitch = Clamp(activeViewport.cameraPitch, -90.0, 90.0);
 
-                Quaternion q = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0);
-                cameraNode.rotation = q;
-
-                if (hotKeyMode == HOTKEYS_MODE_STANDARD)
+                Quaternion rot = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0);
+                
+                if (cameraFlyMode)
                 {
-                    if (input.mouseButtonDown[MOUSEB_MIDDLE] && (selectedNodes.length > 0 || selectedComponents.length > 0))
-                    {
-                        Vector3 centerPoint = SelectedNodesCenterPoint();
-                        Vector3 d = cameraNode.worldPosition - centerPoint;
-                        cameraNode.worldPosition = centerPoint - q * Vector3(0.0, 0.0, d.length);
-                        orbiting = true;
-                    }
+                    CameraRotateAroundCenter(rot);
+                    orbiting = true;
                 }
-                else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+                else if (input.mouseButtonDown[MOUSEB_MIDDLE])
                 {
-                    if (input.mouseButtonDown[MOUSEB_MIDDLE])
-                    {
-                        Vector3 centerPoint = Vector3(0,0,0);
-
-                        if ((selectedNodes.length > 0 || selectedComponents.length > 0))
-                            centerPoint = SelectedNodesCenterPoint();
-                        else
-                            centerPoint = lastSelectedNodesCenterPoint;
-
-                        Vector3 d = cameraNode.worldPosition - centerPoint;
-                        cameraNode.worldPosition = centerPoint - q * Vector3(0.0, 0.0, d.length);
-                        orbiting = true;
-                    }
+                    CameraRotateAroundLookAt(rot);
+                    orbiting = true;
                 }
             }
         }
@@ -1544,59 +1903,33 @@ void UpdateView(float timeStep)
 
     if (orbiting && !input.mouseButtonDown[MOUSEB_MIDDLE])
         orbiting = false;
-
-    if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+    
+    // force to select component node for manipulation if selected only component and not his node
+    if ((editMode != EDIT_SELECT && editNodes.empty) && lastSelectedComponent.Get() !is null)
     {
-        if (viewCloser && lastSelectedDrawable.Get() !is null)
+        if (lastSelectedComponent.Get() !is null)
         {
-            SetMouseLock();
-            BoundingBox bb;
-            Vector3 centerPoint;
-
-            if (selectedNodes.length <= 1)
-            {
-                Drawable@ drawable = lastSelectedDrawable.Get();
-                if (drawable !is null)
-                {
-                    bb = drawable.boundingBox;
-                    centerPoint = drawable.node.worldPosition;
-                }
-            }
-            else
-            {
-                for (uint i = 0; i < selectedNodes.length; i++)
-                {
-                    bb.Merge(selectedNodes[i].position);
-                }
-
-                centerPoint = SelectedNodesCenterPoint();
-            }
-
-            float distance = bb.size.length;
-            if (camera.orthographic) // if we use viewCloser for 2D get current distance to avoid near clip
-                distance = cameraNode.worldPosition.length;
-
-            Quaternion q = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0);
-            cameraNode.rotation = q;
-            cameraNode.worldPosition = centerPoint -  cameraNode.worldDirection * distance;
-            // ReacquireCameraYawPitch();
-            viewCloser =  false;
+            Component@ component  = lastSelectedComponent.Get();
+            SelectNode(component.node, false);
         }
-        else
-            viewCloser =  false;
     }
+}
 
-    // Move/rotate/scale object
-    if (hotKeyMode == HOTKEYS_MODE_BLENDER) // force to select component node for manipulation if selected only component and not his node
+void UpdateView(float timeStep)
+{
+    if (ui.HasModalElement() || ui.focusElement !is null)
     {
-        if ((editMode != EDIT_SELECT && editNodes.empty) && lastSelectedComponent.Get() !is null)
-        {
-            if (lastSelectedComponent.Get() !is null)
-            {
-                Component@ component  = lastSelectedComponent.Get();
-                SelectNode(component.node, false);
-            }
-        }
+        ReleaseMouseLock();
+        return;
+    }
+
+    if (hotKeyMode == HOTKEYS_MODE_STANDARD)
+    {    
+        HandleStandardUserInput(timeStep);
+    }    
+    else if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+    {
+        HandleBlenderUserInput(timeStep);
     }
 
     if (!editNodes.empty && editMode != EDIT_SELECT && input.keyDown[KEY_LCTRL])
@@ -2071,7 +2404,7 @@ Vector3 GetNewNodePosition(bool raycastToMouse = false)
         if (GetSpawnPosition(cameraRay, camera.farClip, position, normal, 0, false))
             return position;
     }
-    return cameraNode.worldPosition + cameraNode.worldRotation * Vector3(0, 0, newNodeDistance);
+    return cameraLookAtNode.worldPosition;
 }
 
 int GetShadowResolution()
@@ -2131,32 +2464,161 @@ bool StopTestAnimation()
     return true;
 }
 
-void LocateNode(Node@ node)
+void MergeNodeBoundingBox(BoundingBox &inout box, Array<Component@>&inout visitedComponents, Node@ node)
 {
     if (node is null || node is editorScene)
         return;
 
-    Vector3 center = node.worldPosition;
-    float distance = newNodeDistance;
-
+    // Merge components bounding box of this node
     for (uint i = 0; i < node.numComponents; ++i)
     {
-        // Determine view distance from drawable component's bounding box. Skip skybox, as its box is very large, as well as lights
-        Drawable@ drawable = cast<Drawable>(node.components[i]);
-        if (drawable !is null && cast<Skybox>(drawable) is null && cast<Light>(drawable) is null)
+        MergeComponentBoundingBox(box, visitedComponents, node.components[i]);
+    }
+
+    // Merge bounding boxes of child nodes recursively
+    for (uint i = 0; i < node.numChildren; ++i)
+    {
+        Node@ child = node.children[i];
+        MergeNodeBoundingBox(box, visitedComponents, child);
+    }
+}
+
+void MergeComponentBoundingBox(BoundingBox &inout box, Array<Component@>&inout visitedComponents, Component@ component)
+{
+    if (component is null || visitedComponents.FindByRef(component) != -1)
+        return;
+
+    Drawable@ drawable = cast<Drawable>(component);
+    if (drawable is null)
+        return;
+
+    // Merge drawable component's bounding box. Skip skybox, as its box is very large, as well as lights
+    if (drawable !is null && cast<Skybox>(drawable) is null && cast<Light>(drawable) is null)
+        box.Merge(drawable.worldBoundingBox);
+    else if (drawable.node !is editorScene)
+        box.Merge(drawable.node.worldPosition);
+
+    visitedComponents.Push(component);
+}
+
+void LocateNodes(Array<Node@> nodes)
+{
+    if (nodes.empty || (nodes.length == 1 && nodes[0] is editorScene))
+        return;
+
+    // Calculate bounding box of all nodes
+    BoundingBox box;
+    Array<Component@> visitedComponents;
+
+    for (uint i = 0; i < nodes.length; ++i)
+    {
+        MergeNodeBoundingBox(box, visitedComponents, nodes[i]);
+    }
+
+    FitCamera(box, true);
+}
+
+void LocateComponents(Array<Component@> components)
+{
+    if (components.empty || components.length == 1 && components[0].node is editorScene)
+        return;
+
+    // Calculate bounding box of all nodes
+    BoundingBox box;
+    Array<Component@> visitedComponents;
+
+    for (uint i = 0; i < components.length; ++i)
+    {
+        MergeComponentBoundingBox(box, visitedComponents, components[i]);
+    }
+
+    FitCamera(box, true);
+}
+
+void LocateNodesAndComponents(Array<Node@> nodes, Array<Component@> components)
+{
+    // Calculate bounding box of all nodes
+    BoundingBox box;
+    Array<Component@> visitedComponents;
+
+    if (!nodes.empty && !(nodes.length == 1 && nodes[0] is editorScene))
+    {
+        for (uint i = 0; i < nodes.length; ++i)
         {
-            BoundingBox box = drawable.worldBoundingBox;
-            center = box.center;
-            // Ensure the object fits on the screen
-            distance = Max(distance, newNodeDistance + box.size.length);
-            break;
+            MergeNodeBoundingBox(box, visitedComponents, nodes[i]);
+        }
+    }
+
+    if (!components.empty)
+    {
+        for (uint i = 0; i < components.length; ++i)
+        {
+            MergeComponentBoundingBox(box, visitedComponents, components[i]);
         }
     }
 
+    FitCamera(box, true);
+}
+
+void FitCamera(BoundingBox box, bool smooth)
+{
+    // Calculate proper camera distance - fit the bounding sphere into the camera frustum
+    Sphere sphere = Sphere(box);
+
+    float aspect = camera.aspectRatio;
+    float fov = 0.0f;
+
+    // Choose the small one from vertical and horizontal fovs
+    if (aspect > 1.0f)
+        fov = camera.fov;
+    else
+        fov = camera.fov * aspect;
+
+    fov *= 0.5f;
+
+    if (sphere.radius < 1.0f)
+        sphere.radius = 1.0f;
+    
+    float distance = sphere.radius / Sin(fov);
+
     if (distance > viewFarClip)
         distance = viewFarClip;
 
-    cameraNode.worldPosition = center - cameraNode.worldDirection * distance;
+    Vector3 dir = cameraNode.direction;
+    dir.Normalize();
+
+    // Make the distance a little farther 
+    distance *= 1.1f;
+
+    // Set zoom value a little bigger
+    float zoom = camera.orthoSize / (sphere.radius * 2.0f);
+    zoom *= 1.1f;
+
+    // We put the pivot node to the center of the bounding sphere 
+    // and put the camera node to the opposite of view direction
+    Vector3 lookAtPos = sphere.center;
+    Vector3 cameraPos = -dir * distance;
+    
+    cameraSmoothInterpolate.Stop();
+
+    if (smooth)
+    {
+        cameraSmoothInterpolate.SetLookAtNodePosition(cameraLookAtNode.worldPosition, lookAtPos);
+        cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, cameraPos);
+
+        if (camera.orthographic)
+            cameraSmoothInterpolate.SetCameraZoom(camera.zoom, zoom);
+        
+        cameraSmoothInterpolate.Start(0.5f);
+    }
+    else
+    {
+        cameraLookAtNode.worldPosition = lookAtPos;
+        cameraNode.position = cameraPos;
+
+        if (camera.orthographic)
+            camera.zoom = zoom;
+    }
 }
 
 Vector3 SelectedNodesCenterPoint()

+ 1 - 10
bin/Data/UI/EditorSettingsDialog.xml

@@ -78,15 +78,6 @@
                         <attribute name="Text" value="Limit camera pitch" />
                     </element>
                 </element>
-                <element style="ListRow">
-                    <attribute name="Layout Spacing" value="8" />
-                    <element type="CheckBox">
-                        <attribute name="Name" value="MouseWheelCameraPositionToggle" />
-                    </element>
-                    <element type="Text">
-                        <attribute name="Text" value="Mouse wheel camera position" />
-                    </element>
-                </element>
                 <element style="ListRow">
                     <attribute name="Layout Spacing" value="8" />
                     <element type="CheckBox">
@@ -165,7 +156,7 @@
                                 <element type="BorderImage" internal="true" style="none">
                                     <element internal="true" style="none">
                                         <element type="Text" style="FileSelectorFilterText">
-                                            <attribute name="Text" value="Use distance" />
+                                            <attribute name="Text" value="View center" />
                                         </element>
                                         <element type="Text" style="FileSelectorFilterText">
                                             <attribute name="Text" value="In center" />