From f78c907c976583f8c6e6f74d272bf90b04f346a8 Mon Sep 17 00:00:00 2001 From: salahchafai <64394387+salahchafai@users.noreply.github.com> Date: Thu, 1 Jul 2021 02:24:45 +0100 Subject: [PATCH 1/3] Improve the editor gizmos --- .../AssetEditors/Gizmos/AxialGizmo.cs | 54 +++- .../AssetEditors/Gizmos/CameraGizmo.cs | 2 +- .../Gizmos/CameraOrientationGizmo.cs | 42 +-- .../Gizmos/GizmoEmissiveColorMaterial.cs | 12 +- .../Gizmos/GizmoShaderMaterial.cs | 27 ++ .../Gizmos/GizmoUniformColorMaterial.cs | 17 +- .../Gizmos/LightDirectionalGizmo.cs | 4 +- .../AssetEditors/Gizmos/LightPointGizmo.cs | 4 +- .../AssetEditors/Gizmos/LightSpotGizmo.cs | 4 +- .../Gizmos/NavigationBoundingBoxGizmo.cs | 2 +- .../AssetEditors/Gizmos/RotationGizmo.cs | 63 +++-- .../AssetEditors/Gizmos/ScaleGizmo.cs | 255 +++++++++++++++--- .../Gizmos/TransformationGizmo.cs | 20 +- .../AssetEditors/Gizmos/TranslationGizmo.cs | 46 ++-- .../AssetEditors/Gizmos/VoxelVolumeGizmo.cs | 2 +- .../SceneEditor/SceneEditorSettings.cs | 6 + .../Editor/CameraOrientationGizmoShader.sdsl | 11 + 17 files changed, 427 insertions(+), 144 deletions(-) create mode 100644 sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoShaderMaterial.cs create mode 100644 sources/engine/Stride.Rendering/Rendering/Editor/CameraOrientationGizmoShader.sdsl diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/AxialGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/AxialGizmo.cs index a6c7848ce7..9b6da57648 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/AxialGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/AxialGizmo.cs @@ -45,12 +45,32 @@ protected AxialGizmo() protected override Entity Create() { - RedUniformMaterial = CreateUniformColorMaterial(RedUniformColor); - GreenUniformMaterial = CreateUniformColorMaterial(GreenUniformColor); - BlueUniformMaterial = CreateUniformColorMaterial(BlueUniformColor); + RedUniformMaterial = CreateEmissiveColorMaterial(RedUniformColor); + GreenUniformMaterial = CreateEmissiveColorMaterial(GreenUniformColor); + BlueUniformMaterial = CreateEmissiveColorMaterial(BlueUniformColor); return null; } + /// + /// Gets the default color associated to the provided axis index. + /// + /// The index of the axis + /// The default color associated + protected Color GetAxisDefaultColor(int axisIndex) + { + switch (axisIndex) + { + case 0: + return RedUniformColor; + case 1: + return GreenUniformColor; + case 2: + return BlueUniformColor; + default: + throw new ArgumentOutOfRangeException("axisIndex"); + } + } + /// /// Gets the default material associated to the provided axis index. /// @@ -78,16 +98,36 @@ protected Material GetAxisDefaultMaterial(int axisIndex) /// the material protected Material CreateUniformColorMaterial(Color color) { - return GizmoUniformColorMaterial.Create(GraphicsDevice, color, false); + return GizmoUniformColorMaterial.Create(GraphicsDevice, color); + } + + /// + /// Creates an emissive color material. + /// + /// The color of the material + /// the material + protected Material CreateEmissiveColorMaterial(Color color) + { + return GizmoEmissiveColorMaterial.Create(GraphicsDevice, color, 0.75f); + } + + /// + /// Creates a material from a shader. + /// + /// the shader's name + /// the material + protected Material CreateShaderMaterial(string shaderName) + { + return GizmoShaderMaterial.Create(GraphicsDevice, shaderName); } protected virtual void UpdateColors() { if (IsEnabled && RedUniformMaterial != null) { - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, RedUniformMaterial, RedUniformColor); - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, GreenUniformMaterial, GreenUniformColor); - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, BlueUniformMaterial, BlueUniformColor); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, RedUniformMaterial, RedUniformColor); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, GreenUniformMaterial, GreenUniformColor); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, BlueUniformMaterial, BlueUniformColor); } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs index 2b3ec6ffc4..9d3c7e23dc 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs @@ -63,7 +63,7 @@ protected override Entity Create() frustumMesh = new CameraFrustumMesh(GraphicsDevice); frustumMesh.Build(GraphicsCommandList, cameraParameters); - var frustumMaterial = GizmoUniformColorMaterial.Create(GraphicsDevice, new Color(0.75f, 0.75f, 1f, 1f)); + var frustumMaterial = GizmoEmissiveColorMaterial.Create(GraphicsDevice, new Color(0.75f, 0.75f, 1f, 1f)); frustum = new Entity("Camera frustumMesh of {0}".ToFormat(root.Id)) { diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraOrientationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraOrientationGizmo.cs index ca11744931..d87439f18c 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraOrientationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraOrientationGizmo.cs @@ -16,6 +16,7 @@ using Stride.Graphics.GeometricPrimitives; using Stride.Rendering; using Stride.Rendering.Compositing; +using Stride.Assets.Presentation.SceneEditor; namespace Stride.Assets.Presentation.AssetEditors.Gizmos { @@ -36,12 +37,12 @@ public class CameraOrientationGizmo : AxialGizmo private static readonly FaceData[] Faces = { - new FaceData("Right", new Vector3(0, MathUtil.PiOverTwo, 0)), - new FaceData("Left", new Vector3(0, -MathUtil.PiOverTwo, 0)), - new FaceData("Top", new Vector3(-MathUtil.PiOverTwo, 0, 0)), - new FaceData("Bottom", new Vector3(MathUtil.PiOverTwo, 0, 0)), - new FaceData("Back", Vector3.Zero), - new FaceData("Front", new Vector3(0, MathUtil.Pi, 0)) + new FaceData("Right", "X", new Vector3(0, MathUtil.PiOverTwo, 0)), + new FaceData("Left", "-X", new Vector3(0, -MathUtil.PiOverTwo, 0)), + new FaceData("Top", "Y", new Vector3(-MathUtil.PiOverTwo, 0, 0)), + new FaceData("Bottom", "-Y", new Vector3(MathUtil.PiOverTwo, 0, 0)), + new FaceData("Back", "-Z", Vector3.Zero), + new FaceData("Front", "Z", new Vector3(0, MathUtil.Pi, 0)) }; /// @@ -58,9 +59,11 @@ public class CameraOrientationGizmo : AxialGizmo /// private const int DefaultSize = 25; - private const float FontSize = 7; + private const float FontSize = 25; - private const float TextScale = 0.08f; + private const float NameIndicatorScale = 0.08f; + + private const float XYZIndicatorScale = 0.18f; private const float OuterExtent = 0.25f; @@ -227,8 +230,8 @@ protected override Entity Create() { base.Create(); - DefaultMaterial = CreateUniformColorMaterial(Color.White); - ElementSelectedMaterial = CreateUniformColorMaterial(Color.Gold); + DefaultMaterial = CreateShaderMaterial("CameraOrientationGizmoShader"); + ElementSelectedMaterial = CreateEmissiveColorMaterial(Color.Gray); var entity = new Entity("View Gizmo"); cameraComponent = new CameraComponent @@ -250,7 +253,7 @@ protected override Entity Create() // create the sprite batch use to draw text spriteBatch = new SpriteBatch(GraphicsDevice) { DefaultDepth = 1 }; - // Add a renderer on the top right size + // Add a renderer on the top right side var cameraOrientationGizmoRenderStage = new RenderStage("CameraOrientationGizmo", "Main"); game.EditorSceneSystem.GraphicsCompositor.RenderStages.Add(cameraOrientationGizmoRenderStage); @@ -320,13 +323,17 @@ private void RenderFaceNames(RenderDrawContext context) var textureToWorldSpace = Matrix.RotationX(MathUtil.Pi) * Matrix.Translation(0, 0, OuterExtent); - foreach (var face in Faces) + var displayDirectionNames = SceneEditorSettings.DisplayDirectionNames.GetValue(); + + var textScale = displayDirectionNames ? NameIndicatorScale : XYZIndicatorScale; + + for (int i = 0; i < Faces.Count(); i++) { - var text = face.Name.ToUpperInvariant(); + var text = displayDirectionNames ? Faces[i].Name.ToUpperInvariant() : Faces[i].XYZComponent; - spriteBatch.Begin(context.GraphicsContext, textureToWorldSpace * face.Rotation * viewMatrix, projectionMatrix, SpriteSortMode.BackToFront, BlendStates.AlphaBlend, context.GraphicsDevice.SamplerStates.LinearClamp, DepthStencilStates.None); + spriteBatch.Begin(context.GraphicsContext, textureToWorldSpace * Faces[i].Rotation * viewMatrix, projectionMatrix, SpriteSortMode.BackToFront, BlendStates.AlphaBlend, context.GraphicsDevice.SamplerStates.LinearClamp, DepthStencilStates.None); var textSize = spriteBatch.MeasureString(defaultFont, text, viewPortSize); - spriteBatch.DrawString(defaultFont, text, Vector2.One * 0.5f, new Color(0, 0, 0, 0.8f), 0, textSize / 2, Vector2.One / FontSize * TextScale, SpriteEffects.None, 0, TextAlignment.Center); + spriteBatch.DrawString(defaultFont, text, Vector2.One * 0.5f, GetAxisDefaultColor(i / 2), 0, textSize / 2, Vector2.One / FontSize * textScale, SpriteEffects.None, 0, TextAlignment.Center); spriteBatch.End(); } @@ -336,11 +343,14 @@ struct FaceData { public readonly string Name; + public readonly string XYZComponent; + public readonly Matrix Rotation; - public FaceData(string name, Vector3 angles) + public FaceData(string name, string xyzComponent, Vector3 angles) { Name = name; + XYZComponent = xyzComponent; Rotation = Matrix.RotationYawPitchRoll(angles.Y, angles.X, angles.Z); } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoEmissiveColorMaterial.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoEmissiveColorMaterial.cs index d632f398a2..9d8e927658 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoEmissiveColorMaterial.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoEmissiveColorMaterial.cs @@ -10,7 +10,7 @@ namespace Stride.Assets.Presentation.AssetEditors.Gizmos { public static class GizmoEmissiveColorMaterial { - public static Material Create(GraphicsDevice device, Color color, float intensity) + public static Material Create(GraphicsDevice device, Color color, float intensity = 1f) { var material = Material.New(device, new MaterialDescriptor { @@ -22,13 +22,19 @@ public static Material Create(GraphicsDevice device, Color color, float intensit } }); + // set the color to the material + UpdateColor(device, material, color, intensity); + + return material; + } + + public static void UpdateColor(GraphicsDevice device, Material material, Color color, float intensity = 1f) + { // set the color to the material material.Passes[0].Parameters.Set(MaterialKeys.DiffuseValue, new Color4(color).ToColorSpace(device.ColorSpace)); material.Passes[0].Parameters.Set(MaterialKeys.EmissiveIntensity, intensity); material.Passes[0].Parameters.Set(MaterialKeys.EmissiveValue, new Color4(color).ToColorSpace(device.ColorSpace)); - - return material; } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoShaderMaterial.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoShaderMaterial.cs new file mode 100644 index 0000000000..c2bf73d3e3 --- /dev/null +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoShaderMaterial.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using Stride.Graphics; +using Stride.Rendering; +using Stride.Rendering.Materials; +using Stride.Rendering.Materials.ComputeColors; + +namespace Stride.Assets.Presentation.AssetEditors.Gizmos +{ + public static class GizmoShaderMaterial + { + public static Material Create(GraphicsDevice device, string shaderName) + { + var material = Material.New(device, new MaterialDescriptor + { + Attributes = + { + Diffuse = new MaterialDiffuseMapFeature(new ComputeShaderClassColor() { MixinReference = shaderName }), + DiffuseModel = new MaterialDiffuseLambertModelFeature(), + Emissive = new MaterialEmissiveMapFeature(new ComputeShaderClassColor() { MixinReference = shaderName }) + } + }); + + return material; + } + } +} diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoUniformColorMaterial.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoUniformColorMaterial.cs index 707fe14fec..a7d5644b8f 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoUniformColorMaterial.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/GizmoUniformColorMaterial.cs @@ -11,20 +11,11 @@ namespace Stride.Assets.Presentation.AssetEditors.Gizmos { public static class GizmoUniformColorMaterial { - public static readonly ValueParameterKey GizmoColorKey = ParameterKeys.NewValue(); - - public static Material Create(GraphicsDevice device, Color color, bool emissive = true) + public static Material Create(GraphicsDevice device, Color color) { var desc = new MaterialDescriptor(); - if (emissive) - { - desc.Attributes.Emissive = new MaterialEmissiveMapFeature(new ComputeColor() { Key = GizmoColorKey }); - } - else - { - desc.Attributes.Diffuse = new MaterialDiffuseMapFeature(new ComputeColor() { Key = GizmoColorKey }); - desc.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature(); - } + desc.Attributes.Diffuse = new MaterialDiffuseMapFeature(new ComputeColor()); + desc.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature(); var material = Material.New(device, desc); @@ -46,7 +37,7 @@ public static Material Create(GraphicsDevice device, Color color, bool emissive public static void UpdateColor(GraphicsDevice device, Material material, Color color) { // set the color to the material - material.Passes[0].Parameters.Set(GizmoColorKey, new Color4(color).ToColorSpace(device.ColorSpace)); + material.Passes[0].Parameters.Set(MaterialKeys.DiffuseValue, new Color4(color).ToColorSpace(device.ColorSpace)); } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightDirectionalGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightDirectionalGizmo.cs index 8d4adfcbf4..b514e2eb26 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightDirectionalGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightDirectionalGizmo.cs @@ -33,7 +33,7 @@ protected override Entity Create() var root = base.Create(); lightRay = new Entity($"Light ray for light gizmo {root.Id}"); - rayMaterial = GizmoUniformColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + rayMaterial = GizmoEmissiveColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); // build the ray mesh var coneMesh = GeometricPrimitive.Cone.New(GraphicsDevice, ConeRadius, ConeHeight, GizmoTessellation).ToMeshDraw(); @@ -57,7 +57,7 @@ public override void Update() base.Update(); // update the color of the ray - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, rayMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, rayMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); } public override bool IsSelected diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightPointGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightPointGizmo.cs index d8f4feb4c5..ee78c59c64 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightPointGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightPointGizmo.cs @@ -45,7 +45,7 @@ protected override Entity Create() pointMesh= new LightPointMesh(GraphicsDevice); pointMesh.Build(); - pointMaterial = GizmoUniformColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + pointMaterial = GizmoEmissiveColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); pointEntity = new Entity("Point Mesh of {0}".ToFormat(root.Id)) { @@ -71,7 +71,7 @@ public override void Update() pointEntity.Transform.Scale = new Vector3(LightPoint.Radius); // update the spot color - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, pointMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, pointMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); } public override bool IsSelected diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs index 043560ad53..17d9e12697 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs @@ -57,7 +57,7 @@ protected override Entity Create() spotMesh = new LightSpotMesh(GraphicsDevice); spotMesh.Build(GraphicsCommandList, LightSpot); - spotMaterial = GizmoUniformColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + spotMaterial = GizmoEmissiveColorMaterial.Create(GraphicsDevice, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); spotEntity = new Entity("Spot Mesh of {0}".ToFormat(root.Id)) { @@ -113,7 +113,7 @@ public override void Update() } // update the spot color - GizmoUniformColorMaterial.UpdateColor(GraphicsDevice, spotMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); + GizmoEmissiveColorMaterial.UpdateColor(GraphicsDevice, spotMaterial, (Color)new Color4(GetLightColor(GraphicsDevice), 1f)); } public override bool IsSelected diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/NavigationBoundingBoxGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/NavigationBoundingBoxGizmo.cs index 0f8bc2ddba..f79735e501 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/NavigationBoundingBoxGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/NavigationBoundingBoxGizmo.cs @@ -29,7 +29,7 @@ public NavigationBoundingBoxGizmo(EntityComponent component) : base(component) protected override Entity Create() { - material = GizmoUniformColorMaterial.Create(GraphicsDevice, Color.CornflowerBlue); + material = GizmoEmissiveColorMaterial.Create(GraphicsDevice, Color.CornflowerBlue); box = new BoxMesh(GraphicsDevice); box.Build(); diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs index 222feeef77..e75338d0a0 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs @@ -13,18 +13,25 @@ namespace Stride.Assets.Presentation.AssetEditors.Gizmos public class RotationGizmo : TransformationGizmo { private const float RotationGizmoRadius = 1f; // the size of the radius of the torus used to rotate the objects - private const float RotationGizmoThickness = 0.02f; // the size of the inner radius torus used to rotate the objects + private const float OriginRadius = GizmoOriginScale * 0.05f; private readonly Entity[] rotationAxes = new Entity[3]; + private Material overlaySphereDefaultMaterial; + private Material overlaySphereSelectedMaterial; + + private Entity overlaySphere; + protected override Entity Create() { base.Create(); + overlaySphereDefaultMaterial = CreateUniformColorMaterial(new Color(0.3f, 0.3f, 0.3f, 0.025f)); + overlaySphereSelectedMaterial = CreateUniformColorMaterial(new Color(0.4f, 0.4f, 0.4f, 0.025f)); + var entity = new Entity("Rotation Gizmo"); - const float OriginSize = GizmoOriginScale * GizmoExtremitySize; - var rotations = new[] { new Vector3(0, 0, MathUtil.Pi / 2), new Vector3(), new Vector3(MathUtil.Pi / 2, 0, 0) }; + var rotations = new[] { new Vector3(0, 0, -MathUtil.Pi / 2), new Vector3(), new Vector3(MathUtil.Pi / 2, 0, 0) }; var bodyMesh = GeometricPrimitive.Torus.New(GraphicsDevice, RotationGizmoRadius, RotationGizmoThickness, GizmoTessellation).ToMeshDraw(); for (int axis = 0; axis < 3; ++axis) @@ -35,10 +42,15 @@ protected override Entity Create() entity.AddChild(rotationAxes[axis]); } + // Add overlay sphere + var overlayMeshDraw = GeometricPrimitive.Sphere.New(GraphicsDevice, RotationGizmoRadius, GizmoTessellation).ToMeshDraw(); + overlaySphere = new Entity("OverlaySphere") { new ModelComponent { Model = new Model { overlaySphereDefaultMaterial, new Mesh { Draw = overlayMeshDraw } }, RenderGroup = RenderGroup } }; + entity.AddChild(overlaySphere); + // Add middle sphere - var sphereMeshDraw = GeometricPrimitive.Sphere.New(GraphicsDevice, 0.25f * OriginSize, GizmoTessellation).ToMeshDraw(); - var sphereEntity = new Entity("OriginCube") { new ModelComponent { Model = new Model { DefaultOriginMaterial, new Mesh { Draw = sphereMeshDraw } }, RenderGroup = RenderGroup } }; - entity.AddChild(sphereEntity); + var sphereMeshDraw = GeometricPrimitive.Sphere.New(GraphicsDevice, OriginRadius, GizmoTessellation).ToMeshDraw(); + var rotationOrigin = new Entity("OriginSphere") { new ModelComponent { Model = new Model { DefaultOriginMaterial, new Mesh { Draw = sphereMeshDraw } }, RenderGroup = RenderGroup } }; + entity.Transform.Children.Add(rotationOrigin.Transform); return entity; } @@ -125,6 +137,7 @@ protected override void UpdateTransformationAxis() protected override void UpdateColors() { base.UpdateColors(); + for (int axis = 0; axis < 3; axis++) { var axisMaterial = GetAxisDefaultMaterial(axis); @@ -132,6 +145,8 @@ protected override void UpdateColors() bool isSelected = (TransformationAxes & transformationAxis) == transformationAxis; rotationAxes[axis].Get().Model.Materials[0] = isSelected ? ElementSelectedMaterial : axisMaterial; } + + overlaySphere.Get().Model.Materials[0] = TransformationStarted ? overlaySphereSelectedMaterial : overlaySphereDefaultMaterial; } /// @@ -140,11 +155,29 @@ protected override void UpdateColors() /// protected override InitialTransformation CalculateTransformation() { - var mouseDrag = Input.MousePosition - StartMousePosition; + // TODO: use cameraComponent.WorldToScreenPosition instead once implemented + // determine the anchor entity's screen position + var anchorEntityWorldPosition = AnchorEntity.Transform.WorldMatrix.TranslationVector; + var cameraComponent = Game.EditorServices.Get().Component; + Vector3.TransformCoordinate(ref anchorEntityWorldPosition, ref cameraComponent.ViewProjectionMatrix, out var clipSpace); + Vector3.TransformCoordinate(ref anchorEntityWorldPosition, ref cameraComponent.ViewMatrix, out var viewSpace); + var anchorEntityScreenPosition = new Vector2 + { + X = (clipSpace.X + 1f) / 2f, + Y = (clipSpace.Y + 1f) / 2f - 1f, + }; + + // determine the vectors going from the anchor entity's position to the start and current mouse positions + var anchorEntityToMouse = new Vector2(Input.MousePosition.X, -Input.MousePosition.Y) - anchorEntityScreenPosition; + var anchorEntityToStartMouse = new Vector2(StartMousePosition.X, -StartMousePosition.Y) - anchorEntityScreenPosition; + + anchorEntityToMouse.X *= cameraComponent.AspectRatio; + anchorEntityToStartMouse.X *= cameraComponent.AspectRatio; + var transformation = new InitialTransformation { Rotation = Quaternion.Identity, Scale = Vector3.One }; // determine the rotation angle - var rotationAngle = Vector2.Dot(new Vector2(mouseDrag.X, -mouseDrag.Y), TransformationDirection) * 2.1f * MathUtil.Pi; // half screen size if little bit more Pi + var rotationAngle = MathF.Atan2(anchorEntityToMouse.X * anchorEntityToStartMouse.Y - anchorEntityToMouse.Y * anchorEntityToStartMouse.X, Vector2.Dot(anchorEntityToStartMouse, anchorEntityToMouse)); // snap the rotation angle if necessary if (UseSnap) @@ -154,16 +187,14 @@ protected override InitialTransformation CalculateTransformation() } // determine the rotation axis in the Gizmo - var rotationAxisGizmo = Vector3.Zero; - for (int i = 0; i < 3; i++) - { - if ((TransformationAxes & ((GizmoTransformationAxes)(1 << i))) != 0) - rotationAxisGizmo[i] = 1; - } - rotationAxisGizmo.Normalize(); + + var rotationAxisWorldUp = rotationAxes[(int)TransformationAxes / 2].Transform.WorldMatrix.Up; + var anchorEntityToCamera = AnchorEntity.Transform.WorldMatrix.TranslationVector - Game.EditorServices.Get().Position; + var rotationAxis = new Vector3(0) { [(int)TransformationAxes / 2] = MathF.Sign(Vector3.Dot(anchorEntityToCamera, rotationAxisWorldUp)) }; + rotationAxis.Normalize(); // set the rotation to apply in the gizmo space - transformation.Rotation = Quaternion.RotationAxis(rotationAxisGizmo, rotationAngle); + transformation.Rotation = Quaternion.RotationAxis(rotationAxis, rotationAngle); return transformation; } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/ScaleGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/ScaleGizmo.cs index 75b659b560..16c96f0722 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/ScaleGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/ScaleGizmo.cs @@ -2,10 +2,13 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; using System.Collections.Generic; +using System.Threading.Tasks; using Stride.Core.Mathematics; +using Stride.Assets.Presentation.AssetEditors.EntityHierarchyEditor.Game; using Stride.Assets.Presentation.AssetEditors.GameEditor.Game; using Stride.Engine; using Stride.Extensions; +using Stride.Graphics; using Stride.Graphics.GeometricPrimitives; using Stride.Rendering; @@ -13,53 +16,174 @@ namespace Stride.Assets.Presentation.AssetEditors.Gizmos { public class ScaleGizmo : AxisTransformationGizmo { + private class EntitySortInfo : IComparer + { + public Entity Entity; + + public float Depth; + + public int Compare(EntitySortInfo x, EntitySortInfo y) + { + return Math.Sign(x.Depth - y.Depth); + } + }; + + private const float AxisExtremitySize = GizmoExtremitySize / 2f; + private const float AxisBodyRadius = GizmoExtremitySize / 9f; + private const float AxisBodyLength = 1f - AxisExtremitySize; + private const float OriginSize = GizmoOriginScale * AxisExtremitySize; + + private readonly Material[] planeMaterials = new Material[3]; + private readonly List sortedEntities = new List(); private readonly List[] scaleAxes = { new List(), new List(), new List() }; + private readonly List[] scalePlanes = { new List(), new List(), new List() }; + private readonly List[] scalePlaneEdges = { new List(), new List(), new List() }; + + private readonly List scalePlaneRoots = new List(); + private readonly List[] scaleOpositeAxes = { new List(), new List(), new List(), }; private Entity scaleOrigin; protected override Entity Create() { base.Create(); - const float ExtremitySize = GizmoExtremitySize / 1.5f; - const float BodyRadius = GizmoExtremitySize / 7.5f; - const float OriginSize = GizmoOriginScale * ExtremitySize; - const float BodyLength = 1f - ExtremitySize; + planeMaterials[0] = CreateUniformColorMaterial(Color.Red.WithAlpha(86)); + planeMaterials[1] = CreateUniformColorMaterial(Color.Green.WithAlpha(86)); + planeMaterials[2] = CreateUniformColorMaterial(Color.Blue.WithAlpha(86)); var entity = new Entity("Scale gizmo"); - var rotations = new[] { Vector3.Zero, new Vector3(0, 0, MathUtil.Pi / 2), new Vector3(0, -MathUtil.Pi / 2f, 0) }; - var extremityMesh = GeometricPrimitive.Cube.New(GraphicsDevice, ExtremitySize).ToMeshDraw(); - var bodyMesh = GeometricPrimitive.Cylinder.New(GraphicsDevice, BodyLength, BodyRadius, GizmoTessellation).ToMeshDraw(); + var axisRootEntities = new[] { new Entity("Root X axis"), new Entity("Root Y axis"), new Entity("Root Z axis") }; + var cubeMesh = GeometricPrimitive.Cube.New(GraphicsDevice, AxisExtremitySize).ToMeshDraw(); + var bodyMesh = GeometricPrimitive.Cylinder.New(GraphicsDevice, AxisBodyLength, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); + // create the axis arrows for (int axis = 0; axis < 3; ++axis) { - var axisMaterial = GetAxisDefaultMaterial(axis); - var extremity = new Entity("ArrowExtremity" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = extremityMesh } }, RenderGroup = RenderGroup } }; - extremity.Transform.Position.X = BodyLength; - scaleAxes[axis].Add(extremity); - - var body = new Entity("ArrowBody" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = bodyMesh } }, RenderGroup = RenderGroup } }; - body.Transform.Position.X = BodyLength / 2; - body.Transform.RotationEulerXYZ = -MathUtil.Pi / 2 * Vector3.UnitZ; - scaleAxes[axis].Add(body); - - // create the arrow entity composed of the cone and body + var material = GetAxisDefaultMaterial(axis); + + // the end cube + var extremityEntity = new Entity("ArrowExtremity" + axis) { new ModelComponent { Model = new Model { material, new Mesh { Draw = cubeMesh } }, RenderGroup = RenderGroup } }; + extremityEntity.Transform.Position.X = AxisBodyLength + AxisExtremitySize * 0.5f; + scaleAxes[axis].Add(extremityEntity); + + // the main body + var bodyEntity = new Entity("ArrowBody" + axis) { new ModelComponent { Model = new Model { material, new Mesh { Draw = bodyMesh } }, RenderGroup = RenderGroup } }; + bodyEntity.Transform.Position.X = AxisBodyLength / 2; + bodyEntity.Transform.RotationEulerXYZ = -MathUtil.Pi / 2 * Vector3.UnitZ; + scaleAxes[axis].Add(bodyEntity); + + // oposite side part (cylinder shown when camera is looking oposite direction to the axis) + var frameMesh = GeometricPrimitive.Cylinder.New(GraphicsDevice, GizmoPlaneLength, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); + var opositeFrameEntity = new Entity("Oposite Frame" + axis) { new ModelComponent { Model = new Model { material, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; + opositeFrameEntity.Transform.Position.X = -GizmoPlaneLength / 2; + opositeFrameEntity.Transform.RotationEulerXYZ = -MathUtil.Pi / 2 * Vector3.UnitZ; + scaleAxes[axis].Add(opositeFrameEntity); + scaleOpositeAxes[axis].Add(opositeFrameEntity.Get()); + + // create the arrow entity composed of the cube and body var arrowEntity = new Entity("ArrowEntity" + axis); - arrowEntity.Transform.Children.Add(extremity.Transform); - arrowEntity.Transform.Children.Add(body.Transform); - arrowEntity.Transform.RotationEulerXYZ = rotations[axis]; + arrowEntity.Transform.Children.Add(extremityEntity.Transform); + arrowEntity.Transform.Children.Add(bodyEntity.Transform); + arrowEntity.Transform.Children.Add(opositeFrameEntity.Transform); // Add the arrow entity to the gizmo entity - entity.Transform.Children.Add(arrowEntity.Transform); + axisRootEntities[axis].Transform.Children.Add(arrowEntity.Transform); } - // Add middle sphere - var sphereMeshDraw = GeometricPrimitive.Cube.New(GraphicsDevice, OriginSize).ToMeshDraw(); - scaleOrigin = new Entity("OriginCube") { new ModelComponent { Model = new Model { DefaultOriginMaterial, new Mesh { Draw = sphereMeshDraw } }, RenderGroup = RenderGroup } }; + // create the scaling planes + for (int axis = 0; axis < 3; ++axis) + { + // The skeleton material + var axisMaterial = GetAxisDefaultMaterial(axis); + + // The 2 frame rectangles + var frameMesh = GeometricPrimitive.Cube.New(GraphicsDevice, new Vector3(AxisBodyRadius / 2f, GizmoPlaneLength / 3f, AxisBodyRadius / 2f)).ToMeshDraw(); + var topFrameEntity = new Entity("TopFrame" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; + var leftFrameEntity = new Entity("LeftFrame" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; + topFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength, GizmoPlaneLength - (GizmoPlaneLength / 6)); + topFrameEntity.Transform.RotationEulerXYZ = new Vector3(MathUtil.Pi / 2f, 0, 0); + leftFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength - (GizmoPlaneLength / 6), GizmoPlaneLength); + scalePlaneEdges[axis].Add(topFrameEntity); + scalePlaneEdges[axis].Add(leftFrameEntity); + + // The transparent planes (2 for correct lighting) + var materialPlane = planeMaterials[axis]; + var planeMesh = GeometricPrimitive.Plane.New(GraphicsDevice, GizmoPlaneLength, GizmoPlaneLength).ToMeshDraw(); + var planeFrameEntityFront = new Entity("FramePlaneFront" + axis) { new ModelComponent { Model = new Model { materialPlane, new Mesh { Draw = planeMesh } }, RenderGroup = RenderGroup } }; + var planeFrameEntityBack = new Entity("FramePlaneBack" + axis) { new ModelComponent { Model = new Model { materialPlane, new Mesh { Draw = planeMesh } }, RenderGroup = RenderGroup } }; + planeFrameEntityFront.Transform.Position = new Vector3(0, GizmoPlaneLength / 2, GizmoPlaneLength / 2); + planeFrameEntityFront.Transform.RotationEulerXYZ = new Vector3(0, MathUtil.Pi / 2f, 0); + planeFrameEntityBack.Transform.Position = new Vector3(0, GizmoPlaneLength / 2, GizmoPlaneLength / 2); + planeFrameEntityBack.Transform.RotationEulerXYZ = new Vector3(0, -MathUtil.Pi / 2f, 0); + scalePlanes[axis].Add(planeFrameEntityFront); + scalePlanes[axis].Add(planeFrameEntityBack); + sortedEntities.Add(new EntitySortInfo { Entity = planeFrameEntityFront }); + sortedEntities.Add(new EntitySortInfo { Entity = planeFrameEntityBack }); + + // Add the different parts of the plane to the plane entity + var planeEntity = new Entity("GizmoPlane" + axis); + planeEntity.Transform.Children.Add(topFrameEntity.Transform); + planeEntity.Transform.Children.Add(leftFrameEntity.Transform); + planeEntity.Transform.Children.Add(planeFrameEntityFront.Transform); + planeEntity.Transform.Children.Add(planeFrameEntityBack.Transform); + scalePlaneRoots.Add(planeEntity); + + // Add the plane entity to the gizmo entity + axisRootEntities[axis].Transform.Children.Add(planeEntity.Transform); + } + + // set the axis root entities rotation and add them to the main entity + var axisRotations = new[] { Vector3.Zero, new Vector3(MathUtil.PiOverTwo, 0, MathUtil.PiOverTwo), new Vector3(-MathUtil.PiOverTwo, -MathUtil.PiOverTwo, 0) }; + for (int axis = 0; axis < 3; axis++) + { + axisRootEntities[axis].TransformValue.RotationEulerXYZ = axisRotations[axis]; + entity.TransformValue.Children.Add(axisRootEntities[axis].TransformValue); + } + + // Add middle cube + var cubeMeshDraw = GeometricPrimitive.Cube.New(GraphicsDevice, OriginSize).ToMeshDraw(); + scaleOrigin = new Entity("OriginCube") { new ModelComponent { Model = new Model { DefaultOriginMaterial, new Mesh { Draw = cubeMeshDraw } }, RenderGroup = RenderGroup } }; entity.Transform.Children.Add(scaleOrigin.Transform); return entity; } - + + public override async Task Update() + { + await base.Update(); + + UpdateDrawOrder(); + } + + protected override void UpdateShape() + { + base.UpdateShape(); + + var cameraService = Game.EditorServices.Get(); + if (cameraService == null) + return; + + var gizmoInverse = Matrix.Invert(WorldMatrix); + var viewInverse = Matrix.Invert(cameraService.ViewMatrix); + var cameraInGizmoSpace = (viewInverse * gizmoInverse).TranslationVector; + + // reset the plane rotations + for (int axis = 0; axis < 3; axis++) + scalePlaneRoots[axis].TransformValue.Rotation = Quaternion.Identity; + + for (int axis = 0; axis < 3; axis++) + { + var isCameraBackfacing = cameraInGizmoSpace[axis] < 0; + scaleOpositeAxes[axis].ForEach(x => x.Enabled = isCameraBackfacing); + + if (isCameraBackfacing) + { + scalePlaneRoots[(axis + 1) % 3].TransformValue.Rotation *= Quaternion.RotationY(MathUtil.Pi); + scalePlaneRoots[(axis + 2) % 3].TransformValue.Rotation *= Quaternion.RotationZ(MathUtil.Pi); + } + } + } + protected override void UpdateTransformationAxis() { var newSelection = GizmoTransformationAxes.None; @@ -68,29 +192,55 @@ protected override void UpdateTransformationAxis() // calculate the ray in the gizmo space var gizmoMatrix = WorldMatrix; var gizmoViewInverse = Matrix.Invert(gizmoMatrix * cameraService.ViewMatrix); - var clickRay = EditorGameHelper.CalculateRayFromMousePosition(cameraService.Component, Input.MousePosition, gizmoViewInverse); - // determine gizmo world matrix and scale - var gizmoScale = gizmoMatrix.Row1.Length(); + // Check if the inverted View Matrix is valid (since it will be use for mouse picking, check the translation vector only) + if (float.IsNaN(gizmoViewInverse.TranslationVector.X) + || float.IsNaN(gizmoViewInverse.TranslationVector.Y) + || float.IsNaN(gizmoViewInverse.TranslationVector.Z)) + { + return; + } + + var clickRay = EditorGameHelper.CalculateRayFromMousePosition(cameraService.Component, Input.MousePosition, gizmoViewInverse); - float hitDistance; var minHitDistance = float.PositiveInfinity; - // select the axis whose intersection is the closest + // Select the closest intersecting translation plane + foreach (var pair in EditorGameComponentGizmoService.PlaneToIndex) + { + var minimum = new Vector3(0) { [pair.Value] = -AxisBodyRadius }; + var maximum = new Vector3(GizmoPlaneLength); + + for (int axis = 0; axis < 3; axis++) + maximum[axis] *= Math.Sign(gizmoViewInverse[3, axis]); // translation planes move to always face the camera. + + maximum[pair.Value] = AxisBodyRadius; + + UpdateSelectionOnCloserIntersection(new BoundingBox(minimum, maximum), clickRay, pair.Key, ref minHitDistance, ref newSelection); + } + + // Overrides selection with the closest intersecting axis if any for (int i = 0; i < 3; i++) { - var minimum = new Vector3(-GizmoExtremitySize / 2); - minimum[i] = 0; - var maximum = new Vector3(+GizmoExtremitySize / 2); - maximum[i] = 1; + // the extremity + var minimum = new Vector3(-AxisExtremitySize) { [i] = AxisBodyLength }; + var maximum = new Vector3(+AxisExtremitySize) { [i] = 1 }; + + UpdateSelectionOnCloserIntersection(new BoundingBox(minimum, maximum), clickRay, (GizmoTransformationAxes)(1 << i), ref minHitDistance, ref newSelection); + + // the body + minimum = new Vector3(-AxisBodyRadius) { [i] = 0 }; + maximum = new Vector3(+AxisBodyRadius) { [i] = AxisBodyLength }; + if (gizmoViewInverse[3, i] < 0) + minimum[i] = -GizmoPlaneLength; // camera is backfacing the axis we should compute intersection with the oposite showing part too UpdateSelectionOnCloserIntersection(new BoundingBox(minimum, maximum), clickRay, (GizmoTransformationAxes)(1 << i), ref minHitDistance, ref newSelection); } - // overrides the current selection with the origin if the intersection distance are similar - var minimumOrigin = new Vector3(-GizmoOriginScale * GizmoExtremitySize / 2); - var maximumOrigin = new Vector3(+GizmoOriginScale * GizmoExtremitySize / 2); - if (new BoundingBox(minimumOrigin, maximumOrigin).Intersects(ref clickRay, out hitDistance) && hitDistance - minHitDistance < gizmoScale * 0.5f) + // overrides the current selection with the origin if intersecting + var minimumOrigin = new Vector3(-OriginSize / 2f); + var maximumOrigin = new Vector3(+OriginSize / 2f); + if (new BoundingBox(minimumOrigin, maximumOrigin).Intersects(ref clickRay)) { newSelection = GizmoTransformationAxes.XYZ; } @@ -141,12 +291,35 @@ protected override void UpdateColors() for (int axis = 0; axis < 3; axis++) { var axisMaterial = GetAxisDefaultMaterial(axis); + var planeMaterial = planeMaterials[axis]; var transformationAxis = (GizmoTransformationAxes)(1 << axis); - bool isSelected = (TransformationAxes & transformationAxis) == transformationAxis; - scaleAxes[axis].ForEach(x => x.Get().Model.Materials[0] = isSelected ? ElementSelectedMaterial: axisMaterial); + bool isAxisSelected = (TransformationAxes & transformationAxis) == transformationAxis; + scaleAxes[axis].ForEach(x => x.Get().Model.Materials[0] = isAxisSelected ? ElementSelectedMaterial : axisMaterial); + bool isPlaneSelected = (TransformationAxes ^ transformationAxis) == GizmoTransformationAxes.XYZ; + scalePlaneEdges[axis].ForEach(x => x.Get().Model.Materials[0] = isPlaneSelected ? ElementSelectedMaterial : axisMaterial); + scalePlanes[axis].ForEach(x => x.Get().Model.Materials[0] = isPlaneSelected ? TransparentElementSelectedMaterial : planeMaterial); } var originSelected = TransformationAxes == GizmoTransformationAxes.XYZ; scaleOrigin.Get().Model.Materials[0] = originSelected ? ElementSelectedMaterial : DefaultOriginMaterial; } + + private void UpdateDrawOrder() + { + var cameraService = Game.EditorServices.Get(); + // calculate the depth of the entities + var viewProjection = cameraService.ViewMatrix * cameraService.ProjectionMatrix; + foreach (EntitySortInfo sortInfo in sortedEntities) + { + var worldViewProjection = sortInfo.Entity.Transform.WorldMatrix * viewProjection; + sortInfo.Depth = worldViewProjection.M43; + // protects against exception thrown by Sort's internals + // TODO: investigate the actual source of the presence of nans which must come from one of the matrices. c.f. XK-4627 + if (float.IsNaN(sortInfo.Depth)) + sortInfo.Depth = float.MaxValue; + } + + // sort the entities by decreasing depth + sortedEntities.Sort(sortedEntities[0]); + } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TransformationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TransformationGizmo.cs index 0149034294..61bd9fa5b0 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TransformationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TransformationGizmo.cs @@ -43,7 +43,6 @@ public bool IsIdentity() public const RenderGroupMask TransformationGizmoGroupMask = RenderGroupMask.Group4; private bool transformationInitialized; - private bool transformationStarted; private bool duplicationDone; /// @@ -56,6 +55,11 @@ public bool IsIdentity() /// public float DefaultScale => GizmoDefaultSize / GraphicsDevice.Presenter.BackBuffer.Height; + /// + /// Returns whether the transformation has started or not + /// + protected bool TransformationStarted { get; private set; } + /// /// The default material for the origin elements /// @@ -135,9 +139,9 @@ protected override Entity Create() { base.Create(); - DefaultOriginMaterial = CreateUniformColorMaterial(Color.White); - ElementSelectedMaterial = CreateUniformColorMaterial(Color.Gold); - TransparentElementSelectedMaterial = CreateUniformColorMaterial(Color.Gold.WithAlpha(86)); + DefaultOriginMaterial = CreateEmissiveColorMaterial(Color.White); + ElementSelectedMaterial = CreateEmissiveColorMaterial(Color.Gold); + TransparentElementSelectedMaterial = CreateEmissiveColorMaterial(Color.Gold.WithAlpha(86)); return null; } @@ -186,7 +190,7 @@ private Matrix GetWorldMatrix(IEditorGameCameraService cameraService) if (AnchorEntity.GetParent() != null) parentMatrix = AnchorEntity.TransformValue.Parent.WorldMatrix; - // We don't use the entity's "WorldMatrix" because it's scale could be zero, which would break the gizmo. + // We don't use the entity's "WorldMatrix" because its scale could be zero, which would break the gizmo. worldMatrix = Matrix.RotationQuaternion(AnchorEntity.Transform.Rotation) * Matrix.Translation(AnchorEntity.Transform.Position) * parentMatrix; @@ -316,7 +320,7 @@ protected virtual void InitializeTransformation() protected virtual void OnTransformationStarted(Vector2 mouseDragPixel) { - transformationStarted = true; + TransformationStarted = true; // keep in memory all initial transformation states InitialTransformations.Clear(); @@ -353,7 +357,7 @@ private async Task TransformSceneEntityBase() OnTransformationFinished(); transformationInitialized = false; - transformationStarted = false; + TransformationStarted = false; return; } @@ -368,7 +372,7 @@ private async Task TransformSceneEntityBase() var mouseDrag = mousePosition - StartMousePosition; // start the transformation only if user has dragged from a given amount of pixel. Determine direction of the transformation. - if (!transformationStarted) + if (!TransformationStarted) { // ensure that the mouse cursor has been moved enough var screenSize = new Vector2(GraphicsDevice.Presenter.BackBuffer.Width, GraphicsDevice.Presenter.BackBuffer.Height); diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TranslationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TranslationGizmo.cs index b053f22a97..8eaa695926 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TranslationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/TranslationGizmo.cs @@ -2,7 +2,6 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Stride.Core.Mathematics; using Stride.Assets.Presentation.AssetEditors.EntityHierarchyEditor.Game; @@ -29,9 +28,9 @@ public int Compare(EntitySortInfo x, EntitySortInfo y) } }; - private const float AxisConeRadius = GizmoExtremitySize / 2f; - private const float AxisConeHeight = 2f * AxisConeRadius; - private const float AxisBodyRadius = GizmoExtremitySize / 7.5f; + private const float AxisConeRadius = GizmoExtremitySize / 3f; + private const float AxisConeHeight = GizmoExtremitySize; + private const float AxisBodyRadius = GizmoExtremitySize / 9f; private const float AxisBodyLength = 1f - AxisConeHeight; private const float OriginRadius = GizmoOriginScale * AxisConeRadius; @@ -75,25 +74,19 @@ protected override Entity Create() bodyEntity.Transform.RotationEulerXYZ = -MathUtil.Pi / 2 * Vector3.UnitZ; translationAxes[axis].Add(bodyEntity); - // oposite side part (cylinder + end sphere shown when camera is looking oposite direction to the axis) + // oposite side part (cylinder shown when camera is looking oposite direction to the axis) var frameMesh = GeometricPrimitive.Cylinder.New(GraphicsDevice, GizmoPlaneLength, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); var opositeFrameEntity = new Entity("Oposite Frame" + axis) { new ModelComponent { Model = new Model { material, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; opositeFrameEntity.Transform.Position.X = - GizmoPlaneLength / 2; opositeFrameEntity.Transform.RotationEulerXYZ = -MathUtil.Pi / 2 * Vector3.UnitZ; - var articulationMesh = GeometricPrimitive.Sphere.New(GraphicsDevice, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); - var opositeSphereEntity = new Entity("FrameSphere" + axis) { new ModelComponent { Model = new Model { material, new Mesh { Draw = articulationMesh } }, RenderGroup = RenderGroup } }; - opositeSphereEntity.Transform.Position = new Vector3(-GizmoPlaneLength, 0, 0); translationAxes[axis].Add(opositeFrameEntity); - translationAxes[axis].Add(opositeSphereEntity); translationOpositeAxes[axis].Add(opositeFrameEntity.Get()); - translationOpositeAxes[axis].Add(opositeSphereEntity.Get()); - // create the arrow entity composed of the cone and bode + // create the arrow entity composed of the cone and body var arrowEntity = new Entity("ArrowEntity" + axis); arrowEntity.Transform.Children.Add(coneEntity.Transform); arrowEntity.Transform.Children.Add(bodyEntity.Transform); arrowEntity.Transform.Children.Add(opositeFrameEntity.Transform); - arrowEntity.Transform.Children.Add(opositeSphereEntity.Transform); // Add the arrow entity to the gizmo entity axisRootEntities[axis].Transform.Children.Add(arrowEntity.Transform); @@ -104,23 +97,17 @@ protected override Entity Create() { // The skeleton material var axisMaterial = GetAxisDefaultMaterial(axis); - - // The 2 frame cylinders - var frameMesh = GeometricPrimitive.Cylinder.New(GraphicsDevice, GizmoPlaneLength, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); + + // The 2 frame rectangles + var frameMesh = GeometricPrimitive.Cube.New(GraphicsDevice, new Vector3(AxisBodyRadius / 2f, GizmoPlaneLength / 3f, AxisBodyRadius / 2f)).ToMeshDraw(); var topFrameEntity = new Entity("TopFrame" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; var leftFrameEntity = new Entity("LeftFrame" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = frameMesh } }, RenderGroup = RenderGroup } }; - topFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength, GizmoPlaneLength / 2); + topFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength, GizmoPlaneLength - (GizmoPlaneLength / 6)); topFrameEntity.Transform.RotationEulerXYZ = new Vector3(MathUtil.Pi / 2f, 0, 0); - leftFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength / 2, GizmoPlaneLength); + leftFrameEntity.Transform.Position = new Vector3(0, GizmoPlaneLength - (GizmoPlaneLength / 6), GizmoPlaneLength); translationPlaneEdges[axis].Add(topFrameEntity); translationPlaneEdges[axis].Add(leftFrameEntity); - // The articulation sphere - var articulationMesh = GeometricPrimitive.Sphere.New(GraphicsDevice, AxisBodyRadius, GizmoTessellation).ToMeshDraw(); - var articulationEntity = new Entity("FrameSphere" + axis) { new ModelComponent { Model = new Model { axisMaterial, new Mesh { Draw = articulationMesh } }, RenderGroup = RenderGroup } }; - articulationEntity.Transform.Position = new Vector3(0, GizmoPlaneLength, GizmoPlaneLength); - translationPlaneEdges[axis].Add(articulationEntity); - // The transparent planes (2 for correct lighting) var materialPlane = planeMaterials[axis]; var planeMesh = GeometricPrimitive.Plane.New(GraphicsDevice, GizmoPlaneLength, GizmoPlaneLength).ToMeshDraw(); @@ -139,7 +126,6 @@ protected override Entity Create() var planeEntity = new Entity("GizmoPlane" + axis); planeEntity.Transform.Children.Add(topFrameEntity.Transform); planeEntity.Transform.Children.Add(leftFrameEntity.Transform); - planeEntity.Transform.Children.Add(articulationEntity.Transform); planeEntity.Transform.Children.Add(planeFrameEntityFront.Transform); planeEntity.Transform.Children.Add(planeFrameEntityBack.Transform); translationPlaneRoots.Add(planeEntity); @@ -148,7 +134,7 @@ protected override Entity Create() axisRootEntities[axis].Transform.Children.Add(planeEntity.Transform); } - // set the axis root entities rotation and add them to the main entityaxisRootEntities[axis] + // set the axis root entities rotation and add them to the main entity var axisRotations = new [] { Vector3.Zero, new Vector3(MathUtil.PiOverTwo, 0, MathUtil.PiOverTwo), new Vector3(-MathUtil.PiOverTwo, -MathUtil.PiOverTwo, 0) }; for (int axis = 0; axis < 3; axis++) { @@ -157,9 +143,8 @@ protected override Entity Create() } // Add middle sphere - var materialSphere = DefaultOriginMaterial; var sphereMeshDraw = GeometricPrimitive.Sphere.New(GraphicsDevice, OriginRadius, GizmoTessellation).ToMeshDraw(); - translationOrigin = new Entity("OriginCube") { new ModelComponent { Model = new Model { materialSphere, new Mesh { Draw = sphereMeshDraw } }, RenderGroup = RenderGroup } }; + translationOrigin = new Entity("OriginSphere") { new ModelComponent { Model = new Model { DefaultOriginMaterial, new Mesh { Draw = sphereMeshDraw } }, RenderGroup = RenderGroup } }; entity.Transform.Children.Add(translationOrigin.Transform); return entity; @@ -191,7 +176,7 @@ protected override void UpdateShape() for (int axis = 0; axis < 3; axis++) { var isCameraBackfacing = cameraInGizmoSpace[axis] < 0; - translationOpositeAxes[axis].ForEach(x=>x.Enabled = isCameraBackfacing); + translationOpositeAxes[axis].ForEach(x => x.Enabled = isCameraBackfacing); if (isCameraBackfacing) { @@ -220,7 +205,6 @@ protected override void UpdateTransformationAxis() var clickRay = EditorGameHelper.CalculateRayFromMousePosition(cameraService.Component, Input.MousePosition, gizmoViewInverse); - float hitDistance; var minHitDistance = float.PositiveInfinity; // Select the closest intersecting translation plane @@ -237,7 +221,7 @@ protected override void UpdateTransformationAxis() UpdateSelectionOnCloserIntersection(new BoundingBox(minimum, maximum), clickRay, pair.Key, ref minHitDistance, ref newSelection); } - // Overrides selection with the closed intersecting axis if any + // Overrides selection with the closest intersecting axis if any minHitDistance = float.PositiveInfinity; for (int i = 0; i < 3; i++) { @@ -257,7 +241,7 @@ protected override void UpdateTransformationAxis() } // overrides the current selection with the origin if intersecting - if (new BoundingSphere(Vector3.Zero, OriginRadius).Intersects(ref clickRay, out hitDistance)) + if (new BoundingSphere(Vector3.Zero, OriginRadius).Intersects(ref clickRay)) newSelection = GizmoTransformationAxes.XYZ; TransformationAxes = newSelection; diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/VoxelVolumeGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/VoxelVolumeGizmo.cs index 47bedc5abb..3ed6a5bf53 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/VoxelVolumeGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/VoxelVolumeGizmo.cs @@ -32,7 +32,7 @@ protected override Entity Create() { debugRootEntity = new Entity($"Voxel volume of {Component.Entity.Name}"); - material = GizmoUniformColorMaterial.Create(GraphicsDevice, Color.CornflowerBlue); + material = GizmoEmissiveColorMaterial.Create(GraphicsDevice, Color.CornflowerBlue); box = new BoxMesh(GraphicsDevice); box.Build(); diff --git a/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs b/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs index 739df5a565..27a237aa76 100644 --- a/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs +++ b/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs @@ -80,6 +80,10 @@ static SceneEditorSettings() { DisplayName = $"{SceneEditor}/{ViewportSettings}/{Tr._p("Settings", "Default snap factor for scale")}" }; + DisplayDirectionNames = new SettingsKey("SceneEditor/ViewportSettings/DisplayDirectionNames", Stride.Core.Assets.Editor.Settings.EditorSettings.SettingsContainer, false) + { + DisplayName = $"{SceneEditor}/{ViewportSettings}/{Tr._p("Settings", "Display direction names instead of XYZ components")}" + }; AskBeforeDeletingEntities = new SettingsKey("SceneEditor/AskBeforeDeletingEntities", Stride.Core.Assets.Editor.Settings.EditorSettings.SettingsContainer, true) { DisplayName = $"{SceneEditor}/{Tr._p("Settings", "Ask before deleting entities")}" @@ -118,6 +122,8 @@ static SceneEditorSettings() public static SettingsKey DefaultScaleSnap { get; } + public static SettingsKey DisplayDirectionNames { get; } + public static SettingsKey AskBeforeDeletingEntities { get; } public static void Save() diff --git a/sources/engine/Stride.Rendering/Rendering/Editor/CameraOrientationGizmoShader.sdsl b/sources/engine/Stride.Rendering/Rendering/Editor/CameraOrientationGizmoShader.sdsl new file mode 100644 index 0000000000..461f20ab9b --- /dev/null +++ b/sources/engine/Stride.Rendering/Rendering/Editor/CameraOrientationGizmoShader.sdsl @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +shader CameraOrientationGizmoShader : ComputeColor, PositionStream4 +{ + override float4 Compute() + { + float yPosRemapped = pow((streams.PositionWS.y + 1) / 2, 3.5f); + return float4(0.6f, 0.6f, 0.6f, 1.0f) * yPosRemapped; + } +}; From a01ab21bdfd658e79d0083641b274eda5a1cd9c3 Mon Sep 17 00:00:00 2001 From: salahchafai <64394387+salahchafai@users.noreply.github.com> Date: Thu, 1 Jul 2021 10:51:36 +0100 Subject: [PATCH 2/3] small fix --- .../AssetEditors/Gizmos/RotationGizmo.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs index e75338d0a0..4a1cb9d6ef 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs @@ -186,12 +186,10 @@ protected override InitialTransformation CalculateTransformation() rotationAngle = MathUtil.Snap(rotationAngle, snapValue); } - // determine the rotation axis in the Gizmo - + // determine the rotation axis var rotationAxisWorldUp = rotationAxes[(int)TransformationAxes / 2].Transform.WorldMatrix.Up; - var anchorEntityToCamera = AnchorEntity.Transform.WorldMatrix.TranslationVector - Game.EditorServices.Get().Position; - var rotationAxis = new Vector3(0) { [(int)TransformationAxes / 2] = MathF.Sign(Vector3.Dot(anchorEntityToCamera, rotationAxisWorldUp)) }; - rotationAxis.Normalize(); + var cameraToAnchorEntity = AnchorEntity.Transform.WorldMatrix.TranslationVector - Game.EditorServices.Get().Position; + var rotationAxis = new Vector3(0) { [(int)TransformationAxes / 2] = MathF.Sign(Vector3.Dot(cameraToAnchorEntity, rotationAxisWorldUp)) }; // set the rotation to apply in the gizmo space transformation.Rotation = Quaternion.RotationAxis(rotationAxis, rotationAngle); From 68714907b6519a56eb1b236f2cf707d1a4c8713c Mon Sep 17 00:00:00 2001 From: salahchafai <64394387+salahchafai@users.noreply.github.com> Date: Thu, 1 Jul 2021 14:17:25 +0100 Subject: [PATCH 3/3] added the option to use linear movement for rotating --- .../AssetEditors/Gizmos/RotationGizmo.cs | 38 ++++++++++++++++--- .../SceneEditor/SceneEditorSettings.cs | 6 +++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs index 4a1cb9d6ef..62b5fac951 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/RotationGizmo.cs @@ -7,6 +7,7 @@ using Stride.Extensions; using Stride.Graphics.GeometricPrimitives; using Stride.Rendering; +using Stride.Assets.Presentation.SceneEditor; namespace Stride.Assets.Presentation.AssetEditors.Gizmos { @@ -154,6 +155,16 @@ protected override void UpdateColors() /// /// protected override InitialTransformation CalculateTransformation() + { + var transformation = new InitialTransformation { Rotation = Quaternion.Identity, Scale = Vector3.One }; + + // set the rotation to apply in the gizmo space + transformation.Rotation = SceneEditorSettings.UseLinearMovementForRotation.GetValue() ? GetRotationFromLinearMovement() : GetRotationFromCircularMovement(); + + return transformation; + } + + private Quaternion GetRotationFromCircularMovement() { // TODO: use cameraComponent.WorldToScreenPosition instead once implemented // determine the anchor entity's screen position @@ -173,8 +184,6 @@ protected override InitialTransformation CalculateTransformation() anchorEntityToMouse.X *= cameraComponent.AspectRatio; anchorEntityToStartMouse.X *= cameraComponent.AspectRatio; - - var transformation = new InitialTransformation { Rotation = Quaternion.Identity, Scale = Vector3.One }; // determine the rotation angle var rotationAngle = MathF.Atan2(anchorEntityToMouse.X * anchorEntityToStartMouse.Y - anchorEntityToMouse.Y * anchorEntityToStartMouse.X, Vector2.Dot(anchorEntityToStartMouse, anchorEntityToMouse)); @@ -187,14 +196,31 @@ protected override InitialTransformation CalculateTransformation() } // determine the rotation axis - var rotationAxisWorldUp = rotationAxes[(int)TransformationAxes / 2].Transform.WorldMatrix.Up; + var rotationAxisWorldUp = rotationAxes[(int)TransformationAxes / 2].Transform.WorldMatrix.Up; var cameraToAnchorEntity = AnchorEntity.Transform.WorldMatrix.TranslationVector - Game.EditorServices.Get().Position; var rotationAxis = new Vector3(0) { [(int)TransformationAxes / 2] = MathF.Sign(Vector3.Dot(cameraToAnchorEntity, rotationAxisWorldUp)) }; - // set the rotation to apply in the gizmo space - transformation.Rotation = Quaternion.RotationAxis(rotationAxis, rotationAngle); + return Quaternion.RotationAxis(rotationAxis, rotationAngle); + } - return transformation; + private Quaternion GetRotationFromLinearMovement() + { + var mouseDrag = Input.MousePosition - StartMousePosition; + + // determine the rotation angle + var rotationAngle = Vector2.Dot(new Vector2(mouseDrag.X, -mouseDrag.Y), TransformationDirection) * 2.1f * MathUtil.Pi; // half screen size if little bit more Pi + + // snap the rotation angle if necessary + if (UseSnap) + { + var snapValue = MathUtil.DegreesToRadians(SnapValue); + rotationAngle = MathUtil.Snap(rotationAngle, snapValue); + } + + // determine the rotation axis + var rotationAxis = new Vector3(0) { [(int)TransformationAxes / 2] = 1 }; + + return Quaternion.RotationAxis(rotationAxis, rotationAngle); } protected override void OnTransformationFinished() diff --git a/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs b/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs index 27a237aa76..e1e5459a99 100644 --- a/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs +++ b/sources/editor/Stride.Assets.Presentation/SceneEditor/SceneEditorSettings.cs @@ -84,6 +84,10 @@ static SceneEditorSettings() { DisplayName = $"{SceneEditor}/{ViewportSettings}/{Tr._p("Settings", "Display direction names instead of XYZ components")}" }; + UseLinearMovementForRotation = new SettingsKey("SceneEditor/ViewportSettings/UseLinearMovementForRotation", Stride.Core.Assets.Editor.Settings.EditorSettings.SettingsContainer, false) + { + DisplayName = $"{SceneEditor}/{ViewportSettings}/{Tr._p("Settings", "Use linear movement for the rotation gizmo")}" + }; AskBeforeDeletingEntities = new SettingsKey("SceneEditor/AskBeforeDeletingEntities", Stride.Core.Assets.Editor.Settings.EditorSettings.SettingsContainer, true) { DisplayName = $"{SceneEditor}/{Tr._p("Settings", "Ask before deleting entities")}" @@ -124,6 +128,8 @@ static SceneEditorSettings() public static SettingsKey DisplayDirectionNames { get; } + public static SettingsKey UseLinearMovementForRotation { get; } + public static SettingsKey AskBeforeDeletingEntities { get; } public static void Save()