432 lines
15 KiB
C#
432 lines
15 KiB
C#
#nullable enable
|
|
using ImGuiNET;
|
|
using T3.Core.Animation;
|
|
using T3.Core.Operator.Interfaces;
|
|
using T3.Core.Utils;
|
|
using T3.Core.Utils.Geometry;
|
|
using T3.Editor.Gui.Interaction.Keyboard;
|
|
using T3.Editor.Gui.Interaction.TransformGizmos;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.Gui.UiHelpers;
|
|
|
|
namespace T3.Editor.Gui.Interaction.Camera;
|
|
|
|
/// <summary>
|
|
/// Controls the manipulation of camera view within an 3d output window.
|
|
/// Each output window has its own instance.
|
|
/// </summary>
|
|
internal sealed class CameraInteraction
|
|
{
|
|
internal static ICameraManipulator[] ManipulationDevices = [];
|
|
|
|
internal void Update(ICamera? camera, bool allowCameraInteraction)
|
|
{
|
|
if (camera == null)
|
|
return;
|
|
|
|
if (!camera.CameraTarget._IsFinite() || !camera.CameraPosition._IsFinite() || !_intendedSetup.Position._IsFinite() || !_smoothedSetup.Position._IsFinite() || !_intendedSetup.Target._IsFinite() || !_smoothedSetup.Target._IsFinite())
|
|
{
|
|
_intendedSetup.Reset();
|
|
_smoothedSetup.Reset();
|
|
camera.CameraTarget = _intendedSetup.Target;
|
|
camera.CameraPosition = _intendedSetup.Position;
|
|
}
|
|
|
|
var cameraNodeModified = !_smoothedSetup.Matches(camera) && !_intendedSetup.Matches(camera);
|
|
if (cameraNodeModified)
|
|
{
|
|
_intendedSetup.SetTo(camera);
|
|
_smoothedSetup.SetTo(camera);
|
|
return;
|
|
}
|
|
|
|
_viewAxis.ComputeForCamera(camera);
|
|
//_deltaTime = ImGui.GetIO().DeltaTime;
|
|
_deltaTime = (float)(Playback.LastFrameDuration).ClampMax(4/60f);
|
|
|
|
var cameraSwitched = camera != _lastCameraNode;
|
|
if (cameraSwitched)
|
|
{
|
|
_intendedSetup.SetTo(camera);
|
|
_smoothedSetup.SetTo(camera);
|
|
_moveVelocity = Vector3.Zero;
|
|
_lastCameraNode = camera;
|
|
}
|
|
|
|
var wasManipulated = false;
|
|
|
|
if (allowCameraInteraction)
|
|
{
|
|
wasManipulated |= ManipulateCameraByMouse();
|
|
wasManipulated |= ManipulateCameraByKeyboard();
|
|
}
|
|
|
|
var frameTime = ImGui.GetTime();
|
|
|
|
foreach (var device in ManipulationDevices)
|
|
{
|
|
device.ManipulateCamera(_intendedSetup, frameTime);
|
|
}
|
|
|
|
var updateRequired = ComputeSmoothMovement(wasManipulated);
|
|
if (!updateRequired)
|
|
return;
|
|
|
|
camera.CameraPosition = _smoothedSetup.Position;
|
|
camera.CameraTarget = _smoothedSetup.Target;
|
|
}
|
|
|
|
public void ResetView()
|
|
{
|
|
_moveVelocity = Vector3.Zero;
|
|
_intendedSetup.Reset();
|
|
}
|
|
|
|
public void SetView(ICamera camera)
|
|
{
|
|
_moveVelocity = Vector3.Zero;
|
|
_intendedSetup.SetTo(camera);
|
|
_smoothedSetup.SetTo(camera);
|
|
}
|
|
|
|
private bool ComputeSmoothMovement(bool isInteracting)
|
|
{
|
|
var stillDamping = !_smoothedSetup.MatchesSetup(_intendedSetup);
|
|
var stillSliding = _moveVelocity.Length() > CameraInteractionParameters.StopDistanceThreshold;
|
|
var stillOrbiting = _orbitVelocity.Length() > 0.0002f;
|
|
|
|
var cameraIsStillMoving = stillDamping
|
|
|| stillSliding
|
|
|| stillOrbiting
|
|
|| _manipulatedByMouseWheel
|
|
|| _manipulatedByKeyboard;
|
|
|
|
if (!isInteracting && !cameraIsStillMoving)
|
|
{
|
|
_smoothedSetup.SetTo(_intendedSetup);
|
|
_orbitVelocity = Vector2.Zero;
|
|
_moveVelocity = Vector3.Zero;
|
|
return false;
|
|
}
|
|
|
|
if (_orbitVelocity.Length() > 0.0002f)
|
|
{
|
|
ApplyOrbitVelocity(_orbitVelocity);
|
|
_orbitVelocity = Vector2.Lerp(_orbitVelocity, Vector2.Zero, (_deltaTime * CameraInteractionParameters.OrbitHorizontalDamping * 60).ClampMax(1f));
|
|
}
|
|
|
|
var maxVelocityForScale = _viewAxis.ViewDistance.Length() * CameraInteractionParameters.MaxMoveVelocity * UserSettings.Config.CameraSpeed;
|
|
if (_moveVelocity.Length() > maxVelocityForScale)
|
|
{
|
|
_moveVelocity *= maxVelocityForScale / _moveVelocity.Length();
|
|
}
|
|
|
|
if (!_manipulatedByKeyboard)
|
|
{
|
|
_moveVelocity = Vector3.Lerp(_moveVelocity, Vector3.Zero, (_deltaTime * CameraInteractionParameters.CameraMoveDamping * 60f).ClampMax(1f));
|
|
}
|
|
|
|
_intendedSetup.Position += _moveVelocity * _deltaTime;
|
|
_intendedSetup.Target += _moveVelocity * _deltaTime;
|
|
|
|
_smoothedSetup.BlendTo(_intendedSetup, (CameraInteractionParameters.CameraDamping * _deltaTime * 60f).Clamp(0,1));
|
|
|
|
_manipulatedByMouseWheel = false;
|
|
_manipulatedByKeyboard = false;
|
|
return true;
|
|
}
|
|
|
|
private bool ManipulateCameraByMouse()
|
|
{
|
|
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup | ImGuiHoveredFlags.ChildWindows))
|
|
return false;
|
|
|
|
var modified = false;
|
|
modified |= HandleMouseWheel();
|
|
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
|
{
|
|
if (ImGui.GetIO().KeyAlt)
|
|
{
|
|
Pan();
|
|
}
|
|
else if (ImGui.GetIO().KeyCtrl)
|
|
{
|
|
modified |= LookAround();
|
|
}
|
|
else
|
|
{
|
|
var delta = new Vector2(ImGui.GetIO().MouseDelta.X,
|
|
-ImGui.GetIO().MouseDelta.Y);
|
|
if (delta.Length() > 0)
|
|
{
|
|
modified = true;
|
|
_orbitVelocity += delta * _deltaTime * -0.1f;
|
|
}
|
|
}
|
|
}
|
|
else if (ImGui.IsMouseDragging(ImGuiMouseButton.Right) && !CustomComponents.IsDragScrolling && !ScalableCanvas.IsAnyCanvasDragged)
|
|
{
|
|
modified |= Pan();
|
|
}
|
|
else if (ImGui.IsMouseDragging(ImGuiMouseButton.Middle))
|
|
{
|
|
modified |= LookAround();
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
private bool HandleMouseWheel()
|
|
{
|
|
var delta = ImGui.GetIO().MouseWheel;
|
|
if (Math.Abs(delta) < 0.01f)
|
|
return false;
|
|
|
|
var viewDistance = _intendedSetup.Position - _intendedSetup.Target;
|
|
var zoomFactorForCurrentFramerate = 1 + (CameraInteractionParameters.ZoomSpeed * FrameDurationFactor);
|
|
|
|
if (UserSettings.Config.AdjustCameraSpeedWithMouseWheel)
|
|
{
|
|
if (
|
|
ImGui.IsMouseDown(ImGuiMouseButton.Left)
|
|
|| ImGui.IsMouseDown(ImGuiMouseButton.Middle)
|
|
|| ImGui.IsMouseDown(ImGuiMouseButton.Right))
|
|
{
|
|
if (delta < 0)
|
|
{
|
|
UserSettings.Config.CameraSpeed /= zoomFactorForCurrentFramerate;
|
|
}
|
|
|
|
if (delta > 0)
|
|
{
|
|
UserSettings.Config.CameraSpeed *= zoomFactorForCurrentFramerate;
|
|
}
|
|
|
|
_intendedSetup.Position = _intendedSetup.Target + viewDistance;
|
|
_manipulatedByMouseWheel = true;
|
|
}
|
|
else
|
|
{
|
|
var acc = CameraInteractionParameters.CameraAcceleration * UserSettings.Config.CameraSpeed * _deltaTime * 60;
|
|
var sign = delta < 0 ? -1 : 1;
|
|
_moveVelocity += Vector3.Normalize(_viewAxis.ViewDistance) * acc * sign;
|
|
_manipulatedByKeyboard = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (delta < 0)
|
|
{
|
|
viewDistance *= zoomFactorForCurrentFramerate;
|
|
}
|
|
|
|
if (delta > 0)
|
|
{
|
|
viewDistance /= zoomFactorForCurrentFramerate;
|
|
}
|
|
|
|
_intendedSetup.Position = _intendedSetup.Target + viewDistance;
|
|
_manipulatedByMouseWheel = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool LookAround()
|
|
{
|
|
if (!ImGui.IsWindowHovered())
|
|
return false;
|
|
|
|
var dragDelta = ImGui.GetIO().MouseDelta;
|
|
if (dragDelta.Length() == 0)
|
|
return false;
|
|
|
|
var factorX = -dragDelta.X * CameraInteractionParameters.RotateMouseSensitivity * 1.5f;
|
|
var factorY = dragDelta.Y * CameraInteractionParameters.RotateMouseSensitivity * 1.5f;
|
|
var rotAroundY = Matrix4x4.CreateFromAxisAngle(_viewAxis.Up, factorX);
|
|
var rotAroundX = Matrix4x4.CreateFromAxisAngle(_viewAxis.Left, factorY);
|
|
var rot = Matrix4x4.Multiply(rotAroundX, rotAroundY);
|
|
|
|
var viewDir2 = new Vector4(_intendedSetup.Target - _intendedSetup.Position, 0);
|
|
var length = viewDir2.Length();
|
|
|
|
var viewDirRotated = Vector4.Transform(viewDir2, rot);
|
|
viewDirRotated = Vector4.Normalize(viewDirRotated) * length;
|
|
|
|
var newTarget = _intendedSetup.Position + new Vector3(viewDirRotated.X, viewDirRotated.Y, viewDirRotated.Z);
|
|
_intendedSetup.Target = newTarget;
|
|
return true;
|
|
}
|
|
|
|
private void ApplyOrbitVelocity(Vector2 orbitVelocity)
|
|
{
|
|
if (UserSettings.Config.AdjustCameraSpeedWithMouseWheel)
|
|
{
|
|
var rotAroundX = Matrix4x4.CreateFromAxisAngle(_viewAxis.Left, orbitVelocity.Y);
|
|
var rotAroundY = Matrix4x4.CreateFromAxisAngle(_viewAxis.Up, orbitVelocity.X);
|
|
var rot = Matrix4x4.Multiply(rotAroundX, rotAroundY);
|
|
|
|
// The following was an attempt to offset rotation target with cameraSpeed.
|
|
// This approach had too many side effects. I'm leaving it here for reference...
|
|
//
|
|
// var view = _intendedSetup.Target - _intendedSetup.Position;
|
|
// var viewLength = view.Length();
|
|
// var viewDirection = view / viewLength;
|
|
//
|
|
// var rotatedViewDir = Vector3.Transform(viewDirection, rot);
|
|
//
|
|
// var tempTarget = _intendedSetup.Position + viewDirection * UserSettings.Config.CameraSpeed * 3;
|
|
// _intendedSetup.Position = tempTarget - rotatedViewDir * UserSettings.Config.CameraSpeed * 3;
|
|
// _intendedSetup.Target = _intendedSetup.Position + rotatedViewDir * viewLength;
|
|
|
|
var viewDir = _intendedSetup.Target - _intendedSetup.Position;
|
|
var viewDirLength = viewDir.Length();
|
|
viewDir /= viewDirLength;
|
|
|
|
var newViewDir = Vector3.Transform(viewDir, rot);
|
|
var newViewVector = newViewDir * viewDirLength;
|
|
_intendedSetup.Position = _intendedSetup.Target - newViewVector;
|
|
|
|
_intendedSetup.Target = _intendedSetup.Position + newViewDir * UserSettings.Config.CameraSpeed * GraphicsMath.DefaultCameraDistance;
|
|
}
|
|
else
|
|
{
|
|
var viewDir = new Vector4(_intendedSetup.Target - _intendedSetup.Position, 1);
|
|
var viewDirLength = viewDir.Length();
|
|
viewDir /= viewDirLength;
|
|
|
|
var rotAroundX = Matrix4x4.CreateFromAxisAngle(_viewAxis.Left, orbitVelocity.Y);
|
|
var rotAroundY = Matrix4x4.CreateFromAxisAngle(_viewAxis.Up, orbitVelocity.X);
|
|
var rot = Matrix4x4.Multiply(rotAroundX, rotAroundY);
|
|
|
|
var newViewDir = Vector4.Transform(viewDir, rot);
|
|
newViewDir = Vector4.Normalize(newViewDir);
|
|
_intendedSetup.Position = _intendedSetup.Target - new Vector3(newViewDir.X, newViewDir.Y, newViewDir.Z) * viewDirLength;
|
|
}
|
|
}
|
|
|
|
private bool Pan()
|
|
{
|
|
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows))
|
|
return false;
|
|
|
|
var dragDelta = ImGui.GetIO().MouseDelta;
|
|
if (dragDelta.Length() == 0)
|
|
return false;
|
|
|
|
var factorX = dragDelta.X / CameraInteractionParameters.RenderWindowHeight;
|
|
var factorY = dragDelta.Y / CameraInteractionParameters.RenderWindowHeight;
|
|
|
|
var length = (_intendedSetup.Target - _intendedSetup.Position).Length() * UserSettings.Config.CameraSpeed;
|
|
var delta = _viewAxis.Left * factorX * length
|
|
+ _viewAxis.Up * factorY * length;
|
|
|
|
_intendedSetup.Position += delta;
|
|
_intendedSetup.Target += delta;
|
|
return false;
|
|
}
|
|
|
|
private bool ManipulateCameraByKeyboard()
|
|
{
|
|
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows) || ImGui.GetIO().KeyCtrl)
|
|
return false;
|
|
|
|
if (!ImGui.IsWindowFocused(ImGuiFocusedFlags.ChildWindows))
|
|
return false;
|
|
|
|
|
|
var acc = CameraInteractionParameters.CameraAcceleration * UserSettings.Config.CameraSpeed * _deltaTime * 60;
|
|
var wasModified = false;
|
|
|
|
if (UserActions.CameraLeft.Triggered())
|
|
{
|
|
_moveVelocity += _viewAxis.Left * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraRight.Triggered())
|
|
{
|
|
_moveVelocity -= _viewAxis.Left * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraForward.Triggered())
|
|
{
|
|
_moveVelocity += Vector3.Normalize(_viewAxis.ViewDistance) * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraBackward.Triggered())
|
|
{
|
|
_moveVelocity -= Vector3.Normalize(_viewAxis.ViewDistance) * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraUp.Triggered())
|
|
{
|
|
_moveVelocity += _viewAxis.Up * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraDown.Triggered())
|
|
{
|
|
_moveVelocity -= _viewAxis.Up * acc;
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraReset.Triggered())
|
|
{
|
|
_moveVelocity = Vector3.Zero;
|
|
_intendedSetup.Reset();
|
|
wasModified = true;
|
|
}
|
|
|
|
if (UserActions.CameraFocusSelection.Triggered())
|
|
{
|
|
_moveVelocity = Vector3.Zero;
|
|
_intendedSetup.Target = TransformGizmoHandling.GetLatestSelectionCenter();
|
|
wasModified = true;
|
|
}
|
|
|
|
_manipulatedByKeyboard |= wasModified;
|
|
return wasModified;
|
|
}
|
|
|
|
private struct ViewAxis
|
|
{
|
|
public Vector3 Up;
|
|
public Vector3 Left;
|
|
public Vector3 ViewDistance;
|
|
|
|
public void ComputeForCamera(ICamera camera)
|
|
{
|
|
ViewDistance = camera.CameraTarget - camera.CameraPosition;
|
|
|
|
var worldUp = Vector3.UnitY;
|
|
var rolledUp = Vector3.Normalize(Vector3.Transform(worldUp, Matrix4x4.CreateFromAxisAngle(ViewDistance, camera.CameraRoll)));
|
|
|
|
Left = Vector3.Normalize(Vector3.Cross(rolledUp, ViewDistance));
|
|
Up = Vector3.Normalize(Vector3.Cross(ViewDistance, Left));
|
|
}
|
|
}
|
|
|
|
public static void ResetCamera(ICamera cam)
|
|
{
|
|
cam.CameraPosition = new Vector3(0, 0, GraphicsMath.DefaultCameraDistance);
|
|
cam.CameraTarget = Vector3.Zero;
|
|
cam.CameraRoll = 0;
|
|
}
|
|
|
|
private ViewAxis _viewAxis;
|
|
private readonly CameraSetup _smoothedSetup = new();
|
|
private readonly CameraSetup _intendedSetup = new();
|
|
|
|
private static float FrameDurationFactor => ImGui.GetIO().DeltaTime;
|
|
private bool _manipulatedByMouseWheel;
|
|
private bool _manipulatedByKeyboard;
|
|
|
|
private Vector3 _moveVelocity;
|
|
private Vector2 _orbitVelocity;
|
|
private ICamera? _lastCameraNode;
|
|
private float _deltaTime;
|
|
} |