Browse Source

Merge pull request #308 from Eideren/gizmo

Gizmos
Vaclav Elias 1 year ago
parent
commit
a0eb2a6225

+ 1 - 0
en/manual/index.md

@@ -10,6 +10,7 @@ These pages contain information about how to use Stride, an open-source C# game
 ## Latest documentation
 
 ### Recent updates
+- <span class="badge text-bg-info">New</span> [Scripts - Gizmos](scripts/gizmos.md) - Description and example of the Flexible Processing system
 - <span class="badge text-bg-info">New</span> [ECS - Flexible Processing](engine/entity-component-system/flexible-processing.md) - Description and example of the Flexible Processing system
 - <span class="badge text-bg-info">Updated</span> [Linux - Setup and requirements](platforms/linux/setup-and-requirements.md) - Fedora OS option added
 - <span class="badge text-bg-success">New</span> [Scripts - Serialization](scripts/serialization.md) - Explanation of serialization

+ 172 - 0
en/manual/scripts/gizmos.md

@@ -0,0 +1,172 @@
+# Gizmos
+
+<span class="badge text-bg-primary">Intermediate</span>
+<span class="badge text-bg-success">Programmer</span>
+
+**Gizmos** are a tool which you can implement over your components to provide visual assistance for designers when manipulating component values.
+
+Here's an exhaustive example one could implement:
+```cs
+using Stride.Core;
+using Stride.Core.Mathematics;
+using Stride.Engine;
+using Stride.Engine.Gizmos;
+using Stride.Graphics;
+using Stride.Graphics.GeometricPrimitives;
+using Stride.Rendering;
+using Stride.Rendering.Materials;
+using Stride.Rendering.Materials.ComputeColors;
+
+// We will be implementing a Gizmo for the following class
+public class MyScript : StartupScript
+{
+    
+}
+
+// This attribute specifies to the engine that the following gizmo class is bound to 'MyScript', 
+// the game studio will pick that up and spawn an instance of that class for each 'MyScript' in the scene
+[GizmoComponent(typeof(MyScript), isMainGizmo:false/*When true, only the first gizmo on an entity with true is visible, false means that it is always visible*/)]
+public class Gizmo : IEntityGizmo
+{
+    private bool _selected, _enabled;
+    private MyScript _component;
+    private ModelComponent _model;
+    private Material _material, _materialOnSelect;
+
+    // This property is set based on whether the gizmo is globally turned on or off in the editor's view settings
+    public bool IsEnabled
+    {
+        get
+        {
+            return _enabled;
+        }
+        set
+        {
+            _enabled = value;
+            _model.Enabled = _enabled;
+        }
+    }
+
+    // The size slider value in the view settings pane
+    public float SizeFactor { get; set; }
+
+    // The editor will set this property whenever the entity the component is on is selected
+    public bool IsSelected
+    {
+        get
+        {
+            return _selected;
+        }
+        set
+        {
+            _selected = value;
+            _model.Materials[0] = _selected ? _materialOnSelect : _material;
+            // The logic below shows gizmos for all components when they are on in the gizmo settings, and when off, only shows the one from the selected entity
+            // Removing the line hides gizmos even when selected when the gizmo settings is off
+            _model.Enabled = _selected || _enabled;
+        }
+    }
+
+    // This constructor is called by the editor, 
+    // A gizmo class MUST contain a constructor with a single parameter of the component's type
+    public Gizmo(MyScript component)
+    {
+        _component = component;
+    }
+
+    public bool HandlesComponentId(OpaqueComponentId pickedComponentId, out Entity? selection)
+    {
+        // This function is called when scene picking/mouse clicking in the scene on a gizmo
+        // The engine calls this function on each gizmos, gizmos in term notify the engine
+        // when the given component comes from them by returning true, and provide the editor with the corresponding entity this gizmo represents
+        if (pickedComponentId.Match(_model))
+        {
+            selection = _component.Entity;
+            return true;
+        }
+        selection = null;
+        return false;
+    }
+
+    public void Initialize(IServiceRegistry services, Scene editorScene)
+    {
+        // As part of initialization, we create a model in the editor scene to visualize the gizmo
+        var graphicsDevice = services.GetSafeServiceAs<IGraphicsDeviceService>().GraphicsDevice;
+
+        // In our case we'll just rely on the GeometricPrimitive API to create a sphere for us
+        // You don't have to create one right away, you can delay it until the component is in the appropriate state
+        // You can also dynamically create and update one in the Update() function further below
+        var sphere = GeometricPrimitive.Sphere.New(graphicsDevice);
+
+        var vertexBuffer = sphere.VertexBuffer;
+        var indexBuffer = sphere.IndexBuffer;
+        var vertexBufferBinding = new VertexBufferBinding(vertexBuffer, new VertexPositionNormalTexture().GetLayout(), vertexBuffer.ElementCount);
+        var indexBufferBinding = new IndexBufferBinding(indexBuffer, sphere.IsIndex32Bits, indexBuffer.ElementCount);
+
+        _material = Material.New(graphicsDevice, new MaterialDescriptor
+        {
+            Attributes =
+            {
+                Emissive = new MaterialEmissiveMapFeature(new ComputeColor(new Color4(0.25f,0.75f,0.25f,0.05f).ToColorSpace(graphicsDevice.ColorSpace))) { UseAlpha = true },
+                Transparency = new MaterialTransparencyBlendFeature()
+            },
+        });
+        _materialOnSelect = Material.New(graphicsDevice, new MaterialDescriptor
+        {
+            Attributes =
+            {
+                Emissive = new MaterialEmissiveMapFeature(new ComputeColor(new Color4(0.25f,0.75f,0.25f,0.5f).ToColorSpace(graphicsDevice.ColorSpace))) { UseAlpha = true },
+                Transparency = new MaterialTransparencyBlendFeature()
+            },
+        });
+
+        _model = new ModelComponent
+        {
+            Model = new Model
+            {
+                (_selected ? _materialOnSelect : _material),
+                new Mesh
+                {
+                    Draw = new MeshDraw
+                    {
+                        StartLocation = 0,
+                        // You can swap to LineList or LineStrip to show the model in wireframe mode, you'll have to adapt your index buffer to that new type though
+                        PrimitiveType = PrimitiveType.TriangleList,
+                        VertexBuffers = new[] { vertexBufferBinding },
+                        IndexBuffer = indexBufferBinding,
+                        DrawCount = indexBuffer.ElementCount,
+                    }
+                }
+            },
+            RenderGroup = IEntityGizmo.PickingRenderGroup, // This RenderGroup allows scene picking/selection, use a different one if you don't want selection
+            Enabled = _selected || _enabled
+        };
+
+        var entity = new Entity($"{nameof(Gizmo)} for {_component.Entity.Name}"){ _model };
+        entity.Transform.UseTRS = false; // We're controlling the matrix directly in this case
+        entity.Scene = editorScene;
+
+        vertexBuffer.DisposeBy(entity);
+        indexBuffer.DisposeBy(entity); // Attach buffers to the entity for manual disposal later
+    }
+
+    public void Dispose()
+    {
+        _model.Entity.Scene = null;
+        _model.Entity.Dispose(); // Clear the two buffers we attached above
+    }
+
+    public void Update()
+    {
+        // This is where you'll update how the gizmo looks based on MyScript's state
+        // Here we'll just ensure the gizmo follows the entity it is representing whenever that entity moves, 
+        // note that UseTRS is disabled above to improve performance and ensure that there are no world space issues
+        _model.Entity.Transform.LocalMatrix = _component.Entity.Transform.WorldMatrix;
+    }
+}
+```
+And the result:
+
+![Green sphere gizmo](media/gizmo.png)
+
+Do note that you may have to restart the editor if it was open while you introduced this new gizmo.

+ 2 - 1
en/manual/scripts/index.md

@@ -39,4 +39,5 @@ You can still use standard C# classes in Stride, but these aren't called scripts
 * [Events](events.md)
 * [Debugging](debugging.md)
 * [Preprocessor variables](preprocessor-variables.md)
-* [Create a model from code](create-a-model-from-code.md)
+* [Create a model from code](create-a-model-from-code.md)
+* [Create Gizmos for you components](gizmos.md)

+ 3 - 0
en/manual/scripts/media/gizmo.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0a62f0db688ba96190cb147747afcb9c43818717b62eb334f86ca6a0dceb0c86
+size 92679

+ 2 - 0
en/manual/toc.yml

@@ -498,6 +498,8 @@ items:
         href: scripts/preprocessor-variables.md
       - name: Create a model from code
         href: scripts/create-a-model-from-code.md
+      - name: Create Gizmos for you components
+        href: scripts/gizmos.md
 
   - name: Sprites
     href: sprites/index.md