The null-conditional operator checks for reference equality of the Object, but doesn't check the validity of the unmanaged pointer. This check is corrected in cases where the object was not immediately returned from the bindings layer and may have been destroyed earlier.
805 lines
31 KiB
C#
805 lines
31 KiB
C#
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FlaxEditor.CustomEditors;
|
|
using FlaxEditor.CustomEditors.Editors;
|
|
using FlaxEditor.Gizmo;
|
|
using FlaxEditor.GUI.Tabs;
|
|
using FlaxEditor.Modules;
|
|
using FlaxEditor.SceneGraph;
|
|
using FlaxEditor.Viewport;
|
|
using FlaxEditor.Viewport.Modes;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
using Object = FlaxEngine.Object;
|
|
|
|
namespace FlaxEditor.Tools
|
|
{
|
|
/// <summary>
|
|
/// Vertex painting tab. Allows to modify paint the models vertex colors.
|
|
/// </summary>
|
|
/// <seealso cref="Tab" />
|
|
[HideInEditor]
|
|
public class VertexPaintingTab : Tab
|
|
{
|
|
[CustomEditor(typeof(ProxyEditor))]
|
|
sealed class ProxyObject
|
|
{
|
|
[HideInEditor, NoSerialize]
|
|
public readonly VertexPaintingTab Tab;
|
|
|
|
public ProxyObject(VertexPaintingTab tab)
|
|
{
|
|
Tab = tab;
|
|
}
|
|
|
|
[EditorOrder(0), EditorDisplay("Brush"), Limit(0.0001f, 100000.0f, 0.1f), Tooltip("The size of the paint brush (sphere radius in world-space).")]
|
|
public float BrushSize
|
|
{
|
|
get => Tab._gizmoMode.BrushSize;
|
|
set => Tab._gizmoMode.BrushSize = value;
|
|
}
|
|
|
|
[EditorOrder(10), EditorDisplay("Brush"), Limit(0.0f, 1.0f, 0.01f), Tooltip("The intensity of the brush when painting. Use lower values to make painting smoother and softer.")]
|
|
public float BrushStrength
|
|
{
|
|
get => Tab._gizmoMode.BrushStrength;
|
|
set => Tab._gizmoMode.BrushStrength = value;
|
|
}
|
|
|
|
[EditorOrder(20), EditorDisplay("Brush"), Limit(0.0f, 1.0f, 0.01f), Tooltip("The falloff parameter for the brush. Adjusts the paint strength for the vertices that are far from the brush center. Use lower values to make painting smoother and softer.")]
|
|
public float BrushFalloff
|
|
{
|
|
get => Tab._gizmoMode.BrushFalloff;
|
|
set => Tab._gizmoMode.BrushFalloff = value;
|
|
}
|
|
|
|
[EditorOrder(30), EditorDisplay("Brush", "Model LOD"), Limit(-1, Model.MaxLODs, 0.001f), Tooltip("The index of the model LOD to paint over it. To paint over all LODs use value -1.")]
|
|
public int ModelLOD
|
|
{
|
|
get => Tab._gizmoMode.ModelLOD;
|
|
set => Tab._gizmoMode.ModelLOD = value;
|
|
}
|
|
|
|
[EditorOrder(100), EditorDisplay("Paint"), Tooltip("The paint color (32-bit RGBA).")]
|
|
public Color PaintColor
|
|
{
|
|
get => Tab._gizmoMode.PaintColor;
|
|
set => Tab._gizmoMode.PaintColor = value;
|
|
}
|
|
|
|
[EditorOrder(110), EditorDisplay("Paint"), Tooltip("The paint color mask. Can be used to not exclude certain color channels from painting and preserve their contents.")]
|
|
public VertexPaintingGizmoMode.VertexColorsMask PaintMask
|
|
{
|
|
get => Tab._gizmoMode.PaintMask;
|
|
set => Tab._gizmoMode.PaintMask = value;
|
|
}
|
|
|
|
[EditorOrder(120), EditorDisplay("Paint"), Tooltip("If checked, the painting will be continuous action while you move the brush over the model. Otherwise it will use single-click paint action form ore precise painting.")]
|
|
public bool ContinuousPaint
|
|
{
|
|
get => Tab._gizmoMode.ContinuousPaint;
|
|
set => Tab._gizmoMode.ContinuousPaint = value;
|
|
}
|
|
|
|
[EditorOrder(200), EditorDisplay("Preview"), Tooltip("The preview mode to use for the selected model visualization.")]
|
|
public VertexPaintingGizmoMode.VertexColorsPreviewMode PreviewMode
|
|
{
|
|
get => Tab._gizmoMode.PreviewMode;
|
|
set => Tab._gizmoMode.PreviewMode = value;
|
|
}
|
|
|
|
[EditorOrder(210), EditorDisplay("Preview"), Limit(0, 100.0f, 0.1f), Tooltip("The size of the vertices for the painted vertices visualization.")]
|
|
public float PreviewVertexSize
|
|
{
|
|
get => Tab._gizmoMode.PreviewVertexSize;
|
|
set => Tab._gizmoMode.PreviewVertexSize = value;
|
|
}
|
|
}
|
|
|
|
sealed class ProxyEditor : GenericEditor
|
|
{
|
|
private Button _removeVertexColorsButton;
|
|
private Button _copyVertexColorsButton;
|
|
private Button _pasteVertexColorsButton;
|
|
|
|
public override void Initialize(LayoutElementsContainer layout)
|
|
{
|
|
base.Initialize(layout);
|
|
|
|
layout.Space(10.0f);
|
|
|
|
_removeVertexColorsButton = layout.Button("Remove vertex colors").Button;
|
|
_removeVertexColorsButton.TooltipText = "Removes the painted vertex colors data from the model instance.";
|
|
_removeVertexColorsButton.Clicked += OnRemoveVertexColorsButtonClicked;
|
|
|
|
_copyVertexColorsButton = layout.Button("Copy vertex colors").Button;
|
|
_copyVertexColorsButton.TooltipText = "Copies the painted vertex colors data from the model instance to the system clipboard.";
|
|
_copyVertexColorsButton.Clicked += OnCopyVertexColorsButtonClicked;
|
|
|
|
_pasteVertexColorsButton = layout.Button("Paste vertex colors").Button;
|
|
_pasteVertexColorsButton.TooltipText = "Pastes the copied vertex colors data from the system clipboard to the model instance.";
|
|
_pasteVertexColorsButton.Clicked += OnPasteVertexColorsButtonClicked;
|
|
}
|
|
|
|
private void OnRemoveVertexColorsButtonClicked()
|
|
{
|
|
var proxy = (ProxyObject)Values[0];
|
|
var undoAction = new EditModelVertexColorsAction(proxy.Tab.SelectedModel);
|
|
proxy.Tab.SelectedModel.RemoveVertexColors();
|
|
undoAction.RecordEnd();
|
|
Editor.Instance.Undo.AddAction(undoAction);
|
|
}
|
|
|
|
private void OnCopyVertexColorsButtonClicked()
|
|
{
|
|
var proxy = (ProxyObject)Values[0];
|
|
Clipboard.Text = EditModelVertexColorsAction.GetState(proxy.Tab.SelectedModel);
|
|
}
|
|
|
|
private void OnPasteVertexColorsButtonClicked()
|
|
{
|
|
var proxy = (ProxyObject)Values[0];
|
|
var undoAction = new EditModelVertexColorsAction(proxy.Tab.SelectedModel);
|
|
EditModelVertexColorsAction.SetState(proxy.Tab.SelectedModel, Clipboard.Text);
|
|
undoAction.RecordEnd();
|
|
Editor.Instance.Undo.AddAction(undoAction);
|
|
}
|
|
|
|
public override void Refresh()
|
|
{
|
|
var proxy = (ProxyObject)Values[0];
|
|
_removeVertexColorsButton.Enabled = _copyVertexColorsButton.Enabled = proxy.Tab.SelectedModel?.HasVertexColors ?? false;
|
|
_pasteVertexColorsButton.Enabled = EditModelVertexColorsAction.IsValidState(Clipboard.Text);
|
|
|
|
base.Refresh();
|
|
}
|
|
}
|
|
|
|
private readonly ProxyObject _proxy;
|
|
private readonly CustomEditorPresenter _presenter;
|
|
private VertexPaintingGizmoMode _gizmoMode;
|
|
internal MaterialBase[] _cachedMaterials;
|
|
private readonly Editor _editor;
|
|
|
|
/// <summary>
|
|
/// The cached selected model. It's synchronized with <see cref="SceneEditingModule.Selection"/>.
|
|
/// </summary>
|
|
public StaticModel SelectedModel;
|
|
|
|
/// <summary>
|
|
/// Occurs when selected model gets changed (to a different value).
|
|
/// </summary>
|
|
public event Action SelectedModelChanged;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="VertexPaintingTab"/> class.
|
|
/// </summary>
|
|
/// <param name="icon">The icon.</param>
|
|
/// <param name="editor">The editor instance.</param>
|
|
public VertexPaintingTab(SpriteHandle icon, Editor editor)
|
|
: base(string.Empty, icon)
|
|
{
|
|
_editor = editor;
|
|
|
|
_proxy = new ProxyObject(this);
|
|
var panel = new Panel(ScrollBars.Vertical)
|
|
{
|
|
AnchorPreset = AnchorPresets.StretchAll,
|
|
Offsets = Margin.Zero,
|
|
Parent = this
|
|
};
|
|
var presenter = new CustomEditorPresenter(null, "No model selected");
|
|
presenter.Panel.Parent = panel;
|
|
_presenter = presenter;
|
|
}
|
|
|
|
private void OnSelectionChanged()
|
|
{
|
|
var node = _editor.SceneEditing.SelectionCount > 0 ? _editor.SceneEditing.Selection[0] as ActorNode : null;
|
|
var model = node?.Actor as StaticModel;
|
|
if (model != SelectedModel)
|
|
{
|
|
if (SelectedModel != null)
|
|
{
|
|
Level.SceneSaving -= OnSceneSaving;
|
|
if (_cachedMaterials != null)
|
|
{
|
|
for (int i = 0; i < _cachedMaterials.Length; i++)
|
|
SelectedModel.SetMaterial(i, _cachedMaterials[i]);
|
|
_cachedMaterials = null;
|
|
}
|
|
}
|
|
_presenter.Select(model ? _proxy : null);
|
|
SelectedModel = model;
|
|
if (SelectedModel != null)
|
|
{
|
|
var entries = SelectedModel.Entries;
|
|
_cachedMaterials = new MaterialBase[entries.Length];
|
|
for (int i = 0; i < _cachedMaterials.Length; i++)
|
|
_cachedMaterials[i] = entries[i].Material;
|
|
Level.SceneSaving += OnSceneSaving;
|
|
}
|
|
SelectedModelChanged?.Invoke();
|
|
}
|
|
}
|
|
|
|
private void OnSceneSaving(Scene scene, Guid id)
|
|
{
|
|
// Ensure that selected model has own materials during saving
|
|
if (_cachedMaterials != null && SelectedModel != null)
|
|
{
|
|
for (int i = 0; i < _cachedMaterials.Length; i++)
|
|
SelectedModel.SetMaterial(i, _cachedMaterials[i]);
|
|
}
|
|
}
|
|
|
|
private void UpdateGizmoMode()
|
|
{
|
|
if (_gizmoMode == null)
|
|
{
|
|
_gizmoMode = new VertexPaintingGizmoMode
|
|
{
|
|
Tab = this,
|
|
};
|
|
_editor.Windows.EditWin.Viewport.AddMode(_gizmoMode);
|
|
}
|
|
_editor.Windows.EditWin.Viewport.SetActiveMode(_gizmoMode);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnSelected()
|
|
{
|
|
base.OnSelected();
|
|
|
|
UpdateGizmoMode();
|
|
OnSelectionChanged();
|
|
_editor.SceneEditing.SelectionChanged += OnSelectionChanged;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnDeselected()
|
|
{
|
|
if (SelectedModel)
|
|
{
|
|
Level.SceneSaving -= OnSceneSaving;
|
|
if (_cachedMaterials != null)
|
|
{
|
|
for (int i = 0; i < _cachedMaterials.Length; i++)
|
|
SelectedModel.SetMaterial(i, _cachedMaterials[i]);
|
|
_cachedMaterials = null;
|
|
}
|
|
_presenter.Deselect();
|
|
SelectedModel = null;
|
|
SelectedModelChanged?.Invoke();
|
|
}
|
|
_editor.SceneEditing.SelectionChanged -= OnSelectionChanged;
|
|
|
|
base.OnDeselected();
|
|
}
|
|
}
|
|
|
|
class VertexPaintingGizmoMode : EditorGizmoMode
|
|
{
|
|
public enum VertexColorsPreviewMode
|
|
{
|
|
None,
|
|
RGB,
|
|
Red,
|
|
Green,
|
|
Blue,
|
|
Alpha,
|
|
}
|
|
|
|
[Flags]
|
|
public enum VertexColorsMask
|
|
{
|
|
Red = 1,
|
|
Green = 2,
|
|
Blue = 4,
|
|
Alpha = 8,
|
|
RGB = Red | Green | Blue,
|
|
RGBA = Red | Green | Blue | Alpha,
|
|
}
|
|
|
|
public VertexPaintingTab Tab;
|
|
public VertexPaintingGizmo Gizmo;
|
|
|
|
public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB;
|
|
public float PreviewVertexSize = 6.0f;
|
|
public float BrushSize = 100.0f;
|
|
public float BrushStrength = 1.0f;
|
|
public float BrushFalloff = 1.0f;
|
|
public bool ContinuousPaint;
|
|
public int ModelLOD = -1;
|
|
public Color PaintColor = Color.White;
|
|
public VertexColorsMask PaintMask = VertexColorsMask.RGB;
|
|
|
|
public override void Init(MainEditorGizmoViewport viewport)
|
|
{
|
|
base.Init(viewport);
|
|
|
|
Gizmo = new VertexPaintingGizmo(viewport, this);
|
|
}
|
|
|
|
public override void OnActivated()
|
|
{
|
|
base.OnActivated();
|
|
|
|
Viewport.Gizmos.Active = Gizmo;
|
|
}
|
|
}
|
|
|
|
sealed class VertexPaintingGizmo : GizmoBase
|
|
{
|
|
private struct MeshData
|
|
{
|
|
public Mesh.Vertex[] VertexBuffer;
|
|
}
|
|
|
|
private MaterialInstance _vertexColorsPreviewMaterial;
|
|
private Model _brushModel;
|
|
private MaterialInstance _brushMaterial;
|
|
private MaterialBase _verticesPreviewMaterial;
|
|
private VertexPaintingGizmoMode _gizmoMode;
|
|
private bool _isPainting;
|
|
private int _paintUpdateCount;
|
|
private bool _hasHit;
|
|
private Vector3 _hitLocation;
|
|
private Vector3 _hitNormal;
|
|
private StaticModel _selectedModel;
|
|
private MeshData[][] _meshDatas;
|
|
private bool _meshDatasInProgress;
|
|
private bool _meshDatasCancel;
|
|
private EditModelVertexColorsAction _undoAction;
|
|
|
|
public bool IsPainting => _isPainting;
|
|
|
|
public VertexPaintingGizmo(IGizmoOwner owner, VertexPaintingGizmoMode mode)
|
|
: base(owner)
|
|
{
|
|
_gizmoMode = mode;
|
|
}
|
|
|
|
private void SetPaintModel(StaticModel model)
|
|
{
|
|
if (_selectedModel == model)
|
|
return;
|
|
PaintEnd();
|
|
WaitForMeshDataRequestEnd();
|
|
_selectedModel = model;
|
|
_meshDatas = null;
|
|
_meshDatasInProgress = false;
|
|
_meshDatasCancel = false;
|
|
_hasHit = false;
|
|
RequestMeshData();
|
|
}
|
|
|
|
private void RequestMeshData()
|
|
{
|
|
if (_meshDatasInProgress)
|
|
return;
|
|
if (_meshDatas != null)
|
|
return;
|
|
_meshDatasInProgress = true;
|
|
_meshDatasCancel = false;
|
|
Task.Run(DownloadMeshData);
|
|
}
|
|
|
|
private void WaitForMeshDataRequestEnd()
|
|
{
|
|
if (_meshDatasInProgress)
|
|
{
|
|
_meshDatasCancel = true;
|
|
for (int i = 0; i < 500 && _meshDatasInProgress; i++)
|
|
Thread.Sleep(10);
|
|
}
|
|
}
|
|
|
|
private void DownloadMeshData()
|
|
{
|
|
try
|
|
{
|
|
if (!_selectedModel)
|
|
{
|
|
_meshDatasInProgress = false;
|
|
return;
|
|
}
|
|
var model = _selectedModel.Model;
|
|
var lods = model.LODs;
|
|
_meshDatas = new MeshData[lods.Length][];
|
|
|
|
for (int lodIndex = 0; lodIndex < lods.Length && !_meshDatasCancel; lodIndex++)
|
|
{
|
|
var lod = lods[lodIndex];
|
|
var meshes = lod.Meshes;
|
|
_meshDatas[lodIndex] = new MeshData[meshes.Length];
|
|
|
|
for (int meshIndex = 0; meshIndex < meshes.Length && !_meshDatasCancel; meshIndex++)
|
|
{
|
|
var mesh = meshes[meshIndex];
|
|
_meshDatas[lodIndex][meshIndex] = new MeshData
|
|
{
|
|
VertexBuffer = mesh.DownloadVertexBuffer()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Editor.LogWarning("Failed to get mesh data. " + ex.Message);
|
|
Editor.LogWarning(ex);
|
|
}
|
|
finally
|
|
{
|
|
_meshDatasInProgress = false;
|
|
}
|
|
}
|
|
|
|
private void PaintStart()
|
|
{
|
|
if (IsPainting)
|
|
return;
|
|
|
|
if (Editor.Instance.Undo.Enabled)
|
|
_undoAction = new EditModelVertexColorsAction(_selectedModel);
|
|
_isPainting = true;
|
|
_paintUpdateCount = 0;
|
|
}
|
|
|
|
private void PaintUpdate()
|
|
{
|
|
if (!IsPainting || _gizmoMode.PaintMask == 0)
|
|
return;
|
|
if (!_gizmoMode.ContinuousPaint && _paintUpdateCount > 0)
|
|
return;
|
|
|
|
Profiler.BeginEvent("Vertex Paint");
|
|
|
|
// Ensure to have vertex data ready
|
|
WaitForMeshDataRequestEnd();
|
|
|
|
// Edit the model vertex colors
|
|
var meshDatas = _meshDatas;
|
|
if (meshDatas == null)
|
|
throw new Exception("Missing mesh data of the model to paint.");
|
|
var instanceTransform = _selectedModel.Transform;
|
|
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
|
|
if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors)
|
|
{
|
|
// Initialize the instance vertex colors with originals from the asset
|
|
for (int lodIndex = 0; lodIndex < meshDatas.Length; lodIndex++)
|
|
{
|
|
if (_gizmoMode.ModelLOD != -1 && _gizmoMode.ModelLOD != lodIndex)
|
|
continue;
|
|
var lodData = meshDatas[lodIndex];
|
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
|
{
|
|
var meshData = lodData[meshIndex];
|
|
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
|
|
{
|
|
ref var v = ref meshData.VertexBuffer[vertexIndex];
|
|
_selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, v.Color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (int lodIndex = 0; lodIndex < meshDatas.Length; lodIndex++)
|
|
{
|
|
if (_gizmoMode.ModelLOD != -1 && _gizmoMode.ModelLOD != lodIndex)
|
|
continue;
|
|
var lodData = meshDatas[lodIndex];
|
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
|
{
|
|
var meshData = lodData[meshIndex];
|
|
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
|
|
{
|
|
ref var v = ref meshData.VertexBuffer[vertexIndex];
|
|
var pos = instanceTransform.LocalToWorld(v.Position);
|
|
var dst = Vector3.Distance(ref pos, ref brushSphere.Center);
|
|
if (dst > brushSphere.Radius)
|
|
continue;
|
|
float strength = _gizmoMode.BrushStrength * Mathf.Lerp(1.0f, 1.0f - (float)dst / (float)brushSphere.Radius, _gizmoMode.BrushFalloff);
|
|
if (strength > Mathf.Epsilon)
|
|
{
|
|
// Paint the vertex
|
|
var color = (Color)_selectedModel.GetVertexColor(lodIndex, meshIndex, vertexIndex);
|
|
var paintColor = _gizmoMode.PaintColor;
|
|
var paintMask = _gizmoMode.PaintMask;
|
|
color = new Color
|
|
(
|
|
Mathf.Lerp(color.R, paintColor.R, ((paintMask & VertexPaintingGizmoMode.VertexColorsMask.Red) == VertexPaintingGizmoMode.VertexColorsMask.Red ? strength : 0.0f)),
|
|
Mathf.Lerp(color.G, paintColor.G, ((paintMask & VertexPaintingGizmoMode.VertexColorsMask.Green) == VertexPaintingGizmoMode.VertexColorsMask.Green ? strength : 0.0f)),
|
|
Mathf.Lerp(color.B, paintColor.B, ((paintMask & VertexPaintingGizmoMode.VertexColorsMask.Blue) == VertexPaintingGizmoMode.VertexColorsMask.Blue ? strength : 0.0f)),
|
|
Mathf.Lerp(color.A, paintColor.A, ((paintMask & VertexPaintingGizmoMode.VertexColorsMask.Alpha) == VertexPaintingGizmoMode.VertexColorsMask.Alpha ? strength : 0.0f))
|
|
);
|
|
_selectedModel.SetVertexColor(lodIndex, meshIndex, vertexIndex, color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_paintUpdateCount++;
|
|
|
|
Profiler.EndEvent();
|
|
}
|
|
|
|
private void PaintEnd()
|
|
{
|
|
if (!IsPainting)
|
|
return;
|
|
|
|
if (_undoAction != null)
|
|
{
|
|
_undoAction.RecordEnd();
|
|
Editor.Instance.Undo.AddAction(_undoAction);
|
|
_undoAction = null;
|
|
}
|
|
_isPainting = false;
|
|
_paintUpdateCount = 0;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool IsControllingMouse => IsPainting;
|
|
|
|
/// <inheritdoc />
|
|
public override BoundingSphere FocusBounds => _selectedModel != null ? _selectedModel.Sphere : base.FocusBounds;
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(float dt)
|
|
{
|
|
_hasHit = false;
|
|
|
|
base.Update(dt);
|
|
|
|
if (!IsActive)
|
|
{
|
|
SetPaintModel(null);
|
|
return;
|
|
}
|
|
|
|
// Select model
|
|
var model = _gizmoMode.Tab.SelectedModel;
|
|
SetPaintModel(model);
|
|
if (!model)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Perform detailed tracing to find cursor location for the brush
|
|
var ray = Owner.MouseRay;
|
|
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
|
|
var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
|
|
var hit = Editor.Instance.Scene.Root.RayCast(ref ray, ref view, out var closest, out var hitNormal, rayCastFlags);
|
|
if (hit != null && hit is ActorNode actorNode && actorNode.Actor as StaticModel != model)
|
|
{
|
|
// Cursor hit other model
|
|
PaintEnd();
|
|
return;
|
|
}
|
|
if (hit != null)
|
|
{
|
|
_hasHit = true;
|
|
_hitLocation = ray.GetPoint(closest);
|
|
_hitNormal = hitNormal;
|
|
}
|
|
|
|
// Handle painting
|
|
if (Owner.IsLeftMouseButtonDown)
|
|
PaintStart();
|
|
else
|
|
PaintEnd();
|
|
PaintUpdate();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Pick()
|
|
{
|
|
// Get mouse ray and try to hit any object
|
|
var ray = Owner.MouseRay;
|
|
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
|
|
var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
|
|
var hit = Editor.Instance.Scene.Root.RayCast(ref ray, ref view, out _, rayCastFlags);
|
|
|
|
// Update selection
|
|
var sceneEditing = Editor.Instance.SceneEditing;
|
|
if (hit != null && hit is ActorNode actorNode && actorNode.Actor is StaticModel model)
|
|
{
|
|
sceneEditing.Select(hit);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Draw(ref RenderContext renderContext)
|
|
{
|
|
if (!IsActive || !_selectedModel)
|
|
return;
|
|
|
|
base.Draw(ref renderContext);
|
|
|
|
if (_hasHit)
|
|
{
|
|
var viewOrigin = renderContext.View.Origin;
|
|
|
|
// Draw paint brush
|
|
if (!_brushModel)
|
|
{
|
|
_brushModel = FlaxEngine.Content.LoadAsyncInternal<Model>("Editor/Primitives/Sphere");
|
|
}
|
|
if (!_brushMaterial)
|
|
{
|
|
var material = FlaxEngine.Content.LoadAsyncInternal<Material>(EditorAssets.FoliageBrushMaterial);
|
|
_brushMaterial = material.CreateVirtualInstance();
|
|
}
|
|
if (_brushModel && _brushMaterial)
|
|
{
|
|
_brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options
|
|
_brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer);
|
|
Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal);
|
|
Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin);
|
|
_brushModel.Draw(ref renderContext, _brushMaterial, ref transform);
|
|
}
|
|
|
|
// Draw intersecting vertices
|
|
var meshDatas = _meshDatas;
|
|
if (meshDatas != null && _gizmoMode.PreviewVertexSize > Mathf.Epsilon)
|
|
{
|
|
if (!_verticesPreviewMaterial)
|
|
{
|
|
_verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial);
|
|
}
|
|
var instanceTransform = _selectedModel.Transform;
|
|
var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f);
|
|
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
|
|
var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD;
|
|
lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1);
|
|
var lodData = meshDatas[lodIndex];
|
|
if (lodData != null)
|
|
{
|
|
for (int meshIndex = 0; meshIndex < lodData.Length; meshIndex++)
|
|
{
|
|
var meshData = lodData[meshIndex];
|
|
if (meshData.VertexBuffer == null)
|
|
continue;
|
|
for (int vertexIndex = 0; vertexIndex < meshData.VertexBuffer.Length; vertexIndex++)
|
|
{
|
|
ref var v = ref meshData.VertexBuffer[vertexIndex];
|
|
var pos = instanceTransform.LocalToWorld(v.Position);
|
|
if (brushSphere.Contains(ref pos) == ContainmentType.Disjoint)
|
|
continue;
|
|
Matrix transform = modelScaleMatrix * Matrix.Translation(pos - viewOrigin);
|
|
_brushModel.Draw(ref renderContext, _verticesPreviewMaterial, ref transform);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update vertex colors preview
|
|
var cachedMaterials = _gizmoMode.Tab._cachedMaterials;
|
|
if (cachedMaterials == null)
|
|
return;
|
|
var previewMode = _gizmoMode.PreviewMode;
|
|
if (previewMode == VertexPaintingGizmoMode.VertexColorsPreviewMode.None)
|
|
{
|
|
for (int i = 0; i < cachedMaterials.Length; i++)
|
|
_selectedModel.SetMaterial(i, cachedMaterials[i]);
|
|
return;
|
|
}
|
|
if (!_vertexColorsPreviewMaterial)
|
|
_vertexColorsPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.VertexColorsPreviewMaterial).CreateVirtualInstance();
|
|
if (!_vertexColorsPreviewMaterial)
|
|
return;
|
|
var channelMask = new Float4();
|
|
switch (previewMode)
|
|
{
|
|
case VertexPaintingGizmoMode.VertexColorsPreviewMode.RGB:
|
|
channelMask = new Float4(1, 1, 1, 0);
|
|
break;
|
|
case VertexPaintingGizmoMode.VertexColorsPreviewMode.Red:
|
|
channelMask.X = 1.0f;
|
|
break;
|
|
case VertexPaintingGizmoMode.VertexColorsPreviewMode.Green:
|
|
channelMask.Y = 1.0f;
|
|
break;
|
|
case VertexPaintingGizmoMode.VertexColorsPreviewMode.Blue:
|
|
channelMask.Z = 1.0f;
|
|
break;
|
|
case VertexPaintingGizmoMode.VertexColorsPreviewMode.Alpha:
|
|
channelMask.W = 1.0f;
|
|
break;
|
|
}
|
|
_vertexColorsPreviewMaterial.SetParameterValue("ChannelMask", channelMask);
|
|
for (int i = 0; i < cachedMaterials.Length; i++)
|
|
_selectedModel.SetMaterial(i, _vertexColorsPreviewMaterial);
|
|
}
|
|
|
|
public override void OnActivated()
|
|
{
|
|
base.OnActivated();
|
|
|
|
_hasHit = false;
|
|
}
|
|
|
|
public override void OnDeactivated()
|
|
{
|
|
base.OnDeactivated();
|
|
|
|
PaintEnd();
|
|
SetPaintModel(null);
|
|
Object.Destroy(ref _vertexColorsPreviewMaterial);
|
|
Object.Destroy(ref _brushMaterial);
|
|
_brushModel = null;
|
|
_verticesPreviewMaterial = null;
|
|
}
|
|
}
|
|
|
|
sealed class EditModelVertexColorsAction : IUndoAction
|
|
{
|
|
private Guid _actorId;
|
|
private string _before, _after;
|
|
|
|
public EditModelVertexColorsAction(StaticModel model)
|
|
{
|
|
_actorId = model.ID;
|
|
_before = GetState(model);
|
|
}
|
|
|
|
public static bool IsValidState(string state)
|
|
{
|
|
return state != null && state.Contains("\"VertexColors\":");
|
|
}
|
|
|
|
public static string GetState(StaticModel model)
|
|
{
|
|
var json = model.ToJson();
|
|
var start = json.IndexOf("\"VertexColors\":");
|
|
if (start == -1)
|
|
return null;
|
|
var end = json.IndexOf(']', start);
|
|
json = "{" + json.Substring(start, end - start) + "]}";
|
|
return json;
|
|
}
|
|
|
|
public static void SetState(StaticModel model, string state)
|
|
{
|
|
if (state == null)
|
|
model.RemoveVertexColors();
|
|
else
|
|
Editor.Internal_DeserializeSceneObject(Object.GetUnmanagedPtr(model), state);
|
|
}
|
|
|
|
public void RecordEnd()
|
|
{
|
|
var model = Object.Find<StaticModel>(ref _actorId);
|
|
_after = GetState(model);
|
|
Editor.Instance.Scene.MarkSceneEdited(model.Scene);
|
|
}
|
|
|
|
private void Set(string state)
|
|
{
|
|
var model = Object.Find<StaticModel>(ref _actorId);
|
|
SetState(model, state);
|
|
Editor.Instance.Scene.MarkSceneEdited(model.Scene);
|
|
}
|
|
|
|
public string ActionString => "Edit Vertex Colors";
|
|
|
|
public void Do()
|
|
{
|
|
Set(_after);
|
|
}
|
|
|
|
public void Undo()
|
|
{
|
|
Set(_before);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_before = _after = null;
|
|
}
|
|
}
|
|
}
|