You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View 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();
}
}
}

View 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();
}
}
}

View 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;
}
}
}
}
}

View 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();
}
}
}

View 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);
}
}
}
}

View 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()
{
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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);
}
}
}
}