492 lines
17 KiB
C#
492 lines
17 KiB
C#
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using FlaxEditor.SceneGraph;
|
|
using FlaxEditor.SceneGraph.Actors;
|
|
using FlaxEditor.States;
|
|
using FlaxEditor.Viewport;
|
|
using FlaxEditor.Viewport.Cameras;
|
|
using FlaxEditor.Viewport.Widgets;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
|
|
namespace FlaxEditor.Windows
|
|
{
|
|
/// <summary>
|
|
/// Main editor window used to modify scene objects. Provides Gizmos and camera viewport navigation.
|
|
/// </summary>
|
|
/// <seealso cref="FlaxEditor.Windows.SceneEditorWindow" />
|
|
public sealed class EditGameWindow : SceneEditorWindow
|
|
{
|
|
/// <summary>
|
|
/// Camera preview output control.
|
|
/// </summary>
|
|
public class CameraPreview : RenderOutputControl
|
|
{
|
|
private bool _isPinned;
|
|
private Button _pinButton;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this preview is pinned.
|
|
/// </summary>
|
|
public bool IsPinned
|
|
{
|
|
get => _isPinned;
|
|
set
|
|
{
|
|
if (_isPinned != value)
|
|
{
|
|
_isPinned = value;
|
|
UpdatePinButton();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the camera.
|
|
/// </summary>
|
|
public Camera Camera
|
|
{
|
|
get => Task.Camera;
|
|
set => Task.Camera = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="CameraPreview"/> class.
|
|
/// </summary>
|
|
public CameraPreview()
|
|
: base(FlaxEngine.Object.New<SceneRenderTask>())
|
|
{
|
|
// Don't steal focus
|
|
AutoFocus = false;
|
|
|
|
const float PinSize = 12.0f;
|
|
const float PinMargin = 2.0f;
|
|
|
|
_pinButton = new Button
|
|
{
|
|
AnchorPreset = AnchorPresets.TopRight,
|
|
Parent = this,
|
|
Bounds = new Rectangle(Width - PinSize - PinMargin, PinMargin, PinSize, PinSize),
|
|
};
|
|
_pinButton.Clicked += () => IsPinned = !IsPinned;
|
|
UpdatePinButton();
|
|
|
|
Task.Begin += OnTaskBegin;
|
|
}
|
|
|
|
private void OnTaskBegin(RenderTask task, GPUContext context)
|
|
{
|
|
var sceneTask = (SceneRenderTask)task;
|
|
|
|
// Copy camera view parameters for the scene rendering
|
|
var view = sceneTask.View;
|
|
var viewport = new FlaxEngine.Viewport(Vector2.Zero, sceneTask.Buffers.Size);
|
|
view.CopyFrom(Camera, ref viewport);
|
|
sceneTask.View = view;
|
|
}
|
|
|
|
private void UpdatePinButton()
|
|
{
|
|
if (_isPinned)
|
|
{
|
|
_pinButton.Text = "-";
|
|
_pinButton.TooltipText = "Unpin preview";
|
|
}
|
|
else
|
|
{
|
|
_pinButton.Text = "+";
|
|
_pinButton.TooltipText = "Pin preview";
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Draw()
|
|
{
|
|
base.Draw();
|
|
|
|
// Draw frame
|
|
Render2D.DrawRectangle(new Rectangle(Vector2.Zero, Size), Color.Black);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDestroy()
|
|
{
|
|
IsPinned = false;
|
|
Camera = null;
|
|
_pinButton = null;
|
|
|
|
base.OnDestroy();
|
|
}
|
|
}
|
|
|
|
private readonly List<CameraPreview> _previews = new List<CameraPreview>();
|
|
private Actor _pilotActor;
|
|
private BoundingBox _pilotBounds;
|
|
private Transform _pilotStart;
|
|
private ViewportWidgetButton _pilotWidget;
|
|
|
|
/// <summary>
|
|
/// The viewport control.
|
|
/// </summary>
|
|
public readonly MainEditorGizmoViewport Viewport;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="EditGameWindow"/> class.
|
|
/// </summary>
|
|
/// <param name="editor">The editor.</param>
|
|
public EditGameWindow(Editor editor)
|
|
: base(editor, true, ScrollBars.None)
|
|
{
|
|
Title = "Editor";
|
|
|
|
// Create viewport
|
|
Viewport = new MainEditorGizmoViewport(editor)
|
|
{
|
|
Parent = this,
|
|
NearPlane = 8.0f,
|
|
FarPlane = 20000.0f
|
|
};
|
|
Viewport.Task.ViewFlags = ViewFlags.DefaultEditor;
|
|
|
|
Editor.Scene.ActorRemoved += SceneOnActorRemoved;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether actor pilot feature is active and in use.
|
|
/// </summary>
|
|
public bool IsPilotActorActive => _pilotActor;
|
|
|
|
/// <summary>
|
|
/// Gets the current actor that is during pilot action.
|
|
/// </summary>
|
|
public Actor ActorToPilot => _pilotActor;
|
|
|
|
/// <summary>
|
|
/// Moves viewport to the actor and attaches actor to the viewport to pilot it over the scene.
|
|
/// </summary>
|
|
/// <param name="actor">The actor to pilot.</param>
|
|
public void PilotActor(Actor actor)
|
|
{
|
|
if (_pilotActor)
|
|
EndPilot();
|
|
|
|
_pilotActor = actor;
|
|
_pilotStart = actor.Transform;
|
|
_pilotBounds = actor.BoxWithChildren;
|
|
Viewport.ViewTransform = _pilotStart;
|
|
if (_pilotWidget == null)
|
|
{
|
|
var container = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft)
|
|
{
|
|
Parent = Viewport,
|
|
};
|
|
_pilotWidget = new ViewportWidgetButton(string.Empty, SpriteHandle.Invalid)
|
|
{
|
|
Parent = container,
|
|
};
|
|
_pilotWidget.Clicked += button => EndPilot();
|
|
container.UnlockChildrenRecursive();
|
|
}
|
|
else
|
|
{
|
|
_pilotWidget.Parent.Visible = true;
|
|
}
|
|
_pilotWidget.Text = string.Format("Piloting actor: {0} (click to stop)", actor.Name);
|
|
_pilotWidget.PerformLayout();
|
|
_pilotWidget.Parent.PerformLayout();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends the actor piloting mode.
|
|
/// </summary>
|
|
public void EndPilot()
|
|
{
|
|
if (_pilotActor == null)
|
|
return;
|
|
|
|
if (Editor.Undo.Enabled)
|
|
{
|
|
ActorNode node = Editor.Scene.GetActorNode(_pilotActor);
|
|
bool navigationDirty = node.AffectsNavigationWithChildren;
|
|
var action = new TransformObjectsAction
|
|
(
|
|
new List<SceneGraphNode> { node },
|
|
new List<Transform> { _pilotStart },
|
|
ref _pilotBounds,
|
|
navigationDirty
|
|
);
|
|
Editor.Undo.AddAction(action);
|
|
}
|
|
_pilotActor = null;
|
|
_pilotWidget.Parent.Visible = false;
|
|
}
|
|
|
|
private void SceneOnActorRemoved(ActorNode actorNode)
|
|
{
|
|
if (actorNode is CameraNode cameraNode)
|
|
{
|
|
// Remove previews using this camera
|
|
HideCameraPreview((Camera)cameraNode.Actor);
|
|
}
|
|
if (actorNode.Actor == _pilotActor)
|
|
{
|
|
EndPilot();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves the viewport to visualize selected actors.
|
|
/// </summary>
|
|
public void ShowSelectedActors()
|
|
{
|
|
if (Viewport.UseOrthographicProjection)
|
|
{
|
|
var orient = Viewport.ViewOrientation;
|
|
((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents, ref orient);
|
|
}
|
|
else
|
|
((FPSCamera)Viewport.ViewportCamera).ShowActors(Viewport.TransformGizmo.SelectedParents);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the camera previews.
|
|
/// </summary>
|
|
private void UpdateCameraPreview()
|
|
{
|
|
// Disable rendering preview during GI baking
|
|
if (Editor.StateMachine.CurrentState.IsPerformanceHeavy)
|
|
{
|
|
HideAllCameraPreviews();
|
|
return;
|
|
}
|
|
|
|
var selection = Editor.SceneEditing.Selection;
|
|
|
|
// Hide unpinned previews for which camera being previews is not selected
|
|
for (int i = 0; i < _previews.Count; i++)
|
|
{
|
|
if (_previews[i].IsPinned)
|
|
continue;
|
|
var camera = _previews[i].Camera;
|
|
var cameraNode = Editor.Scene.GetActorNode(camera);
|
|
if (cameraNode == null || !selection.Contains(cameraNode))
|
|
{
|
|
// Hide it
|
|
HideCameraPreview(_previews[i--]);
|
|
}
|
|
}
|
|
|
|
if (Editor.Options.Options.Interface.ShowSelectedCameraPreview)
|
|
{
|
|
// Find any selected cameras and create previews for them
|
|
for (int i = 0; i < selection.Count; i++)
|
|
{
|
|
if (selection[i] is CameraNode cameraNode)
|
|
{
|
|
// Check limit for cameras
|
|
if (_previews.Count >= 8)
|
|
break;
|
|
|
|
var camera = (Camera)cameraNode.Actor;
|
|
var preview = _previews.FirstOrDefault(x => x.Camera == camera);
|
|
if (preview == null)
|
|
{
|
|
// Show it
|
|
preview = new CameraPreview
|
|
{
|
|
Camera = camera,
|
|
Parent = this
|
|
};
|
|
_previews.Add(preview);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update previews locations
|
|
int count = _previews.Count;
|
|
if (count > 0)
|
|
{
|
|
// Update view dimensions and check if we can show it
|
|
const float aspectRatio = 16.0f / 9.0f;
|
|
const float minHeight = 20;
|
|
const float minWidth = minHeight * aspectRatio;
|
|
const float maxHeight = 150;
|
|
const float maxWidth = maxHeight * aspectRatio;
|
|
const float viewSpaceMaxPercentage = 0.7f;
|
|
const float margin = 10;
|
|
Vector2 totalSize = Size * viewSpaceMaxPercentage - margin;
|
|
Vector2 singleSize = totalSize / count - count * margin;
|
|
float sizeX = Mathf.Clamp(singleSize.X, minWidth, maxWidth);
|
|
float sizeY = sizeX / aspectRatio;
|
|
singleSize = new Vector2(sizeX, sizeY);
|
|
int countPerX = Mathf.FloorToInt(totalSize.X / singleSize.X);
|
|
int countPerY = Mathf.FloorToInt(totalSize.Y / singleSize.Y);
|
|
int index = 0;
|
|
for (int y = 1; y <= countPerY; y++)
|
|
{
|
|
for (int x = 1; x <= countPerX; x++)
|
|
{
|
|
if (index == count)
|
|
break;
|
|
|
|
var pos = Size - (singleSize + margin) * new Vector2(x, y);
|
|
_previews[index++].Bounds = new Rectangle(pos, singleSize);
|
|
}
|
|
|
|
if (index == count)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hides the camera preview that uses given camera.
|
|
/// </summary>
|
|
/// <param name="camera">The camera to hide.</param>
|
|
private void HideCameraPreview(Camera camera)
|
|
{
|
|
var preview = _previews.FirstOrDefault(x => x.Camera == camera);
|
|
if (preview != null)
|
|
HideCameraPreview(preview);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hides the camera preview.
|
|
/// </summary>
|
|
/// <param name="preview">The preview to hide.</param>
|
|
private void HideCameraPreview(CameraPreview preview)
|
|
{
|
|
_previews.Remove(preview);
|
|
preview.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hides all the camera previews.
|
|
/// </summary>
|
|
private void HideAllCameraPreviews()
|
|
{
|
|
while (_previews.Count > 0)
|
|
HideCameraPreview(_previews[0]);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnSceneUnloading(Scene scene, Guid sceneId)
|
|
{
|
|
for (int i = 0; i < _previews.Count; i++)
|
|
{
|
|
if (_previews[i].Camera.Scene == scene)
|
|
HideCameraPreview(_previews[i--]);
|
|
}
|
|
|
|
if (_pilotActor && _pilotActor.Scene == scene)
|
|
{
|
|
EndPilot();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(float deltaTime)
|
|
{
|
|
// TODO: call camera preview update only on selection change, or state change
|
|
UpdateCameraPreview();
|
|
|
|
if (Root.GetKeyDown(KeyboardKeys.F12))
|
|
{
|
|
Viewport.TakeScreenshot();
|
|
}
|
|
|
|
base.Update(deltaTime);
|
|
|
|
if (_pilotActor)
|
|
{
|
|
var transform = Viewport.ViewTransform;
|
|
transform.Scale = _pilotActor.Scale;
|
|
_pilotActor.Transform = transform;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDestroy()
|
|
{
|
|
HideAllCameraPreviews();
|
|
EndPilot();
|
|
_pilotWidget = null;
|
|
|
|
base.OnDestroy();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool UseLayoutData => true;
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutSerialize(XmlWriter writer)
|
|
{
|
|
writer.WriteAttributeString("GridEnabled", Viewport.Grid.Enabled.ToString());
|
|
writer.WriteAttributeString("ShowFpsCounter", Viewport.ShowFpsCounter.ToString());
|
|
writer.WriteAttributeString("ShowNavigation", Viewport.ShowNavigation.ToString());
|
|
writer.WriteAttributeString("NearPlane", Viewport.NearPlane.ToString());
|
|
writer.WriteAttributeString("FarPlane", Viewport.FarPlane.ToString());
|
|
writer.WriteAttributeString("FieldOfView", Viewport.FieldOfView.ToString());
|
|
writer.WriteAttributeString("MovementSpeed", Viewport.MovementSpeed.ToString());
|
|
writer.WriteAttributeString("OrthographicScale", Viewport.OrthographicScale.ToString());
|
|
writer.WriteAttributeString("UseOrthographicProjection", Viewport.UseOrthographicProjection.ToString());
|
|
writer.WriteAttributeString("ViewFlags", ((long)Viewport.Task.View.Flags).ToString());
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutDeserialize(XmlElement node)
|
|
{
|
|
if (bool.TryParse(node.GetAttribute("GridEnabled"), out bool value1))
|
|
Viewport.Grid.Enabled = value1;
|
|
|
|
if (bool.TryParse(node.GetAttribute("ShowFpsCounter"), out value1))
|
|
Viewport.ShowFpsCounter = value1;
|
|
|
|
if (bool.TryParse(node.GetAttribute("ShowNavigation"), out value1))
|
|
Viewport.ShowNavigation = value1;
|
|
|
|
if (float.TryParse(node.GetAttribute("NearPlane"), out float value2))
|
|
Viewport.NearPlane = value2;
|
|
|
|
if (float.TryParse(node.GetAttribute("FarPlane"), out value2))
|
|
Viewport.FarPlane = value2;
|
|
|
|
if (float.TryParse(node.GetAttribute("FieldOfView"), out value2))
|
|
Viewport.FieldOfView = value2;
|
|
|
|
if (float.TryParse(node.GetAttribute("MovementSpeed"), out value2))
|
|
Viewport.MovementSpeed = value2;
|
|
|
|
if (float.TryParse(node.GetAttribute("OrthographicScale"), out value2))
|
|
Viewport.OrthographicScale = value2;
|
|
|
|
if (bool.TryParse(node.GetAttribute("UseOrthographicProjection"), out value1))
|
|
Viewport.UseOrthographicProjection = value1;
|
|
|
|
if (long.TryParse(node.GetAttribute("ViewFlags"), out long value3))
|
|
Viewport.Task.ViewFlags = (ViewFlags)value3;
|
|
|
|
// Reset view flags if opening with different engine version (ViewFlags enum could be modified)
|
|
if (Editor.LastProjectOpenedEngineBuild != Globals.EngineBuildNumber)
|
|
Viewport.Task.ViewFlags = ViewFlags.DefaultEditor;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnLayoutDeserialize()
|
|
{
|
|
Viewport.Grid.Enabled = true;
|
|
Viewport.NearPlane = 8.0f;
|
|
Viewport.FarPlane = 20000.0f;
|
|
Viewport.FieldOfView = 60.0f;
|
|
Viewport.MovementSpeed = 1.0f;
|
|
Viewport.OrthographicScale = 1.0f;
|
|
Viewport.UseOrthographicProjection = false;
|
|
}
|
|
}
|
|
}
|