Saturday, July 12, 2025

Learn how to virtualize an 8-ball pool desk for an AR sport which makes use of each actual and digital objects utilizing Meta Quest 3 (v77) and Unity 6

I’m creating an AR sport in the place I want to virtualize an 8-ball pool desk. Since semantic labeling is virtually ineffective, I want to have some handbook placement of objects and a few automatization. Nonetheless I can not seem to repair the position of the digital pockets for my desk. I’m inserting 3 pockets manually (from my preliminary perspective these are going to be backside left, left center and backside – the order will be argued, however it’ll be a hard and fast one), the opposite 3 pockets are going to be routinely generated. Hovewer more often than not they’re weirdly rotated (as group round a pivot) as they’re all parented to a dad or mum sport object whose dad or mum is the script I’m utilizing to put the pockets and autogenerate the lacking ones.
The primary picture reveals how my ther markers are positioned (aligning them goes to be one other subject), the second how I think about it’s appropriate and third one how more often than not the code flips it incorrectly to the correct.
Learn how to virtualize an 8-ball pool desk for an AR sport which makes use of each actual and digital objects utilizing Meta Quest 3 (v77) and Unity 6
Second image
Third

The present code that handles the logic is the next:

utilizing Oculus.Interplay;
utilizing System.Collections.Generic;
utilizing TMPro;
utilizing UnityEngine;

public class PocketSetupManager : MonoBehaviour
{
    (Header("Marker Placement Settings"))
    (Tooltip("Prefab for the pocket marker (should have collider, rigidbody, Grabbable, HandGrabInteractable, and so forth.)."))
    public GameObject markerPrefab;
    public GameObject pocketMarkerRoot;

    //OVR Controls
    (Tooltip("Reference to left hand OVRHand part (for gesture detection)."))
    public OVRHand leftHand;
    (Tooltip("Reference to proper hand OVRHand part (for gesture detection)."))
    public OVRHand rightHand;
    (Tooltip("Occasion sources for left and proper supply."))
    public OVRMicrogestureEventSource LeftHandEventSource;
    public OVRMicrogestureEventSource RightHandEventSource;

    (Tooltip("Reference to the hand visible parts."))
    public HandVisual leftHandVisual;
    public HandVisual rightHandVisual;

    (Tooltip("Controller button to put a marker (fallback). e.g., Button.One = A (Proper) or X (Left)."))
    public OVRInput.Button placeMarkerButton = OVRInput.Button.One;
    (Tooltip("Controller button to undo final marker (fallback). e.g., Button.Two = B (Proper) or Y (Left)."))
    public OVRInput.Button undoMarkerButton = OVRInput.Button.Two;

    (Header("UI"))
    (Tooltip("Floating instruction textual content exhibited to the person"))
    public TextMeshProUGUI instructionTextUI;

    (Header("Privates"))
    (Tooltip("Floating instruction textual content exhibited to the person"))
    (SerializeField)
    non-public string _instructionText = string.Empty;
    (SerializeField)
    non-public sbyte _totalPockedNeeded = 3;

    non-public float _yCoordinateValueForMarkers = 0;

    non-public readonly string() _pocketNames =
    {
        PocketName.BottomLeftCorner.ToString(),
        PocketName.MiddleLeftCorner.ToString(),
        PocketName.BottomRightCorner.ToString(),
        PocketName.TopLeftCorner.ToString(),
        PocketName.MiddleRightCorner.ToString(),
        PocketName.TopRightCorner.ToString(),
    };

    non-public Listing<GameObject> _placedMarkers = new(6);
    non-public sbyte _markersPlacedCount = 0;
    non-public bool _groupModeActivate = false;
    non-public bool _allPocketsCalculated = false;

    // Begin is known as as soon as earlier than the primary execution of Replace after the MonoBehaviour is created

    non-public void Awake()
    {
        if (instructionTextUI != null)
        {
            instructionTextUI.textual content = _instructionText;
            UpdateInstructionUI();
        }
        else
        {
            Debug.LogWarning("Instruction textual content UI refence not set.");
        }
    }
    void Begin()
    {
        if (LeftHandEventSource == null || RightHandEventSource == null)
        {
            Debug.LogError($"No {nameof(OVRMicrogestureEventSource)} part connected to this gameobject.");
        }
        else
        {
            LeftHandEventSource.GestureRecognizedEvent.AddListener(gesture => OnMicrogestureRecognized(leftHand, gesture));
            RightHandEventSource.GestureRecognizedEvent.AddListener(gesture => OnMicrogestureRecognized(rightHand, gesture));
        }
    }

    // Replace is known as as soon as per body
    void Replace()
    {
        if (_groupModeActivate) return;
        
    }

    void OnApplicationQuit()
    {
        LeftHandEventSource.GestureRecognizedEvent.RemoveAllListeners();
        RightHandEventSource.GestureRecognizedEvent.RemoveAllListeners();
    }

    non-public void OnDestroy()
    {
        LeftHandEventSource.GestureRecognizedEvent.RemoveAllListeners();
        RightHandEventSource.GestureRecognizedEvent.RemoveAllListeners();
    }

    public void OnMicrogestureRecognized(OVRHand hand, OVRHand.MicrogestureType gestureType)
    {
        Debug.Log($"Microgesture occasion: {gestureType} from hand {hand.title}.");

        //Proper hand -> HandType is marked as inner.
        var isRightHand = hand.title.ToLower().Accommodates("proper");

        if (isRightHand)
        {
            swap (gestureType)
            {
                case OVRHand.MicrogestureType.ThumbTap:
                    PlacePocketMarker(hand);
                    break;
                case OVRHand.MicrogestureType.SwipeRight:
                    FinalizePlacements();
                    break;
                case OVRHand.MicrogestureType.SwipeLeft:
                case OVRHand.MicrogestureType.SwipeForward:
                case OVRHand.MicrogestureType.SwipeBackward:
                default:
                    Debug.Log("Gesture presently not supported.");
                    break;
            }
        }

        UpdateInstructionUI();
    }

    non-public void FinalizePlacements()
    {
        if (_allPocketsCalculated)
        {
            _instructionText = "All pockets have been positioned at their revered positions. Nothing to do right here.";
            Debug.Log(_instructionText);
            UpdateInstructionUI();

            return;
        }

        if (_markersPlacedCount < _totalPockedNeeded)
        {
            Debug.LogWarning("Can not finalize: not all required pocket markers are positioned but.");
            return;
        }

        Vector3 bottomLeft = _placedMarkers((byte)PocketName.BottomLeftCorner).rework.place;
        Vector3 middleLeft = _placedMarkers((byte)PocketName.MiddleLeftCorner).rework.place;
        Vector3 bottomRight = _placedMarkers((byte)PocketName.BottomRightCorner).rework.place;

        //High left
        Vector3 bottomLeftToLeftMiddle = middleLeft - bottomLeft;
        Vector3 topLeft = bottomLeft + 2 * bottomLeftToLeftMiddle;

        //Proper center
        Vector3 bottomLeftToBottomRight = bottomRight - bottomLeft;
        Vector3 middleRight = middleLeft + bottomLeftToBottomRight;

        //High Proper
        Vector3 topRight = topLeft + bottomLeftToBottomRight;

        topLeft.y = _yCoordinateValueForMarkers;
        middleRight.y = _yCoordinateValueForMarkers;
        topRight.y = _yCoordinateValueForMarkers;

        GameObject topLeftMarker = Instantiate(markerPrefab, topLeft, Quaternion.id);
        topLeftMarker.title = _pocketNames((byte)PocketName.TopLeftCorner);
        GameObject middleRightMarker = Instantiate(markerPrefab, middleRight, Quaternion.id);
        middleRightMarker.title = _pocketNames((byte)PocketName.MiddleRightCorner);
        GameObject topRightMarker = Instantiate(markerPrefab, topRight, Quaternion.id);
        topRightMarker.title = _pocketNames((byte)PocketName.TopRightCorner);

        if (pocketMarkerRoot != null)
        {
            topLeftMarker.rework.SetParent(pocketMarkerRoot.rework, true);
            middleRightMarker.rework.SetParent(pocketMarkerRoot.rework, true);
            topRightMarker.rework.SetParent(pocketMarkerRoot.rework, true);
        }

        _placedMarkers((byte)PocketName.TopLeftCorner) = topLeftMarker;
        _placedMarkers((byte)PocketName.MiddleRightCorner) = middleRightMarker;
        _placedMarkers((byte)PocketName.TopRightCorner) = topRightMarker;

        _markersPlacedCount = 6;


        _instructionText = "All 6 pockets positioned!";
        Debug.Log("All pockets positioned and finalized.");
        _allPocketsCalculated = true;
        UpdateInstructionUI();
    }

    non-public void PlacePocketMarker(OVRHand hand)
    {
        if (_markersPlacedCount == _totalPockedNeeded)
        {
            _instructionText = "The entire required pockets have been instantiated. You may seize them and reposition them, earlier than swiping proper.";
            UpdateInstructionUI();
            return;
        }

        //if (_groupModeActivate) return;
        if (pocketMarkerRoot == null)
        {
            Debug.LogError("No pocket marker root has been instantiated or has been deleted in runtime, so no (furher) pockets will be positioned.");
            return;
        }

        var palmPosition = GetPalmWorldPosition(hand);

        // For the primary pocket, retailer the Y-level
        if (_markersPlacedCount == 0)
        {
            _yCoordinateValueForMarkers = palmPosition.y;
            Debug.Log($"PalmPosition {palmPosition.y}");
            Debug.DrawRay(palmPosition, Vector3.up * 0.1f, Shade.inexperienced, 2f);
        }

        // Override Y to all the time match the primary pocket’s Y
        palmPosition.y = _yCoordinateValueForMarkers;

        // Instantiate marker at (X,Z) of your hand + mounted Y
        GameObject marker = Instantiate(markerPrefab, palmPosition, Quaternion.id);
        marker.title = _pocketNames(_markersPlacedCount);
        marker.rework.SetParent(pocketMarkerRoot.rework, worldPositionStays: true);

        _placedMarkers.Add(marker);
        _markersPlacedCount++;

        // Suggestions message
        sbyte remaining = (sbyte)(_totalPockedNeeded - _markersPlacedCount);
        _instructionText = remaining > 0
            ? $"<b>{marker.title}</b> positioned! {remaining} extra to go…"
            : $"<b>{marker.title}</b> positioned! Swipe proper to verify.";

        UpdateInstructionUI();
    }

    non-public Vector3 GetPalmWorldPosition(OVRHand hand)
    {
        HandVisual handVisual = (hand == leftHand) ? leftHandVisual : rightHandVisual;

        if (handVisual == null)
        {
            Debug.LogWarning("Hand visible part not assigned.");
            return hand.rework.place;
        }

        return handVisual.GetTransformByHandJointId(Oculus.Interplay.Enter.HandJointId.HandPalm).place;
    }

    non-public void UpdateInstructionUI()
    {
        if (instructionTextUI != null)
        {
            instructionTextUI.textual content = _instructionText;
            _instructionText = string.Empty;
        }
        else
        {
            Debug.LogWarning("Instruction textual content UI not assigned.");
        }
    }
    
    public void UndoLastMarker()
    {
        if (_markersPlacedCount == 0) return;


        _markersPlacedCount--;
        _markersPlacedCount = _markersPlacedCount < 0 ? (sbyte)0 : _markersPlacedCount;
    }
}

The prefab of the pocket additionally comprises a scripts which limits the interplay to X and Z.

utilizing UnityEngine;

(RequireComponent(typeof(Remodel)))
public class XZOnlyConstraint : MonoBehaviour
{
    (Tooltip("Whether or not this marker can presently be grabbed by the person."))
    public bool GrabbableEnabled = true;

    non-public Vector3 _initialPosition;
    non-public Quaternion _initialRotation;
    non-public bool _isInitialized = false;

    public void Initialize(Quaternion worldRotation)
    {
        _initialPosition = rework.place;
        _initialRotation = worldRotation;
        _isInitialized = true;
    }

    public void Initialize() => Initialize(rework.rotation);

    non-public void LateUpdate()
    
}

Learn how to aproach this subject of inserting the pockets in order that I can deal with different components of defining the play space and what I’m doing mistaken?
The important options of the pocket placement half are:

  • Putting the pockets, having the manually positioned pockets aligned.
  • Autogenerated the remaining pockets.
  • Undoing some (or all) misplaced pockets.
  • Confirming the position and hold it someplace so somebody from the opposite Quest can acces it?

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles