diff --git a/AGXUnity/Model/DeformableTerrain.cs b/AGXUnity/Model/DeformableTerrain.cs index e2a2ffbc..aa3c3b9a 100644 --- a/AGXUnity/Model/DeformableTerrain.cs +++ b/AGXUnity/Model/DeformableTerrain.cs @@ -366,10 +366,19 @@ public override void SetHeight( int x, int y, float height ) if ( xstart + width >= resolution || xstart < 0 || ystart + height >= resolution || ystart < 0 ) throw new ArgumentOutOfRangeException( "", $"Requested height patch with start ({xstart},{ystart}) and size ({width},{height}) extends outside of the terrain bounds [0,{TerrainDataResolution - 1}]" ); - if ( Native == null ) - return TerrainData.GetHeights( xstart, ystart, width, height ); + float scale = TerrainData.size.y; + float [,] heights; + if ( Native == null ) { + heights = TerrainData.GetHeights( xstart, ystart, width, height ); + for ( int y = 0; y < height; y++ ) { + for ( int x = 0; x < width; x++ ) { + heights[ y, x ] = heights[ y, x ] * scale; + } + } + return heights; + } - float [,] heights = new float[height,width]; + heights = new float[ height, width ]; for ( int y = 0; y < height; y++ ) { for ( int x = 0; x < width; x++ ) { agx.Vec2i idx = new agx.Vec2i( resolution - 1 - x - xstart, resolution - 1 - y - ystart); diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 00000000..31895d4e --- /dev/null +++ b/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5b9e423d8250ee741aeeacce3ef39adb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor.meta b/Tests/Editor.meta new file mode 100644 index 00000000..25a1a876 --- /dev/null +++ b/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 51424e31a47243f43902a4ebc8d358d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/AGXUnityEditorTests.asmdef b/Tests/Editor/AGXUnityEditorTests.asmdef new file mode 100644 index 00000000..150227f6 --- /dev/null +++ b/Tests/Editor/AGXUnityEditorTests.asmdef @@ -0,0 +1,26 @@ +{ + "name": "AGXUnityEditorTests", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "AGXUnity", + "AGXUnityEditor", + "TestingCommon" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Editor/AGXUnityEditorTests.asmdef.meta b/Tests/Editor/AGXUnityEditorTests.asmdef.meta new file mode 100644 index 00000000..d2dc6baa --- /dev/null +++ b/Tests/Editor/AGXUnityEditorTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a25c20006c4822349a40f5de6294e837 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/EditorMetadataTests.cs b/Tests/Editor/EditorMetadataTests.cs new file mode 100644 index 00000000..6c1dd75e --- /dev/null +++ b/Tests/Editor/EditorMetadataTests.cs @@ -0,0 +1,62 @@ +using AGXUnity; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +using Assembly = System.Reflection.Assembly; + +namespace AGXUnityTesting +{ + public class EditorMetadataTests + { + [Test] + public void ScriptsHaveHelpURLs() + { + var asm = Assembly.GetAssembly(typeof(RigidBody)); + List missing = new List(); + foreach ( var t in asm.ExportedTypes ) { + if ( + ( + t.IsSubclassOf( typeof( ScriptComponent ) ) || + t.IsSubclassOf( typeof( ScriptAsset ) ) + ) && !t.IsAbstract ) { + var help = t.GetCustomAttribute(false); + var hide = t.GetCustomAttribute(); + if ( help == null && hide == null ) + missing.Add( t ); + } + } + + if ( missing.Count > 0 ) { + var errStr = $"The following classes are missing the HelpURL Attribute: \n"; + foreach ( var t in missing ) { + errStr += t.Name + "\n"; + } + Assert.Fail( errStr ); + } + } + + private void CheckScriptIconsInDirectory( string path ) + { + if ( System.IO.Directory.Exists( path ) ) { + foreach ( var subfile in System.IO.Directory.EnumerateFiles( path ) ) + CheckScriptIconsInDirectory( subfile ); + } + else if ( path.EndsWith( ".cs" ) ) { + var mi = AssetImporter.GetAtPath( path ) as MonoImporter; + Assert.NotNull( mi.GetIcon(), $"Script file '{path}' has no icon" ); + } + + } + + [Test] + public void ScriptsHaveIcons() + { + var sourceDir = AGXUnityEditor.IO.Utils.AGXUnitySourceDirectory; + CheckScriptIconsInDirectory( sourceDir ); + } + } +} diff --git a/Tests/Editor/EditorMetadataTests.cs.meta b/Tests/Editor/EditorMetadataTests.cs.meta new file mode 100644 index 00000000..b4e2b593 --- /dev/null +++ b/Tests/Editor/EditorMetadataTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f28a2624eed79b840aa132963ddd3457 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/TerrainGetSetTests.cs b/Tests/Editor/TerrainGetSetTests.cs new file mode 100644 index 00000000..5b773c71 --- /dev/null +++ b/Tests/Editor/TerrainGetSetTests.cs @@ -0,0 +1,123 @@ +using AGXUnity.Model; +using NUnit.Framework; +using UnityEngine; + +namespace AGXUnityTesting +{ + public class TerrainGetSetTests + { + private DeformableTerrain testTerrain; + private Terrain unityTerrain; + + private const float HEIGHT_DELTA = 0.0005f; + + private float GetHeight( int x, int y, bool normalize = false ) + { + var heightScale = normalize ? unityTerrain.terrainData.size.y : 1; + var res = unityTerrain.terrainData.heightmapResolution; + return ( 2 + Mathf.Sin( (float)y / res ) + Mathf.Cos( (float)x / res ) ) / heightScale; + } + + private float RescaleUnityHeight( float height, bool normalized = false ) + { + return height * ( normalized ? unityTerrain.terrainData.size.y : 1.0f ); + } + + [SetUp] + public void SetupWireScene() + { + GameObject go = new GameObject("Test terrain"); + + unityTerrain = go.AddComponent(); + unityTerrain.terrainData = new TerrainData(); + unityTerrain.terrainData.size = new Vector3( 30, 20, 30 ); + unityTerrain.terrainData.heightmapResolution = 33; + + float [,] heights = new float[33,33]; + for ( int y = 0; y < 33; y++ ) + for ( int x = 0; x < 33; x++ ) + heights[ y, x ] = GetHeight( x, y, true ); + + unityTerrain.terrainData.SetHeights( 0, 0, heights ); + + testTerrain = go.AddComponent(); + testTerrain.MaximumDepth = 2; + } + + [Test] + public void TestTerrainGetSingleHeight() + { + var height = testTerrain.GetHeight( 16, 16 ); + var expected = 2 + Mathf.Sin( (float)16 / 33 ) + Mathf.Cos( (float)16 /33 ); + Assert.AreEqual( expected, height, HEIGHT_DELTA ); + } + + [Test] + public void TestTerrainGetMultipleHeights() + { + var height = testTerrain.GetHeights( 10, 10, 10, 10 ); + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( GetHeight( x+10, y+10 ), height[ y, x ], HEIGHT_DELTA ); + } + + [Test] + public void TestSetSingleHeight() + { + testTerrain.SetHeight( 16, 16, 5f ); + var result = testTerrain.GetHeight( 16, 16 ); + + Assert.AreEqual( 5f, result, HEIGHT_DELTA ); + } + + [Test] + public void TestSetSingleHeightIsPropagated() + { + testTerrain.SetHeight( 16, 16, 5f ); + + var result = unityTerrain.terrainData.GetHeight( 16, 16 ); + + Assert.AreEqual( 5f, RescaleUnityHeight( result ), HEIGHT_DELTA ); + } + + [Test] + public void TestSetMultipleHeights() + { + var source = new float[10,10]; + var expected = new float[10,10]; + for ( int y = 0; y < 10; y++ ) { + for ( int x = 0; x < 10; x++ ) { + source[ y, x ] = GetHeight( x, y ); + expected[ y, x ] = GetHeight( x, y ); + } + } + + testTerrain.SetHeights( 10, 10, source ); + var results = testTerrain.GetHeights( 10, 10, 10, 10 ); + + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( expected[ y, x ], results[ y, x ], HEIGHT_DELTA ); + } + + [Test] + public void TestSetMultipleHeightsArePropagated() + { + var source = new float[10,10]; + var expected = new float[10,10]; + for ( int y = 0; y < 10; y++ ) { + for ( int x = 0; x < 10; x++ ) { + source[ y, x ] = GetHeight( x, y ); + expected[ y, x ] = GetHeight( x, y ); + } + } + testTerrain.SetHeights( 10, 10, source ); + + var results = unityTerrain.terrainData.GetHeights( 10, 10, 10, 10 ); + + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( expected[ y, x ], RescaleUnityHeight( results[ y, x ], true ), HEIGHT_DELTA ); + } + } +} diff --git a/Tests/Editor/TerrainGetSetTests.cs.meta b/Tests/Editor/TerrainGetSetTests.cs.meta new file mode 100644 index 00000000..a4375b7a --- /dev/null +++ b/Tests/Editor/TerrainGetSetTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7aa1cecdb3ff2e34bb54c231221569b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta new file mode 100644 index 00000000..cde51799 --- /dev/null +++ b/Tests/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f21ede6780a0f5c45a30c855577f21f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/AGXUnityTests.asmdef b/Tests/Runtime/AGXUnityTests.asmdef new file mode 100644 index 00000000..9043b097 --- /dev/null +++ b/Tests/Runtime/AGXUnityTests.asmdef @@ -0,0 +1,27 @@ +{ + "name": "AGXUnityTests", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "AGXUnity", + "TestingCommon" + ], + "includePlatforms": [ + "Editor", + "LinuxStandalone64", + "WindowsStandalone64" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/AGXUnityTests.asmdef.meta b/Tests/Runtime/AGXUnityTests.asmdef.meta new file mode 100644 index 00000000..a9d02191 --- /dev/null +++ b/Tests/Runtime/AGXUnityTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5e672b1d56459394b95998a38391af3c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/TerrainGetSetTests.cs b/Tests/Runtime/TerrainGetSetTests.cs new file mode 100644 index 00000000..b44ea4b5 --- /dev/null +++ b/Tests/Runtime/TerrainGetSetTests.cs @@ -0,0 +1,130 @@ +using AGXUnity.Model; +using NUnit.Framework; +using System.Collections; +using UnityEngine; +using UnityEngine.TestTools; + +namespace AGXUnityTesting +{ + public class TerrainGetSetTests + { + private DeformableTerrain testTerrain; + private Terrain unityTerrain; + + private const float HEIGHT_DELTA = 0.0005f; + + private float GetHeight( int x, int y, bool normalize = false ) + { + var heightScale = normalize ? unityTerrain.terrainData.size.y : 1; + var res = unityTerrain.terrainData.heightmapResolution; + return ( 2 + Mathf.Sin( (float)y / res ) + Mathf.Cos( (float)x / res ) ) / heightScale; + } + + private float RescaleUnityHeight( float height, bool normalized = false ) + { + return height * ( normalized ? unityTerrain.terrainData.size.y : 1.0f ) - testTerrain.MaximumDepth; + } + + [UnitySetUp] + public IEnumerator SetupWireScene() + { + GameObject go = new GameObject("Test terrain"); + + unityTerrain = go.AddComponent(); + unityTerrain.terrainData = new TerrainData(); + unityTerrain.terrainData.size = new Vector3( 30, 20, 30 ); + unityTerrain.terrainData.heightmapResolution = 33; + + float [,] heights = new float[33,33]; + for ( int y = 0; y < 33; y++ ) + for ( int x = 0; x < 33; x++ ) + heights[ y, x ] = GetHeight( x, y, true ); + + unityTerrain.terrainData.SetHeights( 0, 0, heights ); + + testTerrain = go.AddComponent(); + testTerrain.MaximumDepth = 2; + + yield return TestUtils.WaitUntilLoaded(); + } + + [Test] + public void TestTerrainGetSingleHeight() + { + var height = testTerrain.GetHeight( 16, 16 ); + var expected = 2 + Mathf.Sin( (float)16 / 33 ) + Mathf.Cos( (float)16 /33 ); + Assert.AreEqual( expected, height, HEIGHT_DELTA ); + } + + [Test] + public void TestTerrainGetMultipleHeights() + { + var height = testTerrain.GetHeights( 10, 10, 10, 10 ); + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( GetHeight( x+10, y+10 ), height[ y, x ], HEIGHT_DELTA ); + } + + [Test] + public void TestSetSingleHeight() + { + testTerrain.SetHeight( 16, 16, 5f ); + var result = testTerrain.GetHeight( 16, 16 ); + + Assert.AreEqual( 5f, result ); + } + + [UnityTest] + public IEnumerator TestSetSingleHeightIsPropagated() + { + testTerrain.SetHeight( 16, 16, 5f ); + + yield return TestUtils.SimulateSeconds( 0.1f ); + + var result = unityTerrain.terrainData.GetHeight( 16, 16 ); + + Assert.AreEqual( 5f, RescaleUnityHeight( result ), HEIGHT_DELTA ); + } + + [Test] + public void TestSetMultipleHeights() + { + var source = new float[10,10]; + var expected = new float[10,10]; + for ( int y = 0; y < 10; y++ ) { + for ( int x = 0; x < 10; x++ ) { + source[ y, x ] = GetHeight( x, y ); + expected[ y, x ] = GetHeight( x, y ); + } + } + + testTerrain.SetHeights( 10, 10, source ); + var results = testTerrain.GetHeights( 10, 10, 10, 10 ); + + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( expected[ y, x ], results[ y, x ], HEIGHT_DELTA ); + } + + [UnityTest] + public IEnumerator TestSetMultipleHeightsArePropagated() + { + var source = new float[10,10]; + var expected = new float[10,10]; + for ( int y = 0; y < 10; y++ ) { + for ( int x = 0; x < 10; x++ ) { + source[ y, x ] = GetHeight( x, y ); + expected[ y, x ] = GetHeight( x, y ); + } + } + testTerrain.SetHeights( 10, 10, source ); + yield return TestUtils.SimulateSeconds( 0.1f ); + + var results = unityTerrain.terrainData.GetHeights( 10, 10, 10, 10 ); + + for ( int y = 0; y < 10; y++ ) + for ( int x = 0; x < 10; x++ ) + Assert.AreEqual( expected[ y, x ], RescaleUnityHeight( results[ y, x ], true ), HEIGHT_DELTA ); + } + } +} diff --git a/Tests/Runtime/TerrainGetSetTests.cs.meta b/Tests/Runtime/TerrainGetSetTests.cs.meta new file mode 100644 index 00000000..ae45600c --- /dev/null +++ b/Tests/Runtime/TerrainGetSetTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4caf4bc67f8c6242b077e85b291cdf5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/TestUtils.cs b/Tests/TestUtils.cs new file mode 100644 index 00000000..18983e88 --- /dev/null +++ b/Tests/TestUtils.cs @@ -0,0 +1,62 @@ +#if UNITY_STANDALONE +#define TEST_REALTIME_SYNC +#endif + +#if TEST_NO_REALTIME_SYNC +#undef TEST_REALTIME_SYNC +#endif + +using AGXUnity; +using System.Collections; +using UnityEngine; + +namespace AGXUnityTesting +{ + public static class TestUtils + { + public static IEnumerator WaitUntilLoaded() + { + if ( !Application.isPlaying ) + Debug.LogError( "TestUtils are not supported in edit-mode" ); + else { + yield return new WaitForEndOfFrame(); +#if !TEST_REALTIME_SYNC + Simulation.Instance.AutoSteppingMode = Simulation.AutoSteppingModes.Disabled; +#endif + } + } + + public static IEnumerator SimulateTo( float time ) + { + if ( !Application.isPlaying ) + Debug.LogError( "TestUtils are not supported in edit-mode" ); + else { + yield return WaitUntilLoaded(); + while ( Simulation.Instance.Native.getTimeStamp() < time ) { +#if TEST_REALTIME_SYNC + yield return new WaitForFixedUpdate(); +#else + Simulation.Instance.DoStep(); +#endif + } + } + } + + public static IEnumerator SimulateSeconds( float time ) + { + if ( !Application.isPlaying ) + Debug.LogError( "TestUtils are not supported in edit-mode" ); + else { + yield return WaitUntilLoaded(); + var targetTime = Simulation.Instance.Native.getTimeStamp() + time; + while ( Simulation.Instance.Native.getTimeStamp() < targetTime ) { +#if TEST_REALTIME_SYNC + yield return new WaitForFixedUpdate(); +#else + Simulation.Instance.DoStep(); +#endif + } + } + } + } +} diff --git a/Tests/TestUtils.cs.meta b/Tests/TestUtils.cs.meta new file mode 100644 index 00000000..022132c1 --- /dev/null +++ b/Tests/TestUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b00a00880b81fec4dabfe215406c6c16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/TestingCommon.asmdef b/Tests/TestingCommon.asmdef new file mode 100644 index 00000000..102d7c77 --- /dev/null +++ b/Tests/TestingCommon.asmdef @@ -0,0 +1,16 @@ +{ + "name": "TestingCommon", + "rootNamespace": "", + "references": [ + "GUID:d5acb60c7b83ed24894b00d550fbf2d3" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/TestingCommon.asmdef.meta b/Tests/TestingCommon.asmdef.meta new file mode 100644 index 00000000..8203d667 --- /dev/null +++ b/Tests/TestingCommon.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ab8f6764dff5fe8408e030ee3ce77445 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: