Vrtk: Multiple overlapping Interactable Snap Zones will all try and snap the same object

Created on 7 May 2019  路  8Comments  路  Source: ExtendRealityLtd/VRTK

Environment

  • Source of VRTK: Github
  • Version of VRTK: v4 Github master
  • Version of the Unity software: 2019.1
  • Hardware used: Oculus
  • SDK used: UnityXR

Steps to reproduce

Create two snap zones that potentially overlap each other or at are least close enough to each other that when an interactable is placed it can activate both snap zones.

image

Now place the interactable between the two snap zones and release it so it snaps.

image

The Interactable will snap to the first zone it touched.

Expected behavior

The Interactable should only run the snap logic on the first snap zone it touches.

Current behavior

The snapped interactable runs the snap logic on every snap zone it is touching, which is immediately noticeable when it is trying to reset the scale of the object as the zone it first snaps to will set the size and the second zone will then cache the current size so when the object is removed it is of the wrong size.

zoneissue

bug

Most helpful comment

snapzone

progress!

All 8 comments

My current solution for this is to have each Snap Drop Zone contain a Manager script that basically holds a list of all GameObjects that are currently being snapped in a static List<GameObject> so all snap zones are referring to the same global list (without the need to have a Snap Zone component manager)

Then when an object is dropped into two snap zones it will first check if it can snap the object by checking in this list, if the object doesn't appear in the list then it emits an event that then goes through the snap process and adds the item to the list.

Then the next time a zone tries to snap the item it will fail because it is in the list and therefore not run the snap process again.

Here is my proposed Manager:

```c#
namespace VRTK.Prefabs.Interactions.InteractableSnapZone
{
using UnityEngine;
using UnityEngine.Events;
using System;
using System.Collections.Generic;
using Malimbe.XmlDocumentationAttribute;

public class SnapZoneManager : MonoBehaviour
{
    /// <summary>
    /// Defines the event with the <see cref="GameObject"/>.
    /// </summary>
    [Serializable]
    public class UnityEvent : UnityEvent<GameObject> { }

    #region Zone Events
    /// <summary>
    /// Emitted when a valid <see cref="GameObject"/> is snapped to the zone.
    /// </summary>
    [DocumentedByXml]
    public UnityEvent Validated = new UnityEvent();
    /// <summary>
    /// Emitted when a valid <see cref="GameObject"/> is snapped to the zone.
    /// </summary>
    [DocumentedByXml]
    public UnityEvent NotValidated = new UnityEvent();
    /// <summary>
    /// Emitted when a valid <see cref="GameObject"/> is unsnapped from the zone.
    /// </summary>
    [DocumentedByXml]
    public UnityEvent Cleared = new UnityEvent();
    #endregion

    protected static List<GameObject> snappedObjects = new List<GameObject>();

    public virtual bool CanSnap(GameObject interactable)
    {
        if (snappedObjects.Contains(interactable))
        {
            NotValidated?.Invoke(interactable);
            return false;
        }

        snappedObjects.Add(interactable);
        Validated?.Invoke(interactable);
        return true;
    }

    public virtual void DoCanSnap(GameObject interactable)
    {
        CanSnap(interactable);
    }

    public virtual void ClearSnap(GameObject interactable)
    {
        if (snappedObjects.Remove(interactable))
        {
            Cleared?.Invoke(interactable);
        }
    }
}

}
```

This is the output:

goodsnap

@bddckr thoughts on the proposed solution, or can you come up with any alternatives?

Further investigation led to show there was an actual issue with the CollisionNotifier.EventData comparator meaning the list of colliding objects was not being maintained correctly.

This has now been fixed in zinnia, meaning there's probably a better solution than the above solution.

Now the Interactable contains a list of active colliders (i.e. a list of all snap zones it's touching)

So can now probably query that list on point of snapping and determine if the current zone is the first item in that list, if it is then the snap can occur, if it isn't then the snap wont occur)

In the future that list could also be sorted to snap to the nearest zone origin, etc.

the above solution still isn't enough.

if an interactable intersects two drop zones at the same time, then only one drop zone should become active (the one that will be dropped into)

the above solution will only prevent running the dropped logic it won't prevent any highlighting logic.

This is my proposed logic for fixing the issue:

  • IO touches SDZ
  • Is this a valid collision based on the rule? If no then bail, if yes continue
  • SDZ registers active collision and tells IO of valid interactors
  • On SDZ collision, check if collider is of IO type
  • Is IO being grabbed?

    • If no, then you can鈥檛 snap this on ungrab so instead register a grab listener on the IO and when it is grabbed again then do the check if it鈥檚 a valid IO collision to activate the SDZ
    • If yes, then this is something that can activate the SDZ and can be snapped to an unpopulated zone when dropped.
  • First check to see if the IO can activate the SDZ. Only IOs that aren鈥檛 already activating another SDZ can activate it.

  • To check if the IO is activating another zone, loop through the active collisions on the IO. for each collision that is of type SDZ, if the first found is not this SDZ then it must be activating another SDZ.

    • If it is activating another SDZ then if it stops colliding with that other SDZ then it may be valid to activate the current SDZ, so in this case register a listener on the actual SDZ that it鈥檚 activating the IO on the SDZ deactivate event.
    • If the other SDZ deactivate event occurs then this SDZ will now know the IO that was activating is has now become available to try and activate this SDZ so pass the IO back through the SDZ checklist.
    • Store this listener in a dictionary with the IO as a key on this SDZ. Then if the IO stops touching this SDZ any listener for the other SDZ event can be unregistered.
  • If the IO is not activating another SDZ then listen for the ungrab event on the IO and when the IO is ungrabbed check if it can be snapped to this SDZ.

    • If the SDZ is already populated then don鈥檛 enable the highlight
  • If the SDZ is not populated then engage the highlight

    • If this SDZ is already populated then just consider the ungrab of this IO as if the IO was touching but not grabbed (like above) and register the listener on the IO grab event (so in the future if the IO is grabbed, then all the logic goes through as normal)

  • If the SDZ is not populated when the IO is ungrabbed then initiate the snap procedure.

snapzone

progress!

More progress with extra edge cases handled.
snapzone

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cameronoltmann picture cameronoltmann  路  6Comments

ajaxlex picture ajaxlex  路  8Comments

dantman picture dantman  路  6Comments

garytyler picture garytyler  路  5Comments

AzzyDude24601 picture AzzyDude24601  路  6Comments