Skip to content

Commit

Permalink
Merge pull request #170 from Algoryx/feature/shape-scale-tool
Browse files Browse the repository at this point in the history
Native shape scale tool
  • Loading branch information
FilipAlg authored Nov 15, 2024
2 parents 55b8356 + b171ee0 commit e4ecb89
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 0 deletions.
19 changes: 19 additions & 0 deletions Editor/AGXUnityEditor/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ public CustomToolAttribute( Type type )
}
}

[AttributeUsage( AttributeTargets.Class, AllowMultiple = true )]
public class CustomContextAttribute : Attribute
{
/// <summary>
/// Instance type of the context is implemented for.
/// </summary>
public Type Type = null;

/// <summary>
/// Construct given instance type the context is implemented for.
/// </summary>
/// <param name="type">Instance target type.</param>
public CustomContextAttribute( Type type )
{
Type = type;
}
}


/// <summary>
/// Inspector type drawer attribute for GUI draw methods
/// handling specific types.
Expand Down
71 changes: 71 additions & 0 deletions Editor/AGXUnityEditor/ContextManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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<Type,Type> m_cachedContextTypeMap = new Dictionary<Type,Type>();

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<Type>();
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.GetCustomAttributes<CustomContextAttribute>( false );
if ( customCtxAttribute == null )
continue;

// Exact match - break search.
if ( customCtxAttribute.Any( attr => attr.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.Any( attr => attr.Type.IsAssignableFrom( targetType ) ) )
customCtxTypes.Add( type );
}

var customCtxType = customCtxTypes.FirstOrDefault();
if ( customCtxType != null )
m_cachedContextTypeMap.Add( targetType, customCtxType );

return customCtxType;
}

private static List<string> m_assembliesWithCustomContexts = new List<string>() { Manager.AGXUnityEditorAssemblyName };
}
}
11 changes: 11 additions & 0 deletions Editor/AGXUnityEditor/ContextManager.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

199 changes: 199 additions & 0 deletions Editor/AGXUnityEditor/CustomScale.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using AGXUnity.Collide;
using AGXUnityEditor;
using System;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEngine;

[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 )
{
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;
}

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 )
return;

var GO = target as GameObject;

transform = GO.transform;
pos = GO.transform.position;
rot = GO.transform.rotation;

var shape = GO.GetComponent<Shape>();
if ( shape == null )
return;

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;
case HollowCone holCone: HandleHollowCone( holCone ); break;
}

}
}
11 changes: 11 additions & 0 deletions Editor/AGXUnityEditor/CustomScale.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Editor/AGXUnityEditor/InspectorEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,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()
Expand All @@ -248,6 +252,8 @@ private void OnDisable()
m_targetType = null;
m_targetGameObjects = null;
m_numTargetGameObjectsTargetComponents = 0;

UnityEditor.EditorTools.ToolManager.SetActiveContext( null );
}

private bool IsTargetMostProbablyDeleted()
Expand Down

0 comments on commit e4ecb89

Please sign in to comment.