diff --git a/Assets/01_Scenes/MyProject/GameScene.unity b/Assets/01_Scenes/MyProject/GameScene.unity
index 5ccd2df2..1032445c 100644
--- a/Assets/01_Scenes/MyProject/GameScene.unity
+++ b/Assets/01_Scenes/MyProject/GameScene.unity
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5cc238f3a331986a64f912c5ae7662f7ef12ebb179c8aaae99a8a0fddfddb035
-size 13874110
+oid sha256:bc2e73daec0ff742799e08a87f86a64c22301acfc2a95547e78d6c991ad01db6
+size 13928671
diff --git a/Assets/02_Scripts/Item/ItemData.cs b/Assets/02_Scripts/Item/ItemData.cs
index 9dc1238d..99e0746a 100644
--- a/Assets/02_Scripts/Item/ItemData.cs
+++ b/Assets/02_Scripts/Item/ItemData.cs
@@ -51,5 +51,6 @@ public enum ProductGroup
None,
ChocoBar,
PotatoChip,
+ GreenBeans,
}
}
diff --git a/Assets/02_Scripts/UI/ItemInfoPanel.cs b/Assets/02_Scripts/UI/ItemInfoPanel.cs
index e5658f83..834db23d 100644
--- a/Assets/02_Scripts/UI/ItemInfoPanel.cs
+++ b/Assets/02_Scripts/UI/ItemInfoPanel.cs
@@ -57,7 +57,7 @@ public void Show(ItemData data)
if (hasDiscount)
{
if (_discountPriceText != null) _discountPriceText.text = FormatPrice(data.FinalPrice);
- if (_discountRateText != null) _discountRateText.text = $"-{Mathf.RoundToInt(data.DiscountRate * 100f)}%";
+ if (_discountRateText != null) _discountRateText.text = $"{Mathf.RoundToInt(data.DiscountRate * 100f)}";
}
_root.SetActive(true);
diff --git a/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset b/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset
new file mode 100644
index 00000000..3432550d
--- /dev/null
+++ b/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6784bacdc71c1390522e6afb492e7bb1cc7c990fc1b007059214c38f8512313e
+size 4957
diff --git a/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset.meta b/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset.meta
new file mode 100644
index 00000000..07db1a92
--- /dev/null
+++ b/Assets/03_Models/Stylized_Hands/Poses/Pose_GrabSnack2.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3768d0f9519980b4b8c0f62966a56ab0
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/08_Data/Items/GreenBeans.asset b/Assets/08_Data/Items/GreenBeans.asset
new file mode 100644
index 00000000..9f473dc8
--- /dev/null
+++ b/Assets/08_Data/Items/GreenBeans.asset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b3e8fdcee8105574ab2328760bd532a0e6d7973af4d4e166343450a93282df15
+size 709
diff --git a/Assets/08_Data/Items/GreenBeans.asset.meta b/Assets/08_Data/Items/GreenBeans.asset.meta
new file mode 100644
index 00000000..725c20c9
--- /dev/null
+++ b/Assets/08_Data/Items/GreenBeans.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 736a87880695f4d47b1c732083f04582
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor.meta b/Assets/EasyColliderEditor.meta
new file mode 100644
index 00000000..4cf5df2e
--- /dev/null
+++ b/Assets/EasyColliderEditor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4152f82672157b240a0eb0b802584254
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor/DOTS.meta b/Assets/EasyColliderEditor/DOTS.meta
new file mode 100644
index 00000000..53cf0c1f
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: e9e109fb49381c540b4a5108479c269e
+folderAsset: yes
+timeCreated: 1626271522
+licenseType: Store
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf b/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf
new file mode 100644
index 00000000..ecae91ae
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:89d055cff278e61036ad1fcb43d66531569a2237be013de31be87daca16297fd
+size 138255
diff --git a/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf.meta b/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf.meta
new file mode 100644
index 00000000..8aa830d6
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 078b33d3bbdbd3f478bca270510b58aa
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/DOTS/EasyDOTSDocumentation.pdf
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/DOTS/Scripts.meta b/Assets/EasyColliderEditor/DOTS/Scripts.meta
new file mode 100644
index 00000000..e338d18d
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS/Scripts.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 28e4c31123bcd614b910d5cfe5c82e2e
+folderAsset: yes
+timeCreated: 1626290231
+licenseType: Store
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs b/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs
new file mode 100644
index 00000000..4535e8a3
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs
@@ -0,0 +1,18 @@
+#if (UNITY_EDITOR)
+
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+namespace ECE
+{
+ // This is just the empty class that get's overwritten to add DOTS support.
+ // This make it much simpler for future updates to also work with DOTS as
+ // the only thing it does is convert the colliders through the UI Button.
+ // by having the same class with required method, we don't need to adjust
+ // other scripts each update.
+ public class EasyColliderDOTS
+ {
+ public void OnInspectorGUI(EasyColliderEditor Editor) { }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs.meta b/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs.meta
new file mode 100644
index 00000000..9b543402
--- /dev/null
+++ b/Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: a8ac46b819edd88459956fa0b4832ff7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/DOTS/Scripts/EasyColliderDOTS.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf b/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf
new file mode 100644
index 00000000..e832fdc8
--- /dev/null
+++ b/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1489fe0b47e4b18aa2df1d69426d7469a88f7efe662fb0f37ecf365a6b90f7c9
+size 5063330
diff --git a/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf.meta b/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf.meta
new file mode 100644
index 00000000..7c73a805
--- /dev/null
+++ b/Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: b04cc4b7e03f58f46a6bd2a7b3fe4498
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/EasyColliderEditorDocumentation.pdf
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons.meta b/Assets/EasyColliderEditor/Icons.meta
new file mode 100644
index 00000000..60812467
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 723d55ae3f9555c42bcd3edb401a4613
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIBox32.png b/Assets/EasyColliderEditor/Icons/ECEUIBox32.png
new file mode 100644
index 00000000..94e34f25
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIBox32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f84a2424e992091f841f148dbf9f7698e17a4aec587d2265b88ef209f4517dc5
+size 19184
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIBox32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIBox32.png.meta
new file mode 100644
index 00000000..152bb8c3
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIBox32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: e998347e00b1629449691174d6ce75de
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 82ac149ea221fb545a0fb56f2203b797
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIBox32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png
new file mode 100644
index 00000000..fe73a9d3
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8539e0fa41b5f5edc6152068e5e48bee6180100821c1f18f558e58076bd9788f
+size 21792
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png.meta
new file mode 100644
index 00000000..f0656faa
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 480816d7d5b863f42984e0a47484f216
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: e7eeee5780c59014ea4b310dbd384852
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIBox32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png b/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png
new file mode 100644
index 00000000..d7f44eb1
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:58a6ed72e8a8e4135aa282d07ad765a7cbb4cd76943a76829cbbf4d7851d1dce
+size 19527
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png.meta
new file mode 100644
index 00000000..4668479f
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICapsule32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 1b9402ac744555345b9f6111ff8c4b8a
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: ec9b94295078e8e4989339a3c3f2c388
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUICapsule32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png
new file mode 100644
index 00000000..9bf6945c
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f1e162b48c7b9c635bc380831306f1fd42ae78e23f4ce97e16ecb5464a4ee104
+size 20242
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png.meta
new file mode 100644
index 00000000..c41ee13e
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 0ad3709299b6f5e44a2db34afc52ccd4
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: f27b277bd5d034d4097d222005f63399
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUICapsule32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png
new file mode 100644
index 00000000..5942d2df
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6a6643dffa6fa560129473eda461a5929a2bacdfe03ef64bca8c802bcd2117a8
+size 19797
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png.meta
new file mode 100644
index 00000000..952c430f
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 53b9b7a941c40804b8179b13ecf40b09
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: eaf3439ed7ce6624ca3aba5a5563f408
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png
new file mode 100644
index 00000000..bdd30d55
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:133ea62a996ccaf73b32d279d2df1f7da7453689c33a34ce26111d7b92cc8aca
+size 20493
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png.meta
new file mode 100644
index 00000000..ee7e530e
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: b0c31ecbc9bd28e4e8ac1e5e43ba307e
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 15c81a624ef7f0a46ac5c566730ebca8
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIConvexMesh32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png b/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png
new file mode 100644
index 00000000..d990c1b8
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:786212f9fe7987fae57797492635321c61a962ffb6506806edcc8bbe1fe6bb03
+size 21906
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png.meta
new file mode 100644
index 00000000..a6d316fe
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICylinder32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: ccbaf976cae4b064f9a918823e8f6552
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 4674b33d58a9a5d45b39418124f53ac9
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUICylinder32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png
new file mode 100644
index 00000000..43fd76d4
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8020279a429e85ecc71b90c3d7e2aeea1987937bab3060469b3460668a10f18d
+size 21879
diff --git a/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png.meta
new file mode 100644
index 00000000..a39fcb67
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 2d782a0c73983ee4b95417abf7be42ef
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 6d9f9948f75eff44b8243fe5f1bc1a83
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUICylinder32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png
new file mode 100644
index 00000000..70bc9026
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8f632e058d1d654ca617cb5ecb7fb59c5a9a041f674169df61d417884bcb259a
+size 20028
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png.meta
new file mode 100644
index 00000000..c8c0ad25
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 06f5537b1554be7429d39076a339a6e9
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 43dba5d881a539c46b514427a4041b89
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png
new file mode 100644
index 00000000..7b7a7eb5
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:caa8932a3a5f62bd8519c88964d100e45f6077cc7d7d2dd487f317ae3de9fc47
+size 22812
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png.meta
new file mode 100644
index 00000000..b76c8f2f
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 9759e644c16116c46ad7d928f57b00be
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 1fbf28e7ef18a624b882903ec37cf195
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIRotatedBox32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png
new file mode 100644
index 00000000..c9ba12df
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:204dd2f0c8ddd7de4c6ff94cb5428d6d2b94d901e60e40b140e5cbf677664a74
+size 19846
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png.meta
new file mode 100644
index 00000000..641b972d
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: b909a7c899938ca4d8c9705a045fe410
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: b586c269269556a418cca90db1e955c1
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png
new file mode 100644
index 00000000..7ae72fd9
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:be44f82fc5000d44c7ded1a85b683e425d36096bf65a6bb8e58b40eebd0343e6
+size 22709
diff --git a/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png.meta b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png.meta
new file mode 100644
index 00000000..683331ff
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 764a526c991bd194cbfc2f4319b1d8dc
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: fa24b5ba62ace4d47990cefe71a46da7
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUIRotatedCapsule32Merge.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUISphere32.png b/Assets/EasyColliderEditor/Icons/ECEUISphere32.png
new file mode 100644
index 00000000..29f20f35
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUISphere32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:324c2f03ab47ac8a532112d1fea317f7c194ba46459d0d2803980d138af14533
+size 19835
diff --git a/Assets/EasyColliderEditor/Icons/ECEUISphere32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUISphere32.png.meta
new file mode 100644
index 00000000..af3ea83f
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUISphere32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: e01e5ee2b0ff2214196190f59f373bac
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 50086ebfa2f74a447b9f73bcb4ebf480
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUISphere32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png b/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png
new file mode 100644
index 00000000..a0a5480d
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:88412a0728ab18ac35e99662c22c74915988850697786a092bf5eec4181f9088
+size 20552
diff --git a/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png.meta b/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png.meta
new file mode 100644
index 00000000..8754b445
--- /dev/null
+++ b/Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png.meta
@@ -0,0 +1,106 @@
+fileFormatVersion: 2
+guid: 950da02c7c566da49bd0785d2f903f0f
+TextureImporter:
+ fileIDToRecycleName: {}
+ externalObjects: {}
+ serializedVersion: 9
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: -1
+ aniso: -1
+ mipBias: -100
+ wrapU: 1
+ wrapV: 1
+ wrapW: -1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ platformSettings:
+ - serializedVersion: 2
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ - serializedVersion: 2
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ androidETC2FallbackOverride: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 76f0a0b7288f5c940b9cd55cb702ba24
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ spritePackingTag:
+ pSDRemoveMatte: 0
+ pSDShowRemoveMatteOption: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Icons/ECEUISphereMerge32.png
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts.meta b/Assets/EasyColliderEditor/Scripts.meta
new file mode 100644
index 00000000..31f2cd12
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e652979cf9e1eac4e90f77a15a69ff0c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs
new file mode 100644
index 00000000..9c109dce
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs
@@ -0,0 +1,1832 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using System.Linq;
+using System.Collections;
+#if UNITY_2019_1_OR_NEWER
+using Unity.Collections;
+#endif
+using System;
+
+namespace ECE
+{
+
+ // TODO:
+
+ // Convex mesh colliders can fail silently on primarily badly scaled skinned meshes. (Once in local space, the distances are too small for quickhull.)
+ // have tried slowly reducing calculated epsilon, but that does not work either.
+ // best option is to add a faq to documentation, and explain that scaling from some models (particularily blender it seems)
+ // exports with 100,100,100 scaling on the root
+
+ // NOTE: limit by child bone distance exprimented with, does not function well compared to depenetration methods.
+ // would get vertex distance and closest axis and check distance and toss vertices over the distance to prevent overlap. This does not work well.
+
+ [System.Serializable]
+ public class EasyColliderAutoSkinned : ScriptableObject, ISerializationCallbackReceiver
+ {
+ // All these proprties added to work without preferences file
+ // values can be set from your own script.
+ // default values should be the same as in preferences.
+
+ SKINNED_MESH_DEPENETRATE_ORDER _autoSkinnedDepenetrateOrder;
+ public SKINNED_MESH_DEPENETRATE_ORDER AutoSkinnedDepenetrateOrder
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedDepenetrateOrder : Preferences.AutoSkinnedDepenetrateOrder;
+#else
+ return _autoSkinnedDepenetrateOrder;
+#endif
+ }
+ set
+ {
+ _autoSkinnedDepenetrateOrder = value;
+ }
+ }
+
+ bool _autoSkinnedForce256Triangles = true;
+ public bool AutoSkinnedForce256Triangles
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedForce256Triangles : Preferences.AutoSkinnedForce256Triangles;
+#else
+ return _autoSkinnedForce256Triangles;
+#endif
+ }
+ set { _autoSkinnedForce256Triangles = value; }
+ }
+
+ float _autoSkinnedMinBoneWeight = 0.5f;
+ public float AutoSkinnedMinBoneWeight
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedMinBoneWeight : Preferences.AutoSkinnedMinBoneWeight;
+#else
+ return _autoSkinnedMinBoneWeight;
+#endif
+ }
+ set { _autoSkinnedMinBoneWeight = value; }
+ }
+
+ float _autoSkinnedMinRealignAngle;
+ public float AutoSkinnedMinRealignAngle
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedMinRealignAngle : Preferences.AutoSkinnedMinRealignAngle;
+#else
+ return _autoSkinnedMinRealignAngle;
+#endif
+ }
+ set { _autoSkinnedMinRealignAngle = value; }
+ }
+
+ bool _autoSkinnedAllowRealign;
+ public bool AutoSkinnedAllowRealign
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedAllowRealign : Preferences.AutoSkinnedAllowRealign;
+#else
+ return _autoSkinnedAllowRealign;
+#endif
+ }
+ set { _autoSkinnedAllowRealign = value; }
+ }
+
+
+
+ SKINNED_MESH_COLLIDER_TYPE _autoSkinnedColliderType = SKINNED_MESH_COLLIDER_TYPE.Box;
+ public SKINNED_MESH_COLLIDER_TYPE AutoSkinnedColliderType
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedColliderType : Preferences.AutoSkinnedColliderType;
+#else
+ return _autoSkinnedColliderType;
+#endif
+ }
+ set { _autoSkinnedColliderType = value; }
+ }
+
+
+ bool _autoSkinnedDepenetrate;
+ public bool AutoSkinnedDepenetrate
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedDepenetrate : Preferences.AutoSkinnedDepenetrate;
+#else
+ return _autoSkinnedDepenetrate;
+#endif
+ }
+ set { _autoSkinnedDepenetrate = value; }
+ }
+
+ bool _autoSkinnedIndents = true;
+ public bool AutoSkinnedIndents
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedIndents : Preferences.AutoSkinnedIndents;
+#else
+ return _autoSkinnedIndents;
+#endif
+ }
+ set { _autoSkinnedIndents = value; }
+ }
+
+ int _autoSkinnedIterativeDepenetrationCount = 15;
+ public int AutoSkinnedIterativeDepenetrationCount
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedIterativeDepenetrationCount : Preferences.AutoSkinnedIterativeDepenetrationCount;
+#else
+ return _autoSkinnedIterativeDepenetrationCount;
+#endif
+ }
+ set { _autoSkinnedIterativeDepenetrationCount = value; }
+
+ }
+
+ bool _autoSkinnedPairing = true;
+ public bool AutoSkinnedPairing
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedPairing : Preferences.AutoSkinnedPairing;
+#else
+ return _autoSkinnedPairing;
+#endif
+ }
+ set { _autoSkinnedPairing = value; }
+ }
+
+ bool _autoSkinnedPerBoneSettings;
+ public bool AutoSkinnedPerBoneSettings
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedPerBoneSettings : Preferences.AutoSkinnedPerBoneSettings;
+#else
+ return _autoSkinnedPerBoneSettings;
+#endif
+ }
+ set
+ {
+ _autoSkinnedPerBoneSettings = value;
+ }
+ }
+
+
+
+ float _autoSkinnedShrinkAmount = 0.5f;
+ public float AutoSkinnedShrinkAmount
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedShrinkAmount : Preferences.AutoSkinnedShrinkAmount;
+#else
+ return _autoSkinnedShrinkAmount;
+#endif
+ }
+ set
+ {
+ _autoSkinnedShrinkAmount = value;
+ }
+ }
+
+ bool _autoSkinnedUseDistanceDeltaPairing = true;
+ public bool AutoSkinnedUseDistanceDeltaPairing
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedUseDistanceDeltaPairing : Preferences.AutoSkinnedUseDistanceDeltaPairing;
+#else
+ return _autoSkinnedUseDistanceDeltaPairing;
+#endif
+ }
+ set
+ {
+ _autoSkinnedUseDistanceDeltaPairing = value;
+ }
+ }
+
+
+ float _autoSkinnedPairedDistanceDelta = 0.01f;
+ public float AutoSkinnedPairedDistanceDelta
+ {
+ get
+ {
+#if (UNITY_EDITOR)
+ return Application.isPlaying ? _autoSkinnedPairedDistanceDelta : Preferences.AutoSkinnedPairedDistanceDelta;
+#else
+ return _autoSkinnedPairedDistanceDelta;
+#endif
+ }
+ set
+ {
+ _autoSkinnedPairedDistanceDelta = value;
+ }
+ }
+
+
+
+#if (UNITY_EDITOR)
+ private EasyColliderPreferences _Preferences;
+ private EasyColliderPreferences Preferences
+ {
+ get
+ {
+ if (_Preferences == null)
+ {
+ _Preferences = EasyColliderPreferences.Preferences;
+ }
+ return _Preferences;
+ }
+ }
+#endif
+
+ ///
+ /// Cleans up the data.
+ ///
+ public void Clean()
+ {
+ BoneList = new List();
+ SortedBoneList = new List();
+ renderer = null;
+ transformHashCode = -1;
+ _selectedBone = null;
+ _initialScannedObject = null;
+ }
+
+ EasyColliderAutoSkinnedBone _selectedBone;
+ public void SetSelectedBone(EasyColliderAutoSkinnedBone bone)
+ {
+ _selectedBone = bone;
+ }
+ public EasyColliderAutoSkinnedBone GetSelectedBone()
+ {
+ return _selectedBone;
+ }
+
+ GameObject _initialScannedObject;
+ public GameObject GetInitialScannedObject()
+ {
+ return _initialScannedObject;
+ }
+
+ [SerializeField]
+ ///
+ /// List of bones on the current skinned mesh. In the same order as the skinnedmesh's skinnedMesh.bones transform array.
+ ///
+ ///
+ ///
+ public List BoneList = new List();
+
+ [SerializeField]
+ ///
+ /// hierarchical sorted bones of the current skinned mesh.
+ ///
+ ///
+ ///
+ public List SortedBoneList = new List();
+
+ ///
+ /// Hashcode of the position, rotation, and scale of the transform to more easily detect movement and update preview
+ ///
+ public int transformHashCode;
+
+ ///
+ /// current skinned mesh renderer
+ ///
+ public SkinnedMeshRenderer renderer;
+
+ ///
+ /// Checks if the preview needs updating because the root of the skinned mesh has moved/rotated/scaled.
+ ///
+ ///
+ public bool HasSkinnedMeshRendererTransformed()
+ {
+ if (renderer == null) return false;
+ int newHash = renderer.transform.position.GetHashCode() + renderer.transform.rotation.GetHashCode() + renderer.transform.lossyScale.GetHashCode();
+ if (transformHashCode != newHash)
+ {
+ transformHashCode = newHash;
+ return true;
+ }
+ return false;
+ }
+
+
+ ///
+ /// Goes through all the bones in the bone list and makes sure at least one bone weight on the skinned mesh is above it's threshold. If so, marks that bone as valid.
+ ///
+ ///
+ ///
+ private void SetBoneValidity(SkinnedMeshRenderer smr, List boneList)
+ {
+ BoneWeight[] boneWeights = smr.sharedMesh.boneWeights;
+ foreach (var b in boneWeights)
+ {
+ if (b.boneIndex0 >= 0 && b.weight0 > 0) boneList[b.boneIndex0].IsValid = true;
+ if (b.boneIndex1 >= 0 && b.weight1 > 0) boneList[b.boneIndex1].IsValid = true;
+ if (b.boneIndex2 >= 0 && b.weight2 > 0) boneList[b.boneIndex2].IsValid = true;
+ if (b.boneIndex3 >= 0 && b.weight3 > 0) boneList[b.boneIndex3].IsValid = true;
+ }
+ }
+
+ ///
+ /// Does an initial scan of bones to create the bone list, sorted bone list, pairs them, and sets initial weights
+ ///
+ ///
+ ///
+ public void InitialScanBones(GameObject selectedObject, float weight = 0.5f)
+ {
+ // Debug.Log("InitialScan." + Preferences.SkinnedMeshColliderType);
+ SkinnedMeshRenderer smr = selectedObject.GetComponentInChildren();
+ _initialScannedObject = selectedObject;
+ if (smr == null) return;
+ EasyColliderAutoSkinnedBone[] boneArray = GetSkinnedMeshBones(smr);
+ if (boneArray == null) return;
+ BoneList = boneArray.ToList();
+ if (BoneList.Count == 0) return;
+ SetBoneValidity(smr, BoneList);
+ // set initial bone weights.
+ foreach (var smbs in BoneList)
+ {
+ smbs.BoneWeight = weight;
+ }
+ List boneTransforms = smr.bones.ToList();
+ // pair bones up
+ PairBones(BoneList, boneTransforms);
+
+ // smr has a root property, so we're good.
+ if (smr.rootBone != null)
+ {
+ Transform root = smr.rootBone;
+ // calculate indent levels
+ if (root.parent != null)
+ {
+ SetIndentRecursive(root.parent, boneTransforms, 0);
+ }
+ else
+ {
+ SetIndentRecursive(root, boneTransforms, 0);
+ }
+ // calculate the sorted bone list. (use root's parent if possible, as there can be multiple "root" bones, so without it some wouldn't get detected.)
+ if (root.parent != null)
+ {
+ SortedBoneList = SortBonesRecursive(root.parent, boneTransforms, BoneList, new List());
+ }
+ else
+ {
+ SortedBoneList = SortBonesRecursive(root, boneTransforms, BoneList, new List());
+ }
+ }
+ else
+ {
+ // rootBone property of the skinned mesh is null, which would normally cause issues
+ // so we need to identify the top-level bones, then indent them all, and sort them.
+ List rootBones = IdentifyRootBones(BoneList);
+ foreach (var b in rootBones)
+ {
+ SetIndentRecursive(b.Transform, boneTransforms, 0);
+ }
+ SortedBoneList = NoRootBoneSort(rootBones, boneTransforms);
+ }
+ }
+
+ ///
+ /// Identifies the "root bones" (the top-level bones) in a bone list. Used in cases where the skinned mesh renderers rootBone property is null.
+ ///
+ ///
+ ///
+ List IdentifyRootBones(List boneList)
+ {
+ HashSet examinedBones = new HashSet();
+ // without a root bone, find the "toplevel" bones.
+ List rootBones = new List();
+ foreach (var b in boneList)
+ {
+ if (b.Transform == null) continue;
+ if (examinedBones.Contains(b)) continue;
+ examinedBones.Add(b);
+ EasyColliderAutoSkinnedBone bone = b;
+ bool hasParent = true;
+ while (hasParent)
+ {
+ hasParent = false;
+ Transform parent = bone.Transform.parent;
+ if (parent != null)
+ {
+ foreach (var a in boneList)
+ {
+ if (a.Transform == parent)
+ {
+ if (rootBones.Contains(a) || examinedBones.Contains(a))
+ {
+ // already added it's parent as a root, so stop looking
+ bone = null;
+ hasParent = false;
+ break;
+ }
+ else
+ {
+ // is a new parent, keep goin.
+ bone = a;
+ hasParent = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ examinedBones.Add(bone);
+ if (bone != null)
+ {
+ rootBones.Add(bone);
+ }
+ }
+ return rootBones;
+ }
+
+ ///
+ /// Sorts the bones based on a list of identified rootbones in cases where the skinned mesh renderer's root bone has been remvoed.
+ ///
+ /// identified top level bones
+ /// all bone transforms
+ /// list of bones sorted similar to the unity hierarchy.
+ List NoRootBoneSort(List rootBones, List boneTransforms)
+ {
+ var sortedBones = new List();
+ foreach (var b in rootBones)
+ {
+ // SetIndentRecursive(b.Transform, boneTransforms, 0);
+ List sortedBonesForBone = new List();
+ SortBonesRecursive(b.Transform, boneTransforms, BoneList, sortedBonesForBone);
+ sortedBones.AddRange(sortedBonesForBone);
+ }
+ return sortedBones;
+ }
+
+ ///
+ /// Goes through the boneList and pairs bones togehter by looking at the number of children to determine if theres multiple offshoots (arms/legs)
+ /// then pairing offshoots based on bone-transform chain length.
+ ///
+ ///
+ ///
+ private void PairBones(List boneList, List boneTransforms)
+ {
+ foreach (var smb in boneList)
+ {
+ // skip already paired bones, NOT invalid ones because invalid ones can still have children that are valid and need to be paired.
+ if (smb.IsPaired) continue;
+ if (smb.Transform == null) continue;
+ int childCount = smb.Transform.childCount;
+ // count the number of valid (is a bone) direct children that this bone has.
+ List validChildren = new List();
+ for (int i = 0; i < childCount; i++)
+ {
+ if (boneTransforms.Contains(smb.Transform.GetChild(i)))
+ {
+ validChildren.Add(i);
+ }
+ }
+ // more than one child on a bone, means it has multiple offshoots (like arms/legs off the back / hips)
+ if (validChildren.Count > 1)
+ {
+ // calculate the number of valid bones each offshoot has.
+ // we are identifying which bone offshoots match with other offshoots by the total valid bone length.
+ List perChainTransformCount = new List(new int[validChildren.Count]);
+ List perChainDistances = new List(new float[validChildren.Count]);
+ for (int i = 0; i < validChildren.Count; i++)
+ {
+ Transform child = smb.Transform.GetChild(validChildren[i]);
+ // skip already paired bones.
+ // get all children and count which ones are actually bones.
+ List childTransforms = child.GetComponentsInChildren().ToList();
+ int childsValidBoneCount = 0;
+ foreach (var t in childTransforms)
+ {
+ if (boneTransforms.Contains(t))
+ {
+ childsValidBoneCount++;
+ }
+ perChainDistances[i] = perChainDistances[i] + Vector3.Distance(child.position, t.position);
+ }
+ perChainTransformCount[i] = childsValidBoneCount;
+ }
+ // go through each child and see if the offshoots can be paired, and pair them.
+ for (int i = 0; i < validChildren.Count; i++)
+ {
+ // get the index of the current bone we are looking at.
+ int index = boneTransforms.IndexOf(smb.Transform.GetChild(i));
+ // if the bone is already paired, skip it.
+ if (index < 0 || boneList[index].IsPaired) continue;
+ // identify bones that have similar count in their bone chain using the perChainTransformCount.
+ List pairedBones = new List();
+ for (int j = 0; j < validChildren.Count; j++)
+ {
+ if (j == i) continue;
+ // same number of transforms? likely to be a pair.
+ bool isPaired = perChainTransformCount[i] == perChainTransformCount[j];
+
+ if (AutoSkinnedUseDistanceDeltaPairing)
+ {
+ // works, and simplifying to only using the actual transform works less well than using full chain distance.
+ if (isPaired && (perChainDistances[i] == perChainDistances[j] || Mathf.Abs(perChainDistances[i] - perChainDistances[j]) < AutoSkinnedPairedDistanceDelta))
+ {
+ isPaired = true;
+ }
+ else if (isPaired)
+ {
+ isPaired = false;
+ }
+ }
+
+ if (isPaired)
+ {
+ // bone pair found!
+ Transform c1 = smb.Transform.GetChild(j);
+ int i1 = boneTransforms.IndexOf(c1);
+ // make sure index > 0
+ if (i1 >= 0)
+ {
+ pairedBones.Add(i1);
+ }
+ }
+ }
+
+
+
+ // if we have a paired bone, pair it up (and their children!)
+ if (pairedBones.Count > 0)
+ {
+ // mark the current bone as paired.
+ boneList[index].PairedBones = pairedBones;
+ boneList[index].IsPaired = true;
+ // mark each bone as paired.
+ foreach (var pIndex in pairedBones)
+ {
+ boneList[pIndex].IsPaired = true;
+ }
+ // we need to pair the children as well, create a list of transform's including itself and it's children.
+ List bChildren = boneList[index].Transform.GetComponentsInChildren().ToList();
+ // list to contain each paired-bone's list of transforms.
+ List> pairedChildren = new List>();
+ // create the paired children list, doing the same thing as for the original.
+ foreach (var pIndex in pairedBones)
+ {
+ pairedChildren.Add(boneTransforms[pIndex].GetComponentsInChildren().ToList());
+ }
+ for (int j = 0; j < bChildren.Count; j++)
+ {
+ // get the bone transform index of the child we're looking at.
+ int bIndex = boneTransforms.IndexOf(bChildren[j]);
+ // skip non-bone transforms.
+ if (bIndex < 0) continue;
+ // pair the bones.
+ List cPairedBones = new List();
+ foreach (var tList in pairedChildren)
+ {
+ // pair only the matching indexs in each transform list.
+ int pIndex = boneTransforms.IndexOf(tList[j]);
+ if (pIndex < 0) continue;
+ // add them as a paired bonelist index, and mark the current one as paired.
+ cPairedBones.Add(pIndex);
+ boneList[pIndex].IsPaired = true;
+ }
+ // if we paired this child transform with another transform.
+ if (cPairedBones.Count > 0)
+ {
+ // mark it as paired, set it's paired list, and set it as the display bone.
+ boneList[bIndex].IsPaired = true;
+ boneList[bIndex].PairedBones = cPairedBones;
+ boneList[bIndex].IsPairDisplayBone = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Recursively sorts bones in the boneList into the sortedList to match with how they would be displayed in the scene hierarchy.
+ ///
+ /// initially the skinned mesh's root bone.
+ /// the skinned mesh renderers bones
+ ///
+ ///
+ /// Sorted list of bones.
+ List SortBonesRecursive(Transform current, List boneTransforms, List boneList, List sortedList)
+ {
+ int index = boneTransforms.IndexOf(current);
+ if (index >= 0)
+ {
+ EasyColliderAutoSkinnedBone smb = BoneList[index];
+ smb.BoneName = current.name;
+ sortedList.Add(smb);
+ for (int i = 0; i < current.childCount; i++)
+ {
+ Transform child = current.GetChild(i);
+ SortBonesRecursive(child, boneTransforms, boneList, sortedList);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < current.childCount; i++)
+ {
+ Transform child = current.GetChild(i);
+ SortBonesRecursive(child, boneTransforms, boneList, sortedList);
+ }
+ }
+ return sortedList;
+ }
+
+
+ ///
+ /// Recursively calculates the index level of each bone.
+ ///
+ /// initially the skinned mesh's root bone.
+ ///
+ ///
+ void SetIndentRecursive(Transform current, List boneTransforms, int currentIndent)
+ {
+ int index = boneTransforms.IndexOf(current);
+ if (index >= 0)
+ {
+ // only set the indent and increase the indent if the bone is valid (has at least 1 skinned vertex);
+ if (BoneList[index].IsValid)
+ {
+ BoneList[index].IndentLevel = currentIndent;
+ currentIndent += 1;
+ }
+ for (int i = 0; i < current.childCount; i++)
+ {
+ SetIndentRecursive(current.GetChild(i), boneTransforms, currentIndent);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < current.childCount; i++)
+ {
+ SetIndentRecursive(current.GetChild(i), boneTransforms, currentIndent);
+ }
+ }
+ }
+
+
+
+
+ ///
+ /// (Undoable) Creates a gameobject as a child of parent, with it's forward axis pointed towards the child, and its up axis at the cross of the new forward and the parent's up direction.
+ ///
+ /// parent transform
+ /// child transform
+ /// Gameobject at parent's location with it's forward axis pointing towards child.
+ public GameObject CreateRealignedObject(Transform parent, Transform child, bool forPreview = false)
+ {
+ // realign with a rotated gameboject.
+ GameObject obj = new GameObject(parent.transform.name + "_RotatedCollider");
+ Vector3 childDir = child.position - parent.position;
+ // use either the bone's right or the the bone's forward, whichever isn't the same as the child's direction in the calculation.
+ Vector3 right = parent.transform.right;
+ if (childDir == right) { right = parent.transform.forward; }
+ Vector3 up = Vector3.Cross(childDir, right);
+ obj.transform.rotation = Quaternion.LookRotation(childDir, up);
+ obj.transform.SetParent(parent.transform);
+ obj.layer = obj.transform.parent.gameObject.layer;
+ obj.transform.localPosition = Vector3.zero;
+ if (!forPreview)
+ {
+#if (UNITY_EDITOR)
+ Undo.RegisterCreatedObjectUndo(obj, "Create realign bone object");
+#endif
+ }
+ return obj;
+ }
+
+ ///
+ /// gets the TRS that represents the transform from the parent to the child as the forward direction.
+ ///
+ ///
+ ///
+ ///
+ public Matrix4x4 GetMatrixForObject(Transform parent, Transform child)
+ {
+ Vector3 childDir = child.position - parent.position;
+ Vector3 right = parent.transform.right;
+ if (childDir == right) { right = parent.transform.forward; }
+ Vector3 up = Vector3.Cross(childDir, right);
+ Quaternion q = Quaternion.LookRotation(childDir, up);
+ return Matrix4x4.TRS(parent.position, q, Vector3.one);
+ }
+
+
+ private List ToLocalVerts(Transform transform, List worldVertices)
+ {
+ List localVerts = new List(worldVertices.Count);
+ foreach (Vector3 v in worldVertices)
+ {
+ localVerts.Add(transform.InverseTransformPoint(v));
+ }
+ return localVerts;
+ }
+
+ ///
+ /// Creates and attaches a collider of given type
+ ///
+ /// type of collider
+ /// properties of collider
+ /// data to create collider with
+ /// save path for mesh colliders
+ private Collider GenerateCollider(SKINNED_MESH_COLLIDER_TYPE colliderType, EasyColliderProperties properties, EasyColliderAutoSkinnedBone s, string savePath, bool forPreview = false)
+ {
+ EasyColliderCreator ecc = new EasyColliderCreator();
+#if(UNITY_EDITOR)
+ if (forPreview)
+ {
+ ecc.UndoEnabled = false;
+ }
+#endif
+ switch (colliderType)
+ {
+ case SKINNED_MESH_COLLIDER_TYPE.Box:
+ return ecc.CreateBoxCollider(s.WorldSpaceVertices, properties);
+ case SKINNED_MESH_COLLIDER_TYPE.Capsule:
+ return ecc.CreateCapsuleCollider_MinMax(s.WorldSpaceVertices, properties, CAPSULE_COLLIDER_METHOD.MinMax);
+ case SKINNED_MESH_COLLIDER_TYPE.Sphere:
+ return ecc.CreateSphereCollider_MinMax(s.WorldSpaceVertices, properties);
+ case SKINNED_MESH_COLLIDER_TYPE.Convex_Mesh:
+ s.WorldSpaceVertices = ToLocalVerts(s.Transform, s.WorldSpaceVertices);
+ EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(s.WorldSpaceVertices);
+ Mesh m = qh.Result;
+ if (AutoSkinnedForce256Triangles && m != null)
+ {
+ if (m.triangles.Length / 3 > 255)
+ {
+ // weld vertices to an estimated target, then recalculate the hull, and repeat until
+ // we get a value, or we reach n, which is a failsafe.
+ int n = 0;
+ float weldDistance = 0.0f;
+ while (m.triangles.Length / 3 > 255 && n < 25)
+ {
+ weldDistance = ReduceMeshVerticesOnBone(s, m, weldDistance);
+ qh = EasyColliderQuickHull.CalculateHull(s.WorldSpaceVertices);
+ m = qh.Result;
+ n++;
+ }
+ }
+ }
+ if (m != null)
+ {
+#if (UNITY_EDITOR)
+ if (Preferences.SaveConvexHullAsAsset && !forPreview)
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(qh.Result, s.Transform.gameObject);
+ }
+#endif
+ // ecc.CreateMesh_QuickHull(s.WorldSpaceVertices, s.Transform.gameObject);
+ return ecc.CreateConvexMeshCollider(qh.Result, s.Transform.gameObject, properties);
+ }
+ break;
+ }
+ return null;
+ }
+
+ public void ChangeBoneEnabled(EasyColliderAutoSkinnedBone bone, bool enabled, bool includeChildren)
+ {
+ bone.Enabled = enabled;
+ if (includeChildren)
+ {
+ foreach (var b in BoneList)
+ {
+ if (b.Transform.IsChildOf(bone.Transform))
+ {
+ b.Enabled = enabled;
+ }
+ }
+ }
+ if (AutoSkinnedPairing)
+ {
+ foreach (var index in bone.PairedBones)
+ {
+ ChangeBoneEnabled(BoneList[index], enabled, includeChildren);
+ }
+ }
+ }
+
+ public void ChangeBoneWeight(EasyColliderAutoSkinnedBone bone, float weight, bool includeChildren)
+ {
+ bone.BoneWeight = weight;
+ if (includeChildren)
+ {
+ foreach (var b in BoneList)
+ {
+ if (b.Transform.IsChildOf(bone.Transform))
+ {
+ b.BoneWeight = weight;
+ }
+ }
+ }
+ if (AutoSkinnedPairing)
+ {
+ foreach (var index in bone.PairedBones)
+ {
+ ChangeBoneWeight(BoneList[index], weight, includeChildren);
+ }
+ }
+ }
+
+ public void ChangeBoneColliderType(EasyColliderAutoSkinnedBone bone, SKINNED_MESH_COLLIDER_TYPE colliderType, bool includeChildren)
+ {
+ bone.ColliderType = colliderType;
+ if (includeChildren)
+ {
+ foreach (var b in BoneList)
+ {
+ if (b.Transform.IsChildOf(bone.Transform))
+ {
+ b.ColliderType = colliderType;
+ }
+ }
+ }
+ if (AutoSkinnedPairing)
+ {
+ foreach (var index in bone.PairedBones)
+ {
+ ChangeBoneColliderType(BoneList[index], colliderType, includeChildren);
+ }
+ }
+ }
+
+
+
+ ///
+ /// Estimates a target number of vertices to reach under 256 triangles and merges vertices together until that target is reached.
+ /// returns the final weld distance used to reach below the estimated target.
+ ///
+ /// bone
+ /// quickhull calculated mesh
+ /// previous weld distance from this method, or 0
+ /// weld distance used to reach target vertices.
+ private float ReduceMeshVerticesOnBone(EasyColliderAutoSkinnedBone bone, Mesh m, float prevWeldDist)
+ {
+ // Tends to merge vertices at the start over those at the end, as we detect when we've reached enough and then add the rest.
+ List vertices = new List();
+ HashSet weldedVerts = new HashSet();
+ int targetVerts = (int)(256.0f * ((float)bone.WorldSpaceVertices.Count / ((float)m.triangles.Length / 3)));
+ if (targetVerts >= bone.WorldSpaceVertices.Count || (targetVerts > bone.WorldSpaceVertices.Count * 0.9f))
+ {
+ targetVerts = (int)(bone.WorldSpaceVertices.Count * 0.9f);
+ }
+ int currentVerts = bone.WorldSpaceVertices.Count;
+ float sizeMagnitude = m.bounds.size.magnitude;
+ float weldDistance = (sizeMagnitude) / 50f;
+ if (weldDistance < prevWeldDist)
+ {
+ weldDistance = prevWeldDist + (weldDistance * 0.25f);
+ }
+ // weld close vertices. (n as a failsafe.)
+ int n = 0;
+ while (targetVerts < bone.WorldSpaceVertices.Count && n < 25)
+ {
+ int iterCount = bone.WorldSpaceVertices.Count;
+ for (int i = 0; i < bone.WorldSpaceVertices.Count; i++)
+ {
+ // if we break when we've reached the target,
+ // since the method is also called iterative from an outer loop, we tend to only weld the initial vertices.
+ // so instead we'll try to weld the whole thing
+ int weldCount = 1;
+ Vector3 v1 = bone.WorldSpaceVertices[i];
+ if (weldedVerts.Contains(v1)) continue;
+ for (int j = i; j < bone.WorldSpaceVertices.Count; j++)
+ {
+ Vector3 v2 = bone.WorldSpaceVertices[j];
+ if (weldedVerts.Contains(v2)) continue;
+ if (Vector3.Distance(v1, v2) < weldDistance)
+ {
+ v1 = ((v1 * weldCount) + v2) / (weldCount + 1);
+ weldCount++;
+ weldedVerts.Add(v2);
+ weldedVerts.Add(v1);
+ }
+ }
+ vertices.Add(v1);
+ }
+ weldDistance *= 1.25f;
+ weldedVerts.Clear();
+ bone.WorldSpaceVertices = vertices;
+ vertices = new List();
+ n++;
+ }
+ return weldDistance;
+ }
+
+ private List ToLocalVerts(List worldVertices, Matrix4x4 m)
+ {
+ List localVerts = new List(worldVertices.Count);
+ Matrix4x4 inverse = m.inverse;
+ foreach (Vector3 v in worldVertices)
+ {
+ localVerts.Add(inverse.MultiplyPoint3x4(v));
+ }
+ return localVerts;
+ }
+
+ private EasyColliderData CalculateColliderData(SKINNED_MESH_COLLIDER_TYPE colliderType, EasyColliderAutoSkinnedBone s, EasyColliderProperties properties)
+ {
+ EasyColliderCreator ecc = new EasyColliderCreator();
+ List localVertices = ToLocalVerts(s.WorldSpaceVertices, s.Matrix);
+ switch (colliderType)
+ {
+ case SKINNED_MESH_COLLIDER_TYPE.Box:
+ BoxColliderData bd = ecc.CalculateBoxLocal(localVertices);
+ bd.Matrix = s.Matrix;
+ return bd;
+ case SKINNED_MESH_COLLIDER_TYPE.Capsule:
+ CapsuleColliderData cd = ecc.CalculateCapsuleMinMaxLocal(localVertices, CAPSULE_COLLIDER_METHOD.MinMax);
+ cd.Matrix = s.Matrix;
+ return cd;
+ case SKINNED_MESH_COLLIDER_TYPE.Sphere:
+ SphereColliderData scd = ecc.CalculateSphereMinMaxLocal(localVertices);
+ scd.Matrix = s.Matrix;
+ return scd;
+ case SKINNED_MESH_COLLIDER_TYPE.Convex_Mesh:
+ MeshColliderData mcd = ecc.CalculateMeshColliderQuickHullLocal(localVertices);
+ mcd.Matrix = s.Matrix;
+ return mcd;
+ }
+ return new EasyColliderData();
+ }
+
+
+ public void SetColliderTypeOnAllBones(SKINNED_MESH_COLLIDER_TYPE colliderType)
+ {
+ foreach (EasyColliderAutoSkinnedBone b in BoneList)
+ {
+ b.ColliderType = colliderType;
+ }
+ }
+
+ ///
+ /// Sets the collider type and weight on all bones.
+ ///
+ ///
+ ///
+ public void SetColliderTypeAndWeightOnAllBones(SKINNED_MESH_COLLIDER_TYPE colliderType, float weight)
+ {
+ foreach (EasyColliderAutoSkinnedBone b in BoneList)
+ {
+ b.BoneWeight = weight;
+ b.ColliderType = colliderType;
+ }
+ }
+
+ void BakeSkinnedMesh(SkinnedMeshRenderer skinnedMesh, Mesh m)
+ {
+ Vector3 lossyScale = skinnedMesh.transform.lossyScale;
+ Vector3 local = skinnedMesh.transform.localScale;
+ // this works well enough for most cases.
+ if (lossyScale != Vector3.one)
+ {
+ local.x /= lossyScale.x;
+ local.y /= lossyScale.y;
+ local.z /= lossyScale.z;
+ }
+ skinnedMesh.transform.localScale = local;
+ skinnedMesh.BakeMesh(m);
+ }
+
+ ///
+ /// Calculates the preview data for the auto-skinned secttion.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public List CalculateSkinnedMeshPreview(SkinnedMeshRenderer skinnedMesh, SKINNED_MESH_COLLIDER_TYPE colliderType, EasyColliderProperties properties, float minBoneWeight, bool realignBones = false, float minRealignAngle = 20f, string savePath = "Assets/")
+ {
+ if (skinnedMesh == null) return new List();
+ List TemporaryGameObjects = new List();
+ renderer = skinnedMesh;
+ List data = new List();
+
+ // bake and scale.
+ Mesh m = new Mesh();
+ Vector3 prevScale = skinnedMesh.transform.localScale;
+ BakeSkinnedMesh(skinnedMesh, m);
+ Vector3[] vertices = m.vertices;
+ for (int i = 0; i < vertices.Length; i++)
+ {
+ //transform to world relative by root object
+ vertices[i] = skinnedMesh.transform.TransformPoint(vertices[i]);
+ }
+ skinnedMesh.transform.localScale = prevScale;
+
+ // get skinned mesh bones
+ EasyColliderAutoSkinnedBone[] smbs = BoneList.ToArray();
+ if (smbs == null || BoneList.Count == 0)
+ {
+ return null;
+ }
+ foreach (EasyColliderAutoSkinnedBone smb in smbs)
+ {
+ smb.WorldSpaceVertices.Clear();
+ }
+
+ List generatedColliders = new List();
+
+ // set the world vertex for each bone.
+#if UNITY_2019_1_OR_NEWER
+ SetWorldVertices(smbs, skinnedMesh.sharedMesh.GetAllBoneWeights(), skinnedMesh.sharedMesh.GetBonesPerVertex(), vertices, minBoneWeight);
+#else
+ SetWorldVertices(smbs, skinnedMesh.sharedMesh.boneWeights, vertices);
+#endif
+ Transform[] bones = skinnedMesh.bones;
+
+
+ foreach (EasyColliderAutoSkinnedBone s in smbs)
+ {
+ if (!s.Enabled || !s.IsValid || s.renderer != skinnedMesh) continue;
+ // ignore excluded, null, and bones with no vertices.
+ if (s == null || s.WorldSpaceVertices.Count == 0) continue;
+ // the attach to is the skinned bones transform's gameobject.
+ properties.AttachTo = s.Transform.gameObject;
+ s.Matrix = s.Transform.localToWorldMatrix;
+ // when the mesh isn't optimized, the bones transform is filled
+ if (bones.Length > 0)
+ {
+ // if realigning is enabled, and its not convex meshes
+ if (realignBones && colliderType != SKINNED_MESH_COLLIDER_TYPE.Convex_Mesh && minRealignAngle > 0.0f)
+ {
+ // try realigning by finding the single child bone, comparing the angles, and creating a properly aligned transform
+ Transform child = GetChildBone(s.Transform, bones);
+ if (child != null)
+ {
+ float minAngle = GetMinimumChildAngle(s.Transform, child);
+ if (minAngle >= minRealignAngle)
+ {
+ // TODO: need to readjust how everything is done to use a matrix instead of a transform.
+ // properties.AttachTo = CreateRealignedObject(s.Transform, child);
+ s.Matrix = GetMatrixForObject(s.Transform, child);
+ if (AutoSkinnedDepenetrate)
+ {
+ GameObject newObject = CreateRealignedObject(s.Transform, child);
+ TemporaryGameObjects.Add(newObject);
+ properties.AttachTo = newObject;
+ }
+ }
+ }
+ }
+ }
+ // finally, calculate the collider
+ EasyColliderData d = CalculateColliderData(s.ColliderType, s, properties);
+ // if its not a mesh collider & we're depenetrating.
+ if (AutoSkinnedDepenetrate)
+ {
+ Collider createdCollider = GenerateCollider(s.ColliderType, properties, s, savePath, true);
+ if (createdCollider != null)
+ {
+ generatedColliders.Add(createdCollider);
+ data.Add(d);
+ s.Collider = createdCollider;
+ }
+ }
+ else if (!AutoSkinnedDepenetrate)
+ {
+ data.Add(d);
+ }
+ }
+ if (generatedColliders.Count == data.Count && AutoSkinnedDepenetrate)
+ {
+ CheckDoDepenetration(generatedColliders);
+ for (int i = 0; i < generatedColliders.Count; i++)
+ {
+ Collider c = generatedColliders[i];
+ EasyColliderData d = data[i];
+ if (c is BoxCollider)
+ {
+ BoxColliderData bd = d as BoxColliderData;
+ if (bd == null) continue;
+ BoxCollider bc = c as BoxCollider;
+ bd.Center = bc.center;
+ bd.Size = bc.size;
+ }
+ else if (c is CapsuleCollider)
+ {
+ CapsuleColliderData ccd = d as CapsuleColliderData;
+ if (ccd == null) continue;
+ CapsuleCollider cc = c as CapsuleCollider;
+ ccd.Center = cc.center;
+ ccd.Radius = cc.radius;
+ ccd.Height = cc.height;
+ }
+ else if (c is SphereCollider)
+ {
+ SphereColliderData scd = d as SphereColliderData;
+ if (scd == null) continue;
+ SphereCollider sc = c as SphereCollider;
+ scd.Center = sc.center;
+ scd.Radius = sc.radius;
+ }
+ // would work okay for preview, but not generation.
+ // else if (c is MeshCollider)
+ // {
+ // MeshColliderData mcd = d as MeshColliderData;
+ // if (mcd == null) continue;
+ // MeshCollider mc = c as MeshCollider;
+ // mcd.ConvexMesh = mc.sharedMesh;
+ // }
+ }
+ }
+ // creating and destroying the objects is really the slow part.
+ // creating and destroying the colliders with an UNDO registered was the slow part
+ // since they are only temporary, we can safely just destroy em all without undos.
+ for (int i = 0; i < generatedColliders.Count; i++)
+ {
+ DestroyImmediate(generatedColliders[i]);
+ }
+ for (int i = 0; i < TemporaryGameObjects.Count; i++)
+ {
+ DestroyImmediate(TemporaryGameObjects[i]);
+ }
+ return data;
+ }
+
+
+ ///
+ /// Generates colliders along a bone chain of a skinned mesh renderer.
+ ///
+ /// Skinned mesh renderer
+ /// Type of colliders to use
+ /// Parameters to set on created colliders
+ /// Minimum bone weight to include a vertex in a bones collider
+ /// Should realigning colliders be performed?
+ /// When the minimum angle of all of a bone's axis (up, down, left, right, forward, back) and the vector to the next bone in the chain is >= minRealignAngle, realigning is performed if enabled.
+ /// Full path to save mesh's when colliderType is a Convex Mesh. Ie: C:/UnityProjects/ProjectName/Assets/ConvexHulls/BaseHullName
+ public List GenerateSkinnedMeshColliders(SkinnedMeshRenderer skinnedMesh, SKINNED_MESH_COLLIDER_TYPE colliderType, EasyColliderProperties properties, float minBoneWeight, bool realignBones = false, float minRealignAngle = 20f, string savePath = "Assets/")
+ {
+ if (skinnedMesh == null) return new List();
+ renderer = skinnedMesh;
+
+ // bake the mesh to get world space vertices as this works in all cases (bone transforms are valid, bind poses are valid, or the allow deoptimization is used)
+ // also works for incorrectly rotated roots / mesh renderers etc.
+ // bake and scale.
+ Mesh m = new Mesh();
+ Vector3 prevScale = skinnedMesh.transform.localScale;
+ BakeSkinnedMesh(skinnedMesh, m);
+ Vector3[] vertices = m.vertices;
+ for (int i = 0; i < vertices.Length; i++)
+ {
+ //transform to world relative by root object
+ vertices[i] = skinnedMesh.transform.TransformPoint(vertices[i]);
+ }
+ skinnedMesh.transform.localScale = prevScale;
+
+ // get skinned mesh bones
+ EasyColliderAutoSkinnedBone[] smbs = BoneList.ToArray();
+ if (smbs == null || BoneList.Count == 0)
+ {
+ return null;
+ }
+ foreach (EasyColliderAutoSkinnedBone smb in smbs)
+ {
+ smb.WorldSpaceVertices.Clear();
+ }
+
+ List generatedColliders = new List();
+
+ // set the world vertex for each bone.
+#if UNITY_2019_1_OR_NEWER
+ SetWorldVertices(smbs, skinnedMesh.sharedMesh.GetAllBoneWeights(), skinnedMesh.sharedMesh.GetBonesPerVertex(), vertices, minBoneWeight);
+#else
+ SetWorldVertices(smbs, skinnedMesh.sharedMesh.boneWeights, vertices);
+#endif
+ Transform[] bones = skinnedMesh.bones;
+
+
+ foreach (EasyColliderAutoSkinnedBone s in smbs)
+ {
+ if (!s.Enabled || !s.IsValid || s.renderer != skinnedMesh) continue;
+ if (s == null || s.WorldSpaceVertices.Count == 0) continue;
+ // Debug.Log("Generate on valid bone.");
+ // the attach to is the skinned bones transform's gameobject.
+ properties.AttachTo = s.Transform.gameObject;
+ colliderType = s.ColliderType;
+ // when the mesh isn't optimized, the bones transform is filled
+ if (bones.Length > 0)
+ {
+ // if realigning is enabled, and its not convex meshes
+ if (realignBones && colliderType != SKINNED_MESH_COLLIDER_TYPE.Convex_Mesh)
+ {
+ // try realigning by finding the single child bone, comparing the angles, and creating a properly aligned transform
+ Transform child = GetChildBone(s.Transform, bones);
+ if (child != null)
+ {
+ float minAngle = GetMinimumChildAngle(s.Transform, child);
+ if (minAngle >= minRealignAngle)
+ {
+ properties.AttachTo = CreateRealignedObject(s.Transform, child);
+ }
+ }
+ }
+ }
+ // finally, create the collider.
+ Collider createdCollider = GenerateCollider(colliderType, properties, s, savePath, false);
+ if (createdCollider != null)
+ {
+ generatedColliders.Add(createdCollider);
+ s.Collider = createdCollider;
+ }
+ }
+
+ // do depenetration.
+ CheckDoDepenetration(generatedColliders);
+
+ return generatedColliders;
+ }
+
+ public bool reverse;
+ ///
+ /// Runs the depenetration method(s) if needed.
+ ///
+ ///
+ private void CheckDoDepenetration(List generatedColliders)
+ {
+ if (AutoSkinnedDepenetrate)
+ {
+ List colliders = null;
+ if (AutoSkinnedDepenetrateOrder == SKINNED_MESH_DEPENETRATE_ORDER.OutsideIn || AutoSkinnedDepenetrateOrder == SKINNED_MESH_DEPENETRATE_ORDER.InsideOut)
+ {
+ colliders = new List(generatedColliders.Count);
+ // order by descending indent level.
+ IEnumerable query = BoneList.OrderByDescending(bone => bone.IndentLevel);
+ foreach (var b in query)
+ {
+ if (b.Collider == null || !b.Enabled || !b.IsValid) continue;
+ colliders.Add(b.Collider);
+ }
+ // reverse if needed
+ if (AutoSkinnedDepenetrateOrder == SKINNED_MESH_DEPENETRATE_ORDER.InsideOut)
+ {
+ colliders.Reverse();
+ }
+ }
+ else
+ {
+ // just use generated colliders, reverse if needed.
+ colliders = new List(generatedColliders);
+ if (AutoSkinnedDepenetrateOrder == SKINNED_MESH_DEPENETRATE_ORDER.Reverse)
+ {
+ colliders.Reverse();
+ }
+ }
+ // iteratively shrink and shift!
+ IterativeShrinkAndShift(colliders);
+ }
+ }
+
+ ///
+ /// Number of colliders shrunk during the current shrink iteration.
+ ///
+ private int ShrinkCount;
+
+ ///
+ /// Total shrink amount of all colliders during the current shrink iteration.
+ ///
+ private Vector3 ShrinkAmount;
+
+ ///
+ /// Number of colliders shifted during the current shift iteration.
+ ///
+ private int ShiftCount;
+
+ ///
+ /// Total shift amount of all colliders during the current shift iteration.
+ ///
+ private Vector3 ShiftAmount;
+
+ ///
+ /// Iteratively shrinks then shifts the generated colliders a number of times equal to the value set in preferences.
+ /// Shrinks with a multiplier of 0.5, shifts, then repeats.
+ ///
+ ///
+ private void IterativeShrinkAndShift(List generatedColliders)
+ {
+ for (int i = 0; i < AutoSkinnedIterativeDepenetrationCount; i++)
+ {
+ // keep track of shrink & shift count so we can exit once an iteration detects no shifts or shrinks.
+ ShrinkCount = 0;
+ ShiftCount = 0;
+ ShrinkAmount = Vector3.zero;
+ ShiftAmount = Vector3.zero;
+ // if we can can shrink colliders, do so first.
+ if (AutoSkinnedShrinkAmount > 0)
+ {
+ ShrinkDepenetrate(generatedColliders, AutoSkinnedShrinkAmount);
+ }
+ // then shift the colliders.
+ ShiftDepenetrate(generatedColliders);
+ // no more shrinking or shifting? we're done!
+ if (ShrinkCount == 0 && ShiftCount == 0)
+ {
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Gets a vector3 as a string with more precision by manually building the string with x, y, and z values.
+ ///
+ ///
+ ///
+ public string LogVector(Vector3 vector3)
+ {
+ return "(" + vector3.x + "," + vector3.y + "," + vector3.z + ")";
+ }
+
+
+ ///
+ /// Simply a helpful method that logs each colliders name and overlap count, and their max shift vectors.
+ /// At the end it also logs how many colliders are overlapped with at least 1 other, and the total of the max shift vectors.
+ ///
+ ///
+ private void LogRemainingOverlaps(List generatedColliders)
+ {
+ List> datas = new List>();
+ foreach (Collider c in generatedColliders)
+ {
+ List shifts = new List();
+ Transform cTransform = c.transform;
+ foreach (Collider other in generatedColliders)
+ {
+ if (c != other)
+ {
+ ShiftData data = new ShiftData();
+ if (GetShiftWorld(c, cTransform, other, out data))
+ {
+ if (data.Direction == Vector3.zero) continue;
+ if (data.Size == 0.0f) continue;
+ shifts.Add(data);
+ }
+ }
+ }
+ datas.Add(shifts);
+ }
+ Vector3 totalShiftRemaining = Vector3.zero;
+ int totalOverlapped = 0;
+ for (int i = 0; i < generatedColliders.Count; i++)
+ {
+ if (datas[i].Count > 0)
+ {
+ Vector3 maxs = Vector3.zero;
+ foreach (var s in datas[i])
+ {
+ Vector3 dir = generatedColliders[i].transform.InverseTransformVector(s.Direction * s.Size);
+ dir.x = Mathf.Abs(dir.x);
+ dir.y = Mathf.Abs(dir.y);
+ dir.z = Mathf.Abs(dir.z);
+ maxs.x = Mathf.Max(maxs.x, dir.x);
+ maxs.y = Mathf.Max(maxs.y, dir.y);
+ maxs.z = Mathf.Max(maxs.z, dir.z);
+ }
+ if (maxs != Vector3.zero)
+ {
+ Debug.Log("Collider:" + generatedColliders[i].transform.name + " Overlaps with:" + datas[i].Count + " Max Shift Vector:" + LogVector(maxs));
+ totalShiftRemaining += maxs;
+ totalOverlapped++;
+ }
+ }
+ }
+ Debug.Log("Total Overlapped Colliders:" + totalOverlapped + " Total Shift Vector:" + LogVector(totalShiftRemaining));
+ }
+
+
+ ///
+ /// Attempts to depenetrate colliders by calculating all the penetration shift vectors, then getting the maximum in each local axis, and shrinking the size, height, radius (depeneding on collider type) approriately.
+ /// Mult is used to multiply the value(s) used to shrink (for iterations)
+ ///
+ ///
+ /// Amount to multiple the shrink values with.
+ private void ShrinkDepenetrate(List generatedColliders, float mult = 1.0f)
+ {
+ foreach (Collider c in generatedColliders)
+ {
+ List shiftDatas = new List(generatedColliders.Count - 1);
+ Transform cTransform = c.transform;
+ // get all the shift's needed to depenetrate this collider from all other colliders.
+ foreach (Collider other in generatedColliders)
+ {
+ if (c == other) continue;
+ ShiftData data = new ShiftData();
+ if (GetShiftWorld(c, cTransform, other, out data))
+ {
+ shiftDatas.Add(data);
+ }
+ }
+ // no shifts? skip.
+ if (shiftDatas.Count == 0) continue;
+ // calculate the maximum amount we would need to shift on each axis to depenetrate from all colliders.
+ Vector3 maxShifts = Vector3.zero;
+ foreach (ShiftData data in shiftDatas)
+ {
+ Vector3 direction = cTransform.InverseTransformVector(data.Direction * data.Size);
+ // since we're shrinking (and not shifting), we want the absolute value as sign doesn't matter.
+ direction.x = Mathf.Abs(direction.x);
+ direction.y = Mathf.Abs(direction.y);
+ direction.z = Mathf.Abs(direction.z);
+ if (direction.x > maxShifts.x)
+ {
+ maxShifts.x = direction.x;
+ }
+ if (direction.y > maxShifts.y)
+ {
+ maxShifts.y = direction.y;
+ }
+ if (direction.z > maxShifts.z)
+ {
+ maxShifts.z = direction.z;
+ }
+ }
+ // do we have a value to shrink?
+ if (maxShifts != Vector3.zero)
+ {
+ // keep track of how many shrinks we've done this iteration so we can exit early if complete.
+ ShrinkCount++;
+ ShrinkAmount += maxShifts;
+ if (c is BoxCollider)
+ {
+ // shrink a box collider
+ BoxCollider bc = c as BoxCollider;
+ Vector3 size = bc.size;
+ size -= maxShifts * mult;
+ // if we go into a negative size from all the shifts, this collider's size should be zero.
+ if (size.x < 0 || size.y < 0 || size.z < 0)
+ {
+ size = Vector3.zero;
+ }
+ bc.size = size;
+ }
+ else if (c is CapsuleCollider)
+ {
+ // shrink a capsule collider.
+ CapsuleCollider cc = c as CapsuleCollider;
+ float hChange = 0.0f;
+ float rChange = 0.0f;
+ if (cc.direction == 0) //x
+ {
+ hChange = maxShifts.x;
+ // radius change is the max-shift of the non-height axis.
+ rChange = maxShifts.y > maxShifts.z ? maxShifts.y : maxShifts.z;
+ }
+ else if (cc.direction == 1) //y
+ {
+ hChange = maxShifts.y;
+ rChange = maxShifts.x > maxShifts.z ? maxShifts.x : maxShifts.z;
+ }
+ else //z
+ {
+ hChange = maxShifts.z;
+ rChange = maxShifts.y > maxShifts.x ? maxShifts.y : maxShifts.x;
+ }
+ hChange *= mult;
+ rChange *= mult;
+ cc.height -= hChange;
+ cc.radius -= rChange;
+ if (cc.height < 0 || cc.radius < 0)
+ {
+ cc.height = 0;
+ cc.radius = 0;
+ }
+ }
+ else if (c is SphereCollider)
+ {
+ // simply shrink a sphere by reducing the radius by the maximum shift on any axis.
+ SphereCollider sc = c as SphereCollider;
+ sc.radius -= (Mathf.Max(maxShifts.x, Mathf.Max(maxShifts.y, maxShifts.z))) * mult;
+ if (sc.radius < 0)
+ {
+ sc.radius = 0;
+ }
+ }
+ // works for preview, would need to adjust actual source mesh for generation.
+ // else if (c is MeshCollider)
+ // {
+ // MeshCollider mc = c as MeshCollider;
+ // Vector3[] vertices = mc.sharedMesh.vertices;
+ // for (int i = 0; i < vertices.Length; i++)
+ // {
+ // vertices[i] -= maxShifts * mult;
+ // }
+ // mc.sharedMesh.vertices = vertices;
+ // }
+ }
+ }
+ }
+
+ ///
+ /// Shifts a collider by an amount in local space. Essentially just converts the collider to a box, capsule, or sphere, and shifts the center by amount.
+ ///
+ ///
+ ///
+ private void ShiftCollider(Collider c, Vector3 localAmount)
+ {
+ if (c is BoxCollider)
+ {
+ BoxCollider bc = c as BoxCollider;
+ bc.center += localAmount;
+ }
+ else if (c is CapsuleCollider)
+ {
+ CapsuleCollider cc = c as CapsuleCollider;
+ cc.center += localAmount;
+ }
+ else if (c is SphereCollider)
+ {
+ SphereCollider sc = c as SphereCollider;
+ sc.center += localAmount;
+ }
+ // works for preview, would need to adjust source mesh for generation.
+ // else if (c is MeshCollider)
+ // {
+ // MeshCollider mc = c as MeshCollider;
+ // Vector3[] vertices = mc.sharedMesh.vertices;
+ // for (int i = 0; i < vertices.Length; i++)
+ // {
+ // vertices[i] += localAmount;
+ // }
+ // mc.sharedMesh.vertices = vertices;
+ // }
+ }
+
+ ///
+ /// Attempts to depenetrate colliders by calculating the average of all ComputeDepenetration Vectors and apply it to the collider's center.
+ ///
+ ///
+ private void ShiftDepenetrate(List generatedColliders)
+ {
+ // TODO: use overlap to get direct array to use instead of n^2?
+ foreach (Collider c in generatedColliders)
+ {
+ // create a list of shifts for each collider.
+ List datas = new List(generatedColliders.Count - 1);
+ // cache transform
+ Transform cTransform = c.transform;
+ foreach (Collider other in generatedColliders)
+ {
+ if (c == other) continue;
+ ShiftData data = new ShiftData();
+ // if there's a shift, add it to that colliders shift data list.
+ if (GetShiftWorld(c, cTransform, other, out data))
+ {
+ datas.Add(data);
+ }
+ }
+ // no shifts? skip.
+ if (datas.Count == 0) continue;
+ Vector3 shiftVector = Vector3.zero;
+ // calculate the total shift vector.
+ // since we're doing this on all colliders, colliders's that are "stuck" between child colliders will likely not shift much
+ // and instead colliders that are free to shift in one direction will shift out first, which makes sense to do.
+ foreach (ShiftData data in datas)
+ {
+ shiftVector += cTransform.InverseTransformVector(data.Direction * data.Size);
+ }
+ // if we have a shift vector, shift!
+ if (shiftVector != Vector3.zero)
+ {
+ ShiftCount++;
+ ShiftAmount += shiftVector;
+ shiftVector /= datas.Count;
+ ShiftCollider(c, shiftVector);
+ }
+ }
+ }
+
+ ///
+ /// Data from a Physics.ComputePenetration result.
+ ///
+ private struct ShiftData
+ {
+ public Vector3 Direction;
+ public float Size;
+
+ public ShiftData(Vector3 direction, float size)
+ {
+ this.Direction = direction;
+ this.Size = size;
+ }
+ }
+
+ ///
+ /// Sets set shift data with the penetrations direction and size if there is one.
+ ///
+ ///
+ ///
+ ///
+ /// True if there is a penetration, false otherwise
+ private bool GetShiftWorld(Collider c, Transform ct, Collider other, out ShiftData data)
+ {
+ Vector3 direction = Vector3.zero;
+ float distance = 0.0f;
+ Transform ot = other.transform;
+ if (Physics.ComputePenetration(c, ct.position, ct.rotation, other, ot.position, ot.rotation, out direction, out distance))
+ {
+ data.Direction = direction;
+ data.Size = distance;
+ return true;
+ }
+ data.Direction = Vector3.zero;
+ data.Size = -1f;
+ return false;
+ }
+
+ ///
+ /// Gets the child bone of a transform if it has a single valid child bone.
+ ///
+ /// Bone to get child of
+ /// Array of bones
+ /// Transform of child bone if found, otherwise null
+ private Transform GetChildBone(Transform bone, Transform[] bones)
+ {
+ int boneChildCount = bone.childCount;
+ Transform childBone = null;
+ Transform currentChildTransform = null;
+ int totalValidChildBones = 0;
+ for (int j = 0; j < boneChildCount; j++)
+ {
+ currentChildTransform = bone.GetChild(j);
+ int index = Array.IndexOf(bones, currentChildTransform);
+ if (index >= 0)
+ {
+ totalValidChildBones += 1;
+ childBone = currentChildTransform;
+ }
+ }
+ if (totalValidChildBones == 1)
+ {
+ return childBone;
+ }
+ return null;
+ }
+
+ ///
+ /// Gets the minimum angle between all of transform's axis and the direction from transform to child.
+ ///
+ /// transform to use axis from
+ /// child to get minimum angle to
+ /// Minimum angle from all of transform's axis and the direction from transform to child.
+ private float GetMinimumChildAngle(Transform transform, Transform child)
+ {
+ Vector3 childDir = child.position - transform.position;
+ float minAngle = Mathf.Infinity;
+ float angle = Vector3.Angle(transform.right, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ angle = Vector3.Angle(-transform.right, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ angle = Vector3.Angle(transform.forward, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ angle = Vector3.Angle(-transform.forward, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ angle = Vector3.Angle(transform.up, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ angle = Vector3.Angle(-transform.up, childDir);
+ minAngle = minAngle > angle ? angle : minAngle;
+ return minAngle;
+ }
+
+ ///
+ /// Gets all the skinned mesh bones for the current skinned mesh renderer.
+ ///
+ ///
+ /// Array of skinned mesh bones.
+ private EasyColliderAutoSkinnedBone[] GetSkinnedMeshBones(SkinnedMeshRenderer skinnedMesh)
+ {
+ int validBonesFound = 0;
+ EasyColliderAutoSkinnedBone[] smbs = null;
+ // first, if there are transform in the bones array, we use that to get everything
+ if (skinnedMesh.bones.Length > 0)
+ {
+ smbs = new EasyColliderAutoSkinnedBone[skinnedMesh.bones.Length];
+ // try to match based on bones
+ Transform[] bones = skinnedMesh.bones;
+ for (int i = 0; i < bones.Length; i++)
+ {
+ // a bone was deleted....
+ if (bones[i] == null)
+ {
+ smbs[i] = new EasyColliderAutoSkinnedBone(new Matrix4x4(), i, bones[i]);
+ }
+ else
+ {
+ smbs[i] = new EasyColliderAutoSkinnedBone(bones[i].localToWorldMatrix, i, bones[i]);
+ smbs[i].BoneName = bones[i].name;
+ smbs[i].renderer = skinnedMesh;
+ validBonesFound++;
+ }
+ }
+ }
+ return smbs;
+ }
+
+ // native array and boneweight1 functionality are 2019.1+
+#if UNITY_2019_1_OR_NEWER
+ ///
+ /// Sets the skinned mesh bone's world vertices list.
+ ///
+ /// Skinned mesh we are trying to set world vertices for
+ /// Array of all skinned mesh bones
+ /// Array of all vertices in world space
+ /// Minimum bone weight to include a vertex in a bone's vertex list.
+ private void SetWorldVertices(EasyColliderAutoSkinnedBone[] skinnedMeshBones, NativeArray boneWeights, NativeArray bonesPerVertex, Vector3[] worldVertices, float minBoneWeight)
+ {
+ int boneIndex = 0;
+ for (int i = 0; i < worldVertices.Length; i++)
+ {
+ int numBonesForVertex = bonesPerVertex[i];
+ for (int j = 0; j < numBonesForVertex; j++)
+ {
+ BoneWeight1 boneWeight = boneWeights[boneIndex];
+ if (boneWeight.boneIndex < skinnedMeshBones.Length && boneWeight.weight >= skinnedMeshBones[boneWeight.boneIndex].BoneWeight)
+ {
+ skinnedMeshBones[boneWeight.boneIndex].WorldSpaceVertices.Add(worldVertices[i]);
+ }
+ boneIndex++;
+ }
+ }
+ }
+#endif
+
+ ///
+ /// Sets the skinned mesh bone's world vertices list.
+ ///
+ /// Array of all skinned mesh bones
+ /// Array of all bone weights
+ /// Array of all vertices in world space
+ /// Minimum bone weight to include a vertex in a bone's vertex list.
+ private void SetWorldVertices(EasyColliderAutoSkinnedBone[] skinnedMeshBones, BoneWeight[] boneWeights, Vector3[] worldVertices)
+ {
+ for (int i = 0; i < boneWeights.Length; i++)
+ {
+ // make sure the weight is above the minimum weight.
+ if (skinnedMeshBones[boneWeights[i].boneIndex0] != null && boneWeights[i].weight0 >= skinnedMeshBones[boneWeights[i].boneIndex0].BoneWeight)
+ {
+ // add the vertex to that bone's vertex list
+ skinnedMeshBones[boneWeights[i].boneIndex0].WorldSpaceVertices.Add(worldVertices[i]);
+ }
+ if (skinnedMeshBones[boneWeights[i].boneIndex1] != null && boneWeights[i].weight1 >= skinnedMeshBones[boneWeights[i].boneIndex1].BoneWeight)
+ {
+ // add the vertex to that bone's vertex list
+ skinnedMeshBones[boneWeights[i].boneIndex1].WorldSpaceVertices.Add(worldVertices[i]);
+ }
+ if (skinnedMeshBones[boneWeights[i].boneIndex2] != null && boneWeights[i].weight2 >= skinnedMeshBones[boneWeights[i].boneIndex2].BoneWeight)
+ {
+ // add the vertex to that bone's vertex list
+ skinnedMeshBones[boneWeights[i].boneIndex2].WorldSpaceVertices.Add(worldVertices[i]);
+ }
+ if (skinnedMeshBones[boneWeights[i].boneIndex3] != null && boneWeights[i].weight3 >= skinnedMeshBones[boneWeights[i].boneIndex3].BoneWeight)
+ {
+ // add the vertex to that bone's vertex list
+ skinnedMeshBones[boneWeights[i].boneIndex3].WorldSpaceVertices.Add(worldVertices[i]);
+ }
+ }
+ }
+
+ public void OnBeforeSerialize()
+ {
+ //nothing needed
+ }
+
+ public void OnAfterDeserialize()
+ {
+ // need to relink the classes as otherwise the instances become seperate instances during serialization.
+ for (int i = 0; i < SortedBoneList.Count; i++)
+ {
+ SortedBoneList[i] = BoneList[SortedBoneList[i].BoneIndex];
+ }
+ }
+ }
+}
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs.meta b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs.meta
new file mode 100644
index 00000000..077b7904
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs.meta
@@ -0,0 +1,20 @@
+fileFormatVersion: 2
+guid: 45eb3a409ea753b44ab3bb6b8585be07
+timeCreated: 1591641873
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs
new file mode 100644
index 00000000..c83be8a3
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using UnityEngine;
+namespace ECE
+{
+ [System.Serializable]
+ ///
+ /// Class to hold data for a skinned meshes' bones
+ /// Used because the bones transform array can be empty in cases where optimize gameobjects is used on import
+ /// The bindbose index of a bone, is the same as a bone's boneindex, so boneweight's can still be used.
+ ///
+ public class EasyColliderAutoSkinnedBone
+ {
+
+ public SkinnedMeshRenderer renderer;
+
+ ///
+ /// Create a collider for this bone?
+ ///
+ public bool Enabled = true;
+ ///
+ /// What kind of collider to create.
+ ///
+ public SKINNED_MESH_COLLIDER_TYPE ColliderType = SKINNED_MESH_COLLIDER_TYPE.Box;
+ ///
+ /// Minimum skinning bone weight to include a vertex in the bone's collider calculations.
+ ///
+ public float BoneWeight = 0.5f;
+ ///
+ /// Bone's display name (transform.name)
+ ///
+ public string BoneName = "Default";
+ ///
+ /// Is this bone paired with another bone?
+ ///
+ public bool IsPaired = false;
+ ///
+ /// If this bone is paired, is this the bone that is displayed in the UI?
+ ///
+ public bool IsPairDisplayBone = false;
+ ///
+ /// Is this bone valid? (Has at least 1 vertex that meets it's boneWeight)
+ ///
+ public bool IsValid = false;
+
+ public EasyColliderAutoSkinnedBone(Matrix4x4 bp, int index, Transform t)
+ {
+ BoneIndex = index;
+ BindPose = bp;
+ Transform = t;
+ }
+
+ ///
+ /// Local to world matrix of the bone's transform.
+ ///
+ public Matrix4x4 Matrix;
+
+ ///
+ /// Bind pose of the bone
+ ///
+ public Matrix4x4 BindPose;
+
+ ///
+ /// Bind pose index is the bone index as well.
+ ///
+ public int BoneIndex;
+
+ ///
+ /// Transform of the bone
+ ///
+ public Transform Transform;
+
+ ///
+ /// List of vertices in world space for this bo
+ ///
+ ///
+ ///
+ public List WorldSpaceVertices = new List();
+
+ [SerializeField]
+ ///
+ /// List of other bone's index's in the BoneList that this bone is paired with.
+ ///
+ ///
+ ///
+ public List PairedBones = new List();
+
+ ///
+ /// Collider for this bone.
+ ///
+ public Collider Collider;
+
+ ///
+ /// Indent level of this bone when displaying in UI.
+ ///
+ public int IndentLevel = -1;
+ }
+}
\ No newline at end of file
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs.meta b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs.meta
new file mode 100644
index 00000000..f135b79b
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs.meta
@@ -0,0 +1,20 @@
+fileFormatVersion: 2
+guid: 22d8868ad24ea0c43828e22314004216
+timeCreated: 1625687084
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs
new file mode 100644
index 00000000..150a91fc
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs
@@ -0,0 +1,2176 @@
+
+using System.Collections.Generic;
+using UnityEngine;
+#if (UNITY_EDITOR)
+using UnityEditor;
+#endif
+using System.Linq;
+namespace ECE
+{
+ ///
+ /// Class to calculate and add colliders
+ ///
+ public class EasyColliderCreator
+ {
+
+#if (UNITY_EDITOR)
+
+ ///
+ /// Used in ECEAutoSkinned when generating previews / depenetration.
+ ///
+ public bool UndoEnabled = true;
+
+ ///
+ /// Just an easy way to get an instance of preferences to create colliders with.
+ ///
+ ///
+ private EasyColliderPreferences ECEPreferences
+ {
+ get { return EasyColliderPreferences.Preferences; }
+ }
+#endif
+ ///
+ /// Data struct from calculating a best fit sphere
+ ///
+ private struct BestFitSphere
+ {
+ ///
+ /// Center of the sphere
+ ///
+ public Vector3 Center;
+
+ ///
+ /// Radius of the sphere
+ ///
+ public float Radius;
+
+ ///
+ /// Best Fit Sphere
+ ///
+ /// Center of the sphere
+ /// Radius of the sphere
+ public BestFitSphere(Vector3 center, float radius)
+ {
+ this.Center = center;
+ this.Radius = radius;
+ }
+ }
+
+
+
+ // rotate and duplicate are editor-only
+#if (UNITY_EDITOR)
+
+ ///
+ /// gets a rotation as a quaternion from a transformation matrix.
+ /// useful for matrix operations in earlier versions of unity
+ ///
+ ///
+ ///
+ private Quaternion RotationFromMatrix(Matrix4x4 matrix)
+ {
+ Vector3 forward = new Vector3(matrix[0, 2], matrix[1, 2], matrix[2, 2]);
+ Vector3 up = new Vector3(matrix[0, 1], matrix[1, 1], matrix[2, 1]);
+ return Quaternion.LookRotation(forward, up);
+ }
+
+ ///
+ /// gets a scale from a transformation matrix.
+ /// useful for matrix operations in earlier versions of unity
+ ///
+ ///
+ ///
+ private Vector3 ScaleFromMatrix(Matrix4x4 matrix)
+ {
+ Vector3 mScale = Vector3.zero;
+ mScale.x = new Vector4(matrix[0, 0], matrix[1, 0], matrix[2, 0], matrix[3, 0]).magnitude;
+ mScale.y = new Vector4(matrix[0, 1], matrix[1, 1], matrix[2, 1], matrix[3, 1]).magnitude;
+ mScale.z = new Vector4(matrix[0, 2], matrix[1, 2], matrix[2, 2], matrix[3, 2]).magnitude;
+ return mScale;
+ }
+
+ private Vector3 PositionFromMatrix(Matrix4x4 matrix)
+ {
+ Vector3 mScale = new Vector3(matrix[0, 3], matrix[1, 3], matrix[2, 3]);
+ return mScale;
+ }
+
+ public List CalculateRotateAndDuplicate(EasyColliderPreferences preferences, EasyColliderEditor ece)
+ {
+ EasyColliderRotateDuplicate ecrd = preferences.rotatedDupeSettings;
+ ecrd.attachTo = ece.AttachToObject;
+ return CalculateRotateAndDuplicate(preferences.PreviewColliderType, ece.GetWorldVertices(true), ecrd);
+ }
+
+ private List CalculateRotateAndDuplicate(CREATE_COLLIDER_TYPE result, List worldVertices, EasyColliderRotateDuplicate ecrd)
+ {
+ Transform attachTo = ecrd.attachTo.transform;
+ Transform pivot = ecrd.pivot.transform;
+ List data = new List();
+ // we want to create the normal colliders in the attachto's local space, but rotate them around the pivot point.
+ List localVertices = ToLocalVerts(attachTo, worldVertices);
+ EasyColliderData baseData = null;
+ switch (result)
+ {
+ case CREATE_COLLIDER_TYPE.BOX:
+ // we want the normal colliders calculated in local space of the attach to object.
+ // baseData = CalculateBox(worldVertices, attachTo, false);
+ baseData = CalculateBoxLocal(localVertices);
+ //need to manually handle this here since we're not using the calculate(method) methods, just the calculate methods.
+#if(UNITY_EDITOR)
+ BoxColliderData bc = (BoxColliderData)baseData;
+ bc.Size *= ECEPreferences.ShrinkGrow;
+#endif
+ break;
+ case CREATE_COLLIDER_TYPE.ROTATED_BOX:
+ baseData = CalculateBox(worldVertices, pivot, true, true);
+ break;
+ case CREATE_COLLIDER_TYPE.SPHERE:
+ baseData = CalculateSphereMinMaxLocal(localVertices);
+#if (UNITY_EDITOR)
+ SphereColliderData sc = (SphereColliderData)baseData;
+ sc.Radius *= ECEPreferences.ShrinkGrow;
+#endif
+ break;
+ case CREATE_COLLIDER_TYPE.CAPSULE:
+ baseData = CalculateCapsuleMinMaxLocal(localVertices, ECEPreferences.CapsuleColliderMethod);
+#if (UNITY_EDITOR)
+ CapsuleColliderData cc = (CapsuleColliderData)baseData;
+ cc.Radius *= ECEPreferences.ShrinkGrow;
+ cc.Height *= ECEPreferences.ShrinkGrow;
+#endif
+ break;
+ case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
+ baseData = CalculateCapsuleMinMax(worldVertices, pivot, ECEPreferences.CapsuleColliderMethod, true, true);
+ break;
+ case CREATE_COLLIDER_TYPE.CONVEX_MESH:
+#if (UNITY_EDITOR)
+ AdjustVerticesForShrinkGrow(localVertices, ECEPreferences.ShrinkGrow);
+#endif
+ baseData = CalculateMeshColliderQuickHullLocal(localVertices);
+ break;
+ case CREATE_COLLIDER_TYPE.CYLINDER:
+#if (UNITY_EDITOR)
+ AdjustVerticesForShrinkGrow(localVertices, ECEPreferences.ShrinkGrow);
+#endif
+ baseData = CalculateCylinderColliderLocal(localVertices, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
+ break;
+ }
+
+
+ if (result != CREATE_COLLIDER_TYPE.ROTATED_BOX && result != CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
+ {
+ // rotated colliders have thier own matrix.
+ baseData.Matrix = attachTo.localToWorldMatrix;
+ }
+
+ // just calculating the angle and axis to rotate
+ float rotationAngle = (ecrd.EndRotation - ecrd.StartRotation) / (ecrd.NumberOfDuplications);
+ float currentAngle = ecrd.StartRotation;
+ Vector3 axis = Vector3.zero;
+ axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.X ? pivot.right : axis;
+ axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.Y ? pivot.up : axis;
+ axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.Z ? pivot.forward : axis;
+
+ Quaternion baseRotation = RotationFromMatrix(baseData.Matrix);
+ // will just be the attachTo's position at this point.
+ Vector3 basePosition = PositionFromMatrix(baseData.Matrix);
+ // lossy scale essentially.
+ Vector3 baseScale = ScaleFromMatrix(baseData.Matrix);
+ //Debug.Log("Base Pos:" + basePosition + " Base Rotation:" + baseRotation + " Base Scale:" + baseScale);
+ // used in the longer version, leaving in here for future reference use.
+ // Matrix4x4 pivotTranslation = Matrix4x4.Translate(pivot.position);
+
+ for (int i = 0; i < ecrd.NumberOfDuplications; i++)
+ {
+ // rotation around pivot's axis.
+ Quaternion q = Quaternion.AngleAxis(currentAngle, axis);
+ // We're gonna leave this here for my future self, as matrix math still confuses me
+ // create rotation matrix
+ // Matrix4x4 rotationMatrix = Matrix4x4.TRS(Vector3.zero, q * baseRotation, mScale);
+ // // // calculate rotated forward from the pivot to the attach to object.
+ //Vector3 forward = q * (attachTo.position - pivot.position);
+ // // create a matrix for the rotated pivot to attach translation
+ // var pushTranslate = Matrix4x4.Translate(forward);
+ // // use the rotation matrix
+ // m = rotationMatrix;
+ // // translated it to the pivot point.
+ // m = pivotTranslation * m;
+ // // push out from pivot
+ // m = pushTranslate * m;
+ // Above: simplified
+ //m = Matrix4x4.TRS(basePosition + forward, q * baseRotation, baseScale);
+ // but the preview matrix isn't correclty factoring in non-uniform scale.
+ //}
+ Vector3 forward = q * (attachTo.position - pivot.position);
+ Matrix4x4 m; m = Matrix4x4.TRS(basePosition + forward, q * baseRotation, baseScale);
+ // preview not correctly using non-uniform scale.
+ switch (result)
+ {
+ case CREATE_COLLIDER_TYPE.CAPSULE:
+ case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
+ {
+ CapsuleColliderData d1 = baseData as CapsuleColliderData;
+ CapsuleColliderData d2 = new CapsuleColliderData();
+ d2.Clone(d1);
+ d2.Matrix = m;
+ data.Add(d2);
+ break;
+ }
+ case CREATE_COLLIDER_TYPE.BOX:
+ case CREATE_COLLIDER_TYPE.ROTATED_BOX:
+ {
+ BoxColliderData d1 = baseData as BoxColliderData;
+ BoxColliderData d2 = new BoxColliderData();
+ d2.Clone(d1);
+ d2.Matrix = m;
+ data.Add(d2);
+ break;
+ }
+ case CREATE_COLLIDER_TYPE.SPHERE:
+ {
+ SphereColliderData d1 = baseData as SphereColliderData;
+ SphereColliderData d2 = new SphereColliderData();
+ d2.Clone(d1);
+ d2.Matrix = m;
+ data.Add(d2);
+ break;
+ }
+ case CREATE_COLLIDER_TYPE.CONVEX_MESH:
+ case CREATE_COLLIDER_TYPE.CYLINDER:
+ {
+ MeshColliderData d1 = baseData as MeshColliderData;
+ MeshColliderData d2 = new MeshColliderData();
+ d2.Clone(d1);
+ d2.Matrix = m;
+ data.Add(d2);
+ break;
+ }
+ }
+ currentAngle += rotationAngle;
+ }
+ return data;
+ }
+
+
+ ///
+ /// for actually creating the colliders.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public List CreateRotateAndDuplicateColliders(CREATE_COLLIDER_TYPE collider_type, List worldVertices, EasyColliderProperties properties)
+ {
+ // Debug.Log("ECC: Create Rotated and Duplicate Colliders");
+ List CreatedColliders = new List();
+ EasyColliderRotateDuplicate ecrd = ECEPreferences.rotatedDupeSettings;
+ float rotationPerCollider = (ecrd.EndRotation - ecrd.StartRotation) / ecrd.NumberOfDuplications;
+ float currentRotation = 0.0f;
+ Transform parent = properties.AttachTo.transform;
+ List data = CalculateRotateAndDuplicate(collider_type, worldVertices, ecrd);
+ foreach (EasyColliderData d in data)
+ {
+ GameObject obj = new GameObject("Rotated Collider");
+#if (UNITY_EDITOR)
+ Undo.RegisterCreatedObjectUndo(obj, " Create Rotated Collider");
+#endif
+ obj.transform.rotation = RotationFromMatrix(d.Matrix);
+ obj.transform.SetParent(ecrd.pivot.transform);
+ obj.transform.position = PositionFromMatrix(d.Matrix);
+ obj.transform.localScale = Vector3.one;
+ obj.layer = properties.Layer;
+ properties.AttachTo = obj;
+ properties.Orientation = COLLIDER_ORIENTATION.ROTATED;
+ Collider collider = null;
+ switch (collider_type)
+ {
+ case CREATE_COLLIDER_TYPE.BOX:
+ obj.name = "Rotated Box Collider";
+ collider = CreateBoxCollider(d as BoxColliderData, properties, false);
+ break;
+ case CREATE_COLLIDER_TYPE.ROTATED_BOX:
+ obj.name = "Rotated Box Collider";
+ BoxColliderData rbd = d as BoxColliderData;
+ collider = CreateBoxCollider(rbd, properties, false);
+ break;
+ case CREATE_COLLIDER_TYPE.SPHERE:
+ obj.name = "Rotated Sphere Collider";
+ collider = CreateSphereCollider(d as SphereColliderData, properties, false);
+ break;
+ case CREATE_COLLIDER_TYPE.CAPSULE:
+ obj.name = "Rotated Capsule Collider";
+ collider = CreateCapsuleCollider(d as CapsuleColliderData, properties, false);
+ break;
+ case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
+ obj.name = "Rotated Capsule Collider";
+ CapsuleColliderData rcd = d as CapsuleColliderData;
+ collider = CreateCapsuleCollider(rcd, properties, false);
+ break;
+ case CREATE_COLLIDER_TYPE.CONVEX_MESH:
+ case CREATE_COLLIDER_TYPE.CYLINDER:
+ MeshColliderData mcd = d as MeshColliderData;
+ if (ECEPreferences.SaveConvexHullAsAsset)
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(mcd.ConvexMesh, parent.gameObject);
+ }
+ collider = CreateConvexMeshCollider(mcd.ConvexMesh, properties.AttachTo, properties);
+ break;
+ }
+ CreatedColliders.Add(collider);
+ currentRotation += rotationPerCollider;
+#if (UNITY_EDITOR)
+ Undo.SetTransformParent(obj.transform, parent, "Change Parent");
+#else
+ obj.transform.SetParent(parent);
+#endif
+ obj.transform.localScale = Vector3.one;
+ PostColliderCreationProcess(collider, properties);
+ }
+ return CreatedColliders;
+ }
+
+#endif
+
+ // merge colliders are editor-only.
+#if (UNITY_EDITOR)
+
+ #region MergeColliders
+
+
+
+ ///
+ /// Merges all colliders in the list to a single resultant collider and returns it.
+ ///
+ /// List of colliders to merge
+ /// Type of collider we want the colliders merged into
+ /// Properties to set on the new collider
+ /// Single merged collider.
+ public Collider MergeColliders(List collidersToMerge, CREATE_COLLIDER_TYPE result, EasyColliderProperties properties)
+ {
+ if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
+ }
+ EasyColliderData data = MergeCollidersPreview(collidersToMerge, result, properties.AttachTo.transform);
+ if (result == CREATE_COLLIDER_TYPE.BOX || result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
+ {
+ if (result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
+ {
+ properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
+ }
+ return CreateBoxCollider(data as BoxColliderData, properties);
+ }
+ else if (result == CREATE_COLLIDER_TYPE.CAPSULE || result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
+ {
+ if (result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
+ {
+ properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
+ }
+ return CreateCapsuleCollider(data as CapsuleColliderData, properties);
+ }
+ else if (result == CREATE_COLLIDER_TYPE.SPHERE)
+ {
+ return CreateSphereCollider(data as SphereColliderData, properties);
+ }
+ else if (result == CREATE_COLLIDER_TYPE.CONVEX_MESH || result == CREATE_COLLIDER_TYPE.CYLINDER)
+ {
+ MeshColliderData d = data as MeshColliderData;
+ if (ECEPreferences.SaveConvexHullAsAsset)
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(d.ConvexMesh, properties.AttachTo);
+ }
+ return CreateConvexMeshCollider(d.ConvexMesh, properties.AttachTo, properties);
+ }
+ return null;
+ }
+
+ ///
+ /// Returns the first transform of a non-null collider
+ ///
+ /// list of colliders
+ /// first non-null collider's transform, null if no non-null colliders
+ private Transform GetFirstNonNullTransform(List collidersList)
+ {
+ foreach (Collider c in collidersList)
+ {
+ if (c != null)
+ {
+ return c.transform;
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Calculates the preview data for merged colliders.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public EasyColliderData MergeCollidersPreview(List collidersToMerge, CREATE_COLLIDER_TYPE result, Transform attachTo)
+ {
+ List worldVertices = GetWorldVertsForColliders(collidersToMerge);
+ EasyColliderData d = new EasyColliderData();
+ if (result == CREATE_COLLIDER_TYPE.CONVEX_MESH)
+ {
+ return EasyColliderQuickHull.CalculateHullData(worldVertices, attachTo);
+ }
+ else if (result == CREATE_COLLIDER_TYPE.BOX || result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
+ {
+ if (result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
+ {
+ attachTo = GetFirstNonNullTransform(collidersToMerge);
+ }
+ return CalculateBox(worldVertices, attachTo, false);
+ }
+ else if (result == CREATE_COLLIDER_TYPE.SPHERE)
+ {
+ // does it make sense to allow different sphere methods -> or just min-max method.
+ if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.MinMax)
+ {
+ return CalculateSphereMinMax(worldVertices, attachTo);
+ }
+ else if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.Distance)
+ {
+ return CalculateSphereDistance(worldVertices, attachTo);
+ }
+ else if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.BestFit)
+ {
+ return CalculateSphereBestFit(worldVertices, attachTo);
+ }
+ }
+ else if (result == CREATE_COLLIDER_TYPE.CAPSULE || result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
+ {
+ if (result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
+ {
+ attachTo = GetFirstNonNullTransform(collidersToMerge);
+ }
+ // does it make sense to allow capsule methods? -> can use various min max + dia, radius etc.
+ if (ECEPreferences.CapsuleColliderMethod == CAPSULE_COLLIDER_METHOD.BestFit)
+ {
+ return CalculateCapsuleBestFit(worldVertices, attachTo, false);
+ }
+ else
+ {
+ return CalculateCapsuleMinMax(worldVertices, attachTo, ECEPreferences.CapsuleColliderMethod, false);
+ }
+ }
+ else if (result == CREATE_COLLIDER_TYPE.CYLINDER)
+ {
+ return CalculateCylinderCollider(worldVertices, attachTo);
+ }
+ return d;
+ }
+
+ public List GetWorldVertsForColliders(List colliders)
+ {
+ List worldVertices = new List();
+ foreach (Collider col in colliders)
+ {
+ // Get world vertices for mesh collider.
+ MeshCollider mc = col as MeshCollider;
+ if (mc != null)
+ {
+ AddWorldVerts(mc, worldVertices);
+ continue;
+ }
+ BoxCollider box = col as BoxCollider;
+ if (box != null)
+ {
+ AddWorldVerts(box, worldVertices);
+ continue;
+ }
+ CapsuleCollider capsule = col as CapsuleCollider;
+ if (capsule != null)
+ {
+ AddWorldVerts(capsule, worldVertices);
+ continue;
+ }
+ SphereCollider sphere = col as SphereCollider;
+ if (sphere != null)
+ {
+ AddWorldVerts(sphere, worldVertices);
+ continue;
+ }
+ }
+ return worldVertices;
+ }
+
+ ///
+ /// Adds the vertices of a mesh collider to the world vertices list
+ ///
+ /// mesh collider
+ /// world vertices list
+ private void AddWorldVerts(MeshCollider meshCollider, List worldVertices)
+ {
+ if (meshCollider == null || meshCollider.sharedMesh == null) return;
+ Vector3[] vertices = meshCollider.sharedMesh.vertices;
+ Transform t = meshCollider.transform;
+ for (int i = 0; i < vertices.Length; i++)
+ {
+ vertices[i] = t.TransformPoint(vertices[i]);
+ }
+ worldVertices.AddRange(vertices);
+ }
+
+ ///
+ /// Adds the vertices of a box collider to the world vertices list
+ ///
+ /// box collider
+ /// world vertices list
+ private void AddWorldVerts(BoxCollider boxCollider, List worldVertices)
+ {
+ Vector3 halfSize = boxCollider.size / 2;
+ Vector3 center = boxCollider.center;
+ Vector3[] vertices = new Vector3[8]{
+ center + halfSize, //0
+ center + new Vector3(halfSize.x, halfSize.y, -halfSize.z), //1
+ center + new Vector3(halfSize.x, -halfSize.y, halfSize.z), //2
+ center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z), //3
+ center + new Vector3(-halfSize.x, halfSize.y, halfSize.z), //4
+ center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z), //5
+ center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z), //6
+ center - halfSize, //7
+ };
+ int triangleOffset = worldVertices.Count;
+ Transform t = boxCollider.transform;
+ for (int i = 0; i < vertices.Length; i++)
+ {
+ vertices[i] = t.TransformPoint(vertices[i]);
+ }
+ // add triangles and verts
+ worldVertices.AddRange(vertices);
+ }
+
+ ///
+ /// Adds the vertices of a sphere collider to the world vertices list
+ ///
+ /// sphere collider
+ /// world vertices list
+ private void AddWorldVerts(SphereCollider sphereCollider, List worldVertices)
+ {
+ AddWorldVertsSphere(sphereCollider.transform, sphereCollider.center, sphereCollider.radius, worldVertices);
+ }
+
+ ///
+ /// Adds the vertices of a capsule collider to the world vertices list
+ ///
+ /// capsule collider
+ /// world vertices list
+ private void AddWorldVerts(CapsuleCollider capsuleCollider, List worldVertices)
+ {
+ Vector3 top = Vector3.zero;
+ Vector3 bottom = Vector3.zero;
+ if (capsuleCollider.direction == 0) //x
+ {
+ top = capsuleCollider.center + Vector3.right * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ bottom = capsuleCollider.center - Vector3.right * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ }
+ else if (capsuleCollider.direction == 1) //y
+ {
+ top = capsuleCollider.center + Vector3.up * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ bottom = capsuleCollider.center - Vector3.up * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ }
+ else if (capsuleCollider.direction == 2) //z
+ {
+ top = capsuleCollider.center + Vector3.forward * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ bottom = capsuleCollider.center - Vector3.forward * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
+ }
+
+ //manually reselect collider points in an order that maintains rotation for rotated capsule colliders
+ // again the name method is brittle, but seems the easiest way to identify rotated colliders without cluttering tags.
+ if (capsuleCollider.gameObject.name.Contains("Rotated"))
+ {
+ Transform t = capsuleCollider.transform;
+ worldVertices.Add(t.TransformPoint(top));
+ worldVertices.Add(t.TransformPoint(bottom));
+ worldVertices.Add(t.TransformPoint(capsuleCollider.center + Vector3.Cross(top, bottom).normalized * capsuleCollider.radius));
+ }
+
+ // Easiest to just add the full top and bottom spheres as the result is the same for any collider.
+ // as they contain the min and max values of collider, and the middle section is all on the same plane as the halfsphere's base for convex meshes.
+ // we could write a seperate method to only add the half-spheres, but there's no need.
+ // top sphere
+ AddWorldVertsSphere(capsuleCollider.transform, top, capsuleCollider.radius, worldVertices);
+ // bottom sphere
+ AddWorldVertsSphere(capsuleCollider.transform, bottom, capsuleCollider.radius, worldVertices);
+ }
+
+ ///
+ /// Adds world space points around a sphere
+ ///
+ /// transform of the collider
+ /// center of the sphere
+ /// radius of the sphere
+ ///
+ private void AddWorldVertsSphere(Transform t, Vector3 center, float radius, List worldVertices)
+ {
+ int accuracy = ECEPreferences.MergeCollidersRoundnessAccuracy;
+ // 360 degrees in radians.
+ float sin, cos = sin = 0.0f;
+ for (int i = 1; i < accuracy; i++)
+ {
+ // center shifted to the z-axis
+ float h = (i / (float)accuracy) * radius * 2;
+ Vector3 centerX = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.right;
+ Vector3 centerY = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.up;
+ Vector3 centerZ = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.forward;
+ float newRadius = Mathf.Sqrt(radius * 2 * h - Mathf.Pow(h, 2));
+ for (int j = 0; j <= accuracy; j++)
+ {
+ float angleStep = ((j / (float)accuracy) * 360f) * Mathf.Deg2Rad;
+ sin = Mathf.Sin(angleStep);
+ cos = Mathf.Cos(angleStep);
+ // constant z.
+ float xZ = centerZ.x + newRadius * sin;
+ float yZ = centerZ.y + newRadius * cos;
+ // constant x.
+ float yX = centerX.y + newRadius * sin;
+ float zX = centerX.z + newRadius * cos;
+ // constant y.
+ float zY = centerY.z + (newRadius * sin);
+ float xY = centerY.x + (newRadius * cos);
+ //
+ worldVertices.Add(t.TransformPoint(new Vector3(centerX.x, yX, zX)));
+ worldVertices.Add(t.TransformPoint(new Vector3(xY, centerY.y, zY)));
+ worldVertices.Add(t.TransformPoint(new Vector3(xZ, yZ, centerZ.z)));
+ }
+ }
+ }
+
+ #endregion
+
+#endif
+
+ #region ColliderDataCalculation
+
+ ///
+ /// Calculates the best fit sphere for a series of points. Providing a larger list of points increases accuracy.
+ ///
+ /// Local space vertices
+ /// The best fit sphere
+ private BestFitSphere CalculateBestFitSphere(List localVertices)
+ {
+ // # of points.
+ int n = localVertices.Count;
+ // Calculate average x, y, and z value of vertices.
+ float xAvg, yAvg, zAvg = xAvg = yAvg = 0.0f;
+ foreach (Vector3 vertex in localVertices)
+ {
+ xAvg += vertex.x;
+ yAvg += vertex.y;
+ zAvg += vertex.z;
+ }
+ xAvg = xAvg * (1.0f / n);
+ yAvg = yAvg * (1.0f / n);
+ zAvg = zAvg * (1.0f / n);
+ // Do some fun math with matrices
+ // B Vector.
+ Vector3 B = Vector3.zero;
+ // Can use a 4x4 as a 3x3 with the 4x4 as 0,0,0,1 in the last row/column.
+ Matrix4x4 AM = Matrix4x4.zero;
+ AM.m33 = 1;
+ float x2, y2, z2 = x2 = y2 = 0.0f;
+ foreach (Vector3 vertex in localVertices)
+ {
+ AM[0, 0] += 2 * (vertex.x * (vertex.x - xAvg)) / n;
+ AM[0, 1] += 2 * (vertex.x * (vertex.y - yAvg)) / n;
+ AM[0, 2] += 2 * (vertex.x * (vertex.z - zAvg)) / n;
+ AM[1, 0] += 2 * (vertex.y * (vertex.x - xAvg)) / n;
+ AM[1, 1] += 2 * (vertex.y * (vertex.y - yAvg)) / n;
+ AM[1, 2] += 2 * (vertex.y * (vertex.z - zAvg)) / n;
+ AM[2, 0] += 2 * (vertex.z * (vertex.x - xAvg)) / n;
+ AM[2, 1] += 2 * (vertex.z * (vertex.y - yAvg)) / n;
+ AM[2, 2] += 2 * (vertex.z * (vertex.z - zAvg)) / n;
+ x2 = vertex.x * vertex.x;
+ y2 = vertex.y * vertex.y;
+ z2 = vertex.z * vertex.z;
+ B.x += ((x2 + y2 + z2) * (vertex.x - xAvg)) / n;
+ B.y += ((x2 + y2 + z2) * (vertex.y - yAvg)) / n;
+ B.z += ((x2 + y2 + z2) * (vertex.z - zAvg)) / n;
+ }
+ // Calculate the center of the best-fit sphere.
+ Vector3 center = (AM.transpose * AM).inverse * AM.transpose * B;
+ // Calculate radius.
+ float radius = 0.0f;
+ foreach (Vector3 vertex in localVertices)
+ {
+ radius += Mathf.Pow((vertex.x - center.x), 2) + Mathf.Pow(vertex.y - center.y, 2) + Mathf.Pow(vertex.z - center.z, 2);
+ }
+ radius = Mathf.Sqrt(radius / localVertices.Count);
+ BestFitSphere bfs = new BestFitSphere(center, radius);
+ return bfs;
+ }
+
+ ///
+ /// Calculates a box's data from a list of world space vertices
+ ///
+ /// list of vertices in world space
+ /// transform the box will be attached to
+ /// are we creating a rotated box?
+ /// true when the attach to transform is not already correctly rotated for a rotated collider
+ /// adjusts the size of the box collider
+ /// Data appropriate variables set for a box collider
+ public BoxColliderData CalculateBox(List worldVertices, Transform attachTo, bool isRotated = false, bool requiresMatrixCalc = false, float shrinkGrow = 1f)
+ {
+ if (isRotated && worldVertices.Count < 3)
+ {
+ return new BoxColliderData();
+ }
+ else if (worldVertices.Count < 2)
+ {
+ return new BoxColliderData();
+ }
+ Quaternion q = Quaternion.identity;
+ Matrix4x4 m;
+ List localVertices = new List();
+ if (isRotated && worldVertices.Count >= 3 && requiresMatrixCalc)
+ {
+ Vector3 forward = worldVertices[1] - worldVertices[0];
+ Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
+ q = Quaternion.LookRotation(forward, up);
+ // world space matrix, pos doesn't matter here in preview.
+ m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
+ Matrix4x4 mi = m.inverse;
+ // dont actually have the object in the preview.
+ for (int i = 0; i < worldVertices.Count; i++)
+ {
+ localVertices.Add(mi.MultiplyPoint3x4(worldVertices[i]));
+ }
+ }
+ else
+ {
+ localVertices = ToLocalVerts(attachTo, worldVertices);
+ m = attachTo.localToWorldMatrix;
+ }
+ BoxColliderData data = CalculateBoxLocal(localVertices);
+ data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_BOX : CREATE_COLLIDER_TYPE.BOX;
+ data.Matrix = m;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Size *= shrinkGrow;
+ return data;
+ }
+
+ ///
+ /// Calculates box collider data for a list of local space vertices
+ ///
+ /// list of local space vertices
+ /// box collider data with center and size set
+ public BoxColliderData CalculateBoxLocal(List vertices)
+ {
+ float xMin, yMin, zMin = xMin = yMin = Mathf.Infinity;
+ float xMax, yMax, zMax = xMax = yMax = -Mathf.Infinity;
+ foreach (Vector3 vertex in vertices)
+ {
+ //x min & max.
+ xMin = (vertex.x < xMin) ? vertex.x : xMin;
+ xMax = (vertex.x > xMax) ? vertex.x : xMax;
+ //y min & max
+ yMin = (vertex.y < yMin) ? vertex.y : yMin;
+ yMax = (vertex.y > yMax) ? vertex.y : yMax;
+ //z min & max
+ zMin = (vertex.z < zMin) ? vertex.z : zMin;
+ zMax = (vertex.z > zMax) ? vertex.z : zMax;
+ }
+ Vector3 max = new Vector3(xMax, yMax, zMax);
+ Vector3 min = new Vector3(xMin, yMin, zMin);
+ Vector3 size = max - min;
+ Vector3 center = (max + min) / 2;
+ // set data from calculated values
+ BoxColliderData data = new BoxColliderData();
+ data.Center = center;
+ data.ColliderType = CREATE_COLLIDER_TYPE.BOX;
+ data.IsValid = true;
+ data.Size = size;
+ return data;
+ }
+
+ ///
+ /// Calculates a capsule's data from the given values using the best fit method and a list of world space vertices
+ ///
+ /// list of vertices in world space
+ /// transform the capsule will be attached to
+ /// are we creating a rotated capsule?
+ /// true when the attach to transform is not already correctly rotated for a rotated collider
+ /// adjust the height/radius of the capsule collider
+ /// Data with appropriate variables set for a capsule collider
+ public CapsuleColliderData CalculateCapsuleBestFit(List worldVertices, Transform attachTo, bool isRotated, bool requiresMatrixCalc = false, float shrinkGrow = 1f)
+ {
+ if (worldVertices.Count >= 3)
+ {
+ Quaternion q = Quaternion.identity;
+ Matrix4x4 m;
+ List localVertices = new List();
+ if (isRotated && requiresMatrixCalc)
+ {
+ // for rotated colliders we also re-calculate the to local even though the transform is changed.
+ // this better handles scale-shearing.
+ Vector3 forward = worldVertices[1] - worldVertices[0];
+ Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
+ q = Quaternion.LookRotation(forward, up);
+ m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
+ for (int i = 0; i < worldVertices.Count; i++)
+ {
+ localVertices.Add(m.inverse.MultiplyPoint3x4(worldVertices[i]));
+ }
+ }
+ else
+ {
+ localVertices = ToLocalVerts(attachTo, worldVertices);
+ m = attachTo.localToWorldMatrix;
+ }
+ CapsuleColliderData data = CalculateCapsuleBestFitLocal(localVertices);
+ data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_CAPSULE : CREATE_COLLIDER_TYPE.CAPSULE;
+ data.Matrix = m;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Radius *= shrinkGrow;
+ data.Height *= shrinkGrow;
+ return data;
+ }
+ return new CapsuleColliderData();
+ }
+
+ ///
+ /// Calculates a best-fit capsule collider from a list of local space vertices
+ ///
+ /// local space vertices
+ /// Capsule collider data with center, direction and height
+ public CapsuleColliderData CalculateCapsuleBestFitLocal(List localVertices)
+ {
+ if (localVertices.Count < 3)
+ {
+ Debug.LogWarning("EasyColliderCreator: Too few vertices passed to calculate a best fit capsule collider.");
+ return new CapsuleColliderData();
+ }
+ // height from first 2 verts selected.
+ Vector3 v0 = localVertices[0];
+ Vector3 v1 = localVertices[1];
+ float height = Vector3.Distance(v0, v1);
+ float dX = Mathf.Abs(v1.x - v0.x);
+ float dY = Mathf.Abs(v1.y - v0.y);
+ float dZ = Mathf.Abs(v1.z - v0.z);
+ localVertices.RemoveAt(1);
+ localVertices.RemoveAt(0);
+ BestFitSphere bfs = CalculateBestFitSphere(localVertices);
+ Vector3 center = bfs.Center;
+ int direction = 0;
+ if (dX > dY && dX > dZ)
+ {
+ direction = 0;
+ center.x = (v1.x + v0.x) / 2;
+ }
+ else if (dY > dX && dY > dZ)
+ {
+ direction = 1;
+ center.y = (v1.y + v0.y) / 2;
+ }
+ else
+ {
+ direction = 2;
+ center.z = (v1.z + v0.z) / 2;
+ }
+ CapsuleColliderData data = new CapsuleColliderData();
+ data.Center = center;
+ data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
+ data.Direction = direction;
+ data.Height = height;
+ data.IsValid = true;
+ data.Radius = bfs.Radius;
+ return data;
+ }
+
+ ///
+ /// Calculates a capsule's data from the given values using the min max method and a list of world space vertices
+ ///
+ /// list of vertices in world space
+ /// transform the capsule will be attached to
+ /// method we are using to create the capsule (ie MinMaxPlusRadius)
+ /// are we creating a rotated capsule?
+ /// true when the attach to transform is not already correctly rotated for a rotated collider
+ /// adjust the height/radius of the capsule collider
+ /// Data with appropriate variables set for a capsule collider
+ public CapsuleColliderData CalculateCapsuleMinMax(List worldVertices, Transform attachTo, CAPSULE_COLLIDER_METHOD method, bool isRotated, bool requiresMatrixCalc = false, float shrinkGrow = 1f)
+ {
+ if (isRotated && worldVertices.Count < 3)
+ {
+ return new CapsuleColliderData();
+ }
+ else if (worldVertices.Count < 2)
+ {
+ return new CapsuleColliderData();
+ }
+ List localVertices = new List();
+ Matrix4x4 m;
+ Quaternion q;
+ if (isRotated && worldVertices.Count >= 3 && requiresMatrixCalc)
+ {
+ Vector3 forward = worldVertices[1] - worldVertices[0];
+ Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
+ q = Quaternion.LookRotation(forward, up);
+ m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
+ for (int i = 0; i < worldVertices.Count; i++)
+ {
+ localVertices.Add(m.inverse.MultiplyPoint3x4(worldVertices[i]));
+ }
+ }
+ else
+ {
+ localVertices = ToLocalVerts(attachTo.transform, worldVertices);
+ m = attachTo.localToWorldMatrix;
+ }
+ CapsuleColliderData data = CalculateCapsuleMinMaxLocal(localVertices, method);
+ data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_CAPSULE : CREATE_COLLIDER_TYPE.CAPSULE;
+ data.Matrix = m;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Radius *= shrinkGrow;
+ data.Height *= shrinkGrow;
+ return data;
+ }
+
+ ///
+ /// Calculates a capsule collider from a list of local space vertices
+ ///
+ /// List of local space vertices
+ /// method to use when calculating (used to add radius or diameter to height of capsule)
+ /// Capsule collider data with center, direction, and height
+ public CapsuleColliderData CalculateCapsuleMinMaxLocal(List localVertices, CAPSULE_COLLIDER_METHOD method)
+ {
+ // calculate min and max points from vertices.
+ Vector3 min = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
+ Vector3 max = new Vector3(-Mathf.Infinity, -Mathf.Infinity, -Mathf.Infinity);
+ foreach (Vector3 vertex in localVertices)
+ {
+ // Calc minimums
+ min.x = vertex.x < min.x ? vertex.x : min.x;
+ min.y = vertex.y < min.y ? vertex.y : min.y;
+ min.z = vertex.z < min.z ? vertex.z : min.z;
+ // Calc maximums
+ max.x = vertex.x > max.x ? vertex.x : max.x;
+ max.y = vertex.y > max.y ? vertex.y : max.y;
+ max.z = vertex.z > max.z ? vertex.z : max.z;
+ }
+ // Deltas for max-min
+ float dX = max.x - min.x;
+ float dY = max.y - min.y;
+ float dZ = max.z - min.z;
+ // center is between min and max values.
+ Vector3 center = (max + min) / 2;
+ int direction = 0;
+ float height = 0;
+ // set direction and height.
+ if (dX > dY && dX > dZ) // direction is x
+ {
+ direction = 0;
+ // height is the max difference in x.
+ height = dX;
+ }
+ else if (dY > dX && dY > dZ) // direction is y
+ {
+ direction = 1;
+ height = dY;
+ }
+ else // direction is z.
+ {
+ direction = 2;
+ height = dZ;
+ }
+ // Calculate radius, makes sure that all vertices are within the radius.
+ // Esentially to points on plane defined by direction axis, and find the furthest distance.
+ float maxRadius = -Mathf.Infinity;
+ Vector3 current = Vector3.zero;
+ foreach (Vector3 vertex in localVertices)
+ {
+ current = vertex;
+ if (direction == 0)
+ {
+ current.x = center.x;
+ }
+ else if (direction == 1)
+ {
+ current.y = center.y;
+ }
+ else if (direction == 2)
+ {
+ current.z = center.z;
+ }
+ float d = Vector3.Distance(current, center);
+ if (d > maxRadius)
+ {
+ maxRadius = d;
+ }
+ }
+ // method add radius / diameter
+ if (method == CAPSULE_COLLIDER_METHOD.MinMaxPlusRadius)
+ {
+ height += maxRadius;
+ }
+ else if (method == CAPSULE_COLLIDER_METHOD.MinMaxPlusDiameter)
+ {
+ height += maxRadius * 2;
+ }
+ CapsuleColliderData data = new CapsuleColliderData();
+ data.Center = center;
+ data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
+ data.Direction = direction;
+ data.Height = height;
+ data.IsValid = true;
+ data.Radius = maxRadius;
+ return data;
+ }
+
+
+
+ //TODO: Do a local-only method for cylinders.
+
+ ///
+ /// Calculates the data needed to create a cylinder shaped convex mesh collider using a list of world space vertices
+ ///
+ /// list of selected world space vertices
+ /// transform the collider will be attached to
+ /// number of sides on the cylinder
+ /// Data to create a a cylinder collider with type, convex mesh, validity, and matrix set
+ public MeshColliderData CalculateCylinderCollider(List worldVertices, Transform attachTo, int numberOfSides = 12, CYLINDER_ORIENTATION orientation = CYLINDER_ORIENTATION.Automatic, float cylinderOffset = 0.0f, float shrinkGrow = 1f)
+ {
+ MeshColliderData data = new MeshColliderData();
+ // convert to local
+ List localVerts = ToLocalVerts(attachTo, worldVertices);
+#if (UNITY_EDITOR)
+ List cylinderLocalPoints = CalculateCylinderPointsLocal(localVerts, attachTo, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
+#else
+ List cylinderLocalPoints = CalculateCylinderPointsLocal(localVerts, attachTo, numberOfSides, orientation, cylinderOffset);
+#endif
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ if (shrinkGrow != 1.0f)
+ {
+ AdjustVerticesForShrinkGrow(cylinderLocalPoints, shrinkGrow);
+ }
+ // build the mesh using quickhull and the cylinder points.
+ EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(cylinderLocalPoints);
+ data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
+ data.ConvexMesh = qh.Result;
+ if (qh.Result != null)
+ {
+ data.IsValid = true;
+ }
+ data.Matrix = attachTo.transform.localToWorldMatrix;
+ return data;
+ }
+
+ ///
+ /// Calculates the mesh collider data for a cylinder shaped convex mesh collider using a list of local space vertices
+ ///
+ /// list of local space vertices
+ /// number of sides on the cylinder
+ /// Automatic: Height is along largest axis. X,Y,Z orient height along X, Y, or Z respectively.
+ /// angle to offset cylinder rotation in degrees.
+ /// Mesh collider data with convex mesh set>
+ public MeshColliderData CalculateCylinderColliderLocal(List vertices, int numberOfSides = 12, CYLINDER_ORIENTATION orientation = CYLINDER_ORIENTATION.Automatic, float cylinderOffset = 0.0f)
+ {
+ MeshColliderData data = new MeshColliderData();
+#if (UNITY_EDITOR)
+ List cylinderLocalPoints = CalculateCylinderPointsLocal(vertices, null, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
+#else
+ List cylinderLocalPoints = CalculateCylinderPointsLocal(vertices, null, numberOfSides, orientation, cylinderOffset);
+#endif
+ EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(cylinderLocalPoints);
+ data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
+ data.ConvexMesh = qh.Result;
+ if (qh.Result != null)
+ {
+ data.IsValid = true;
+ }
+ data.Matrix = new Matrix4x4();
+ return data;
+ }
+
+ ///
+ /// Calculates mesh collider data for a list of local space vertices
+ ///
+ /// list of local space vertices
+ /// Mesh collider data with convex mesh set
+ public MeshColliderData CalculateMeshColliderQuickHullLocal(List localVertices)
+ {
+ MeshColliderData data = new MeshColliderData();
+ EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(localVertices);
+ data.ConvexMesh = qh.Result;
+ if (qh.Result != null)
+ {
+ data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
+ data.IsValid = true;
+ }
+ return data;
+ }
+
+ ///
+ /// Calculates a sphere using the best fit method and a list of world space vertices
+ ///
+ /// list of vertex positions in world space
+ /// transform sphere would be attached to
+ /// adjusts the radius of the sphere collider
+ /// Data with appropriate variables set for a sphere collider
+ public SphereColliderData CalculateSphereBestFit(List worldVertices, Transform attachTo, float shrinkGrow = 1f)
+ {
+ if (worldVertices.Count < 2)
+ {
+ return new SphereColliderData();
+ }
+ List localVertices = ToLocalVerts(attachTo, worldVertices);
+ // set data from values
+ SphereColliderData data = CalculateSphereBestFitLocal(localVertices);
+ data.Matrix = attachTo.localToWorldMatrix;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Radius *= shrinkGrow;
+ return data; ;
+ }
+
+ ///
+ /// Calculates a best fit sphere collider using a list of local space vertices
+ ///
+ /// list of local space vertices
+ /// Sphere collider data with center and radius set
+ public SphereColliderData CalculateSphereBestFitLocal(List localVertices)
+ {
+ BestFitSphere bfs = CalculateBestFitSphere(localVertices);
+ // set data from values
+ SphereColliderData data = new SphereColliderData();
+ data.Center = bfs.Center;
+ data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
+ data.IsValid = true;
+ data.Radius = bfs.Radius;
+ return data;
+ }
+
+
+ // distance sphere is editor-only for now
+ ///
+ /// Calculates a sphere using the distance method and a list of world space vertices
+ ///
+ /// list of vertex positions in world space
+ /// transform sphere would be attached to
+ /// adjusts the radius of the sphere collider
+ /// Data with appropriate variables set for a sphere collider
+ public SphereColliderData CalculateSphereDistance(List worldVertices, Transform attachTo, float shrinkGrow = 1f)
+ {
+ if (worldVertices.Count < 2)
+ {
+ return new SphereColliderData();
+ }
+ List localVertices = ToLocalVerts(attachTo, worldVertices);
+ // set data from values
+ SphereColliderData data = CalculateSphereDistanceLocal(localVertices);
+ data.Matrix = attachTo.localToWorldMatrix;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Radius *= shrinkGrow;
+ return data;
+ }
+
+ ///
+ /// Calculates a sphere collider using a list of local space vertices
+ ///
+ /// list of local space vertices
+ /// Sphere collider data with center and radius
+ public SphereColliderData CalculateSphereDistanceLocal(List localVertices)
+ {
+ // if calculations take to long, it switches to a faster less accurate algorithm using the mean.
+ bool switchToFasterAlgorithm = false;
+#if (UNITY_EDITOR)
+ double startTime = EditorApplication.timeSinceStartup;
+#else
+ double startTime = Time.realtimeSinceStartup;
+#endif
+ double maxTime = 0.1f;
+ Vector3 distanceVert1 = Vector3.zero;
+ Vector3 distanceVert2 = Vector3.zero;
+ float maxDistance = -Mathf.Infinity;
+ float distance = 0;
+ for (int i = 0; i < localVertices.Count; i++)
+ {
+ for (int j = i + 1; j < localVertices.Count; j++)
+ {
+ distance = Vector3.Distance(localVertices[i], localVertices[j]);
+ if (distance > maxDistance)
+ {
+ maxDistance = distance;
+ distanceVert1 = localVertices[i];
+ distanceVert2 = localVertices[j];
+ }
+ }
+#if (UNITY_EDITOR)
+ if (EditorApplication.timeSinceStartup - startTime > maxTime)
+ {
+ switchToFasterAlgorithm = true;
+ break;
+ }
+#else
+ if (Time.realtimeSinceStartup - startTime > maxTime)
+ {
+ switchToFasterAlgorithm = true;
+ break;
+ }
+#endif
+ }
+ if (switchToFasterAlgorithm)
+ {
+ // use a significantly faster algorithm that is less accurate for a large # of points.
+ Vector3 mean = Vector3.zero;
+ foreach (Vector3 vertex in localVertices)
+ {
+ mean += vertex;
+ }
+ mean = mean / localVertices.Count;
+ foreach (Vector3 vertex in localVertices)
+ {
+ distance = Vector3.Distance(vertex, mean);
+ if (distance > maxDistance)
+ {
+ distanceVert1 = vertex;
+ maxDistance = distance;
+ }
+ }
+ maxDistance = -Mathf.Infinity;
+ foreach (Vector3 vertex in localVertices)
+ {
+ distance = Vector3.Distance(vertex, distanceVert1);
+ if (distance > maxDistance)
+ {
+ maxDistance = distance;
+ distanceVert2 = vertex;
+ }
+ }
+ }
+ // set data from values
+ SphereColliderData data = new SphereColliderData();
+ data.Center = (distanceVert1 + distanceVert2) / 2;
+ data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
+ data.IsValid = true;
+ data.Radius = maxDistance / 2;
+ return data;
+ }
+
+ ///
+ /// Calculates a sphere using the min max method and a list of world space vertices
+ ///
+ /// list of vertex positions in world space
+ /// transform sphere would be attached to
+ /// adjusts the radius of the sphere collider
+ /// Data with appropriate variables set for a sphere collider
+ public SphereColliderData CalculateSphereMinMax(List worldVertices, Transform attachTo, float shrinkGrow = 1f)
+ {
+ if (worldVertices.Count < 2)
+ {
+ return new SphereColliderData();
+ }
+ // use local space verts.
+ List localVertices = ToLocalVerts(attachTo, worldVertices);
+ SphereColliderData data = CalculateSphereMinMaxLocal(localVertices);
+ data.Matrix = attachTo.localToWorldMatrix;
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ data.Radius *= shrinkGrow;
+ return data;
+ }
+
+ ///
+ /// Calculates a sphere collider using a list of local space vertices
+ ///
+ /// local space vertices
+ /// Sphere collider data with center and radius set
+ public SphereColliderData CalculateSphereMinMaxLocal(List localVertices)
+ {
+ float xMin, yMin, zMin = xMin = yMin = Mathf.Infinity;
+ float xMax, yMax, zMax = xMax = yMax = -Mathf.Infinity;
+ for (int i = 0; i < localVertices.Count; i++)
+ {
+ //x min & max.
+ xMin = (localVertices[i].x < xMin) ? localVertices[i].x : xMin;
+ xMax = (localVertices[i].x > xMax) ? localVertices[i].x : xMax;
+ //y min & max
+ yMin = (localVertices[i].y < yMin) ? localVertices[i].y : yMin;
+ yMax = (localVertices[i].y > yMax) ? localVertices[i].y : yMax;
+ //z min & max
+ zMin = (localVertices[i].z < zMin) ? localVertices[i].z : zMin;
+ zMax = (localVertices[i].z > zMax) ? localVertices[i].z : zMax;
+ }
+ // calculate center
+ Vector3 center = (new Vector3(xMin, yMin, zMin) + new Vector3(xMax, yMax, zMax)) / 2;
+ // calculate radius to contain all points
+ float maxDistance = 0.0f;
+ float distance = 0.0f;
+ // this is what makes it slightly differnt than just converting a box into a sphere.
+ foreach (Vector3 vertex in localVertices)
+ {
+ distance = Vector3.Distance(vertex, center);
+ if (distance > maxDistance)
+ {
+ maxDistance = distance;
+ }
+ }
+ SphereColliderData data = new SphereColliderData();
+ data.Center = center;
+ data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
+ data.IsValid = true;
+ data.Radius = maxDistance;
+ return data;
+ }
+
+ #endregion
+
+ // editor only
+ #region CreateMeshColliders
+#if (UNITY_EDITOR)
+ ///
+ /// Create and saves a mesh with a minimum number of triangles that includes all selected vertices. Editor only.
+ ///
+ /// Full path to save location including a base object name ie: "C:/UnityProjects/ProjectName/Assets/ConvexHulls/SaveNameBase"
+ /// Vertices to create the mesh with in world space
+ /// Gameobject the mesh will be attached to
+ /// shrinks/grows the resulting mesh collider, similar to scaling an object
+ /// The created mesh
+ public Mesh CreateMesh_Messy(List worldSpaceVertices, GameObject attachTo, GameObject selected, float shrinkGrow = 1.0f)
+ {
+ // use vertices to make a useable mesh that contains all the selected points.
+ // The mesh is only used to generate the convex hull
+ Mesh mesh = new Mesh();
+ // get all vertices in world space and convert to local space.
+ List localVertices = worldSpaceVertices.Select(vertex => attachTo.transform.InverseTransformPoint(vertex)).ToList();
+#if(UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ AdjustVerticesForShrinkGrow(localVertices, shrinkGrow);
+ while (localVertices.Count % 3 != 0)
+ {
+ localVertices.Add(localVertices[localVertices.Count % 3]);
+ }
+ // attempt to deal with degenerate triangles (so if user changes the mesh collider flags manually, no crashes will occur)
+ Vector3 p0, p1, p2 = p1 = p0 = Vector3.zero;
+ Vector3 s1, s2 = s1 = Vector3.zero;
+ List verts = new List();
+ int index = localVertices.Count - 1;
+ while (index >= 0) // need to make sure we include the last vertex.
+ {
+ p0 = localVertices[index];
+ p1 = localVertices[(index - 1 >= 0) ? index - 1 : localVertices.Count - 1];
+ p2 = localVertices[(index - 2 >= 0) ? index - 2 : localVertices.Count - 2];
+ s1 = (p0 - p1).normalized;
+ s2 = (p0 - p2).normalized;
+ int degenIndex = localVertices.Count; // so we can automatically re-use the last vertices if needed.
+ bool degenFixed = false;
+ while (s1 == s2 || -s1 == s2 || (s2 == Vector3.zero && s1 != Vector3.zero))
+ {
+ degenFixed = true;
+ degenIndex--;
+ if (degenIndex < 0)
+ {
+ Debug.LogError("Easy Collider Editor: Unable to generate a valid mesh collider from the selected points. This happens when all points are in a straight line.");
+ return null;
+ }
+ p2 = localVertices[degenIndex];
+ s2 = (p0 - p2).normalized;
+ }
+ // if we fixed a degenerate we still need to do the last vertex, so only move back 2 indexs' in that case.
+ index -= degenFixed ? 2 : 3;
+ verts.Add(p0);
+ verts.Add(p1);
+ verts.Add(p2);
+ }
+
+ int[] triangles = new int[verts.Count];
+ for (int i = 0; i < verts.Count; i++)
+ {
+ triangles[i] = i;
+ }
+ // mesh.vertices = vertices;
+ mesh.vertices = verts.ToArray();
+ mesh.triangles = triangles;
+ // the mesh has to be saved somewhere so it can actually be used (although this is still just optional)
+ try
+ {
+ if (selected == null)
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(mesh, attachTo);
+ }
+ else
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(mesh, selected);
+ }
+ return mesh;
+ }
+ catch
+ {
+ Debug.LogError("EasyColliderEditor: Error saving mesh at path:" + EasyColliderSaving.GetValidConvexHullPath(attachTo));
+ return null;
+ }
+ }
+
+ ///
+ /// Calculates a convex hull for a list of world-space points for use by the previwer.
+ ///
+ /// world-space points to generate a hull on
+ /// transform the collider will be attached to, used to convert to local space
+ /// shrinks/grows the resulting mesh collider, similar to scaling an object
+ /// EasyColliderData with mesh, matrix, and validity
+ public MeshColliderData CalculateHullData(List points, Transform attachTo, float shrinkGrow = 1.0f)
+ {
+ if (points == null || points.Count < 4)
+ {
+ // can't calculate yet.
+ return new MeshColliderData();
+ }
+#if(UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ AdjustVerticesForShrinkGrow(points, shrinkGrow);
+ return EasyColliderQuickHull.CalculateHullData(points, attachTo);
+ }
+
+ ///
+ /// Creates and saves (if set in preferences) a convex mesh collider using QuickHull. Editor only.
+ ///
+ /// Local or world space vertices
+ /// Gameobject the collider will be attached to
+ /// are the vertices already in local space?
+ /// if in the editor, and not-null the output mesh will be saved in a location according to this and preferences, otherwise it will use the attach to.
+ /// shrinks/grows the resulting mesh collider, similar to scaling an object
+ ///
+ public Mesh CreateMesh_QuickHull(List vertices, GameObject attachTo, bool isLocal = false, GameObject selected = null, float shrinkGrow = 1.0f)
+ {
+ List localVerts = isLocal ? vertices : ToLocalVerts(attachTo.transform, vertices);
+#if (UNITY_EDITOR)
+ shrinkGrow = ECEPreferences.ShrinkGrow;
+#endif
+ AdjustVerticesForShrinkGrow(localVerts, shrinkGrow);
+ EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(localVerts);
+ if (ECEPreferences.SaveConvexHullAsAsset)
+ {
+ if (selected == null)
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(qh.Result, attachTo);
+ }
+ else
+ {
+ EasyColliderSaving.CreateAndSaveMeshAsset(qh.Result, selected);
+ }
+ }
+ return qh.Result;
+ }
+#endif
+
+ ///
+ /// Creates a convex mesh collider component from the mesh using all cooking options, so mesh does not have to be "valid"
+ ///
+ /// Mesh to make a convex hull from
+ /// Gameobject the convex hull will be attached to
+ /// Parameters to set on created collider
+ public MeshCollider CreateConvexMeshCollider(Mesh mesh, GameObject attachToObject, EasyColliderProperties properties, bool postProcess = true)
+ {
+ // Create a mesh collider
+#if (UNITY_EDITOR)
+ MeshCollider createdCollider = Undo.AddComponent(attachToObject);
+#else
+ MeshCollider createdCollider = attachToObject.AddComponent();
+#endif
+ createdCollider.sharedMesh = mesh;
+ //enable all cooking options.
+#if UNITY_2018_3_OR_NEWER
+ createdCollider.cookingOptions = ~MeshColliderCookingOptions.None;
+#elif UNITY_2017_3_OR_NEWER
+ createdCollider.cookingOptions = ~MeshColliderCookingOptions.None;
+ // Auto inflate mesh to the minimum amount
+ createdCollider.skinWidth = 0.000001f;
+#else
+ // for very old unity versions that didn't have cooking options.
+ createdCollider.inflateMesh = true;
+ createdCollider.skinWidth = 0.000001f;
+#endif
+ // Would be nice if we could do a try/catch on the baking to only inflate if we have to, but that doesn't work.
+ createdCollider.convex = true;
+ PostColliderCreation(createdCollider, properties, postProcess);
+ //disable read/write to save memory based on user preferences.
+ //for limitations see bottom of: https://docs.unity3d.com/Manual/class-MeshCollider.html
+#if (UNITY_EDITOR)
+ if (!ECEPreferences.ConvexMeshReadWriteEnabled)
+ {
+ mesh.UploadMeshData(true);
+ // fixes issue where mesh read/write was not correctly saving.
+ EditorUtility.SetDirty(mesh);
+ }
+#endif
+ return createdCollider;
+ }
+
+
+ #endregion
+
+ // creating colliders uses undos, the data itself can be used during runtime.
+ #region CreatePrimitiveColliders
+
+ ///
+ /// Creates a Box collider
+ ///
+ /// data to create box from
+ /// properties to set on collider
+ /// Created collider
+ private BoxCollider CreateBoxCollider(BoxColliderData data, EasyColliderProperties properties, bool postProcess = true)
+ {
+#if (UNITY_EDITOR)
+ BoxCollider boxCollider;
+ if (UndoEnabled)
+ {
+ boxCollider = Undo.AddComponent(properties.AttachTo);
+ }
+ else
+ {
+ boxCollider = properties.AttachTo.AddComponent();
+ }
+#else
+ BoxCollider boxCollider = properties.AttachTo.AddComponent();
+#endif
+ boxCollider.size = data.Size;
+ boxCollider.center = data.Center;
+ PostColliderCreation(boxCollider, properties, postProcess);
+ return boxCollider;
+ }
+
+
+
+ ///
+ /// Creates a box collider using a list of world space vertices
+ ///
+ /// List of world space vertices
+ /// Properties of collider
+ /// The created box collider
+ public BoxCollider CreateBoxCollider(List vertices, EasyColliderProperties properties, bool isLocal = false)
+ {
+ if (vertices.Count >= 2)
+ {
+ BoxColliderData data;
+ if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ if (vertices.Count >= 3)
+ {
+ GameObject obj = CreateGameObjectOrientation(vertices, properties.AttachTo, "Rotated Box Collider");
+ // still want to recalculate using the transform matrix, as it better handles uneven scale / shearing across multiple children
+ if (obj != null)
+ {
+ obj.layer = properties.Layer;
+ properties.AttachTo = obj;
+ }
+ data = CalculateBox(vertices, properties.AttachTo.transform, true);
+ }
+ else
+ {
+ Debug.LogWarning("Easy Collider Editor: Creating a Rotated Box Collider requires at least 3 points to be selected.");
+ return null;
+ }
+ }
+ else
+ {
+ if (!isLocal)
+ {
+ data = CalculateBox(vertices, properties.AttachTo.transform);
+ }
+ else
+ {
+ data = CalculateBoxLocal(vertices);
+ }
+ }
+ return CreateBoxCollider(data, properties);
+ }
+ return null;
+ }
+
+ ///
+ /// Creates a capsule collider (editor undoable)
+ ///
+ /// data to create capsule from
+ /// properties to set on collider
+ /// created capsule collider
+ private CapsuleCollider CreateCapsuleCollider(CapsuleColliderData data, EasyColliderProperties properties, bool postProcess = true)
+ {
+#if (UNITY_EDITOR)
+ CapsuleCollider capsuleCollider;
+ if (UndoEnabled)
+ {
+ capsuleCollider = Undo.AddComponent(properties.AttachTo);
+ }
+ else
+ {
+ capsuleCollider = properties.AttachTo.AddComponent();
+ }
+#else
+ CapsuleCollider capsuleCollider = properties.AttachTo.AddComponent();
+#endif
+ capsuleCollider.direction = data.Direction;
+ capsuleCollider.height = data.Height;
+ capsuleCollider.center = data.Center;
+ capsuleCollider.radius = data.Radius;
+ // set properties
+ PostColliderCreation(capsuleCollider, properties);
+ return capsuleCollider;
+ }
+
+ ///
+ /// Creates a capsule collider usintg the best fit algorithm and a list of world space vertices
+ ///
+ /// List of world vertices
+ /// Properties of collider
+ /// The created capsule collider
+ public CapsuleCollider CreateCapsuleCollider_BestFit(List worldVertices, EasyColliderProperties properties)
+ {
+ if (worldVertices.Count >= 3)
+ {
+ CapsuleColliderData data = new CapsuleColliderData();
+ if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ GameObject obj = CreateGameObjectOrientation(worldVertices, properties.AttachTo, "Rotated Capsule Collider");
+ if (obj != null)
+ {
+ properties.AttachTo = obj;
+ obj.layer = properties.Layer;
+ }
+ data = CalculateCapsuleBestFit(worldVertices, properties.AttachTo.transform, true);
+ }
+ else
+ {
+ data = CalculateCapsuleBestFit(worldVertices, properties.AttachTo.transform, false);
+ }
+ return CreateCapsuleCollider(data, properties);
+ }
+ return null;
+ }
+
+ ///
+ /// Creates a capsule collider using the Min-Max method and a list of world space vertices
+ ///
+ /// List of world space vertices
+ /// Properties to set on collider
+ /// Min-Max method to use to add radius' to height.
+ /// The created capsule collider
+ public CapsuleCollider CreateCapsuleCollider_MinMax(List worldVertices, EasyColliderProperties properties, CAPSULE_COLLIDER_METHOD method, bool isLocal = false)
+ {
+ CapsuleColliderData data;
+ if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED && worldVertices.Count >= 3)
+ {
+ GameObject obj = CreateGameObjectOrientation(worldVertices, properties.AttachTo, "Rotated Capsule Collider");
+ if (obj != null)
+ {
+ properties.AttachTo = obj;
+ obj.layer = properties.AttachTo.layer;
+ }
+ data = CalculateCapsuleMinMax(worldVertices, properties.AttachTo.transform, method, true);
+ }
+ else
+ {
+ if (!isLocal)
+ {
+ data = CalculateCapsuleMinMax(worldVertices, properties.AttachTo.transform, method, false);
+ }
+ else
+ {
+ data = CalculateCapsuleMinMaxLocal(worldVertices, method);
+ }
+ }
+ return CreateCapsuleCollider(data, properties);
+ }
+
+ ///
+ /// Creates a sphere collider, editor undo-able
+ ///
+ /// data to create the sphere collider from
+ /// properties to set on the collider
+ /// the created sphere collider
+ private SphereCollider CreateSphereCollider(SphereColliderData data, EasyColliderProperties properties, bool postProcess = true)
+ {
+#if (UNITY_EDITOR)
+ SphereCollider sphereCollider;
+ if (UndoEnabled)
+ {
+ sphereCollider = Undo.AddComponent(properties.AttachTo);
+ }
+ else
+ {
+ sphereCollider = properties.AttachTo.AddComponent();
+ }
+#else
+ SphereCollider sphereCollider = properties.AttachTo.AddComponent();
+#endif
+ sphereCollider.radius = data.Radius;
+ sphereCollider.center = data.Center;
+ PostColliderCreation(sphereCollider, properties, postProcess);
+ return sphereCollider;
+ }
+
+ ///
+ /// Creates a sphere collider using the best fit sphere algorithm and a list of world space vertices
+ ///
+ /// List of world space vertices
+ /// Properties of collider
+ ///
+ public SphereCollider CreateSphereCollider_BestFit(List worldVertices, EasyColliderProperties properties)
+ {
+ if (worldVertices.Count >= 2)
+ {
+ // Convert to local space.
+ SphereColliderData data = CalculateSphereBestFit(worldVertices, properties.AttachTo.transform);
+ return CreateSphereCollider(data, properties);
+ }
+ return null;
+ }
+
+ ///
+ /// Creates a Sphere Collider using the distance method and a list of world space vertices
+ ///
+ /// List of world space vertices
+ /// Properties of collider
+ ///
+ public SphereCollider CreateSphereCollider_Distance(List worldVertices, EasyColliderProperties properties)
+ {
+ if (worldVertices.Count >= 2)
+ {
+ SphereColliderData data = CalculateSphereDistance(worldVertices, properties.AttachTo.transform);
+ return CreateSphereCollider(data, properties);
+ }
+ return null;
+ }
+
+ ///
+ /// Creates a sphere collider using the min max method and a list of world space vertices
+ ///
+ /// List of world space vertices
+ /// Properties of collider
+ ///
+ public SphereCollider CreateSphereCollider_MinMax(List worldVertices, EasyColliderProperties properties, bool isLocal = false)
+ {
+ if (worldVertices.Count >= 2)
+ {
+ if (!isLocal)
+ {
+ SphereColliderData data = CalculateSphereMinMax(worldVertices, properties.AttachTo.transform);
+ return CreateSphereCollider(data, properties);
+ }
+ else
+ {
+ SphereColliderData data = CalculateSphereMinMaxLocal(worldVertices);
+ return CreateSphereCollider(data, properties);
+ }
+ }
+ return null;
+ }
+
+ #endregion
+
+ #region PostCreationProcessing
+
+ ///
+ /// can be used at runtime to set a specific custom post processor if desired, otherwise defaults to EasyColliderPostProccessor
+ ///
+ public static IEasyColliderPostProcessor EasyColliderPostProcessor;
+
+ ///
+ /// Can add any custom processing to a manually created collider here.
+ /// Current this handles re-centering the pivot of colliders if specified in preferences. (IE: always make collider holders & pivot at center)
+ /// These should only be things that have show no visual effect on the collider preview.
+ /// IE: things like rotating and re-aligning a collider along the new axis'. Or recentering the position of a collider center to 0, and moving it's collider holder to compensate.
+ ///
+ ///
+ ///
+ public void PostColliderCreationProcess(Collider createdCollider, EasyColliderProperties properties)
+ {
+ // set to default if null
+ if (EasyColliderPostProcessor == null) { EasyColliderPostProcessor = new EasyColliderPostProccessor(); }
+
+ if (createdCollider is BoxCollider)
+ {
+ // adjust pivot to the center of the collider if specified.
+ BoxCollider bc = (BoxCollider)createdCollider;
+#if(UNITY_EDITOR)
+ if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ bc.transform.position = bc.transform.TransformPoint(bc.center);
+ bc.center = Vector3.zero;
+ // bc.transform.localPosition = bc.center;
+ // bc.center = Vector3.zero;
+ }
+#endif
+ // handle other box related things in future here.
+ if (EasyColliderPostProcessor != null)
+ {
+ EasyColliderPostProcessor.PostProcessCollider(bc, properties);
+ }
+
+ }
+ else if (createdCollider is SphereCollider)
+ {
+ SphereCollider sc = (SphereCollider)createdCollider;
+#if(UNITY_EDITOR)
+ if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ sc.transform.position = sc.transform.TransformPoint(sc.center);
+ sc.center = Vector3.zero;
+ }
+#endif
+ //handle other sphere collider things here.
+ if (EasyColliderPostProcessor != null)
+ {
+ EasyColliderPostProcessor.PostProcessCollider(sc, properties);
+ }
+ }
+ else if (createdCollider is CapsuleCollider)
+ {
+ CapsuleCollider cc = (CapsuleCollider)createdCollider;
+#if (UNITY_EDITOR)
+ Transform t = cc.transform;
+ if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
+ {
+ cc.transform.position = cc.transform.TransformPoint(cc.center);
+ cc.center = Vector3.zero;
+ }
+ if (ECEPreferences.CylinderAsCapsuleOrientation)
+ {
+ // aligning to cylinder axis as well.
+ int dir = cc.direction;
+ int prefDir = (int)ECEPreferences.CylinderOrientation - 1;
+ // not set to automatic alignment, so user wants a specific alignment (above would make it -1)
+ if (prefDir >= 0 && prefDir != dir)
+ {
+ Vector3 forward = Vector3.zero;
+ Vector3 up = Vector3.zero;
+ if (dir == 0)
+ {
+ // currently aligned with x-axis (right)
+ if (prefDir == 1) // up
+ {
+ forward = t.transform.up;
+ up = t.transform.right;
+ }
+ else if (prefDir == 2) // forward
+ {
+ forward = t.transform.right;
+ up = t.transform.forward;
+ }
+ }
+ else if (dir == 1)
+ {
+ // current aligned with y-axis (up)
+ if (prefDir == 0) // right
+ {
+ forward = t.transform.right;
+ up = t.transform.forward;
+ }
+ else if (prefDir == 2) // forward
+ {
+ forward = t.transform.up;
+ up = t.transform.right;
+ }
+ }
+ else if (dir == 2)
+ {
+ if (prefDir == 0) // right
+ {
+ forward = t.transform.right;
+ up = -t.transform.up;
+ }
+ else if (prefDir == 1) // up
+ {
+ forward = t.transform.up;
+ up = t.transform.forward;
+ }
+ }
+
+ if (!cc.transform.name.Contains("Rotated Capsule Collider"))
+ {
+ // need to create a collider holder to align if it's not a rotated collider.
+ GameObject o = new GameObject("EasyColliderHolder");
+ Undo.RegisterCreatedObjectUndo(o, "Create Collider Holder");
+ o.transform.SetParent(cc.transform);
+ // move the capsule to the new object.
+ CapsuleCollider replacedCapsule = Undo.AddComponent(o);
+ replacedCapsule.center = cc.center;
+ replacedCapsule.radius = cc.radius;
+ replacedCapsule.direction = cc.direction;
+ replacedCapsule.height = cc.height;
+ GameObject.DestroyImmediate(cc);
+ // correct it's position.
+ cc = replacedCapsule;
+ cc.transform.localPosition = cc.center;
+ cc.center = Vector3.zero;
+ // correct it's rotation and direction to align.
+ cc.transform.rotation = Quaternion.LookRotation(forward, up);
+ cc.direction = prefDir;
+ }
+ else
+ {
+ if (!ECEPreferences.RotatedColliderPivotAtCenter)
+ {
+ // the pivot isn't at center, so we need to transform the point to world space, then back to local after rotating it's transform.
+ Vector3 wsCenter = cc.transform.TransformPoint(cc.center);
+ cc.transform.rotation = Quaternion.LookRotation(forward, up);
+ cc.center = cc.transform.InverseTransformPoint(wsCenter);
+ cc.direction = prefDir;
+ }
+ else
+ {
+ // the pivot is already at the center, so we're good, just align it.
+ cc.transform.rotation = Quaternion.LookRotation(forward, up);
+ cc.direction = prefDir;
+ }
+ }
+ }
+ }
+#endif
+ if (EasyColliderPostProcessor != null)
+ {
+ EasyColliderPostProcessor.PostProcessCollider(cc, properties);
+ }
+ }
+ else if (createdCollider is MeshCollider)
+ {
+ MeshCollider mc = (MeshCollider)createdCollider;
+ if (EasyColliderPostProcessor != null)
+ {
+ EasyColliderPostProcessor.PostProcessCollider(mc, properties);
+ }
+ }
+ }
+ #endregion
+
+
+ #region OtherHelperMethods
+
+ public List CalculateCylinderPointsLocal(List vertices, Transform attachTo, int numberOfSides, CYLINDER_ORIENTATION orientation, float cylinderOffset)
+ {
+ BoxColliderData data = CalculateBoxLocal(vertices);
+ // calculate height and direction based on the height.
+ // height is the max value between the box's directions.
+ float height = 0.0f;
+ // direction is the height's axis
+ int direction = 0;
+ if (orientation == CYLINDER_ORIENTATION.Automatic)
+ {
+ // calculate height as max of each axis, and direction as whichever value it is.
+ height = Mathf.Max(Mathf.Max(data.Size.x, data.Size.y), data.Size.z);
+ direction = (height == data.Size.x) ? 0 : (height == data.Size.y) ? 1 : 2;
+ }
+ else
+ {
+ height = data.Size[(int)orientation - 1]; // x=1, y=2, z=3 to their vector3 indexs.
+ direction = (int)orientation - 1; // direction is also the same
+ }
+ // calculate max distance to the center of the box.
+ float distance = 0.0f;
+ // max distance is the radius
+ float maxDistance = 0.0f;
+ Vector3 current = Vector3.zero;
+
+ // calculate radius for the given dimensions by squasing along height axis and measuring distance.
+ foreach (Vector3 v in vertices)
+ {
+ current.x = (direction == 0) ? data.Center.x : v.x;
+ current.y = (direction == 1) ? data.Center.y : v.y;
+ current.z = (direction == 2) ? data.Center.z : v.z;
+ distance = Vector3.Distance(current, data.Center);
+ if (distance > maxDistance) maxDistance = distance;
+ }
+
+ // half height for offset of points from center.
+ float halfHeight = height / 2;
+ // amount to increment the angle when adding points.
+ float angleIncrement = 360f / numberOfSides;
+ // top and bottom points to build a circle around.
+ Vector3 top, bottom = top = data.Center;
+ // adjust the top / bottom x or y or z depending on direction by the half height.
+ top.x = (direction == 0) ? top.x + halfHeight : top.x;
+ top.y = (direction == 1) ? top.y + halfHeight : top.y;
+ top.z = (direction == 2) ? top.z + halfHeight : top.z;
+ bottom.x = (direction == 0) ? bottom.x - halfHeight : bottom.x;
+ bottom.y = (direction == 1) ? bottom.y - halfHeight : bottom.y;
+ bottom.z = (direction == 2) ? bottom.z - halfHeight : bottom.z;
+ // list of points
+ List points = new List();
+ for (float a = 0 + cylinderOffset; a < 360f + cylinderOffset; a += angleIncrement)
+ {
+ // calculate pair of offset to use for a circle
+ float b = maxDistance * Mathf.Sin(a * Mathf.Deg2Rad);
+ float c = maxDistance * Mathf.Cos(a * Mathf.Deg2Rad);
+ // only adjust the axis if it is not the height axis.
+ if (direction == 0)
+ {
+ top.y = b + data.Center.y;
+ top.z = c + data.Center.z;
+ bottom.y = b + data.Center.y;
+ bottom.z = c + data.Center.z;
+ }
+ else if (direction == 1)
+ {
+ top.x = b + data.Center.x;
+ top.z = c + data.Center.z;
+ bottom.x = b + data.Center.x;
+ bottom.z = c + data.Center.z;
+ }
+ else
+ {
+ top.y = b + data.Center.y;
+ top.x = c + data.Center.x;
+ bottom.y = b + data.Center.y;
+ bottom.x = c + data.Center.x;
+ }
+ points.Add(top);
+ points.Add(bottom);
+ }
+ return points;
+ }
+
+ ///
+ /// Creates a gameobject attach to parent with it's local position at zero, and it's up direction oriented in the direction of the first 2 world vertices.
+ ///
+ /// List of world space vertices
+ /// Parent to attach gameobject to
+ /// Name of gameobject to create
+ ///
+ private GameObject CreateGameObjectOrientation(List worldVertices, GameObject parent, string name)
+ {
+ GameObject obj = new GameObject(name);
+ if (worldVertices.Count >= 3)
+ {
+ // calculate forward and up.
+ Vector3 forward = worldVertices[1] - worldVertices[0];
+ Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
+ obj.transform.rotation = Quaternion.LookRotation(forward, up);
+ obj.transform.SetParent(parent.transform);
+ obj.transform.localPosition = Vector3.zero;
+ // we definitely want the local scale of a rotated object to be uniform 1,1,1
+ obj.transform.localScale = Vector3.one;
+#if (UNITY_EDITOR) // Rotated collider pivot at center is editor-only,
+ if (ECEPreferences.RotatedColliderPivotAtCenter)
+ {
+ Vector3 min = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
+ Vector3 max = new Vector3(-Mathf.Infinity, -Mathf.Infinity, -Mathf.Infinity);
+ foreach (Vector3 v in worldVertices)
+ {
+ Vector3 localV = obj.transform.InverseTransformPoint(v);
+ min.x = localV.x < min.x ? localV.x : min.x;
+ min.y = localV.y < min.y ? localV.y : min.y;
+ min.z = localV.z < min.z ? localV.z : min.z;
+
+ max.x = localV.x > max.x ? localV.x : max.x;
+ max.y = localV.y > max.y ? localV.y : max.y;
+ max.z = localV.z > max.z ? localV.z : max.z;
+ }
+ Vector3 center = (max + min) / 2;
+ center = obj.transform.TransformPoint(center);
+ center = parent.transform.InverseTransformPoint(center);
+ obj.transform.localPosition = center;
+ }
+ Undo.RegisterCreatedObjectUndo(obj, "Create Rotated GameObject");
+#endif
+ return obj;
+ }
+ return null;
+ }
+
+
+
+
+ ///
+ /// Just a helper method to draw a point in world space
+ ///
+ ///
+ ///
+ private void DebugDrawPoint(Vector3 worldLoc, Color color, float dist = 0.01f)
+ {
+ Debug.DrawLine(worldLoc - Vector3.up * dist, worldLoc + Vector3.up * dist, color, 0.01f, false);
+ Debug.DrawLine(worldLoc - Vector3.left * dist, worldLoc + Vector3.left * dist, color, 0.01f, false);
+ Debug.DrawLine(worldLoc - Vector3.forward * dist, worldLoc + Vector3.forward * dist, color, 0.01f, false);
+ }
+
+ ///
+ /// A method that can be ran on any collider for things like setting collider properties or post processing.
+ ///
+ /// Collider that was created
+ /// Properties object with the properties to set
+ private void PostColliderCreation(Collider collider, EasyColliderProperties properties, bool postProcess = true)
+ {
+ SetPropertiesOnCollider(collider, properties);
+ if (postProcess)
+ {
+ PostColliderCreationProcess(collider, properties);
+ }
+ }
+
+ private void SetPropertiesOnCollider(Collider collider, EasyColliderProperties properties)
+ {
+ if (collider != null)
+ {
+ collider.isTrigger = properties.IsTrigger;
+ collider.sharedMaterial = properties.PhysicMaterial;
+
+#if (UNITY_2022_2_OR_NEWER)
+ collider.layerOverridePriority = properties.LayerOverridePriority;
+ collider.includeLayers = properties.IncludeLayers;
+ collider.excludeLayers = properties.ExcludeLayers;
+ collider.providesContacts = properties.ProvidesContacts;
+#endif
+ }
+ }
+
+ ///
+ /// Converts the list of world vertices to local positions
+ ///
+ /// Transform to use for local space
+ /// World space position of vertices
+ /// Localspace position w.r.t transform of worldVertices
+ private List ToLocalVerts(Transform transform, List worldVertices)
+ {
+ List localVerts = new List(worldVertices.Count);
+ foreach (Vector3 v in worldVertices)
+ {
+ localVerts.Add(transform.InverseTransformPoint(v));
+ }
+ return localVerts;
+ }
+
+
+ ///
+ /// positions vertices closer to the center of all vertices based on the shrink grow value. Used for creating mesh colliders (including cylinder colliders) to essentially scale the output.
+ ///
+ ///
+ ///
+ void AdjustVerticesForShrinkGrow(List localVerts, float shrinkGrow)
+ {
+ // since this is for a mesh collider, we'll just calc the center and move the verts towards it by an equal distance.
+ // works for cylinder collider, adjusts it exactly like scaling the cylinder mesh type.
+ Vector3 center = Vector3.zero;
+ float count = localVerts.Count;
+ foreach (var v in localVerts)
+ {
+ center += v;
+ }
+ center /= count;
+ for (int i = 0; i < localVerts.Count; i++)
+ {
+ Vector3 fromCenter = localVerts[i] - center;
+ float magnitudeFromCenter = fromCenter.magnitude;
+ magnitudeFromCenter *= shrinkGrow;
+ Vector3 newPos = center + (fromCenter.normalized * magnitudeFromCenter);
+ localVerts[i] = newPos;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs.meta b/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs.meta
new file mode 100644
index 00000000..138f5f32
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 241873a664ee81d418528c165ef0dd12
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs
new file mode 100644
index 00000000..fdc1baf2
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs
@@ -0,0 +1,125 @@
+using UnityEngine;
+namespace ECE
+{
+ ///
+ /// Data holder for collider calculations.
+ ///
+ public class EasyColliderData
+ {
+ ///
+ /// Type of collider data
+ ///
+ public CREATE_COLLIDER_TYPE ColliderType;
+
+ ///
+ /// Did the collider calculation complete
+ ///
+ public bool IsValid = false;
+
+ ///
+ /// TRS matrix of the attach to object, or TRS matrix of what the rotated collider will have
+ ///
+ public Matrix4x4 Matrix;
+
+ public void Clone(EasyColliderData data)
+ {
+ this.ColliderType = data.ColliderType;
+ this.IsValid = data.IsValid;
+ this.Matrix = data.Matrix;
+ }
+
+ }
+
+ ///
+ /// Data for creating a sphere collider
+ ///
+ public class SphereColliderData : EasyColliderData
+ {
+ ///
+ /// Radius of the collider
+ ///
+ public float Radius;
+
+ ///
+ /// Center of the collider
+ ///
+ public Vector3 Center;
+
+ public void Clone(SphereColliderData data)
+ {
+ base.Clone(data);
+ this.Radius = data.Radius;
+ this.Center = data.Center;
+ }
+ }
+
+ ///
+ /// Data for creating a capsule collider
+ ///
+ public class CapsuleColliderData : SphereColliderData
+ {
+ ///
+ /// Direction of the capsule collider
+ ///
+ public int Direction;
+
+ ///
+ /// Height of the capsule collider
+ ///
+ public float Height;
+
+ public void Clone(CapsuleColliderData data)
+ {
+ base.Clone(data);
+ this.Direction = data.Direction;
+ this.Height = data.Height;
+ }
+ }
+
+
+ ///
+ /// Data for creating a box collider
+ ///
+ public class BoxColliderData : EasyColliderData
+ {
+ ///
+ /// Center of the box collider
+ ///
+ public Vector3 Center;
+
+ ///
+ /// Size of the box collider
+ ///
+ public Vector3 Size;
+
+ public void Clone(BoxColliderData data)
+ {
+ base.Clone(data);
+ this.Center = data.Center;
+ this.Size = data.Size;
+ this.Matrix = data.Matrix;
+ }
+
+ public override string ToString()
+ {
+ return "Rotated box collider. Center:" + Center.ToString() + " Size:" + Size.ToString();
+ }
+ }
+
+ ///
+ /// Data for creating a mesh collider
+ ///
+ public class MeshColliderData : EasyColliderData
+ {
+ ///
+ /// Mesh of the convex mesh collider
+ ///
+ public Mesh ConvexMesh;
+
+ public void Clone(MeshColliderData data)
+ {
+ base.Clone(data);
+ this.ConvexMesh = data.ConvexMesh;
+ }
+ }
+}
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs.meta b/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs.meta
new file mode 100644
index 00000000..5b239d98
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderData.cs.meta
@@ -0,0 +1,20 @@
+fileFormatVersion: 2
+guid: f703485b98da54c489209f1fd7ad2db9
+timeCreated: 1593006697
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderData.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs
new file mode 100644
index 00000000..98533bd1
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs
@@ -0,0 +1,160 @@
+#if (UNITY_EDITOR)
+using UnityEngine;
+using UnityEditor;
+namespace ECE
+{
+ public static class EasyColliderDraw
+ {
+ ///
+ /// Draws a box
+ ///
+ /// Transform of the box
+ /// Center of the box in local space
+ /// Half the size of the box
+ /// Color of lines used to draw the box
+ private static void DrawBox(Transform transform, Vector3 center, Vector3 halfSize, Color color)
+ {
+ Vector3 p1 = transform.TransformPoint(center + halfSize);
+ Vector3 p2 = transform.TransformPoint(center - halfSize);
+ Vector3 p3 = transform.TransformPoint(center + new Vector3(halfSize.x, halfSize.y, -halfSize.z));
+ Vector3 p4 = transform.TransformPoint(center + new Vector3(halfSize.x, -halfSize.y, halfSize.z));
+ Vector3 p5 = transform.TransformPoint(center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z));
+ Vector3 p6 = transform.TransformPoint(center + new Vector3(-halfSize.x, halfSize.y, halfSize.z));
+ Vector3 p7 = transform.TransformPoint(center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z));
+ Vector3 p8 = transform.TransformPoint(center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z));
+ Handles.color = color;
+ Handles.DrawLine(p1, p3);
+ Handles.DrawLine(p1, p4);
+ Handles.DrawLine(p1, p6);
+ Handles.DrawLine(p8, p3);
+ Handles.DrawLine(p8, p6);
+ Handles.DrawLine(p8, p2);
+ Handles.DrawLine(p7, p6);
+ Handles.DrawLine(p7, p2);
+ Handles.DrawLine(p7, p4);
+ Handles.DrawLine(p5, p4);
+ Handles.DrawLine(p5, p2);
+ Handles.DrawLine(p5, p3);
+ }
+
+ ///
+ /// Draws a box collider
+ ///
+ /// Box collider to draw
+ /// Color of lines used to draw
+ private static void DrawBoxCollider(BoxCollider boxCollider, Color color)
+ {
+ DrawBox(boxCollider.transform, boxCollider.center, boxCollider.size / 2, color);
+ }
+
+ private static EasyColliderPreviewer previewer;
+ public static EasyColliderPreviewer Previewer
+ {
+ get
+ {
+ if (previewer == null)
+ {
+ previewer = ScriptableObject.CreateInstance();
+ }
+ return previewer;
+ }
+ }
+
+ ///
+ /// Draws a capsule collider
+ ///
+ /// Capsule collider to draw
+ /// Color of lines to draw
+ private static void DrawCapsuleCollider(CapsuleCollider capsuleCollider, Color color)
+ {
+ CapsuleColliderData data = new CapsuleColliderData();
+ data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
+ data.Height = capsuleCollider.height;
+ data.Radius = capsuleCollider.radius;
+ data.Center = capsuleCollider.center;
+ data.Direction = capsuleCollider.direction;
+ data.Matrix = capsuleCollider.transform.localToWorldMatrix;
+ Previewer.DrawCapsuleCollider(data, color);
+ return;
+ }
+
+ ///
+ /// Draws a primitive collider (box, sphere, capsule) using lines.
+ ///
+ /// Collider to draw
+ /// Color of lines to draw with
+ public static void DrawCollider(Collider collider, Color color)
+ {
+ if (collider == null) return;
+ if (collider is BoxCollider)
+ {
+ DrawBoxCollider(collider as BoxCollider, color);
+ }
+ else if (collider is SphereCollider)
+ {
+ DrawSphereCollider(collider as SphereCollider, color);
+ }
+ else if (collider is CapsuleCollider)
+ {
+ DrawCapsuleCollider(collider as CapsuleCollider, color);
+ }
+ else if (collider is MeshCollider)
+ {
+ DrawMeshCollider(collider as MeshCollider, color);
+ }
+ }
+
+
+ ///
+ /// Shader used to draw mesh colliders.
+ ///
+ static Shader MeshColliderShader;
+
+ ///
+ /// Draws a mesh collider by drawing lines connecting it's meshs vertices.
+ ///
+ /// Mesh Collider
+ /// Color to draw lines with
+ private static void DrawMeshCollider(MeshCollider collider, Color color)
+ {
+ // try to find mesh collider.
+ if (MeshColliderShader == null)
+ {
+ MeshColliderShader = Shader.Find("Custom/EasyColliderMeshColliderPreview");
+ }
+ // if we have the shader, draw it using the wireframe and the color
+ if (MeshColliderShader != null && collider != null && collider.sharedMesh != null)
+ {
+ Material wireMat = new Material(MeshColliderShader);
+ wireMat.SetColor("_Color", color);
+ wireMat.SetPass(0);
+ GL.wireframe = true;
+ Graphics.DrawMeshNow(collider.sharedMesh, collider.transform.localToWorldMatrix);
+ GL.wireframe = false;
+ }
+ else
+ {
+ // no shader? fall back to old draw box method.
+ DrawBox(collider.transform, collider.transform.InverseTransformPoint(collider.bounds.center), collider.transform.InverseTransformVector(collider.bounds.extents), color);
+ }
+ }
+
+
+ // Draws a sphere collider, taken from previous version
+ ///
+ /// Draws a sphere collider.
+ ///
+ /// Sphere collider to draw
+ /// Color of lines used to draw
+ private static void DrawSphereCollider(SphereCollider sphereCollider, Color color)
+ {
+ SphereColliderData data = new SphereColliderData();
+ data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
+ data.Center = sphereCollider.center;
+ data.Radius = sphereCollider.radius;
+ data.Matrix = sphereCollider.transform.localToWorldMatrix;
+ Previewer.DrawSphereCollider(data, color);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs.meta b/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs.meta
new file mode 100644
index 00000000..9a712102
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs.meta
@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 3c95b61c7ebb7b14e9688b95552cef80
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+AssetOrigin:
+ serializedVersion: 1
+ productId: 67880
+ packageName: Easy Collider Editor
+ packageVersion: 6.20.1
+ assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs
+ uploadId: 885904
diff --git a/Assets/EasyColliderEditor/Scripts/EasyColliderEditor.cs b/Assets/EasyColliderEditor/Scripts/EasyColliderEditor.cs
new file mode 100644
index 00000000..bf9490b8
--- /dev/null
+++ b/Assets/EasyColliderEditor/Scripts/EasyColliderEditor.cs
@@ -0,0 +1,3112 @@
+#if (UNITY_EDITOR)
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+namespace ECE
+{
+ [System.Serializable]
+ public class EasyColliderEditor : ScriptableObject, ISerializationCallbackReceiver
+ {
+ #region VHACD
+ // VHACD section
+ ///
+ /// An instance of VHACD for use in each step when using RunVHACDStep
+ ///
+ private EasyColliderVHACD _VHACD;
+
+ ///
+ /// Array of meshes created from VHACDRunStep
+ ///
+ private Mesh[] _VHACDMeshes;
+
+ ///
+ /// Maximum number of vhacd recalculations
+ ///
+ private int _VHACDMaxCalculations = 3;
+
+ ///
+ /// Current vhacd calculation
+ ///
+ private int _VHACDCurrentCalculation = 0;
+
+ ///
+ /// List of colliders created by vhacd
+ ///
+ public List VHACDCreatedColliders = new List();
+
+ public List _VHACDConvertedData = new List();
+
+ ///
+ /// Checks if the computation is finished and valid.
+ ///
+ /// If force under 256 triangles is enabled, if the computation is finished but not valid,
+ /// will start a recomputation with a lower vertex limit.
+ /// Only recomputes a certain # of times before logging a warning and finishing.
+ ///
+ /// If it's not enabled, just checks if the computation of convex hull data is complete
+ ///
+ /// Parameters of current vhacd calculation
+ /// True if complete or complete and valid, false otherwise.
+ public bool VHACDCheckCompute(VHACDParameters parameters)
+ {
+ // check to see if we're forcing under 256 triangles and can still recalculate.
+ if (parameters.forceUnder256Triangles && _VHACDCurrentCalculation < _VHACDMaxCalculations)
+ {
+ // if the computation is finished
+ if (_VHACD.IsComputeFinished())
+ {
+ // and valid
+ if (_VHACD.IsValid())
+ {
+ // were done.
+ return true;
+ }
+ else
+ {
+ // recompute the colliders (this changes the max number of vertices per hull so we get under 256)
+ _VHACD.RecomputeVHACD();
+ // increase the current calculation count.
+ _VHACDCurrentCalculation += 1;
+ return false;
+ }
+ }
+ // compute isn't finished.
+ else { return false; }
+ }
+ else
+ {
+ // if it's finished and we reached calculation max, tell user to reduce number of vertices per CH.
+ if (_VHACD.IsComputeFinished()
+ && _VHACDCurrentCalculation == _VHACDMaxCalculations
+ && _VHACD.IsValid() == false)
+ {
+ Debug.LogWarning("EasyColliderEditor: VHACD computation completed, but the final result had a number of triangles > 255. Try decreasing max vertices per hull, or increasing the number of hulls to prevent these errors.");
+ }
+ // return if the compute is finished.
+ return _VHACD.IsComputeFinished();
+ }
+ }
+
+ ///
+ /// Checks if VHACD is null
+ ///
+ /// false if null
+ public bool VHACDExists()
+ {
+ if (_VHACD == null) { return false; }
+ return true;
+ }
+
+ private Dictionary _VHACDPreviewResult = new Dictionary();
+ public Dictionary VHACDGetPreview()
+ {
+ if (_VHACDPreviewResult != null && _VHACDPreviewResult.Count > 0)
+ {
+ return _VHACDPreviewResult;
+ }
+ return null;
+ }
+
+ public void VHACDClearPreviewResult()
+ {
+ _VHACDPreviewResult = new Dictionary();
+ _VHACDConvertedData = new List();
+ }
+
+ ///
+ /// Runs VHACD step by step
+ ///
+ /// Current step to run, 0-5
+ /// Parameters to use
+ /// Save path for meshes
+ /// Gameobject to attach mesh collider's to.
+ /// true if step is valid and completes, false otherwise
+ public bool VHACDRunStep(int step, VHACDParameters parameters, bool saveAsAsset)
+ {
+ switch (step)
+ {
+ case 0: // setup
+ // clean before another run which re-initializes itself.
+ // fix for an issue with the newer version of vhacd (although it's not used yet)
+ if (_VHACD != null)
+ {
+ if (!_VHACD.IsComputeFinished())
+ {
+ return false;
+ }
+ _VHACD.Clean();
+ }
+ _VHACD = new EasyColliderVHACD();
+ _VHACDCurrentCalculation = 0;
+ _VHACD.Init(true);
+ VHACDCreatedColliders = new List();
+ if (parameters.SeparateChildMeshes && parameters.UseSelectedVertices)
+ { // occasionally this can happen somehow even though it shouldn't, this should treat the issue but not the cause.
+ parameters.SeparateChildMeshes = false;
+ parameters.UseSelectedVertices = true;
+ }
+ return _VHACD.SetParameters(parameters);
+ case 1: // prepare mesh data.
+ // if we're not using just the selected vertices, ie we are using the whole mesh at once.
+ if (!parameters.UseSelectedVertices)
+ {
+ // for seperate child meshes, we attach each result to each mesh.
+ // this allows one to run it on multiple meshes all with the same parameters using a common (or temporary) parent object.
+ if (parameters.SeparateChildMeshes && parameters.MeshFilters[parameters.CurrentMeshFilter] != null)
+ {
+
+ //TODO: figure out a smart wayt hat communicates that attach to objects are used with separate child meshes
+ // child object as the attach to with separate child meshes? as that functionality is then the same as the normal child object one?
+ // \nChild Object: Attach colliders to a child of Attach To field.\nIndividual Child Objects: Each collider is attached to its own child whos parent is a child of the Attach To field.
+ // so since attach to says it attachs to the thing int he attach to field, we should probably change the behaviour on the IndividualChildObjects thing.
+
+ // if (parameters.vhacdResultMethod == VHACD_RESULT_METHOD.AttachTo && parameters.SeparateChildMeshes && !parameters.PerMeshAttachOverride)
+ // {
+
+ // default to using the attach to object, only if it's null so we don't override anything that was already set through previous separated child meshes.
+ if (parameters.AttachTo == null)
+ {
+ parameters.AttachTo = AttachToObject;
+ }
+ // if we're overriding to per mesh, use the mesh filters gameobject as the base attach to object.
+ if (parameters.PerMeshAttachOverride)
+ {
+ parameters.AttachTo = parameters.MeshFilters[parameters.CurrentMeshFilter].gameObject;
+ }
+
+ // it's a temporary added component.
+ if (AddedInstanceIDs.Contains(GetIDFor(parameters.AttachTo)))
+ {
+ if (parameters.AttachTo.transform.parent != null)
+ {
+ // update attach to so we dont lose created colliders, and adjust save name.
+ parameters.AttachTo = parameters.AttachTo.transform.parent.gameObject;
+ if (!parameters.IsCalculationForPreview)
+ {
+ parameters.SavePath = parameters.SavePath.Remove(parameters.SavePath.LastIndexOf("/") + 1) + parameters.AttachTo.name;
+ }
+ }
+ }
+ }
+ // if we're including child meshes, we need to prepare them differently,
+ // all the child meshes essentially get merged into 1 mesh and sent into VHACD.
+ if (IncludeChildMeshes && !parameters.SeparateChildMeshes)
+ {
+ // mesh filters need to be passed as the vertices need to be transformed to attach to's local space.
+ return _VHACD.PrepareMeshData(parameters.MeshFilters, parameters.AttachTo.transform);
+ }
+ else
+ {
+ // a single mesh, or each individual seperated child mesh gets prepared.
+ // attach to the attach to object.
+ if (parameters.MeshFilters[parameters.CurrentMeshFilter] != null)
+ {
+ bool prepared = _VHACD.PrepareMeshData(parameters.MeshFilters[parameters.CurrentMeshFilter], parameters.AttachTo.transform, parameters.MeshFilters[parameters.CurrentMeshFilter].sharedMesh);
+ if (!prepared)
+ {
+ Debug.LogWarning("EasyColliderEditor: Unable to run VHACD on: " + parameters.MeshFilters[parameters.CurrentMeshFilter].name + ". Likely due to missing a mesh in the mesh filter.", parameters.MeshFilters[parameters.CurrentMeshFilter].gameObject);
+ }
+ }
+ return true;
+ }
+ }
+ else // use selected verts.
+ {
+ // Use selected verts is enabled, so we need to create a mesh from the selected vertices.
+ Mesh m = CreateVHACDSelectedVerticesPreviewMesh(parameters);
+ // prepare the mesh
+ return _VHACD.PrepareMeshData(parameters.MeshFilters[parameters.CurrentMeshFilter], parameters.AttachTo.transform, m);
+ }
+ case 2: // calculate (run VHACD on the prepared-mesh)
+ if (_VHACDCurrentCalculation == 0)
+ {
+ _VHACD.RunVHACD();
+ _VHACDCurrentCalculation = 1;
+ return false;
+ }
+ else if (_VHACD.IsComputeFinished()) // check if compute is finished
+ {
+ // we recalculate if necessary, otherwise calculation is done.
+ return VHACDCheckCompute(parameters);
+ }
+ return false;
+ case 3: // save meshes as assets if needed / get the data for each mesh from VHACD and build a mesh.
+ if (!parameters.IsCalculationForPreview)
+ {
+ _VHACDMeshes = _VHACD.CreateConvexHullMeshes();
+ if (saveAsAsset && parameters.ConvertTo == VHACD_CONVERSION.None)
+ {
+ _VHACDMeshes = EasyColliderSaving.CreateAndSaveMeshAssets(_VHACDMeshes, parameters.SavePath, parameters.SaveSuffix);
+ }
+ }
+ else if (parameters.IsCalculationForPreview)
+ {
+ // for the preview.
+ _VHACDMeshes = _VHACD.CreateConvexHullMeshes();
+ if (parameters.ConvertTo == VHACD_CONVERSION.None)
+ {
+ if (_VHACDPreviewResult.ContainsKey(parameters.AttachTo.transform))
+ {
+ _VHACDPreviewResult[parameters.AttachTo.transform] = _VHACDPreviewResult[parameters.AttachTo.transform].Concat(_VHACDMeshes).ToArray();
+ }
+ else
+ {
+ _VHACDPreviewResult.Add(parameters.AttachTo.transform, _VHACDMeshes);
+ }
+ }
+ else
+ {
+ EasyColliderCreator convert = new EasyColliderCreator();
+ foreach (Mesh m in _VHACDMeshes)
+ {
+ EasyColliderData ecd = null;
+ if (parameters.ConvertTo == VHACD_CONVERSION.Boxes)
+ {
+ ecd = convert.CalculateBoxLocal(m.vertices.ToList());
+ }
+ else if (parameters.ConvertTo == VHACD_CONVERSION.Spheres)
+ {
+ ecd = convert.CalculateSphereMinMaxLocal(m.vertices.ToList());
+ }
+ else if (parameters.ConvertTo == VHACD_CONVERSION.Capsules)
+ {
+ ecd = convert.CalculateCapsuleMinMaxLocal(m.vertices.ToList(), CAPSULE_COLLIDER_METHOD.MinMax);
+ }
+ ecd.Matrix = parameters.AttachTo.transform.localToWorldMatrix;
+ _VHACDConvertedData.Add(ecd);
+ }
+ }
+ }
+ return true;
+ case 4: // use the data from VHACD to generate convex mesh colliders.
+ if (parameters.IsCalculationForPreview) { return true; } // skip step 4 for previews.
+ if (parameters.vhacdResultMethod != VHACD_RESULT_METHOD.AttachTo)
+ {
+ // prevents non-per mesh override duplication of "VHACDColliders" when using separate child meshes
+ // otherwise each mesh would create another "VHACDColliders" as a child of the original base VHACDColliders object.
+ if (parameters.AttachTo == null || (parameters.AttachTo != null && !parameters.AttachTo.name.Contains("VHACDColliders")))
+ {
+ // if the method isn't the default attach to, we create a parent to hold colliders
+ GameObject parent = new GameObject("VHACDColliders");
+ // since all verts were coverted to the attach to's location/position/rotation we use it's settings.
+ parent.transform.parent = parameters.AttachTo.transform;
+ parent.transform.position = parameters.AttachTo.transform.position;
+ parent.transform.rotation = parameters.AttachTo.transform.rotation;
+ parent.transform.localScale = Vector3.one;
+ if (ECEPreferences.CopyParentObjectLayer)
+ {
+ parent.layer = parameters.AttachTo.layer;
+ }
+ else
+ {
+ parent.layer = GameObjectLayerOverride;
+ }
+ Undo.RegisterCreatedObjectUndo(parent, "Create VHACD Collider Holder");
+ parameters.AttachTo = parent;
+ }
+ }
+ // keep the attach to object in case we need to create children.
+ GameObject attachTo = parameters.AttachTo;
+ EasyColliderCreator ecc = new EasyColliderCreator();
+ for (int i = 0; i < _VHACDMeshes.Length; i++)
+ {
+ // for individual child objects.
+ if (parameters.vhacdResultMethod == VHACD_RESULT_METHOD.IndividualChildObjects)
+ {
+ // we create a child at the same position and rotation as the common parent (the attachto object)
+ GameObject child = new GameObject("VHACDCollider");
+ child.transform.parent = parameters.AttachTo.transform;
+ child.transform.position = parameters.AttachTo.transform.position;
+ child.transform.rotation = parameters.AttachTo.transform.rotation;
+ child.transform.localScale = Vector3.one;
+ if (ECEPreferences.CopyParentObjectLayer)
+ {
+ child.layer = parameters.AttachTo.layer;
+ }
+ else
+ {
+ child.layer = GameObjectLayerOverride;
+ }
+ attachTo = child;
+ Undo.RegisterCreatedObjectUndo(child, "Create VHACD Collider");
+ }
+ if (parameters.ConvertTo == VHACD_CONVERSION.None)
+ {
+ // create a convex mesh collider.
+ MeshCollider mc = ecc.CreateConvexMeshCollider(_VHACDMeshes[i], attachTo, GetProperties());
+ CreatedColliders.Add(mc);
+ VHACDCreatedColliders.Add(mc);
+ AddedColliderIDs.Add(GetIDFor(mc));
+ }
+ else if (parameters.ConvertTo == VHACD_CONVERSION.Boxes)
+ {
+ EasyColliderProperties p = GetProperties();
+ p.AttachTo = attachTo;
+ BoxCollider bc = ecc.CreateBoxCollider(_VHACDMeshes[i].vertices.ToList(), p, true);
+ CreatedColliders.Add(bc);
+ AddedColliderIDs.Add(GetIDFor(bc));
+ }
+ else if (parameters.ConvertTo == VHACD_CONVERSION.Spheres)
+ {
+ EasyColliderProperties p = GetProperties();
+ p.AttachTo = attachTo;
+ SphereCollider sc = ecc.CreateSphereCollider_MinMax(_VHACDMeshes[i].vertices.ToList(), p, true);
+ CreatedColliders.Add(sc);
+ AddedColliderIDs.Add(GetIDFor(sc));
+ }
+ else if (parameters.ConvertTo == VHACD_CONVERSION.Capsules)
+ {
+ EasyColliderProperties p = GetProperties();
+ p.AttachTo = attachTo;
+ CapsuleCollider cc = ecc.CreateCapsuleCollider_MinMax(_VHACDMeshes[i].vertices.ToList(), p, CAPSULE_COLLIDER_METHOD.MinMax, true);
+ CreatedColliders.Add(cc);
+ AddedColliderIDs.Add(GetIDFor(cc));
+ }
+ }
+ return true;
+ case 5: // clean up
+ if (parameters.IsCalculationForPreview) { return true; } // skip step 5 for previews.
+ if (parameters.UseSelectedVertices)
+ {
+ Undo.RecordObject(this, "Run VHACD");
+ ClearSelectedVertices();
+ }
+ // compute buffer gets all points set to origin when VHACD is run without use only selected vertices. So let's reupdate it.
+ if (Compute != null)
+ {
+ Compute.UpdateSelectedBuffer(GetWorldVertices());
+ }
+ _VHACD.Clean();
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Creates a mesh from the current VHACD preview using the "use selected vertices" method
+ ///
+ /// Current VHACD parameters
+ /// Mesh created from the current VHACD preview using full-triangles, and adding remaining vertices by closest distance
+ private Mesh CreateVHACDSelectedVerticesPreviewMesh(VHACDParameters parameters)
+ {
+ // list of created meshes, 1 for each mesh filter.
+ List createdMeshList = new List();
+ // arrays to hold vertices of triangles of each mesh filter, and transform for each mesh filter.
+ Vector3[] vertices = new Vector3[0];
+ int[] triangles = new int[0];
+ Vector3[] normals = new Vector3[0];
+ Transform t = null;
+ // variables to hold easy collider vertices for each triangle of the mesh
+ EasyColliderVertex ecv0, ecv1, ecv2 = ecv1 = ecv0 = null;
+ foreach (MeshFilter mf in MeshFilters)
+ {
+ if (mf == null) continue;
+ // hashset of used vertices for use after full triangle pass.
+ HashSet usedVerticesSet = new HashSet();
+ // dictionary of vertex : vertex index to update.
+ Dictionary ecvVertIndexDictionary = new Dictionary();
+ // calculated mesh vertices and triangles to create a mesh with.
+ List verticesList = new List();
+ List trianglesList = new List();
+ // transform, verts, and tris of current mesh filter.
+ t = mf.transform;
+ vertices = mf.sharedMesh.vertices;
+ triangles = mf.sharedMesh.triangles;
+ normals = mf.sharedMesh.normals;
+ // keep track of how many verts have been added just to make it a little easier.
+ int vertexCount = 0;
+ // go through triangles to see if the whole triangle is selected.
+ for (int i = 0; i < triangles.Length; i += 3)
+ {
+ // vertices of the triangle.
+ ecv0 = new EasyColliderVertex(t, vertices[triangles[i]]);
+ ecv1 = new EasyColliderVertex(t, vertices[triangles[i + 1]]);
+ ecv2 = new EasyColliderVertex(t, vertices[triangles[i + 2]]);
+ // if the full triangle is selected, add it.
+ if (SelectedVerticesSet.Contains(ecv0) && SelectedVerticesSet.Contains(ecv1) && SelectedVerticesSet.Contains(ecv2))
+ {
+ // Debug.Log("Full triangle.");
+ // add verts in world-space to convert later.
+ Vector3 v0 = vertices[triangles[i]];
+ Vector3 v1 = vertices[triangles[i + 1]];
+ Vector3 v2 = vertices[triangles[i + 2]];
+ // if it's been used before.
+ if (ecvVertIndexDictionary.ContainsKey(ecv0))
+ {
+ // Debug.Log("Vertex already in dictionary");
+ // it's in the dictionary, so add the value in the dict to the triangle list
+ trianglesList.Add(ecvVertIndexDictionary[ecv0]);
+ }
+ else
+ {
+ // we haven't used this vertex yet, so add it
+ verticesList.Add(v0);
+ vertexCount++;
+ trianglesList.Add(vertexCount - 1);
+ // remember to add the vertex to the dictionary with the appropriate index value.
+ ecvVertIndexDictionary.Add(ecv0, vertexCount - 1);
+ // and add them to the used vertices set
+ usedVerticesSet.Add(ecv0);
+ }
+ // ecv1 - repeat above.
+ if (ecvVertIndexDictionary.ContainsKey(ecv1))
+ {
+ trianglesList.Add(ecvVertIndexDictionary[ecv1]);
+ }
+ else
+ {
+ verticesList.Add(v1);
+ vertexCount++;
+ trianglesList.Add(vertexCount - 1);
+ ecvVertIndexDictionary.Add(ecv1, vertexCount - 1);
+ usedVerticesSet.Add(ecv1);
+ }
+ // ecv2 - repeat above
+ if (ecvVertIndexDictionary.ContainsKey(ecv2))
+ {
+ trianglesList.Add(ecvVertIndexDictionary[ecv2]);
+ }
+ else
+ {
+ verticesList.Add(v2);
+ vertexCount++;
+ trianglesList.Add(vertexCount - 1);
+ ecvVertIndexDictionary.Add(ecv2, vertexCount - 1);
+ usedVerticesSet.Add(ecv2);
+ }
+ }
+ }
+
+ // hashset of selected vertices.
+ HashSet selectedVerticesSet = new HashSet(SelectedVerticesSet);
+ // int count = selectedVerticesSet.Count;
+ // remove unused vertices that are full triangles.
+ selectedVerticesSet.ExceptWith(usedVerticesSet);
+ // Debug.Log("Remaining vertices count:" + selectedVerticesSet.Count + " Total At Start:" + count);
+ // list of reamining verts (vertices that are selected that arent' a part of at least 1 full triangle.)
+ List remainingVertsLocal = new List();
+ // transform remaining verts to world-space.
+ foreach (EasyColliderVertex ecv in selectedVerticesSet)
+ {
+ // make sure the transform is the same as the current mesh filter.
+ if (ecv.T == t)
+ {
+ remainingVertsLocal.Add(ecv.LocalPosition);
+ }
+ }
+ // here we are checking to see if there was at least 1 full triangle to build from, if there wasn't then we make one.
+ // need at least 1 triangle to build from, but need at least 3 verts to do so.
+ if (trianglesList.Count < 3 && remainingVertsLocal.Count >= 3)
+ {
+ // find closest 3 in-order points (faster.. less accurate)
+ float totalMinDistance = Mathf.Infinity;
+ int[] vertIndexs = new int[3];
+ for (int i = 0; i < remainingVertsLocal.Count - 3; i++)
+ {
+ // d0 -> d1
+ float d01 = Vector3.Distance(remainingVertsLocal[i], remainingVertsLocal[i + 1]);
+ // d1 -> d2
+ float d12 = Vector3.Distance(remainingVertsLocal[i + 1], remainingVertsLocal[i + 2]);
+ // d2 -> d0
+ float d20 = Vector3.Distance(remainingVertsLocal[i + 2], remainingVertsLocal[i]);
+ // new "smallest" in-order triangle
+ if (d01 + d12 + d20 < totalMinDistance)
+ {
+ totalMinDistance = d01 + d12 + d20;
+ vertIndexs[0] = i;
+ vertIndexs[1] = i + 1;
+ vertIndexs[2] = i + 2;
+ }
+ }
+ // add the vertices
+ verticesList.Add(remainingVertsLocal[vertIndexs[0]]);
+ verticesList.Add(remainingVertsLocal[vertIndexs[1]]);
+ verticesList.Add(remainingVertsLocal[vertIndexs[2]]);
+ // add the triangle
+ trianglesList.Add(verticesList.Count - 3);
+ trianglesList.Add(verticesList.Count - 2);
+ trianglesList.Add(verticesList.Count - 1);
+ }
+ // add the remaining left-over non-full triangle vertices.
+ float minDistance = Mathf.Infinity;
+ int vertIndex0, vertIndex1 = vertIndex0 = -1;
+
+ foreach (Vector3 pos in remainingVertsLocal)
+ {
+ // find "closest" triangle edge quickly.
+ minDistance = Mathf.Infinity;
+ for (int i = 0; i < trianglesList.Count; i += 3)
+ {
+ // calc distance from vertex to each triangle point.
+ float d0, d1, d2 = d1 = d0 = 0;
+ d0 = Vector3.Distance(pos, verticesList[trianglesList[i]]);
+ d1 = Vector3.Distance(pos, verticesList[trianglesList[i + 1]]);
+ d2 = Vector3.Distance(pos, verticesList[trianglesList[i + 2]]);
+ // find lowest distance from remaining vert to a triangle vertex
+ if (d0 < minDistance)
+ {
+ // set the min distance
+ minDistance = d0;
+ // update the i0 index
+ vertIndex0 = trianglesList[i];
+ // update i1 index based on which other vertex in the triangle is closer.
+ vertIndex1 = d1 < d2 ? trianglesList[i + 1] : trianglesList[i + 2];
+ }
+ // repeat for d1.
+ if (d1 < minDistance)
+ {
+ minDistance = d1;
+ vertIndex0 = trianglesList[i + 1];
+ vertIndex1 = d0 < d2 ? trianglesList[i] : trianglesList[i + 2];
+ }
+ // d2 not needed because d0 -> d1, d2 or d1 -> d0, d2 would give the same result if d2 was lowest.
+ }
+ // now we have the "closest" triangle..
+ // add the vertex
+ if (vertIndex0 >= 0 && vertIndex1 >= 0)
+ {
+ verticesList.Add(pos);
+ // add the the triangle.
+ trianglesList.Add(verticesList.Count - 1);
+ trianglesList.Add(vertIndex0);
+ trianglesList.Add(vertIndex1);
+ }
+ else
+ {
+ // we have a single lonely vertex that needs to be added.
+ // luckily, just adding it 3 times and setting is a triangle works for VHACD....
+ verticesList.Add(pos);
+ verticesList.Add(pos);
+ verticesList.Add(pos);
+ trianglesList.Add(verticesList.Count - 1);
+ trianglesList.Add(verticesList.Count - 2);
+ trianglesList.Add(verticesList.Count - 3);
+ }
+
+ }
+
+
+ // extrude selected vertices.
+ if (parameters.NormalExtrudeMultiplier > 0.0f)
+ {
+ int vCount = verticesList.Count;
+ Dictionary vertexIndexToTriangleIndexDictionary = new Dictionary();
+ int count = trianglesList.Count;
+ for (int i = 0; i < count; i++)
+ {
+ if (vertexIndexToTriangleIndexDictionary.ContainsKey(trianglesList[i]))
+ {
+ trianglesList.Add(vertexIndexToTriangleIndexDictionary[trianglesList[i]]);
+ }
+ else
+ {
+ int nCount = 0;
+ Vector3 p = verticesList[trianglesList[i]];
+ Vector3 n = Vector3.zero;
+ for (int j = 0; j < vertices.Length; j++)
+ {
+ if (vertices[j] == p)
+ {
+ nCount++;
+ n += normals[j];
+ }
+ }
+ n.Normalize();
+ trianglesList.Add(verticesList.Count);
+ vertexIndexToTriangleIndexDictionary.Add(trianglesList[i], verticesList.Count);
+ verticesList.Add(p + n * parameters.NormalExtrudeMultiplier);
+ }
+ }
+ }
+
+ // covert from local mesh space to world then
+ // convert verts from world to attach to local space.
+ Transform attachTo = parameters.AttachTo.transform;
+ for (int i = 0; i < verticesList.Count; i++)
+ {
+ verticesList[i] = t.TransformPoint(verticesList[i]);
+ verticesList[i] = attachTo.InverseTransformPoint(verticesList[i]);
+ }
+
+ // create and return the mesh for use in vhacd.
+ Mesh m = new Mesh();
+#if (UNITY_2017_3_OR_NEWER)
+ m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
+#endif
+ m.vertices = verticesList.ToArray();
+ m.triangles = trianglesList.ToArray();
+ createdMeshList.Add(m);
+ }
+ Mesh result = new Mesh();
+ // use 32 bit index format for high # of verts.
+#if (UNITY_2017_3_OR_NEWER)
+ result.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
+#endif
+ List cis = new List();
+ // create combine instances for each created mesh.
+ for (int i = 0; i < createdMeshList.Count; i++)
+ {
+ if (createdMeshList[i] != null)
+ {
+ CombineInstance ci = new CombineInstance();
+ ci.mesh = createdMeshList[i];
+ cis.Add(ci);
+ }
+ }
+ //combine the mesh with the combine instances, NOT using the matrix.
+ result.CombineMeshes(cis.ToArray(), true, false);
+ return result;
+ }
+
+ #endregion
+
+
+
+
+
+#if UNITY_6000_4_OR_NEWER
+ //switch to EntityIDs
+
+ [SerializeField]
+ private List _AddedColliderIDs;
+ ///
+ /// List of added colliders through GetInstanceID()
+ ///
+ public List AddedColliderIDs
+ {
+ get
+ {
+ if (_AddedColliderIDs == null)
+ {
+ _AddedColliderIDs = new List();
+ }
+ return _AddedColliderIDs;
+ }
+ set { _AddedColliderIDs = value; }
+ }
+
+
+ [SerializeField]
+ private List _AddedInstanceIDs;
+ ///
+ /// List of all object's instance IDs that should be destroyed on cleanup. These are things like
+ /// MeshCollider used for vertex selection.
+ /// MeshFilter for skinned mesh renderers for mesh colliders.
+ /// Compute shader component.
+ /// Gizmo drawing component.
+ ///
+ public List AddedInstanceIDs
+ {
+ get
+ {
+ if (_AddedInstanceIDs == null)
+ {
+ _AddedInstanceIDs = new List();
+ }
+ return _AddedInstanceIDs;
+ }
+ set { _AddedInstanceIDs = value; }
+ }
+
+#else
+
+ [SerializeField]
+ private List _AddedColliderIDs;
+ ///
+ /// List of added colliders through GetInstanceID()
+ ///
+ public List AddedColliderIDs
+ {
+ get
+ {
+ if (_AddedColliderIDs == null)
+ {
+ _AddedColliderIDs = new List();
+ }
+ return _AddedColliderIDs;
+ }
+ set { _AddedColliderIDs = value; }
+ }
+
+ [SerializeField]
+ private List _AddedInstanceIDs;
+ ///
+ /// List of all object's instance IDs that should be destroyed on cleanup. These are things like
+ /// MeshCollider used for vertex selection.
+ /// MeshFilter for skinned mesh renderers for mesh colliders.
+ /// Compute shader component.
+ /// Gizmo drawing component.
+ ///
+ public List AddedInstanceIDs
+ {
+ get
+ {
+ if (_AddedInstanceIDs == null)
+ {
+ _AddedInstanceIDs = new List();
+ }
+ return _AddedInstanceIDs;
+ }
+ set { _AddedInstanceIDs = value; }
+ }
+#endif
+ [SerializeField]
+ private GameObject _AttachToObject;
+ ///
+ /// If different from the selected gameobject, the attach to object is used to convert to local vertices / attach the collider to.
+ ///
+ public GameObject AttachToObject
+ {
+ get
+ {
+ if (_AttachToObject == null)
+ {
+ return SelectedGameObject;
+ }
+ return _AttachToObject;
+ }
+ set { _AttachToObject = value; }
+ }
+
+
+
+ ///
+ /// Are we automatically including child skinned meshes when include child meshes is enabled?
+ ///
+ public bool AutoIncludeChildSkinnedMeshes { get { return ECEPreferences.AutoIncludeChildSkinnedMeshes; } }
+
+ [SerializeField]
+ private bool _ColliderSelectEnabled;
+ ///
+ /// Is Collider Selection Enabled? Toggles colliders on and off when changed.
+ ///
+ public bool ColliderSelectEnabled
+ {
+ get { return _ColliderSelectEnabled; }
+ set
+ {
+ _ColliderSelectEnabled = value;
+ }
+ }
+
+ [SerializeField]
+ private EasyColliderCompute _Compute;
+ ///
+ /// Compute shader script
+ ///
+ public EasyColliderCompute Compute
+ {
+ get
+ {
+ if (_Compute == null && SelectedGameObject != null)
+ {
+ _Compute = SelectedGameObject.GetComponent();
+ }
+ return _Compute;
+ }
+ set
+ {
+ _Compute = value;
+ if (value != null && DisplayMeshVertices)
+ {
+ _Compute.SetDisplayAllBuffer(GetAllWorldMeshVertices());
+ }
+ }
+ }
+
+ [SerializeField]
+ private List _CreatedColliders;
+ ///
+ /// List of colliders we created
+ ///
+ public List CreatedColliders
+ {
+ get
+ {
+ if (_CreatedColliders == null)
+ {
+ _CreatedColliders = new List();
+ }
+ return _CreatedColliders;
+ }
+ set { _CreatedColliders = value; }
+ }
+
+ [SerializeField]
+ ///
+ /// Volume per vertex density. Used in displaying vertices so less verts in more space displays larger and vice versa.
+ ///
+ public float DensityScale = 1.0f;
+
+ public bool DisplayMeshVertices
+ {
+ get { return ECEPreferences.DisplayAllVertices; }
+ }
+
+ private static EasyColliderPreferences _ECEPreferences;
+ public static EasyColliderPreferences ECEPreferences
+ {
+ get
+ {
+ if (_ECEPreferences == null)
+ {
+ _ECEPreferences = EasyColliderPreferences.Preferences;
+ }
+ return _ECEPreferences;
+ }
+ }
+
+ [SerializeField]
+ ///
+ /// Does the selected gameboject have a skinned mesh renderer as a child somewhere?
+ ///
+ public bool HasSkinnedMeshRenderer = false;
+
+ [SerializeField]
+ private EasyColliderGizmos _Gizmos;
+ ///
+ /// Gizmos component for drawing vertices and selections.
+ ///
+ public EasyColliderGizmos Gizmos
+ {
+ get
+ {
+ if (_Gizmos == null && SelectedGameObject != null)
+ {
+ _Gizmos = SelectedGameObject.GetComponent();
+ }
+ return _Gizmos;
+ }
+ set
+ {
+ _Gizmos = value;
+ if (value != null && DisplayMeshVertices)
+ {
+ _Gizmos.DisplayVertexPositions = GetAllWorldMeshVertices();
+ }
+ }
+ }
+
+ [SerializeField]
+ private bool _IncludeChildMeshes;
+ ///
+ /// Are we including child meshes for vertex selection?
+ ///
+ public bool IncludeChildMeshes
+ {
+ get { return _IncludeChildMeshes; }
+ set
+ {
+ if (_IncludeChildMeshes != value)
+ {
+ OnlyDeselectableColliders.Clear();
+ // lets store things.
+ // List selectedVertsBeforeChange = new List(SelectedVertices);
+ //HashSet selectedNonVerts = new HashSet(SelectedNonVerticesSet);
+ HashSet selectedChildColliders = new HashSet(SelectedColliders);
+ if (!value && SelectedGameObject != null)
+ {
+ selectedChildColliders.ExceptWith(SelectedGameObject.GetComponents());
+ }
+ // why do we do this?
+ GameObject selected = SelectedGameObject;
+ GameObject attach = AttachToObject;
+ SelectedGameObject = null;
+ _IncludeChildMeshes = value;
+ //OnIncludeChildMeshesChanged(value);
+ SelectedGameObject = selected;
+ AttachToObject = attach;
+
+ /* foreach(var v in selectedVertsBeforeChange)
+ {
+ SelectVertex(v, true);
+ }*/
+ foreach (var c in selectedChildColliders)
+ {
+ SelectCollider(c);
+ OnlyDeselectableColliders.Add(c);
+ }
+ }
+ // _IncludeChildMeshes = value;
+ // SetupChildObjects(value);
+ // CalculateDensity();
+ if (value == false)
+ {
+ // CleanChildSelectedVertices();
+ }
+ }
+ }
+
+ public HashSet OnlyDeselectableColliders = new HashSet();
+
+ /* void OnIncludeChildMeshesChanged(bool value)
+ {
+ HashSet rootMeshFilters = new HashSet();
+ HashSet childMeshFilters = new HashSet();
+ rootMeshFilters.UnionWith(SelectedGameObject.GetComponents());
+ childMeshFilters.UnionWith(SelectedGameObject.GetComponentsInChildren());
+ childMeshFilters.ExceptWith
+ if (!value)
+ {
+ // CleanUpObject()
+ }
+ }*/
+
+ [SerializeField]
+ private bool _IsTrigger;
+ ///
+ /// Created colliders marked as trigger?
+ ///
+ ///
+ public bool IsTrigger { get { return _IsTrigger; } set { _IsTrigger = value; } }
+
+ [SerializeField]
+ private List _LastSelectedVertices;
+ ///
+ /// List of the vertices that were selected last
+ ///
+ public List LastSelectedVertices
+ {
+ get
+ {
+ if (_LastSelectedVertices == null)
+ {
+ _LastSelectedVertices = new List();
+ }
+ return _LastSelectedVertices;
+ }
+ set { _LastSelectedVertices = value; }
+ }
+
+ [SerializeField]
+ private List _MeshFilters;
+ ///
+ /// List of mesh filters on SelectedGameobject + children (if IncludeChildMeshes)
+ ///
+ public List MeshFilters
+ {
+ get
+ {
+ if (_MeshFilters == null)
+ {
+ _MeshFilters = new List();
+ }
+ return _MeshFilters;
+ }
+ set { _MeshFilters = value; }
+ }
+
+ [SerializeField]
+ private List _NonKinematicRigidbodies;
+ ///