Merge branch 'NoriteSC-GizmoSnaping'

This commit is contained in:
Wojtek Figat
2024-02-24 12:57:16 +01:00
15 changed files with 357 additions and 48 deletions

View File

@@ -51,6 +51,11 @@ namespace FlaxEditor.Gizmo
/// </summary>
bool SnapToGround { get; }
/// <summary>
/// Gets a value indicating whether to use vertex snapping (check if user pressed the given input key to call action).
/// </summary>
bool SnapToVertex { get; }
/// <summary>
/// Gets the view forward direction.
/// </summary>

View File

@@ -269,7 +269,13 @@ namespace FlaxEditor.Gizmo
protected override int SelectionCount => _selectionParents.Count;
/// <inheritdoc />
protected override Transform GetSelectedObject(int index)
protected override SceneGraphNode GetSelectedObject(int index)
{
return _selectionParents[index];
}
/// <inheritdoc />
protected override Transform GetSelectedTransform(int index)
{
return _selectionParents[index].Transform;
}

View File

@@ -72,6 +72,12 @@ namespace FlaxEditor.Gizmo
const float gizmoModelsScale2RealGizmoSize = 0.075f;
Mesh sphereMesh, cubeMesh;
Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3);
Matrix.Multiply(ref m3, ref world, out m1);
mx1 = m1;
mx1.M41 += 0.05f;
switch (_activeMode)
{
case Mode.Translate:
@@ -81,10 +87,6 @@ namespace FlaxEditor.Gizmo
var transAxisMesh = _modelTranslationAxis.LODs[0].Meshes[0];
cubeMesh = _modelCube.LODs[0].Meshes[0];
sphereMesh = _modelSphere.LODs[0].Meshes[0];
Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3);
Matrix.Multiply(ref m3, ref world, out m1);
mx1 = m1;
mx1.M41 += 0.05f;
// X axis
Matrix.RotationY(-Mathf.PiOverTwo, out m2);
@@ -130,10 +132,6 @@ namespace FlaxEditor.Gizmo
break;
var rotationAxisMesh = _modelRotationAxis.LODs[0].Meshes[0];
sphereMesh = _modelSphere.LODs[0].Meshes[0];
Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3);
Matrix.Multiply(ref m3, ref world, out m1);
mx1 = m1;
mx1.M41 += 0.05f;
// X axis
Matrix.RotationZ(Mathf.PiOverTwo, out m2);
@@ -163,10 +161,6 @@ namespace FlaxEditor.Gizmo
var scaleAxisMesh = _modelScaleAxis.LODs[0].Meshes[0];
cubeMesh = _modelCube.LODs[0].Meshes[0];
sphereMesh = _modelSphere.LODs[0].Meshes[0];
Matrix.Scaling(gizmoModelsScale2RealGizmoSize, out m3);
Matrix.Multiply(ref m3, ref world, out m1);
mx1 = m1;
mx1.M41 -= 0.05f;
// X axis
Matrix.RotationY(-Mathf.PiOverTwo, out m2);
@@ -206,6 +200,26 @@ namespace FlaxEditor.Gizmo
break;
}
}
// Vertex snapping
if (verts != null && SelectedModel != null && selectedvert != -1)
{
if (!_modelCube || !_modelCube.IsLoaded)
return;
cubeMesh = _modelCube.LODs[0].Meshes[0];
Transform t = SelectedModel.Transform;
Vector3 selected = ((verts[selectedvert].Position * t.Orientation) * t.Scale) + t.Translation;
Matrix matrix = new Transform(selected, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
cubeMesh.Draw(ref renderContext, _materialSphere, ref matrix);
if (otherVerts != null && otherSelectedvert != -1)
{
t = otherTransform;
matrix = new Transform(selected, t.Orientation, new Float3(gizmoModelsScale2RealGizmoSize)).GetWorld();
cubeMesh.Draw(ref renderContext, _materialSphere, ref matrix);
}
}
}
}
}

View File

@@ -27,7 +27,7 @@ namespace FlaxEditor.Gizmo
// Get center point
Vector3 center = Vector3.Zero;
for (int i = 0; i < count; i++)
center += GetSelectedObject(i).Translation;
center += GetSelectedTransform(i).Translation;
// Return arithmetic average or whatever it means
return center / count;
@@ -36,11 +36,9 @@ namespace FlaxEditor.Gizmo
private bool IntersectsRotateCircle(Vector3 normal, ref Ray ray, out Real distance)
{
var plane = new Plane(Vector3.Zero, normal);
if (!plane.Intersects(ref ray, out distance))
return false;
Vector3 hitPoint = ray.Position + ray.Direction * distance;
Real distanceNormalized = hitPoint.Length / RotateRadiusRaw;
return Mathf.IsInRange(distanceNormalized, 0.9f, 1.1f);
}

View File

@@ -12,42 +12,42 @@ namespace FlaxEditor.Gizmo
/// <summary>
/// None.
/// </summary>
None,
None = 0,
/// <summary>
/// The X axis.
/// </summary>
X,
X = 1,
/// <summary>
/// The Y axis.
/// </summary>
Y,
Y = 2,
/// <summary>
/// The Z axis.
/// </summary>
Z,
Z = 4,
/// <summary>
/// The XY plane.
/// </summary>
XY,
XY = X | Y,
/// <summary>
/// The ZX plane.
/// </summary>
ZX,
ZX = Z | X,
/// <summary>
/// The YZ plane.
/// </summary>
YZ,
YZ = Y | Z,
/// <summary>
/// The center point.
/// </summary>
Center,
Center = 8,
};
/// <summary>

View File

@@ -53,6 +53,15 @@ namespace FlaxEditor.Gizmo
private Vector3 _translationDelta;
private Vector3 _translationScaleSnapDelta;
//vertex snaping stff
private Mesh.Vertex[] verts;
private Mesh.Vertex[] otherVerts;
private Transform otherTransform;
private StaticModel SelectedModel;
private bool hasSelectedVertex;
private int selectedvert;
private int otherSelectedvert;
/// <summary>
/// Gets the gizmo position.
/// </summary>
@@ -108,7 +117,7 @@ namespace FlaxEditor.Gizmo
_startTransforms.Capacity = Mathf.NextPowerOfTwo(count);
for (var i = 0; i < count; i++)
{
_startTransforms.Add(GetSelectedObject(i));
_startTransforms.Add(GetSelectedTransform(i));
}
GetSelectedObjectsBounds(out _startBounds, out _navigationDirty);
@@ -135,11 +144,12 @@ namespace FlaxEditor.Gizmo
private void UpdateGizmoPosition()
{
// Get gizmo pivot
switch (_activePivotType)
{
case PivotType.ObjectCenter:
if (SelectionCount > 0)
Position = GetSelectedObject(0).Translation;
Position = GetSelectedTransform(0).Translation;
break;
case PivotType.SelectionCenter:
Position = GetSelectionCenter();
@@ -148,6 +158,16 @@ namespace FlaxEditor.Gizmo
Position = Vector3.Zero;
break;
}
// Apply vertex snapping
if (verts != null && SelectedModel != null)
{
Transform t = SelectedModel.Transform;
Vector3 selected = ((verts[selectedvert].Position * t.Orientation) * t.Scale) + t.Translation;
Position += -(Position - selected);
}
// Apply current movement
Position += _translationDelta;
}
@@ -179,8 +199,9 @@ namespace FlaxEditor.Gizmo
float gizmoSize = Editor.Instance.Options.Options.Visual.GizmoSize;
_screenScale = (float)(vLength.Length / GizmoScaleFactor * gizmoSize);
}
// Setup world
Quaternion orientation = GetSelectedObject(0).Orientation;
Quaternion orientation = GetSelectedTransform(0).Orientation;
_gizmoWorld = new Transform(position, orientation, new Float3(_screenScale));
if (_activeTransformSpace == TransformSpace.World && _activeMode != Mode.Scale)
{
@@ -421,8 +442,12 @@ namespace FlaxEditor.Gizmo
{
switch (_activeMode)
{
case Mode.Scale:
case Mode.Translate:
UpdateTranslateScale();
if (Owner.SnapToVertex)
UpdateVertexSnapping();
break;
case Mode.Scale:
UpdateTranslateScale();
break;
case Mode.Rotate:
@@ -434,7 +459,12 @@ namespace FlaxEditor.Gizmo
{
// If nothing selected, try to select any axis
if (!isLeftBtnDown && !Owner.IsRightMouseButtonDown)
SelectAxis();
{
if (Owner.SnapToVertex)
SelectVertexSnapping();
else
SelectAxis();
}
}
// Set positions of the gizmo
@@ -503,6 +533,7 @@ namespace FlaxEditor.Gizmo
// Deactivate
_isActive = false;
_activeAxis = Axis.None;
EndVertexSnapping();
return;
}
@@ -517,6 +548,137 @@ namespace FlaxEditor.Gizmo
UpdateMatrices();
}
private void SelectVertexSnapping()
{
// Find the closest object in selection that is hit by the mouse ray
var ray = new SceneGraphNode.RayCastData
{
Ray = Owner.MouseRay,
};
var closestDistance = Real.MaxValue;
StaticModel closestModel = null;
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
if (obj.EditableObject is StaticModel model)
{
if (obj.RayCastSelf(ref ray, out var distance, out var normal) && distance < closestDistance)
{
closestDistance = distance;
closestModel = model;
}
}
}
if (closestModel == null)
{
// Find the closest object in selection (in case ray didn't hit anything)
for (int i = 0; i < SelectionCount; i++)
{
var obj = GetSelectedObject(i);
if (obj.EditableObject is StaticModel model)
{
var bounds = model.Box;
CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point);
var distance = Vector3.Distance(ref point, ref ray.Ray.Position);
if (distance < closestDistance)
{
closestDistance = distance;
closestModel = model;
}
}
}
}
SelectedModel = closestModel;
if (closestModel == null)
return;
// Find the closest vertex to bounding box point (collision detection approximation)
// TODO: replace this with collision detection which supports concave shapes (compute shader)
var hitPoint = SelectedModel.Transform.WorldToLocal(ray.Ray.GetPoint(closestDistance));
// TODO: support multi-mesh models
verts = closestModel.Model.LODs[0].Meshes[0].DownloadVertexBuffer();
closestDistance = Vector3.Distance(hitPoint, verts[0].Position);
for (int j = 0; j < verts.Length; j++)
{
var distance = Vector3.Distance(hitPoint, verts[j].Position);
if (distance <= closestDistance)
{
closestDistance = distance;
selectedvert = j;
}
}
}
private void EndVertexSnapping()
{
// Clear current vertex snapping data
SelectedModel = null;
verts = null;
otherVerts = null;
}
private void UpdateVertexSnapping()
{
if (Owner.SceneGraphRoot == null)
return;
Profiler.BeginEvent("VertexSnap");
// Ray cast others
if (verts != null)
{
var ray = Owner.MouseRay;
var rayCast = new SceneGraphNode.RayCastData
{
Ray = ray,
Flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives,
ExcludeObjects = new(),
};
for (int i = 0; i < SelectionCount; i++)
rayCast.ExcludeObjects.Add(GetSelectedObject(i));
// Raycast objects
var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _);
if (hit != null && hit.EditableObject is StaticModel model)
{
otherTransform = model.Transform;
Vector3 point = rayCast.Ray.Position + (rayCast.Ray.Direction * distance);
//[To Do] comlite this there is not suport for multy mesh model
otherVerts = model.Model.LODs[0].Meshes[0].DownloadVertexBuffer();
//find closest vertex to bounding box point (collision detection approximation)
//[ToDo] replace this with collision detection with is suporting concave shapes (compute shader)
point = hit.Transform.WorldToLocal(point);
var closestDistance = Vector3.Distance(point, otherVerts[0].Position);
for (int i = 0; i < otherVerts.Length; i++)
{
distance = Vector3.Distance(point, otherVerts[i].Position);
if (distance < closestDistance)
{
closestDistance = distance;
otherSelectedvert = i;
}
}
if (closestDistance > 25)
{
otherSelectedvert = -1;
otherVerts = null;
}
}
}
if (verts != null && SelectedModel != null && otherVerts != null)
{
// Snap current vertex to the other vertex
Vector3 selected = SelectedModel.Transform.LocalToWorld(verts[selectedvert].Position);
Vector3 other = otherTransform.LocalToWorld(otherVerts[otherSelectedvert].Position);
_translationDelta = other - selected;
}
Profiler.EndEvent();
}
/// <summary>
/// Gets a value indicating whether this tool can transform objects.
/// </summary>
@@ -532,11 +694,19 @@ namespace FlaxEditor.Gizmo
/// </summary>
protected abstract int SelectionCount { get; }
/// <summary>
/// Gets the selected object.
/// </summary>
/// <param name="index">The selected object index.</param>
/// <returns>The selected object (eg. actor node).</returns>
protected abstract SceneGraphNode GetSelectedObject(int index);
/// <summary>
/// Gets the selected object transformation.
/// </summary>
/// <param name="index">The selected object index.</param>
protected abstract Transform GetSelectedObject(int index);
/// <returns>The transformation of the selected object.</returns>
protected abstract Transform GetSelectedTransform(int index);
/// <summary>
/// Gets the selected objects bounding box (contains the whole selection).

View File

@@ -112,6 +112,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Scene", "Snap To Ground"), EditorOrder(500)]
public InputBinding SnapToGround = new InputBinding(KeyboardKeys.End);
[DefaultValue(typeof(InputBinding), "End")]
[EditorDisplay("Scene", "Vertex Snapping"), EditorOrder(550)]
public InputBinding SnapToVertex = new InputBinding(KeyboardKeys.V);
[DefaultValue(typeof(InputBinding), "F5")]
[EditorDisplay("Scene", "Play/Stop"), EditorOrder(510)]
public InputBinding Play = new InputBinding(KeyboardKeys.F5);

View File

@@ -200,12 +200,8 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
var id = ID;
var bytes = id.ToByteArray();
for (int i = 0; i < 6; i++)
{
bytes[0] += 1;
AddChildNode(new SideLinkNode(this, new Guid(bytes), i));
}
AddChildNode(new SideLinkNode(this, GetSubID(id, i), i));
}
/// <inheritdoc />

View File

@@ -169,12 +169,8 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
var id = ID;
var bytes = id.ToByteArray();
for (int i = 0; i < 6; i++)
{
bytes[0] += 1;
AddChildNode(new SideLinkNode(this, new Guid(bytes), i));
}
AddChildNode(new SideLinkNode(this, GetSubID(id, i), i));
}
/// <inheritdoc />

View File

@@ -16,4 +16,62 @@ namespace FlaxEditor.SceneGraph.Actors
{
}
}
/// <summary>
/// Scene tree node for instance of <see cref="Foliage"/>.
/// </summary>
[HideInEditor]
public sealed class FoliageInstanceNode : SceneGraphNode
{
/// <summary>
/// The foliage actor that owns this instance.
/// </summary>
public Foliage Actor;
/// <summary>
/// Index of the foliage instance.
/// </summary>
public int Index;
/// <inheritdoc />
public FoliageInstanceNode(Foliage actor, int index)
: base(GetSubID(actor.ID, index))
{
Actor = actor;
Index = index;
}
/// <inheritdoc />
public override string Name => "Foliage Instance";
/// <inheritdoc />
public override SceneNode ParentScene
{
get
{
var scene = Actor ? Actor.Scene : null;
return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null;
}
}
/// <inheritdoc />
public override Transform Transform
{
get => Actor.GetInstance(Index).Transform;
set => Actor.SetInstanceTransform(Index, ref value);
}
/// <inheritdoc />
public override bool IsActive => Actor.IsActive;
/// <inheritdoc />
public override bool IsActiveInHierarchy => Actor.IsActiveInHierarchy;
/// <inheritdoc />
public override int OrderInParent
{
get => Index;
set { }
}
}
}

View File

@@ -77,11 +77,9 @@ namespace FlaxEditor.SceneGraph.Actors
public NavLinkNode(Actor actor)
: base(actor)
{
var bytes = ID.ToByteArray();
bytes[0] += 1;
AddChildNode(new LinkNode(this, new Guid(bytes), true));
bytes[0] += 1;
AddChildNode(new LinkNode(this, new Guid(bytes), false));
var id = ID;
AddChildNode(new LinkNode(this, GetSubID(id, 0), true));
AddChildNode(new LinkNode(this, GetSubID(id, 1), false));
}
/// <inheritdoc />

View File

@@ -146,7 +146,6 @@ namespace FlaxEditor.SceneGraph
{
if (ChildNodes.Contains(node))
return true;
return ChildNodes.Any(x => x.ContainsInHierarchy(node));
}
@@ -216,6 +215,18 @@ namespace FlaxEditor.SceneGraph
/// The flags.
/// </summary>
public FlagTypes Flags;
/// <summary>
/// The list of objects to exclude from tracing against. Null if unused.
/// </summary>
public List<object> ExcludeObjects;
/// <summary>
/// Initializes a new instance of the <see cref="RayCastData"/> struct.
/// </summary>
public RayCastData()
{
}
}
/// <summary>
@@ -276,7 +287,7 @@ namespace FlaxEditor.SceneGraph
SceneGraphNode minTarget = null;
Real minDistance = Real.MaxValue;
Vector3 minDistanceNormal = Vector3.Up;
if (RayCastSelf(ref ray, out distance, out normal))
if (RayMask(ref ray) && RayCastSelf(ref ray, out distance, out normal))
{
minTarget = this;
minDistance = distance;
@@ -301,6 +312,25 @@ namespace FlaxEditor.SceneGraph
return minTarget;
}
private bool RayMask(ref RayCastData ray)
{
if (ray.ExcludeObjects != null)
{
for (int j = 0; j < ray.ExcludeObjects.Count; j++)
{
if (ray.ExcludeObjects[j] == EditableObject)
{
// Remove form exclude because it is passed by ref and function is recursive it will slowly shrink the list until nothing is left as a micro optimization
ray.ExcludeObjects.RemoveAt(j);
if (ray.ExcludeObjects.Count == 0)
ray.ExcludeObjects = null;
return false;
}
}
}
return true;
}
/// <summary>
/// Checks if given ray intersects with the node.
/// </summary>
@@ -439,5 +469,20 @@ namespace FlaxEditor.SceneGraph
protected virtual void OnParentChanged()
{
}
/// <summary>
/// Randomizes the owner node identifier.
/// </summary>
/// <param name="ownerId">The owner node ID.</param>
/// <param name="index">The sub-object index.</param>
/// <returns>The sub-object ID.</returns>
protected static unsafe Guid GetSubID(Guid ownerId, int index)
{
var id = ownerId;
var idPtr = (FlaxEngine.Json.JsonSerializer.GuidInterop*)&id;
idPtr->B ^= (uint)(index * 387);
idPtr->D += (uint)(index + 1);
return id;
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Gizmo;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Tools.Foliage.Undo;
using FlaxEngine;
@@ -69,7 +70,19 @@ namespace FlaxEditor.Tools.Foliage
}
/// <inheritdoc />
protected override Transform GetSelectedObject(int index)
protected override SceneGraphNode GetSelectedObject(int index)
{
var foliage = GizmoMode.SelectedFoliage;
if (!foliage)
throw new InvalidOperationException("No foliage selected.");
var instanceIndex = GizmoMode.SelectedInstanceIndex;
if (instanceIndex < 0 || instanceIndex >= foliage.InstancesCount)
throw new InvalidOperationException("No foliage instance selected.");
return new FoliageInstanceNode(foliage, instanceIndex);
}
/// <inheritdoc />
protected override Transform GetSelectedTransform(int index)
{
var foliage = GizmoMode.SelectedFoliage;
if (!foliage)

View File

@@ -68,6 +68,9 @@ namespace FlaxEditor.Viewport
/// <inheritdoc />
public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root);
/// <inheritdoc />
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000;

View File

@@ -337,6 +337,9 @@ namespace FlaxEditor.Viewport
/// <inheritdoc />
public bool SnapToGround => false;
/// <inheritdoc />
public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000;