[Editor][Plugin] Expose and cleanup gizmos
PR Details
This PR provides support for user-defined gizmos, most of the logic was already there, I just had to clean out the editor only stuff from the api.
Here's a simple example:
[GizmoComponent(typeof(TestClass), 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 TestClass _component;
private ModelComponent _model;
private Material _material, _materialOnSelect;
public bool IsEnabled
{
get
{
return _enabled;
}
set
{
_enabled = value;
_model.Enabled = _enabled;
}
}
public float SizeFactor { get; set; }
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;
}
}
public Gizmo(TestClass component) // This constructor is called by the editor
{
_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, 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)
{
var graphicsDevice = services.GetSafeServiceAs<IGraphicsDeviceService>().GraphicsDevice;
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()
{
// Ensures the gizmo follows the entity it is representing, 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;
}
}
Types of changes
- [x] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
Checklist
- [x] My change requires a change to the documentation. - I'll take care of that once this is in
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [x] I have built and run the editor to try this change out.
Ohh, does it mean, I can access it through code only and add to any entity as it is just a regular component?
@VaclavElias It's not really a component, it only inherits from the IEntityGizmo interface.
The editor has facilities to automate the spawning and management of those gizmos when creating and destroying components with gizmos. The player as no such thing built in, but it's not that hard to implement on your side: create an instance alongside the component you want to have a gizmo on, call Initialize on the instance after creation, then Update on every update, and dispose when the component is removed from the scene. Once I'm looking at creating documentation for this I'll see if I can setup a processor or similar to automatically toggle gizmos on and off in a running game.
Thanks for the hints as usually. I will try it.
Yes, it could be useful to enable/disable it through running game as I found it still very useful even now, to orient in the 3D, when debugging/testing running game.
Also, if I may suggest. Adding optionally text X,Y,Z would be also helpful.
Looks like it can only work for a user gizmo if isMainGizmo is true. In my testing, it doesn't show when false. @Eideren Is that expected?
@Kryptos-FR it is not, I have two entities here, one with the component as first, and the other one as second, both show up with isMainGizmo:false as expected
Can you send a repro ?
@Eideren looks like it is working now. Maybe I needed to restart the editor. LGTM.
Thanks for reviewing @Kryptos-FR