private void PreviewArea() { //visualize: Vector3[] playspacePerimeterOnFloor = new Vector3[_playspaceCorners.Count]; Vector3[] playspacePerimeterOnCeiling = new Vector3[_playspaceCorners.Count]; //populate floor and ceiling corner loops: for (int i = 0; i < _playspaceCorners.Count; i++) { //cache floor and ceiling locations: Vector3 floor = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i]); Vector3 ceiling = floor + new Vector3(0, Height); playspacePerimeterOnFloor[i] = floor; playspacePerimeterOnCeiling[i] = ceiling; //draw corner supports: LineRenderer cornerSupportsOutline = Lines.DrawLine($"PlayspaceCornerSupport{i}", Color.green, Color.green, .005f, floor, ceiling); cornerSupportsOutline.material = perimeterOutlineMaterial; } //draw ceiling and floor perimeter: LineRenderer ceilingOutline = Lines.DrawLine($"PlayspaceCeilingOutline", Color.green, Color.green, .005f, playspacePerimeterOnCeiling); LineRenderer floorOutline = Lines.DrawLine($"PlayspaceFloorOutline", Color.green, Color.green, .005f, playspacePerimeterOnFloor); ceilingOutline.material = perimeterOutlineMaterial; floorOutline.material = perimeterOutlineMaterial; }
//Loops: private void Update() { //wait for hand input to come online: if (!HandInput.Ready) { return; } //shoulder: float shoulderDistance = shoulderWidth * .5f; //swap for the left shoulder: if (handedness == MLHandTracking.HandType.Left) { shoulderDistance *= -1; } //source locations: Vector3 flatForward = Vector3.ProjectOnPlane(_mainCamera.forward, Vector3.up); Vector3 shoulder = TransformUtilities.WorldPosition(_mainCamera.position, Quaternion.LookRotation(flatForward), new Vector2(shoulderDistance, Mathf.Abs(shoulderDistanceBelowHead) * -1)); Vector3 pointerOrigin = Vector3.Lerp(Hand.Skeleton.Thumb.Knuckle.positionFiltered, Hand.Skeleton.Position, .5f); //direction: Quaternion orientation = Quaternion.LookRotation(Vector3.Normalize(pointerOrigin - shoulder), Hand.Skeleton.Rotation * Vector3.up); //application: transform.position = pointerOrigin; transform.rotation = orientation; }
private void FindWalls_GenerateGeometry() { //transform matrix: Vector3 correctedForward = Vector3.ProjectOnPlane(pcfAnchor.Rotation * Vector3.forward, Vector3.up).normalized; Quaternion correctedRotation = Quaternion.LookRotation(correctedForward); Matrix4x4 transformMatrix = Matrix4x4.TRS(pcfAnchor.Position, correctedRotation, Vector3.one); //build wall geometry: Vector3 verticalOffset = Vector3.up * Height; List <Vector3> wallVerticies = new List <Vector3>(); List <int> wallTriangles = new List <int>(); for (int i = 0; i < _playspaceCorners.Count - 1; i++) { Vector3 pointA = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i]); Vector3 pointB = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i + 1]); //locate verticies (local space): Vector3 a = transformMatrix.inverse.MultiplyPoint3x4(pointA); Vector3 b = transformMatrix.inverse.MultiplyPoint3x4(pointB); Vector3 c = b + verticalOffset; Vector3 d = a + verticalOffset; //store verticies (reverse order so normals face inwards): wallVerticies.Add(d); wallVerticies.Add(c); wallVerticies.Add(b); wallVerticies.Add(a); //store triangles wallTriangles.Add(wallVerticies.Count - 4); wallTriangles.Add(wallVerticies.Count - 3); wallTriangles.Add(wallVerticies.Count - 2); wallTriangles.Add(wallVerticies.Count - 2); wallTriangles.Add(wallVerticies.Count - 1); wallTriangles.Add(wallVerticies.Count - 4); } //get points for polygon construction: Vector2[] polygonPoints = new Vector2[_playspaceCorners.Count - 1]; Vector3[] floorVerticies = new Vector3[_playspaceCorners.Count - 1]; Vector3[] ceilingVerticies = new Vector3[_playspaceCorners.Count - 1]; for (int i = 0; i < _playspaceCorners.Count - 1; i++) { Vector3 point = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i]); Vector3 local = transformMatrix.inverse.MultiplyPoint3x4(point); polygonPoints[i] = new Vector2(local.x, local.z); floorVerticies[i] = local; ceilingVerticies[i] = local + verticalOffset; } //triangles: int[] floorTriangles = new Triangulator(polygonPoints).Triangulate(); BuildGeometry(wallVerticies.ToArray(), wallTriangles.ToArray(), floorVerticies, floorTriangles, ceilingVerticies); //FindWalls_Analyze(); ChangeState(State.ConfirmArea); }
/// <summary> /// Updates peers on the position, scale, and rotation of this transmission object. /// </summary> public void Synchronize() { if (_isMine) { //update relative: localPosition = TransformUtilities.LocalPosition(Transmission.Instance.sharedOrigin.position, Transmission.Instance.sharedOrigin.rotation, transform.position); rotationOffset = TransformUtilities.GetRotationOffset(Transmission.Instance.sharedOrigin.rotation, transform.rotation); //send out the change to this transform: Transmission.Send(new TransformSyncMessage(this)); } }
private void HandleTransformSync(TransformSyncMessage transformSyncMessage) { //is this a pose message for us? if (transformSyncMessage.ig != guid) { return; } //unpack: Vector3 receivedLocalPosition = new Vector3((float)transformSyncMessage.px, (float)transformSyncMessage.py, (float)transformSyncMessage.pz); Quaternion receivedRotationOffset = new Quaternion((float)transformSyncMessage.rx, (float)transformSyncMessage.ry, (float)transformSyncMessage.rz, (float)transformSyncMessage.rw); _targetPosition = TransformUtilities.WorldPosition(Transmission.Instance.sharedOrigin.position, Transmission.Instance.sharedOrigin.rotation, receivedLocalPosition); _targetRotation = TransformUtilities.ApplyRotationOffset(Transmission.Instance.sharedOrigin.rotation, receivedRotationOffset); targetScale = new Vector3((float)transformSyncMessage.sx, (float)transformSyncMessage.sy, (float)transformSyncMessage.sz); }
//Init: private void Awake() { //sets: localPosition = TransformUtilities.LocalPosition(Transmission.Instance.sharedOrigin.position, Transmission.Instance.sharedOrigin.rotation, transform.position); rotationOffset = TransformUtilities.GetRotationOffset(Transmission.Instance.sharedOrigin.rotation, transform.rotation); targetScale = transform.localScale; //catalog: _all.Add(guid, this); //hooks: Transmission.Instance.OnPeerFound.AddListener(HandlePeerFound); Transmission.Instance.OnTransformSync.AddListener(HandleTransformSync); Transmission.Instance.OnOwnershipGained.AddListener(HandleOwnershipGained); Transmission.Instance.OnOwnershipTransferDenied.AddListener(HandleOwnershipTransferDenied); StartCoroutine("ShareTransformStatus"); }
public void Update(ManagedHand managedHand, Vector3 keyPointLocation, params Vector3[] decayPoints) { if (!managedHand.Visible) { //lost: if (Visible) { FireLostEvent(); _progress.locationHistory.Clear(); } return; } //visibility status: bool currentVisibility = true; //too close to next joint in chain means visibility failed: if (Vector3.Distance(keyPointLocation, _mainCamera.transform.position) < _minHeadDistance) { currentVisibility = false; } else { for (int i = 0; i < decayPoints.Length; i++) { if (Vector3.Distance(keyPointLocation, decayPoints[i]) < _lostKeyPointDistance) { currentVisibility = false; break; } } } positionRaw = keyPointLocation; //lost visibility: if (!currentVisibility && Visible) { FireLostEvent(); _progress.locationHistory.Clear(); return; } //history cache: _progress.locationHistory.Add(keyPointLocation); //only need 3 in our history: if (_progress.locationHistory.Count > 3) { _progress.locationHistory.RemoveAt(0); } //we have enough history: if (_progress.locationHistory.Count == 3) { //movement intent stats: Vector3 vectorA = _progress.locationHistory[_progress.locationHistory.Count - 2] - _progress.locationHistory[_progress.locationHistory.Count - 3]; Vector3 vectorB = _progress.locationHistory[_progress.locationHistory.Count - 1] - _progress.locationHistory[_progress.locationHistory.Count - 2]; float delta = Vector3.Distance(_progress.locationHistory[_progress.locationHistory.Count - 3], _progress.locationHistory[_progress.locationHistory.Count - 1]); float angle = Vector3.Angle(vectorA, vectorB); Stability = 1 - Mathf.Clamp01(delta / _maxDistance); //moving in a constant direction? if (angle < 90) { _progress.target = _progress.locationHistory[_progress.locationHistory.Count - 1]; } //snap or smooth: if (Stability == 0) { positionFiltered = _progress.target; } else { positionFiltered = Vector3.SmoothDamp(positionFiltered, _progress.target, ref _progress.velocity, _smoothTime * Stability); } } else { positionFiltered = keyPointLocation; } //inside the camera plane - flatten against the plane? InsideClipPlane = TransformUtilities.InsideClipPlane(positionFiltered); if (InsideClipPlane) { positionFiltered = TransformUtilities.LocationOnClipPlane(positionFiltered); } //gained visibility: if (currentVisibility && !Visible) { //we must also break distance for point proximity: for (int i = 0; i < decayPoints.Length; i++) { if (Vector3.Distance(keyPointLocation, decayPoints[i]) < _foundKeyPointDistance) { currentVisibility = false; break; } } //still good? if (currentVisibility) { FireFoundEvent(); } } }
//Public Methods: public void Update() { if (!_managedHand.Visible) { return; } //pinch rotation offset mirror: Vector3 rotationOffset = _pinchAbsoluteRotationOffset; if (_managedHand.Hand.Type == MLHandTracking.HandType.Left) { rotationOffset.y *= -1; } //holders: Vector3 pinchPosition = Vector3.zero; Quaternion pinchRotation = Quaternion.identity; //pinch interaction point radius: if (_managedHand.Skeleton.Thumb.Tip.Visible && _managedHand.Skeleton.Index.Tip.Visible) { Pinch.radius = Vector3.Distance(_managedHand.Skeleton.Thumb.Tip.positionFiltered, _managedHand.Skeleton.Index.Tip.positionFiltered); } if (_managedHand.Skeleton.Thumb.Tip.Visible) //absolute placement: { //are we swapping modes? if (_pinchIsRelative) { _pinchIsRelative = false; _pinchTransitioning = true; _pinchTransitionStartTime = Time.realtimeSinceStartup; } pinchPosition = _managedHand.Skeleton.Thumb.Tip.positionFiltered; pinchRotation = TransformUtilities.RotateQuaternion(_managedHand.Skeleton.Rotation, rotationOffset); //gather offset distance: if (_managedHand.Skeleton.Index.Knuckle.Visible && _managedHand.Skeleton.Thumb.Knuckle.Visible) { Vector3 mcpMidpoint = Vector3.Lerp(_managedHand.Skeleton.Index.Knuckle.positionFiltered, _managedHand.Skeleton.Thumb.Knuckle.positionFiltered, .5f); _pinchRelativePositionDistance = Vector3.Distance(mcpMidpoint, pinchPosition); } } else //relative placement: { //are we swapping modes? if (!_pinchIsRelative) { _pinchIsRelative = true; _pinchTransitioning = true; _pinchTransitionStartTime = Time.realtimeSinceStartup; } //place between available mcps: if (_managedHand.Skeleton.Index.Knuckle.Visible && _managedHand.Skeleton.Thumb.Knuckle.Visible) { pinchPosition = Vector3.Lerp(_managedHand.Skeleton.Index.Knuckle.positionFiltered, _managedHand.Skeleton.Thumb.Knuckle.positionFiltered, .5f); //rotate: pinchRotation = TransformUtilities.RotateQuaternion(_managedHand.Skeleton.Rotation, _pinchRelativeRotationOffset); //move out along rotation forward: pinchPosition += pinchRotation * Vector3.forward * _pinchRelativePositionDistance; } else { //just use previous: pinchPosition = Pinch.position; pinchRotation = Pinch.rotation; } } //sticky release reduction: if (_collapsed) { if (_managedHand.Skeleton.Thumb.Tip.Visible && _managedHand.Skeleton.Index.Tip.Visible) { //if starting to release, start using a point between the thumb and index tips: if (Vector3.Distance(_managedHand.Skeleton.Thumb.Tip.positionFiltered, _managedHand.Skeleton.Index.Tip.positionFiltered) > _dynamicReleaseDistance) { pinchPosition = Vector3.Lerp(_managedHand.Skeleton.Thumb.Tip.positionFiltered, _managedHand.Skeleton.Index.Tip.positionFiltered, .3f); } } } //apply pinch pose - to avoid jumps when relative placement is used we smooth until close enough: if (_pinchTransitioning) { //position: Pinch.position = Vector3.SmoothDamp(Pinch.position, pinchPosition, ref _pinchArrivalPositionVelocity, _pinchTransitionTime); float positionDelta = Vector3.Distance(Pinch.position, pinchPosition); //rotation: Pinch.rotation = MotionUtilities.SmoothDamp(Pinch.rotation, pinchRotation, ref _pinchArrivalRotationVelocity, _pinchTransitionTime); float rotationDelta = Quaternion.Angle(Pinch.rotation, pinchRotation); //close enough to hand off? if (positionDelta < .001f && rotationDelta < 5) { _pinchTransitioning = false; } //taking too long? if (Time.realtimeSinceStartup - _pinchTransitionStartTime > _pinchTransitionMaxDuration) { _pinchTransitioning = false; } } else { Pinch.position = pinchPosition; Pinch.rotation = pinchRotation; } //grasp interaction point: Bounds graspBounds = CalculateGraspBounds ( _managedHand.Skeleton.Thumb.Knuckle, _managedHand.Skeleton.Thumb.Joint, _managedHand.Skeleton.Thumb.Tip, _managedHand.Skeleton.Index.Knuckle, _managedHand.Skeleton.Index.Joint, _managedHand.Skeleton.Index.Tip, _managedHand.Skeleton.Middle.Knuckle, _managedHand.Skeleton.Middle.Joint, _managedHand.Skeleton.Middle.Tip ); Grasp.position = _managedHand.Skeleton.Position; //when points are being initially found they can be wildly off and this could cause a massively large volume: Grasp.radius = Mathf.Min(graspBounds.size.magnitude, _maxGraspRadius); Grasp.rotation = _managedHand.Skeleton.Rotation; //intent updated: if (_currentInteractionState != null) { _currentInteractionState.FireUpdate(); } //keypose change proposed: if (_managedHand.Hand.KeyPose != VerifiedGesture && _managedHand.Hand.KeyPose != _proposedKeyPose) { //queue a new proposed change to keypose: _proposedKeyPose = _managedHand.Hand.KeyPose; _keyPoseChangedTime = Time.realtimeSinceStartup; } //keypose change acceptance: if (_managedHand.Hand.KeyPose != VerifiedGesture && Time.realtimeSinceStartup - _keyPoseChangedTime > _keyPoseStabailityDuration) { //reset: Point.active = false; Pinch.active = false; Grasp.active = false; if (_collapsed) { //intent end: if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.C || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.OpenHand || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.L || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Finger) { if (_managedHand.Skeleton.Thumb.Tip.Visible && _managedHand.Skeleton.Index.Tip.Visible) { //dynamic release: if (Vector3.Distance(_managedHand.Skeleton.Thumb.Tip.positionFiltered, _managedHand.Skeleton.Index.Tip.positionFiltered) > _dynamicReleaseDistance) { //end intent: _collapsed = false; _currentInteractionState.FireEnd(); _currentInteractionState = null; //accept keypose change: VerifiedGesture = _managedHand.Hand.KeyPose; _proposedKeyPose = _managedHand.Hand.KeyPose; OnVerifiedGestureChanged?.Invoke(_managedHand, VerifiedGesture); if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Finger || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.L) { Intent = IntentPose.Pointing; OnIntentChanged?.Invoke(_managedHand, Intent); } else if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.C || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.OpenHand || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Thumb) { Intent = IntentPose.Relaxed; OnIntentChanged?.Invoke(_managedHand, Intent); } } } } } else { //intent begin: if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Pinch || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Ok || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Fist) { _collapsed = true; if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Pinch || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Ok) { Intent = IntentPose.Pinching; Pinch.active = true; _currentInteractionState = Pinch.Touch; _currentInteractionState.FireBegin(); OnIntentChanged?.Invoke(_managedHand, Intent); } else if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Fist) { Intent = IntentPose.Grasping; Grasp.active = true; _currentInteractionState = Grasp.Touch; _currentInteractionState.FireBegin(); OnIntentChanged?.Invoke(_managedHand, Intent); } } if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Finger || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.L) { Intent = IntentPose.Pointing; Point.active = true; OnIntentChanged?.Invoke(_managedHand, Intent); } else if (_managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.C || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.OpenHand || _managedHand.Hand.KeyPose == MLHandTracking.HandKeyPose.Thumb) { Intent = IntentPose.Relaxed; OnIntentChanged?.Invoke(_managedHand, Intent); } //accept keypose change: VerifiedGesture = _managedHand.Hand.KeyPose; _proposedKeyPose = _managedHand.Hand.KeyPose; OnVerifiedGestureChanged?.Invoke(_managedHand, VerifiedGesture); } } UpdateCurrentInteractionPoint(); UpdatePalmEvents(); }
//Public Methods: public void Update() { //update keypoints and fingers: WristCenter.Update(_managedHand, _managedHand.Hand.Wrist.Center.Position, _managedHand.Hand.Center); HandCenter.Update(_managedHand, _managedHand.Hand.Center); _thumbMCP.Update(_managedHand, _managedHand.Hand.Thumb.MCP.Position, _managedHand.Hand.Index.MCP.Position, _managedHand.Hand.Center); _thumbPIP.Update(_managedHand, _managedHand.Hand.Thumb.IP.Position, _managedHand.Hand.Thumb.MCP.Position, _managedHand.Hand.Center); _thumbTip.Update(_managedHand, _managedHand.Hand.Thumb.Tip.Position, _managedHand.Hand.Thumb.IP.Position, _managedHand.Hand.Middle.Tip.Position, _managedHand.Hand.Thumb.MCP.Position, _managedHand.Hand.Center); Thumb.Update(); _indexMCP.Update(_managedHand, _managedHand.Hand.Index.MCP.Position, _managedHand.Hand.Center); _indexPIP.Update(_managedHand, _managedHand.Hand.Index.PIP.Position, _managedHand.Hand.Index.MCP.Position, _managedHand.Hand.Center); _indexTip.Update(_managedHand, _managedHand.Hand.Index.Tip.Position, _managedHand.Hand.Index.PIP.Position, _managedHand.Hand.Index.MCP.Position, _managedHand.Hand.Center, _managedHand.Hand.Thumb.IP.Position, _managedHand.Hand.Middle.Tip.Position); Index.Update(); _middleMCP.Update(_managedHand, _managedHand.Hand.Middle.MCP.Position, _managedHand.Hand.Center); _middlePIP.Update(_managedHand, _managedHand.Hand.Middle.PIP.Position, _managedHand.Hand.Middle.MCP.Position, _managedHand.Hand.Center); _middleTip.Update(_managedHand, _managedHand.Hand.Middle.Tip.Position, _managedHand.Hand.Middle.PIP.Position, _managedHand.Hand.Middle.MCP.Position, _managedHand.Hand.Ring.Tip.Position, _managedHand.Hand.Center); Middle.Update(); _ringMCP.Update(_managedHand, _managedHand.Hand.Ring.MCP.Position, _managedHand.Hand.Center); _ringTip.Update(_managedHand, _managedHand.Hand.Ring.Tip.Position, _managedHand.Hand.Ring.MCP.Position, _managedHand.Hand.Pinky.Tip.Position, _managedHand.Hand.Middle.Tip.Position, _managedHand.Hand.Center); Ring.Update(); _pinkyMCP.Update(_managedHand, _managedHand.Hand.Pinky.MCP.Position, _managedHand.Hand.Center); _pinkyTip.Update(_managedHand, _managedHand.Hand.Pinky.Tip.Position, _managedHand.Hand.Pinky.MCP.Position, _managedHand.Hand.Ring.Tip.Position, _managedHand.Hand.Center); Pinky.Update(); //we need a hand to continue: if (!_managedHand.Visible) { return; } //correct distances: float thumbMcpToWristDistance = Vector3.Distance(_thumbMCP.positionFiltered, WristCenter.positionFiltered) * .5f; //fix the distance between the wrist and thumbMcp as it incorrectly expands as the hand gets further from the camera: float distancePercentage = Mathf.Clamp01(Vector3.Distance(_mainCamera.transform.position, WristCenter.positionFiltered) / .5f); distancePercentage = 1 - Percentage(distancePercentage, .90f, 1) * .4f; thumbMcpToWristDistance *= distancePercentage; Vector3 wristToPalmDirection = Vector3.Normalize(Vector3.Normalize(HandCenter.positionFiltered - WristCenter.positionFiltered)); Vector3 center = WristCenter.positionFiltered + (wristToPalmDirection * thumbMcpToWristDistance); Vector3 camToWristDirection = Vector3.Normalize(WristCenter.positionFiltered - _mainCamera.transform.position); //rays needed for planarity discovery for in/out palm facing direction: Vector3 camToWrist = new Ray(WristCenter.positionFiltered, camToWristDirection).GetPoint(1); Vector3 camToThumbMcp = new Ray(_thumbMCP.positionFiltered, Vector3.Normalize(_thumbMCP.positionFiltered - _mainCamera.transform.position)).GetPoint(1); Vector3 camToPalm = new Ray(center, Vector3.Normalize(center - _mainCamera.transform.position)).GetPoint(1); //discover palm facing direction to camera: Plane palmFacingPlane = new Plane(camToWrist, camToPalm, camToThumbMcp); if (_managedHand.Hand.HandType == MLHandType.Left) { palmFacingPlane.Flip(); } float palmForwardFacing = Mathf.Sign(Vector3.Dot(palmFacingPlane.normal, _mainCamera.transform.forward)); //use thumb/palm/wrist alignment to determine amount of roll in the hand: Vector3 toThumbMcp = Vector3.Normalize(_thumbMCP.positionFiltered - center); Vector3 toPalm = Vector3.Normalize(center - WristCenter.positionFiltered); float handRollAmount = (1 - Vector3.Dot(toThumbMcp, toPalm)) * palmForwardFacing; //where between the wrist and thumbMcp should we slide inwards to get the palm in the center: Vector3 toPalmOrigin = Vector3.Lerp(WristCenter.positionFiltered, _thumbMCP.positionFiltered, .35f); //get a direction from the camera to toPalmOrigin as psuedo up for use in quaternion construction: Vector3 toCam = Vector3.Normalize(_mainCamera.transform.position - toPalmOrigin); //construct a quaternion that helps get angles needed between the wrist and thumbMCP to point towards the palm center: Vector3 wristToThumbMcp = Vector3.Normalize(_thumbMCP.positionFiltered - WristCenter.positionFiltered); Quaternion towardsCamUpReference = Quaternion.identity; if (wristToThumbMcp != Vector3.zero && toCam != Vector3.zero) { towardsCamUpReference = Quaternion.LookRotation(wristToThumbMcp, toCam); } //rotate the inwards vector depending on hand roll to know where to push the palm back: float inwardsVectorRotation = 90; if (_managedHand.Hand.HandType == MLHandType.Left) { inwardsVectorRotation = -90; } towardsCamUpReference = Quaternion.AngleAxis(handRollAmount * inwardsVectorRotation, towardsCamUpReference * Vector3.forward) * towardsCamUpReference; Vector3 inwardsVector = towardsCamUpReference * Vector3.up; //slide palm location along inwards vector to get it into proper physical location in the center of the hand: center = toPalmOrigin - inwardsVector * thumbMcpToWristDistance; Vector3 deadCenter = center; //as the hand flattens back out balance corrected location with originally provided location for better forward origin: center = Vector3.Lerp(center, HandCenter.positionFiltered, Mathf.Abs(handRollAmount)); //get a forward using the corrected palm location: Vector3 forward = Vector3.Normalize(center - WristCenter.positionFiltered); //switch back to physical center of hand - this reduces surface-to-surface movement of the center between back and palm: center = deadCenter; //get an initial hand up: Plane handPlane = new Plane(WristCenter.positionFiltered, _thumbMCP.positionFiltered, center); if (_managedHand.Hand.HandType == MLHandType.Left) { handPlane.Flip(); } Vector3 up = handPlane.normal; //find out how much the back of the hand is facing the camera so we have a safe set of features for a stronger forward: Vector3 centerToCam = Vector3.Normalize(_mainCamera.transform.position - WristCenter.positionFiltered); float facingDot = Vector3.Dot(centerToCam, up); if (facingDot > .5f) { float handBackFacingCamAmount = Percentage(facingDot, .5f, 1); //steer forward for more accuracy based on the visibility of the back of the hand: if (_middleMCP.Visible) { Vector3 toMiddleMcp = Vector3.Normalize(_middleMCP.positionFiltered - center); forward = Vector3.Lerp(forward, toMiddleMcp, handBackFacingCamAmount); } else if (_indexMCP.Visible) { Vector3 inIndexMcp = Vector3.Normalize(_indexMCP.positionFiltered - center); forward = Vector3.Lerp(forward, inIndexMcp, handBackFacingCamAmount); } } //make sure palm distance from wrist is consistant while also leveraging steered forward: center = WristCenter.positionFiltered + (forward * thumbMcpToWristDistance); //an initial rotation of the hand: Quaternion orientation = Quaternion.identity; if (forward != Vector3.zero && up != Vector3.zero) { orientation = Quaternion.LookRotation(forward, up); } //as the hand rolls counter-clockwise the thumbMcp loses accuracy so we need to interpolate to the back of the hand's features: if (_indexMCP.Visible && _middleMCP.Visible) { Vector3 knucklesVector = Vector3.Normalize(_middleMCP.positionFiltered - _indexMCP.positionFiltered); float knucklesDot = Vector3.Dot(knucklesVector, Vector3.up); if (knucklesDot > .5f) { float counterClockwiseRoll = Percentage(Vector3.Dot(knucklesVector, Vector3.up), .35f, .7f); center = Vector3.Lerp(center, HandCenter.positionFiltered, counterClockwiseRoll); forward = Vector3.Lerp(forward, Vector3.Normalize(_middleMCP.positionFiltered - HandCenter.positionFiltered), counterClockwiseRoll); Plane backHandPlane = new Plane(HandCenter.positionFiltered, _indexMCP.positionFiltered, _middleMCP.positionFiltered); if (_managedHand.Hand.HandType == MLHandType.Left) { backHandPlane.Flip(); } up = Vector3.Lerp(up, backHandPlane.normal, counterClockwiseRoll); orientation = Quaternion.LookRotation(forward, up); } } //as the wrist tilts away from the camera (with the thumb down) at extreme angles the hand center will move toward the thumb: float handTiltAwayAmount = 1 - Percentage(Vector3.Distance(HandCenter.positionFiltered, WristCenter.positionFiltered), .025f, .04f); Vector3 handTiltAwayCorrectionPoint = WristCenter.positionFiltered + camToWristDirection * thumbMcpToWristDistance; center = Vector3.Lerp(center, handTiltAwayCorrectionPoint, handTiltAwayAmount); forward = Vector3.Lerp(forward, Vector3.Normalize(handTiltAwayCorrectionPoint - WristCenter.positionFiltered), handTiltAwayAmount); Plane wristPlane = new Plane(WristCenter.positionFiltered, _thumbMCP.positionFiltered, center); if (_managedHand.Hand.HandType == MLHandType.Left) { wristPlane.Flip(); } up = Vector3.Lerp(up, wristPlane.normal, handTiltAwayAmount); if (forward != Vector3.zero && up != Vector3.zero) { orientation = Quaternion.LookRotation(forward, up); } //steering for if thumb/index are not available from self-occlusion to help rotate the hand better outwards better: float forwardUpAmount = Vector3.Dot(forward, Vector3.up); if (forwardUpAmount > .7f && _indexMCP.Visible && _ringMCP.Visible) { float angle = 0; if (_managedHand.Hand.HandType == MLHandType.Right) { Vector3 knucklesVector = Vector3.Normalize(_ringMCP.positionFiltered - _indexMCP.positionFiltered); angle = Vector3.Angle(knucklesVector, orientation * Vector3.right); angle *= -1; } else { Vector3 knucklesVector = Vector3.Normalize(_indexMCP.positionFiltered - _ringMCP.positionFiltered); angle = Vector3.Angle(knucklesVector, orientation * Vector3.right); } Quaternion selfOcclusionSteering = Quaternion.AngleAxis(angle, forward); orientation = selfOcclusionSteering * orientation; } else { //when palm is facing down we need to rotate some to compensate for an offset: float rollCorrection = Mathf.Clamp01(Vector3.Dot(orientation * Vector3.up, Vector3.up)); float rollCorrectionAmount = -30; if (_managedHand.Hand.HandType == MLHandType.Left) { rollCorrectionAmount = 30; } orientation = Quaternion.AngleAxis(rollCorrectionAmount * rollCorrection, forward) * orientation; } //inside the camera plane: InsideClipPlane = TransformUtilities.InsideClipPlane(center); //set pose: Position = center; Rotation = orientation; }
private void FindWalls_LocateBoundry() { //remove initial guides: RemovePlottedBounds(); //sort by angles from a base vector to find clockwise order: Vector3 baseVector = Vector3.Normalize(_virtualWalls[0].plane.Center - _plottedBounds.center); SortedDictionary <float, int> sortedDirection = new SortedDictionary <float, int>(); for (int i = 0; i < _virtualWalls.Count; i++) { Vector3 toNext = Vector3.Normalize(_virtualWalls[i].plane.Center - _plottedBounds.center); float angle = Vector3.SignedAngle(baseVector, toNext, Vector3.up) + 180; if (sortedDirection.ContainsKey(angle)) { //we have a bad set of locations so it is best to just everything: Create(); break; } else { sortedDirection.Add(angle, i); } } int[] clockwiseOrder = sortedDirection.Values.ToArray <int>(); //find and connect 'betweens' which end up being final walls of playspace List <bool> physicalStatus = new List <bool>(); _playspaceCorners = new List <Vector3>(); for (int i = 0; i < clockwiseOrder.Length; i++) { //parts: int next = (i + 1) % clockwiseOrder.Length; float angle = Vector3.Angle(_virtualWalls[clockwiseOrder[i]].plane.Rotation * Vector3.right, _virtualWalls[clockwiseOrder[next]].plane.Rotation * Vector3.right); //save physical status: physicalStatus.Add(_virtualWalls[clockwiseOrder[next]].physical); //add solved between: if (angle < 45 || angle > 135) { //wall - use mid point: Vector3 mid = Vector3.Lerp(_virtualWalls[clockwiseOrder[i]].plane.Center, _virtualWalls[clockwiseOrder[next]].plane.Center, .5f); mid.y = _roomFloorHeight; _playspaceCorners.Add(TransformUtilities.LocalPosition(pcfAnchor.Position, pcfAnchor.Rotation, mid)); } else { //corner - use intersection by creating inverted lines from each plane: Vector3 point = Vector2.zero; if (MathUtilities.RayIntersection( new Ray(_virtualWalls[clockwiseOrder[i]].plane.Center, _virtualWalls[clockwiseOrder[i]].plane.Rotation * Vector3.right), new Ray(_virtualWalls[clockwiseOrder[next]].plane.Center, _virtualWalls[clockwiseOrder[next]].plane.Rotation * Vector3.left), ref point)) { point.y = _roomFloorHeight; _playspaceCorners.Add(TransformUtilities.LocalPosition(pcfAnchor.Position, pcfAnchor.Rotation, point)); } } } //close loop: _playspaceCorners.Add(_playspaceCorners[0]); //store walls: List <PlayspaceWall> playspaceWalls = new List <PlayspaceWall>(); for (int i = 0; i < _playspaceCorners.Count - 1; i++) { Vector3 pointA = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i]); Vector3 pointB = TransformUtilities.WorldPosition(pcfAnchor.Position, pcfAnchor.Rotation, _playspaceCorners[i + 1]); Vector3 vector = pointB - pointA; Vector3 normal = Quaternion.AngleAxis(90, Vector3.up) * vector.normalized; Vector3 center = Vector3.Lerp(pointA, pointB, .5f); center.y = _roomVerticalCenter; float width = vector.magnitude; PlayspaceWall wall = new PlayspaceWall(center, Quaternion.LookRotation(normal), width, Height, physicalStatus[i]); playspaceWalls.Add(wall); } Walls = playspaceWalls.ToArray(); FindWalls_GenerateGeometry(); }
private void ConfirmPrimaryWall_HandleTriggerDown(byte controllerId, float triggerValue) { //we must have a primary wall: if (PrimaryWall == -1) { return; } //reset walls: RightWall = -1; LeftWall = -1; RearWall = -1; //find room center (this may need additional calculation for more accuracy): Bounds finalBounds = new Bounds(Walls[0].Center, Vector3.zero); for (int i = 1; i < Walls.Length; i++) { finalBounds.Encapsulate(Walls[i].Center); } //make center relative to pcf so relocalization keeps this value accurate: _playspaceCenter = TransformUtilities.LocalPosition(pcfAnchor.Position, pcfAnchor.Rotation, finalBounds.center); //find additional largest key walls: SortedDictionary <float, int> rightWalls = new SortedDictionary <float, int>(); SortedDictionary <float, int> rearWalls = new SortedDictionary <float, int>(); SortedDictionary <float, int> leftWalls = new SortedDictionary <float, int>(); for (int i = 0; i < Walls.Length; i++) { //skip walls that match the primary: if (i != PrimaryWall) //skip primary wall and walls on the same plane { //get angle relationship: float angle = Vector3.SignedAngle(Walls[PrimaryWall].Normal, Walls[i].Normal, Vector3.up); if (angle < 0) { angle += 360; } //get size: float wallSize = Walls[i].width * Walls[i].height; //right: if (angle >= 225 && angle < 315) { leftWalls.Add(wallSize, i); } //rear: if (angle > 135 && angle < 225) { rearWalls.Add(wallSize, i); } //left: if (angle >= 45 && angle <= 135) { rightWalls.Add(wallSize, i); } } } //set key walls to largest (might need to verify they are also facing the correct direction as well later): if (rightWalls.Count != 0) { RightWall = rightWalls.ElementAt(rightWalls.Count - 1).Value; } if (rearWalls.Count != 0) { RearWall = rearWalls.ElementAt(rearWalls.Count - 1).Value; } if (leftWalls.Count != 0) { LeftWall = leftWalls.ElementAt(leftWalls.Count - 1).Value; } //unhook: MLInput.OnTriggerDown -= ConfirmPrimaryWall_HandleTriggerDown; //something went wrong with the shape of the room, best to force a rest: if (PrimaryWall == -1 || LeftWall == -1 || RightWall == -1 || RearWall == -1) { Create(); } else { ChangeState(State.Complete); } }