From 9ad233a560393e9a9ad570d07fc2f4016707feb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 9 Apr 2024 16:59:07 +0200 Subject: [PATCH 01/12] Add WIP Conversion tool, so far for Colliders and Rigidbodies --- Editor/AGXUnityEditor/Menus/TopMenu.cs | 6 + .../Windows/ConvertPhysXToAGXWindow.cs | 584 ++++++++++++++++++ .../Windows/ConvertPhysXToAGXWindow.cs.meta | 11 + 3 files changed, 601 insertions(+) create mode 100644 Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs create mode 100644 Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs.meta diff --git a/Editor/AGXUnityEditor/Menus/TopMenu.cs b/Editor/AGXUnityEditor/Menus/TopMenu.cs index e52111a7..eb6392cd 100644 --- a/Editor/AGXUnityEditor/Menus/TopMenu.cs +++ b/Editor/AGXUnityEditor/Menus/TopMenu.cs @@ -552,6 +552,12 @@ public static void ConvertRenderingMaterials() Windows.ConvertMaterialsWindow.Open(); } + [MenuItem( "AGXUnity/Utils/Convert PhysX components to AGX", priority = 80 )] + public static void ConvertPhysXToAGX() + { + Windows.ConvertPhysXToAGXWindow.Open(); + } + [MenuItem( "AGXUnity/Settings...", priority = 81 )] public static void OpenSettings() { diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs new file mode 100644 index 00000000..157932e4 --- /dev/null +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -0,0 +1,584 @@ +using AGXUnity.Utils; +using AGXUnityEditor.Utils; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; +using AGXUnityEditor.UIElements; +using System; +using agxPowerLine; +using AGXUnity.Model; +using UnityEditor.VersionControl; +using agx; +using System.Collections; + +namespace AGXUnityEditor.Windows +{ + public class ConvertPhysXToAGXWindow : EditorWindow + { + private enum PhysXType + { + Collider, + RigidBody, + Constraint + } + + private enum SortType + { + Name, + Type + } + + + // Uses ScriptableObject base class to support undo/redo + private class AssetData : ScriptableObject + { + public bool Updatable; + public bool Convert; + public GameObject gameObject; + //public string Path; + public Type type; + public List dependentAssets; + public bool rootAsset; // Used by nested RBs + public PhysXType physXType; + } + + public static ConvertPhysXToAGXWindow Open() + { + // Get existing open window or if none, make a new one: + var window = GetWindow( false, + "Convert Components from PhysX to AGX", + true ); + return window; + } + + private Texture2D[] m_statusIcons; + + private SortType m_sortType = SortType.Name; + private int m_selectedIndex = -1; + private int m_hoverIndex = -1; + + private List m_tableRows = new List(); + private MixedToggle m_toggleAll; + + private Type[] m_basicColliderTypes = new Type[]{ + typeof(BoxCollider), + typeof(SphereCollider), + typeof(CapsuleCollider), + typeof(MeshCollider), + typeof(WheelCollider)}; + + private Type[] m_constraintTypes = new Type[]{ + typeof(HingeJoint), + typeof(FixedJoint), + typeof(ConfigurableJoint), + typeof(CharacterJoint), + typeof(SpringJoint)}; + + private List m_physXAssets; + private void GatherSceneObjects() + { + //var assets = AssetDatabase.FindAssets( "t:Material", null ); + m_physXAssets = new List(); + foreach (var type in m_basicColliderTypes) + m_physXAssets.AddRange(FindColliderAssetData(type, PhysXType.Collider)); + + var terrains = FindColliderAssetData(typeof(TerrainCollider), PhysXType.Collider); + for (int i = terrains.Count; --i >= 0;) + { + if (terrains[i].gameObject.GetComponent() != null) + terrains.RemoveAt(i); + } + m_physXAssets.AddRange(terrains); + + m_physXAssets.AddRange(FindRigidbodyData()); + + //m_physXObjects.AddRange(FindAssetData(typeof(Constraint))); + + SortAssets(); + } + + private void SortAssets() + { + switch (m_sortType) + { + case SortType.Name: + m_physXAssets.Sort( ( n1, n2 ) => n1.gameObject.name.CompareTo(n2.gameObject.name )); + break; + + case SortType.Type: + // This is default + break; + } + } + + private class RBList + { + public Rigidbody rigidbody; + public List rbChildren; + public List ownChildren; + public bool root; + } + + // Get RBs. We need to keep track of: + // - Which RBs have children but are not themselves children of RBs + // - Which colliders belong to this RB (and not to a child RB) + private List FindRigidbodyData() + { + var components = FindObjectsOfType(typeof(Rigidbody), true); + + var allRBs = components.Select( rb => + { + var entry = new RBList(); + entry.rigidbody = (Rigidbody)rb; + entry.root = false; + var go = ((UnityEngine.Component)rb).gameObject; + entry.rbChildren = go.GetComponentsInChildren().ToList(); + entry.rbChildren.Remove(entry.rigidbody); + + entry.ownChildren = go.GetComponentsInChildren().ToList(); + foreach (var childRB in entry.rbChildren) + { + var grandChildren = childRB.gameObject.GetComponentsInChildren(); + foreach (var grandChild in grandChildren) + entry.ownChildren.Remove(grandChild); + entry.ownChildren.Remove(childRB.gameObject.transform); + } + + return entry; + }).ToList(); + + // Find root RBs + var rbsThatAreRBChildren = new List(); + foreach (var rb in allRBs) + { + if (rb.rbChildren.Count > 0) + { + rb.root = true; + rbsThatAreRBChildren.AddRange(rb.rbChildren); + } + } + foreach (var rb in allRBs) + { + if (rbsThatAreRBChildren.Contains(rb.rigidbody)) + rb.root = false; + } + + return allRBs.Select( rb => + { + var data = CreateInstance(); + data.Convert = false; + data.type = typeof(Rigidbody); + data.physXType = PhysXType.RigidBody; + data.gameObject = rb.rigidbody.gameObject; + data.Updatable = true; + data.rootAsset = rb.root; + + // AssetData for colliders in RB + data.dependentAssets = FindColliderAssetDataInGameObjects(rb.ownChildren); + + return data; + } ).ToList(); + } + + private List FindColliderAssetDataInGameObjects(List transforms) + { + var physXAssets = new List(); + foreach (var type in m_basicColliderTypes) + { + List colliders = new List(); + foreach (var t in transforms) + { + var components = t.GetComponents(type); + foreach (var c in components) + colliders.Add(c.gameObject); + } + physXAssets.AddRange( + colliders.Select( a => + { + var data = CreateInstance(); + data.Convert = false; + data.type = type; + data.physXType = PhysXType.Collider; + data.gameObject = a; + data.Updatable = type != typeof(WheelCollider); + + return data; + } ).ToList()); + } + + // Debug.Log("Number of assetDatas: " + physXAssets.Count); + + return physXAssets; + } + + private List FindColliderAssetData(Type type, PhysXType physXType) + { + var components = FindObjectsOfType(type, true); + + return components.Where( o => ((Collider)o).attachedRigidbody == null ) + .Select( o => + { + var data = CreateInstance(); + data.Convert = false; + data.type = type; + data.physXType = physXType; + data.gameObject = ((UnityEngine.Component)o).gameObject; + data.Updatable = type != typeof(WheelCollider); + + return data; + } ).ToList(); + } + + // TODO: Replace hover/select coloring with proper USS styling + private void UpdateColors() + { + for ( int i = 0; i < m_tableRows.Count(); i++ ) { + Color bgc = InspectorGUI.BackgroundColor; + + if ( i == m_selectedIndex ) + bgc = Color.Lerp( bgc, Color.blue, 0.1f ); + + if ( i == m_hoverIndex ) + bgc = Color.Lerp( bgc, Color.white, 0.1f ); + + if ( m_hoverIndex != i && m_selectedIndex != i ) + bgc = i % 2 == 0 ? ( bgc * 0.8f ) : bgc; + + m_tableRows[ i ].style.backgroundColor = bgc; + } + } + + private VisualElement TableRowUI( AssetData asset ) + { + + var row = new VisualElement(); + row.SetPadding(3,3,3,0); + var ve = new VisualElement(); + + ve.SetEnabled( asset.Updatable ); + var index = m_tableRows.Count(); + row.RegisterCallback( mde => + { + EditorUtility.FocusProjectWindow(); + Selection.activeObject = asset.gameObject; + m_selectedIndex = index; + + UpdateColors(); + } ); + row.RegisterCallback( mde => + { + m_hoverIndex = index; + UpdateColors(); + } ); + row.RegisterCallback( mde => + { + m_hoverIndex = -1; + UpdateColors(); + } ); + ve.style.flexDirection = FlexDirection.Row; + + var activeToggle = new Toggle() { value = asset.Convert }; + activeToggle.RegisterValueChangedCallback( ce => asset.Convert = ce.newValue ); + activeToggle.style.width = 20; + activeToggle.SetMargin( 0, 0, 0, StyleKeyword.Null ); + if ( asset.Updatable) + m_toggleAll.AddControlledToggle( activeToggle ); + + var flex = new VisualElement(); + flex.style.flexDirection = FlexDirection.Row; + flex.style.justifyContent = Justify.SpaceBetween; + flex.style.flexGrow = 1; + + var nameLabel = new Label( asset.gameObject.name ); + nameLabel.style.flexGrow = 0.3f; + nameLabel.style.flexBasis = 0; + nameLabel.style.overflow = Overflow.Hidden; + nameLabel.style.unityTextAlign = TextAnchor.MiddleLeft; + nameLabel.style.marginRight = 2; + + // TODO this could probably be faster if we saved the image somewhere but then we would have to do a nice lookup + var TypeIcon = new Image( ) { image = EditorGUIUtility.ObjectContent(null, asset.type).image }; + TypeIcon.style.height = 20; + TypeIcon.style.width = 20; + + Label componentLabel = null; + if (asset.dependentAssets == null) + componentLabel = new Label( asset.type.ToString() ); + else + componentLabel = new Label( asset.type.ToString() + " - Colliders: " + asset.dependentAssets.Count); + componentLabel.style.flexGrow = 0.6f; + componentLabel.style.flexBasis = 0; + componentLabel.style.overflow = Overflow.Hidden; + nameLabel.style.unityTextAlign = TextAnchor.MiddleLeft; + componentLabel.style.marginRight = 2; + + ve.Add( activeToggle ); + flex.Add( nameLabel ); + flex.Add( TypeIcon ); + flex.Add( componentLabel ); + ve.Add( flex ); + row.Add( ve ); + + m_tableRows.Add( row ); + + return row; + } + + private void OnEnable() + { + GatherSceneObjects(); + + Undo.undoRedoPerformed += () => Repopulate(false); + } + + private void CreateGUI() + { + rootVisualElement.SetPadding( 19, 15, 15, 15 ); + + var RPLabel = new Label( $"This utility attempts to convert PhysX Components to corresponding AGX Components." ); + RPLabel.style.marginBottom = 15; + rootVisualElement.Add( RPLabel ); + + // TODO starting here with just colliders, moving on from there to RBs. Think about how the different lists will look + var assetConverter = new VisualElement(); + assetConverter.style.height = 600; + assetConverter.SetBorder( 2, Color.Lerp( InspectorGUI.BackgroundColor, Color.black, 0.2f ) ); + assetConverter.SetBorderRadius( 5 ); + assetConverter.SetPadding( 5 ); + + var description = new Label( "PhysXObjects in Scene. GameObjects with multiple matching Components can appear on multiple lines." ); + description.style.whiteSpace = WhiteSpace.Normal; + description.style.marginBottom = 10; + assetConverter.Add( description ); + +// var sortMenu = EditorGUILayout.EnumFlagsField( AGXUnity.Utils.GUI.MakeLabel( "Properties" ), +// m_sortType, +// InspectorEditor.Skin.Popup ); +// var sortMenu = new EnumField("Test", m_sortType); +// sortMenu. +// //sortMenu.style.marginBottom = 15; +// rootVisualElement.Add( sortMenu ); + + var header = new VisualElement(); + header.style.flexDirection = FlexDirection.Row; + header.style.justifyContent = Justify.SpaceBetween; + header.style.flexShrink = 0; + m_toggleAll = new MixedToggle(); + header.Add( m_toggleAll ); + + var numAssets = new VisualElement(); + numAssets.style.flexDirection = FlexDirection.Row; + numAssets.style.alignItems = Align.Center; + numAssets.style.unityTextAlign = TextAnchor.MiddleLeft; + + numAssets.Add(new Label() { text = "Upgradable assets: " }); + + var image = new Image() { image = m_statusIcons[ 0 ] }; + image.style.width = 20; + image.style.height = 20; + numAssets.Add( image ); + + var lab = new Label() { text = $" {(m_physXAssets != null ? m_physXAssets.Where( a => a.Updatable).Count() : 0)}" }; + lab.style.width = 15; + numAssets.Add( lab ); + header.Add( numAssets ); + + var scroll = new ScrollView(); + + foreach ( var asset in m_physXAssets ) + scroll.Add( TableRowUI( asset ) ); + + UpdateColors(); + + assetConverter.Add( header ); + assetConverter.Add( scroll ); + + var footer = new VisualElement(); + footer.style.flexDirection = FlexDirection.Row; + footer.style.justifyContent = Justify.SpaceBetween; + footer.style.marginTop = 10; + footer.style.flexShrink = 0; + + var convertButton = new Button() { text = "Convert Selected" }; + convertButton.clicked += ConvertSelected; + + var refreshButton = new Button(); + refreshButton.style.width = 40; + refreshButton.style.marginLeft = 0; + refreshButton.clicked += () => Repopulate( ); + + var refreshIcon = new Image { image = IconManager.GetIcon( MiscIcon.Update ) }; + refreshIcon.style.flexBasis = 0; + refreshIcon.style.flexGrow = 1; + refreshButton.Add( refreshIcon ); + + footer.Add( refreshButton ); + footer.Add( convertButton ); + assetConverter.Add( footer ); + + rootVisualElement.Add( assetConverter ); + } + + private void Repopulate(bool gatherAssets = true) + { + GatherSceneObjects(); + m_tableRows.Clear(); + + rootVisualElement.Clear(); + CreateGUI(); + } + + private void ConvertSelected() + { + using ( new UndoCollapseBlock("Convert Selected PhysX Assets") ) { + foreach(var asset in m_physXAssets ) { // .Where(m => m.Status == MaterialStatus.Updatable ) + if ( !asset.Convert ) + continue; + + if (asset.physXType == PhysXType.Collider) + ConvertCollider(asset); + + if (asset.physXType == PhysXType.RigidBody) + { + ConvertRigidbody(asset); + foreach (var dependentCollider in asset.dependentAssets) + ConvertCollider(dependentCollider); + } + +// EditorUtility.SetDirty( asset ); +// EditorUtility.SetDirty( asset.gameObject.GetComponent(asset.type) ); + } + } + + AssetDatabase.SaveAssets(); + Repopulate(false); + } + + + private void ConvertRigidbody(AssetData asset) + { + var physXRb = asset.gameObject.GetComponent(); + var agxRB = Undo.AddComponent(asset.gameObject); + + agxRB.MassProperties.Mass.UseDefault = false; + agxRB.MassProperties.Mass.Value = physXRb.mass; + + agxRB.LinearVelocityDamping = new Vector3(physXRb.drag, physXRb.drag, physXRb.drag); + agxRB.AngularVelocityDamping = new Vector3(physXRb.angularDrag, physXRb.angularDrag, physXRb.angularDrag); + + if (!physXRb.automaticCenterOfMass) + { + agxRB.MassProperties.CenterOfMassOffset.UseDefault = false; + agxRB.MassProperties.CenterOfMassOffset.Value = physXRb.centerOfMass; + } + + if (!physXRb.automaticInertiaTensor) + { + agxRB.MassProperties.InertiaDiagonal.UseDefault = false; + agxRB.MassProperties.InertiaDiagonal.Value = physXRb.centerOfMass; + } + + if (physXRb.isKinematic) + agxRB.MotionControl = agx.RigidBody.MotionControl.KINEMATICS; + + if (asset.rootAsset) + asset.gameObject.AddComponent(); + + Undo.DestroyObjectImmediate(physXRb); + } + + private void ConvertCollider(AssetData asset) + { + // We need to create a child gameObject with the AGX collider to account for the "Center" property + var newObject = new GameObject(); + + Undo.RegisterCreatedObjectUndo(newObject, newObject.name); + newObject.transform.parent = asset.gameObject.transform; + + newObject.transform.localRotation = Quaternion.identity; + newObject.transform.localScale = Vector3.one; // AGX colliders ignore transform scale, except MeshColliders + + newObject.isStatic = asset.gameObject.isStatic; + + var localScale = asset.gameObject.transform.localScale; + + switch (asset.type.ToString()) // Can't switch on type, workaround with strings + { + case "UnityEngine.SphereCollider": + var physXSphere = asset.gameObject.GetComponent(); + var agxSphere = Undo.AddComponent(newObject); + + newObject.name = "AGXUnity.Collide.Sphere"; + newObject.transform.localPosition = physXSphere.center; + + // SphereCollider radius is old radius times largest of absolute value of localScale axes + agxSphere.Radius = physXSphere.radius * Mathf.Max(new float[]{ + Mathf.Abs(localScale.x), + Mathf.Abs(localScale.y), + Mathf.Abs(localScale.z)}); + + agxSphere.IsSensor = physXSphere.isTrigger; + + Undo.DestroyObjectImmediate(physXSphere); + break; + + case "UnityEngine.BoxCollider": + var physXBox = asset.gameObject.GetComponent(); + var agxBox = Undo.AddComponent(newObject); + + newObject.name = "AGXUnity.Collide.Box"; + newObject.transform.localPosition = physXBox.center; + + agxBox.HalfExtents = new Vector3(physXBox.size.x / 2f * Mathf.Abs(localScale.x), + physXBox.size.y / 2f * Mathf.Abs(localScale.y), + physXBox.size.z / 2f * Mathf.Abs(localScale.z)); + + agxBox.IsSensor = physXBox.isTrigger; + + Undo.DestroyObjectImmediate(physXBox); + break; + + case "UnityEngine.CapsuleCollider": + var physXCapsule = asset.gameObject.GetComponent(); + var agxCapsule = Undo.AddComponent(newObject); + + newObject.name = "AGXUnity.Collide.Capsule"; + newObject.transform.localPosition = physXCapsule.center; + + // Capsule radius is old radius times abs largest xz-localScale axis. PhysX capsule height is including caps, agx is without + newObject.transform.localScale = Vector3.one; + var radius = physXCapsule.radius * Mathf.Max(new float[]{ + Mathf.Abs(localScale.x), + Mathf.Abs(localScale.z)}); + agxCapsule.Radius = radius; + agxCapsule.Height = (physXCapsule.height - radius * 2) * Mathf.Abs(localScale.y); + + agxCapsule.IsSensor = physXCapsule.isTrigger; + + Undo.DestroyObjectImmediate(physXCapsule); + break; + + case "UnityEngine.MeshCollider": + var physXMesh = asset.gameObject.GetComponent(); + var agxMesh = Undo.AddComponent(newObject); + + newObject.name = "AGXUnity.Collide.Mesh"; + newObject.transform.localPosition = Vector3.zero; + + agxMesh.AddSourceObject(physXMesh.sharedMesh); + + agxMesh.IsSensor = physXMesh.isTrigger; + + Undo.DestroyObjectImmediate(physXMesh); + break; + + case "UnityEngine.TerrainCollider": + Undo.DestroyObjectImmediate(newObject); + Undo.AddComponent(asset.gameObject); + break; + } + } + } +} diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs.meta b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs.meta new file mode 100644 index 00000000..bbdd165a --- /dev/null +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a2753588708d2948be3d7e1ed724bf5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 8a79cd48d04bbfad4c718d01b7651ca27fa168ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 9 Apr 2024 17:02:13 +0200 Subject: [PATCH 02/12] Create AGX objects in middle of scene view camera instead of at origin --- Editor/AGXUnityEditor/Menus/TopMenu.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Editor/AGXUnityEditor/Menus/TopMenu.cs b/Editor/AGXUnityEditor/Menus/TopMenu.cs index eb6392cd..049a2dec 100644 --- a/Editor/AGXUnityEditor/Menus/TopMenu.cs +++ b/Editor/AGXUnityEditor/Menus/TopMenu.cs @@ -3,6 +3,7 @@ using AGXUnity.Model; using AGXUnity.Utils; using UnityEditor; +using UnityEditor.SceneManagement; using UnityEngine; using Mesh = AGXUnity.Collide.Mesh; using Plane = AGXUnity.Collide.Plane; @@ -26,9 +27,16 @@ private static GameObject CreateShape( MenuCommand command ) return null; var parent = command.context as GameObject; + var views = SceneView.sceneViews; if ( parent != null ) go.transform.SetParent( parent.transform, false ); - + else if (SceneView.sceneViews.Count > 0) + { + var view = SceneView.sceneViews[0] as SceneView; + if (view != null) + view.MoveToView(go.transform); + } + AGXUnity.Rendering.ShapeVisual.Create( go.GetComponent() ); Undo.RegisterCreatedObjectUndo( go, "shape" ); From e9d4692c0c84ea31db8e5cbc572c25e6d3cd5343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Wed, 17 Apr 2024 13:08:54 +0200 Subject: [PATCH 03/12] Fix missing icon bug --- Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index 157932e4..479b6bb7 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -330,6 +330,11 @@ private void OnEnable() { GatherSceneObjects(); + m_statusIcons = new Texture2D[ 3 ]; + m_statusIcons[ 0 ] = IconManager.GetIcon( "convertible_material" ); + m_statusIcons[ 1 ] = IconManager.GetIcon( "compatible_material" ); + m_statusIcons[ 2 ] = IconManager.GetIcon( "pass" ); + Undo.undoRedoPerformed += () => Repopulate(false); } From ab63ad74088d50e0ead51a9f3c746c3c6fea175e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Wed, 17 Apr 2024 13:09:59 +0200 Subject: [PATCH 04/12] Remove DeformableTerrain until tool considers Pager --- Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index 479b6bb7..738d7a26 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -84,6 +84,8 @@ private void GatherSceneObjects() foreach (var type in m_basicColliderTypes) m_physXAssets.AddRange(FindColliderAssetData(type, PhysXType.Collider)); + // TODO Need to take care of DeformableTerrainPager before this works as intended + /* var terrains = FindColliderAssetData(typeof(TerrainCollider), PhysXType.Collider); for (int i = terrains.Count; --i >= 0;) { @@ -91,6 +93,7 @@ private void GatherSceneObjects() terrains.RemoveAt(i); } m_physXAssets.AddRange(terrains); + */ m_physXAssets.AddRange(FindRigidbodyData()); @@ -334,7 +337,7 @@ private void OnEnable() m_statusIcons[ 0 ] = IconManager.GetIcon( "convertible_material" ); m_statusIcons[ 1 ] = IconManager.GetIcon( "compatible_material" ); m_statusIcons[ 2 ] = IconManager.GetIcon( "pass" ); - + Undo.undoRedoPerformed += () => Repopulate(false); } From f736be66b6e3f217177f36c6a0c2e17bf5a9d00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Mon, 2 Dec 2024 11:04:41 +0100 Subject: [PATCH 05/12] Compatibility updates --- .../Windows/ConvertPhysXToAGXWindow.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index 738d7a26..b704d54f 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -129,7 +129,12 @@ private class RBList // - Which colliders belong to this RB (and not to a child RB) private List FindRigidbodyData() { - var components = FindObjectsOfType(typeof(Rigidbody), true); +#if UNITY_2023_1_OR_NEWER + var components = FindObjectsByType(typeof(Rigidbody), FindObjectsInactive.Include, FindObjectsSortMode.None); +#else + var components = FindObjectsOfType(typeof(RigidBody), true); +#endif + var allRBs = components.Select( rb => { @@ -218,7 +223,11 @@ private List FindColliderAssetDataInGameObjects(List trans private List FindColliderAssetData(Type type, PhysXType physXType) { +#if UNITY_2023_1_OR_NEWER + var components = FindObjectsByType(type, FindObjectsInactive.Include, FindObjectsSortMode.None); +#else var components = FindObjectsOfType(type, true); +#endif return components.Where( o => ((Collider)o).attachedRigidbody == null ) .Select( o => @@ -473,8 +482,15 @@ private void ConvertRigidbody(AssetData asset) agxRB.MassProperties.Mass.UseDefault = false; agxRB.MassProperties.Mass.Value = physXRb.mass; - agxRB.LinearVelocityDamping = new Vector3(physXRb.drag, physXRb.drag, physXRb.drag); - agxRB.AngularVelocityDamping = new Vector3(physXRb.angularDrag, physXRb.angularDrag, physXRb.angularDrag); +#if UNITY_6000_0_OR_NEWER + var linearVelocityDamping = physXRb.linearDamping; + var angularVelocityDamping = physXRb.angularDamping; +#else + var linearVelocityDamping = physXRb.drag; + var angularVelocityDamping = physXRb.angularDrag; +#endif + agxRB.LinearVelocityDamping = new Vector3(linearVelocityDamping, linearVelocityDamping, linearVelocityDamping); + agxRB.AngularVelocityDamping = new Vector3(angularVelocityDamping, angularVelocityDamping, angularVelocityDamping); if (!physXRb.automaticCenterOfMass) { From b1c32e8ad582b05507386305eefe52dda11f8302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Mon, 2 Dec 2024 13:49:54 +0100 Subject: [PATCH 06/12] Convert prefabs --- .../Windows/ConvertPhysXToAGXWindow.cs | 621 ++++++++++++------ 1 file changed, 428 insertions(+), 193 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index b704d54f..c27942af 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -1,4 +1,3 @@ -using AGXUnity.Utils; using AGXUnityEditor.Utils; using System.Collections.Generic; using System.Linq; @@ -7,11 +6,6 @@ using UnityEngine.UIElements; using AGXUnityEditor.UIElements; using System; -using agxPowerLine; -using AGXUnity.Model; -using UnityEditor.VersionControl; -using agx; -using System.Collections; namespace AGXUnityEditor.Windows { @@ -37,13 +31,31 @@ private class AssetData : ScriptableObject public bool Updatable; public bool Convert; public GameObject gameObject; - //public string Path; public Type type; public List dependentAssets; public bool rootAsset; // Used by nested RBs public PhysXType physXType; } + private class PrefabData : ScriptableObject + { + public string Name; + public string Path; + public int ConvertibleCount; + public bool Convert; + + // Factory method to create an instance of PrefabData + public static PrefabData CreateInstance( string name, string path, int convertibleCount, bool selected = false ) + { + var instance = ScriptableObject.CreateInstance(); + instance.Name = name; + instance.Path = path; + instance.ConvertibleCount = convertibleCount; + instance.Convert = selected; + return instance; + } + } + public static ConvertPhysXToAGXWindow Open() { // Get existing open window or if none, make a new one: @@ -60,7 +72,8 @@ public static ConvertPhysXToAGXWindow Open() private int m_hoverIndex = -1; private List m_tableRows = new List(); - private MixedToggle m_toggleAll; + private MixedToggle m_toggleAllSceneObjects; + private MixedToggle m_toggleAllPrefabObjects; private Type[] m_basicColliderTypes = new Type[]{ typeof(BoxCollider), @@ -79,10 +92,16 @@ public static ConvertPhysXToAGXWindow Open() private List m_physXAssets; private void GatherSceneObjects() { - //var assets = AssetDatabase.FindAssets( "t:Material", null ); - m_physXAssets = new List(); - foreach (var type in m_basicColliderTypes) - m_physXAssets.AddRange(FindColliderAssetData(type, PhysXType.Collider)); + m_physXAssets = GatherObjects(); + + SortAssets(); + } + + private List GatherObjects( GameObject prefabObject = null ) + { + var convertibleObjects = new List(); + foreach ( var type in m_basicColliderTypes ) + convertibleObjects.AddRange( FindColliderAssetData( type, PhysXType.Collider, prefabObject ) ); // TODO Need to take care of DeformableTerrainPager before this works as intended /* @@ -95,27 +114,52 @@ private void GatherSceneObjects() m_physXAssets.AddRange(terrains); */ - m_physXAssets.AddRange(FindRigidbodyData()); + convertibleObjects.AddRange( FindRigidbodyData( prefabObject ) ); - //m_physXObjects.AddRange(FindAssetData(typeof(Constraint))); - - SortAssets(); + return convertibleObjects; } private void SortAssets() { - switch (m_sortType) - { - case SortType.Name: - m_physXAssets.Sort( ( n1, n2 ) => n1.gameObject.name.CompareTo(n2.gameObject.name )); + switch ( m_sortType ) { + case SortType.Name: + m_physXAssets.Sort( ( n1, n2 ) => n1.gameObject.name.CompareTo( n2.gameObject.name ) ); break; - + case SortType.Type: // This is default break; } } + private List m_prefabPhysXAssets; + private void GatherPrefabData() + { + m_prefabPhysXAssets = new List(); + + string[] prefabPaths = AssetDatabase.FindAssets("t:Prefab") + .Select(AssetDatabase.GUIDToAssetPath) + .ToArray(); + + foreach ( var prefabPath in prefabPaths ) { + GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath); + if ( prefab == null ) + continue; + + int count = 0; + count += prefab.GetComponentsInChildren( true ).Length; + count += prefab.GetComponentsInChildren( true ).Length; + count += prefab.GetComponentsInChildren( true ).Length; + + if ( count > 0 ) { + var prefabData = PrefabData.CreateInstance(prefab.name, prefabPath, count); + m_prefabPhysXAssets.Add( prefabData ); + } + } + + m_prefabPhysXAssets = m_prefabPhysXAssets.OrderBy( p => p.Name ).ToList(); + } + private class RBList { public Rigidbody rigidbody; @@ -127,14 +171,21 @@ private class RBList // Get RBs. We need to keep track of: // - Which RBs have children but are not themselves children of RBs // - Which colliders belong to this RB (and not to a child RB) - private List FindRigidbodyData() + private List FindRigidbodyData( GameObject prefabObject = null ) { + bool sceneMode = prefabObject == null; + + UnityEngine.Object[] components; + if ( sceneMode ) { #if UNITY_2023_1_OR_NEWER - var components = FindObjectsByType(typeof(Rigidbody), FindObjectsInactive.Include, FindObjectsSortMode.None); + components = FindObjectsByType( typeof( Rigidbody ), FindObjectsInactive.Include, FindObjectsSortMode.None ); #else - var components = FindObjectsOfType(typeof(RigidBody), true); + components = FindObjectsOfType(typeof(RigidBody), true); #endif - + } + else { + components = prefabObject.GetComponentsInChildren( true ); + } var allRBs = components.Select( rb => { @@ -159,61 +210,54 @@ private List FindRigidbodyData() // Find root RBs var rbsThatAreRBChildren = new List(); - foreach (var rb in allRBs) - { - if (rb.rbChildren.Count > 0) - { + foreach ( var rb in allRBs ) { + if ( rb.rbChildren.Count > 0 ) { rb.root = true; - rbsThatAreRBChildren.AddRange(rb.rbChildren); + rbsThatAreRBChildren.AddRange( rb.rbChildren ); } } - foreach (var rb in allRBs) - { - if (rbsThatAreRBChildren.Contains(rb.rigidbody)) + foreach ( var rb in allRBs ) { + if ( rbsThatAreRBChildren.Contains( rb.rigidbody ) ) rb.root = false; } - return allRBs.Select( rb => - { + return allRBs.Select( rb => { var data = CreateInstance(); - data.Convert = false; - data.type = typeof(Rigidbody); + data.Convert = !sceneMode; + data.type = typeof( Rigidbody ); data.physXType = PhysXType.RigidBody; data.gameObject = rb.rigidbody.gameObject; data.Updatable = true; data.rootAsset = rb.root; // AssetData for colliders in RB - data.dependentAssets = FindColliderAssetDataInGameObjects(rb.ownChildren); + data.dependentAssets = FindColliderAssetDataInGameObjects( rb.ownChildren, !sceneMode ); return data; } ).ToList(); } - private List FindColliderAssetDataInGameObjects(List transforms) + private List FindColliderAssetDataInGameObjects( List transforms, bool convert ) { var physXAssets = new List(); - foreach (var type in m_basicColliderTypes) - { + foreach ( var type in m_basicColliderTypes ) { List colliders = new List(); - foreach (var t in transforms) - { + foreach ( var t in transforms ) { var components = t.GetComponents(type); - foreach (var c in components) - colliders.Add(c.gameObject); + foreach ( var c in components ) + colliders.Add( c.gameObject ); } physXAssets.AddRange( - colliders.Select( a => - { + colliders.Select( a => { var data = CreateInstance(); - data.Convert = false; + data.Convert = convert; data.type = type; data.physXType = PhysXType.Collider; data.gameObject = a; - data.Updatable = type != typeof(WheelCollider); + data.Updatable = type != typeof( WheelCollider ); return data; - } ).ToList()); + } ).ToList() ); } // Debug.Log("Number of assetDatas: " + physXAssets.Count); @@ -221,29 +265,35 @@ private List FindColliderAssetDataInGameObjects(List trans return physXAssets; } - private List FindColliderAssetData(Type type, PhysXType physXType) + private List FindColliderAssetData( Type type, PhysXType physXType, GameObject prefabObject = null ) { + bool sceneMode = prefabObject == null; + + UnityEngine.Object[] components; + if ( sceneMode ) { #if UNITY_2023_1_OR_NEWER - var components = FindObjectsByType(type, FindObjectsInactive.Include, FindObjectsSortMode.None); + components = FindObjectsByType( type, FindObjectsInactive.Include, FindObjectsSortMode.None ); #else - var components = FindObjectsOfType(type, true); + components = FindObjectsOfType(type, true); #endif + } + else { + components = prefabObject.GetComponentsInChildren( type, true ); + } - return components.Where( o => ((Collider)o).attachedRigidbody == null ) - .Select( o => - { - var data = CreateInstance(); - data.Convert = false; - data.type = type; - data.physXType = physXType; - data.gameObject = ((UnityEngine.Component)o).gameObject; - data.Updatable = type != typeof(WheelCollider); - - return data; - } ).ToList(); + return components.Where( o => ( (Collider)o ).attachedRigidbody == null ) + .Select( o => { + var data = CreateInstance(); + data.Convert = !sceneMode; + data.type = type; + data.physXType = physXType; + data.gameObject = ( (UnityEngine.Component)o ).gameObject; + data.Updatable = type != typeof( WheelCollider ); + + return data; + } ).ToList(); } - // TODO: Replace hover/select coloring with proper USS styling private void UpdateColors() { for ( int i = 0; i < m_tableRows.Count(); i++ ) { @@ -262,41 +312,66 @@ private void UpdateColors() } } - private VisualElement TableRowUI( AssetData asset ) + private void OnEnable() { + GatherSceneObjects(); + m_statusIcons = new Texture2D[ 3 ]; + m_statusIcons[ 0 ] = IconManager.GetIcon( "convertible_material" ); + m_statusIcons[ 1 ] = IconManager.GetIcon( "compatible_material" ); + m_statusIcons[ 2 ] = IconManager.GetIcon( "pass" ); + + Undo.undoRedoPerformed += () => Repopulate( false ); + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + } + + private void OnDisable() + { + Undo.undoRedoPerformed -= () => Repopulate( false ); + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + } + + private void OnPlayModeStateChanged( PlayModeStateChange state ) + { + if ( state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode ) { + Repaint(); + } + + if ( state == PlayModeStateChange.ExitingPlayMode ) { + Repopulate(); // Rebuild UI and data when leaving Play mode + } + } + + private VisualElement CreateSceneRowUI( AssetData asset ) + { var row = new VisualElement(); - row.SetPadding(3,3,3,0); - var ve = new VisualElement(); + row.SetPadding( 3, 3, 3, 0 ); - ve.SetEnabled( asset.Updatable ); + row.SetEnabled( asset.Updatable ); var index = m_tableRows.Count(); - row.RegisterCallback( mde => - { + row.RegisterCallback( mde => { EditorUtility.FocusProjectWindow(); Selection.activeObject = asset.gameObject; m_selectedIndex = index; UpdateColors(); } ); - row.RegisterCallback( mde => - { + row.RegisterCallback( mde => { m_hoverIndex = index; UpdateColors(); } ); - row.RegisterCallback( mde => - { + row.RegisterCallback( mde => { m_hoverIndex = -1; UpdateColors(); } ); - ve.style.flexDirection = FlexDirection.Row; + row.style.flexDirection = FlexDirection.Row; - var activeToggle = new Toggle() { value = asset.Convert }; - activeToggle.RegisterValueChangedCallback( ce => asset.Convert = ce.newValue ); - activeToggle.style.width = 20; - activeToggle.SetMargin( 0, 0, 0, StyleKeyword.Null ); - if ( asset.Updatable) - m_toggleAll.AddControlledToggle( activeToggle ); + var toggleConversion = new Toggle() { value = asset.Convert }; + toggleConversion.RegisterValueChangedCallback( ce => asset.Convert = ce.newValue ); + toggleConversion.style.width = 20; + toggleConversion.SetMargin( 0, 0, 0, StyleKeyword.Null ); + if ( asset.Updatable ) + m_toggleAllSceneObjects.AddControlledToggle( toggleConversion ); var flex = new VisualElement(); flex.style.flexDirection = FlexDirection.Row; @@ -316,81 +391,166 @@ private VisualElement TableRowUI( AssetData asset ) TypeIcon.style.width = 20; Label componentLabel = null; - if (asset.dependentAssets == null) + if ( asset.dependentAssets == null ) componentLabel = new Label( asset.type.ToString() ); else - componentLabel = new Label( asset.type.ToString() + " - Colliders: " + asset.dependentAssets.Count); + componentLabel = new Label( asset.type.ToString() + " - Colliders: " + asset.dependentAssets.Count ); componentLabel.style.flexGrow = 0.6f; componentLabel.style.flexBasis = 0; componentLabel.style.overflow = Overflow.Hidden; nameLabel.style.unityTextAlign = TextAnchor.MiddleLeft; componentLabel.style.marginRight = 2; - ve.Add( activeToggle ); + row.Add( toggleConversion ); flex.Add( nameLabel ); flex.Add( TypeIcon ); flex.Add( componentLabel ); - ve.Add( flex ); - row.Add( ve ); + row.Add( flex ); m_tableRows.Add( row ); return row; } - private void OnEnable() + private VisualElement CreatePrefabRowUI( PrefabData prefabData ) { - GatherSceneObjects(); + int rowIndex = m_tableRows.Count; // Keep track of row index for alternating colors + var row = new VisualElement(); + row.style.flexDirection = FlexDirection.Row; + row.style.justifyContent = Justify.SpaceBetween; + row.SetPadding( 3, 3, 3, 0 ); - m_statusIcons = new Texture2D[ 3 ]; - m_statusIcons[ 0 ] = IconManager.GetIcon( "convertible_material" ); - m_statusIcons[ 1 ] = IconManager.GetIcon( "compatible_material" ); - m_statusIcons[ 2 ] = IconManager.GetIcon( "pass" ); + var index = m_tableRows.Count(); + row.RegisterCallback( _ => { + m_hoverIndex = index; + UpdateColors(); + } ); + row.RegisterCallback( _ => { + m_hoverIndex = -1; + UpdateColors(); + } ); + row.RegisterCallback( _ => { + m_selectedIndex = index; + var prefabAsset = AssetDatabase.LoadAssetAtPath(prefabData.Path); + if ( prefabAsset != null ) { + EditorGUIUtility.PingObject( prefabAsset ); + } + } ); + + var toggleConversion = new Toggle { value = prefabData.Convert }; + toggleConversion.style.width = 20; + toggleConversion.SetMargin( 0, 5, 0, StyleKeyword.Null ); + toggleConversion.RegisterValueChangedCallback( ce => prefabData.Convert = ce.newValue ); + m_toggleAllPrefabObjects.AddControlledToggle( toggleConversion ); - Undo.undoRedoPerformed += () => Repopulate(false); + var nameLabel = new Label(prefabData.Name); + nameLabel.style.flexGrow = 0.3f; + nameLabel.style.flexBasis = 0; + nameLabel.style.overflow = Overflow.Hidden; + nameLabel.style.unityTextAlign = TextAnchor.MiddleLeft; + nameLabel.style.marginRight = 5; + + var pathLabel = new Label(prefabData.Path); + pathLabel.style.flexGrow = 0.4f; + pathLabel.style.flexBasis = 0; + pathLabel.style.overflow = Overflow.Hidden; + pathLabel.style.unityTextAlign = TextAnchor.MiddleLeft; + pathLabel.style.marginRight = 5; + + var countLabel = new Label($"Components: {prefabData.ConvertibleCount}"); + countLabel.style.flexGrow = 0.2f; + countLabel.style.flexBasis = 0; + countLabel.style.unityTextAlign = TextAnchor.MiddleLeft; + + row.Add( toggleConversion ); + row.Add( nameLabel ); + row.Add( pathLabel ); + row.Add( countLabel ); + + // Keep track of rows for further updates + m_tableRows.Add( row ); + + return row; } private void CreateGUI() { rootVisualElement.SetPadding( 19, 15, 15, 15 ); - var RPLabel = new Label( $"This utility attempts to convert PhysX Components to corresponding AGX Components." ); - RPLabel.style.marginBottom = 15; + var RPLabel = new Label($"This utility attempts to convert PhysX Components to corresponding AGX Components."); + RPLabel.style.marginBottom = 5; rootVisualElement.Add( RPLabel ); - // TODO starting here with just colliders, moving on from there to RBs. Think about how the different lists will look - var assetConverter = new VisualElement(); - assetConverter.style.height = 600; - assetConverter.SetBorder( 2, Color.Lerp( InspectorGUI.BackgroundColor, Color.black, 0.2f ) ); - assetConverter.SetBorderRadius( 5 ); - assetConverter.SetPadding( 5 ); + // Actions for both boxes + var footer = new VisualElement(); + footer.style.flexDirection = FlexDirection.Row; + footer.style.justifyContent = Justify.SpaceBetween; + footer.style.marginBottom = 10; + footer.style.flexShrink = 0; + + var convertButton = new Button() { text = "Convert Selected" }; + convertButton.clicked += ConvertSelected; + + var refreshButton = new Button(); + refreshButton.style.width = 40; + refreshButton.style.marginLeft = 0; + refreshButton.clicked += () => Repopulate(); + + var refreshIcon = new Image { image = IconManager.GetIcon( MiscIcon.Update ) }; + refreshIcon.style.flexBasis = 0; + refreshIcon.style.flexGrow = 1; + refreshButton.Add( refreshIcon ); + + footer.Add( refreshButton ); + footer.Add( convertButton ); + rootVisualElement.Add( footer ); + + + // Scene Object Converter Box + var sceneConverter = CreateSceneObjectBox(); + rootVisualElement.Add( sceneConverter ); + + // Prefab Converter Box + var prefabConverter = CreatePrefabObjectBox(); + rootVisualElement.Add( prefabConverter ); + } + + + private VisualElement CreateSceneObjectBox() + { + var sceneBox = new VisualElement(); + + sceneBox.style.height = 300; + sceneBox.SetBorder( 2, Color.Lerp( InspectorGUI.BackgroundColor, Color.black, 0.2f ) ); + sceneBox.SetBorderRadius( 5 ); + sceneBox.SetPadding( 5 ); var description = new Label( "PhysXObjects in Scene. GameObjects with multiple matching Components can appear on multiple lines." ); description.style.whiteSpace = WhiteSpace.Normal; description.style.marginBottom = 10; - assetConverter.Add( description ); + sceneBox.Add( description ); -// var sortMenu = EditorGUILayout.EnumFlagsField( AGXUnity.Utils.GUI.MakeLabel( "Properties" ), -// m_sortType, -// InspectorEditor.Skin.Popup ); -// var sortMenu = new EnumField("Test", m_sortType); -// sortMenu. -// //sortMenu.style.marginBottom = 15; -// rootVisualElement.Add( sortMenu ); + // var sortMenu = EditorGUILayout.EnumFlagsField( AGXUnity.Utils.GUI.MakeLabel( "Properties" ), + // m_sortType, + // InspectorEditor.Skin.Popup ); + // var sortMenu = new EnumField("Test", m_sortType); + // sortMenu. + // //sortMenu.style.marginBottom = 15; + // sceneBox.Add( sortMenu ); var header = new VisualElement(); header.style.flexDirection = FlexDirection.Row; header.style.justifyContent = Justify.SpaceBetween; header.style.flexShrink = 0; - m_toggleAll = new MixedToggle(); - header.Add( m_toggleAll ); + m_toggleAllSceneObjects = new MixedToggle(); + header.Add( m_toggleAllSceneObjects ); var numAssets = new VisualElement(); numAssets.style.flexDirection = FlexDirection.Row; numAssets.style.alignItems = Align.Center; numAssets.style.unityTextAlign = TextAnchor.MiddleLeft; - - numAssets.Add(new Label() { text = "Upgradable assets: " }); + + numAssets.Add( new Label() { text = "Upgradable assets: " } ); var image = new Image() { image = m_statusIcons[ 0 ] }; image.style.width = 20; @@ -402,79 +562,156 @@ private void CreateGUI() numAssets.Add( lab ); header.Add( numAssets ); - var scroll = new ScrollView(); - + // Create table rows + var scrolledTable = new ScrollView(); foreach ( var asset in m_physXAssets ) - scroll.Add( TableRowUI( asset ) ); - + scrolledTable.Add( CreateSceneRowUI( asset ) ); UpdateColors(); - assetConverter.Add( header ); - assetConverter.Add( scroll ); + sceneBox.Add( header ); + sceneBox.Add( scrolledTable ); - var footer = new VisualElement(); - footer.style.flexDirection = FlexDirection.Row; - footer.style.justifyContent = Justify.SpaceBetween; - footer.style.marginTop = 10; - footer.style.flexShrink = 0; + return sceneBox; + } - var convertButton = new Button() { text = "Convert Selected" }; - convertButton.clicked += ConvertSelected; + private VisualElement CreatePrefabObjectBox() + { + var prefabBox = new VisualElement(); + prefabBox.style.height = 300; + prefabBox.SetBorder( 2, Color.Lerp( InspectorGUI.BackgroundColor, Color.black, 0.2f ) ); + prefabBox.SetBorderRadius( 5 ); + prefabBox.SetPadding( 5 ); + + if ( EditorApplication.isPlayingOrWillChangePlaymode ) { + var warningLabel = new Label("Prefab conversion not available in Play mode."); + warningLabel.style.color = Color.red; + warningLabel.style.unityFontStyleAndWeight = FontStyle.Bold; + warningLabel.style.marginTop = 10; + warningLabel.style.marginBottom = 10; + prefabBox.Add( warningLabel ); + return prefabBox; // Return early since prefab conversion is disabled in Play mode + } - var refreshButton = new Button(); - refreshButton.style.width = 40; - refreshButton.style.marginLeft = 0; - refreshButton.clicked += () => Repopulate( ); + var description = new Label("PhysXObjects in Prefabs. Lists prefabs with convertible components. Note: You cannot undo this convert action!"); + description.style.whiteSpace = WhiteSpace.Normal; + description.style.marginBottom = 10; + prefabBox.Add( description ); - var refreshIcon = new Image { image = IconManager.GetIcon( MiscIcon.Update ) }; - refreshIcon.style.flexBasis = 0; - refreshIcon.style.flexGrow = 1; - refreshButton.Add( refreshIcon ); - footer.Add( refreshButton ); - footer.Add( convertButton ); - assetConverter.Add( footer ); + var header = new VisualElement(); + header.style.flexDirection = FlexDirection.Row; + header.style.justifyContent = Justify.SpaceBetween; + header.style.flexShrink = 0; + header.style.overflow = Overflow.Hidden; + + m_toggleAllPrefabObjects = new MixedToggle(); + m_toggleAllPrefabObjects.style.flexGrow = 0; + m_toggleAllPrefabObjects.style.flexShrink = 0; + header.Add( m_toggleAllPrefabObjects ); + + var numAssets = new VisualElement(); + numAssets.style.flexDirection = FlexDirection.Row; + numAssets.style.alignItems = Align.Center; + numAssets.style.unityTextAlign = TextAnchor.MiddleLeft; + numAssets.style.flexGrow = 1; // Allows this section to take up remaining space + numAssets.style.justifyContent = Justify.FlexEnd; + numAssets.Add( new Label() { text = "Upgradable prefabs: " } ); + + var image = new Image() { image = m_statusIcons[ 0 ] }; + image.style.width = 20; + image.style.height = 20; + image.style.marginLeft = 5; + image.style.marginRight = 5; + numAssets.Add( image ); + + var label = new Label() { text = $" {m_prefabPhysXAssets.Count}" }; + label.style.flexGrow = 0; // Fixed size for label + label.style.flexShrink = 0; + numAssets.Add( label ); + + header.Add( numAssets ); + + prefabBox.Add( header ); + + var scrolledTable = new ScrollView(); + foreach ( var prefabData in m_prefabPhysXAssets ) // Populate rows with prefabs + scrolledTable.Add( CreatePrefabRowUI( prefabData ) ); - rootVisualElement.Add( assetConverter ); + prefabBox.Add( scrolledTable ); + return prefabBox; } - private void Repopulate(bool gatherAssets = true) + private void Repopulate( bool gatherAssets = true ) { + // TODO gatherassets? probably remove the option GatherSceneObjects(); + GatherPrefabData(); m_tableRows.Clear(); rootVisualElement.Clear(); CreateGUI(); } + private void ConvertObjects( List assets ) + { + foreach ( var asset in assets ) { // .Where(m => m.Status == MaterialStatus.Updatable ) + if ( !asset.Convert ) + continue; + + Debug.Log( $"Asset: {asset.gameObject.name}, type: {asset.type}" ); + + if ( asset.physXType == PhysXType.Collider ) + ConvertCollider( asset ); + + if ( asset.physXType == PhysXType.RigidBody ) { + ConvertRigidbody( asset ); + foreach ( var dependentCollider in asset.dependentAssets ) + ConvertCollider( dependentCollider ); + } + + // TODO maybe remove these + EditorUtility.SetDirty( asset ); + //EditorUtility.SetDirty( asset.gameObject.GetComponent( asset.type ) ); + } + } + private void ConvertSelected() { - using ( new UndoCollapseBlock("Convert Selected PhysX Assets") ) { - foreach(var asset in m_physXAssets ) { // .Where(m => m.Status == MaterialStatus.Updatable ) - if ( !asset.Convert ) + using ( new UndoCollapseBlock( "Convert Selected PhysX Assets" ) ) { + ConvertObjects( m_physXAssets ); + } + + // TODO: possibly remove Undo, it won't work + using ( new UndoCollapseBlock( "Convert Selected Prefab Assets" ) ) { + foreach ( var prefab in m_prefabPhysXAssets ) { + if ( !prefab.Convert ) continue; - if (asset.physXType == PhysXType.Collider) - ConvertCollider(asset); + //GameObject prefabObject = AssetDatabase.LoadAssetAtPath(prefab.Path); + GameObject prefabObject = PrefabUtility.LoadPrefabContents(prefab.Path); + if ( prefabObject == null ) + continue; + + var objects = GatherObjects(prefabObject); + try { + ConvertObjects( objects ); - if (asset.physXType == PhysXType.RigidBody) - { - ConvertRigidbody(asset); - foreach (var dependentCollider in asset.dependentAssets) - ConvertCollider(dependentCollider); + PrefabUtility.SaveAsPrefabAsset(prefabObject, prefab.Path); + } + finally { + // Ensure the prefab contents are unloaded from memory + PrefabUtility.UnloadPrefabContents( prefabObject ); } -// EditorUtility.SetDirty( asset ); -// EditorUtility.SetDirty( asset.gameObject.GetComponent(asset.type) ); + Debug.Log( $"Converted asset: {prefab.Name}, objects: {objects.Count}" ); } } AssetDatabase.SaveAssets(); - Repopulate(false); + Repopulate( false ); } - - private void ConvertRigidbody(AssetData asset) + private void ConvertRigidbody( AssetData asset ) { var physXRb = asset.gameObject.GetComponent(); var agxRB = Undo.AddComponent(asset.gameObject); @@ -489,38 +726,36 @@ private void ConvertRigidbody(AssetData asset) var linearVelocityDamping = physXRb.drag; var angularVelocityDamping = physXRb.angularDrag; #endif - agxRB.LinearVelocityDamping = new Vector3(linearVelocityDamping, linearVelocityDamping, linearVelocityDamping); - agxRB.AngularVelocityDamping = new Vector3(angularVelocityDamping, angularVelocityDamping, angularVelocityDamping); + agxRB.LinearVelocityDamping = new Vector3( linearVelocityDamping, linearVelocityDamping, linearVelocityDamping ); + agxRB.AngularVelocityDamping = new Vector3( angularVelocityDamping, angularVelocityDamping, angularVelocityDamping ); - if (!physXRb.automaticCenterOfMass) - { + if ( !physXRb.automaticCenterOfMass ) { agxRB.MassProperties.CenterOfMassOffset.UseDefault = false; agxRB.MassProperties.CenterOfMassOffset.Value = physXRb.centerOfMass; } - if (!physXRb.automaticInertiaTensor) - { + if ( !physXRb.automaticInertiaTensor ) { agxRB.MassProperties.InertiaDiagonal.UseDefault = false; agxRB.MassProperties.InertiaDiagonal.Value = physXRb.centerOfMass; } - if (physXRb.isKinematic) + if ( physXRb.isKinematic ) agxRB.MotionControl = agx.RigidBody.MotionControl.KINEMATICS; - if (asset.rootAsset) + if ( asset.rootAsset ) asset.gameObject.AddComponent(); - Undo.DestroyObjectImmediate(physXRb); + Undo.DestroyObjectImmediate( physXRb ); } - private void ConvertCollider(AssetData asset) + private void ConvertCollider( AssetData asset ) { // We need to create a child gameObject with the AGX collider to account for the "Center" property var newObject = new GameObject(); - Undo.RegisterCreatedObjectUndo(newObject, newObject.name); + Undo.RegisterCreatedObjectUndo( newObject, newObject.name ); newObject.transform.parent = asset.gameObject.transform; - + newObject.transform.localRotation = Quaternion.identity; newObject.transform.localScale = Vector3.one; // AGX colliders ignore transform scale, except MeshColliders @@ -528,9 +763,9 @@ private void ConvertCollider(AssetData asset) var localScale = asset.gameObject.transform.localScale; - switch (asset.type.ToString()) // Can't switch on type, workaround with strings + switch ( asset.type.ToString() ) // Can't switch on type, workaround with strings { - case "UnityEngine.SphereCollider": + case "UnityEngine.SphereCollider": var physXSphere = asset.gameObject.GetComponent(); var agxSphere = Undo.AddComponent(newObject); @@ -538,33 +773,33 @@ private void ConvertCollider(AssetData asset) newObject.transform.localPosition = physXSphere.center; // SphereCollider radius is old radius times largest of absolute value of localScale axes - agxSphere.Radius = physXSphere.radius * Mathf.Max(new float[]{ - Mathf.Abs(localScale.x), + agxSphere.Radius = physXSphere.radius * Mathf.Max( new float[]{ + Mathf.Abs(localScale.x), Mathf.Abs(localScale.y), - Mathf.Abs(localScale.z)}); + Mathf.Abs(localScale.z)} ); agxSphere.IsSensor = physXSphere.isTrigger; - Undo.DestroyObjectImmediate(physXSphere); + Undo.DestroyObjectImmediate( physXSphere ); break; - case "UnityEngine.BoxCollider": + case "UnityEngine.BoxCollider": var physXBox = asset.gameObject.GetComponent(); var agxBox = Undo.AddComponent(newObject); newObject.name = "AGXUnity.Collide.Box"; newObject.transform.localPosition = physXBox.center; - agxBox.HalfExtents = new Vector3(physXBox.size.x / 2f * Mathf.Abs(localScale.x), - physXBox.size.y / 2f * Mathf.Abs(localScale.y), - physXBox.size.z / 2f * Mathf.Abs(localScale.z)); - + agxBox.HalfExtents = new Vector3( physXBox.size.x / 2f * Mathf.Abs( localScale.x ), + physXBox.size.y / 2f * Mathf.Abs( localScale.y ), + physXBox.size.z / 2f * Mathf.Abs( localScale.z ) ); + agxBox.IsSensor = physXBox.isTrigger; - Undo.DestroyObjectImmediate(physXBox); - break; + Undo.DestroyObjectImmediate( physXBox ); + break; - case "UnityEngine.CapsuleCollider": + case "UnityEngine.CapsuleCollider": var physXCapsule = asset.gameObject.GetComponent(); var agxCapsule = Undo.AddComponent(newObject); @@ -574,34 +809,34 @@ private void ConvertCollider(AssetData asset) // Capsule radius is old radius times abs largest xz-localScale axis. PhysX capsule height is including caps, agx is without newObject.transform.localScale = Vector3.one; var radius = physXCapsule.radius * Mathf.Max(new float[]{ - Mathf.Abs(localScale.x), + Mathf.Abs(localScale.x), Mathf.Abs(localScale.z)}); agxCapsule.Radius = radius; - agxCapsule.Height = (physXCapsule.height - radius * 2) * Mathf.Abs(localScale.y); + agxCapsule.Height = ( physXCapsule.height - radius * 2 ) * Mathf.Abs( localScale.y ); agxCapsule.IsSensor = physXCapsule.isTrigger; - Undo.DestroyObjectImmediate(physXCapsule); - break; + Undo.DestroyObjectImmediate( physXCapsule ); + break; - case "UnityEngine.MeshCollider": + case "UnityEngine.MeshCollider": var physXMesh = asset.gameObject.GetComponent(); var agxMesh = Undo.AddComponent(newObject); newObject.name = "AGXUnity.Collide.Mesh"; newObject.transform.localPosition = Vector3.zero; - agxMesh.AddSourceObject(physXMesh.sharedMesh); + agxMesh.AddSourceObject( physXMesh.sharedMesh ); agxMesh.IsSensor = physXMesh.isTrigger; - Undo.DestroyObjectImmediate(physXMesh); + Undo.DestroyObjectImmediate( physXMesh ); break; - case "UnityEngine.TerrainCollider": - Undo.DestroyObjectImmediate(newObject); - Undo.AddComponent(asset.gameObject); - break; + case "UnityEngine.TerrainCollider": + Undo.DestroyObjectImmediate( newObject ); + Undo.AddComponent( asset.gameObject ); + break; } } } From 2c3e331eb959e43dba3d5c28b3ef94706fe87572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 3 Dec 2024 07:55:05 +0100 Subject: [PATCH 07/12] Fix collider scale --- .../Windows/ConvertPhysXToAGXWindow.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index c27942af..d5b21799 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -658,8 +658,6 @@ private void ConvertObjects( List assets ) if ( !asset.Convert ) continue; - Debug.Log( $"Asset: {asset.gameObject.name}, type: {asset.type}" ); - if ( asset.physXType == PhysXType.Collider ) ConvertCollider( asset ); @@ -687,7 +685,6 @@ private void ConvertSelected() if ( !prefab.Convert ) continue; - //GameObject prefabObject = AssetDatabase.LoadAssetAtPath(prefab.Path); GameObject prefabObject = PrefabUtility.LoadPrefabContents(prefab.Path); if ( prefabObject == null ) continue; @@ -703,7 +700,7 @@ private void ConvertSelected() PrefabUtility.UnloadPrefabContents( prefabObject ); } - Debug.Log( $"Converted asset: {prefab.Name}, objects: {objects.Count}" ); + Debug.Log( $"Converted prefab: {prefab.Name}, objects: {objects.Count}" ); } } @@ -761,7 +758,7 @@ private void ConvertCollider( AssetData asset ) newObject.isStatic = asset.gameObject.isStatic; - var localScale = asset.gameObject.transform.localScale; + var colliderScale = asset.gameObject.transform.lossyScale; switch ( asset.type.ToString() ) // Can't switch on type, workaround with strings { @@ -774,9 +771,9 @@ private void ConvertCollider( AssetData asset ) // SphereCollider radius is old radius times largest of absolute value of localScale axes agxSphere.Radius = physXSphere.radius * Mathf.Max( new float[]{ - Mathf.Abs(localScale.x), - Mathf.Abs(localScale.y), - Mathf.Abs(localScale.z)} ); + Mathf.Abs(colliderScale.x), + Mathf.Abs(colliderScale.y), + Mathf.Abs(colliderScale.z)} ); agxSphere.IsSensor = physXSphere.isTrigger; @@ -790,9 +787,9 @@ private void ConvertCollider( AssetData asset ) newObject.name = "AGXUnity.Collide.Box"; newObject.transform.localPosition = physXBox.center; - agxBox.HalfExtents = new Vector3( physXBox.size.x / 2f * Mathf.Abs( localScale.x ), - physXBox.size.y / 2f * Mathf.Abs( localScale.y ), - physXBox.size.z / 2f * Mathf.Abs( localScale.z ) ); + agxBox.HalfExtents = new Vector3( physXBox.size.x / 2f * Mathf.Abs( colliderScale.x ), + physXBox.size.y / 2f * Mathf.Abs( colliderScale.y ), + physXBox.size.z / 2f * Mathf.Abs( colliderScale.z ) ); agxBox.IsSensor = physXBox.isTrigger; @@ -809,10 +806,10 @@ private void ConvertCollider( AssetData asset ) // Capsule radius is old radius times abs largest xz-localScale axis. PhysX capsule height is including caps, agx is without newObject.transform.localScale = Vector3.one; var radius = physXCapsule.radius * Mathf.Max(new float[]{ - Mathf.Abs(localScale.x), - Mathf.Abs(localScale.z)}); + Mathf.Abs(colliderScale.x), + Mathf.Abs(colliderScale.z)}); agxCapsule.Radius = radius; - agxCapsule.Height = ( physXCapsule.height - radius * 2 ) * Mathf.Abs( localScale.y ); + agxCapsule.Height = ( physXCapsule.height - radius * 2 ) * Mathf.Abs( colliderScale.y ); agxCapsule.IsSensor = physXCapsule.isTrigger; From 73a839dc85e16b29d0cca2f3a5d1550ece5b47d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 3 Dec 2024 08:50:54 +0100 Subject: [PATCH 08/12] List Physic (sic) Materials --- .../Windows/ConvertPhysXToAGXWindow.cs | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index d5b21799..a88b01d8 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -15,7 +15,8 @@ private enum PhysXType { Collider, RigidBody, - Constraint + Constraint, + PhysicsMaterial } private enum SortType @@ -43,15 +44,17 @@ private class PrefabData : ScriptableObject public string Path; public int ConvertibleCount; public bool Convert; + public bool Updatable; // Factory method to create an instance of PrefabData - public static PrefabData CreateInstance( string name, string path, int convertibleCount, bool selected = false ) + public static PrefabData CreateInstance( string name, string path, int convertibleCount, bool selected = false, bool updatable = true ) { var instance = ScriptableObject.CreateInstance(); instance.Name = name; instance.Path = path; instance.ConvertibleCount = convertibleCount; instance.Convert = selected; + instance.Updatable = updatable; return instance; } } @@ -157,6 +160,29 @@ private void GatherPrefabData() } } + // The aptly named Physic Material apparently changed its name to the less Danish-English Physics Material +#if UNITY_6000_0_OR_NEWER + string[] pmPaths = AssetDatabase.FindAssets("t:PhysicsMaterial") + .Select(AssetDatabase.GUIDToAssetPath) + .ToArray(); + foreach ( var pmPath in pmPaths ) { + PhysicsMaterial physicMaterial = AssetDatabase.LoadAssetAtPath(pmPath); + + var prefabData = PrefabData.CreateInstance(physicMaterial.name, pmPath, 1, updatable: false); + m_prefabPhysXAssets.Add( prefabData ); + } +#else + string[] pmPaths = AssetDatabase.FindAssets("t:PhysicMaterial") + .Select(AssetDatabase.GUIDToAssetPath) + .ToArray(); + foreach ( var pmPath in pmPaths ) { + PhysicsMaterial physicMaterial = AssetDatabase.LoadAssetAtPath(pmPath); + + var prefabData = PrefabData.CreateInstance(physicMaterial.name, pmPath, 1, updatable: false); + m_prefabPhysXAssets.Add( prefabData ); + } +#endif + m_prefabPhysXAssets = m_prefabPhysXAssets.OrderBy( p => p.Name ).ToList(); } @@ -260,8 +286,6 @@ private List FindColliderAssetDataInGameObjects( List tran } ).ToList() ); } - // Debug.Log("Number of assetDatas: " + physXAssets.Count); - return physXAssets; } @@ -346,8 +370,8 @@ private VisualElement CreateSceneRowUI( AssetData asset ) { var row = new VisualElement(); row.SetPadding( 3, 3, 3, 0 ); - row.SetEnabled( asset.Updatable ); + var index = m_tableRows.Count(); row.RegisterCallback( mde => { EditorUtility.FocusProjectWindow(); @@ -414,11 +438,12 @@ private VisualElement CreateSceneRowUI( AssetData asset ) private VisualElement CreatePrefabRowUI( PrefabData prefabData ) { - int rowIndex = m_tableRows.Count; // Keep track of row index for alternating colors + int rowIndex = m_tableRows.Count; var row = new VisualElement(); row.style.flexDirection = FlexDirection.Row; row.style.justifyContent = Justify.SpaceBetween; row.SetPadding( 3, 3, 3, 0 ); + row.SetEnabled( prefabData.Updatable ); var index = m_tableRows.Count(); row.RegisterCallback( _ => { @@ -441,7 +466,8 @@ private VisualElement CreatePrefabRowUI( PrefabData prefabData ) toggleConversion.style.width = 20; toggleConversion.SetMargin( 0, 5, 0, StyleKeyword.Null ); toggleConversion.RegisterValueChangedCallback( ce => prefabData.Convert = ce.newValue ); - m_toggleAllPrefabObjects.AddControlledToggle( toggleConversion ); + if ( prefabData.Updatable ) + m_toggleAllPrefabObjects.AddControlledToggle( toggleConversion ); var nameLabel = new Label(prefabData.Name); nameLabel.style.flexGrow = 0.3f; @@ -467,7 +493,6 @@ private VisualElement CreatePrefabRowUI( PrefabData prefabData ) row.Add( pathLabel ); row.Add( countLabel ); - // Keep track of rows for further updates m_tableRows.Add( row ); return row; @@ -530,14 +555,6 @@ private VisualElement CreateSceneObjectBox() description.style.marginBottom = 10; sceneBox.Add( description ); - // var sortMenu = EditorGUILayout.EnumFlagsField( AGXUnity.Utils.GUI.MakeLabel( "Properties" ), - // m_sortType, - // InspectorEditor.Skin.Popup ); - // var sortMenu = new EnumField("Test", m_sortType); - // sortMenu. - // //sortMenu.style.marginBottom = 15; - // sceneBox.Add( sortMenu ); - var header = new VisualElement(); header.style.flexDirection = FlexDirection.Row; header.style.justifyContent = Justify.SpaceBetween; @@ -613,7 +630,7 @@ private VisualElement CreatePrefabObjectBox() numAssets.style.flexDirection = FlexDirection.Row; numAssets.style.alignItems = Align.Center; numAssets.style.unityTextAlign = TextAnchor.MiddleLeft; - numAssets.style.flexGrow = 1; // Allows this section to take up remaining space + numAssets.style.flexGrow = 1; numAssets.style.justifyContent = Justify.FlexEnd; numAssets.Add( new Label() { text = "Upgradable prefabs: " } ); @@ -624,8 +641,8 @@ private VisualElement CreatePrefabObjectBox() image.style.marginRight = 5; numAssets.Add( image ); - var label = new Label() { text = $" {m_prefabPhysXAssets.Count}" }; - label.style.flexGrow = 0; // Fixed size for label + var label = new Label() { text = $" {(m_prefabPhysXAssets != null ? m_prefabPhysXAssets.Where( a => a.Updatable).Count() : 0)}" }; + label.style.flexGrow = 0; label.style.flexShrink = 0; numAssets.Add( label ); @@ -643,7 +660,6 @@ private VisualElement CreatePrefabObjectBox() private void Repopulate( bool gatherAssets = true ) { - // TODO gatherassets? probably remove the option GatherSceneObjects(); GatherPrefabData(); m_tableRows.Clear(); @@ -654,7 +670,7 @@ private void Repopulate( bool gatherAssets = true ) private void ConvertObjects( List assets ) { - foreach ( var asset in assets ) { // .Where(m => m.Status == MaterialStatus.Updatable ) + foreach ( var asset in assets ) { if ( !asset.Convert ) continue; @@ -667,9 +683,7 @@ private void ConvertObjects( List assets ) ConvertCollider( dependentCollider ); } - // TODO maybe remove these EditorUtility.SetDirty( asset ); - //EditorUtility.SetDirty( asset.gameObject.GetComponent( asset.type ) ); } } @@ -696,7 +710,6 @@ private void ConvertSelected() PrefabUtility.SaveAsPrefabAsset(prefabObject, prefab.Path); } finally { - // Ensure the prefab contents are unloaded from memory PrefabUtility.UnloadPrefabContents( prefabObject ); } From 559bd60c6de004994608795fd762cba641934cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 3 Dec 2024 09:08:24 +0100 Subject: [PATCH 09/12] Terrain handling --- .../Windows/ConvertPhysXToAGXWindow.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index a88b01d8..7b4e8b25 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -106,16 +106,26 @@ private List GatherObjects( GameObject prefabObject = null ) foreach ( var type in m_basicColliderTypes ) convertibleObjects.AddRange( FindColliderAssetData( type, PhysXType.Collider, prefabObject ) ); - // TODO Need to take care of DeformableTerrainPager before this works as intended - /* - var terrains = FindColliderAssetData(typeof(TerrainCollider), PhysXType.Collider); - for (int i = terrains.Count; --i >= 0;) +#if UNITY_2023_1_OR_NEWER + bool foundTerrainPager = FindObjectsByType( typeof( AGXUnity.Model.DeformableTerrainPager ), FindObjectsInactive.Include, FindObjectsSortMode.None ).Length > 0; +#else + bool foundTerrainPager = FindObjectsOfType( typeof( AGXUnity.Model.DeformableTerrainPager ), true ).Length > 0; +#endif + + if (foundTerrainPager) { - if (terrains[i].gameObject.GetComponent() != null) - terrains.RemoveAt(i); + Debug.Log("Found at least one Deformable Terrain Pager, assuming Terrain Colliders to be part of that system. If there are separate terrains that should have Terrain Colliders, add manually."); + } + else + { + var terrains = FindColliderAssetData(typeof(TerrainCollider), PhysXType.Collider); + for (int i = terrains.Count; --i >= 0;) + { + if (terrains[i].gameObject.GetComponent() != null) + terrains.RemoveAt(i); + } + convertibleObjects.AddRange(terrains); } - m_physXAssets.AddRange(terrains); - */ convertibleObjects.AddRange( FindRigidbodyData( prefabObject ) ); From 3459b48e8b29dbbb53e99dc9b6d50d974fbbffb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Tue, 3 Dec 2024 16:37:08 +0100 Subject: [PATCH 10/12] Constraints and terrainpager --- .../Windows/ConvertPhysXToAGXWindow.cs | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index 7b4e8b25..ca6a4ab9 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -65,6 +65,7 @@ public static ConvertPhysXToAGXWindow Open() var window = GetWindow( false, "Convert Components from PhysX to AGX", true ); + window.Repopulate(); return window; } @@ -106,6 +107,9 @@ private List GatherObjects( GameObject prefabObject = null ) foreach ( var type in m_basicColliderTypes ) convertibleObjects.AddRange( FindColliderAssetData( type, PhysXType.Collider, prefabObject ) ); + foreach ( var type in m_constraintTypes ) + convertibleObjects.AddRange( FindConstraintAssetData( type, PhysXType.Constraint, prefabObject ) ); + #if UNITY_2023_1_OR_NEWER bool foundTerrainPager = FindObjectsByType( typeof( AGXUnity.Model.DeformableTerrainPager ), FindObjectsInactive.Include, FindObjectsSortMode.None ).Length > 0; #else @@ -114,7 +118,7 @@ private List GatherObjects( GameObject prefabObject = null ) if (foundTerrainPager) { - Debug.Log("Found at least one Deformable Terrain Pager, assuming Terrain Colliders to be part of that system. If there are separate terrains that should have Terrain Colliders, add manually."); + Debug.Log("Convert PhysX to AGX Tool: Found at least one Deformable Terrain Pager, assuming Terrain Colliders to be part of that system. If there are separate terrains that should have Terrain Colliders, add manually."); } else { @@ -328,6 +332,34 @@ private List FindColliderAssetData( Type type, PhysXType physXType, G } ).ToList(); } + private List FindConstraintAssetData( Type type, PhysXType physXType, GameObject prefabObject = null ) + { + bool sceneMode = prefabObject == null; + + UnityEngine.Object[] components; + if ( sceneMode ) { +#if UNITY_2023_1_OR_NEWER + components = FindObjectsByType( type, FindObjectsInactive.Include, FindObjectsSortMode.None ); +#else + components = FindObjectsOfType( type, true ); +#endif + } + else { + components = prefabObject.GetComponentsInChildren( type, true ); + } + + return components.Select( o => { + var data = CreateInstance(); + data.Convert = false; + data.type = type; + data.physXType = physXType; + data.gameObject = ( (UnityEngine.Component)o ).gameObject; + data.Updatable = false; + + return data; + } ).ToList(); + } + private void UpdateColors() { for ( int i = 0; i < m_tableRows.Count(); i++ ) { @@ -703,7 +735,6 @@ private void ConvertSelected() ConvertObjects( m_physXAssets ); } - // TODO: possibly remove Undo, it won't work using ( new UndoCollapseBlock( "Convert Selected Prefab Assets" ) ) { foreach ( var prefab in m_prefabPhysXAssets ) { if ( !prefab.Convert ) @@ -855,7 +886,12 @@ private void ConvertCollider( AssetData asset ) case "UnityEngine.TerrainCollider": Undo.DestroyObjectImmediate( newObject ); - Undo.AddComponent( asset.gameObject ); + var terrain = asset.gameObject.GetComponent(); + if (terrain.leftNeighbor != null || terrain.rightNeighbor != null || + terrain.topNeighbor != null || terrain.bottomNeighbor != null) + Undo.AddComponent( asset.gameObject ); + else + Undo.AddComponent( asset.gameObject ); break; } } From b529866f95780bd2edd4c8429c3b522ac7d595fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rk?= Date: Thu, 5 Dec 2024 13:20:59 +0100 Subject: [PATCH 11/12] Bug fix for lod meshes and instructions adjustment --- .../Windows/ConvertPhysXToAGXWindow.cs | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index ca6a4ab9..5ab4bf9f 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -544,7 +544,11 @@ private void CreateGUI() { rootVisualElement.SetPadding( 19, 15, 15, 15 ); - var RPLabel = new Label($"This utility attempts to convert PhysX Components to corresponding AGX Components."); + var RPLabel = new Label($"Convenience utility attempting to convert simple PhysX assets to corresponding AGX assets. Intended for colliders and rigid bodies. Refresh the view after the scene changes."); + RPLabel.style.whiteSpace = WhiteSpace.Normal; // Enables text wrapping + RPLabel.style.flexGrow = 1; // Ensures the label grows with its container + RPLabel.style.flexShrink = 1; // Allows it to shrink if needed + RPLabel.style.overflow = Overflow.Hidden; // Prevents text from overflowing the container RPLabel.style.marginBottom = 5; rootVisualElement.Add( RPLabel ); @@ -592,7 +596,7 @@ private VisualElement CreateSceneObjectBox() sceneBox.SetBorderRadius( 5 ); sceneBox.SetPadding( 5 ); - var description = new Label( "PhysXObjects in Scene. GameObjects with multiple matching Components can appear on multiple lines." ); + var description = new Label( "PhysX GameObjects in the open main scene. GameObjects with multiple matching Components can appear on multiple lines." ); description.style.whiteSpace = WhiteSpace.Normal; description.style.marginBottom = 10; sceneBox.Add( description ); @@ -608,12 +612,14 @@ private VisualElement CreateSceneObjectBox() numAssets.style.flexDirection = FlexDirection.Row; numAssets.style.alignItems = Align.Center; numAssets.style.unityTextAlign = TextAnchor.MiddleLeft; - + numAssets.style.flexGrow = 1; + numAssets.style.justifyContent = Justify.FlexEnd; numAssets.Add( new Label() { text = "Upgradable assets: " } ); var image = new Image() { image = m_statusIcons[ 0 ] }; image.style.width = 20; image.style.height = 20; + image.style.marginLeft = 5; numAssets.Add( image ); var lab = new Label() { text = $" {(m_physXAssets != null ? m_physXAssets.Where( a => a.Updatable).Count() : 0)}" }; @@ -651,7 +657,7 @@ private VisualElement CreatePrefabObjectBox() return prefabBox; // Return early since prefab conversion is disabled in Play mode } - var description = new Label("PhysXObjects in Prefabs. Lists prefabs with convertible components. Note: You cannot undo this convert action!"); + var description = new Label("Prefabs containing PhysX eligible components. Note: Conversion on prefabs cannot be undone!"); description.style.whiteSpace = WhiteSpace.Normal; description.style.marginBottom = 10; prefabBox.Add( description ); @@ -680,7 +686,6 @@ private VisualElement CreatePrefabObjectBox() image.style.width = 20; image.style.height = 20; image.style.marginLeft = 5; - image.style.marginRight = 5; numAssets.Add( image ); var label = new Label() { text = $" {(m_prefabPhysXAssets != null ? m_prefabPhysXAssets.Where( a => a.Updatable).Count() : 0)}" }; @@ -693,8 +698,9 @@ private VisualElement CreatePrefabObjectBox() prefabBox.Add( header ); var scrolledTable = new ScrollView(); - foreach ( var prefabData in m_prefabPhysXAssets ) // Populate rows with prefabs - scrolledTable.Add( CreatePrefabRowUI( prefabData ) ); + if (m_prefabPhysXAssets != null) + foreach ( var prefabData in m_prefabPhysXAssets ) // Populate rows with prefabs + scrolledTable.Add( CreatePrefabRowUI( prefabData ) ); prefabBox.Add( scrolledTable ); return prefabBox; @@ -713,7 +719,7 @@ private void Repopulate( bool gatherAssets = true ) private void ConvertObjects( List assets ) { foreach ( var asset in assets ) { - if ( !asset.Convert ) + if ( !asset.Convert || !asset.Updatable ) continue; if ( asset.physXType == PhysXType.Collider ) @@ -872,12 +878,24 @@ private void ConvertCollider( AssetData asset ) case "UnityEngine.MeshCollider": var physXMesh = asset.gameObject.GetComponent(); - var agxMesh = Undo.AddComponent(newObject); + var agxMesh = Undo.AddComponent(newObject);// newObject.name = "AGXUnity.Collide.Mesh"; newObject.transform.localPosition = Vector3.zero; - agxMesh.AddSourceObject( physXMesh.sharedMesh ); + if (physXMesh.sharedMesh.isReadable) + { + agxMesh.AddSourceObject( physXMesh.sharedMesh ); + } + else + { + Mesh readableMesh = new Mesh(); + readableMesh.vertices = physXMesh.sharedMesh.vertices; + readableMesh.triangles = physXMesh.sharedMesh.triangles; + agxMesh.AddSourceObject( readableMesh ); + + Undo.RegisterCompleteObjectUndo(readableMesh, "Create Readable Mesh"); + } agxMesh.IsSensor = physXMesh.isTrigger; From 10cade04a9e5d8ae196b46b191dbaab0d2ed9c8e Mon Sep 17 00:00:00 2001 From: Filip Henningsson Date: Fri, 26 Sep 2025 16:32:47 +0200 Subject: [PATCH 12/12] Fix some review comments --- Editor/AGXUnityEditor/Menus/TopMenu.cs | 10 +- .../Windows/ConvertPhysXToAGXWindow.cs | 94 ++++++++----------- 2 files changed, 42 insertions(+), 62 deletions(-) diff --git a/Editor/AGXUnityEditor/Menus/TopMenu.cs b/Editor/AGXUnityEditor/Menus/TopMenu.cs index 090cecd9..f1d98e34 100644 --- a/Editor/AGXUnityEditor/Menus/TopMenu.cs +++ b/Editor/AGXUnityEditor/Menus/TopMenu.cs @@ -4,7 +4,6 @@ using AGXUnity.Sensor; using AGXUnity.Utils; using UnityEditor; -using UnityEditor.SceneManagement; using UnityEngine; using Mesh = AGXUnity.Collide.Mesh; using Plane = AGXUnity.Collide.Plane; @@ -31,13 +30,12 @@ private static GameObject CreateShape( MenuCommand command ) var views = SceneView.sceneViews; if ( parent != null ) go.transform.SetParent( parent.transform, false ); - else if (SceneView.sceneViews.Count > 0) - { + else if ( SceneView.sceneViews.Count > 0 ) { var view = SceneView.sceneViews[0] as SceneView; - if (view != null) - view.MoveToView(go.transform); + if ( view != null ) + view.MoveToView( go.transform ); } - + AGXUnity.Rendering.ShapeVisual.Create( go.GetComponent() ); Undo.RegisterCreatedObjectUndo( go, "shape" ); diff --git a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs index 5ab4bf9f..e0bc7654 100644 --- a/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs +++ b/Editor/AGXUnityEditor/Windows/ConvertPhysXToAGXWindow.cs @@ -1,11 +1,11 @@ +using AGXUnityEditor.UIElements; using AGXUnityEditor.Utils; +using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; -using AGXUnityEditor.UIElements; -using System; namespace AGXUnityEditor.Windows { @@ -116,19 +116,16 @@ private List GatherObjects( GameObject prefabObject = null ) bool foundTerrainPager = FindObjectsOfType( typeof( AGXUnity.Model.DeformableTerrainPager ), true ).Length > 0; #endif - if (foundTerrainPager) - { - Debug.Log("Convert PhysX to AGX Tool: Found at least one Deformable Terrain Pager, assuming Terrain Colliders to be part of that system. If there are separate terrains that should have Terrain Colliders, add manually."); + if ( foundTerrainPager ) { + Debug.Log( "Convert PhysX to AGX Tool: Found at least one Deformable Terrain Pager, assuming Terrain Colliders to be part of that system. If there are separate terrains that should have Terrain Colliders, add manually." ); } - else - { + else { var terrains = FindColliderAssetData(typeof(TerrainCollider), PhysXType.Collider); - for (int i = terrains.Count; --i >= 0;) - { - if (terrains[i].gameObject.GetComponent() != null) - terrains.RemoveAt(i); + for ( int i = terrains.Count; --i >= 0; ) { + if ( terrains[ i ].gameObject.GetComponent() != null ) + terrains.RemoveAt( i ); } - convertibleObjects.AddRange(terrains); + convertibleObjects.AddRange( terrains ); } convertibleObjects.AddRange( FindRigidbodyData( prefabObject ) ); @@ -176,26 +173,20 @@ private void GatherPrefabData() // The aptly named Physic Material apparently changed its name to the less Danish-English Physics Material #if UNITY_6000_0_OR_NEWER - string[] pmPaths = AssetDatabase.FindAssets("t:PhysicsMaterial") - .Select(AssetDatabase.GUIDToAssetPath) - .ToArray(); + IEnumerable pmPaths = AssetDatabase.FindAssets("t:PhysicsMaterial") + .Select(AssetDatabase.GUIDToAssetPath); foreach ( var pmPath in pmPaths ) { PhysicsMaterial physicMaterial = AssetDatabase.LoadAssetAtPath(pmPath); - - var prefabData = PrefabData.CreateInstance(physicMaterial.name, pmPath, 1, updatable: false); - m_prefabPhysXAssets.Add( prefabData ); - } #else - string[] pmPaths = AssetDatabase.FindAssets("t:PhysicMaterial") - .Select(AssetDatabase.GUIDToAssetPath) - .ToArray(); + IEnumerable pmPaths= AssetDatabase.FindAssets("t:PhysicMaterial") + .Select(AssetDatabase.GUIDToAssetPath); foreach ( var pmPath in pmPaths ) { - PhysicsMaterial physicMaterial = AssetDatabase.LoadAssetAtPath(pmPath); + PhysicMaterial physicMaterial = AssetDatabase.LoadAssetAtPath(pmPath); +#endif var prefabData = PrefabData.CreateInstance(physicMaterial.name, pmPath, 1, updatable: false); m_prefabPhysXAssets.Add( prefabData ); } -#endif m_prefabPhysXAssets = m_prefabPhysXAssets.OrderBy( p => p.Name ).ToList(); } @@ -220,7 +211,7 @@ private List FindRigidbodyData( GameObject prefabObject = null ) #if UNITY_2023_1_OR_NEWER components = FindObjectsByType( typeof( Rigidbody ), FindObjectsInactive.Include, FindObjectsSortMode.None ); #else - components = FindObjectsOfType(typeof(RigidBody), true); + components = FindObjectsOfType( typeof( Rigidbody ), true ); #endif } else { @@ -312,7 +303,7 @@ private List FindColliderAssetData( Type type, PhysXType physXType, G #if UNITY_2023_1_OR_NEWER components = FindObjectsByType( type, FindObjectsInactive.Include, FindObjectsSortMode.None ); #else - components = FindObjectsOfType(type, true); + components = FindObjectsOfType( type, true ); #endif } else { @@ -349,15 +340,15 @@ private List FindConstraintAssetData( Type type, PhysXType physXType, } return components.Select( o => { - var data = CreateInstance(); - data.Convert = false; - data.type = type; - data.physXType = physXType; - data.gameObject = ( (UnityEngine.Component)o ).gameObject; - data.Updatable = false; + var data = CreateInstance(); + data.Convert = false; + data.type = type; + data.physXType = physXType; + data.gameObject = ( (UnityEngine.Component)o ).gameObject; + data.Updatable = false; - return data; - } ).ToList(); + return data; + } ).ToList(); } private void UpdateColors() @@ -689,7 +680,7 @@ private VisualElement CreatePrefabObjectBox() numAssets.Add( image ); var label = new Label() { text = $" {(m_prefabPhysXAssets != null ? m_prefabPhysXAssets.Where( a => a.Updatable).Count() : 0)}" }; - label.style.flexGrow = 0; + label.style.flexGrow = 0; label.style.flexShrink = 0; numAssets.Add( label ); @@ -698,7 +689,7 @@ private VisualElement CreatePrefabObjectBox() prefabBox.Add( header ); var scrolledTable = new ScrollView(); - if (m_prefabPhysXAssets != null) + if ( m_prefabPhysXAssets != null ) foreach ( var prefabData in m_prefabPhysXAssets ) // Populate rows with prefabs scrolledTable.Add( CreatePrefabRowUI( prefabData ) ); @@ -746,19 +737,12 @@ private void ConvertSelected() if ( !prefab.Convert ) continue; - GameObject prefabObject = PrefabUtility.LoadPrefabContents(prefab.Path); - if ( prefabObject == null ) + var scope = new PrefabUtility.EditPrefabContentsScope( prefab.Path ); + if ( scope.prefabContentsRoot == null ) continue; - var objects = GatherObjects(prefabObject); - try { - ConvertObjects( objects ); - - PrefabUtility.SaveAsPrefabAsset(prefabObject, prefab.Path); - } - finally { - PrefabUtility.UnloadPrefabContents( prefabObject ); - } + var objects = GatherObjects(scope.prefabContentsRoot); + ConvertObjects( objects ); Debug.Log( $"Converted prefab: {prefab.Name}, objects: {objects.Count}" ); } @@ -793,7 +777,7 @@ private void ConvertRigidbody( AssetData asset ) if ( !physXRb.automaticInertiaTensor ) { agxRB.MassProperties.InertiaDiagonal.UseDefault = false; - agxRB.MassProperties.InertiaDiagonal.Value = physXRb.centerOfMass; + agxRB.MassProperties.InertiaDiagonal.Value = physXRb.inertiaTensor; } if ( physXRb.isKinematic ) @@ -883,18 +867,16 @@ private void ConvertCollider( AssetData asset ) newObject.name = "AGXUnity.Collide.Mesh"; newObject.transform.localPosition = Vector3.zero; - if (physXMesh.sharedMesh.isReadable) - { + if ( physXMesh.sharedMesh.isReadable ) { agxMesh.AddSourceObject( physXMesh.sharedMesh ); } - else - { + else { Mesh readableMesh = new Mesh(); readableMesh.vertices = physXMesh.sharedMesh.vertices; readableMesh.triangles = physXMesh.sharedMesh.triangles; agxMesh.AddSourceObject( readableMesh ); - - Undo.RegisterCompleteObjectUndo(readableMesh, "Create Readable Mesh"); + + Undo.RegisterCreatedObjectUndo( readableMesh, "Create Readable Mesh" ); } agxMesh.IsSensor = physXMesh.isTrigger; @@ -905,8 +887,8 @@ private void ConvertCollider( AssetData asset ) case "UnityEngine.TerrainCollider": Undo.DestroyObjectImmediate( newObject ); var terrain = asset.gameObject.GetComponent(); - if (terrain.leftNeighbor != null || terrain.rightNeighbor != null || - terrain.topNeighbor != null || terrain.bottomNeighbor != null) + if ( terrain.leftNeighbor != null || terrain.rightNeighbor != null || + terrain.topNeighbor != null || terrain.bottomNeighbor != null ) Undo.AddComponent( asset.gameObject ); else Undo.AddComponent( asset.gameObject );