Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tap to place and fix a few issues #874

Merged
merged 25 commits into from
Aug 30, 2017

Conversation

Zod-
Copy link

@Zod- Zod- commented Aug 17, 2017

FIrst I only wanted to do some cleanup but then found a few issues in the TapToPlace test scene:

  1. Placing an object on another one will put both in IsBeingPlaced. Fixed this by setting TapToPlace as Modal rather than just a global listener.
  2. When moving a child with PlaceParentOnTap the parent or siblings can still get hit by ray cast because only the child itself gets the IgnoreRayCast layer. Setting the parent recursively instead when it's used.
  3. RoomMesh was missing in the test scene.

I refactored quite a bit of the code as it was very cryptic what it was doing like that nested tertiary operator with a ray cast. I've split the SetLayerRecursively method into 2 parts to make it clear that it does cache and moved it into GameObjectExtensions. There was another SetLayerRecursively in Utils that I've also moved in there and set obsolete.

void SetLayerRecursively(this GameObject root, int layer)
void SetLayerRecursively(this GameObject root, int layer, out Dictionary<GameObject, int> cache)
void ApplyLayerCacheRecursively(this GameObject root, Dictionary<GameObject, int> cache)

I've separated the traverse of the transform hierarchy and moved it into TransformExtensions, using a queue instead of recursive method calls.

IEnumerable<Transform> IterateHierarchy(this Transform root)
IEnumerable<Transform> IterateHierarchy(this Transform root, ICollection<Transform> ignore)
e.g.
foreach (var child in root.transform.IterateHierarchy())

The new extension methods are covered by several integration tests since we need some of those.

There are still a few methods that all use their own way of doing a recursive traverse, could probably clean those up too.

These 3 methods are still in TapToPlace but could be moved somewhere else, if so I wasn't sure where to put them.

static Vector3 GetPlacementPosition(Vector3 headPosition, Vector3 gazeDirection, float defaultGazeDistance)
static bool SpatialMappingRaycast(Vector3 origin, Vector3 direction, out RaycastHit spatialMapHit)
static Vector3 GetGazePlacementPosition(Vector3 headPosition, Vector3 gazeDirection, float defaultGazeDistance)

@msftclas
Copy link

@Zod-,
Thanks for having already signed the Contribution License Agreement. Your agreement was validated by Microsoft. We will now review your pull request.
Thanks,
Microsoft Pull Request Bot

Copy link
Contributor

@StephenHodgson StephenHodgson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Just a few comments.

using NUnit.Framework;
using UnityEngine;

namespace HoloToolkit.Unity.Tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome. Just one suggestion is to move this into HoloToolkit-UnitTests/Utilities/Extensions folder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're technically integration tests since they interact with the unity engine and instantiate several game objects in the tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, but that's where the other integration tests are located at the moment.

{
WorldAnchorManager.Instance.AttachAnchor(gameObject, SavedAnchorFriendlyName);
}
WorldAnchorManager.Instance.AttachAnchor(gameObject, SavedAnchorFriendlyName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably still check to make sure WorldAnchorManager.Instance != null. It's optional to have in the scene.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's implied by the else, here without diff bloat:

if (WorldAnchorManager.Instance == null)
{
     Debug.LogError("This script expects that you have a WorldAnchorManager component in your scene.");
} else if (!IsBeingPlaced)
{

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my PR addressing the updates to the WorldAnchorManager I actually remove this line. The WorldAnchorManger shouldn't be a required dependency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I did not. Looks like I also made the hard dependency. I think it would be nice if we didn't need to require it. The script should work as is.

{
if (!ParentGameObjectToPlace)
{
Debug.LogWarning("No parent has been set or found.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to spam log warnings if there isn't a parent intentionally setup on purpose?

Copy link
Author

@Zod- Zod- Aug 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I actually had trouble keeping those log messages in from the old code, rather than just throwing them away.

//Removes existing world anchor if any exist.
WorldAnchorManager.Instance.RemoveAnchor(gameObject);
//Removes existing world anchor if any exist.
WorldAnchorManager.Instance.RemoveAnchor(gameObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check the instance here too. WorldAnchorManager should be optional.

{
var layerCacheTarget = PlaceParentOnTap ? ParentGameObjectToPlace : gameObject;
layerCacheTarget.SetLayerRecursively(IgnoreRaycastLayer, out layerCache);
InputManager.Instance.PushModalInputHandler(gameObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome. In fact, we probably don't even have to change the layers of the object or its children because of this change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modal shouldn't exclude from raycasts if I understood it correctly, I'll give it a try.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As expected the object will just keep moving towards the player when not setting the layers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking.

ParentGameObjectToPlace = gameObject.transform.parent.gameObject;
}
}
SpatialMappingManager.Instance.DrawVisualMeshes = IsBeingPlaced && AllowMeshVisualizationControl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SpatialMappingManager is also optional. We should check if we have a valid instance.

/// <param name="headPosition"></param>
/// <param name="gazeDirection"></param>
/// <param name="defaultGazeDistance"></param>
/// <returns></returns>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add the descriptions of these parameters and what we're returning.


if (ignore.Contains(parent)) { continue; }

foreach (Transform child in parent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: for loop instead of foreach

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the inner loop?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For any loop if possible. Just a perf optimization.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess parent.GetChild(index)? I won't be able to change the while loop as its dependent on the queue that constantly gets filled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

            for (int i = 0; i < parent.childCount; i++)
            {
                parentsQueue.Enqueue(parent.GetChild(i));
            }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, parent isn't a Transform Component?

Partly why I avoid var when types are not evident. No worries then.

@@ -10,14 +10,10 @@ namespace HoloToolkit.Unity
/// </summary>
public static class Utils
{
[System.Obsolete("Use GameObjectExtensions.SetLayerRecursively(gameObject, layer) instead;")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Does this PR also update the core MRTK scripts that use this obsolete reference?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find any references to this method.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we not just deleting this class then?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the strategy is for making methods obsolete. This is in the public Utils class so people could potentially use it in their projects. Do we just straight up remove methods or set them obsolete and delete them in 1-2 releases?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like your approach of making it obsolete! Let's use it for now and see how it pans out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zod- yup you're correct, generally we mark as obsolete and remove later after people get a chance to update their references.

Move attach/remove into separate methods.
Remove warnings about parents in TapToPlace
Check SpatialMapper for null
Added more docs to helper methods
Renamed variables in IterateHierarchy
Use for loop for children and move null check to the
top of the loop with the other continue.
Add a test for the null check
@Zod-
Copy link
Author

Zod- commented Aug 19, 2017

Is there any reason there are 2 extensions for getting the full path of an object in a hierarchy?

There is:

TransformExtensions.GetFullPath(this Transform transform, string delimiter = ".", string prefix = "/")
GameObjectExtensions.GetFullPath(this GameObject go)

Each with different results and implementations. We could just link the GameObject one to the more customizable Transform one if we want to keep it.


objectToSet.gameObject.layer = IgnoreRaycastLayer;
}
spatialMapHit = new RaycastHit();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not new and instead caller should check for null? So hitInfo.point does not fail?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like on the physics raycast the caller checks for the return to be true. You can't set the hit info to null since it's non-nullable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right!

@StephenHodgson
Copy link
Contributor

Is there any reason there are 2 extensions for getting the full path of an object in a hierarchy?

I'm okay with removing one of them.

foreach (var child in root.transform.EnumerateHierarchy())
{
int layer;
if (!cache.TryGetValue(child.gameObject, out layer)) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: braces

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn I keep forgetting about those

Copy link

@jessemcculloch jessemcculloch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look great. I like all the tests. Well done @Zod-

@Zod-
Copy link
Author

Zod- commented Aug 28, 2017

I've written some more tests in a branch based on this one that doesn't include any changes to classes in the main toolkit.

@NeerajW NeerajW merged commit 8ed1817 into microsoft:master Aug 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants