Skip to content

Commit 8acbb64

Browse files
authored
Merge pull request #9973 from Zee2/handlecollider-fix
Partial fix for BoundsControl collider issues and inconsistency
2 parents 0eb4211 + fdb04cd commit 8acbb64

File tree

3 files changed

+131
-29
lines changed

3 files changed

+131
-29
lines changed

Assets/MRTK/SDK/Features/UX/Scripts/BoundsControl/Visuals/PerAxisHandles.cs

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Microsoft.MixedReality.Toolkit.UI.BoundsControl
1010
{
1111
/// <summary>
12-
/// Rotation handles for <see cref="BoundsControl"/> that are used for rotating the
12+
/// Handles for <see cref="BoundsControl"/> that are used for manipulating the
1313
/// Gameobject BoundsControl is attached to with near or far interaction
1414
/// </summary>
1515
public abstract class PerAxisHandles : HandlesBase
@@ -75,6 +75,9 @@ private void UpdateColliderType()
7575
// remove old colliders
7676
bool shouldCreateNewCollider = false;
7777
var oldBoxCollider = handle.GetComponent<BoxCollider>();
78+
79+
// Caution, Destroy() will destroy one frame later.
80+
// Do not check later for presence this frame!
7881
if (oldBoxCollider != null && config.HandlePrefabColliderType == HandlePrefabCollider.Sphere)
7982
{
8083
shouldCreateNewCollider = true;
@@ -92,7 +95,8 @@ private void UpdateColliderType()
9295
{
9396
// attach new collider
9497
var handleBounds = VisualUtils.GetMaxBounds(GetVisual(handle).gameObject);
95-
var invScale = handleBounds.size.x == 0.0f ? 0.0f : config.HandleSize / handleBounds.size.x;
98+
float maxDim = VisualUtils.GetMaxComponent(handleBounds.size);
99+
float invScale = maxDim == 0.0f ? 0.0f : config.HandleSize / maxDim;
96100
Vector3 colliderSizeScaled = handleBounds.size * invScale;
97101
Vector3 colliderCenterScaled = handleBounds.center * invScale;
98102
if (config.HandlePrefabColliderType == HandlePrefabCollider.Box)
@@ -106,7 +110,7 @@ private void UpdateColliderType()
106110
{
107111
SphereCollider sphere = handle.gameObject.AddComponent<SphereCollider>();
108112
sphere.center = colliderCenterScaled;
109-
sphere.radius = colliderSizeScaled.x * 0.5f;
113+
sphere.radius = VisualUtils.GetMaxComponent(colliderSizeScaled) * 0.5f;
110114
sphere.radius += VisualUtils.GetMaxComponent(config.ColliderPadding);
111115
}
112116
}
@@ -185,10 +189,12 @@ private void CreateHandles(Transform parent)
185189
handle.transform.position = HandlePositions[i];
186190
handle.transform.parent = parent;
187191

188-
Bounds midpointBounds = CreateVisual(i, handle);
189-
float maxDim = VisualUtils.GetMaxComponent(midpointBounds.size);
192+
Bounds handleVisualBounds = CreateVisual(i, handle);
193+
float maxDim = VisualUtils.GetMaxComponent(handleVisualBounds.size);
190194
float invScale = maxDim == 0.0f ? 0.0f : config.HandleSize / maxDim;
191-
VisualUtils.AddComponentsToAffordance(handle, new Bounds(midpointBounds.center * invScale, midpointBounds.size * invScale),
195+
196+
// TODO: Some subclasses of PerAxisHandles shouldn't use CursorContextInfo.CursorAction.Rotate
197+
VisualUtils.AddComponentsToAffordance(handle, new Bounds(handleVisualBounds.center * invScale, handleVisualBounds.size * invScale),
192198
config.HandlePrefabColliderType, CursorContextInfo.CursorAction.Rotate, config.ColliderPadding, parent, config.DrawTetherWhenManipulating);
193199

194200
handles.Add(handle.transform);
@@ -209,11 +215,14 @@ protected override void RecreateVisuals()
209215
{
210216
// get old child and remove it
211217
obsoleteChild.parent = null;
218+
219+
// Caution, Destroy() will destroy one frame later.
220+
// Do not check later for presence this frame!
212221
Object.Destroy(obsoleteChild.gameObject);
213222
}
214223
else
215224
{
216-
Debug.LogError("couldn't find rotation visual on recreating visuals");
225+
Debug.LogError("Couldn't find handle visual on recreating visuals");
217226
}
218227

219228
// create new visual
@@ -228,7 +237,8 @@ protected override void RecreateVisuals()
228237

229238
protected override void UpdateColliderBounds(Transform handle, Vector3 visualSize)
230239
{
231-
var invScale = visualSize.x == 0.0f ? 0.0f : config.HandleSize / visualSize.x;
240+
float maxDim = VisualUtils.GetMaxComponent(visualSize);
241+
float invScale = maxDim == 0.0f ? 0.0f : config.HandleSize / maxDim;
232242
GetVisual(handle).transform.localScale = new Vector3(invScale, invScale, invScale);
233243
Vector3 colliderSizeScaled = visualSize * invScale;
234244
if (config.HandlePrefabColliderType == HandlePrefabCollider.Box)
@@ -240,47 +250,53 @@ protected override void UpdateColliderBounds(Transform handle, Vector3 visualSiz
240250
else
241251
{
242252
SphereCollider collider = handle.gameObject.GetComponent<SphereCollider>();
243-
collider.radius = colliderSizeScaled.x * 0.5f;
253+
collider.radius = VisualUtils.GetMaxComponent(colliderSizeScaled) * 0.5f;
244254
collider.radius += VisualUtils.GetMaxComponent(config.ColliderPadding);
245255
}
246256
}
247257

248258
private Bounds CreateVisual(int handleIndex, GameObject parent)
249259
{
250-
GameObject midpointVisual;
260+
GameObject handleVisual;
251261
GameObject prefabType = config.HandlePrefab;
252262
if (prefabType != null)
253263
{
254-
midpointVisual = Object.Instantiate(prefabType);
264+
handleVisual = Object.Instantiate(prefabType);
255265
}
256266
else
257267
{
258-
midpointVisual = GameObject.CreatePrimitive(PrimitiveType.Sphere);
259-
// deactivate collider on visuals and register for deletion - actual collider
260-
// of handle is attached to the handle gameobject, not the visual
261-
var collider = midpointVisual.GetComponent<SphereCollider>();
268+
handleVisual = GameObject.CreatePrimitive(PrimitiveType.Sphere);
269+
// We only want the Primitive sphere mesh, but CreatePrimitive will
270+
// give us a sphere collider too. Remove the sphere collider here
271+
// so we can manually add our own properly configured collider later.
272+
var collider = handleVisual.GetComponent<SphereCollider>();
262273
collider.enabled = false;
263-
Object.Destroy(collider);
264-
}
265274

266-
Quaternion realignment = GetRotationRealignment(handleIndex);
267-
midpointVisual.transform.localRotation = realignment * midpointVisual.transform.localRotation;
275+
// Caution, Destroy() will destroy one frame later.
276+
// Do not check later for presence this frame!
277+
Object.Destroy(collider);
278+
}
268279

269-
Bounds midpointBounds = VisualUtils.GetMaxBounds(midpointVisual);
270-
float maxDim = VisualUtils.GetMaxComponent(midpointBounds.size);
280+
// handleVisualBounds are returned in handleVisual-local space.
281+
Bounds handleVisualBounds = VisualUtils.GetMaxBounds(handleVisual);
282+
float maxDim = VisualUtils.GetMaxComponent(handleVisualBounds.size);
271283
float invScale = maxDim == 0.0f ? 0.0f : config.HandleSize / maxDim;
272284

273-
midpointVisual.name = visualsName;
274-
midpointVisual.transform.parent = parent.transform;
275-
midpointVisual.transform.localScale = new Vector3(invScale, invScale, invScale);
276-
midpointVisual.transform.localPosition = Vector3.zero;
285+
handleVisual.name = visualsName;
286+
handleVisual.transform.parent = parent.transform;
287+
handleVisual.transform.localScale = new Vector3(invScale, invScale, invScale);
288+
handleVisual.transform.localPosition = Vector3.zero;
289+
handleVisual.transform.localRotation = Quaternion.identity;
290+
291+
Quaternion realignment = GetRotationRealignment(handleIndex);
292+
parent.transform.localRotation = realignment;
277293

278294
if (config.HandleMaterial != null)
279295
{
280-
VisualUtils.ApplyMaterialToAllRenderers(midpointVisual, config.HandleMaterial);
296+
VisualUtils.ApplyMaterialToAllRenderers(handleVisual, config.HandleMaterial);
281297
}
282298

283-
return midpointBounds;
299+
return handleVisualBounds;
284300
}
285301

286302
#region BoundsControlHandlerBase

Assets/MRTK/SDK/Features/UX/Scripts/BoundsControl/Visuals/ProximityEffect/ProximityEffect.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,9 @@ private void ScaleObject(ProximityState state, Transform scaleVisual, float obje
276276
weight = lerp ? config.CloseGrowRate : 1.0f;
277277
break;
278278
}
279-
280-
float newLocalScale = (scaleVisual.localScale.x * (1.0f - weight)) + (objectSize * targetScale * weight);
279+
280+
float maxScaleAxis = VisualUtils.GetMaxComponent(scaleVisual.localScale);
281+
float newLocalScale = (maxScaleAxis * (1.0f - weight)) + (objectSize * targetScale * weight);
281282
scaleVisual.localScale = new Vector3(newLocalScale, newLocalScale, newLocalScale);
282283
}
283284

Assets/MRTK/Tests/PlayModeTests/BoundsControlTests.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2083,6 +2083,91 @@ public IEnumerator PerAxisHandlePrefabTest([ValueSource("perAxisHandleTestData")
20832083
yield return null;
20842084
}
20852085

2086+
/// <summary>
2087+
/// Test for verifying automatically generated handle colliders are properly aligned to the handle visual
2088+
/// </summary>
2089+
[UnityTest]
2090+
public IEnumerator PerAxisHandleAlignmentTest([ValueSource("perAxisHandleTestData")] PerAxisHandleTestData testData)
2091+
{
2092+
var boundsControl = InstantiateSceneAndDefaultBoundsControl();
2093+
yield return VerifyInitialBoundsCorrect(boundsControl);
2094+
2095+
// Create an oblong-shaped handle
2096+
// (cylinder primitive will do, as it is longer than it is wide!)
2097+
var cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
2098+
GameObject.Destroy(cylinder.GetComponent<CapsuleCollider>());
2099+
2100+
// Wait for Destroy() to do its thing
2101+
yield return null;
2102+
2103+
// Set the rotation handles to be cylinders!
2104+
System.Reflection.PropertyInfo propName = boundsControl.GetType().GetProperty(testData.configPropertyName);
2105+
PerAxisHandlesConfiguration config = (PerAxisHandlesConfiguration)propName.GetValue(boundsControl);
2106+
config.HandlePrefab = cylinder;
2107+
2108+
// Reflection voodoo to retrieve the ColliderPadding value regardless of which
2109+
// handle configuration subclass we're currently using
2110+
System.Type configType = config.GetType();
2111+
var paddingProperty = configType.GetProperty("ColliderPadding");
2112+
Vector3 padding = (Vector3)paddingProperty.GetValue(config);
2113+
2114+
// Wait for BoundsControl to update to new handle prefab/rebuild rig
2115+
yield return null;
2116+
yield return new WaitForFixedUpdate();
2117+
2118+
// Iterate over all handle transforms
2119+
foreach(Transform handle in boundsControl.transform.Find("rigRoot"))
2120+
{
2121+
// Is this the handle type we're currently looking for?
2122+
if(handle.name.StartsWith(testData.handleName))
2123+
{
2124+
BoxCollider handleCollider = handle.GetComponent<BoxCollider>();
2125+
2126+
Vector3[] colliderPoints = new Vector3[8];
2127+
Vector3[] globalColliderPoints = new Vector3[8];
2128+
2129+
// Strip the padding off the collider bounds, so that these bounds will match up
2130+
// correctly with the visual bounds, if the alignment was properly done.
2131+
VisualUtils.GetCornerPositionsFromBounds(new Bounds(handleCollider.center, handleCollider.size - padding), ref colliderPoints);
2132+
2133+
// Perform a local-global transformation on all corners of the local collider bounds.
2134+
for(int i = 0; i < colliderPoints.Length; ++i)
2135+
{
2136+
globalColliderPoints[i] = handle.TransformPoint(colliderPoints[i]);
2137+
}
2138+
2139+
Transform visual = handle.GetChild(0);
2140+
Bounds handleBounds = VisualUtils.GetMaxBounds(visual.gameObject);
2141+
2142+
Vector3[] visualPoints = new Vector3[8];
2143+
Vector3[] globalVisualPoints = new Vector3[8];
2144+
2145+
VisualUtils.GetCornerPositionsFromBounds(handleBounds, ref visualPoints);
2146+
2147+
// Perform a local-global transformation on all corners of the local visual handle bounds.
2148+
for(int i = 0; i < visualPoints.Length; ++i)
2149+
{
2150+
globalVisualPoints[i] = visual.TransformPoint(visualPoints[i]);
2151+
}
2152+
2153+
// Make sure all corners/vertices of the bounds are coherent after realignment, in global space
2154+
bool flag = true;
2155+
for(int i = 0; i < globalColliderPoints.Length; ++i)
2156+
{
2157+
if(globalColliderPoints[i] != globalVisualPoints[i])
2158+
{
2159+
flag = false;
2160+
Debug.LogError($"Bounds mismatch, collider point: {globalColliderPoints[i].ToString("F3")}, visual point: {globalVisualPoints[i].ToString("F3")}");
2161+
}
2162+
}
2163+
2164+
Assert.IsTrue(flag, "Handle collider does not match visual bounds, likely incorrect realignment of handle/visual transforms");
2165+
}
2166+
}
2167+
2168+
yield return null;
2169+
}
2170+
20862171
/// <summary>
20872172
/// Test for verifying changing the handle prefabs during runtime
20882173
/// in regular and flatten mode and making sure the entire rig won't be recreated

0 commit comments

Comments
 (0)