mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-11-11 08:38:45 +00:00
提交
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -80,7 +80,7 @@ namespace Plugins.JNGame.Sync.Frame
|
|||||||
|
|
||||||
public override Task OnInit()
|
public override Task OnInit()
|
||||||
{
|
{
|
||||||
Physics.autoSimulation = false;
|
Physics.simulationMode = SimulationMode.Script;
|
||||||
Physics.autoSyncTransforms = false;
|
Physics.autoSyncTransforms = false;
|
||||||
this.OnReset();
|
this.OnReset();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -108,6 +108,8 @@ namespace Plugins.JNGame.Sync.Frame
|
|||||||
this._nLocalFrame = 0;
|
this._nLocalFrame = 0;
|
||||||
this._nServerFrame = 0;
|
this._nServerFrame = 0;
|
||||||
this._nLocalRunFrame = 0;
|
this._nLocalRunFrame = 0;
|
||||||
|
this.dtTotal = 0;
|
||||||
|
this.dtInputTotal = 0;
|
||||||
|
|
||||||
Physics.SyncTransforms();
|
Physics.SyncTransforms();
|
||||||
EventDispatcher.Event.Dispatch(JNSyncFrameEvent.CREATE);
|
EventDispatcher.Event.Dispatch(JNSyncFrameEvent.CREATE);
|
||||||
@@ -174,9 +176,9 @@ namespace Plugins.JNGame.Sync.Frame
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//执行下一帧物理
|
// //执行下一帧物理
|
||||||
Physics.Simulate((float)dt / 1000);
|
// Physics.Simulate((float)dt / 1000);
|
||||||
Physics.SyncTransforms();
|
// Physics.SyncTransforms();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace Plugins.JNGame.Util
|
|||||||
|
|
||||||
public async UniTask<T> Post<T,TA>(string url,TA data)
|
public async UniTask<T> Post<T,TA>(string url,TA data)
|
||||||
{
|
{
|
||||||
var request = UnityWebRequest.Post($"{this._config.BaseURL}/{url}",JsonConvert.SerializeObject(data));
|
var request = UnityWebRequest.PostWwwForm($"{this._config.BaseURL}/{url}",JsonConvert.SerializeObject(data));
|
||||||
return JsonConvert.DeserializeObject<T>((await request.SendWebRequest()).downloadHandler.text);
|
return JsonConvert.DeserializeObject<T>((await request.SendWebRequest()).downloadHandler.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ GameObject:
|
|||||||
- component: {fileID: 664841175096201691}
|
- component: {fileID: 664841175096201691}
|
||||||
- component: {fileID: 664841175096201690}
|
- component: {fileID: 664841175096201690}
|
||||||
- component: {fileID: 664841175096201689}
|
- component: {fileID: 664841175096201689}
|
||||||
|
- component: {fileID: 574656058432384995}
|
||||||
|
- component: {fileID: 2113456302054558747}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Sphere 1
|
m_Name: Sphere 1
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -25,13 +27,13 @@ Transform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 664841175096201695}
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 0
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!33 &664841175096201690
|
--- !u!33 &664841175096201690
|
||||||
MeshFilter:
|
MeshFilter:
|
||||||
@@ -83,3 +85,51 @@ MeshRenderer:
|
|||||||
m_SortingLayer: 0
|
m_SortingLayer: 0
|
||||||
m_SortingOrder: 0
|
m_SortingOrder: 0
|
||||||
m_AdditionalVertexStreams: {fileID: 0}
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!135 &574656058432384995
|
||||||
|
SphereCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Radius: 0.5
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!54 &2113456302054558747
|
||||||
|
Rigidbody:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 4
|
||||||
|
m_Mass: 1
|
||||||
|
m_Drag: 0
|
||||||
|
m_AngularDrag: 0.05
|
||||||
|
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||||
|
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||||
|
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ImplicitCom: 1
|
||||||
|
m_ImplicitTensor: 1
|
||||||
|
m_UseGravity: 1
|
||||||
|
m_IsKinematic: 0
|
||||||
|
m_Interpolate: 0
|
||||||
|
m_Constraints: 0
|
||||||
|
m_CollisionDetection: 0
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ GameObject:
|
|||||||
- component: {fileID: 664841175096201691}
|
- component: {fileID: 664841175096201691}
|
||||||
- component: {fileID: 664841175096201690}
|
- component: {fileID: 664841175096201690}
|
||||||
- component: {fileID: 664841175096201689}
|
- component: {fileID: 664841175096201689}
|
||||||
|
- component: {fileID: -289448904210163489}
|
||||||
|
- component: {fileID: -1515477822795860743}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Sphere 2
|
m_Name: Sphere 2
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -25,13 +27,13 @@ Transform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 664841175096201695}
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 0
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!33 &664841175096201690
|
--- !u!33 &664841175096201690
|
||||||
MeshFilter:
|
MeshFilter:
|
||||||
@@ -83,3 +85,51 @@ MeshRenderer:
|
|||||||
m_SortingLayer: 0
|
m_SortingLayer: 0
|
||||||
m_SortingOrder: 0
|
m_SortingOrder: 0
|
||||||
m_AdditionalVertexStreams: {fileID: 0}
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!54 &-289448904210163489
|
||||||
|
Rigidbody:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 4
|
||||||
|
m_Mass: 1
|
||||||
|
m_Drag: 0
|
||||||
|
m_AngularDrag: 0.05
|
||||||
|
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||||
|
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||||
|
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ImplicitCom: 1
|
||||||
|
m_ImplicitTensor: 1
|
||||||
|
m_UseGravity: 1
|
||||||
|
m_IsKinematic: 0
|
||||||
|
m_Interpolate: 0
|
||||||
|
m_Constraints: 0
|
||||||
|
m_CollisionDetection: 0
|
||||||
|
--- !u!135 &-1515477822795860743
|
||||||
|
SphereCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Radius: 0.5
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ GameObject:
|
|||||||
- component: {fileID: 664841175096201691}
|
- component: {fileID: 664841175096201691}
|
||||||
- component: {fileID: 664841175096201690}
|
- component: {fileID: 664841175096201690}
|
||||||
- component: {fileID: 664841175096201689}
|
- component: {fileID: 664841175096201689}
|
||||||
|
- component: {fileID: 5459639529223246069}
|
||||||
|
- component: {fileID: 6482499274382216103}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Sphere
|
m_Name: Sphere
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -25,13 +27,13 @@ Transform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 664841175096201695}
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
m_LocalPosition: {x: 0, y: 6.03, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_RootOrder: 0
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!33 &664841175096201690
|
--- !u!33 &664841175096201690
|
||||||
MeshFilter:
|
MeshFilter:
|
||||||
@@ -83,3 +85,51 @@ MeshRenderer:
|
|||||||
m_SortingLayer: 0
|
m_SortingLayer: 0
|
||||||
m_SortingOrder: 0
|
m_SortingOrder: 0
|
||||||
m_AdditionalVertexStreams: {fileID: 0}
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!135 &5459639529223246069
|
||||||
|
SphereCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Radius: 0.5
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!54 &6482499274382216103
|
||||||
|
Rigidbody:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 664841175096201695}
|
||||||
|
serializedVersion: 4
|
||||||
|
m_Mass: 1
|
||||||
|
m_Drag: 0
|
||||||
|
m_AngularDrag: 0.05
|
||||||
|
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||||
|
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||||
|
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ImplicitCom: 1
|
||||||
|
m_ImplicitTensor: 1
|
||||||
|
m_UseGravity: 1
|
||||||
|
m_IsKinematic: 0
|
||||||
|
m_Interpolate: 0
|
||||||
|
m_Constraints: 0
|
||||||
|
m_CollisionDetection: 0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 5f759e05d198dbb4fa441d263f243edb
|
guid: 636828e4e4b4ac1468433f7433083e68
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
8
JNFrame/Assets/Samples/Unity Physics.meta
Normal file
8
JNFrame/Assets/Samples/Unity Physics.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 19f77faebdc567b488940d510d374f17
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
JNFrame/Assets/Samples/Unity Physics/1.0.16.meta
Normal file
8
JNFrame/Assets/Samples/Unity Physics/1.0.16.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 63defbb40447be042a23a204dbabfea1
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 05b2d911c4f5b3942a29cd70d1ac526b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 924801193ae5c634d814f3e2efdf5e5c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 016d8a3094fcbdf44961b10be9ac5c56
|
||||||
|
timeCreated: 1680116734
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: df54d2bd08525d5449ae01cf06c0117f
|
||||||
|
timeCreated: 1678291379
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
class BeveledBoxBoundsHandle : BoxBoundsHandle
|
||||||
|
{
|
||||||
|
public float bevelRadius
|
||||||
|
{
|
||||||
|
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!m_IsDragging)
|
||||||
|
m_BevelRadius = math.max(0f, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
|
||||||
|
bool m_IsDragging = false;
|
||||||
|
|
||||||
|
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
|
||||||
|
|
||||||
|
public new void DrawHandle()
|
||||||
|
{
|
||||||
|
int prevHotControl = GUIUtility.hotControl;
|
||||||
|
if (prevHotControl == 0)
|
||||||
|
m_IsDragging = false;
|
||||||
|
base.DrawHandle();
|
||||||
|
int currHotcontrol = GUIUtility.hotControl;
|
||||||
|
if (currHotcontrol != prevHotControl)
|
||||||
|
m_IsDragging = currHotcontrol != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawWireframe()
|
||||||
|
{
|
||||||
|
if (this.bevelRadius <= 0f)
|
||||||
|
{
|
||||||
|
base.DrawWireframe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cameraPosition = float3.zero;
|
||||||
|
var cameraForward = new float3 { z = 1f };
|
||||||
|
if (Camera.current != null)
|
||||||
|
{
|
||||||
|
cameraPosition = Camera.current.transform.position;
|
||||||
|
cameraForward = Camera.current.transform.forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = new Bounds(this.center, this.size);
|
||||||
|
bool isCameraInsideBox = Camera.current != null && bounds.Contains(Handles.inverseMatrix.MultiplyPoint(cameraPosition));
|
||||||
|
var bevelRadius = this.bevelRadius;
|
||||||
|
var origin = (float3)this.center;
|
||||||
|
var size = (float3)this.size;
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), bevelRadius, 0, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), bevelRadius, 1, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), bevelRadius, 2, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), bevelRadius, 2, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
var corner = 0.5f * size - new float3(1f) * bevelRadius;
|
||||||
|
var axisx = new float3(1f, 0f, 0f);
|
||||||
|
var axisy = new float3(0f, 1f, 0f);
|
||||||
|
var axisz = new float3(0f, 0f, 1f);
|
||||||
|
|
||||||
|
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
|
||||||
|
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
|
||||||
|
var invMatrix = Handles.inverseMatrix;
|
||||||
|
cameraPosition = invMatrix.MultiplyPoint(cameraPosition);
|
||||||
|
cameraForward = invMatrix.MultiplyVector(cameraForward);
|
||||||
|
var cameraOrtho = Camera.current == null || Camera.current.orthographic;
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[0]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[1]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[2]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[3]);
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[4]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[5]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[6]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraPosition, cameraForward, cameraOrtho, bevelRadius, out s_Corners[7]);
|
||||||
|
|
||||||
|
for (int i = 0; i < s_Corners.Length; i++)
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[i], true);
|
||||||
|
|
||||||
|
// Draw the horizon edges between the corners
|
||||||
|
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
|
||||||
|
{
|
||||||
|
int dnA = upA + 4;
|
||||||
|
int dnB = upB + 4;
|
||||||
|
|
||||||
|
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
|
||||||
|
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
|
||||||
|
|
||||||
|
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
|
||||||
|
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
|
||||||
|
|
||||||
|
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
|
||||||
|
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: be997ba7f25d7f44b8996dfac81c1a3a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
class BeveledCylinderBoundsHandle : PrimitiveBoundsHandle
|
||||||
|
{
|
||||||
|
public float bevelRadius
|
||||||
|
{
|
||||||
|
get => math.min(m_BevelRadius, math.cmin(GetSize()) * 0.5f);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!m_IsDragging)
|
||||||
|
m_BevelRadius = math.max(0f, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float m_BevelRadius = ConvexHullGenerationParameters.Default.BevelRadius;
|
||||||
|
|
||||||
|
bool m_IsDragging = false;
|
||||||
|
|
||||||
|
public float height
|
||||||
|
{
|
||||||
|
get => GetSize().z;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var size = GetSize();
|
||||||
|
size.z = math.max(0f, value);
|
||||||
|
SetSize(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float radius
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var size = (float3)GetSize();
|
||||||
|
var diameter = 0f;
|
||||||
|
// only consider size values on enabled axes
|
||||||
|
if (IsAxisEnabled(0)) diameter = math.max(diameter, math.abs(size.x));
|
||||||
|
else if (IsAxisEnabled(1)) diameter = math.max(diameter, math.abs(size.y));
|
||||||
|
return diameter * 0.5f;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var size = (float3)GetSize();
|
||||||
|
size.x = size.y = math.max(0f, value * 2.0f);
|
||||||
|
SetSize(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sideCount
|
||||||
|
{
|
||||||
|
get => m_SideCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == m_SideCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_SideCount = value;
|
||||||
|
|
||||||
|
Array.Resize(ref m_TopPoints, m_SideCount * 2);
|
||||||
|
Array.Resize(ref m_BottomPoints, m_SideCount * 2);
|
||||||
|
Array.Resize(ref m_Corners, m_SideCount * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int m_SideCount;
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.Corner[] m_Corners = Array.Empty<PhysicsBoundsHandleUtility.Corner>();
|
||||||
|
Vector3[] m_TopPoints = Array.Empty<Vector3>();
|
||||||
|
Vector3[] m_BottomPoints = Array.Empty<Vector3>();
|
||||||
|
|
||||||
|
public new void DrawHandle()
|
||||||
|
{
|
||||||
|
int prevHotControl = GUIUtility.hotControl;
|
||||||
|
if (prevHotControl == 0)
|
||||||
|
m_IsDragging = false;
|
||||||
|
base.DrawHandle();
|
||||||
|
int currHotcontrol = GUIUtility.hotControl;
|
||||||
|
if (currHotcontrol != prevHotControl)
|
||||||
|
m_IsDragging = currHotcontrol != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawWireframe()
|
||||||
|
{
|
||||||
|
using (new Handles.DrawingScope(Handles.matrix))
|
||||||
|
{
|
||||||
|
var backfacedColor = PhysicsBoundsHandleUtility.GetStateColor(true);
|
||||||
|
var frontfacedColor = Handles.color;
|
||||||
|
bool isCameraInsideBox = false;
|
||||||
|
|
||||||
|
var radius = this.radius;
|
||||||
|
var bevelRadius = this.bevelRadius;
|
||||||
|
|
||||||
|
var halfHeight = new float3(0f, 0f, height * 0.5f);
|
||||||
|
var ctr = (float3)center;
|
||||||
|
var halfAngleStep = math.PI / m_SideCount;
|
||||||
|
var angleStep = 2f * halfAngleStep;
|
||||||
|
const float kHalfPI = (math.PI * 0.5f);
|
||||||
|
|
||||||
|
var bottom = ctr + halfHeight + new float3 { z = bevelRadius };
|
||||||
|
var top = ctr - halfHeight - new float3 { z = bevelRadius };
|
||||||
|
var tangent = new float3(1, 0, 0);
|
||||||
|
var binormal = new float3(0, 1, 0);
|
||||||
|
var topBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(top, -tangent, binormal, axes, isCameraInsideBox);
|
||||||
|
var bottomBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(bottom, tangent, binormal, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
var cameraCenter = float3.zero;
|
||||||
|
var cameraForward = new float3 { z = 1f };
|
||||||
|
if (Camera.current != null)
|
||||||
|
{
|
||||||
|
cameraCenter = Camera.current.transform.position;
|
||||||
|
cameraForward = Camera.current.transform.forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
|
||||||
|
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
|
||||||
|
var invMatrix = Handles.inverseMatrix;
|
||||||
|
cameraCenter = invMatrix.MultiplyPoint(cameraCenter);
|
||||||
|
cameraForward = invMatrix.MultiplyVector(cameraForward);
|
||||||
|
var cameraOrtho = Camera.current != null && Camera.current.orthographic;
|
||||||
|
|
||||||
|
var noSides = (radius - bevelRadius) < PhysicsBoundsHandleUtility.kDistanceEpsilon;
|
||||||
|
var up = new float3(0, 0, -1f);
|
||||||
|
|
||||||
|
var t = ((m_SideCount - 2) * angleStep);
|
||||||
|
var xyAngle0 = new float3(math.cos(t), math.sin(t), 0f);
|
||||||
|
|
||||||
|
t = (m_SideCount - 1) * angleStep;
|
||||||
|
var xyAngle1 = new float3(math.cos(t), math.sin(t), 0f);
|
||||||
|
var sideways1 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
|
||||||
|
var direction1 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
|
||||||
|
var bevelGreaterThanZero = bevelRadius > 0f;
|
||||||
|
var bevelLessThanCylinderRadius = bevelRadius < radius;
|
||||||
|
for (var i = 0; i < m_SideCount; ++i)
|
||||||
|
{
|
||||||
|
t = i * angleStep;
|
||||||
|
var xyAngle2 = new float3(math.cos(t), math.sin(t), 0f);
|
||||||
|
var sideways2 = new float3(math.cos(t + kHalfPI - halfAngleStep), math.sin(t + kHalfPI - halfAngleStep), 0f);
|
||||||
|
var direction2 = new float3(math.cos(t + halfAngleStep), math.sin(t + halfAngleStep), 0f);
|
||||||
|
|
||||||
|
var offset0 = xyAngle0 * (radius - bevelRadius);
|
||||||
|
var offset1 = xyAngle1 * (radius - bevelRadius);
|
||||||
|
var offset2 = xyAngle2 * (radius - bevelRadius);
|
||||||
|
|
||||||
|
var top1 = ctr + offset1 - (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
var bottom1 = ctr + offset1 + (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
|
||||||
|
var top2 = ctr + offset2 - (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
var bottom2 = ctr + offset2 + (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
|
||||||
|
var startOffset = direction1 * bevelRadius;
|
||||||
|
|
||||||
|
if (bevelGreaterThanZero)
|
||||||
|
{
|
||||||
|
var upOffset = up * bevelRadius;
|
||||||
|
|
||||||
|
// top/bottom caps
|
||||||
|
if (bevelLessThanCylinderRadius)
|
||||||
|
{
|
||||||
|
Handles.color = topBackFaced ? backfacedColor : frontfacedColor;
|
||||||
|
Handles.DrawLine(top1 + upOffset, top2 + upOffset);
|
||||||
|
|
||||||
|
Handles.color = bottomBackFaced ? backfacedColor : frontfacedColor;
|
||||||
|
Handles.DrawLine(bottom1 - upOffset, bottom2 - upOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currSideMidPoint = ctr + ((top1 + bottom1 + top2 + bottom2) * 0.25f) + startOffset;
|
||||||
|
var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currSideMidPoint, up, sideways2, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
Handles.color = currSideBackFaced ? backfacedColor : frontfacedColor;
|
||||||
|
if (!noSides)
|
||||||
|
{
|
||||||
|
// Square side of bevelled cylinder
|
||||||
|
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
|
||||||
|
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
|
||||||
|
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
|
||||||
|
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Square side of bevelled cylinder, when squashed to a single line
|
||||||
|
Handles.DrawLine(top2 + startOffset, bottom2 + startOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var top0 = ctr + offset0 - (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
var bottom0 = ctr + offset0 + (halfHeight - new float3 { z = bevelRadius });
|
||||||
|
|
||||||
|
var prevMidPoint = ctr + ((top0 + top1 + bottom0 + bottom1) * 0.25f) + startOffset;
|
||||||
|
var prevSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(prevMidPoint, up, sideways1, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
var currMidPoint = ctr + ((top1 + top2 + bottom1 + bottom2) * 0.25f) + startOffset;
|
||||||
|
var currSideBackFaced = PhysicsBoundsHandleUtility.IsBackfaced(currMidPoint, up, sideways2, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
// Square side of bevelled cylinder
|
||||||
|
Handles.color = (currSideBackFaced && prevSideBackFaced) ? backfacedColor : frontfacedColor;
|
||||||
|
Handles.DrawLine(bottom1 + startOffset, top1 + startOffset);
|
||||||
|
|
||||||
|
Handles.color = (currSideBackFaced && topBackFaced) ? backfacedColor : frontfacedColor;
|
||||||
|
Handles.DrawLine(top1 + startOffset, top2 + startOffset);
|
||||||
|
|
||||||
|
Handles.color = (currSideBackFaced && bottomBackFaced) ? backfacedColor : frontfacedColor;
|
||||||
|
Handles.DrawLine(bottom2 + startOffset, bottom1 + startOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bevelGreaterThanZero)
|
||||||
|
{
|
||||||
|
Handles.color = frontfacedColor;
|
||||||
|
|
||||||
|
var cornerIndex0 = i;
|
||||||
|
var cornerIndex1 = i + m_SideCount;
|
||||||
|
{
|
||||||
|
var orientation = quaternion.LookRotation(xyAngle2, up);
|
||||||
|
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(top2,
|
||||||
|
new float3x3(direction1, up, direction2),
|
||||||
|
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
|
||||||
|
bevelRadius, out m_Corners[cornerIndex0]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var orientation = quaternion.LookRotation(xyAngle2, -up);
|
||||||
|
var cornerNormal = math.normalize(math.mul(orientation, new float3(0f, 1f, 1f)));
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(bottom2,
|
||||||
|
new float3x3(direction2, -up, direction1),
|
||||||
|
cornerNormal, cameraCenter, cameraForward, cameraOrtho,
|
||||||
|
bevelRadius, out m_Corners[cornerIndex1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
direction1 = direction2;
|
||||||
|
sideways1 = sideways2;
|
||||||
|
xyAngle0 = xyAngle1;
|
||||||
|
xyAngle1 = xyAngle2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bevelGreaterThanZero)
|
||||||
|
{
|
||||||
|
Handles.color = frontfacedColor;
|
||||||
|
for (int a = m_SideCount - 1, b = 0; b < m_SideCount; a = b, ++b)
|
||||||
|
{
|
||||||
|
var up0 = a;
|
||||||
|
var dn0 = a + m_SideCount;
|
||||||
|
|
||||||
|
var up1 = b;
|
||||||
|
var dn1 = b + m_SideCount;
|
||||||
|
|
||||||
|
// Side horizon on vertical curved edge
|
||||||
|
if (m_Corners[up1].splitCount > 1 &&
|
||||||
|
m_Corners[dn1].splitCount > 1)
|
||||||
|
{
|
||||||
|
if ((m_Corners[up1].splitAxis[0].y || m_Corners[up1].splitAxis[1].y) &&
|
||||||
|
(m_Corners[dn1].splitAxis[0].y || m_Corners[dn1].splitAxis[1].y))
|
||||||
|
{
|
||||||
|
var point0 = m_Corners[up1].splitAxis[0].y ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
|
||||||
|
var point1 = m_Corners[dn1].splitAxis[0].y ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
|
||||||
|
Handles.DrawLine(point0, point1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Top horizon on horizontal curved edge
|
||||||
|
if (m_Corners[up0].splitCount > 1 &&
|
||||||
|
m_Corners[up1].splitCount > 1)
|
||||||
|
{
|
||||||
|
if ((m_Corners[up0].splitAxis[0].x || m_Corners[up0].splitAxis[1].x) &&
|
||||||
|
(m_Corners[up1].splitAxis[0].z || m_Corners[up1].splitAxis[1].z))
|
||||||
|
{
|
||||||
|
var point0 = m_Corners[up0].splitAxis[0].x ? m_Corners[up0].points[0] : m_Corners[up0].points[1];
|
||||||
|
var point1 = m_Corners[up1].splitAxis[0].z ? m_Corners[up1].points[0] : m_Corners[up1].points[1];
|
||||||
|
Handles.DrawLine(point0, point1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Bottom horizon on horizontal curved edge
|
||||||
|
if (m_Corners[dn0].splitCount > 1 &&
|
||||||
|
m_Corners[dn1].splitCount > 1)
|
||||||
|
{
|
||||||
|
if ((m_Corners[dn0].splitAxis[0].z || m_Corners[dn0].splitAxis[1].z) &&
|
||||||
|
(m_Corners[dn1].splitAxis[0].x || m_Corners[dn1].splitAxis[1].x))
|
||||||
|
{
|
||||||
|
var point0 = m_Corners[dn0].splitAxis[0].z ? m_Corners[dn0].points[0] : m_Corners[dn0].points[1];
|
||||||
|
var point1 = m_Corners[dn1].splitAxis[0].x ? m_Corners[dn1].points[0] : m_Corners[dn1].points[1];
|
||||||
|
Handles.DrawLine(point0, point1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < m_Corners.Length; ++i)
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(m_Corners[i], new bool3(true, true, !noSides));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Bounds OnHandleChanged(HandleDirection handle, Bounds boundsOnClick, Bounds newBounds)
|
||||||
|
{
|
||||||
|
const int k_DirectionX = 0;
|
||||||
|
const int k_DirectionY = 1;
|
||||||
|
const int k_DirectionZ = 2;
|
||||||
|
|
||||||
|
var changedAxis = k_DirectionX;
|
||||||
|
var otherRadiusAxis = k_DirectionY;
|
||||||
|
switch (handle)
|
||||||
|
{
|
||||||
|
case HandleDirection.NegativeY:
|
||||||
|
case HandleDirection.PositiveY:
|
||||||
|
changedAxis = k_DirectionY;
|
||||||
|
otherRadiusAxis = k_DirectionX;
|
||||||
|
break;
|
||||||
|
case HandleDirection.NegativeZ:
|
||||||
|
case HandleDirection.PositiveZ:
|
||||||
|
changedAxis = k_DirectionZ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var upperBound = newBounds.max;
|
||||||
|
var lowerBound = newBounds.min;
|
||||||
|
|
||||||
|
var convexDiameter = 2f * bevelRadius;
|
||||||
|
|
||||||
|
// ensure changed dimension cannot be made less than convex diameter
|
||||||
|
if (upperBound[changedAxis] - lowerBound[changedAxis] < convexDiameter)
|
||||||
|
{
|
||||||
|
switch (handle)
|
||||||
|
{
|
||||||
|
case HandleDirection.PositiveX:
|
||||||
|
case HandleDirection.PositiveY:
|
||||||
|
case HandleDirection.PositiveZ:
|
||||||
|
upperBound[changedAxis] = lowerBound[changedAxis] + convexDiameter;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lowerBound[changedAxis] = upperBound[changedAxis] - convexDiameter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure radius changes uniformly
|
||||||
|
if (changedAxis != k_DirectionZ)
|
||||||
|
{
|
||||||
|
var rad = 0.5f * (upperBound[changedAxis] - lowerBound[changedAxis]);
|
||||||
|
|
||||||
|
lowerBound[otherRadiusAxis] = center[otherRadiusAxis] - rad;
|
||||||
|
upperBound[otherRadiusAxis] = center[otherRadiusAxis] + rad;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Bounds((upperBound + lowerBound) * 0.5f, upperBound - lowerBound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f5e502486013d44c9cc7466f6c3bcd1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using static UnityEditor.IMGUI.Controls.PrimitiveBoundsHandle;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
static class PhysicsBoundsHandleUtility
|
||||||
|
{
|
||||||
|
internal const float kBackfaceAlphaMultiplier = 0.2f;
|
||||||
|
const float kDegreeEpsilon = 0.001f;
|
||||||
|
public const float kDistanceEpsilon = 0.0001f;
|
||||||
|
|
||||||
|
public static bool IsBackfaced(float3 localPos, float3 localTangent, float3 localBinormal, Axes axes, bool isCameraInsideBox)
|
||||||
|
{
|
||||||
|
// if inside the box then ignore back facing alpha multiplier (otherwise all handles will look disabled)
|
||||||
|
if (isCameraInsideBox || axes != Axes.All)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// use tangent and binormal to calculate normal in case handle matrix is skewed
|
||||||
|
float3 worldTangent = math.normalize(Handles.matrix.MultiplyVector(localTangent));
|
||||||
|
float3 worldBinormal = math.normalize(Handles.matrix.MultiplyVector(localBinormal));
|
||||||
|
float3 worldDir = math.normalize(math.cross(worldTangent, worldBinormal));
|
||||||
|
|
||||||
|
// adjust color if handle is back facing
|
||||||
|
float cosV;
|
||||||
|
|
||||||
|
var currentCamera = Camera.current;
|
||||||
|
if (currentCamera != null && !currentCamera.orthographic)
|
||||||
|
{
|
||||||
|
float3 cameraPos = currentCamera.transform.position;
|
||||||
|
float3 worldPos = Handles.matrix.MultiplyPoint(localPos);
|
||||||
|
cosV = math.dot(math.normalize(cameraPos - worldPos), worldDir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float3 cameraForward = currentCamera == null ? Vector3.forward : currentCamera.transform.forward;
|
||||||
|
cosV = math.dot(-cameraForward, worldDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cosV < -0.0001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color GetStateColor(bool isBackfaced)
|
||||||
|
{
|
||||||
|
float alphaMultiplier = isBackfaced ? kBackfaceAlphaMultiplier : 1f;
|
||||||
|
return Handles.color * new Color(1f, 1f, 1f, alphaMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AdjustMidpointHandleColor(bool isBackfaced)
|
||||||
|
{
|
||||||
|
Handles.color = GetStateColor(isBackfaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly Vector3[] s_FacePoints = new Vector3[8];
|
||||||
|
static readonly Vector3[] s_LinePoints = new Vector3[2];
|
||||||
|
|
||||||
|
static readonly int[] k_NextAxis = { 1, 2, 0 };
|
||||||
|
|
||||||
|
public static void DrawFace(float3 center, float3 size, float cornerRadius, int normalAxis, Axes axes, bool isCameraInsideBox)
|
||||||
|
{
|
||||||
|
// 0 = 0 1 2
|
||||||
|
// 1 = 1 2 0
|
||||||
|
// 2 = 2 0 1
|
||||||
|
|
||||||
|
int a = normalAxis;
|
||||||
|
int b = k_NextAxis[a];
|
||||||
|
int c = k_NextAxis[b];
|
||||||
|
|
||||||
|
cornerRadius = math.abs(cornerRadius);
|
||||||
|
size *= 0.5f;
|
||||||
|
var normal = new float3 { [a] = size[a] };
|
||||||
|
var ctr = center + normal;
|
||||||
|
size -= new float3(cornerRadius);
|
||||||
|
|
||||||
|
// check if our face is a point
|
||||||
|
if (math.abs(size[c]) < kDistanceEpsilon &&
|
||||||
|
math.abs(size[b]) < kDistanceEpsilon)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector3[] points;
|
||||||
|
// check if our face is a line or not
|
||||||
|
if (math.abs(size[c]) >= kDistanceEpsilon &&
|
||||||
|
math.abs(size[b]) >= kDistanceEpsilon)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
|
||||||
|
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = size[c] };
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
|
||||||
|
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = -size[b], [c] = -size[c] };
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
|
||||||
|
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
|
||||||
|
s_FacePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
|
||||||
|
points = s_FacePoints;
|
||||||
|
}
|
||||||
|
else if (math.abs(size[c]) >= kDistanceEpsilon)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = size[c] };
|
||||||
|
s_LinePoints[i++] = ctr + new float3 { [b] = size[b], [c] = -size[c] };
|
||||||
|
points = s_LinePoints;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = size[b] };
|
||||||
|
s_LinePoints[i++] = ctr + new float3 { [c] = size[c], [b] = -size[b] };
|
||||||
|
points = s_LinePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 tangent, biNormal;
|
||||||
|
if (size[c] > 0)
|
||||||
|
{
|
||||||
|
tangent = math.cross(normal, math.normalizesafe(new float3 { [c] = size[c] }));
|
||||||
|
biNormal = math.cross(normal, tangent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tangent = math.cross(normal, math.normalizesafe(new float3 { [b] = size[b] }));
|
||||||
|
biNormal = math.cross(normal, tangent);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new Handles.DrawingScope(Handles.color))
|
||||||
|
{
|
||||||
|
AdjustMidpointHandleColor(IsBackfaced(ctr, tangent, biNormal, axes, isCameraInsideBox));
|
||||||
|
Handles.DrawLines(points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Corner
|
||||||
|
{
|
||||||
|
public float3 angle;
|
||||||
|
public float3x2 intersections;
|
||||||
|
public float3x2 points;
|
||||||
|
public float3x3 axes;
|
||||||
|
public float3x3 normals;
|
||||||
|
public bool3x2 splitAxis;
|
||||||
|
public int splitCount;
|
||||||
|
|
||||||
|
public float3 position;
|
||||||
|
public float radius;
|
||||||
|
public bool isBackFaced;
|
||||||
|
|
||||||
|
public float3 cameraForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CalculateCornerHorizon(float3 cornerPosition, quaternion orientation, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner corner)
|
||||||
|
{
|
||||||
|
var axisx = new float3(1f, 0f, 0f);
|
||||||
|
var axisy = new float3(0f, 1f, 0f);
|
||||||
|
var axisz = new float3(0f, 0f, 1f);
|
||||||
|
|
||||||
|
// a vector pointing away from the center of the corner
|
||||||
|
var cornerNormal = math.normalize(math.mul(orientation, new float3(1f, 1f, 1f)));
|
||||||
|
|
||||||
|
var axes = math.mul(new float3x3(orientation), new float3x3(axisx, axisy, axisz));
|
||||||
|
CalculateCornerHorizon(cornerPosition,
|
||||||
|
axes,
|
||||||
|
cornerNormal,
|
||||||
|
cameraCenter, cameraForward, cameraOrtho,
|
||||||
|
radius, out corner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CalculateCornerHorizon(float3 cornerPosition, float3x3 axes, float3 cornerNormal, float3 cameraCenter, float3 cameraForward, bool cameraOrtho, float radius, out Corner corner)
|
||||||
|
{
|
||||||
|
var cameraToCenter = cornerPosition - cameraCenter; // vector from camera to center
|
||||||
|
var sqrRadius = radius * radius;
|
||||||
|
var sqrDistCameraToCenter = math.lengthsq(cameraToCenter);
|
||||||
|
var sqrOffset = (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
|
||||||
|
|
||||||
|
if (!cameraOrtho)
|
||||||
|
cameraForward = cameraToCenter;
|
||||||
|
|
||||||
|
var normals = new float3x3
|
||||||
|
{
|
||||||
|
c0 = math.normalize(math.cross(axes[1], axes[2])),
|
||||||
|
c1 = math.normalize(math.cross(axes[2], axes[0])),
|
||||||
|
c2 = math.normalize(math.cross(axes[0], axes[1]))
|
||||||
|
};
|
||||||
|
|
||||||
|
corner = new Corner
|
||||||
|
{
|
||||||
|
angle = new float3(
|
||||||
|
Vector3.Angle(axes[0], axes[1]),
|
||||||
|
Vector3.Angle(axes[1], axes[2]),
|
||||||
|
Vector3.Angle(axes[2], axes[0])
|
||||||
|
),
|
||||||
|
intersections = default,
|
||||||
|
points = default,
|
||||||
|
splitAxis = default,
|
||||||
|
|
||||||
|
axes = axes,
|
||||||
|
normals = normals,
|
||||||
|
|
||||||
|
position = cornerPosition,
|
||||||
|
radius = radius,
|
||||||
|
cameraForward = cameraForward,
|
||||||
|
isBackFaced = math.dot(cornerNormal, cameraForward) > 0,
|
||||||
|
splitCount = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (math.abs(sqrDistCameraToCenter) <= sqrRadius)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int n = 0, sign = -1; n < 2; n++, sign += 2)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var axis1 = normals[i] * sign;
|
||||||
|
var axis2 = axes[(i + 1) % 3] * sign;
|
||||||
|
var axis3 = axes[(i + 2) % 3] * sign;
|
||||||
|
|
||||||
|
var Q = Vector3.Angle(cameraForward, axis1);
|
||||||
|
var f = math.tan(math.radians(90 - math.min(Q, 180 - Q)));
|
||||||
|
var g = sqrOffset + f * f * sqrOffset;
|
||||||
|
if (g >= sqrRadius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var e = math.degrees(math.asin(math.sqrt(g) / radius));
|
||||||
|
var vectorToPointOnHorizon = Quaternion.AngleAxis(e, axis1) * math.normalize(math.cross(axis1, cameraForward));
|
||||||
|
|
||||||
|
vectorToPointOnHorizon = math.normalize(Vector3.ProjectOnPlane(vectorToPointOnHorizon, axis1));
|
||||||
|
|
||||||
|
var intersectionDirection = vectorToPointOnHorizon;
|
||||||
|
var angle1 = Vector3.SignedAngle(axis2, intersectionDirection, axis1);
|
||||||
|
var angle2 = Vector3.SignedAngle(axis3, intersectionDirection, axis1);
|
||||||
|
|
||||||
|
if (angle1 <= 0 || angle2 >= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var point = corner.position + (float3)(intersectionDirection * radius);
|
||||||
|
|
||||||
|
if (corner.splitCount < 2)
|
||||||
|
{
|
||||||
|
corner.splitAxis[corner.splitCount][i] = true;
|
||||||
|
corner.intersections[corner.splitCount] = intersectionDirection;
|
||||||
|
corner.points[corner.splitCount] = point;
|
||||||
|
|
||||||
|
corner.splitCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!math.any(corner.splitAxis[0]) &&
|
||||||
|
!math.any(corner.splitAxis[1]))
|
||||||
|
{
|
||||||
|
corner.splitCount = 0;
|
||||||
|
corner.splitAxis[0] = false;
|
||||||
|
corner.splitAxis[1] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawCorner(Corner corner, bool3 showAxis)
|
||||||
|
{
|
||||||
|
var color = Handles.color;
|
||||||
|
var axes = corner.axes;
|
||||||
|
var intersections = corner.intersections;
|
||||||
|
var normals = corner.normals;
|
||||||
|
var origin = corner.position;
|
||||||
|
var radius = corner.radius;
|
||||||
|
|
||||||
|
if (corner.splitCount <= 1)
|
||||||
|
{
|
||||||
|
AdjustMidpointHandleColor(corner.isBackFaced);
|
||||||
|
if (showAxis[0]) Handles.DrawWireArc(origin, normals[0], axes[1], corner.angle[1], radius);
|
||||||
|
if (showAxis[1]) Handles.DrawWireArc(origin, normals[1], axes[2], corner.angle[2], radius);
|
||||||
|
if (showAxis[2]) Handles.DrawWireArc(origin, normals[2], axes[0], corner.angle[0], radius);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var angleLength = Vector3.SignedAngle(Vector3.ProjectOnPlane(intersections[0], corner.cameraForward),
|
||||||
|
Vector3.ProjectOnPlane(intersections[1], corner.cameraForward), corner.cameraForward);
|
||||||
|
bool reversePolarity = angleLength < 0;
|
||||||
|
if (reversePolarity)
|
||||||
|
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[1] - origin, -angleLength, radius);
|
||||||
|
else
|
||||||
|
Handles.DrawWireArc(origin, corner.cameraForward, corner.points[0] - origin, angleLength, radius);
|
||||||
|
|
||||||
|
|
||||||
|
var backfacedColor = GetStateColor(true);
|
||||||
|
|
||||||
|
var axesBackfaced = new bool3(math.length(intersections[0] - axes[0]) < kDistanceEpsilon || math.length(intersections[1] - axes[0]) < kDistanceEpsilon,
|
||||||
|
math.length(intersections[0] - axes[1]) < kDistanceEpsilon || math.length(intersections[1] - axes[1]) < kDistanceEpsilon,
|
||||||
|
math.length(intersections[0] - axes[2]) < kDistanceEpsilon || math.length(intersections[1] - axes[2]) < kDistanceEpsilon);
|
||||||
|
|
||||||
|
var color1 = reversePolarity ? color : backfacedColor;
|
||||||
|
var color2 = reversePolarity ? backfacedColor : color;
|
||||||
|
|
||||||
|
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
|
||||||
|
{
|
||||||
|
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
|
||||||
|
{
|
||||||
|
if (!axesBackfaced[A]) { angleLength = Vector3.Angle(intersections[0], axes[A]); axesBackfaced[A] = (angleLength<kDegreeEpsilon || angleLength> corner.angle[C] - kDegreeEpsilon); }
|
||||||
|
if (!axesBackfaced[B]) { angleLength = Vector3.Angle(intersections[1], axes[A]); axesBackfaced[B] = (angleLength<kDegreeEpsilon || angleLength> corner.angle[C] - kDegreeEpsilon); }
|
||||||
|
}
|
||||||
|
else if (corner.splitAxis[0][C])
|
||||||
|
{
|
||||||
|
if (showAxis[C])
|
||||||
|
{
|
||||||
|
angleLength = Vector3.Angle(intersections[0], axes[A]);
|
||||||
|
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[0], -angleLength, radius);
|
||||||
|
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[0], corner.angle[A] - angleLength, radius);
|
||||||
|
}
|
||||||
|
axesBackfaced[A] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
//if (corner.splitAxis[1][C])
|
||||||
|
{
|
||||||
|
if (showAxis[C])
|
||||||
|
{
|
||||||
|
angleLength = Vector3.Angle(intersections[1], axes[A]);
|
||||||
|
Handles.color = color2; Handles.DrawWireArc(origin, normals[C], intersections[1], -angleLength, radius);
|
||||||
|
Handles.color = color1; Handles.DrawWireArc(origin, normals[C], intersections[1], corner.angle[A] - angleLength, radius);
|
||||||
|
}
|
||||||
|
axesBackfaced[B] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for singularity
|
||||||
|
if (math.all(axesBackfaced))
|
||||||
|
axesBackfaced = corner.isBackFaced;
|
||||||
|
|
||||||
|
for (int A = 1, B = 2, C = 0; C < 3; A = B, B = C, C++)
|
||||||
|
{
|
||||||
|
if (!showAxis[C])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (corner.splitAxis[0][C] == corner.splitAxis[1][C])
|
||||||
|
{
|
||||||
|
Handles.color = (axesBackfaced[B] && axesBackfaced[A]) ? color1 : color2;
|
||||||
|
Handles.DrawWireArc(origin, normals[C], axes[A], corner.angle[A], radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Handles.color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3c60d0e0940c3c419ec030b9685c622
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
class PhysicsCapsuleBoundsHandle : CapsuleBoundsHandle
|
||||||
|
{
|
||||||
|
static PhysicsBoundsHandleUtility.Corner[] s_Corners = new PhysicsBoundsHandleUtility.Corner[8];
|
||||||
|
|
||||||
|
protected override void DrawWireframe()
|
||||||
|
{
|
||||||
|
if (this.radius <= 0f)
|
||||||
|
{
|
||||||
|
base.DrawWireframe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cameraPos = default(float3);
|
||||||
|
var cameraFwd = new float3 { z = 1f };
|
||||||
|
var cameraOrtho = true;
|
||||||
|
if (Camera.current != null)
|
||||||
|
{
|
||||||
|
cameraPos = Camera.current.transform.position;
|
||||||
|
cameraFwd = Camera.current.transform.forward;
|
||||||
|
cameraOrtho = Camera.current.orthographic;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = new float3(this.radius * 2f, this.radius * 2f, height);
|
||||||
|
var radius = this.radius;
|
||||||
|
var origin = (float3)this.center;
|
||||||
|
var bounds = new Bounds(this.center, size);
|
||||||
|
|
||||||
|
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
|
||||||
|
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
|
||||||
|
var invMatrix = Handles.inverseMatrix;
|
||||||
|
var cameraCenter = (float3)invMatrix.MultiplyPoint(cameraPos);
|
||||||
|
var cameraForward = (float3)invMatrix.MultiplyVector(cameraFwd);
|
||||||
|
|
||||||
|
bool isCameraInsideBox = Camera.current != null
|
||||||
|
&& bounds.Contains(invMatrix.MultiplyPoint(cameraPos));
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(-1f, 1f, 1f), radius, 0, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 1, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, -1f, 1f), radius, 1, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, 1f), radius, 2, axes, isCameraInsideBox);
|
||||||
|
PhysicsBoundsHandleUtility.DrawFace(origin, size * new float3(1f, 1f, -1f), radius, 2, axes, isCameraInsideBox);
|
||||||
|
|
||||||
|
var corner = 0.5f * size - new float3(1f) * radius;
|
||||||
|
var axisx = new float3(1f, 0f, 0f);
|
||||||
|
var axisy = new float3(0f, 1f, 0f);
|
||||||
|
var axisz = new float3(0f, 0f, 1f);
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, -1f), quaternion.LookRotation(-axisz, axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[0]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, 1f, 1f), quaternion.LookRotation(-axisx, axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[1]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, 1f), quaternion.LookRotation(axisz, axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[2]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, 1f, -1f), quaternion.LookRotation(axisx, axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[3]);
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, -1f), quaternion.LookRotation(-axisx, -axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[4]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(-1f, -1f, 1f), quaternion.LookRotation(axisz, -axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[5]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, 1f), quaternion.LookRotation(axisx, -axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[6]);
|
||||||
|
PhysicsBoundsHandleUtility.CalculateCornerHorizon(origin + corner * new float3(1f, -1f, -1f), quaternion.LookRotation(-axisz, -axisy), cameraCenter, cameraForward, cameraOrtho, radius, out s_Corners[7]);
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[0], new bool3(false, true, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[3], new bool3(true, false, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[4], new bool3(true, false, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[7], new bool3(false, true, true));
|
||||||
|
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[1], new bool3(true, false, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[2], new bool3(false, true, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[5], new bool3(false, true, true));
|
||||||
|
PhysicsBoundsHandleUtility.DrawCorner(s_Corners[6], new bool3(true, false, true));
|
||||||
|
|
||||||
|
// Draw the horizon edges between the corners
|
||||||
|
for (int upA = 3, upB = 0; upB < 4; upA = upB, upB++)
|
||||||
|
{
|
||||||
|
int dnA = upA + 4;
|
||||||
|
int dnB = upB + 4;
|
||||||
|
|
||||||
|
if (s_Corners[upA].splitAxis[0].z && s_Corners[upB].splitAxis[1].x) Handles.DrawLine(s_Corners[upA].points[0], s_Corners[upB].points[1]);
|
||||||
|
if (s_Corners[upA].splitAxis[1].z && s_Corners[upB].splitAxis[0].x) Handles.DrawLine(s_Corners[upA].points[1], s_Corners[upB].points[0]);
|
||||||
|
|
||||||
|
if (s_Corners[dnA].splitAxis[0].x && s_Corners[dnB].splitAxis[1].z) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[dnB].points[1]);
|
||||||
|
if (s_Corners[dnA].splitAxis[1].x && s_Corners[dnB].splitAxis[0].z) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[dnB].points[0]);
|
||||||
|
|
||||||
|
if (s_Corners[dnA].splitAxis[0].y && s_Corners[upA].splitAxis[1].y) Handles.DrawLine(s_Corners[dnA].points[0], s_Corners[upA].points[1]);
|
||||||
|
if (s_Corners[dnA].splitAxis[1].y && s_Corners[upA].splitAxis[0].y) Handles.DrawLine(s_Corners[dnA].points[1], s_Corners[upA].points[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c1af170fae6badc40bb2ed5376031c6f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
class PhysicsSphereBoundsHandle : SphereBoundsHandle
|
||||||
|
{
|
||||||
|
protected override void DrawWireframe()
|
||||||
|
{
|
||||||
|
bool x = IsAxisEnabled(Axes.X);
|
||||||
|
bool y = IsAxisEnabled(Axes.Y);
|
||||||
|
bool z = IsAxisEnabled(Axes.Z);
|
||||||
|
|
||||||
|
if (x && !y && !z)
|
||||||
|
Handles.DrawLine(Vector3.right * radius, Vector3.left * radius);
|
||||||
|
if (!x && y && !z)
|
||||||
|
Handles.DrawLine(Vector3.up * radius, Vector3.down * radius);
|
||||||
|
if (!x && !y && z)
|
||||||
|
Handles.DrawLine(Vector3.forward * radius, Vector3.back * radius);
|
||||||
|
|
||||||
|
const float kEpsilon = 0.000001F;
|
||||||
|
|
||||||
|
if (radius > 0)
|
||||||
|
{
|
||||||
|
var frontfacedColor = Handles.color;
|
||||||
|
var backfacedColor = Handles.color * new Color(1f, 1f, 1f, PhysicsBoundsHandleUtility.kBackfaceAlphaMultiplier);
|
||||||
|
var discVisible = new bool[]
|
||||||
|
{
|
||||||
|
y && z,
|
||||||
|
x && z,
|
||||||
|
x && y
|
||||||
|
};
|
||||||
|
var discOrientations = new float3[]
|
||||||
|
{
|
||||||
|
Vector3.right,
|
||||||
|
Vector3.up,
|
||||||
|
Vector3.forward
|
||||||
|
};
|
||||||
|
|
||||||
|
// Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position
|
||||||
|
// by the inverse matrix so that the two-shaded wireframe will have the proper orientation.
|
||||||
|
var invMatrix = Handles.inverseMatrix;
|
||||||
|
|
||||||
|
var cameraCenter = Camera.current == null ? Vector3.zero : Camera.current.transform.position;
|
||||||
|
var cameraToCenter = center - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center
|
||||||
|
var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude;
|
||||||
|
var sqrRadius = radius * radius; // squared radius
|
||||||
|
var isCameraOrthographic = Camera.current == null || Camera.current.orthographic;
|
||||||
|
var sqrOffset = isCameraOrthographic ? 0 : (sqrRadius * sqrRadius / sqrDistCameraToCenter); // squared distance from actual center to drawn disc center
|
||||||
|
var insideAmount = sqrOffset / sqrRadius;
|
||||||
|
if (insideAmount < 1)
|
||||||
|
{
|
||||||
|
if (math.abs(sqrDistCameraToCenter) >= kEpsilon)
|
||||||
|
{
|
||||||
|
using (new Handles.DrawingScope(frontfacedColor))
|
||||||
|
{
|
||||||
|
if (isCameraOrthographic)
|
||||||
|
{
|
||||||
|
var horizonRadius = radius;
|
||||||
|
var horizonCenter = center;
|
||||||
|
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var horizonRadius = math.sqrt(sqrRadius - sqrOffset);
|
||||||
|
var horizonCenter = center - sqrRadius * cameraToCenter / sqrDistCameraToCenter;
|
||||||
|
Handles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var planeNormal = cameraToCenter.normalized;
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
if (!discVisible[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var discOrientation = discOrientations[i];
|
||||||
|
|
||||||
|
var angleBetweenDiscAndNormal = math.acos(math.dot(discOrientation, planeNormal));
|
||||||
|
angleBetweenDiscAndNormal = (math.PI * 0.5f) - math.min(angleBetweenDiscAndNormal, math.PI - angleBetweenDiscAndNormal);
|
||||||
|
|
||||||
|
float f = math.tan(angleBetweenDiscAndNormal);
|
||||||
|
float g = math.sqrt(sqrOffset + f * f * sqrOffset) / radius;
|
||||||
|
if (g < 1)
|
||||||
|
{
|
||||||
|
var angleToHorizon = math.degrees(math.asin(g));
|
||||||
|
var discTangent = math.cross(discOrientation, planeNormal);
|
||||||
|
var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent;
|
||||||
|
var horizonArcLength = (90 - angleToHorizon) * 2.0f;
|
||||||
|
|
||||||
|
using (new Handles.DrawingScope(frontfacedColor))
|
||||||
|
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius);
|
||||||
|
using (new Handles.DrawingScope(backfacedColor))
|
||||||
|
Handles.DrawWireArc(center, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (new Handles.DrawingScope(backfacedColor))
|
||||||
|
Handles.DrawWireDisc(center, discOrientation, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (new Handles.DrawingScope(backfacedColor))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var discOrientation = discOrientations[i];
|
||||||
|
Handles.DrawWireDisc(center, discOrientation, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d4f82f07aa94f9241ad081812f8c3b1a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 30413a61e5b44d54dbd680b077804e45
|
||||||
|
timeCreated: 1678288991
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(BallAndSocketJoint))]
|
||||||
|
public class BallAndSocketEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
protected virtual void OnSceneGUI()
|
||||||
|
{
|
||||||
|
BallAndSocketJoint ballAndSocket = (BallAndSocketJoint)target;
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
EditorUtilities.EditPivot(ballAndSocket.worldFromA, ballAndSocket.worldFromB, ballAndSocket.AutoSetConnected,
|
||||||
|
ref ballAndSocket.PositionLocal, ref ballAndSocket.PositionInConnectedEntity, ballAndSocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8bfafdfc59379ec4c86d512554577279
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(CustomPhysicsMaterialTagNames))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
class CustomPhysicsMaterialTagNamesEditor : BaseEditor
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[AutoPopulate(ElementFormatString = "Custom Physics Material Tag {0}", Resizable = false, Reorderable = false)]
|
||||||
|
ReorderableList m_TagNames;
|
||||||
|
#pragma warning restore 649
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9a6f5e4709a915246864e50578af0cf3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using static Unity.Physics.Math;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides utilities that use Handles to set positions and axes,
|
||||||
|
/// </summary>
|
||||||
|
public class EditorUtilities
|
||||||
|
{
|
||||||
|
// Editor for a joint pivot or pivot pair
|
||||||
|
public static void EditPivot(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA,
|
||||||
|
ref float3 pivotA, ref float3 pivotB, Object target)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
float3 pivotAinW = Handles.PositionHandle(math.transform(worldFromA, pivotA), quaternion.identity);
|
||||||
|
float3 pivotBinW;
|
||||||
|
|
||||||
|
if (lockBtoA)
|
||||||
|
{
|
||||||
|
pivotBinW = pivotAinW;
|
||||||
|
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pivotBinW = Handles.PositionHandle(math.transform(worldFromB, pivotB), quaternion.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
Undo.RecordObject(target, "Edit joint pivot");
|
||||||
|
pivotA = math.transform(math.inverse(worldFromA), pivotAinW);
|
||||||
|
pivotB = math.transform(math.inverse(worldFromB), pivotBinW);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handles.DrawLine(worldFromA.pos, pivotAinW);
|
||||||
|
Handles.DrawLine(worldFromB.pos, pivotBinW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor for a joint axis or axis pair
|
||||||
|
public class AxisEditor
|
||||||
|
{
|
||||||
|
// Even though we're only editing axes and not rotations, we need to track a full rotation in order to keep the rotation handle stable
|
||||||
|
private quaternion m_RefA = quaternion.identity;
|
||||||
|
private quaternion m_RefB = quaternion.identity;
|
||||||
|
|
||||||
|
// Detect changes in the object being edited to reset the reference orientations
|
||||||
|
private Object m_LastTarget;
|
||||||
|
|
||||||
|
private static bool NormalizeSafe(ref float3 x)
|
||||||
|
{
|
||||||
|
float lengthSq = math.lengthsq(x);
|
||||||
|
const float epsSq = 1e-8f;
|
||||||
|
if (math.abs(lengthSq - 1) > epsSq)
|
||||||
|
{
|
||||||
|
if (lengthSq > epsSq)
|
||||||
|
{
|
||||||
|
x *= math.rsqrt(lengthSq);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = new float3(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool NormalizePerpendicular(float3 axis, ref float3 perpendicular)
|
||||||
|
{
|
||||||
|
// make sure perpendicular is actually perpendicular to direction
|
||||||
|
float dot = math.dot(axis, perpendicular);
|
||||||
|
float absDot = math.abs(dot);
|
||||||
|
if (absDot > 1.0f - 1e-5f)
|
||||||
|
{
|
||||||
|
// parallel, choose an arbitrary perpendicular
|
||||||
|
float3 dummy;
|
||||||
|
CalculatePerpendicularNormalized(axis, out perpendicular, out dummy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absDot > 1e-5f)
|
||||||
|
{
|
||||||
|
// reject direction
|
||||||
|
perpendicular -= dot * axis;
|
||||||
|
NormalizeSafe(ref perpendicular);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NormalizeSafe(ref perpendicular);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(RigidTransform worldFromA, RigidTransform worldFromB, bool lockBtoA, float3 pivotA, float3 pivotB,
|
||||||
|
ref float3 directionA, ref float3 directionB, ref float3 perpendicularA, ref float3 perpendicularB, Object target)
|
||||||
|
{
|
||||||
|
// Work in world space
|
||||||
|
float3 directionAinW = math.rotate(worldFromA, directionA);
|
||||||
|
float3 directionBinW = math.rotate(worldFromB, directionB);
|
||||||
|
float3 perpendicularAinW = math.rotate(worldFromB, perpendicularA);
|
||||||
|
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
// If the target changed, fix up the inputs and reset the reference orientations to align with the new target's axes
|
||||||
|
if (target != m_LastTarget)
|
||||||
|
{
|
||||||
|
m_LastTarget = target;
|
||||||
|
|
||||||
|
// Enforce normalized directions
|
||||||
|
changed |= NormalizeSafe(ref directionAinW);
|
||||||
|
changed |= NormalizeSafe(ref directionBinW);
|
||||||
|
|
||||||
|
// Enforce normalized perpendiculars, orthogonal to their respective directions
|
||||||
|
changed |= NormalizePerpendicular(directionAinW, ref perpendicularAinW);
|
||||||
|
changed |= NormalizePerpendicular(directionBinW, ref perpendicularBinW);
|
||||||
|
|
||||||
|
// Calculate the rotation of the joint in A from direction and perpendicular
|
||||||
|
float3x3 rotationA = new float3x3(directionAinW, perpendicularAinW, math.cross(directionAinW, perpendicularAinW));
|
||||||
|
m_RefA = new quaternion(rotationA);
|
||||||
|
|
||||||
|
if (lockBtoA)
|
||||||
|
{
|
||||||
|
m_RefB = m_RefA;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate the rotation of the joint in B from direction and perpendicular
|
||||||
|
float3x3 rotationB = new float3x3(directionBinW, perpendicularBinW, math.cross(directionBinW, perpendicularBinW));
|
||||||
|
m_RefB = new quaternion(rotationB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
// Make rotators
|
||||||
|
quaternion oldRefA = m_RefA;
|
||||||
|
quaternion oldRefB = m_RefB;
|
||||||
|
|
||||||
|
float3 pivotAinW = math.transform(worldFromA, pivotA);
|
||||||
|
m_RefA = Handles.RotationHandle(m_RefA, pivotAinW);
|
||||||
|
|
||||||
|
float3 pivotBinW;
|
||||||
|
if (lockBtoA)
|
||||||
|
{
|
||||||
|
directionB = math.rotate(math.inverse(worldFromB), directionAinW);
|
||||||
|
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularAinW);
|
||||||
|
pivotBinW = pivotAinW;
|
||||||
|
m_RefB = m_RefA;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pivotBinW = math.transform(worldFromB, pivotB);
|
||||||
|
m_RefB = Handles.RotationHandle(m_RefB, pivotBinW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply changes from the rotators
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
quaternion dqA = math.mul(m_RefA, math.inverse(oldRefA));
|
||||||
|
quaternion dqB = math.mul(m_RefB, math.inverse(oldRefB));
|
||||||
|
directionAinW = math.mul(dqA, directionAinW);
|
||||||
|
directionBinW = math.mul(dqB, directionBinW);
|
||||||
|
perpendicularAinW = math.mul(dqB, perpendicularAinW);
|
||||||
|
perpendicularBinW = math.mul(dqB, perpendicularBinW);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back if the axes changed
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
Undo.RecordObject(target, "Edit joint axis");
|
||||||
|
directionA = math.rotate(math.inverse(worldFromA), directionAinW);
|
||||||
|
directionB = math.rotate(math.inverse(worldFromB), directionBinW);
|
||||||
|
perpendicularA = math.rotate(math.inverse(worldFromB), perpendicularAinW);
|
||||||
|
perpendicularB = math.rotate(math.inverse(worldFromB), perpendicularBinW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the updated axes
|
||||||
|
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
|
||||||
|
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, directionAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
|
||||||
|
Handles.ArrowHandleCap(0, pivotAinW, Quaternion.FromToRotation(z, perpendicularAinW), HandleUtility.GetHandleSize(pivotAinW) * 0.75f, Event.current.type);
|
||||||
|
if (!lockBtoA)
|
||||||
|
{
|
||||||
|
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, directionBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
|
||||||
|
Handles.ArrowHandleCap(0, pivotBinW, Quaternion.FromToRotation(z, perpendicularBinW), HandleUtility.GetHandleSize(pivotBinW) * 0.75f, Event.current.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EditLimits(RigidTransform worldFromA, RigidTransform worldFromB, float3 pivotA, float3 axisA, float3 axisB, float3 perpendicularA, float3 perpendicularB,
|
||||||
|
ref float minLimit, ref float maxLimit, JointAngularLimitHandle limitHandle, Object target)
|
||||||
|
{
|
||||||
|
// Transform to world space
|
||||||
|
float3 pivotAinW = math.transform(worldFromA, pivotA);
|
||||||
|
float3 axisAinW = math.rotate(worldFromA, axisA);
|
||||||
|
float3 perpendicularAinW = math.rotate(worldFromA, perpendicularA);
|
||||||
|
float3 axisBinW = math.rotate(worldFromA, axisB);
|
||||||
|
float3 perpendicularBinW = math.rotate(worldFromB, perpendicularB);
|
||||||
|
|
||||||
|
// Get rotations from joint space
|
||||||
|
// JointAngularLimitHandle uses axis = (1, 0, 0) with angle = 0 at (0, 0, 1), so choose the rotations to point those in the directions of our axis and perpendicular
|
||||||
|
float3x3 worldFromJointA = new float3x3(axisAinW, -math.cross(axisAinW, perpendicularAinW), perpendicularAinW);
|
||||||
|
float3x3 worldFromJointB = new float3x3(axisBinW, -math.cross(axisBinW, perpendicularBinW), perpendicularBinW);
|
||||||
|
float3x3 jointBFromA = math.mul(math.transpose(worldFromJointB), worldFromJointA);
|
||||||
|
|
||||||
|
// Set orientation for the angular limit control
|
||||||
|
float angle = CalculateTwistAngle(new quaternion(jointBFromA), 0); // index = 0 because axis is the first column in worldFromJoint
|
||||||
|
quaternion limitOrientation = math.mul(quaternion.AxisAngle(axisAinW, angle), new quaternion(worldFromJointA));
|
||||||
|
Matrix4x4 handleMatrix = Matrix4x4.TRS(pivotAinW, limitOrientation, Vector3.one);
|
||||||
|
|
||||||
|
float size = HandleUtility.GetHandleSize(pivotAinW) * 0.75f;
|
||||||
|
|
||||||
|
limitHandle.xMin = -maxLimit;
|
||||||
|
limitHandle.xMax = -minLimit;
|
||||||
|
limitHandle.xMotion = ConfigurableJointMotion.Limited;
|
||||||
|
limitHandle.yMotion = ConfigurableJointMotion.Locked;
|
||||||
|
limitHandle.zMotion = ConfigurableJointMotion.Locked;
|
||||||
|
limitHandle.yHandleColor = new Color(0, 0, 0, 0);
|
||||||
|
limitHandle.zHandleColor = new Color(0, 0, 0, 0);
|
||||||
|
limitHandle.radius = size;
|
||||||
|
|
||||||
|
using (new Handles.DrawingScope(handleMatrix))
|
||||||
|
{
|
||||||
|
// Draw the reference axis
|
||||||
|
float3 z = new float3(0, 0, 1); // ArrowHandleCap() draws an arrow pointing in (0, 0, 1)
|
||||||
|
Handles.ArrowHandleCap(0, float3.zero, Quaternion.FromToRotation(z, new float3(1, 0, 0)), size, Event.current.type);
|
||||||
|
|
||||||
|
// Draw the limit editor handle
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
limitHandle.DrawHandle();
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
// Record the target object before setting new limits so changes can be undone/redone
|
||||||
|
Undo.RecordObject(target, "Edit joint angular limits");
|
||||||
|
minLimit = -limitHandle.xMax;
|
||||||
|
maxLimit = -limitHandle.xMin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5c7e2e7874c307546bb3ab423b21a11e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(LimitedHingeJoint))]
|
||||||
|
public class LimitedHingeEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
|
||||||
|
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label("Editors:");
|
||||||
|
limitedHinge.EditPivots = GUILayout.Toggle(limitedHinge.EditPivots, new GUIContent("Pivot"), "Button");
|
||||||
|
limitedHinge.EditAxes = GUILayout.Toggle(limitedHinge.EditAxes, new GUIContent("Axis"), "Button");
|
||||||
|
limitedHinge.EditLimits = GUILayout.Toggle(limitedHinge.EditLimits, new GUIContent("Limits"), "Button");
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
DrawDefaultInspector();
|
||||||
|
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnSceneGUI()
|
||||||
|
{
|
||||||
|
LimitedHingeJoint limitedHinge = (LimitedHingeJoint)target;
|
||||||
|
|
||||||
|
if (limitedHinge.EditPivots)
|
||||||
|
{
|
||||||
|
EditorUtilities.EditPivot(limitedHinge.worldFromA, limitedHinge.worldFromB, limitedHinge.AutoSetConnected,
|
||||||
|
ref limitedHinge.PositionLocal, ref limitedHinge.PositionInConnectedEntity, limitedHinge);
|
||||||
|
}
|
||||||
|
if (limitedHinge.EditAxes)
|
||||||
|
{
|
||||||
|
m_AxisEditor.Update(limitedHinge.worldFromA, limitedHinge.worldFromB,
|
||||||
|
limitedHinge.AutoSetConnected,
|
||||||
|
limitedHinge.PositionLocal, limitedHinge.PositionInConnectedEntity,
|
||||||
|
ref limitedHinge.HingeAxisLocal, ref limitedHinge.HingeAxisInConnectedEntity,
|
||||||
|
ref limitedHinge.PerpendicularAxisLocal, ref limitedHinge.PerpendicularAxisInConnectedEntity,
|
||||||
|
limitedHinge);
|
||||||
|
}
|
||||||
|
if (limitedHinge.EditLimits)
|
||||||
|
{
|
||||||
|
EditorUtilities.EditLimits(limitedHinge.worldFromA, limitedHinge.worldFromB,
|
||||||
|
limitedHinge.PositionLocal,
|
||||||
|
limitedHinge.HingeAxisLocal, limitedHinge.HingeAxisInConnectedEntity,
|
||||||
|
limitedHinge.PerpendicularAxisLocal, limitedHinge.PerpendicularAxisInConnectedEntity,
|
||||||
|
ref limitedHinge.MinAngle, ref limitedHinge.MaxAngle, m_LimitHandle, limitedHinge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f79ce4d7693ece94fb5927586cd79ad9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(PhysicsBodyAuthoring))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
class PhysicsBodyAuthoringEditor : BaseEditor
|
||||||
|
{
|
||||||
|
static class Content
|
||||||
|
{
|
||||||
|
public static readonly GUIContent MassLabel = EditorGUIUtility.TrTextContent("Mass");
|
||||||
|
public static readonly GUIContent CenterOfMassLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Center of Mass", "Center of mass in the space of this body's transform."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent InertiaTensorLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Inertia Tensor", "Resistance to angular motion about each axis of rotation."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent OrientationLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Orientation", "Orientation of the body's inertia tensor in the space of its transform."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Advanced", "Advanced options"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[AutoPopulate] SerializedProperty m_MotionType;
|
||||||
|
[AutoPopulate] SerializedProperty m_Smoothing;
|
||||||
|
[AutoPopulate] SerializedProperty m_Mass;
|
||||||
|
[AutoPopulate] SerializedProperty m_GravityFactor;
|
||||||
|
[AutoPopulate] SerializedProperty m_LinearDamping;
|
||||||
|
[AutoPopulate] SerializedProperty m_AngularDamping;
|
||||||
|
[AutoPopulate] SerializedProperty m_InitialLinearVelocity;
|
||||||
|
[AutoPopulate] SerializedProperty m_InitialAngularVelocity;
|
||||||
|
[AutoPopulate] SerializedProperty m_OverrideDefaultMassDistribution;
|
||||||
|
[AutoPopulate] SerializedProperty m_CenterOfMass;
|
||||||
|
[AutoPopulate] SerializedProperty m_Orientation;
|
||||||
|
[AutoPopulate] SerializedProperty m_InertiaTensor;
|
||||||
|
[AutoPopulate] SerializedProperty m_WorldIndex;
|
||||||
|
[AutoPopulate] SerializedProperty m_CustomTags;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
bool showAdvanced;
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_MotionType);
|
||||||
|
|
||||||
|
if (m_MotionType.intValue != (int)BodyMotionType.Static)
|
||||||
|
EditorGUILayout.PropertyField(m_Smoothing);
|
||||||
|
|
||||||
|
var dynamic = m_MotionType.intValue == (int)BodyMotionType.Dynamic;
|
||||||
|
|
||||||
|
if (dynamic)
|
||||||
|
EditorGUILayout.PropertyField(m_Mass, Content.MassLabel);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
|
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||||
|
EditorGUI.BeginProperty(position, Content.MassLabel, m_Mass);
|
||||||
|
EditorGUI.FloatField(position, Content.MassLabel, float.PositiveInfinity);
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_LinearDamping, true);
|
||||||
|
EditorGUILayout.PropertyField(m_AngularDamping, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_MotionType.intValue != (int)BodyMotionType.Static)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_InitialLinearVelocity, true);
|
||||||
|
EditorGUILayout.PropertyField(m_InitialAngularVelocity, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_MotionType.intValue == (int)BodyMotionType.Dynamic)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_GravityFactor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAdvanced = EditorGUILayout.Foldout(showAdvanced, Content.AdvancedLabel);
|
||||||
|
if (showAdvanced)
|
||||||
|
{
|
||||||
|
++EditorGUI.indentLevel;
|
||||||
|
EditorGUILayout.PropertyField(m_WorldIndex);
|
||||||
|
if (m_MotionType.intValue != (int)BodyMotionType.Static)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_OverrideDefaultMassDistribution);
|
||||||
|
if (m_OverrideDefaultMassDistribution.boolValue)
|
||||||
|
{
|
||||||
|
++EditorGUI.indentLevel;
|
||||||
|
EditorGUILayout.PropertyField(m_CenterOfMass, Content.CenterOfMassLabel);
|
||||||
|
|
||||||
|
EditorGUI.BeginDisabledGroup(!dynamic);
|
||||||
|
if (dynamic)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_Orientation, Content.OrientationLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_InertiaTensor, Content.InertiaTensorLabel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
|
var position =
|
||||||
|
EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(m_InertiaTensor));
|
||||||
|
EditorGUI.BeginProperty(position, Content.InertiaTensorLabel, m_InertiaTensor);
|
||||||
|
EditorGUI.Vector3Field(position, Content.InertiaTensorLabel,
|
||||||
|
Vector3.one * float.PositiveInfinity);
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
|
--EditorGUI.indentLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUILayout.PropertyField(m_CustomTags);
|
||||||
|
--EditorGUI.indentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
DisplayStatusMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageType m_Status;
|
||||||
|
List<string> m_StatusMessages = new List<string>(8);
|
||||||
|
|
||||||
|
void DisplayStatusMessages()
|
||||||
|
{
|
||||||
|
m_Status = MessageType.None;
|
||||||
|
m_StatusMessages.Clear();
|
||||||
|
|
||||||
|
var hierarchyStatus = StatusMessageUtility.GetHierarchyStatusMessage(targets, out var hierarchyStatusMessage);
|
||||||
|
if (!string.IsNullOrEmpty(hierarchyStatusMessage))
|
||||||
|
{
|
||||||
|
m_StatusMessages.Add(hierarchyStatusMessage);
|
||||||
|
m_Status = (MessageType)math.max((int)m_Status, (int)hierarchyStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_StatusMessages.Count > 0)
|
||||||
|
EditorGUILayout.HelpBox(string.Join("\n\n", m_StatusMessages), m_Status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a63bbe7a0e8f0934fb7896991b8408ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(PhysicsCategoryNames))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
class PhysicsCategoryNamesEditor : BaseEditor
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[AutoPopulate(ElementFormatString = "Category {0}", Resizable = false, Reorderable = false)]
|
||||||
|
ReorderableList m_CategoryNames;
|
||||||
|
#pragma warning restore 649
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fcbb532fa3ab5ca4cb5005410a82cc83
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 209b4a4e54f038844baf182ae186f813
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(RagdollJoint))]
|
||||||
|
public class RagdollJointEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private EditorUtilities.AxisEditor m_AxisEditor = new EditorUtilities.AxisEditor();
|
||||||
|
private JointAngularLimitHandle m_LimitHandle = new JointAngularLimitHandle();
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
RagdollJoint ragdoll = (RagdollJoint)target;
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
GUILayout.BeginVertical();
|
||||||
|
GUILayout.Space(10.0f);
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label("Editors:");
|
||||||
|
ragdoll.EditPivots = GUILayout.Toggle(ragdoll.EditPivots, new GUIContent("Pivot"), "Button");
|
||||||
|
ragdoll.EditAxes = GUILayout.Toggle(ragdoll.EditAxes, new GUIContent("Axis"), "Button");
|
||||||
|
ragdoll.EditLimits = GUILayout.Toggle(ragdoll.EditLimits, new GUIContent("Limits"), "Button");
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.Space(10.0f);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
|
||||||
|
DrawDefaultInspector();
|
||||||
|
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawCone(float3 point, float3 axis, float angle, Color color)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
Handles.color = color;
|
||||||
|
|
||||||
|
float3 dir;
|
||||||
|
float scale = Math.NormalizeWithLength(axis, out dir);
|
||||||
|
|
||||||
|
float3 arm;
|
||||||
|
{
|
||||||
|
float3 perp1, perp2;
|
||||||
|
Math.CalculatePerpendicularNormalized(dir, out perp1, out perp2);
|
||||||
|
arm = math.mul(quaternion.AxisAngle(perp1, angle), dir) * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int res = 16;
|
||||||
|
quaternion q = quaternion.AxisAngle(dir, 2.0f * (float)math.PI / res);
|
||||||
|
for (int i = 0; i < res; i++)
|
||||||
|
{
|
||||||
|
float3 nextArm = math.mul(q, arm);
|
||||||
|
Handles.DrawLine(point, point + arm);
|
||||||
|
Handles.DrawLine(point + arm, point + nextArm);
|
||||||
|
arm = nextArm;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnSceneGUI()
|
||||||
|
{
|
||||||
|
RagdollJoint ragdoll = (RagdollJoint)target;
|
||||||
|
|
||||||
|
bool drawCones = false;
|
||||||
|
if (ragdoll.EditPivots)
|
||||||
|
{
|
||||||
|
EditorUtilities.EditPivot(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
|
||||||
|
ref ragdoll.PositionLocal, ref ragdoll.PositionInConnectedEntity, ragdoll);
|
||||||
|
}
|
||||||
|
if (ragdoll.EditAxes)
|
||||||
|
{
|
||||||
|
m_AxisEditor.Update(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.AutoSetConnected,
|
||||||
|
ragdoll.PositionLocal, ragdoll.PositionInConnectedEntity, ref ragdoll.TwistAxisLocal, ref ragdoll.TwistAxisInConnectedEntity,
|
||||||
|
ref ragdoll.PerpendicularAxisLocal, ref ragdoll.PerpendicularAxisInConnectedEntity, ragdoll);
|
||||||
|
drawCones = true;
|
||||||
|
}
|
||||||
|
if (ragdoll.EditLimits)
|
||||||
|
{
|
||||||
|
EditorUtilities.EditLimits(ragdoll.worldFromA, ragdoll.worldFromB, ragdoll.PositionLocal, ragdoll.TwistAxisLocal, ragdoll.TwistAxisInConnectedEntity,
|
||||||
|
ragdoll.PerpendicularAxisLocal, ragdoll.PerpendicularAxisInConnectedEntity, ref ragdoll.MinTwistAngle, ref ragdoll.MaxTwistAngle, m_LimitHandle, ragdoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawCones)
|
||||||
|
{
|
||||||
|
float3 pivotB = math.transform(ragdoll.worldFromB, ragdoll.PositionInConnectedEntity);
|
||||||
|
float3 axisB = math.rotate(ragdoll.worldFromB, ragdoll.TwistAxisInConnectedEntity);
|
||||||
|
DrawCone(pivotB, axisB, math.radians(ragdoll.MaxConeAngle), Color.yellow);
|
||||||
|
|
||||||
|
float3 perpendicularB = math.rotate(ragdoll.worldFromB, ragdoll.PerpendicularAxisInConnectedEntity);
|
||||||
|
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MinPerpendicularAngle + 90f), Color.red);
|
||||||
|
DrawCone(pivotB, perpendicularB, math.radians(ragdoll.MaxPerpendicularAngle + 90f), Color.red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c97c008c29536647add5a287db56ae2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23d0032aba561954aaf56c65acf751bd
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
abstract class BaseDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
protected abstract bool IsCompatible(SerializedProperty property);
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
return IsCompatible(property)
|
||||||
|
? EditorGUI.GetPropertyHeight(property)
|
||||||
|
: EditorGUIUtility.singleLineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
if (IsCompatible(property))
|
||||||
|
DoGUI(position, property, label);
|
||||||
|
else
|
||||||
|
EditorGUIControls.DisplayCompatibilityWarning(position, label, ObjectNames.NicifyVariableName(GetType().Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void DoGUI(Rect position, SerializedProperty property, GUIContent label);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4ce107efdd5d5f439469b8b109f601f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
|
||||||
|
class EnumFlagsDrawer : BaseDrawer
|
||||||
|
{
|
||||||
|
protected override bool IsCompatible(SerializedProperty property)
|
||||||
|
{
|
||||||
|
return property.propertyType == SerializedPropertyType.Enum;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
|
||||||
|
var value = property.longValue;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
value = Convert.ToInt64(
|
||||||
|
EditorGUI.EnumFlagsField(position, label, (Enum)Enum.ToObject(fieldInfo.FieldType, value))
|
||||||
|
);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
property.longValue = value;
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 91d983b20a4beed4794e82ce61acb452
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(EulerAngles))]
|
||||||
|
class EulerAnglesDrawer : BaseDrawer
|
||||||
|
{
|
||||||
|
protected override bool IsCompatible(SerializedProperty property) => true;
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
|
||||||
|
return EditorGUI.GetPropertyHeight(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var value = property.FindPropertyRelative(nameof(EulerAngles.Value));
|
||||||
|
EditorGUI.PropertyField(position, value, label, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aa19ca1ba7846e443a44ba0fff644d17
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(ExpandChildrenAttribute))]
|
||||||
|
class ExpandChildrenDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
property.isExpanded = true;
|
||||||
|
return EditorGUI.GetPropertyHeight(property)
|
||||||
|
- EditorGUIUtility.standardVerticalSpacing
|
||||||
|
- EditorGUIUtility.singleLineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var endProperty = property.GetEndProperty();
|
||||||
|
var childProperty = property.Copy();
|
||||||
|
childProperty.NextVisible(true);
|
||||||
|
while (!SerializedProperty.EqualContents(childProperty, endProperty))
|
||||||
|
{
|
||||||
|
position.height = EditorGUI.GetPropertyHeight(childProperty);
|
||||||
|
OnChildPropertyGUI(position, childProperty);
|
||||||
|
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
childProperty.NextVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnChildPropertyGUI(Rect position, SerializedProperty childProperty)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, childProperty, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0c02598cdac1be04a9039046cc3b8459
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(PhysicsMaterialCoefficient))]
|
||||||
|
class PhysicsMaterialCoefficientDrawer : BaseDrawer
|
||||||
|
{
|
||||||
|
static class Styles
|
||||||
|
{
|
||||||
|
public const float PopupWidth = 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) =>
|
||||||
|
EditorGUIUtility.singleLineHeight;
|
||||||
|
|
||||||
|
protected override bool IsCompatible(SerializedProperty property) => true;
|
||||||
|
|
||||||
|
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
EditorGUI.PropertyField(
|
||||||
|
new Rect(position) { xMax = position.xMax - Styles.PopupWidth },
|
||||||
|
property.FindPropertyRelative("Value"),
|
||||||
|
label
|
||||||
|
);
|
||||||
|
|
||||||
|
var indent = EditorGUI.indentLevel;
|
||||||
|
EditorGUI.indentLevel = 0;
|
||||||
|
EditorGUI.PropertyField(
|
||||||
|
new Rect(position) { xMin = position.xMax - Styles.PopupWidth + EditorGUIUtility.standardVerticalSpacing },
|
||||||
|
property.FindPropertyRelative("CombineMode"),
|
||||||
|
GUIContent.none
|
||||||
|
);
|
||||||
|
EditorGUI.indentLevel = indent;
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 637c07be1ce65064687a822ad33de1aa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(PhysicsMaterialProperties))]
|
||||||
|
class PhysicsMaterialPropertiesDrawer : BaseDrawer
|
||||||
|
{
|
||||||
|
static class Content
|
||||||
|
{
|
||||||
|
public static readonly GUIContent AdvancedGroupFoldout = EditorGUIUtility.TrTextContent("Advanced");
|
||||||
|
public static readonly GUIContent BelongsToLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Belongs To",
|
||||||
|
"Specifies the categories to which this object belongs."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent CollidesWithLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Collides With",
|
||||||
|
"Specifies the categories of objects with which this object will collide, " +
|
||||||
|
"or with which it will raise events if intersecting a trigger."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent CollisionFilterGroupFoldout =
|
||||||
|
EditorGUIUtility.TrTextContent("Collision Filter");
|
||||||
|
public static readonly GUIContent CustomFlagsLabel =
|
||||||
|
EditorGUIUtility.TrTextContent("Custom Tags", "Specify custom tags to read at run-time.");
|
||||||
|
public static readonly GUIContent FrictionLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Friction",
|
||||||
|
"Specifies how resistant the body is to motion when sliding along other surfaces, " +
|
||||||
|
"as well as what value should be used when colliding with an object that has a different value."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent RestitutionLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Restitution",
|
||||||
|
"Specifies how bouncy the object will be when colliding with other surfaces, " +
|
||||||
|
"as well as what value should be used when colliding with an object that has a different value."
|
||||||
|
);
|
||||||
|
public static readonly GUIContent CollisionResponseLabel = EditorGUIUtility.TrTextContent(
|
||||||
|
"Collision Response",
|
||||||
|
"Specifies whether the shape should collide normally, raise trigger events when intersecting other shapes, " +
|
||||||
|
"collide normally and raise notifications of collision events with other shapes, " +
|
||||||
|
"or completely ignore collisions (but still move and intercept queries)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string k_CollisionFilterGroupKey = "m_BelongsToCategories";
|
||||||
|
const string k_AdvancedGroupKey = "m_CustomMaterialTags";
|
||||||
|
|
||||||
|
Dictionary<string, SerializedObject> m_SerializedTemplates = new Dictionary<string, SerializedObject>();
|
||||||
|
|
||||||
|
SerializedProperty GetTemplateValueProperty(SerializedProperty property)
|
||||||
|
{
|
||||||
|
var key = property.propertyPath;
|
||||||
|
var template = property.FindPropertyRelative("m_Template").objectReferenceValue;
|
||||||
|
SerializedObject serializedTemplate;
|
||||||
|
if (
|
||||||
|
!m_SerializedTemplates.TryGetValue(key, out serializedTemplate)
|
||||||
|
|| serializedTemplate?.targetObject != template
|
||||||
|
)
|
||||||
|
m_SerializedTemplates[key] = serializedTemplate = template == null ? null : new SerializedObject(template);
|
||||||
|
serializedTemplate?.Update();
|
||||||
|
return serializedTemplate?.FindProperty("m_Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindToggleAndValueProperties(
|
||||||
|
SerializedProperty property, SerializedProperty templateValueProperty, string relativePath,
|
||||||
|
out SerializedProperty toggle, out SerializedProperty value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var relative = property.FindPropertyRelative(relativePath);
|
||||||
|
toggle = relative.FindPropertyRelative("m_Override");
|
||||||
|
value = toggle.boolValue || templateValueProperty == null
|
||||||
|
? relative.FindPropertyRelative("m_Value")
|
||||||
|
: templateValueProperty.FindPropertyRelative(relativePath).FindPropertyRelative("m_Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var templateValueProperty = GetTemplateValueProperty(property);
|
||||||
|
|
||||||
|
// m_CollisionResponse, collision filter foldout, advanced foldout
|
||||||
|
var height = 3f * EditorGUIUtility.singleLineHeight + 2f * EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
|
||||||
|
// m_BelongsTo, m_CollidesWith
|
||||||
|
var group = property.FindPropertyRelative(k_CollisionFilterGroupKey);
|
||||||
|
if (group.isExpanded)
|
||||||
|
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
|
||||||
|
|
||||||
|
// m_CustomTags
|
||||||
|
group = property.FindPropertyRelative(k_AdvancedGroupKey);
|
||||||
|
if (group.isExpanded)
|
||||||
|
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
|
||||||
|
|
||||||
|
// m_Template
|
||||||
|
if (property.FindPropertyRelative("m_SupportsTemplate").boolValue)
|
||||||
|
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
|
||||||
|
// m_Friction, m_Restitution
|
||||||
|
FindToggleAndValueProperties(property, templateValueProperty, "m_CollisionResponse", out _, out var collisionResponse);
|
||||||
|
// Check if regular collider
|
||||||
|
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
|
||||||
|
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
|
||||||
|
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
|
||||||
|
height += 2f * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsCompatible(SerializedProperty property) => true;
|
||||||
|
|
||||||
|
static void DisplayOverridableProperty(
|
||||||
|
Rect position, GUIContent label, SerializedProperty toggle, SerializedProperty value, bool templateAssigned
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (templateAssigned)
|
||||||
|
{
|
||||||
|
var labelWidth = EditorGUIUtility.labelWidth;
|
||||||
|
EditorGUIUtility.labelWidth -= 16f + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
var togglePosition = new Rect(position) { width = EditorGUIUtility.labelWidth + 16f + EditorGUIUtility.standardVerticalSpacing };
|
||||||
|
EditorGUI.PropertyField(togglePosition, toggle, label);
|
||||||
|
EditorGUIUtility.labelWidth = labelWidth;
|
||||||
|
|
||||||
|
EditorGUI.BeginDisabledGroup(!toggle.boolValue);
|
||||||
|
var indent = EditorGUI.indentLevel;
|
||||||
|
EditorGUI.indentLevel = 0;
|
||||||
|
EditorGUI.PropertyField(new Rect(position) { xMin = togglePosition.xMax }, value, GUIContent.none, true);
|
||||||
|
EditorGUI.indentLevel = indent;
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, value, label, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var template = property.FindPropertyRelative("m_Template");
|
||||||
|
var templateAssigned = template.objectReferenceValue != null;
|
||||||
|
var supportsTemplate = property.FindPropertyRelative("m_SupportsTemplate");
|
||||||
|
if (supportsTemplate.boolValue)
|
||||||
|
{
|
||||||
|
position.height = EditorGUI.GetPropertyHeight(template);
|
||||||
|
EditorGUI.PropertyField(position, template);
|
||||||
|
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var templateValue = GetTemplateValueProperty(property);
|
||||||
|
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_CollisionResponse", out var collisionResponseDropDown, out var collisionResponse);
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.CollisionResponseLabel, collisionResponseDropDown, collisionResponse, templateAssigned);
|
||||||
|
|
||||||
|
SerializedProperty toggle;
|
||||||
|
|
||||||
|
// Check if regular collider
|
||||||
|
CollisionResponsePolicy collisionResponseEnum = (CollisionResponsePolicy)collisionResponse.intValue;
|
||||||
|
if (collisionResponseEnum == CollisionResponsePolicy.Collide ||
|
||||||
|
collisionResponseEnum == CollisionResponsePolicy.CollideRaiseCollisionEvents)
|
||||||
|
{
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_Friction", out toggle, out var friction);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.FrictionLabel, toggle, friction, templateAssigned);
|
||||||
|
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_Restitution", out toggle, out var restitution);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.RestitutionLabel, toggle, restitution, templateAssigned);
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision filter group
|
||||||
|
var collisionFilterGroup = property.FindPropertyRelative(k_CollisionFilterGroupKey);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
collisionFilterGroup.isExpanded =
|
||||||
|
EditorGUI.Foldout(position, collisionFilterGroup.isExpanded, Content.CollisionFilterGroupFoldout, true);
|
||||||
|
if (collisionFilterGroup.isExpanded)
|
||||||
|
{
|
||||||
|
++EditorGUI.indentLevel;
|
||||||
|
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_BelongsToCategories", out toggle, out var belongsTo);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.BelongsToLabel, toggle, belongsTo, templateAssigned);
|
||||||
|
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_CollidesWithCategories", out toggle, out var collidesWith);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.CollidesWithLabel, toggle, collidesWith, templateAssigned);
|
||||||
|
|
||||||
|
--EditorGUI.indentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advanced group
|
||||||
|
var advancedGroup = property.FindPropertyRelative(k_AdvancedGroupKey);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
advancedGroup.isExpanded =
|
||||||
|
EditorGUI.Foldout(position, advancedGroup.isExpanded, Content.AdvancedGroupFoldout, true);
|
||||||
|
if (advancedGroup.isExpanded)
|
||||||
|
{
|
||||||
|
++EditorGUI.indentLevel;
|
||||||
|
|
||||||
|
FindToggleAndValueProperties(property, templateValue, "m_CustomMaterialTags", out toggle, out var customFlags);
|
||||||
|
position.y = position.yMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
position.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
DisplayOverridableProperty(position, Content.CustomFlagsLabel, toggle, customFlags, templateAssigned);
|
||||||
|
|
||||||
|
--EditorGUI.indentLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 72400ec801359f34e9b82e3be224cde5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(SoftRangeAttribute))]
|
||||||
|
class SoftRangeDrawer : BaseDrawer
|
||||||
|
{
|
||||||
|
protected override bool IsCompatible(SerializedProperty property)
|
||||||
|
{
|
||||||
|
return property.propertyType == SerializedPropertyType.Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DoGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
var attr = attribute as SoftRangeAttribute;
|
||||||
|
EditorGUIControls.SoftSlider(
|
||||||
|
position, label, property, attr.SliderMin, attr.SliderMax, attr.TextFieldMin, attr.TextFieldMax
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b3973ec625484cb418fb92f5b2bbffdd
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
abstract class TagsDrawer<T> : PropertyDrawer where T : ScriptableObject, ITagNames
|
||||||
|
{
|
||||||
|
static class Styles
|
||||||
|
{
|
||||||
|
public static readonly string EverythingName = L10n.Tr("Everything");
|
||||||
|
public static readonly string MixedName = L10n.Tr("Mixed...");
|
||||||
|
public static readonly string NothingName = L10n.Tr("Nothing");
|
||||||
|
|
||||||
|
public static readonly string MultipleAssetsTooltip =
|
||||||
|
L10n.Tr("Multiple {0} assets found. UI will display labels defined in {1}.");
|
||||||
|
|
||||||
|
public static readonly GUIContent MultipleAssetsWarning =
|
||||||
|
new GUIContent { image = EditorGUIUtility.Load("console.warnicon") as Texture };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int MaxNumCategories { get; }
|
||||||
|
protected abstract string DefaultCategoryName { get; }
|
||||||
|
internal string FirstChildPropertyPath { get; set; } // TODO: remove when all usages of bool[] are migrated
|
||||||
|
|
||||||
|
string DefaultFormatString => L10n.Tr($"(Undefined {DefaultCategoryName})");
|
||||||
|
|
||||||
|
string[] DefaultOptions =>
|
||||||
|
m_DefaultOptions ?? (
|
||||||
|
m_DefaultOptions =
|
||||||
|
Enumerable.Range(0, MaxNumCategories)
|
||||||
|
.Select(i => string.Format(DefaultFormatString, i))
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
string[] m_DefaultOptions;
|
||||||
|
|
||||||
|
string[] GetOptions()
|
||||||
|
{
|
||||||
|
if (m_Options != null)
|
||||||
|
return m_Options;
|
||||||
|
|
||||||
|
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
|
||||||
|
m_NamesAssets = guids
|
||||||
|
.Select(AssetDatabase.GUIDToAssetPath)
|
||||||
|
.Select(AssetDatabase.LoadAssetAtPath<T>)
|
||||||
|
.Where(c => c != null)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
m_Options = m_NamesAssets.FirstOrDefault()?.TagNames.ToArray() ?? DefaultOptions;
|
||||||
|
for (var i = 0; i < m_Options.Length; ++i)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(m_Options[i]))
|
||||||
|
m_Options[i] = DefaultOptions[i];
|
||||||
|
|
||||||
|
m_Options[i] = $"{i}: {m_Options[i]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_Options;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] m_Options;
|
||||||
|
|
||||||
|
static string GetButtonLabel(int value, IReadOnlyList<string> optionNames)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return Styles.NothingName;
|
||||||
|
case ~0:
|
||||||
|
return Styles.EverythingName;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
if (value == 1 << i)
|
||||||
|
return optionNames[i];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Styles.MixedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
T[] m_NamesAssets;
|
||||||
|
|
||||||
|
// TODO: remove when all usages of bool[] are migrated
|
||||||
|
SerializedProperty GetFirstChildProperty(SerializedProperty property)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(FirstChildPropertyPath))
|
||||||
|
return property.FindPropertyRelative(FirstChildPropertyPath);
|
||||||
|
var sp = property.Copy();
|
||||||
|
sp.NextVisible(true);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
if (m_NamesAssets?.Length > 1)
|
||||||
|
position.xMax -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
|
||||||
|
var controlPosition = EditorGUI.PrefixLabel(position, label);
|
||||||
|
|
||||||
|
var indent = EditorGUI.indentLevel;
|
||||||
|
EditorGUI.indentLevel = 0;
|
||||||
|
var showMixed = EditorGUI.showMixedValue;
|
||||||
|
|
||||||
|
var value = 0;
|
||||||
|
var everything = 0;
|
||||||
|
var sp = GetFirstChildProperty(property);
|
||||||
|
for (int i = 0, count = MaxNumCategories; i < count; ++i)
|
||||||
|
{
|
||||||
|
EditorGUI.showMixedValue |= sp.hasMultipleDifferentValues;
|
||||||
|
value |= sp.boolValue ? 1 << i : 0;
|
||||||
|
everything |= 1 << i;
|
||||||
|
sp.NextVisible(false);
|
||||||
|
}
|
||||||
|
// in case size is smaller than 32
|
||||||
|
if (value == everything)
|
||||||
|
value = ~0;
|
||||||
|
|
||||||
|
var options = GetOptions();
|
||||||
|
if (
|
||||||
|
EditorGUI.DropdownButton(
|
||||||
|
controlPosition,
|
||||||
|
EditorGUIUtility.TrTempContent(GetButtonLabel(value, options)),
|
||||||
|
FocusType.Passive,
|
||||||
|
EditorStyles.popup
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var menu = new GenericMenu();
|
||||||
|
|
||||||
|
menu.AddItem(
|
||||||
|
new GUIContent(Styles.NothingName),
|
||||||
|
value == 0,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
sp = GetFirstChildProperty(property);
|
||||||
|
for (int i = 0, count = MaxNumCategories; i < count; ++i)
|
||||||
|
{
|
||||||
|
sp.boolValue = false;
|
||||||
|
sp.NextVisible(false);
|
||||||
|
}
|
||||||
|
sp.serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
menu.AddItem(
|
||||||
|
new GUIContent(Styles.EverythingName),
|
||||||
|
value == ~0,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
sp = GetFirstChildProperty(property);
|
||||||
|
for (int i = 0, count = MaxNumCategories; i < count; ++i)
|
||||||
|
{
|
||||||
|
sp.boolValue = true;
|
||||||
|
sp.NextVisible(false);
|
||||||
|
}
|
||||||
|
sp.serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var option = 0; option < options.Length; ++option)
|
||||||
|
{
|
||||||
|
var callbackValue = option;
|
||||||
|
menu.AddItem(
|
||||||
|
EditorGUIUtility.TrTextContent(options[option]),
|
||||||
|
((1 << option) & value) != 0,
|
||||||
|
args =>
|
||||||
|
{
|
||||||
|
var changedBitAndValue = (KeyValuePair<int, bool>)args;
|
||||||
|
sp = GetFirstChildProperty(property);
|
||||||
|
for (int i = 0, count = changedBitAndValue.Key; i < count; ++i)
|
||||||
|
sp.NextVisible(false);
|
||||||
|
sp.boolValue = changedBitAndValue.Value;
|
||||||
|
sp.serializedObject.ApplyModifiedProperties();
|
||||||
|
},
|
||||||
|
new KeyValuePair<int, bool>(callbackValue, ((1 << option) & value) == 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.AddSeparator(string.Empty);
|
||||||
|
|
||||||
|
menu.AddItem(
|
||||||
|
EditorGUIUtility.TrTempContent($"Edit {ObjectNames.NicifyVariableName(typeof(T).Name)}"),
|
||||||
|
false,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
if (m_NamesAssets.Length > 0)
|
||||||
|
Selection.activeObject = m_NamesAssets[0];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{typeof(T).Name}.asset");
|
||||||
|
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<T>(), assetPath);
|
||||||
|
Selection.activeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
|
||||||
|
m_Options = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
menu.DropDown(controlPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = showMixed;
|
||||||
|
EditorGUI.indentLevel = indent;
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
|
||||||
|
if (m_NamesAssets?.Length > 1)
|
||||||
|
{
|
||||||
|
var id = GUIUtility.GetControlID(FocusType.Passive);
|
||||||
|
if (Event.current.type == EventType.Repaint)
|
||||||
|
{
|
||||||
|
position.width = EditorGUIUtility.singleLineHeight;
|
||||||
|
position.x = controlPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
Styles.MultipleAssetsWarning.tooltip = string.Format(
|
||||||
|
Styles.MultipleAssetsTooltip,
|
||||||
|
ObjectNames.NicifyVariableName(typeof(T).Name),
|
||||||
|
m_NamesAssets.FirstOrDefault(n => n != null)?.name
|
||||||
|
);
|
||||||
|
GUIStyle.none.Draw(position, Styles.MultipleAssetsWarning, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyDrawer(typeof(CustomPhysicsBodyTags))]
|
||||||
|
class CustomBodyTagsDrawer : TagsDrawer<CustomPhysicsBodyTagNames>
|
||||||
|
{
|
||||||
|
protected override string DefaultCategoryName => "Custom Physics Body Tag";
|
||||||
|
protected override int MaxNumCategories => 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyDrawer(typeof(CustomPhysicsMaterialTags))]
|
||||||
|
class CustomMaterialTagsDrawer : TagsDrawer<CustomPhysicsMaterialTagNames>
|
||||||
|
{
|
||||||
|
protected override string DefaultCategoryName => "Custom Physics Material Tag";
|
||||||
|
protected override int MaxNumCategories => 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyDrawer(typeof(PhysicsCategoryTags))]
|
||||||
|
class PhysicsCategoryTagsDrawer : TagsDrawer<PhysicsCategoryNames>
|
||||||
|
{
|
||||||
|
protected override string DefaultCategoryName => "Physics Category";
|
||||||
|
protected override int MaxNumCategories => 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bacae2b76edd4224d8ccf21bbeb71833
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Unity.Physics.Custom.Editor",
|
||||||
|
"references": [
|
||||||
|
"Unity.Burst",
|
||||||
|
"Unity.Collections",
|
||||||
|
"Unity.Entities",
|
||||||
|
"Unity.Mathematics",
|
||||||
|
"Unity.Physics",
|
||||||
|
"Unity.Physics.Editor",
|
||||||
|
"Unity.Physics.Hybrid",
|
||||||
|
"Unity.Physics.Samples",
|
||||||
|
"Unity.Physics.Custom"
|
||||||
|
],
|
||||||
|
"includePlatforms": [
|
||||||
|
"Editor"
|
||||||
|
],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": true,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dde4aa253ec0b874ba280d1bde6b5386
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f3d7894b5b13854409e8c11b2c933f3e
|
||||||
|
timeCreated: 1678290090
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
[InitializeOnLoad]
|
||||||
|
static class EditorGUIControls
|
||||||
|
{
|
||||||
|
static EditorGUIControls()
|
||||||
|
{
|
||||||
|
if (k_SoftSlider == null)
|
||||||
|
Debug.LogException(new MissingMemberException("Could not find expected signature of EditorGUI.Slider() for soft slider."));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Styles
|
||||||
|
{
|
||||||
|
public static readonly string CompatibilityWarning = L10n.Tr("Not compatible with {0}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DisplayCompatibilityWarning(Rect position, GUIContent label, string incompatibleType)
|
||||||
|
{
|
||||||
|
EditorGUI.HelpBox(
|
||||||
|
EditorGUI.PrefixLabel(position, label),
|
||||||
|
string.Format(Styles.CompatibilityWarning, incompatibleType),
|
||||||
|
MessageType.Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly MethodInfo k_SoftSlider = typeof(EditorGUI).GetMethod(
|
||||||
|
"Slider",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic,
|
||||||
|
null,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
typeof(Rect), // position
|
||||||
|
typeof(GUIContent), // label
|
||||||
|
typeof(float), // value
|
||||||
|
typeof(float), // sliderMin
|
||||||
|
typeof(float), // sliderMax
|
||||||
|
typeof(float), // textFieldMin
|
||||||
|
typeof(float) // textFieldMax
|
||||||
|
},
|
||||||
|
Array.Empty<ParameterModifier>()
|
||||||
|
);
|
||||||
|
|
||||||
|
static readonly object[] k_SoftSliderArgs = new object[7];
|
||||||
|
|
||||||
|
public static void SoftSlider(
|
||||||
|
Rect position, GUIContent label, SerializedProperty property,
|
||||||
|
float sliderMin, float sliderMax,
|
||||||
|
float textFieldMin, float textFieldMax
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (property.propertyType != SerializedPropertyType.Float)
|
||||||
|
{
|
||||||
|
DisplayCompatibilityWarning(position, label, property.propertyType.ToString());
|
||||||
|
}
|
||||||
|
else if (k_SoftSlider == null)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
EditorGUI.PropertyField(position, property, label);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
property.floatValue = math.clamp(property.floatValue, textFieldMin, textFieldMax);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
k_SoftSliderArgs[0] = position;
|
||||||
|
k_SoftSliderArgs[1] = label;
|
||||||
|
k_SoftSliderArgs[2] = property.floatValue;
|
||||||
|
k_SoftSliderArgs[3] = sliderMin;
|
||||||
|
k_SoftSliderArgs[4] = sliderMax;
|
||||||
|
k_SoftSliderArgs[5] = textFieldMin;
|
||||||
|
k_SoftSliderArgs[6] = textFieldMax;
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
var result = k_SoftSlider.Invoke(null, k_SoftSliderArgs);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
property.floatValue = (float)result;
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 871e6b9236392b347a91f460fd84f976
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
enum MatrixState
|
||||||
|
{
|
||||||
|
UniformScale,
|
||||||
|
NonUniformScale,
|
||||||
|
ZeroScale,
|
||||||
|
NotValidTRS
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ManipulatorUtility
|
||||||
|
{
|
||||||
|
public static MatrixState GetMatrixState(ref float4x4 localToWorld)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
localToWorld.c0.w != 0f
|
||||||
|
|| localToWorld.c1.w != 0f
|
||||||
|
|| localToWorld.c2.w != 0f
|
||||||
|
|| localToWorld.c3.w != 1f
|
||||||
|
)
|
||||||
|
return MatrixState.NotValidTRS;
|
||||||
|
|
||||||
|
var m = new float3x3(localToWorld.c0.xyz, localToWorld.c1.xyz, localToWorld.c2.xyz);
|
||||||
|
var lossyScale = new float3(math.length(m.c0.xyz), math.length(m.c1.xyz), math.length(m.c2.xyz));
|
||||||
|
if (math.determinant(m) < 0f)
|
||||||
|
lossyScale.x *= -1f;
|
||||||
|
if (math.lengthsq(lossyScale) == 0f)
|
||||||
|
return MatrixState.ZeroScale;
|
||||||
|
return math.abs(math.cmax(lossyScale)) - math.abs(math.cmin(lossyScale)) > 0.000001f
|
||||||
|
? MatrixState.NonUniformScale
|
||||||
|
: MatrixState.UniformScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1ed6267b3a6f75e4aa4aca986451797e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
static class SceneViewUtility
|
||||||
|
{
|
||||||
|
static class Styles
|
||||||
|
{
|
||||||
|
public static readonly GUIStyle ProgressBarTrack = new GUIStyle
|
||||||
|
{
|
||||||
|
fixedHeight = 4f,
|
||||||
|
normal = new GUIStyleState { background = Texture2D.whiteTexture }
|
||||||
|
};
|
||||||
|
public static readonly GUIStyle ProgressBarIndicator = new GUIStyle
|
||||||
|
{
|
||||||
|
fixedHeight = 4f,
|
||||||
|
normal = new GUIStyleState { background = Texture2D.whiteTexture }
|
||||||
|
};
|
||||||
|
public static readonly GUIStyle SceneViewStatusMessage = new GUIStyle("NotificationBackground")
|
||||||
|
{
|
||||||
|
fontSize = EditorStyles.label.fontSize
|
||||||
|
};
|
||||||
|
|
||||||
|
static Styles() => SceneViewStatusMessage.padding = SceneViewStatusMessage.border;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string k_NotificationsPrefKey = "SceneView/Tools/Notifications";
|
||||||
|
const bool k_DefaultNotifications = true;
|
||||||
|
const string k_NotificationSpeedPrefKey = "SceneView/Tools/Notification Speed";
|
||||||
|
const float k_DefaultNotificationsSpeed = 20f;
|
||||||
|
|
||||||
|
const float k_NotificationDuration = 1f;
|
||||||
|
const float k_NotificationFadeInTime = 0.04f;
|
||||||
|
const float k_NotificationFadeOutTime = 0.2f;
|
||||||
|
static readonly AnimationCurve k_NotificationFadeCurve = new AnimationCurve
|
||||||
|
{
|
||||||
|
keys = new[]
|
||||||
|
{
|
||||||
|
new Keyframe { time = 0f, value = 0f, outTangent = 1f / k_NotificationFadeInTime },
|
||||||
|
new Keyframe { time = k_NotificationFadeInTime, value = 1f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_NotificationDuration - k_NotificationFadeOutTime, value = 1f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_NotificationDuration, value = 0f, inTangent = -1f / k_NotificationFadeOutTime }
|
||||||
|
},
|
||||||
|
postWrapMode = WrapMode.Clamp,
|
||||||
|
preWrapMode = WrapMode.Clamp
|
||||||
|
};
|
||||||
|
const float k_IndeterminateProgressCurveDuration = 2f;
|
||||||
|
static readonly AnimationCurve k_IndeterminateProgressCurveLeftMargin = new AnimationCurve
|
||||||
|
{
|
||||||
|
keys = new[]
|
||||||
|
{
|
||||||
|
new Keyframe { time = 0f, value = 0f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0.25f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 1f, inTangent = 0f, outTangent = 0f }
|
||||||
|
},
|
||||||
|
postWrapMode = WrapMode.Loop,
|
||||||
|
preWrapMode = WrapMode.Loop
|
||||||
|
};
|
||||||
|
static readonly AnimationCurve k_IndeterminateProgressCurveRightMargin = new AnimationCurve
|
||||||
|
{
|
||||||
|
keys = new[]
|
||||||
|
{
|
||||||
|
new Keyframe { time = 0f, value = 1f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_IndeterminateProgressCurveDuration / 2f, value = 0f, inTangent = 0f, outTangent = 0f },
|
||||||
|
new Keyframe { time = k_IndeterminateProgressCurveDuration, value = 0f, inTangent = 0f, outTangent = 0f }
|
||||||
|
},
|
||||||
|
postWrapMode = WrapMode.Loop,
|
||||||
|
preWrapMode = WrapMode.Loop
|
||||||
|
};
|
||||||
|
|
||||||
|
static string s_StatusMessage;
|
||||||
|
static DateTime s_StartTime;
|
||||||
|
static bool s_IsTemporary;
|
||||||
|
static Func<float> s_GetProgress;
|
||||||
|
|
||||||
|
public static void DisplayProgressNotification(string message, Func<float> getProgress) =>
|
||||||
|
// insert an extra line to make room for progress bar
|
||||||
|
DisplayNotificationInSceneView(getProgress == null ? message : $"{message}\n", false, getProgress);
|
||||||
|
|
||||||
|
public static void DisplayPersistentNotification(string message) =>
|
||||||
|
DisplayNotificationInSceneView(message, false, null);
|
||||||
|
|
||||||
|
public static void DisplayTemporaryNotification(string message) =>
|
||||||
|
DisplayNotificationInSceneView(message, true, null);
|
||||||
|
|
||||||
|
static void DisplayNotificationInSceneView(string message, bool temporary, Func<float> getProgress)
|
||||||
|
{
|
||||||
|
s_StatusMessage = message ?? string.Empty;
|
||||||
|
s_StartTime = DateTime.Now;
|
||||||
|
s_IsTemporary = temporary;
|
||||||
|
s_GetProgress = getProgress;
|
||||||
|
ClearNotificationInSceneView();
|
||||||
|
SceneView.duringSceneGui += ToolNotificationCallback;
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ToolNotificationCallback(SceneView obj)
|
||||||
|
{
|
||||||
|
if (Camera.current == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var duration = math.max(s_StatusMessage.Length, 1)
|
||||||
|
/ EditorPrefs.GetFloat(k_NotificationSpeedPrefKey, k_DefaultNotificationsSpeed);
|
||||||
|
var t = (float)(DateTime.Now - s_StartTime).TotalSeconds;
|
||||||
|
if (
|
||||||
|
s_IsTemporary
|
||||||
|
&& (t >= duration || !EditorPrefs.GetBool(k_NotificationsPrefKey, k_DefaultNotifications))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ClearNotificationInSceneView();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Handles.BeginGUI();
|
||||||
|
var color = GUI.color;
|
||||||
|
var progress = s_GetProgress?.Invoke() ?? 0f;
|
||||||
|
GUI.color *=
|
||||||
|
new Color(1f, 1f, 1f, math.max(k_NotificationFadeCurve.Evaluate(math.abs(t) / duration), progress));
|
||||||
|
var rect = new Rect { size = Camera.current.pixelRect.size / EditorGUIUtility.pixelsPerPoint };
|
||||||
|
using (new GUILayout.AreaScope(rect))
|
||||||
|
using (new GUILayout.HorizontalScope())
|
||||||
|
{
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
using (new GUILayout.VerticalScope())
|
||||||
|
{
|
||||||
|
GUILayout.Space(rect.height * 0.75f);
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
var maxWidth = rect.width * 0.5f;
|
||||||
|
GUILayout.Box(s_StatusMessage, Styles.SceneViewStatusMessage, GUILayout.MaxWidth(maxWidth));
|
||||||
|
if (s_GetProgress != null)
|
||||||
|
{
|
||||||
|
rect = GUILayoutUtility.GetLastRect();
|
||||||
|
rect = Styles.SceneViewStatusMessage.padding.Remove(rect);
|
||||||
|
rect.y = rect.yMax - Styles.ProgressBarTrack.fixedHeight;
|
||||||
|
rect.height = Styles.ProgressBarTrack.fixedHeight;
|
||||||
|
var c = GUI.color;
|
||||||
|
GUI.color *= Color.black;
|
||||||
|
GUI.Box(rect, GUIContent.none, Styles.ProgressBarTrack);
|
||||||
|
GUI.color = c;
|
||||||
|
if (progress >= 0f && progress <= 1f)
|
||||||
|
{
|
||||||
|
rect.width *= progress;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var w = rect.width;
|
||||||
|
rect.xMin = rect.xMin + w * k_IndeterminateProgressCurveLeftMargin.Evaluate(t);
|
||||||
|
rect.xMax = rect.xMax - w * k_IndeterminateProgressCurveRightMargin.Evaluate(t);
|
||||||
|
}
|
||||||
|
GUI.Box(rect, GUIContent.none, Styles.ProgressBarIndicator);
|
||||||
|
}
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
}
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
}
|
||||||
|
GUI.color = color;
|
||||||
|
Handles.EndGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearNotificationInSceneView() => SceneView.duringSceneGui -= ToolNotificationCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b7c9bbfa7889d6e4faa42c14d33bebb7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Unity.Physics.Authoring;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Editor
|
||||||
|
{
|
||||||
|
static class StatusMessageUtility
|
||||||
|
{
|
||||||
|
public static MessageType GetHierarchyStatusMessage(IReadOnlyList<UnityEngine.Object> targets, out string statusMessage)
|
||||||
|
{
|
||||||
|
statusMessage = string.Empty;
|
||||||
|
if (targets.Count == 0)
|
||||||
|
return MessageType.None;
|
||||||
|
|
||||||
|
var numChildTargets = 0;
|
||||||
|
foreach (Component c in targets)
|
||||||
|
{
|
||||||
|
// hierarchy roots and leaf shapes do not emit a message
|
||||||
|
if (
|
||||||
|
c == null
|
||||||
|
|| c.transform.parent == null
|
||||||
|
|| PhysicsShapeExtensions.GetPrimaryBody(c.gameObject) != c.gameObject
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var targetType = c.GetType();
|
||||||
|
// only bodies (both explicit and implicit static bodies) will emit a message
|
||||||
|
if (
|
||||||
|
targetType == typeof(PhysicsBodyAuthoring)
|
||||||
|
|| targetType == typeof(Rigidbody)
|
||||||
|
|| c.GetComponent<PhysicsBodyAuthoring>() == null
|
||||||
|
&& c.GetComponent<Rigidbody>() == null
|
||||||
|
)
|
||||||
|
++numChildTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (numChildTargets)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return MessageType.None;
|
||||||
|
case 1:
|
||||||
|
statusMessage =
|
||||||
|
L10n.Tr("Target will be un-parented during the conversion process in order to take part in physics simulation.");
|
||||||
|
return MessageType.Warning;
|
||||||
|
default:
|
||||||
|
statusMessage =
|
||||||
|
L10n.Tr("One or more targets will be un-parented during the conversion process in order to take part in physics simulation.");
|
||||||
|
return MessageType.Warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageType GetMatrixStatusMessage(
|
||||||
|
IReadOnlyList<MatrixState> matrixStates, out string statusMessage
|
||||||
|
)
|
||||||
|
{
|
||||||
|
statusMessage = string.Empty;
|
||||||
|
if (matrixStates.Contains(MatrixState.NotValidTRS))
|
||||||
|
{
|
||||||
|
statusMessage = L10n.Tr(
|
||||||
|
matrixStates.Count == 1
|
||||||
|
? "Target's local-to-world matrix is not a valid transformation."
|
||||||
|
: "One or more targets' local-to-world matrices are not valid transformations."
|
||||||
|
);
|
||||||
|
return MessageType.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrixStates.Contains(MatrixState.ZeroScale))
|
||||||
|
{
|
||||||
|
statusMessage =
|
||||||
|
L10n.Tr(matrixStates.Count == 1 ? "Target has zero scale." : "One or more targets has zero scale.");
|
||||||
|
return MessageType.Warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrixStates.Contains(MatrixState.NonUniformScale))
|
||||||
|
{
|
||||||
|
statusMessage = L10n.Tr(
|
||||||
|
matrixStates.Count == 1
|
||||||
|
? "Target has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
|
||||||
|
: "One or more targets has non-uniform scale. Shape data will be transformed during conversion in order to bake scale into the run-time format."
|
||||||
|
);
|
||||||
|
return MessageType.Warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 73b942099bb387a48abd183643304f85
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 447d6d85fbacd7b4ea06884cf28f875e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Unity.Physics.Custom.Editor")]
|
||||||
|
[assembly: InternalsVisibleTo("Unity.Physics.Custom.EditModeTests")]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 79534194088d466faf51dedb0dbb3012
|
||||||
|
timeCreated: 1680051514
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[RequireComponent(typeof(PhysicsBodyAuthoring))]
|
||||||
|
public abstract class BaseBodyPairConnector : MonoBehaviour
|
||||||
|
{
|
||||||
|
public PhysicsBodyAuthoring LocalBody => GetComponent<PhysicsBodyAuthoring>();
|
||||||
|
public PhysicsBodyAuthoring ConnectedBody;
|
||||||
|
|
||||||
|
public RigidTransform worldFromA => LocalBody == null
|
||||||
|
? RigidTransform.identity
|
||||||
|
: Math.DecomposeRigidBodyTransform(LocalBody.transform.localToWorldMatrix);
|
||||||
|
|
||||||
|
public RigidTransform worldFromB => ConnectedBody == null
|
||||||
|
? RigidTransform.identity
|
||||||
|
: Math.DecomposeRigidBodyTransform(ConnectedBody.transform.localToWorldMatrix);
|
||||||
|
|
||||||
|
|
||||||
|
public Entity EntityA { get; set; }
|
||||||
|
|
||||||
|
public Entity EntityB { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
// included so tick box appears in Editor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 239c8ffb2d0d90043a794cea1df207f7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 67021bc32b354a27adca79ce72cc0632
|
||||||
|
timeCreated: 1678114562
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: debd1c7da217451682429a0a443b3fb4
|
||||||
|
timeCreated: 1678225532
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using Unity.Physics.GraphicsIntegration;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[TemporaryBakingType]
|
||||||
|
public struct PhysicsBodyAuthoringData : IComponentData
|
||||||
|
{
|
||||||
|
public bool IsDynamic;
|
||||||
|
public float Mass;
|
||||||
|
public bool OverrideDefaultMassDistribution;
|
||||||
|
public MassDistribution CustomMassDistribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhysicsBodyAuthoringBaker : BasePhysicsBaker<PhysicsBodyAuthoring>
|
||||||
|
{
|
||||||
|
internal List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
|
||||||
|
internal List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
|
||||||
|
|
||||||
|
public override void Bake(PhysicsBodyAuthoring authoring)
|
||||||
|
{
|
||||||
|
// Priority is to Legacy Components. Ignore if baked by Legacy.
|
||||||
|
if (GetComponent<Rigidbody>() || GetComponent<UnityEngine.Collider>())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = GetEntity(TransformUsageFlags.Dynamic);
|
||||||
|
// To process later in the Baking System
|
||||||
|
AddComponent(entity, new PhysicsBodyAuthoringData
|
||||||
|
{
|
||||||
|
IsDynamic = (authoring.MotionType == BodyMotionType.Dynamic),
|
||||||
|
Mass = authoring.Mass,
|
||||||
|
OverrideDefaultMassDistribution = authoring.OverrideDefaultMassDistribution,
|
||||||
|
CustomMassDistribution = authoring.CustomMassDistribution
|
||||||
|
});
|
||||||
|
|
||||||
|
AddSharedComponent(entity, new PhysicsWorldIndex(authoring.WorldIndex));
|
||||||
|
|
||||||
|
var bodyTransform = GetComponent<Transform>();
|
||||||
|
|
||||||
|
var motionType = authoring.MotionType;
|
||||||
|
var hasSmoothing = authoring.Smoothing != BodySmoothing.None;
|
||||||
|
|
||||||
|
PostProcessTransform(bodyTransform, motionType);
|
||||||
|
|
||||||
|
var customTags = authoring.CustomTags;
|
||||||
|
if (!customTags.Equals(CustomPhysicsBodyTags.Nothing))
|
||||||
|
AddComponent(entity, new PhysicsCustomTags { Value = customTags.Value });
|
||||||
|
|
||||||
|
// Check that there is at least one collider in the hierarchy to add these three
|
||||||
|
GetComponentsInChildren(colliderComponents);
|
||||||
|
GetComponentsInChildren(physicsShapeComponents);
|
||||||
|
if (colliderComponents.Count > 0 || physicsShapeComponents.Count > 0)
|
||||||
|
{
|
||||||
|
AddComponent(entity, new PhysicsCompoundData()
|
||||||
|
{
|
||||||
|
AssociateBlobToBody = false,
|
||||||
|
ConvertedBodyInstanceID = authoring.GetInstanceID(),
|
||||||
|
Hash = default,
|
||||||
|
});
|
||||||
|
AddComponent<PhysicsRootBaked>(entity);
|
||||||
|
AddComponent<PhysicsCollider>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authoring.MotionType == BodyMotionType.Static || IsStatic())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var massProperties = MassProperties.UnitSphere;
|
||||||
|
|
||||||
|
AddComponent(entity, authoring.MotionType == BodyMotionType.Dynamic ?
|
||||||
|
PhysicsMass.CreateDynamic(massProperties, authoring.Mass) :
|
||||||
|
PhysicsMass.CreateKinematic(massProperties));
|
||||||
|
|
||||||
|
var physicsVelocity = new PhysicsVelocity
|
||||||
|
{
|
||||||
|
Linear = authoring.InitialLinearVelocity,
|
||||||
|
Angular = authoring.InitialAngularVelocity
|
||||||
|
};
|
||||||
|
AddComponent(entity, physicsVelocity);
|
||||||
|
|
||||||
|
if (authoring.MotionType == BodyMotionType.Dynamic)
|
||||||
|
{
|
||||||
|
// TODO make these optional in editor?
|
||||||
|
AddComponent(entity, new PhysicsDamping
|
||||||
|
{
|
||||||
|
Linear = authoring.LinearDamping,
|
||||||
|
Angular = authoring.AngularDamping
|
||||||
|
});
|
||||||
|
if (authoring.GravityFactor != 1)
|
||||||
|
{
|
||||||
|
AddComponent(entity, new PhysicsGravityFactor
|
||||||
|
{
|
||||||
|
Value = authoring.GravityFactor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (authoring.MotionType == BodyMotionType.Kinematic)
|
||||||
|
{
|
||||||
|
AddComponent(entity, new PhysicsGravityFactor
|
||||||
|
{
|
||||||
|
Value = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSmoothing)
|
||||||
|
{
|
||||||
|
AddComponent(entity, new PhysicsGraphicalSmoothing());
|
||||||
|
if (authoring.Smoothing == BodySmoothing.Interpolation)
|
||||||
|
{
|
||||||
|
AddComponent(entity, new PhysicsGraphicalInterpolationBuffer
|
||||||
|
{
|
||||||
|
PreviousTransform = Math.DecomposeRigidBodyTransform(bodyTransform.localToWorldMatrix),
|
||||||
|
PreviousVelocity = physicsVelocity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequireMatchingQueriesForUpdate]
|
||||||
|
[UpdateAfter(typeof(EndColliderBakingSystem))]
|
||||||
|
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
|
||||||
|
public partial class PhysicsBodyBakingSystem : SystemBase
|
||||||
|
{
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
// Fill in the MassProperties based on the potential calculated value by BuildCompoundColliderBakingSystem
|
||||||
|
foreach (var(physicsMass, bodyData, collider) in
|
||||||
|
SystemAPI.Query<RefRW<PhysicsMass>, RefRO<PhysicsBodyAuthoringData>, RefRO<PhysicsCollider>>().WithOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabledEntities))
|
||||||
|
{
|
||||||
|
// Build mass component
|
||||||
|
var massProperties = collider.ValueRO.MassProperties;
|
||||||
|
if (bodyData.ValueRO.OverrideDefaultMassDistribution)
|
||||||
|
{
|
||||||
|
massProperties.MassDistribution = bodyData.ValueRO.CustomMassDistribution;
|
||||||
|
// Increase the angular expansion factor to account for the shift in center of mass
|
||||||
|
massProperties.AngularExpansionFactor += math.length(massProperties.MassDistribution.Transform.pos - bodyData.ValueRO.CustomMassDistribution.Transform.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
physicsMass.ValueRW = bodyData.ValueRO.IsDynamic ?
|
||||||
|
PhysicsMass.CreateDynamic(massProperties, bodyData.ValueRO.Mass) :
|
||||||
|
PhysicsMass.CreateKinematic(massProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 922a37a81b1c6cc4da69a1148c8d2988
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Profiling;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
class PhysicsShapeBaker : BaseColliderBaker<PhysicsShapeAuthoring>
|
||||||
|
{
|
||||||
|
public static List<PhysicsShapeAuthoring> physicsShapeComponents = new List<PhysicsShapeAuthoring>();
|
||||||
|
public static List<UnityEngine.Collider> colliderComponents = new List<UnityEngine.Collider>();
|
||||||
|
|
||||||
|
bool ShouldConvertShape(PhysicsShapeAuthoring authoring)
|
||||||
|
{
|
||||||
|
return authoring.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject GetPrimaryBody(GameObject shape, out bool hasBodyComponent, out bool isStaticBody)
|
||||||
|
{
|
||||||
|
var pb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
|
||||||
|
var rb = FindFirstEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
|
||||||
|
hasBodyComponent = (pb != null || rb != null);
|
||||||
|
isStaticBody = false;
|
||||||
|
|
||||||
|
if (pb != null)
|
||||||
|
{
|
||||||
|
return rb == null ? pb.gameObject :
|
||||||
|
pb.transform.IsChildOf(rb.transform) ? pb.gameObject : rb.gameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rb != null)
|
||||||
|
return rb.gameObject;
|
||||||
|
|
||||||
|
// for implicit static shape, first see if it is part of static optimized hierarchy
|
||||||
|
isStaticBody = FindTopmostStaticEnabledAncestor(shape, out var topStatic);
|
||||||
|
if (topStatic != null)
|
||||||
|
return topStatic;
|
||||||
|
|
||||||
|
// otherwise, find topmost enabled Collider or PhysicsShapeAuthoring
|
||||||
|
var topCollider = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_CollidersBuffer);
|
||||||
|
var topShape = FindTopmostEnabledAncestor(shape, PhysicsShapeExtensions_NonBursted.s_ShapesBuffer);
|
||||||
|
|
||||||
|
return topCollider == null
|
||||||
|
? topShape == null ? shape.gameObject : topShape
|
||||||
|
: topShape == null
|
||||||
|
? topCollider
|
||||||
|
: topShape.transform.IsChildOf(topCollider.transform)
|
||||||
|
? topCollider
|
||||||
|
: topShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapeComputationDataBaking GetInputDataFromAuthoringComponent(PhysicsShapeAuthoring shape, Entity colliderEntity)
|
||||||
|
{
|
||||||
|
GameObject shapeGameObject = shape.gameObject;
|
||||||
|
var body = GetPrimaryBody(shapeGameObject, out bool hasBodyComponent, out bool isStaticBody);
|
||||||
|
var child = shapeGameObject;
|
||||||
|
var shapeInstanceID = shape.GetInstanceID();
|
||||||
|
|
||||||
|
var bodyEntity = GetEntity(body, TransformUsageFlags.Dynamic);
|
||||||
|
|
||||||
|
// prepare the static root
|
||||||
|
if (isStaticBody)
|
||||||
|
{
|
||||||
|
var staticRootMarker = CreateAdditionalEntity(TransformUsageFlags.Dynamic, true, "StaticRootBakeMarker");
|
||||||
|
AddComponent(staticRootMarker, new BakeStaticRoot() { Body = bodyEntity, ConvertedBodyInstanceID = body.transform.GetInstanceID() });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track dependencies to the transforms
|
||||||
|
Transform shapeTransform = GetComponent<Transform>(shape);
|
||||||
|
Transform bodyTransform = GetComponent<Transform>(body);
|
||||||
|
var instance = new ColliderInstanceBaking
|
||||||
|
{
|
||||||
|
AuthoringComponentId = shapeInstanceID,
|
||||||
|
BodyEntity = bodyEntity,
|
||||||
|
ShapeEntity = GetEntity(shapeGameObject, TransformUsageFlags.Dynamic),
|
||||||
|
ChildEntity = GetEntity(child, TransformUsageFlags.Dynamic),
|
||||||
|
BodyFromShape = ColliderInstanceBaking.GetCompoundFromChild(shapeTransform, bodyTransform),
|
||||||
|
};
|
||||||
|
|
||||||
|
var data = GenerateComputationData(shape, instance, colliderEntity);
|
||||||
|
|
||||||
|
data.Instance.ConvertedAuthoringInstanceID = shapeInstanceID;
|
||||||
|
data.Instance.ConvertedBodyInstanceID = bodyTransform.GetInstanceID();
|
||||||
|
|
||||||
|
var rb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_RigidbodiesBuffer);
|
||||||
|
var pb = FindFirstEnabledAncestor(shapeGameObject, PhysicsShapeExtensions_NonBursted.s_PhysicsBodiesBuffer);
|
||||||
|
// The Rigidbody cannot know about the Physics Shape Component. We need to take responsibility of baking the collider.
|
||||||
|
if (rb || (!rb && !pb) && body == shapeGameObject)
|
||||||
|
{
|
||||||
|
GetComponents(physicsShapeComponents);
|
||||||
|
GetComponents(colliderComponents);
|
||||||
|
// We need to check that there are no other colliders in the same object, if so, only the first one should do this, otherwise there will be 2 bakers adding this to the entity
|
||||||
|
// This will be needed to trigger BuildCompoundColliderBakingSystem
|
||||||
|
// If they are legacy Colliders and PhysicsShapeAuthoring in the same object, the PhysicsShapeAuthoring will add this
|
||||||
|
if (colliderComponents.Count == 0 && physicsShapeComponents.Count > 0 && physicsShapeComponents[0].GetInstanceID() == shapeInstanceID)
|
||||||
|
{
|
||||||
|
var entity = GetEntity(TransformUsageFlags.Dynamic);
|
||||||
|
|
||||||
|
// // Rigid Body bakes always add the PhysicsWorldIndex component and process transform
|
||||||
|
if (!hasBodyComponent)
|
||||||
|
{
|
||||||
|
AddSharedComponent(entity, new PhysicsWorldIndex());
|
||||||
|
PostProcessTransform(bodyTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddComponent(entity, new PhysicsCompoundData()
|
||||||
|
{
|
||||||
|
AssociateBlobToBody = false,
|
||||||
|
ConvertedBodyInstanceID = shapeInstanceID,
|
||||||
|
Hash = default,
|
||||||
|
});
|
||||||
|
AddComponent<PhysicsRootBaked>(entity);
|
||||||
|
AddComponent<PhysicsCollider>(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material ProduceMaterial(PhysicsShapeAuthoring shape)
|
||||||
|
{
|
||||||
|
var materialTemplate = shape.MaterialTemplate;
|
||||||
|
if (materialTemplate != null)
|
||||||
|
DependsOn(materialTemplate);
|
||||||
|
return shape.GetMaterial();
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionFilter ProduceCollisionFilter(PhysicsShapeAuthoring shape)
|
||||||
|
{
|
||||||
|
return shape.GetFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityEngine.Mesh GetMesh(PhysicsShapeAuthoring shape, out float4x4 childToShape)
|
||||||
|
{
|
||||||
|
var mesh = shape.CustomMesh;
|
||||||
|
childToShape = float4x4.identity;
|
||||||
|
|
||||||
|
if (mesh == null)
|
||||||
|
{
|
||||||
|
// Try to get a mesh in the children
|
||||||
|
var filter = GetComponentInChildren<MeshFilter>();
|
||||||
|
if (filter != null && filter.sharedMesh != null)
|
||||||
|
{
|
||||||
|
mesh = filter.sharedMesh;
|
||||||
|
var childTransform = GetComponent<Transform>(filter);
|
||||||
|
childToShape = math.mul(shape.transform.worldToLocalMatrix, childTransform.localToWorldMatrix);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mesh == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} assigned on {shape.name}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DependsOn(mesh);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetMeshes(PhysicsShapeAuthoring shape, out List<UnityEngine.Mesh> meshes, out List<float4x4> childrenToShape)
|
||||||
|
{
|
||||||
|
meshes = new List<UnityEngine.Mesh>();
|
||||||
|
childrenToShape = new List<float4x4>();
|
||||||
|
|
||||||
|
if (shape.CustomMesh != null)
|
||||||
|
{
|
||||||
|
meshes.Add(shape.CustomMesh);
|
||||||
|
childrenToShape.Add(float4x4.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get all the meshes in the children
|
||||||
|
var meshFilters = GetComponentsInChildren<MeshFilter>();
|
||||||
|
|
||||||
|
foreach (var meshFilter in meshFilters)
|
||||||
|
{
|
||||||
|
if (meshFilter != null && meshFilter.sharedMesh != null)
|
||||||
|
{
|
||||||
|
var shapeAuthoring = GetComponent<PhysicsShapeAuthoring>(meshFilter);
|
||||||
|
if (shapeAuthoring != null && shapeAuthoring != shape)
|
||||||
|
{
|
||||||
|
// Skip this case, since it will be treated independently
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshes.Add(meshFilter.sharedMesh);
|
||||||
|
|
||||||
|
// Don't calculate the children to shape if not needed, to avoid approximation that could prevent collider to be shared
|
||||||
|
if (shape.transform.localToWorldMatrix.Equals(meshFilter.transform.localToWorldMatrix))
|
||||||
|
childrenToShape.Add(float4x4.identity);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var transform = math.mul(shape.transform.worldToLocalMatrix, meshFilter.transform.localToWorldMatrix);
|
||||||
|
childrenToShape.Add(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
DependsOn(meshes.Last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return meshes.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityEngine.Mesh CombineMeshes(PhysicsShapeAuthoring shape, List<UnityEngine.Mesh> meshes, List<float4x4> childrenToShape)
|
||||||
|
{
|
||||||
|
var instances = new List<CombineInstance>();
|
||||||
|
var numVertices = 0;
|
||||||
|
for (var i = 0; i < meshes.Count; ++i)
|
||||||
|
{
|
||||||
|
var currentMesh = meshes[i];
|
||||||
|
var currentChildToShape = childrenToShape[i];
|
||||||
|
if (!currentMesh.IsValidForConversion(shape.gameObject))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Mesh '{currentMesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine submeshes manually
|
||||||
|
numVertices += meshes[i].vertexCount;
|
||||||
|
var combinedSubmeshes = new UnityEngine.Mesh();
|
||||||
|
combinedSubmeshes.vertices = currentMesh.vertices;
|
||||||
|
|
||||||
|
var combinedIndices = new List<int>();
|
||||||
|
for (int indexSubMesh = 0; indexSubMesh < meshes[i].subMeshCount; ++indexSubMesh)
|
||||||
|
{
|
||||||
|
combinedIndices.AddRange(currentMesh.GetIndices(indexSubMesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedSubmeshes.SetIndices(combinedIndices, MeshTopology.Triangles, 0);
|
||||||
|
combinedSubmeshes.RecalculateNormals();
|
||||||
|
var instance = new CombineInstance
|
||||||
|
{
|
||||||
|
mesh = combinedSubmeshes,
|
||||||
|
transform = currentChildToShape,
|
||||||
|
};
|
||||||
|
instances.Add(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mesh = new UnityEngine.Mesh();
|
||||||
|
mesh.indexFormat = numVertices > UInt16.MaxValue ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
||||||
|
mesh.CombineMeshes(instances.ToArray());
|
||||||
|
mesh.RecalculateBounds();
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShapeComputationDataBaking GenerateComputationData(PhysicsShapeAuthoring shape, ColliderInstanceBaking colliderInstance, Entity colliderEntity)
|
||||||
|
{
|
||||||
|
var res = new ShapeComputationDataBaking
|
||||||
|
{
|
||||||
|
Instance = colliderInstance,
|
||||||
|
Material = ProduceMaterial(shape),
|
||||||
|
CollisionFilter = ProduceCollisionFilter(shape),
|
||||||
|
ForceUniqueIdentifier = shape.ForceUnique ? (uint)shape.GetInstanceID() : 0u
|
||||||
|
};
|
||||||
|
|
||||||
|
var transform = shape.transform;
|
||||||
|
var localToWorld = transform.localToWorldMatrix;
|
||||||
|
var shapeToWorld = shape.GetShapeToWorldMatrix();
|
||||||
|
EulerAngles orientation;
|
||||||
|
|
||||||
|
res.ShapeType = shape.ShapeType;
|
||||||
|
switch (shape.ShapeType)
|
||||||
|
{
|
||||||
|
case ShapeType.Box:
|
||||||
|
{
|
||||||
|
res.BoxProperties = shape.GetBoxProperties(out orientation)
|
||||||
|
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.Capsule:
|
||||||
|
{
|
||||||
|
res.CapsuleProperties = shape.GetCapsuleProperties()
|
||||||
|
.BakeToBodySpace(localToWorld, shapeToWorld)
|
||||||
|
.ToRuntime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.Sphere:
|
||||||
|
{
|
||||||
|
res.SphereProperties = shape.GetSphereProperties(out orientation)
|
||||||
|
.BakeToBodySpace(localToWorld, shapeToWorld, ref orientation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.Cylinder:
|
||||||
|
{
|
||||||
|
res.CylinderProperties = shape.GetCylinderProperties(out orientation)
|
||||||
|
.BakeToBodySpace(localToWorld, shapeToWorld, orientation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.Plane:
|
||||||
|
{
|
||||||
|
shape.GetPlaneProperties(out var center, out var size, out orientation);
|
||||||
|
PhysicsShapeExtensions.BakeToBodySpace(
|
||||||
|
center, size, orientation, localToWorld, shapeToWorld,
|
||||||
|
out res.PlaneVertices.c0, out res.PlaneVertices.c1, out res.PlaneVertices.c2, out res.PlaneVertices.c3
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.ConvexHull:
|
||||||
|
{
|
||||||
|
res.ConvexHullProperties.Filter = res.CollisionFilter;
|
||||||
|
res.ConvexHullProperties.Material = res.Material;
|
||||||
|
res.ConvexHullProperties.GenerationParameters = shape.ConvexHullGenerationParameters.ToRunTime();
|
||||||
|
|
||||||
|
CreateMeshAuthoringData(shape, colliderEntity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShapeType.Mesh:
|
||||||
|
{
|
||||||
|
res.MeshProperties.Filter = res.CollisionFilter;
|
||||||
|
res.MeshProperties.Material = res.Material;
|
||||||
|
|
||||||
|
CreateMeshAuthoringData(shape, colliderEntity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateMeshAuthoringData(PhysicsShapeAuthoring shape, Entity colliderEntity)
|
||||||
|
{
|
||||||
|
if (GetMeshes(shape, out var meshes, out var childrenToShape))
|
||||||
|
{
|
||||||
|
// Combine all detected meshes into a single one
|
||||||
|
var mesh = CombineMeshes(shape, meshes, childrenToShape);
|
||||||
|
if (!mesh.IsValidForConversion(shape.gameObject))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Mesh '{mesh}' assigned on {shape.name} is not readable. Ensure that you have enabled Read/Write on its import settings."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bakeFromShape = shape.GetLocalToShapeMatrix();
|
||||||
|
var meshBakingData = new PhysicsMeshAuthoringData()
|
||||||
|
{
|
||||||
|
Convex = shape.ShapeType == ShapeType.ConvexHull,
|
||||||
|
Mesh = mesh,
|
||||||
|
BakeFromShape = bakeFromShape,
|
||||||
|
MeshBounds = mesh.bounds,
|
||||||
|
ChildToShape = float4x4.identity
|
||||||
|
};
|
||||||
|
AddComponent(colliderEntity, meshBakingData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No {nameof(PhysicsShapeAuthoring.CustomMesh)} or {nameof(MeshFilter.sharedMesh)} assigned on {shape.name}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Bake(PhysicsShapeAuthoring authoring)
|
||||||
|
{
|
||||||
|
var shapeBakingData = new PhysicsColliderAuthoringData();
|
||||||
|
|
||||||
|
// First pass
|
||||||
|
Profiler.BeginSample("Collect Inputs from Authoring Components");
|
||||||
|
|
||||||
|
if (ShouldConvertShape(authoring))
|
||||||
|
{
|
||||||
|
// We can have multiple Colliders of the same type on the same game object, so instead of adding the components to the baking entity
|
||||||
|
// we add the components to an additional entity. These new entities will be processed by the baking system
|
||||||
|
var colliderEntity = CreateAdditionalEntity(TransformUsageFlags.None, true);
|
||||||
|
shapeBakingData.ShapeComputationalData = GetInputDataFromAuthoringComponent(authoring, colliderEntity);
|
||||||
|
AddComponent(colliderEntity, shapeBakingData);
|
||||||
|
|
||||||
|
// The data will be filled in by the BaseShapeBakingSystem, but we add it here so it gets reverted from the entity if the collider component is deleted
|
||||||
|
AddComponent(colliderEntity, new PhysicsColliderBakedData()
|
||||||
|
{
|
||||||
|
BodyEntity = shapeBakingData.ShapeComputationalData.Instance.BodyEntity,
|
||||||
|
BodyFromShape = shapeBakingData.ShapeComputationalData.Instance.BodyFromShape,
|
||||||
|
ChildEntity = shapeBakingData.ShapeComputationalData.Instance.ChildEntity,
|
||||||
|
// It is a leaf if the Shape Entity equals Body Entity
|
||||||
|
IsLeafEntityBody = (shapeBakingData.ShapeComputationalData.Instance.ShapeEntity.Equals(shapeBakingData.ShapeComputationalData.Instance.BodyEntity))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Profiler.EndSample();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c7b2bb3849ee2c24ea6f8416bc3c8ee0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
/// <summary> The physics body authoring. This class cannot be inherited. </summary>
|
||||||
|
#if UNITY_2021_2_OR_NEWER
|
||||||
|
[Icon(k_IconPath)]
|
||||||
|
#endif
|
||||||
|
[AddComponentMenu("Entities/Physics/Physics Body")]
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class PhysicsBodyAuthoring : MonoBehaviour
|
||||||
|
{
|
||||||
|
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_Rigidbody@64.png";
|
||||||
|
|
||||||
|
PhysicsBodyAuthoring() {}
|
||||||
|
|
||||||
|
public BodyMotionType MotionType { get => m_MotionType; set => m_MotionType = value; }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Specifies whether the body should be fully physically simulated, moved directly, or fixed in place.")]
|
||||||
|
BodyMotionType m_MotionType;
|
||||||
|
|
||||||
|
public BodySmoothing Smoothing { get => m_Smoothing; set => m_Smoothing = value; }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Specifies how this body's motion in its graphics representation should be smoothed when the rendering framerate is greater than the fixed step rate used by physics.")]
|
||||||
|
BodySmoothing m_Smoothing = BodySmoothing.None;
|
||||||
|
|
||||||
|
const float k_MinimumMass = 0.001f;
|
||||||
|
|
||||||
|
public float Mass
|
||||||
|
{
|
||||||
|
get => m_MotionType == BodyMotionType.Dynamic ? m_Mass : float.PositiveInfinity;
|
||||||
|
set => m_Mass = math.max(k_MinimumMass, value);
|
||||||
|
}
|
||||||
|
[SerializeField]
|
||||||
|
float m_Mass = 1.0f;
|
||||||
|
|
||||||
|
public float LinearDamping { get => m_LinearDamping; set => m_LinearDamping = math.max(0f, value); }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("This is applied to a body's linear velocity reducing it over time.")]
|
||||||
|
float m_LinearDamping = 0.01f;
|
||||||
|
|
||||||
|
public float AngularDamping { get => m_AngularDamping; set => m_AngularDamping = math.max(0f, value); }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("This is applied to a body's angular velocity reducing it over time.")]
|
||||||
|
float m_AngularDamping = 0.05f;
|
||||||
|
|
||||||
|
public float3 InitialLinearVelocity { get => m_InitialLinearVelocity; set => m_InitialLinearVelocity = value; }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("The initial linear velocity of the body in world space")]
|
||||||
|
float3 m_InitialLinearVelocity = float3.zero;
|
||||||
|
|
||||||
|
public float3 InitialAngularVelocity { get => m_InitialAngularVelocity; set => m_InitialAngularVelocity = value; }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("This represents the initial rotation speed around each axis in the local motion space of the body i.e. around the center of mass")]
|
||||||
|
float3 m_InitialAngularVelocity = float3.zero;
|
||||||
|
|
||||||
|
public float GravityFactor
|
||||||
|
{
|
||||||
|
get => m_MotionType == BodyMotionType.Dynamic ? m_GravityFactor : 0f;
|
||||||
|
set => m_GravityFactor = value;
|
||||||
|
}
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Scales the amount of gravity to apply to this body.")]
|
||||||
|
float m_GravityFactor = 1f;
|
||||||
|
|
||||||
|
public bool OverrideDefaultMassDistribution
|
||||||
|
{
|
||||||
|
#pragma warning disable 618
|
||||||
|
get => m_OverrideDefaultMassDistribution;
|
||||||
|
set => m_OverrideDefaultMassDistribution = value;
|
||||||
|
#pragma warning restore 618
|
||||||
|
}
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("Default mass distribution is based on the shapes associated with this body.")]
|
||||||
|
bool m_OverrideDefaultMassDistribution;
|
||||||
|
|
||||||
|
public MassDistribution CustomMassDistribution
|
||||||
|
{
|
||||||
|
get => new MassDistribution
|
||||||
|
{
|
||||||
|
Transform = new RigidTransform(m_Orientation, m_CenterOfMass),
|
||||||
|
InertiaTensor =
|
||||||
|
m_MotionType == BodyMotionType.Dynamic ? m_InertiaTensor : new float3(float.PositiveInfinity)
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_CenterOfMass = value.Transform.pos;
|
||||||
|
m_Orientation.SetValue(value.Transform.rot);
|
||||||
|
m_InertiaTensor = value.InertiaTensor;
|
||||||
|
#pragma warning disable 618
|
||||||
|
m_OverrideDefaultMassDistribution = true;
|
||||||
|
#pragma warning restore 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float3 m_CenterOfMass;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
EulerAngles m_Orientation = EulerAngles.Default;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
// Default value to solid unit sphere : https://en.wikipedia.org/wiki/List_of_moments_of_inertia
|
||||||
|
float3 m_InertiaTensor = new float3(2f / 5f);
|
||||||
|
|
||||||
|
public uint WorldIndex { get => m_WorldIndex; set => m_WorldIndex = value; }
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("The index of the physics world this body belongs to. Default physics world has index 0.")]
|
||||||
|
uint m_WorldIndex = 0;
|
||||||
|
|
||||||
|
public CustomPhysicsBodyTags CustomTags { get => m_CustomTags; set => m_CustomTags = value; }
|
||||||
|
[SerializeField]
|
||||||
|
CustomPhysicsBodyTags m_CustomTags = CustomPhysicsBodyTags.Nothing;
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
// included so tick box appears in Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
m_Mass = math.max(k_MinimumMass, m_Mass);
|
||||||
|
m_LinearDamping = math.max(m_LinearDamping, 0f);
|
||||||
|
m_AngularDamping = math.max(m_AngularDamping, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ccea9ea98e38942e0b0938c27ed1903e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,929 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Unity.Burst;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using Unity.Jobs;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
public sealed class UnimplementedShapeException : NotImplementedException
|
||||||
|
{
|
||||||
|
public UnimplementedShapeException(ShapeType shapeType)
|
||||||
|
: base($"Unknown shape type {shapeType} requires explicit implementation") {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_2021_2_OR_NEWER
|
||||||
|
[Icon(k_IconPath)]
|
||||||
|
#endif
|
||||||
|
[AddComponentMenu("Entities/Physics/Physics Shape")]
|
||||||
|
public sealed class PhysicsShapeAuthoring : MonoBehaviour, IInheritPhysicsMaterialProperties, ISerializationCallbackReceiver
|
||||||
|
{
|
||||||
|
const string k_IconPath = "Packages/com.unity.physics/Unity.Physics.Editor/Editor Default Resources/Icons/d_BoxCollider@64.png";
|
||||||
|
|
||||||
|
PhysicsShapeAuthoring() {}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
struct CylindricalProperties
|
||||||
|
{
|
||||||
|
public float Height;
|
||||||
|
public float Radius;
|
||||||
|
[HideInInspector]
|
||||||
|
public int Axis;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly int[] k_NextAxis = { 1, 2, 0 };
|
||||||
|
|
||||||
|
public ShapeType ShapeType => m_ShapeType;
|
||||||
|
[SerializeField]
|
||||||
|
ShapeType m_ShapeType = ShapeType.Box;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float3 m_PrimitiveCenter;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float3 m_PrimitiveSize = new float3(1f, 1f, 1f);
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
EulerAngles m_PrimitiveOrientation = EulerAngles.Default;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[ExpandChildren]
|
||||||
|
CylindricalProperties m_Capsule = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[ExpandChildren]
|
||||||
|
CylindricalProperties m_Cylinder = new CylindricalProperties { Height = 1f, Radius = 0.5f, Axis = 2 };
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("How many sides the convex cylinder shape should have.")]
|
||||||
|
[Range(CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount)]
|
||||||
|
int m_CylinderSideCount = 20;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float m_SphereRadius = 0.5f;
|
||||||
|
|
||||||
|
public ConvexHullGenerationParameters ConvexHullGenerationParameters => m_ConvexHullGenerationParameters;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip(
|
||||||
|
"Specifies the minimum weight of a skinned vertex assigned to this shape and/or its transform children required for it to be included for automatic detection. " +
|
||||||
|
"A value of 0 will include all points with any weight assigned to this shape's hierarchy."
|
||||||
|
)]
|
||||||
|
[Range(0f, 1f)]
|
||||||
|
float m_MinimumSkinnedVertexWeight = 0.1f;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[ExpandChildren]
|
||||||
|
ConvexHullGenerationParameters m_ConvexHullGenerationParameters = ConvexHullGenerationParameters.Default.ToAuthoring();
|
||||||
|
|
||||||
|
// TODO: remove this accessor in favor of GetRawVertices() when blob data is serializable
|
||||||
|
internal UnityEngine.Mesh CustomMesh => m_CustomMesh;
|
||||||
|
[SerializeField]
|
||||||
|
[Tooltip("If no custom mesh is specified, then one will be generated using this body's rendered meshes.")]
|
||||||
|
UnityEngine.Mesh m_CustomMesh;
|
||||||
|
|
||||||
|
public bool ForceUnique { get => m_ForceUnique; set => m_ForceUnique = value; }
|
||||||
|
[SerializeField]
|
||||||
|
bool m_ForceUnique;
|
||||||
|
|
||||||
|
public PhysicsMaterialTemplate MaterialTemplate { get => m_Material.Template; set => m_Material.Template = value; }
|
||||||
|
PhysicsMaterialTemplate IInheritPhysicsMaterialProperties.Template
|
||||||
|
{
|
||||||
|
get => m_Material.Template;
|
||||||
|
set => m_Material.Template = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverrideCollisionResponse { get => m_Material.OverrideCollisionResponse; set => m_Material.OverrideCollisionResponse = value; }
|
||||||
|
|
||||||
|
public CollisionResponsePolicy CollisionResponse { get => m_Material.CollisionResponse; set => m_Material.CollisionResponse = value; }
|
||||||
|
|
||||||
|
public bool OverrideFriction { get => m_Material.OverrideFriction; set => m_Material.OverrideFriction = value; }
|
||||||
|
|
||||||
|
public PhysicsMaterialCoefficient Friction { get => m_Material.Friction; set => m_Material.Friction = value; }
|
||||||
|
|
||||||
|
public bool OverrideRestitution
|
||||||
|
{
|
||||||
|
get => m_Material.OverrideRestitution;
|
||||||
|
set => m_Material.OverrideRestitution = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsMaterialCoefficient Restitution
|
||||||
|
{
|
||||||
|
get => m_Material.Restitution;
|
||||||
|
set => m_Material.Restitution = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverrideBelongsTo
|
||||||
|
{
|
||||||
|
get => m_Material.OverrideBelongsTo;
|
||||||
|
set => m_Material.OverrideBelongsTo = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsCategoryTags BelongsTo
|
||||||
|
{
|
||||||
|
get => m_Material.BelongsTo;
|
||||||
|
set => m_Material.BelongsTo = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverrideCollidesWith
|
||||||
|
{
|
||||||
|
get => m_Material.OverrideCollidesWith;
|
||||||
|
set => m_Material.OverrideCollidesWith = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsCategoryTags CollidesWith
|
||||||
|
{
|
||||||
|
get => m_Material.CollidesWith;
|
||||||
|
set => m_Material.CollidesWith = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverrideCustomTags
|
||||||
|
{
|
||||||
|
get => m_Material.OverrideCustomTags;
|
||||||
|
set => m_Material.OverrideCustomTags = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomPhysicsMaterialTags CustomTags { get => m_Material.CustomTags; set => m_Material.CustomTags = value; }
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
PhysicsMaterialProperties m_Material = new PhysicsMaterialProperties(true);
|
||||||
|
|
||||||
|
public BoxGeometry GetBoxProperties() => GetBoxProperties(out _);
|
||||||
|
|
||||||
|
internal BoxGeometry GetBoxProperties(out EulerAngles orientation)
|
||||||
|
{
|
||||||
|
orientation = m_PrimitiveOrientation;
|
||||||
|
return new BoxGeometry
|
||||||
|
{
|
||||||
|
Center = m_PrimitiveCenter,
|
||||||
|
Size = m_PrimitiveSize,
|
||||||
|
Orientation = m_PrimitiveOrientation,
|
||||||
|
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetCylindricalProperties(
|
||||||
|
CylindricalProperties props,
|
||||||
|
out float3 center, out float height, out float radius, out EulerAngles orientation,
|
||||||
|
bool rebuildOrientation
|
||||||
|
)
|
||||||
|
{
|
||||||
|
center = m_PrimitiveCenter;
|
||||||
|
var lookVector = math.mul(m_PrimitiveOrientation, new float3 { [props.Axis] = 1f });
|
||||||
|
// use previous axis so forward will prefer up
|
||||||
|
var upVector = math.mul(m_PrimitiveOrientation, new float3 { [k_NextAxis[k_NextAxis[props.Axis]]] = 1f });
|
||||||
|
orientation = m_PrimitiveOrientation;
|
||||||
|
if (rebuildOrientation && props.Axis != 2)
|
||||||
|
orientation.SetValue(quaternion.LookRotation(lookVector, upVector));
|
||||||
|
radius = props.Radius;
|
||||||
|
height = props.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CapsuleGeometryAuthoring GetCapsuleProperties()
|
||||||
|
{
|
||||||
|
GetCylindricalProperties(
|
||||||
|
m_Capsule, out var center, out var height, out var radius, out var orientationEuler, m_ShapeType != ShapeType.Capsule
|
||||||
|
);
|
||||||
|
return new CapsuleGeometryAuthoring
|
||||||
|
{
|
||||||
|
OrientationEuler = orientationEuler,
|
||||||
|
Center = center,
|
||||||
|
Height = height,
|
||||||
|
Radius = radius
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CylinderGeometry GetCylinderProperties() => GetCylinderProperties(out _);
|
||||||
|
|
||||||
|
internal CylinderGeometry GetCylinderProperties(out EulerAngles orientation)
|
||||||
|
{
|
||||||
|
GetCylindricalProperties(
|
||||||
|
m_Cylinder, out var center, out var height, out var radius, out orientation, m_ShapeType != ShapeType.Cylinder
|
||||||
|
);
|
||||||
|
return new CylinderGeometry
|
||||||
|
{
|
||||||
|
Center = center,
|
||||||
|
Height = height,
|
||||||
|
Radius = radius,
|
||||||
|
Orientation = orientation,
|
||||||
|
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius,
|
||||||
|
SideCount = m_CylinderSideCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public SphereGeometry GetSphereProperties(out quaternion orientation)
|
||||||
|
{
|
||||||
|
var result = GetSphereProperties(out EulerAngles euler);
|
||||||
|
orientation = euler;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SphereGeometry GetSphereProperties(out EulerAngles orientation)
|
||||||
|
{
|
||||||
|
orientation = m_PrimitiveOrientation;
|
||||||
|
return new SphereGeometry
|
||||||
|
{
|
||||||
|
Center = m_PrimitiveCenter,
|
||||||
|
Radius = m_SphereRadius
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetPlaneProperties(out float3 center, out float2 size, out quaternion orientation)
|
||||||
|
{
|
||||||
|
GetPlaneProperties(out center, out size, out EulerAngles euler);
|
||||||
|
orientation = euler;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void GetPlaneProperties(out float3 center, out float2 size, out EulerAngles orientation)
|
||||||
|
{
|
||||||
|
center = m_PrimitiveCenter;
|
||||||
|
orientation = m_PrimitiveOrientation;
|
||||||
|
|
||||||
|
if (m_ShapeType == ShapeType.Plane)
|
||||||
|
{
|
||||||
|
size = m_PrimitiveSize.xz;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCapsuleAxis();
|
||||||
|
var look = m_Capsule.Axis;
|
||||||
|
var nextAx = k_NextAxis[look];
|
||||||
|
var prevAx = k_NextAxis[k_NextAxis[look]];
|
||||||
|
var ax2 = m_PrimitiveSize[nextAx] > m_PrimitiveSize[prevAx] ? nextAx : prevAx;
|
||||||
|
size = new float2(m_PrimitiveSize[ax2], m_PrimitiveSize[look]);
|
||||||
|
|
||||||
|
var up = k_NextAxis[ax2] == look ? k_NextAxis[look] : k_NextAxis[ax2];
|
||||||
|
var offset = quaternion.LookRotation(new float3 { [look] = 1f }, new float3 { [up] = 1f });
|
||||||
|
|
||||||
|
orientation.SetValue(math.mul(m_PrimitiveOrientation, offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly HashSet<int> s_BoneIDs = new HashSet<int>();
|
||||||
|
static readonly HashSet<Transform> s_BonesInHierarchy = new HashSet<Transform>();
|
||||||
|
static readonly List<Vector3> s_Vertices = new List<Vector3>(65535);
|
||||||
|
static readonly List<int> s_Indices = new List<int>(65535);
|
||||||
|
|
||||||
|
static UnityEngine.Mesh ReusableBakeMesh =>
|
||||||
|
s_ReusableBakeMesh ??
|
||||||
|
(s_ReusableBakeMesh = new UnityEngine.Mesh { hideFlags = HideFlags.HideAndDontSave });
|
||||||
|
static UnityEngine.Mesh s_ReusableBakeMesh;
|
||||||
|
|
||||||
|
public void GetConvexHullProperties(NativeList<float3> pointCloud) =>
|
||||||
|
GetConvexHullProperties(pointCloud, true, default, default, default, default);
|
||||||
|
|
||||||
|
internal void GetConvexHullProperties(
|
||||||
|
NativeList<float3> pointCloud, bool validate,
|
||||||
|
NativeList<HashableShapeInputs> inputs, NativeList<int> allSkinIndices, NativeList<float> allBlendShapeWeights,
|
||||||
|
HashSet<UnityEngine.Mesh> meshAssets
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (pointCloud.IsCreated)
|
||||||
|
pointCloud.Clear();
|
||||||
|
if (inputs.IsCreated)
|
||||||
|
inputs.Clear();
|
||||||
|
if (allSkinIndices.IsCreated)
|
||||||
|
allSkinIndices.Clear();
|
||||||
|
if (allBlendShapeWeights.IsCreated)
|
||||||
|
allBlendShapeWeights.Clear();
|
||||||
|
meshAssets?.Clear();
|
||||||
|
|
||||||
|
if (m_CustomMesh != null)
|
||||||
|
{
|
||||||
|
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppendMeshPropertiesToNativeBuffers(
|
||||||
|
transform.localToWorldMatrix, m_CustomMesh, pointCloud, default, validate, inputs, meshAssets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
|
||||||
|
{
|
||||||
|
foreach (var meshFilter in scope.Buffer)
|
||||||
|
{
|
||||||
|
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
|
||||||
|
{
|
||||||
|
AppendMeshPropertiesToNativeBuffers(
|
||||||
|
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, pointCloud, default, validate, inputs, meshAssets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var skinnedPoints = new NativeList<float3>(8192, Allocator.Temp))
|
||||||
|
using (var skinnedInputs = new NativeList<HashableShapeInputs>(8, Allocator.Temp))
|
||||||
|
{
|
||||||
|
GetAllSkinnedPointsInHierarchyBelongingToShape(
|
||||||
|
this, skinnedPoints, validate, skinnedInputs, allSkinIndices, allBlendShapeWeights
|
||||||
|
);
|
||||||
|
if (pointCloud.IsCreated)
|
||||||
|
pointCloud.AddRange(skinnedPoints.AsArray());
|
||||||
|
if (inputs.IsCreated)
|
||||||
|
inputs.AddRange(skinnedInputs.AsArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void GetAllSkinnedPointsInHierarchyBelongingToShape(
|
||||||
|
PhysicsShapeAuthoring shape, NativeList<float3> pointCloud, bool validate,
|
||||||
|
NativeList<HashableShapeInputs> inputs, NativeList<int> allIncludedIndices, NativeList<float> allBlendShapeWeights
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (pointCloud.IsCreated)
|
||||||
|
pointCloud.Clear();
|
||||||
|
|
||||||
|
if (inputs.IsCreated)
|
||||||
|
inputs.Clear();
|
||||||
|
|
||||||
|
// get all the transforms that belong to this shape
|
||||||
|
s_BonesInHierarchy.Clear();
|
||||||
|
using (var scope = new GetActiveChildrenScope<Transform>(shape, shape.transform))
|
||||||
|
{
|
||||||
|
foreach (var bone in scope.Buffer)
|
||||||
|
{
|
||||||
|
if (scope.IsChildActiveAndBelongsToShape(bone))
|
||||||
|
s_BonesInHierarchy.Add(bone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all skinned mesh renderers in which this shape's transform might be a bone
|
||||||
|
using (var scope = new GetActiveChildrenScope<SkinnedMeshRenderer>(shape, shape.transform.root))
|
||||||
|
{
|
||||||
|
foreach (var skin in scope.Buffer)
|
||||||
|
{
|
||||||
|
var mesh = skin.sharedMesh;
|
||||||
|
if (
|
||||||
|
!skin.enabled
|
||||||
|
|| mesh == null
|
||||||
|
|| validate && !mesh.IsValidForConversion(shape.gameObject)
|
||||||
|
|| !scope.IsChildActiveAndBelongsToShape(skin)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// get indices of this shape's transform hierarchy in skinned mesh's bone array
|
||||||
|
s_BoneIDs.Clear();
|
||||||
|
var bones = skin.bones;
|
||||||
|
for (int i = 0, count = bones.Length; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (s_BonesInHierarchy.Contains(bones[i]))
|
||||||
|
s_BoneIDs.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_BoneIDs.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// sample the vertices
|
||||||
|
if (pointCloud.IsCreated)
|
||||||
|
{
|
||||||
|
skin.BakeMesh(ReusableBakeMesh);
|
||||||
|
ReusableBakeMesh.GetVertices(s_Vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all vertices weighted to at least one bone in this shape's transform hierarchy
|
||||||
|
var bonesPerVertex = mesh.GetBonesPerVertex(); // Allocator.None
|
||||||
|
var weights = mesh.GetAllBoneWeights(); // Allocator.None
|
||||||
|
var vertexIndex = 0;
|
||||||
|
var weightsOffset = 0;
|
||||||
|
var shapeFromSkin = math.mul(shape.transform.worldToLocalMatrix, skin.transform.localToWorldMatrix);
|
||||||
|
var includedIndices = new NativeList<int>(mesh.vertexCount, Allocator.Temp);
|
||||||
|
foreach (var weightCount in bonesPerVertex)
|
||||||
|
{
|
||||||
|
var totalWeight = 0f;
|
||||||
|
for (var i = 0; i < weightCount; ++i)
|
||||||
|
{
|
||||||
|
var weight = weights[weightsOffset + i];
|
||||||
|
if (s_BoneIDs.Contains(weight.boneIndex))
|
||||||
|
totalWeight += weight.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight > shape.m_MinimumSkinnedVertexWeight)
|
||||||
|
{
|
||||||
|
if (pointCloud.IsCreated)
|
||||||
|
pointCloud.Add(math.mul(shapeFromSkin, new float4(s_Vertices[vertexIndex], 1f)).xyz);
|
||||||
|
includedIndices.Add(vertexIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
weightsOffset += weightCount;
|
||||||
|
++vertexIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputs.IsCreated || !allIncludedIndices.IsCreated || !allBlendShapeWeights.IsCreated)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var blendShapeWeights = new NativeArray<float>(mesh.blendShapeCount, Allocator.Temp);
|
||||||
|
for (var i = 0; i < blendShapeWeights.Length; ++i)
|
||||||
|
blendShapeWeights[i] = skin.GetBlendShapeWeight(i);
|
||||||
|
|
||||||
|
var data = HashableShapeInputs.FromSkinnedMesh(
|
||||||
|
mesh, shapeFromSkin, includedIndices.AsArray(), allIncludedIndices, blendShapeWeights, allBlendShapeWeights
|
||||||
|
);
|
||||||
|
inputs.Add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_BonesInHierarchy.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetMeshProperties(NativeList<float3> vertices, NativeList<int3> triangles) =>
|
||||||
|
GetMeshProperties(vertices, triangles, true, default);
|
||||||
|
|
||||||
|
internal void GetMeshProperties(
|
||||||
|
NativeList<float3> vertices, NativeList<int3> triangles, bool validate, NativeList<HashableShapeInputs> inputs, HashSet<UnityEngine.Mesh> meshAssets = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (vertices.IsCreated)
|
||||||
|
vertices.Clear();
|
||||||
|
if (triangles.IsCreated)
|
||||||
|
triangles.Clear();
|
||||||
|
if (inputs.IsCreated)
|
||||||
|
inputs.Clear();
|
||||||
|
meshAssets?.Clear();
|
||||||
|
|
||||||
|
if (m_CustomMesh != null)
|
||||||
|
{
|
||||||
|
if (validate && !m_CustomMesh.IsValidForConversion(gameObject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppendMeshPropertiesToNativeBuffers(
|
||||||
|
transform.localToWorldMatrix, m_CustomMesh, vertices, triangles, validate, inputs, meshAssets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var scope = new GetActiveChildrenScope<MeshFilter>(this, transform))
|
||||||
|
{
|
||||||
|
foreach (var meshFilter in scope.Buffer)
|
||||||
|
{
|
||||||
|
if (scope.IsChildActiveAndBelongsToShape(meshFilter, validate))
|
||||||
|
{
|
||||||
|
AppendMeshPropertiesToNativeBuffers(
|
||||||
|
meshFilter.transform.localToWorldMatrix, meshFilter.sharedMesh, vertices, triangles, validate, inputs, meshAssets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendMeshPropertiesToNativeBuffers(
|
||||||
|
float4x4 localToWorld, UnityEngine.Mesh mesh, NativeList<float3> vertices, NativeList<int3> triangles, bool validate,
|
||||||
|
NativeList<HashableShapeInputs> inputs, HashSet<UnityEngine.Mesh> meshAssets
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (mesh == null || validate && !mesh.IsValidForConversion(gameObject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var childToShape = math.mul(transform.worldToLocalMatrix, localToWorld);
|
||||||
|
|
||||||
|
AppendMeshPropertiesToNativeBuffers(childToShape, mesh, vertices, triangles, inputs, meshAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void AppendMeshPropertiesToNativeBuffers(
|
||||||
|
float4x4 childToShape, UnityEngine.Mesh mesh, NativeList<float3> vertices, NativeList<int3> triangles,
|
||||||
|
NativeList<HashableShapeInputs> inputs, HashSet<UnityEngine.Mesh> meshAssets
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var offset = 0u;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
// TODO: when min spec is 2020.1, collect all meshes and their data via single Burst job rather than one at a time
|
||||||
|
using (var meshData = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh))
|
||||||
|
#else
|
||||||
|
using (var meshData = UnityEngine.Mesh.AcquireReadOnlyMeshData(mesh))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (vertices.IsCreated)
|
||||||
|
{
|
||||||
|
offset = (uint)vertices.Length;
|
||||||
|
var tmpVertices = new NativeArray<Vector3>(meshData[0].vertexCount, Allocator.Temp);
|
||||||
|
meshData[0].GetVertices(tmpVertices);
|
||||||
|
if (vertices.Capacity < vertices.Length + tmpVertices.Length)
|
||||||
|
vertices.Capacity = vertices.Length + tmpVertices.Length;
|
||||||
|
foreach (var v in tmpVertices)
|
||||||
|
vertices.Add(math.mul(childToShape, new float4(v, 1f)).xyz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangles.IsCreated)
|
||||||
|
{
|
||||||
|
switch (meshData[0].indexFormat)
|
||||||
|
{
|
||||||
|
case IndexFormat.UInt16:
|
||||||
|
var indices16 = meshData[0].GetIndexData<ushort>();
|
||||||
|
var numTriangles = indices16.Length / 3;
|
||||||
|
if (triangles.Capacity < triangles.Length + numTriangles)
|
||||||
|
triangles.Capacity = triangles.Length + numTriangles;
|
||||||
|
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
|
||||||
|
{
|
||||||
|
var subMesh = meshData[0].GetSubMesh(sm);
|
||||||
|
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
|
||||||
|
triangles.Add((int3) new uint3(offset + indices16[i], offset + indices16[i + 1], offset + indices16[i + 2]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IndexFormat.UInt32:
|
||||||
|
var indices32 = meshData[0].GetIndexData<uint>();
|
||||||
|
numTriangles = indices32.Length / 3;
|
||||||
|
if (triangles.Capacity < triangles.Length + numTriangles)
|
||||||
|
triangles.Capacity = triangles.Length + numTriangles;
|
||||||
|
for (var sm = 0; sm < meshData[0].subMeshCount; ++sm)
|
||||||
|
{
|
||||||
|
var subMesh = meshData[0].GetSubMesh(sm);
|
||||||
|
for (int i = subMesh.indexStart, count = 0; count < subMesh.indexCount; i += 3, count += 3)
|
||||||
|
triangles.Add((int3) new uint3(offset + indices32[i], offset + indices32[i + 1], offset + indices32[i + 2]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.IsCreated)
|
||||||
|
inputs.Add(HashableShapeInputs.FromMesh(mesh, childToShape));
|
||||||
|
|
||||||
|
meshAssets?.Add(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCapsuleAxis()
|
||||||
|
{
|
||||||
|
var cmax = math.cmax(m_PrimitiveSize);
|
||||||
|
var cmin = math.cmin(m_PrimitiveSize);
|
||||||
|
if (cmin == cmax)
|
||||||
|
return;
|
||||||
|
m_Capsule.Axis = m_PrimitiveSize.GetMaxAxis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCylinderAxis() => m_Cylinder.Axis = m_PrimitiveSize.GetDeviantAxis();
|
||||||
|
|
||||||
|
void Sync(ref CylindricalProperties props)
|
||||||
|
{
|
||||||
|
props.Height = m_PrimitiveSize[props.Axis];
|
||||||
|
props.Radius = 0.5f * math.max(
|
||||||
|
m_PrimitiveSize[k_NextAxis[props.Axis]],
|
||||||
|
m_PrimitiveSize[k_NextAxis[k_NextAxis[props.Axis]]]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncCapsuleProperties()
|
||||||
|
{
|
||||||
|
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
|
||||||
|
UpdateCapsuleAxis();
|
||||||
|
else
|
||||||
|
m_Capsule.Axis = 2;
|
||||||
|
Sync(ref m_Capsule);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncCylinderProperties()
|
||||||
|
{
|
||||||
|
if (m_ShapeType == ShapeType.Box || m_ShapeType == ShapeType.Plane)
|
||||||
|
UpdateCylinderAxis();
|
||||||
|
else
|
||||||
|
m_Cylinder.Axis = 2;
|
||||||
|
Sync(ref m_Cylinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncSphereProperties()
|
||||||
|
{
|
||||||
|
m_SphereRadius = 0.5f * math.cmax(m_PrimitiveSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetBox(BoxGeometry geometry)
|
||||||
|
{
|
||||||
|
var euler = m_PrimitiveOrientation;
|
||||||
|
euler.SetValue(geometry.Orientation);
|
||||||
|
SetBox(geometry, euler);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetBox(BoxGeometry geometry, EulerAngles orientation)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Box;
|
||||||
|
m_PrimitiveCenter = geometry.Center;
|
||||||
|
m_PrimitiveSize = math.max(geometry.Size, new float3());
|
||||||
|
m_PrimitiveOrientation = orientation;
|
||||||
|
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
|
||||||
|
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCapsule(CapsuleGeometryAuthoring geometry)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Capsule;
|
||||||
|
m_PrimitiveCenter = geometry.Center;
|
||||||
|
m_PrimitiveOrientation = geometry.OrientationEuler;
|
||||||
|
|
||||||
|
var radius = math.max(0f, geometry.Radius);
|
||||||
|
var height = math.max(0f, geometry.Height);
|
||||||
|
m_PrimitiveSize = new float3(radius * 2f, radius * 2f, height);
|
||||||
|
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCylinder(CylinderGeometry geometry)
|
||||||
|
{
|
||||||
|
var euler = m_PrimitiveOrientation;
|
||||||
|
euler.SetValue(geometry.Orientation);
|
||||||
|
SetCylinder(geometry, euler);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetCylinder(CylinderGeometry geometry, EulerAngles orientation)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Cylinder;
|
||||||
|
m_PrimitiveCenter = geometry.Center;
|
||||||
|
m_PrimitiveOrientation = orientation;
|
||||||
|
|
||||||
|
geometry.Radius = math.max(0f, geometry.Radius);
|
||||||
|
geometry.Height = math.max(0f, geometry.Height);
|
||||||
|
m_PrimitiveSize = new float3(geometry.Radius * 2f, geometry.Radius * 2f, geometry.Height);
|
||||||
|
|
||||||
|
m_ConvexHullGenerationParameters.BevelRadius = geometry.BevelRadius;
|
||||||
|
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSphere(SphereGeometry geometry, quaternion orientation)
|
||||||
|
{
|
||||||
|
var euler = m_PrimitiveOrientation;
|
||||||
|
euler.SetValue(orientation);
|
||||||
|
SetSphere(geometry, euler);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetSphere(SphereGeometry geometry)
|
||||||
|
{
|
||||||
|
SetSphere(geometry, m_PrimitiveOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetSphere(SphereGeometry geometry, EulerAngles orientation)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Sphere;
|
||||||
|
m_PrimitiveCenter = geometry.Center;
|
||||||
|
|
||||||
|
var radius = math.max(0f, geometry.Radius);
|
||||||
|
m_PrimitiveSize = new float3(2f * radius, 2f * radius, 2f * radius);
|
||||||
|
|
||||||
|
m_PrimitiveOrientation = orientation;
|
||||||
|
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPlane(float3 center, float2 size, quaternion orientation)
|
||||||
|
{
|
||||||
|
var euler = m_PrimitiveOrientation;
|
||||||
|
euler.SetValue(orientation);
|
||||||
|
SetPlane(center, size, euler);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetPlane(float3 center, float2 size, EulerAngles orientation)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Plane;
|
||||||
|
m_PrimitiveCenter = center;
|
||||||
|
m_PrimitiveOrientation = orientation;
|
||||||
|
m_PrimitiveSize = new float3(size.x, 0f, size.y);
|
||||||
|
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConvexHull(
|
||||||
|
ConvexHullGenerationParameters hullGenerationParameters, float minimumSkinnedVertexWeight
|
||||||
|
)
|
||||||
|
{
|
||||||
|
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
|
||||||
|
SetConvexHull(hullGenerationParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConvexHull(ConvexHullGenerationParameters hullGenerationParameters, UnityEngine.Mesh customMesh = null)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.ConvexHull;
|
||||||
|
m_CustomMesh = customMesh;
|
||||||
|
hullGenerationParameters.OnValidate();
|
||||||
|
m_ConvexHullGenerationParameters = hullGenerationParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMesh(UnityEngine.Mesh mesh = null)
|
||||||
|
{
|
||||||
|
m_ShapeType = ShapeType.Mesh;
|
||||||
|
m_CustomMesh = mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
// included so tick box appears in Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
const int k_LatestVersion = 1;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
int m_SerializedVersion = 0;
|
||||||
|
|
||||||
|
void ISerializationCallbackReceiver.OnBeforeSerialize() {}
|
||||||
|
|
||||||
|
void ISerializationCallbackReceiver.OnAfterDeserialize() => UpgradeVersionIfNecessary();
|
||||||
|
|
||||||
|
#pragma warning disable 618
|
||||||
|
void UpgradeVersionIfNecessary()
|
||||||
|
{
|
||||||
|
if (m_SerializedVersion < k_LatestVersion)
|
||||||
|
{
|
||||||
|
// old data from version < 1 have been removed
|
||||||
|
if (m_SerializedVersion < 1)
|
||||||
|
m_SerializedVersion = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning restore 618
|
||||||
|
|
||||||
|
static void Validate(ref CylindricalProperties props)
|
||||||
|
{
|
||||||
|
props.Height = math.max(0f, props.Height);
|
||||||
|
props.Radius = math.max(0f, props.Radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
UpgradeVersionIfNecessary();
|
||||||
|
|
||||||
|
m_PrimitiveSize = math.max(m_PrimitiveSize, new float3());
|
||||||
|
Validate(ref m_Capsule);
|
||||||
|
Validate(ref m_Cylinder);
|
||||||
|
switch (m_ShapeType)
|
||||||
|
{
|
||||||
|
case ShapeType.Box:
|
||||||
|
SetBox(GetBoxProperties(out var orientation), orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Capsule:
|
||||||
|
SetCapsule(GetCapsuleProperties());
|
||||||
|
break;
|
||||||
|
case ShapeType.Cylinder:
|
||||||
|
SetCylinder(GetCylinderProperties(out orientation), orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Sphere:
|
||||||
|
SetSphere(GetSphereProperties(out orientation), orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Plane:
|
||||||
|
GetPlaneProperties(out var center, out var size2D, out orientation);
|
||||||
|
SetPlane(center, size2D, orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.ConvexHull:
|
||||||
|
case ShapeType.Mesh:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnimplementedShapeException(m_ShapeType);
|
||||||
|
}
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
m_CylinderSideCount =
|
||||||
|
math.clamp(m_CylinderSideCount, CylinderGeometry.MinSideCount, CylinderGeometry.MaxSideCount);
|
||||||
|
m_ConvexHullGenerationParameters.OnValidate();
|
||||||
|
|
||||||
|
PhysicsMaterialProperties.OnValidate(ref m_Material, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// matrix to transform point from shape space into world space
|
||||||
|
public float4x4 GetShapeToWorldMatrix() =>
|
||||||
|
new float4x4(Math.DecomposeRigidBodyTransform(transform.localToWorldMatrix));
|
||||||
|
|
||||||
|
// matrix to transform point from object's local transform matrix into shape space
|
||||||
|
internal float4x4 GetLocalToShapeMatrix() =>
|
||||||
|
math.mul(math.inverse(GetShapeToWorldMatrix()), transform.localToWorldMatrix);
|
||||||
|
|
||||||
|
internal unsafe void BakePoints(NativeArray<float3> points)
|
||||||
|
{
|
||||||
|
var localToShapeQuantized = GetLocalToShapeMatrix();
|
||||||
|
using (var aabb = new NativeArray<Aabb>(1, Allocator.TempJob))
|
||||||
|
{
|
||||||
|
new PhysicsShapeExtensions.GetAabbJob { Points = points, Aabb = aabb }.Run();
|
||||||
|
HashableShapeInputs.GetQuantizedTransformations(localToShapeQuantized, aabb[0], out localToShapeQuantized);
|
||||||
|
}
|
||||||
|
using (var bakedPoints = new NativeArray<float3>(points.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory))
|
||||||
|
{
|
||||||
|
new BakePointsJob
|
||||||
|
{
|
||||||
|
Points = points,
|
||||||
|
LocalToShape = localToShapeQuantized,
|
||||||
|
Output = bakedPoints
|
||||||
|
}.Schedule(points.Length, 16).Complete();
|
||||||
|
|
||||||
|
UnsafeUtility.MemCpy(points.GetUnsafePtr(), bakedPoints.GetUnsafePtr(), points.Length * UnsafeUtility.SizeOf<float3>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BurstCompile]
|
||||||
|
struct BakePointsJob : IJobParallelFor
|
||||||
|
{
|
||||||
|
[Collections.ReadOnly]
|
||||||
|
public NativeArray<float3> Points;
|
||||||
|
public float4x4 LocalToShape;
|
||||||
|
public NativeArray<float3> Output;
|
||||||
|
|
||||||
|
public void Execute(int index) => Output[index] = math.mul(LocalToShape, new float4(Points[index], 1f)).xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fit this shape to render geometry in its GameObject hierarchy. Children in the hierarchy will
|
||||||
|
/// influence the result if they have enabled MeshRenderer components or have vertices bound to
|
||||||
|
/// them on a SkinnedMeshRenderer. Children will only count as influences if this shape is the
|
||||||
|
/// first ancestor shape in their hierarchy. As such, you should add shape components to all
|
||||||
|
/// GameObjects that should have them before you call this method on any of them.
|
||||||
|
/// </summary>
|
||||||
|
///
|
||||||
|
/// <exception cref="UnimplementedShapeException"> Thrown when an Unimplemented Shape error
|
||||||
|
/// condition occurs. </exception>
|
||||||
|
///
|
||||||
|
/// <param name="minimumSkinnedVertexWeight"> (Optional)
|
||||||
|
/// The minimum total weight that a vertex in a skinned mesh must have assigned to this object
|
||||||
|
/// and/or any of its influencing children. </param>
|
||||||
|
|
||||||
|
public void FitToEnabledRenderMeshes(float minimumSkinnedVertexWeight = 0f)
|
||||||
|
{
|
||||||
|
var shapeType = m_ShapeType;
|
||||||
|
m_MinimumSkinnedVertexWeight = minimumSkinnedVertexWeight;
|
||||||
|
|
||||||
|
using (var points = new NativeList<float3>(65535, Allocator.Persistent))
|
||||||
|
{
|
||||||
|
// temporarily un-assign custom mesh and assume this shape is a convex hull
|
||||||
|
var customMesh = m_CustomMesh;
|
||||||
|
m_CustomMesh = null;
|
||||||
|
GetConvexHullProperties(points, Application.isPlaying, default, default, default, default);
|
||||||
|
m_CustomMesh = customMesh;
|
||||||
|
if (points.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: find best rotation, particularly if any points came from skinned mesh
|
||||||
|
var orientation = quaternion.identity;
|
||||||
|
var bounds = new Bounds(points[0], float3.zero);
|
||||||
|
for (int i = 1, count = points.Length; i < count; ++i)
|
||||||
|
bounds.Encapsulate(points[i]);
|
||||||
|
|
||||||
|
SetBox(
|
||||||
|
new BoxGeometry
|
||||||
|
{
|
||||||
|
Center = bounds.center,
|
||||||
|
Size = bounds.size,
|
||||||
|
Orientation = orientation,
|
||||||
|
BevelRadius = m_ConvexHullGenerationParameters.BevelRadius
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (shapeType)
|
||||||
|
{
|
||||||
|
case ShapeType.Capsule:
|
||||||
|
SetCapsule(GetCapsuleProperties());
|
||||||
|
break;
|
||||||
|
case ShapeType.Cylinder:
|
||||||
|
SetCylinder(GetCylinderProperties(out var orientation), orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Sphere:
|
||||||
|
SetSphere(GetSphereProperties(out orientation), orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Plane:
|
||||||
|
// force recalculation of plane orientation by making it think shape type is out of date
|
||||||
|
m_ShapeType = ShapeType.Box;
|
||||||
|
GetPlaneProperties(out var center, out var size2D, out orientation);
|
||||||
|
SetPlane(center, size2D, orientation);
|
||||||
|
break;
|
||||||
|
case ShapeType.Box:
|
||||||
|
case ShapeType.ConvexHull:
|
||||||
|
case ShapeType.Mesh:
|
||||||
|
m_ShapeType = shapeType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnimplementedShapeException(shapeType);
|
||||||
|
}
|
||||||
|
SyncCapsuleProperties();
|
||||||
|
SyncCylinderProperties();
|
||||||
|
SyncSphereProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("UNITY_EDITOR")]
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
InitializeConvexHullGenerationParameters();
|
||||||
|
FitToEnabledRenderMeshes(m_MinimumSkinnedVertexWeight);
|
||||||
|
// TODO: also pick best primitive shape
|
||||||
|
UnityEditor.SceneView.RepaintAll();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeConvexHullGenerationParameters()
|
||||||
|
{
|
||||||
|
var pointCloud = new NativeList<float3>(65535, Allocator.Temp);
|
||||||
|
GetConvexHullProperties(pointCloud, false, default, default, default, default);
|
||||||
|
m_ConvexHullGenerationParameters.InitializeToRecommendedAuthoringValues(pointCloud.AsArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b275e5f92732148048d7b77e264ac30e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(menuName = "Unity Physics/Custom Physics Material Tag Names", fileName = "Custom Material Tag Names", order = 506)]
|
||||||
|
public sealed partial class CustomPhysicsMaterialTagNames : ScriptableObject, ITagNames
|
||||||
|
{
|
||||||
|
CustomPhysicsMaterialTagNames() {}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> TagNames => m_TagNames;
|
||||||
|
[SerializeField]
|
||||||
|
[FormerlySerializedAs("m_FlagNames")]
|
||||||
|
string[] m_TagNames = Enumerable.Range(0, 8).Select(i => string.Empty).ToArray();
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
if (m_TagNames.Length != 8)
|
||||||
|
Array.Resize(ref m_TagNames, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7c8956dd10f66427faa4a4067c5dd97d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {fileID: 2800000, guid: 7c7a0159cb0d5433b90c970978f6cf5c, type: 3}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public struct CustomPhysicsMaterialTags : IEquatable<CustomPhysicsMaterialTags>
|
||||||
|
{
|
||||||
|
public static CustomPhysicsMaterialTags Everything => new CustomPhysicsMaterialTags { Value = unchecked((byte)~0) };
|
||||||
|
public static CustomPhysicsMaterialTags Nothing => new CustomPhysicsMaterialTags { Value = 0 };
|
||||||
|
|
||||||
|
public bool Tag00;
|
||||||
|
public bool Tag01;
|
||||||
|
public bool Tag02;
|
||||||
|
public bool Tag03;
|
||||||
|
public bool Tag04;
|
||||||
|
public bool Tag05;
|
||||||
|
public bool Tag06;
|
||||||
|
public bool Tag07;
|
||||||
|
|
||||||
|
internal bool this[int i]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: return Tag00;
|
||||||
|
case 1: return Tag01;
|
||||||
|
case 2: return Tag02;
|
||||||
|
case 3: return Tag03;
|
||||||
|
case 4: return Tag04;
|
||||||
|
case 5: return Tag05;
|
||||||
|
case 6: return Tag06;
|
||||||
|
case 7: return Tag07;
|
||||||
|
default: return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SafetyChecks.CheckInRangeAndThrow(i, new int2(0, 7), nameof(i));
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: Tag00 = value; break;
|
||||||
|
case 1: Tag01 = value; break;
|
||||||
|
case 2: Tag02 = value; break;
|
||||||
|
case 3: Tag03 = value; break;
|
||||||
|
case 4: Tag04 = value; break;
|
||||||
|
case 5: Tag05 = value; break;
|
||||||
|
case 6: Tag06 = value; break;
|
||||||
|
case 7: Tag07 = value; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var result = 0;
|
||||||
|
result |= (Tag00 ? 1 : 0) << 0;
|
||||||
|
result |= (Tag01 ? 1 : 0) << 1;
|
||||||
|
result |= (Tag02 ? 1 : 0) << 2;
|
||||||
|
result |= (Tag03 ? 1 : 0) << 3;
|
||||||
|
result |= (Tag04 ? 1 : 0) << 4;
|
||||||
|
result |= (Tag05 ? 1 : 0) << 5;
|
||||||
|
result |= (Tag06 ? 1 : 0) << 6;
|
||||||
|
result |= (Tag07 ? 1 : 0) << 7;
|
||||||
|
return (byte)result;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Tag00 = (value & (1 << 0)) != 0;
|
||||||
|
Tag01 = (value & (1 << 1)) != 0;
|
||||||
|
Tag02 = (value & (1 << 2)) != 0;
|
||||||
|
Tag03 = (value & (1 << 3)) != 0;
|
||||||
|
Tag04 = (value & (1 << 4)) != 0;
|
||||||
|
Tag05 = (value & (1 << 5)) != 0;
|
||||||
|
Tag06 = (value & (1 << 6)) != 0;
|
||||||
|
Tag07 = (value & (1 << 7)) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CustomPhysicsMaterialTags other) => Value == other.Value;
|
||||||
|
|
||||||
|
public override bool Equals(object obj) => obj is CustomPhysicsMaterialTags other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode() => Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 05cffa1394b6f481ca7e686dfc6404b9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[InitializeOnLoad]
|
||||||
|
class EditorInitialization
|
||||||
|
{
|
||||||
|
static readonly string k_CustomDefine = "UNITY_PHYSICS_CUSTOM";
|
||||||
|
|
||||||
|
static EditorInitialization()
|
||||||
|
{
|
||||||
|
var definesStr = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||||
|
var defines = definesStr.Split(';').ToList();
|
||||||
|
var found = defines.Find(define => define.Equals(k_CustomDefine));
|
||||||
|
if (found == null)
|
||||||
|
{
|
||||||
|
defines.Add(k_CustomDefine);
|
||||||
|
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
|
||||||
|
string.Join(";", defines.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23f03a5c38aa4a6e81448a178cc0a09f
|
||||||
|
timeCreated: 1680128866
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f26f023234dd844797f8158565ecb4f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
[BakingType]
|
||||||
|
public struct JointEntityBaking : IComponentData
|
||||||
|
{
|
||||||
|
public Entity Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BallAndSocketJoint : BaseJoint
|
||||||
|
{
|
||||||
|
// Editor only settings
|
||||||
|
[HideInInspector]
|
||||||
|
public bool EditPivots;
|
||||||
|
|
||||||
|
[Tooltip("If checked, PositionLocal will snap to match PositionInConnectedEntity")]
|
||||||
|
public bool AutoSetConnected = true;
|
||||||
|
|
||||||
|
public float3 PositionLocal;
|
||||||
|
public float3 PositionInConnectedEntity;
|
||||||
|
|
||||||
|
public virtual void UpdateAuto()
|
||||||
|
{
|
||||||
|
if (AutoSetConnected)
|
||||||
|
{
|
||||||
|
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
|
||||||
|
PositionInConnectedEntity = math.transform(bFromA, PositionLocal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class JointBaker<T> : Baker<T> where T : BaseJoint
|
||||||
|
{
|
||||||
|
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(BaseJoint authoring)
|
||||||
|
{
|
||||||
|
return new PhysicsConstrainedBodyPair(
|
||||||
|
GetEntity(TransformUsageFlags.Dynamic),
|
||||||
|
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
|
||||||
|
authoring.EnableCollision
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity CreateJointEntity(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, PhysicsJoint joint)
|
||||||
|
{
|
||||||
|
using (var joints = new NativeArray<PhysicsJoint>(1, Allocator.Temp) { [0] = joint })
|
||||||
|
using (var jointEntities = new NativeList<Entity>(1, Allocator.Temp))
|
||||||
|
{
|
||||||
|
CreateJointEntities(worldIndex, constrainedBodyPair, joints, jointEntities);
|
||||||
|
return jointEntities[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetWorldIndex(Component c)
|
||||||
|
{
|
||||||
|
uint worldIndex = 0;
|
||||||
|
if (c)
|
||||||
|
{
|
||||||
|
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
|
||||||
|
if (physicsBody != null)
|
||||||
|
{
|
||||||
|
worldIndex = physicsBody.WorldIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return worldIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetWorldIndexFromBaseJoint(BaseJoint authoring)
|
||||||
|
{
|
||||||
|
var physicsBody = GetComponent<PhysicsBodyAuthoring>(authoring);
|
||||||
|
uint worldIndex = physicsBody.WorldIndex;
|
||||||
|
if (authoring.ConnectedBody == null)
|
||||||
|
{
|
||||||
|
return worldIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectedBody = GetComponent<PhysicsBodyAuthoring>(authoring.ConnectedBody);
|
||||||
|
if (connectedBody != null)
|
||||||
|
{
|
||||||
|
Assertions.Assert.AreEqual(worldIndex, connectedBody.WorldIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return worldIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities)
|
||||||
|
{
|
||||||
|
if (!joints.IsCreated || joints.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (newJointEntities.IsCreated)
|
||||||
|
newJointEntities.Clear();
|
||||||
|
else
|
||||||
|
newJointEntities = new NativeList<Entity>(joints.Length, Allocator.Temp);
|
||||||
|
|
||||||
|
// create all new joints
|
||||||
|
var multipleJoints = joints.Length > 1;
|
||||||
|
|
||||||
|
var entity = GetEntity(TransformUsageFlags.Dynamic);
|
||||||
|
for (var i = 0; i < joints.Length; ++i)
|
||||||
|
{
|
||||||
|
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
|
||||||
|
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
|
||||||
|
|
||||||
|
AddComponent(jointEntity, constrainedBodyPair);
|
||||||
|
AddComponent(jointEntity, joints[i]);
|
||||||
|
|
||||||
|
newJointEntities.Add(jointEntity);
|
||||||
|
|
||||||
|
if (GetComponent<ModifyJointLimitsAuthoring>() != null)
|
||||||
|
{
|
||||||
|
AddComponent(jointEntity, new JointEntityBaking()
|
||||||
|
{
|
||||||
|
Entity = entity
|
||||||
|
});
|
||||||
|
AddSharedComponentManaged(jointEntity, new ModifyJointLimits());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multipleJoints)
|
||||||
|
{
|
||||||
|
// set companion buffers for new joints
|
||||||
|
for (var i = 0; i < joints.Length; ++i)
|
||||||
|
{
|
||||||
|
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
|
||||||
|
for (var j = 0; j < joints.Length; ++j)
|
||||||
|
{
|
||||||
|
if (i == j)
|
||||||
|
continue;
|
||||||
|
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BallAndSocketJointBaker : JointBaker<BallAndSocketJoint>
|
||||||
|
{
|
||||||
|
public override void Bake(BallAndSocketJoint authoring)
|
||||||
|
{
|
||||||
|
authoring.UpdateAuto();
|
||||||
|
var physicsJoint = PhysicsJoint.CreateBallAndSocket(authoring.PositionLocal, authoring.PositionInConnectedEntity);
|
||||||
|
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
|
||||||
|
|
||||||
|
var constraintBodyPair = GetConstrainedBodyPair(authoring);
|
||||||
|
|
||||||
|
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
|
||||||
|
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 28a669fa0e32f734c9d34661ccdfd58d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
public abstract class BaseJoint : BaseBodyPairConnector
|
||||||
|
{
|
||||||
|
public bool EnableCollision;
|
||||||
|
public float3 MaxImpulse = float.PositiveInfinity;
|
||||||
|
|
||||||
|
void OnEnable()
|
||||||
|
{
|
||||||
|
// included so tick box appears in Editor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 02bf6429cb18f3147ae5f7d8e3fd5f07
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
public class FreeHingeJoint : BallAndSocketJoint
|
||||||
|
{
|
||||||
|
// Editor only settings
|
||||||
|
[HideInInspector]
|
||||||
|
public bool EditAxes;
|
||||||
|
|
||||||
|
public float3 HingeAxisLocal;
|
||||||
|
public float3 HingeAxisInConnectedEntity;
|
||||||
|
|
||||||
|
public override void UpdateAuto()
|
||||||
|
{
|
||||||
|
base.UpdateAuto();
|
||||||
|
if (AutoSetConnected)
|
||||||
|
{
|
||||||
|
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
|
||||||
|
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FreeHingeJointBaker : JointBaker<FreeHingeJoint>
|
||||||
|
{
|
||||||
|
public override void Bake(FreeHingeJoint authoring)
|
||||||
|
{
|
||||||
|
authoring.UpdateAuto();
|
||||||
|
|
||||||
|
Math.CalculatePerpendicularNormalized(authoring.HingeAxisLocal, out var perpendicularLocal, out _);
|
||||||
|
Math.CalculatePerpendicularNormalized(authoring.HingeAxisInConnectedEntity, out var perpendicularConnected, out _);
|
||||||
|
|
||||||
|
var physicsJoint = PhysicsJoint.CreateHinge(
|
||||||
|
new BodyFrame {Axis = authoring.HingeAxisLocal, Position = authoring.PositionLocal, PerpendicularAxis = perpendicularLocal},
|
||||||
|
new BodyFrame {Axis = authoring.HingeAxisInConnectedEntity, Position = authoring.PositionInConnectedEntity, PerpendicularAxis = perpendicularConnected }
|
||||||
|
);
|
||||||
|
|
||||||
|
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
|
||||||
|
|
||||||
|
var constraintBodyPair = GetConstrainedBodyPair(authoring);
|
||||||
|
|
||||||
|
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
|
||||||
|
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e5dc8c079bffa454ca607aac63ea7e7a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Unity.Physics.Authoring
|
||||||
|
{
|
||||||
|
// This Joint allows you to lock one or more of the 6 degrees of freedom of a constrained body.
|
||||||
|
// This is achieved by combining the appropriate lower level 'constraint atoms' to form the higher level Joint.
|
||||||
|
// In this case Linear and Angular constraint atoms are combined.
|
||||||
|
// One use-case for this Joint could be to restrict a 3d simulation to a 2d plane.
|
||||||
|
public class LimitDOFJoint : BaseJoint
|
||||||
|
{
|
||||||
|
public bool3 LockLinearAxes;
|
||||||
|
public bool3 LockAngularAxes;
|
||||||
|
|
||||||
|
public PhysicsJoint CreateLimitDOFJoint(RigidTransform offset)
|
||||||
|
{
|
||||||
|
var constraints = new FixedList512Bytes<Constraint>();
|
||||||
|
if (math.any(LockLinearAxes))
|
||||||
|
{
|
||||||
|
constraints.Add(new Constraint
|
||||||
|
{
|
||||||
|
ConstrainedAxes = LockLinearAxes,
|
||||||
|
Type = ConstraintType.Linear,
|
||||||
|
Min = 0,
|
||||||
|
Max = 0,
|
||||||
|
SpringFrequency = Constraint.DefaultSpringFrequency,
|
||||||
|
SpringDamping = Constraint.DefaultSpringDamping,
|
||||||
|
MaxImpulse = MaxImpulse,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (math.any(LockAngularAxes))
|
||||||
|
{
|
||||||
|
constraints.Add(new Constraint
|
||||||
|
{
|
||||||
|
ConstrainedAxes = LockAngularAxes,
|
||||||
|
Type = ConstraintType.Angular,
|
||||||
|
Min = 0,
|
||||||
|
Max = 0,
|
||||||
|
SpringFrequency = Constraint.DefaultSpringFrequency,
|
||||||
|
SpringDamping = Constraint.DefaultSpringDamping,
|
||||||
|
MaxImpulse = MaxImpulse,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var joint = new PhysicsJoint
|
||||||
|
{
|
||||||
|
BodyAFromJoint = BodyFrame.Identity,
|
||||||
|
BodyBFromJoint = offset
|
||||||
|
};
|
||||||
|
joint.SetConstraints(constraints);
|
||||||
|
return joint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LimitDOFJointBaker : Baker<LimitDOFJoint>
|
||||||
|
{
|
||||||
|
public Entity CreateJointEntity(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, PhysicsJoint joint)
|
||||||
|
{
|
||||||
|
using (var joints = new NativeArray<PhysicsJoint>(1, Allocator.Temp) { [0] = joint })
|
||||||
|
using (var jointEntities = new NativeList<Entity>(1, Allocator.Temp))
|
||||||
|
{
|
||||||
|
CreateJointEntities(worldIndex, constrainedBodyPair, joints, jointEntities);
|
||||||
|
return jointEntities[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateJointEntities(uint worldIndex, PhysicsConstrainedBodyPair constrainedBodyPair, NativeArray<PhysicsJoint> joints, NativeList<Entity> newJointEntities = default)
|
||||||
|
{
|
||||||
|
if (!joints.IsCreated || joints.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (newJointEntities.IsCreated)
|
||||||
|
newJointEntities.Clear();
|
||||||
|
else
|
||||||
|
newJointEntities = new NativeList<Entity>(joints.Length, Allocator.Temp);
|
||||||
|
|
||||||
|
// create all new joints
|
||||||
|
var multipleJoints = joints.Length > 1;
|
||||||
|
|
||||||
|
for (var i = 0; i < joints.Length; ++i)
|
||||||
|
{
|
||||||
|
var jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic);
|
||||||
|
AddSharedComponent(jointEntity, new PhysicsWorldIndex(worldIndex));
|
||||||
|
|
||||||
|
AddComponent(jointEntity, constrainedBodyPair);
|
||||||
|
AddComponent(jointEntity, joints[i]);
|
||||||
|
|
||||||
|
newJointEntities.Add(jointEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multipleJoints)
|
||||||
|
{
|
||||||
|
// set companion buffers for new joints
|
||||||
|
for (var i = 0; i < joints.Length; ++i)
|
||||||
|
{
|
||||||
|
var companions = AddBuffer<PhysicsJointCompanion>(newJointEntities[i]);
|
||||||
|
for (var j = 0; j < joints.Length; ++j)
|
||||||
|
{
|
||||||
|
if (i == j)
|
||||||
|
continue;
|
||||||
|
companions.Add(new PhysicsJointCompanion {JointEntity = newJointEntities[j]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PhysicsConstrainedBodyPair GetConstrainedBodyPair(LimitDOFJoint authoring)
|
||||||
|
{
|
||||||
|
return new PhysicsConstrainedBodyPair(
|
||||||
|
GetEntity(TransformUsageFlags.Dynamic),
|
||||||
|
authoring.ConnectedBody == null ? Entity.Null : GetEntity(authoring.ConnectedBody, TransformUsageFlags.Dynamic),
|
||||||
|
authoring.EnableCollision
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetWorldIndex(Component c)
|
||||||
|
{
|
||||||
|
uint worldIndex = 0;
|
||||||
|
var physicsBody = GetComponent<PhysicsBodyAuthoring>(c);
|
||||||
|
if (physicsBody != null)
|
||||||
|
{
|
||||||
|
worldIndex = physicsBody.WorldIndex;
|
||||||
|
}
|
||||||
|
return worldIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Bake(LimitDOFJoint authoring)
|
||||||
|
{
|
||||||
|
if (!math.any(authoring.LockLinearAxes) && !math.any(authoring.LockAngularAxes))
|
||||||
|
return;
|
||||||
|
|
||||||
|
RigidTransform bFromA = math.mul(math.inverse(authoring.worldFromB), authoring.worldFromA);
|
||||||
|
PhysicsJoint physicsJoint = authoring.CreateLimitDOFJoint(bFromA);
|
||||||
|
|
||||||
|
var worldIndex = GetWorldIndex(authoring);
|
||||||
|
CreateJointEntity(
|
||||||
|
worldIndex,
|
||||||
|
GetConstrainedBodyPair(authoring),
|
||||||
|
physicsJoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9457d04025dadec4b85ae39dad2e1822
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user