diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs
index 3ab0a917b..af3043fd6 100644
--- a/Source/Editor/Gizmo/IGizmoOwner.cs
+++ b/Source/Editor/Gizmo/IGizmoOwner.cs
@@ -51,6 +51,11 @@ namespace FlaxEditor.Gizmo
///
bool SnapToGround { get; }
+ ///
+ /// Gets a value indicating whether to use vertex snapping (check if user pressed the given input key to call action).
+ ///
+ bool SnapToVertex { get; }
+
///
/// Gets the view forward direction.
///
diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs
index 741b89d2d..c000f4a30 100644
--- a/Source/Editor/Gizmo/TransformGizmo.cs
+++ b/Source/Editor/Gizmo/TransformGizmo.cs
@@ -269,7 +269,13 @@ namespace FlaxEditor.Gizmo
protected override int SelectionCount => _selectionParents.Count;
///
- protected override Transform GetSelectedObject(int index)
+ protected override SceneGraphNode GetSelectedObject(int index)
+ {
+ return _selectionParents[index];
+ }
+
+ ///
+ protected override Transform GetSelectedTransform(int index)
{
return _selectionParents[index].Transform;
}
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
index 0d421a17e..cb7a7600c 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Draw.cs
@@ -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);
+ }
+ }
}
}
}
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
index 86c0a6afe..2b9eae7b9 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Selection.cs
@@ -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);
}
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Types.cs b/Source/Editor/Gizmo/TransformGizmoBase.Types.cs
index 377bd28a0..66acb4c2b 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.Types.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.Types.cs
@@ -12,42 +12,42 @@ namespace FlaxEditor.Gizmo
///
/// None.
///
- None,
+ None = 0,
///
/// The X axis.
///
- X,
+ X = 1,
///
/// The Y axis.
///
- Y,
+ Y = 2,
///
/// The Z axis.
///
- Z,
+ Z = 4,
///
/// The XY plane.
///
- XY,
+ XY = X | Y,
///
/// The ZX plane.
///
- ZX,
+ ZX = Z | X,
///
/// The YZ plane.
///
- YZ,
+ YZ = Y | Z,
///
/// The center point.
///
- Center,
+ Center = 8,
};
///
diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs
index 6123d348a..3e788d28f 100644
--- a/Source/Editor/Gizmo/TransformGizmoBase.cs
+++ b/Source/Editor/Gizmo/TransformGizmoBase.cs
@@ -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;
+
///
/// Gets the gizmo position.
///
@@ -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();
+ }
+
///
/// Gets a value indicating whether this tool can transform objects.
///
@@ -532,11 +694,19 @@ namespace FlaxEditor.Gizmo
///
protected abstract int SelectionCount { get; }
+ ///
+ /// Gets the selected object.
+ ///
+ /// The selected object index.
+ /// The selected object (eg. actor node).
+ protected abstract SceneGraphNode GetSelectedObject(int index);
+
///
/// Gets the selected object transformation.
///
/// The selected object index.
- protected abstract Transform GetSelectedObject(int index);
+ /// The transformation of the selected object.
+ protected abstract Transform GetSelectedTransform(int index);
///
/// Gets the selected objects bounding box (contains the whole selection).
diff --git a/Source/Editor/Options/InputOptions.cs b/Source/Editor/Options/InputOptions.cs
index e2e7d0e71..ec50d1501 100644
--- a/Source/Editor/Options/InputOptions.cs
+++ b/Source/Editor/Options/InputOptions.cs
@@ -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);
diff --git a/Source/Editor/SceneGraph/Actors/BoxBrushNode.cs b/Source/Editor/SceneGraph/Actors/BoxBrushNode.cs
index da28dad2b..3d5bd9ed2 100644
--- a/Source/Editor/SceneGraph/Actors/BoxBrushNode.cs
+++ b/Source/Editor/SceneGraph/Actors/BoxBrushNode.cs
@@ -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));
}
///
diff --git a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs
index 92850857b..a2265e3bf 100644
--- a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs
+++ b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs
@@ -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));
}
///
diff --git a/Source/Editor/SceneGraph/Actors/FoliageNode.cs b/Source/Editor/SceneGraph/Actors/FoliageNode.cs
index 9cb405092..9e24d5b66 100644
--- a/Source/Editor/SceneGraph/Actors/FoliageNode.cs
+++ b/Source/Editor/SceneGraph/Actors/FoliageNode.cs
@@ -16,4 +16,62 @@ namespace FlaxEditor.SceneGraph.Actors
{
}
}
+
+ ///
+ /// Scene tree node for instance of .
+ ///
+ [HideInEditor]
+ public sealed class FoliageInstanceNode : SceneGraphNode
+ {
+ ///
+ /// The foliage actor that owns this instance.
+ ///
+ public Foliage Actor;
+
+ ///
+ /// Index of the foliage instance.
+ ///
+ public int Index;
+
+ ///
+ public FoliageInstanceNode(Foliage actor, int index)
+ : base(GetSubID(actor.ID, index))
+ {
+ Actor = actor;
+ Index = index;
+ }
+
+ ///
+ public override string Name => "Foliage Instance";
+
+ ///
+ public override SceneNode ParentScene
+ {
+ get
+ {
+ var scene = Actor ? Actor.Scene : null;
+ return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null;
+ }
+ }
+
+ ///
+ public override Transform Transform
+ {
+ get => Actor.GetInstance(Index).Transform;
+ set => Actor.SetInstanceTransform(Index, ref value);
+ }
+
+ ///
+ public override bool IsActive => Actor.IsActive;
+
+ ///
+ public override bool IsActiveInHierarchy => Actor.IsActiveInHierarchy;
+
+ ///
+ public override int OrderInParent
+ {
+ get => Index;
+ set { }
+ }
+ }
}
diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
index a0e90622f..4872c312a 100644
--- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
+++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs
@@ -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));
}
///
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index 672a239dc..a8a26a1b3 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -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.
///
public FlagTypes Flags;
+
+ ///
+ /// The list of objects to exclude from tracing against. Null if unused.
+ ///
+ public List