You're breathtaking!
This commit is contained in:
118
Source/Editor/Viewport/Cameras/ArcBallCamera.cs
Normal file
118
Source/Editor/Viewport/Cameras/ArcBallCamera.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ViewportCamera"/> that orbits around the fixed location.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Cameras.ViewportCamera" />
|
||||
[HideInEditor]
|
||||
public class ArcBallCamera : ViewportCamera
|
||||
{
|
||||
private Vector3 _orbitCenter;
|
||||
private float _orbitRadius;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the orbit center.
|
||||
/// </summary>
|
||||
public Vector3 OrbitCenter
|
||||
{
|
||||
get => _orbitCenter;
|
||||
set
|
||||
{
|
||||
_orbitCenter = value;
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the orbit radius.
|
||||
/// </summary>
|
||||
public float OrbitRadius
|
||||
{
|
||||
get => _orbitRadius;
|
||||
set
|
||||
{
|
||||
_orbitRadius = Mathf.Max(value, 0.0001f);
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool UseMovementSpeed => false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArcBallCamera"/> class.
|
||||
/// </summary>
|
||||
public ArcBallCamera()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArcBallCamera"/> class.
|
||||
/// </summary>
|
||||
/// <param name="orbitCenter">The orbit center.</param>
|
||||
/// <param name="radius">The orbit radius.</param>
|
||||
public ArcBallCamera(Vector3 orbitCenter, float radius)
|
||||
{
|
||||
_orbitCenter = orbitCenter;
|
||||
_orbitRadius = radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view direction.
|
||||
/// </summary>
|
||||
/// <param name="direction">The view direction.</param>
|
||||
public void SetView(Vector3 direction)
|
||||
{
|
||||
// Rotate
|
||||
Viewport.ViewDirection = direction;
|
||||
|
||||
// Update view position
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation.
|
||||
/// </summary>
|
||||
/// <param name="orientation">The view rotation.</param>
|
||||
public void SetView(Quaternion orientation)
|
||||
{
|
||||
// Rotate
|
||||
Viewport.ViewOrientation = orientation;
|
||||
|
||||
// Update view position
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
private void UpdatePosition()
|
||||
{
|
||||
// Move camera to look at orbit center point
|
||||
Vector3 localPosition = Viewport.ViewDirection * (-1 * _orbitRadius);
|
||||
Viewport.ViewPosition = _orbitCenter + localPosition;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse)
|
||||
{
|
||||
centerMouse = true;
|
||||
|
||||
EditorViewport.Input input;
|
||||
Viewport.GetInput(out input);
|
||||
|
||||
// Rotate
|
||||
Viewport.YawPitch += mouseDelta;
|
||||
|
||||
// Zoom
|
||||
if (input.IsZooming)
|
||||
{
|
||||
_orbitRadius = Mathf.Clamp(_orbitRadius - (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f), 0.001f, 10000.0f);
|
||||
}
|
||||
|
||||
// Update view position
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
245
Source/Editor/Viewport/Cameras/FPSCamera.cs
Normal file
245
Source/Editor/Viewport/Cameras/FPSCamera.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ViewportCamera"/> that simulated the first-person camera which can fly though the scene.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Cameras.ViewportCamera" />
|
||||
[HideInEditor]
|
||||
public class FPSCamera : ViewportCamera
|
||||
{
|
||||
private Transform _startMove;
|
||||
private Transform _endMove;
|
||||
private float _moveStartTime = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this viewport is animating movement.
|
||||
/// </summary>
|
||||
public bool IsAnimatingMove => _moveStartTime > Mathf.Epsilon;
|
||||
|
||||
/// <summary>
|
||||
/// The target point location. It's used to orbit around it whe user clicks Alt+LMB.
|
||||
/// </summary>
|
||||
public Vector3 TargetPoint = new Vector3(-200);
|
||||
|
||||
/// <summary>
|
||||
/// Sets view.
|
||||
/// </summary>
|
||||
/// <param name="position">The view position.</param>
|
||||
/// <param name="direction">The view direction.</param>
|
||||
public void SetView(Vector3 position, Vector3 direction)
|
||||
{
|
||||
if (IsAnimatingMove)
|
||||
return;
|
||||
|
||||
// Rotate and move
|
||||
Viewport.ViewPosition = position;
|
||||
Viewport.ViewDirection = direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view.
|
||||
/// </summary>
|
||||
/// <param name="position">The view position.</param>
|
||||
/// <param name="orientation">The view rotation.</param>
|
||||
public void SetView(Vector3 position, Quaternion orientation)
|
||||
{
|
||||
if (IsAnimatingMove)
|
||||
return;
|
||||
|
||||
// Rotate and move
|
||||
Viewport.ViewPosition = position;
|
||||
Viewport.ViewOrientation = orientation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start animating viewport movement to the target transformation.
|
||||
/// </summary>
|
||||
/// <param name="position">The target position.</param>
|
||||
/// <param name="orientation">The target orientation.</param>
|
||||
public void MoveViewport(Vector3 position, Quaternion orientation)
|
||||
{
|
||||
MoveViewport(new Transform(position, orientation));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start animating viewport movement to the target transformation.
|
||||
/// </summary>
|
||||
/// <param name="target">The target transform.</param>
|
||||
public void MoveViewport(Transform target)
|
||||
{
|
||||
_startMove = Viewport.ViewTransform;
|
||||
_endMove = target;
|
||||
_moveStartTime = Time.UnscaledGameTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize the actor.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to preview.</param>
|
||||
public void ShowActor(Actor actor)
|
||||
{
|
||||
BoundingSphere sphere;
|
||||
Editor.GetActorEditorSphere(actor, out sphere);
|
||||
ShowSphere(ref sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
/// <param name="actors">The actors to show.</param>
|
||||
public void ShowActors(List<SceneGraphNode> actors)
|
||||
{
|
||||
if (actors.Count == 0)
|
||||
return;
|
||||
|
||||
BoundingSphere mergesSphere = BoundingSphere.Empty;
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
if (actors[i] is ActorNode actor)
|
||||
{
|
||||
BoundingSphere sphere;
|
||||
Editor.GetActorEditorSphere(actor.Actor, out sphere);
|
||||
BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere);
|
||||
}
|
||||
}
|
||||
|
||||
ShowSphere(ref mergesSphere);
|
||||
}
|
||||
|
||||
private void ShowSphere(ref BoundingSphere sphere)
|
||||
{
|
||||
// Calculate view transform
|
||||
Quaternion orientation = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f);
|
||||
Vector3 position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f);
|
||||
|
||||
// Move viewport
|
||||
TargetPoint = sphere.Center;
|
||||
MoveViewport(position, orientation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
// Update animated movement
|
||||
if (IsAnimatingMove)
|
||||
{
|
||||
// Calculate linear progress
|
||||
float animationDuration = 0.5f;
|
||||
float time = Time.UnscaledGameTime;
|
||||
float progress = (time - _moveStartTime) / animationDuration;
|
||||
|
||||
// Check for end
|
||||
if (progress >= 1.0f)
|
||||
{
|
||||
// Animation has been finished
|
||||
_moveStartTime = -1;
|
||||
}
|
||||
|
||||
// Animate camera
|
||||
float a = Mathf.Saturate(progress);
|
||||
a = a * a * a;
|
||||
Transform targetTransform = Transform.Lerp(_startMove, _endMove, a);
|
||||
targetTransform.Scale = Vector3.Zero;
|
||||
Viewport.ViewPosition = targetTransform.Translation;
|
||||
Viewport.ViewOrientation = targetTransform.Orientation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse)
|
||||
{
|
||||
centerMouse = true;
|
||||
|
||||
if (IsAnimatingMove)
|
||||
return;
|
||||
|
||||
EditorViewport.Input input;
|
||||
Viewport.GetInput(out input);
|
||||
var mainViewport = Viewport as MainEditorGizmoViewport;
|
||||
bool isUsingGizmo = mainViewport != null && mainViewport.TransformGizmo.ActiveAxis != TransformGizmo.Axis.None;
|
||||
|
||||
// Get current view properties
|
||||
float yaw = Viewport.Yaw;
|
||||
float pitch = Viewport.Pitch;
|
||||
var position = Viewport.ViewPosition;
|
||||
var rotation = Viewport.ViewOrientation;
|
||||
|
||||
// Compute base vectors for camera movement
|
||||
var forward = Vector3.Forward * rotation;
|
||||
var up = Vector3.Up * rotation;
|
||||
var right = Vector3.Cross(forward, up);
|
||||
|
||||
// Dolly
|
||||
if (input.IsPanning || input.IsMoving || input.IsRotating)
|
||||
{
|
||||
Vector3 move;
|
||||
Vector3.Transform(ref moveDelta, ref rotation, out move);
|
||||
position += move;
|
||||
}
|
||||
|
||||
// Pan
|
||||
if (input.IsPanning)
|
||||
{
|
||||
var panningSpeed = 0.8f;
|
||||
position -= right * (mouseDelta.X * panningSpeed);
|
||||
position -= up * (mouseDelta.Y * panningSpeed);
|
||||
}
|
||||
|
||||
// Move
|
||||
if (input.IsMoving)
|
||||
{
|
||||
// Move camera over XZ plane
|
||||
var projectedForward = Vector3.Normalize(new Vector3(forward.X, 0, forward.Z));
|
||||
position -= projectedForward * mouseDelta.Y;
|
||||
yaw += mouseDelta.X;
|
||||
}
|
||||
|
||||
// Rotate or orbit
|
||||
if (input.IsRotating || (input.IsOrbiting && !isUsingGizmo))
|
||||
{
|
||||
yaw += mouseDelta.X;
|
||||
pitch += mouseDelta.Y;
|
||||
}
|
||||
|
||||
// Zoom in/out
|
||||
if (input.IsZooming && !input.IsRotating)
|
||||
{
|
||||
position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f);
|
||||
if (input.IsAltDown)
|
||||
{
|
||||
position += forward * (Viewport.MouseSpeed * 40 * Viewport.MouseDeltaRight.ValuesSum);
|
||||
}
|
||||
}
|
||||
|
||||
// Move camera with the gizmo
|
||||
if (input.IsOrbiting && isUsingGizmo)
|
||||
{
|
||||
centerMouse = false;
|
||||
Viewport.ViewPosition += mainViewport.TransformGizmo.LastDelta.Translation;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update view
|
||||
Viewport.Yaw = yaw;
|
||||
Viewport.Pitch = pitch;
|
||||
if (input.IsOrbiting)
|
||||
{
|
||||
float orbitRadius = Vector3.Distance(ref position, ref TargetPoint);
|
||||
Vector3 localPosition = Viewport.ViewDirection * (-1 * orbitRadius);
|
||||
Viewport.ViewPosition = TargetPoint + localPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetPoint += position - Viewport.ViewPosition;
|
||||
Viewport.ViewPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Source/Editor/Viewport/Cameras/IViewportCamera.cs
Normal file
28
Source/Editor/Viewport/Cameras/IViewportCamera.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// The interface for the editor viewport camera controllers. Handles the input logic updates and the preview rendering viewport.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public interface IViewportCamera
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the camera.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">The delta time (in seconds).</param>
|
||||
void Update(float deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the view.
|
||||
/// </summary>
|
||||
/// <param name="dt">The delta time (in seconds).</param>
|
||||
/// <param name="moveDelta">The move delta (scaled).</param>
|
||||
/// <param name="mouseDelta">The mouse delta (scaled).</param>
|
||||
/// <param name="centerMouse">True if center mouse after the update.</param>
|
||||
void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse);
|
||||
}
|
||||
}
|
||||
83
Source/Editor/Viewport/Cameras/ViewportCamera.cs
Normal file
83
Source/Editor/Viewport/Cameras/ViewportCamera.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for <see cref="EditorViewport"/> view controllers.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Cameras.IViewportCamera" />
|
||||
[HideInEditor]
|
||||
public abstract class ViewportCamera : IViewportCamera
|
||||
{
|
||||
private EditorViewport _viewport;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent viewport.
|
||||
/// </summary>
|
||||
public EditorViewport Viewport
|
||||
{
|
||||
get => _viewport;
|
||||
internal set => _viewport = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the viewport camera uses movement speed.
|
||||
/// </summary>
|
||||
public virtual bool UseMovementSpeed => true;
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
|
||||
/// </summary>
|
||||
/// <param name="objectBounds">The target object bounds.</param>
|
||||
/// <param name="marginDistanceScale">The margin distance scale of the orbit radius.</param>
|
||||
public void SerArcBallView(BoundingBox objectBounds, float marginDistanceScale = 2.0f)
|
||||
{
|
||||
SerArcBallView(BoundingSphere.FromBox(objectBounds), marginDistanceScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view for the given target object bounds.
|
||||
/// </summary>
|
||||
/// <param name="objectBounds">The target object bounds.</param>
|
||||
/// <param name="marginDistanceScale">The margin distance scale of the orbit radius.</param>
|
||||
public void SerArcBallView(BoundingSphere objectBounds, float marginDistanceScale = 2.0f)
|
||||
{
|
||||
SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), objectBounds.Center, objectBounds.Radius * marginDistanceScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view for the given orbit radius.
|
||||
/// </summary>
|
||||
/// <param name="orbitRadius">The orbit radius.</param>
|
||||
public void SerArcBallView(float orbitRadius)
|
||||
{
|
||||
SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view orientation and position to match the arc ball camera style view.
|
||||
/// </summary>
|
||||
/// <param name="orientation">The view rotation.</param>
|
||||
/// <param name="orbitCenter">The orbit center location.</param>
|
||||
/// <param name="orbitRadius">The orbit radius.</param>
|
||||
public void SerArcBallView(Quaternion orientation, Vector3 orbitCenter, float orbitRadius)
|
||||
{
|
||||
// Rotate
|
||||
Viewport.ViewOrientation = orientation;
|
||||
|
||||
// Move camera to look at orbit center point
|
||||
Vector3 localPosition = Viewport.ViewDirection * (-1 * orbitRadius);
|
||||
Viewport.ViewPosition = orbitCenter + localPosition;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void UpdateView(float dt, ref Vector3 moveDelta, ref Vector2 mouseDelta, out bool centerMouse);
|
||||
}
|
||||
}
|
||||
92
Source/Editor/Viewport/EditorGizmoViewport.cs
Normal file
92
Source/Editor/Viewport/EditorGizmoViewport.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport
|
||||
{
|
||||
/// <summary>
|
||||
/// Viewport with free camera and gizmo tools.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
|
||||
/// <seealso cref="IGizmoOwner" />
|
||||
public class EditorGizmoViewport : EditorViewport, IGizmoOwner
|
||||
{
|
||||
private UpdateDelegate _update;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EditorGizmoViewport"/> class.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <param name="undo">The undo.</param>
|
||||
public EditorGizmoViewport(SceneRenderTask task, Undo undo)
|
||||
: base(task, new FPSCamera(), true)
|
||||
{
|
||||
Undo = undo;
|
||||
|
||||
SetUpdate(ref _update, OnUpdate);
|
||||
}
|
||||
|
||||
private void OnUpdate(float deltaTime)
|
||||
{
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
{
|
||||
Gizmos[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GizmosCollection Gizmos { get; } = new GizmosCollection();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SceneRenderTask RenderTask => Task;
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ViewFarPlane => FarPlane;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLeftMouseButtonDown => _input.IsMouseLeftDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRightMouseButtonDown => _input.IsMouseRightDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAltKeyDown => _input.IsAltDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsControlDown => _input.IsControlDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 MouseDelta => _mouseDeltaLeft * 1000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Undo Undo { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void AddUpdateCallbacks(RootControl root)
|
||||
{
|
||||
base.AddUpdateCallbacks(root);
|
||||
|
||||
root.UpdateCallbacksToAdd.Add(_update);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void RemoveUpdateCallbacks(RootControl root)
|
||||
{
|
||||
base.RemoveUpdateCallbacks(root);
|
||||
|
||||
root.UpdateCallbacksToRemove.Add(_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
1333
Source/Editor/Viewport/EditorViewport.cs
Normal file
1333
Source/Editor/Viewport/EditorViewport.cs
Normal file
File diff suppressed because it is too large
Load Diff
158
Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs
Normal file
158
Source/Editor/Viewport/MainEditorGizmoViewport.Modes.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
|
||||
namespace FlaxEditor.Viewport
|
||||
{
|
||||
public partial class MainEditorGizmoViewport
|
||||
{
|
||||
private EditorGizmoMode _activeMode;
|
||||
private readonly List<EditorGizmoMode> _modes = new List<EditorGizmoMode>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active gizmo mode.
|
||||
/// </summary>
|
||||
public EditorGizmoMode ActiveMode => _activeMode;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when active mode gets changed.
|
||||
/// </summary>
|
||||
public event Action<EditorGizmoMode> ActiveModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The sculpt terrain gizmo.
|
||||
/// </summary>
|
||||
public Tools.Terrain.SculptTerrainGizmoMode SculptTerrainGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The paint terrain gizmo.
|
||||
/// </summary>
|
||||
public Tools.Terrain.PaintTerrainGizmoMode PaintTerrainGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The edit terrain gizmo.
|
||||
/// </summary>
|
||||
public Tools.Terrain.EditTerrainGizmoMode EditTerrainGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The paint foliage gizmo.
|
||||
/// </summary>
|
||||
public Tools.Foliage.PaintFoliageGizmoMode PaintFoliageGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The edit foliage gizmo.
|
||||
/// </summary>
|
||||
public Tools.Foliage.EditFoliageGizmoMode EditFoliageGizmo;
|
||||
|
||||
private void InitModes()
|
||||
{
|
||||
// Add default modes used by the editor
|
||||
_modes.Add(new TransformGizmoMode());
|
||||
_modes.Add(new NoGizmoMode());
|
||||
_modes.Add(SculptTerrainGizmo = new Tools.Terrain.SculptTerrainGizmoMode());
|
||||
_modes.Add(PaintTerrainGizmo = new Tools.Terrain.PaintTerrainGizmoMode());
|
||||
_modes.Add(EditTerrainGizmo = new Tools.Terrain.EditTerrainGizmoMode());
|
||||
_modes.Add(PaintFoliageGizmo = new Tools.Foliage.PaintFoliageGizmoMode());
|
||||
_modes.Add(EditFoliageGizmo = new Tools.Foliage.EditFoliageGizmoMode());
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
{
|
||||
_modes[i].Init(this);
|
||||
}
|
||||
|
||||
// Activate transform mode first
|
||||
_activeMode = _modes[0];
|
||||
}
|
||||
|
||||
private void DisposeModes()
|
||||
{
|
||||
// Cleanup
|
||||
_activeMode = null;
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
_modes[i].Dispose();
|
||||
_modes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the mode to the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void AddMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (_modes.Contains(mode))
|
||||
throw new ArgumentException("Already added.");
|
||||
if (mode.Viewport != null)
|
||||
throw new ArgumentException("Already added to other viewport.");
|
||||
|
||||
_modes.Add(mode);
|
||||
mode.Init(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the mode from the viewport.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void RemoveMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException(nameof(mode));
|
||||
if (!_modes.Contains(mode))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (mode.Viewport != this)
|
||||
throw new ArgumentException("Not added to this viewport.");
|
||||
|
||||
if (_activeMode == mode)
|
||||
SetActiveMode(null);
|
||||
|
||||
_modes.Remove(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active mode.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void SetActiveMode(EditorGizmoMode mode)
|
||||
{
|
||||
if (mode == _activeMode)
|
||||
return;
|
||||
if (mode != null)
|
||||
{
|
||||
if (!_modes.Contains(mode))
|
||||
throw new ArgumentException("Not added.");
|
||||
if (mode.Viewport != this)
|
||||
throw new ArgumentException("Not added to this viewport.");
|
||||
}
|
||||
|
||||
_activeMode?.OnDeactivated();
|
||||
|
||||
Gizmos.Active = null;
|
||||
_activeMode = mode;
|
||||
|
||||
_activeMode?.OnActivated();
|
||||
|
||||
ActiveModeChanged?.Invoke(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active mode.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The mode type.</typeparam>
|
||||
/// <returns>The activated mode.</returns>
|
||||
public T SetActiveMode<T>() where T : EditorGizmoMode
|
||||
{
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
{
|
||||
if (_modes[i] is T mode)
|
||||
{
|
||||
SetActiveMode(mode);
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("Not added mode to activate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
1047
Source/Editor/Viewport/MainEditorGizmoViewport.cs
Normal file
1047
Source/Editor/Viewport/MainEditorGizmoViewport.cs
Normal file
File diff suppressed because it is too large
Load Diff
67
Source/Editor/Viewport/Modes/EditorGizmoMode.cs
Normal file
67
Source/Editor/Viewport/Modes/EditorGizmoMode.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Modes
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor viewport gizmo mode descriptor. Defines which gizmo tools to show and how to handle scene editing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only one gizmo mode can be active at the time. It defines the viewport toolset usage.
|
||||
/// </remarks>
|
||||
[HideInEditor]
|
||||
public abstract class EditorGizmoMode
|
||||
{
|
||||
private MainEditorGizmoViewport _viewport;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the viewport.
|
||||
/// </summary>
|
||||
public MainEditorGizmoViewport Viewport => _viewport;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when mode gets activated.
|
||||
/// </summary>
|
||||
public event Action Activated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when mode gets deactivated.
|
||||
/// </summary>
|
||||
public event Action Deactivated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the specified mode and links it to the viewport.
|
||||
/// </summary>
|
||||
/// <param name="viewport">The viewport.</param>
|
||||
public virtual void Init(MainEditorGizmoViewport viewport)
|
||||
{
|
||||
_viewport = viewport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mode. Called on editor exit or when mode gets removed from the current viewport.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_viewport = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when mode gets activated.
|
||||
/// </summary>
|
||||
public virtual void OnActivated()
|
||||
{
|
||||
Activated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when mode gets deactivated.
|
||||
/// </summary>
|
||||
public virtual void OnDeactivated()
|
||||
{
|
||||
Deactivated?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/Editor/Viewport/Modes/NoGizmoMode.cs
Normal file
19
Source/Editor/Viewport/Modes/NoGizmoMode.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.Viewport.Modes
|
||||
{
|
||||
/// <summary>
|
||||
/// The editor gizmo editor mode that does nothing. Can be used to hide other gizmos when using a specific editor tool.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Modes.EditorGizmoMode" />
|
||||
public class NoGizmoMode : EditorGizmoMode
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnActivated()
|
||||
{
|
||||
base.OnActivated();
|
||||
|
||||
Viewport.Gizmos.Active = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/Editor/Viewport/Modes/TransformGizmoMode.cs
Normal file
19
Source/Editor/Viewport/Modes/TransformGizmoMode.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.Viewport.Modes
|
||||
{
|
||||
/// <summary>
|
||||
/// The default editor mode that uses <see cref="FlaxEditor.Gizmo.TransformGizmo"/> for objects transforming.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Modes.EditorGizmoMode" />
|
||||
public class TransformGizmoMode : EditorGizmoMode
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void OnActivated()
|
||||
{
|
||||
base.OnActivated();
|
||||
|
||||
Viewport.Gizmos.Active = Viewport.TransformGizmo;
|
||||
}
|
||||
}
|
||||
}
|
||||
797
Source/Editor/Viewport/PrefabWindowViewport.cs
Normal file
797
Source/Editor/Viewport/PrefabWindowViewport.cs
Normal file
@@ -0,0 +1,797 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEditor.Viewport.Previews;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor viewport used by the <see cref="PrefabWindow"/>
|
||||
/// </summary>
|
||||
/// <seealso cref="PrefabWindow" />
|
||||
/// <seealso cref="PrefabPreview" />
|
||||
/// <seealso cref="IGizmoOwner" />
|
||||
public class PrefabWindowViewport : PrefabPreview, IEditorPrimitivesOwner
|
||||
{
|
||||
private readonly PrefabWindow _window;
|
||||
private UpdateDelegate _update;
|
||||
|
||||
private readonly ViewportWidgetButton _gizmoModeTranslate;
|
||||
private readonly ViewportWidgetButton _gizmoModeRotate;
|
||||
private readonly ViewportWidgetButton _gizmoModeScale;
|
||||
|
||||
private ViewportWidgetButton _translateSnappng;
|
||||
private ViewportWidgetButton _rotateSnapping;
|
||||
private ViewportWidgetButton _scaleSnapping;
|
||||
|
||||
private readonly DragAssets _dragAssets = new DragAssets(ValidateDragItem);
|
||||
private readonly DragActorType _dragActorType = new DragActorType(ValidateDragActorType);
|
||||
private readonly DragHandlers _dragHandlers = new DragHandlers();
|
||||
|
||||
/// <summary>
|
||||
/// The transform gizmo.
|
||||
/// </summary>
|
||||
public readonly TransformGizmo TransformGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The selection outline postFx.
|
||||
/// </summary>
|
||||
public SelectionOutline SelectionOutline;
|
||||
|
||||
/// <summary>
|
||||
/// The editor primitives postFx.
|
||||
/// </summary>
|
||||
public EditorPrimitives EditorPrimitives;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrefabWindowViewport"/> class.
|
||||
/// </summary>
|
||||
/// <param name="window">Editor window.</param>
|
||||
public PrefabWindowViewport(PrefabWindow window)
|
||||
: base(true)
|
||||
{
|
||||
_window = window;
|
||||
_window.SelectionChanged += OnSelectionChanged;
|
||||
Undo = window.Undo;
|
||||
ViewportCamera = new FPSCamera();
|
||||
|
||||
// Prepare rendering task
|
||||
Task.ActorsSource = ActorsSources.CustomActors;
|
||||
Task.ViewFlags = ViewFlags.DefaultEditor & ~ViewFlags.EditorSprites;
|
||||
Task.PostRender += OnPostRender;
|
||||
|
||||
// Create post effects
|
||||
SelectionOutline = FlaxEngine.Object.New<SelectionOutline>();
|
||||
SelectionOutline.SelectionGetter = () => TransformGizmo.SelectedParents;
|
||||
Task.CustomPostFx.Add(SelectionOutline);
|
||||
EditorPrimitives = FlaxEngine.Object.New<EditorPrimitives>();
|
||||
EditorPrimitives.Viewport = this;
|
||||
Task.CustomPostFx.Add(EditorPrimitives);
|
||||
|
||||
// Add transformation gizmo
|
||||
TransformGizmo = new TransformGizmo(this);
|
||||
TransformGizmo.ApplyTransformation += ApplyTransform;
|
||||
TransformGizmo.ModeChanged += OnGizmoModeChanged;
|
||||
TransformGizmo.Duplicate += _window.Duplicate;
|
||||
Gizmos.Active = TransformGizmo;
|
||||
|
||||
// Transform space widget
|
||||
var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.World16, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ActiveTransformSpace == TransformGizmo.TransformSpace.World,
|
||||
TooltipText = "Gizmo transform space (world or local)",
|
||||
Parent = transformSpaceWidget
|
||||
};
|
||||
transformSpaceToggle.Toggled += OnTransformSpaceToggle;
|
||||
transformSpaceWidget.Parent = this;
|
||||
|
||||
// Scale snapping widget
|
||||
var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleStep16, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.ScaleSnapEnabled,
|
||||
TooltipText = "Enable scale snapping",
|
||||
Parent = scaleSnappingWidget
|
||||
};
|
||||
enableScaleSnapping.Toggled += OnScaleSnappingToggle;
|
||||
var scaleSnappingCM = new ContextMenu();
|
||||
_scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM);
|
||||
_scaleSnapping.TooltipText = "Scale snapping values";
|
||||
for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportScaleSnapValues[i];
|
||||
var button = scaleSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick;
|
||||
scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide;
|
||||
_scaleSnapping.Parent = scaleSnappingWidget;
|
||||
scaleSnappingWidget.Parent = this;
|
||||
|
||||
// Rotation snapping widget
|
||||
var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateStep16, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.RotationSnapEnabled,
|
||||
TooltipText = "Enable rotation snapping",
|
||||
Parent = rotateSnappingWidget
|
||||
};
|
||||
enableRotateSnapping.Toggled += OnRotateSnappingToggle;
|
||||
var rotateSnappingCM = new ContextMenu();
|
||||
_rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM);
|
||||
_rotateSnapping.TooltipText = "Rotation snapping values";
|
||||
for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportRotateSnapValues[i];
|
||||
var button = rotateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick;
|
||||
rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide;
|
||||
_rotateSnapping.Parent = rotateSnappingWidget;
|
||||
rotateSnappingWidget.Parent = this;
|
||||
|
||||
// Translation snapping widget
|
||||
var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid16, null, true)
|
||||
{
|
||||
Checked = TransformGizmo.TranslationSnapEnable,
|
||||
TooltipText = "Enable position snapping",
|
||||
Parent = translateSnappingWidget
|
||||
};
|
||||
enableTranslateSnapping.Toggled += OnTranslateSnappingToggle;
|
||||
var translateSnappingCM = new ContextMenu();
|
||||
_translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM);
|
||||
_translateSnappng.TooltipText = "Position snapping values";
|
||||
for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++)
|
||||
{
|
||||
var v = EditorViewportTranslateSnapValues[i];
|
||||
var button = translateSnappingCM.AddButton(v.ToString());
|
||||
button.Tag = v;
|
||||
}
|
||||
translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick;
|
||||
translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide;
|
||||
_translateSnappng.Parent = translateSnappingWidget;
|
||||
translateSnappingWidget.Parent = this;
|
||||
|
||||
// Gizmo mode widget
|
||||
var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
|
||||
_gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate16, null, true)
|
||||
{
|
||||
Tag = TransformGizmo.Mode.Translate,
|
||||
TooltipText = "Translate gizmo mode",
|
||||
Checked = true,
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeTranslate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate16, null, true)
|
||||
{
|
||||
Tag = TransformGizmo.Mode.Rotate,
|
||||
TooltipText = "Rotate gizmo mode",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeRotate.Toggled += OnGizmoModeToggle;
|
||||
_gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale16, null, true)
|
||||
{
|
||||
Tag = TransformGizmo.Mode.Scale,
|
||||
TooltipText = "Scale gizmo mode",
|
||||
Parent = gizmoMode
|
||||
};
|
||||
_gizmoModeScale.Toggled += OnGizmoModeToggle;
|
||||
gizmoMode.Parent = this;
|
||||
|
||||
_dragHandlers.Add(_dragActorType);
|
||||
_dragHandlers.Add(_dragAssets);
|
||||
|
||||
// Setup input actions
|
||||
InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
|
||||
InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
|
||||
InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
|
||||
InputActions.Add(options => options.FocusSelection, ShowSelectedActors);
|
||||
|
||||
SetUpdate(ref _update, OnUpdate);
|
||||
}
|
||||
|
||||
private void OnUpdate(float deltaTime)
|
||||
{
|
||||
for (int i = 0; i < Gizmos.Count; i++)
|
||||
{
|
||||
Gizmos[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPostRender(GPUContext context, RenderContext renderContext)
|
||||
{
|
||||
if (renderContext.View.Mode != ViewMode.Default)
|
||||
{
|
||||
var task = renderContext.Task;
|
||||
|
||||
// Render editor primitives, gizmo and debug shapes in debug view modes
|
||||
// Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers
|
||||
EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the viewport to visualize selected actors.
|
||||
/// </summary>
|
||||
public void ShowSelectedActors()
|
||||
{
|
||||
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public GizmosCollection Gizmos { get; } = new GizmosCollection();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SceneRenderTask RenderTask => Task;
|
||||
|
||||
/// <inheritdoc />
|
||||
public float ViewFarPlane => FarPlane;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLeftMouseButtonDown => _input.IsMouseLeftDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsRightMouseButtonDown => _input.IsMouseRightDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAltKeyDown => _input.IsAltDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsControlDown => _input.IsControlDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SnapToGround => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 MouseDelta => _mouseDeltaLeft * 1000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Undo Undo { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void AddUpdateCallbacks(RootControl root)
|
||||
{
|
||||
base.AddUpdateCallbacks(root);
|
||||
|
||||
root.UpdateCallbacksToAdd.Add(_update);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void RemoveUpdateCallbacks(RootControl root)
|
||||
{
|
||||
base.RemoveUpdateCallbacks(root);
|
||||
|
||||
root.UpdateCallbacksToRemove.Add(_update);
|
||||
}
|
||||
|
||||
private void OnGizmoModeToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ActiveMode = (TransformGizmo.Mode)(int)button.Tag;
|
||||
}
|
||||
|
||||
private void OnTranslateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable;
|
||||
}
|
||||
|
||||
private void OnRotateSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnScaleSnappingToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled;
|
||||
}
|
||||
|
||||
private void OnTransformSpaceToggle(ViewportWidgetButton button)
|
||||
{
|
||||
TransformGizmo.ToggleTransformSpace();
|
||||
}
|
||||
|
||||
private void OnGizmoModeChanged()
|
||||
{
|
||||
// Update all viewport widgets status
|
||||
var mode = TransformGizmo.ActiveMode;
|
||||
_gizmoModeTranslate.Checked = mode == TransformGizmo.Mode.Translate;
|
||||
_gizmoModeRotate.Checked = mode == TransformGizmo.Mode.Rotate;
|
||||
_gizmoModeScale.Checked = mode == TransformGizmo.Mode.Scale;
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportScaleSnapValues =
|
||||
{
|
||||
0.05f,
|
||||
0.1f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
2.0f,
|
||||
4.0f,
|
||||
6.0f,
|
||||
8.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetScaleSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.ScaleSnapValue = v;
|
||||
_scaleSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetScaleSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportRotateSnapValues =
|
||||
{
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
15.0f,
|
||||
30.0f,
|
||||
45.0f,
|
||||
60.0f,
|
||||
90.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetRotateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.RotationSnapValue = v;
|
||||
_rotateSnapping.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetRotateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly float[] EditorViewportTranslateSnapValues =
|
||||
{
|
||||
0.1f,
|
||||
0.5f,
|
||||
1.0f,
|
||||
5.0f,
|
||||
10.0f,
|
||||
100.0f,
|
||||
1000.0f,
|
||||
};
|
||||
|
||||
private void OnWidgetTranslateSnapClick(ContextMenuButton button)
|
||||
{
|
||||
var v = (float)button.Tag;
|
||||
TransformGizmo.TranslationSnapValue = v;
|
||||
_translateSnappng.Text = v.ToString();
|
||||
}
|
||||
|
||||
private void OnWidgetTranslateSnapShowHide(Control control)
|
||||
{
|
||||
if (control.Visible == false)
|
||||
return;
|
||||
|
||||
var ccm = (ContextMenu)control;
|
||||
foreach (var e in ccm.Items)
|
||||
{
|
||||
if (e is ContextMenuButton b)
|
||||
{
|
||||
var v = (float)b.Tag;
|
||||
b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f
|
||||
? Style.Current.CheckBoxTick
|
||||
: SpriteHandle.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the transform to the collection of scene graph nodes.
|
||||
/// </summary>
|
||||
/// <param name="selection">The selection.</param>
|
||||
/// <param name="translationDelta">The translation delta.</param>
|
||||
/// <param name="rotationDelta">The rotation delta.</param>
|
||||
/// <param name="scaleDelta">The scale delta.</param>
|
||||
public void ApplyTransform(List<SceneGraphNode> selection, ref Vector3 translationDelta, ref Quaternion rotationDelta, ref Vector3 scaleDelta)
|
||||
{
|
||||
bool applyRotation = !rotationDelta.IsIdentity;
|
||||
bool useObjCenter = TransformGizmo.ActivePivot == TransformGizmo.PivotType.ObjectCenter;
|
||||
Vector3 gizmoPosition = TransformGizmo.Position;
|
||||
|
||||
// Transform selected objects
|
||||
for (int i = 0; i < selection.Count; i++)
|
||||
{
|
||||
var obj = selection[i];
|
||||
var trans = obj.Transform;
|
||||
|
||||
// Apply rotation
|
||||
if (applyRotation)
|
||||
{
|
||||
Vector3 pivotOffset = trans.Translation - gizmoPosition;
|
||||
if (useObjCenter || pivotOffset.IsZero)
|
||||
{
|
||||
//trans.Orientation *= rotationDelta;
|
||||
trans.Orientation *= Quaternion.Invert(trans.Orientation) * rotationDelta * trans.Orientation;
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix.RotationQuaternion(ref trans.Orientation, out var transWorld);
|
||||
Matrix.RotationQuaternion(ref rotationDelta, out var deltaWorld);
|
||||
Matrix world = transWorld * Matrix.Translation(pivotOffset) * deltaWorld * Matrix.Translation(-pivotOffset);
|
||||
trans.SetRotation(ref world);
|
||||
trans.Translation += world.TranslationVector;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply scale
|
||||
const float scaleLimit = 99_999_999.0f;
|
||||
trans.Scale = Vector3.Clamp(trans.Scale + scaleDelta, new Vector3(-scaleLimit), new Vector3(scaleLimit));
|
||||
|
||||
// Apply translation
|
||||
trans.Translation += translationDelta;
|
||||
|
||||
obj.Transform = trans;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnLeftMouseButtonUp()
|
||||
{
|
||||
// Skip if was controlling mouse or mouse is not over the area
|
||||
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos))
|
||||
return;
|
||||
|
||||
if (TransformGizmo.IsActive)
|
||||
{
|
||||
// Ensure player is not moving objects
|
||||
if (TransformGizmo.ActiveAxis != TransformGizmo.Axis.None)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For now just pick objects in transform gizmo mode
|
||||
return;
|
||||
}
|
||||
|
||||
// Get mouse ray and try to hit any object
|
||||
var ray = MouseRay;
|
||||
var view = new Ray(ViewPosition, ViewDirection);
|
||||
var hit = _window.Graph.Root.RayCast(ref ray, ref view, out _, SceneGraphNode.RayCastData.FlagTypes.SkipColliders);
|
||||
|
||||
// Update selection
|
||||
if (hit != null)
|
||||
{
|
||||
// For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor)
|
||||
if (hit is ActorChildNode actorChildNode)
|
||||
{
|
||||
var parentNode = actorChildNode.ParentNode;
|
||||
bool canChildBeSelected = _window.Selection.Contains(parentNode);
|
||||
if (!canChildBeSelected)
|
||||
{
|
||||
for (int i = 0; i < parentNode.ChildNodes.Count; i++)
|
||||
{
|
||||
if (_window.Selection.Contains(parentNode.ChildNodes[i]))
|
||||
{
|
||||
canChildBeSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canChildBeSelected)
|
||||
{
|
||||
// Select parent
|
||||
hit = parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
bool addRemove = Root.GetKey(KeyboardKeys.Control);
|
||||
bool isSelected = _window.Selection.Contains(hit);
|
||||
|
||||
if (addRemove)
|
||||
{
|
||||
if (isSelected)
|
||||
_window.Deselect(hit);
|
||||
else
|
||||
_window.Select(hit, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.Select(hit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.Deselect();
|
||||
}
|
||||
|
||||
// Keep focus
|
||||
Focus();
|
||||
|
||||
base.OnLeftMouseButtonUp();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
return _dragHandlers.OnDragEnter(data);
|
||||
}
|
||||
|
||||
private static bool ValidateDragItem(ContentItem contentItem)
|
||||
{
|
||||
if (contentItem is AssetItem assetItem)
|
||||
{
|
||||
if (assetItem.IsOfType<ParticleSystem>())
|
||||
return true;
|
||||
if (assetItem.IsOfType<MaterialBase>())
|
||||
return true;
|
||||
if (assetItem.IsOfType<ModelBase>())
|
||||
return true;
|
||||
if (assetItem.IsOfType<AudioClip>())
|
||||
return true;
|
||||
if (assetItem.IsOfType<Prefab>())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateDragActorType(ScriptType actorType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
return _dragHandlers.Effect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragHandlers.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
private Vector3 PostProcessSpawnedActorLocation(Actor actor, ref Vector3 hitLocation)
|
||||
{
|
||||
BoundingBox box;
|
||||
Editor.GetActorEditorBox(actor, out box);
|
||||
|
||||
// Place the object
|
||||
var location = hitLocation - (box.Size.Length * 0.5f) * ViewDirection;
|
||||
|
||||
// Apply grid snapping if enabled
|
||||
if (UseSnapping || TransformGizmo.TranslationSnapEnable)
|
||||
{
|
||||
float snapValue = TransformGizmo.TranslationSnapValue;
|
||||
location = new Vector3(
|
||||
(int)(location.X / snapValue) * snapValue,
|
||||
(int)(location.Y / snapValue) * snapValue,
|
||||
(int)(location.Z / snapValue) * snapValue);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
private void Spawn(AssetItem item, SceneGraphNode hit, ref Vector2 location, ref Vector3 hitLocation)
|
||||
{
|
||||
if (item is BinaryAssetItem binaryAssetItem)
|
||||
{
|
||||
if (binaryAssetItem.Type == typeof(ParticleSystem))
|
||||
{
|
||||
var particleSystem = FlaxEngine.Content.LoadAsync<ParticleSystem>(item.ID);
|
||||
var actor = new ParticleEffect();
|
||||
actor.Name = item.ShortName;
|
||||
actor.ParticleSystem = particleSystem;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
return;
|
||||
}
|
||||
if (typeof(MaterialBase).IsAssignableFrom(binaryAssetItem.Type))
|
||||
{
|
||||
if (hit is StaticModelNode staticModelNode)
|
||||
{
|
||||
var staticModel = (StaticModel)staticModelNode.Actor;
|
||||
var ray = ConvertMouseToRay(ref location);
|
||||
if (staticModel.IntersectsEntry(ref ray, out _, out _, out var entryIndex))
|
||||
{
|
||||
var material = FlaxEngine.Content.LoadAsync<MaterialBase>(item.ID);
|
||||
using (new UndoBlock(Undo, staticModel, "Change material"))
|
||||
staticModel.SetMaterial(entryIndex, material);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof(SkinnedModel).IsAssignableFrom(binaryAssetItem.Type))
|
||||
{
|
||||
var model = FlaxEngine.Content.LoadAsync<SkinnedModel>(item.ID);
|
||||
var actor = new AnimatedModel();
|
||||
actor.Name = item.ShortName;
|
||||
actor.SkinnedModel = model;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
return;
|
||||
}
|
||||
if (typeof(Model).IsAssignableFrom(binaryAssetItem.Type))
|
||||
{
|
||||
var model = FlaxEngine.Content.LoadAsync<Model>(item.ID);
|
||||
var actor = new StaticModel();
|
||||
actor.Name = item.ShortName;
|
||||
actor.Model = model;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
return;
|
||||
}
|
||||
if (typeof(AudioClip).IsAssignableFrom(binaryAssetItem.Type))
|
||||
{
|
||||
var clip = FlaxEngine.Content.LoadAsync<AudioClip>(item.ID);
|
||||
var actor = new AudioSource();
|
||||
actor.Name = item.ShortName;
|
||||
actor.Clip = clip;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
return;
|
||||
}
|
||||
if (typeof(Prefab).IsAssignableFrom(binaryAssetItem.Type))
|
||||
{
|
||||
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(item.ID);
|
||||
var actor = PrefabManager.SpawnPrefab(prefab, null);
|
||||
actor.Name = item.ShortName;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Spawn(Actor actor)
|
||||
{
|
||||
_window.Spawn(actor);
|
||||
}
|
||||
|
||||
private void Spawn(ScriptType item, SceneGraphNode hit, ref Vector3 hitLocation)
|
||||
{
|
||||
var actor = item.CreateInstance() as Actor;
|
||||
if (actor == null)
|
||||
{
|
||||
Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
|
||||
return;
|
||||
}
|
||||
actor.Name = item.Name;
|
||||
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
|
||||
Spawn(actor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
// Check if drag sth
|
||||
Vector3 hitLocation = ViewPosition;
|
||||
SceneGraphNode hit = null;
|
||||
if (_dragHandlers.HasValidDrag)
|
||||
{
|
||||
// Get mouse ray and try to hit any object
|
||||
var ray = ConvertMouseToRay(ref location);
|
||||
var view = new Ray(ViewPosition, ViewDirection);
|
||||
hit = _window.Graph.Root.RayCast(ref ray, ref view, out var closest, SceneGraphNode.RayCastData.FlagTypes.SkipColliders);
|
||||
if (hit != null)
|
||||
{
|
||||
// Use hit location
|
||||
hitLocation = ray.Position + ray.Direction * closest;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use area in front of the viewport
|
||||
hitLocation = ViewPosition + ViewDirection * 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Drag assets
|
||||
if (_dragAssets.HasValidDrag)
|
||||
{
|
||||
result = _dragAssets.Effect;
|
||||
|
||||
// Process items
|
||||
for (int i = 0; i < _dragAssets.Objects.Count; i++)
|
||||
{
|
||||
var item = _dragAssets.Objects[i];
|
||||
Spawn(item, hit, ref location, ref hitLocation);
|
||||
}
|
||||
}
|
||||
// Drag actor type
|
||||
else if (_dragActorType.HasValidDrag)
|
||||
{
|
||||
result = _dragActorType.Effect;
|
||||
|
||||
// Process items
|
||||
for (int i = 0; i < _dragActorType.Objects.Count; i++)
|
||||
{
|
||||
var item = _dragActorType.Objects[i];
|
||||
Spawn(item, hit, ref hitLocation);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
FlaxEngine.Object.Destroy(ref SelectionOutline);
|
||||
FlaxEngine.Object.Destroy(ref EditorPrimitives);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
258
Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
Normal file
258
Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using FlaxEngine;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Animated model asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class AnimatedModelPreview : AssetPreview
|
||||
{
|
||||
private AnimatedModel _previewModel;
|
||||
private StaticModel _previewNodesActor;
|
||||
private Model _previewNodesModel;
|
||||
private int _previewNodesCounter;
|
||||
private List<Vector3> _previewNodesVB;
|
||||
private List<int> _previewNodesIB;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the skinned model asset to preview.
|
||||
/// </summary>
|
||||
public SkinnedModel SkinnedModel
|
||||
{
|
||||
get => _previewModel.SkinnedModel;
|
||||
set => _previewModel.SkinnedModel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the skinned model actor used to preview selected asset.
|
||||
/// </summary>
|
||||
public AnimatedModel PreviewActor => _previewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether play the animation in editor.
|
||||
/// </summary>
|
||||
public bool PlayAnimation { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show animated model skeleton nodes debug view.
|
||||
/// </summary>
|
||||
public bool ShowNodes { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether scale the model to the normalized bounds.
|
||||
/// </summary>
|
||||
public bool ScaleToFit { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom mask for the skeleton nodes. Nodes missing from this list will be skipped during rendering. Works only if <see cref="ShowNodes"/> is set to true and the array matches the attached <see cref="SkinnedModel"/> nodes hierarchy.
|
||||
/// </summary>
|
||||
public bool[] NodesMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnimatedModelPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public AnimatedModelPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
Task.Begin += OnBegin;
|
||||
|
||||
// Setup preview scene
|
||||
_previewModel = new AnimatedModel();
|
||||
_previewModel.UseTimeScale = false;
|
||||
_previewModel.UpdateWhenOffscreen = true;
|
||||
//_previewModel.BoundsScale = 1000.0f;
|
||||
_previewModel.UpdateMode = AnimatedModel.AnimationUpdateMode.Manual;
|
||||
_previewNodesModel = FlaxEngine.Content.CreateVirtualAsset<Model>();
|
||||
_previewNodesModel.SetupLODs(new[] { 1 });
|
||||
_previewNodesActor = new StaticModel();
|
||||
_previewNodesActor.Model = _previewNodesModel;
|
||||
_previewNodesActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
|
||||
|
||||
// Link actors for rendering
|
||||
Task.AddCustomActor(_previewModel);
|
||||
Task.AddCustomActor(_previewNodesActor);
|
||||
|
||||
if (useWidgets)
|
||||
{
|
||||
// Preview LOD
|
||||
{
|
||||
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
|
||||
var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f);
|
||||
previewLODValue.Parent = previewLOD;
|
||||
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
|
||||
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBegin(RenderTask task, GPUContext context)
|
||||
{
|
||||
if (!ScaleToFit)
|
||||
{
|
||||
_previewModel.Scale = Vector3.One;
|
||||
_previewModel.Position = Vector3.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update preview model scale to fit the preview
|
||||
var skinnedModel = SkinnedModel;
|
||||
if (skinnedModel && skinnedModel.IsLoaded)
|
||||
{
|
||||
float targetSize = 50.0f;
|
||||
BoundingBox box = skinnedModel.GetBox();
|
||||
float maxSize = Mathf.Max(0.001f, box.Size.MaxValue);
|
||||
float scale = targetSize / maxSize;
|
||||
_previewModel.Scale = new Vector3(scale);
|
||||
_previewModel.Position = box.Center * (-0.5f * scale) + new Vector3(0, -10, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
// Manually update animation
|
||||
if (PlayAnimation)
|
||||
{
|
||||
_previewModel.UpdateAnimation();
|
||||
}
|
||||
|
||||
// Update the nodes debug (once every few frames)
|
||||
_previewNodesActor.Transform = _previewModel.Transform;
|
||||
var updateNodesCount = PlayAnimation || _previewNodesVB?.Count == 0 ? 1 : 10;
|
||||
_previewNodesActor.IsActive = ShowNodes;
|
||||
if (_previewNodesCounter++ % updateNodesCount == 0 && ShowNodes)
|
||||
{
|
||||
_previewModel.GetCurrentPose(out var pose);
|
||||
var nodes = _previewModel.SkinnedModel?.Nodes;
|
||||
if (pose == null || pose.Length == 0 || nodes == null)
|
||||
{
|
||||
_previewNodesActor.IsActive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_previewNodesVB == null)
|
||||
_previewNodesVB = new List<Vector3>(1024 * 2);
|
||||
else
|
||||
_previewNodesVB.Clear();
|
||||
if (_previewNodesIB == null)
|
||||
_previewNodesIB = new List<int>(1024 * 3);
|
||||
else
|
||||
_previewNodesIB.Clear();
|
||||
|
||||
// Draw bounding box at the node locations
|
||||
var nodesMask = NodesMask != null && NodesMask.Length == nodes.Length ? NodesMask : null;
|
||||
var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
|
||||
for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
|
||||
{
|
||||
if (nodesMask != null && !nodesMask[nodeIndex])
|
||||
continue;
|
||||
|
||||
var transform = pose[nodeIndex];
|
||||
transform.Decompose(out var scale, out Matrix _, out _);
|
||||
transform = Matrix.Invert(Matrix.Scaling(scale)) * transform;
|
||||
|
||||
// Some inlined code to improve performance
|
||||
var box = localBox * transform;
|
||||
//
|
||||
var iStart = _previewNodesVB.Count;
|
||||
box.GetCorners(_previewNodesVB);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
_previewNodesIB.Add(iStart + 4);
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
_previewNodesIB.Add(iStart + 2);
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
_previewNodesIB.Add(iStart + 5);
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 2);
|
||||
_previewNodesIB.Add(iStart + 3);
|
||||
_previewNodesIB.Add(iStart + 2);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 2);
|
||||
_previewNodesIB.Add(iStart + 6);
|
||||
_previewNodesIB.Add(iStart + 2);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 3);
|
||||
_previewNodesIB.Add(iStart + 7);
|
||||
_previewNodesIB.Add(iStart + 3);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 4);
|
||||
_previewNodesIB.Add(iStart + 5);
|
||||
_previewNodesIB.Add(iStart + 4);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 4);
|
||||
_previewNodesIB.Add(iStart + 7);
|
||||
_previewNodesIB.Add(iStart + 4);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 5);
|
||||
_previewNodesIB.Add(iStart + 6);
|
||||
_previewNodesIB.Add(iStart + 5);
|
||||
//
|
||||
_previewNodesIB.Add(iStart + 6);
|
||||
_previewNodesIB.Add(iStart + 7);
|
||||
_previewNodesIB.Add(iStart + 6);
|
||||
//
|
||||
}
|
||||
|
||||
// Nodes connections
|
||||
for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
|
||||
{
|
||||
int parentIndex = nodes[nodeIndex].ParentIndex;
|
||||
|
||||
if (parentIndex != -1)
|
||||
{
|
||||
if (nodesMask != null && (!nodesMask[nodeIndex] || !nodesMask[parentIndex]))
|
||||
continue;
|
||||
|
||||
var parentPos = pose[parentIndex].TranslationVector;
|
||||
var bonePos = pose[nodeIndex].TranslationVector;
|
||||
|
||||
var iStart = _previewNodesVB.Count;
|
||||
_previewNodesVB.Add(parentPos);
|
||||
_previewNodesVB.Add(bonePos);
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
_previewNodesIB.Add(iStart + 1);
|
||||
_previewNodesIB.Add(iStart + 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (_previewNodesIB.Count > 0)
|
||||
_previewNodesModel.LODs[0].Meshes[0].UpdateMesh(_previewNodesVB, _previewNodesIB);
|
||||
else
|
||||
_previewNodesActor.IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Ensure to cleanup created actor objects
|
||||
_previewNodesActor.Model = null;
|
||||
Object.Destroy(ref _previewModel);
|
||||
Object.Destroy(ref _previewNodesActor);
|
||||
Object.Destroy(ref _previewNodesModel);
|
||||
NodesMask = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
144
Source/Editor/Viewport/Previews/AssetPreview.cs
Normal file
144
Source/Editor/Viewport/Previews/AssetPreview.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic asset preview editor viewport base class.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
|
||||
public abstract class AssetPreview : EditorViewport
|
||||
{
|
||||
private ContextMenuButton _showDefaultSceneButton;
|
||||
|
||||
/// <summary>
|
||||
/// The preview light. Allows to modify rendering settings.
|
||||
/// </summary>
|
||||
public DirectionalLight PreviewLight;
|
||||
|
||||
/// <summary>
|
||||
/// The env probe. Allows to modify rendering settings.
|
||||
/// </summary>
|
||||
public EnvironmentProbe EnvProbe;
|
||||
|
||||
/// <summary>
|
||||
/// The sky. Allows to modify rendering settings.
|
||||
/// </summary>
|
||||
public Sky Sky;
|
||||
|
||||
/// <summary>
|
||||
/// The sky light. Allows to modify rendering settings.
|
||||
/// </summary>
|
||||
public SkyLight SkyLight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the post fx volume. Allows to modify rendering settings.
|
||||
/// </summary>
|
||||
public PostFxVolume PostFxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show default scene actors (sky, env probe, skylight, directional light, etc.).
|
||||
/// </summary>
|
||||
public bool ShowDefaultSceneActors
|
||||
{
|
||||
get => PreviewLight.IsActive;
|
||||
set
|
||||
{
|
||||
if (ShowDefaultSceneActors != value)
|
||||
{
|
||||
PreviewLight.IsActive = value;
|
||||
EnvProbe.IsActive = value;
|
||||
Sky.IsActive = value;
|
||||
SkyLight.IsActive = value;
|
||||
|
||||
if (_showDefaultSceneButton != null)
|
||||
_showDefaultSceneButton.Checked = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">If set to <c>true</c> use widgets for viewport, otherwise hide them.</param>
|
||||
/// <param name="orbitRadius">The initial orbit radius.</param>
|
||||
protected AssetPreview(bool useWidgets, float orbitRadius = 50.0f)
|
||||
: this(useWidgets, new ArcBallCamera(Vector3.Zero, orbitRadius))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">If set to <c>true</c> use widgets for viewport, otherwise hide them.</param>
|
||||
/// <param name="camera">The camera controller.</param>
|
||||
protected AssetPreview(bool useWidgets, ViewportCamera camera)
|
||||
: base(Object.New<SceneRenderTask>(), camera, useWidgets)
|
||||
{
|
||||
Task.ViewFlags = ViewFlags.DefaultAssetPreview;
|
||||
Task.AllowGlobalCustomPostFx = false;
|
||||
|
||||
var orbitRadius = 200.0f;
|
||||
if (camera is ArcBallCamera arcBallCamera)
|
||||
orbitRadius = arcBallCamera.OrbitRadius;
|
||||
camera.SerArcBallView(new Quaternion(-0.08f, -0.92f, 0.31f, -0.23f), Vector3.Zero, orbitRadius);
|
||||
|
||||
if (useWidgets)
|
||||
{
|
||||
// Show Default Scene
|
||||
_showDefaultSceneButton = ViewWidgetShowMenu.AddButton("Default Scene", () => ShowDefaultSceneActors = !ShowDefaultSceneActors);
|
||||
_showDefaultSceneButton.Checked = true;
|
||||
}
|
||||
|
||||
// Setup preview scene
|
||||
PreviewLight = new DirectionalLight();
|
||||
PreviewLight.Brightness = 8.0f;
|
||||
PreviewLight.ShadowsMode = ShadowsCastingMode.None;
|
||||
PreviewLight.Orientation = Quaternion.Euler(new Vector3(52.1477f, -109.109f, -111.739f));
|
||||
//
|
||||
EnvProbe = new EnvironmentProbe();
|
||||
EnvProbe.AutoUpdate = false;
|
||||
EnvProbe.CustomProbe = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture);
|
||||
//
|
||||
Sky = new Sky();
|
||||
Sky.SunLight = PreviewLight;
|
||||
Sky.SunPower = 9.0f;
|
||||
//
|
||||
SkyLight = new SkyLight();
|
||||
SkyLight.Mode = SkyLight.Modes.CustomTexture;
|
||||
SkyLight.Brightness = 2.1f;
|
||||
SkyLight.CustomTexture = EnvProbe.CustomProbe;
|
||||
//
|
||||
PostFxVolume = new PostFxVolume();
|
||||
PostFxVolume.IsBounded = false;
|
||||
|
||||
// Link actors for rendering
|
||||
Task.ActorsSource = ActorsSources.CustomActors;
|
||||
Task.AddCustomActor(PreviewLight);
|
||||
Task.AddCustomActor(EnvProbe);
|
||||
Task.AddCustomActor(Sky);
|
||||
Task.AddCustomActor(SkyLight);
|
||||
Task.AddCustomActor(PostFxVolume);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.Probe.IsLoaded && PostFxVolume.HasContentLoaded;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Ensure to cleanup created actor objects
|
||||
Object.Destroy(ref PreviewLight);
|
||||
Object.Destroy(ref EnvProbe);
|
||||
Object.Destroy(ref Sky);
|
||||
Object.Destroy(ref SkyLight);
|
||||
Object.Destroy(ref PostFxVolume);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
236
Source/Editor/Viewport/Previews/AudioClipPreview.cs
Normal file
236
Source/Editor/Viewport/Previews/AudioClipPreview.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio clip PCM data editor preview.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class AudioClipPreview : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio clip drawing modes.
|
||||
/// </summary>
|
||||
public enum DrawModes
|
||||
{
|
||||
/// <summary>
|
||||
/// Fills the whole control area with the full clip duration.
|
||||
/// </summary>
|
||||
Fill,
|
||||
|
||||
/// <summary>
|
||||
/// Draws single audio clip. Uses the view scale parameter.
|
||||
/// </summary>
|
||||
Single,
|
||||
|
||||
/// <summary>
|
||||
/// Draws the looped audio clip. Uses the view scale parameter.
|
||||
/// </summary>
|
||||
Looped,
|
||||
};
|
||||
|
||||
private readonly object _locker = new object();
|
||||
private AudioClip _asset;
|
||||
private float[] _pcmData;
|
||||
private AudioDataInfo _pcmInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the clip to preview.
|
||||
/// </summary>
|
||||
public AudioClip Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_asset == value)
|
||||
return;
|
||||
|
||||
_asset = value;
|
||||
_pcmData = null;
|
||||
|
||||
if (_asset)
|
||||
{
|
||||
// Use async task to gather PCM data (engine loads it from the asset)
|
||||
Task.Run(DownloadData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether audio data has been fetched from the asset (done as an async task). It is required to be valid in order to draw the audio buffer preview.
|
||||
/// </summary>
|
||||
public bool HasData
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
return _pcmData != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The draw mode.
|
||||
/// </summary>
|
||||
public DrawModes DrawMode = DrawModes.Fill;
|
||||
|
||||
/// <summary>
|
||||
/// The view scale parameter. Increase it to zoom in the audio. Usage depends on the current <see cref="DrawMode"/>.
|
||||
/// </summary>
|
||||
public float ViewScale = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the audio PCM data spectrum.
|
||||
/// </summary>
|
||||
public Color Color = Color.White;
|
||||
|
||||
/// <summary>
|
||||
/// The audio units per second (on time axis).
|
||||
/// </summary>
|
||||
public static readonly float UnitsPerSecond = 100.0f;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
var info = _pcmInfo;
|
||||
if (_asset == null || _pcmData == null || info.NumSamples == 0)
|
||||
return;
|
||||
var height = Height;
|
||||
var width = Width;
|
||||
var samplesPerChannel = info.NumSamples / info.NumChannels;
|
||||
var length = (float)samplesPerChannel / info.SampleRate;
|
||||
var color = Color;
|
||||
if (!EnabledInHierarchy)
|
||||
color *= 0.4f;
|
||||
|
||||
// Compute the scaled y-value used to render the channel data
|
||||
float sampleYScale = height / info.NumChannels;
|
||||
|
||||
// Compute amount of samples that are contained in the view
|
||||
float unitsPerSecond = UnitsPerSecond * ViewScale;
|
||||
float clipDefaultWidth = length * unitsPerSecond;
|
||||
float clipsInView = width / clipDefaultWidth;
|
||||
float clipWidth;
|
||||
uint samplesPerIndex;
|
||||
switch (DrawMode)
|
||||
{
|
||||
case DrawModes.Fill:
|
||||
clipsInView = 1.0f;
|
||||
clipWidth = width;
|
||||
samplesPerIndex = (uint)(samplesPerChannel / width);
|
||||
break;
|
||||
case DrawModes.Single:
|
||||
clipsInView = Mathf.Min(clipsInView, 1.0f);
|
||||
clipWidth = clipDefaultWidth;
|
||||
samplesPerIndex = (uint)(info.SampleRate / unitsPerSecond) * info.NumChannels;
|
||||
break;
|
||||
case DrawModes.Looped:
|
||||
clipWidth = width / clipsInView;
|
||||
samplesPerIndex = (uint)(info.SampleRate / unitsPerSecond) * info.NumChannels;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
const uint maxSamplesPerIndex = 64;
|
||||
uint samplesPerIndexDiff = Math.Max(1, samplesPerIndex / Math.Min(samplesPerIndex, maxSamplesPerIndex));
|
||||
|
||||
// Render each clip separately
|
||||
for (uint clipIndex = 0; clipIndex < Mathf.CeilToInt(clipsInView); clipIndex++)
|
||||
{
|
||||
var clipX = clipWidth * clipIndex;
|
||||
var clipRight = Mathf.Min(width, clipX + clipWidth);
|
||||
|
||||
// Render each channel separately so outer loop is the sound wave channel index
|
||||
for (uint channelIndex = 0; channelIndex < info.NumChannels; channelIndex++)
|
||||
{
|
||||
uint currentSample = channelIndex;
|
||||
float yCenter = Y + ((2 * channelIndex) + 1) * height / (2.0f * info.NumChannels);
|
||||
|
||||
// Loop through each pixel (in x direction)
|
||||
for (float pixelX = clipX; pixelX < clipRight; pixelX++)
|
||||
{
|
||||
// Reset the sample sum and num samples in pixel for each pixel
|
||||
float samplesSum = 0;
|
||||
int samplesInPixel = 0;
|
||||
|
||||
// Loop through all pixels in this x-frame and sum all audio data
|
||||
uint samplesEnd = Math.Min(currentSample + samplesPerIndex, info.NumSamples);
|
||||
for (uint sampleIndex = currentSample; sampleIndex < samplesEnd; sampleIndex += samplesPerIndexDiff)
|
||||
{
|
||||
samplesSum += Mathf.Abs(_pcmData[sampleIndex]);
|
||||
samplesInPixel++;
|
||||
}
|
||||
currentSample = samplesEnd;
|
||||
if (samplesInPixel > 0)
|
||||
{
|
||||
float averageSampleValue = samplesSum / samplesInPixel;
|
||||
float averageSampleValueScaled = averageSampleValue * sampleYScale;
|
||||
|
||||
// Don't try to draw anything if the audio data was too quiet
|
||||
if (averageSampleValueScaled > 0.1f)
|
||||
{
|
||||
// Draw vertical line mirrored around x-axis for channel equal to average sample value height
|
||||
Render2D.DrawLine(new Vector2(pixelX, yCenter - averageSampleValueScaled), new Vector2(pixelX, yCenter + averageSampleValueScaled), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the audio clip raw PCM data. Use it from async thread to prevent blocking,
|
||||
/// </summary>
|
||||
private void DownloadData()
|
||||
{
|
||||
var asset = _asset;
|
||||
|
||||
if (!asset)
|
||||
{
|
||||
Editor.LogWarning("Failed to get audio clip PCM data. Missing asset.");
|
||||
return;
|
||||
}
|
||||
|
||||
float[] data;
|
||||
AudioDataInfo dataInfo;
|
||||
try
|
||||
{
|
||||
asset.ExtractDataFloat(out data, out dataInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogWarning("Failed to get audio clip PCM data. " + ex.Message);
|
||||
Editor.LogWarning(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Length != dataInfo.NumSamples)
|
||||
{
|
||||
Editor.LogWarning("Failed to get audio clip PCM data. Invalid samples count. Returned buffer has other size.");
|
||||
}
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
// If asset has been modified during data fetching, ignore it
|
||||
if (_asset == asset)
|
||||
{
|
||||
_pcmData = data;
|
||||
_pcmInfo = dataInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Source/Editor/Viewport/Previews/CubeTexturePreview.cs
Normal file
251
Source/Editor/Viewport/Previews/CubeTexturePreview.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Cube Texture asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Previews.MaterialPreview" />
|
||||
public class CubeTexturePreview : MaterialPreview
|
||||
{
|
||||
private ChannelFlags _channelFlags = ChannelFlags.All;
|
||||
private bool _usePointSampler = false;
|
||||
private float _mipLevel = -1;
|
||||
private ContextMenu _mipWidgetMenu;
|
||||
private ContextMenuButton _filterWidgetPointButton;
|
||||
private ContextMenuButton _filterWidgetLinearButton;
|
||||
|
||||
/// <summary>
|
||||
/// The preview material instance used to draw texture.
|
||||
/// </summary>
|
||||
protected MaterialInstance _previewMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cube texture to preview.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cube texture.
|
||||
/// </value>
|
||||
public CubeTexture CubeTexture
|
||||
{
|
||||
set
|
||||
{
|
||||
// Prepare material and assign texture asset as a parameter
|
||||
if (_previewMaterial == null || _previewMaterial.WaitForLoaded())
|
||||
{
|
||||
// Error
|
||||
Editor.LogError("Cannot load preview material.");
|
||||
return;
|
||||
}
|
||||
|
||||
var baseMaterial = _previewMaterial.BaseMaterial;
|
||||
if (baseMaterial == null || baseMaterial.WaitForLoaded())
|
||||
{
|
||||
// Error
|
||||
Editor.LogError("Cannot load base material for preview material.");
|
||||
return;
|
||||
}
|
||||
|
||||
_previewMaterial.SetParameterValue("CubeTexture", value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view channels to show.
|
||||
/// </summary>
|
||||
public ChannelFlags ViewChannels
|
||||
{
|
||||
get => _channelFlags;
|
||||
set
|
||||
{
|
||||
if (_channelFlags != value)
|
||||
{
|
||||
_channelFlags = value;
|
||||
UpdateMask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether use point sampler when drawing the texture. The default value is false.
|
||||
/// </summary>
|
||||
public bool UsePointSampler
|
||||
{
|
||||
get => _usePointSampler;
|
||||
set
|
||||
{
|
||||
if (_usePointSampler != value)
|
||||
{
|
||||
_usePointSampler = value;
|
||||
_previewMaterial.SetParameterValue("PointSampler", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mip level to show. The default value is -1.
|
||||
/// </summary>
|
||||
public float MipLevel
|
||||
{
|
||||
get => _mipLevel;
|
||||
set
|
||||
{
|
||||
if (!Mathf.NearEqual(_mipLevel, value))
|
||||
{
|
||||
_mipLevel = value;
|
||||
_previewMaterial.SetParameterValue("Mip", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CubeTexturePreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
// Create virtual material material
|
||||
_previewMaterial = FlaxEngine.Content.CreateVirtualAsset<MaterialInstance>();
|
||||
if (_previewMaterial != null)
|
||||
_previewMaterial.BaseMaterial = FlaxEngine.Content.LoadAsyncInternal<Material>("Editor/CubeTexturePreviewMaterial");
|
||||
|
||||
// Add widgets
|
||||
if (useWidgets)
|
||||
{
|
||||
// Channels widget
|
||||
var channelsWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
//
|
||||
var channelR = new ViewportWidgetButton("R", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture red channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelR.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Red : (ViewChannels & ~ChannelFlags.Red);
|
||||
var channelG = new ViewportWidgetButton("G", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture green channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelG.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Green : (ViewChannels & ~ChannelFlags.Green);
|
||||
var channelB = new ViewportWidgetButton("B", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture blue channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelB.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Blue : (ViewChannels & ~ChannelFlags.Blue);
|
||||
var channelA = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture alpha channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelA.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Alpha : (ViewChannels & ~ChannelFlags.Alpha);
|
||||
//
|
||||
channelsWidget.Parent = this;
|
||||
|
||||
// Mip widget
|
||||
var mipWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
_mipWidgetMenu = new ContextMenu();
|
||||
_mipWidgetMenu.VisibleChanged += OnMipWidgetMenuOnVisibleChanged;
|
||||
var mipWidgetButton = new ViewportWidgetButton("Mip", SpriteHandle.Invalid, _mipWidgetMenu)
|
||||
{
|
||||
TooltipText = "The mip level to show. The default is -1.",
|
||||
Parent = mipWidget
|
||||
};
|
||||
//
|
||||
mipWidget.Parent = this;
|
||||
|
||||
// Filter widget
|
||||
var filterWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
var filterWidgetMenu = new ContextMenu();
|
||||
filterWidgetMenu.VisibleChanged += OnFilterWidgetMenuVisibleChanged;
|
||||
_filterWidgetPointButton = filterWidgetMenu.AddButton("Point", () => UsePointSampler = true);
|
||||
_filterWidgetLinearButton = filterWidgetMenu.AddButton("Linear", () => UsePointSampler = false);
|
||||
var filterWidgetButton = new ViewportWidgetButton("Filter", SpriteHandle.Invalid, filterWidgetMenu)
|
||||
{
|
||||
TooltipText = "The texture preview filtering mode. The default is Linear.",
|
||||
Parent = filterWidget
|
||||
};
|
||||
//
|
||||
filterWidget.Parent = this;
|
||||
}
|
||||
|
||||
// Link it
|
||||
Material = _previewMaterial;
|
||||
}
|
||||
|
||||
private void OnFilterWidgetMenuVisibleChanged(Control control)
|
||||
{
|
||||
if (!control.Visible)
|
||||
return;
|
||||
|
||||
_filterWidgetPointButton.Checked = UsePointSampler;
|
||||
_filterWidgetLinearButton.Checked = !UsePointSampler;
|
||||
}
|
||||
|
||||
private void OnMipWidgetMenuOnVisibleChanged(Control control)
|
||||
{
|
||||
if (!control.Visible)
|
||||
return;
|
||||
|
||||
var textureObj = _previewMaterial.GetParameterValue("CubeTexture");
|
||||
|
||||
if (textureObj is TextureBase texture && !texture.WaitForLoaded())
|
||||
{
|
||||
_mipWidgetMenu.ItemsContainer.DisposeChildren();
|
||||
var mipLevels = texture.MipLevels;
|
||||
for (int i = -1; i < mipLevels; i++)
|
||||
{
|
||||
var button = _mipWidgetMenu.AddButton(i.ToString(), OnMipWidgetClicked);
|
||||
button.Tag = i;
|
||||
button.Checked = Mathf.Abs(MipLevel - i) < 0.9f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMipWidgetClicked(ContextMenuButton button)
|
||||
{
|
||||
MipLevel = (int)button.Tag;
|
||||
}
|
||||
|
||||
private void UpdateMask()
|
||||
{
|
||||
Vector4 mask = Vector4.One;
|
||||
if ((_channelFlags & ChannelFlags.Red) == 0)
|
||||
mask.X = 0;
|
||||
if ((_channelFlags & ChannelFlags.Green) == 0)
|
||||
mask.Y = 0;
|
||||
if ((_channelFlags & ChannelFlags.Blue) == 0)
|
||||
mask.Z = 0;
|
||||
if ((_channelFlags & ChannelFlags.Alpha) == 0)
|
||||
mask.W = 0;
|
||||
_previewMaterial.SetParameterValue("Mask", mask);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
ViewportWidgetsContainer.ArrangeWidgets(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasLoadedAssets => base.HasLoadedAssets && _previewMaterial.IsLoaded && _previewMaterial.BaseMaterial.IsLoaded;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Material = null;
|
||||
Object.Destroy(ref _previewMaterial);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Source/Editor/Viewport/Previews/IESProfilePreview.cs
Normal file
72
Source/Editor/Viewport/Previews/IESProfilePreview.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Preview control for <see cref="IESProfile"/> asset.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Previews.TexturePreviewBase" />
|
||||
public class IESProfilePreview : TexturePreviewBase
|
||||
{
|
||||
private IESProfile _asset;
|
||||
private MaterialInstance _previewMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the asset to preview.
|
||||
/// </summary>
|
||||
public IESProfile Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
if (_asset != value)
|
||||
{
|
||||
_asset = value;
|
||||
_previewMaterial.SetParameterValue("Texture", value);
|
||||
UpdateTextureRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IESProfilePreview"/> class.
|
||||
/// </summary>
|
||||
public IESProfilePreview()
|
||||
{
|
||||
var baseMaterial = FlaxEngine.Content.LoadAsyncInternal<Material>(EditorAssets.IesProfilePreviewMaterial);
|
||||
|
||||
// Wait for base (don't want to async material parameters set due to async loading)
|
||||
if (baseMaterial == null || baseMaterial.WaitForLoaded())
|
||||
throw new FlaxException("Cannot load IES Profile preview material.");
|
||||
|
||||
// Create preview material (virtual)
|
||||
_previewMaterial = baseMaterial.CreateVirtualInstance();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Object.Destroy(ref _previewMaterial);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void CalculateTextureRect(out Rectangle rect)
|
||||
{
|
||||
CalculateTextureRect(new Vector2(256), Size, out rect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawTexture(ref Rectangle rect)
|
||||
{
|
||||
// Check if has loaded asset
|
||||
if (_asset && _asset.IsLoaded)
|
||||
{
|
||||
Render2D.DrawMaterial(_previewMaterial, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
342
Source/Editor/Viewport/Previews/MaterialPreview.cs
Normal file
342
Source/Editor/Viewport/Previews/MaterialPreview.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.Surface;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Material or Material Instance asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class MaterialPreview : AssetPreview, IVisjectSurfaceOwner
|
||||
{
|
||||
private static readonly string[] Models =
|
||||
{
|
||||
"Sphere",
|
||||
"Cube",
|
||||
"Plane",
|
||||
"Cylinder",
|
||||
"Cone"
|
||||
};
|
||||
|
||||
private StaticModel _previewModel;
|
||||
private Decal _decal;
|
||||
private Terrain _terrain;
|
||||
private ParticleEffect _particleEffect;
|
||||
private MaterialBase _particleEffectMaterial;
|
||||
private ParticleEmitter _particleEffectEmitter;
|
||||
private ParticleSystem _particleEffectSystem;
|
||||
private ParticleEmitterSurface _particleEffectSurface;
|
||||
private MaterialBase _material;
|
||||
private int _selectedModelIndex;
|
||||
private Image _guiMaterialControl;
|
||||
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>.
|
||||
/// </summary>
|
||||
public MaterialBase Material
|
||||
{
|
||||
get => _material;
|
||||
set
|
||||
{
|
||||
if (_material != value)
|
||||
{
|
||||
_material = value;
|
||||
UpdateMaterial();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected preview model index.
|
||||
/// </summary>
|
||||
public int SelectedModelIndex
|
||||
{
|
||||
get => _selectedModelIndex;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > Models.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
_selectedModelIndex = value;
|
||||
_previewModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/" + Models[value]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MaterialPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public MaterialPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
// Setup preview scene
|
||||
_previewModel = new StaticModel();
|
||||
_previewModel.Transform = new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f));
|
||||
SelectedModelIndex = 0;
|
||||
|
||||
// Link actors for rendering
|
||||
Task.AddCustomActor(_previewModel);
|
||||
|
||||
// Create context menu for primitive switching
|
||||
if (useWidgets && ViewWidgetButtonMenu != null)
|
||||
{
|
||||
ViewWidgetButtonMenu.AddSeparator();
|
||||
var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu;
|
||||
|
||||
// Fill out all models
|
||||
for (int i = 0; i < Models.Length; i++)
|
||||
{
|
||||
var button = modelSelect.AddButton(Models[i]);
|
||||
button.Tag = i;
|
||||
}
|
||||
|
||||
// Link the action
|
||||
modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasLoadedAssets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.HasLoadedAssets)
|
||||
return false;
|
||||
UpdateMaterial();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
UpdateMaterial();
|
||||
}
|
||||
|
||||
private void UpdateMaterial()
|
||||
{
|
||||
// If material is a surface link it to the preview model.
|
||||
// Otherwise use postFx volume to render custom postFx material.
|
||||
MaterialBase surfaceMaterial = null;
|
||||
MaterialBase postFxMaterial = null;
|
||||
MaterialBase decalMaterial = null;
|
||||
MaterialBase guiMaterial = null;
|
||||
MaterialBase terrainMaterial = null;
|
||||
MaterialBase particleMaterial = null;
|
||||
bool usePreviewActor = true;
|
||||
if (_material != null)
|
||||
{
|
||||
if (_material is MaterialInstance materialInstance && materialInstance.BaseMaterial == null)
|
||||
{
|
||||
// Material instance without a base material should not be used
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (_material.Info.Domain)
|
||||
{
|
||||
case MaterialDomain.Surface:
|
||||
surfaceMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.PostProcess:
|
||||
postFxMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.Decal:
|
||||
decalMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.GUI:
|
||||
usePreviewActor = false;
|
||||
guiMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.Terrain:
|
||||
usePreviewActor = false;
|
||||
terrainMaterial = _material;
|
||||
break;
|
||||
case MaterialDomain.Particle:
|
||||
usePreviewActor = false;
|
||||
particleMaterial = _material;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Surface
|
||||
if (_previewModel.Model == null)
|
||||
throw new Exception("Missing preview model asset.");
|
||||
if (_previewModel.Model.WaitForLoaded())
|
||||
throw new Exception("Preview model asset failed to load.");
|
||||
_previewModel.SetMaterial(0, surfaceMaterial);
|
||||
_previewModel.IsActive = usePreviewActor;
|
||||
|
||||
// PostFx
|
||||
_postFxMaterialsCache[0] = postFxMaterial;
|
||||
PostFxVolume.PostFxMaterials = new PostFxMaterialsSettings
|
||||
{
|
||||
Materials = _postFxMaterialsCache,
|
||||
};
|
||||
|
||||
// Decal
|
||||
if (decalMaterial && _decal == null)
|
||||
{
|
||||
_decal = new Decal();
|
||||
_decal.Size = new Vector3(100.0f);
|
||||
_decal.LocalOrientation = Quaternion.RotationZ(Mathf.PiOverTwo);
|
||||
Task.AddCustomActor(_decal);
|
||||
}
|
||||
if (_decal)
|
||||
{
|
||||
_decal.Material = decalMaterial;
|
||||
}
|
||||
|
||||
// GUI
|
||||
if (guiMaterial && _guiMaterialControl == null)
|
||||
{
|
||||
_guiMaterialControl = new Image
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
KeepAspectRatio = false,
|
||||
Brush = new MaterialBrush(),
|
||||
Parent = this,
|
||||
IndexInParent = 0,
|
||||
};
|
||||
}
|
||||
if (_guiMaterialControl != null)
|
||||
{
|
||||
((MaterialBrush)_guiMaterialControl.Brush).Material = guiMaterial;
|
||||
_guiMaterialControl.Enabled = _guiMaterialControl.Visible = guiMaterial != null;
|
||||
}
|
||||
|
||||
// Terrain
|
||||
if (terrainMaterial && _terrain == null)
|
||||
{
|
||||
_terrain = new Terrain();
|
||||
_terrain.Setup(1, 63);
|
||||
var chunkSize = _terrain.ChunkSize;
|
||||
var heightMapSize = chunkSize * Terrain.PatchEdgeChunksCount + 1;
|
||||
var heightMapLength = heightMapSize * heightMapSize;
|
||||
var heightmap = new float[heightMapLength];
|
||||
var patchCoord = new Int2(0, 0);
|
||||
_terrain.AddPatch(ref patchCoord);
|
||||
_terrain.SetupPatchHeightMap(ref patchCoord, heightmap, null, true);
|
||||
_terrain.LocalPosition = new Vector3(-1000, 0, -1000);
|
||||
Task.AddCustomActor(_terrain);
|
||||
}
|
||||
if (_terrain != null)
|
||||
{
|
||||
_terrain.IsActive = terrainMaterial != null;
|
||||
_terrain.Material = terrainMaterial;
|
||||
}
|
||||
|
||||
// Particle
|
||||
if (particleMaterial && _particleEffect == null)
|
||||
{
|
||||
_particleEffect = new ParticleEffect();
|
||||
_particleEffect.IsLooping = true;
|
||||
_particleEffect.UseTimeScale = false;
|
||||
Task.AddCustomActor(_particleEffect);
|
||||
}
|
||||
if (_particleEffect != null)
|
||||
{
|
||||
_particleEffect.IsActive = particleMaterial != null;
|
||||
if (particleMaterial)
|
||||
_particleEffect.UpdateSimulation();
|
||||
if (_particleEffectMaterial != particleMaterial && particleMaterial)
|
||||
{
|
||||
_particleEffectMaterial = particleMaterial;
|
||||
if (!_particleEffectEmitter)
|
||||
{
|
||||
var srcAsset = FlaxEngine.Content.LoadInternal<ParticleEmitter>("Editor/Particles/Particle Material Preview");
|
||||
Editor.Instance.ContentEditing.FastTempAssetClone(srcAsset.Path, out var clonedPath);
|
||||
_particleEffectEmitter = FlaxEngine.Content.Load<ParticleEmitter>(clonedPath);
|
||||
}
|
||||
if (_particleEffectSurface == null)
|
||||
_particleEffectSurface = new ParticleEmitterSurface(this, null, null);
|
||||
if (_particleEffectEmitter)
|
||||
{
|
||||
if (!_particleEffectSurface.Load())
|
||||
{
|
||||
var spriteModuleNode = _particleEffectSurface.FindNode(15, 400);
|
||||
spriteModuleNode.Values[2] = particleMaterial.ID;
|
||||
_particleEffectSurface.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_material = null;
|
||||
|
||||
if (_guiMaterialControl != null)
|
||||
{
|
||||
_guiMaterialControl.Dispose();
|
||||
_guiMaterialControl = null;
|
||||
}
|
||||
|
||||
Object.Destroy(ref _previewModel);
|
||||
Object.Destroy(ref _decal);
|
||||
Object.Destroy(ref _terrain);
|
||||
Object.Destroy(ref _particleEffect);
|
||||
Object.Destroy(ref _particleEffectEmitter);
|
||||
Object.Destroy(ref _particleEffectSystem);
|
||||
_particleEffectMaterial = null;
|
||||
_particleEffectSurface = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
string ISurfaceContext.SurfaceName => string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
byte[] ISurfaceContext.SurfaceData
|
||||
{
|
||||
get => _particleEffectEmitter.LoadSurface(false);
|
||||
set
|
||||
{
|
||||
_particleEffectEmitter.SaveSurface(value);
|
||||
_particleEffectEmitter.Reload();
|
||||
|
||||
if (!_particleEffectSystem)
|
||||
{
|
||||
_particleEffectSystem = FlaxEngine.Content.CreateVirtualAsset<ParticleSystem>();
|
||||
_particleEffectSystem.Init(_particleEffectEmitter, 5.0f);
|
||||
_particleEffect.ParticleSystem = _particleEffectSystem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ISurfaceContext.OnContextCreated(VisjectSurfaceContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Undo Undo => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
void IVisjectSurfaceOwner.OnSurfaceEditedChanged()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IVisjectSurfaceOwner.OnSurfaceGraphEdited()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IVisjectSurfaceOwner.OnSurfaceClose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Source/Editor/Viewport/Previews/ModelPreview.cs
Normal file
95
Source/Editor/Viewport/Previews/ModelPreview.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Model asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class ModelPreview : AssetPreview
|
||||
{
|
||||
private StaticModel _previewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model asset to preview.
|
||||
/// </summary>
|
||||
public Model Model
|
||||
{
|
||||
get => _previewModel.Model;
|
||||
set => _previewModel.Model = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model actor used to preview selected asset.
|
||||
/// </summary>
|
||||
public StaticModel PreviewActor => _previewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether scale the model to the normalized bounds.
|
||||
/// </summary>
|
||||
public bool ScaleToFit { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public ModelPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
Task.Begin += OnBegin;
|
||||
|
||||
// Setup preview scene
|
||||
_previewModel = new StaticModel();
|
||||
|
||||
// Link actors for rendering
|
||||
Task.AddCustomActor(_previewModel);
|
||||
|
||||
if (useWidgets)
|
||||
{
|
||||
// Preview LOD
|
||||
{
|
||||
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
|
||||
var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f);
|
||||
previewLODValue.Parent = previewLOD;
|
||||
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
|
||||
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBegin(RenderTask task, GPUContext context)
|
||||
{
|
||||
if (!ScaleToFit)
|
||||
{
|
||||
_previewModel.Scale = Vector3.One;
|
||||
_previewModel.Position = Vector3.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update preview model scale to fit the preview
|
||||
var model = Model;
|
||||
if (model && model.IsLoaded)
|
||||
{
|
||||
float targetSize = 50.0f;
|
||||
BoundingBox box = model.GetBox();
|
||||
float maxSize = Mathf.Max(0.001f, box.Size.MaxValue);
|
||||
float scale = targetSize / maxSize;
|
||||
_previewModel.Scale = new Vector3(scale);
|
||||
_previewModel.Position = box.Center * (-0.5f * scale) + new Vector3(0, -10, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Ensure to cleanup created actor objects
|
||||
Object.Destroy(ref _previewModel);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs
Normal file
90
Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEngine;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Particle Emitter asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class ParticleEmitterPreview : ParticleSystemPreview
|
||||
{
|
||||
private ParticleEmitter _emitter;
|
||||
private ParticleSystem _system;
|
||||
private float _playbackDuration = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the particle emitter asset to preview.
|
||||
/// </summary>
|
||||
public ParticleEmitter Emitter
|
||||
{
|
||||
get => _emitter;
|
||||
set
|
||||
{
|
||||
if (_emitter != value)
|
||||
{
|
||||
_emitter = value;
|
||||
_system.Init(value, _playbackDuration);
|
||||
PreviewActor.ResetSimulation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the duration of the emitter playback (in seconds).
|
||||
/// </summary>
|
||||
public float PlaybackDuration
|
||||
{
|
||||
get => _playbackDuration;
|
||||
set
|
||||
{
|
||||
value = Mathf.Clamp(value, 0.1f, 100000000000.0f);
|
||||
if (Mathf.NearEqual(_playbackDuration, value))
|
||||
return;
|
||||
|
||||
_playbackDuration = value;
|
||||
if (_system != null)
|
||||
{
|
||||
_system.Init(_emitter, _playbackDuration);
|
||||
PreviewActor.ResetSimulation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParticleEmitterPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public ParticleEmitterPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
_system = FlaxEngine.Content.CreateVirtualAsset<ParticleSystem>();
|
||||
System = _system;
|
||||
|
||||
if (useWidgets)
|
||||
{
|
||||
var playbackDuration = ViewWidgetButtonMenu.AddButton("Duration");
|
||||
var playbackDurationValue = new FloatValueBox(_playbackDuration, 75, 2, 50.0f, 0.1f, 1000000.0f, 0.1f);
|
||||
playbackDurationValue.Parent = playbackDuration;
|
||||
playbackDurationValue.ValueChanged += () => PlaybackDuration = playbackDurationValue.Value;
|
||||
ViewWidgetButtonMenu.VisibleChanged += control => playbackDurationValue.Value = PlaybackDuration;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasLoadedAssets => (_emitter == null || _emitter.IsLoaded) && base.HasLoadedAssets;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Cleanup objects
|
||||
_emitter = null;
|
||||
Object.Destroy(ref _system);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
240
Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
Normal file
240
Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Viewport.Cameras;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Particle System asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class ParticleSystemPreview : AssetPreview
|
||||
{
|
||||
private ParticleEffect _previewEffect;
|
||||
private ContextMenuButton _showBoundsButton;
|
||||
private ContextMenuButton _showOriginButton;
|
||||
private ContextMenuButton _showParticleCounterButton;
|
||||
private StaticModel _boundsModel;
|
||||
private StaticModel _originModel;
|
||||
private bool _showParticlesCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the particle system asset to preview.
|
||||
/// </summary>
|
||||
public ParticleSystem System
|
||||
{
|
||||
get => _previewEffect.ParticleSystem;
|
||||
set => _previewEffect.ParticleSystem = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the particle effect actor used to preview selected asset.
|
||||
/// </summary>
|
||||
public ParticleEffect PreviewActor => _previewEffect;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to play the particles simulation in editor.
|
||||
/// </summary>
|
||||
public bool PlaySimulation { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show particle effect bounding box.
|
||||
/// </summary>
|
||||
public bool ShowBounds
|
||||
{
|
||||
get => _boundsModel?.IsActive ?? false;
|
||||
set
|
||||
{
|
||||
if (value == ShowBounds)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (!_boundsModel)
|
||||
{
|
||||
_boundsModel = new StaticModel();
|
||||
_boundsModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Gizmo/WireBox");
|
||||
_boundsModel.Model.WaitForLoaded();
|
||||
_boundsModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>("Editor/Gizmo/MaterialWireFocus"));
|
||||
Task.AddCustomActor(_boundsModel);
|
||||
}
|
||||
else if (!_boundsModel.IsActive)
|
||||
{
|
||||
_boundsModel.IsActive = true;
|
||||
Task.AddCustomActor(_boundsModel);
|
||||
}
|
||||
|
||||
UpdateBoundsModel();
|
||||
}
|
||||
else
|
||||
{
|
||||
_boundsModel.IsActive = false;
|
||||
Task.RemoveCustomActor(_boundsModel);
|
||||
}
|
||||
|
||||
if (_showBoundsButton != null)
|
||||
_showBoundsButton.Checked = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show particle effect origin point.
|
||||
/// </summary>
|
||||
public bool ShowOrigin
|
||||
{
|
||||
get => _originModel?.IsActive ?? false;
|
||||
set
|
||||
{
|
||||
if (value == ShowOrigin)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (!_originModel)
|
||||
{
|
||||
_originModel = new StaticModel();
|
||||
_originModel.Model = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/Sphere");
|
||||
_originModel.Model.WaitForLoaded();
|
||||
_originModel.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>("Editor/Gizmo/MaterialAxisFocus"));
|
||||
_originModel.Position = _previewEffect.Position;
|
||||
_originModel.Scale = new Vector3(0.1f);
|
||||
Task.AddCustomActor(_originModel);
|
||||
}
|
||||
else if (!_originModel.IsActive)
|
||||
{
|
||||
_originModel.IsActive = true;
|
||||
Task.AddCustomActor(_originModel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_originModel.IsActive = false;
|
||||
Task.RemoveCustomActor(_originModel);
|
||||
}
|
||||
|
||||
if (_showOriginButton != null)
|
||||
_showOriginButton.Checked = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show spawned particles counter (for CPU particles only).
|
||||
/// </summary>
|
||||
public bool ShowParticlesCounter
|
||||
{
|
||||
get => _showParticlesCounter;
|
||||
set
|
||||
{
|
||||
if (value == _showParticlesCounter)
|
||||
return;
|
||||
|
||||
_showParticlesCounter = value;
|
||||
|
||||
if (_showParticleCounterButton != null)
|
||||
_showParticleCounterButton.Checked = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParticleSystemPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public ParticleSystemPreview(bool useWidgets)
|
||||
: base(useWidgets, new FPSCamera())
|
||||
{
|
||||
// Setup preview scene
|
||||
_previewEffect = new ParticleEffect();
|
||||
_previewEffect.UseTimeScale = false;
|
||||
_previewEffect.IsLooping = true;
|
||||
_previewEffect.CustomViewRenderTask = Task;
|
||||
|
||||
// Link actors for rendering
|
||||
Task.AddCustomActor(_previewEffect);
|
||||
|
||||
if (useWidgets)
|
||||
{
|
||||
_showBoundsButton = ViewWidgetShowMenu.AddButton("Bounds", () => ShowBounds = !ShowBounds);
|
||||
_showOriginButton = ViewWidgetShowMenu.AddButton("Origin", () => ShowOrigin = !ShowOrigin);
|
||||
_showParticleCounterButton = ViewWidgetShowMenu.AddButton("Particles Counter", () => ShowParticlesCounter = !ShowParticlesCounter);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBoundsModel()
|
||||
{
|
||||
var bounds = _previewEffect.Box;
|
||||
Transform t = Transform.Identity;
|
||||
t.Translation = bounds.Center;
|
||||
t.Scale = bounds.Size;
|
||||
_boundsModel.Transform = t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fits the particle system into view (scales the emitter based on the current bounds of the system).
|
||||
/// </summary>
|
||||
/// <param name="targetSize">The target size of the effect.</param>
|
||||
public void FitIntoView(float targetSize = 300.0f)
|
||||
{
|
||||
_previewEffect.Scale = Vector3.One;
|
||||
float maxSize = Mathf.Max(0.001f, _previewEffect.Box.Size.MaxValue);
|
||||
_previewEffect.Scale = new Vector3(targetSize / maxSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasLoadedAssets => _previewEffect.HasContentLoaded && base.HasLoadedAssets;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
// Manually update simulation
|
||||
if (PlaySimulation)
|
||||
{
|
||||
_previewEffect.UpdateSimulation();
|
||||
}
|
||||
|
||||
// Keep bounds matching the model
|
||||
if (_boundsModel && _boundsModel.IsActive)
|
||||
{
|
||||
UpdateBoundsModel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
if (_showParticlesCounter)
|
||||
{
|
||||
var count = _previewEffect.ParticlesCount;
|
||||
Render2D.DrawText(
|
||||
Style.Current.FontSmall,
|
||||
"Particles: " + count,
|
||||
new Rectangle(Vector2.Zero, Size),
|
||||
Color.Wheat,
|
||||
TextAlignment.Near,
|
||||
TextAlignment.Far);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Cleanup objects
|
||||
_previewEffect.ParticleSystem = null;
|
||||
Object.Destroy(ref _previewEffect);
|
||||
Object.Destroy(ref _boundsModel);
|
||||
Object.Destroy(ref _originModel);
|
||||
_showBoundsButton = null;
|
||||
_showOriginButton = null;
|
||||
_showParticleCounterButton = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Source/Editor/Viewport/Previews/PrefabPreview.cs
Normal file
96
Source/Editor/Viewport/Previews/PrefabPreview.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefab asset preview editor viewport.
|
||||
/// </summary>
|
||||
/// <seealso cref="AssetPreview" />
|
||||
public class PrefabPreview : AssetPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// The preview that is during prefab instance spawning. Used to link some actors such as UIControl to preview scene and view.
|
||||
/// </summary>
|
||||
internal static PrefabPreview LoadingPreview;
|
||||
|
||||
private Prefab _prefab;
|
||||
private Actor _instance;
|
||||
internal Control customControlLinked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prefab asset to preview.
|
||||
/// </summary>
|
||||
public Prefab Prefab
|
||||
{
|
||||
get => _prefab;
|
||||
set
|
||||
{
|
||||
if (_prefab != value)
|
||||
{
|
||||
if (_instance)
|
||||
{
|
||||
if (customControlLinked != null)
|
||||
{
|
||||
customControlLinked.Parent = null;
|
||||
customControlLinked = null;
|
||||
}
|
||||
Task.RemoveCustomActor(_instance);
|
||||
Object.Destroy(_instance);
|
||||
}
|
||||
|
||||
_prefab = value;
|
||||
|
||||
if (_prefab)
|
||||
{
|
||||
_prefab.WaitForLoaded(); // TODO: use lazy prefab spawning to reduce stalls
|
||||
|
||||
var prevPreview = LoadingPreview;
|
||||
LoadingPreview = this;
|
||||
|
||||
_instance = PrefabManager.SpawnPrefab(_prefab, null);
|
||||
|
||||
LoadingPreview = prevPreview;
|
||||
|
||||
if (_instance == null)
|
||||
{
|
||||
_prefab = null;
|
||||
throw new FlaxException("Failed to spawn a prefab for the preview.");
|
||||
}
|
||||
Task.AddCustomActor(_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the prefab spawned for the preview.
|
||||
/// </summary>
|
||||
public Actor Instance
|
||||
{
|
||||
get => _instance;
|
||||
internal set => _instance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrefabPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
|
||||
public PrefabPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
// Cleanup
|
||||
Prefab = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
677
Source/Editor/Viewport/Previews/TexturePreview.cs
Normal file
677
Source/Editor/Viewport/Previews/TexturePreview.cs
Normal file
@@ -0,0 +1,677 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Viewport.Widgets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for texture previews. Draws a surface in the UI and supports view moving/zooming.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public abstract class TexturePreviewBase : ContainerControl
|
||||
{
|
||||
private Rectangle _textureRect;
|
||||
private Vector2 _lastMosuePos;
|
||||
private Vector2 _viewPos;
|
||||
private float _viewScale = 1.0f;
|
||||
private bool _isMouseDown;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected TexturePreviewBase()
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll;
|
||||
Offsets = Margin.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the view to the center.
|
||||
/// </summary>
|
||||
public void CenterView()
|
||||
{
|
||||
_viewScale = 1.0f;
|
||||
_viewPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the texture rectangle.
|
||||
/// </summary>
|
||||
protected void UpdateTextureRect()
|
||||
{
|
||||
CalculateTextureRect(out _textureRect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the texture rectangle.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle.</param>
|
||||
protected abstract void CalculateTextureRect(out Rectangle rect);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the texture rect fr the given texture and the view size.
|
||||
/// </summary>
|
||||
/// <param name="textureSize">Size of the texture.</param>
|
||||
/// <param name="viewSize">Size of the view.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
protected static void CalculateTextureRect(Vector2 textureSize, Vector2 viewSize, out Rectangle result)
|
||||
{
|
||||
Vector2 size = Vector2.Max(textureSize, Vector2.One);
|
||||
float aspectRatio = size.X / size.Y;
|
||||
float h = viewSize.X / aspectRatio;
|
||||
float w = viewSize.Y * aspectRatio;
|
||||
if (w > h)
|
||||
{
|
||||
float diff = (viewSize.Y - h) * 0.5f;
|
||||
result = new Rectangle(0, diff, viewSize.X, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
float diff = (viewSize.X - w) * 0.5f;
|
||||
result = new Rectangle(diff, 0, w, viewSize.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="rect">The target texture view rectangle.</param>
|
||||
protected abstract void DrawTexture(ref Rectangle rect);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture view rect (scaled and offseted).
|
||||
/// </summary>
|
||||
protected Rectangle TextureViewRect => (_textureRect + _viewPos) * _viewScale;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
Render2D.PushClip(new Rectangle(Vector2.Zero, Size));
|
||||
|
||||
// Calculate texture view rectangle
|
||||
UpdateTextureRect();
|
||||
var textureRect = TextureViewRect;
|
||||
|
||||
// Call drawing
|
||||
DrawTexture(ref textureRect);
|
||||
|
||||
// Add overlay during debugger breakpoint hang
|
||||
if (Editor.Instance.Simulation.IsDuringBreakpointHang)
|
||||
{
|
||||
var bounds = new Rectangle(Vector2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, new Color(0.0f, 0.0f, 0.0f, 0.2f));
|
||||
Render2D.DrawText(Style.Current.FontLarge, "Debugger breakpoint hit...", bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
Render2D.PopClip();
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Vector2 location)
|
||||
{
|
||||
// Store mouse position
|
||||
_lastMosuePos = location;
|
||||
|
||||
base.OnMouseEnter(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
// Check if mouse is down
|
||||
if (_isMouseDown)
|
||||
{
|
||||
// Calculate mouse delta
|
||||
Vector2 delta = location - _lastMosuePos;
|
||||
|
||||
// Move view
|
||||
_viewPos += delta;
|
||||
}
|
||||
|
||||
// Store mouse position
|
||||
_lastMosuePos = location;
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
base.OnMouseLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseWheel(Vector2 location, float delta)
|
||||
{
|
||||
if (base.OnMouseWheel(location, delta))
|
||||
return true;
|
||||
|
||||
// Zoom
|
||||
float prevScale = _viewScale;
|
||||
_viewScale = Mathf.Clamp(_viewScale + delta * 0.24f, 0.001f, 20.0f);
|
||||
|
||||
// Move view to make use of the control much more soother
|
||||
//float coeff = (prevScale + (_viewScale - prevScale)) / prevScale;
|
||||
//_viewPos += (location * coeff - location) / _viewScale;
|
||||
//_viewPos += location / _viewScale;
|
||||
Vector2 sizeDelta = (_viewScale - prevScale) * _textureRect.Size;
|
||||
_viewPos += sizeDelta * 0.5f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
// Set flag
|
||||
_isMouseDown = true;
|
||||
_lastMosuePos = location;
|
||||
Cursor = CursorType.SizeAll;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnSizeChanged()
|
||||
{
|
||||
base.OnSizeChanged();
|
||||
|
||||
// Update texture rectangle and move view to the center
|
||||
UpdateTextureRect();
|
||||
CenterView();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture channel flags.
|
||||
/// </summary>
|
||||
[Flags, HideInEditor]
|
||||
public enum ChannelFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// The none.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The red channel.
|
||||
/// </summary>
|
||||
Red = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The green channel.
|
||||
/// </summary>
|
||||
Green = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The blue channel.
|
||||
/// </summary>
|
||||
Blue = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The alpha channel.
|
||||
/// </summary>
|
||||
Alpha = 8,
|
||||
|
||||
/// <summary>
|
||||
/// All texture channels.
|
||||
/// </summary>
|
||||
All = Red | Green | Blue | Alpha
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for texture previews with custom drawing features. Uses in-build postFx material to render a texture.
|
||||
/// </summary>
|
||||
/// <seealso cref="TexturePreviewBase" />
|
||||
public abstract class TexturePreviewCustomBase : TexturePreviewBase
|
||||
{
|
||||
private ChannelFlags _channelFlags = ChannelFlags.All;
|
||||
private bool _usePointSampler = false;
|
||||
private float _mipLevel = -1;
|
||||
private ContextMenu _mipWidgetMenu;
|
||||
private ContextMenuButton _filterWidgetPointButton;
|
||||
private ContextMenuButton _filterWidgetLinearButton;
|
||||
|
||||
/// <summary>
|
||||
/// The preview material instance used to draw texture.
|
||||
/// </summary>
|
||||
protected MaterialInstance _previewMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the view channels to show.
|
||||
/// </summary>
|
||||
public ChannelFlags ViewChannels
|
||||
{
|
||||
get => _channelFlags;
|
||||
set
|
||||
{
|
||||
if (_channelFlags != value)
|
||||
{
|
||||
_channelFlags = value;
|
||||
UpdateMask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether use point sampler when drawing the texture. The default value is false.
|
||||
/// </summary>
|
||||
public bool UsePointSampler
|
||||
{
|
||||
get => _usePointSampler;
|
||||
set
|
||||
{
|
||||
if (_usePointSampler != value)
|
||||
{
|
||||
_usePointSampler = value;
|
||||
_previewMaterial.SetParameterValue("PointSampler", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mip level to show. The default value is -1.
|
||||
/// </summary>
|
||||
public float MipLevel
|
||||
{
|
||||
get => _mipLevel;
|
||||
set
|
||||
{
|
||||
if (!Mathf.NearEqual(_mipLevel, value))
|
||||
{
|
||||
_mipLevel = value;
|
||||
_previewMaterial.SetParameterValue("Mip", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <param name="useWidgets">True if show viewport widgets.</param>
|
||||
protected TexturePreviewCustomBase(bool useWidgets)
|
||||
{
|
||||
// Create preview material (virtual)
|
||||
var baseMaterial = FlaxEngine.Content.LoadAsyncInternal<Material>("Editor/TexturePreviewMaterial");
|
||||
if (baseMaterial == null)
|
||||
throw new FlaxException("Cannot load texture preview material.");
|
||||
_previewMaterial = baseMaterial.CreateVirtualInstance();
|
||||
if (_previewMaterial == null)
|
||||
throw new FlaxException("Failed to create virtual material instance for preview material.");
|
||||
|
||||
// Add widgets
|
||||
if (useWidgets)
|
||||
{
|
||||
// Channels widget
|
||||
var channelsWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
//
|
||||
var channelR = new ViewportWidgetButton("R", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture red channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelR.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Red : (ViewChannels & ~ChannelFlags.Red);
|
||||
var channelG = new ViewportWidgetButton("G", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture green channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelG.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Green : (ViewChannels & ~ChannelFlags.Green);
|
||||
var channelB = new ViewportWidgetButton("B", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture blue channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelB.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Blue : (ViewChannels & ~ChannelFlags.Blue);
|
||||
var channelA = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true)
|
||||
{
|
||||
Checked = true,
|
||||
TooltipText = "Show/hide texture alpha channel",
|
||||
Parent = channelsWidget
|
||||
};
|
||||
channelA.Toggled += button => ViewChannels = button.Checked ? ViewChannels | ChannelFlags.Alpha : (ViewChannels & ~ChannelFlags.Alpha);
|
||||
//
|
||||
channelsWidget.Parent = this;
|
||||
|
||||
// Mip widget
|
||||
var mipWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
_mipWidgetMenu = new ContextMenu();
|
||||
_mipWidgetMenu.VisibleChanged += OnMipWidgetMenuOnVisibleChanged;
|
||||
var mipWidgetButton = new ViewportWidgetButton("Mip", SpriteHandle.Invalid, _mipWidgetMenu)
|
||||
{
|
||||
TooltipText = "The mip level to show. The default is -1.",
|
||||
Parent = mipWidget
|
||||
};
|
||||
//
|
||||
mipWidget.Parent = this;
|
||||
|
||||
// Filter widget
|
||||
var filterWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
|
||||
var filterWidgetMenu = new ContextMenu();
|
||||
filterWidgetMenu.VisibleChanged += OnFilterWidgetMenuVisibleChanged;
|
||||
_filterWidgetPointButton = filterWidgetMenu.AddButton("Point", () => UsePointSampler = true);
|
||||
_filterWidgetLinearButton = filterWidgetMenu.AddButton("Linear", () => UsePointSampler = false);
|
||||
var filterWidgetButton = new ViewportWidgetButton("Filter", SpriteHandle.Invalid, filterWidgetMenu)
|
||||
{
|
||||
TooltipText = "The texture preview filtering mode. The default is Linear.",
|
||||
Parent = filterWidget
|
||||
};
|
||||
//
|
||||
filterWidget.Parent = this;
|
||||
}
|
||||
|
||||
// Wait for base (don't want to async material parameters set due to async loading)
|
||||
baseMaterial.WaitForLoaded();
|
||||
}
|
||||
|
||||
private void OnFilterWidgetMenuVisibleChanged(Control control)
|
||||
{
|
||||
if (!control.Visible)
|
||||
return;
|
||||
|
||||
_filterWidgetPointButton.Checked = UsePointSampler;
|
||||
_filterWidgetLinearButton.Checked = !UsePointSampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture to draw (material parameter).
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
protected void SetTexture(object value)
|
||||
{
|
||||
_previewMaterial.SetParameterValue("Texture", value);
|
||||
UpdateTextureRect();
|
||||
}
|
||||
|
||||
private void OnMipWidgetMenuOnVisibleChanged(Control control)
|
||||
{
|
||||
if (!control.Visible)
|
||||
return;
|
||||
|
||||
var textureObj = _previewMaterial.GetParameterValue("Texture");
|
||||
|
||||
if (textureObj is TextureBase texture && !texture.WaitForLoaded())
|
||||
{
|
||||
_mipWidgetMenu.ItemsContainer.DisposeChildren();
|
||||
var mipLevels = texture.MipLevels;
|
||||
for (int i = -1; i < mipLevels; i++)
|
||||
{
|
||||
var button = _mipWidgetMenu.AddButton(i.ToString(), OnMipWidgetClicked);
|
||||
button.Tag = i;
|
||||
if (i == -1)
|
||||
button.TooltipText = "Default mip.";
|
||||
button.Checked = Mathf.Abs(MipLevel - i) < 0.9f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMipWidgetClicked(ContextMenuButton button)
|
||||
{
|
||||
MipLevel = (int)button.Tag;
|
||||
}
|
||||
|
||||
private void UpdateMask()
|
||||
{
|
||||
Vector4 mask = Vector4.One;
|
||||
if ((_channelFlags & ChannelFlags.Red) == 0)
|
||||
mask.X = 0;
|
||||
if ((_channelFlags & ChannelFlags.Green) == 0)
|
||||
mask.Y = 0;
|
||||
if ((_channelFlags & ChannelFlags.Blue) == 0)
|
||||
mask.Z = 0;
|
||||
if ((_channelFlags & ChannelFlags.Alpha) == 0)
|
||||
mask.W = 0;
|
||||
_previewMaterial.SetParameterValue("Mask", mask);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
ViewportWidgetsContainer.ArrangeWidgets(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Object.Destroy(ref _previewMaterial);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture preview GUI control. Draws <see cref="FlaxEngine.Texture"/> in the UI and supports view moving/zomming.
|
||||
/// </summary>
|
||||
/// <seealso cref="TexturePreviewBase" />
|
||||
public class SimpleTexturePreview : TexturePreviewBase
|
||||
{
|
||||
private Texture _asset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the asset to preview.
|
||||
/// </summary>
|
||||
public Texture Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
if (_asset != value)
|
||||
{
|
||||
_asset = value;
|
||||
UpdateTextureRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color used to multiply texture colors.
|
||||
/// </summary>
|
||||
public Color Color { get; set; } = Color.White;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void CalculateTextureRect(out Rectangle rect)
|
||||
{
|
||||
CalculateTextureRect(_asset?.Size ?? new Vector2(100), Size, out rect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawTexture(ref Rectangle rect)
|
||||
{
|
||||
// Background
|
||||
Render2D.FillRectangle(rect, Color.Gray);
|
||||
|
||||
// Check if has loaded asset
|
||||
if (_asset && _asset.IsLoaded)
|
||||
{
|
||||
Render2D.DrawTexture(_asset, rect, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sprite atlas preview GUI control. Draws <see cref="SpriteAtlas"/> in the UI and supports view moving/zomming.
|
||||
/// </summary>
|
||||
/// <seealso cref="TexturePreviewBase" />
|
||||
public class SimpleSpriteAtlasPreview : TexturePreviewBase
|
||||
{
|
||||
private SpriteAtlas _asset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the asset to preview.
|
||||
/// </summary>
|
||||
public SpriteAtlas Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
if (_asset != value)
|
||||
{
|
||||
_asset = value;
|
||||
UpdateTextureRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color used to multiply texture colors.
|
||||
/// </summary>
|
||||
public Color Color { get; set; } = Color.White;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void CalculateTextureRect(out Rectangle rect)
|
||||
{
|
||||
CalculateTextureRect(_asset?.Size ?? new Vector2(100), Size, out rect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawTexture(ref Rectangle rect)
|
||||
{
|
||||
// Background
|
||||
Render2D.FillRectangle(rect, Color.Gray);
|
||||
|
||||
// Check if has loaded asset
|
||||
if (_asset && _asset.IsLoaded)
|
||||
{
|
||||
Render2D.DrawTexture(_asset, rect, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture preview GUI control. Draws <see cref="FlaxEngine.Texture"/> in the UI and supports view moving/zooming.
|
||||
/// Supports texture channels masking and color transformations.
|
||||
/// </summary>
|
||||
/// <seealso cref="TexturePreviewCustomBase" />
|
||||
public class TexturePreview : TexturePreviewCustomBase
|
||||
{
|
||||
private TextureBase _asset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the texture to preview.
|
||||
/// </summary>
|
||||
public TextureBase Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
if (_asset != value)
|
||||
{
|
||||
_asset = value;
|
||||
SetTexture(_asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TexturePreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">True if show viewport widgets.</param>
|
||||
/// <inheritdoc />
|
||||
public TexturePreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void CalculateTextureRect(out Rectangle rect)
|
||||
{
|
||||
CalculateTextureRect(_asset?.Size ?? new Vector2(100), Size, out rect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawTexture(ref Rectangle rect)
|
||||
{
|
||||
// Background
|
||||
Render2D.FillRectangle(rect, Color.Gray);
|
||||
|
||||
// Check if has loaded asset
|
||||
if (_asset && _asset.IsLoaded)
|
||||
{
|
||||
Render2D.DrawMaterial(_previewMaterial, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sprite atlas preview GUI control. Draws <see cref="FlaxEngine.SpriteAtlas"/> in the UI and supports view moving/zomming.
|
||||
/// Supports texture channels masking and color transformations.
|
||||
/// </summary>
|
||||
/// <seealso cref="TexturePreviewCustomBase" />
|
||||
public class SpriteAtlasPreview : TexturePreviewCustomBase
|
||||
{
|
||||
private SpriteAtlas _asset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sprite atlas to preview.
|
||||
/// </summary>
|
||||
public SpriteAtlas Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
if (_asset != value)
|
||||
{
|
||||
_asset = value;
|
||||
SetTexture(_asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpriteAtlasPreview"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useWidgets">True if show viewport widgets.</param>
|
||||
/// <inheritdoc />
|
||||
public SpriteAtlasPreview(bool useWidgets)
|
||||
: base(useWidgets)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void CalculateTextureRect(out Rectangle rect)
|
||||
{
|
||||
CalculateTextureRect(_asset?.Size ?? new Vector2(100), Size, out rect);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawTexture(ref Rectangle rect)
|
||||
{
|
||||
// Background
|
||||
Render2D.FillRectangle(rect, Color.Gray);
|
||||
|
||||
// Check if has loaded asset
|
||||
if (_asset && _asset.IsLoaded)
|
||||
{
|
||||
Render2D.DrawMaterial(_previewMaterial, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
Normal file
162
Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport.Widgets
|
||||
{
|
||||
/// <summary>
|
||||
/// Viewport Widget Button class.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
[HideInEditor]
|
||||
public class ViewportWidgetButton : Control
|
||||
{
|
||||
private string _text;
|
||||
private SpriteHandle _icon;
|
||||
private ContextMenu _cm;
|
||||
private bool _checked;
|
||||
private bool _autoCheck;
|
||||
private bool _isMosueDown;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when user toggles checked state.
|
||||
/// </summary>
|
||||
public event Action<ViewportWidgetButton> Toggled;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when user click the button.
|
||||
/// </summary>
|
||||
public event Action<ViewportWidgetButton> Clicked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="ViewportWidgetButton"/> is checked.
|
||||
/// </summary>
|
||||
public bool Checked
|
||||
{
|
||||
get => _checked;
|
||||
set => _checked = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewportWidgetButton"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="icon">The icon.</param>
|
||||
/// <param name="contextMenu">The context menu.</param>
|
||||
/// <param name="autoCheck">if set to <c>true</c> will be automatic checked on mouse click.</param>
|
||||
public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false)
|
||||
: base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
|
||||
{
|
||||
_text = text;
|
||||
_icon = icon;
|
||||
_cm = contextMenu;
|
||||
_autoCheck = autoCheck;
|
||||
|
||||
if (_cm != null)
|
||||
_cm.VisibleChanged += CmOnVisibleChanged;
|
||||
}
|
||||
|
||||
private void CmOnVisibleChanged(Control control)
|
||||
{
|
||||
if (_cm != null && !_cm.IsOpened)
|
||||
{
|
||||
if (HasParent && Parent.HasParent)
|
||||
{
|
||||
// Focus viewport
|
||||
Parent.Parent.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float CalculateButtonWidth(float textWidth, bool hasIcon)
|
||||
{
|
||||
return (hasIcon ? ViewportWidgetsContainer.WidgetsIconSize : 0) + (textWidth > 0 ? textWidth + 8 : 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
// Cache data
|
||||
var style = Style.Current;
|
||||
const float iconSize = ViewportWidgetsContainer.WidgetsIconSize;
|
||||
var iconRect = new Rectangle(0, (Height - iconSize) / 2, iconSize, iconSize);
|
||||
var textRect = new Rectangle(0, 0, Width + 1, Height + 1);
|
||||
|
||||
// Check if is checked or mouse is over and auto check feature is enabled
|
||||
if (_checked)
|
||||
Render2D.FillRectangle(textRect, style.BackgroundSelected * (IsMouseOver ? 0.9f : 0.6f));
|
||||
else if (_autoCheck && IsMouseOver)
|
||||
Render2D.FillRectangle(textRect, style.BackgroundHighlighted);
|
||||
|
||||
// Check if has icon
|
||||
if (_icon.IsValid)
|
||||
{
|
||||
// Draw icon
|
||||
Render2D.DrawSprite(_icon, iconRect, style.Foreground);
|
||||
|
||||
// Update text rectangle
|
||||
textRect.Location.X += iconSize;
|
||||
textRect.Size.X -= iconSize;
|
||||
}
|
||||
|
||||
// Draw text
|
||||
Render2D.DrawText(style.FontMedium, _text, textRect, style.Foreground * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_isMosueDown = true;
|
||||
}
|
||||
if (_autoCheck)
|
||||
{
|
||||
// Toggle
|
||||
Checked = !_checked;
|
||||
Toggled?.Invoke(this);
|
||||
}
|
||||
|
||||
_cm?.Show(this, new Vector2(-1, Height + 2));
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isMosueDown)
|
||||
{
|
||||
_isMosueDown = false;
|
||||
Clicked?.Invoke(this);
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
var style = Style.Current;
|
||||
|
||||
if (style != null && style.FontMedium)
|
||||
Width = CalculateButtonWidth(style.FontMedium.MeasureText(_text).X, _icon.IsValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Source/Editor/Viewport/Widgets/ViewportWidgetsContainer.cs
Normal file
140
Source/Editor/Viewport/Widgets/ViewportWidgetsContainer.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Viewport.Widgets
|
||||
{
|
||||
/// <summary>
|
||||
/// The viewport widget location.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public enum ViewportWidgetLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// The upper left corner of the parent container.
|
||||
/// </summary>
|
||||
UpperLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The upper right corner of the parent container.
|
||||
/// </summary>
|
||||
UpperRight,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Viewport Widgets Container control
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
[HideInEditor]
|
||||
public class ViewportWidgetsContainer : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The widgets margin.
|
||||
/// </summary>
|
||||
public const float WidgetsMargin = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The widgets height.
|
||||
/// </summary>
|
||||
public const float WidgetsHeight = 18;
|
||||
|
||||
/// <summary>
|
||||
/// The widgets icon size.
|
||||
/// </summary>
|
||||
public const float WidgetsIconSize = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the widget location.
|
||||
/// </summary>
|
||||
public ViewportWidgetLocation WidgetLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewportWidgetsContainer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
public ViewportWidgetsContainer(ViewportWidgetLocation location)
|
||||
: base(0, WidgetsMargin, 64, WidgetsHeight + 2)
|
||||
{
|
||||
AutoFocus = false;
|
||||
WidgetLocation = location;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
// Cache data
|
||||
var style = Style.Current;
|
||||
var clientRect = new Rectangle(Vector2.Zero, Size);
|
||||
|
||||
// Draw background
|
||||
Render2D.FillRectangle(clientRect, style.LightBackground * (IsMouseOver ? 0.3f : 0.2f));
|
||||
|
||||
base.Draw();
|
||||
|
||||
// Draw frame
|
||||
Render2D.DrawRectangle(clientRect, style.BackgroundSelected * (IsMouseOver ? 1.0f : 0.6f));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnChildResized(Control control)
|
||||
{
|
||||
base.OnChildResized(control);
|
||||
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
base.PerformLayoutBeforeChildren();
|
||||
|
||||
float x = 1;
|
||||
for (int i = 0; i < _children.Count; i++)
|
||||
{
|
||||
var c = _children[i];
|
||||
var w = c.Width;
|
||||
|
||||
c.Bounds = new Rectangle(x, 1, w, Height - 2);
|
||||
|
||||
x += w;
|
||||
}
|
||||
|
||||
Width = x + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arranges the widgets of the control.
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
public static void ArrangeWidgets(ContainerControl control)
|
||||
{
|
||||
// Arrange viewport widgets
|
||||
const float margin = ViewportWidgetsContainer.WidgetsMargin;
|
||||
float left = margin;
|
||||
float right = control.Width - margin;
|
||||
for (int i = 0; i < control.ChildrenCount; i++)
|
||||
{
|
||||
if (control.Children[i] is ViewportWidgetsContainer widget && widget.Visible)
|
||||
{
|
||||
float x;
|
||||
switch (widget.WidgetLocation)
|
||||
{
|
||||
case ViewportWidgetLocation.UpperLeft:
|
||||
x = left;
|
||||
left += widget.Width + margin;
|
||||
break;
|
||||
case ViewportWidgetLocation.UpperRight:
|
||||
x = right - widget.Width;
|
||||
right = x - margin;
|
||||
break;
|
||||
default:
|
||||
x = 0;
|
||||
break;
|
||||
}
|
||||
widget.Location = new Vector2(x, margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user