446 lines
16 KiB
C#
446 lines
16 KiB
C#
#nullable enable
|
|
using ImGuiNET;
|
|
using T3.Core.Animation;
|
|
using T3.Core.DataTypes;
|
|
using T3.Core.Operator;
|
|
using T3.Core.Operator.Interfaces;
|
|
using T3.Editor.Gui.Interaction;
|
|
using T3.Editor.Gui.Interaction.Camera;
|
|
using T3.Editor.Gui.Interaction.TransformGizmos;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.UiModel.ProjectHandling;
|
|
using T3.Editor.UiModel.Selection;
|
|
|
|
namespace T3.Editor.Gui.Windows.Output;
|
|
|
|
/// <summary>
|
|
/// Handles switching and pinning of interactive <see cref="ICamera"/>s for views.
|
|
/// It uses four different modes and draws the dropdown to switch between them
|
|
/// </summary>
|
|
internal sealed class CameraSelectionHandling
|
|
{
|
|
public ICamera? CameraForRendering { get; private set; }
|
|
public bool BypassCamera { get; private set; }
|
|
|
|
public bool PreventCameraInteraction { get; private set; }
|
|
|
|
public bool PreventImageCanvasInteraction { get; private set; }
|
|
|
|
private enum ControlModes
|
|
{
|
|
/// <summary>
|
|
/// If selected op is Camera, manipulate this.
|
|
/// else
|
|
/// if playback is running
|
|
/// move ViewerCamera to first camera in graph
|
|
/// else
|
|
/// manipulate ViewerCamera
|
|
/// </summary>
|
|
SceneViewerFollowing,
|
|
|
|
/// <summary>
|
|
/// Always use 3rd person view of ViewerCamera, regardless of selected Operator.
|
|
/// </summary>
|
|
UseViewer,
|
|
|
|
/// <summary>
|
|
/// If rendered, op is Command-Type and manipulate first found camera in graph
|
|
/// or if rendered Op is ImageType and selected Op is Camera manipulate camera
|
|
/// </summary>
|
|
AutoUseFirstCam,
|
|
|
|
/// <summary>
|
|
/// Uses the camera picked from dropdown for manipulation.
|
|
/// If camera wasn't recently updated, fall back to last other mode.
|
|
/// </summary>
|
|
PickedACamera,
|
|
}
|
|
|
|
public CameraSelectionHandling()
|
|
{
|
|
_getPlayback = () => Playback.Current;
|
|
}
|
|
|
|
public CameraSelectionHandling(NodeSelection nodeSelection, Func<Playback> getPlayback)
|
|
{
|
|
_nodeSelection = nodeSelection;
|
|
_getPlayback = getPlayback;
|
|
}
|
|
|
|
private readonly NodeSelection? _nodeSelection;
|
|
|
|
// preparation for each window to have its own playback
|
|
private readonly Func<Playback> _getPlayback;
|
|
|
|
private NodeSelection? NodeSelection => _nodeSelection ?? ProjectView.Focused?.NodeSelection;
|
|
|
|
public void Update(Instance? drawnInstance, Type? drawnType, bool preventInteractions = false)
|
|
{
|
|
var currentPlayback = _getPlayback();
|
|
var timeInBars = currentPlayback.TimeInBars;
|
|
_hasAnimationTimeChanged = Math.Abs(_lastUpdateTime - timeInBars) > 0.001f;
|
|
_lastUpdateTime = timeInBars;
|
|
PreventImageCanvasInteraction = false;
|
|
|
|
_drawnInstance = drawnInstance;
|
|
if (drawnInstance == null)
|
|
{
|
|
PreventCameraInteraction = true;
|
|
CameraForRendering = _outputWindowViewCamera;
|
|
_recentlyUsedCameras.Clear();
|
|
return;
|
|
}
|
|
|
|
// Update recently used cameras (this is expensive!)
|
|
UpdateRecentCameras(drawnInstance);
|
|
|
|
ICamera? cameraForManipulation = null;
|
|
CameraForRendering = null;
|
|
|
|
PreventCameraInteraction = preventInteractions;
|
|
|
|
if (_controlMode == ControlModes.PickedACamera)
|
|
{
|
|
var isPickStillValid = false;
|
|
foreach (var recentCam in _recentlyUsedCameras)
|
|
{
|
|
if (recentCam is not Instance camInstance)
|
|
continue;
|
|
|
|
if (camInstance.SymbolChildId == _pickedCameraId)
|
|
{
|
|
cameraForManipulation = recentCam;
|
|
CameraForRendering = _outputWindowViewCamera;
|
|
isPickStillValid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isPickStillValid)
|
|
{
|
|
_controlMode = _lastControlMode;
|
|
}
|
|
}
|
|
|
|
_drawnTypeIsCommand = drawnType == typeof(Command);
|
|
|
|
|
|
switch (_controlMode)
|
|
{
|
|
case ControlModes.SceneViewerFollowing:
|
|
{
|
|
if (!_drawnTypeIsCommand)
|
|
{
|
|
PreventCameraInteraction = true;
|
|
}
|
|
|
|
cameraForManipulation = _outputWindowViewCamera;
|
|
|
|
if (_firstCamInGraph != null)
|
|
{
|
|
PreventImageCanvasInteraction = true;
|
|
var isCamOpSelected = IsCamOpSelected();
|
|
if (isCamOpSelected)
|
|
{
|
|
cameraForManipulation = _firstCamInGraph;
|
|
}
|
|
|
|
if (_hasAnimationTimeChanged)
|
|
{
|
|
_outputWindowViewCamera.CameraPosition = _firstCamInGraph.CameraPosition;
|
|
_outputWindowViewCamera.CameraTarget = _firstCamInGraph.CameraTarget;
|
|
_outputWindowViewCamera.CameraRoll = _firstCamInGraph.CameraRoll;
|
|
}
|
|
}
|
|
|
|
BypassCamera = true;
|
|
break;
|
|
}
|
|
|
|
case ControlModes.UseViewer:
|
|
cameraForManipulation = _outputWindowViewCamera;
|
|
BypassCamera = true;
|
|
break;
|
|
|
|
case ControlModes.AutoUseFirstCam:
|
|
{
|
|
if (_firstCamInGraph != null)
|
|
{
|
|
var isCamOpSelected = IsCamOpSelected();
|
|
if (!isCamOpSelected)
|
|
{
|
|
|
|
if (_drawnTypeIsCommand)
|
|
{
|
|
cameraForManipulation = _outputWindowViewCamera;
|
|
}
|
|
else
|
|
{
|
|
cameraForManipulation = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PreventImageCanvasInteraction = true;
|
|
cameraForManipulation = _firstCamInGraph;
|
|
}
|
|
|
|
CameraForRendering = _outputWindowViewCamera;
|
|
//CameraInteraction.ResetCamera(_outputWindowViewCamera);
|
|
}
|
|
else
|
|
{
|
|
cameraForManipulation = _outputWindowViewCamera;
|
|
}
|
|
|
|
BypassCamera = false;
|
|
break;
|
|
}
|
|
case ControlModes.PickedACamera:
|
|
PreventImageCanvasInteraction = true;
|
|
break;
|
|
}
|
|
|
|
if (_controlMode != ControlModes.PickedACamera)
|
|
{
|
|
//CameraForRendering = cameraForManipulation;
|
|
}
|
|
else
|
|
{
|
|
//PreventImageCanvasInteraction = true;
|
|
}
|
|
|
|
CameraForRendering ??= _outputWindowViewCamera;
|
|
|
|
if (TransformGizmoHandling.IsDragging)
|
|
PreventCameraInteraction = true;
|
|
|
|
if (cameraForManipulation != null)
|
|
{
|
|
_cameraInteraction.Update(cameraForManipulation, !PreventCameraInteraction);
|
|
}
|
|
|
|
bool IsCamOpSelected()
|
|
{
|
|
var firstSelectedInstance = NodeSelection?.GetSelectedInstanceWithoutComposition();
|
|
return firstSelectedInstance is ICamera camera && camera == _firstCamInGraph;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches for all recently updated (evaluated) camera ops in the current operator
|
|
/// </summary>
|
|
private void UpdateRecentCameras(Instance drawnInstance)
|
|
{
|
|
_recentlyUsedCameras.Clear();
|
|
|
|
var parentInstance = drawnInstance.Parent;
|
|
if (parentInstance != null)
|
|
{
|
|
var children = parentInstance.Children.Values;
|
|
foreach (var child in children)
|
|
{
|
|
if (child is not ICamera cam2)
|
|
continue;
|
|
|
|
if (child.Outputs[0].DirtyFlag.FramesSinceLastUpdate > 1)
|
|
continue;
|
|
|
|
_recentlyUsedCameras.Add(cam2);
|
|
}
|
|
}
|
|
|
|
var selectedInstance = NodeSelection?.GetSelectedInstanceWithoutComposition();
|
|
if (selectedInstance is ICamera selectedCamera
|
|
&& !_recentlyUsedCameras.Contains(selectedCamera))
|
|
{
|
|
_recentlyUsedCameras.Add(selectedCamera);
|
|
}
|
|
|
|
_firstCamInGraph = _recentlyUsedCameras.Count > 0 ? _recentlyUsedCameras[0] : null;
|
|
}
|
|
|
|
public void DrawCameraControlSelection()
|
|
{
|
|
if (_drawnInstance == null)
|
|
return;
|
|
|
|
ImGui.SetNextItemWidth(145*T3Ui.UiScaleFactor);
|
|
|
|
var label = String.Empty;
|
|
|
|
switch (_controlMode)
|
|
{
|
|
case ControlModes.AutoUseFirstCam:
|
|
label = "Auto";
|
|
break;
|
|
|
|
case ControlModes.SceneViewerFollowing:
|
|
label = "Follow";
|
|
break;
|
|
|
|
case ControlModes.UseViewer:
|
|
label = "Viewer";
|
|
break;
|
|
|
|
case ControlModes.PickedACamera:
|
|
label = "Locked to Op";
|
|
break;
|
|
}
|
|
|
|
var isAuto = _controlMode == ControlModes.AutoUseFirstCam;
|
|
|
|
var width = ImGui.GetFrameHeight() * 5;
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
|
|
var labelColor = isAuto ? UiColors.TextMuted : UiColors.ForegroundFull;
|
|
ImGui.PushStyleColor(ImGuiCol.Text, labelColor.Rgba);
|
|
|
|
var isClicked = ImGui.Button($" {label}##CameraMode", new Vector2(width, ImGui.GetFrameHeight()));
|
|
ImGui.PopStyleColor();
|
|
ImGui.PopStyleVar();
|
|
var min = ImGui.GetItemRectMin();
|
|
var max = ImGui.GetItemRectMax();
|
|
if (isClicked)
|
|
{
|
|
ImGui.OpenPopup("cameraPopup");
|
|
ImGui.SetNextWindowPos(new Vector2(min.X, max.Y));
|
|
}
|
|
|
|
var verticalOffset = (ImGui.GetFrameHeight() - Icons.FontSize) /2 + 1;
|
|
Icons.DrawIconAtScreenPosition(Icon.Camera, min + new Vector2(5 * T3Ui.UiScaleFactor, verticalOffset), ImGui.GetWindowDrawList(), labelColor);
|
|
|
|
if (ImGui.BeginPopup("cameraPopup", ImGuiWindowFlags.None))
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(6, 6) * T3Ui.UiScaleFactor);
|
|
|
|
if(CustomComponents.DrawMenuItem(42+0, "Auto",
|
|
isChecked:_controlMode == ControlModes.AutoUseFirstCam,
|
|
isEnabled:true))
|
|
{
|
|
_controlMode = ControlModes.AutoUseFirstCam;
|
|
_pickedCameraId = Guid.Empty;
|
|
}
|
|
|
|
CustomComponents.TooltipForLastItem("Shows the first scene camera. For manipulation select the camera operator in graph or in the list below.");
|
|
if (ImGui.IsItemHovered() && _firstCamInGraph is Instance camInstance && NodeSelection != null)
|
|
FrameStats.AddHoveredId(camInstance.SymbolChildId);
|
|
|
|
if(CustomComponents.DrawMenuItem(42+1, "Viewer",
|
|
isChecked:_controlMode == ControlModes.UseViewer,
|
|
isEnabled:_drawnTypeIsCommand))
|
|
{
|
|
_controlMode = ControlModes.UseViewer;
|
|
_pickedCameraId = Guid.Empty;
|
|
}
|
|
|
|
CustomComponents.TooltipForLastItem("Ignores scene cameras. This can be useful in combination with [ShowCamGizmos].");
|
|
|
|
if(CustomComponents.DrawMenuItem(42+2, "Viewer (Following)",
|
|
isChecked:_controlMode == ControlModes.SceneViewerFollowing,
|
|
isEnabled:_drawnTypeIsCommand))
|
|
|
|
{
|
|
_controlMode = ControlModes.SceneViewerFollowing;
|
|
_pickedCameraId = Guid.Empty;
|
|
}
|
|
|
|
if (ImGui.IsItemHovered() && _firstCamInGraph is Instance camInstance2 && NodeSelection != null)
|
|
FrameStats.AddHoveredId(camInstance2.SymbolChildId);
|
|
|
|
CustomComponents
|
|
.TooltipForLastItem("During playback the scene viewer is following the scene camera. Otherwise it can be independently manipulated without affecting the scene camera.");
|
|
|
|
if (_recentlyUsedCameras.Count > 0)
|
|
{
|
|
ImGui.Separator();
|
|
CustomComponents.MenuGroupHeader("Active Cameras");
|
|
}
|
|
|
|
if (_drawnInstance?.Parent != null)
|
|
{
|
|
for (var index = 0; index < _recentlyUsedCameras.Count; index++)
|
|
{
|
|
var cam = _recentlyUsedCameras[index];
|
|
if (cam is not Instance cameraInstance ||
|
|
!_drawnInstance.Parent.Symbol.Children.TryGetValue(cameraInstance.SymbolChildId, out var symbolChild))
|
|
continue;
|
|
|
|
ImGui.PushID(cameraInstance.SymbolChildId.GetHashCode());
|
|
{
|
|
if (symbolChild != null
|
|
&& CustomComponents.DrawMenuItem(9999+index, symbolChild.ReadableName, isChecked:cameraInstance.SymbolChildId == _pickedCameraId)
|
|
)
|
|
{
|
|
_lastControlMode = _controlMode;
|
|
_controlMode = ControlModes.PickedACamera;
|
|
_pickedCameraId = cameraInstance.SymbolChildId;
|
|
GraphCanvasUtils.SelectAndCenterChildIdInView(symbolChild.Id);
|
|
}
|
|
|
|
if (ImGui.IsItemHovered() && NodeSelection != null)
|
|
{
|
|
FrameStats.AddHoveredId(cameraInstance.SymbolChildId);
|
|
}
|
|
}
|
|
ImGui.PopID();
|
|
}
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.EndPopup();
|
|
}
|
|
else
|
|
{
|
|
CustomComponents.TooltipForLastItem("Camera control mode",
|
|
"This affects which camera will be manipulated by the view controls. Please also review to the tooltips of the dropdown options.");
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
}
|
|
|
|
public void ResetView()
|
|
{
|
|
_cameraInteraction.ResetView();
|
|
}
|
|
|
|
internal void SaveStateTo(OutputWindowState state)
|
|
{
|
|
state.CameraControlMode = (OutputWindowState.CameraControlModes)(int)_controlMode;
|
|
state.CameraPosition = [_outputWindowViewCamera.CameraPosition.X, _outputWindowViewCamera.CameraPosition.Y, _outputWindowViewCamera.CameraPosition.Z];
|
|
state.CameraTarget = [_outputWindowViewCamera.CameraTarget.X, _outputWindowViewCamera.CameraTarget.Y, _outputWindowViewCamera.CameraTarget.Z];
|
|
state.CameraRoll = _outputWindowViewCamera.CameraRoll;
|
|
}
|
|
|
|
internal void LoadStateFrom(OutputWindowState state)
|
|
{
|
|
_controlMode = (ControlModes)(int)state.CameraControlMode;
|
|
if (state.CameraPosition.Length == 3)
|
|
{
|
|
_outputWindowViewCamera.CameraPosition = new Vector3(state.CameraPosition[0], state.CameraPosition[1], state.CameraPosition[2]);
|
|
}
|
|
if (state.CameraTarget.Length == 3)
|
|
{
|
|
_outputWindowViewCamera.CameraTarget = new Vector3(state.CameraTarget[0], state.CameraTarget[1], state.CameraTarget[2]);
|
|
}
|
|
_outputWindowViewCamera.CameraRoll = state.CameraRoll;
|
|
_cameraInteraction.SetView(_outputWindowViewCamera);
|
|
}
|
|
|
|
private bool _drawnTypeIsCommand;
|
|
private ControlModes _controlMode = ControlModes.AutoUseFirstCam;
|
|
private readonly ViewCamera _outputWindowViewCamera = new();
|
|
private readonly CameraInteraction _cameraInteraction = new();
|
|
|
|
private double _lastUpdateTime;
|
|
private bool _hasAnimationTimeChanged;
|
|
|
|
private Guid _pickedCameraId = Guid.Empty;
|
|
|
|
private Instance? _drawnInstance;
|
|
private readonly List<ICamera> _recentlyUsedCameras = [];
|
|
|
|
private ControlModes _lastControlMode;
|
|
|
|
private ICamera? _firstCamInGraph;
|
|
} |