28.2 C
New York
Friday, July 11, 2025

How one can 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 wish to virtualize an 8-ball pool desk. Since semantic labeling is virtually ineffective, I wish to have some handbook placement of objects and a few automatization. Nonetheless I can not seem to repair the location of the digital pockets for my desk. I’m putting 3 pockets manually (from my preliminary perspective these are going to be backside left, left center and backside – the order might be argued, however it will be a set 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 father or mother sport object whose father or mother is the script I’m utilizing to put the pockets and autogenerate the lacking ones.
The primary picture exhibits how my ther markers are positioned (aligning them goes to be one other challenge), the second how I think about it’s appropriate and third one how more often than not the code flips it incorrectly to the fitting.

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 (must have collider, rigidbody, Grabbable, HandGrabInteractable, etc.).")]
    public GameObject markerPrefab;
    public GameObject pocketMarkerRoot;

    //OVR Controls
    [Tooltip("Reference to left hand OVRHand component (for gesture detection).")]
    public OVRHand leftHand;
    [Tooltip("Reference to right hand OVRHand component (for gesture detection).")]
    public OVRHand rightHand;
    [Tooltip("Event sources for left and right source.")]
    public OVRMicrogestureEventSource LeftHandEventSource;
    public OVRMicrogestureEventSource RightHandEventSource;

    [Tooltip("Reference to the hand visual components.")]
    public HandVisual leftHandVisual;
    public HandVisual rightHandVisual;

    [Tooltip("Controller button to place a marker (fallback). e.g., Button.One = A (Right) or X (Left).")]
    public OVRInput.Button placeMarkerButton = OVRInput.Button.One;
    [Tooltip("Controller button to undo last marker (fallback). e.g., Button.Two = B (Right) or Y (Left).")]
    public OVRInput.Button undoMarkerButton = OVRInput.Button.Two;

    [Header("UI")]
    [Tooltip("Floating instruction text displayed to the user")]
    public TextMeshProUGUI instructionTextUI;

    [Header("Privates")]
    [Tooltip("Floating instruction text displayed to the user")]
    [SerializeField]
    personal string _instructionText = string.Empty;
    [SerializeField]
    personal sbyte _totalPockedNeeded = 3;

    personal float _yCoordinateValueForMarkers = 0;

    personal readonly string[] _pocketNames =
    {
        PocketName.BottomLeftCorner.ToString(),
        PocketName.MiddleLeftCorner.ToString(),
        PocketName.BottomRightCorner.ToString(),
        PocketName.TopLeftCorner.ToString(),
        PocketName.MiddleRightCorner.ToString(),
        PocketName.TopRightCorner.ToString(),
    };

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

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

    personal 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)} element hooked up to this gameobject.");
        }
        else
        {
            LeftHandEventSource.GestureRecognizedEvent.AddListener(gesture => OnMicrogestureRecognized(leftHand, gesture));
            RightHandEventSource.GestureRecognizedEvent.AddListener(gesture => OnMicrogestureRecognized(rightHand, gesture));
        }
    }

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

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

    personal 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 inside.
        var isRightHand = hand.title.ToLower().Incorporates("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 at the moment not supported.");
                    break;
            }
        }

        UpdateInstructionUI();
    }

    personal 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't 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.identification);
        topLeftMarker.title = _pocketNames[(byte)PocketName.TopLeftCorner];
        GameObject middleRightMarker = Instantiate(markerPrefab, middleRight, Quaternion.identification);
        middleRightMarker.title = _pocketNames[(byte)PocketName.MiddleRightCorner];
        GameObject topRightMarker = Instantiate(markerPrefab, topRight, Quaternion.identification);
        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();
    }

    personal void PlacePocketMarker(OVRHand hand)
    {
        if (_markersPlacedCount == _totalPockedNeeded)
        {
            _instructionText = "All the 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 might 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, Colour.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.identification);
        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();
    }

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

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

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

    personal 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 incorporates a scripts which limits the interplay to X and Z.

utilizing UnityEngine;

[RequireComponent(typeof(Transform))]
public class XZOnlyConstraint : MonoBehaviour
{
    [Tooltip("Whether this marker can currently be grabbed by the user.")]
    public bool GrabbableEnabled = true;

    personal Vector3 _initialPosition;
    personal Quaternion _initialRotation;
    personal bool _isInitialized = false;

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

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

    personal void LateUpdate()
     !this.isActiveAndEnabled)
            return;

        // Lock Y peak
        Vector3 pos = rework.place;
        pos.y = _initialPosition.y;
        
        // Lock rotation to preserved
        rework.SetPositionAndRotation(pos, _initialRotation);
    
}

How one can aproach this challenge of putting the pockets in order that I can deal with different components of defining the play space and what I’m doing flawed?
The important options of the pocket placement half are:

  • Inserting the pockets, having the manually positioned pockets aligned.
  • Autogenerated the remaining pockets.
  • Undoing some (or all) misplaced pockets.
  • Confirming the location and preserve 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