Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Stride.Core.BuildEngine;
using Stride.Core;
using Stride.Core.Annotations;
using Stride.Core.Extensions;
using Stride.Core.Mathematics;
using Stride.Assets.Presentation.AssetEditors.EntityHierarchyEditor.Services;
using Stride.Assets.Presentation.AssetEditors.EntityHierarchyEditor.ViewModels;
using Stride.Assets.Presentation.AssetEditors.GameEditor;
using Stride.Assets.Presentation.AssetEditors.GameEditor.Game;
using Stride.Assets.Presentation.AssetEditors.GameEditor.Services;
using Stride.Assets.Presentation.AssetEditors.Gizmos;
using Stride.Assets.Presentation.SceneEditor;
using Stride.Core;
using Stride.Core.Annotations;
using Stride.Core.BuildEngine;
using Stride.Core.Extensions;
using Stride.Core.Mathematics;
using Stride.Editor.EditorGame.Game;
using Stride.Engine;
using Stride.Input;
using Stride.Rendering;
using Stride.Rendering.Compositing;

Expand All @@ -39,6 +40,12 @@ public class EditorGameEntityTransformService : EditorGameMouseServiceBase, IEdi
private double gizmoSize = 1.0f;
private bool dynamicSnappingInUse = false;

private bool isEntityDuplicationInProgress = false;
private Entity entityDuplicationPreviousEntityWithGizmo;
private IReadOnlyCollection<Entity> entityDuplicationPreviousSelection;
private readonly Dictionary<Entity, Entity> entityDuplicationSrcToPreviewEntityMap = [];
private readonly Dictionary<AbsoluteId, Entity> entityDuplicationSrcIdToPreviewEntityMap = [];

public EditorGameEntityTransformService([NotNull] EntityHierarchyEditorViewModel editor, [NotNull] IEditorGameController controller)
{
if (editor == null) throw new ArgumentNullException(nameof(editor));
Expand Down Expand Up @@ -98,7 +105,14 @@ public Entity EntityWithGizmo
}
}

public override IEnumerable<Type> Dependencies { get { yield return typeof(IEditorGameEntitySelectionService); } }
public override IEnumerable<Type> Dependencies
{
get
{
yield return typeof(IEditorGameEntitySelectionService);
yield return typeof(EditorGameModelSelectionService);
}
}
Comment on lines +108 to +115

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is not the only place this is done like this but, why keep doing it like this? I mean, if the types are known at compile time (i.e., not dynamically determined), this is equivalent, even better if the JIT can optimize it more in the future:

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change this if wanted and people don't mind having some in this way and others in the yield return code.
I think there is a subtle difference if they do include base.Dependencies, ie. [.. base.Dependencies, typeof(A), typeof(B)] since it's making an expanded array though the likelihood of an extremely large array through chaining is highly unlikely in practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am fine to use new syntax from now on (for compactness & readability).
We can fix the others in separate PR later.
(but fine to merge as is and do everything later too, it's not a deal breaker)


Transformation IEditorGameTransformViewModelService.ActiveTransformation
{
Expand Down Expand Up @@ -140,9 +154,7 @@ public override ValueTask DisposeAsync()
{
EnsureNotDestroyed(nameof(EditorGameEntityTransformService));

var selectionService = Services.Get<IEditorGameEntitySelectionService>();
if (selectionService != null)
selectionService.SelectionUpdated -= UpdateModifiedEntitiesList;
OnDeactivate();
return base.DisposeAsync();
}

Expand Down Expand Up @@ -179,6 +191,9 @@ protected override Task<bool> Initialize(EditorServiceGame editorGame)
TranslationGizmo = new TranslationGizmo();
RotationGizmo = new RotationGizmo();
ScaleGizmo = new ScaleGizmo();
TranslationGizmo.TransformationStarted += OnGizmoTransformationStarted;
ScaleGizmo.TransformationStarted += OnGizmoTransformationStarted;
RotationGizmo.TransformationStarted += OnGizmoTransformationStarted;
TranslationGizmo.TransformationEnded += OnGizmoTransformationFinished;
ScaleGizmo.TransformationEnded += OnGizmoTransformationFinished;
RotationGizmo.TransformationEnded += OnGizmoTransformationFinished;
Expand All @@ -187,18 +202,14 @@ protected override Task<bool> Initialize(EditorServiceGame editorGame)
transformationGizmos.Add(RotationGizmo);
transformationGizmos.Add(ScaleGizmo);

Services.Get<IEditorGameEntitySelectionService>().SelectionUpdated += UpdateModifiedEntitiesList;

// Initialize and add the Gizmo entities to the gizmo scene
MicrothreadLocalDatabases.MountCommonDatabase();

// initialize the gizmo
foreach (var gizmo in transformationGizmos)
gizmo.Initialize(game.Services, editorScene);

// Deactivate all transformation gizmo by default
foreach (var gizmo in transformationGizmos)
gizmo.IsEnabled = false;
OnActivate();

// set the default active transformation gizmo
ActiveTransformationGizmo = TranslationGizmo;
Expand All @@ -208,6 +219,40 @@ protected override Task<bool> Initialize(EditorServiceGame editorGame)
return Task.FromResult(true);
}

protected void OnActivate()
{
Services.Get<IEditorGameEntitySelectionService>().SelectionUpdated += UpdateModifiedEntitiesList;

// Deactivate all transformation gizmo by default
foreach (var gizmo in transformationGizmos)
gizmo.IsEnabled = false;
}

protected void OnDeactivate()
{
EntityWithGizmo = null;
foreach (var gizmo in transformationGizmos)
{
gizmo.CancelTransform();
gizmo.ModifiedEntities = [];
gizmo.IsEnabled = false;
}

isEntityDuplicationInProgress = false;
foreach (var (_, previewEntity) in entityDuplicationSrcToPreviewEntityMap)
{
// Detach from scene
previewEntity.SetParent(null);
previewEntity.Scene = null;
}
entityDuplicationSrcToPreviewEntityMap.Clear();
entityDuplicationSrcIdToPreviewEntityMap.Clear();

var selectionService = Services.Get<IEditorGameEntitySelectionService>();
if (selectionService != null)
selectionService.SelectionUpdated -= UpdateModifiedEntitiesList;
}

private async Task Update()
{
while (!IsDisposed)
Expand All @@ -228,39 +273,43 @@ private async Task Update()
// Activate transformation snapping
if (game.Input.IsKeyPressed(SceneEditorSettings.TranslationGizmo.GetValue()))
{
await editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Translation);
editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Translation);
}

// Activate rotation snapping
if (game.Input.IsKeyPressed(SceneEditorSettings.RotationGizmo.GetValue()))
{
await editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Rotation);
editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Rotation);
}

// Activate scale snapping
if (game.Input.IsKeyPressed(SceneEditorSettings.ScaleGizmo.GetValue()))
{
await editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Scale);
editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = Transformation.Scale);
}

// Toggle between different snapping methods
if (game.Input.IsKeyPressed(SceneEditorSettings.SwitchGizmo.GetValue()))
{
var current = activeTransformation;
var next = (int)(current + 1) % Enum.GetValues<Transformation>().Length;
await editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = (Transformation)next);
editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = (Transformation)next);
}

if (game.Input.IsKeyPressed(Keys.Escape)
&& activeTransformationGizmo is not null
&& activeTransformationGizmo.IsTransformationInProgress)
{
activeTransformationGizmo.CancelTransform();
}
}

IEnumerable<Task> tasks;
lock (transformationGizmos)
foreach (var x in transformationGizmos)
{
tasks = transformationGizmos.Select(x => x.Update());
x.Update();
}

IsControllingMouse = activeTransformationGizmo != null && activeTransformationGizmo.IsUnderMouse() && IsMouseAvailable;

await Task.WhenAll(tasks);
IsControllingMouse = activeTransformationGizmo != null && activeTransformationGizmo.IsEnabled && activeTransformationGizmo.IsUnderMouse() && IsMouseAvailable;
}

await game.Script.NextFrame();
Expand Down Expand Up @@ -300,12 +349,17 @@ private void DynamicSnapSelectionToGrid(bool useDynamicSnapping)

private void UpdateModifiedEntitiesList(object sender, [NotNull] EntitySelectionEventArgs e)
{
if (!IsActive || isEntityDuplicationInProgress)
{
return;
}
EntityWithGizmo = e.NewSelection.LastOrDefault();
if (ActiveTransformationGizmo != null && EntityWithGizmo == null)
if (ActiveTransformationGizmo != null && !ActiveTransformationGizmo.IsTransformationInProgress && EntityWithGizmo == null)
{
// Reset the transformation axes if the selection is cleared.
ActiveTransformationGizmo.ClearTransformationAxes();
}

var modifiedEntities = new List<Entity>();
modifiedEntities.AddRange(e.NewSelection);

Expand All @@ -317,13 +371,86 @@ private void UpdateModifiedEntitiesList(object sender, [NotNull] EntitySelection
}
}

private void OnGizmoTransformationFinished(object sender, EventArgs e)
private void OnGizmoTransformationStarted(object sender, EventArgs e)
{
isEntityDuplicationInProgress = game.Input.IsKeyDown(Keys.LeftCtrl) || game.Input.IsKeyDown(Keys.RightCtrl);
if (isEntityDuplicationInProgress)
{
// Duplication occurs in the editor so we should make the gizmo update proxy entities
// until the editor returns the duplicated entities
entityDuplicationPreviousEntityWithGizmo = EntityWithGizmo;
entityDuplicationPreviousSelection = ActiveTransformationGizmo?.ModifiedEntities ?? [];
entityDuplicationSrcToPreviewEntityMap.Clear();
foreach (var id in Selection.GetSelectedRootIds())
{
var entity = (Entity)controller.FindGameSidePart(id);
var previewEntity = BuildDuplicationPreviewEntity(entity);

entityDuplicationSrcToPreviewEntityMap[entity] = previewEntity;
entityDuplicationSrcIdToPreviewEntityMap[id] = previewEntity;
}
ActiveTransformationGizmo.RemapModifyingEntities(entityDuplicationSrcToPreviewEntityMap);
if (EntityWithGizmo is not null
&& entityDuplicationSrcToPreviewEntityMap.TryGetValue(EntityWithGizmo, out var anchorProxyEntity))
{
EntityWithGizmo = anchorProxyEntity;
}

var modelSelectionService = Services.Get<EditorGameModelSelectionService>();
modelSelectionService?.ChangeSelection(entityDuplicationSrcIdToPreviewEntityMap.Values.ToList());
}
}

private void OnGizmoTransformationFinished(object sender, TransformationEndedEventArgs e)
{
if (isEntityDuplicationInProgress)
{
var newTransformations = new Dictionary<AbsoluteId, TransformationTRS?>();
foreach (var (srcId, previewEntity) in entityDuplicationSrcIdToPreviewEntityMap)
{
newTransformations.Add(srcId, new TransformationTRS(previewEntity.Transform));
// Detach from scene
previewEntity.SetParent(null);
previewEntity.Scene = null;
}
entityDuplicationSrcIdToPreviewEntityMap.Clear();
entityDuplicationSrcToPreviewEntityMap.Clear();

if (e.IsCanceled)
{
// Reselect previous entities
ActiveTransformationGizmo?.ModifiedEntities = entityDuplicationPreviousSelection;
EntityWithGizmo = entityDuplicationPreviousEntityWithGizmo;
var modelSelectionService = Services.Get<EditorGameModelSelectionService>();
modelSelectionService?.ChangeSelection(entityDuplicationPreviousSelection);
}
else
{
// Clear preview entities selection (which will select the duplicated entities after the editor finishes actual duplication)
ActiveTransformationGizmo?.ModifiedEntities = [];
EntityWithGizmo = null;
var modelSelectionService = Services.Get<EditorGameModelSelectionService>();
modelSelectionService?.ChangeSelection([]);
// Confirm duplication
editor.Dispatcher.InvokeAsync(() => editor.DuplicateEntities(newTransformations));
}

isEntityDuplicationInProgress = false;
entityDuplicationPreviousEntityWithGizmo = null;
entityDuplicationPreviousSelection = null;
return;
}

if (e.IsCanceled)
{
return;
}

var transformations = new Dictionary<AbsoluteId, TransformationTRS>();
foreach (var item in Selection.GetSelectedRootIds())
foreach (var id in Selection.GetSelectedRootIds())
{
var entity = (Entity)controller.FindGameSidePart(item);
transformations.Add(item, new TransformationTRS(entity.Transform));
var entity = (Entity)controller.FindGameSidePart(id);
transformations.Add(id, new TransformationTRS(entity.Transform));
}

InvokeTransformationFinished(transformations);
Expand Down Expand Up @@ -362,5 +489,21 @@ void IEditorGameTransformViewModelService.UpdateSnap(Transformation transformati
}
});
}

private static Entity BuildDuplicationPreviewEntity(Entity srcEntity)
{
var dupePreviewEntity = srcEntity.Clone();
dupePreviewEntity.Name = $"Duplication {dupePreviewEntity.Name}";
var parentEntity = srcEntity.GetParent();
if (parentEntity is not null)
{
dupePreviewEntity.SetParent(parentEntity);
}
else
{
dupePreviewEntity.Scene = srcEntity.Scene;
}
return dupePreviewEntity;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ private void SelectionUpdated(object sender, [NotNull] EntitySelectionEventArgs
});
}

public void ChangeSelection(IEnumerable<Entity> entities)
{
var recursiveSelection = new HashSet<Entity>(entities);
foreach (var childEntity in entities.SelectDeep(x => x.Transform.Children.Select(y => y.Entity)))
{
recursiveSelection.Add(childEntity);
}

editor.Controller.InvokeAsync(() =>
{
// update the selection on the gizmo entities.
selectedEntities.Clear();
selectedEntities.AddRange(recursiveSelection);
});
}

class WireframeFilter : RenderStageFilter
{
private readonly HashSet<Entity> selectedEntities;
Expand Down
Loading