dev.TransformGizmos - tixl3d/tixl GitHub Wiki
dev.TransformGizmos
The evaluation state has a configuration variable that defines, if gizmo should be visible:
in EvaluationContext.cs
...
public enum GizmoVisibility
{
Inherit = -1,
Off = 0,
On = 1,
IfSelected = 2,
public GizmoVisibility ShowGizmos { get; set; }
}
The output window can set this initial state (similar to camera position or requested image size).
During the evaluation, only for updated operators this can be overridden by implementing the ITransformable
interface.
public interface ITransformable
{
IInputSlot TranslationInput { get; }
IInputSlot RotationInput { get; }
IInputSlot ScaleInput { get; }
Action<Instance, EvaluationContext> TransformCallback { get; set; }
}
E.g for the [Transform] op:
[Guid("284d2183-197d-47fd-b130-873cced78b1c")]
internal sealed class Transform : Instance<Transform>, ITransformable //<-- this
{
// Implement the interface and define which parameters are control which transform gizmos.
IInputSlot ITransformable.TranslationInput => Translation;
IInputSlot ITransformable.RotationInput => Rotation;
IInputSlot ITransformable.ScaleInput => Scale;
public Action<Instance, EvaluationContext> TransformCallback { get; set; }
This assignments also allows to control parameters that are named differently, like Center
or Offset
. And gizmos that do not exist yet`. Like Rotation, and scale.
So basically adding transform gizmos to operators with Vec3-parameters is trivial:
- Add the interface
- Implement the interface (e.g. like in [Transform])
- Done.
Implementing the transform-gizmo might be tricky for some ops, because the gizmo will only be shown, if the operators is evaluated. The shadergraph does heavy optimization and caching that avoid evaluating parameters and ops that are not dirty. Internally the transform-gizmo-handling will "force invalidate" to force the editor to update them.
Let's try that.
BozSDF
has a Center
parameter:
[GraphParam]
[Input(Guid = "951b2983-1359-41e4-8fb0-8d97c50ed8d6")]
public readonly InputSlot<Vector3> Center = new();
After adding the Interface we get the following error:
Interface member 'IInputSlot T3.Core.Operator.Interfaces.ITransformable.RotationInput' is not implemented
Interface member 'IInputSlot T3.Core.Operator.Interfaces.ITransformable.ScaleInput' is not implemented
Interface member 'Action<Instance,EvaluationContext>
T3.Core.Operator.Interfaces.ITransformable.TransformCallback' is not implemented Interface member 'IInputSlot T3.Core.Operator.Interfaces.ITransformable.TranslationInput' is not implemented
We implement the interfaces like this:
// This maps the parameters
IInputSlot ITransformable.TranslationInput => Center;
IInputSlot ITransformable.RotationInput => null;
IInputSlot ITransformable.ScaleInput => null;
// This is called when the operators is updated.
public Action<Instance, EvaluationContext> TransformCallback { get; set; }
Then in Update()
we need to add this line:
private void Update(EvaluationContext context)
{
TransformCallback?.Invoke(this, context); // this will invoke drawing the gizmo
ShaderNode.Update(context);
}
Okay. This is actually slightly complicated:
When the editor selection changes, this method is being called:
in NodeSelection.AddSelection()
:
if (instance is ITransformable transformable)
{
TransformGizmoHandling.RegisterSelectedTransformable(childUi, transformable);
}
So if something is selected and something supports transform gizmo handling than we know about it, because TransformGizmoHandling
keeps a list of these.
public static void RegisterSelectedTransformable(SymbolUi.Child node, ITransformable transformable)
{
if (_selectedTransformables.Contains(transformable))
return;
transformable.TransformCallback = TransformCallback; // <--- Set callback
_selectedTransformables.Add(transformable);
}
It also sets the callback event which will be called from inside the operator Update()
method. So it will be called multiple times, if an operator is invoked multiple times (e.g. inside a loop).
The TransformCallback()
is where the magic is happing:
/// <summary>
/// Called from <see cref="ITransformable"/> operators during update call
/// </summary>
public static void TransformCallback(Instance instance, EvaluationContext context)
{
It does all kind of transform matrix operators and eventually uses the current transform matrix from the graph, the camera matrix and output window's size and position to use imgui to draw the gizmo. Yes! Those lines are drawn with imgui!
All the ray-casting and hit-testing is done manually: For instance in the HandleDragOnAxis() {}
method. It's quite involved and this does does not work for all perspectives.
To extend that from translation gizmo to rotation and scale gizmo, we would need more HandleDragOnAxis
overrides for scale and something circular for rotation.
Notes for implementation:
- We should allow switching between Rotate, Translate, Scale and probably non (e.g. select)
- Switching should be possible with hotkeys. Ideally QWER (yay... interfering with camera movement)
- Side-Note: Eventually we should switch to Unreal's camera interaction, where WASD only works while holding the right mouse button.
For switching we added a new property TransformGizmoMode
to the Evaluation Context. This can later be used in the TransformCallback
to check which transform gizmo mode to use.
if(_evaluationContext.TransformGizmoMode == TransformGizmoModes.Scale) {}