mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交
This commit is contained in:
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:
|
@@ -0,0 +1,27 @@
|
||||
using Unity.Entities;
|
||||
using static Unity.Physics.Math;
|
||||
|
||||
namespace Unity.Physics.Authoring
|
||||
{
|
||||
public class LimitedDistanceJoint : BallAndSocketJoint
|
||||
{
|
||||
public float MinDistance;
|
||||
public float MaxDistance;
|
||||
}
|
||||
|
||||
class LimitedDistanceJointBaker : JointBaker<LimitedDistanceJoint>
|
||||
{
|
||||
public override void Bake(LimitedDistanceJoint authoring)
|
||||
{
|
||||
authoring.UpdateAuto();
|
||||
|
||||
var physicsJoint = PhysicsJoint.CreateLimitedDistance(authoring.PositionLocal, authoring.PositionInConnectedEntity, new FloatRange(authoring.MinDistance, authoring.MaxDistance));
|
||||
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
|
||||
|
||||
var constraintBodyPair = GetConstrainedBodyPair(authoring);
|
||||
|
||||
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
|
||||
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d79d98418b67aa44db932451943018a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,61 @@
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using static Unity.Physics.Math;
|
||||
|
||||
namespace Unity.Physics.Authoring
|
||||
{
|
||||
public class LimitedHingeJoint : FreeHingeJoint
|
||||
{
|
||||
// Editor only settings
|
||||
[HideInInspector]
|
||||
public bool EditLimits;
|
||||
|
||||
public float3 PerpendicularAxisLocal;
|
||||
public float3 PerpendicularAxisInConnectedEntity;
|
||||
public float MinAngle;
|
||||
public float MaxAngle;
|
||||
|
||||
public override void UpdateAuto()
|
||||
{
|
||||
base.UpdateAuto();
|
||||
if (AutoSetConnected)
|
||||
{
|
||||
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
|
||||
HingeAxisInConnectedEntity = math.mul(bFromA.rot, HingeAxisLocal);
|
||||
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LimitedHingeJointBaker : JointBaker<LimitedHingeJoint>
|
||||
{
|
||||
public override void Bake(LimitedHingeJoint authoring)
|
||||
{
|
||||
authoring.UpdateAuto();
|
||||
|
||||
var physicsJoint = PhysicsJoint.CreateLimitedHinge(
|
||||
new BodyFrame
|
||||
{
|
||||
Axis = math.normalize(authoring.HingeAxisLocal),
|
||||
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisLocal),
|
||||
Position = authoring.PositionLocal
|
||||
},
|
||||
new BodyFrame
|
||||
{
|
||||
Axis = math.normalize(authoring.HingeAxisInConnectedEntity),
|
||||
PerpendicularAxis = math.normalize(authoring.PerpendicularAxisInConnectedEntity),
|
||||
Position = authoring.PositionInConnectedEntity
|
||||
},
|
||||
math.radians(new FloatRange(authoring.MinAngle, authoring.MaxAngle))
|
||||
);
|
||||
|
||||
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
|
||||
|
||||
var constraintBodyPair = GetConstrainedBodyPair(authoring);
|
||||
|
||||
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
|
||||
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbaf9813ee7af2f41a5fd776598ab5b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Physics;
|
||||
using Unity.Physics.Authoring;
|
||||
using Unity.Physics.Extensions;
|
||||
using Unity.Physics.Systems;
|
||||
using UnityEngine;
|
||||
using FloatRange = Unity.Physics.Math.FloatRange;
|
||||
|
||||
namespace Unity.Physics.Authoring
|
||||
{
|
||||
// stores an initial value and a pair of scalar curves to apply to relevant constraints on the joint
|
||||
struct ModifyJointLimits : ISharedComponentData, IEquatable<ModifyJointLimits>
|
||||
{
|
||||
public PhysicsJoint InitialValue;
|
||||
public ParticleSystem.MinMaxCurve AngularRangeScalar;
|
||||
public ParticleSystem.MinMaxCurve LinearRangeScalar;
|
||||
|
||||
public bool Equals(ModifyJointLimits other) =>
|
||||
AngularRangeScalar.Equals(other.AngularRangeScalar) && LinearRangeScalar.Equals(other.LinearRangeScalar);
|
||||
|
||||
public override bool Equals(object obj) => obj is ModifyJointLimits other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
unchecked((AngularRangeScalar.GetHashCode() * 397) ^ LinearRangeScalar.GetHashCode());
|
||||
}
|
||||
|
||||
// an authoring component to add to a GameObject with one or more Joint
|
||||
public class ModifyJointLimitsAuthoring : MonoBehaviour
|
||||
{
|
||||
public ParticleSystem.MinMaxCurve AngularRangeScalar = new ParticleSystem.MinMaxCurve(
|
||||
1f,
|
||||
min: new AnimationCurve(
|
||||
new Keyframe(0f, 0f, 0f, 0f),
|
||||
new Keyframe(2f, -2f, 0f, 0f),
|
||||
new Keyframe(4f, 0f, 0f, 0f)
|
||||
)
|
||||
{
|
||||
preWrapMode = WrapMode.Loop,
|
||||
postWrapMode = WrapMode.Loop
|
||||
},
|
||||
max: new AnimationCurve(
|
||||
new Keyframe(0f, 1f, 0f, 0f),
|
||||
new Keyframe(2f, -1f, 0f, 0f),
|
||||
new Keyframe(4f, 1f, 0f, 0f)
|
||||
)
|
||||
{
|
||||
preWrapMode = WrapMode.Loop,
|
||||
postWrapMode = WrapMode.Loop
|
||||
}
|
||||
);
|
||||
|
||||
public ParticleSystem.MinMaxCurve LinearRangeScalar = new ParticleSystem.MinMaxCurve(
|
||||
1f,
|
||||
min: new AnimationCurve(
|
||||
new Keyframe(0f, 1f, 0f, 0f),
|
||||
new Keyframe(2f, 0.5f, 0f, 0f),
|
||||
new Keyframe(4f, 1f, 0f, 0f)
|
||||
)
|
||||
{
|
||||
preWrapMode = WrapMode.Loop,
|
||||
postWrapMode = WrapMode.Loop
|
||||
},
|
||||
max: new AnimationCurve(
|
||||
new Keyframe(0f, 0.5f, 0f, 0f),
|
||||
new Keyframe(2f, 0f, 0f, 0f),
|
||||
new Keyframe(4f, 0.5f, 0f, 0f)
|
||||
)
|
||||
{
|
||||
preWrapMode = WrapMode.Loop,
|
||||
postWrapMode = WrapMode.Loop
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[BakingType]
|
||||
public class ModifyJointLimitsBakingData : IComponentData
|
||||
{
|
||||
public ParticleSystem.MinMaxCurve AngularRangeScalar;
|
||||
public ParticleSystem.MinMaxCurve LinearRangeScalar;
|
||||
}
|
||||
|
||||
class ModifyJointLimitsBaker : Baker<ModifyJointLimitsAuthoring>
|
||||
{
|
||||
public override void Bake(ModifyJointLimitsAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(TransformUsageFlags.Dynamic);
|
||||
AddComponentObject(entity, new ModifyJointLimitsBakingData
|
||||
{
|
||||
AngularRangeScalar = authoring.AngularRangeScalar,
|
||||
LinearRangeScalar = authoring.LinearRangeScalar
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// after joints have been converted, find the entities they produced and add ModifyJointLimits to them
|
||||
[UpdateAfter(typeof(EndJointBakingSystem))]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
|
||||
partial struct ModifyJointLimitsBakingSystem : ISystem
|
||||
{
|
||||
private EntityQuery _ModifyJointLimitsBakingDataQuery;
|
||||
private EntityQuery _JointEntityBakingQuery;
|
||||
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
_ModifyJointLimitsBakingDataQuery = state.GetEntityQuery(new EntityQueryDesc
|
||||
{
|
||||
All = new[] {ComponentType.ReadOnly<ModifyJointLimitsBakingData>()},
|
||||
Options = EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab
|
||||
});
|
||||
|
||||
_JointEntityBakingQuery = state.GetEntityQuery(new EntityQueryDesc
|
||||
{
|
||||
All = new[] {ComponentType.ReadOnly<JointEntityBaking>()}
|
||||
});
|
||||
|
||||
_ModifyJointLimitsBakingDataQuery.AddChangedVersionFilter(typeof(ModifyJointLimitsBakingData));
|
||||
_JointEntityBakingQuery.AddChangedVersionFilter(typeof(JointEntityBaking));
|
||||
}
|
||||
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
if (_ModifyJointLimitsBakingDataQuery.IsEmpty && _JointEntityBakingQuery.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all the joints
|
||||
NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)> jointsLookUp =
|
||||
new NativeParallelMultiHashMap<Entity, (Entity, PhysicsJoint)>(10, Allocator.TempJob);
|
||||
|
||||
foreach (var(jointEntity, physicsJoint, entity) in SystemAPI
|
||||
.Query<RefRO<JointEntityBaking>, RefRO<PhysicsJoint>>().WithEntityAccess()
|
||||
.WithOptions(EntityQueryOptions.IncludeDisabledEntities | EntityQueryOptions.IncludePrefab))
|
||||
{
|
||||
jointsLookUp.Add(jointEntity.ValueRO.Entity, (entity, physicsJoint.ValueRO));
|
||||
}
|
||||
|
||||
foreach (var(modifyJointLimits, entity) in SystemAPI.Query<ModifyJointLimitsBakingData>()
|
||||
.WithEntityAccess().WithOptions(EntityQueryOptions.IncludeDisabledEntities |
|
||||
EntityQueryOptions.IncludePrefab))
|
||||
{
|
||||
var angularModification = new ParticleSystem.MinMaxCurve(
|
||||
multiplier: math.radians(modifyJointLimits.AngularRangeScalar.curveMultiplier),
|
||||
min: modifyJointLimits.AngularRangeScalar.curveMin,
|
||||
max: modifyJointLimits.AngularRangeScalar.curveMax
|
||||
);
|
||||
|
||||
foreach (var joint in jointsLookUp.GetValuesForKey(entity))
|
||||
{
|
||||
state.EntityManager.SetSharedComponentManaged(joint.Item1, new ModifyJointLimits
|
||||
{
|
||||
InitialValue = joint.Item2,
|
||||
AngularRangeScalar = angularModification,
|
||||
LinearRangeScalar = modifyJointLimits.LinearRangeScalar
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
jointsLookUp.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// apply an animated effect to the limits on supported types of joints
|
||||
[RequireMatchingQueriesForUpdate]
|
||||
[UpdateInGroup(typeof(PhysicsSystemGroup), OrderLast = true)]
|
||||
partial struct ModifyJointLimitsSystem : ISystem
|
||||
{
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var time = (float)SystemAPI.Time.ElapsedTime;
|
||||
|
||||
foreach (var(joint, modification) in SystemAPI.Query<RefRW<PhysicsJoint>, ModifyJointLimits>())
|
||||
{
|
||||
var animatedAngularScalar = new FloatRange(
|
||||
modification.AngularRangeScalar.curveMin.Evaluate(time),
|
||||
modification.AngularRangeScalar.curveMax.Evaluate(time)
|
||||
);
|
||||
var animatedLinearScalar = new FloatRange(
|
||||
modification.LinearRangeScalar.curveMin.Evaluate(time),
|
||||
modification.LinearRangeScalar.curveMax.Evaluate(time)
|
||||
);
|
||||
|
||||
// in each case, get relevant properties from the initial value based on joint type, and apply scalar
|
||||
switch (joint.ValueRW.JointType)
|
||||
{
|
||||
// Custom type could be anything, so this demo just applies changes to all constraints
|
||||
case JointType.Custom:
|
||||
var constraints = modification.InitialValue.GetConstraints();
|
||||
for (var i = 0; i < constraints.Length; i++)
|
||||
{
|
||||
var constraint = constraints[i];
|
||||
var isAngular = constraint.Type == ConstraintType.Angular;
|
||||
var scalar = math.select(animatedLinearScalar, animatedAngularScalar, isAngular);
|
||||
var constraintRange = (FloatRange)(new float2(constraint.Min, constraint.Max) * scalar);
|
||||
constraint.Min = constraintRange.Min;
|
||||
constraint.Max = constraintRange.Max;
|
||||
constraints[i] = constraint;
|
||||
}
|
||||
|
||||
joint.ValueRW.SetConstraints(constraints);
|
||||
break;
|
||||
// other types have corresponding getters/setters to retrieve more meaningful data
|
||||
case JointType.LimitedDistance:
|
||||
var distanceRange = modification.InitialValue.GetLimitedDistanceRange();
|
||||
joint.ValueRW.SetLimitedDistanceRange(distanceRange * (float2)animatedLinearScalar);
|
||||
break;
|
||||
case JointType.LimitedHinge:
|
||||
var angularRange = modification.InitialValue.GetLimitedHingeRange();
|
||||
joint.ValueRW.SetLimitedHingeRange(angularRange * (float2)animatedAngularScalar);
|
||||
break;
|
||||
case JointType.Prismatic:
|
||||
var distanceOnAxis = modification.InitialValue.GetPrismaticRange();
|
||||
joint.ValueRW.SetPrismaticRange(distanceOnAxis * (float2)animatedLinearScalar);
|
||||
break;
|
||||
// ragdoll joints are composed of two separate joints with different meanings
|
||||
case JointType.RagdollPrimaryCone:
|
||||
modification.InitialValue.GetRagdollPrimaryConeAndTwistRange(
|
||||
out var maxConeAngle,
|
||||
out var angularTwistRange
|
||||
);
|
||||
joint.ValueRW.SetRagdollPrimaryConeAndTwistRange(
|
||||
maxConeAngle * animatedAngularScalar.Max,
|
||||
angularTwistRange * (float2)animatedAngularScalar
|
||||
);
|
||||
break;
|
||||
case JointType.RagdollPerpendicularCone:
|
||||
var angularPlaneRange = modification.InitialValue.GetRagdollPerpendicularConeRange();
|
||||
joint.ValueRW.SetRagdollPerpendicularConeRange(angularPlaneRange *
|
||||
(float2)animatedAngularScalar);
|
||||
break;
|
||||
// remaining types have no limits on their Constraint atoms to meaningfully modify
|
||||
case JointType.BallAndSocket:
|
||||
case JointType.Fixed:
|
||||
case JointType.Hinge:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09435b2483c7b48409764517bbfff081
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,58 @@
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Physics.Math;
|
||||
|
||||
namespace Unity.Physics.Authoring
|
||||
{
|
||||
public class PrismaticJoint : BallAndSocketJoint
|
||||
{
|
||||
public float3 AxisLocal;
|
||||
public float3 AxisInConnectedEntity;
|
||||
public float3 PerpendicularAxisLocal;
|
||||
public float3 PerpendicularAxisInConnectedEntity;
|
||||
public float MinDistanceOnAxis;
|
||||
public float MaxDistanceOnAxis;
|
||||
|
||||
public override void UpdateAuto()
|
||||
{
|
||||
base.UpdateAuto();
|
||||
if (AutoSetConnected)
|
||||
{
|
||||
RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
|
||||
AxisInConnectedEntity = math.mul(bFromA.rot, AxisLocal);
|
||||
PerpendicularAxisInConnectedEntity = math.mul(bFromA.rot, PerpendicularAxisLocal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PrismaticJointBaker : JointBaker<PrismaticJoint>
|
||||
{
|
||||
public override void Bake(PrismaticJoint authoring)
|
||||
{
|
||||
authoring.UpdateAuto();
|
||||
|
||||
var physicsJoint = PhysicsJoint.CreatePrismatic(
|
||||
new BodyFrame
|
||||
{
|
||||
Axis = authoring.AxisLocal,
|
||||
PerpendicularAxis = authoring.PerpendicularAxisLocal,
|
||||
Position = authoring.PositionLocal
|
||||
},
|
||||
new BodyFrame
|
||||
{
|
||||
Axis = authoring.AxisInConnectedEntity,
|
||||
PerpendicularAxis = authoring.PerpendicularAxisInConnectedEntity,
|
||||
Position = authoring.PositionInConnectedEntity
|
||||
},
|
||||
new FloatRange(authoring.MinDistanceOnAxis, authoring.MaxDistanceOnAxis)
|
||||
);
|
||||
|
||||
physicsJoint.SetImpulseEventThresholdAllConstraints(authoring.MaxImpulse);
|
||||
|
||||
var constraintBodyPair = GetConstrainedBodyPair(authoring);
|
||||
|
||||
uint worldIndex = GetWorldIndexFromBaseJoint(authoring);
|
||||
CreateJointEntity(worldIndex, constraintBodyPair, physicsJoint);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b226e7ae02ee34c458747d6c2ee8456c
|
||||
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