private void FireManipulationEvent(AxisManipulationHelperType amhType, bool started)
        {
            switch (amhType)
            {
            case AxisManipulationHelperType.Center:
                if (started)
                {
                    this.CenterManipulationStarted?.Invoke(this, EventArgs.Empty);
                }
                else
                {
                    this.CenterManipulationEnded?.Invoke(this, EventArgs.Empty);
                }

                break;

            case AxisManipulationHelperType.Axis:
                if (started)
                {
                    this.AxisManipulationStarted?.Invoke(this, EventArgs.Empty);
                }
                else
                {
                    this.AxisManipulationEnded?.Invoke(this, EventArgs.Empty);
                }

                break;

            case AxisManipulationHelperType.Plane:
                if (started)
                {
                    this.PlaneManipulationStarted?.Invoke(this, EventArgs.Empty);
                }
                else
                {
                    this.PlaneManipulationEnded?.Invoke(this, EventArgs.Empty);
                }

                break;
            }
        }
        private AxisManipulationHelper CreateHandle(AxisManipulationHelperType amhType, AxisType axisType, Prefab prefab, Material idleMaterial, Material grabbedMaterial, Material focusedMaterial, Quaternion orientation, AxisManipulationHelper[] relatedHandlers)
        {
            // Entity name suffix
            var suffix = $"{amhType}_{axisType}";

            // Handle root
            var handle = new Entity($"handle_{suffix}")
                         .AddComponent(new Transform3D()
            {
                LocalScale = Vector3.One * this.HandleScale,
            });

            this.rigRootEntity.AddChild(handle);

            if (prefab != null)
            {
                // Instantiate prefab
                var prefabInstance = prefab.Instantiate();

                var prefabTransform = prefabInstance.FindComponent <Transform3D>();
                prefabTransform.LocalOrientation = orientation;

                handle.AddChild(prefabInstance);
            }
            else
            {
                // Generate default look for the handle
                Vector3   position = Vector3.Zero;
                Vector3   size     = Vector3.One;
                Component mesh     = null;
                Component collider = null;

                switch (amhType)
                {
                case AxisManipulationHelperType.Center:
                    var sphereDiameter = 1f;

                    mesh = new SphereMesh()
                    {
                        Diameter = sphereDiameter,
                    };
                    collider = new SphereCollider3D()
                    {
                        Margin = 0.0001f,
                        Radius = sphereDiameter,
                    };
                    break;

                case AxisManipulationHelperType.Axis:
                    var axisLength    = 4f;
                    var axisThickness = 0.5f;

                    size = new Vector3(axisLength, axisThickness, axisThickness);

                    mesh     = new CubeMesh();
                    collider = new BoxCollider3D()
                    {
                        Margin = 0.0001f,
                        Size   = size + Vector3.One,
                        Offset = Vector3.UnitX,
                    };

                    position = 0.5f * Vector3.UnitX * (axisLength + 2f);
                    break;

                case AxisManipulationHelperType.Plane:
                    var planeLength    = 2f;
                    var planeThickness = 0.25f;

                    size = new Vector3(planeLength, planeThickness, planeLength);

                    mesh     = new CubeMesh();
                    collider = new BoxCollider3D()
                    {
                        Margin = 0.0001f,
                        Size   = size + Vector3.One,
                        Offset = Vector3.UnitX + Vector3.UnitZ,
                    };

                    position = 0.5f * Vector3.Normalize(Vector3.UnitX + Vector3.UnitZ) * (planeLength + 2f);
                    break;
                }

                // Collider entity
                var handleCollider = new Entity($"collider_{suffix}")
                                     .AddComponent(new Transform3D()
                {
                    LocalPosition    = Vector3.Transform(position, orientation),
                    LocalOrientation = orientation,
                })
                                     .AddComponent(collider)
                                     .AddComponent(new StaticBody3D()
                {
                    CollisionCategories = this.CollisionCategory,
                    IsSensor            = true,
                })
                                     .AddComponent(new NearInteractionGrabbable());

                // Visual entity
                var handleVisual = new Entity($"visuals_{suffix}")
                                   .AddComponent(new Transform3D()
                {
                    LocalScale = size,
                })
                                   .AddComponent(mesh)
                                   .AddComponent(new MeshRenderer())
                                   .AddComponent(new MaterialComponent());

                // Build hierarchy
                handle.AddChild(handleCollider);
                handleCollider.AddChild(handleVisual);
            }

            // Apply material
            var materialComponents = handle.FindComponentsInChildren <MaterialComponent>().ToArray();

            this.ApplyMaterialToAllComponents(materialComponents, idleMaterial);

            // Register helper object
            var helperTargetEntity = handle.FindComponentInChildren <NearInteractionGrabbable>()?.Owner;

            if (helperTargetEntity == null)
            {
                throw new Exception($"The handle entity needs to have a {nameof(NearInteractionGrabbable)} component.");
            }

            var handleHelper = new AxisManipulationHelper()
            {
                Type               = amhType,
                AxisType           = axisType,
                BaseEntity         = handle,
                MaterialComponents = materialComponents,
                IdleMaterial       = idleMaterial,
                GrabbedMaterial    = grabbedMaterial,
                FocusedMaterial    = focusedMaterial,
                RelatedHandles     = relatedHandlers,
            };

            this.helpers.Add(helperTargetEntity, handleHelper);

            return(handleHelper);
        }