From e2cdafebe9355bec79dbdabd73a9fd3ad1c87533 Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Thu, 7 Mar 2024 09:35:43 +0100 Subject: [PATCH 1/3] Added basic scaling support using unity's scale tool --- Editor/AGXUnityEditor/Attributes.cs | 19 +++ Editor/AGXUnityEditor/ContextManager.cs | 72 +++++++++ Editor/AGXUnityEditor/ContextManager.cs.meta | 11 ++ Editor/AGXUnityEditor/CustomScale.cs | 153 +++++++++++++++++++ Editor/AGXUnityEditor/CustomScale.cs.meta | 11 ++ Editor/AGXUnityEditor/InspectorEditor.cs | 6 + 6 files changed, 272 insertions(+) create mode 100644 Editor/AGXUnityEditor/ContextManager.cs create mode 100644 Editor/AGXUnityEditor/ContextManager.cs.meta create mode 100644 Editor/AGXUnityEditor/CustomScale.cs create mode 100644 Editor/AGXUnityEditor/CustomScale.cs.meta diff --git a/Editor/AGXUnityEditor/Attributes.cs b/Editor/AGXUnityEditor/Attributes.cs index e0785304..bbf1b327 100644 --- a/Editor/AGXUnityEditor/Attributes.cs +++ b/Editor/AGXUnityEditor/Attributes.cs @@ -29,6 +29,25 @@ public CustomToolAttribute( Type type ) } } + [AttributeUsage( AttributeTargets.Class, AllowMultiple = false )] + public class CustomContextAttribute : Attribute + { + /// + /// Instance type of the context is implemented for. + /// + public Type Type = null; + + /// + /// Construct given instance type the context is implemented for. + /// + /// Instance target type. + public CustomContextAttribute( Type type ) + { + Type = type; + } + } + + /// /// Inspector type drawer attribute for GUI draw methods /// handling specific types. diff --git a/Editor/AGXUnityEditor/ContextManager.cs b/Editor/AGXUnityEditor/ContextManager.cs new file mode 100644 index 00000000..e32d4b1f --- /dev/null +++ b/Editor/AGXUnityEditor/ContextManager.cs @@ -0,0 +1,72 @@ +using AGXUnityEditor.Tools; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEditor.EditorTools; +using UnityEngine; + +namespace AGXUnityEditor +{ + [InitializeOnLoad] + public static class ContextManager + { + static Dictionary m_cachedContextTypeMap = new Dictionary(); + + public static void RegisterCustomContextAssembly( string assemblyName ) + { + if ( !m_assembliesWithCustomContexts.Contains( assemblyName ) ) + m_assembliesWithCustomContexts.Add( assemblyName ); + } + + public static Type GetCustomContextForType( Type targetType ) + { + if ( m_cachedContextTypeMap.TryGetValue( targetType, out var cachedCtx ) ) + return cachedCtx; + + Type[] types = null; + try { + types = m_assembliesWithCustomContexts.SelectMany( name => Assembly.Load( name ).GetTypes() ).ToArray(); + } + catch ( Exception e ) { + Debug.LogException( e ); + Debug.LogError( "Failed loading custom context assemblies." ); + types = Assembly.Load( Manager.AGXUnityEditorAssemblyName ).GetTypes(); + } + + var customCtxTypes = new List(); + foreach ( var type in types ) { + // CustomTool attribute can only be used with tools + // inheriting from CustomTargetTool. + if ( !typeof( EditorToolContext ).IsAssignableFrom( type ) ) + continue; + + var customCtxAttribute = type.GetCustomAttribute( false ); + if ( customCtxAttribute == null ) + continue; + + // Exact match - break search. + if ( customCtxAttribute.Type == targetType ) { + customCtxTypes.Clear(); + customCtxTypes.Add( type ); + break; + } + + // Type of custom tool desired type is assignable from current + // target type. Store this if an exact match comes later. + // E.g.: CustomTool( typeof( Shape ) ) and CustomTool( typeof( Box ) ). + else if ( customCtxAttribute.Type.IsAssignableFrom( targetType ) ) + customCtxTypes.Add( type ); + } + + var customCtxType = customCtxTypes.FirstOrDefault(); + if ( customCtxType != null ) + m_cachedContextTypeMap.Add( targetType, customCtxType ); + + return customCtxType; + } + + private static List m_assembliesWithCustomContexts = new List() { Manager.AGXUnityEditorAssemblyName }; + } +} \ No newline at end of file diff --git a/Editor/AGXUnityEditor/ContextManager.cs.meta b/Editor/AGXUnityEditor/ContextManager.cs.meta new file mode 100644 index 00000000..aed633ba --- /dev/null +++ b/Editor/AGXUnityEditor/ContextManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8f20e18e24e27b49a0cc1f30c139b37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AGXUnityEditor/CustomScale.cs b/Editor/AGXUnityEditor/CustomScale.cs new file mode 100644 index 00000000..76e7e1b5 --- /dev/null +++ b/Editor/AGXUnityEditor/CustomScale.cs @@ -0,0 +1,153 @@ +using AGXUnity.Collide; +using AGXUnityEditor; +using AGXUnityEditor.Utils; +using System; +using UnityEditor; +using UnityEditor.EditorTools; +using UnityEngine; + +[CustomContext( typeof( AGXUnity.Collide.Shape ) )] +public class ScaleContext : EditorToolContext +{ + protected override Type GetEditorToolType( Tool tool ) + { + switch ( tool ) { + case Tool.Scale: + return typeof( ShapeScaleTool ); + default: + return base.GetEditorToolType( tool ); + } + } +} + +class ShapeScaleTool : EditorTool +{ + private Transform transform; + private Vector3 pos; + private Quaternion rot; + + private void HandleBox( Box box ) + { + box.HalfExtents = Handles.ScaleHandle( box.HalfExtents, pos, rot ); + } + + private void HandleCapsule( Capsule cap ) + { + Vector3 scale = new Vector3(cap.Radius, cap.Height,cap.Radius); + var newScale = Handles.ScaleHandle( scale, pos, rot ); + + cap.Height = newScale.y; + if ( newScale.x != scale.x ) + cap.Radius = newScale.x; + else if ( newScale.z != scale.z ) + cap.Radius = newScale.z; + } + + private void HandleCylinder( Cylinder cyl ) + { + Vector3 scale = new Vector3(cyl.Radius, cyl.Height,cyl.Radius); + var newScale = Handles.ScaleHandle( scale, pos, rot ); + + cyl.Height = newScale.y; + if ( newScale.x != scale.x ) + cyl.Radius = newScale.x; + else if ( newScale.z != scale.z ) + cyl.Radius = newScale.z; + } + + private void HandleSphere( Sphere sphere ) + { + Vector3 scale = new Vector3(sphere.Radius, sphere.Radius, sphere.Radius); + var newScale = Handles.ScaleHandle( scale, pos, rot ); + + if ( newScale.y != scale.y ) + sphere.Radius = newScale.y; + if ( newScale.x != scale.x ) + sphere.Radius = newScale.x; + else if ( newScale.z != scale.z ) + sphere.Radius = newScale.z; + } + + float m_initialBR = 0.0f; + float m_initialTR = 0.0f; + float m_initialHeight = 0.0f; + + static Color s_XAxisColor = new Color(73f / 85f, 62f / 255f, 29f / 255f, 0.93f); + static Color s_YAxisColor = new Color(154f / 255f, 81f / 85f, 24f / 85f, 0.93f); + static Color s_ZAxisColor = new Color(58f / 255f, 122f / 255f, 248f / 255f, 0.93f); + + private void HandleCone( Cone cone ) + { + var size = HandleUtility.GetHandleSize(pos); + + var c = Handles.color; + + var center = pos + transform.up * cone.Height * 0.5f; + + Handles.color = s_ZAxisColor; + cone.BottomRadius = Handles.ScaleSlider( cone.BottomRadius, pos, transform.forward, rot, size, 0.1f ); + Handles.color = s_XAxisColor; + cone.TopRadius = Handles.ScaleSlider( cone.TopRadius, pos + transform.up * cone.Height, transform.forward, rot, size, 0.1f ); + Handles.color = s_YAxisColor; + var newHeight = Handles.ScaleSlider( cone.Height, center, transform.up, rot, size, 0.1f ); + if(newHeight != cone.Height ) { + cone.Height = newHeight; + transform.position = center - transform.up * cone.Height * 0.5f; + } + + Handles.color = c; + + if ( Event.current.type == EventType.MouseDown ) { + m_initialBR = cone.BottomRadius; + m_initialTR = cone.TopRadius; + m_initialHeight = cone.Height; + } + + EditorGUI.BeginChangeCheck(); + var scale = Handles.ScaleValueHandle( 1.0f, center, rot, size, Handles.CubeHandleCap, 0.1f ); + if ( EditorGUI.EndChangeCheck() ) { + cone.BottomRadius = m_initialBR * scale; + cone.TopRadius = m_initialTR * scale; + cone.Height = m_initialHeight * scale; + transform.position = center - transform.up * cone.Height * 0.5f; + } + } + + private void HandleHollowCylinder( HollowCylinder cyl ) + { + Vector3 scale = new Vector3(cyl.Radius, cyl.Height,cyl.Radius); + var newScale = Handles.ScaleHandle( scale, pos, rot ); + + cyl.Height = newScale.y; + if ( newScale.x != scale.x ) + cyl.Radius = newScale.x; + else if ( newScale.z != scale.z ) + cyl.Radius = newScale.z; + } + + public override void OnToolGUI( EditorWindow _ ) + { + if ( target == null ) + return; + + var GO = target as GameObject; + + transform = GO.transform; + pos = GO.transform.position; + rot = GO.transform.rotation; + + var shape = GO.GetComponent(); + Undo.RecordObject( shape, "Resize collider" ); + Undo.RecordObject( transform, "transform"); + + switch ( shape ) { + case Box box: HandleBox( box ); break; + case Capsule cap: HandleCapsule( cap ); break; + case Cylinder cyl: HandleCylinder( cyl ); break; + case Sphere sphere: HandleSphere( sphere ); break; + case Cone cone: HandleCone( cone ); break; + case HollowCylinder holCyl: HandleHollowCylinder( holCyl ); break; + } + + } +} \ No newline at end of file diff --git a/Editor/AGXUnityEditor/CustomScale.cs.meta b/Editor/AGXUnityEditor/CustomScale.cs.meta new file mode 100644 index 00000000..730a7346 --- /dev/null +++ b/Editor/AGXUnityEditor/CustomScale.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a615faac00fa54489de88f54173f7c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AGXUnityEditor/InspectorEditor.cs b/Editor/AGXUnityEditor/InspectorEditor.cs index def1c132..e486f4ec 100644 --- a/Editor/AGXUnityEditor/InspectorEditor.cs +++ b/Editor/AGXUnityEditor/InspectorEditor.cs @@ -184,6 +184,10 @@ private void OnEnable() } ToolManager.OnTargetEditorEnable( this.targets, this ); + + var ctx = ContextManager.GetCustomContextForType(m_targetType); + if ( ctx != null ) + UnityEditor.EditorTools.ToolManager.SetActiveContext( ctx ); } private void OnDisable() @@ -200,6 +204,8 @@ private void OnDisable() m_targetType = null; m_targetGameObjects = null; m_numTargetGameObjectsTargetComponents = 0; + + UnityEditor.EditorTools.ToolManager.SetActiveContext( null ); } private bool IsTargetMostProbablyDeleted() From 374c40d41403b28a7a800bf28913c9b2b75dd56e Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Mon, 11 Nov 2024 16:52:31 +0100 Subject: [PATCH 2/3] Added HollowCone and explicitly specify supported shapes --- Editor/AGXUnityEditor/Attributes.cs | 2 +- Editor/AGXUnityEditor/ContextManager.cs | 9 ++-- Editor/AGXUnityEditor/CustomScale.cs | 68 +++++++++++++++++++++---- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Editor/AGXUnityEditor/Attributes.cs b/Editor/AGXUnityEditor/Attributes.cs index bbf1b327..3eafaf83 100644 --- a/Editor/AGXUnityEditor/Attributes.cs +++ b/Editor/AGXUnityEditor/Attributes.cs @@ -29,7 +29,7 @@ public CustomToolAttribute( Type type ) } } - [AttributeUsage( AttributeTargets.Class, AllowMultiple = false )] + [AttributeUsage( AttributeTargets.Class, AllowMultiple = true )] public class CustomContextAttribute : Attribute { /// diff --git a/Editor/AGXUnityEditor/ContextManager.cs b/Editor/AGXUnityEditor/ContextManager.cs index e32d4b1f..c86f84de 100644 --- a/Editor/AGXUnityEditor/ContextManager.cs +++ b/Editor/AGXUnityEditor/ContextManager.cs @@ -1,4 +1,3 @@ -using AGXUnityEditor.Tools; using System; using System.Collections.Generic; using System.Linq; @@ -42,12 +41,12 @@ public static Type GetCustomContextForType( Type targetType ) if ( !typeof( EditorToolContext ).IsAssignableFrom( type ) ) continue; - var customCtxAttribute = type.GetCustomAttribute( false ); + var customCtxAttribute = type.GetCustomAttributes( false ); if ( customCtxAttribute == null ) continue; // Exact match - break search. - if ( customCtxAttribute.Type == targetType ) { + if ( customCtxAttribute.Any( attr => attr.Type == targetType ) ) { customCtxTypes.Clear(); customCtxTypes.Add( type ); break; @@ -56,7 +55,7 @@ public static Type GetCustomContextForType( Type targetType ) // Type of custom tool desired type is assignable from current // target type. Store this if an exact match comes later. // E.g.: CustomTool( typeof( Shape ) ) and CustomTool( typeof( Box ) ). - else if ( customCtxAttribute.Type.IsAssignableFrom( targetType ) ) + else if ( customCtxAttribute.Any( attr => attr.Type.IsAssignableFrom( targetType ) ) ) customCtxTypes.Add( type ); } @@ -69,4 +68,4 @@ public static Type GetCustomContextForType( Type targetType ) private static List m_assembliesWithCustomContexts = new List() { Manager.AGXUnityEditorAssemblyName }; } -} \ No newline at end of file +} diff --git a/Editor/AGXUnityEditor/CustomScale.cs b/Editor/AGXUnityEditor/CustomScale.cs index 76e7e1b5..1c888a2d 100644 --- a/Editor/AGXUnityEditor/CustomScale.cs +++ b/Editor/AGXUnityEditor/CustomScale.cs @@ -1,12 +1,17 @@ using AGXUnity.Collide; using AGXUnityEditor; -using AGXUnityEditor.Utils; using System; using UnityEditor; using UnityEditor.EditorTools; using UnityEngine; -[CustomContext( typeof( AGXUnity.Collide.Shape ) )] +[CustomContext( typeof( AGXUnity.Collide.Box ) )] +[CustomContext( typeof( AGXUnity.Collide.Capsule ) )] +[CustomContext( typeof( AGXUnity.Collide.Cylinder ) )] +[CustomContext( typeof( AGXUnity.Collide.Sphere ) )] +[CustomContext( typeof( AGXUnity.Collide.Cone ) )] +[CustomContext( typeof( AGXUnity.Collide.HollowCylinder ) )] +[CustomContext( typeof( AGXUnity.Collide.HollowCone ) )] public class ScaleContext : EditorToolContext { protected override Type GetEditorToolType( Tool tool ) @@ -90,7 +95,7 @@ private void HandleCone( Cone cone ) cone.TopRadius = Handles.ScaleSlider( cone.TopRadius, pos + transform.up * cone.Height, transform.forward, rot, size, 0.1f ); Handles.color = s_YAxisColor; var newHeight = Handles.ScaleSlider( cone.Height, center, transform.up, rot, size, 0.1f ); - if(newHeight != cone.Height ) { + if ( newHeight != cone.Height ) { cone.Height = newHeight; transform.position = center - transform.up * cone.Height * 0.5f; } @@ -125,6 +130,43 @@ private void HandleHollowCylinder( HollowCylinder cyl ) cyl.Radius = newScale.z; } + private void HandleHollowCone( HollowCone cone ) + { + var size = HandleUtility.GetHandleSize(pos); + + var c = Handles.color; + + var center = pos + transform.up * cone.Height * 0.5f; + + Handles.color = s_ZAxisColor; + cone.BottomRadius = Handles.ScaleSlider( cone.BottomRadius, pos, transform.forward, rot, size, 0.1f ); + Handles.color = s_XAxisColor; + cone.TopRadius = Handles.ScaleSlider( cone.TopRadius, pos + transform.up * cone.Height, transform.forward, rot, size, 0.1f ); + Handles.color = s_YAxisColor; + var newHeight = Handles.ScaleSlider( cone.Height, center, transform.up, rot, size, 0.1f ); + if ( newHeight != cone.Height ) { + cone.Height = newHeight; + transform.position = center - transform.up * cone.Height * 0.5f; + } + + Handles.color = c; + + if ( Event.current.type == EventType.MouseDown ) { + m_initialBR = cone.BottomRadius; + m_initialTR = cone.TopRadius; + m_initialHeight = cone.Height; + } + + EditorGUI.BeginChangeCheck(); + var scale = Handles.ScaleValueHandle( 1.0f, center, rot, size, Handles.CubeHandleCap, 0.1f ); + if ( EditorGUI.EndChangeCheck() ) { + cone.BottomRadius = m_initialBR * scale; + cone.TopRadius = m_initialTR * scale; + cone.Height = m_initialHeight * scale; + transform.position = center - transform.up * cone.Height * 0.5f; + } + } + public override void OnToolGUI( EditorWindow _ ) { if ( target == null ) @@ -137,17 +179,21 @@ public override void OnToolGUI( EditorWindow _ ) rot = GO.transform.rotation; var shape = GO.GetComponent(); + if ( shape == null ) + return; + Undo.RecordObject( shape, "Resize collider" ); - Undo.RecordObject( transform, "transform"); + Undo.RecordObject( transform, "transform" ); switch ( shape ) { - case Box box: HandleBox( box ); break; - case Capsule cap: HandleCapsule( cap ); break; - case Cylinder cyl: HandleCylinder( cyl ); break; - case Sphere sphere: HandleSphere( sphere ); break; - case Cone cone: HandleCone( cone ); break; + case Box box: HandleBox( box ); break; + case Capsule cap: HandleCapsule( cap ); break; + case Cylinder cyl: HandleCylinder( cyl ); break; + case Sphere sphere: HandleSphere( sphere ); break; + case Cone cone: HandleCone( cone ); break; case HollowCylinder holCyl: HandleHollowCylinder( holCyl ); break; + case HollowCone holCone: HandleHollowCone( holCone ); break; } - + } -} \ No newline at end of file +} From b171ee06d2fd285b1902c446f7f2502bbee0eb4f Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Tue, 12 Nov 2024 08:12:12 +0100 Subject: [PATCH 3/3] Fixed formatting --- Editor/AGXUnityEditor/InspectorEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/AGXUnityEditor/InspectorEditor.cs b/Editor/AGXUnityEditor/InspectorEditor.cs index b3aabd6d..a44b0712 100644 --- a/Editor/AGXUnityEditor/InspectorEditor.cs +++ b/Editor/AGXUnityEditor/InspectorEditor.cs @@ -252,7 +252,7 @@ private void OnDisable() m_targetType = null; m_targetGameObjects = null; m_numTargetGameObjectsTargetComponents = 0; - + UnityEditor.EditorTools.ToolManager.SetActiveContext( null ); }