// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using FlaxEditor.Surface; using FlaxEngine; using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews { /// /// Material or Material Instance asset preview editor viewport. /// /// 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 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]; /// /// Gets or sets the material asset to preview. It can be or . /// public MaterialBase Material { get => _material; set { if (_material != value) { _material = value; UpdateMaterial(); } } } /// /// Gets or sets the selected preview model index. /// public int SelectedModelIndex { get => _selectedModelIndex; set { if (value < 0 || value > Models.Length) throw new ArgumentOutOfRangeException(); _selectedModelIndex = value; _previewModel.Model = FlaxEngine.Content.LoadAsyncInternal("Editor/Primitives/" + Models[value]); _previewModel.Transform = Transforms[value]; } } /// /// Initializes a new instance of the class. /// /// if set to true use widgets. 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 && 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; } } /// public override bool HasLoadedAssets { get { if (!base.HasLoadedAssets) return false; UpdateMaterial(); return true; } } /// 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; case MaterialDomain.Deformable: // TODO: preview Deformable material (eg. by using Spline with Spline Model) 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("Editor/Particles/Particle Material Preview"); Editor.Instance.ContentEditing.FastTempAssetClone(srcAsset.Path, out var clonedPath); _particleEffectEmitter = FlaxEngine.Content.Load(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(); } } } } } /// 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(); } /// string ISurfaceContext.SurfaceName => string.Empty; /// byte[] ISurfaceContext.SurfaceData { get => _particleEffectEmitter.LoadSurface(false); set { _particleEffectEmitter.SaveSurface(value); _particleEffectEmitter.Reload(); if (!_particleEffectSystem) { _particleEffectSystem = FlaxEngine.Content.CreateVirtualAsset(); _particleEffectSystem.Init(_particleEffectEmitter, 5.0f); _particleEffect.ParticleSystem = _particleEffectSystem; } } } /// void ISurfaceContext.OnContextCreated(VisjectSurfaceContext context) { } /// public Undo Undo => null; /// void IVisjectSurfaceOwner.OnSurfaceEditedChanged() { } /// void IVisjectSurfaceOwner.OnSurfaceGraphEdited() { } /// void IVisjectSurfaceOwner.OnSurfaceClose() { } } }