Files
FlaxEngine/Source/Editor/Viewport/Previews/MaterialPreview.cs
2023-06-28 16:27:42 +02:00

412 lines
15 KiB
C#

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Surface;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
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 static readonly Transform[] Transforms =
{
new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)),
new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)),
new Transform(Vector3.Zero, Quaternion.Identity, new Vector3(0.45f)),
new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)),
new Transform(Vector3.Zero, Quaternion.RotationY(Mathf.Pi), new Vector3(0.45f)),
};
private StaticModel _previewModel;
private Decal _decal;
private Terrain _terrain;
private Spline _spline;
private SplineModel _splineModel;
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];
private ContextMenu _modelWidgetButtonMenu;
/// <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]);
_previewModel.Transform = Transforms[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();
SelectedModelIndex = 0;
// Link actors for rendering
Task.AddCustomActor(_previewModel);
// Create context menu for primitive switching
if (useWidgets)
{
// Model mode widget
var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_modelWidgetButtonMenu = new ContextMenu();
_modelWidgetButtonMenu.VisibleChanged += control =>
{
if (!control.Visible)
return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Fill out all models
for (int i = 0; i < Models.Length; i++)
{
var index = i;
var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index;
button.Tag = index;
}
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{
TooltipText = "Change material model",
Parent = modelMode,
};
modelMode.Parent = this;
}
}
/// <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;
MaterialBase deformableMaterial = 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;
case MaterialDomain.Deformable:
usePreviewActor = false;
deformableMaterial = _material;
break;
case MaterialDomain.VolumeParticle:
usePreviewActor = false;
break;
default: throw new ArgumentOutOfRangeException();
}
}
}
// Surface
if (_previewModel.Model != null)
{
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
{
Size = new Vector3(100.0f),
LocalOrientation = Quaternion.RotationZ(Mathf.PiOverTwo)
};
Task.AddCustomActor(_decal);
}
if (_decal)
{
_decal.Material = decalMaterial;
}
// GUI
if (guiMaterial && _guiMaterialControl == null)
{
_guiMaterialControl = new Image
{
Offsets = Margin.Zero,
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
{
IsLooping = true,
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();
}
}
}
}
// Deformable
if (deformableMaterial && _spline == null)
{
_spline = new Spline();
_spline.AddSplineLocalPoint(new Vector3(0, 0, -50.0f), false);
_spline.AddSplineLocalPoint(new Vector3(0, 0, 50.0f));
_splineModel = new SplineModel
{
Scale = new Vector3(0.45f),
Parent = _spline,
};
Task.AddCustomActor(_spline);
}
if (_splineModel != null)
{
_splineModel.IsActive = deformableMaterial != null;
_splineModel.Model = _previewModel.Model;
_splineModel.SetMaterial(0, deformableMaterial);
}
}
/// <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 _spline);
Object.Destroy(ref _splineModel);
Object.Destroy(ref _particleEffect);
Object.Destroy(ref _particleEffectEmitter);
Object.Destroy(ref _particleEffectSystem);
_particleEffectMaterial = null;
_particleEffectSurface = null;
base.OnDestroy();
}
/// <inheritdoc />
public Asset SurfaceAsset => null;
/// <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 />
public VisjectSurfaceContext ParentContext => null;
/// <inheritdoc />
void ISurfaceContext.OnContextCreated(VisjectSurfaceContext context)
{
}
/// <inheritdoc />
public Undo Undo => null;
/// <inheritdoc />
void IVisjectSurfaceOwner.OnSurfaceEditedChanged()
{
}
/// <inheritdoc />
void IVisjectSurfaceOwner.OnSurfaceGraphEdited()
{
}
/// <inheritdoc />
void IVisjectSurfaceOwner.OnSurfaceClose()
{
}
}
}